diff options
author | vvvv <vvvv@yandex-team.com> | 2024-11-07 12:29:36 +0300 |
---|---|---|
committer | vvvv <vvvv@yandex-team.com> | 2024-11-07 13:49:47 +0300 |
commit | d4c258e9431675bab6745c8638df6e3dfd4dca6b (patch) | |
tree | b5efcfa11351152a4c872fccaea35749141c0b11 /yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils | |
parent | 13a4f274caef5cfdaf0263b24e4d6bdd5521472b (diff) | |
download | ydb-d4c258e9431675bab6745c8638df6e3dfd4dca6b.tar.gz |
Moved other yql/essentials libs YQL-19206
init
commit_hash:7d4c435602078407bbf20dd3c32f9c90d2bbcbc0
Diffstat (limited to 'yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils')
203 files changed, 297386 insertions, 0 deletions
diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/activity/backend_progress.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/activity/backend_progress.c new file mode 100644 index 00000000000..fb48eafef9a --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/activity/backend_progress.c @@ -0,0 +1,133 @@ +/* ---------- + * backend_progress.c + * + * Command progress reporting infrastructure. + * + * Copyright (c) 2001-2023, PostgreSQL Global Development Group + * + * src/backend/utils/activity/backend_progress.c + * ---------- + */ +#include "postgres.h" + +#include "port/atomics.h" /* for memory barriers */ +#include "utils/backend_progress.h" +#include "utils/backend_status.h" + + +/*----------- + * pgstat_progress_start_command() - + * + * Set st_progress_command (and st_progress_command_target) in own backend + * entry. Also, zero-initialize st_progress_param array. + *----------- + */ +void +pgstat_progress_start_command(ProgressCommandType cmdtype, Oid relid) +{ + volatile PgBackendStatus *beentry = MyBEEntry; + + if (!beentry || !pgstat_track_activities) + return; + + PGSTAT_BEGIN_WRITE_ACTIVITY(beentry); + beentry->st_progress_command = cmdtype; + beentry->st_progress_command_target = relid; + MemSet(&beentry->st_progress_param, 0, sizeof(beentry->st_progress_param)); + PGSTAT_END_WRITE_ACTIVITY(beentry); +} + +/*----------- + * pgstat_progress_update_param() - + * + * Update index'th member in st_progress_param[] of own backend entry. + *----------- + */ +void +pgstat_progress_update_param(int index, int64 val) +{ + volatile PgBackendStatus *beentry = MyBEEntry; + + Assert(index >= 0 && index < PGSTAT_NUM_PROGRESS_PARAM); + + if (!beentry || !pgstat_track_activities) + return; + + PGSTAT_BEGIN_WRITE_ACTIVITY(beentry); + beentry->st_progress_param[index] = val; + PGSTAT_END_WRITE_ACTIVITY(beentry); +} + +/*----------- + * pgstat_progress_incr_param() - + * + * Increment index'th member in st_progress_param[] of own backend entry. + *----------- + */ +void +pgstat_progress_incr_param(int index, int64 incr) +{ + volatile PgBackendStatus *beentry = MyBEEntry; + + Assert(index >= 0 && index < PGSTAT_NUM_PROGRESS_PARAM); + + if (!beentry || !pgstat_track_activities) + return; + + PGSTAT_BEGIN_WRITE_ACTIVITY(beentry); + beentry->st_progress_param[index] += incr; + PGSTAT_END_WRITE_ACTIVITY(beentry); +} + +/*----------- + * pgstat_progress_update_multi_param() - + * + * Update multiple members in st_progress_param[] of own backend entry. + * This is atomic; readers won't see intermediate states. + *----------- + */ +void +pgstat_progress_update_multi_param(int nparam, const int *index, + const int64 *val) +{ + volatile PgBackendStatus *beentry = MyBEEntry; + int i; + + if (!beentry || !pgstat_track_activities || nparam == 0) + return; + + PGSTAT_BEGIN_WRITE_ACTIVITY(beentry); + + for (i = 0; i < nparam; ++i) + { + Assert(index[i] >= 0 && index[i] < PGSTAT_NUM_PROGRESS_PARAM); + + beentry->st_progress_param[index[i]] = val[i]; + } + + PGSTAT_END_WRITE_ACTIVITY(beentry); +} + +/*----------- + * pgstat_progress_end_command() - + * + * Reset st_progress_command (and st_progress_command_target) in own backend + * entry. This signals the end of the command. + *----------- + */ +void +pgstat_progress_end_command(void) +{ + volatile PgBackendStatus *beentry = MyBEEntry; + + if (!beentry || !pgstat_track_activities) + return; + + if (beentry->st_progress_command == PROGRESS_COMMAND_INVALID) + return; + + PGSTAT_BEGIN_WRITE_ACTIVITY(beentry); + beentry->st_progress_command = PROGRESS_COMMAND_INVALID; + beentry->st_progress_command_target = InvalidOid; + PGSTAT_END_WRITE_ACTIVITY(beentry); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/activity/backend_status.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/activity/backend_status.c new file mode 100644 index 00000000000..9f422904df7 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/activity/backend_status.c @@ -0,0 +1,1216 @@ +/* ---------- + * backend_status.c + * Backend status reporting infrastructure. + * + * Copyright (c) 2001-2023, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/utils/activity/backend_status.c + * ---------- + */ +#include "postgres.h" + +#include "access/xact.h" +#include "libpq/libpq.h" +#include "miscadmin.h" +#include "pg_trace.h" +#include "pgstat.h" +#include "port/atomics.h" /* for memory barriers */ +#include "storage/ipc.h" +#include "storage/proc.h" /* for MyProc */ +#include "storage/sinvaladt.h" +#include "utils/ascii.h" +#include "utils/backend_status.h" +#include "utils/guc.h" /* for application_name */ +#include "utils/memutils.h" + + +/* ---------- + * Total number of backends including auxiliary + * + * We reserve a slot for each possible BackendId, plus one for each + * possible auxiliary process type. (This scheme assumes there is not + * more than one of any auxiliary process type at a time.) MaxBackends + * includes autovacuum workers and background workers as well. + * ---------- + */ +#define NumBackendStatSlots (MaxBackends + NUM_AUXPROCTYPES) + + +/* ---------- + * GUC parameters + * ---------- + */ +__thread bool pgstat_track_activities = false; +__thread int pgstat_track_activity_query_size = 1024; + + +/* exposed so that backend_progress.c can access it */ +__thread PgBackendStatus *MyBEEntry = NULL; + + +static __thread PgBackendStatus *BackendStatusArray = NULL; +static __thread char *BackendAppnameBuffer = NULL; +static __thread char *BackendClientHostnameBuffer = NULL; +static __thread char *BackendActivityBuffer = NULL; +static __thread Size BackendActivityBufferSize = 0; +#ifdef USE_SSL +static __thread PgBackendSSLStatus *BackendSslStatusBuffer = NULL; +#endif +#ifdef ENABLE_GSS +static PgBackendGSSStatus *BackendGssStatusBuffer = NULL; +#endif + + +/* Status for backends including auxiliary */ +static __thread LocalPgBackendStatus *localBackendStatusTable = NULL; + +/* Total number of backends including auxiliary */ +static __thread int localNumBackends = 0; + +static __thread MemoryContext backendStatusSnapContext; + + +static void pgstat_beshutdown_hook(int code, Datum arg); +static void pgstat_read_current_status(void); +static void pgstat_setup_backend_status_context(void); + + +/* + * Report shared-memory space needed by CreateSharedBackendStatus. + */ +Size +BackendStatusShmemSize(void) +{ + Size size; + + /* BackendStatusArray: */ + size = mul_size(sizeof(PgBackendStatus), NumBackendStatSlots); + /* BackendAppnameBuffer: */ + size = add_size(size, + mul_size(NAMEDATALEN, NumBackendStatSlots)); + /* BackendClientHostnameBuffer: */ + size = add_size(size, + mul_size(NAMEDATALEN, NumBackendStatSlots)); + /* BackendActivityBuffer: */ + size = add_size(size, + mul_size(pgstat_track_activity_query_size, NumBackendStatSlots)); +#ifdef USE_SSL + /* BackendSslStatusBuffer: */ + size = add_size(size, + mul_size(sizeof(PgBackendSSLStatus), NumBackendStatSlots)); +#endif +#ifdef ENABLE_GSS + /* BackendGssStatusBuffer: */ + size = add_size(size, + mul_size(sizeof(PgBackendGSSStatus), NumBackendStatSlots)); +#endif + return size; +} + +/* + * Initialize the shared status array and several string buffers + * during postmaster startup. + */ +void +CreateSharedBackendStatus(void) +{ + Size size; + bool found; + int i; + char *buffer; + + /* Create or attach to the shared array */ + size = mul_size(sizeof(PgBackendStatus), NumBackendStatSlots); + BackendStatusArray = (PgBackendStatus *) + ShmemInitStruct("Backend Status Array", size, &found); + + if (!found) + { + /* + * We're the first - initialize. + */ + MemSet(BackendStatusArray, 0, size); + } + + /* Create or attach to the shared appname buffer */ + size = mul_size(NAMEDATALEN, NumBackendStatSlots); + BackendAppnameBuffer = (char *) + ShmemInitStruct("Backend Application Name Buffer", size, &found); + + if (!found) + { + MemSet(BackendAppnameBuffer, 0, size); + + /* Initialize st_appname pointers. */ + buffer = BackendAppnameBuffer; + for (i = 0; i < NumBackendStatSlots; i++) + { + BackendStatusArray[i].st_appname = buffer; + buffer += NAMEDATALEN; + } + } + + /* Create or attach to the shared client hostname buffer */ + size = mul_size(NAMEDATALEN, NumBackendStatSlots); + BackendClientHostnameBuffer = (char *) + ShmemInitStruct("Backend Client Host Name Buffer", size, &found); + + if (!found) + { + MemSet(BackendClientHostnameBuffer, 0, size); + + /* Initialize st_clienthostname pointers. */ + buffer = BackendClientHostnameBuffer; + for (i = 0; i < NumBackendStatSlots; i++) + { + BackendStatusArray[i].st_clienthostname = buffer; + buffer += NAMEDATALEN; + } + } + + /* Create or attach to the shared activity buffer */ + BackendActivityBufferSize = mul_size(pgstat_track_activity_query_size, + NumBackendStatSlots); + BackendActivityBuffer = (char *) + ShmemInitStruct("Backend Activity Buffer", + BackendActivityBufferSize, + &found); + + if (!found) + { + MemSet(BackendActivityBuffer, 0, BackendActivityBufferSize); + + /* Initialize st_activity pointers. */ + buffer = BackendActivityBuffer; + for (i = 0; i < NumBackendStatSlots; i++) + { + BackendStatusArray[i].st_activity_raw = buffer; + buffer += pgstat_track_activity_query_size; + } + } + +#ifdef USE_SSL + /* Create or attach to the shared SSL status buffer */ + size = mul_size(sizeof(PgBackendSSLStatus), NumBackendStatSlots); + BackendSslStatusBuffer = (PgBackendSSLStatus *) + ShmemInitStruct("Backend SSL Status Buffer", size, &found); + + if (!found) + { + PgBackendSSLStatus *ptr; + + MemSet(BackendSslStatusBuffer, 0, size); + + /* Initialize st_sslstatus pointers. */ + ptr = BackendSslStatusBuffer; + for (i = 0; i < NumBackendStatSlots; i++) + { + BackendStatusArray[i].st_sslstatus = ptr; + ptr++; + } + } +#endif + +#ifdef ENABLE_GSS + /* Create or attach to the shared GSSAPI status buffer */ + size = mul_size(sizeof(PgBackendGSSStatus), NumBackendStatSlots); + BackendGssStatusBuffer = (PgBackendGSSStatus *) + ShmemInitStruct("Backend GSS Status Buffer", size, &found); + + if (!found) + { + PgBackendGSSStatus *ptr; + + MemSet(BackendGssStatusBuffer, 0, size); + + /* Initialize st_gssstatus pointers. */ + ptr = BackendGssStatusBuffer; + for (i = 0; i < NumBackendStatSlots; i++) + { + BackendStatusArray[i].st_gssstatus = ptr; + ptr++; + } + } +#endif +} + +/* + * Initialize pgstats backend activity state, and set up our on-proc-exit + * hook. Called from InitPostgres and AuxiliaryProcessMain. For auxiliary + * process, MyBackendId is invalid. Otherwise, MyBackendId must be set, but we + * must not have started any transaction yet (since the exit hook must run + * after the last transaction exit). + * + * NOTE: MyDatabaseId isn't set yet; so the shutdown hook has to be careful. + */ +void +pgstat_beinit(void) +{ + /* Initialize MyBEEntry */ + if (MyBackendId != InvalidBackendId) + { + Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends); + MyBEEntry = &BackendStatusArray[MyBackendId - 1]; + } + else + { + /* Must be an auxiliary process */ + Assert(MyAuxProcType != NotAnAuxProcess); + + /* + * Assign the MyBEEntry for an auxiliary process. Since it doesn't + * have a BackendId, the slot is statically allocated based on the + * auxiliary process type (MyAuxProcType). Backends use slots indexed + * in the range from 0 to MaxBackends (exclusive), so we use + * MaxBackends + AuxProcType as the index of the slot for an auxiliary + * process. + */ + MyBEEntry = &BackendStatusArray[MaxBackends + MyAuxProcType]; + } + + /* Set up a process-exit hook to clean up */ + on_shmem_exit(pgstat_beshutdown_hook, 0); +} + + +/* ---------- + * pgstat_bestart() - + * + * Initialize this backend's entry in the PgBackendStatus array. + * Called from InitPostgres. + * + * Apart from auxiliary processes, MyBackendId, MyDatabaseId, + * session userid, and application_name must be set for a + * backend (hence, this cannot be combined with pgstat_beinit). + * Note also that we must be inside a transaction if this isn't an aux + * process, as we may need to do encoding conversion on some strings. + * ---------- + */ +void +pgstat_bestart(void) +{ + volatile PgBackendStatus *vbeentry = MyBEEntry; + PgBackendStatus lbeentry; +#ifdef USE_SSL + PgBackendSSLStatus lsslstatus; +#endif +#ifdef ENABLE_GSS + PgBackendGSSStatus lgssstatus; +#endif + + /* pgstats state must be initialized from pgstat_beinit() */ + Assert(vbeentry != NULL); + + /* + * To minimize the time spent modifying the PgBackendStatus entry, and + * avoid risk of errors inside the critical section, we first copy the + * shared-memory struct to a local variable, then modify the data in the + * local variable, then copy the local variable back to shared memory. + * Only the last step has to be inside the critical section. + * + * Most of the data we copy from shared memory is just going to be + * overwritten, but the struct's not so large that it's worth the + * maintenance hassle to copy only the needful fields. + */ + memcpy(&lbeentry, + unvolatize(PgBackendStatus *, vbeentry), + sizeof(PgBackendStatus)); + + /* These structs can just start from zeroes each time, though */ +#ifdef USE_SSL + memset(&lsslstatus, 0, sizeof(lsslstatus)); +#endif +#ifdef ENABLE_GSS + memset(&lgssstatus, 0, sizeof(lgssstatus)); +#endif + + /* + * Now fill in all the fields of lbeentry, except for strings that are + * out-of-line data. Those have to be handled separately, below. + */ + lbeentry.st_procpid = MyProcPid; + lbeentry.st_backendType = MyBackendType; + lbeentry.st_proc_start_timestamp = MyStartTimestamp; + lbeentry.st_activity_start_timestamp = 0; + lbeentry.st_state_start_timestamp = 0; + lbeentry.st_xact_start_timestamp = 0; + lbeentry.st_databaseid = MyDatabaseId; + + /* We have userid for client-backends, wal-sender and bgworker processes */ + if (lbeentry.st_backendType == B_BACKEND + || lbeentry.st_backendType == B_WAL_SENDER + || lbeentry.st_backendType == B_BG_WORKER) + lbeentry.st_userid = GetSessionUserId(); + else + lbeentry.st_userid = InvalidOid; + + /* + * We may not have a MyProcPort (eg, if this is the autovacuum process). + * If so, use all-zeroes client address, which is dealt with specially in + * pg_stat_get_backend_client_addr and pg_stat_get_backend_client_port. + */ + if (MyProcPort) + memcpy(&lbeentry.st_clientaddr, &MyProcPort->raddr, + sizeof(lbeentry.st_clientaddr)); + else + MemSet(&lbeentry.st_clientaddr, 0, sizeof(lbeentry.st_clientaddr)); + +#ifdef USE_SSL + if (MyProcPort && MyProcPort->ssl_in_use) + { + lbeentry.st_ssl = true; + lsslstatus.ssl_bits = be_tls_get_cipher_bits(MyProcPort); + strlcpy(lsslstatus.ssl_version, be_tls_get_version(MyProcPort), NAMEDATALEN); + strlcpy(lsslstatus.ssl_cipher, be_tls_get_cipher(MyProcPort), NAMEDATALEN); + be_tls_get_peer_subject_name(MyProcPort, lsslstatus.ssl_client_dn, NAMEDATALEN); + be_tls_get_peer_serial(MyProcPort, lsslstatus.ssl_client_serial, NAMEDATALEN); + be_tls_get_peer_issuer_name(MyProcPort, lsslstatus.ssl_issuer_dn, NAMEDATALEN); + } + else + { + lbeentry.st_ssl = false; + } +#else + lbeentry.st_ssl = false; +#endif + +#ifdef ENABLE_GSS + if (MyProcPort && MyProcPort->gss != NULL) + { + const char *princ = be_gssapi_get_princ(MyProcPort); + + lbeentry.st_gss = true; + lgssstatus.gss_auth = be_gssapi_get_auth(MyProcPort); + lgssstatus.gss_enc = be_gssapi_get_enc(MyProcPort); + lgssstatus.gss_delegation = be_gssapi_get_delegation(MyProcPort); + if (princ) + strlcpy(lgssstatus.gss_princ, princ, NAMEDATALEN); + } + else + { + lbeentry.st_gss = false; + } +#else + lbeentry.st_gss = false; +#endif + + lbeentry.st_state = STATE_UNDEFINED; + lbeentry.st_progress_command = PROGRESS_COMMAND_INVALID; + lbeentry.st_progress_command_target = InvalidOid; + lbeentry.st_query_id = UINT64CONST(0); + + /* + * we don't zero st_progress_param here to save cycles; nobody should + * examine it until st_progress_command has been set to something other + * than PROGRESS_COMMAND_INVALID + */ + + /* + * We're ready to enter the critical section that fills the shared-memory + * status entry. We follow the protocol of bumping st_changecount before + * and after; and make sure it's even afterwards. We use a volatile + * pointer here to ensure the compiler doesn't try to get cute. + */ + PGSTAT_BEGIN_WRITE_ACTIVITY(vbeentry); + + /* make sure we'll memcpy the same st_changecount back */ + lbeentry.st_changecount = vbeentry->st_changecount; + + memcpy(unvolatize(PgBackendStatus *, vbeentry), + &lbeentry, + sizeof(PgBackendStatus)); + + /* + * We can write the out-of-line strings and structs using the pointers + * that are in lbeentry; this saves some de-volatilizing messiness. + */ + lbeentry.st_appname[0] = '\0'; + if (MyProcPort && MyProcPort->remote_hostname) + strlcpy(lbeentry.st_clienthostname, MyProcPort->remote_hostname, + NAMEDATALEN); + else + lbeentry.st_clienthostname[0] = '\0'; + lbeentry.st_activity_raw[0] = '\0'; + /* Also make sure the last byte in each string area is always 0 */ + lbeentry.st_appname[NAMEDATALEN - 1] = '\0'; + lbeentry.st_clienthostname[NAMEDATALEN - 1] = '\0'; + lbeentry.st_activity_raw[pgstat_track_activity_query_size - 1] = '\0'; + +#ifdef USE_SSL + memcpy(lbeentry.st_sslstatus, &lsslstatus, sizeof(PgBackendSSLStatus)); +#endif +#ifdef ENABLE_GSS + memcpy(lbeentry.st_gssstatus, &lgssstatus, sizeof(PgBackendGSSStatus)); +#endif + + PGSTAT_END_WRITE_ACTIVITY(vbeentry); + + /* Update app name to current GUC setting */ + if (application_name) + pgstat_report_appname(application_name); +} + +/* + * Clear out our entry in the PgBackendStatus array. + */ +static void +pgstat_beshutdown_hook(int code, Datum arg) +{ + volatile PgBackendStatus *beentry = MyBEEntry; + + /* + * Clear my status entry, following the protocol of bumping st_changecount + * before and after. We use a volatile pointer here to ensure the + * compiler doesn't try to get cute. + */ + PGSTAT_BEGIN_WRITE_ACTIVITY(beentry); + + beentry->st_procpid = 0; /* mark invalid */ + + PGSTAT_END_WRITE_ACTIVITY(beentry); + + /* so that functions can check if backend_status.c is up via MyBEEntry */ + MyBEEntry = NULL; +} + +/* + * Discard any data collected in the current transaction. Any subsequent + * request will cause new snapshots to be read. + * + * This is also invoked during transaction commit or abort to discard the + * no-longer-wanted snapshot. + */ +void +pgstat_clear_backend_activity_snapshot(void) +{ + /* Release memory, if any was allocated */ + if (backendStatusSnapContext) + { + MemoryContextDelete(backendStatusSnapContext); + backendStatusSnapContext = NULL; + } + + /* Reset variables */ + localBackendStatusTable = NULL; + localNumBackends = 0; +} + +static void +pgstat_setup_backend_status_context(void) +{ + if (!backendStatusSnapContext) + backendStatusSnapContext = AllocSetContextCreate(TopMemoryContext, + "Backend Status Snapshot", + ALLOCSET_SMALL_SIZES); +} + + +/* ---------- + * pgstat_report_activity() - + * + * Called from tcop/postgres.c to report what the backend is actually doing + * (but note cmd_str can be NULL for certain cases). + * + * All updates of the status entry follow the protocol of bumping + * st_changecount before and after. We use a volatile pointer here to + * ensure the compiler doesn't try to get cute. + * ---------- + */ +void +pgstat_report_activity(BackendState state, const char *cmd_str) +{ + volatile PgBackendStatus *beentry = MyBEEntry; + TimestampTz start_timestamp; + TimestampTz current_timestamp; + int len = 0; + + TRACE_POSTGRESQL_STATEMENT_STATUS(cmd_str); + + if (!beentry) + return; + + if (!pgstat_track_activities) + { + if (beentry->st_state != STATE_DISABLED) + { + volatile PGPROC *proc = MyProc; + + /* + * track_activities is disabled, but we last reported a + * non-disabled state. As our final update, change the state and + * clear fields we will not be updating anymore. + */ + PGSTAT_BEGIN_WRITE_ACTIVITY(beentry); + beentry->st_state = STATE_DISABLED; + beentry->st_state_start_timestamp = 0; + beentry->st_activity_raw[0] = '\0'; + beentry->st_activity_start_timestamp = 0; + /* st_xact_start_timestamp and wait_event_info are also disabled */ + beentry->st_xact_start_timestamp = 0; + beentry->st_query_id = UINT64CONST(0); + proc->wait_event_info = 0; + PGSTAT_END_WRITE_ACTIVITY(beentry); + } + return; + } + + /* + * To minimize the time spent modifying the entry, and avoid risk of + * errors inside the critical section, fetch all the needed data first. + */ + start_timestamp = GetCurrentStatementStartTimestamp(); + if (cmd_str != NULL) + { + /* + * Compute length of to-be-stored string unaware of multi-byte + * characters. For speed reasons that'll get corrected on read, rather + * than computed every write. + */ + len = Min(strlen(cmd_str), pgstat_track_activity_query_size - 1); + } + current_timestamp = GetCurrentTimestamp(); + + /* + * If the state has changed from "active" or "idle in transaction", + * calculate the duration. + */ + if ((beentry->st_state == STATE_RUNNING || + beentry->st_state == STATE_FASTPATH || + beentry->st_state == STATE_IDLEINTRANSACTION || + beentry->st_state == STATE_IDLEINTRANSACTION_ABORTED) && + state != beentry->st_state) + { + long secs; + int usecs; + + TimestampDifference(beentry->st_state_start_timestamp, + current_timestamp, + &secs, &usecs); + + if (beentry->st_state == STATE_RUNNING || + beentry->st_state == STATE_FASTPATH) + pgstat_count_conn_active_time((PgStat_Counter) secs * 1000000 + usecs); + else + pgstat_count_conn_txn_idle_time((PgStat_Counter) secs * 1000000 + usecs); + } + + /* + * Now update the status entry + */ + PGSTAT_BEGIN_WRITE_ACTIVITY(beentry); + + beentry->st_state = state; + beentry->st_state_start_timestamp = current_timestamp; + + /* + * If a new query is started, we reset the query identifier as it'll only + * be known after parse analysis, to avoid reporting last query's + * identifier. + */ + if (state == STATE_RUNNING) + beentry->st_query_id = UINT64CONST(0); + + if (cmd_str != NULL) + { + memcpy((char *) beentry->st_activity_raw, cmd_str, len); + beentry->st_activity_raw[len] = '\0'; + beentry->st_activity_start_timestamp = start_timestamp; + } + + PGSTAT_END_WRITE_ACTIVITY(beentry); +} + +/* -------- + * pgstat_report_query_id() - + * + * Called to update top-level query identifier. + * -------- + */ +void +pgstat_report_query_id(uint64 query_id, bool force) +{ + volatile PgBackendStatus *beentry = MyBEEntry; + + /* + * if track_activities is disabled, st_query_id should already have been + * reset + */ + if (!beentry || !pgstat_track_activities) + return; + + /* + * We only report the top-level query identifiers. The stored query_id is + * reset when a backend calls pgstat_report_activity(STATE_RUNNING), or + * with an explicit call to this function using the force flag. If the + * saved query identifier is not zero it means that it's not a top-level + * command, so ignore the one provided unless it's an explicit call to + * reset the identifier. + */ + if (beentry->st_query_id != 0 && !force) + return; + + /* + * Update my status entry, following the protocol of bumping + * st_changecount before and after. We use a volatile pointer here to + * ensure the compiler doesn't try to get cute. + */ + PGSTAT_BEGIN_WRITE_ACTIVITY(beentry); + beentry->st_query_id = query_id; + PGSTAT_END_WRITE_ACTIVITY(beentry); +} + + +/* ---------- + * pgstat_report_appname() - + * + * Called to update our application name. + * ---------- + */ +void +pgstat_report_appname(const char *appname) +{ + volatile PgBackendStatus *beentry = MyBEEntry; + int len; + + if (!beentry) + return; + + /* This should be unnecessary if GUC did its job, but be safe */ + len = pg_mbcliplen(appname, strlen(appname), NAMEDATALEN - 1); + + /* + * Update my status entry, following the protocol of bumping + * st_changecount before and after. We use a volatile pointer here to + * ensure the compiler doesn't try to get cute. + */ + PGSTAT_BEGIN_WRITE_ACTIVITY(beentry); + + memcpy((char *) beentry->st_appname, appname, len); + beentry->st_appname[len] = '\0'; + + PGSTAT_END_WRITE_ACTIVITY(beentry); +} + +/* + * Report current transaction start timestamp as the specified value. + * Zero means there is no active transaction. + */ +void +pgstat_report_xact_timestamp(TimestampTz tstamp) +{ + volatile PgBackendStatus *beentry = MyBEEntry; + + if (!pgstat_track_activities || !beentry) + return; + + /* + * Update my status entry, following the protocol of bumping + * st_changecount before and after. We use a volatile pointer here to + * ensure the compiler doesn't try to get cute. + */ + PGSTAT_BEGIN_WRITE_ACTIVITY(beentry); + + beentry->st_xact_start_timestamp = tstamp; + + PGSTAT_END_WRITE_ACTIVITY(beentry); +} + +/* ---------- + * pgstat_read_current_status() - + * + * Copy the current contents of the PgBackendStatus array to local memory, + * if not already done in this transaction. + * ---------- + */ +static void +pgstat_read_current_status(void) +{ + volatile PgBackendStatus *beentry; + LocalPgBackendStatus *localtable; + LocalPgBackendStatus *localentry; + char *localappname, + *localclienthostname, + *localactivity; +#ifdef USE_SSL + PgBackendSSLStatus *localsslstatus; +#endif +#ifdef ENABLE_GSS + PgBackendGSSStatus *localgssstatus; +#endif + int i; + + if (localBackendStatusTable) + return; /* already done */ + + pgstat_setup_backend_status_context(); + + /* + * Allocate storage for local copy of state data. We can presume that + * none of these requests overflow size_t, because we already calculated + * the same values using mul_size during shmem setup. However, with + * probably-silly values of pgstat_track_activity_query_size and + * max_connections, the localactivity buffer could exceed 1GB, so use + * "huge" allocation for that one. + */ + localtable = (LocalPgBackendStatus *) + MemoryContextAlloc(backendStatusSnapContext, + sizeof(LocalPgBackendStatus) * NumBackendStatSlots); + localappname = (char *) + MemoryContextAlloc(backendStatusSnapContext, + NAMEDATALEN * NumBackendStatSlots); + localclienthostname = (char *) + MemoryContextAlloc(backendStatusSnapContext, + NAMEDATALEN * NumBackendStatSlots); + localactivity = (char *) + MemoryContextAllocHuge(backendStatusSnapContext, + (Size) pgstat_track_activity_query_size * + (Size) NumBackendStatSlots); +#ifdef USE_SSL + localsslstatus = (PgBackendSSLStatus *) + MemoryContextAlloc(backendStatusSnapContext, + sizeof(PgBackendSSLStatus) * NumBackendStatSlots); +#endif +#ifdef ENABLE_GSS + localgssstatus = (PgBackendGSSStatus *) + MemoryContextAlloc(backendStatusSnapContext, + sizeof(PgBackendGSSStatus) * NumBackendStatSlots); +#endif + + localNumBackends = 0; + + beentry = BackendStatusArray; + localentry = localtable; + for (i = 1; i <= NumBackendStatSlots; i++) + { + /* + * Follow the protocol of retrying if st_changecount changes while we + * copy the entry, or if it's odd. (The check for odd is needed to + * cover the case where we are able to completely copy the entry while + * the source backend is between increment steps.) We use a volatile + * pointer here to ensure the compiler doesn't try to get cute. + */ + for (;;) + { + int before_changecount; + int after_changecount; + + pgstat_begin_read_activity(beentry, before_changecount); + + localentry->backendStatus.st_procpid = beentry->st_procpid; + /* Skip all the data-copying work if entry is not in use */ + if (localentry->backendStatus.st_procpid > 0) + { + memcpy(&localentry->backendStatus, unvolatize(PgBackendStatus *, beentry), sizeof(PgBackendStatus)); + + /* + * For each PgBackendStatus field that is a pointer, copy the + * pointed-to data, then adjust the local copy of the pointer + * field to point at the local copy of the data. + * + * strcpy is safe even if the string is modified concurrently, + * because there's always a \0 at the end of the buffer. + */ + strcpy(localappname, (char *) beentry->st_appname); + localentry->backendStatus.st_appname = localappname; + strcpy(localclienthostname, (char *) beentry->st_clienthostname); + localentry->backendStatus.st_clienthostname = localclienthostname; + strcpy(localactivity, (char *) beentry->st_activity_raw); + localentry->backendStatus.st_activity_raw = localactivity; +#ifdef USE_SSL + if (beentry->st_ssl) + { + memcpy(localsslstatus, beentry->st_sslstatus, sizeof(PgBackendSSLStatus)); + localentry->backendStatus.st_sslstatus = localsslstatus; + } +#endif +#ifdef ENABLE_GSS + if (beentry->st_gss) + { + memcpy(localgssstatus, beentry->st_gssstatus, sizeof(PgBackendGSSStatus)); + localentry->backendStatus.st_gssstatus = localgssstatus; + } +#endif + } + + pgstat_end_read_activity(beentry, after_changecount); + + if (pgstat_read_activity_complete(before_changecount, + after_changecount)) + break; + + /* Make sure we can break out of loop if stuck... */ + CHECK_FOR_INTERRUPTS(); + } + + /* Only valid entries get included into the local array */ + if (localentry->backendStatus.st_procpid > 0) + { + /* + * The BackendStatusArray index is exactly the BackendId of the + * source backend. Note that this means localBackendStatusTable + * is in order by backend_id. pgstat_get_beentry_by_backend_id() + * depends on that. + */ + localentry->backend_id = i; + BackendIdGetTransactionIds(i, + &localentry->backend_xid, + &localentry->backend_xmin, + &localentry->backend_subxact_count, + &localentry->backend_subxact_overflowed); + + localentry++; + localappname += NAMEDATALEN; + localclienthostname += NAMEDATALEN; + localactivity += pgstat_track_activity_query_size; +#ifdef USE_SSL + localsslstatus++; +#endif +#ifdef ENABLE_GSS + localgssstatus++; +#endif + localNumBackends++; + } + + beentry++; + } + + /* Set the pointer only after completion of a valid table */ + localBackendStatusTable = localtable; +} + + +/* ---------- + * pgstat_get_backend_current_activity() - + * + * Return a string representing the current activity of the backend with + * the specified PID. This looks directly at the BackendStatusArray, + * and so will provide current information regardless of the age of our + * transaction's snapshot of the status array. + * + * It is the caller's responsibility to invoke this only for backends whose + * state is expected to remain stable while the result is in use. The + * only current use is in deadlock reporting, where we can expect that + * the target backend is blocked on a lock. (There are corner cases + * where the target's wait could get aborted while we are looking at it, + * but the very worst consequence is to return a pointer to a string + * that's been changed, so we won't worry too much.) + * + * Note: return strings for special cases match pg_stat_get_backend_activity. + * ---------- + */ +const char * +pgstat_get_backend_current_activity(int pid, bool checkUser) +{ + PgBackendStatus *beentry; + int i; + + beentry = BackendStatusArray; + for (i = 1; i <= MaxBackends; i++) + { + /* + * Although we expect the target backend's entry to be stable, that + * doesn't imply that anyone else's is. To avoid identifying the + * wrong backend, while we check for a match to the desired PID we + * must follow the protocol of retrying if st_changecount changes + * while we examine the entry, or if it's odd. (This might be + * unnecessary, since fetching or storing an int is almost certainly + * atomic, but let's play it safe.) We use a volatile pointer here to + * ensure the compiler doesn't try to get cute. + */ + volatile PgBackendStatus *vbeentry = beentry; + bool found; + + for (;;) + { + int before_changecount; + int after_changecount; + + pgstat_begin_read_activity(vbeentry, before_changecount); + + found = (vbeentry->st_procpid == pid); + + pgstat_end_read_activity(vbeentry, after_changecount); + + if (pgstat_read_activity_complete(before_changecount, + after_changecount)) + break; + + /* Make sure we can break out of loop if stuck... */ + CHECK_FOR_INTERRUPTS(); + } + + if (found) + { + /* Now it is safe to use the non-volatile pointer */ + if (checkUser && !superuser() && beentry->st_userid != GetUserId()) + return "<insufficient privilege>"; + else if (*(beentry->st_activity_raw) == '\0') + return "<command string not enabled>"; + else + { + /* this'll leak a bit of memory, but that seems acceptable */ + return pgstat_clip_activity(beentry->st_activity_raw); + } + } + + beentry++; + } + + /* If we get here, caller is in error ... */ + return "<backend information not available>"; +} + +/* ---------- + * pgstat_get_crashed_backend_activity() - + * + * Return a string representing the current activity of the backend with + * the specified PID. Like the function above, but reads shared memory with + * the expectation that it may be corrupt. On success, copy the string + * into the "buffer" argument and return that pointer. On failure, + * return NULL. + * + * This function is only intended to be used by the postmaster to report the + * query that crashed a backend. In particular, no attempt is made to + * follow the correct concurrency protocol when accessing the + * BackendStatusArray. But that's OK, in the worst case we'll return a + * corrupted message. We also must take care not to trip on ereport(ERROR). + * ---------- + */ +const char * +pgstat_get_crashed_backend_activity(int pid, char *buffer, int buflen) +{ + volatile PgBackendStatus *beentry; + int i; + + beentry = BackendStatusArray; + + /* + * We probably shouldn't get here before shared memory has been set up, + * but be safe. + */ + if (beentry == NULL || BackendActivityBuffer == NULL) + return NULL; + + for (i = 1; i <= MaxBackends; i++) + { + if (beentry->st_procpid == pid) + { + /* Read pointer just once, so it can't change after validation */ + const char *activity = beentry->st_activity_raw; + const char *activity_last; + + /* + * We mustn't access activity string before we verify that it + * falls within the BackendActivityBuffer. To make sure that the + * entire string including its ending is contained within the + * buffer, subtract one activity length from the buffer size. + */ + activity_last = BackendActivityBuffer + BackendActivityBufferSize + - pgstat_track_activity_query_size; + + if (activity < BackendActivityBuffer || + activity > activity_last) + return NULL; + + /* If no string available, no point in a report */ + if (activity[0] == '\0') + return NULL; + + /* + * Copy only ASCII-safe characters so we don't run into encoding + * problems when reporting the message; and be sure not to run off + * the end of memory. As only ASCII characters are reported, it + * doesn't seem necessary to perform multibyte aware clipping. + */ + ascii_safe_strlcpy(buffer, activity, + Min(buflen, pgstat_track_activity_query_size)); + + return buffer; + } + + beentry++; + } + + /* PID not found */ + return NULL; +} + +/* ---------- + * pgstat_get_my_query_id() - + * + * Return current backend's query identifier. + */ +uint64 +pgstat_get_my_query_id(void) +{ + if (!MyBEEntry) + return 0; + + /* + * There's no need for a lock around pgstat_begin_read_activity / + * pgstat_end_read_activity here as it's only called from + * pg_stat_get_activity which is already protected, or from the same + * backend which means that there won't be concurrent writes. + */ + return MyBEEntry->st_query_id; +} + +/* ---------- + * cmp_lbestatus + * + * Comparison function for bsearch() on an array of LocalPgBackendStatus. + * The backend_id field is used to compare the arguments. + * ---------- + */ +static int +cmp_lbestatus(const void *a, const void *b) +{ + const LocalPgBackendStatus *lbestatus1 = (const LocalPgBackendStatus *) a; + const LocalPgBackendStatus *lbestatus2 = (const LocalPgBackendStatus *) b; + + return lbestatus1->backend_id - lbestatus2->backend_id; +} + +/* ---------- + * pgstat_get_beentry_by_backend_id() - + * + * Support function for the SQL-callable pgstat* functions. Returns + * our local copy of the current-activity entry for one backend, + * or NULL if the given beid doesn't identify any known session. + * + * The beid argument is the BackendId of the desired session + * (note that this is unlike pgstat_get_local_beentry_by_index()). + * + * NB: caller is responsible for a check if the user is permitted to see + * this info (especially the querystring). + * ---------- + */ +PgBackendStatus * +pgstat_get_beentry_by_backend_id(BackendId beid) +{ + LocalPgBackendStatus *ret = pgstat_get_local_beentry_by_backend_id(beid); + + if (ret) + return &ret->backendStatus; + + return NULL; +} + + +/* ---------- + * pgstat_get_local_beentry_by_backend_id() - + * + * Like pgstat_get_beentry_by_backend_id() but with locally computed additions + * (like xid and xmin values of the backend) + * + * The beid argument is the BackendId of the desired session + * (note that this is unlike pgstat_get_local_beentry_by_index()). + * + * NB: caller is responsible for checking if the user is permitted to see this + * info (especially the querystring). + * ---------- + */ +LocalPgBackendStatus * +pgstat_get_local_beentry_by_backend_id(BackendId beid) +{ + LocalPgBackendStatus key; + + pgstat_read_current_status(); + + /* + * Since the localBackendStatusTable is in order by backend_id, we can use + * bsearch() to search it efficiently. + */ + key.backend_id = beid; + return bsearch(&key, localBackendStatusTable, localNumBackends, + sizeof(LocalPgBackendStatus), cmp_lbestatus); +} + + +/* ---------- + * pgstat_get_local_beentry_by_index() - + * + * Like pgstat_get_beentry_by_backend_id() but with locally computed additions + * (like xid and xmin values of the backend) + * + * The idx argument is a 1-based index in the localBackendStatusTable + * (note that this is unlike pgstat_get_beentry_by_backend_id()). + * Returns NULL if the argument is out of range (no current caller does that). + * + * NB: caller is responsible for a check if the user is permitted to see + * this info (especially the querystring). + * ---------- + */ +LocalPgBackendStatus * +pgstat_get_local_beentry_by_index(int idx) +{ + pgstat_read_current_status(); + + if (idx < 1 || idx > localNumBackends) + return NULL; + + return &localBackendStatusTable[idx - 1]; +} + + +/* ---------- + * pgstat_fetch_stat_numbackends() - + * + * Support function for the SQL-callable pgstat* functions. Returns + * the number of sessions known in the localBackendStatusTable, i.e. + * the maximum 1-based index to pass to pgstat_get_local_beentry_by_index(). + * ---------- + */ +int +pgstat_fetch_stat_numbackends(void) +{ + pgstat_read_current_status(); + + return localNumBackends; +} + +/* + * Convert a potentially unsafely truncated activity string (see + * PgBackendStatus.st_activity_raw's documentation) into a correctly truncated + * one. + * + * The returned string is allocated in the caller's memory context and may be + * freed. + */ +char * +pgstat_clip_activity(const char *raw_activity) +{ + char *activity; + int rawlen; + int cliplen; + + /* + * Some callers, like pgstat_get_backend_current_activity(), do not + * guarantee that the buffer isn't concurrently modified. We try to take + * care that the buffer is always terminated by a NUL byte regardless, but + * let's still be paranoid about the string's length. In those cases the + * underlying buffer is guaranteed to be pgstat_track_activity_query_size + * large. + */ + activity = pnstrdup(raw_activity, pgstat_track_activity_query_size - 1); + + /* now double-guaranteed to be NUL terminated */ + rawlen = strlen(activity); + + /* + * All supported server-encodings make it possible to determine the length + * of a multi-byte character from its first byte (this is not the case for + * client encodings, see GB18030). As st_activity is always stored using + * server encoding, this allows us to perform multi-byte aware truncation, + * even if the string earlier was truncated in the middle of a multi-byte + * character. + */ + cliplen = pg_mbcliplen(activity, rawlen, + pgstat_track_activity_query_size - 1); + + activity[cliplen] = '\0'; + + return activity; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/activity/pgstat.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/activity/pgstat.c new file mode 100644 index 00000000000..18cdde8d05c --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/activity/pgstat.c @@ -0,0 +1,1719 @@ +/* ---------- + * pgstat.c + * Infrastructure for the cumulative statistics system. + * + * The cumulative statistics system accumulates statistics for different kinds + * of objects. Some kinds of statistics are collected for a fixed number of + * objects (most commonly 1), e.g., checkpointer statistics. Other kinds of + * statistics are collected for a varying number of objects + * (e.g. relations). See PgStat_KindInfo for a list of currently handled + * statistics. + * + * Statistics are loaded from the filesystem during startup (by the startup + * process), unless preceded by a crash, in which case all stats are + * discarded. They are written out by the checkpointer process just before + * shutting down, except when shutting down in immediate mode. + * + * Fixed-numbered stats are stored in plain (non-dynamic) shared memory. + * + * Statistics for variable-numbered objects are stored in dynamic shared + * memory and can be found via a dshash hashtable. The statistics counters are + * not part of the dshash entry (PgStatShared_HashEntry) directly, but are + * separately allocated (PgStatShared_HashEntry->body). The separate + * allocation allows different kinds of statistics to be stored in the same + * hashtable without wasting space in PgStatShared_HashEntry. + * + * Variable-numbered stats are addressed by PgStat_HashKey while running. It + * is not possible to have statistics for an object that cannot be addressed + * that way at runtime. A wider identifier can be used when serializing to + * disk (used for replication slot stats). + * + * To avoid contention on the shared hashtable, each backend has a + * backend-local hashtable (pgStatEntryRefHash) in front of the shared + * hashtable, containing references (PgStat_EntryRef) to shared hashtable + * entries. The shared hashtable only needs to be accessed when no prior + * reference is found in the local hashtable. Besides pointing to the + * shared hashtable entry (PgStatShared_HashEntry) PgStat_EntryRef also + * contains a pointer to the shared statistics data, as a process-local + * address, to reduce access costs. + * + * The names for structs stored in shared memory are prefixed with + * PgStatShared instead of PgStat. Each stats entry in shared memory is + * protected by a dedicated lwlock. + * + * Most stats updates are first accumulated locally in each process as pending + * entries, then later flushed to shared memory (just after commit, or by + * idle-timeout). This practically eliminates contention on individual stats + * entries. For most kinds of variable-numbered pending stats data is stored + * in PgStat_EntryRef->pending. All entries with pending data are in the + * pgStatPending list. Pending statistics updates are flushed out by + * pgstat_report_stat(). + * + * The behavior of different kinds of statistics is determined by the kind's + * entry in pgstat_kind_infos, see PgStat_KindInfo for details. + * + * The consistency of read accesses to statistics can be configured using the + * stats_fetch_consistency GUC (see config.sgml and monitoring.sgml for the + * settings). When using PGSTAT_FETCH_CONSISTENCY_CACHE or + * PGSTAT_FETCH_CONSISTENCY_SNAPSHOT statistics are stored in + * pgStatLocal.snapshot. + * + * To keep things manageable, stats handling is split across several + * files. Infrastructure pieces are in: + * - pgstat.c - this file, to tie it all together + * - pgstat_shmem.c - nearly everything dealing with shared memory, including + * the maintenance of hashtable entries + * - pgstat_xact.c - transactional integration, including the transactional + * creation and dropping of stats entries + * + * Each statistics kind is handled in a dedicated file: + * - pgstat_archiver.c + * - pgstat_bgwriter.c + * - pgstat_checkpointer.c + * - pgstat_database.c + * - pgstat_function.c + * - pgstat_io.c + * - pgstat_relation.c + * - pgstat_replslot.c + * - pgstat_slru.c + * - pgstat_subscription.c + * - pgstat_wal.c + * + * Whenever possible infrastructure files should not contain code related to + * specific kinds of stats. + * + * + * Copyright (c) 2001-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/utils/activity/pgstat.c + * ---------- + */ +#include "postgres.h" + +#include <unistd.h> + +#include "access/transam.h" +#include "access/xact.h" +#include "lib/dshash.h" +#include "pgstat.h" +#include "port/atomics.h" +#include "storage/fd.h" +#include "storage/ipc.h" +#include "storage/lwlock.h" +#include "storage/pg_shmem.h" +#include "storage/shmem.h" +#include "utils/guc_hooks.h" +#include "utils/memutils.h" +#include "utils/pgstat_internal.h" +#include "utils/timestamp.h" + + +/* ---------- + * Timer definitions. + * + * In milliseconds. + * ---------- + */ + +/* minimum interval non-forced stats flushes.*/ +#define PGSTAT_MIN_INTERVAL 1000 +/* how long until to block flushing pending stats updates */ +#define PGSTAT_MAX_INTERVAL 60000 +/* when to call pgstat_report_stat() again, even when idle */ +#define PGSTAT_IDLE_INTERVAL 10000 + +/* ---------- + * Initial size hints for the hash tables used in statistics. + * ---------- + */ + +#define PGSTAT_SNAPSHOT_HASH_SIZE 512 + + +/* hash table for statistics snapshots entry */ +typedef struct PgStat_SnapshotEntry +{ + PgStat_HashKey key; + char status; /* for simplehash use */ + void *data; /* the stats data itself */ +} PgStat_SnapshotEntry; + + +/* ---------- + * Backend-local Hash Table Definitions + * ---------- + */ + +/* for stats snapshot entries */ +#define SH_PREFIX pgstat_snapshot +#define SH_ELEMENT_TYPE PgStat_SnapshotEntry +#define SH_KEY_TYPE PgStat_HashKey +#define SH_KEY key +#define SH_HASH_KEY(tb, key) \ + pgstat_hash_hash_key(&key, sizeof(PgStat_HashKey), NULL) +#define SH_EQUAL(tb, a, b) \ + pgstat_cmp_hash_key(&a, &b, sizeof(PgStat_HashKey), NULL) == 0 +#define SH_SCOPE static inline +#define SH_DEFINE +#define SH_DECLARE +#include "lib/simplehash.h" + + +/* ---------- + * Local function forward declarations + * ---------- + */ + +static void pgstat_write_statsfile(void); +static void pgstat_read_statsfile(void); + +static void pgstat_reset_after_failure(void); + +static bool pgstat_flush_pending_entries(bool nowait); + +static void pgstat_prep_snapshot(void); +static void pgstat_build_snapshot(void); +static void pgstat_build_snapshot_fixed(PgStat_Kind kind); + +static inline bool pgstat_is_kind_valid(int ikind); + + +/* ---------- + * GUC parameters + * ---------- + */ + +__thread bool pgstat_track_counts = false; +__thread int pgstat_fetch_consistency = PGSTAT_FETCH_CONSISTENCY_CACHE; + + +/* ---------- + * state shared with pgstat_*.c + * ---------- + */ + +__thread PgStat_LocalState pgStatLocal; + + +/* ---------- + * Local data + * + * NB: There should be only variables related to stats infrastructure here, + * not for specific kinds of stats. + * ---------- + */ + +/* + * Memory contexts containing the pgStatEntryRefHash table, the + * pgStatSharedRef entries, and pending data respectively. Mostly to make it + * easier to track / attribute memory usage. + */ + +static __thread MemoryContext pgStatPendingContext = NULL; + +/* + * Backend local list of PgStat_EntryRef with unflushed pending stats. + * + * Newly pending entries should only ever be added to the end of the list, + * otherwise pgstat_flush_pending_entries() might not see them immediately. + */ +static __thread dlist_head pgStatPending ;void pgStatPending_init(void) { dlist_init(&pgStatPending); } + + +/* + * Force the next stats flush to happen regardless of + * PGSTAT_MIN_INTERVAL. Useful in test scripts. + */ +static __thread bool pgStatForceNextFlush = false; + +/* + * Force-clear existing snapshot before next use when stats_fetch_consistency + * is changed. + */ +static __thread bool force_stats_snapshot_clear = false; + + +/* + * For assertions that check pgstat is not used before initialization / after + * shutdown. + */ +#ifdef USE_ASSERT_CHECKING +static bool pgstat_is_initialized = false; +static bool pgstat_is_shutdown = false; +#endif + + +/* + * The different kinds of statistics. + * + * If reasonably possible, handling specific to one kind of stats should go + * through this abstraction, rather than making more of pgstat.c aware. + * + * See comments for struct PgStat_KindInfo for details about the individual + * fields. + * + * XXX: It'd be nicer to define this outside of this file. But there doesn't + * seem to be a great way of doing that, given the split across multiple + * files. + */ +static const PgStat_KindInfo pgstat_kind_infos[PGSTAT_NUM_KINDS] = { + + /* stats kinds for variable-numbered objects */ + + [PGSTAT_KIND_DATABASE] = { + .name = "database", + + .fixed_amount = false, + /* so pg_stat_database entries can be seen in all databases */ + .accessed_across_databases = true, + + .shared_size = sizeof(PgStatShared_Database), + .shared_data_off = offsetof(PgStatShared_Database, stats), + .shared_data_len = sizeof(((PgStatShared_Database *) 0)->stats), + .pending_size = sizeof(PgStat_StatDBEntry), + + .flush_pending_cb = pgstat_database_flush_cb, + .reset_timestamp_cb = pgstat_database_reset_timestamp_cb, + }, + + [PGSTAT_KIND_RELATION] = { + .name = "relation", + + .fixed_amount = false, + + .shared_size = sizeof(PgStatShared_Relation), + .shared_data_off = offsetof(PgStatShared_Relation, stats), + .shared_data_len = sizeof(((PgStatShared_Relation *) 0)->stats), + .pending_size = sizeof(PgStat_TableStatus), + + .flush_pending_cb = pgstat_relation_flush_cb, + .delete_pending_cb = pgstat_relation_delete_pending_cb, + }, + + [PGSTAT_KIND_FUNCTION] = { + .name = "function", + + .fixed_amount = false, + + .shared_size = sizeof(PgStatShared_Function), + .shared_data_off = offsetof(PgStatShared_Function, stats), + .shared_data_len = sizeof(((PgStatShared_Function *) 0)->stats), + .pending_size = sizeof(PgStat_FunctionCounts), + + .flush_pending_cb = pgstat_function_flush_cb, + }, + + [PGSTAT_KIND_REPLSLOT] = { + .name = "replslot", + + .fixed_amount = false, + + .accessed_across_databases = true, + .named_on_disk = true, + + .shared_size = sizeof(PgStatShared_ReplSlot), + .shared_data_off = offsetof(PgStatShared_ReplSlot, stats), + .shared_data_len = sizeof(((PgStatShared_ReplSlot *) 0)->stats), + + .reset_timestamp_cb = pgstat_replslot_reset_timestamp_cb, + .to_serialized_name = pgstat_replslot_to_serialized_name_cb, + .from_serialized_name = pgstat_replslot_from_serialized_name_cb, + }, + + [PGSTAT_KIND_SUBSCRIPTION] = { + .name = "subscription", + + .fixed_amount = false, + /* so pg_stat_subscription_stats entries can be seen in all databases */ + .accessed_across_databases = true, + + .shared_size = sizeof(PgStatShared_Subscription), + .shared_data_off = offsetof(PgStatShared_Subscription, stats), + .shared_data_len = sizeof(((PgStatShared_Subscription *) 0)->stats), + .pending_size = sizeof(PgStat_BackendSubEntry), + + .flush_pending_cb = pgstat_subscription_flush_cb, + .reset_timestamp_cb = pgstat_subscription_reset_timestamp_cb, + }, + + + /* stats for fixed-numbered (mostly 1) objects */ + + [PGSTAT_KIND_ARCHIVER] = { + .name = "archiver", + + .fixed_amount = true, + + .reset_all_cb = pgstat_archiver_reset_all_cb, + .snapshot_cb = pgstat_archiver_snapshot_cb, + }, + + [PGSTAT_KIND_BGWRITER] = { + .name = "bgwriter", + + .fixed_amount = true, + + .reset_all_cb = pgstat_bgwriter_reset_all_cb, + .snapshot_cb = pgstat_bgwriter_snapshot_cb, + }, + + [PGSTAT_KIND_CHECKPOINTER] = { + .name = "checkpointer", + + .fixed_amount = true, + + .reset_all_cb = pgstat_checkpointer_reset_all_cb, + .snapshot_cb = pgstat_checkpointer_snapshot_cb, + }, + + [PGSTAT_KIND_IO] = { + .name = "io", + + .fixed_amount = true, + + .reset_all_cb = pgstat_io_reset_all_cb, + .snapshot_cb = pgstat_io_snapshot_cb, + }, + + [PGSTAT_KIND_SLRU] = { + .name = "slru", + + .fixed_amount = true, + + .reset_all_cb = pgstat_slru_reset_all_cb, + .snapshot_cb = pgstat_slru_snapshot_cb, + }, + + [PGSTAT_KIND_WAL] = { + .name = "wal", + + .fixed_amount = true, + + .reset_all_cb = pgstat_wal_reset_all_cb, + .snapshot_cb = pgstat_wal_snapshot_cb, + }, +}; + + +/* ------------------------------------------------------------ + * Functions managing the state of the stats system for all backends. + * ------------------------------------------------------------ + */ + +/* + * Read on-disk stats into memory at server start. + * + * Should only be called by the startup process or in single user mode. + */ +void +pgstat_restore_stats(void) +{ + pgstat_read_statsfile(); +} + +/* + * Remove the stats file. This is currently used only if WAL recovery is + * needed after a crash. + * + * Should only be called by the startup process or in single user mode. + */ +void +pgstat_discard_stats(void) +{ + int ret; + + /* NB: this needs to be done even in single user mode */ + + ret = unlink(PGSTAT_STAT_PERMANENT_FILENAME); + if (ret != 0) + { + if (errno == ENOENT) + elog(DEBUG2, + "didn't need to unlink permanent stats file \"%s\" - didn't exist", + PGSTAT_STAT_PERMANENT_FILENAME); + else + ereport(LOG, + (errcode_for_file_access(), + errmsg("could not unlink permanent statistics file \"%s\": %m", + PGSTAT_STAT_PERMANENT_FILENAME))); + } + else + { + ereport(DEBUG2, + (errcode_for_file_access(), + errmsg_internal("unlinked permanent statistics file \"%s\"", + PGSTAT_STAT_PERMANENT_FILENAME))); + } + + /* + * Reset stats contents. This will set reset timestamps of fixed-numbered + * stats to the current time (no variable stats exist). + */ + pgstat_reset_after_failure(); +} + +/* + * pgstat_before_server_shutdown() needs to be called by exactly one process + * during regular server shutdowns. Otherwise all stats will be lost. + * + * We currently only write out stats for proc_exit(0). We might want to change + * that at some point... But right now pgstat_discard_stats() would be called + * during the start after a disorderly shutdown, anyway. + */ +void +pgstat_before_server_shutdown(int code, Datum arg) +{ + Assert(pgStatLocal.shmem != NULL); + Assert(!pgStatLocal.shmem->is_shutdown); + + /* + * Stats should only be reported after pgstat_initialize() and before + * pgstat_shutdown(). This is a convenient point to catch most violations + * of this rule. + */ + Assert(pgstat_is_initialized && !pgstat_is_shutdown); + + /* flush out our own pending changes before writing out */ + pgstat_report_stat(true); + + /* + * Only write out file during normal shutdown. Don't even signal that + * we've shutdown during irregular shutdowns, because the shutdown + * sequence isn't coordinated to ensure this backend shuts down last. + */ + if (code == 0) + { + pgStatLocal.shmem->is_shutdown = true; + pgstat_write_statsfile(); + } +} + + +/* ------------------------------------------------------------ + * Backend initialization / shutdown functions + * ------------------------------------------------------------ + */ + +/* + * Shut down a single backend's statistics reporting at process exit. + * + * Flush out any remaining statistics counts. Without this, operations + * triggered during backend exit (such as temp table deletions) won't be + * counted. + */ +static void +pgstat_shutdown_hook(int code, Datum arg) +{ + Assert(!pgstat_is_shutdown); + Assert(IsUnderPostmaster || !IsPostmasterEnvironment); + + /* + * If we got as far as discovering our own database ID, we can flush out + * what we did so far. Otherwise, we'd be reporting an invalid database + * ID, so forget it. (This means that accesses to pg_database during + * failed backend starts might never get counted.) + */ + if (OidIsValid(MyDatabaseId)) + pgstat_report_disconnect(MyDatabaseId); + + pgstat_report_stat(true); + + /* there shouldn't be any pending changes left */ + Assert(dlist_is_empty(&pgStatPending)); + dlist_init(&pgStatPending); + + pgstat_detach_shmem(); + +#ifdef USE_ASSERT_CHECKING + pgstat_is_shutdown = true; +#endif +} + +/* + * Initialize pgstats state, and set up our on-proc-exit hook. Called from + * BaseInit(). + * + * NOTE: MyDatabaseId isn't set yet; so the shutdown hook has to be careful. + */ +void +pgstat_initialize(void) +{ + Assert(!pgstat_is_initialized); + + pgstat_attach_shmem(); + + pgstat_init_wal(); + + /* Set up a process-exit hook to clean up */ + before_shmem_exit(pgstat_shutdown_hook, 0); + +#ifdef USE_ASSERT_CHECKING + pgstat_is_initialized = true; +#endif +} + + +/* ------------------------------------------------------------ + * Public functions used by backends follow + * ------------------------------------------------------------ + */ + +/* + * Must be called by processes that performs DML: tcop/postgres.c, logical + * receiver processes, SPI worker, etc. to flush pending statistics updates to + * shared memory. + * + * Unless called with 'force', pending stats updates are flushed happen once + * per PGSTAT_MIN_INTERVAL (1000ms). When not forced, stats flushes do not + * block on lock acquisition, except if stats updates have been pending for + * longer than PGSTAT_MAX_INTERVAL (60000ms). + * + * Whenever pending stats updates remain at the end of pgstat_report_stat() a + * suggested idle timeout is returned. Currently this is always + * PGSTAT_IDLE_INTERVAL (10000ms). Callers can use the returned time to set up + * a timeout after which to call pgstat_report_stat(true), but are not + * required to do so. + * + * Note that this is called only when not within a transaction, so it is fair + * to use transaction stop time as an approximation of current time. + */ +long +pgstat_report_stat(bool force) +{ + static __thread TimestampTz pending_since = 0; + static __thread TimestampTz last_flush = 0; + bool partial_flush; + TimestampTz now; + bool nowait; + + pgstat_assert_is_up(); + Assert(!IsTransactionOrTransactionBlock()); + + /* "absorb" the forced flush even if there's nothing to flush */ + if (pgStatForceNextFlush) + { + force = true; + pgStatForceNextFlush = false; + } + + /* Don't expend a clock check if nothing to do */ + if (dlist_is_empty(&pgStatPending) && + !have_iostats && + !have_slrustats && + !pgstat_have_pending_wal()) + { + Assert(pending_since == 0); + return 0; + } + + /* + * There should never be stats to report once stats are shut down. Can't + * assert that before the checks above, as there is an unconditional + * pgstat_report_stat() call in pgstat_shutdown_hook() - which at least + * the process that ran pgstat_before_server_shutdown() will still call. + */ + Assert(!pgStatLocal.shmem->is_shutdown); + + if (force) + { + /* + * Stats reports are forced either when it's been too long since stats + * have been reported or in processes that force stats reporting to + * happen at specific points (including shutdown). In the former case + * the transaction stop time might be quite old, in the latter it + * would never get cleared. + */ + now = GetCurrentTimestamp(); + } + else + { + now = GetCurrentTransactionStopTimestamp(); + + if (pending_since > 0 && + TimestampDifferenceExceeds(pending_since, now, PGSTAT_MAX_INTERVAL)) + { + /* don't keep pending updates longer than PGSTAT_MAX_INTERVAL */ + force = true; + } + else if (last_flush > 0 && + !TimestampDifferenceExceeds(last_flush, now, PGSTAT_MIN_INTERVAL)) + { + /* don't flush too frequently */ + if (pending_since == 0) + pending_since = now; + + return PGSTAT_IDLE_INTERVAL; + } + } + + pgstat_update_dbstats(now); + + /* don't wait for lock acquisition when !force */ + nowait = !force; + + partial_flush = false; + + /* flush database / relation / function / ... stats */ + partial_flush |= pgstat_flush_pending_entries(nowait); + + /* flush IO stats */ + partial_flush |= pgstat_flush_io(nowait); + + /* flush wal stats */ + partial_flush |= pgstat_flush_wal(nowait); + + /* flush SLRU stats */ + partial_flush |= pgstat_slru_flush(nowait); + + last_flush = now; + + /* + * If some of the pending stats could not be flushed due to lock + * contention, let the caller know when to retry. + */ + if (partial_flush) + { + /* force should have prevented us from getting here */ + Assert(!force); + + /* remember since when stats have been pending */ + if (pending_since == 0) + pending_since = now; + + return PGSTAT_IDLE_INTERVAL; + } + + pending_since = 0; + + return 0; +} + +/* + * Force locally pending stats to be flushed during the next + * pgstat_report_stat() call. This is useful for writing tests. + */ +void +pgstat_force_next_flush(void) +{ + pgStatForceNextFlush = true; +} + +/* + * Only for use by pgstat_reset_counters() + */ +static bool +match_db_entries(PgStatShared_HashEntry *entry, Datum match_data) +{ + return entry->key.dboid == DatumGetObjectId(MyDatabaseId); +} + +/* + * Reset counters for our database. + * + * Permission checking for this function is managed through the normal + * GRANT system. + */ +void +pgstat_reset_counters(void) +{ + TimestampTz ts = GetCurrentTimestamp(); + + pgstat_reset_matching_entries(match_db_entries, + ObjectIdGetDatum(MyDatabaseId), + ts); +} + +/* + * Reset a single variable-numbered entry. + * + * If the stats kind is within a database, also reset the database's + * stat_reset_timestamp. + * + * Permission checking for this function is managed through the normal + * GRANT system. + */ +void +pgstat_reset(PgStat_Kind kind, Oid dboid, Oid objoid) +{ + const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind); + TimestampTz ts = GetCurrentTimestamp(); + + /* not needed atm, and doesn't make sense with the current signature */ + Assert(!pgstat_get_kind_info(kind)->fixed_amount); + + /* reset the "single counter" */ + pgstat_reset_entry(kind, dboid, objoid, ts); + + if (!kind_info->accessed_across_databases) + pgstat_reset_database_timestamp(dboid, ts); +} + +/* + * Reset stats for all entries of a kind. + * + * Permission checking for this function is managed through the normal + * GRANT system. + */ +void +pgstat_reset_of_kind(PgStat_Kind kind) +{ + const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind); + TimestampTz ts = GetCurrentTimestamp(); + + if (kind_info->fixed_amount) + kind_info->reset_all_cb(ts); + else + pgstat_reset_entries_of_kind(kind, ts); +} + + +/* ------------------------------------------------------------ + * Fetching of stats + * ------------------------------------------------------------ + */ + +/* + * Discard any data collected in the current transaction. Any subsequent + * request will cause new snapshots to be read. + * + * This is also invoked during transaction commit or abort to discard + * the no-longer-wanted snapshot. Updates of stats_fetch_consistency can + * cause this routine to be called. + */ +void +pgstat_clear_snapshot(void) +{ + pgstat_assert_is_up(); + + memset(&pgStatLocal.snapshot.fixed_valid, 0, + sizeof(pgStatLocal.snapshot.fixed_valid)); + pgStatLocal.snapshot.stats = NULL; + pgStatLocal.snapshot.mode = PGSTAT_FETCH_CONSISTENCY_NONE; + + /* Release memory, if any was allocated */ + if (pgStatLocal.snapshot.context) + { + MemoryContextDelete(pgStatLocal.snapshot.context); + + /* Reset variables */ + pgStatLocal.snapshot.context = NULL; + } + + /* + * Historically the backend_status.c facilities lived in this file, and + * were reset with the same function. For now keep it that way, and + * forward the reset request. + */ + pgstat_clear_backend_activity_snapshot(); + + /* Reset this flag, as it may be possible that a cleanup was forced. */ + force_stats_snapshot_clear = false; +} + +void * +pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, Oid objoid) +{ + PgStat_HashKey key; + PgStat_EntryRef *entry_ref; + void *stats_data; + const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind); + + /* should be called from backends */ + Assert(IsUnderPostmaster || !IsPostmasterEnvironment); + Assert(!kind_info->fixed_amount); + + pgstat_prep_snapshot(); + + key.kind = kind; + key.dboid = dboid; + key.objoid = objoid; + + /* if we need to build a full snapshot, do so */ + if (pgstat_fetch_consistency == PGSTAT_FETCH_CONSISTENCY_SNAPSHOT) + pgstat_build_snapshot(); + + /* if caching is desired, look up in cache */ + if (pgstat_fetch_consistency > PGSTAT_FETCH_CONSISTENCY_NONE) + { + PgStat_SnapshotEntry *entry = NULL; + + entry = pgstat_snapshot_lookup(pgStatLocal.snapshot.stats, key); + + if (entry) + return entry->data; + + /* + * If we built a full snapshot and the key is not in + * pgStatLocal.snapshot.stats, there are no matching stats. + */ + if (pgstat_fetch_consistency == PGSTAT_FETCH_CONSISTENCY_SNAPSHOT) + return NULL; + } + + pgStatLocal.snapshot.mode = pgstat_fetch_consistency; + + entry_ref = pgstat_get_entry_ref(kind, dboid, objoid, false, NULL); + + if (entry_ref == NULL || entry_ref->shared_entry->dropped) + { + /* create empty entry when using PGSTAT_FETCH_CONSISTENCY_CACHE */ + if (pgstat_fetch_consistency == PGSTAT_FETCH_CONSISTENCY_CACHE) + { + PgStat_SnapshotEntry *entry = NULL; + bool found; + + entry = pgstat_snapshot_insert(pgStatLocal.snapshot.stats, key, &found); + Assert(!found); + entry->data = NULL; + } + return NULL; + } + + /* + * Allocate in caller's context for PGSTAT_FETCH_CONSISTENCY_NONE, + * otherwise we could quickly end up with a fair bit of memory used due to + * repeated accesses. + */ + if (pgstat_fetch_consistency == PGSTAT_FETCH_CONSISTENCY_NONE) + stats_data = palloc(kind_info->shared_data_len); + else + stats_data = MemoryContextAlloc(pgStatLocal.snapshot.context, + kind_info->shared_data_len); + + pgstat_lock_entry_shared(entry_ref, false); + memcpy(stats_data, + pgstat_get_entry_data(kind, entry_ref->shared_stats), + kind_info->shared_data_len); + pgstat_unlock_entry(entry_ref); + + if (pgstat_fetch_consistency > PGSTAT_FETCH_CONSISTENCY_NONE) + { + PgStat_SnapshotEntry *entry = NULL; + bool found; + + entry = pgstat_snapshot_insert(pgStatLocal.snapshot.stats, key, &found); + entry->data = stats_data; + } + + return stats_data; +} + +/* + * If a stats snapshot has been taken, return the timestamp at which that was + * done, and set *have_snapshot to true. Otherwise *have_snapshot is set to + * false. + */ +TimestampTz +pgstat_get_stat_snapshot_timestamp(bool *have_snapshot) +{ + if (force_stats_snapshot_clear) + pgstat_clear_snapshot(); + + if (pgStatLocal.snapshot.mode == PGSTAT_FETCH_CONSISTENCY_SNAPSHOT) + { + *have_snapshot = true; + return pgStatLocal.snapshot.snapshot_timestamp; + } + + *have_snapshot = false; + + return 0; +} + +bool +pgstat_have_entry(PgStat_Kind kind, Oid dboid, Oid objoid) +{ + /* fixed-numbered stats always exist */ + if (pgstat_get_kind_info(kind)->fixed_amount) + return true; + + return pgstat_get_entry_ref(kind, dboid, objoid, false, NULL) != NULL; +} + +/* + * Ensure snapshot for fixed-numbered 'kind' exists. + * + * Typically used by the pgstat_fetch_* functions for a kind of stats, before + * massaging the data into the desired format. + */ +void +pgstat_snapshot_fixed(PgStat_Kind kind) +{ + Assert(pgstat_is_kind_valid(kind)); + Assert(pgstat_get_kind_info(kind)->fixed_amount); + + if (force_stats_snapshot_clear) + pgstat_clear_snapshot(); + + if (pgstat_fetch_consistency == PGSTAT_FETCH_CONSISTENCY_SNAPSHOT) + pgstat_build_snapshot(); + else + pgstat_build_snapshot_fixed(kind); + + Assert(pgStatLocal.snapshot.fixed_valid[kind]); +} + +static void +pgstat_prep_snapshot(void) +{ + if (force_stats_snapshot_clear) + pgstat_clear_snapshot(); + + if (pgstat_fetch_consistency == PGSTAT_FETCH_CONSISTENCY_NONE || + pgStatLocal.snapshot.stats != NULL) + return; + + if (!pgStatLocal.snapshot.context) + pgStatLocal.snapshot.context = AllocSetContextCreate(TopMemoryContext, + "PgStat Snapshot", + ALLOCSET_SMALL_SIZES); + + pgStatLocal.snapshot.stats = + pgstat_snapshot_create(pgStatLocal.snapshot.context, + PGSTAT_SNAPSHOT_HASH_SIZE, + NULL); +} + +static void +pgstat_build_snapshot(void) +{ + dshash_seq_status hstat; + PgStatShared_HashEntry *p; + + /* should only be called when we need a snapshot */ + Assert(pgstat_fetch_consistency == PGSTAT_FETCH_CONSISTENCY_SNAPSHOT); + + /* snapshot already built */ + if (pgStatLocal.snapshot.mode == PGSTAT_FETCH_CONSISTENCY_SNAPSHOT) + return; + + pgstat_prep_snapshot(); + + Assert(pgStatLocal.snapshot.stats->members == 0); + + pgStatLocal.snapshot.snapshot_timestamp = GetCurrentTimestamp(); + + /* + * Snapshot all variable stats. + */ + dshash_seq_init(&hstat, pgStatLocal.shared_hash, false); + while ((p = dshash_seq_next(&hstat)) != NULL) + { + PgStat_Kind kind = p->key.kind; + const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind); + bool found; + PgStat_SnapshotEntry *entry; + PgStatShared_Common *stats_data; + + /* + * Check if the stats object should be included in the snapshot. + * Unless the stats kind can be accessed from all databases (e.g., + * database stats themselves), we only include stats for the current + * database or objects not associated with a database (e.g. shared + * relations). + */ + if (p->key.dboid != MyDatabaseId && + p->key.dboid != InvalidOid && + !kind_info->accessed_across_databases) + continue; + + if (p->dropped) + continue; + + Assert(pg_atomic_read_u32(&p->refcount) > 0); + + stats_data = dsa_get_address(pgStatLocal.dsa, p->body); + Assert(stats_data); + + entry = pgstat_snapshot_insert(pgStatLocal.snapshot.stats, p->key, &found); + Assert(!found); + + entry->data = MemoryContextAlloc(pgStatLocal.snapshot.context, + kind_info->shared_size); + + /* + * Acquire the LWLock directly instead of using + * pg_stat_lock_entry_shared() which requires a reference. + */ + LWLockAcquire(&stats_data->lock, LW_SHARED); + memcpy(entry->data, + pgstat_get_entry_data(kind, stats_data), + kind_info->shared_size); + LWLockRelease(&stats_data->lock); + } + dshash_seq_term(&hstat); + + /* + * Build snapshot of all fixed-numbered stats. + */ + for (int kind = PGSTAT_KIND_FIRST_VALID; kind <= PGSTAT_KIND_LAST; kind++) + { + const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind); + + if (!kind_info->fixed_amount) + { + Assert(kind_info->snapshot_cb == NULL); + continue; + } + + pgstat_build_snapshot_fixed(kind); + } + + pgStatLocal.snapshot.mode = PGSTAT_FETCH_CONSISTENCY_SNAPSHOT; +} + +static void +pgstat_build_snapshot_fixed(PgStat_Kind kind) +{ + const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind); + + Assert(kind_info->fixed_amount); + Assert(kind_info->snapshot_cb != NULL); + + if (pgstat_fetch_consistency == PGSTAT_FETCH_CONSISTENCY_NONE) + { + /* rebuild every time */ + pgStatLocal.snapshot.fixed_valid[kind] = false; + } + else if (pgStatLocal.snapshot.fixed_valid[kind]) + { + /* in snapshot mode we shouldn't get called again */ + Assert(pgstat_fetch_consistency == PGSTAT_FETCH_CONSISTENCY_CACHE); + return; + } + + Assert(!pgStatLocal.snapshot.fixed_valid[kind]); + + kind_info->snapshot_cb(); + + Assert(!pgStatLocal.snapshot.fixed_valid[kind]); + pgStatLocal.snapshot.fixed_valid[kind] = true; +} + + +/* ------------------------------------------------------------ + * Backend-local pending stats infrastructure + * ------------------------------------------------------------ + */ + +/* + * Returns the appropriate PgStat_EntryRef, preparing it to receive pending + * stats if not already done. + * + * If created_entry is non-NULL, it'll be set to true if the entry is newly + * created, false otherwise. + */ +PgStat_EntryRef * +pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, Oid objoid, bool *created_entry) +{ + PgStat_EntryRef *entry_ref; + + /* need to be able to flush out */ + Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL); + + if (unlikely(!pgStatPendingContext)) + { + pgStatPendingContext = + AllocSetContextCreate(TopMemoryContext, + "PgStat Pending", + ALLOCSET_SMALL_SIZES); + } + + entry_ref = pgstat_get_entry_ref(kind, dboid, objoid, + true, created_entry); + + if (entry_ref->pending == NULL) + { + size_t entrysize = pgstat_get_kind_info(kind)->pending_size; + + Assert(entrysize != (size_t) -1); + + entry_ref->pending = MemoryContextAllocZero(pgStatPendingContext, entrysize); + dlist_push_tail(&pgStatPending, &entry_ref->pending_node); + } + + return entry_ref; +} + +/* + * Return an existing stats entry, or NULL. + * + * This should only be used for helper function for pgstatfuncs.c - outside of + * that it shouldn't be needed. + */ +PgStat_EntryRef * +pgstat_fetch_pending_entry(PgStat_Kind kind, Oid dboid, Oid objoid) +{ + PgStat_EntryRef *entry_ref; + + entry_ref = pgstat_get_entry_ref(kind, dboid, objoid, false, NULL); + + if (entry_ref == NULL || entry_ref->pending == NULL) + return NULL; + + return entry_ref; +} + +void +pgstat_delete_pending_entry(PgStat_EntryRef *entry_ref) +{ + PgStat_Kind kind = entry_ref->shared_entry->key.kind; + const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind); + void *pending_data = entry_ref->pending; + + Assert(pending_data != NULL); + /* !fixed_amount stats should be handled explicitly */ + Assert(!pgstat_get_kind_info(kind)->fixed_amount); + + if (kind_info->delete_pending_cb) + kind_info->delete_pending_cb(entry_ref); + + pfree(pending_data); + entry_ref->pending = NULL; + + dlist_delete(&entry_ref->pending_node); +} + +/* + * Flush out pending stats for database objects (databases, relations, + * functions). + */ +static bool +pgstat_flush_pending_entries(bool nowait) +{ + bool have_pending = false; + dlist_node *cur = NULL; + + /* + * Need to be a bit careful iterating over the list of pending entries. + * Processing a pending entry may queue further pending entries to the end + * of the list that we want to process, so a simple iteration won't do. + * Further complicating matters is that we want to delete the current + * entry in each iteration from the list if we flushed successfully. + * + * So we just keep track of the next pointer in each loop iteration. + */ + if (!dlist_is_empty(&pgStatPending)) + cur = dlist_head_node(&pgStatPending); + + while (cur) + { + PgStat_EntryRef *entry_ref = + dlist_container(PgStat_EntryRef, pending_node, cur); + PgStat_HashKey key = entry_ref->shared_entry->key; + PgStat_Kind kind = key.kind; + const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind); + bool did_flush; + dlist_node *next; + + Assert(!kind_info->fixed_amount); + Assert(kind_info->flush_pending_cb != NULL); + + /* flush the stats, if possible */ + did_flush = kind_info->flush_pending_cb(entry_ref, nowait); + + Assert(did_flush || nowait); + + /* determine next entry, before deleting the pending entry */ + if (dlist_has_next(&pgStatPending, cur)) + next = dlist_next_node(&pgStatPending, cur); + else + next = NULL; + + /* if successfully flushed, remove entry */ + if (did_flush) + pgstat_delete_pending_entry(entry_ref); + else + have_pending = true; + + cur = next; + } + + Assert(dlist_is_empty(&pgStatPending) == !have_pending); + + return have_pending; +} + + +/* ------------------------------------------------------------ + * Helper / infrastructure functions + * ------------------------------------------------------------ + */ + +PgStat_Kind +pgstat_get_kind_from_str(char *kind_str) +{ + for (int kind = PGSTAT_KIND_FIRST_VALID; kind <= PGSTAT_KIND_LAST; kind++) + { + if (pg_strcasecmp(kind_str, pgstat_kind_infos[kind].name) == 0) + return kind; + } + + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid statistics kind: \"%s\"", kind_str))); + return PGSTAT_KIND_DATABASE; /* avoid compiler warnings */ +} + +static inline bool +pgstat_is_kind_valid(int ikind) +{ + return ikind >= PGSTAT_KIND_FIRST_VALID && ikind <= PGSTAT_KIND_LAST; +} + +const PgStat_KindInfo * +pgstat_get_kind_info(PgStat_Kind kind) +{ + Assert(pgstat_is_kind_valid(kind)); + + return &pgstat_kind_infos[kind]; +} + +/* + * Stats should only be reported after pgstat_initialize() and before + * pgstat_shutdown(). This check is put in a few central places to catch + * violations of this rule more easily. + */ +#ifdef USE_ASSERT_CHECKING +void +pgstat_assert_is_up(void) +{ + Assert(pgstat_is_initialized && !pgstat_is_shutdown); +} +#endif + + +/* ------------------------------------------------------------ + * reading and writing of on-disk stats file + * ------------------------------------------------------------ + */ + +/* helpers for pgstat_write_statsfile() */ +static void +write_chunk(FILE *fpout, void *ptr, size_t len) +{ + int rc; + + rc = fwrite(ptr, len, 1, fpout); + + /* we'll check for errors with ferror once at the end */ + (void) rc; +} + +#define write_chunk_s(fpout, ptr) write_chunk(fpout, ptr, sizeof(*ptr)) + +/* + * This function is called in the last process that is accessing the shared + * stats so locking is not required. + */ +static void +pgstat_write_statsfile(void) +{ + FILE *fpout; + int32 format_id; + const char *tmpfile = PGSTAT_STAT_PERMANENT_TMPFILE; + const char *statfile = PGSTAT_STAT_PERMANENT_FILENAME; + dshash_seq_status hstat; + PgStatShared_HashEntry *ps; + + pgstat_assert_is_up(); + + /* we're shutting down, so it's ok to just override this */ + pgstat_fetch_consistency = PGSTAT_FETCH_CONSISTENCY_NONE; + + elog(DEBUG2, "writing stats file \"%s\"", statfile); + + /* + * Open the statistics temp file to write out the current values. + */ + fpout = AllocateFile(tmpfile, PG_BINARY_W); + if (fpout == NULL) + { + ereport(LOG, + (errcode_for_file_access(), + errmsg("could not open temporary statistics file \"%s\": %m", + tmpfile))); + return; + } + + /* + * Write the file header --- currently just a format ID. + */ + format_id = PGSTAT_FILE_FORMAT_ID; + write_chunk_s(fpout, &format_id); + + /* + * XXX: The following could now be generalized to just iterate over + * pgstat_kind_infos instead of knowing about the different kinds of + * stats. + */ + + /* + * Write archiver stats struct + */ + pgstat_build_snapshot_fixed(PGSTAT_KIND_ARCHIVER); + write_chunk_s(fpout, &pgStatLocal.snapshot.archiver); + + /* + * Write bgwriter stats struct + */ + pgstat_build_snapshot_fixed(PGSTAT_KIND_BGWRITER); + write_chunk_s(fpout, &pgStatLocal.snapshot.bgwriter); + + /* + * Write checkpointer stats struct + */ + pgstat_build_snapshot_fixed(PGSTAT_KIND_CHECKPOINTER); + write_chunk_s(fpout, &pgStatLocal.snapshot.checkpointer); + + /* + * Write IO stats struct + */ + pgstat_build_snapshot_fixed(PGSTAT_KIND_IO); + write_chunk_s(fpout, &pgStatLocal.snapshot.io); + + /* + * Write SLRU stats struct + */ + pgstat_build_snapshot_fixed(PGSTAT_KIND_SLRU); + write_chunk_s(fpout, &pgStatLocal.snapshot.slru); + + /* + * Write WAL stats struct + */ + pgstat_build_snapshot_fixed(PGSTAT_KIND_WAL); + write_chunk_s(fpout, &pgStatLocal.snapshot.wal); + + /* + * Walk through the stats entries + */ + dshash_seq_init(&hstat, pgStatLocal.shared_hash, false); + while ((ps = dshash_seq_next(&hstat)) != NULL) + { + PgStatShared_Common *shstats; + const PgStat_KindInfo *kind_info = NULL; + + CHECK_FOR_INTERRUPTS(); + + /* we may have some "dropped" entries not yet removed, skip them */ + Assert(!ps->dropped); + if (ps->dropped) + continue; + + shstats = (PgStatShared_Common *) dsa_get_address(pgStatLocal.dsa, ps->body); + + kind_info = pgstat_get_kind_info(ps->key.kind); + + /* if not dropped the valid-entry refcount should exist */ + Assert(pg_atomic_read_u32(&ps->refcount) > 0); + + if (!kind_info->to_serialized_name) + { + /* normal stats entry, identified by PgStat_HashKey */ + fputc('S', fpout); + write_chunk_s(fpout, &ps->key); + } + else + { + /* stats entry identified by name on disk (e.g. slots) */ + NameData name; + + kind_info->to_serialized_name(&ps->key, shstats, &name); + + fputc('N', fpout); + write_chunk_s(fpout, &ps->key.kind); + write_chunk_s(fpout, &name); + } + + /* Write except the header part of the entry */ + write_chunk(fpout, + pgstat_get_entry_data(ps->key.kind, shstats), + pgstat_get_entry_len(ps->key.kind)); + } + dshash_seq_term(&hstat); + + /* + * No more output to be done. Close the temp file and replace the old + * pgstat.stat with it. The ferror() check replaces testing for error + * after each individual fputc or fwrite (in write_chunk()) above. + */ + fputc('E', fpout); + + if (ferror(fpout)) + { + ereport(LOG, + (errcode_for_file_access(), + errmsg("could not write temporary statistics file \"%s\": %m", + tmpfile))); + FreeFile(fpout); + unlink(tmpfile); + } + else if (FreeFile(fpout) < 0) + { + ereport(LOG, + (errcode_for_file_access(), + errmsg("could not close temporary statistics file \"%s\": %m", + tmpfile))); + unlink(tmpfile); + } + else if (rename(tmpfile, statfile) < 0) + { + ereport(LOG, + (errcode_for_file_access(), + errmsg("could not rename temporary statistics file \"%s\" to \"%s\": %m", + tmpfile, statfile))); + unlink(tmpfile); + } +} + +/* helpers for pgstat_read_statsfile() */ +static bool +read_chunk(FILE *fpin, void *ptr, size_t len) +{ + return fread(ptr, 1, len, fpin) == len; +} + +#define read_chunk_s(fpin, ptr) read_chunk(fpin, ptr, sizeof(*ptr)) + +/* + * Reads in existing statistics file into the shared stats hash. + * + * This function is called in the only process that is accessing the shared + * stats so locking is not required. + */ +static void +pgstat_read_statsfile(void) +{ + FILE *fpin; + int32 format_id; + bool found; + const char *statfile = PGSTAT_STAT_PERMANENT_FILENAME; + PgStat_ShmemControl *shmem = pgStatLocal.shmem; + + /* shouldn't be called from postmaster */ + Assert(IsUnderPostmaster || !IsPostmasterEnvironment); + + elog(DEBUG2, "reading stats file \"%s\"", statfile); + + /* + * Try to open the stats file. If it doesn't exist, the backends simply + * returns zero for anything and statistics simply starts from scratch + * with empty counters. + * + * ENOENT is a possibility if stats collection was previously disabled or + * has not yet written the stats file for the first time. Any other + * failure condition is suspicious. + */ + if ((fpin = AllocateFile(statfile, PG_BINARY_R)) == NULL) + { + if (errno != ENOENT) + ereport(LOG, + (errcode_for_file_access(), + errmsg("could not open statistics file \"%s\": %m", + statfile))); + pgstat_reset_after_failure(); + return; + } + + /* + * Verify it's of the expected format. + */ + if (!read_chunk_s(fpin, &format_id) || + format_id != PGSTAT_FILE_FORMAT_ID) + goto error; + + /* + * XXX: The following could now be generalized to just iterate over + * pgstat_kind_infos instead of knowing about the different kinds of + * stats. + */ + + /* + * Read archiver stats struct + */ + if (!read_chunk_s(fpin, &shmem->archiver.stats)) + goto error; + + /* + * Read bgwriter stats struct + */ + if (!read_chunk_s(fpin, &shmem->bgwriter.stats)) + goto error; + + /* + * Read checkpointer stats struct + */ + if (!read_chunk_s(fpin, &shmem->checkpointer.stats)) + goto error; + + /* + * Read IO stats struct + */ + if (!read_chunk_s(fpin, &shmem->io.stats)) + goto error; + + /* + * Read SLRU stats struct + */ + if (!read_chunk_s(fpin, &shmem->slru.stats)) + goto error; + + /* + * Read WAL stats struct + */ + if (!read_chunk_s(fpin, &shmem->wal.stats)) + goto error; + + /* + * We found an existing statistics file. Read it and put all the hash + * table entries into place. + */ + for (;;) + { + int t = fgetc(fpin); + + switch (t) + { + case 'S': + case 'N': + { + PgStat_HashKey key; + PgStatShared_HashEntry *p; + PgStatShared_Common *header; + + CHECK_FOR_INTERRUPTS(); + + if (t == 'S') + { + /* normal stats entry, identified by PgStat_HashKey */ + if (!read_chunk_s(fpin, &key)) + goto error; + + if (!pgstat_is_kind_valid(key.kind)) + goto error; + } + else + { + /* stats entry identified by name on disk (e.g. slots) */ + const PgStat_KindInfo *kind_info = NULL; + PgStat_Kind kind; + NameData name; + + if (!read_chunk_s(fpin, &kind)) + goto error; + if (!read_chunk_s(fpin, &name)) + goto error; + if (!pgstat_is_kind_valid(kind)) + goto error; + + kind_info = pgstat_get_kind_info(kind); + + if (!kind_info->from_serialized_name) + goto error; + + if (!kind_info->from_serialized_name(&name, &key)) + { + /* skip over data for entry we don't care about */ + if (fseek(fpin, pgstat_get_entry_len(kind), SEEK_CUR) != 0) + goto error; + + continue; + } + + Assert(key.kind == kind); + } + + /* + * This intentionally doesn't use pgstat_get_entry_ref() - + * putting all stats into checkpointer's + * pgStatEntryRefHash would be wasted effort and memory. + */ + p = dshash_find_or_insert(pgStatLocal.shared_hash, &key, &found); + + /* don't allow duplicate entries */ + if (found) + { + dshash_release_lock(pgStatLocal.shared_hash, p); + elog(WARNING, "found duplicate stats entry %d/%u/%u", + key.kind, key.dboid, key.objoid); + goto error; + } + + header = pgstat_init_entry(key.kind, p); + dshash_release_lock(pgStatLocal.shared_hash, p); + + if (!read_chunk(fpin, + pgstat_get_entry_data(key.kind, header), + pgstat_get_entry_len(key.kind))) + goto error; + + break; + } + case 'E': + /* check that 'E' actually signals end of file */ + if (fgetc(fpin) != EOF) + goto error; + + goto done; + + default: + goto error; + } + } + +done: + FreeFile(fpin); + + elog(DEBUG2, "removing permanent stats file \"%s\"", statfile); + unlink(statfile); + + return; + +error: + ereport(LOG, + (errmsg("corrupted statistics file \"%s\"", statfile))); + + pgstat_reset_after_failure(); + + goto done; +} + +/* + * Helper to reset / drop stats after a crash or after restoring stats from + * disk failed, potentially after already loading parts. + */ +static void +pgstat_reset_after_failure(void) +{ + TimestampTz ts = GetCurrentTimestamp(); + + /* reset fixed-numbered stats */ + for (int kind = PGSTAT_KIND_FIRST_VALID; kind <= PGSTAT_KIND_LAST; kind++) + { + const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind); + + if (!kind_info->fixed_amount) + continue; + + kind_info->reset_all_cb(ts); + } + + /* and drop variable-numbered ones */ + pgstat_drop_all_entries(); +} + +/* + * GUC assign_hook for stats_fetch_consistency. + */ +void +assign_stats_fetch_consistency(int newval, void *extra) +{ + /* + * Changing this value in a transaction may cause snapshot state + * inconsistencies, so force a clear of the current snapshot on the next + * snapshot build attempt. + */ + if (pgstat_fetch_consistency != newval) + force_stats_snapshot_clear = true; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/activity/pgstat_archiver.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/activity/pgstat_archiver.c new file mode 100644 index 00000000000..f6af36e60cd --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/activity/pgstat_archiver.c @@ -0,0 +1,111 @@ +/* ------------------------------------------------------------------------- + * + * pgstat_archiver.c + * Implementation of archiver statistics. + * + * This file contains the implementation of archiver statistics. It is kept + * separate from pgstat.c to enforce the line between the statistics access / + * storage implementation and the details about individual types of + * statistics. + * + * Copyright (c) 2001-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/utils/activity/pgstat_archiver.c + * ------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "utils/pgstat_internal.h" +#include "utils/timestamp.h" + + +/* + * Report archiver statistics + */ +void +pgstat_report_archiver(const char *xlog, bool failed) +{ + PgStatShared_Archiver *stats_shmem = &pgStatLocal.shmem->archiver; + TimestampTz now = GetCurrentTimestamp(); + + pgstat_begin_changecount_write(&stats_shmem->changecount); + + if (failed) + { + ++stats_shmem->stats.failed_count; + memcpy(&stats_shmem->stats.last_failed_wal, xlog, + sizeof(stats_shmem->stats.last_failed_wal)); + stats_shmem->stats.last_failed_timestamp = now; + } + else + { + ++stats_shmem->stats.archived_count; + memcpy(&stats_shmem->stats.last_archived_wal, xlog, + sizeof(stats_shmem->stats.last_archived_wal)); + stats_shmem->stats.last_archived_timestamp = now; + } + + pgstat_end_changecount_write(&stats_shmem->changecount); +} + +/* + * Support function for the SQL-callable pgstat* functions. Returns + * a pointer to the archiver statistics struct. + */ +PgStat_ArchiverStats * +pgstat_fetch_stat_archiver(void) +{ + pgstat_snapshot_fixed(PGSTAT_KIND_ARCHIVER); + + return &pgStatLocal.snapshot.archiver; +} + +void +pgstat_archiver_reset_all_cb(TimestampTz ts) +{ + PgStatShared_Archiver *stats_shmem = &pgStatLocal.shmem->archiver; + + /* see explanation above PgStatShared_Archiver for the reset protocol */ + LWLockAcquire(&stats_shmem->lock, LW_EXCLUSIVE); + pgstat_copy_changecounted_stats(&stats_shmem->reset_offset, + &stats_shmem->stats, + sizeof(stats_shmem->stats), + &stats_shmem->changecount); + stats_shmem->stats.stat_reset_timestamp = ts; + LWLockRelease(&stats_shmem->lock); +} + +void +pgstat_archiver_snapshot_cb(void) +{ + PgStatShared_Archiver *stats_shmem = &pgStatLocal.shmem->archiver; + PgStat_ArchiverStats *stat_snap = &pgStatLocal.snapshot.archiver; + PgStat_ArchiverStats *reset_offset = &stats_shmem->reset_offset; + PgStat_ArchiverStats reset; + + pgstat_copy_changecounted_stats(stat_snap, + &stats_shmem->stats, + sizeof(stats_shmem->stats), + &stats_shmem->changecount); + + LWLockAcquire(&stats_shmem->lock, LW_SHARED); + memcpy(&reset, reset_offset, sizeof(stats_shmem->stats)); + LWLockRelease(&stats_shmem->lock); + + /* compensate by reset offsets */ + if (stat_snap->archived_count == reset.archived_count) + { + stat_snap->last_archived_wal[0] = 0; + stat_snap->last_archived_timestamp = 0; + } + stat_snap->archived_count -= reset.archived_count; + + if (stat_snap->failed_count == reset.failed_count) + { + stat_snap->last_failed_wal[0] = 0; + stat_snap->last_failed_timestamp = 0; + } + stat_snap->failed_count -= reset.failed_count; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/activity/pgstat_bgwriter.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/activity/pgstat_bgwriter.c new file mode 100644 index 00000000000..ca31485cb0a --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/activity/pgstat_bgwriter.c @@ -0,0 +1,115 @@ +/* ------------------------------------------------------------------------- + * + * pgstat_bgwriter.c + * Implementation of bgwriter statistics. + * + * This file contains the implementation of bgwriter statistics. It is kept + * separate from pgstat.c to enforce the line between the statistics access / + * storage implementation and the details about individual types of + * statistics. + * + * Copyright (c) 2001-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/utils/activity/pgstat_bgwriter.c + * ------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "utils/pgstat_internal.h" + + +__thread PgStat_BgWriterStats PendingBgWriterStats = {0}; + + +/* + * Report bgwriter and IO statistics + */ +void +pgstat_report_bgwriter(void) +{ + PgStatShared_BgWriter *stats_shmem = &pgStatLocal.shmem->bgwriter; + static const PgStat_BgWriterStats all_zeroes; + + Assert(!pgStatLocal.shmem->is_shutdown); + pgstat_assert_is_up(); + + /* + * This function can be called even if nothing at all has happened. In + * this case, avoid unnecessarily modifying the stats entry. + */ + if (memcmp(&PendingBgWriterStats, &all_zeroes, sizeof(all_zeroes)) == 0) + return; + + pgstat_begin_changecount_write(&stats_shmem->changecount); + +#define BGWRITER_ACC(fld) stats_shmem->stats.fld += PendingBgWriterStats.fld + BGWRITER_ACC(buf_written_clean); + BGWRITER_ACC(maxwritten_clean); + BGWRITER_ACC(buf_alloc); +#undef BGWRITER_ACC + + pgstat_end_changecount_write(&stats_shmem->changecount); + + /* + * Clear out the statistics buffer, so it can be re-used. + */ + MemSet(&PendingBgWriterStats, 0, sizeof(PendingBgWriterStats)); + + /* + * Report IO statistics + */ + pgstat_flush_io(false); +} + +/* + * Support function for the SQL-callable pgstat* functions. Returns + * a pointer to the bgwriter statistics struct. + */ +PgStat_BgWriterStats * +pgstat_fetch_stat_bgwriter(void) +{ + pgstat_snapshot_fixed(PGSTAT_KIND_BGWRITER); + + return &pgStatLocal.snapshot.bgwriter; +} + +void +pgstat_bgwriter_reset_all_cb(TimestampTz ts) +{ + PgStatShared_BgWriter *stats_shmem = &pgStatLocal.shmem->bgwriter; + + /* see explanation above PgStatShared_BgWriter for the reset protocol */ + LWLockAcquire(&stats_shmem->lock, LW_EXCLUSIVE); + pgstat_copy_changecounted_stats(&stats_shmem->reset_offset, + &stats_shmem->stats, + sizeof(stats_shmem->stats), + &stats_shmem->changecount); + stats_shmem->stats.stat_reset_timestamp = ts; + LWLockRelease(&stats_shmem->lock); +} + +void +pgstat_bgwriter_snapshot_cb(void) +{ + PgStatShared_BgWriter *stats_shmem = &pgStatLocal.shmem->bgwriter; + PgStat_BgWriterStats *reset_offset = &stats_shmem->reset_offset; + PgStat_BgWriterStats reset; + + pgstat_copy_changecounted_stats(&pgStatLocal.snapshot.bgwriter, + &stats_shmem->stats, + sizeof(stats_shmem->stats), + &stats_shmem->changecount); + + LWLockAcquire(&stats_shmem->lock, LW_SHARED); + memcpy(&reset, reset_offset, sizeof(stats_shmem->stats)); + LWLockRelease(&stats_shmem->lock); + + /* compensate by reset offsets */ +#define BGWRITER_COMP(fld) pgStatLocal.snapshot.bgwriter.fld -= reset.fld; + BGWRITER_COMP(buf_written_clean); + BGWRITER_COMP(maxwritten_clean); + BGWRITER_COMP(buf_alloc); +#undef BGWRITER_COMP +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/activity/pgstat_checkpointer.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/activity/pgstat_checkpointer.c new file mode 100644 index 00000000000..b246f17e115 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/activity/pgstat_checkpointer.c @@ -0,0 +1,126 @@ +/* ------------------------------------------------------------------------- + * + * pgstat_checkpointer.c + * Implementation of checkpoint statistics. + * + * This file contains the implementation of checkpoint statistics. It is kept + * separate from pgstat.c to enforce the line between the statistics access / + * storage implementation and the details about individual types of + * statistics. + * + * Copyright (c) 2001-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/utils/activity/pgstat_checkpointer.c + * ------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "utils/pgstat_internal.h" + + +__thread PgStat_CheckpointerStats PendingCheckpointerStats = {0}; + + +/* + * Report checkpointer and IO statistics + */ +void +pgstat_report_checkpointer(void) +{ + /* We assume this initializes to zeroes */ + static const PgStat_CheckpointerStats all_zeroes; + PgStatShared_Checkpointer *stats_shmem = &pgStatLocal.shmem->checkpointer; + + Assert(!pgStatLocal.shmem->is_shutdown); + pgstat_assert_is_up(); + + /* + * This function can be called even if nothing at all has happened. In + * this case, avoid unnecessarily modifying the stats entry. + */ + if (memcmp(&PendingCheckpointerStats, &all_zeroes, + sizeof(all_zeroes)) == 0) + return; + + pgstat_begin_changecount_write(&stats_shmem->changecount); + +#define CHECKPOINTER_ACC(fld) stats_shmem->stats.fld += PendingCheckpointerStats.fld + CHECKPOINTER_ACC(timed_checkpoints); + CHECKPOINTER_ACC(requested_checkpoints); + CHECKPOINTER_ACC(checkpoint_write_time); + CHECKPOINTER_ACC(checkpoint_sync_time); + CHECKPOINTER_ACC(buf_written_checkpoints); + CHECKPOINTER_ACC(buf_written_backend); + CHECKPOINTER_ACC(buf_fsync_backend); +#undef CHECKPOINTER_ACC + + pgstat_end_changecount_write(&stats_shmem->changecount); + + /* + * Clear out the statistics buffer, so it can be re-used. + */ + MemSet(&PendingCheckpointerStats, 0, sizeof(PendingCheckpointerStats)); + + /* + * Report IO statistics + */ + pgstat_flush_io(false); +} + +/* + * pgstat_fetch_stat_checkpointer() - + * + * Support function for the SQL-callable pgstat* functions. Returns + * a pointer to the checkpointer statistics struct. + */ +PgStat_CheckpointerStats * +pgstat_fetch_stat_checkpointer(void) +{ + pgstat_snapshot_fixed(PGSTAT_KIND_CHECKPOINTER); + + return &pgStatLocal.snapshot.checkpointer; +} + +void +pgstat_checkpointer_reset_all_cb(TimestampTz ts) +{ + PgStatShared_Checkpointer *stats_shmem = &pgStatLocal.shmem->checkpointer; + + /* see explanation above PgStatShared_Checkpointer for the reset protocol */ + LWLockAcquire(&stats_shmem->lock, LW_EXCLUSIVE); + pgstat_copy_changecounted_stats(&stats_shmem->reset_offset, + &stats_shmem->stats, + sizeof(stats_shmem->stats), + &stats_shmem->changecount); + LWLockRelease(&stats_shmem->lock); +} + +void +pgstat_checkpointer_snapshot_cb(void) +{ + PgStatShared_Checkpointer *stats_shmem = &pgStatLocal.shmem->checkpointer; + PgStat_CheckpointerStats *reset_offset = &stats_shmem->reset_offset; + PgStat_CheckpointerStats reset; + + pgstat_copy_changecounted_stats(&pgStatLocal.snapshot.checkpointer, + &stats_shmem->stats, + sizeof(stats_shmem->stats), + &stats_shmem->changecount); + + LWLockAcquire(&stats_shmem->lock, LW_SHARED); + memcpy(&reset, reset_offset, sizeof(stats_shmem->stats)); + LWLockRelease(&stats_shmem->lock); + + /* compensate by reset offsets */ +#define CHECKPOINTER_COMP(fld) pgStatLocal.snapshot.checkpointer.fld -= reset.fld; + CHECKPOINTER_COMP(timed_checkpoints); + CHECKPOINTER_COMP(requested_checkpoints); + CHECKPOINTER_COMP(checkpoint_write_time); + CHECKPOINTER_COMP(checkpoint_sync_time); + CHECKPOINTER_COMP(buf_written_checkpoints); + CHECKPOINTER_COMP(buf_written_backend); + CHECKPOINTER_COMP(buf_fsync_backend); +#undef CHECKPOINTER_COMP +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/activity/pgstat_database.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/activity/pgstat_database.c new file mode 100644 index 00000000000..bd2765d86bd --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/activity/pgstat_database.c @@ -0,0 +1,441 @@ +/* ------------------------------------------------------------------------- + * + * pgstat_database.c + * Implementation of database statistics. + * + * This file contains the implementation of database statistics. It is kept + * separate from pgstat.c to enforce the line between the statistics access / + * storage implementation and the details about individual types of + * statistics. + * + * Copyright (c) 2001-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/utils/activity/pgstat_database.c + * ------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "utils/pgstat_internal.h" +#include "utils/timestamp.h" +#include "storage/procsignal.h" + + +static bool pgstat_should_report_connstat(void); + + +__thread PgStat_Counter pgStatBlockReadTime = 0; +__thread PgStat_Counter pgStatBlockWriteTime = 0; +__thread PgStat_Counter pgStatActiveTime = 0; +__thread PgStat_Counter pgStatTransactionIdleTime = 0; +__thread SessionEndType pgStatSessionEndCause = DISCONNECT_NORMAL; + + +static __thread int pgStatXactCommit = 0; +static __thread int pgStatXactRollback = 0; +static __thread PgStat_Counter pgLastSessionReportTime = 0; + + +/* + * Remove entry for the database being dropped. + */ +void +pgstat_drop_database(Oid databaseid) +{ + pgstat_drop_transactional(PGSTAT_KIND_DATABASE, databaseid, InvalidOid); +} + +/* + * Called from autovacuum.c to report startup of an autovacuum process. + * We are called before InitPostgres is done, so can't rely on MyDatabaseId; + * the db OID must be passed in, instead. + */ +void +pgstat_report_autovac(Oid dboid) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Database *dbentry; + + /* can't get here in single user mode */ + Assert(IsUnderPostmaster); + + /* + * End-of-vacuum is reported instantly. Report the start the same way for + * consistency. Vacuum doesn't run frequently and is a long-lasting + * operation so it doesn't matter if we get blocked here a little. + */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_DATABASE, + dboid, InvalidOid, false); + + dbentry = (PgStatShared_Database *) entry_ref->shared_stats; + dbentry->stats.last_autovac_time = GetCurrentTimestamp(); + + pgstat_unlock_entry(entry_ref); +} + +/* + * Report a Hot Standby recovery conflict. + */ +void +pgstat_report_recovery_conflict(int reason) +{ + PgStat_StatDBEntry *dbentry; + + Assert(IsUnderPostmaster); + if (!pgstat_track_counts) + return; + + dbentry = pgstat_prep_database_pending(MyDatabaseId); + + switch (reason) + { + case PROCSIG_RECOVERY_CONFLICT_DATABASE: + + /* + * Since we drop the information about the database as soon as it + * replicates, there is no point in counting these conflicts. + */ + break; + case PROCSIG_RECOVERY_CONFLICT_TABLESPACE: + dbentry->conflict_tablespace++; + break; + case PROCSIG_RECOVERY_CONFLICT_LOCK: + dbentry->conflict_lock++; + break; + case PROCSIG_RECOVERY_CONFLICT_SNAPSHOT: + dbentry->conflict_snapshot++; + break; + case PROCSIG_RECOVERY_CONFLICT_BUFFERPIN: + dbentry->conflict_bufferpin++; + break; + case PROCSIG_RECOVERY_CONFLICT_LOGICALSLOT: + dbentry->conflict_logicalslot++; + break; + case PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK: + dbentry->conflict_startup_deadlock++; + break; + } +} + +/* + * Report a detected deadlock. + */ +void +pgstat_report_deadlock(void) +{ + PgStat_StatDBEntry *dbent; + + if (!pgstat_track_counts) + return; + + dbent = pgstat_prep_database_pending(MyDatabaseId); + dbent->deadlocks++; +} + +/* + * Report one or more checksum failures. + */ +void +pgstat_report_checksum_failures_in_db(Oid dboid, int failurecount) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Database *sharedent; + + if (!pgstat_track_counts) + return; + + /* + * Update the shared stats directly - checksum failures should never be + * common enough for that to be a problem. + */ + entry_ref = + pgstat_get_entry_ref_locked(PGSTAT_KIND_DATABASE, dboid, InvalidOid, false); + + sharedent = (PgStatShared_Database *) entry_ref->shared_stats; + sharedent->stats.checksum_failures += failurecount; + sharedent->stats.last_checksum_failure = GetCurrentTimestamp(); + + pgstat_unlock_entry(entry_ref); +} + +/* + * Report one checksum failure in the current database. + */ +void +pgstat_report_checksum_failure(void) +{ + pgstat_report_checksum_failures_in_db(MyDatabaseId, 1); +} + +/* + * Report creation of temporary file. + */ +void +pgstat_report_tempfile(size_t filesize) +{ + PgStat_StatDBEntry *dbent; + + if (!pgstat_track_counts) + return; + + dbent = pgstat_prep_database_pending(MyDatabaseId); + dbent->temp_bytes += filesize; + dbent->temp_files++; +} + +/* + * Notify stats system of a new connection. + */ +void +pgstat_report_connect(Oid dboid) +{ + PgStat_StatDBEntry *dbentry; + + if (!pgstat_should_report_connstat()) + return; + + pgLastSessionReportTime = MyStartTimestamp; + + dbentry = pgstat_prep_database_pending(MyDatabaseId); + dbentry->sessions++; +} + +/* + * Notify the stats system of a disconnect. + */ +void +pgstat_report_disconnect(Oid dboid) +{ + PgStat_StatDBEntry *dbentry; + + if (!pgstat_should_report_connstat()) + return; + + dbentry = pgstat_prep_database_pending(MyDatabaseId); + + switch (pgStatSessionEndCause) + { + case DISCONNECT_NOT_YET: + case DISCONNECT_NORMAL: + /* we don't collect these */ + break; + case DISCONNECT_CLIENT_EOF: + dbentry->sessions_abandoned++; + break; + case DISCONNECT_FATAL: + dbentry->sessions_fatal++; + break; + case DISCONNECT_KILLED: + dbentry->sessions_killed++; + break; + } +} + +/* + * Support function for the SQL-callable pgstat* functions. Returns + * the collected statistics for one database or NULL. NULL doesn't mean + * that the database doesn't exist, just that there are no statistics, so the + * caller is better off to report ZERO instead. + */ +PgStat_StatDBEntry * +pgstat_fetch_stat_dbentry(Oid dboid) +{ + return (PgStat_StatDBEntry *) + pgstat_fetch_entry(PGSTAT_KIND_DATABASE, dboid, InvalidOid); +} + +void +AtEOXact_PgStat_Database(bool isCommit, bool parallel) +{ + /* Don't count parallel worker transaction stats */ + if (!parallel) + { + /* + * Count transaction commit or abort. (We use counters, not just + * bools, in case the reporting message isn't sent right away.) + */ + if (isCommit) + pgStatXactCommit++; + else + pgStatXactRollback++; + } +} + +/* + * Subroutine for pgstat_report_stat(): Handle xact commit/rollback and I/O + * timings. + */ +void +pgstat_update_dbstats(TimestampTz ts) +{ + PgStat_StatDBEntry *dbentry; + + /* + * If not connected to a database yet, don't attribute time to "shared + * state" (InvalidOid is used to track stats for shared relations, etc.). + */ + if (!OidIsValid(MyDatabaseId)) + return; + + dbentry = pgstat_prep_database_pending(MyDatabaseId); + + /* + * Accumulate xact commit/rollback and I/O timings to stats entry of the + * current database. + */ + dbentry->xact_commit += pgStatXactCommit; + dbentry->xact_rollback += pgStatXactRollback; + dbentry->blk_read_time += pgStatBlockReadTime; + dbentry->blk_write_time += pgStatBlockWriteTime; + + if (pgstat_should_report_connstat()) + { + long secs; + int usecs; + + /* + * pgLastSessionReportTime is initialized to MyStartTimestamp by + * pgstat_report_connect(). + */ + TimestampDifference(pgLastSessionReportTime, ts, &secs, &usecs); + pgLastSessionReportTime = ts; + dbentry->session_time += (PgStat_Counter) secs * 1000000 + usecs; + dbentry->active_time += pgStatActiveTime; + dbentry->idle_in_transaction_time += pgStatTransactionIdleTime; + } + + pgStatXactCommit = 0; + pgStatXactRollback = 0; + pgStatBlockReadTime = 0; + pgStatBlockWriteTime = 0; + pgStatActiveTime = 0; + pgStatTransactionIdleTime = 0; +} + +/* + * We report session statistics only for normal backend processes. Parallel + * workers run in parallel, so they don't contribute to session times, even + * though they use CPU time. Walsender processes could be considered here, + * but they have different session characteristics from normal backends (for + * example, they are always "active"), so they would skew session statistics. + */ +static bool +pgstat_should_report_connstat(void) +{ + return MyBackendType == B_BACKEND; +} + +/* + * Find or create a local PgStat_StatDBEntry entry for dboid. + */ +PgStat_StatDBEntry * +pgstat_prep_database_pending(Oid dboid) +{ + PgStat_EntryRef *entry_ref; + + /* + * This should not report stats on database objects before having + * connected to a database. + */ + Assert(!OidIsValid(dboid) || OidIsValid(MyDatabaseId)); + + entry_ref = pgstat_prep_pending_entry(PGSTAT_KIND_DATABASE, dboid, InvalidOid, + NULL); + + return entry_ref->pending; +} + +/* + * Reset the database's reset timestamp, without resetting the contents of the + * database stats. + */ +void +pgstat_reset_database_timestamp(Oid dboid, TimestampTz ts) +{ + PgStat_EntryRef *dbref; + PgStatShared_Database *dbentry; + + dbref = pgstat_get_entry_ref_locked(PGSTAT_KIND_DATABASE, MyDatabaseId, InvalidOid, + false); + + dbentry = (PgStatShared_Database *) dbref->shared_stats; + dbentry->stats.stat_reset_timestamp = ts; + + pgstat_unlock_entry(dbref); +} + +/* + * Flush out pending stats for the entry + * + * If nowait is true, this function returns false if lock could not + * immediately acquired, otherwise true is returned. + */ +bool +pgstat_database_flush_cb(PgStat_EntryRef *entry_ref, bool nowait) +{ + PgStatShared_Database *sharedent; + PgStat_StatDBEntry *pendingent; + + pendingent = (PgStat_StatDBEntry *) entry_ref->pending; + sharedent = (PgStatShared_Database *) entry_ref->shared_stats; + + if (!pgstat_lock_entry(entry_ref, nowait)) + return false; + +#define PGSTAT_ACCUM_DBCOUNT(item) \ + (sharedent)->stats.item += (pendingent)->item + + PGSTAT_ACCUM_DBCOUNT(xact_commit); + PGSTAT_ACCUM_DBCOUNT(xact_rollback); + PGSTAT_ACCUM_DBCOUNT(blocks_fetched); + PGSTAT_ACCUM_DBCOUNT(blocks_hit); + + PGSTAT_ACCUM_DBCOUNT(tuples_returned); + PGSTAT_ACCUM_DBCOUNT(tuples_fetched); + PGSTAT_ACCUM_DBCOUNT(tuples_inserted); + PGSTAT_ACCUM_DBCOUNT(tuples_updated); + PGSTAT_ACCUM_DBCOUNT(tuples_deleted); + + /* last_autovac_time is reported immediately */ + Assert(pendingent->last_autovac_time == 0); + + PGSTAT_ACCUM_DBCOUNT(conflict_tablespace); + PGSTAT_ACCUM_DBCOUNT(conflict_lock); + PGSTAT_ACCUM_DBCOUNT(conflict_snapshot); + PGSTAT_ACCUM_DBCOUNT(conflict_logicalslot); + PGSTAT_ACCUM_DBCOUNT(conflict_bufferpin); + PGSTAT_ACCUM_DBCOUNT(conflict_startup_deadlock); + + PGSTAT_ACCUM_DBCOUNT(temp_bytes); + PGSTAT_ACCUM_DBCOUNT(temp_files); + PGSTAT_ACCUM_DBCOUNT(deadlocks); + + /* checksum failures are reported immediately */ + Assert(pendingent->checksum_failures == 0); + Assert(pendingent->last_checksum_failure == 0); + + PGSTAT_ACCUM_DBCOUNT(blk_read_time); + PGSTAT_ACCUM_DBCOUNT(blk_write_time); + + PGSTAT_ACCUM_DBCOUNT(sessions); + PGSTAT_ACCUM_DBCOUNT(session_time); + PGSTAT_ACCUM_DBCOUNT(active_time); + PGSTAT_ACCUM_DBCOUNT(idle_in_transaction_time); + PGSTAT_ACCUM_DBCOUNT(sessions_abandoned); + PGSTAT_ACCUM_DBCOUNT(sessions_fatal); + PGSTAT_ACCUM_DBCOUNT(sessions_killed); +#undef PGSTAT_ACCUM_DBCOUNT + + pgstat_unlock_entry(entry_ref); + + memset(pendingent, 0, sizeof(*pendingent)); + + return true; +} + +void +pgstat_database_reset_timestamp_cb(PgStatShared_Common *header, TimestampTz ts) +{ + ((PgStatShared_Database *) header)->stats.stat_reset_timestamp = ts; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/activity/pgstat_function.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/activity/pgstat_function.c new file mode 100644 index 00000000000..3c99ff2a6c6 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/activity/pgstat_function.c @@ -0,0 +1,243 @@ +/* ------------------------------------------------------------------------- + * + * pgstat_function.c + * Implementation of function statistics. + * + * This file contains the implementation of function statistics. It is kept + * separate from pgstat.c to enforce the line between the statistics access / + * storage implementation and the details about individual types of + * statistics. + * + * Copyright (c) 2001-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/utils/activity/pgstat_function.c + * ------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "fmgr.h" +#include "utils/inval.h" +#include "utils/pgstat_internal.h" +#include "utils/syscache.h" + + +/* ---------- + * GUC parameters + * ---------- + */ +__thread int pgstat_track_functions = TRACK_FUNC_OFF; + + +/* + * Total time charged to functions so far in the current backend. + * We use this to help separate "self" and "other" time charges. + * (We assume this initializes to zero.) + */ +static __thread instr_time total_func_time; + + +/* + * Ensure that stats are dropped if transaction aborts. + */ +void +pgstat_create_function(Oid proid) +{ + pgstat_create_transactional(PGSTAT_KIND_FUNCTION, + MyDatabaseId, + proid); +} + +/* + * Ensure that stats are dropped if transaction commits. + * + * NB: This is only reliable because pgstat_init_function_usage() does some + * extra work. If other places start emitting function stats they likely need + * similar logic. + */ +void +pgstat_drop_function(Oid proid) +{ + pgstat_drop_transactional(PGSTAT_KIND_FUNCTION, + MyDatabaseId, + proid); +} + +/* + * Initialize function call usage data. + * Called by the executor before invoking a function. + */ +void +pgstat_init_function_usage(FunctionCallInfo fcinfo, + PgStat_FunctionCallUsage *fcu) +{ + PgStat_EntryRef *entry_ref; + PgStat_FunctionCounts *pending; + bool created_entry; + + if (pgstat_track_functions <= fcinfo->flinfo->fn_stats) + { + /* stats not wanted */ + fcu->fs = NULL; + return; + } + + entry_ref = pgstat_prep_pending_entry(PGSTAT_KIND_FUNCTION, + MyDatabaseId, + fcinfo->flinfo->fn_oid, + &created_entry); + + /* + * If no shared entry already exists, check if the function has been + * deleted concurrently. This can go unnoticed until here because + * executing a statement that just calls a function, does not trigger + * cache invalidation processing. The reason we care about this case is + * that otherwise we could create a new stats entry for an already dropped + * function (for relations etc this is not possible because emitting stats + * requires a lock for the relation to already have been acquired). + * + * It's somewhat ugly to have a behavioral difference based on + * track_functions being enabled/disabled. But it seems acceptable, given + * that there's already behavioral differences depending on whether the + * function is the caches etc. + * + * For correctness it'd be sufficient to set ->dropped to true. However, + * the accepted invalidation will commonly cause "low level" failures in + * PL code, with an OID in the error message. Making this harder to + * test... + */ + if (created_entry) + { + AcceptInvalidationMessages(); + if (!SearchSysCacheExists1(PROCOID, ObjectIdGetDatum(fcinfo->flinfo->fn_oid))) + { + pgstat_drop_entry(PGSTAT_KIND_FUNCTION, MyDatabaseId, + fcinfo->flinfo->fn_oid); + ereport(ERROR, errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("function call to dropped function")); + } + } + + pending = entry_ref->pending; + + fcu->fs = pending; + + /* save stats for this function, later used to compensate for recursion */ + fcu->save_f_total_time = pending->total_time; + + /* save current backend-wide total time */ + fcu->save_total = total_func_time; + + /* get clock time as of function start */ + INSTR_TIME_SET_CURRENT(fcu->start); +} + +/* + * Calculate function call usage and update stat counters. + * Called by the executor after invoking a function. + * + * In the case of a set-returning function that runs in value-per-call mode, + * we will see multiple pgstat_init_function_usage/pgstat_end_function_usage + * calls for what the user considers a single call of the function. The + * finalize flag should be TRUE on the last call. + */ +void +pgstat_end_function_usage(PgStat_FunctionCallUsage *fcu, bool finalize) +{ + PgStat_FunctionCounts *fs = fcu->fs; + instr_time total; + instr_time others; + instr_time self; + + /* stats not wanted? */ + if (fs == NULL) + return; + + /* total elapsed time in this function call */ + INSTR_TIME_SET_CURRENT(total); + INSTR_TIME_SUBTRACT(total, fcu->start); + + /* self usage: elapsed minus anything already charged to other calls */ + others = total_func_time; + INSTR_TIME_SUBTRACT(others, fcu->save_total); + self = total; + INSTR_TIME_SUBTRACT(self, others); + + /* update backend-wide total time */ + INSTR_TIME_ADD(total_func_time, self); + + /* + * Compute the new total_time as the total elapsed time added to the + * pre-call value of total_time. This is necessary to avoid + * double-counting any time taken by recursive calls of myself. (We do + * not need any similar kluge for self time, since that already excludes + * any recursive calls.) + */ + INSTR_TIME_ADD(total, fcu->save_f_total_time); + + /* update counters in function stats table */ + if (finalize) + fs->numcalls++; + fs->total_time = total; + INSTR_TIME_ADD(fs->self_time, self); +} + +/* + * Flush out pending stats for the entry + * + * If nowait is true, this function returns false if lock could not + * immediately acquired, otherwise true is returned. + */ +bool +pgstat_function_flush_cb(PgStat_EntryRef *entry_ref, bool nowait) +{ + PgStat_FunctionCounts *localent; + PgStatShared_Function *shfuncent; + + localent = (PgStat_FunctionCounts *) entry_ref->pending; + shfuncent = (PgStatShared_Function *) entry_ref->shared_stats; + + /* localent always has non-zero content */ + + if (!pgstat_lock_entry(entry_ref, nowait)) + return false; + + shfuncent->stats.numcalls += localent->numcalls; + shfuncent->stats.total_time += + INSTR_TIME_GET_MICROSEC(localent->total_time); + shfuncent->stats.self_time += + INSTR_TIME_GET_MICROSEC(localent->self_time); + + pgstat_unlock_entry(entry_ref); + + return true; +} + +/* + * find any existing PgStat_FunctionCounts entry for specified function + * + * If no entry, return NULL, don't create a new one + */ +PgStat_FunctionCounts * +find_funcstat_entry(Oid func_id) +{ + PgStat_EntryRef *entry_ref; + + entry_ref = pgstat_fetch_pending_entry(PGSTAT_KIND_FUNCTION, MyDatabaseId, func_id); + + if (entry_ref) + return entry_ref->pending; + return NULL; +} + +/* + * Support function for the SQL-callable pgstat* functions. Returns + * the collected statistics for one function or NULL. + */ +PgStat_StatFuncEntry * +pgstat_fetch_stat_funcentry(Oid func_id) +{ + return (PgStat_StatFuncEntry *) + pgstat_fetch_entry(PGSTAT_KIND_FUNCTION, MyDatabaseId, func_id); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/activity/pgstat_io.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/activity/pgstat_io.c new file mode 100644 index 00000000000..f91914bd731 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/activity/pgstat_io.c @@ -0,0 +1,461 @@ +/* ------------------------------------------------------------------------- + * + * pgstat_io.c + * Implementation of IO statistics. + * + * This file contains the implementation of IO statistics. It is kept separate + * from pgstat.c to enforce the line between the statistics access / storage + * implementation and the details about individual types of statistics. + * + * Copyright (c) 2021-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/utils/activity/pgstat_io.c + * ------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "executor/instrument.h" +#include "storage/bufmgr.h" +#include "utils/pgstat_internal.h" + + +typedef struct PgStat_PendingIO +{ + PgStat_Counter counts[IOOBJECT_NUM_TYPES][IOCONTEXT_NUM_TYPES][IOOP_NUM_TYPES]; + instr_time pending_times[IOOBJECT_NUM_TYPES][IOCONTEXT_NUM_TYPES][IOOP_NUM_TYPES]; +} PgStat_PendingIO; + + +static __thread PgStat_PendingIO PendingIOStats; +__thread bool have_iostats = false; + + +/* + * Check that stats have not been counted for any combination of IOObject, + * IOContext, and IOOp which are not tracked for the passed-in BackendType. If + * stats are tracked for this combination and IO times are non-zero, counts + * should be non-zero. + * + * The passed-in PgStat_BktypeIO must contain stats from the BackendType + * specified by the second parameter. Caller is responsible for locking the + * passed-in PgStat_BktypeIO, if needed. + */ +bool +pgstat_bktype_io_stats_valid(PgStat_BktypeIO *backend_io, + BackendType bktype) +{ + for (int io_object = 0; io_object < IOOBJECT_NUM_TYPES; io_object++) + { + for (int io_context = 0; io_context < IOCONTEXT_NUM_TYPES; io_context++) + { + for (int io_op = 0; io_op < IOOP_NUM_TYPES; io_op++) + { + /* we do track it */ + if (pgstat_tracks_io_op(bktype, io_object, io_context, io_op)) + { + /* ensure that if IO times are non-zero, counts are > 0 */ + if (backend_io->times[io_object][io_context][io_op] != 0 && + backend_io->counts[io_object][io_context][io_op] <= 0) + return false; + + continue; + } + + /* we don't track it, and it is not 0 */ + if (backend_io->counts[io_object][io_context][io_op] != 0) + return false; + } + } + } + + return true; +} + +void +pgstat_count_io_op(IOObject io_object, IOContext io_context, IOOp io_op) +{ + pgstat_count_io_op_n(io_object, io_context, io_op, 1); +} + +void +pgstat_count_io_op_n(IOObject io_object, IOContext io_context, IOOp io_op, uint32 cnt) +{ + Assert((unsigned int) io_object < IOOBJECT_NUM_TYPES); + Assert((unsigned int) io_context < IOCONTEXT_NUM_TYPES); + Assert((unsigned int) io_op < IOOP_NUM_TYPES); + Assert(pgstat_tracks_io_op(MyBackendType, io_object, io_context, io_op)); + + PendingIOStats.counts[io_object][io_context][io_op] += cnt; + + have_iostats = true; +} + +instr_time +pgstat_prepare_io_time(void) +{ + instr_time io_start; + + if (track_io_timing) + INSTR_TIME_SET_CURRENT(io_start); + else + INSTR_TIME_SET_ZERO(io_start); + + return io_start; +} + +/* + * Like pgstat_count_io_op_n() except it also accumulates time. + */ +void +pgstat_count_io_op_time(IOObject io_object, IOContext io_context, IOOp io_op, + instr_time start_time, uint32 cnt) +{ + if (track_io_timing) + { + instr_time io_time; + + INSTR_TIME_SET_CURRENT(io_time); + INSTR_TIME_SUBTRACT(io_time, start_time); + + if (io_op == IOOP_WRITE || io_op == IOOP_EXTEND) + { + pgstat_count_buffer_write_time(INSTR_TIME_GET_MICROSEC(io_time)); + if (io_object == IOOBJECT_RELATION) + INSTR_TIME_ADD(pgBufferUsage.blk_write_time, io_time); + } + else if (io_op == IOOP_READ) + { + pgstat_count_buffer_read_time(INSTR_TIME_GET_MICROSEC(io_time)); + if (io_object == IOOBJECT_RELATION) + INSTR_TIME_ADD(pgBufferUsage.blk_read_time, io_time); + } + + INSTR_TIME_ADD(PendingIOStats.pending_times[io_object][io_context][io_op], + io_time); + } + + pgstat_count_io_op_n(io_object, io_context, io_op, cnt); +} + +PgStat_IO * +pgstat_fetch_stat_io(void) +{ + pgstat_snapshot_fixed(PGSTAT_KIND_IO); + + return &pgStatLocal.snapshot.io; +} + +/* + * Flush out locally pending IO statistics + * + * If no stats have been recorded, this function returns false. + * + * If nowait is true, this function returns true if the lock could not be + * acquired. Otherwise, return false. + */ +bool +pgstat_flush_io(bool nowait) +{ + LWLock *bktype_lock; + PgStat_BktypeIO *bktype_shstats; + + if (!have_iostats) + return false; + + bktype_lock = &pgStatLocal.shmem->io.locks[MyBackendType]; + bktype_shstats = + &pgStatLocal.shmem->io.stats.stats[MyBackendType]; + + if (!nowait) + LWLockAcquire(bktype_lock, LW_EXCLUSIVE); + else if (!LWLockConditionalAcquire(bktype_lock, LW_EXCLUSIVE)) + return true; + + for (int io_object = 0; io_object < IOOBJECT_NUM_TYPES; io_object++) + { + for (int io_context = 0; io_context < IOCONTEXT_NUM_TYPES; io_context++) + { + for (int io_op = 0; io_op < IOOP_NUM_TYPES; io_op++) + { + instr_time time; + + bktype_shstats->counts[io_object][io_context][io_op] += + PendingIOStats.counts[io_object][io_context][io_op]; + + time = PendingIOStats.pending_times[io_object][io_context][io_op]; + + bktype_shstats->times[io_object][io_context][io_op] += + INSTR_TIME_GET_MICROSEC(time); + } + } + } + + Assert(pgstat_bktype_io_stats_valid(bktype_shstats, MyBackendType)); + + LWLockRelease(bktype_lock); + + memset(&PendingIOStats, 0, sizeof(PendingIOStats)); + + have_iostats = false; + + return false; +} + +const char * +pgstat_get_io_context_name(IOContext io_context) +{ + switch (io_context) + { + case IOCONTEXT_BULKREAD: + return "bulkread"; + case IOCONTEXT_BULKWRITE: + return "bulkwrite"; + case IOCONTEXT_NORMAL: + return "normal"; + case IOCONTEXT_VACUUM: + return "vacuum"; + } + + elog(ERROR, "unrecognized IOContext value: %d", io_context); + pg_unreachable(); +} + +const char * +pgstat_get_io_object_name(IOObject io_object) +{ + switch (io_object) + { + case IOOBJECT_RELATION: + return "relation"; + case IOOBJECT_TEMP_RELATION: + return "temp relation"; + } + + elog(ERROR, "unrecognized IOObject value: %d", io_object); + pg_unreachable(); +} + +void +pgstat_io_reset_all_cb(TimestampTz ts) +{ + for (int i = 0; i < BACKEND_NUM_TYPES; i++) + { + LWLock *bktype_lock = &pgStatLocal.shmem->io.locks[i]; + PgStat_BktypeIO *bktype_shstats = &pgStatLocal.shmem->io.stats.stats[i]; + + LWLockAcquire(bktype_lock, LW_EXCLUSIVE); + + /* + * Use the lock in the first BackendType's PgStat_BktypeIO to protect + * the reset timestamp as well. + */ + if (i == 0) + pgStatLocal.shmem->io.stats.stat_reset_timestamp = ts; + + memset(bktype_shstats, 0, sizeof(*bktype_shstats)); + LWLockRelease(bktype_lock); + } +} + +void +pgstat_io_snapshot_cb(void) +{ + for (int i = 0; i < BACKEND_NUM_TYPES; i++) + { + LWLock *bktype_lock = &pgStatLocal.shmem->io.locks[i]; + PgStat_BktypeIO *bktype_shstats = &pgStatLocal.shmem->io.stats.stats[i]; + PgStat_BktypeIO *bktype_snap = &pgStatLocal.snapshot.io.stats[i]; + + LWLockAcquire(bktype_lock, LW_SHARED); + + /* + * Use the lock in the first BackendType's PgStat_BktypeIO to protect + * the reset timestamp as well. + */ + if (i == 0) + pgStatLocal.snapshot.io.stat_reset_timestamp = + pgStatLocal.shmem->io.stats.stat_reset_timestamp; + + /* using struct assignment due to better type safety */ + *bktype_snap = *bktype_shstats; + LWLockRelease(bktype_lock); + } +} + +/* +* IO statistics are not collected for all BackendTypes. +* +* The following BackendTypes do not participate in the cumulative stats +* subsystem or do not perform IO on which we currently track: +* - Syslogger because it is not connected to shared memory +* - Archiver because most relevant archiving IO is delegated to a +* specialized command or module +* - WAL Receiver and WAL Writer IO is not tracked in pg_stat_io for now +* +* Function returns true if BackendType participates in the cumulative stats +* subsystem for IO and false if it does not. +* +* When adding a new BackendType, also consider adding relevant restrictions to +* pgstat_tracks_io_object() and pgstat_tracks_io_op(). +*/ +bool +pgstat_tracks_io_bktype(BackendType bktype) +{ + /* + * List every type so that new backend types trigger a warning about + * needing to adjust this switch. + */ + switch (bktype) + { + case B_INVALID: + case B_ARCHIVER: + case B_LOGGER: + case B_WAL_RECEIVER: + case B_WAL_WRITER: + return false; + + case B_AUTOVAC_LAUNCHER: + case B_AUTOVAC_WORKER: + case B_BACKEND: + case B_BG_WORKER: + case B_BG_WRITER: + case B_CHECKPOINTER: + case B_STANDALONE_BACKEND: + case B_STARTUP: + case B_WAL_SENDER: + return true; + } + + return false; +} + +/* + * Some BackendTypes do not perform IO on certain IOObjects or in certain + * IOContexts. Some IOObjects are never operated on in some IOContexts. Check + * that the given BackendType is expected to do IO in the given IOContext and + * on the given IOObject and that the given IOObject is expected to be operated + * on in the given IOContext. + */ +bool +pgstat_tracks_io_object(BackendType bktype, IOObject io_object, + IOContext io_context) +{ + bool no_temp_rel; + + /* + * Some BackendTypes should never track IO statistics. + */ + if (!pgstat_tracks_io_bktype(bktype)) + return false; + + /* + * Currently, IO on temporary relations can only occur in the + * IOCONTEXT_NORMAL IOContext. + */ + if (io_context != IOCONTEXT_NORMAL && + io_object == IOOBJECT_TEMP_RELATION) + return false; + + /* + * In core Postgres, only regular backends and WAL Sender processes + * executing queries will use local buffers and operate on temporary + * relations. Parallel workers will not use local buffers (see + * InitLocalBuffers()); however, extensions leveraging background workers + * have no such limitation, so track IO on IOOBJECT_TEMP_RELATION for + * BackendType B_BG_WORKER. + */ + no_temp_rel = bktype == B_AUTOVAC_LAUNCHER || bktype == B_BG_WRITER || + bktype == B_CHECKPOINTER || bktype == B_AUTOVAC_WORKER || + bktype == B_STANDALONE_BACKEND || bktype == B_STARTUP; + + if (no_temp_rel && io_context == IOCONTEXT_NORMAL && + io_object == IOOBJECT_TEMP_RELATION) + return false; + + /* + * Some BackendTypes do not currently perform any IO in certain + * IOContexts, and, while it may not be inherently incorrect for them to + * do so, excluding those rows from the view makes the view easier to use. + */ + if ((bktype == B_CHECKPOINTER || bktype == B_BG_WRITER) && + (io_context == IOCONTEXT_BULKREAD || + io_context == IOCONTEXT_BULKWRITE || + io_context == IOCONTEXT_VACUUM)) + return false; + + if (bktype == B_AUTOVAC_LAUNCHER && io_context == IOCONTEXT_VACUUM) + return false; + + if ((bktype == B_AUTOVAC_WORKER || bktype == B_AUTOVAC_LAUNCHER) && + io_context == IOCONTEXT_BULKWRITE) + return false; + + return true; +} + +/* + * Some BackendTypes will never do certain IOOps and some IOOps should not + * occur in certain IOContexts or on certain IOObjects. Check that the given + * IOOp is valid for the given BackendType in the given IOContext and on the + * given IOObject. Note that there are currently no cases of an IOOp being + * invalid for a particular BackendType only within a certain IOContext and/or + * only on a certain IOObject. + */ +bool +pgstat_tracks_io_op(BackendType bktype, IOObject io_object, + IOContext io_context, IOOp io_op) +{ + bool strategy_io_context; + + /* if (io_context, io_object) will never collect stats, we're done */ + if (!pgstat_tracks_io_object(bktype, io_object, io_context)) + return false; + + /* + * Some BackendTypes will not do certain IOOps. + */ + if ((bktype == B_BG_WRITER || bktype == B_CHECKPOINTER) && + (io_op == IOOP_READ || io_op == IOOP_EVICT || io_op == IOOP_HIT)) + return false; + + if ((bktype == B_AUTOVAC_LAUNCHER || bktype == B_BG_WRITER || + bktype == B_CHECKPOINTER) && io_op == IOOP_EXTEND) + return false; + + /* + * Temporary tables are not logged and thus do not require fsync'ing. + * Writeback is not requested for temporary tables. + */ + if (io_object == IOOBJECT_TEMP_RELATION && + (io_op == IOOP_FSYNC || io_op == IOOP_WRITEBACK)) + return false; + + /* + * Some IOOps are not valid in certain IOContexts and some IOOps are only + * valid in certain contexts. + */ + if (io_context == IOCONTEXT_BULKREAD && io_op == IOOP_EXTEND) + return false; + + strategy_io_context = io_context == IOCONTEXT_BULKREAD || + io_context == IOCONTEXT_BULKWRITE || io_context == IOCONTEXT_VACUUM; + + /* + * IOOP_REUSE is only relevant when a BufferAccessStrategy is in use. + */ + if (!strategy_io_context && io_op == IOOP_REUSE) + return false; + + /* + * IOOP_FSYNC IOOps done by a backend using a BufferAccessStrategy are + * counted in the IOCONTEXT_NORMAL IOContext. See comment in + * register_dirty_segment() for more details. + */ + if (strategy_io_context && io_op == IOOP_FSYNC) + return false; + + + return true; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/activity/pgstat_relation.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/activity/pgstat_relation.c new file mode 100644 index 00000000000..9876e0c1e82 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/activity/pgstat_relation.c @@ -0,0 +1,955 @@ +/* ------------------------------------------------------------------------- + * + * pgstat_relation.c + * Implementation of relation statistics. + * + * This file contains the implementation of function relation. It is kept + * separate from pgstat.c to enforce the line between the statistics access / + * storage implementation and the details about individual types of + * statistics. + * + * Copyright (c) 2001-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/utils/activity/pgstat_relation.c + * ------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/twophase_rmgr.h" +#include "access/xact.h" +#include "catalog/partition.h" +#include "postmaster/autovacuum.h" +#include "utils/memutils.h" +#include "utils/pgstat_internal.h" +#include "utils/rel.h" +#include "utils/timestamp.h" +#include "catalog/catalog.h" + + +/* Record that's written to 2PC state file when pgstat state is persisted */ +typedef struct TwoPhasePgStatRecord +{ + PgStat_Counter tuples_inserted; /* tuples inserted in xact */ + PgStat_Counter tuples_updated; /* tuples updated in xact */ + PgStat_Counter tuples_deleted; /* tuples deleted in xact */ + /* tuples i/u/d prior to truncate/drop */ + PgStat_Counter inserted_pre_truncdrop; + PgStat_Counter updated_pre_truncdrop; + PgStat_Counter deleted_pre_truncdrop; + Oid id; /* table's OID */ + bool shared; /* is it a shared catalog? */ + bool truncdropped; /* was the relation truncated/dropped? */ +} TwoPhasePgStatRecord; + + +static PgStat_TableStatus *pgstat_prep_relation_pending(Oid rel_id, bool isshared); +static void add_tabstat_xact_level(PgStat_TableStatus *pgstat_info, int nest_level); +static void ensure_tabstat_xact_level(PgStat_TableStatus *pgstat_info); +static void save_truncdrop_counters(PgStat_TableXactStatus *trans, bool is_drop); +static void restore_truncdrop_counters(PgStat_TableXactStatus *trans); + + +/* + * Copy stats between relations. This is used for things like REINDEX + * CONCURRENTLY. + */ +void +pgstat_copy_relation_stats(Relation dst, Relation src) +{ + PgStat_StatTabEntry *srcstats; + PgStatShared_Relation *dstshstats; + PgStat_EntryRef *dst_ref; + + srcstats = pgstat_fetch_stat_tabentry_ext(src->rd_rel->relisshared, + RelationGetRelid(src)); + if (!srcstats) + return; + + dst_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + dst->rd_rel->relisshared ? InvalidOid : MyDatabaseId, + RelationGetRelid(dst), + false); + + dstshstats = (PgStatShared_Relation *) dst_ref->shared_stats; + dstshstats->stats = *srcstats; + + pgstat_unlock_entry(dst_ref); +} + +/* + * Initialize a relcache entry to count access statistics. Called whenever a + * relation is opened. + * + * We assume that a relcache entry's pgstat_info field is zeroed by relcache.c + * when the relcache entry is made; thereafter it is long-lived data. + * + * This does not create a reference to a stats entry in shared memory, nor + * allocate memory for the pending stats. That happens in + * pgstat_assoc_relation(). + */ +void +pgstat_init_relation(Relation rel) +{ + char relkind = rel->rd_rel->relkind; + + /* + * We only count stats for relations with storage and partitioned tables + */ + if (!RELKIND_HAS_STORAGE(relkind) && relkind != RELKIND_PARTITIONED_TABLE) + { + rel->pgstat_enabled = false; + rel->pgstat_info = NULL; + return; + } + + if (!pgstat_track_counts) + { + if (rel->pgstat_info) + pgstat_unlink_relation(rel); + + /* We're not counting at all */ + rel->pgstat_enabled = false; + rel->pgstat_info = NULL; + return; + } + + rel->pgstat_enabled = true; +} + +/* + * Prepare for statistics for this relation to be collected. + * + * This ensures we have a reference to the stats entry before stats can be + * generated. That is important because a relation drop in another connection + * could otherwise lead to the stats entry being dropped, which then later + * would get recreated when flushing stats. + * + * This is separate from pgstat_init_relation() as it is not uncommon for + * relcache entries to be opened without ever getting stats reported. + */ +void +pgstat_assoc_relation(Relation rel) +{ + Assert(rel->pgstat_enabled); + Assert(rel->pgstat_info == NULL); + + /* Else find or make the PgStat_TableStatus entry, and update link */ + rel->pgstat_info = pgstat_prep_relation_pending(RelationGetRelid(rel), + rel->rd_rel->relisshared); + + /* don't allow link a stats to multiple relcache entries */ + Assert(rel->pgstat_info->relation == NULL); + + /* mark this relation as the owner */ + rel->pgstat_info->relation = rel; +} + +/* + * Break the mutual link between a relcache entry and pending stats entry. + * This must be called whenever one end of the link is removed. + */ +void +pgstat_unlink_relation(Relation rel) +{ + /* remove the link to stats info if any */ + if (rel->pgstat_info == NULL) + return; + + /* link sanity check */ + Assert(rel->pgstat_info->relation == rel); + rel->pgstat_info->relation = NULL; + rel->pgstat_info = NULL; +} + +/* + * Ensure that stats are dropped if transaction aborts. + */ +void +pgstat_create_relation(Relation rel) +{ + pgstat_create_transactional(PGSTAT_KIND_RELATION, + rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId, + RelationGetRelid(rel)); +} + +/* + * Ensure that stats are dropped if transaction commits. + */ +void +pgstat_drop_relation(Relation rel) +{ + int nest_level = GetCurrentTransactionNestLevel(); + PgStat_TableStatus *pgstat_info; + + pgstat_drop_transactional(PGSTAT_KIND_RELATION, + rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId, + RelationGetRelid(rel)); + + if (!pgstat_should_count_relation(rel)) + return; + + /* + * Transactionally set counters to 0. That ensures that accesses to + * pg_stat_xact_all_tables inside the transaction show 0. + */ + pgstat_info = rel->pgstat_info; + if (pgstat_info->trans && + pgstat_info->trans->nest_level == nest_level) + { + save_truncdrop_counters(pgstat_info->trans, true); + pgstat_info->trans->tuples_inserted = 0; + pgstat_info->trans->tuples_updated = 0; + pgstat_info->trans->tuples_deleted = 0; + } +} + +/* + * Report that the table was just vacuumed and flush IO statistics. + */ +void +pgstat_report_vacuum(Oid tableoid, bool shared, + PgStat_Counter livetuples, PgStat_Counter deadtuples) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + Oid dboid = (shared ? InvalidOid : MyDatabaseId); + TimestampTz ts; + + if (!pgstat_track_counts) + return; + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + dboid, tableoid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + tabentry->live_tuples = livetuples; + tabentry->dead_tuples = deadtuples; + + /* + * It is quite possible that a non-aggressive VACUUM ended up skipping + * various pages, however, we'll zero the insert counter here regardless. + * It's currently used only to track when we need to perform an "insert" + * autovacuum, which are mainly intended to freeze newly inserted tuples. + * Zeroing this may just mean we'll not try to vacuum the table again + * until enough tuples have been inserted to trigger another insert + * autovacuum. An anti-wraparound autovacuum will catch any persistent + * stragglers. + */ + tabentry->ins_since_vacuum = 0; + + if (IsAutoVacuumWorkerProcess()) + { + tabentry->last_autovacuum_time = ts; + tabentry->autovacuum_count++; + } + else + { + tabentry->last_vacuum_time = ts; + tabentry->vacuum_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* + * Flush IO statistics now. pgstat_report_stat() will flush IO stats, + * however this will not be called until after an entire autovacuum cycle + * is done -- which will likely vacuum many relations -- or until the + * VACUUM command has processed all tables and committed. + */ + pgstat_flush_io(false); +} + +/* + * Report that the table was just analyzed and flush IO statistics. + * + * Caller must provide new live- and dead-tuples estimates, as well as a + * flag indicating whether to reset the mod_since_analyze counter. + */ +void +pgstat_report_analyze(Relation rel, + PgStat_Counter livetuples, PgStat_Counter deadtuples, + bool resetcounter) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + Oid dboid = (rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId); + + if (!pgstat_track_counts) + return; + + /* + * Unlike VACUUM, ANALYZE might be running inside a transaction that has + * already inserted and/or deleted rows in the target table. ANALYZE will + * have counted such rows as live or dead respectively. Because we will + * report our counts of such rows at transaction end, we should subtract + * off these counts from the update we're making now, else they'll be + * double-counted after commit. (This approach also ensures that the + * shared stats entry ends up with the right numbers if we abort instead + * of committing.) + * + * Waste no time on partitioned tables, though. + */ + if (pgstat_should_count_relation(rel) && + rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE) + { + PgStat_TableXactStatus *trans; + + for (trans = rel->pgstat_info->trans; trans; trans = trans->upper) + { + livetuples -= trans->tuples_inserted - trans->tuples_deleted; + deadtuples -= trans->tuples_updated + trans->tuples_deleted; + } + /* count stuff inserted by already-aborted subxacts, too */ + deadtuples -= rel->pgstat_info->counts.delta_dead_tuples; + /* Since ANALYZE's counts are estimates, we could have underflowed */ + livetuples = Max(livetuples, 0); + deadtuples = Max(deadtuples, 0); + } + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, dboid, + RelationGetRelid(rel), + false); + /* can't get dropped while accessed */ + Assert(entry_ref != NULL && entry_ref->shared_stats != NULL); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + tabentry->live_tuples = livetuples; + tabentry->dead_tuples = deadtuples; + + /* + * If commanded, reset mod_since_analyze to zero. This forgets any + * changes that were committed while the ANALYZE was in progress, but we + * have no good way to estimate how many of those there were. + */ + if (resetcounter) + tabentry->mod_since_analyze = 0; + + if (IsAutoVacuumWorkerProcess()) + { + tabentry->last_autoanalyze_time = GetCurrentTimestamp(); + tabentry->autoanalyze_count++; + } + else + { + tabentry->last_analyze_time = GetCurrentTimestamp(); + tabentry->analyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); +} + +/* + * count a tuple insertion of n tuples + */ +void +pgstat_count_heap_insert(Relation rel, PgStat_Counter n) +{ + if (pgstat_should_count_relation(rel)) + { + PgStat_TableStatus *pgstat_info = rel->pgstat_info; + + ensure_tabstat_xact_level(pgstat_info); + pgstat_info->trans->tuples_inserted += n; + } +} + +/* + * count a tuple update + */ +void +pgstat_count_heap_update(Relation rel, bool hot, bool newpage) +{ + Assert(!(hot && newpage)); + + if (pgstat_should_count_relation(rel)) + { + PgStat_TableStatus *pgstat_info = rel->pgstat_info; + + ensure_tabstat_xact_level(pgstat_info); + pgstat_info->trans->tuples_updated++; + + /* + * tuples_hot_updated and tuples_newpage_updated counters are + * nontransactional, so just advance them + */ + if (hot) + pgstat_info->counts.tuples_hot_updated++; + else if (newpage) + pgstat_info->counts.tuples_newpage_updated++; + } +} + +/* + * count a tuple deletion + */ +void +pgstat_count_heap_delete(Relation rel) +{ + if (pgstat_should_count_relation(rel)) + { + PgStat_TableStatus *pgstat_info = rel->pgstat_info; + + ensure_tabstat_xact_level(pgstat_info); + pgstat_info->trans->tuples_deleted++; + } +} + +/* + * update tuple counters due to truncate + */ +void +pgstat_count_truncate(Relation rel) +{ + if (pgstat_should_count_relation(rel)) + { + PgStat_TableStatus *pgstat_info = rel->pgstat_info; + + ensure_tabstat_xact_level(pgstat_info); + save_truncdrop_counters(pgstat_info->trans, false); + pgstat_info->trans->tuples_inserted = 0; + pgstat_info->trans->tuples_updated = 0; + pgstat_info->trans->tuples_deleted = 0; + } +} + +/* + * update dead-tuples count + * + * The semantics of this are that we are reporting the nontransactional + * recovery of "delta" dead tuples; so delta_dead_tuples decreases + * rather than increasing, and the change goes straight into the per-table + * counter, not into transactional state. + */ +void +pgstat_update_heap_dead_tuples(Relation rel, int delta) +{ + if (pgstat_should_count_relation(rel)) + { + PgStat_TableStatus *pgstat_info = rel->pgstat_info; + + pgstat_info->counts.delta_dead_tuples -= delta; + } +} + +/* + * Support function for the SQL-callable pgstat* functions. Returns + * the collected statistics for one table or NULL. NULL doesn't mean + * that the table doesn't exist, just that there are no statistics, so the + * caller is better off to report ZERO instead. + */ +PgStat_StatTabEntry * +pgstat_fetch_stat_tabentry(Oid relid) +{ + return pgstat_fetch_stat_tabentry_ext(IsSharedRelation(relid), relid); +} + +/* + * More efficient version of pgstat_fetch_stat_tabentry(), allowing to specify + * whether the to-be-accessed table is a shared relation or not. + */ +PgStat_StatTabEntry * +pgstat_fetch_stat_tabentry_ext(bool shared, Oid reloid) +{ + Oid dboid = (shared ? InvalidOid : MyDatabaseId); + + return (PgStat_StatTabEntry *) + pgstat_fetch_entry(PGSTAT_KIND_RELATION, dboid, reloid); +} + +/* + * find any existing PgStat_TableStatus entry for rel + * + * Find any existing PgStat_TableStatus entry for rel_id in the current + * database. If not found, try finding from shared tables. + * + * If no entry found, return NULL, don't create a new one + */ +PgStat_TableStatus * +find_tabstat_entry(Oid rel_id) +{ + PgStat_EntryRef *entry_ref; + + entry_ref = pgstat_fetch_pending_entry(PGSTAT_KIND_RELATION, MyDatabaseId, rel_id); + if (!entry_ref) + entry_ref = pgstat_fetch_pending_entry(PGSTAT_KIND_RELATION, InvalidOid, rel_id); + + if (entry_ref) + return entry_ref->pending; + return NULL; +} + +/* + * Perform relation stats specific end-of-transaction work. Helper for + * AtEOXact_PgStat. + * + * Transfer transactional insert/update counts into the base tabstat entries. + * We don't bother to free any of the transactional state, since it's all in + * TopTransactionContext and will go away anyway. + */ +void +AtEOXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit) +{ + PgStat_TableXactStatus *trans; + + for (trans = xact_state->first; trans != NULL; trans = trans->next) + { + PgStat_TableStatus *tabstat; + + Assert(trans->nest_level == 1); + Assert(trans->upper == NULL); + tabstat = trans->parent; + Assert(tabstat->trans == trans); + /* restore pre-truncate/drop stats (if any) in case of aborted xact */ + if (!isCommit) + restore_truncdrop_counters(trans); + /* count attempted actions regardless of commit/abort */ + tabstat->counts.tuples_inserted += trans->tuples_inserted; + tabstat->counts.tuples_updated += trans->tuples_updated; + tabstat->counts.tuples_deleted += trans->tuples_deleted; + if (isCommit) + { + tabstat->counts.truncdropped = trans->truncdropped; + if (trans->truncdropped) + { + /* forget live/dead stats seen by backend thus far */ + tabstat->counts.delta_live_tuples = 0; + tabstat->counts.delta_dead_tuples = 0; + } + /* insert adds a live tuple, delete removes one */ + tabstat->counts.delta_live_tuples += + trans->tuples_inserted - trans->tuples_deleted; + /* update and delete each create a dead tuple */ + tabstat->counts.delta_dead_tuples += + trans->tuples_updated + trans->tuples_deleted; + /* insert, update, delete each count as one change event */ + tabstat->counts.changed_tuples += + trans->tuples_inserted + trans->tuples_updated + + trans->tuples_deleted; + } + else + { + /* inserted tuples are dead, deleted tuples are unaffected */ + tabstat->counts.delta_dead_tuples += + trans->tuples_inserted + trans->tuples_updated; + /* an aborted xact generates no changed_tuple events */ + } + tabstat->trans = NULL; + } +} + +/* + * Perform relation stats specific end-of-sub-transaction work. Helper for + * AtEOSubXact_PgStat. + * + * Transfer transactional insert/update counts into the next higher + * subtransaction state. + */ +void +AtEOSubXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit, int nestDepth) +{ + PgStat_TableXactStatus *trans; + PgStat_TableXactStatus *next_trans; + + for (trans = xact_state->first; trans != NULL; trans = next_trans) + { + PgStat_TableStatus *tabstat; + + next_trans = trans->next; + Assert(trans->nest_level == nestDepth); + tabstat = trans->parent; + Assert(tabstat->trans == trans); + + if (isCommit) + { + if (trans->upper && trans->upper->nest_level == nestDepth - 1) + { + if (trans->truncdropped) + { + /* propagate the truncate/drop status one level up */ + save_truncdrop_counters(trans->upper, false); + /* replace upper xact stats with ours */ + trans->upper->tuples_inserted = trans->tuples_inserted; + trans->upper->tuples_updated = trans->tuples_updated; + trans->upper->tuples_deleted = trans->tuples_deleted; + } + else + { + trans->upper->tuples_inserted += trans->tuples_inserted; + trans->upper->tuples_updated += trans->tuples_updated; + trans->upper->tuples_deleted += trans->tuples_deleted; + } + tabstat->trans = trans->upper; + pfree(trans); + } + else + { + /* + * When there isn't an immediate parent state, we can just + * reuse the record instead of going through a palloc/pfree + * pushup (this works since it's all in TopTransactionContext + * anyway). We have to re-link it into the parent level, + * though, and that might mean pushing a new entry into the + * pgStatXactStack. + */ + PgStat_SubXactStatus *upper_xact_state; + + upper_xact_state = pgstat_get_xact_stack_level(nestDepth - 1); + trans->next = upper_xact_state->first; + upper_xact_state->first = trans; + trans->nest_level = nestDepth - 1; + } + } + else + { + /* + * On abort, update top-level tabstat counts, then forget the + * subtransaction + */ + + /* first restore values obliterated by truncate/drop */ + restore_truncdrop_counters(trans); + /* count attempted actions regardless of commit/abort */ + tabstat->counts.tuples_inserted += trans->tuples_inserted; + tabstat->counts.tuples_updated += trans->tuples_updated; + tabstat->counts.tuples_deleted += trans->tuples_deleted; + /* inserted tuples are dead, deleted tuples are unaffected */ + tabstat->counts.delta_dead_tuples += + trans->tuples_inserted + trans->tuples_updated; + tabstat->trans = trans->upper; + pfree(trans); + } + } +} + +/* + * Generate 2PC records for all the pending transaction-dependent relation + * stats. + */ +void +AtPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state) +{ + PgStat_TableXactStatus *trans; + + for (trans = xact_state->first; trans != NULL; trans = trans->next) + { + PgStat_TableStatus *tabstat PG_USED_FOR_ASSERTS_ONLY; + TwoPhasePgStatRecord record; + + Assert(trans->nest_level == 1); + Assert(trans->upper == NULL); + tabstat = trans->parent; + Assert(tabstat->trans == trans); + + record.tuples_inserted = trans->tuples_inserted; + record.tuples_updated = trans->tuples_updated; + record.tuples_deleted = trans->tuples_deleted; + record.inserted_pre_truncdrop = trans->inserted_pre_truncdrop; + record.updated_pre_truncdrop = trans->updated_pre_truncdrop; + record.deleted_pre_truncdrop = trans->deleted_pre_truncdrop; + record.id = tabstat->id; + record.shared = tabstat->shared; + record.truncdropped = trans->truncdropped; + + RegisterTwoPhaseRecord(TWOPHASE_RM_PGSTAT_ID, 0, + &record, sizeof(TwoPhasePgStatRecord)); + } +} + +/* + * All we need do here is unlink the transaction stats state from the + * nontransactional state. The nontransactional action counts will be + * reported to the stats system immediately, while the effects on live and + * dead tuple counts are preserved in the 2PC state file. + * + * Note: AtEOXact_PgStat_Relations is not called during PREPARE. + */ +void +PostPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state) +{ + PgStat_TableXactStatus *trans; + + for (trans = xact_state->first; trans != NULL; trans = trans->next) + { + PgStat_TableStatus *tabstat; + + tabstat = trans->parent; + tabstat->trans = NULL; + } +} + +/* + * 2PC processing routine for COMMIT PREPARED case. + * + * Load the saved counts into our local pgstats state. + */ +void +pgstat_twophase_postcommit(TransactionId xid, uint16 info, + void *recdata, uint32 len) +{ + TwoPhasePgStatRecord *rec = (TwoPhasePgStatRecord *) recdata; + PgStat_TableStatus *pgstat_info; + + /* Find or create a tabstat entry for the rel */ + pgstat_info = pgstat_prep_relation_pending(rec->id, rec->shared); + + /* Same math as in AtEOXact_PgStat, commit case */ + pgstat_info->counts.tuples_inserted += rec->tuples_inserted; + pgstat_info->counts.tuples_updated += rec->tuples_updated; + pgstat_info->counts.tuples_deleted += rec->tuples_deleted; + pgstat_info->counts.truncdropped = rec->truncdropped; + if (rec->truncdropped) + { + /* forget live/dead stats seen by backend thus far */ + pgstat_info->counts.delta_live_tuples = 0; + pgstat_info->counts.delta_dead_tuples = 0; + } + pgstat_info->counts.delta_live_tuples += + rec->tuples_inserted - rec->tuples_deleted; + pgstat_info->counts.delta_dead_tuples += + rec->tuples_updated + rec->tuples_deleted; + pgstat_info->counts.changed_tuples += + rec->tuples_inserted + rec->tuples_updated + + rec->tuples_deleted; +} + +/* + * 2PC processing routine for ROLLBACK PREPARED case. + * + * Load the saved counts into our local pgstats state, but treat them + * as aborted. + */ +void +pgstat_twophase_postabort(TransactionId xid, uint16 info, + void *recdata, uint32 len) +{ + TwoPhasePgStatRecord *rec = (TwoPhasePgStatRecord *) recdata; + PgStat_TableStatus *pgstat_info; + + /* Find or create a tabstat entry for the rel */ + pgstat_info = pgstat_prep_relation_pending(rec->id, rec->shared); + + /* Same math as in AtEOXact_PgStat, abort case */ + if (rec->truncdropped) + { + rec->tuples_inserted = rec->inserted_pre_truncdrop; + rec->tuples_updated = rec->updated_pre_truncdrop; + rec->tuples_deleted = rec->deleted_pre_truncdrop; + } + pgstat_info->counts.tuples_inserted += rec->tuples_inserted; + pgstat_info->counts.tuples_updated += rec->tuples_updated; + pgstat_info->counts.tuples_deleted += rec->tuples_deleted; + pgstat_info->counts.delta_dead_tuples += + rec->tuples_inserted + rec->tuples_updated; +} + +/* + * Flush out pending stats for the entry + * + * If nowait is true, this function returns false if lock could not + * immediately acquired, otherwise true is returned. + * + * Some of the stats are copied to the corresponding pending database stats + * entry when successfully flushing. + */ +bool +pgstat_relation_flush_cb(PgStat_EntryRef *entry_ref, bool nowait) +{ + static const PgStat_TableCounts all_zeroes; + Oid dboid; + PgStat_TableStatus *lstats; /* pending stats entry */ + PgStatShared_Relation *shtabstats; + PgStat_StatTabEntry *tabentry; /* table entry of shared stats */ + PgStat_StatDBEntry *dbentry; /* pending database entry */ + + dboid = entry_ref->shared_entry->key.dboid; + lstats = (PgStat_TableStatus *) entry_ref->pending; + shtabstats = (PgStatShared_Relation *) entry_ref->shared_stats; + + /* + * Ignore entries that didn't accumulate any actual counts, such as + * indexes that were opened by the planner but not used. + */ + if (memcmp(&lstats->counts, &all_zeroes, + sizeof(PgStat_TableCounts)) == 0) + { + return true; + } + + if (!pgstat_lock_entry(entry_ref, nowait)) + return false; + + /* add the values to the shared entry. */ + tabentry = &shtabstats->stats; + + tabentry->numscans += lstats->counts.numscans; + if (lstats->counts.numscans) + { + TimestampTz t = GetCurrentTransactionStopTimestamp(); + + if (t > tabentry->lastscan) + tabentry->lastscan = t; + } + tabentry->tuples_returned += lstats->counts.tuples_returned; + tabentry->tuples_fetched += lstats->counts.tuples_fetched; + tabentry->tuples_inserted += lstats->counts.tuples_inserted; + tabentry->tuples_updated += lstats->counts.tuples_updated; + tabentry->tuples_deleted += lstats->counts.tuples_deleted; + tabentry->tuples_hot_updated += lstats->counts.tuples_hot_updated; + tabentry->tuples_newpage_updated += lstats->counts.tuples_newpage_updated; + + /* + * If table was truncated/dropped, first reset the live/dead counters. + */ + if (lstats->counts.truncdropped) + { + tabentry->live_tuples = 0; + tabentry->dead_tuples = 0; + tabentry->ins_since_vacuum = 0; + } + + tabentry->live_tuples += lstats->counts.delta_live_tuples; + tabentry->dead_tuples += lstats->counts.delta_dead_tuples; + tabentry->mod_since_analyze += lstats->counts.changed_tuples; + tabentry->ins_since_vacuum += lstats->counts.tuples_inserted; + tabentry->blocks_fetched += lstats->counts.blocks_fetched; + tabentry->blocks_hit += lstats->counts.blocks_hit; + + /* Clamp live_tuples in case of negative delta_live_tuples */ + tabentry->live_tuples = Max(tabentry->live_tuples, 0); + /* Likewise for dead_tuples */ + tabentry->dead_tuples = Max(tabentry->dead_tuples, 0); + + pgstat_unlock_entry(entry_ref); + + /* The entry was successfully flushed, add the same to database stats */ + dbentry = pgstat_prep_database_pending(dboid); + dbentry->tuples_returned += lstats->counts.tuples_returned; + dbentry->tuples_fetched += lstats->counts.tuples_fetched; + dbentry->tuples_inserted += lstats->counts.tuples_inserted; + dbentry->tuples_updated += lstats->counts.tuples_updated; + dbentry->tuples_deleted += lstats->counts.tuples_deleted; + dbentry->blocks_fetched += lstats->counts.blocks_fetched; + dbentry->blocks_hit += lstats->counts.blocks_hit; + + return true; +} + +void +pgstat_relation_delete_pending_cb(PgStat_EntryRef *entry_ref) +{ + PgStat_TableStatus *pending = (PgStat_TableStatus *) entry_ref->pending; + + if (pending->relation) + pgstat_unlink_relation(pending->relation); +} + +/* + * Find or create a PgStat_TableStatus entry for rel. New entry is created and + * initialized if not exists. + */ +static PgStat_TableStatus * +pgstat_prep_relation_pending(Oid rel_id, bool isshared) +{ + PgStat_EntryRef *entry_ref; + PgStat_TableStatus *pending; + + entry_ref = pgstat_prep_pending_entry(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + rel_id, NULL); + pending = entry_ref->pending; + pending->id = rel_id; + pending->shared = isshared; + + return pending; +} + +/* + * add a new (sub)transaction state record + */ +static void +add_tabstat_xact_level(PgStat_TableStatus *pgstat_info, int nest_level) +{ + PgStat_SubXactStatus *xact_state; + PgStat_TableXactStatus *trans; + + /* + * If this is the first rel to be modified at the current nest level, we + * first have to push a transaction stack entry. + */ + xact_state = pgstat_get_xact_stack_level(nest_level); + + /* Now make a per-table stack entry */ + trans = (PgStat_TableXactStatus *) + MemoryContextAllocZero(TopTransactionContext, + sizeof(PgStat_TableXactStatus)); + trans->nest_level = nest_level; + trans->upper = pgstat_info->trans; + trans->parent = pgstat_info; + trans->next = xact_state->first; + xact_state->first = trans; + pgstat_info->trans = trans; +} + +/* + * Add a new (sub)transaction record if needed. + */ +static void +ensure_tabstat_xact_level(PgStat_TableStatus *pgstat_info) +{ + int nest_level = GetCurrentTransactionNestLevel(); + + if (pgstat_info->trans == NULL || + pgstat_info->trans->nest_level != nest_level) + add_tabstat_xact_level(pgstat_info, nest_level); +} + +/* + * Whenever a table is truncated/dropped, we save its i/u/d counters so that + * they can be cleared, and if the (sub)xact that executed the truncate/drop + * later aborts, the counters can be restored to the saved (pre-truncate/drop) + * values. + * + * Note that for truncate we do this on the first truncate in any particular + * subxact level only. + */ +static void +save_truncdrop_counters(PgStat_TableXactStatus *trans, bool is_drop) +{ + if (!trans->truncdropped || is_drop) + { + trans->inserted_pre_truncdrop = trans->tuples_inserted; + trans->updated_pre_truncdrop = trans->tuples_updated; + trans->deleted_pre_truncdrop = trans->tuples_deleted; + trans->truncdropped = true; + } +} + +/* + * restore counters when a truncate aborts + */ +static void +restore_truncdrop_counters(PgStat_TableXactStatus *trans) +{ + if (trans->truncdropped) + { + trans->tuples_inserted = trans->inserted_pre_truncdrop; + trans->tuples_updated = trans->updated_pre_truncdrop; + trans->tuples_deleted = trans->deleted_pre_truncdrop; + } +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/activity/pgstat_replslot.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/activity/pgstat_replslot.c new file mode 100644 index 00000000000..f08abad4945 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/activity/pgstat_replslot.c @@ -0,0 +1,224 @@ +/* ------------------------------------------------------------------------- + * + * pgstat_replslot.c + * Implementation of replication slot statistics. + * + * This file contains the implementation of replication slot statistics. It is kept + * separate from pgstat.c to enforce the line between the statistics access / + * storage implementation and the details about individual types of + * statistics. + * + * Replication slot stats work a bit different than other variable-numbered + * stats. Slots do not have oids (so they can be created on physical + * replicas). Use the slot index as object id while running. However, the slot + * index can change when restarting. That is addressed by using the name when + * (de-)serializing. After a restart it is possible for slots to have been + * dropped while shut down, which is addressed by not restoring stats for + * slots that cannot be found by name when starting up. + * + * Copyright (c) 2001-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/utils/activity/pgstat_replslot.c + * ------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "replication/slot.h" +#include "utils/builtins.h" /* for namestrcpy() */ +#include "utils/pgstat_internal.h" + + +static int get_replslot_index(const char *name); + + +/* + * Reset counters for a single replication slot. + * + * Permission checking for this function is managed through the normal + * GRANT system. + */ +void +pgstat_reset_replslot(const char *name) +{ + ReplicationSlot *slot; + + Assert(name != NULL); + + /* Check if the slot exits with the given name. */ + slot = SearchNamedReplicationSlot(name, true); + + if (!slot) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("replication slot \"%s\" does not exist", + name))); + + /* + * Nothing to do for physical slots as we collect stats only for logical + * slots. + */ + if (SlotIsPhysical(slot)) + return; + + /* reset this one entry */ + pgstat_reset(PGSTAT_KIND_REPLSLOT, InvalidOid, + ReplicationSlotIndex(slot)); +} + +/* + * Report replication slot statistics. + * + * We can rely on the stats for the slot to exist and to belong to this + * slot. We can only get here if pgstat_create_replslot() or + * pgstat_acquire_replslot() have already been called. + */ +void +pgstat_report_replslot(ReplicationSlot *slot, const PgStat_StatReplSlotEntry *repSlotStat) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_ReplSlot *shstatent; + PgStat_StatReplSlotEntry *statent; + + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_REPLSLOT, InvalidOid, + ReplicationSlotIndex(slot), false); + shstatent = (PgStatShared_ReplSlot *) entry_ref->shared_stats; + statent = &shstatent->stats; + + /* Update the replication slot statistics */ +#define REPLSLOT_ACC(fld) statent->fld += repSlotStat->fld + REPLSLOT_ACC(spill_txns); + REPLSLOT_ACC(spill_count); + REPLSLOT_ACC(spill_bytes); + REPLSLOT_ACC(stream_txns); + REPLSLOT_ACC(stream_count); + REPLSLOT_ACC(stream_bytes); + REPLSLOT_ACC(total_txns); + REPLSLOT_ACC(total_bytes); +#undef REPLSLOT_ACC + + pgstat_unlock_entry(entry_ref); +} + +/* + * Report replication slot creation. + * + * NB: This gets called with ReplicationSlotAllocationLock already held, be + * careful about calling back into slot.c. + */ +void +pgstat_create_replslot(ReplicationSlot *slot) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_ReplSlot *shstatent; + + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_REPLSLOT, InvalidOid, + ReplicationSlotIndex(slot), false); + shstatent = (PgStatShared_ReplSlot *) entry_ref->shared_stats; + + /* + * NB: need to accept that there might be stats from an older slot, e.g. + * if we previously crashed after dropping a slot. + */ + memset(&shstatent->stats, 0, sizeof(shstatent->stats)); + + pgstat_unlock_entry(entry_ref); +} + +/* + * Report replication slot has been acquired. + * + * This guarantees that a stats entry exists during later + * pgstat_report_replslot() calls. + * + * If we previously crashed, no stats data exists. But if we did not crash, + * the stats do belong to this slot: + * - the stats cannot belong to a dropped slot, pgstat_drop_replslot() would + * have been called + * - if the slot was removed while shut down, + * pgstat_replslot_from_serialized_name_cb() returning false would have + * caused the stats to be dropped + */ +void +pgstat_acquire_replslot(ReplicationSlot *slot) +{ + pgstat_get_entry_ref(PGSTAT_KIND_REPLSLOT, InvalidOid, + ReplicationSlotIndex(slot), true, NULL); +} + +/* + * Report replication slot drop. + */ +void +pgstat_drop_replslot(ReplicationSlot *slot) +{ + pgstat_drop_entry(PGSTAT_KIND_REPLSLOT, InvalidOid, + ReplicationSlotIndex(slot)); +} + +/* + * Support function for the SQL-callable pgstat* functions. Returns + * a pointer to the replication slot statistics struct. + */ +PgStat_StatReplSlotEntry * +pgstat_fetch_replslot(NameData slotname) +{ + int idx = get_replslot_index(NameStr(slotname)); + + if (idx == -1) + return NULL; + + return (PgStat_StatReplSlotEntry *) + pgstat_fetch_entry(PGSTAT_KIND_REPLSLOT, InvalidOid, idx); +} + +void +pgstat_replslot_to_serialized_name_cb(const PgStat_HashKey *key, const PgStatShared_Common *header, NameData *name) +{ + /* + * This is only called late during shutdown. The set of existing slots + * isn't allowed to change at this point, we can assume that a slot exists + * at the offset. + */ + if (!ReplicationSlotName(key->objoid, name)) + elog(ERROR, "could not find name for replication slot index %u", + key->objoid); +} + +bool +pgstat_replslot_from_serialized_name_cb(const NameData *name, PgStat_HashKey *key) +{ + int idx = get_replslot_index(NameStr(*name)); + + /* slot might have been deleted */ + if (idx == -1) + return false; + + key->kind = PGSTAT_KIND_REPLSLOT; + key->dboid = InvalidOid; + key->objoid = idx; + + return true; +} + +void +pgstat_replslot_reset_timestamp_cb(PgStatShared_Common *header, TimestampTz ts) +{ + ((PgStatShared_ReplSlot *) header)->stats.stat_reset_timestamp = ts; +} + +static int +get_replslot_index(const char *name) +{ + ReplicationSlot *slot; + + Assert(name != NULL); + + slot = SearchNamedReplicationSlot(name, true); + + if (!slot) + return -1; + + return ReplicationSlotIndex(slot); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/activity/pgstat_shmem.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/activity/pgstat_shmem.c new file mode 100644 index 00000000000..7b08dec26f9 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/activity/pgstat_shmem.c @@ -0,0 +1,1007 @@ +/* ------------------------------------------------------------------------- + * + * pgstat_shmem.c + * Storage of stats entries in shared memory + * + * Copyright (c) 2001-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/utils/activity/pgstat_shmem.c + * ------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "pgstat.h" +#include "storage/shmem.h" +#include "utils/memutils.h" +#include "utils/pgstat_internal.h" + + +#define PGSTAT_ENTRY_REF_HASH_SIZE 128 + +/* hash table entry for finding the PgStat_EntryRef for a key */ +typedef struct PgStat_EntryRefHashEntry +{ + PgStat_HashKey key; /* hash key */ + char status; /* for simplehash use */ + PgStat_EntryRef *entry_ref; +} PgStat_EntryRefHashEntry; + + +/* for references to shared statistics entries */ +#define SH_PREFIX pgstat_entry_ref_hash +#define SH_ELEMENT_TYPE PgStat_EntryRefHashEntry +#define SH_KEY_TYPE PgStat_HashKey +#define SH_KEY key +#define SH_HASH_KEY(tb, key) \ + pgstat_hash_hash_key(&key, sizeof(PgStat_HashKey), NULL) +#define SH_EQUAL(tb, a, b) \ + pgstat_cmp_hash_key(&a, &b, sizeof(PgStat_HashKey), NULL) == 0 +#define SH_SCOPE static inline +#define SH_DEFINE +#define SH_DECLARE +#include "lib/simplehash.h" + + +static void pgstat_drop_database_and_contents(Oid dboid); + +static void pgstat_free_entry(PgStatShared_HashEntry *shent, dshash_seq_status *hstat); + +static void pgstat_release_entry_ref(PgStat_HashKey key, PgStat_EntryRef *entry_ref, bool discard_pending); +static bool pgstat_need_entry_refs_gc(void); +static void pgstat_gc_entry_refs(void); +static void pgstat_release_all_entry_refs(bool discard_pending); +typedef bool (*ReleaseMatchCB) (PgStat_EntryRefHashEntry *, Datum data); +static void pgstat_release_matching_entry_refs(bool discard_pending, ReleaseMatchCB match, Datum match_data); + +static void pgstat_setup_memcxt(void); + + +/* parameter for the shared hash */ +static const dshash_parameters dsh_params = { + sizeof(PgStat_HashKey), + sizeof(PgStatShared_HashEntry), + pgstat_cmp_hash_key, + pgstat_hash_hash_key, + LWTRANCHE_PGSTATS_HASH +}; + + +/* + * Backend local references to shared stats entries. If there are pending + * updates to a stats entry, the PgStat_EntryRef is added to the pgStatPending + * list. + * + * When a stats entry is dropped each backend needs to release its reference + * to it before the memory can be released. To trigger that + * pgStatLocal.shmem->gc_request_count is incremented - which each backend + * compares to their copy of pgStatSharedRefAge on a regular basis. + */ +static __thread pgstat_entry_ref_hash_hash *pgStatEntryRefHash = NULL; +static __thread int pgStatSharedRefAge = 0; /* cache age of pgStatShmLookupCache */ + +/* + * Memory contexts containing the pgStatEntryRefHash table and the + * pgStatSharedRef entries respectively. Kept separate to make it easier to + * track / attribute memory usage. + */ +static __thread MemoryContext pgStatSharedRefContext = NULL; +static __thread MemoryContext pgStatEntryRefHashContext = NULL; + + +/* ------------------------------------------------------------ + * Public functions called from postmaster follow + * ------------------------------------------------------------ + */ + +/* + * The size of the shared memory allocation for stats stored in the shared + * stats hash table. This allocation will be done as part of the main shared + * memory, rather than dynamic shared memory, allowing it to be initialized in + * postmaster. + */ +static Size +pgstat_dsa_init_size(void) +{ + Size sz; + + /* + * The dshash header / initial buckets array needs to fit into "plain" + * shared memory, but it's beneficial to not need dsm segments + * immediately. A size of 256kB seems works well and is not + * disproportional compared to other constant sized shared memory + * allocations. NB: To avoid DSMs further, the user can configure + * min_dynamic_shared_memory. + */ + sz = 256 * 1024; + Assert(dsa_minimum_size() <= sz); + return MAXALIGN(sz); +} + +/* + * Compute shared memory space needed for cumulative statistics + */ +Size +StatsShmemSize(void) +{ + Size sz; + + sz = MAXALIGN(sizeof(PgStat_ShmemControl)); + sz = add_size(sz, pgstat_dsa_init_size()); + + return sz; +} + +/* + * Initialize cumulative statistics system during startup + */ +void +StatsShmemInit(void) +{ + bool found; + Size sz; + + sz = StatsShmemSize(); + pgStatLocal.shmem = (PgStat_ShmemControl *) + ShmemInitStruct("Shared Memory Stats", sz, &found); + + if (!IsUnderPostmaster) + { + dsa_area *dsa; + dshash_table *dsh; + PgStat_ShmemControl *ctl = pgStatLocal.shmem; + char *p = (char *) ctl; + + Assert(!found); + + /* the allocation of pgStatLocal.shmem itself */ + p += MAXALIGN(sizeof(PgStat_ShmemControl)); + + /* + * Create a small dsa allocation in plain shared memory. This is + * required because postmaster cannot use dsm segments. It also + * provides a small efficiency win. + */ + ctl->raw_dsa_area = p; + p += MAXALIGN(pgstat_dsa_init_size()); + dsa = dsa_create_in_place(ctl->raw_dsa_area, + pgstat_dsa_init_size(), + LWTRANCHE_PGSTATS_DSA, 0); + dsa_pin(dsa); + + /* + * To ensure dshash is created in "plain" shared memory, temporarily + * limit size of dsa to the initial size of the dsa. + */ + dsa_set_size_limit(dsa, pgstat_dsa_init_size()); + + /* + * With the limit in place, create the dshash table. XXX: It'd be nice + * if there were dshash_create_in_place(). + */ + dsh = dshash_create(dsa, &dsh_params, 0); + ctl->hash_handle = dshash_get_hash_table_handle(dsh); + + /* lift limit set above */ + dsa_set_size_limit(dsa, -1); + + /* + * Postmaster will never access these again, thus free the local + * dsa/dshash references. + */ + dshash_detach(dsh); + dsa_detach(dsa); + + pg_atomic_init_u64(&ctl->gc_request_count, 1); + + + /* initialize fixed-numbered stats */ + LWLockInitialize(&ctl->archiver.lock, LWTRANCHE_PGSTATS_DATA); + LWLockInitialize(&ctl->bgwriter.lock, LWTRANCHE_PGSTATS_DATA); + LWLockInitialize(&ctl->checkpointer.lock, LWTRANCHE_PGSTATS_DATA); + LWLockInitialize(&ctl->slru.lock, LWTRANCHE_PGSTATS_DATA); + LWLockInitialize(&ctl->wal.lock, LWTRANCHE_PGSTATS_DATA); + + for (int i = 0; i < BACKEND_NUM_TYPES; i++) + LWLockInitialize(&ctl->io.locks[i], + LWTRANCHE_PGSTATS_DATA); + } + else + { + Assert(found); + } +} + +void +pgstat_attach_shmem(void) +{ + MemoryContext oldcontext; + + Assert(pgStatLocal.dsa == NULL); + + /* stats shared memory persists for the backend lifetime */ + oldcontext = MemoryContextSwitchTo(TopMemoryContext); + + pgStatLocal.dsa = dsa_attach_in_place(pgStatLocal.shmem->raw_dsa_area, + NULL); + dsa_pin_mapping(pgStatLocal.dsa); + + pgStatLocal.shared_hash = dshash_attach(pgStatLocal.dsa, &dsh_params, + pgStatLocal.shmem->hash_handle, 0); + + MemoryContextSwitchTo(oldcontext); +} + +void +pgstat_detach_shmem(void) +{ + Assert(pgStatLocal.dsa); + + /* we shouldn't leave references to shared stats */ + pgstat_release_all_entry_refs(false); + + dshash_detach(pgStatLocal.shared_hash); + pgStatLocal.shared_hash = NULL; + + dsa_detach(pgStatLocal.dsa); + pgStatLocal.dsa = NULL; +} + + +/* ------------------------------------------------------------ + * Maintenance of shared memory stats entries + * ------------------------------------------------------------ + */ + +PgStatShared_Common * +pgstat_init_entry(PgStat_Kind kind, + PgStatShared_HashEntry *shhashent) +{ + /* Create new stats entry. */ + dsa_pointer chunk; + PgStatShared_Common *shheader; + + /* + * Initialize refcount to 1, marking it as valid / not dropped. The entry + * can't be freed before the initialization because it can't be found as + * long as we hold the dshash partition lock. Caller needs to increase + * further if a longer lived reference is needed. + */ + pg_atomic_init_u32(&shhashent->refcount, 1); + shhashent->dropped = false; + + chunk = dsa_allocate0(pgStatLocal.dsa, pgstat_get_kind_info(kind)->shared_size); + shheader = dsa_get_address(pgStatLocal.dsa, chunk); + shheader->magic = 0xdeadbeef; + + /* Link the new entry from the hash entry. */ + shhashent->body = chunk; + + LWLockInitialize(&shheader->lock, LWTRANCHE_PGSTATS_DATA); + + return shheader; +} + +static PgStatShared_Common * +pgstat_reinit_entry(PgStat_Kind kind, PgStatShared_HashEntry *shhashent) +{ + PgStatShared_Common *shheader; + + shheader = dsa_get_address(pgStatLocal.dsa, shhashent->body); + + /* mark as not dropped anymore */ + pg_atomic_fetch_add_u32(&shhashent->refcount, 1); + shhashent->dropped = false; + + /* reinitialize content */ + Assert(shheader->magic == 0xdeadbeef); + memset(pgstat_get_entry_data(kind, shheader), 0, + pgstat_get_entry_len(kind)); + + return shheader; +} + +static void +pgstat_setup_shared_refs(void) +{ + if (likely(pgStatEntryRefHash != NULL)) + return; + + pgStatEntryRefHash = + pgstat_entry_ref_hash_create(pgStatEntryRefHashContext, + PGSTAT_ENTRY_REF_HASH_SIZE, NULL); + pgStatSharedRefAge = pg_atomic_read_u64(&pgStatLocal.shmem->gc_request_count); + Assert(pgStatSharedRefAge != 0); +} + +/* + * Helper function for pgstat_get_entry_ref(). + */ +static void +pgstat_acquire_entry_ref(PgStat_EntryRef *entry_ref, + PgStatShared_HashEntry *shhashent, + PgStatShared_Common *shheader) +{ + Assert(shheader->magic == 0xdeadbeef); + Assert(pg_atomic_read_u32(&shhashent->refcount) > 0); + + pg_atomic_fetch_add_u32(&shhashent->refcount, 1); + + dshash_release_lock(pgStatLocal.shared_hash, shhashent); + + entry_ref->shared_stats = shheader; + entry_ref->shared_entry = shhashent; +} + +/* + * Helper function for pgstat_get_entry_ref(). + */ +static bool +pgstat_get_entry_ref_cached(PgStat_HashKey key, PgStat_EntryRef **entry_ref_p) +{ + bool found; + PgStat_EntryRefHashEntry *cache_entry; + + /* + * We immediately insert a cache entry, because it avoids 1) multiple + * hashtable lookups in case of a cache miss 2) having to deal with + * out-of-memory errors after incrementing PgStatShared_Common->refcount. + */ + + cache_entry = pgstat_entry_ref_hash_insert(pgStatEntryRefHash, key, &found); + + if (!found || !cache_entry->entry_ref) + { + PgStat_EntryRef *entry_ref; + + cache_entry->entry_ref = entry_ref = + MemoryContextAlloc(pgStatSharedRefContext, + sizeof(PgStat_EntryRef)); + entry_ref->shared_stats = NULL; + entry_ref->shared_entry = NULL; + entry_ref->pending = NULL; + + found = false; + } + else if (cache_entry->entry_ref->shared_stats == NULL) + { + Assert(cache_entry->entry_ref->pending == NULL); + found = false; + } + else + { + PgStat_EntryRef *entry_ref PG_USED_FOR_ASSERTS_ONLY; + + entry_ref = cache_entry->entry_ref; + Assert(entry_ref->shared_entry != NULL); + Assert(entry_ref->shared_stats != NULL); + + Assert(entry_ref->shared_stats->magic == 0xdeadbeef); + /* should have at least our reference */ + Assert(pg_atomic_read_u32(&entry_ref->shared_entry->refcount) > 0); + } + + *entry_ref_p = cache_entry->entry_ref; + return found; +} + +/* + * Get a shared stats reference. If create is true, the shared stats object is + * created if it does not exist. + * + * When create is true, and created_entry is non-NULL, it'll be set to true + * if the entry is newly created, false otherwise. + */ +PgStat_EntryRef * +pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, Oid objoid, bool create, + bool *created_entry) +{ + PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objoid = objoid}; + PgStatShared_HashEntry *shhashent; + PgStatShared_Common *shheader = NULL; + PgStat_EntryRef *entry_ref; + + /* + * passing in created_entry only makes sense if we possibly could create + * entry. + */ + Assert(create || created_entry == NULL); + pgstat_assert_is_up(); + Assert(pgStatLocal.shared_hash != NULL); + Assert(!pgStatLocal.shmem->is_shutdown); + + pgstat_setup_memcxt(); + pgstat_setup_shared_refs(); + + if (created_entry != NULL) + *created_entry = false; + + /* + * Check if other backends dropped stats that could not be deleted because + * somebody held references to it. If so, check this backend's references. + * This is not expected to happen often. The location of the check is a + * bit random, but this is a relatively frequently called path, so better + * than most. + */ + if (pgstat_need_entry_refs_gc()) + pgstat_gc_entry_refs(); + + /* + * First check the lookup cache hashtable in local memory. If we find a + * match here we can avoid taking locks / causing contention. + */ + if (pgstat_get_entry_ref_cached(key, &entry_ref)) + return entry_ref; + + Assert(entry_ref != NULL); + + /* + * Do a lookup in the hash table first - it's quite likely that the entry + * already exists, and that way we only need a shared lock. + */ + shhashent = dshash_find(pgStatLocal.shared_hash, &key, false); + + if (create && !shhashent) + { + bool shfound; + + /* + * It's possible that somebody created the entry since the above + * lookup. If so, fall through to the same path as if we'd have if it + * already had been created before the dshash_find() calls. + */ + shhashent = dshash_find_or_insert(pgStatLocal.shared_hash, &key, &shfound); + if (!shfound) + { + shheader = pgstat_init_entry(kind, shhashent); + pgstat_acquire_entry_ref(entry_ref, shhashent, shheader); + + if (created_entry != NULL) + *created_entry = true; + + return entry_ref; + } + } + + if (!shhashent) + { + /* + * If we're not creating, delete the reference again. In all + * likelihood it's just a stats lookup - no point wasting memory for a + * shared ref to nothing... + */ + pgstat_release_entry_ref(key, entry_ref, false); + + return NULL; + } + else + { + /* + * Can get here either because dshash_find() found a match, or if + * dshash_find_or_insert() found a concurrently inserted entry. + */ + + if (shhashent->dropped && create) + { + /* + * There are legitimate cases where the old stats entry might not + * yet have been dropped by the time it's reused. The most obvious + * case are replication slot stats, where a new slot can be + * created with the same index just after dropping. But oid + * wraparound can lead to other cases as well. We just reset the + * stats to their plain state. + */ + shheader = pgstat_reinit_entry(kind, shhashent); + pgstat_acquire_entry_ref(entry_ref, shhashent, shheader); + + if (created_entry != NULL) + *created_entry = true; + + return entry_ref; + } + else if (shhashent->dropped) + { + dshash_release_lock(pgStatLocal.shared_hash, shhashent); + pgstat_release_entry_ref(key, entry_ref, false); + + return NULL; + } + else + { + shheader = dsa_get_address(pgStatLocal.dsa, shhashent->body); + pgstat_acquire_entry_ref(entry_ref, shhashent, shheader); + + return entry_ref; + } + } +} + +static void +pgstat_release_entry_ref(PgStat_HashKey key, PgStat_EntryRef *entry_ref, + bool discard_pending) +{ + if (entry_ref && entry_ref->pending) + { + if (discard_pending) + pgstat_delete_pending_entry(entry_ref); + else + elog(ERROR, "releasing ref with pending data"); + } + + if (entry_ref && entry_ref->shared_stats) + { + Assert(entry_ref->shared_stats->magic == 0xdeadbeef); + Assert(entry_ref->pending == NULL); + + /* + * This can't race with another backend looking up the stats entry and + * increasing the refcount because it is not "legal" to create + * additional references to dropped entries. + */ + if (pg_atomic_fetch_sub_u32(&entry_ref->shared_entry->refcount, 1) == 1) + { + PgStatShared_HashEntry *shent; + + /* + * We're the last referrer to this entry, try to drop the shared + * entry. + */ + + /* only dropped entries can reach a 0 refcount */ + Assert(entry_ref->shared_entry->dropped); + + shent = dshash_find(pgStatLocal.shared_hash, + &entry_ref->shared_entry->key, + true); + if (!shent) + elog(ERROR, "could not find just referenced shared stats entry"); + + Assert(pg_atomic_read_u32(&entry_ref->shared_entry->refcount) == 0); + Assert(entry_ref->shared_entry == shent); + + pgstat_free_entry(shent, NULL); + } + } + + if (!pgstat_entry_ref_hash_delete(pgStatEntryRefHash, key)) + elog(ERROR, "entry ref vanished before deletion"); + + if (entry_ref) + pfree(entry_ref); +} + +bool +pgstat_lock_entry(PgStat_EntryRef *entry_ref, bool nowait) +{ + LWLock *lock = &entry_ref->shared_stats->lock; + + if (nowait) + return LWLockConditionalAcquire(lock, LW_EXCLUSIVE); + + LWLockAcquire(lock, LW_EXCLUSIVE); + return true; +} + +/* + * Separate from pgstat_lock_entry() as most callers will need to lock + * exclusively. + */ +bool +pgstat_lock_entry_shared(PgStat_EntryRef *entry_ref, bool nowait) +{ + LWLock *lock = &entry_ref->shared_stats->lock; + + if (nowait) + return LWLockConditionalAcquire(lock, LW_SHARED); + + LWLockAcquire(lock, LW_SHARED); + return true; +} + +void +pgstat_unlock_entry(PgStat_EntryRef *entry_ref) +{ + LWLockRelease(&entry_ref->shared_stats->lock); +} + +/* + * Helper function to fetch and lock shared stats. + */ +PgStat_EntryRef * +pgstat_get_entry_ref_locked(PgStat_Kind kind, Oid dboid, Oid objoid, + bool nowait) +{ + PgStat_EntryRef *entry_ref; + + /* find shared table stats entry corresponding to the local entry */ + entry_ref = pgstat_get_entry_ref(kind, dboid, objoid, true, NULL); + + /* lock the shared entry to protect the content, skip if failed */ + if (!pgstat_lock_entry(entry_ref, nowait)) + return NULL; + + return entry_ref; +} + +void +pgstat_request_entry_refs_gc(void) +{ + pg_atomic_fetch_add_u64(&pgStatLocal.shmem->gc_request_count, 1); +} + +static bool +pgstat_need_entry_refs_gc(void) +{ + uint64 curage; + + if (!pgStatEntryRefHash) + return false; + + /* should have been initialized when creating pgStatEntryRefHash */ + Assert(pgStatSharedRefAge != 0); + + curage = pg_atomic_read_u64(&pgStatLocal.shmem->gc_request_count); + + return pgStatSharedRefAge != curage; +} + +static void +pgstat_gc_entry_refs(void) +{ + pgstat_entry_ref_hash_iterator i; + PgStat_EntryRefHashEntry *ent; + uint64 curage; + + curage = pg_atomic_read_u64(&pgStatLocal.shmem->gc_request_count); + Assert(curage != 0); + + /* + * Some entries have been dropped. Invalidate cache pointer to them. + */ + pgstat_entry_ref_hash_start_iterate(pgStatEntryRefHash, &i); + while ((ent = pgstat_entry_ref_hash_iterate(pgStatEntryRefHash, &i)) != NULL) + { + PgStat_EntryRef *entry_ref = ent->entry_ref; + + Assert(!entry_ref->shared_stats || + entry_ref->shared_stats->magic == 0xdeadbeef); + + if (!entry_ref->shared_entry->dropped) + continue; + + /* cannot gc shared ref that has pending data */ + if (entry_ref->pending != NULL) + continue; + + pgstat_release_entry_ref(ent->key, entry_ref, false); + } + + pgStatSharedRefAge = curage; +} + +static void +pgstat_release_matching_entry_refs(bool discard_pending, ReleaseMatchCB match, + Datum match_data) +{ + pgstat_entry_ref_hash_iterator i; + PgStat_EntryRefHashEntry *ent; + + if (pgStatEntryRefHash == NULL) + return; + + pgstat_entry_ref_hash_start_iterate(pgStatEntryRefHash, &i); + + while ((ent = pgstat_entry_ref_hash_iterate(pgStatEntryRefHash, &i)) + != NULL) + { + Assert(ent->entry_ref != NULL); + + if (match && !match(ent, match_data)) + continue; + + pgstat_release_entry_ref(ent->key, ent->entry_ref, discard_pending); + } +} + +/* + * Release all local references to shared stats entries. + * + * When a process exits it cannot do so while still holding references onto + * stats entries, otherwise the shared stats entries could never be freed. + */ +static void +pgstat_release_all_entry_refs(bool discard_pending) +{ + if (pgStatEntryRefHash == NULL) + return; + + pgstat_release_matching_entry_refs(discard_pending, NULL, 0); + Assert(pgStatEntryRefHash->members == 0); + pgstat_entry_ref_hash_destroy(pgStatEntryRefHash); + pgStatEntryRefHash = NULL; +} + +static bool +match_db(PgStat_EntryRefHashEntry *ent, Datum match_data) +{ + Oid dboid = DatumGetObjectId(match_data); + + return ent->key.dboid == dboid; +} + +static void +pgstat_release_db_entry_refs(Oid dboid) +{ + pgstat_release_matching_entry_refs( /* discard pending = */ true, + match_db, + ObjectIdGetDatum(dboid)); +} + + +/* ------------------------------------------------------------ + * Dropping and resetting of stats entries + * ------------------------------------------------------------ + */ + +static void +pgstat_free_entry(PgStatShared_HashEntry *shent, dshash_seq_status *hstat) +{ + dsa_pointer pdsa; + + /* + * Fetch dsa pointer before deleting entry - that way we can free the + * memory after releasing the lock. + */ + pdsa = shent->body; + + if (!hstat) + dshash_delete_entry(pgStatLocal.shared_hash, shent); + else + dshash_delete_current(hstat); + + dsa_free(pgStatLocal.dsa, pdsa); +} + +/* + * Helper for both pgstat_drop_database_and_contents() and + * pgstat_drop_entry(). If hstat is non-null delete the shared entry using + * dshash_delete_current(), otherwise use dshash_delete_entry(). In either + * case the entry needs to be already locked. + */ +static bool +pgstat_drop_entry_internal(PgStatShared_HashEntry *shent, + dshash_seq_status *hstat) +{ + Assert(shent->body != InvalidDsaPointer); + + /* should already have released local reference */ + if (pgStatEntryRefHash) + Assert(!pgstat_entry_ref_hash_lookup(pgStatEntryRefHash, shent->key)); + + /* + * Signal that the entry is dropped - this will eventually cause other + * backends to release their references. + */ + if (shent->dropped) + elog(ERROR, "can only drop stats once"); + shent->dropped = true; + + /* release refcount marking entry as not dropped */ + if (pg_atomic_sub_fetch_u32(&shent->refcount, 1) == 0) + { + pgstat_free_entry(shent, hstat); + return true; + } + else + { + if (!hstat) + dshash_release_lock(pgStatLocal.shared_hash, shent); + return false; + } +} + +/* + * Drop stats for the database and all the objects inside that database. + */ +static void +pgstat_drop_database_and_contents(Oid dboid) +{ + dshash_seq_status hstat; + PgStatShared_HashEntry *p; + uint64 not_freed_count = 0; + + Assert(OidIsValid(dboid)); + + Assert(pgStatLocal.shared_hash != NULL); + + /* + * This backend might very well be the only backend holding a reference to + * about-to-be-dropped entries. Ensure that we're not preventing it from + * being cleaned up till later. + * + * Doing this separately from the dshash iteration below avoids having to + * do so while holding a partition lock on the shared hashtable. + */ + pgstat_release_db_entry_refs(dboid); + + /* some of the dshash entries are to be removed, take exclusive lock. */ + dshash_seq_init(&hstat, pgStatLocal.shared_hash, true); + while ((p = dshash_seq_next(&hstat)) != NULL) + { + if (p->dropped) + continue; + + if (p->key.dboid != dboid) + continue; + + if (!pgstat_drop_entry_internal(p, &hstat)) + { + /* + * Even statistics for a dropped database might currently be + * accessed (consider e.g. database stats for pg_stat_database). + */ + not_freed_count++; + } + } + dshash_seq_term(&hstat); + + /* + * If some of the stats data could not be freed, signal the reference + * holders to run garbage collection of their cached pgStatShmLookupCache. + */ + if (not_freed_count > 0) + pgstat_request_entry_refs_gc(); +} + +bool +pgstat_drop_entry(PgStat_Kind kind, Oid dboid, Oid objoid) +{ + PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objoid = objoid}; + PgStatShared_HashEntry *shent; + bool freed = true; + + /* delete local reference */ + if (pgStatEntryRefHash) + { + PgStat_EntryRefHashEntry *lohashent = + pgstat_entry_ref_hash_lookup(pgStatEntryRefHash, key); + + if (lohashent) + pgstat_release_entry_ref(lohashent->key, lohashent->entry_ref, + true); + } + + /* mark entry in shared hashtable as deleted, drop if possible */ + shent = dshash_find(pgStatLocal.shared_hash, &key, true); + if (shent) + { + freed = pgstat_drop_entry_internal(shent, NULL); + + /* + * Database stats contain other stats. Drop those as well when + * dropping the database. XXX: Perhaps this should be done in a + * slightly more principled way? But not obvious what that'd look + * like, and so far this is the only case... + */ + if (key.kind == PGSTAT_KIND_DATABASE) + pgstat_drop_database_and_contents(key.dboid); + } + + return freed; +} + +void +pgstat_drop_all_entries(void) +{ + dshash_seq_status hstat; + PgStatShared_HashEntry *ps; + uint64 not_freed_count = 0; + + dshash_seq_init(&hstat, pgStatLocal.shared_hash, true); + while ((ps = dshash_seq_next(&hstat)) != NULL) + { + if (ps->dropped) + continue; + + if (!pgstat_drop_entry_internal(ps, &hstat)) + not_freed_count++; + } + dshash_seq_term(&hstat); + + if (not_freed_count > 0) + pgstat_request_entry_refs_gc(); +} + +static void +shared_stat_reset_contents(PgStat_Kind kind, PgStatShared_Common *header, + TimestampTz ts) +{ + const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind); + + memset(pgstat_get_entry_data(kind, header), 0, + pgstat_get_entry_len(kind)); + + if (kind_info->reset_timestamp_cb) + kind_info->reset_timestamp_cb(header, ts); +} + +/* + * Reset one variable-numbered stats entry. + */ +void +pgstat_reset_entry(PgStat_Kind kind, Oid dboid, Oid objoid, TimestampTz ts) +{ + PgStat_EntryRef *entry_ref; + + Assert(!pgstat_get_kind_info(kind)->fixed_amount); + + entry_ref = pgstat_get_entry_ref(kind, dboid, objoid, false, NULL); + if (!entry_ref || entry_ref->shared_entry->dropped) + return; + + (void) pgstat_lock_entry(entry_ref, false); + shared_stat_reset_contents(kind, entry_ref->shared_stats, ts); + pgstat_unlock_entry(entry_ref); +} + +/* + * Scan through the shared hashtable of stats, resetting statistics if + * approved by the provided do_reset() function. + */ +void +pgstat_reset_matching_entries(bool (*do_reset) (PgStatShared_HashEntry *, Datum), + Datum match_data, TimestampTz ts) +{ + dshash_seq_status hstat; + PgStatShared_HashEntry *p; + + /* dshash entry is not modified, take shared lock */ + dshash_seq_init(&hstat, pgStatLocal.shared_hash, false); + while ((p = dshash_seq_next(&hstat)) != NULL) + { + PgStatShared_Common *header; + + if (p->dropped) + continue; + + if (!do_reset(p, match_data)) + continue; + + header = dsa_get_address(pgStatLocal.dsa, p->body); + + LWLockAcquire(&header->lock, LW_EXCLUSIVE); + + shared_stat_reset_contents(p->key.kind, header, ts); + + LWLockRelease(&header->lock); + } + dshash_seq_term(&hstat); +} + +static bool +match_kind(PgStatShared_HashEntry *p, Datum match_data) +{ + return p->key.kind == DatumGetInt32(match_data); +} + +void +pgstat_reset_entries_of_kind(PgStat_Kind kind, TimestampTz ts) +{ + pgstat_reset_matching_entries(match_kind, Int32GetDatum(kind), ts); +} + +static void +pgstat_setup_memcxt(void) +{ + if (unlikely(!pgStatSharedRefContext)) + pgStatSharedRefContext = + AllocSetContextCreate(TopMemoryContext, + "PgStat Shared Ref", + ALLOCSET_SMALL_SIZES); + if (unlikely(!pgStatEntryRefHashContext)) + pgStatEntryRefHashContext = + AllocSetContextCreate(TopMemoryContext, + "PgStat Shared Ref Hash", + ALLOCSET_SMALL_SIZES); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/activity/pgstat_slru.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/activity/pgstat_slru.c new file mode 100644 index 00000000000..9e7afdfe2d6 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/activity/pgstat_slru.c @@ -0,0 +1,248 @@ +/* ------------------------------------------------------------------------- + * + * pgstat_slru.c + * Implementation of SLRU statistics. + * + * This file contains the implementation of SLRU statistics. It is kept + * separate from pgstat.c to enforce the line between the statistics access / + * storage implementation and the details about individual types of + * statistics. + * + * Copyright (c) 2001-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/utils/activity/pgstat_slru.c + * ------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "utils/pgstat_internal.h" +#include "utils/timestamp.h" + + +static inline PgStat_SLRUStats *get_slru_entry(int slru_idx); +static void pgstat_reset_slru_counter_internal(int index, TimestampTz ts); + + +/* + * SLRU statistics counts waiting to be flushed out. We assume this variable + * inits to zeroes. Entries are one-to-one with slru_names[]. Changes of + * SLRU counters are reported within critical sections so we use static memory + * in order to avoid memory allocation. + */ +static __thread PgStat_SLRUStats pending_SLRUStats[SLRU_NUM_ELEMENTS]; +__thread bool have_slrustats = false; + + +/* + * Reset counters for a single SLRU. + * + * Permission checking for this function is managed through the normal + * GRANT system. + */ +void +pgstat_reset_slru(const char *name) +{ + TimestampTz ts = GetCurrentTimestamp(); + + Assert(name != NULL); + + pgstat_reset_slru_counter_internal(pgstat_get_slru_index(name), ts); +} + +/* + * SLRU statistics count accumulation functions --- called from slru.c + */ + +void +pgstat_count_slru_page_zeroed(int slru_idx) +{ + get_slru_entry(slru_idx)->blocks_zeroed += 1; +} + +void +pgstat_count_slru_page_hit(int slru_idx) +{ + get_slru_entry(slru_idx)->blocks_hit += 1; +} + +void +pgstat_count_slru_page_exists(int slru_idx) +{ + get_slru_entry(slru_idx)->blocks_exists += 1; +} + +void +pgstat_count_slru_page_read(int slru_idx) +{ + get_slru_entry(slru_idx)->blocks_read += 1; +} + +void +pgstat_count_slru_page_written(int slru_idx) +{ + get_slru_entry(slru_idx)->blocks_written += 1; +} + +void +pgstat_count_slru_flush(int slru_idx) +{ + get_slru_entry(slru_idx)->flush += 1; +} + +void +pgstat_count_slru_truncate(int slru_idx) +{ + get_slru_entry(slru_idx)->truncate += 1; +} + +/* + * Support function for the SQL-callable pgstat* functions. Returns + * a pointer to the slru statistics struct. + */ +PgStat_SLRUStats * +pgstat_fetch_slru(void) +{ + pgstat_snapshot_fixed(PGSTAT_KIND_SLRU); + + return pgStatLocal.snapshot.slru; +} + +/* + * Returns SLRU name for an index. The index may be above SLRU_NUM_ELEMENTS, + * in which case this returns NULL. This allows writing code that does not + * know the number of entries in advance. + */ +const char * +pgstat_get_slru_name(int slru_idx) +{ + if (slru_idx < 0 || slru_idx >= SLRU_NUM_ELEMENTS) + return NULL; + + return slru_names[slru_idx]; +} + +/* + * Determine index of entry for a SLRU with a given name. If there's no exact + * match, returns index of the last "other" entry used for SLRUs defined in + * external projects. + */ +int +pgstat_get_slru_index(const char *name) +{ + int i; + + for (i = 0; i < SLRU_NUM_ELEMENTS; i++) + { + if (strcmp(slru_names[i], name) == 0) + return i; + } + + /* return index of the last entry (which is the "other" one) */ + return (SLRU_NUM_ELEMENTS - 1); +} + +/* + * Flush out locally pending SLRU stats entries + * + * If nowait is true, this function returns false on lock failure. Otherwise + * this function always returns true. + * + * If nowait is true, this function returns true if the lock could not be + * acquired. Otherwise return false. + */ +bool +pgstat_slru_flush(bool nowait) +{ + PgStatShared_SLRU *stats_shmem = &pgStatLocal.shmem->slru; + int i; + + if (!have_slrustats) + return false; + + if (!nowait) + LWLockAcquire(&stats_shmem->lock, LW_EXCLUSIVE); + else if (!LWLockConditionalAcquire(&stats_shmem->lock, LW_EXCLUSIVE)) + return true; + + for (i = 0; i < SLRU_NUM_ELEMENTS; i++) + { + PgStat_SLRUStats *sharedent = &stats_shmem->stats[i]; + PgStat_SLRUStats *pendingent = &pending_SLRUStats[i]; + +#define SLRU_ACC(fld) sharedent->fld += pendingent->fld + SLRU_ACC(blocks_zeroed); + SLRU_ACC(blocks_hit); + SLRU_ACC(blocks_read); + SLRU_ACC(blocks_written); + SLRU_ACC(blocks_exists); + SLRU_ACC(flush); + SLRU_ACC(truncate); +#undef SLRU_ACC + } + + /* done, clear the pending entry */ + MemSet(pending_SLRUStats, 0, sizeof(pending_SLRUStats)); + + LWLockRelease(&stats_shmem->lock); + + have_slrustats = false; + + return false; +} + +void +pgstat_slru_reset_all_cb(TimestampTz ts) +{ + for (int i = 0; i < SLRU_NUM_ELEMENTS; i++) + pgstat_reset_slru_counter_internal(i, ts); +} + +void +pgstat_slru_snapshot_cb(void) +{ + PgStatShared_SLRU *stats_shmem = &pgStatLocal.shmem->slru; + + LWLockAcquire(&stats_shmem->lock, LW_SHARED); + + memcpy(pgStatLocal.snapshot.slru, &stats_shmem->stats, + sizeof(stats_shmem->stats)); + + LWLockRelease(&stats_shmem->lock); +} + +/* + * Returns pointer to entry with counters for given SLRU (based on the name + * stored in SlruCtl as lwlock tranche name). + */ +static inline PgStat_SLRUStats * +get_slru_entry(int slru_idx) +{ + pgstat_assert_is_up(); + + /* + * The postmaster should never register any SLRU statistics counts; if it + * did, the counts would be duplicated into child processes via fork(). + */ + Assert(IsUnderPostmaster || !IsPostmasterEnvironment); + + Assert((slru_idx >= 0) && (slru_idx < SLRU_NUM_ELEMENTS)); + + have_slrustats = true; + + return &pending_SLRUStats[slru_idx]; +} + +static void +pgstat_reset_slru_counter_internal(int index, TimestampTz ts) +{ + PgStatShared_SLRU *stats_shmem = &pgStatLocal.shmem->slru; + + LWLockAcquire(&stats_shmem->lock, LW_EXCLUSIVE); + + memset(&stats_shmem->stats[index], 0, sizeof(PgStat_SLRUStats)); + stats_shmem->stats[index].stat_reset_timestamp = ts; + + LWLockRelease(&stats_shmem->lock); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/activity/pgstat_subscription.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/activity/pgstat_subscription.c new file mode 100644 index 00000000000..084d89c1476 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/activity/pgstat_subscription.c @@ -0,0 +1,114 @@ +/* ------------------------------------------------------------------------- + * + * pgstat_subscription.c + * Implementation of subscription statistics. + * + * This file contains the implementation of subscription statistics. It is kept + * separate from pgstat.c to enforce the line between the statistics access / + * storage implementation and the details about individual types of + * statistics. + * + * Copyright (c) 2001-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/utils/activity/pgstat_subscription.c + * ------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "utils/pgstat_internal.h" + + +/* + * Report a subscription error. + */ +void +pgstat_report_subscription_error(Oid subid, bool is_apply_error) +{ + PgStat_EntryRef *entry_ref; + PgStat_BackendSubEntry *pending; + + entry_ref = pgstat_prep_pending_entry(PGSTAT_KIND_SUBSCRIPTION, + InvalidOid, subid, NULL); + pending = entry_ref->pending; + + if (is_apply_error) + pending->apply_error_count++; + else + pending->sync_error_count++; +} + +/* + * Report creating the subscription. + */ +void +pgstat_create_subscription(Oid subid) +{ + /* Ensures that stats are dropped if transaction rolls back */ + pgstat_create_transactional(PGSTAT_KIND_SUBSCRIPTION, + InvalidOid, subid); + + /* Create and initialize the subscription stats entry */ + pgstat_get_entry_ref(PGSTAT_KIND_SUBSCRIPTION, InvalidOid, subid, + true, NULL); + pgstat_reset_entry(PGSTAT_KIND_SUBSCRIPTION, InvalidOid, subid, 0); +} + +/* + * Report dropping the subscription. + * + * Ensures that stats are dropped if transaction commits. + */ +void +pgstat_drop_subscription(Oid subid) +{ + pgstat_drop_transactional(PGSTAT_KIND_SUBSCRIPTION, + InvalidOid, subid); +} + +/* + * Support function for the SQL-callable pgstat* functions. Returns + * the collected statistics for one subscription or NULL. + */ +PgStat_StatSubEntry * +pgstat_fetch_stat_subscription(Oid subid) +{ + return (PgStat_StatSubEntry *) + pgstat_fetch_entry(PGSTAT_KIND_SUBSCRIPTION, InvalidOid, subid); +} + +/* + * Flush out pending stats for the entry + * + * If nowait is true, this function returns false if lock could not + * immediately acquired, otherwise true is returned. + */ +bool +pgstat_subscription_flush_cb(PgStat_EntryRef *entry_ref, bool nowait) +{ + PgStat_BackendSubEntry *localent; + PgStatShared_Subscription *shsubent; + + localent = (PgStat_BackendSubEntry *) entry_ref->pending; + shsubent = (PgStatShared_Subscription *) entry_ref->shared_stats; + + /* localent always has non-zero content */ + + if (!pgstat_lock_entry(entry_ref, nowait)) + return false; + +#define SUB_ACC(fld) shsubent->stats.fld += localent->fld + SUB_ACC(apply_error_count); + SUB_ACC(sync_error_count); +#undef SUB_ACC + + pgstat_unlock_entry(entry_ref); + return true; +} + +void +pgstat_subscription_reset_timestamp_cb(PgStatShared_Common *header, TimestampTz ts) +{ + ((PgStatShared_Subscription *) header)->stats.stat_reset_timestamp = ts; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/activity/pgstat_wal.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/activity/pgstat_wal.c new file mode 100644 index 00000000000..d8c2a51349a --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/activity/pgstat_wal.c @@ -0,0 +1,186 @@ +/* ------------------------------------------------------------------------- + * + * pgstat_wal.c + * Implementation of WAL statistics. + * + * This file contains the implementation of WAL statistics. It is kept + * separate from pgstat.c to enforce the line between the statistics access / + * storage implementation and the details about individual types of + * statistics. + * + * Copyright (c) 2001-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/utils/activity/pgstat_wal.c + * ------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "utils/pgstat_internal.h" +#include "executor/instrument.h" + + +__thread PgStat_PendingWalStats PendingWalStats = {0}; + +/* + * WAL usage counters saved from pgWalUsage at the previous call to + * pgstat_report_wal(). This is used to calculate how much WAL usage + * happens between pgstat_report_wal() calls, by subtracting + * the previous counters from the current ones. + */ +static __thread WalUsage prevWalUsage; + + +/* + * Calculate how much WAL usage counters have increased and update + * shared WAL and IO statistics. + * + * Must be called by processes that generate WAL, that do not call + * pgstat_report_stat(), like walwriter. + * + * "force" set to true ensures that the statistics are flushed; note that + * this needs to acquire the pgstat shmem LWLock, waiting on it. When + * set to false, the statistics may not be flushed if the lock could not + * be acquired. + */ +void +pgstat_report_wal(bool force) +{ + bool nowait; + + /* like in pgstat.c, don't wait for lock acquisition when !force */ + nowait = !force; + + /* flush wal stats */ + pgstat_flush_wal(nowait); + + /* flush IO stats */ + pgstat_flush_io(nowait); +} + +/* + * Support function for the SQL-callable pgstat* functions. Returns + * a pointer to the WAL statistics struct. + */ +PgStat_WalStats * +pgstat_fetch_stat_wal(void) +{ + pgstat_snapshot_fixed(PGSTAT_KIND_WAL); + + return &pgStatLocal.snapshot.wal; +} + +/* + * Calculate how much WAL usage counters have increased by subtracting the + * previous counters from the current ones. + * + * If nowait is true, this function returns true if the lock could not be + * acquired. Otherwise return false. + */ +bool +pgstat_flush_wal(bool nowait) +{ + PgStatShared_Wal *stats_shmem = &pgStatLocal.shmem->wal; + WalUsage wal_usage_diff = {0}; + + Assert(IsUnderPostmaster || !IsPostmasterEnvironment); + Assert(pgStatLocal.shmem != NULL && + !pgStatLocal.shmem->is_shutdown); + + /* + * This function can be called even if nothing at all has happened. Avoid + * taking lock for nothing in that case. + */ + if (!pgstat_have_pending_wal()) + return false; + + /* + * We don't update the WAL usage portion of the local WalStats elsewhere. + * Calculate how much WAL usage counters were increased by subtracting the + * previous counters from the current ones. + */ + WalUsageAccumDiff(&wal_usage_diff, &pgWalUsage, &prevWalUsage); + + if (!nowait) + LWLockAcquire(&stats_shmem->lock, LW_EXCLUSIVE); + else if (!LWLockConditionalAcquire(&stats_shmem->lock, LW_EXCLUSIVE)) + return true; + +#define WALSTAT_ACC(fld, var_to_add) \ + (stats_shmem->stats.fld += var_to_add.fld) +#define WALSTAT_ACC_INSTR_TIME(fld) \ + (stats_shmem->stats.fld += INSTR_TIME_GET_MICROSEC(PendingWalStats.fld)) + WALSTAT_ACC(wal_records, wal_usage_diff); + WALSTAT_ACC(wal_fpi, wal_usage_diff); + WALSTAT_ACC(wal_bytes, wal_usage_diff); + WALSTAT_ACC(wal_buffers_full, PendingWalStats); + WALSTAT_ACC(wal_write, PendingWalStats); + WALSTAT_ACC(wal_sync, PendingWalStats); + WALSTAT_ACC_INSTR_TIME(wal_write_time); + WALSTAT_ACC_INSTR_TIME(wal_sync_time); +#undef WALSTAT_ACC_INSTR_TIME +#undef WALSTAT_ACC + + LWLockRelease(&stats_shmem->lock); + + /* + * Save the current counters for the subsequent calculation of WAL usage. + */ + prevWalUsage = pgWalUsage; + + /* + * Clear out the statistics buffer, so it can be re-used. + */ + MemSet(&PendingWalStats, 0, sizeof(PendingWalStats)); + + return false; +} + +void +pgstat_init_wal(void) +{ + /* + * Initialize prevWalUsage with pgWalUsage so that pgstat_flush_wal() can + * calculate how much pgWalUsage counters are increased by subtracting + * prevWalUsage from pgWalUsage. + */ + prevWalUsage = pgWalUsage; +} + +/* + * To determine whether any WAL activity has occurred since last time, not + * only the number of generated WAL records but also the numbers of WAL + * writes and syncs need to be checked. Because even transaction that + * generates no WAL records can write or sync WAL data when flushing the + * data pages. + */ +bool +pgstat_have_pending_wal(void) +{ + return pgWalUsage.wal_records != prevWalUsage.wal_records || + PendingWalStats.wal_write != 0 || + PendingWalStats.wal_sync != 0; +} + +void +pgstat_wal_reset_all_cb(TimestampTz ts) +{ + PgStatShared_Wal *stats_shmem = &pgStatLocal.shmem->wal; + + LWLockAcquire(&stats_shmem->lock, LW_EXCLUSIVE); + memset(&stats_shmem->stats, 0, sizeof(stats_shmem->stats)); + stats_shmem->stats.stat_reset_timestamp = ts; + LWLockRelease(&stats_shmem->lock); +} + +void +pgstat_wal_snapshot_cb(void) +{ + PgStatShared_Wal *stats_shmem = &pgStatLocal.shmem->wal; + + LWLockAcquire(&stats_shmem->lock, LW_SHARED); + memcpy(&pgStatLocal.snapshot.wal, &stats_shmem->stats, + sizeof(pgStatLocal.snapshot.wal)); + LWLockRelease(&stats_shmem->lock); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/activity/pgstat_xact.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/activity/pgstat_xact.c new file mode 100644 index 00000000000..c272ba6823b --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/activity/pgstat_xact.c @@ -0,0 +1,383 @@ +/* ------------------------------------------------------------------------- + * + * pgstat_xact.c + * Transactional integration for the cumulative statistics system. + * + * Copyright (c) 2001-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/utils/activity/pgstat_xact.c + * ------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/transam.h" +#include "access/xact.h" +#include "pgstat.h" +#include "utils/memutils.h" +#include "utils/pgstat_internal.h" + + +typedef struct PgStat_PendingDroppedStatsItem +{ + xl_xact_stats_item item; + bool is_create; + dlist_node node; +} PgStat_PendingDroppedStatsItem; + + +static void AtEOXact_PgStat_DroppedStats(PgStat_SubXactStatus *xact_state, bool isCommit); +static void AtEOSubXact_PgStat_DroppedStats(PgStat_SubXactStatus *xact_state, + bool isCommit, int nestDepth); + +static __thread PgStat_SubXactStatus *pgStatXactStack = NULL; + + +/* + * Called from access/transam/xact.c at top-level transaction commit/abort. + */ +void +AtEOXact_PgStat(bool isCommit, bool parallel) +{ + PgStat_SubXactStatus *xact_state; + + AtEOXact_PgStat_Database(isCommit, parallel); + + /* handle transactional stats information */ + xact_state = pgStatXactStack; + if (xact_state != NULL) + { + Assert(xact_state->nest_level == 1); + Assert(xact_state->prev == NULL); + + AtEOXact_PgStat_Relations(xact_state, isCommit); + AtEOXact_PgStat_DroppedStats(xact_state, isCommit); + } + pgStatXactStack = NULL; + + /* Make sure any stats snapshot is thrown away */ + pgstat_clear_snapshot(); +} + +/* + * When committing, drop stats for objects dropped in the transaction. When + * aborting, drop stats for objects created in the transaction. + */ +static void +AtEOXact_PgStat_DroppedStats(PgStat_SubXactStatus *xact_state, bool isCommit) +{ + dlist_mutable_iter iter; + int not_freed_count = 0; + + if (dclist_count(&xact_state->pending_drops) == 0) + return; + + dclist_foreach_modify(iter, &xact_state->pending_drops) + { + PgStat_PendingDroppedStatsItem *pending = + dclist_container(PgStat_PendingDroppedStatsItem, node, iter.cur); + xl_xact_stats_item *it = &pending->item; + + if (isCommit && !pending->is_create) + { + /* + * Transaction that dropped an object committed. Drop the stats + * too. + */ + if (!pgstat_drop_entry(it->kind, it->dboid, it->objoid)) + not_freed_count++; + } + else if (!isCommit && pending->is_create) + { + /* + * Transaction that created an object aborted. Drop the stats + * associated with the object. + */ + if (!pgstat_drop_entry(it->kind, it->dboid, it->objoid)) + not_freed_count++; + } + + dclist_delete_from(&xact_state->pending_drops, &pending->node); + pfree(pending); + } + + if (not_freed_count > 0) + pgstat_request_entry_refs_gc(); +} + +/* + * Called from access/transam/xact.c at subtransaction commit/abort. + */ +void +AtEOSubXact_PgStat(bool isCommit, int nestDepth) +{ + PgStat_SubXactStatus *xact_state; + + /* merge the sub-transaction's transactional stats into the parent */ + xact_state = pgStatXactStack; + if (xact_state != NULL && + xact_state->nest_level >= nestDepth) + { + /* delink xact_state from stack immediately to simplify reuse case */ + pgStatXactStack = xact_state->prev; + + AtEOSubXact_PgStat_Relations(xact_state, isCommit, nestDepth); + AtEOSubXact_PgStat_DroppedStats(xact_state, isCommit, nestDepth); + + pfree(xact_state); + } +} + +/* + * Like AtEOXact_PgStat_DroppedStats(), but for subtransactions. + */ +static void +AtEOSubXact_PgStat_DroppedStats(PgStat_SubXactStatus *xact_state, + bool isCommit, int nestDepth) +{ + PgStat_SubXactStatus *parent_xact_state; + dlist_mutable_iter iter; + int not_freed_count = 0; + + if (dclist_count(&xact_state->pending_drops) == 0) + return; + + parent_xact_state = pgstat_get_xact_stack_level(nestDepth - 1); + + dclist_foreach_modify(iter, &xact_state->pending_drops) + { + PgStat_PendingDroppedStatsItem *pending = + dclist_container(PgStat_PendingDroppedStatsItem, node, iter.cur); + xl_xact_stats_item *it = &pending->item; + + dclist_delete_from(&xact_state->pending_drops, &pending->node); + + if (!isCommit && pending->is_create) + { + /* + * Subtransaction creating a new stats object aborted. Drop the + * stats object. + */ + if (!pgstat_drop_entry(it->kind, it->dboid, it->objoid)) + not_freed_count++; + pfree(pending); + } + else if (isCommit) + { + /* + * Subtransaction dropping a stats object committed. Can't yet + * remove the stats object, the surrounding transaction might + * still abort. Pass it on to the parent. + */ + dclist_push_tail(&parent_xact_state->pending_drops, &pending->node); + } + else + { + pfree(pending); + } + } + + Assert(dclist_count(&xact_state->pending_drops) == 0); + if (not_freed_count > 0) + pgstat_request_entry_refs_gc(); +} + +/* + * Save the transactional stats state at 2PC transaction prepare. + */ +void +AtPrepare_PgStat(void) +{ + PgStat_SubXactStatus *xact_state; + + xact_state = pgStatXactStack; + if (xact_state != NULL) + { + Assert(xact_state->nest_level == 1); + Assert(xact_state->prev == NULL); + + AtPrepare_PgStat_Relations(xact_state); + } +} + +/* + * Clean up after successful PREPARE. + * + * Note: AtEOXact_PgStat is not called during PREPARE. + */ +void +PostPrepare_PgStat(void) +{ + PgStat_SubXactStatus *xact_state; + + /* + * We don't bother to free any of the transactional state, since it's all + * in TopTransactionContext and will go away anyway. + */ + xact_state = pgStatXactStack; + if (xact_state != NULL) + { + Assert(xact_state->nest_level == 1); + Assert(xact_state->prev == NULL); + + PostPrepare_PgStat_Relations(xact_state); + } + pgStatXactStack = NULL; + + /* Make sure any stats snapshot is thrown away */ + pgstat_clear_snapshot(); +} + +/* + * Ensure (sub)transaction stack entry for the given nest_level exists, adding + * it if needed. + */ +PgStat_SubXactStatus * +pgstat_get_xact_stack_level(int nest_level) +{ + PgStat_SubXactStatus *xact_state; + + xact_state = pgStatXactStack; + if (xact_state == NULL || xact_state->nest_level != nest_level) + { + xact_state = (PgStat_SubXactStatus *) + MemoryContextAlloc(TopTransactionContext, + sizeof(PgStat_SubXactStatus)); + dclist_init(&xact_state->pending_drops); + xact_state->nest_level = nest_level; + xact_state->prev = pgStatXactStack; + xact_state->first = NULL; + pgStatXactStack = xact_state; + } + return xact_state; +} + +/* + * Get stat items that need to be dropped at commit / abort. + * + * When committing, stats for objects that have been dropped in the + * transaction are returned. When aborting, stats for newly created objects are + * returned. + * + * Used by COMMIT / ABORT and 2PC PREPARE processing when building their + * respective WAL records, to ensure stats are dropped in case of a crash / on + * standbys. + * + * The list of items is allocated in CurrentMemoryContext and must be freed by + * the caller (directly or via memory context reset). + */ +int +pgstat_get_transactional_drops(bool isCommit, xl_xact_stats_item **items) +{ + PgStat_SubXactStatus *xact_state = pgStatXactStack; + int nitems = 0; + dlist_iter iter; + + if (xact_state == NULL) + return 0; + + /* + * We expect to be called for subtransaction abort (which logs a WAL + * record), but not for subtransaction commit (which doesn't). + */ + Assert(!isCommit || xact_state->nest_level == 1); + Assert(!isCommit || xact_state->prev == NULL); + + *items = palloc(dclist_count(&xact_state->pending_drops) + * sizeof(xl_xact_stats_item)); + + dclist_foreach(iter, &xact_state->pending_drops) + { + PgStat_PendingDroppedStatsItem *pending = + dclist_container(PgStat_PendingDroppedStatsItem, node, iter.cur); + + if (isCommit && pending->is_create) + continue; + if (!isCommit && !pending->is_create) + continue; + + Assert(nitems < dclist_count(&xact_state->pending_drops)); + (*items)[nitems++] = pending->item; + } + + return nitems; +} + +/* + * Execute scheduled drops post-commit. Called from xact_redo_commit() / + * xact_redo_abort() during recovery, and from FinishPreparedTransaction() + * during normal 2PC COMMIT/ABORT PREPARED processing. + */ +void +pgstat_execute_transactional_drops(int ndrops, struct xl_xact_stats_item *items, bool is_redo) +{ + int not_freed_count = 0; + + if (ndrops == 0) + return; + + for (int i = 0; i < ndrops; i++) + { + xl_xact_stats_item *it = &items[i]; + + if (!pgstat_drop_entry(it->kind, it->dboid, it->objoid)) + not_freed_count++; + } + + if (not_freed_count > 0) + pgstat_request_entry_refs_gc(); +} + +static void +create_drop_transactional_internal(PgStat_Kind kind, Oid dboid, Oid objoid, bool is_create) +{ + int nest_level = GetCurrentTransactionNestLevel(); + PgStat_SubXactStatus *xact_state; + PgStat_PendingDroppedStatsItem *drop = (PgStat_PendingDroppedStatsItem *) + MemoryContextAlloc(TopTransactionContext, sizeof(PgStat_PendingDroppedStatsItem)); + + xact_state = pgstat_get_xact_stack_level(nest_level); + + drop->is_create = is_create; + drop->item.kind = kind; + drop->item.dboid = dboid; + drop->item.objoid = objoid; + + dclist_push_tail(&xact_state->pending_drops, &drop->node); +} + +/* + * Create a stats entry for a newly created database object in a transactional + * manner. + * + * I.e. if the current (sub-)transaction aborts, the stats entry will also be + * dropped. + */ +void +pgstat_create_transactional(PgStat_Kind kind, Oid dboid, Oid objoid) +{ + if (pgstat_get_entry_ref(kind, dboid, objoid, false, NULL)) + { + ereport(WARNING, + errmsg("resetting existing statistics for kind %s, db=%u, oid=%u", + (pgstat_get_kind_info(kind))->name, dboid, objoid)); + + pgstat_reset(kind, dboid, objoid); + } + + create_drop_transactional_internal(kind, dboid, objoid, /* create */ true); +} + +/* + * Drop a stats entry for a just dropped database object in a transactional + * manner. + * + * I.e. if the current (sub-)transaction aborts, the stats entry will stay + * alive. + */ +void +pgstat_drop_transactional(PgStat_Kind kind, Oid dboid, Oid objoid) +{ + create_drop_transactional_internal(kind, dboid, objoid, /* create */ false); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/activity/wait_event.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/activity/wait_event.c new file mode 100644 index 00000000000..3e41319878a --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/activity/wait_event.c @@ -0,0 +1,767 @@ +/* ---------- + * wait_event.c + * Wait event reporting infrastructure. + * + * Copyright (c) 2001-2023, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/utils/activity/wait_event.c + * + * NOTES + * + * To make pgstat_report_wait_start() and pgstat_report_wait_end() as + * lightweight as possible, they do not check if shared memory (MyProc + * specifically, where the wait event is stored) is already available. Instead + * we initially set my_wait_event_info to a process local variable, which then + * is redirected to shared memory using pgstat_set_wait_event_storage(). For + * the same reason pgstat_track_activities is not checked - the check adds + * more work than it saves. + * + * ---------- + */ +#include "postgres.h" + +#include "storage/lmgr.h" /* for GetLockNameFromTagType */ +#include "storage/lwlock.h" /* for GetLWLockIdentifier */ +#include "utils/wait_event.h" + + +static const char *pgstat_get_wait_activity(WaitEventActivity w); +static const char *pgstat_get_wait_client(WaitEventClient w); +static const char *pgstat_get_wait_ipc(WaitEventIPC w); +static const char *pgstat_get_wait_timeout(WaitEventTimeout w); +static const char *pgstat_get_wait_io(WaitEventIO w); + + +static __thread uint32 local_my_wait_event_info; +__thread uint32 *my_wait_event_info; void my_wait_event_info_init() { my_wait_event_info = &local_my_wait_event_info; } + + +/* + * Configure wait event reporting to report wait events to *wait_event_info. + * *wait_event_info needs to be valid until pgstat_reset_wait_event_storage() + * is called. + * + * Expected to be called during backend startup, to point my_wait_event_info + * into shared memory. + */ +void +pgstat_set_wait_event_storage(uint32 *wait_event_info) +{ + my_wait_event_info = wait_event_info; +} + +/* + * Reset wait event storage location. + * + * Expected to be called during backend shutdown, before the location set up + * pgstat_set_wait_event_storage() becomes invalid. + */ +void +pgstat_reset_wait_event_storage(void) +{ + my_wait_event_info = &local_my_wait_event_info; +} + +/* ---------- + * pgstat_get_wait_event_type() - + * + * Return a string representing the current wait event type, backend is + * waiting on. + */ +const char * +pgstat_get_wait_event_type(uint32 wait_event_info) +{ + uint32 classId; + const char *event_type; + + /* report process as not waiting. */ + if (wait_event_info == 0) + return NULL; + + classId = wait_event_info & 0xFF000000; + + switch (classId) + { + case PG_WAIT_LWLOCK: + event_type = "LWLock"; + break; + case PG_WAIT_LOCK: + event_type = "Lock"; + break; + case PG_WAIT_BUFFER_PIN: + event_type = "BufferPin"; + break; + case PG_WAIT_ACTIVITY: + event_type = "Activity"; + break; + case PG_WAIT_CLIENT: + event_type = "Client"; + break; + case PG_WAIT_EXTENSION: + event_type = "Extension"; + break; + case PG_WAIT_IPC: + event_type = "IPC"; + break; + case PG_WAIT_TIMEOUT: + event_type = "Timeout"; + break; + case PG_WAIT_IO: + event_type = "IO"; + break; + default: + event_type = "???"; + break; + } + + return event_type; +} + +/* ---------- + * pgstat_get_wait_event() - + * + * Return a string representing the current wait event, backend is + * waiting on. + */ +const char * +pgstat_get_wait_event(uint32 wait_event_info) +{ + uint32 classId; + uint16 eventId; + const char *event_name; + + /* report process as not waiting. */ + if (wait_event_info == 0) + return NULL; + + classId = wait_event_info & 0xFF000000; + eventId = wait_event_info & 0x0000FFFF; + + switch (classId) + { + case PG_WAIT_LWLOCK: + event_name = GetLWLockIdentifier(classId, eventId); + break; + case PG_WAIT_LOCK: + event_name = GetLockNameFromTagType(eventId); + break; + case PG_WAIT_BUFFER_PIN: + event_name = "BufferPin"; + break; + case PG_WAIT_ACTIVITY: + { + WaitEventActivity w = (WaitEventActivity) wait_event_info; + + event_name = pgstat_get_wait_activity(w); + break; + } + case PG_WAIT_CLIENT: + { + WaitEventClient w = (WaitEventClient) wait_event_info; + + event_name = pgstat_get_wait_client(w); + break; + } + case PG_WAIT_EXTENSION: + event_name = "Extension"; + break; + case PG_WAIT_IPC: + { + WaitEventIPC w = (WaitEventIPC) wait_event_info; + + event_name = pgstat_get_wait_ipc(w); + break; + } + case PG_WAIT_TIMEOUT: + { + WaitEventTimeout w = (WaitEventTimeout) wait_event_info; + + event_name = pgstat_get_wait_timeout(w); + break; + } + case PG_WAIT_IO: + { + WaitEventIO w = (WaitEventIO) wait_event_info; + + event_name = pgstat_get_wait_io(w); + break; + } + default: + event_name = "unknown wait event"; + break; + } + + return event_name; +} + +/* ---------- + * pgstat_get_wait_activity() - + * + * Convert WaitEventActivity to string. + * ---------- + */ +static const char * +pgstat_get_wait_activity(WaitEventActivity w) +{ + const char *event_name = "unknown wait event"; + + switch (w) + { + case WAIT_EVENT_ARCHIVER_MAIN: + event_name = "ArchiverMain"; + break; + case WAIT_EVENT_AUTOVACUUM_MAIN: + event_name = "AutoVacuumMain"; + break; + case WAIT_EVENT_BGWRITER_HIBERNATE: + event_name = "BgWriterHibernate"; + break; + case WAIT_EVENT_BGWRITER_MAIN: + event_name = "BgWriterMain"; + break; + case WAIT_EVENT_CHECKPOINTER_MAIN: + event_name = "CheckpointerMain"; + break; + case WAIT_EVENT_LOGICAL_APPLY_MAIN: + event_name = "LogicalApplyMain"; + break; + case WAIT_EVENT_LOGICAL_LAUNCHER_MAIN: + event_name = "LogicalLauncherMain"; + break; + case WAIT_EVENT_LOGICAL_PARALLEL_APPLY_MAIN: + event_name = "LogicalParallelApplyMain"; + break; + case WAIT_EVENT_RECOVERY_WAL_STREAM: + event_name = "RecoveryWalStream"; + break; + case WAIT_EVENT_SYSLOGGER_MAIN: + event_name = "SysLoggerMain"; + break; + case WAIT_EVENT_WAL_RECEIVER_MAIN: + event_name = "WalReceiverMain"; + break; + case WAIT_EVENT_WAL_SENDER_MAIN: + event_name = "WalSenderMain"; + break; + case WAIT_EVENT_WAL_WRITER_MAIN: + event_name = "WalWriterMain"; + break; + /* no default case, so that compiler will warn */ + } + + return event_name; +} + +/* ---------- + * pgstat_get_wait_client() - + * + * Convert WaitEventClient to string. + * ---------- + */ +static const char * +pgstat_get_wait_client(WaitEventClient w) +{ + const char *event_name = "unknown wait event"; + + switch (w) + { + case WAIT_EVENT_CLIENT_READ: + event_name = "ClientRead"; + break; + case WAIT_EVENT_CLIENT_WRITE: + event_name = "ClientWrite"; + break; + case WAIT_EVENT_GSS_OPEN_SERVER: + event_name = "GSSOpenServer"; + break; + case WAIT_EVENT_LIBPQWALRECEIVER_CONNECT: + event_name = "LibPQWalReceiverConnect"; + break; + case WAIT_EVENT_LIBPQWALRECEIVER_RECEIVE: + event_name = "LibPQWalReceiverReceive"; + break; + case WAIT_EVENT_SSL_OPEN_SERVER: + event_name = "SSLOpenServer"; + break; + case WAIT_EVENT_WAL_SENDER_WAIT_WAL: + event_name = "WalSenderWaitForWAL"; + break; + case WAIT_EVENT_WAL_SENDER_WRITE_DATA: + event_name = "WalSenderWriteData"; + break; + /* no default case, so that compiler will warn */ + } + + return event_name; +} + +/* ---------- + * pgstat_get_wait_ipc() - + * + * Convert WaitEventIPC to string. + * ---------- + */ +static const char * +pgstat_get_wait_ipc(WaitEventIPC w) +{ + const char *event_name = "unknown wait event"; + + switch (w) + { + case WAIT_EVENT_APPEND_READY: + event_name = "AppendReady"; + break; + case WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND: + event_name = "ArchiveCleanupCommand"; + break; + case WAIT_EVENT_ARCHIVE_COMMAND: + event_name = "ArchiveCommand"; + break; + case WAIT_EVENT_BACKEND_TERMINATION: + event_name = "BackendTermination"; + break; + case WAIT_EVENT_BACKUP_WAIT_WAL_ARCHIVE: + event_name = "BackupWaitWalArchive"; + break; + case WAIT_EVENT_BGWORKER_SHUTDOWN: + event_name = "BgWorkerShutdown"; + break; + case WAIT_EVENT_BGWORKER_STARTUP: + event_name = "BgWorkerStartup"; + break; + case WAIT_EVENT_BTREE_PAGE: + event_name = "BtreePage"; + break; + case WAIT_EVENT_BUFFER_IO: + event_name = "BufferIO"; + break; + case WAIT_EVENT_CHECKPOINT_DONE: + event_name = "CheckpointDone"; + break; + case WAIT_EVENT_CHECKPOINT_START: + event_name = "CheckpointStart"; + break; + case WAIT_EVENT_EXECUTE_GATHER: + event_name = "ExecuteGather"; + break; + case WAIT_EVENT_HASH_BATCH_ALLOCATE: + event_name = "HashBatchAllocate"; + break; + case WAIT_EVENT_HASH_BATCH_ELECT: + event_name = "HashBatchElect"; + break; + case WAIT_EVENT_HASH_BATCH_LOAD: + event_name = "HashBatchLoad"; + break; + case WAIT_EVENT_HASH_BUILD_ALLOCATE: + event_name = "HashBuildAllocate"; + break; + case WAIT_EVENT_HASH_BUILD_ELECT: + event_name = "HashBuildElect"; + break; + case WAIT_EVENT_HASH_BUILD_HASH_INNER: + event_name = "HashBuildHashInner"; + break; + case WAIT_EVENT_HASH_BUILD_HASH_OUTER: + event_name = "HashBuildHashOuter"; + break; + case WAIT_EVENT_HASH_GROW_BATCHES_DECIDE: + event_name = "HashGrowBatchesDecide"; + break; + case WAIT_EVENT_HASH_GROW_BATCHES_ELECT: + event_name = "HashGrowBatchesElect"; + break; + case WAIT_EVENT_HASH_GROW_BATCHES_FINISH: + event_name = "HashGrowBatchesFinish"; + break; + case WAIT_EVENT_HASH_GROW_BATCHES_REALLOCATE: + event_name = "HashGrowBatchesReallocate"; + break; + case WAIT_EVENT_HASH_GROW_BATCHES_REPARTITION: + event_name = "HashGrowBatchesRepartition"; + break; + case WAIT_EVENT_HASH_GROW_BUCKETS_ELECT: + event_name = "HashGrowBucketsElect"; + break; + case WAIT_EVENT_HASH_GROW_BUCKETS_REALLOCATE: + event_name = "HashGrowBucketsReallocate"; + break; + case WAIT_EVENT_HASH_GROW_BUCKETS_REINSERT: + event_name = "HashGrowBucketsReinsert"; + break; + case WAIT_EVENT_LOGICAL_APPLY_SEND_DATA: + event_name = "LogicalApplySendData"; + break; + case WAIT_EVENT_LOGICAL_PARALLEL_APPLY_STATE_CHANGE: + event_name = "LogicalParallelApplyStateChange"; + break; + case WAIT_EVENT_LOGICAL_SYNC_DATA: + event_name = "LogicalSyncData"; + break; + case WAIT_EVENT_LOGICAL_SYNC_STATE_CHANGE: + event_name = "LogicalSyncStateChange"; + break; + case WAIT_EVENT_MQ_INTERNAL: + event_name = "MessageQueueInternal"; + break; + case WAIT_EVENT_MQ_PUT_MESSAGE: + event_name = "MessageQueuePutMessage"; + break; + case WAIT_EVENT_MQ_RECEIVE: + event_name = "MessageQueueReceive"; + break; + case WAIT_EVENT_MQ_SEND: + event_name = "MessageQueueSend"; + break; + case WAIT_EVENT_PARALLEL_BITMAP_SCAN: + event_name = "ParallelBitmapScan"; + break; + case WAIT_EVENT_PARALLEL_CREATE_INDEX_SCAN: + event_name = "ParallelCreateIndexScan"; + break; + case WAIT_EVENT_PARALLEL_FINISH: + event_name = "ParallelFinish"; + break; + case WAIT_EVENT_PROCARRAY_GROUP_UPDATE: + event_name = "ProcArrayGroupUpdate"; + break; + case WAIT_EVENT_PROC_SIGNAL_BARRIER: + event_name = "ProcSignalBarrier"; + break; + case WAIT_EVENT_PROMOTE: + event_name = "Promote"; + break; + case WAIT_EVENT_RECOVERY_CONFLICT_SNAPSHOT: + event_name = "RecoveryConflictSnapshot"; + break; + case WAIT_EVENT_RECOVERY_CONFLICT_TABLESPACE: + event_name = "RecoveryConflictTablespace"; + break; + case WAIT_EVENT_RECOVERY_END_COMMAND: + event_name = "RecoveryEndCommand"; + break; + case WAIT_EVENT_RECOVERY_PAUSE: + event_name = "RecoveryPause"; + break; + case WAIT_EVENT_REPLICATION_ORIGIN_DROP: + event_name = "ReplicationOriginDrop"; + break; + case WAIT_EVENT_REPLICATION_SLOT_DROP: + event_name = "ReplicationSlotDrop"; + break; + case WAIT_EVENT_RESTORE_COMMAND: + event_name = "RestoreCommand"; + break; + case WAIT_EVENT_SAFE_SNAPSHOT: + event_name = "SafeSnapshot"; + break; + case WAIT_EVENT_SYNC_REP: + event_name = "SyncRep"; + break; + case WAIT_EVENT_WAL_RECEIVER_EXIT: + event_name = "WalReceiverExit"; + break; + case WAIT_EVENT_WAL_RECEIVER_WAIT_START: + event_name = "WalReceiverWaitStart"; + break; + case WAIT_EVENT_XACT_GROUP_UPDATE: + event_name = "XactGroupUpdate"; + break; + /* no default case, so that compiler will warn */ + } + + return event_name; +} + +/* ---------- + * pgstat_get_wait_timeout() - + * + * Convert WaitEventTimeout to string. + * ---------- + */ +static const char * +pgstat_get_wait_timeout(WaitEventTimeout w) +{ + const char *event_name = "unknown wait event"; + + switch (w) + { + case WAIT_EVENT_BASE_BACKUP_THROTTLE: + event_name = "BaseBackupThrottle"; + break; + case WAIT_EVENT_CHECKPOINT_WRITE_DELAY: + event_name = "CheckpointWriteDelay"; + break; + case WAIT_EVENT_PG_SLEEP: + event_name = "PgSleep"; + break; + case WAIT_EVENT_RECOVERY_APPLY_DELAY: + event_name = "RecoveryApplyDelay"; + break; + case WAIT_EVENT_RECOVERY_RETRIEVE_RETRY_INTERVAL: + event_name = "RecoveryRetrieveRetryInterval"; + break; + case WAIT_EVENT_REGISTER_SYNC_REQUEST: + event_name = "RegisterSyncRequest"; + break; + case WAIT_EVENT_SPIN_DELAY: + event_name = "SpinDelay"; + break; + case WAIT_EVENT_VACUUM_DELAY: + event_name = "VacuumDelay"; + break; + case WAIT_EVENT_VACUUM_TRUNCATE: + event_name = "VacuumTruncate"; + break; + /* no default case, so that compiler will warn */ + } + + return event_name; +} + +/* ---------- + * pgstat_get_wait_io() - + * + * Convert WaitEventIO to string. + * ---------- + */ +static const char * +pgstat_get_wait_io(WaitEventIO w) +{ + const char *event_name = "unknown wait event"; + + switch (w) + { + case WAIT_EVENT_BASEBACKUP_READ: + event_name = "BaseBackupRead"; + break; + case WAIT_EVENT_BASEBACKUP_SYNC: + event_name = "BaseBackupSync"; + break; + case WAIT_EVENT_BASEBACKUP_WRITE: + event_name = "BaseBackupWrite"; + break; + case WAIT_EVENT_BUFFILE_READ: + event_name = "BufFileRead"; + break; + case WAIT_EVENT_BUFFILE_WRITE: + event_name = "BufFileWrite"; + break; + case WAIT_EVENT_BUFFILE_TRUNCATE: + event_name = "BufFileTruncate"; + break; + case WAIT_EVENT_CONTROL_FILE_READ: + event_name = "ControlFileRead"; + break; + case WAIT_EVENT_CONTROL_FILE_SYNC: + event_name = "ControlFileSync"; + break; + case WAIT_EVENT_CONTROL_FILE_SYNC_UPDATE: + event_name = "ControlFileSyncUpdate"; + break; + case WAIT_EVENT_CONTROL_FILE_WRITE: + event_name = "ControlFileWrite"; + break; + case WAIT_EVENT_CONTROL_FILE_WRITE_UPDATE: + event_name = "ControlFileWriteUpdate"; + break; + case WAIT_EVENT_COPY_FILE_READ: + event_name = "CopyFileRead"; + break; + case WAIT_EVENT_COPY_FILE_WRITE: + event_name = "CopyFileWrite"; + break; + case WAIT_EVENT_DATA_FILE_EXTEND: + event_name = "DataFileExtend"; + break; + case WAIT_EVENT_DATA_FILE_FLUSH: + event_name = "DataFileFlush"; + break; + case WAIT_EVENT_DATA_FILE_IMMEDIATE_SYNC: + event_name = "DataFileImmediateSync"; + break; + case WAIT_EVENT_DATA_FILE_PREFETCH: + event_name = "DataFilePrefetch"; + break; + case WAIT_EVENT_DATA_FILE_READ: + event_name = "DataFileRead"; + break; + case WAIT_EVENT_DATA_FILE_SYNC: + event_name = "DataFileSync"; + break; + case WAIT_EVENT_DATA_FILE_TRUNCATE: + event_name = "DataFileTruncate"; + break; + case WAIT_EVENT_DATA_FILE_WRITE: + event_name = "DataFileWrite"; + break; + case WAIT_EVENT_DSM_ALLOCATE: + event_name = "DSMAllocate"; + break; + case WAIT_EVENT_DSM_FILL_ZERO_WRITE: + event_name = "DSMFillZeroWrite"; + break; + case WAIT_EVENT_LOCK_FILE_ADDTODATADIR_READ: + event_name = "LockFileAddToDataDirRead"; + break; + case WAIT_EVENT_LOCK_FILE_ADDTODATADIR_SYNC: + event_name = "LockFileAddToDataDirSync"; + break; + case WAIT_EVENT_LOCK_FILE_ADDTODATADIR_WRITE: + event_name = "LockFileAddToDataDirWrite"; + break; + case WAIT_EVENT_LOCK_FILE_CREATE_READ: + event_name = "LockFileCreateRead"; + break; + case WAIT_EVENT_LOCK_FILE_CREATE_SYNC: + event_name = "LockFileCreateSync"; + break; + case WAIT_EVENT_LOCK_FILE_CREATE_WRITE: + event_name = "LockFileCreateWrite"; + break; + case WAIT_EVENT_LOCK_FILE_RECHECKDATADIR_READ: + event_name = "LockFileReCheckDataDirRead"; + break; + case WAIT_EVENT_LOGICAL_REWRITE_CHECKPOINT_SYNC: + event_name = "LogicalRewriteCheckpointSync"; + break; + case WAIT_EVENT_LOGICAL_REWRITE_MAPPING_SYNC: + event_name = "LogicalRewriteMappingSync"; + break; + case WAIT_EVENT_LOGICAL_REWRITE_MAPPING_WRITE: + event_name = "LogicalRewriteMappingWrite"; + break; + case WAIT_EVENT_LOGICAL_REWRITE_SYNC: + event_name = "LogicalRewriteSync"; + break; + case WAIT_EVENT_LOGICAL_REWRITE_TRUNCATE: + event_name = "LogicalRewriteTruncate"; + break; + case WAIT_EVENT_LOGICAL_REWRITE_WRITE: + event_name = "LogicalRewriteWrite"; + break; + case WAIT_EVENT_RELATION_MAP_READ: + event_name = "RelationMapRead"; + break; + case WAIT_EVENT_RELATION_MAP_REPLACE: + event_name = "RelationMapReplace"; + break; + case WAIT_EVENT_RELATION_MAP_WRITE: + event_name = "RelationMapWrite"; + break; + case WAIT_EVENT_REORDER_BUFFER_READ: + event_name = "ReorderBufferRead"; + break; + case WAIT_EVENT_REORDER_BUFFER_WRITE: + event_name = "ReorderBufferWrite"; + break; + case WAIT_EVENT_REORDER_LOGICAL_MAPPING_READ: + event_name = "ReorderLogicalMappingRead"; + break; + case WAIT_EVENT_REPLICATION_SLOT_READ: + event_name = "ReplicationSlotRead"; + break; + case WAIT_EVENT_REPLICATION_SLOT_RESTORE_SYNC: + event_name = "ReplicationSlotRestoreSync"; + break; + case WAIT_EVENT_REPLICATION_SLOT_SYNC: + event_name = "ReplicationSlotSync"; + break; + case WAIT_EVENT_REPLICATION_SLOT_WRITE: + event_name = "ReplicationSlotWrite"; + break; + case WAIT_EVENT_SLRU_FLUSH_SYNC: + event_name = "SLRUFlushSync"; + break; + case WAIT_EVENT_SLRU_READ: + event_name = "SLRURead"; + break; + case WAIT_EVENT_SLRU_SYNC: + event_name = "SLRUSync"; + break; + case WAIT_EVENT_SLRU_WRITE: + event_name = "SLRUWrite"; + break; + case WAIT_EVENT_SNAPBUILD_READ: + event_name = "SnapbuildRead"; + break; + case WAIT_EVENT_SNAPBUILD_SYNC: + event_name = "SnapbuildSync"; + break; + case WAIT_EVENT_SNAPBUILD_WRITE: + event_name = "SnapbuildWrite"; + break; + case WAIT_EVENT_TIMELINE_HISTORY_FILE_SYNC: + event_name = "TimelineHistoryFileSync"; + break; + case WAIT_EVENT_TIMELINE_HISTORY_FILE_WRITE: + event_name = "TimelineHistoryFileWrite"; + break; + case WAIT_EVENT_TIMELINE_HISTORY_READ: + event_name = "TimelineHistoryRead"; + break; + case WAIT_EVENT_TIMELINE_HISTORY_SYNC: + event_name = "TimelineHistorySync"; + break; + case WAIT_EVENT_TIMELINE_HISTORY_WRITE: + event_name = "TimelineHistoryWrite"; + break; + case WAIT_EVENT_TWOPHASE_FILE_READ: + event_name = "TwophaseFileRead"; + break; + case WAIT_EVENT_TWOPHASE_FILE_SYNC: + event_name = "TwophaseFileSync"; + break; + case WAIT_EVENT_TWOPHASE_FILE_WRITE: + event_name = "TwophaseFileWrite"; + break; + case WAIT_EVENT_VERSION_FILE_SYNC: + event_name = "VersionFileSync"; + break; + case WAIT_EVENT_VERSION_FILE_WRITE: + event_name = "VersionFileWrite"; + break; + case WAIT_EVENT_WALSENDER_TIMELINE_HISTORY_READ: + event_name = "WALSenderTimelineHistoryRead"; + break; + case WAIT_EVENT_WAL_BOOTSTRAP_SYNC: + event_name = "WALBootstrapSync"; + break; + case WAIT_EVENT_WAL_BOOTSTRAP_WRITE: + event_name = "WALBootstrapWrite"; + break; + case WAIT_EVENT_WAL_COPY_READ: + event_name = "WALCopyRead"; + break; + case WAIT_EVENT_WAL_COPY_SYNC: + event_name = "WALCopySync"; + break; + case WAIT_EVENT_WAL_COPY_WRITE: + event_name = "WALCopyWrite"; + break; + case WAIT_EVENT_WAL_INIT_SYNC: + event_name = "WALInitSync"; + break; + case WAIT_EVENT_WAL_INIT_WRITE: + event_name = "WALInitWrite"; + break; + case WAIT_EVENT_WAL_READ: + event_name = "WALRead"; + break; + case WAIT_EVENT_WAL_SYNC: + event_name = "WALSync"; + break; + case WAIT_EVENT_WAL_SYNC_METHOD_ASSIGN: + event_name = "WALSyncMethodAssign"; + break; + case WAIT_EVENT_WAL_WRITE: + event_name = "WALWrite"; + break; + + /* no default case, so that compiler will warn */ + } + + return event_name; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/acl.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/acl.c new file mode 100644 index 00000000000..2dcbcd7e2f0 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/acl.c @@ -0,0 +1,5410 @@ +/*------------------------------------------------------------------------- + * + * acl.c + * Basic access control list data structures manipulation routines. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/adt/acl.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <ctype.h> + +#include "access/htup_details.h" +#include "catalog/catalog.h" +#include "catalog/namespace.h" +#include "catalog/pg_auth_members.h" +#include "catalog/pg_authid.h" +#include "catalog/pg_class.h" +#include "catalog/pg_database.h" +#include "catalog/pg_foreign_data_wrapper.h" +#include "catalog/pg_foreign_server.h" +#include "catalog/pg_language.h" +#include "catalog/pg_namespace.h" +#include "catalog/pg_parameter_acl.h" +#include "catalog/pg_proc.h" +#include "catalog/pg_tablespace.h" +#include "catalog/pg_type.h" +#include "commands/dbcommands.h" +#include "commands/proclang.h" +#include "commands/tablespace.h" +#include "common/hashfn.h" +#include "foreign/foreign.h" +#include "funcapi.h" +#include "lib/qunique.h" +#include "miscadmin.h" +#include "utils/acl.h" +#include "utils/array.h" +#include "utils/builtins.h" +#include "utils/catcache.h" +#include "utils/guc.h" +#include "utils/inval.h" +#include "utils/lsyscache.h" +#include "utils/memutils.h" +#include "utils/syscache.h" +#include "utils/varlena.h" + +typedef struct +{ + const char *name; + AclMode value; +} priv_map; + +/* + * We frequently need to test whether a given role is a member of some other + * role. In most of these tests the "given role" is the same, namely the + * active current user. So we can optimize it by keeping cached lists of all + * the roles the "given role" is a member of, directly or indirectly. + * + * Possibly this mechanism should be generalized to allow caching membership + * info for multiple roles? + * + * Each element of cached_roles is an OID list of constituent roles for the + * corresponding element of cached_role (always including the cached_role + * itself). There's a separate cache for each RoleRecurseType, with the + * corresponding semantics. + */ +enum RoleRecurseType +{ + ROLERECURSE_MEMBERS = 0, /* recurse unconditionally */ + ROLERECURSE_PRIVS = 1, /* recurse through inheritable grants */ + ROLERECURSE_SETROLE = 2 /* recurse through grants with set_option */ +}; +static __thread Oid cached_role[] = {InvalidOid, InvalidOid, InvalidOid}; +static __thread List *cached_roles[] = {NIL, NIL, NIL}; +static __thread uint32 cached_db_hash; + + +static const char *getid(const char *s, char *n, Node *escontext); +static void putid(char *p, const char *s); +static Acl *allocacl(int n); +static void check_acl(const Acl *acl); +static const char *aclparse(const char *s, AclItem *aip, Node *escontext); +static bool aclitem_match(const AclItem *a1, const AclItem *a2); +static int aclitemComparator(const void *arg1, const void *arg2); +static void check_circularity(const Acl *old_acl, const AclItem *mod_aip, + Oid ownerId); +static Acl *recursive_revoke(Acl *acl, Oid grantee, AclMode revoke_privs, + Oid ownerId, DropBehavior behavior); + +static AclMode convert_any_priv_string(text *priv_type_text, + const priv_map *privileges); + +static Oid convert_table_name(text *tablename); +static AclMode convert_table_priv_string(text *priv_type_text); +static AclMode convert_sequence_priv_string(text *priv_type_text); +static AttrNumber convert_column_name(Oid tableoid, text *column); +static AclMode convert_column_priv_string(text *priv_type_text); +static Oid convert_database_name(text *databasename); +static AclMode convert_database_priv_string(text *priv_type_text); +static Oid convert_foreign_data_wrapper_name(text *fdwname); +static AclMode convert_foreign_data_wrapper_priv_string(text *priv_type_text); +static Oid convert_function_name(text *functionname); +static AclMode convert_function_priv_string(text *priv_type_text); +static Oid convert_language_name(text *languagename); +static AclMode convert_language_priv_string(text *priv_type_text); +static Oid convert_schema_name(text *schemaname); +static AclMode convert_schema_priv_string(text *priv_type_text); +static Oid convert_server_name(text *servername); +static AclMode convert_server_priv_string(text *priv_type_text); +static Oid convert_tablespace_name(text *tablespacename); +static AclMode convert_tablespace_priv_string(text *priv_type_text); +static Oid convert_type_name(text *typename); +static AclMode convert_type_priv_string(text *priv_type_text); +static AclMode convert_parameter_priv_string(text *priv_text); +static AclMode convert_role_priv_string(text *priv_type_text); +static AclResult pg_role_aclcheck(Oid role_oid, Oid roleid, AclMode mode); + +static void RoleMembershipCacheCallback(Datum arg, int cacheid, uint32 hashvalue); + + +/* + * getid + * Consumes the first alphanumeric string (identifier) found in string + * 's', ignoring any leading white space. If it finds a double quote + * it returns the word inside the quotes. + * + * RETURNS: + * the string position in 's' that points to the next non-space character + * in 's', after any quotes. Also: + * - loads the identifier into 'n'. (If no identifier is found, 'n' + * contains an empty string.) 'n' must be NAMEDATALEN bytes. + * + * Errors are reported via ereport, unless escontext is an ErrorSaveData node, + * in which case we log the error there and return NULL. + */ +static const char * +getid(const char *s, char *n, Node *escontext) +{ + int len = 0; + bool in_quotes = false; + + Assert(s && n); + + while (isspace((unsigned char) *s)) + s++; + /* This code had better match what putid() does, below */ + for (; + *s != '\0' && + (isalnum((unsigned char) *s) || + *s == '_' || + *s == '"' || + in_quotes); + s++) + { + if (*s == '"') + { + /* safe to look at next char (could be '\0' though) */ + if (*(s + 1) != '"') + { + in_quotes = !in_quotes; + continue; + } + /* it's an escaped double quote; skip the escaping char */ + s++; + } + + /* Add the character to the string */ + if (len >= NAMEDATALEN - 1) + ereturn(escontext, NULL, + (errcode(ERRCODE_NAME_TOO_LONG), + errmsg("identifier too long"), + errdetail("Identifier must be less than %d characters.", + NAMEDATALEN))); + + n[len++] = *s; + } + n[len] = '\0'; + while (isspace((unsigned char) *s)) + s++; + return s; +} + +/* + * Write a role name at *p, adding double quotes if needed. + * There must be at least (2*NAMEDATALEN)+2 bytes available at *p. + * This needs to be kept in sync with dequoteAclUserName in pg_dump/dumputils.c + */ +static void +putid(char *p, const char *s) +{ + const char *src; + bool safe = true; + + for (src = s; *src; src++) + { + /* This test had better match what getid() does, above */ + if (!isalnum((unsigned char) *src) && *src != '_') + { + safe = false; + break; + } + } + if (!safe) + *p++ = '"'; + for (src = s; *src; src++) + { + /* A double quote character in a username is encoded as "" */ + if (*src == '"') + *p++ = '"'; + *p++ = *src; + } + if (!safe) + *p++ = '"'; + *p = '\0'; +} + +/* + * aclparse + * Consumes and parses an ACL specification of the form: + * [group|user] [A-Za-z0-9]*=[rwaR]* + * from string 's', ignoring any leading white space or white space + * between the optional id type keyword (group|user) and the actual + * ACL specification. + * + * The group|user decoration is unnecessary in the roles world, + * but we still accept it for backward compatibility. + * + * This routine is called by the parser as well as aclitemin(), hence + * the added generality. + * + * RETURNS: + * the string position in 's' immediately following the ACL + * specification. Also: + * - loads the structure pointed to by 'aip' with the appropriate + * UID/GID, id type identifier and mode type values. + * + * Errors are reported via ereport, unless escontext is an ErrorSaveData node, + * in which case we log the error there and return NULL. + */ +static const char * +aclparse(const char *s, AclItem *aip, Node *escontext) +{ + AclMode privs, + goption, + read; + char name[NAMEDATALEN]; + char name2[NAMEDATALEN]; + + Assert(s && aip); + + s = getid(s, name, escontext); + if (s == NULL) + return NULL; + if (*s != '=') + { + /* we just read a keyword, not a name */ + if (strcmp(name, "group") != 0 && strcmp(name, "user") != 0) + ereturn(escontext, NULL, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("unrecognized key word: \"%s\"", name), + errhint("ACL key word must be \"group\" or \"user\"."))); + /* move s to the name beyond the keyword */ + s = getid(s, name, escontext); + if (s == NULL) + return NULL; + if (name[0] == '\0') + ereturn(escontext, NULL, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("missing name"), + errhint("A name must follow the \"group\" or \"user\" key word."))); + } + + if (*s != '=') + ereturn(escontext, NULL, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("missing \"=\" sign"))); + + privs = goption = ACL_NO_RIGHTS; + + for (++s, read = 0; isalpha((unsigned char) *s) || *s == '*'; s++) + { + switch (*s) + { + case '*': + goption |= read; + break; + case ACL_INSERT_CHR: + read = ACL_INSERT; + break; + case ACL_SELECT_CHR: + read = ACL_SELECT; + break; + case ACL_UPDATE_CHR: + read = ACL_UPDATE; + break; + case ACL_DELETE_CHR: + read = ACL_DELETE; + break; + case ACL_TRUNCATE_CHR: + read = ACL_TRUNCATE; + break; + case ACL_REFERENCES_CHR: + read = ACL_REFERENCES; + break; + case ACL_TRIGGER_CHR: + read = ACL_TRIGGER; + break; + case ACL_EXECUTE_CHR: + read = ACL_EXECUTE; + break; + case ACL_USAGE_CHR: + read = ACL_USAGE; + break; + case ACL_CREATE_CHR: + read = ACL_CREATE; + break; + case ACL_CREATE_TEMP_CHR: + read = ACL_CREATE_TEMP; + break; + case ACL_CONNECT_CHR: + read = ACL_CONNECT; + break; + case ACL_SET_CHR: + read = ACL_SET; + break; + case ACL_ALTER_SYSTEM_CHR: + read = ACL_ALTER_SYSTEM; + break; + case 'R': /* ignore old RULE privileges */ + read = 0; + break; + default: + ereturn(escontext, NULL, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid mode character: must be one of \"%s\"", + ACL_ALL_RIGHTS_STR))); + } + + privs |= read; + } + + if (name[0] == '\0') + aip->ai_grantee = ACL_ID_PUBLIC; + else + { + aip->ai_grantee = get_role_oid(name, true); + if (!OidIsValid(aip->ai_grantee)) + ereturn(escontext, NULL, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("role \"%s\" does not exist", name))); + } + + /* + * XXX Allow a degree of backward compatibility by defaulting the grantor + * to the superuser. + */ + if (*s == '/') + { + s = getid(s + 1, name2, escontext); + if (s == NULL) + return NULL; + if (name2[0] == '\0') + ereturn(escontext, NULL, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("a name must follow the \"/\" sign"))); + aip->ai_grantor = get_role_oid(name2, true); + if (!OidIsValid(aip->ai_grantor)) + ereturn(escontext, NULL, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("role \"%s\" does not exist", name2))); + } + else + { + aip->ai_grantor = BOOTSTRAP_SUPERUSERID; + ereport(WARNING, + (errcode(ERRCODE_INVALID_GRANTOR), + errmsg("defaulting grantor to user ID %u", + BOOTSTRAP_SUPERUSERID))); + } + + ACLITEM_SET_PRIVS_GOPTIONS(*aip, privs, goption); + + return s; +} + +/* + * allocacl + * Allocates storage for a new Acl with 'n' entries. + * + * RETURNS: + * the new Acl + */ +static Acl * +allocacl(int n) +{ + Acl *new_acl; + Size size; + + if (n < 0) + elog(ERROR, "invalid size: %d", n); + size = ACL_N_SIZE(n); + new_acl = (Acl *) palloc0(size); + SET_VARSIZE(new_acl, size); + new_acl->ndim = 1; + new_acl->dataoffset = 0; /* we never put in any nulls */ + new_acl->elemtype = ACLITEMOID; + ARR_LBOUND(new_acl)[0] = 1; + ARR_DIMS(new_acl)[0] = n; + return new_acl; +} + +/* + * Create a zero-entry ACL + */ +Acl * +make_empty_acl(void) +{ + return allocacl(0); +} + +/* + * Copy an ACL + */ +Acl * +aclcopy(const Acl *orig_acl) +{ + Acl *result_acl; + + result_acl = allocacl(ACL_NUM(orig_acl)); + + memcpy(ACL_DAT(result_acl), + ACL_DAT(orig_acl), + ACL_NUM(orig_acl) * sizeof(AclItem)); + + return result_acl; +} + +/* + * Concatenate two ACLs + * + * This is a bit cheesy, since we may produce an ACL with redundant entries. + * Be careful what the result is used for! + */ +Acl * +aclconcat(const Acl *left_acl, const Acl *right_acl) +{ + Acl *result_acl; + + result_acl = allocacl(ACL_NUM(left_acl) + ACL_NUM(right_acl)); + + memcpy(ACL_DAT(result_acl), + ACL_DAT(left_acl), + ACL_NUM(left_acl) * sizeof(AclItem)); + + memcpy(ACL_DAT(result_acl) + ACL_NUM(left_acl), + ACL_DAT(right_acl), + ACL_NUM(right_acl) * sizeof(AclItem)); + + return result_acl; +} + +/* + * Merge two ACLs + * + * This produces a properly merged ACL with no redundant entries. + * Returns NULL on NULL input. + */ +Acl * +aclmerge(const Acl *left_acl, const Acl *right_acl, Oid ownerId) +{ + Acl *result_acl; + AclItem *aip; + int i, + num; + + /* Check for cases where one or both are empty/null */ + if (left_acl == NULL || ACL_NUM(left_acl) == 0) + { + if (right_acl == NULL || ACL_NUM(right_acl) == 0) + return NULL; + else + return aclcopy(right_acl); + } + else + { + if (right_acl == NULL || ACL_NUM(right_acl) == 0) + return aclcopy(left_acl); + } + + /* Merge them the hard way, one item at a time */ + result_acl = aclcopy(left_acl); + + aip = ACL_DAT(right_acl); + num = ACL_NUM(right_acl); + + for (i = 0; i < num; i++, aip++) + { + Acl *tmp_acl; + + tmp_acl = aclupdate(result_acl, aip, ACL_MODECHG_ADD, + ownerId, DROP_RESTRICT); + pfree(result_acl); + result_acl = tmp_acl; + } + + return result_acl; +} + +/* + * Sort the items in an ACL (into an arbitrary but consistent order) + */ +void +aclitemsort(Acl *acl) +{ + if (acl != NULL && ACL_NUM(acl) > 1) + qsort(ACL_DAT(acl), ACL_NUM(acl), sizeof(AclItem), aclitemComparator); +} + +/* + * Check if two ACLs are exactly equal + * + * This will not detect equality if the two arrays contain the same items + * in different orders. To handle that case, sort both inputs first, + * using aclitemsort(). + */ +bool +aclequal(const Acl *left_acl, const Acl *right_acl) +{ + /* Check for cases where one or both are empty/null */ + if (left_acl == NULL || ACL_NUM(left_acl) == 0) + { + if (right_acl == NULL || ACL_NUM(right_acl) == 0) + return true; + else + return false; + } + else + { + if (right_acl == NULL || ACL_NUM(right_acl) == 0) + return false; + } + + if (ACL_NUM(left_acl) != ACL_NUM(right_acl)) + return false; + + if (memcmp(ACL_DAT(left_acl), + ACL_DAT(right_acl), + ACL_NUM(left_acl) * sizeof(AclItem)) == 0) + return true; + + return false; +} + +/* + * Verify that an ACL array is acceptable (one-dimensional and has no nulls) + */ +static void +check_acl(const Acl *acl) +{ + if (ARR_ELEMTYPE(acl) != ACLITEMOID) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("ACL array contains wrong data type"))); + if (ARR_NDIM(acl) != 1) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("ACL arrays must be one-dimensional"))); + if (ARR_HASNULL(acl)) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("ACL arrays must not contain null values"))); +} + +/* + * aclitemin + * Allocates storage for, and fills in, a new AclItem given a string + * 's' that contains an ACL specification. See aclparse for details. + * + * RETURNS: + * the new AclItem + */ +Datum +aclitemin(PG_FUNCTION_ARGS) +{ + const char *s = PG_GETARG_CSTRING(0); + Node *escontext = fcinfo->context; + AclItem *aip; + + aip = (AclItem *) palloc(sizeof(AclItem)); + + s = aclparse(s, aip, escontext); + if (s == NULL) + PG_RETURN_NULL(); + + while (isspace((unsigned char) *s)) + ++s; + if (*s) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("extra garbage at the end of the ACL specification"))); + + PG_RETURN_ACLITEM_P(aip); +} + +/* + * aclitemout + * Allocates storage for, and fills in, a new null-delimited string + * containing a formatted ACL specification. See aclparse for details. + * + * RETURNS: + * the new string + */ +Datum +aclitemout(PG_FUNCTION_ARGS) +{ + AclItem *aip = PG_GETARG_ACLITEM_P(0); + char *p; + char *out; + HeapTuple htup; + unsigned i; + + out = palloc(strlen("=/") + + 2 * N_ACL_RIGHTS + + 2 * (2 * NAMEDATALEN + 2) + + 1); + + p = out; + *p = '\0'; + + if (aip->ai_grantee != ACL_ID_PUBLIC) + { + htup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(aip->ai_grantee)); + if (HeapTupleIsValid(htup)) + { + putid(p, NameStr(((Form_pg_authid) GETSTRUCT(htup))->rolname)); + ReleaseSysCache(htup); + } + else + { + /* Generate numeric OID if we don't find an entry */ + sprintf(p, "%u", aip->ai_grantee); + } + } + while (*p) + ++p; + + *p++ = '='; + + for (i = 0; i < N_ACL_RIGHTS; ++i) + { + if (ACLITEM_GET_PRIVS(*aip) & (UINT64CONST(1) << i)) + *p++ = ACL_ALL_RIGHTS_STR[i]; + if (ACLITEM_GET_GOPTIONS(*aip) & (UINT64CONST(1) << i)) + *p++ = '*'; + } + + *p++ = '/'; + *p = '\0'; + + htup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(aip->ai_grantor)); + if (HeapTupleIsValid(htup)) + { + putid(p, NameStr(((Form_pg_authid) GETSTRUCT(htup))->rolname)); + ReleaseSysCache(htup); + } + else + { + /* Generate numeric OID if we don't find an entry */ + sprintf(p, "%u", aip->ai_grantor); + } + + PG_RETURN_CSTRING(out); +} + +/* + * aclitem_match + * Two AclItems are considered to match iff they have the same + * grantee and grantor; the privileges are ignored. + */ +static bool +aclitem_match(const AclItem *a1, const AclItem *a2) +{ + return a1->ai_grantee == a2->ai_grantee && + a1->ai_grantor == a2->ai_grantor; +} + +/* + * aclitemComparator + * qsort comparison function for AclItems + */ +static int +aclitemComparator(const void *arg1, const void *arg2) +{ + const AclItem *a1 = (const AclItem *) arg1; + const AclItem *a2 = (const AclItem *) arg2; + + if (a1->ai_grantee > a2->ai_grantee) + return 1; + if (a1->ai_grantee < a2->ai_grantee) + return -1; + if (a1->ai_grantor > a2->ai_grantor) + return 1; + if (a1->ai_grantor < a2->ai_grantor) + return -1; + if (a1->ai_privs > a2->ai_privs) + return 1; + if (a1->ai_privs < a2->ai_privs) + return -1; + return 0; +} + +/* + * aclitem equality operator + */ +Datum +aclitem_eq(PG_FUNCTION_ARGS) +{ + AclItem *a1 = PG_GETARG_ACLITEM_P(0); + AclItem *a2 = PG_GETARG_ACLITEM_P(1); + bool result; + + result = a1->ai_privs == a2->ai_privs && + a1->ai_grantee == a2->ai_grantee && + a1->ai_grantor == a2->ai_grantor; + PG_RETURN_BOOL(result); +} + +/* + * aclitem hash function + * + * We make aclitems hashable not so much because anyone is likely to hash + * them, as because we want array equality to work on aclitem arrays, and + * with the typcache mechanism we must have a hash or btree opclass. + */ +Datum +hash_aclitem(PG_FUNCTION_ARGS) +{ + AclItem *a = PG_GETARG_ACLITEM_P(0); + + /* not very bright, but avoids any issue of padding in struct */ + PG_RETURN_UINT32((uint32) (a->ai_privs + a->ai_grantee + a->ai_grantor)); +} + +/* + * 64-bit hash function for aclitem. + * + * Similar to hash_aclitem, but accepts a seed and returns a uint64 value. + */ +Datum +hash_aclitem_extended(PG_FUNCTION_ARGS) +{ + AclItem *a = PG_GETARG_ACLITEM_P(0); + uint64 seed = PG_GETARG_INT64(1); + uint32 sum = (uint32) (a->ai_privs + a->ai_grantee + a->ai_grantor); + + return (seed == 0) ? UInt64GetDatum(sum) : hash_uint32_extended(sum, seed); +} + +/* + * acldefault() --- create an ACL describing default access permissions + * + * Change this routine if you want to alter the default access policy for + * newly-created objects (or any object with a NULL acl entry). When + * you make a change here, don't forget to update the GRANT man page, + * which explains all the default permissions. + * + * Note that these are the hard-wired "defaults" that are used in the + * absence of any pg_default_acl entry. + */ +Acl * +acldefault(ObjectType objtype, Oid ownerId) +{ + AclMode world_default; + AclMode owner_default; + int nacl; + Acl *acl; + AclItem *aip; + + switch (objtype) + { + case OBJECT_COLUMN: + /* by default, columns have no extra privileges */ + world_default = ACL_NO_RIGHTS; + owner_default = ACL_NO_RIGHTS; + break; + case OBJECT_TABLE: + world_default = ACL_NO_RIGHTS; + owner_default = ACL_ALL_RIGHTS_RELATION; + break; + case OBJECT_SEQUENCE: + world_default = ACL_NO_RIGHTS; + owner_default = ACL_ALL_RIGHTS_SEQUENCE; + break; + case OBJECT_DATABASE: + /* for backwards compatibility, grant some rights by default */ + world_default = ACL_CREATE_TEMP | ACL_CONNECT; + owner_default = ACL_ALL_RIGHTS_DATABASE; + break; + case OBJECT_FUNCTION: + /* Grant EXECUTE by default, for now */ + world_default = ACL_EXECUTE; + owner_default = ACL_ALL_RIGHTS_FUNCTION; + break; + case OBJECT_LANGUAGE: + /* Grant USAGE by default, for now */ + world_default = ACL_USAGE; + owner_default = ACL_ALL_RIGHTS_LANGUAGE; + break; + case OBJECT_LARGEOBJECT: + world_default = ACL_NO_RIGHTS; + owner_default = ACL_ALL_RIGHTS_LARGEOBJECT; + break; + case OBJECT_SCHEMA: + world_default = ACL_NO_RIGHTS; + owner_default = ACL_ALL_RIGHTS_SCHEMA; + break; + case OBJECT_TABLESPACE: + world_default = ACL_NO_RIGHTS; + owner_default = ACL_ALL_RIGHTS_TABLESPACE; + break; + case OBJECT_FDW: + world_default = ACL_NO_RIGHTS; + owner_default = ACL_ALL_RIGHTS_FDW; + break; + case OBJECT_FOREIGN_SERVER: + world_default = ACL_NO_RIGHTS; + owner_default = ACL_ALL_RIGHTS_FOREIGN_SERVER; + break; + case OBJECT_DOMAIN: + case OBJECT_TYPE: + world_default = ACL_USAGE; + owner_default = ACL_ALL_RIGHTS_TYPE; + break; + case OBJECT_PARAMETER_ACL: + world_default = ACL_NO_RIGHTS; + owner_default = ACL_ALL_RIGHTS_PARAMETER_ACL; + break; + default: + elog(ERROR, "unrecognized object type: %d", (int) objtype); + world_default = ACL_NO_RIGHTS; /* keep compiler quiet */ + owner_default = ACL_NO_RIGHTS; + break; + } + + nacl = 0; + if (world_default != ACL_NO_RIGHTS) + nacl++; + if (owner_default != ACL_NO_RIGHTS) + nacl++; + + acl = allocacl(nacl); + aip = ACL_DAT(acl); + + if (world_default != ACL_NO_RIGHTS) + { + aip->ai_grantee = ACL_ID_PUBLIC; + aip->ai_grantor = ownerId; + ACLITEM_SET_PRIVS_GOPTIONS(*aip, world_default, ACL_NO_RIGHTS); + aip++; + } + + /* + * Note that the owner's entry shows all ordinary privileges but no grant + * options. This is because his grant options come "from the system" and + * not from his own efforts. (The SQL spec says that the owner's rights + * come from a "_SYSTEM" authid.) However, we do consider that the + * owner's ordinary privileges are self-granted; this lets him revoke + * them. We implement the owner's grant options without any explicit + * "_SYSTEM"-like ACL entry, by internally special-casing the owner + * wherever we are testing grant options. + */ + if (owner_default != ACL_NO_RIGHTS) + { + aip->ai_grantee = ownerId; + aip->ai_grantor = ownerId; + ACLITEM_SET_PRIVS_GOPTIONS(*aip, owner_default, ACL_NO_RIGHTS); + } + + return acl; +} + + +/* + * SQL-accessible version of acldefault(). Hackish mapping from "char" type to + * OBJECT_* values. + */ +Datum +acldefault_sql(PG_FUNCTION_ARGS) +{ + char objtypec = PG_GETARG_CHAR(0); + Oid owner = PG_GETARG_OID(1); + ObjectType objtype = 0; + + switch (objtypec) + { + case 'c': + objtype = OBJECT_COLUMN; + break; + case 'r': + objtype = OBJECT_TABLE; + break; + case 's': + objtype = OBJECT_SEQUENCE; + break; + case 'd': + objtype = OBJECT_DATABASE; + break; + case 'f': + objtype = OBJECT_FUNCTION; + break; + case 'l': + objtype = OBJECT_LANGUAGE; + break; + case 'L': + objtype = OBJECT_LARGEOBJECT; + break; + case 'n': + objtype = OBJECT_SCHEMA; + break; + case 'p': + objtype = OBJECT_PARAMETER_ACL; + break; + case 't': + objtype = OBJECT_TABLESPACE; + break; + case 'F': + objtype = OBJECT_FDW; + break; + case 'S': + objtype = OBJECT_FOREIGN_SERVER; + break; + case 'T': + objtype = OBJECT_TYPE; + break; + default: + elog(ERROR, "unrecognized object type abbreviation: %c", objtypec); + } + + PG_RETURN_ACL_P(acldefault(objtype, owner)); +} + + +/* + * Update an ACL array to add or remove specified privileges. + * + * old_acl: the input ACL array + * mod_aip: defines the privileges to be added, removed, or substituted + * modechg: ACL_MODECHG_ADD, ACL_MODECHG_DEL, or ACL_MODECHG_EQL + * ownerId: Oid of object owner + * behavior: RESTRICT or CASCADE behavior for recursive removal + * + * ownerid and behavior are only relevant when the update operation specifies + * deletion of grant options. + * + * The result is a modified copy; the input object is not changed. + * + * NB: caller is responsible for having detoasted the input ACL, if needed. + */ +Acl * +aclupdate(const Acl *old_acl, const AclItem *mod_aip, + int modechg, Oid ownerId, DropBehavior behavior) +{ + Acl *new_acl = NULL; + AclItem *old_aip, + *new_aip = NULL; + AclMode old_rights, + old_goptions, + new_rights, + new_goptions; + int dst, + num; + + /* Caller probably already checked old_acl, but be safe */ + check_acl(old_acl); + + /* If granting grant options, check for circularity */ + if (modechg != ACL_MODECHG_DEL && + ACLITEM_GET_GOPTIONS(*mod_aip) != ACL_NO_RIGHTS) + check_circularity(old_acl, mod_aip, ownerId); + + num = ACL_NUM(old_acl); + old_aip = ACL_DAT(old_acl); + + /* + * Search the ACL for an existing entry for this grantee and grantor. If + * one exists, just modify the entry in-place (well, in the same position, + * since we actually return a copy); otherwise, insert the new entry at + * the end. + */ + + for (dst = 0; dst < num; ++dst) + { + if (aclitem_match(mod_aip, old_aip + dst)) + { + /* found a match, so modify existing item */ + new_acl = allocacl(num); + new_aip = ACL_DAT(new_acl); + memcpy(new_acl, old_acl, ACL_SIZE(old_acl)); + break; + } + } + + if (dst == num) + { + /* need to append a new item */ + new_acl = allocacl(num + 1); + new_aip = ACL_DAT(new_acl); + memcpy(new_aip, old_aip, num * sizeof(AclItem)); + + /* initialize the new entry with no permissions */ + new_aip[dst].ai_grantee = mod_aip->ai_grantee; + new_aip[dst].ai_grantor = mod_aip->ai_grantor; + ACLITEM_SET_PRIVS_GOPTIONS(new_aip[dst], + ACL_NO_RIGHTS, ACL_NO_RIGHTS); + num++; /* set num to the size of new_acl */ + } + + old_rights = ACLITEM_GET_RIGHTS(new_aip[dst]); + old_goptions = ACLITEM_GET_GOPTIONS(new_aip[dst]); + + /* apply the specified permissions change */ + switch (modechg) + { + case ACL_MODECHG_ADD: + ACLITEM_SET_RIGHTS(new_aip[dst], + old_rights | ACLITEM_GET_RIGHTS(*mod_aip)); + break; + case ACL_MODECHG_DEL: + ACLITEM_SET_RIGHTS(new_aip[dst], + old_rights & ~ACLITEM_GET_RIGHTS(*mod_aip)); + break; + case ACL_MODECHG_EQL: + ACLITEM_SET_RIGHTS(new_aip[dst], + ACLITEM_GET_RIGHTS(*mod_aip)); + break; + } + + new_rights = ACLITEM_GET_RIGHTS(new_aip[dst]); + new_goptions = ACLITEM_GET_GOPTIONS(new_aip[dst]); + + /* + * If the adjusted entry has no permissions, delete it from the list. + */ + if (new_rights == ACL_NO_RIGHTS) + { + memmove(new_aip + dst, + new_aip + dst + 1, + (num - dst - 1) * sizeof(AclItem)); + /* Adjust array size to be 'num - 1' items */ + ARR_DIMS(new_acl)[0] = num - 1; + SET_VARSIZE(new_acl, ACL_N_SIZE(num - 1)); + } + + /* + * Remove abandoned privileges (cascading revoke). Currently we can only + * handle this when the grantee is not PUBLIC. + */ + if ((old_goptions & ~new_goptions) != 0) + { + Assert(mod_aip->ai_grantee != ACL_ID_PUBLIC); + new_acl = recursive_revoke(new_acl, mod_aip->ai_grantee, + (old_goptions & ~new_goptions), + ownerId, behavior); + } + + return new_acl; +} + +/* + * Update an ACL array to reflect a change of owner to the parent object + * + * old_acl: the input ACL array (must not be NULL) + * oldOwnerId: Oid of the old object owner + * newOwnerId: Oid of the new object owner + * + * The result is a modified copy; the input object is not changed. + * + * NB: caller is responsible for having detoasted the input ACL, if needed. + */ +Acl * +aclnewowner(const Acl *old_acl, Oid oldOwnerId, Oid newOwnerId) +{ + Acl *new_acl; + AclItem *new_aip; + AclItem *old_aip; + AclItem *dst_aip; + AclItem *src_aip; + AclItem *targ_aip; + bool newpresent = false; + int dst, + src, + targ, + num; + + check_acl(old_acl); + + /* + * Make a copy of the given ACL, substituting new owner ID for old + * wherever it appears as either grantor or grantee. Also note if the new + * owner ID is already present. + */ + num = ACL_NUM(old_acl); + old_aip = ACL_DAT(old_acl); + new_acl = allocacl(num); + new_aip = ACL_DAT(new_acl); + memcpy(new_aip, old_aip, num * sizeof(AclItem)); + for (dst = 0, dst_aip = new_aip; dst < num; dst++, dst_aip++) + { + if (dst_aip->ai_grantor == oldOwnerId) + dst_aip->ai_grantor = newOwnerId; + else if (dst_aip->ai_grantor == newOwnerId) + newpresent = true; + if (dst_aip->ai_grantee == oldOwnerId) + dst_aip->ai_grantee = newOwnerId; + else if (dst_aip->ai_grantee == newOwnerId) + newpresent = true; + } + + /* + * If the old ACL contained any references to the new owner, then we may + * now have generated an ACL containing duplicate entries. Find them and + * merge them so that there are not duplicates. (This is relatively + * expensive since we use a stupid O(N^2) algorithm, but it's unlikely to + * be the normal case.) + * + * To simplify deletion of duplicate entries, we temporarily leave them in + * the array but set their privilege masks to zero; when we reach such an + * entry it's just skipped. (Thus, a side effect of this code will be to + * remove privilege-free entries, should there be any in the input.) dst + * is the next output slot, targ is the currently considered input slot + * (always >= dst), and src scans entries to the right of targ looking for + * duplicates. Once an entry has been emitted to dst it is known + * duplicate-free and need not be considered anymore. + */ + if (newpresent) + { + dst = 0; + for (targ = 0, targ_aip = new_aip; targ < num; targ++, targ_aip++) + { + /* ignore if deleted in an earlier pass */ + if (ACLITEM_GET_RIGHTS(*targ_aip) == ACL_NO_RIGHTS) + continue; + /* find and merge any duplicates */ + for (src = targ + 1, src_aip = targ_aip + 1; src < num; + src++, src_aip++) + { + if (ACLITEM_GET_RIGHTS(*src_aip) == ACL_NO_RIGHTS) + continue; + if (aclitem_match(targ_aip, src_aip)) + { + ACLITEM_SET_RIGHTS(*targ_aip, + ACLITEM_GET_RIGHTS(*targ_aip) | + ACLITEM_GET_RIGHTS(*src_aip)); + /* mark the duplicate deleted */ + ACLITEM_SET_RIGHTS(*src_aip, ACL_NO_RIGHTS); + } + } + /* and emit to output */ + new_aip[dst] = *targ_aip; + dst++; + } + /* Adjust array size to be 'dst' items */ + ARR_DIMS(new_acl)[0] = dst; + SET_VARSIZE(new_acl, ACL_N_SIZE(dst)); + } + + return new_acl; +} + + +/* + * When granting grant options, we must disallow attempts to set up circular + * chains of grant options. Suppose A (the object owner) grants B some + * privileges with grant option, and B re-grants them to C. If C could + * grant the privileges to B as well, then A would be unable to effectively + * revoke the privileges from B, since recursive_revoke would consider that + * B still has 'em from C. + * + * We check for this by recursively deleting all grant options belonging to + * the target grantee, and then seeing if the would-be grantor still has the + * grant option or not. + */ +static void +check_circularity(const Acl *old_acl, const AclItem *mod_aip, + Oid ownerId) +{ + Acl *acl; + AclItem *aip; + int i, + num; + AclMode own_privs; + + check_acl(old_acl); + + /* + * For now, grant options can only be granted to roles, not PUBLIC. + * Otherwise we'd have to work a bit harder here. + */ + Assert(mod_aip->ai_grantee != ACL_ID_PUBLIC); + + /* The owner always has grant options, no need to check */ + if (mod_aip->ai_grantor == ownerId) + return; + + /* Make a working copy */ + acl = allocacl(ACL_NUM(old_acl)); + memcpy(acl, old_acl, ACL_SIZE(old_acl)); + + /* Zap all grant options of target grantee, plus what depends on 'em */ +cc_restart: + num = ACL_NUM(acl); + aip = ACL_DAT(acl); + for (i = 0; i < num; i++) + { + if (aip[i].ai_grantee == mod_aip->ai_grantee && + ACLITEM_GET_GOPTIONS(aip[i]) != ACL_NO_RIGHTS) + { + Acl *new_acl; + + /* We'll actually zap ordinary privs too, but no matter */ + new_acl = aclupdate(acl, &aip[i], ACL_MODECHG_DEL, + ownerId, DROP_CASCADE); + + pfree(acl); + acl = new_acl; + + goto cc_restart; + } + } + + /* Now we can compute grantor's independently-derived privileges */ + own_privs = aclmask(acl, + mod_aip->ai_grantor, + ownerId, + ACL_GRANT_OPTION_FOR(ACLITEM_GET_GOPTIONS(*mod_aip)), + ACLMASK_ALL); + own_privs = ACL_OPTION_TO_PRIVS(own_privs); + + if ((ACLITEM_GET_GOPTIONS(*mod_aip) & ~own_privs) != 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_GRANT_OPERATION), + errmsg("grant options cannot be granted back to your own grantor"))); + + pfree(acl); +} + + +/* + * Ensure that no privilege is "abandoned". A privilege is abandoned + * if the user that granted the privilege loses the grant option. (So + * the chain through which it was granted is broken.) Either the + * abandoned privileges are revoked as well, or an error message is + * printed, depending on the drop behavior option. + * + * acl: the input ACL list + * grantee: the user from whom some grant options have been revoked + * revoke_privs: the grant options being revoked + * ownerId: Oid of object owner + * behavior: RESTRICT or CASCADE behavior for recursive removal + * + * The input Acl object is pfree'd if replaced. + */ +static Acl * +recursive_revoke(Acl *acl, + Oid grantee, + AclMode revoke_privs, + Oid ownerId, + DropBehavior behavior) +{ + AclMode still_has; + AclItem *aip; + int i, + num; + + check_acl(acl); + + /* The owner can never truly lose grant options, so short-circuit */ + if (grantee == ownerId) + return acl; + + /* The grantee might still have some grant options via another grantor */ + still_has = aclmask(acl, grantee, ownerId, + ACL_GRANT_OPTION_FOR(revoke_privs), + ACLMASK_ALL); + revoke_privs &= ~ACL_OPTION_TO_PRIVS(still_has); + if (revoke_privs == ACL_NO_RIGHTS) + return acl; + +restart: + num = ACL_NUM(acl); + aip = ACL_DAT(acl); + for (i = 0; i < num; i++) + { + if (aip[i].ai_grantor == grantee + && (ACLITEM_GET_PRIVS(aip[i]) & revoke_privs) != 0) + { + AclItem mod_acl; + Acl *new_acl; + + if (behavior == DROP_RESTRICT) + ereport(ERROR, + (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST), + errmsg("dependent privileges exist"), + errhint("Use CASCADE to revoke them too."))); + + mod_acl.ai_grantor = grantee; + mod_acl.ai_grantee = aip[i].ai_grantee; + ACLITEM_SET_PRIVS_GOPTIONS(mod_acl, + revoke_privs, + revoke_privs); + + new_acl = aclupdate(acl, &mod_acl, ACL_MODECHG_DEL, + ownerId, behavior); + + pfree(acl); + acl = new_acl; + + goto restart; + } + } + + return acl; +} + + +/* + * aclmask --- compute bitmask of all privileges held by roleid. + * + * When 'how' = ACLMASK_ALL, this simply returns the privilege bits + * held by the given roleid according to the given ACL list, ANDed + * with 'mask'. (The point of passing 'mask' is to let the routine + * exit early if all privileges of interest have been found.) + * + * When 'how' = ACLMASK_ANY, returns as soon as any bit in the mask + * is known true. (This lets us exit soonest in cases where the + * caller is only going to test for zero or nonzero result.) + * + * Usage patterns: + * + * To see if any of a set of privileges are held: + * if (aclmask(acl, roleid, ownerId, privs, ACLMASK_ANY) != 0) + * + * To see if all of a set of privileges are held: + * if (aclmask(acl, roleid, ownerId, privs, ACLMASK_ALL) == privs) + * + * To determine exactly which of a set of privileges are held: + * heldprivs = aclmask(acl, roleid, ownerId, privs, ACLMASK_ALL); + */ +AclMode +aclmask(const Acl *acl, Oid roleid, Oid ownerId, + AclMode mask, AclMaskHow how) +{ + AclMode result; + AclMode remaining; + AclItem *aidat; + int i, + num; + + /* + * Null ACL should not happen, since caller should have inserted + * appropriate default + */ + if (acl == NULL) + elog(ERROR, "null ACL"); + + check_acl(acl); + + /* Quick exit for mask == 0 */ + if (mask == 0) + return 0; + + result = 0; + + /* Owner always implicitly has all grant options */ + if ((mask & ACLITEM_ALL_GOPTION_BITS) && + has_privs_of_role(roleid, ownerId)) + { + result = mask & ACLITEM_ALL_GOPTION_BITS; + if ((how == ACLMASK_ALL) ? (result == mask) : (result != 0)) + return result; + } + + num = ACL_NUM(acl); + aidat = ACL_DAT(acl); + + /* + * Check privileges granted directly to roleid or to public + */ + for (i = 0; i < num; i++) + { + AclItem *aidata = &aidat[i]; + + if (aidata->ai_grantee == ACL_ID_PUBLIC || + aidata->ai_grantee == roleid) + { + result |= aidata->ai_privs & mask; + if ((how == ACLMASK_ALL) ? (result == mask) : (result != 0)) + return result; + } + } + + /* + * Check privileges granted indirectly via role memberships. We do this in + * a separate pass to minimize expensive indirect membership tests. In + * particular, it's worth testing whether a given ACL entry grants any + * privileges still of interest before we perform the has_privs_of_role + * test. + */ + remaining = mask & ~result; + for (i = 0; i < num; i++) + { + AclItem *aidata = &aidat[i]; + + if (aidata->ai_grantee == ACL_ID_PUBLIC || + aidata->ai_grantee == roleid) + continue; /* already checked it */ + + if ((aidata->ai_privs & remaining) && + has_privs_of_role(roleid, aidata->ai_grantee)) + { + result |= aidata->ai_privs & mask; + if ((how == ACLMASK_ALL) ? (result == mask) : (result != 0)) + return result; + remaining = mask & ~result; + } + } + + return result; +} + + +/* + * aclmask_direct --- compute bitmask of all privileges held by roleid. + * + * This is exactly like aclmask() except that we consider only privileges + * held *directly* by roleid, not those inherited via role membership. + */ +static AclMode +aclmask_direct(const Acl *acl, Oid roleid, Oid ownerId, + AclMode mask, AclMaskHow how) +{ + AclMode result; + AclItem *aidat; + int i, + num; + + /* + * Null ACL should not happen, since caller should have inserted + * appropriate default + */ + if (acl == NULL) + elog(ERROR, "null ACL"); + + check_acl(acl); + + /* Quick exit for mask == 0 */ + if (mask == 0) + return 0; + + result = 0; + + /* Owner always implicitly has all grant options */ + if ((mask & ACLITEM_ALL_GOPTION_BITS) && + roleid == ownerId) + { + result = mask & ACLITEM_ALL_GOPTION_BITS; + if ((how == ACLMASK_ALL) ? (result == mask) : (result != 0)) + return result; + } + + num = ACL_NUM(acl); + aidat = ACL_DAT(acl); + + /* + * Check privileges granted directly to roleid (and not to public) + */ + for (i = 0; i < num; i++) + { + AclItem *aidata = &aidat[i]; + + if (aidata->ai_grantee == roleid) + { + result |= aidata->ai_privs & mask; + if ((how == ACLMASK_ALL) ? (result == mask) : (result != 0)) + return result; + } + } + + return result; +} + + +/* + * aclmembers + * Find out all the roleids mentioned in an Acl. + * Note that we do not distinguish grantors from grantees. + * + * *roleids is set to point to a palloc'd array containing distinct OIDs + * in sorted order. The length of the array is the function result. + */ +int +aclmembers(const Acl *acl, Oid **roleids) +{ + Oid *list; + const AclItem *acldat; + int i, + j; + + if (acl == NULL || ACL_NUM(acl) == 0) + { + *roleids = NULL; + return 0; + } + + check_acl(acl); + + /* Allocate the worst-case space requirement */ + list = palloc(ACL_NUM(acl) * 2 * sizeof(Oid)); + acldat = ACL_DAT(acl); + + /* + * Walk the ACL collecting mentioned RoleIds. + */ + j = 0; + for (i = 0; i < ACL_NUM(acl); i++) + { + const AclItem *ai = &acldat[i]; + + if (ai->ai_grantee != ACL_ID_PUBLIC) + list[j++] = ai->ai_grantee; + /* grantor is currently never PUBLIC, but let's check anyway */ + if (ai->ai_grantor != ACL_ID_PUBLIC) + list[j++] = ai->ai_grantor; + } + + /* Sort the array */ + qsort(list, j, sizeof(Oid), oid_cmp); + + /* + * We could repalloc the array down to minimum size, but it's hardly worth + * it since it's only transient memory. + */ + *roleids = list; + + /* Remove duplicates from the array */ + return qunique(list, j, sizeof(Oid), oid_cmp); +} + + +/* + * aclinsert (exported function) + */ +Datum +aclinsert(PG_FUNCTION_ARGS) +{ + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("aclinsert is no longer supported"))); + + PG_RETURN_NULL(); /* keep compiler quiet */ +} + +Datum +aclremove(PG_FUNCTION_ARGS) +{ + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("aclremove is no longer supported"))); + + PG_RETURN_NULL(); /* keep compiler quiet */ +} + +Datum +aclcontains(PG_FUNCTION_ARGS) +{ + Acl *acl = PG_GETARG_ACL_P(0); + AclItem *aip = PG_GETARG_ACLITEM_P(1); + AclItem *aidat; + int i, + num; + + check_acl(acl); + num = ACL_NUM(acl); + aidat = ACL_DAT(acl); + for (i = 0; i < num; ++i) + { + if (aip->ai_grantee == aidat[i].ai_grantee && + aip->ai_grantor == aidat[i].ai_grantor && + (ACLITEM_GET_RIGHTS(*aip) & ACLITEM_GET_RIGHTS(aidat[i])) == ACLITEM_GET_RIGHTS(*aip)) + PG_RETURN_BOOL(true); + } + PG_RETURN_BOOL(false); +} + +Datum +makeaclitem(PG_FUNCTION_ARGS) +{ + Oid grantee = PG_GETARG_OID(0); + Oid grantor = PG_GETARG_OID(1); + text *privtext = PG_GETARG_TEXT_PP(2); + bool goption = PG_GETARG_BOOL(3); + AclItem *result; + AclMode priv; + static const priv_map any_priv_map[] = { + {"SELECT", ACL_SELECT}, + {"INSERT", ACL_INSERT}, + {"UPDATE", ACL_UPDATE}, + {"DELETE", ACL_DELETE}, + {"TRUNCATE", ACL_TRUNCATE}, + {"REFERENCES", ACL_REFERENCES}, + {"TRIGGER", ACL_TRIGGER}, + {"EXECUTE", ACL_EXECUTE}, + {"USAGE", ACL_USAGE}, + {"CREATE", ACL_CREATE}, + {"TEMP", ACL_CREATE_TEMP}, + {"TEMPORARY", ACL_CREATE_TEMP}, + {"CONNECT", ACL_CONNECT}, + {"SET", ACL_SET}, + {"ALTER SYSTEM", ACL_ALTER_SYSTEM}, + {"RULE", 0}, /* ignore old RULE privileges */ + {NULL, 0} + }; + + priv = convert_any_priv_string(privtext, any_priv_map); + + result = (AclItem *) palloc(sizeof(AclItem)); + + result->ai_grantee = grantee; + result->ai_grantor = grantor; + + ACLITEM_SET_PRIVS_GOPTIONS(*result, priv, + (goption ? priv : ACL_NO_RIGHTS)); + + PG_RETURN_ACLITEM_P(result); +} + + +/* + * convert_any_priv_string: recognize privilege strings for has_foo_privilege + * + * We accept a comma-separated list of case-insensitive privilege names, + * producing a bitmask of the OR'd privilege bits. We are liberal about + * whitespace between items, not so much about whitespace within items. + * The allowed privilege names are given as an array of priv_map structs, + * terminated by one with a NULL name pointer. + */ +static AclMode +convert_any_priv_string(text *priv_type_text, + const priv_map *privileges) +{ + AclMode result = 0; + char *priv_type = text_to_cstring(priv_type_text); + char *chunk; + char *next_chunk; + + /* We rely on priv_type being a private, modifiable string */ + for (chunk = priv_type; chunk; chunk = next_chunk) + { + int chunk_len; + const priv_map *this_priv; + + /* Split string at commas */ + next_chunk = strchr(chunk, ','); + if (next_chunk) + *next_chunk++ = '\0'; + + /* Drop leading/trailing whitespace in this chunk */ + while (*chunk && isspace((unsigned char) *chunk)) + chunk++; + chunk_len = strlen(chunk); + while (chunk_len > 0 && isspace((unsigned char) chunk[chunk_len - 1])) + chunk_len--; + chunk[chunk_len] = '\0'; + + /* Match to the privileges list */ + for (this_priv = privileges; this_priv->name; this_priv++) + { + if (pg_strcasecmp(this_priv->name, chunk) == 0) + { + result |= this_priv->value; + break; + } + } + if (!this_priv->name) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unrecognized privilege type: \"%s\"", chunk))); + } + + pfree(priv_type); + return result; +} + + +static const char * +convert_aclright_to_string(int aclright) +{ + switch (aclright) + { + case ACL_INSERT: + return "INSERT"; + case ACL_SELECT: + return "SELECT"; + case ACL_UPDATE: + return "UPDATE"; + case ACL_DELETE: + return "DELETE"; + case ACL_TRUNCATE: + return "TRUNCATE"; + case ACL_REFERENCES: + return "REFERENCES"; + case ACL_TRIGGER: + return "TRIGGER"; + case ACL_EXECUTE: + return "EXECUTE"; + case ACL_USAGE: + return "USAGE"; + case ACL_CREATE: + return "CREATE"; + case ACL_CREATE_TEMP: + return "TEMPORARY"; + case ACL_CONNECT: + return "CONNECT"; + case ACL_SET: + return "SET"; + case ACL_ALTER_SYSTEM: + return "ALTER SYSTEM"; + default: + elog(ERROR, "unrecognized aclright: %d", aclright); + return NULL; + } +} + + +/*---------- + * Convert an aclitem[] to a table. + * + * Example: + * + * aclexplode('{=r/joe,foo=a*w/joe}'::aclitem[]) + * + * returns the table + * + * {{ OID(joe), 0::OID, 'SELECT', false }, + * { OID(joe), OID(foo), 'INSERT', true }, + * { OID(joe), OID(foo), 'UPDATE', false }} + *---------- + */ +Datum +aclexplode(PG_FUNCTION_ARGS) +{ + Acl *acl = PG_GETARG_ACL_P(0); + FuncCallContext *funcctx; + int *idx; + AclItem *aidat; + + if (SRF_IS_FIRSTCALL()) + { + TupleDesc tupdesc; + MemoryContext oldcontext; + + check_acl(acl); + + funcctx = SRF_FIRSTCALL_INIT(); + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + /* + * build tupdesc for result tuples (matches out parameters in pg_proc + * entry) + */ + tupdesc = CreateTemplateTupleDesc(4); + TupleDescInitEntry(tupdesc, (AttrNumber) 1, "grantor", + OIDOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 2, "grantee", + OIDOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 3, "privilege_type", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 4, "is_grantable", + BOOLOID, -1, 0); + + funcctx->tuple_desc = BlessTupleDesc(tupdesc); + + /* allocate memory for user context */ + idx = (int *) palloc(sizeof(int[2])); + idx[0] = 0; /* ACL array item index */ + idx[1] = -1; /* privilege type counter */ + funcctx->user_fctx = (void *) idx; + + MemoryContextSwitchTo(oldcontext); + } + + funcctx = SRF_PERCALL_SETUP(); + idx = (int *) funcctx->user_fctx; + aidat = ACL_DAT(acl); + + /* need test here in case acl has no items */ + while (idx[0] < ACL_NUM(acl)) + { + AclItem *aidata; + AclMode priv_bit; + + idx[1]++; + if (idx[1] == N_ACL_RIGHTS) + { + idx[1] = 0; + idx[0]++; + if (idx[0] >= ACL_NUM(acl)) /* done */ + break; + } + aidata = &aidat[idx[0]]; + priv_bit = UINT64CONST(1) << idx[1]; + + if (ACLITEM_GET_PRIVS(*aidata) & priv_bit) + { + Datum result; + Datum values[4]; + bool nulls[4] = {0}; + HeapTuple tuple; + + values[0] = ObjectIdGetDatum(aidata->ai_grantor); + values[1] = ObjectIdGetDatum(aidata->ai_grantee); + values[2] = CStringGetTextDatum(convert_aclright_to_string(priv_bit)); + values[3] = BoolGetDatum((ACLITEM_GET_GOPTIONS(*aidata) & priv_bit) != 0); + + tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); + result = HeapTupleGetDatum(tuple); + + SRF_RETURN_NEXT(funcctx, result); + } + } + + SRF_RETURN_DONE(funcctx); +} + + +/* + * has_table_privilege variants + * These are all named "has_table_privilege" at the SQL level. + * They take various combinations of relation name, relation OID, + * user name, user OID, or implicit user = current_user. + * + * The result is a boolean value: true if user has the indicated + * privilege, false if not. The variants that take a relation OID + * return NULL if the OID doesn't exist (rather than failing, as + * they did before Postgres 8.4). + */ + +/* + * has_table_privilege_name_name + * Check user privileges on a table given + * name username, text tablename, and text priv name. + */ +Datum +has_table_privilege_name_name(PG_FUNCTION_ARGS) +{ + Name rolename = PG_GETARG_NAME(0); + text *tablename = PG_GETARG_TEXT_PP(1); + text *priv_type_text = PG_GETARG_TEXT_PP(2); + Oid roleid; + Oid tableoid; + AclMode mode; + AclResult aclresult; + + roleid = get_role_oid_or_public(NameStr(*rolename)); + tableoid = convert_table_name(tablename); + mode = convert_table_priv_string(priv_type_text); + + aclresult = pg_class_aclcheck(tableoid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * has_table_privilege_name + * Check user privileges on a table given + * text tablename and text priv name. + * current_user is assumed + */ +Datum +has_table_privilege_name(PG_FUNCTION_ARGS) +{ + text *tablename = PG_GETARG_TEXT_PP(0); + text *priv_type_text = PG_GETARG_TEXT_PP(1); + Oid roleid; + Oid tableoid; + AclMode mode; + AclResult aclresult; + + roleid = GetUserId(); + tableoid = convert_table_name(tablename); + mode = convert_table_priv_string(priv_type_text); + + aclresult = pg_class_aclcheck(tableoid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * has_table_privilege_name_id + * Check user privileges on a table given + * name usename, table oid, and text priv name. + */ +Datum +has_table_privilege_name_id(PG_FUNCTION_ARGS) +{ + Name username = PG_GETARG_NAME(0); + Oid tableoid = PG_GETARG_OID(1); + text *priv_type_text = PG_GETARG_TEXT_PP(2); + Oid roleid; + AclMode mode; + AclResult aclresult; + + roleid = get_role_oid_or_public(NameStr(*username)); + mode = convert_table_priv_string(priv_type_text); + + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(tableoid))) + PG_RETURN_NULL(); + + aclresult = pg_class_aclcheck(tableoid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * has_table_privilege_id + * Check user privileges on a table given + * table oid, and text priv name. + * current_user is assumed + */ +Datum +has_table_privilege_id(PG_FUNCTION_ARGS) +{ + Oid tableoid = PG_GETARG_OID(0); + text *priv_type_text = PG_GETARG_TEXT_PP(1); + Oid roleid; + AclMode mode; + AclResult aclresult; + + roleid = GetUserId(); + mode = convert_table_priv_string(priv_type_text); + + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(tableoid))) + PG_RETURN_NULL(); + + aclresult = pg_class_aclcheck(tableoid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * has_table_privilege_id_name + * Check user privileges on a table given + * roleid, text tablename, and text priv name. + */ +Datum +has_table_privilege_id_name(PG_FUNCTION_ARGS) +{ + Oid roleid = PG_GETARG_OID(0); + text *tablename = PG_GETARG_TEXT_PP(1); + text *priv_type_text = PG_GETARG_TEXT_PP(2); + Oid tableoid; + AclMode mode; + AclResult aclresult; + + tableoid = convert_table_name(tablename); + mode = convert_table_priv_string(priv_type_text); + + aclresult = pg_class_aclcheck(tableoid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * has_table_privilege_id_id + * Check user privileges on a table given + * roleid, table oid, and text priv name. + */ +Datum +has_table_privilege_id_id(PG_FUNCTION_ARGS) +{ + Oid roleid = PG_GETARG_OID(0); + Oid tableoid = PG_GETARG_OID(1); + text *priv_type_text = PG_GETARG_TEXT_PP(2); + AclMode mode; + AclResult aclresult; + + mode = convert_table_priv_string(priv_type_text); + + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(tableoid))) + PG_RETURN_NULL(); + + aclresult = pg_class_aclcheck(tableoid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * Support routines for has_table_privilege family. + */ + +/* + * Given a table name expressed as a string, look it up and return Oid + */ +static Oid +convert_table_name(text *tablename) +{ + RangeVar *relrv; + + relrv = makeRangeVarFromNameList(textToQualifiedNameList(tablename)); + + /* We might not even have permissions on this relation; don't lock it. */ + return RangeVarGetRelid(relrv, NoLock, false); +} + +/* + * convert_table_priv_string + * Convert text string to AclMode value. + */ +static AclMode +convert_table_priv_string(text *priv_type_text) +{ + static const priv_map table_priv_map[] = { + {"SELECT", ACL_SELECT}, + {"SELECT WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_SELECT)}, + {"INSERT", ACL_INSERT}, + {"INSERT WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_INSERT)}, + {"UPDATE", ACL_UPDATE}, + {"UPDATE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_UPDATE)}, + {"DELETE", ACL_DELETE}, + {"DELETE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_DELETE)}, + {"TRUNCATE", ACL_TRUNCATE}, + {"TRUNCATE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_TRUNCATE)}, + {"REFERENCES", ACL_REFERENCES}, + {"REFERENCES WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_REFERENCES)}, + {"TRIGGER", ACL_TRIGGER}, + {"TRIGGER WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_TRIGGER)}, + {"RULE", 0}, /* ignore old RULE privileges */ + {"RULE WITH GRANT OPTION", 0}, + {NULL, 0} + }; + + return convert_any_priv_string(priv_type_text, table_priv_map); +} + +/* + * has_sequence_privilege variants + * These are all named "has_sequence_privilege" at the SQL level. + * They take various combinations of relation name, relation OID, + * user name, user OID, or implicit user = current_user. + * + * The result is a boolean value: true if user has the indicated + * privilege, false if not. The variants that take a relation OID + * return NULL if the OID doesn't exist. + */ + +/* + * has_sequence_privilege_name_name + * Check user privileges on a sequence given + * name username, text sequencename, and text priv name. + */ +Datum +has_sequence_privilege_name_name(PG_FUNCTION_ARGS) +{ + Name rolename = PG_GETARG_NAME(0); + text *sequencename = PG_GETARG_TEXT_PP(1); + text *priv_type_text = PG_GETARG_TEXT_PP(2); + Oid roleid; + Oid sequenceoid; + AclMode mode; + AclResult aclresult; + + roleid = get_role_oid_or_public(NameStr(*rolename)); + mode = convert_sequence_priv_string(priv_type_text); + sequenceoid = convert_table_name(sequencename); + if (get_rel_relkind(sequenceoid) != RELKIND_SEQUENCE) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is not a sequence", + text_to_cstring(sequencename)))); + + aclresult = pg_class_aclcheck(sequenceoid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * has_sequence_privilege_name + * Check user privileges on a sequence given + * text sequencename and text priv name. + * current_user is assumed + */ +Datum +has_sequence_privilege_name(PG_FUNCTION_ARGS) +{ + text *sequencename = PG_GETARG_TEXT_PP(0); + text *priv_type_text = PG_GETARG_TEXT_PP(1); + Oid roleid; + Oid sequenceoid; + AclMode mode; + AclResult aclresult; + + roleid = GetUserId(); + mode = convert_sequence_priv_string(priv_type_text); + sequenceoid = convert_table_name(sequencename); + if (get_rel_relkind(sequenceoid) != RELKIND_SEQUENCE) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is not a sequence", + text_to_cstring(sequencename)))); + + aclresult = pg_class_aclcheck(sequenceoid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * has_sequence_privilege_name_id + * Check user privileges on a sequence given + * name usename, sequence oid, and text priv name. + */ +Datum +has_sequence_privilege_name_id(PG_FUNCTION_ARGS) +{ + Name username = PG_GETARG_NAME(0); + Oid sequenceoid = PG_GETARG_OID(1); + text *priv_type_text = PG_GETARG_TEXT_PP(2); + Oid roleid; + AclMode mode; + AclResult aclresult; + char relkind; + + roleid = get_role_oid_or_public(NameStr(*username)); + mode = convert_sequence_priv_string(priv_type_text); + relkind = get_rel_relkind(sequenceoid); + if (relkind == '\0') + PG_RETURN_NULL(); + else if (relkind != RELKIND_SEQUENCE) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is not a sequence", + get_rel_name(sequenceoid)))); + + aclresult = pg_class_aclcheck(sequenceoid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * has_sequence_privilege_id + * Check user privileges on a sequence given + * sequence oid, and text priv name. + * current_user is assumed + */ +Datum +has_sequence_privilege_id(PG_FUNCTION_ARGS) +{ + Oid sequenceoid = PG_GETARG_OID(0); + text *priv_type_text = PG_GETARG_TEXT_PP(1); + Oid roleid; + AclMode mode; + AclResult aclresult; + char relkind; + + roleid = GetUserId(); + mode = convert_sequence_priv_string(priv_type_text); + relkind = get_rel_relkind(sequenceoid); + if (relkind == '\0') + PG_RETURN_NULL(); + else if (relkind != RELKIND_SEQUENCE) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is not a sequence", + get_rel_name(sequenceoid)))); + + aclresult = pg_class_aclcheck(sequenceoid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * has_sequence_privilege_id_name + * Check user privileges on a sequence given + * roleid, text sequencename, and text priv name. + */ +Datum +has_sequence_privilege_id_name(PG_FUNCTION_ARGS) +{ + Oid roleid = PG_GETARG_OID(0); + text *sequencename = PG_GETARG_TEXT_PP(1); + text *priv_type_text = PG_GETARG_TEXT_PP(2); + Oid sequenceoid; + AclMode mode; + AclResult aclresult; + + mode = convert_sequence_priv_string(priv_type_text); + sequenceoid = convert_table_name(sequencename); + if (get_rel_relkind(sequenceoid) != RELKIND_SEQUENCE) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is not a sequence", + text_to_cstring(sequencename)))); + + aclresult = pg_class_aclcheck(sequenceoid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * has_sequence_privilege_id_id + * Check user privileges on a sequence given + * roleid, sequence oid, and text priv name. + */ +Datum +has_sequence_privilege_id_id(PG_FUNCTION_ARGS) +{ + Oid roleid = PG_GETARG_OID(0); + Oid sequenceoid = PG_GETARG_OID(1); + text *priv_type_text = PG_GETARG_TEXT_PP(2); + AclMode mode; + AclResult aclresult; + char relkind; + + mode = convert_sequence_priv_string(priv_type_text); + relkind = get_rel_relkind(sequenceoid); + if (relkind == '\0') + PG_RETURN_NULL(); + else if (relkind != RELKIND_SEQUENCE) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is not a sequence", + get_rel_name(sequenceoid)))); + + aclresult = pg_class_aclcheck(sequenceoid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * convert_sequence_priv_string + * Convert text string to AclMode value. + */ +static AclMode +convert_sequence_priv_string(text *priv_type_text) +{ + static const priv_map sequence_priv_map[] = { + {"USAGE", ACL_USAGE}, + {"USAGE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_USAGE)}, + {"SELECT", ACL_SELECT}, + {"SELECT WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_SELECT)}, + {"UPDATE", ACL_UPDATE}, + {"UPDATE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_UPDATE)}, + {NULL, 0} + }; + + return convert_any_priv_string(priv_type_text, sequence_priv_map); +} + + +/* + * has_any_column_privilege variants + * These are all named "has_any_column_privilege" at the SQL level. + * They take various combinations of relation name, relation OID, + * user name, user OID, or implicit user = current_user. + * + * The result is a boolean value: true if user has the indicated + * privilege for any column of the table, false if not. The variants + * that take a relation OID return NULL if the OID doesn't exist. + */ + +/* + * has_any_column_privilege_name_name + * Check user privileges on any column of a table given + * name username, text tablename, and text priv name. + */ +Datum +has_any_column_privilege_name_name(PG_FUNCTION_ARGS) +{ + Name rolename = PG_GETARG_NAME(0); + text *tablename = PG_GETARG_TEXT_PP(1); + text *priv_type_text = PG_GETARG_TEXT_PP(2); + Oid roleid; + Oid tableoid; + AclMode mode; + AclResult aclresult; + + roleid = get_role_oid_or_public(NameStr(*rolename)); + tableoid = convert_table_name(tablename); + mode = convert_column_priv_string(priv_type_text); + + /* First check at table level, then examine each column if needed */ + aclresult = pg_class_aclcheck(tableoid, roleid, mode); + if (aclresult != ACLCHECK_OK) + aclresult = pg_attribute_aclcheck_all(tableoid, roleid, mode, + ACLMASK_ANY); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * has_any_column_privilege_name + * Check user privileges on any column of a table given + * text tablename and text priv name. + * current_user is assumed + */ +Datum +has_any_column_privilege_name(PG_FUNCTION_ARGS) +{ + text *tablename = PG_GETARG_TEXT_PP(0); + text *priv_type_text = PG_GETARG_TEXT_PP(1); + Oid roleid; + Oid tableoid; + AclMode mode; + AclResult aclresult; + + roleid = GetUserId(); + tableoid = convert_table_name(tablename); + mode = convert_column_priv_string(priv_type_text); + + /* First check at table level, then examine each column if needed */ + aclresult = pg_class_aclcheck(tableoid, roleid, mode); + if (aclresult != ACLCHECK_OK) + aclresult = pg_attribute_aclcheck_all(tableoid, roleid, mode, + ACLMASK_ANY); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * has_any_column_privilege_name_id + * Check user privileges on any column of a table given + * name usename, table oid, and text priv name. + */ +Datum +has_any_column_privilege_name_id(PG_FUNCTION_ARGS) +{ + Name username = PG_GETARG_NAME(0); + Oid tableoid = PG_GETARG_OID(1); + text *priv_type_text = PG_GETARG_TEXT_PP(2); + Oid roleid; + AclMode mode; + AclResult aclresult; + + roleid = get_role_oid_or_public(NameStr(*username)); + mode = convert_column_priv_string(priv_type_text); + + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(tableoid))) + PG_RETURN_NULL(); + + /* First check at table level, then examine each column if needed */ + aclresult = pg_class_aclcheck(tableoid, roleid, mode); + if (aclresult != ACLCHECK_OK) + aclresult = pg_attribute_aclcheck_all(tableoid, roleid, mode, + ACLMASK_ANY); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * has_any_column_privilege_id + * Check user privileges on any column of a table given + * table oid, and text priv name. + * current_user is assumed + */ +Datum +has_any_column_privilege_id(PG_FUNCTION_ARGS) +{ + Oid tableoid = PG_GETARG_OID(0); + text *priv_type_text = PG_GETARG_TEXT_PP(1); + Oid roleid; + AclMode mode; + AclResult aclresult; + + roleid = GetUserId(); + mode = convert_column_priv_string(priv_type_text); + + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(tableoid))) + PG_RETURN_NULL(); + + /* First check at table level, then examine each column if needed */ + aclresult = pg_class_aclcheck(tableoid, roleid, mode); + if (aclresult != ACLCHECK_OK) + aclresult = pg_attribute_aclcheck_all(tableoid, roleid, mode, + ACLMASK_ANY); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * has_any_column_privilege_id_name + * Check user privileges on any column of a table given + * roleid, text tablename, and text priv name. + */ +Datum +has_any_column_privilege_id_name(PG_FUNCTION_ARGS) +{ + Oid roleid = PG_GETARG_OID(0); + text *tablename = PG_GETARG_TEXT_PP(1); + text *priv_type_text = PG_GETARG_TEXT_PP(2); + Oid tableoid; + AclMode mode; + AclResult aclresult; + + tableoid = convert_table_name(tablename); + mode = convert_column_priv_string(priv_type_text); + + /* First check at table level, then examine each column if needed */ + aclresult = pg_class_aclcheck(tableoid, roleid, mode); + if (aclresult != ACLCHECK_OK) + aclresult = pg_attribute_aclcheck_all(tableoid, roleid, mode, + ACLMASK_ANY); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * has_any_column_privilege_id_id + * Check user privileges on any column of a table given + * roleid, table oid, and text priv name. + */ +Datum +has_any_column_privilege_id_id(PG_FUNCTION_ARGS) +{ + Oid roleid = PG_GETARG_OID(0); + Oid tableoid = PG_GETARG_OID(1); + text *priv_type_text = PG_GETARG_TEXT_PP(2); + AclMode mode; + AclResult aclresult; + + mode = convert_column_priv_string(priv_type_text); + + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(tableoid))) + PG_RETURN_NULL(); + + /* First check at table level, then examine each column if needed */ + aclresult = pg_class_aclcheck(tableoid, roleid, mode); + if (aclresult != ACLCHECK_OK) + aclresult = pg_attribute_aclcheck_all(tableoid, roleid, mode, + ACLMASK_ANY); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + + +/* + * has_column_privilege variants + * These are all named "has_column_privilege" at the SQL level. + * They take various combinations of relation name, relation OID, + * column name, column attnum, user name, user OID, or + * implicit user = current_user. + * + * The result is a boolean value: true if user has the indicated + * privilege, false if not. The variants that take a relation OID + * return NULL (rather than throwing an error) if that relation OID + * doesn't exist. Likewise, the variants that take an integer attnum + * return NULL (rather than throwing an error) if there is no such + * pg_attribute entry. All variants return NULL if an attisdropped + * column is selected. These rules are meant to avoid unnecessary + * failures in queries that scan pg_attribute. + */ + +/* + * column_privilege_check: check column privileges, but don't throw an error + * for dropped column or table + * + * Returns 1 if have the privilege, 0 if not, -1 if dropped column/table. + */ +static int +column_privilege_check(Oid tableoid, AttrNumber attnum, + Oid roleid, AclMode mode) +{ + AclResult aclresult; + bool is_missing = false; + + /* + * If convert_column_name failed, we can just return -1 immediately. + */ + if (attnum == InvalidAttrNumber) + return -1; + + /* + * Check for column-level privileges first. This serves in part as a check + * on whether the column even exists, so we need to do it before checking + * table-level privilege. + */ + aclresult = pg_attribute_aclcheck_ext(tableoid, attnum, roleid, + mode, &is_missing); + if (aclresult == ACLCHECK_OK) + return 1; + else if (is_missing) + return -1; + + /* Next check if we have the privilege at the table level */ + aclresult = pg_class_aclcheck_ext(tableoid, roleid, mode, &is_missing); + if (aclresult == ACLCHECK_OK) + return 1; + else if (is_missing) + return -1; + else + return 0; +} + +/* + * has_column_privilege_name_name_name + * Check user privileges on a column given + * name username, text tablename, text colname, and text priv name. + */ +Datum +has_column_privilege_name_name_name(PG_FUNCTION_ARGS) +{ + Name rolename = PG_GETARG_NAME(0); + text *tablename = PG_GETARG_TEXT_PP(1); + text *column = PG_GETARG_TEXT_PP(2); + text *priv_type_text = PG_GETARG_TEXT_PP(3); + Oid roleid; + Oid tableoid; + AttrNumber colattnum; + AclMode mode; + int privresult; + + roleid = get_role_oid_or_public(NameStr(*rolename)); + tableoid = convert_table_name(tablename); + colattnum = convert_column_name(tableoid, column); + mode = convert_column_priv_string(priv_type_text); + + privresult = column_privilege_check(tableoid, colattnum, roleid, mode); + if (privresult < 0) + PG_RETURN_NULL(); + PG_RETURN_BOOL(privresult); +} + +/* + * has_column_privilege_name_name_attnum + * Check user privileges on a column given + * name username, text tablename, int attnum, and text priv name. + */ +Datum +has_column_privilege_name_name_attnum(PG_FUNCTION_ARGS) +{ + Name rolename = PG_GETARG_NAME(0); + text *tablename = PG_GETARG_TEXT_PP(1); + AttrNumber colattnum = PG_GETARG_INT16(2); + text *priv_type_text = PG_GETARG_TEXT_PP(3); + Oid roleid; + Oid tableoid; + AclMode mode; + int privresult; + + roleid = get_role_oid_or_public(NameStr(*rolename)); + tableoid = convert_table_name(tablename); + mode = convert_column_priv_string(priv_type_text); + + privresult = column_privilege_check(tableoid, colattnum, roleid, mode); + if (privresult < 0) + PG_RETURN_NULL(); + PG_RETURN_BOOL(privresult); +} + +/* + * has_column_privilege_name_id_name + * Check user privileges on a column given + * name username, table oid, text colname, and text priv name. + */ +Datum +has_column_privilege_name_id_name(PG_FUNCTION_ARGS) +{ + Name username = PG_GETARG_NAME(0); + Oid tableoid = PG_GETARG_OID(1); + text *column = PG_GETARG_TEXT_PP(2); + text *priv_type_text = PG_GETARG_TEXT_PP(3); + Oid roleid; + AttrNumber colattnum; + AclMode mode; + int privresult; + + roleid = get_role_oid_or_public(NameStr(*username)); + colattnum = convert_column_name(tableoid, column); + mode = convert_column_priv_string(priv_type_text); + + privresult = column_privilege_check(tableoid, colattnum, roleid, mode); + if (privresult < 0) + PG_RETURN_NULL(); + PG_RETURN_BOOL(privresult); +} + +/* + * has_column_privilege_name_id_attnum + * Check user privileges on a column given + * name username, table oid, int attnum, and text priv name. + */ +Datum +has_column_privilege_name_id_attnum(PG_FUNCTION_ARGS) +{ + Name username = PG_GETARG_NAME(0); + Oid tableoid = PG_GETARG_OID(1); + AttrNumber colattnum = PG_GETARG_INT16(2); + text *priv_type_text = PG_GETARG_TEXT_PP(3); + Oid roleid; + AclMode mode; + int privresult; + + roleid = get_role_oid_or_public(NameStr(*username)); + mode = convert_column_priv_string(priv_type_text); + + privresult = column_privilege_check(tableoid, colattnum, roleid, mode); + if (privresult < 0) + PG_RETURN_NULL(); + PG_RETURN_BOOL(privresult); +} + +/* + * has_column_privilege_id_name_name + * Check user privileges on a column given + * oid roleid, text tablename, text colname, and text priv name. + */ +Datum +has_column_privilege_id_name_name(PG_FUNCTION_ARGS) +{ + Oid roleid = PG_GETARG_OID(0); + text *tablename = PG_GETARG_TEXT_PP(1); + text *column = PG_GETARG_TEXT_PP(2); + text *priv_type_text = PG_GETARG_TEXT_PP(3); + Oid tableoid; + AttrNumber colattnum; + AclMode mode; + int privresult; + + tableoid = convert_table_name(tablename); + colattnum = convert_column_name(tableoid, column); + mode = convert_column_priv_string(priv_type_text); + + privresult = column_privilege_check(tableoid, colattnum, roleid, mode); + if (privresult < 0) + PG_RETURN_NULL(); + PG_RETURN_BOOL(privresult); +} + +/* + * has_column_privilege_id_name_attnum + * Check user privileges on a column given + * oid roleid, text tablename, int attnum, and text priv name. + */ +Datum +has_column_privilege_id_name_attnum(PG_FUNCTION_ARGS) +{ + Oid roleid = PG_GETARG_OID(0); + text *tablename = PG_GETARG_TEXT_PP(1); + AttrNumber colattnum = PG_GETARG_INT16(2); + text *priv_type_text = PG_GETARG_TEXT_PP(3); + Oid tableoid; + AclMode mode; + int privresult; + + tableoid = convert_table_name(tablename); + mode = convert_column_priv_string(priv_type_text); + + privresult = column_privilege_check(tableoid, colattnum, roleid, mode); + if (privresult < 0) + PG_RETURN_NULL(); + PG_RETURN_BOOL(privresult); +} + +/* + * has_column_privilege_id_id_name + * Check user privileges on a column given + * oid roleid, table oid, text colname, and text priv name. + */ +Datum +has_column_privilege_id_id_name(PG_FUNCTION_ARGS) +{ + Oid roleid = PG_GETARG_OID(0); + Oid tableoid = PG_GETARG_OID(1); + text *column = PG_GETARG_TEXT_PP(2); + text *priv_type_text = PG_GETARG_TEXT_PP(3); + AttrNumber colattnum; + AclMode mode; + int privresult; + + colattnum = convert_column_name(tableoid, column); + mode = convert_column_priv_string(priv_type_text); + + privresult = column_privilege_check(tableoid, colattnum, roleid, mode); + if (privresult < 0) + PG_RETURN_NULL(); + PG_RETURN_BOOL(privresult); +} + +/* + * has_column_privilege_id_id_attnum + * Check user privileges on a column given + * oid roleid, table oid, int attnum, and text priv name. + */ +Datum +has_column_privilege_id_id_attnum(PG_FUNCTION_ARGS) +{ + Oid roleid = PG_GETARG_OID(0); + Oid tableoid = PG_GETARG_OID(1); + AttrNumber colattnum = PG_GETARG_INT16(2); + text *priv_type_text = PG_GETARG_TEXT_PP(3); + AclMode mode; + int privresult; + + mode = convert_column_priv_string(priv_type_text); + + privresult = column_privilege_check(tableoid, colattnum, roleid, mode); + if (privresult < 0) + PG_RETURN_NULL(); + PG_RETURN_BOOL(privresult); +} + +/* + * has_column_privilege_name_name + * Check user privileges on a column given + * text tablename, text colname, and text priv name. + * current_user is assumed + */ +Datum +has_column_privilege_name_name(PG_FUNCTION_ARGS) +{ + text *tablename = PG_GETARG_TEXT_PP(0); + text *column = PG_GETARG_TEXT_PP(1); + text *priv_type_text = PG_GETARG_TEXT_PP(2); + Oid roleid; + Oid tableoid; + AttrNumber colattnum; + AclMode mode; + int privresult; + + roleid = GetUserId(); + tableoid = convert_table_name(tablename); + colattnum = convert_column_name(tableoid, column); + mode = convert_column_priv_string(priv_type_text); + + privresult = column_privilege_check(tableoid, colattnum, roleid, mode); + if (privresult < 0) + PG_RETURN_NULL(); + PG_RETURN_BOOL(privresult); +} + +/* + * has_column_privilege_name_attnum + * Check user privileges on a column given + * text tablename, int attnum, and text priv name. + * current_user is assumed + */ +Datum +has_column_privilege_name_attnum(PG_FUNCTION_ARGS) +{ + text *tablename = PG_GETARG_TEXT_PP(0); + AttrNumber colattnum = PG_GETARG_INT16(1); + text *priv_type_text = PG_GETARG_TEXT_PP(2); + Oid roleid; + Oid tableoid; + AclMode mode; + int privresult; + + roleid = GetUserId(); + tableoid = convert_table_name(tablename); + mode = convert_column_priv_string(priv_type_text); + + privresult = column_privilege_check(tableoid, colattnum, roleid, mode); + if (privresult < 0) + PG_RETURN_NULL(); + PG_RETURN_BOOL(privresult); +} + +/* + * has_column_privilege_id_name + * Check user privileges on a column given + * table oid, text colname, and text priv name. + * current_user is assumed + */ +Datum +has_column_privilege_id_name(PG_FUNCTION_ARGS) +{ + Oid tableoid = PG_GETARG_OID(0); + text *column = PG_GETARG_TEXT_PP(1); + text *priv_type_text = PG_GETARG_TEXT_PP(2); + Oid roleid; + AttrNumber colattnum; + AclMode mode; + int privresult; + + roleid = GetUserId(); + colattnum = convert_column_name(tableoid, column); + mode = convert_column_priv_string(priv_type_text); + + privresult = column_privilege_check(tableoid, colattnum, roleid, mode); + if (privresult < 0) + PG_RETURN_NULL(); + PG_RETURN_BOOL(privresult); +} + +/* + * has_column_privilege_id_attnum + * Check user privileges on a column given + * table oid, int attnum, and text priv name. + * current_user is assumed + */ +Datum +has_column_privilege_id_attnum(PG_FUNCTION_ARGS) +{ + Oid tableoid = PG_GETARG_OID(0); + AttrNumber colattnum = PG_GETARG_INT16(1); + text *priv_type_text = PG_GETARG_TEXT_PP(2); + Oid roleid; + AclMode mode; + int privresult; + + roleid = GetUserId(); + mode = convert_column_priv_string(priv_type_text); + + privresult = column_privilege_check(tableoid, colattnum, roleid, mode); + if (privresult < 0) + PG_RETURN_NULL(); + PG_RETURN_BOOL(privresult); +} + +/* + * Support routines for has_column_privilege family. + */ + +/* + * Given a table OID and a column name expressed as a string, look it up + * and return the column number. Returns InvalidAttrNumber in cases + * where caller should return NULL instead of failing. + */ +static AttrNumber +convert_column_name(Oid tableoid, text *column) +{ + char *colname; + HeapTuple attTuple; + AttrNumber attnum; + + colname = text_to_cstring(column); + + /* + * We don't use get_attnum() here because it will report that dropped + * columns don't exist. We need to treat dropped columns differently from + * nonexistent columns. + */ + attTuple = SearchSysCache2(ATTNAME, + ObjectIdGetDatum(tableoid), + CStringGetDatum(colname)); + if (HeapTupleIsValid(attTuple)) + { + Form_pg_attribute attributeForm; + + attributeForm = (Form_pg_attribute) GETSTRUCT(attTuple); + /* We want to return NULL for dropped columns */ + if (attributeForm->attisdropped) + attnum = InvalidAttrNumber; + else + attnum = attributeForm->attnum; + ReleaseSysCache(attTuple); + } + else + { + char *tablename = get_rel_name(tableoid); + + /* + * If the table OID is bogus, or it's just been dropped, we'll get + * NULL back. In such cases we want has_column_privilege to return + * NULL too, so just return InvalidAttrNumber. + */ + if (tablename != NULL) + { + /* tableoid exists, colname does not, so throw error */ + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column \"%s\" of relation \"%s\" does not exist", + colname, tablename))); + } + /* tableoid doesn't exist, so act like attisdropped case */ + attnum = InvalidAttrNumber; + } + + pfree(colname); + return attnum; +} + +/* + * convert_column_priv_string + * Convert text string to AclMode value. + */ +static AclMode +convert_column_priv_string(text *priv_type_text) +{ + static const priv_map column_priv_map[] = { + {"SELECT", ACL_SELECT}, + {"SELECT WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_SELECT)}, + {"INSERT", ACL_INSERT}, + {"INSERT WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_INSERT)}, + {"UPDATE", ACL_UPDATE}, + {"UPDATE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_UPDATE)}, + {"REFERENCES", ACL_REFERENCES}, + {"REFERENCES WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_REFERENCES)}, + {NULL, 0} + }; + + return convert_any_priv_string(priv_type_text, column_priv_map); +} + + +/* + * has_database_privilege variants + * These are all named "has_database_privilege" at the SQL level. + * They take various combinations of database name, database OID, + * user name, user OID, or implicit user = current_user. + * + * The result is a boolean value: true if user has the indicated + * privilege, false if not, or NULL if object doesn't exist. + */ + +/* + * has_database_privilege_name_name + * Check user privileges on a database given + * name username, text databasename, and text priv name. + */ +Datum +has_database_privilege_name_name(PG_FUNCTION_ARGS) +{ + Name username = PG_GETARG_NAME(0); + text *databasename = PG_GETARG_TEXT_PP(1); + text *priv_type_text = PG_GETARG_TEXT_PP(2); + Oid roleid; + Oid databaseoid; + AclMode mode; + AclResult aclresult; + + roleid = get_role_oid_or_public(NameStr(*username)); + databaseoid = convert_database_name(databasename); + mode = convert_database_priv_string(priv_type_text); + + aclresult = object_aclcheck(DatabaseRelationId, databaseoid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * has_database_privilege_name + * Check user privileges on a database given + * text databasename and text priv name. + * current_user is assumed + */ +Datum +has_database_privilege_name(PG_FUNCTION_ARGS) +{ + text *databasename = PG_GETARG_TEXT_PP(0); + text *priv_type_text = PG_GETARG_TEXT_PP(1); + Oid roleid; + Oid databaseoid; + AclMode mode; + AclResult aclresult; + + roleid = GetUserId(); + databaseoid = convert_database_name(databasename); + mode = convert_database_priv_string(priv_type_text); + + aclresult = object_aclcheck(DatabaseRelationId, databaseoid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * has_database_privilege_name_id + * Check user privileges on a database given + * name usename, database oid, and text priv name. + */ +Datum +has_database_privilege_name_id(PG_FUNCTION_ARGS) +{ + Name username = PG_GETARG_NAME(0); + Oid databaseoid = PG_GETARG_OID(1); + text *priv_type_text = PG_GETARG_TEXT_PP(2); + Oid roleid; + AclMode mode; + AclResult aclresult; + + roleid = get_role_oid_or_public(NameStr(*username)); + mode = convert_database_priv_string(priv_type_text); + + if (!SearchSysCacheExists1(DATABASEOID, ObjectIdGetDatum(databaseoid))) + PG_RETURN_NULL(); + + aclresult = object_aclcheck(DatabaseRelationId, databaseoid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * has_database_privilege_id + * Check user privileges on a database given + * database oid, and text priv name. + * current_user is assumed + */ +Datum +has_database_privilege_id(PG_FUNCTION_ARGS) +{ + Oid databaseoid = PG_GETARG_OID(0); + text *priv_type_text = PG_GETARG_TEXT_PP(1); + Oid roleid; + AclMode mode; + AclResult aclresult; + + roleid = GetUserId(); + mode = convert_database_priv_string(priv_type_text); + + if (!SearchSysCacheExists1(DATABASEOID, ObjectIdGetDatum(databaseoid))) + PG_RETURN_NULL(); + + aclresult = object_aclcheck(DatabaseRelationId, databaseoid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * has_database_privilege_id_name + * Check user privileges on a database given + * roleid, text databasename, and text priv name. + */ +Datum +has_database_privilege_id_name(PG_FUNCTION_ARGS) +{ + Oid roleid = PG_GETARG_OID(0); + text *databasename = PG_GETARG_TEXT_PP(1); + text *priv_type_text = PG_GETARG_TEXT_PP(2); + Oid databaseoid; + AclMode mode; + AclResult aclresult; + + databaseoid = convert_database_name(databasename); + mode = convert_database_priv_string(priv_type_text); + + aclresult = object_aclcheck(DatabaseRelationId, databaseoid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * has_database_privilege_id_id + * Check user privileges on a database given + * roleid, database oid, and text priv name. + */ +Datum +has_database_privilege_id_id(PG_FUNCTION_ARGS) +{ + Oid roleid = PG_GETARG_OID(0); + Oid databaseoid = PG_GETARG_OID(1); + text *priv_type_text = PG_GETARG_TEXT_PP(2); + AclMode mode; + AclResult aclresult; + + mode = convert_database_priv_string(priv_type_text); + + if (!SearchSysCacheExists1(DATABASEOID, ObjectIdGetDatum(databaseoid))) + PG_RETURN_NULL(); + + aclresult = object_aclcheck(DatabaseRelationId, databaseoid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * Support routines for has_database_privilege family. + */ + +/* + * Given a database name expressed as a string, look it up and return Oid + */ +static Oid +convert_database_name(text *databasename) +{ + char *dbname = text_to_cstring(databasename); + + return get_database_oid(dbname, false); +} + +/* + * convert_database_priv_string + * Convert text string to AclMode value. + */ +static AclMode +convert_database_priv_string(text *priv_type_text) +{ + static const priv_map database_priv_map[] = { + {"CREATE", ACL_CREATE}, + {"CREATE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_CREATE)}, + {"TEMPORARY", ACL_CREATE_TEMP}, + {"TEMPORARY WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_CREATE_TEMP)}, + {"TEMP", ACL_CREATE_TEMP}, + {"TEMP WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_CREATE_TEMP)}, + {"CONNECT", ACL_CONNECT}, + {"CONNECT WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_CONNECT)}, + {NULL, 0} + }; + + return convert_any_priv_string(priv_type_text, database_priv_map); +} + + +/* + * has_foreign_data_wrapper_privilege variants + * These are all named "has_foreign_data_wrapper_privilege" at the SQL level. + * They take various combinations of foreign-data wrapper name, + * fdw OID, user name, user OID, or implicit user = current_user. + * + * The result is a boolean value: true if user has the indicated + * privilege, false if not. + */ + +/* + * has_foreign_data_wrapper_privilege_name_name + * Check user privileges on a foreign-data wrapper given + * name username, text fdwname, and text priv name. + */ +Datum +has_foreign_data_wrapper_privilege_name_name(PG_FUNCTION_ARGS) +{ + Name username = PG_GETARG_NAME(0); + text *fdwname = PG_GETARG_TEXT_PP(1); + text *priv_type_text = PG_GETARG_TEXT_PP(2); + Oid roleid; + Oid fdwid; + AclMode mode; + AclResult aclresult; + + roleid = get_role_oid_or_public(NameStr(*username)); + fdwid = convert_foreign_data_wrapper_name(fdwname); + mode = convert_foreign_data_wrapper_priv_string(priv_type_text); + + aclresult = object_aclcheck(ForeignDataWrapperRelationId, fdwid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * has_foreign_data_wrapper_privilege_name + * Check user privileges on a foreign-data wrapper given + * text fdwname and text priv name. + * current_user is assumed + */ +Datum +has_foreign_data_wrapper_privilege_name(PG_FUNCTION_ARGS) +{ + text *fdwname = PG_GETARG_TEXT_PP(0); + text *priv_type_text = PG_GETARG_TEXT_PP(1); + Oid roleid; + Oid fdwid; + AclMode mode; + AclResult aclresult; + + roleid = GetUserId(); + fdwid = convert_foreign_data_wrapper_name(fdwname); + mode = convert_foreign_data_wrapper_priv_string(priv_type_text); + + aclresult = object_aclcheck(ForeignDataWrapperRelationId, fdwid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * has_foreign_data_wrapper_privilege_name_id + * Check user privileges on a foreign-data wrapper given + * name usename, foreign-data wrapper oid, and text priv name. + */ +Datum +has_foreign_data_wrapper_privilege_name_id(PG_FUNCTION_ARGS) +{ + Name username = PG_GETARG_NAME(0); + Oid fdwid = PG_GETARG_OID(1); + text *priv_type_text = PG_GETARG_TEXT_PP(2); + Oid roleid; + AclMode mode; + AclResult aclresult; + + roleid = get_role_oid_or_public(NameStr(*username)); + mode = convert_foreign_data_wrapper_priv_string(priv_type_text); + + if (!SearchSysCacheExists1(FOREIGNDATAWRAPPEROID, ObjectIdGetDatum(fdwid))) + PG_RETURN_NULL(); + + aclresult = object_aclcheck(ForeignDataWrapperRelationId, fdwid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * has_foreign_data_wrapper_privilege_id + * Check user privileges on a foreign-data wrapper given + * foreign-data wrapper oid, and text priv name. + * current_user is assumed + */ +Datum +has_foreign_data_wrapper_privilege_id(PG_FUNCTION_ARGS) +{ + Oid fdwid = PG_GETARG_OID(0); + text *priv_type_text = PG_GETARG_TEXT_PP(1); + Oid roleid; + AclMode mode; + AclResult aclresult; + + roleid = GetUserId(); + mode = convert_foreign_data_wrapper_priv_string(priv_type_text); + + if (!SearchSysCacheExists1(FOREIGNDATAWRAPPEROID, ObjectIdGetDatum(fdwid))) + PG_RETURN_NULL(); + + aclresult = object_aclcheck(ForeignDataWrapperRelationId, fdwid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * has_foreign_data_wrapper_privilege_id_name + * Check user privileges on a foreign-data wrapper given + * roleid, text fdwname, and text priv name. + */ +Datum +has_foreign_data_wrapper_privilege_id_name(PG_FUNCTION_ARGS) +{ + Oid roleid = PG_GETARG_OID(0); + text *fdwname = PG_GETARG_TEXT_PP(1); + text *priv_type_text = PG_GETARG_TEXT_PP(2); + Oid fdwid; + AclMode mode; + AclResult aclresult; + + fdwid = convert_foreign_data_wrapper_name(fdwname); + mode = convert_foreign_data_wrapper_priv_string(priv_type_text); + + aclresult = object_aclcheck(ForeignDataWrapperRelationId, fdwid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * has_foreign_data_wrapper_privilege_id_id + * Check user privileges on a foreign-data wrapper given + * roleid, fdw oid, and text priv name. + */ +Datum +has_foreign_data_wrapper_privilege_id_id(PG_FUNCTION_ARGS) +{ + Oid roleid = PG_GETARG_OID(0); + Oid fdwid = PG_GETARG_OID(1); + text *priv_type_text = PG_GETARG_TEXT_PP(2); + AclMode mode; + AclResult aclresult; + + mode = convert_foreign_data_wrapper_priv_string(priv_type_text); + + if (!SearchSysCacheExists1(FOREIGNDATAWRAPPEROID, ObjectIdGetDatum(fdwid))) + PG_RETURN_NULL(); + + aclresult = object_aclcheck(ForeignDataWrapperRelationId, fdwid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * Support routines for has_foreign_data_wrapper_privilege family. + */ + +/* + * Given a FDW name expressed as a string, look it up and return Oid + */ +static Oid +convert_foreign_data_wrapper_name(text *fdwname) +{ + char *fdwstr = text_to_cstring(fdwname); + + return get_foreign_data_wrapper_oid(fdwstr, false); +} + +/* + * convert_foreign_data_wrapper_priv_string + * Convert text string to AclMode value. + */ +static AclMode +convert_foreign_data_wrapper_priv_string(text *priv_type_text) +{ + static const priv_map foreign_data_wrapper_priv_map[] = { + {"USAGE", ACL_USAGE}, + {"USAGE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_USAGE)}, + {NULL, 0} + }; + + return convert_any_priv_string(priv_type_text, foreign_data_wrapper_priv_map); +} + + +/* + * has_function_privilege variants + * These are all named "has_function_privilege" at the SQL level. + * They take various combinations of function name, function OID, + * user name, user OID, or implicit user = current_user. + * + * The result is a boolean value: true if user has the indicated + * privilege, false if not, or NULL if object doesn't exist. + */ + +/* + * has_function_privilege_name_name + * Check user privileges on a function given + * name username, text functionname, and text priv name. + */ +Datum +has_function_privilege_name_name(PG_FUNCTION_ARGS) +{ + Name username = PG_GETARG_NAME(0); + text *functionname = PG_GETARG_TEXT_PP(1); + text *priv_type_text = PG_GETARG_TEXT_PP(2); + Oid roleid; + Oid functionoid; + AclMode mode; + AclResult aclresult; + + roleid = get_role_oid_or_public(NameStr(*username)); + functionoid = convert_function_name(functionname); + mode = convert_function_priv_string(priv_type_text); + + aclresult = object_aclcheck(ProcedureRelationId, functionoid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * has_function_privilege_name + * Check user privileges on a function given + * text functionname and text priv name. + * current_user is assumed + */ +Datum +has_function_privilege_name(PG_FUNCTION_ARGS) +{ + text *functionname = PG_GETARG_TEXT_PP(0); + text *priv_type_text = PG_GETARG_TEXT_PP(1); + Oid roleid; + Oid functionoid; + AclMode mode; + AclResult aclresult; + + roleid = GetUserId(); + functionoid = convert_function_name(functionname); + mode = convert_function_priv_string(priv_type_text); + + aclresult = object_aclcheck(ProcedureRelationId, functionoid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * has_function_privilege_name_id + * Check user privileges on a function given + * name usename, function oid, and text priv name. + */ +Datum +has_function_privilege_name_id(PG_FUNCTION_ARGS) +{ + Name username = PG_GETARG_NAME(0); + Oid functionoid = PG_GETARG_OID(1); + text *priv_type_text = PG_GETARG_TEXT_PP(2); + Oid roleid; + AclMode mode; + AclResult aclresult; + + roleid = get_role_oid_or_public(NameStr(*username)); + mode = convert_function_priv_string(priv_type_text); + + if (!SearchSysCacheExists1(PROCOID, ObjectIdGetDatum(functionoid))) + PG_RETURN_NULL(); + + aclresult = object_aclcheck(ProcedureRelationId, functionoid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * has_function_privilege_id + * Check user privileges on a function given + * function oid, and text priv name. + * current_user is assumed + */ +Datum +has_function_privilege_id(PG_FUNCTION_ARGS) +{ + Oid functionoid = PG_GETARG_OID(0); + text *priv_type_text = PG_GETARG_TEXT_PP(1); + Oid roleid; + AclMode mode; + AclResult aclresult; + + roleid = GetUserId(); + mode = convert_function_priv_string(priv_type_text); + + if (!SearchSysCacheExists1(PROCOID, ObjectIdGetDatum(functionoid))) + PG_RETURN_NULL(); + + aclresult = object_aclcheck(ProcedureRelationId, functionoid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * has_function_privilege_id_name + * Check user privileges on a function given + * roleid, text functionname, and text priv name. + */ +Datum +has_function_privilege_id_name(PG_FUNCTION_ARGS) +{ + Oid roleid = PG_GETARG_OID(0); + text *functionname = PG_GETARG_TEXT_PP(1); + text *priv_type_text = PG_GETARG_TEXT_PP(2); + Oid functionoid; + AclMode mode; + AclResult aclresult; + + functionoid = convert_function_name(functionname); + mode = convert_function_priv_string(priv_type_text); + + aclresult = object_aclcheck(ProcedureRelationId, functionoid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * has_function_privilege_id_id + * Check user privileges on a function given + * roleid, function oid, and text priv name. + */ +Datum +has_function_privilege_id_id(PG_FUNCTION_ARGS) +{ + Oid roleid = PG_GETARG_OID(0); + Oid functionoid = PG_GETARG_OID(1); + text *priv_type_text = PG_GETARG_TEXT_PP(2); + AclMode mode; + AclResult aclresult; + + mode = convert_function_priv_string(priv_type_text); + + if (!SearchSysCacheExists1(PROCOID, ObjectIdGetDatum(functionoid))) + PG_RETURN_NULL(); + + aclresult = object_aclcheck(ProcedureRelationId, functionoid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * Support routines for has_function_privilege family. + */ + +/* + * Given a function name expressed as a string, look it up and return Oid + */ +static Oid +convert_function_name(text *functionname) +{ + char *funcname = text_to_cstring(functionname); + Oid oid; + + oid = DatumGetObjectId(DirectFunctionCall1(regprocedurein, + CStringGetDatum(funcname))); + + if (!OidIsValid(oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("function \"%s\" does not exist", funcname))); + + return oid; +} + +/* + * convert_function_priv_string + * Convert text string to AclMode value. + */ +static AclMode +convert_function_priv_string(text *priv_type_text) +{ + static const priv_map function_priv_map[] = { + {"EXECUTE", ACL_EXECUTE}, + {"EXECUTE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_EXECUTE)}, + {NULL, 0} + }; + + return convert_any_priv_string(priv_type_text, function_priv_map); +} + + +/* + * has_language_privilege variants + * These are all named "has_language_privilege" at the SQL level. + * They take various combinations of language name, language OID, + * user name, user OID, or implicit user = current_user. + * + * The result is a boolean value: true if user has the indicated + * privilege, false if not, or NULL if object doesn't exist. + */ + +/* + * has_language_privilege_name_name + * Check user privileges on a language given + * name username, text languagename, and text priv name. + */ +Datum +has_language_privilege_name_name(PG_FUNCTION_ARGS) +{ + Name username = PG_GETARG_NAME(0); + text *languagename = PG_GETARG_TEXT_PP(1); + text *priv_type_text = PG_GETARG_TEXT_PP(2); + Oid roleid; + Oid languageoid; + AclMode mode; + AclResult aclresult; + + roleid = get_role_oid_or_public(NameStr(*username)); + languageoid = convert_language_name(languagename); + mode = convert_language_priv_string(priv_type_text); + + aclresult = object_aclcheck(LanguageRelationId, languageoid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * has_language_privilege_name + * Check user privileges on a language given + * text languagename and text priv name. + * current_user is assumed + */ +Datum +has_language_privilege_name(PG_FUNCTION_ARGS) +{ + text *languagename = PG_GETARG_TEXT_PP(0); + text *priv_type_text = PG_GETARG_TEXT_PP(1); + Oid roleid; + Oid languageoid; + AclMode mode; + AclResult aclresult; + + roleid = GetUserId(); + languageoid = convert_language_name(languagename); + mode = convert_language_priv_string(priv_type_text); + + aclresult = object_aclcheck(LanguageRelationId, languageoid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * has_language_privilege_name_id + * Check user privileges on a language given + * name usename, language oid, and text priv name. + */ +Datum +has_language_privilege_name_id(PG_FUNCTION_ARGS) +{ + Name username = PG_GETARG_NAME(0); + Oid languageoid = PG_GETARG_OID(1); + text *priv_type_text = PG_GETARG_TEXT_PP(2); + Oid roleid; + AclMode mode; + AclResult aclresult; + + roleid = get_role_oid_or_public(NameStr(*username)); + mode = convert_language_priv_string(priv_type_text); + + if (!SearchSysCacheExists1(LANGOID, ObjectIdGetDatum(languageoid))) + PG_RETURN_NULL(); + + aclresult = object_aclcheck(LanguageRelationId, languageoid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * has_language_privilege_id + * Check user privileges on a language given + * language oid, and text priv name. + * current_user is assumed + */ +Datum +has_language_privilege_id(PG_FUNCTION_ARGS) +{ + Oid languageoid = PG_GETARG_OID(0); + text *priv_type_text = PG_GETARG_TEXT_PP(1); + Oid roleid; + AclMode mode; + AclResult aclresult; + + roleid = GetUserId(); + mode = convert_language_priv_string(priv_type_text); + + if (!SearchSysCacheExists1(LANGOID, ObjectIdGetDatum(languageoid))) + PG_RETURN_NULL(); + + aclresult = object_aclcheck(LanguageRelationId, languageoid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * has_language_privilege_id_name + * Check user privileges on a language given + * roleid, text languagename, and text priv name. + */ +Datum +has_language_privilege_id_name(PG_FUNCTION_ARGS) +{ + Oid roleid = PG_GETARG_OID(0); + text *languagename = PG_GETARG_TEXT_PP(1); + text *priv_type_text = PG_GETARG_TEXT_PP(2); + Oid languageoid; + AclMode mode; + AclResult aclresult; + + languageoid = convert_language_name(languagename); + mode = convert_language_priv_string(priv_type_text); + + aclresult = object_aclcheck(LanguageRelationId, languageoid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * has_language_privilege_id_id + * Check user privileges on a language given + * roleid, language oid, and text priv name. + */ +Datum +has_language_privilege_id_id(PG_FUNCTION_ARGS) +{ + Oid roleid = PG_GETARG_OID(0); + Oid languageoid = PG_GETARG_OID(1); + text *priv_type_text = PG_GETARG_TEXT_PP(2); + AclMode mode; + AclResult aclresult; + + mode = convert_language_priv_string(priv_type_text); + + if (!SearchSysCacheExists1(LANGOID, ObjectIdGetDatum(languageoid))) + PG_RETURN_NULL(); + + aclresult = object_aclcheck(LanguageRelationId, languageoid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * Support routines for has_language_privilege family. + */ + +/* + * Given a language name expressed as a string, look it up and return Oid + */ +static Oid +convert_language_name(text *languagename) +{ + char *langname = text_to_cstring(languagename); + + return get_language_oid(langname, false); +} + +/* + * convert_language_priv_string + * Convert text string to AclMode value. + */ +static AclMode +convert_language_priv_string(text *priv_type_text) +{ + static const priv_map language_priv_map[] = { + {"USAGE", ACL_USAGE}, + {"USAGE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_USAGE)}, + {NULL, 0} + }; + + return convert_any_priv_string(priv_type_text, language_priv_map); +} + + +/* + * has_schema_privilege variants + * These are all named "has_schema_privilege" at the SQL level. + * They take various combinations of schema name, schema OID, + * user name, user OID, or implicit user = current_user. + * + * The result is a boolean value: true if user has the indicated + * privilege, false if not, or NULL if object doesn't exist. + */ + +/* + * has_schema_privilege_name_name + * Check user privileges on a schema given + * name username, text schemaname, and text priv name. + */ +Datum +has_schema_privilege_name_name(PG_FUNCTION_ARGS) +{ + Name username = PG_GETARG_NAME(0); + text *schemaname = PG_GETARG_TEXT_PP(1); + text *priv_type_text = PG_GETARG_TEXT_PP(2); + Oid roleid; + Oid schemaoid; + AclMode mode; + AclResult aclresult; + + roleid = get_role_oid_or_public(NameStr(*username)); + schemaoid = convert_schema_name(schemaname); + mode = convert_schema_priv_string(priv_type_text); + + aclresult = object_aclcheck(NamespaceRelationId, schemaoid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * has_schema_privilege_name + * Check user privileges on a schema given + * text schemaname and text priv name. + * current_user is assumed + */ +Datum +has_schema_privilege_name(PG_FUNCTION_ARGS) +{ + text *schemaname = PG_GETARG_TEXT_PP(0); + text *priv_type_text = PG_GETARG_TEXT_PP(1); + Oid roleid; + Oid schemaoid; + AclMode mode; + AclResult aclresult; + + roleid = GetUserId(); + schemaoid = convert_schema_name(schemaname); + mode = convert_schema_priv_string(priv_type_text); + + aclresult = object_aclcheck(NamespaceRelationId, schemaoid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * has_schema_privilege_name_id + * Check user privileges on a schema given + * name usename, schema oid, and text priv name. + */ +Datum +has_schema_privilege_name_id(PG_FUNCTION_ARGS) +{ + Name username = PG_GETARG_NAME(0); + Oid schemaoid = PG_GETARG_OID(1); + text *priv_type_text = PG_GETARG_TEXT_PP(2); + Oid roleid; + AclMode mode; + AclResult aclresult; + + roleid = get_role_oid_or_public(NameStr(*username)); + mode = convert_schema_priv_string(priv_type_text); + + if (!SearchSysCacheExists1(NAMESPACEOID, ObjectIdGetDatum(schemaoid))) + PG_RETURN_NULL(); + + aclresult = object_aclcheck(NamespaceRelationId, schemaoid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * has_schema_privilege_id + * Check user privileges on a schema given + * schema oid, and text priv name. + * current_user is assumed + */ +Datum +has_schema_privilege_id(PG_FUNCTION_ARGS) +{ + Oid schemaoid = PG_GETARG_OID(0); + text *priv_type_text = PG_GETARG_TEXT_PP(1); + Oid roleid; + AclMode mode; + AclResult aclresult; + + roleid = GetUserId(); + mode = convert_schema_priv_string(priv_type_text); + + if (!SearchSysCacheExists1(NAMESPACEOID, ObjectIdGetDatum(schemaoid))) + PG_RETURN_NULL(); + + aclresult = object_aclcheck(NamespaceRelationId, schemaoid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * has_schema_privilege_id_name + * Check user privileges on a schema given + * roleid, text schemaname, and text priv name. + */ +Datum +has_schema_privilege_id_name(PG_FUNCTION_ARGS) +{ + Oid roleid = PG_GETARG_OID(0); + text *schemaname = PG_GETARG_TEXT_PP(1); + text *priv_type_text = PG_GETARG_TEXT_PP(2); + Oid schemaoid; + AclMode mode; + AclResult aclresult; + + schemaoid = convert_schema_name(schemaname); + mode = convert_schema_priv_string(priv_type_text); + + aclresult = object_aclcheck(NamespaceRelationId, schemaoid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * has_schema_privilege_id_id + * Check user privileges on a schema given + * roleid, schema oid, and text priv name. + */ +Datum +has_schema_privilege_id_id(PG_FUNCTION_ARGS) +{ + Oid roleid = PG_GETARG_OID(0); + Oid schemaoid = PG_GETARG_OID(1); + text *priv_type_text = PG_GETARG_TEXT_PP(2); + AclMode mode; + AclResult aclresult; + + mode = convert_schema_priv_string(priv_type_text); + + if (!SearchSysCacheExists1(NAMESPACEOID, ObjectIdGetDatum(schemaoid))) + PG_RETURN_NULL(); + + aclresult = object_aclcheck(NamespaceRelationId, schemaoid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * Support routines for has_schema_privilege family. + */ + +/* + * Given a schema name expressed as a string, look it up and return Oid + */ +static Oid +convert_schema_name(text *schemaname) +{ + char *nspname = text_to_cstring(schemaname); + + return get_namespace_oid(nspname, false); +} + +/* + * convert_schema_priv_string + * Convert text string to AclMode value. + */ +static AclMode +convert_schema_priv_string(text *priv_type_text) +{ + static const priv_map schema_priv_map[] = { + {"CREATE", ACL_CREATE}, + {"CREATE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_CREATE)}, + {"USAGE", ACL_USAGE}, + {"USAGE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_USAGE)}, + {NULL, 0} + }; + + return convert_any_priv_string(priv_type_text, schema_priv_map); +} + + +/* + * has_server_privilege variants + * These are all named "has_server_privilege" at the SQL level. + * They take various combinations of foreign server name, + * server OID, user name, user OID, or implicit user = current_user. + * + * The result is a boolean value: true if user has the indicated + * privilege, false if not. + */ + +/* + * has_server_privilege_name_name + * Check user privileges on a foreign server given + * name username, text servername, and text priv name. + */ +Datum +has_server_privilege_name_name(PG_FUNCTION_ARGS) +{ + Name username = PG_GETARG_NAME(0); + text *servername = PG_GETARG_TEXT_PP(1); + text *priv_type_text = PG_GETARG_TEXT_PP(2); + Oid roleid; + Oid serverid; + AclMode mode; + AclResult aclresult; + + roleid = get_role_oid_or_public(NameStr(*username)); + serverid = convert_server_name(servername); + mode = convert_server_priv_string(priv_type_text); + + aclresult = object_aclcheck(ForeignServerRelationId, serverid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * has_server_privilege_name + * Check user privileges on a foreign server given + * text servername and text priv name. + * current_user is assumed + */ +Datum +has_server_privilege_name(PG_FUNCTION_ARGS) +{ + text *servername = PG_GETARG_TEXT_PP(0); + text *priv_type_text = PG_GETARG_TEXT_PP(1); + Oid roleid; + Oid serverid; + AclMode mode; + AclResult aclresult; + + roleid = GetUserId(); + serverid = convert_server_name(servername); + mode = convert_server_priv_string(priv_type_text); + + aclresult = object_aclcheck(ForeignServerRelationId, serverid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * has_server_privilege_name_id + * Check user privileges on a foreign server given + * name usename, foreign server oid, and text priv name. + */ +Datum +has_server_privilege_name_id(PG_FUNCTION_ARGS) +{ + Name username = PG_GETARG_NAME(0); + Oid serverid = PG_GETARG_OID(1); + text *priv_type_text = PG_GETARG_TEXT_PP(2); + Oid roleid; + AclMode mode; + AclResult aclresult; + + roleid = get_role_oid_or_public(NameStr(*username)); + mode = convert_server_priv_string(priv_type_text); + + if (!SearchSysCacheExists1(FOREIGNSERVEROID, ObjectIdGetDatum(serverid))) + PG_RETURN_NULL(); + + aclresult = object_aclcheck(ForeignServerRelationId, serverid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * has_server_privilege_id + * Check user privileges on a foreign server given + * server oid, and text priv name. + * current_user is assumed + */ +Datum +has_server_privilege_id(PG_FUNCTION_ARGS) +{ + Oid serverid = PG_GETARG_OID(0); + text *priv_type_text = PG_GETARG_TEXT_PP(1); + Oid roleid; + AclMode mode; + AclResult aclresult; + + roleid = GetUserId(); + mode = convert_server_priv_string(priv_type_text); + + if (!SearchSysCacheExists1(FOREIGNSERVEROID, ObjectIdGetDatum(serverid))) + PG_RETURN_NULL(); + + aclresult = object_aclcheck(ForeignServerRelationId, serverid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * has_server_privilege_id_name + * Check user privileges on a foreign server given + * roleid, text servername, and text priv name. + */ +Datum +has_server_privilege_id_name(PG_FUNCTION_ARGS) +{ + Oid roleid = PG_GETARG_OID(0); + text *servername = PG_GETARG_TEXT_PP(1); + text *priv_type_text = PG_GETARG_TEXT_PP(2); + Oid serverid; + AclMode mode; + AclResult aclresult; + + serverid = convert_server_name(servername); + mode = convert_server_priv_string(priv_type_text); + + aclresult = object_aclcheck(ForeignServerRelationId, serverid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * has_server_privilege_id_id + * Check user privileges on a foreign server given + * roleid, server oid, and text priv name. + */ +Datum +has_server_privilege_id_id(PG_FUNCTION_ARGS) +{ + Oid roleid = PG_GETARG_OID(0); + Oid serverid = PG_GETARG_OID(1); + text *priv_type_text = PG_GETARG_TEXT_PP(2); + AclMode mode; + AclResult aclresult; + + mode = convert_server_priv_string(priv_type_text); + + if (!SearchSysCacheExists1(FOREIGNSERVEROID, ObjectIdGetDatum(serverid))) + PG_RETURN_NULL(); + + aclresult = object_aclcheck(ForeignServerRelationId, serverid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * Support routines for has_server_privilege family. + */ + +/* + * Given a server name expressed as a string, look it up and return Oid + */ +static Oid +convert_server_name(text *servername) +{ + char *serverstr = text_to_cstring(servername); + + return get_foreign_server_oid(serverstr, false); +} + +/* + * convert_server_priv_string + * Convert text string to AclMode value. + */ +static AclMode +convert_server_priv_string(text *priv_type_text) +{ + static const priv_map server_priv_map[] = { + {"USAGE", ACL_USAGE}, + {"USAGE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_USAGE)}, + {NULL, 0} + }; + + return convert_any_priv_string(priv_type_text, server_priv_map); +} + + +/* + * has_tablespace_privilege variants + * These are all named "has_tablespace_privilege" at the SQL level. + * They take various combinations of tablespace name, tablespace OID, + * user name, user OID, or implicit user = current_user. + * + * The result is a boolean value: true if user has the indicated + * privilege, false if not. + */ + +/* + * has_tablespace_privilege_name_name + * Check user privileges on a tablespace given + * name username, text tablespacename, and text priv name. + */ +Datum +has_tablespace_privilege_name_name(PG_FUNCTION_ARGS) +{ + Name username = PG_GETARG_NAME(0); + text *tablespacename = PG_GETARG_TEXT_PP(1); + text *priv_type_text = PG_GETARG_TEXT_PP(2); + Oid roleid; + Oid tablespaceoid; + AclMode mode; + AclResult aclresult; + + roleid = get_role_oid_or_public(NameStr(*username)); + tablespaceoid = convert_tablespace_name(tablespacename); + mode = convert_tablespace_priv_string(priv_type_text); + + aclresult = object_aclcheck(TableSpaceRelationId, tablespaceoid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * has_tablespace_privilege_name + * Check user privileges on a tablespace given + * text tablespacename and text priv name. + * current_user is assumed + */ +Datum +has_tablespace_privilege_name(PG_FUNCTION_ARGS) +{ + text *tablespacename = PG_GETARG_TEXT_PP(0); + text *priv_type_text = PG_GETARG_TEXT_PP(1); + Oid roleid; + Oid tablespaceoid; + AclMode mode; + AclResult aclresult; + + roleid = GetUserId(); + tablespaceoid = convert_tablespace_name(tablespacename); + mode = convert_tablespace_priv_string(priv_type_text); + + aclresult = object_aclcheck(TableSpaceRelationId, tablespaceoid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * has_tablespace_privilege_name_id + * Check user privileges on a tablespace given + * name usename, tablespace oid, and text priv name. + */ +Datum +has_tablespace_privilege_name_id(PG_FUNCTION_ARGS) +{ + Name username = PG_GETARG_NAME(0); + Oid tablespaceoid = PG_GETARG_OID(1); + text *priv_type_text = PG_GETARG_TEXT_PP(2); + Oid roleid; + AclMode mode; + AclResult aclresult; + + roleid = get_role_oid_or_public(NameStr(*username)); + mode = convert_tablespace_priv_string(priv_type_text); + + if (!SearchSysCacheExists1(TABLESPACEOID, ObjectIdGetDatum(tablespaceoid))) + PG_RETURN_NULL(); + + aclresult = object_aclcheck(TableSpaceRelationId, tablespaceoid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * has_tablespace_privilege_id + * Check user privileges on a tablespace given + * tablespace oid, and text priv name. + * current_user is assumed + */ +Datum +has_tablespace_privilege_id(PG_FUNCTION_ARGS) +{ + Oid tablespaceoid = PG_GETARG_OID(0); + text *priv_type_text = PG_GETARG_TEXT_PP(1); + Oid roleid; + AclMode mode; + AclResult aclresult; + + roleid = GetUserId(); + mode = convert_tablespace_priv_string(priv_type_text); + + if (!SearchSysCacheExists1(TABLESPACEOID, ObjectIdGetDatum(tablespaceoid))) + PG_RETURN_NULL(); + + aclresult = object_aclcheck(TableSpaceRelationId, tablespaceoid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * has_tablespace_privilege_id_name + * Check user privileges on a tablespace given + * roleid, text tablespacename, and text priv name. + */ +Datum +has_tablespace_privilege_id_name(PG_FUNCTION_ARGS) +{ + Oid roleid = PG_GETARG_OID(0); + text *tablespacename = PG_GETARG_TEXT_PP(1); + text *priv_type_text = PG_GETARG_TEXT_PP(2); + Oid tablespaceoid; + AclMode mode; + AclResult aclresult; + + tablespaceoid = convert_tablespace_name(tablespacename); + mode = convert_tablespace_priv_string(priv_type_text); + + aclresult = object_aclcheck(TableSpaceRelationId, tablespaceoid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * has_tablespace_privilege_id_id + * Check user privileges on a tablespace given + * roleid, tablespace oid, and text priv name. + */ +Datum +has_tablespace_privilege_id_id(PG_FUNCTION_ARGS) +{ + Oid roleid = PG_GETARG_OID(0); + Oid tablespaceoid = PG_GETARG_OID(1); + text *priv_type_text = PG_GETARG_TEXT_PP(2); + AclMode mode; + AclResult aclresult; + + mode = convert_tablespace_priv_string(priv_type_text); + + if (!SearchSysCacheExists1(TABLESPACEOID, ObjectIdGetDatum(tablespaceoid))) + PG_RETURN_NULL(); + + aclresult = object_aclcheck(TableSpaceRelationId, tablespaceoid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * Support routines for has_tablespace_privilege family. + */ + +/* + * Given a tablespace name expressed as a string, look it up and return Oid + */ +static Oid +convert_tablespace_name(text *tablespacename) +{ + char *spcname = text_to_cstring(tablespacename); + + return get_tablespace_oid(spcname, false); +} + +/* + * convert_tablespace_priv_string + * Convert text string to AclMode value. + */ +static AclMode +convert_tablespace_priv_string(text *priv_type_text) +{ + static const priv_map tablespace_priv_map[] = { + {"CREATE", ACL_CREATE}, + {"CREATE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_CREATE)}, + {NULL, 0} + }; + + return convert_any_priv_string(priv_type_text, tablespace_priv_map); +} + +/* + * has_type_privilege variants + * These are all named "has_type_privilege" at the SQL level. + * They take various combinations of type name, type OID, + * user name, user OID, or implicit user = current_user. + * + * The result is a boolean value: true if user has the indicated + * privilege, false if not, or NULL if object doesn't exist. + */ + +/* + * has_type_privilege_name_name + * Check user privileges on a type given + * name username, text typename, and text priv name. + */ +Datum +has_type_privilege_name_name(PG_FUNCTION_ARGS) +{ + Name username = PG_GETARG_NAME(0); + text *typename = PG_GETARG_TEXT_PP(1); + text *priv_type_text = PG_GETARG_TEXT_PP(2); + Oid roleid; + Oid typeoid; + AclMode mode; + AclResult aclresult; + + roleid = get_role_oid_or_public(NameStr(*username)); + typeoid = convert_type_name(typename); + mode = convert_type_priv_string(priv_type_text); + + aclresult = object_aclcheck(TypeRelationId, typeoid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * has_type_privilege_name + * Check user privileges on a type given + * text typename and text priv name. + * current_user is assumed + */ +Datum +has_type_privilege_name(PG_FUNCTION_ARGS) +{ + text *typename = PG_GETARG_TEXT_PP(0); + text *priv_type_text = PG_GETARG_TEXT_PP(1); + Oid roleid; + Oid typeoid; + AclMode mode; + AclResult aclresult; + + roleid = GetUserId(); + typeoid = convert_type_name(typename); + mode = convert_type_priv_string(priv_type_text); + + aclresult = object_aclcheck(TypeRelationId, typeoid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * has_type_privilege_name_id + * Check user privileges on a type given + * name usename, type oid, and text priv name. + */ +Datum +has_type_privilege_name_id(PG_FUNCTION_ARGS) +{ + Name username = PG_GETARG_NAME(0); + Oid typeoid = PG_GETARG_OID(1); + text *priv_type_text = PG_GETARG_TEXT_PP(2); + Oid roleid; + AclMode mode; + AclResult aclresult; + + roleid = get_role_oid_or_public(NameStr(*username)); + mode = convert_type_priv_string(priv_type_text); + + if (!SearchSysCacheExists1(TYPEOID, ObjectIdGetDatum(typeoid))) + PG_RETURN_NULL(); + + aclresult = object_aclcheck(TypeRelationId, typeoid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * has_type_privilege_id + * Check user privileges on a type given + * type oid, and text priv name. + * current_user is assumed + */ +Datum +has_type_privilege_id(PG_FUNCTION_ARGS) +{ + Oid typeoid = PG_GETARG_OID(0); + text *priv_type_text = PG_GETARG_TEXT_PP(1); + Oid roleid; + AclMode mode; + AclResult aclresult; + + roleid = GetUserId(); + mode = convert_type_priv_string(priv_type_text); + + if (!SearchSysCacheExists1(TYPEOID, ObjectIdGetDatum(typeoid))) + PG_RETURN_NULL(); + + aclresult = object_aclcheck(TypeRelationId, typeoid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * has_type_privilege_id_name + * Check user privileges on a type given + * roleid, text typename, and text priv name. + */ +Datum +has_type_privilege_id_name(PG_FUNCTION_ARGS) +{ + Oid roleid = PG_GETARG_OID(0); + text *typename = PG_GETARG_TEXT_PP(1); + text *priv_type_text = PG_GETARG_TEXT_PP(2); + Oid typeoid; + AclMode mode; + AclResult aclresult; + + typeoid = convert_type_name(typename); + mode = convert_type_priv_string(priv_type_text); + + aclresult = object_aclcheck(TypeRelationId, typeoid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * has_type_privilege_id_id + * Check user privileges on a type given + * roleid, type oid, and text priv name. + */ +Datum +has_type_privilege_id_id(PG_FUNCTION_ARGS) +{ + Oid roleid = PG_GETARG_OID(0); + Oid typeoid = PG_GETARG_OID(1); + text *priv_type_text = PG_GETARG_TEXT_PP(2); + AclMode mode; + AclResult aclresult; + + mode = convert_type_priv_string(priv_type_text); + + if (!SearchSysCacheExists1(TYPEOID, ObjectIdGetDatum(typeoid))) + PG_RETURN_NULL(); + + aclresult = object_aclcheck(TypeRelationId, typeoid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * Support routines for has_type_privilege family. + */ + +/* + * Given a type name expressed as a string, look it up and return Oid + */ +static Oid +convert_type_name(text *typename) +{ + char *typname = text_to_cstring(typename); + Oid oid; + + oid = DatumGetObjectId(DirectFunctionCall1(regtypein, + CStringGetDatum(typname))); + + if (!OidIsValid(oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("type \"%s\" does not exist", typname))); + + return oid; +} + +/* + * convert_type_priv_string + * Convert text string to AclMode value. + */ +static AclMode +convert_type_priv_string(text *priv_type_text) +{ + static const priv_map type_priv_map[] = { + {"USAGE", ACL_USAGE}, + {"USAGE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_USAGE)}, + {NULL, 0} + }; + + return convert_any_priv_string(priv_type_text, type_priv_map); +} + +/* + * has_parameter_privilege variants + * These are all named "has_parameter_privilege" at the SQL level. + * They take various combinations of parameter name with + * user name, user OID, or implicit user = current_user. + * + * The result is a boolean value: true if user has been granted + * the indicated privilege or false if not. + */ + +/* + * has_param_priv_byname + * + * Helper function to check user privileges on a parameter given the + * role by Oid, parameter by text name, and privileges as AclMode. + */ +static bool +has_param_priv_byname(Oid roleid, const text *parameter, AclMode priv) +{ + char *paramstr = text_to_cstring(parameter); + + return pg_parameter_aclcheck(paramstr, roleid, priv) == ACLCHECK_OK; +} + +/* + * has_parameter_privilege_name_name + * Check user privileges on a parameter given name username, text + * parameter, and text priv name. + */ +Datum +has_parameter_privilege_name_name(PG_FUNCTION_ARGS) +{ + Name username = PG_GETARG_NAME(0); + text *parameter = PG_GETARG_TEXT_PP(1); + AclMode priv = convert_parameter_priv_string(PG_GETARG_TEXT_PP(2)); + Oid roleid = get_role_oid_or_public(NameStr(*username)); + + PG_RETURN_BOOL(has_param_priv_byname(roleid, parameter, priv)); +} + +/* + * has_parameter_privilege_name + * Check user privileges on a parameter given text parameter and text priv + * name. current_user is assumed + */ +Datum +has_parameter_privilege_name(PG_FUNCTION_ARGS) +{ + text *parameter = PG_GETARG_TEXT_PP(0); + AclMode priv = convert_parameter_priv_string(PG_GETARG_TEXT_PP(1)); + + PG_RETURN_BOOL(has_param_priv_byname(GetUserId(), parameter, priv)); +} + +/* + * has_parameter_privilege_id_name + * Check user privileges on a parameter given roleid, text parameter, and + * text priv name. + */ +Datum +has_parameter_privilege_id_name(PG_FUNCTION_ARGS) +{ + Oid roleid = PG_GETARG_OID(0); + text *parameter = PG_GETARG_TEXT_PP(1); + AclMode priv = convert_parameter_priv_string(PG_GETARG_TEXT_PP(2)); + + PG_RETURN_BOOL(has_param_priv_byname(roleid, parameter, priv)); +} + +/* + * Support routines for has_parameter_privilege family. + */ + +/* + * convert_parameter_priv_string + * Convert text string to AclMode value. + */ +static AclMode +convert_parameter_priv_string(text *priv_text) +{ + static const priv_map parameter_priv_map[] = { + {"SET", ACL_SET}, + {"SET WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_SET)}, + {"ALTER SYSTEM", ACL_ALTER_SYSTEM}, + {"ALTER SYSTEM WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_ALTER_SYSTEM)}, + {NULL, 0} + }; + + return convert_any_priv_string(priv_text, parameter_priv_map); +} + +/* + * pg_has_role variants + * These are all named "pg_has_role" at the SQL level. + * They take various combinations of role name, role OID, + * user name, user OID, or implicit user = current_user. + * + * The result is a boolean value: true if user has the indicated + * privilege, false if not. + */ + +/* + * pg_has_role_name_name + * Check user privileges on a role given + * name username, name rolename, and text priv name. + */ +Datum +pg_has_role_name_name(PG_FUNCTION_ARGS) +{ + Name username = PG_GETARG_NAME(0); + Name rolename = PG_GETARG_NAME(1); + text *priv_type_text = PG_GETARG_TEXT_PP(2); + Oid roleid; + Oid roleoid; + AclMode mode; + AclResult aclresult; + + roleid = get_role_oid(NameStr(*username), false); + roleoid = get_role_oid(NameStr(*rolename), false); + mode = convert_role_priv_string(priv_type_text); + + aclresult = pg_role_aclcheck(roleoid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * pg_has_role_name + * Check user privileges on a role given + * name rolename and text priv name. + * current_user is assumed + */ +Datum +pg_has_role_name(PG_FUNCTION_ARGS) +{ + Name rolename = PG_GETARG_NAME(0); + text *priv_type_text = PG_GETARG_TEXT_PP(1); + Oid roleid; + Oid roleoid; + AclMode mode; + AclResult aclresult; + + roleid = GetUserId(); + roleoid = get_role_oid(NameStr(*rolename), false); + mode = convert_role_priv_string(priv_type_text); + + aclresult = pg_role_aclcheck(roleoid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * pg_has_role_name_id + * Check user privileges on a role given + * name usename, role oid, and text priv name. + */ +Datum +pg_has_role_name_id(PG_FUNCTION_ARGS) +{ + Name username = PG_GETARG_NAME(0); + Oid roleoid = PG_GETARG_OID(1); + text *priv_type_text = PG_GETARG_TEXT_PP(2); + Oid roleid; + AclMode mode; + AclResult aclresult; + + roleid = get_role_oid(NameStr(*username), false); + mode = convert_role_priv_string(priv_type_text); + + aclresult = pg_role_aclcheck(roleoid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * pg_has_role_id + * Check user privileges on a role given + * role oid, and text priv name. + * current_user is assumed + */ +Datum +pg_has_role_id(PG_FUNCTION_ARGS) +{ + Oid roleoid = PG_GETARG_OID(0); + text *priv_type_text = PG_GETARG_TEXT_PP(1); + Oid roleid; + AclMode mode; + AclResult aclresult; + + roleid = GetUserId(); + mode = convert_role_priv_string(priv_type_text); + + aclresult = pg_role_aclcheck(roleoid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * pg_has_role_id_name + * Check user privileges on a role given + * roleid, name rolename, and text priv name. + */ +Datum +pg_has_role_id_name(PG_FUNCTION_ARGS) +{ + Oid roleid = PG_GETARG_OID(0); + Name rolename = PG_GETARG_NAME(1); + text *priv_type_text = PG_GETARG_TEXT_PP(2); + Oid roleoid; + AclMode mode; + AclResult aclresult; + + roleoid = get_role_oid(NameStr(*rolename), false); + mode = convert_role_priv_string(priv_type_text); + + aclresult = pg_role_aclcheck(roleoid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * pg_has_role_id_id + * Check user privileges on a role given + * roleid, role oid, and text priv name. + */ +Datum +pg_has_role_id_id(PG_FUNCTION_ARGS) +{ + Oid roleid = PG_GETARG_OID(0); + Oid roleoid = PG_GETARG_OID(1); + text *priv_type_text = PG_GETARG_TEXT_PP(2); + AclMode mode; + AclResult aclresult; + + mode = convert_role_priv_string(priv_type_text); + + aclresult = pg_role_aclcheck(roleoid, roleid, mode); + + PG_RETURN_BOOL(aclresult == ACLCHECK_OK); +} + +/* + * Support routines for pg_has_role family. + */ + +/* + * convert_role_priv_string + * Convert text string to AclMode value. + * + * We use USAGE to denote whether the privileges of the role are accessible + * (has_privs_of_role), MEMBER to denote is_member, and MEMBER WITH GRANT + * (or ADMIN) OPTION to denote is_admin. There is no ACL bit corresponding + * to MEMBER so we cheat and use ACL_CREATE for that. This convention + * is shared only with pg_role_aclcheck, below. + */ +static AclMode +convert_role_priv_string(text *priv_type_text) +{ + static const priv_map role_priv_map[] = { + {"USAGE", ACL_USAGE}, + {"MEMBER", ACL_CREATE}, + {"SET", ACL_SET}, + {"USAGE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_CREATE)}, + {"USAGE WITH ADMIN OPTION", ACL_GRANT_OPTION_FOR(ACL_CREATE)}, + {"MEMBER WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_CREATE)}, + {"MEMBER WITH ADMIN OPTION", ACL_GRANT_OPTION_FOR(ACL_CREATE)}, + {"SET WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_CREATE)}, + {"SET WITH ADMIN OPTION", ACL_GRANT_OPTION_FOR(ACL_CREATE)}, + {NULL, 0} + }; + + return convert_any_priv_string(priv_type_text, role_priv_map); +} + +/* + * pg_role_aclcheck + * Quick-and-dirty support for pg_has_role + */ +static AclResult +pg_role_aclcheck(Oid role_oid, Oid roleid, AclMode mode) +{ + if (mode & ACL_GRANT_OPTION_FOR(ACL_CREATE)) + { + if (is_admin_of_role(roleid, role_oid)) + return ACLCHECK_OK; + } + if (mode & ACL_CREATE) + { + if (is_member_of_role(roleid, role_oid)) + return ACLCHECK_OK; + } + if (mode & ACL_USAGE) + { + if (has_privs_of_role(roleid, role_oid)) + return ACLCHECK_OK; + } + if (mode & ACL_SET) + { + if (member_can_set_role(roleid, role_oid)) + return ACLCHECK_OK; + } + return ACLCHECK_NO_PRIV; +} + + +/* + * initialization function (called by InitPostgres) + */ +void +initialize_acl(void) +{ + if (!IsBootstrapProcessingMode()) + { + cached_db_hash = + GetSysCacheHashValue1(DATABASEOID, + ObjectIdGetDatum(MyDatabaseId)); + + /* + * In normal mode, set a callback on any syscache invalidation of rows + * of pg_auth_members (for roles_is_member_of()) pg_database (for + * roles_is_member_of()) + */ + CacheRegisterSyscacheCallback(AUTHMEMROLEMEM, + RoleMembershipCacheCallback, + (Datum) 0); + CacheRegisterSyscacheCallback(AUTHOID, + RoleMembershipCacheCallback, + (Datum) 0); + CacheRegisterSyscacheCallback(DATABASEOID, + RoleMembershipCacheCallback, + (Datum) 0); + } +} + +/* + * RoleMembershipCacheCallback + * Syscache inval callback function + */ +static void +RoleMembershipCacheCallback(Datum arg, int cacheid, uint32 hashvalue) +{ + if (cacheid == DATABASEOID && + hashvalue != cached_db_hash && + hashvalue != 0) + { + return; /* ignore pg_database changes for other DBs */ + } + + /* Force membership caches to be recomputed on next use */ + cached_role[ROLERECURSE_MEMBERS] = InvalidOid; + cached_role[ROLERECURSE_PRIVS] = InvalidOid; + cached_role[ROLERECURSE_SETROLE] = InvalidOid; +} + +/* + * Get a list of roles that the specified roleid is a member of + * + * Type ROLERECURSE_MEMBERS recurses through all grants; ROLERECURSE_PRIVS + * recurses only through inheritable grants; and ROLERECURSE_SETROLE recurses + * only through grants with set_option. + * + * Since indirect membership testing is relatively expensive, we cache + * a list of memberships. Hence, the result is only guaranteed good until + * the next call of roles_is_member_of()! + * + * For the benefit of select_best_grantor, the result is defined to be + * in breadth-first order, ie, closer relationships earlier. + * + * If admin_of is not InvalidOid, this function sets *admin_role, either + * to the OID of the first role in the result list that directly possesses + * ADMIN OPTION on the role corresponding to admin_of, or to InvalidOid if + * there is no such role. + */ +static List * +roles_is_member_of(Oid roleid, enum RoleRecurseType type, + Oid admin_of, Oid *admin_role) +{ + Oid dba; + List *roles_list; + ListCell *l; + List *new_cached_roles; + MemoryContext oldctx; + + Assert(OidIsValid(admin_of) == PointerIsValid(admin_role)); + if (admin_role != NULL) + *admin_role = InvalidOid; + + /* If cache is valid and ADMIN OPTION not sought, just return the list */ + if (cached_role[type] == roleid && !OidIsValid(admin_of) && + OidIsValid(cached_role[type])) + return cached_roles[type]; + + /* + * Role expansion happens in a non-database backend when guc.c checks + * ROLE_PG_READ_ALL_SETTINGS for a physical walsender SHOW command. In + * that case, no role gets pg_database_owner. + */ + if (!OidIsValid(MyDatabaseId)) + dba = InvalidOid; + else + { + HeapTuple dbtup; + + dbtup = SearchSysCache1(DATABASEOID, ObjectIdGetDatum(MyDatabaseId)); + if (!HeapTupleIsValid(dbtup)) + elog(ERROR, "cache lookup failed for database %u", MyDatabaseId); + dba = ((Form_pg_database) GETSTRUCT(dbtup))->datdba; + ReleaseSysCache(dbtup); + } + + /* + * Find all the roles that roleid is a member of, including multi-level + * recursion. The role itself will always be the first element of the + * resulting list. + * + * Each element of the list is scanned to see if it adds any indirect + * memberships. We can use a single list as both the record of + * already-found memberships and the agenda of roles yet to be scanned. + * This is a bit tricky but works because the foreach() macro doesn't + * fetch the next list element until the bottom of the loop. + */ + roles_list = list_make1_oid(roleid); + + foreach(l, roles_list) + { + Oid memberid = lfirst_oid(l); + CatCList *memlist; + int i; + + /* Find roles that memberid is directly a member of */ + memlist = SearchSysCacheList1(AUTHMEMMEMROLE, + ObjectIdGetDatum(memberid)); + for (i = 0; i < memlist->n_members; i++) + { + HeapTuple tup = &memlist->members[i]->tuple; + Form_pg_auth_members form = (Form_pg_auth_members) GETSTRUCT(tup); + Oid otherid = form->roleid; + + /* + * While otherid==InvalidOid shouldn't appear in the catalog, the + * OidIsValid() avoids crashing if that arises. + */ + if (otherid == admin_of && form->admin_option && + OidIsValid(admin_of) && !OidIsValid(*admin_role)) + *admin_role = memberid; + + /* If we're supposed to ignore non-heritable grants, do so. */ + if (type == ROLERECURSE_PRIVS && !form->inherit_option) + continue; + + /* If we're supposed to ignore non-SET grants, do so. */ + if (type == ROLERECURSE_SETROLE && !form->set_option) + continue; + + /* + * Even though there shouldn't be any loops in the membership + * graph, we must test for having already seen this role. It is + * legal for instance to have both A->B and A->C->B. + */ + roles_list = list_append_unique_oid(roles_list, otherid); + } + ReleaseSysCacheList(memlist); + + /* implement pg_database_owner implicit membership */ + if (memberid == dba && OidIsValid(dba)) + roles_list = list_append_unique_oid(roles_list, + ROLE_PG_DATABASE_OWNER); + } + + /* + * Copy the completed list into TopMemoryContext so it will persist. + */ + oldctx = MemoryContextSwitchTo(TopMemoryContext); + new_cached_roles = list_copy(roles_list); + MemoryContextSwitchTo(oldctx); + list_free(roles_list); + + /* + * Now safe to assign to state variable + */ + cached_role[type] = InvalidOid; /* just paranoia */ + list_free(cached_roles[type]); + cached_roles[type] = new_cached_roles; + cached_role[type] = roleid; + + /* And now we can return the answer */ + return cached_roles[type]; +} + + +/* + * Does member have the privileges of role (directly or indirectly)? + * + * This is defined not to recurse through grants that are not inherited, + * and only inherited grants confer the associated privileges automatically. + * + * See also member_can_set_role, below. + */ +bool +has_privs_of_role(Oid member, Oid role) +{ + /* Fast path for simple case */ + if (member == role) + return true; + + /* Superusers have every privilege, so are part of every role */ + if (superuser_arg(member)) + return true; + + /* + * Find all the roles that member has the privileges of, including + * multi-level recursion, then see if target role is any one of them. + */ + return list_member_oid(roles_is_member_of(member, ROLERECURSE_PRIVS, + InvalidOid, NULL), + role); +} + +/* + * Can member use SET ROLE to this role? + * + * There must be a chain of grants from 'member' to 'role' each of which + * permits SET ROLE; that is, each of which has set_option = true. + * + * It doesn't matter whether the grants are inheritable. That's a separate + * question; see has_privs_of_role. + * + * This function should be used to determine whether the session user can + * use SET ROLE to become the target user. We also use it to determine whether + * the session user can change an existing object to be owned by the target + * user, or create new objects owned by the target user. + */ +bool +member_can_set_role(Oid member, Oid role) +{ + /* Fast path for simple case */ + if (member == role) + return true; + + /* Superusers have every privilege, so can always SET ROLE */ + if (superuser_arg(member)) + return true; + + /* + * Find all the roles that member can access via SET ROLE, including + * multi-level recursion, then see if target role is any one of them. + */ + return list_member_oid(roles_is_member_of(member, ROLERECURSE_SETROLE, + InvalidOid, NULL), + role); +} + +/* + * Permission violation error unless able to SET ROLE to target role. + */ +void +check_can_set_role(Oid member, Oid role) +{ + if (!member_can_set_role(member, role)) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be able to SET ROLE \"%s\"", + GetUserNameFromId(role, false)))); +} + +/* + * Is member a member of role (directly or indirectly)? + * + * This is defined to recurse through grants whether they are inherited or not. + * + * Do not use this for privilege checking, instead use has_privs_of_role(). + * Don't use it for determining whether it's possible to SET ROLE to some + * other role; for that, use member_can_set_role(). And don't use it for + * determining whether it's OK to create an object owned by some other role: + * use member_can_set_role() for that, too. + * + * In short, calling this function is the wrong thing to do nearly everywhere. + */ +bool +is_member_of_role(Oid member, Oid role) +{ + /* Fast path for simple case */ + if (member == role) + return true; + + /* Superusers have every privilege, so are part of every role */ + if (superuser_arg(member)) + return true; + + /* + * Find all the roles that member is a member of, including multi-level + * recursion, then see if target role is any one of them. + */ + return list_member_oid(roles_is_member_of(member, ROLERECURSE_MEMBERS, + InvalidOid, NULL), + role); +} + +/* + * Is member a member of role, not considering superuserness? + * + * This is identical to is_member_of_role except we ignore superuser + * status. + * + * Do not use this for privilege checking, instead use has_privs_of_role() + */ +bool +is_member_of_role_nosuper(Oid member, Oid role) +{ + /* Fast path for simple case */ + if (member == role) + return true; + + /* + * Find all the roles that member is a member of, including multi-level + * recursion, then see if target role is any one of them. + */ + return list_member_oid(roles_is_member_of(member, ROLERECURSE_MEMBERS, + InvalidOid, NULL), + role); +} + + +/* + * Is member an admin of role? That is, is member the role itself (subject to + * restrictions below), a member (directly or indirectly) WITH ADMIN OPTION, + * or a superuser? + */ +bool +is_admin_of_role(Oid member, Oid role) +{ + Oid admin_role; + + if (superuser_arg(member)) + return true; + + /* By policy, a role cannot have WITH ADMIN OPTION on itself. */ + if (member == role) + return false; + + (void) roles_is_member_of(member, ROLERECURSE_MEMBERS, role, &admin_role); + return OidIsValid(admin_role); +} + +/* + * Find a role whose privileges "member" inherits which has ADMIN OPTION + * on "role", ignoring super-userness. + * + * There might be more than one such role; prefer one which involves fewer + * hops. That is, if member has ADMIN OPTION, prefer that over all other + * options; if not, prefer a role from which member inherits more directly + * over more indirect inheritance. + */ +Oid +select_best_admin(Oid member, Oid role) +{ + Oid admin_role; + + /* By policy, a role cannot have WITH ADMIN OPTION on itself. */ + if (member == role) + return InvalidOid; + + (void) roles_is_member_of(member, ROLERECURSE_PRIVS, role, &admin_role); + return admin_role; +} + + +/* does what it says ... */ +static int +count_one_bits(AclMode mask) +{ + int nbits = 0; + + /* this code relies on AclMode being an unsigned type */ + while (mask) + { + if (mask & 1) + nbits++; + mask >>= 1; + } + return nbits; +} + + +/* + * Select the effective grantor ID for a GRANT or REVOKE operation. + * + * The grantor must always be either the object owner or some role that has + * been explicitly granted grant options. This ensures that all granted + * privileges appear to flow from the object owner, and there are never + * multiple "original sources" of a privilege. Therefore, if the would-be + * grantor is a member of a role that has the needed grant options, we have + * to do the grant as that role instead. + * + * It is possible that the would-be grantor is a member of several roles + * that have different subsets of the desired grant options, but no one + * role has 'em all. In this case we pick a role with the largest number + * of desired options. Ties are broken in favor of closer ancestors. + * + * roleId: the role attempting to do the GRANT/REVOKE + * privileges: the privileges to be granted/revoked + * acl: the ACL of the object in question + * ownerId: the role owning the object in question + * *grantorId: receives the OID of the role to do the grant as + * *grantOptions: receives the grant options actually held by grantorId + * + * If no grant options exist, we set grantorId to roleId, grantOptions to 0. + */ +void +select_best_grantor(Oid roleId, AclMode privileges, + const Acl *acl, Oid ownerId, + Oid *grantorId, AclMode *grantOptions) +{ + AclMode needed_goptions = ACL_GRANT_OPTION_FOR(privileges); + List *roles_list; + int nrights; + ListCell *l; + + /* + * The object owner is always treated as having all grant options, so if + * roleId is the owner it's easy. Also, if roleId is a superuser it's + * easy: superusers are implicitly members of every role, so they act as + * the object owner. + */ + if (roleId == ownerId || superuser_arg(roleId)) + { + *grantorId = ownerId; + *grantOptions = needed_goptions; + return; + } + + /* + * Otherwise we have to do a careful search to see if roleId has the + * privileges of any suitable role. Note: we can hang onto the result of + * roles_is_member_of() throughout this loop, because aclmask_direct() + * doesn't query any role memberships. + */ + roles_list = roles_is_member_of(roleId, ROLERECURSE_PRIVS, + InvalidOid, NULL); + + /* initialize candidate result as default */ + *grantorId = roleId; + *grantOptions = ACL_NO_RIGHTS; + nrights = 0; + + foreach(l, roles_list) + { + Oid otherrole = lfirst_oid(l); + AclMode otherprivs; + + otherprivs = aclmask_direct(acl, otherrole, ownerId, + needed_goptions, ACLMASK_ALL); + if (otherprivs == needed_goptions) + { + /* Found a suitable grantor */ + *grantorId = otherrole; + *grantOptions = otherprivs; + return; + } + + /* + * If it has just some of the needed privileges, remember best + * candidate. + */ + if (otherprivs != ACL_NO_RIGHTS) + { + int nnewrights = count_one_bits(otherprivs); + + if (nnewrights > nrights) + { + *grantorId = otherrole; + *grantOptions = otherprivs; + nrights = nnewrights; + } + } + } +} + +/* + * get_role_oid - Given a role name, look up the role's OID. + * + * If missing_ok is false, throw an error if role name not found. If + * true, just return InvalidOid. + */ +Oid +get_role_oid(const char *rolname, bool missing_ok) +{ + Oid oid; + + oid = GetSysCacheOid1(AUTHNAME, Anum_pg_authid_oid, + CStringGetDatum(rolname)); + if (!OidIsValid(oid) && !missing_ok) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("role \"%s\" does not exist", rolname))); + return oid; +} + +/* + * get_role_oid_or_public - As above, but return ACL_ID_PUBLIC if the + * role name is "public". + */ +Oid +get_role_oid_or_public(const char *rolname) +{ + if (strcmp(rolname, "public") == 0) + return ACL_ID_PUBLIC; + + return get_role_oid(rolname, false); +} + +/* + * Given a RoleSpec node, return the OID it corresponds to. If missing_ok is + * true, return InvalidOid if the role does not exist. + * + * PUBLIC is always disallowed here. Routines wanting to handle the PUBLIC + * case must check the case separately. + */ +Oid +get_rolespec_oid(const RoleSpec *role, bool missing_ok) +{ + Oid oid; + + switch (role->roletype) + { + case ROLESPEC_CSTRING: + Assert(role->rolename); + oid = get_role_oid(role->rolename, missing_ok); + break; + + case ROLESPEC_CURRENT_ROLE: + case ROLESPEC_CURRENT_USER: + oid = GetUserId(); + break; + + case ROLESPEC_SESSION_USER: + oid = GetSessionUserId(); + break; + + case ROLESPEC_PUBLIC: + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("role \"%s\" does not exist", "public"))); + oid = InvalidOid; /* make compiler happy */ + break; + + default: + elog(ERROR, "unexpected role type %d", role->roletype); + } + + return oid; +} + +/* + * Given a RoleSpec node, return the pg_authid HeapTuple it corresponds to. + * Caller must ReleaseSysCache when done with the result tuple. + */ +HeapTuple +get_rolespec_tuple(const RoleSpec *role) +{ + HeapTuple tuple; + + switch (role->roletype) + { + case ROLESPEC_CSTRING: + Assert(role->rolename); + tuple = SearchSysCache1(AUTHNAME, CStringGetDatum(role->rolename)); + if (!HeapTupleIsValid(tuple)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("role \"%s\" does not exist", role->rolename))); + break; + + case ROLESPEC_CURRENT_ROLE: + case ROLESPEC_CURRENT_USER: + tuple = SearchSysCache1(AUTHOID, GetUserId()); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for role %u", GetUserId()); + break; + + case ROLESPEC_SESSION_USER: + tuple = SearchSysCache1(AUTHOID, GetSessionUserId()); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for role %u", GetSessionUserId()); + break; + + case ROLESPEC_PUBLIC: + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("role \"%s\" does not exist", "public"))); + tuple = NULL; /* make compiler happy */ + break; + + default: + elog(ERROR, "unexpected role type %d", role->roletype); + } + + return tuple; +} + +/* + * Given a RoleSpec, returns a palloc'ed copy of the corresponding role's name. + */ +char * +get_rolespec_name(const RoleSpec *role) +{ + HeapTuple tp; + Form_pg_authid authForm; + char *rolename; + + tp = get_rolespec_tuple(role); + authForm = (Form_pg_authid) GETSTRUCT(tp); + rolename = pstrdup(NameStr(authForm->rolname)); + ReleaseSysCache(tp); + + return rolename; +} + +/* + * Given a RoleSpec, throw an error if the name is reserved, using detail_msg, + * if provided (which must be already translated). + * + * If node is NULL, no error is thrown. If detail_msg is NULL then no detail + * message is provided. + */ +void +check_rolespec_name(const RoleSpec *role, const char *detail_msg) +{ + if (!role) + return; + + if (role->roletype != ROLESPEC_CSTRING) + return; + + if (IsReservedName(role->rolename)) + { + if (detail_msg) + ereport(ERROR, + (errcode(ERRCODE_RESERVED_NAME), + errmsg("role name \"%s\" is reserved", + role->rolename), + errdetail_internal("%s", detail_msg))); + else + ereport(ERROR, + (errcode(ERRCODE_RESERVED_NAME), + errmsg("role name \"%s\" is reserved", + role->rolename))); + } +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/amutils.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/amutils.c new file mode 100644 index 00000000000..48852bf79e2 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/amutils.c @@ -0,0 +1,467 @@ +/*------------------------------------------------------------------------- + * + * amutils.c + * SQL-level APIs related to index access methods. + * + * Copyright (c) 2016-2023, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/utils/adt/amutils.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/amapi.h" +#include "access/htup_details.h" +#include "catalog/pg_class.h" +#include "catalog/pg_index.h" +#include "utils/builtins.h" +#include "utils/syscache.h" + + +/* Convert string property name to enum, for efficiency */ +struct am_propname +{ + const char *name; + IndexAMProperty prop; +}; + +static const struct am_propname am_propnames[] = +{ + { + "asc", AMPROP_ASC + }, + { + "desc", AMPROP_DESC + }, + { + "nulls_first", AMPROP_NULLS_FIRST + }, + { + "nulls_last", AMPROP_NULLS_LAST + }, + { + "orderable", AMPROP_ORDERABLE + }, + { + "distance_orderable", AMPROP_DISTANCE_ORDERABLE + }, + { + "returnable", AMPROP_RETURNABLE + }, + { + "search_array", AMPROP_SEARCH_ARRAY + }, + { + "search_nulls", AMPROP_SEARCH_NULLS + }, + { + "clusterable", AMPROP_CLUSTERABLE + }, + { + "index_scan", AMPROP_INDEX_SCAN + }, + { + "bitmap_scan", AMPROP_BITMAP_SCAN + }, + { + "backward_scan", AMPROP_BACKWARD_SCAN + }, + { + "can_order", AMPROP_CAN_ORDER + }, + { + "can_unique", AMPROP_CAN_UNIQUE + }, + { + "can_multi_col", AMPROP_CAN_MULTI_COL + }, + { + "can_exclude", AMPROP_CAN_EXCLUDE + }, + { + "can_include", AMPROP_CAN_INCLUDE + }, +}; + +static IndexAMProperty +lookup_prop_name(const char *name) +{ + int i; + + for (i = 0; i < lengthof(am_propnames); i++) + { + if (pg_strcasecmp(am_propnames[i].name, name) == 0) + return am_propnames[i].prop; + } + + /* We do not throw an error, so that AMs can define their own properties */ + return AMPROP_UNKNOWN; +} + +/* + * Common code for properties that are just bit tests of indoptions. + * + * tuple: the pg_index heaptuple + * attno: identify the index column to test the indoptions of. + * guard: if false, a boolean false result is forced (saves code in caller). + * iopt_mask: mask for interesting indoption bit. + * iopt_expect: value for a "true" result (should be 0 or iopt_mask). + * + * Returns false to indicate a NULL result (for "unknown/inapplicable"), + * otherwise sets *res to the boolean value to return. + */ +static bool +test_indoption(HeapTuple tuple, int attno, bool guard, + int16 iopt_mask, int16 iopt_expect, + bool *res) +{ + Datum datum; + int2vector *indoption; + int16 indoption_val; + + if (!guard) + { + *res = false; + return true; + } + + datum = SysCacheGetAttrNotNull(INDEXRELID, tuple, Anum_pg_index_indoption); + + indoption = ((int2vector *) DatumGetPointer(datum)); + indoption_val = indoption->values[attno - 1]; + + *res = (indoption_val & iopt_mask) == iopt_expect; + + return true; +} + + +/* + * Test property of an index AM, index, or index column. + * + * This is common code for different SQL-level funcs, so the amoid and + * index_oid parameters are mutually exclusive; we look up the amoid from the + * index_oid if needed, or if no index oid is given, we're looking at AM-wide + * properties. + */ +static Datum +indexam_property(FunctionCallInfo fcinfo, + const char *propname, + Oid amoid, Oid index_oid, int attno) +{ + bool res = false; + bool isnull = false; + int natts = 0; + IndexAMProperty prop; + IndexAmRoutine *routine; + + /* Try to convert property name to enum (no error if not known) */ + prop = lookup_prop_name(propname); + + /* If we have an index OID, look up the AM, and get # of columns too */ + if (OidIsValid(index_oid)) + { + HeapTuple tuple; + Form_pg_class rd_rel; + + Assert(!OidIsValid(amoid)); + tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(index_oid)); + if (!HeapTupleIsValid(tuple)) + PG_RETURN_NULL(); + rd_rel = (Form_pg_class) GETSTRUCT(tuple); + if (rd_rel->relkind != RELKIND_INDEX && + rd_rel->relkind != RELKIND_PARTITIONED_INDEX) + { + ReleaseSysCache(tuple); + PG_RETURN_NULL(); + } + amoid = rd_rel->relam; + natts = rd_rel->relnatts; + ReleaseSysCache(tuple); + } + + /* + * At this point, either index_oid == InvalidOid or it's a valid index + * OID. Also, after this test and the one below, either attno == 0 for + * index-wide or AM-wide tests, or it's a valid column number in a valid + * index. + */ + if (attno < 0 || attno > natts) + PG_RETURN_NULL(); + + /* + * Get AM information. If we don't have a valid AM OID, return NULL. + */ + routine = GetIndexAmRoutineByAmId(amoid, true); + if (routine == NULL) + PG_RETURN_NULL(); + + /* + * If there's an AM property routine, give it a chance to override the + * generic logic. Proceed if it returns false. + */ + if (routine->amproperty && + routine->amproperty(index_oid, attno, prop, propname, + &res, &isnull)) + { + if (isnull) + PG_RETURN_NULL(); + PG_RETURN_BOOL(res); + } + + if (attno > 0) + { + HeapTuple tuple; + Form_pg_index rd_index; + bool iskey = true; + + /* + * Handle column-level properties. Many of these need the pg_index row + * (which we also need to use to check for nonkey atts) so we fetch + * that first. + */ + tuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(index_oid)); + if (!HeapTupleIsValid(tuple)) + PG_RETURN_NULL(); + rd_index = (Form_pg_index) GETSTRUCT(tuple); + + Assert(index_oid == rd_index->indexrelid); + Assert(attno > 0 && attno <= rd_index->indnatts); + + isnull = true; + + /* + * If amcaninclude, we might be looking at an attno for a nonkey + * column, for which we (generically) assume that most properties are + * null. + */ + if (routine->amcaninclude + && attno > rd_index->indnkeyatts) + iskey = false; + + switch (prop) + { + case AMPROP_ASC: + if (iskey && + test_indoption(tuple, attno, routine->amcanorder, + INDOPTION_DESC, 0, &res)) + isnull = false; + break; + + case AMPROP_DESC: + if (iskey && + test_indoption(tuple, attno, routine->amcanorder, + INDOPTION_DESC, INDOPTION_DESC, &res)) + isnull = false; + break; + + case AMPROP_NULLS_FIRST: + if (iskey && + test_indoption(tuple, attno, routine->amcanorder, + INDOPTION_NULLS_FIRST, INDOPTION_NULLS_FIRST, &res)) + isnull = false; + break; + + case AMPROP_NULLS_LAST: + if (iskey && + test_indoption(tuple, attno, routine->amcanorder, + INDOPTION_NULLS_FIRST, 0, &res)) + isnull = false; + break; + + case AMPROP_ORDERABLE: + + /* + * generic assumption is that nonkey columns are not orderable + */ + res = iskey ? routine->amcanorder : false; + isnull = false; + break; + + case AMPROP_DISTANCE_ORDERABLE: + + /* + * The conditions for whether a column is distance-orderable + * are really up to the AM (at time of writing, only GiST + * supports it at all). The planner has its own idea based on + * whether it finds an operator with amoppurpose 'o', but + * getting there from just the index column type seems like a + * lot of work. So instead we expect the AM to handle this in + * its amproperty routine. The generic result is to return + * false if the AM says it never supports this, or if this is + * a nonkey column, and null otherwise (meaning we don't + * know). + */ + if (!iskey || !routine->amcanorderbyop) + { + res = false; + isnull = false; + } + break; + + case AMPROP_RETURNABLE: + + /* note that we ignore iskey for this property */ + + isnull = false; + res = false; + + if (routine->amcanreturn) + { + /* + * If possible, the AM should handle this test in its + * amproperty function without opening the rel. But this + * is the generic fallback if it does not. + */ + Relation indexrel = index_open(index_oid, AccessShareLock); + + res = index_can_return(indexrel, attno); + index_close(indexrel, AccessShareLock); + } + break; + + case AMPROP_SEARCH_ARRAY: + if (iskey) + { + res = routine->amsearcharray; + isnull = false; + } + break; + + case AMPROP_SEARCH_NULLS: + if (iskey) + { + res = routine->amsearchnulls; + isnull = false; + } + break; + + default: + break; + } + + ReleaseSysCache(tuple); + + if (!isnull) + PG_RETURN_BOOL(res); + PG_RETURN_NULL(); + } + + if (OidIsValid(index_oid)) + { + /* + * Handle index-level properties. Currently, these only depend on the + * AM, but that might not be true forever, so we make users name an + * index not just an AM. + */ + switch (prop) + { + case AMPROP_CLUSTERABLE: + PG_RETURN_BOOL(routine->amclusterable); + + case AMPROP_INDEX_SCAN: + PG_RETURN_BOOL(routine->amgettuple ? true : false); + + case AMPROP_BITMAP_SCAN: + PG_RETURN_BOOL(routine->amgetbitmap ? true : false); + + case AMPROP_BACKWARD_SCAN: + PG_RETURN_BOOL(routine->amcanbackward); + + default: + PG_RETURN_NULL(); + } + } + + /* + * Handle AM-level properties (those that control what you can say in + * CREATE INDEX). + */ + switch (prop) + { + case AMPROP_CAN_ORDER: + PG_RETURN_BOOL(routine->amcanorder); + + case AMPROP_CAN_UNIQUE: + PG_RETURN_BOOL(routine->amcanunique); + + case AMPROP_CAN_MULTI_COL: + PG_RETURN_BOOL(routine->amcanmulticol); + + case AMPROP_CAN_EXCLUDE: + PG_RETURN_BOOL(routine->amgettuple ? true : false); + + case AMPROP_CAN_INCLUDE: + PG_RETURN_BOOL(routine->amcaninclude); + + default: + PG_RETURN_NULL(); + } +} + +/* + * Test property of an AM specified by AM OID + */ +Datum +pg_indexam_has_property(PG_FUNCTION_ARGS) +{ + Oid amoid = PG_GETARG_OID(0); + char *propname = text_to_cstring(PG_GETARG_TEXT_PP(1)); + + return indexam_property(fcinfo, propname, amoid, InvalidOid, 0); +} + +/* + * Test property of an index specified by index OID + */ +Datum +pg_index_has_property(PG_FUNCTION_ARGS) +{ + Oid relid = PG_GETARG_OID(0); + char *propname = text_to_cstring(PG_GETARG_TEXT_PP(1)); + + return indexam_property(fcinfo, propname, InvalidOid, relid, 0); +} + +/* + * Test property of an index column specified by index OID and column number + */ +Datum +pg_index_column_has_property(PG_FUNCTION_ARGS) +{ + Oid relid = PG_GETARG_OID(0); + int32 attno = PG_GETARG_INT32(1); + char *propname = text_to_cstring(PG_GETARG_TEXT_PP(2)); + + /* Reject attno 0 immediately, so that attno > 0 identifies this case */ + if (attno <= 0) + PG_RETURN_NULL(); + + return indexam_property(fcinfo, propname, InvalidOid, relid, attno); +} + +/* + * Return the name of the given phase, as used for progress reporting by the + * given AM. + */ +Datum +pg_indexam_progress_phasename(PG_FUNCTION_ARGS) +{ + Oid amoid = PG_GETARG_OID(0); + int32 phasenum = PG_GETARG_INT32(1); + IndexAmRoutine *routine; + char *name; + + routine = GetIndexAmRoutineByAmId(amoid, true); + if (routine == NULL || !routine->ambuildphasename) + PG_RETURN_NULL(); + + name = routine->ambuildphasename(phasenum); + if (!name) + PG_RETURN_NULL(); + + PG_RETURN_DATUM(CStringGetTextDatum(name)); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/array_expanded.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/array_expanded.c new file mode 100644 index 00000000000..4509fddeb91 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/array_expanded.c @@ -0,0 +1,453 @@ +/*------------------------------------------------------------------------- + * + * array_expanded.c + * Basic functions for manipulating expanded arrays. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/adt/array_expanded.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/tupmacs.h" +#include "utils/array.h" +#include "utils/lsyscache.h" +#include "utils/memutils.h" + + +/* "Methods" required for an expanded object */ +static Size EA_get_flat_size(ExpandedObjectHeader *eohptr); +static void EA_flatten_into(ExpandedObjectHeader *eohptr, + void *result, Size allocated_size); + +static const ExpandedObjectMethods EA_methods = +{ + EA_get_flat_size, + EA_flatten_into +}; + +/* Other local functions */ +static void copy_byval_expanded_array(ExpandedArrayHeader *eah, + ExpandedArrayHeader *oldeah); + + +/* + * expand_array: convert an array Datum into an expanded array + * + * The expanded object will be a child of parentcontext. + * + * Some callers can provide cache space to avoid repeated lookups of element + * type data across calls; if so, pass a metacache pointer, making sure that + * metacache->element_type is initialized to InvalidOid before first call. + * If no cross-call caching is required, pass NULL for metacache. + */ +Datum +expand_array(Datum arraydatum, MemoryContext parentcontext, + ArrayMetaState *metacache) +{ + ArrayType *array; + ExpandedArrayHeader *eah; + MemoryContext objcxt; + MemoryContext oldcxt; + ArrayMetaState fakecache; + + /* + * Allocate private context for expanded object. We start by assuming + * that the array won't be very large; but if it does grow a lot, don't + * constrain aset.c's large-context behavior. + */ + objcxt = AllocSetContextCreate(parentcontext, + "expanded array", + ALLOCSET_START_SMALL_SIZES); + + /* Set up expanded array header */ + eah = (ExpandedArrayHeader *) + MemoryContextAlloc(objcxt, sizeof(ExpandedArrayHeader)); + + EOH_init_header(&eah->hdr, &EA_methods, objcxt); + eah->ea_magic = EA_MAGIC; + + /* If the source is an expanded array, we may be able to optimize */ + if (VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(arraydatum))) + { + ExpandedArrayHeader *oldeah = (ExpandedArrayHeader *) DatumGetEOHP(arraydatum); + + Assert(oldeah->ea_magic == EA_MAGIC); + + /* + * Update caller's cache if provided; we don't need it this time, but + * next call might be for a non-expanded source array. Furthermore, + * if the caller didn't provide a cache area, use some local storage + * to cache anyway, thereby avoiding a catalog lookup in the case + * where we fall through to the flat-copy code path. + */ + if (metacache == NULL) + metacache = &fakecache; + metacache->element_type = oldeah->element_type; + metacache->typlen = oldeah->typlen; + metacache->typbyval = oldeah->typbyval; + metacache->typalign = oldeah->typalign; + + /* + * If element type is pass-by-value and we have a Datum-array + * representation, just copy the source's metadata and Datum/isnull + * arrays. The original flat array, if present at all, adds no + * additional information so we need not copy it. + */ + if (oldeah->typbyval && oldeah->dvalues != NULL) + { + copy_byval_expanded_array(eah, oldeah); + /* return a R/W pointer to the expanded array */ + return EOHPGetRWDatum(&eah->hdr); + } + + /* + * Otherwise, either we have only a flat representation or the + * elements are pass-by-reference. In either case, the best thing + * seems to be to copy the source as a flat representation and then + * deconstruct that later if necessary. For the pass-by-ref case, we + * could perhaps save some cycles with custom code that generates the + * deconstructed representation in parallel with copying the values, + * but it would be a lot of extra code for fairly marginal gain. So, + * fall through into the flat-source code path. + */ + } + + /* + * Detoast and copy source array into private context, as a flat array. + * + * Note that this coding risks leaking some memory in the private context + * if we have to fetch data from a TOAST table; however, experimentation + * says that the leak is minimal. Doing it this way saves a copy step, + * which seems worthwhile, especially if the array is large enough to need + * external storage. + */ + oldcxt = MemoryContextSwitchTo(objcxt); + array = DatumGetArrayTypePCopy(arraydatum); + MemoryContextSwitchTo(oldcxt); + + eah->ndims = ARR_NDIM(array); + /* note these pointers point into the fvalue header! */ + eah->dims = ARR_DIMS(array); + eah->lbound = ARR_LBOUND(array); + + /* Save array's element-type data for possible use later */ + eah->element_type = ARR_ELEMTYPE(array); + if (metacache && metacache->element_type == eah->element_type) + { + /* We have a valid cache of representational data */ + eah->typlen = metacache->typlen; + eah->typbyval = metacache->typbyval; + eah->typalign = metacache->typalign; + } + else + { + /* No, so look it up */ + get_typlenbyvalalign(eah->element_type, + &eah->typlen, + &eah->typbyval, + &eah->typalign); + /* Update cache if provided */ + if (metacache) + { + metacache->element_type = eah->element_type; + metacache->typlen = eah->typlen; + metacache->typbyval = eah->typbyval; + metacache->typalign = eah->typalign; + } + } + + /* we don't make a deconstructed representation now */ + eah->dvalues = NULL; + eah->dnulls = NULL; + eah->dvalueslen = 0; + eah->nelems = 0; + eah->flat_size = 0; + + /* remember we have a flat representation */ + eah->fvalue = array; + eah->fstartptr = ARR_DATA_PTR(array); + eah->fendptr = ((char *) array) + ARR_SIZE(array); + + /* return a R/W pointer to the expanded array */ + return EOHPGetRWDatum(&eah->hdr); +} + +/* + * helper for expand_array(): copy pass-by-value Datum-array representation + */ +static void +copy_byval_expanded_array(ExpandedArrayHeader *eah, + ExpandedArrayHeader *oldeah) +{ + MemoryContext objcxt = eah->hdr.eoh_context; + int ndims = oldeah->ndims; + int dvalueslen = oldeah->dvalueslen; + + /* Copy array dimensionality information */ + eah->ndims = ndims; + /* We can alloc both dimensionality arrays with one palloc */ + eah->dims = (int *) MemoryContextAlloc(objcxt, ndims * 2 * sizeof(int)); + eah->lbound = eah->dims + ndims; + /* .. but don't assume the source's arrays are contiguous */ + memcpy(eah->dims, oldeah->dims, ndims * sizeof(int)); + memcpy(eah->lbound, oldeah->lbound, ndims * sizeof(int)); + + /* Copy element-type data */ + eah->element_type = oldeah->element_type; + eah->typlen = oldeah->typlen; + eah->typbyval = oldeah->typbyval; + eah->typalign = oldeah->typalign; + + /* Copy the deconstructed representation */ + eah->dvalues = (Datum *) MemoryContextAlloc(objcxt, + dvalueslen * sizeof(Datum)); + memcpy(eah->dvalues, oldeah->dvalues, dvalueslen * sizeof(Datum)); + if (oldeah->dnulls) + { + eah->dnulls = (bool *) MemoryContextAlloc(objcxt, + dvalueslen * sizeof(bool)); + memcpy(eah->dnulls, oldeah->dnulls, dvalueslen * sizeof(bool)); + } + else + eah->dnulls = NULL; + eah->dvalueslen = dvalueslen; + eah->nelems = oldeah->nelems; + eah->flat_size = oldeah->flat_size; + + /* we don't make a flat representation */ + eah->fvalue = NULL; + eah->fstartptr = NULL; + eah->fendptr = NULL; +} + +/* + * get_flat_size method for expanded arrays + */ +static Size +EA_get_flat_size(ExpandedObjectHeader *eohptr) +{ + ExpandedArrayHeader *eah = (ExpandedArrayHeader *) eohptr; + int nelems; + int ndims; + Datum *dvalues; + bool *dnulls; + Size nbytes; + int i; + + Assert(eah->ea_magic == EA_MAGIC); + + /* Easy if we have a valid flattened value */ + if (eah->fvalue) + return ARR_SIZE(eah->fvalue); + + /* If we have a cached size value, believe that */ + if (eah->flat_size) + return eah->flat_size; + + /* + * Compute space needed by examining dvalues/dnulls. Note that the result + * array will have a nulls bitmap if dnulls isn't NULL, even if the array + * doesn't actually contain any nulls now. + */ + nelems = eah->nelems; + ndims = eah->ndims; + Assert(nelems == ArrayGetNItems(ndims, eah->dims)); + dvalues = eah->dvalues; + dnulls = eah->dnulls; + nbytes = 0; + for (i = 0; i < nelems; i++) + { + if (dnulls && dnulls[i]) + continue; + nbytes = att_addlength_datum(nbytes, eah->typlen, dvalues[i]); + nbytes = att_align_nominal(nbytes, eah->typalign); + /* check for overflow of total request */ + if (!AllocSizeIsValid(nbytes)) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("array size exceeds the maximum allowed (%d)", + (int) MaxAllocSize))); + } + + if (dnulls) + nbytes += ARR_OVERHEAD_WITHNULLS(ndims, nelems); + else + nbytes += ARR_OVERHEAD_NONULLS(ndims); + + /* cache for next time */ + eah->flat_size = nbytes; + + return nbytes; +} + +/* + * flatten_into method for expanded arrays + */ +static void +EA_flatten_into(ExpandedObjectHeader *eohptr, + void *result, Size allocated_size) +{ + ExpandedArrayHeader *eah = (ExpandedArrayHeader *) eohptr; + ArrayType *aresult = (ArrayType *) result; + int nelems; + int ndims; + int32 dataoffset; + + Assert(eah->ea_magic == EA_MAGIC); + + /* Easy if we have a valid flattened value */ + if (eah->fvalue) + { + Assert(allocated_size == ARR_SIZE(eah->fvalue)); + memcpy(result, eah->fvalue, allocated_size); + return; + } + + /* Else allocation should match previous get_flat_size result */ + Assert(allocated_size == eah->flat_size); + + /* Fill result array from dvalues/dnulls */ + nelems = eah->nelems; + ndims = eah->ndims; + + if (eah->dnulls) + dataoffset = ARR_OVERHEAD_WITHNULLS(ndims, nelems); + else + dataoffset = 0; /* marker for no null bitmap */ + + /* We must ensure that any pad space is zero-filled */ + memset(aresult, 0, allocated_size); + + SET_VARSIZE(aresult, allocated_size); + aresult->ndim = ndims; + aresult->dataoffset = dataoffset; + aresult->elemtype = eah->element_type; + memcpy(ARR_DIMS(aresult), eah->dims, ndims * sizeof(int)); + memcpy(ARR_LBOUND(aresult), eah->lbound, ndims * sizeof(int)); + + CopyArrayEls(aresult, + eah->dvalues, eah->dnulls, nelems, + eah->typlen, eah->typbyval, eah->typalign, + false); +} + +/* + * Argument fetching support code + */ + +/* + * DatumGetExpandedArray: get a writable expanded array from an input argument + * + * Caution: if the input is a read/write pointer, this returns the input + * argument; so callers must be sure that their changes are "safe", that is + * they cannot leave the array in a corrupt state. + */ +ExpandedArrayHeader * +DatumGetExpandedArray(Datum d) +{ + /* If it's a writable expanded array already, just return it */ + if (VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(d))) + { + ExpandedArrayHeader *eah = (ExpandedArrayHeader *) DatumGetEOHP(d); + + Assert(eah->ea_magic == EA_MAGIC); + return eah; + } + + /* Else expand the hard way */ + d = expand_array(d, CurrentMemoryContext, NULL); + return (ExpandedArrayHeader *) DatumGetEOHP(d); +} + +/* + * As above, when caller has the ability to cache element type info + */ +ExpandedArrayHeader * +DatumGetExpandedArrayX(Datum d, ArrayMetaState *metacache) +{ + /* If it's a writable expanded array already, just return it */ + if (VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(d))) + { + ExpandedArrayHeader *eah = (ExpandedArrayHeader *) DatumGetEOHP(d); + + Assert(eah->ea_magic == EA_MAGIC); + /* Update cache if provided */ + if (metacache) + { + metacache->element_type = eah->element_type; + metacache->typlen = eah->typlen; + metacache->typbyval = eah->typbyval; + metacache->typalign = eah->typalign; + } + return eah; + } + + /* Else expand using caller's cache if any */ + d = expand_array(d, CurrentMemoryContext, metacache); + return (ExpandedArrayHeader *) DatumGetEOHP(d); +} + +/* + * DatumGetAnyArrayP: return either an expanded array or a detoasted varlena + * array. The result must not be modified in-place. + */ +AnyArrayType * +DatumGetAnyArrayP(Datum d) +{ + ExpandedArrayHeader *eah; + + /* + * If it's an expanded array (RW or RO), return the header pointer. + */ + if (VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(d))) + { + eah = (ExpandedArrayHeader *) DatumGetEOHP(d); + Assert(eah->ea_magic == EA_MAGIC); + return (AnyArrayType *) eah; + } + + /* Else do regular detoasting as needed */ + return (AnyArrayType *) PG_DETOAST_DATUM(d); +} + +/* + * Create the Datum/isnull representation of an expanded array object + * if we didn't do so previously + */ +void +deconstruct_expanded_array(ExpandedArrayHeader *eah) +{ + if (eah->dvalues == NULL) + { + MemoryContext oldcxt = MemoryContextSwitchTo(eah->hdr.eoh_context); + Datum *dvalues; + bool *dnulls; + int nelems; + + dnulls = NULL; + deconstruct_array(eah->fvalue, + eah->element_type, + eah->typlen, eah->typbyval, eah->typalign, + &dvalues, + ARR_HASNULL(eah->fvalue) ? &dnulls : NULL, + &nelems); + + /* + * Update header only after successful completion of this step. If + * deconstruct_array fails partway through, worst consequence is some + * leaked memory in the object's context. If the caller fails at a + * later point, that's fine, since the deconstructed representation is + * valid anyhow. + */ + eah->dvalues = dvalues; + eah->dnulls = dnulls; + eah->dvalueslen = eah->nelems = nelems; + MemoryContextSwitchTo(oldcxt); + } +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/array_selfuncs.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/array_selfuncs.c new file mode 100644 index 00000000000..9207a5ed193 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/array_selfuncs.c @@ -0,0 +1,1193 @@ +/*------------------------------------------------------------------------- + * + * array_selfuncs.c + * Functions for selectivity estimation of array operators + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/adt/array_selfuncs.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <math.h> + +#include "access/htup_details.h" +#include "catalog/pg_collation.h" +#include "catalog/pg_operator.h" +#include "catalog/pg_statistic.h" +#include "utils/array.h" +#include "utils/builtins.h" +#include "utils/lsyscache.h" +#include "utils/selfuncs.h" +#include "utils/typcache.h" + + +/* Default selectivity constant for "@>" and "<@" operators */ +#define DEFAULT_CONTAIN_SEL 0.005 + +/* Default selectivity constant for "&&" operator */ +#define DEFAULT_OVERLAP_SEL 0.01 + +/* Default selectivity for given operator */ +#define DEFAULT_SEL(operator) \ + ((operator) == OID_ARRAY_OVERLAP_OP ? \ + DEFAULT_OVERLAP_SEL : DEFAULT_CONTAIN_SEL) + +static Selectivity calc_arraycontsel(VariableStatData *vardata, Datum constval, + Oid elemtype, Oid operator); +static Selectivity mcelem_array_selec(ArrayType *array, + TypeCacheEntry *typentry, + Datum *mcelem, int nmcelem, + float4 *numbers, int nnumbers, + float4 *hist, int nhist, + Oid operator); +static Selectivity mcelem_array_contain_overlap_selec(Datum *mcelem, int nmcelem, + float4 *numbers, int nnumbers, + Datum *array_data, int nitems, + Oid operator, TypeCacheEntry *typentry); +static Selectivity mcelem_array_contained_selec(Datum *mcelem, int nmcelem, + float4 *numbers, int nnumbers, + Datum *array_data, int nitems, + float4 *hist, int nhist, + Oid operator, TypeCacheEntry *typentry); +static float *calc_hist(const float4 *hist, int nhist, int n); +static float *calc_distr(const float *p, int n, int m, float rest); +static int floor_log2(uint32 n); +static bool find_next_mcelem(Datum *mcelem, int nmcelem, Datum value, + int *index, TypeCacheEntry *typentry); +static int element_compare(const void *key1, const void *key2, void *arg); +static int float_compare_desc(const void *key1, const void *key2); + + +/* + * scalararraysel_containment + * Estimate selectivity of ScalarArrayOpExpr via array containment. + * + * If we have const =/<> ANY/ALL (array_var) then we can estimate the + * selectivity as though this were an array containment operator, + * array_var op ARRAY[const]. + * + * scalararraysel() has already verified that the ScalarArrayOpExpr's operator + * is the array element type's default equality or inequality operator, and + * has aggressively simplified both inputs to constants. + * + * Returns selectivity (0..1), or -1 if we fail to estimate selectivity. + */ +Selectivity +scalararraysel_containment(PlannerInfo *root, + Node *leftop, Node *rightop, + Oid elemtype, bool isEquality, bool useOr, + int varRelid) +{ + Selectivity selec; + VariableStatData vardata; + Datum constval; + TypeCacheEntry *typentry; + FmgrInfo *cmpfunc; + + /* + * rightop must be a variable, else punt. + */ + examine_variable(root, rightop, varRelid, &vardata); + if (!vardata.rel) + { + ReleaseVariableStats(vardata); + return -1.0; + } + + /* + * leftop must be a constant, else punt. + */ + if (!IsA(leftop, Const)) + { + ReleaseVariableStats(vardata); + return -1.0; + } + if (((Const *) leftop)->constisnull) + { + /* qual can't succeed if null on left */ + ReleaseVariableStats(vardata); + return (Selectivity) 0.0; + } + constval = ((Const *) leftop)->constvalue; + + /* Get element type's default comparison function */ + typentry = lookup_type_cache(elemtype, TYPECACHE_CMP_PROC_FINFO); + if (!OidIsValid(typentry->cmp_proc_finfo.fn_oid)) + { + ReleaseVariableStats(vardata); + return -1.0; + } + cmpfunc = &typentry->cmp_proc_finfo; + + /* + * If the operator is <>, swap ANY/ALL, then invert the result later. + */ + if (!isEquality) + useOr = !useOr; + + /* Get array element stats for var, if available */ + if (HeapTupleIsValid(vardata.statsTuple) && + statistic_proc_security_check(&vardata, cmpfunc->fn_oid)) + { + Form_pg_statistic stats; + AttStatsSlot sslot; + AttStatsSlot hslot; + + stats = (Form_pg_statistic) GETSTRUCT(vardata.statsTuple); + + /* MCELEM will be an array of same type as element */ + if (get_attstatsslot(&sslot, vardata.statsTuple, + STATISTIC_KIND_MCELEM, InvalidOid, + ATTSTATSSLOT_VALUES | ATTSTATSSLOT_NUMBERS)) + { + /* For ALL case, also get histogram of distinct-element counts */ + if (useOr || + !get_attstatsslot(&hslot, vardata.statsTuple, + STATISTIC_KIND_DECHIST, InvalidOid, + ATTSTATSSLOT_NUMBERS)) + memset(&hslot, 0, sizeof(hslot)); + + /* + * For = ANY, estimate as var @> ARRAY[const]. + * + * For = ALL, estimate as var <@ ARRAY[const]. + */ + if (useOr) + selec = mcelem_array_contain_overlap_selec(sslot.values, + sslot.nvalues, + sslot.numbers, + sslot.nnumbers, + &constval, 1, + OID_ARRAY_CONTAINS_OP, + typentry); + else + selec = mcelem_array_contained_selec(sslot.values, + sslot.nvalues, + sslot.numbers, + sslot.nnumbers, + &constval, 1, + hslot.numbers, + hslot.nnumbers, + OID_ARRAY_CONTAINED_OP, + typentry); + + free_attstatsslot(&hslot); + free_attstatsslot(&sslot); + } + else + { + /* No most-common-elements info, so do without */ + if (useOr) + selec = mcelem_array_contain_overlap_selec(NULL, 0, + NULL, 0, + &constval, 1, + OID_ARRAY_CONTAINS_OP, + typentry); + else + selec = mcelem_array_contained_selec(NULL, 0, + NULL, 0, + &constval, 1, + NULL, 0, + OID_ARRAY_CONTAINED_OP, + typentry); + } + + /* + * MCE stats count only non-null rows, so adjust for null rows. + */ + selec *= (1.0 - stats->stanullfrac); + } + else + { + /* No stats at all, so do without */ + if (useOr) + selec = mcelem_array_contain_overlap_selec(NULL, 0, + NULL, 0, + &constval, 1, + OID_ARRAY_CONTAINS_OP, + typentry); + else + selec = mcelem_array_contained_selec(NULL, 0, + NULL, 0, + &constval, 1, + NULL, 0, + OID_ARRAY_CONTAINED_OP, + typentry); + /* we assume no nulls here, so no stanullfrac correction */ + } + + ReleaseVariableStats(vardata); + + /* + * If the operator is <>, invert the results. + */ + if (!isEquality) + selec = 1.0 - selec; + + CLAMP_PROBABILITY(selec); + + return selec; +} + +/* + * arraycontsel -- restriction selectivity for array @>, &&, <@ operators + */ +Datum +arraycontsel(PG_FUNCTION_ARGS) +{ + PlannerInfo *root = (PlannerInfo *) PG_GETARG_POINTER(0); + Oid operator = PG_GETARG_OID(1); + List *args = (List *) PG_GETARG_POINTER(2); + int varRelid = PG_GETARG_INT32(3); + VariableStatData vardata; + Node *other; + bool varonleft; + Selectivity selec; + Oid element_typeid; + + /* + * If expression is not (variable op something) or (something op + * variable), then punt and return a default estimate. + */ + if (!get_restriction_variable(root, args, varRelid, + &vardata, &other, &varonleft)) + PG_RETURN_FLOAT8(DEFAULT_SEL(operator)); + + /* + * Can't do anything useful if the something is not a constant, either. + */ + if (!IsA(other, Const)) + { + ReleaseVariableStats(vardata); + PG_RETURN_FLOAT8(DEFAULT_SEL(operator)); + } + + /* + * The "&&", "@>" and "<@" operators are strict, so we can cope with a + * NULL constant right away. + */ + if (((Const *) other)->constisnull) + { + ReleaseVariableStats(vardata); + PG_RETURN_FLOAT8(0.0); + } + + /* + * If var is on the right, commute the operator, so that we can assume the + * var is on the left in what follows. + */ + if (!varonleft) + { + if (operator == OID_ARRAY_CONTAINS_OP) + operator = OID_ARRAY_CONTAINED_OP; + else if (operator == OID_ARRAY_CONTAINED_OP) + operator = OID_ARRAY_CONTAINS_OP; + } + + /* + * OK, there's a Var and a Const we're dealing with here. We need the + * Const to be an array with same element type as column, else we can't do + * anything useful. (Such cases will likely fail at runtime, but here + * we'd rather just return a default estimate.) + */ + element_typeid = get_base_element_type(((Const *) other)->consttype); + if (element_typeid != InvalidOid && + element_typeid == get_base_element_type(vardata.vartype)) + { + selec = calc_arraycontsel(&vardata, ((Const *) other)->constvalue, + element_typeid, operator); + } + else + { + selec = DEFAULT_SEL(operator); + } + + ReleaseVariableStats(vardata); + + CLAMP_PROBABILITY(selec); + + PG_RETURN_FLOAT8((float8) selec); +} + +/* + * arraycontjoinsel -- join selectivity for array @>, &&, <@ operators + */ +Datum +arraycontjoinsel(PG_FUNCTION_ARGS) +{ + /* For the moment this is just a stub */ + Oid operator = PG_GETARG_OID(1); + + PG_RETURN_FLOAT8(DEFAULT_SEL(operator)); +} + +/* + * Calculate selectivity for "arraycolumn @> const", "arraycolumn && const" + * or "arraycolumn <@ const" based on the statistics + * + * This function is mainly responsible for extracting the pg_statistic data + * to be used; we then pass the problem on to mcelem_array_selec(). + */ +static Selectivity +calc_arraycontsel(VariableStatData *vardata, Datum constval, + Oid elemtype, Oid operator) +{ + Selectivity selec; + TypeCacheEntry *typentry; + FmgrInfo *cmpfunc; + ArrayType *array; + + /* Get element type's default comparison function */ + typentry = lookup_type_cache(elemtype, TYPECACHE_CMP_PROC_FINFO); + if (!OidIsValid(typentry->cmp_proc_finfo.fn_oid)) + return DEFAULT_SEL(operator); + cmpfunc = &typentry->cmp_proc_finfo; + + /* + * The caller made sure the const is an array with same element type, so + * get it now + */ + array = DatumGetArrayTypeP(constval); + + if (HeapTupleIsValid(vardata->statsTuple) && + statistic_proc_security_check(vardata, cmpfunc->fn_oid)) + { + Form_pg_statistic stats; + AttStatsSlot sslot; + AttStatsSlot hslot; + + stats = (Form_pg_statistic) GETSTRUCT(vardata->statsTuple); + + /* MCELEM will be an array of same type as column */ + if (get_attstatsslot(&sslot, vardata->statsTuple, + STATISTIC_KIND_MCELEM, InvalidOid, + ATTSTATSSLOT_VALUES | ATTSTATSSLOT_NUMBERS)) + { + /* + * For "array <@ const" case we also need histogram of distinct + * element counts. + */ + if (operator != OID_ARRAY_CONTAINED_OP || + !get_attstatsslot(&hslot, vardata->statsTuple, + STATISTIC_KIND_DECHIST, InvalidOid, + ATTSTATSSLOT_NUMBERS)) + memset(&hslot, 0, sizeof(hslot)); + + /* Use the most-common-elements slot for the array Var. */ + selec = mcelem_array_selec(array, typentry, + sslot.values, sslot.nvalues, + sslot.numbers, sslot.nnumbers, + hslot.numbers, hslot.nnumbers, + operator); + + free_attstatsslot(&hslot); + free_attstatsslot(&sslot); + } + else + { + /* No most-common-elements info, so do without */ + selec = mcelem_array_selec(array, typentry, + NULL, 0, NULL, 0, NULL, 0, + operator); + } + + /* + * MCE stats count only non-null rows, so adjust for null rows. + */ + selec *= (1.0 - stats->stanullfrac); + } + else + { + /* No stats at all, so do without */ + selec = mcelem_array_selec(array, typentry, + NULL, 0, NULL, 0, NULL, 0, + operator); + /* we assume no nulls here, so no stanullfrac correction */ + } + + /* If constant was toasted, release the copy we made */ + if (PointerGetDatum(array) != constval) + pfree(array); + + return selec; +} + +/* + * Array selectivity estimation based on most common elements statistics + * + * This function just deconstructs and sorts the array constant's contents, + * and then passes the problem on to mcelem_array_contain_overlap_selec or + * mcelem_array_contained_selec depending on the operator. + */ +static Selectivity +mcelem_array_selec(ArrayType *array, TypeCacheEntry *typentry, + Datum *mcelem, int nmcelem, + float4 *numbers, int nnumbers, + float4 *hist, int nhist, + Oid operator) +{ + Selectivity selec; + int num_elems; + Datum *elem_values; + bool *elem_nulls; + bool null_present; + int nonnull_nitems; + int i; + + /* + * Prepare constant array data for sorting. Sorting lets us find unique + * elements and efficiently merge with the MCELEM array. + */ + deconstruct_array(array, + typentry->type_id, + typentry->typlen, + typentry->typbyval, + typentry->typalign, + &elem_values, &elem_nulls, &num_elems); + + /* Collapse out any null elements */ + nonnull_nitems = 0; + null_present = false; + for (i = 0; i < num_elems; i++) + { + if (elem_nulls[i]) + null_present = true; + else + elem_values[nonnull_nitems++] = elem_values[i]; + } + + /* + * Query "column @> '{anything, null}'" matches nothing. For the other + * two operators, presence of a null in the constant can be ignored. + */ + if (null_present && operator == OID_ARRAY_CONTAINS_OP) + { + pfree(elem_values); + pfree(elem_nulls); + return (Selectivity) 0.0; + } + + /* Sort extracted elements using their default comparison function. */ + qsort_arg(elem_values, nonnull_nitems, sizeof(Datum), + element_compare, typentry); + + /* Separate cases according to operator */ + if (operator == OID_ARRAY_CONTAINS_OP || operator == OID_ARRAY_OVERLAP_OP) + selec = mcelem_array_contain_overlap_selec(mcelem, nmcelem, + numbers, nnumbers, + elem_values, nonnull_nitems, + operator, typentry); + else if (operator == OID_ARRAY_CONTAINED_OP) + selec = mcelem_array_contained_selec(mcelem, nmcelem, + numbers, nnumbers, + elem_values, nonnull_nitems, + hist, nhist, + operator, typentry); + else + { + elog(ERROR, "arraycontsel called for unrecognized operator %u", + operator); + selec = 0.0; /* keep compiler quiet */ + } + + pfree(elem_values); + pfree(elem_nulls); + return selec; +} + +/* + * Estimate selectivity of "column @> const" and "column && const" based on + * most common element statistics. This estimation assumes element + * occurrences are independent. + * + * mcelem (of length nmcelem) and numbers (of length nnumbers) are from + * the array column's MCELEM statistics slot, or are NULL/0 if stats are + * not available. array_data (of length nitems) is the constant's elements. + * + * Both the mcelem and array_data arrays are assumed presorted according + * to the element type's cmpfunc. Null elements are not present. + * + * TODO: this estimate probably could be improved by using the distinct + * elements count histogram. For example, excepting the special case of + * "column @> '{}'", we can multiply the calculated selectivity by the + * fraction of nonempty arrays in the column. + */ +static Selectivity +mcelem_array_contain_overlap_selec(Datum *mcelem, int nmcelem, + float4 *numbers, int nnumbers, + Datum *array_data, int nitems, + Oid operator, TypeCacheEntry *typentry) +{ + Selectivity selec, + elem_selec; + int mcelem_index, + i; + bool use_bsearch; + float4 minfreq; + + /* + * There should be three more Numbers than Values, because the last three + * cells should hold minimal and maximal frequency among the non-null + * elements, and then the frequency of null elements. Ignore the Numbers + * if not right. + */ + if (nnumbers != nmcelem + 3) + { + numbers = NULL; + nnumbers = 0; + } + + if (numbers) + { + /* Grab the lowest observed frequency */ + minfreq = numbers[nmcelem]; + } + else + { + /* Without statistics make some default assumptions */ + minfreq = 2 * (float4) DEFAULT_CONTAIN_SEL; + } + + /* Decide whether it is faster to use binary search or not. */ + if (nitems * floor_log2((uint32) nmcelem) < nmcelem + nitems) + use_bsearch = true; + else + use_bsearch = false; + + if (operator == OID_ARRAY_CONTAINS_OP) + { + /* + * Initial selectivity for "column @> const" query is 1.0, and it will + * be decreased with each element of constant array. + */ + selec = 1.0; + } + else + { + /* + * Initial selectivity for "column && const" query is 0.0, and it will + * be increased with each element of constant array. + */ + selec = 0.0; + } + + /* Scan mcelem and array in parallel. */ + mcelem_index = 0; + for (i = 0; i < nitems; i++) + { + bool match = false; + + /* Ignore any duplicates in the array data. */ + if (i > 0 && + element_compare(&array_data[i - 1], &array_data[i], typentry) == 0) + continue; + + /* Find the smallest MCELEM >= this array item. */ + if (use_bsearch) + { + match = find_next_mcelem(mcelem, nmcelem, array_data[i], + &mcelem_index, typentry); + } + else + { + while (mcelem_index < nmcelem) + { + int cmp = element_compare(&mcelem[mcelem_index], + &array_data[i], + typentry); + + if (cmp < 0) + mcelem_index++; + else + { + if (cmp == 0) + match = true; /* mcelem is found */ + break; + } + } + } + + if (match && numbers) + { + /* MCELEM matches the array item; use its frequency. */ + elem_selec = numbers[mcelem_index]; + mcelem_index++; + } + else + { + /* + * The element is not in MCELEM. Punt, but assume that the + * selectivity cannot be more than minfreq / 2. + */ + elem_selec = Min(DEFAULT_CONTAIN_SEL, minfreq / 2); + } + + /* + * Update overall selectivity using the current element's selectivity + * and an assumption of element occurrence independence. + */ + if (operator == OID_ARRAY_CONTAINS_OP) + selec *= elem_selec; + else + selec = selec + elem_selec - selec * elem_selec; + + /* Clamp intermediate results to stay sane despite roundoff error */ + CLAMP_PROBABILITY(selec); + } + + return selec; +} + +/* + * Estimate selectivity of "column <@ const" based on most common element + * statistics. + * + * mcelem (of length nmcelem) and numbers (of length nnumbers) are from + * the array column's MCELEM statistics slot, or are NULL/0 if stats are + * not available. array_data (of length nitems) is the constant's elements. + * hist (of length nhist) is from the array column's DECHIST statistics slot, + * or is NULL/0 if those stats are not available. + * + * Both the mcelem and array_data arrays are assumed presorted according + * to the element type's cmpfunc. Null elements are not present. + * + * Independent element occurrence would imply a particular distribution of + * distinct element counts among matching rows. Real data usually falsifies + * that assumption. For example, in a set of 11-element integer arrays having + * elements in the range [0..10], element occurrences are typically not + * independent. If they were, a sufficiently-large set would include all + * distinct element counts 0 through 11. We correct for this using the + * histogram of distinct element counts. + * + * In the "column @> const" and "column && const" cases, we usually have a + * "const" with low number of elements (otherwise we have selectivity close + * to 0 or 1 respectively). That's why the effect of dependence related + * to distinct element count distribution is negligible there. In the + * "column <@ const" case, number of elements is usually high (otherwise we + * have selectivity close to 0). That's why we should do a correction with + * the array distinct element count distribution here. + * + * Using the histogram of distinct element counts produces a different + * distribution law than independent occurrences of elements. This + * distribution law can be described as follows: + * + * P(o1, o2, ..., on) = f1^o1 * (1 - f1)^(1 - o1) * f2^o2 * + * (1 - f2)^(1 - o2) * ... * fn^on * (1 - fn)^(1 - on) * hist[m] / ind[m] + * + * where: + * o1, o2, ..., on - occurrences of elements 1, 2, ..., n + * (1 - occurrence, 0 - no occurrence) in row + * f1, f2, ..., fn - frequencies of elements 1, 2, ..., n + * (scalar values in [0..1]) according to collected statistics + * m = o1 + o2 + ... + on = total number of distinct elements in row + * hist[m] - histogram data for occurrence of m elements. + * ind[m] - probability of m occurrences from n events assuming their + * probabilities to be equal to frequencies of array elements. + * + * ind[m] = sum(f1^o1 * (1 - f1)^(1 - o1) * f2^o2 * (1 - f2)^(1 - o2) * + * ... * fn^on * (1 - fn)^(1 - on), o1, o2, ..., on) | o1 + o2 + .. on = m + */ +static Selectivity +mcelem_array_contained_selec(Datum *mcelem, int nmcelem, + float4 *numbers, int nnumbers, + Datum *array_data, int nitems, + float4 *hist, int nhist, + Oid operator, TypeCacheEntry *typentry) +{ + int mcelem_index, + i, + unique_nitems = 0; + float selec, + minfreq, + nullelem_freq; + float *dist, + *mcelem_dist, + *hist_part; + float avg_count, + mult, + rest; + float *elem_selec; + + /* + * There should be three more Numbers than Values in the MCELEM slot, + * because the last three cells should hold minimal and maximal frequency + * among the non-null elements, and then the frequency of null elements. + * Punt if not right, because we can't do much without the element freqs. + */ + if (numbers == NULL || nnumbers != nmcelem + 3) + return DEFAULT_CONTAIN_SEL; + + /* Can't do much without a count histogram, either */ + if (hist == NULL || nhist < 3) + return DEFAULT_CONTAIN_SEL; + + /* + * Grab some of the summary statistics that compute_array_stats() stores: + * lowest frequency, frequency of null elements, and average distinct + * element count. + */ + minfreq = numbers[nmcelem]; + nullelem_freq = numbers[nmcelem + 2]; + avg_count = hist[nhist - 1]; + + /* + * "rest" will be the sum of the frequencies of all elements not + * represented in MCELEM. The average distinct element count is the sum + * of the frequencies of *all* elements. Begin with that; we will proceed + * to subtract the MCELEM frequencies. + */ + rest = avg_count; + + /* + * mult is a multiplier representing estimate of probability that each + * mcelem that is not present in constant doesn't occur. + */ + mult = 1.0f; + + /* + * elem_selec is array of estimated frequencies for elements in the + * constant. + */ + elem_selec = (float *) palloc(sizeof(float) * nitems); + + /* Scan mcelem and array in parallel. */ + mcelem_index = 0; + for (i = 0; i < nitems; i++) + { + bool match = false; + + /* Ignore any duplicates in the array data. */ + if (i > 0 && + element_compare(&array_data[i - 1], &array_data[i], typentry) == 0) + continue; + + /* + * Iterate over MCELEM until we find an entry greater than or equal to + * this element of the constant. Update "rest" and "mult" for mcelem + * entries skipped over. + */ + while (mcelem_index < nmcelem) + { + int cmp = element_compare(&mcelem[mcelem_index], + &array_data[i], + typentry); + + if (cmp < 0) + { + mult *= (1.0f - numbers[mcelem_index]); + rest -= numbers[mcelem_index]; + mcelem_index++; + } + else + { + if (cmp == 0) + match = true; /* mcelem is found */ + break; + } + } + + if (match) + { + /* MCELEM matches the array item. */ + elem_selec[unique_nitems] = numbers[mcelem_index]; + /* "rest" is decremented for all mcelems, matched or not */ + rest -= numbers[mcelem_index]; + mcelem_index++; + } + else + { + /* + * The element is not in MCELEM. Punt, but assume that the + * selectivity cannot be more than minfreq / 2. + */ + elem_selec[unique_nitems] = Min(DEFAULT_CONTAIN_SEL, + minfreq / 2); + } + + unique_nitems++; + } + + /* + * If we handled all constant elements without exhausting the MCELEM + * array, finish walking it to complete calculation of "rest" and "mult". + */ + while (mcelem_index < nmcelem) + { + mult *= (1.0f - numbers[mcelem_index]); + rest -= numbers[mcelem_index]; + mcelem_index++; + } + + /* + * The presence of many distinct rare elements materially decreases + * selectivity. Use the Poisson distribution to estimate the probability + * of a column value having zero occurrences of such elements. See above + * for the definition of "rest". + */ + mult *= exp(-rest); + + /*---------- + * Using the distinct element count histogram requires + * O(unique_nitems * (nmcelem + unique_nitems)) + * operations. Beyond a certain computational cost threshold, it's + * reasonable to sacrifice accuracy for decreased planning time. We limit + * the number of operations to EFFORT * nmcelem; since nmcelem is limited + * by the column's statistics target, the work done is user-controllable. + * + * If the number of operations would be too large, we can reduce it + * without losing all accuracy by reducing unique_nitems and considering + * only the most-common elements of the constant array. To make the + * results exactly match what we would have gotten with only those + * elements to start with, we'd have to remove any discarded elements' + * frequencies from "mult", but since this is only an approximation + * anyway, we don't bother with that. Therefore it's sufficient to qsort + * elem_selec[] and take the largest elements. (They will no longer match + * up with the elements of array_data[], but we don't care.) + *---------- + */ +#define EFFORT 100 + + if ((nmcelem + unique_nitems) > 0 && + unique_nitems > EFFORT * nmcelem / (nmcelem + unique_nitems)) + { + /* + * Use the quadratic formula to solve for largest allowable N. We + * have A = 1, B = nmcelem, C = - EFFORT * nmcelem. + */ + double b = (double) nmcelem; + int n; + + n = (int) ((sqrt(b * b + 4 * EFFORT * b) - b) / 2); + + /* Sort, then take just the first n elements */ + qsort(elem_selec, unique_nitems, sizeof(float), + float_compare_desc); + unique_nitems = n; + } + + /* + * Calculate probabilities of each distinct element count for both mcelems + * and constant elements. At this point, assume independent element + * occurrence. + */ + dist = calc_distr(elem_selec, unique_nitems, unique_nitems, 0.0f); + mcelem_dist = calc_distr(numbers, nmcelem, unique_nitems, rest); + + /* ignore hist[nhist-1], which is the average not a histogram member */ + hist_part = calc_hist(hist, nhist - 1, unique_nitems); + + selec = 0.0f; + for (i = 0; i <= unique_nitems; i++) + { + /* + * mult * dist[i] / mcelem_dist[i] gives us probability of qual + * matching from assumption of independent element occurrence with the + * condition that distinct element count = i. + */ + if (mcelem_dist[i] > 0) + selec += hist_part[i] * mult * dist[i] / mcelem_dist[i]; + } + + pfree(dist); + pfree(mcelem_dist); + pfree(hist_part); + pfree(elem_selec); + + /* Take into account occurrence of NULL element. */ + selec *= (1.0f - nullelem_freq); + + CLAMP_PROBABILITY(selec); + + return selec; +} + +/* + * Calculate the first n distinct element count probabilities from a + * histogram of distinct element counts. + * + * Returns a palloc'd array of n+1 entries, with array[k] being the + * probability of element count k, k in [0..n]. + * + * We assume that a histogram box with bounds a and b gives 1 / ((b - a + 1) * + * (nhist - 1)) probability to each value in (a,b) and an additional half of + * that to a and b themselves. + */ +static float * +calc_hist(const float4 *hist, int nhist, int n) +{ + float *hist_part; + int k, + i = 0; + float prev_interval = 0, + next_interval; + float frac; + + hist_part = (float *) palloc((n + 1) * sizeof(float)); + + /* + * frac is a probability contribution for each interval between histogram + * values. We have nhist - 1 intervals, so contribution of each one will + * be 1 / (nhist - 1). + */ + frac = 1.0f / ((float) (nhist - 1)); + + for (k = 0; k <= n; k++) + { + int count = 0; + + /* + * Count the histogram boundaries equal to k. (Although the histogram + * should theoretically contain only exact integers, entries are + * floats so there could be roundoff error in large values. Treat any + * fractional value as equal to the next larger k.) + */ + while (i < nhist && hist[i] <= k) + { + count++; + i++; + } + + if (count > 0) + { + /* k is an exact bound for at least one histogram box. */ + float val; + + /* Find length between current histogram value and the next one */ + if (i < nhist) + next_interval = hist[i] - hist[i - 1]; + else + next_interval = 0; + + /* + * count - 1 histogram boxes contain k exclusively. They + * contribute a total of (count - 1) * frac probability. Also + * factor in the partial histogram boxes on either side. + */ + val = (float) (count - 1); + if (next_interval > 0) + val += 0.5f / next_interval; + if (prev_interval > 0) + val += 0.5f / prev_interval; + hist_part[k] = frac * val; + + prev_interval = next_interval; + } + else + { + /* k does not appear as an exact histogram bound. */ + if (prev_interval > 0) + hist_part[k] = frac / prev_interval; + else + hist_part[k] = 0.0f; + } + } + + return hist_part; +} + +/* + * Consider n independent events with probabilities p[]. This function + * calculates probabilities of exact k of events occurrence for k in [0..m]. + * Returns a palloc'd array of size m+1. + * + * "rest" is the sum of the probabilities of all low-probability events not + * included in p. + * + * Imagine matrix M of size (n + 1) x (m + 1). Element M[i,j] denotes the + * probability that exactly j of first i events occur. Obviously M[0,0] = 1. + * For any constant j, each increment of i increases the probability iff the + * event occurs. So, by the law of total probability: + * M[i,j] = M[i - 1, j] * (1 - p[i]) + M[i - 1, j - 1] * p[i] + * for i > 0, j > 0. + * M[i,0] = M[i - 1, 0] * (1 - p[i]) for i > 0. + */ +static float * +calc_distr(const float *p, int n, int m, float rest) +{ + float *row, + *prev_row, + *tmp; + int i, + j; + + /* + * Since we return only the last row of the matrix and need only the + * current and previous row for calculations, allocate two rows. + */ + row = (float *) palloc((m + 1) * sizeof(float)); + prev_row = (float *) palloc((m + 1) * sizeof(float)); + + /* M[0,0] = 1 */ + row[0] = 1.0f; + for (i = 1; i <= n; i++) + { + float t = p[i - 1]; + + /* Swap rows */ + tmp = row; + row = prev_row; + prev_row = tmp; + + /* Calculate next row */ + for (j = 0; j <= i && j <= m; j++) + { + float val = 0.0f; + + if (j < i) + val += prev_row[j] * (1.0f - t); + if (j > 0) + val += prev_row[j - 1] * t; + row[j] = val; + } + } + + /* + * The presence of many distinct rare (not in "p") elements materially + * decreases selectivity. Model their collective occurrence with the + * Poisson distribution. + */ + if (rest > DEFAULT_CONTAIN_SEL) + { + float t; + + /* Swap rows */ + tmp = row; + row = prev_row; + prev_row = tmp; + + for (i = 0; i <= m; i++) + row[i] = 0.0f; + + /* Value of Poisson distribution for 0 occurrences */ + t = exp(-rest); + + /* + * Calculate convolution of previously computed distribution and the + * Poisson distribution. + */ + for (i = 0; i <= m; i++) + { + for (j = 0; j <= m - i; j++) + row[j + i] += prev_row[j] * t; + + /* Get Poisson distribution value for (i + 1) occurrences */ + t *= rest / (float) (i + 1); + } + } + + pfree(prev_row); + return row; +} + +/* Fast function for floor value of 2 based logarithm calculation. */ +static int +floor_log2(uint32 n) +{ + int logval = 0; + + if (n == 0) + return -1; + if (n >= (1 << 16)) + { + n >>= 16; + logval += 16; + } + if (n >= (1 << 8)) + { + n >>= 8; + logval += 8; + } + if (n >= (1 << 4)) + { + n >>= 4; + logval += 4; + } + if (n >= (1 << 2)) + { + n >>= 2; + logval += 2; + } + if (n >= (1 << 1)) + { + logval += 1; + } + return logval; +} + +/* + * find_next_mcelem binary-searches a most common elements array, starting + * from *index, for the first member >= value. It saves the position of the + * match into *index and returns true if it's an exact match. (Note: we + * assume the mcelem elements are distinct so there can't be more than one + * exact match.) + */ +static bool +find_next_mcelem(Datum *mcelem, int nmcelem, Datum value, int *index, + TypeCacheEntry *typentry) +{ + int l = *index, + r = nmcelem - 1, + i, + res; + + while (l <= r) + { + i = (l + r) / 2; + res = element_compare(&mcelem[i], &value, typentry); + if (res == 0) + { + *index = i; + return true; + } + else if (res < 0) + l = i + 1; + else + r = i - 1; + } + *index = l; + return false; +} + +/* + * Comparison function for elements. + * + * We use the element type's default btree opclass, and its default collation + * if the type is collation-sensitive. + * + * XXX consider using SortSupport infrastructure + */ +static int +element_compare(const void *key1, const void *key2, void *arg) +{ + Datum d1 = *((const Datum *) key1); + Datum d2 = *((const Datum *) key2); + TypeCacheEntry *typentry = (TypeCacheEntry *) arg; + FmgrInfo *cmpfunc = &typentry->cmp_proc_finfo; + Datum c; + + c = FunctionCall2Coll(cmpfunc, typentry->typcollation, d1, d2); + return DatumGetInt32(c); +} + +/* + * Comparison function for sorting floats into descending order. + */ +static int +float_compare_desc(const void *key1, const void *key2) +{ + float d1 = *((const float *) key1); + float d2 = *((const float *) key2); + + if (d1 > d2) + return -1; + else if (d1 < d2) + return 1; + else + return 0; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/array_typanalyze.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/array_typanalyze.c new file mode 100644 index 00000000000..ce6a8179f6f --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/array_typanalyze.c @@ -0,0 +1,791 @@ +/*------------------------------------------------------------------------- + * + * array_typanalyze.c + * Functions for gathering statistics from array columns + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/adt/array_typanalyze.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/detoast.h" +#include "commands/vacuum.h" +#include "utils/array.h" +#include "utils/builtins.h" +#include "utils/datum.h" +#include "utils/lsyscache.h" +#include "utils/typcache.h" + + +/* + * To avoid consuming too much memory, IO and CPU load during analysis, and/or + * too much space in the resulting pg_statistic rows, we ignore arrays that + * are wider than ARRAY_WIDTH_THRESHOLD (after detoasting!). Note that this + * number is considerably more than the similar WIDTH_THRESHOLD limit used + * in analyze.c's standard typanalyze code. + */ +#define ARRAY_WIDTH_THRESHOLD 0x10000 + +/* Extra data for compute_array_stats function */ +typedef struct +{ + /* Information about array element type */ + Oid type_id; /* element type's OID */ + Oid eq_opr; /* default equality operator's OID */ + Oid coll_id; /* collation to use */ + bool typbyval; /* physical properties of element type */ + int16 typlen; + char typalign; + + /* + * Lookup data for element type's comparison and hash functions (these are + * in the type's typcache entry, which we expect to remain valid over the + * lifespan of the ANALYZE run) + */ + FmgrInfo *cmp; + FmgrInfo *hash; + + /* Saved state from std_typanalyze() */ + AnalyzeAttrComputeStatsFunc std_compute_stats; + void *std_extra_data; +} ArrayAnalyzeExtraData; + +/* + * While compute_array_stats is running, we keep a pointer to the extra data + * here for use by assorted subroutines. compute_array_stats doesn't + * currently need to be re-entrant, so avoiding this is not worth the extra + * notational cruft that would be needed. + */ +static __thread ArrayAnalyzeExtraData *array_extra_data; + +/* A hash table entry for the Lossy Counting algorithm */ +typedef struct +{ + Datum key; /* This is 'e' from the LC algorithm. */ + int frequency; /* This is 'f'. */ + int delta; /* And this is 'delta'. */ + int last_container; /* For de-duplication of array elements. */ +} TrackItem; + +/* A hash table entry for distinct-elements counts */ +typedef struct +{ + int count; /* Count of distinct elements in an array */ + int frequency; /* Number of arrays seen with this count */ +} DECountItem; + +static void compute_array_stats(VacAttrStats *stats, + AnalyzeAttrFetchFunc fetchfunc, int samplerows, double totalrows); +static void prune_element_hashtable(HTAB *elements_tab, int b_current); +static uint32 element_hash(const void *key, Size keysize); +static int element_match(const void *key1, const void *key2, Size keysize); +static int element_compare(const void *key1, const void *key2); +static int trackitem_compare_frequencies_desc(const void *e1, const void *e2, void *arg); +static int trackitem_compare_element(const void *e1, const void *e2, void *arg); +static int countitem_compare_count(const void *e1, const void *e2, void *arg); + + +/* + * array_typanalyze -- typanalyze function for array columns + */ +Datum +array_typanalyze(PG_FUNCTION_ARGS) +{ + VacAttrStats *stats = (VacAttrStats *) PG_GETARG_POINTER(0); + Oid element_typeid; + TypeCacheEntry *typentry; + ArrayAnalyzeExtraData *extra_data; + + /* + * Call the standard typanalyze function. It may fail to find needed + * operators, in which case we also can't do anything, so just fail. + */ + if (!std_typanalyze(stats)) + PG_RETURN_BOOL(false); + + /* + * Check attribute data type is a varlena array (or a domain over one). + */ + element_typeid = get_base_element_type(stats->attrtypid); + if (!OidIsValid(element_typeid)) + elog(ERROR, "array_typanalyze was invoked for non-array type %u", + stats->attrtypid); + + /* + * Gather information about the element type. If we fail to find + * something, return leaving the state from std_typanalyze() in place. + */ + typentry = lookup_type_cache(element_typeid, + TYPECACHE_EQ_OPR | + TYPECACHE_CMP_PROC_FINFO | + TYPECACHE_HASH_PROC_FINFO); + + if (!OidIsValid(typentry->eq_opr) || + !OidIsValid(typentry->cmp_proc_finfo.fn_oid) || + !OidIsValid(typentry->hash_proc_finfo.fn_oid)) + PG_RETURN_BOOL(true); + + /* Store our findings for use by compute_array_stats() */ + extra_data = (ArrayAnalyzeExtraData *) palloc(sizeof(ArrayAnalyzeExtraData)); + extra_data->type_id = typentry->type_id; + extra_data->eq_opr = typentry->eq_opr; + extra_data->coll_id = stats->attrcollid; /* collation we should use */ + extra_data->typbyval = typentry->typbyval; + extra_data->typlen = typentry->typlen; + extra_data->typalign = typentry->typalign; + extra_data->cmp = &typentry->cmp_proc_finfo; + extra_data->hash = &typentry->hash_proc_finfo; + + /* Save old compute_stats and extra_data for scalar statistics ... */ + extra_data->std_compute_stats = stats->compute_stats; + extra_data->std_extra_data = stats->extra_data; + + /* ... and replace with our info */ + stats->compute_stats = compute_array_stats; + stats->extra_data = extra_data; + + /* + * Note we leave stats->minrows set as std_typanalyze set it. Should it + * be increased for array analysis purposes? + */ + + PG_RETURN_BOOL(true); +} + +/* + * compute_array_stats() -- compute statistics for an array column + * + * This function computes statistics useful for determining selectivity of + * the array operators <@, &&, and @>. It is invoked by ANALYZE via the + * compute_stats hook after sample rows have been collected. + * + * We also invoke the standard compute_stats function, which will compute + * "scalar" statistics relevant to the btree-style array comparison operators. + * However, exact duplicates of an entire array may be rare despite many + * arrays sharing individual elements. This especially afflicts long arrays, + * which are also liable to lack all scalar statistics due to the low + * WIDTH_THRESHOLD used in analyze.c. So, in addition to the standard stats, + * we find the most common array elements and compute a histogram of distinct + * element counts. + * + * The algorithm used is Lossy Counting, as proposed in the paper "Approximate + * frequency counts over data streams" by G. S. Manku and R. Motwani, in + * Proceedings of the 28th International Conference on Very Large Data Bases, + * Hong Kong, China, August 2002, section 4.2. The paper is available at + * http://www.vldb.org/conf/2002/S10P03.pdf + * + * The Lossy Counting (aka LC) algorithm goes like this: + * Let s be the threshold frequency for an item (the minimum frequency we + * are interested in) and epsilon the error margin for the frequency. Let D + * be a set of triples (e, f, delta), where e is an element value, f is that + * element's frequency (actually, its current occurrence count) and delta is + * the maximum error in f. We start with D empty and process the elements in + * batches of size w. (The batch size is also known as "bucket size" and is + * equal to 1/epsilon.) Let the current batch number be b_current, starting + * with 1. For each element e we either increment its f count, if it's + * already in D, or insert a new triple into D with values (e, 1, b_current + * - 1). After processing each batch we prune D, by removing from it all + * elements with f + delta <= b_current. After the algorithm finishes we + * suppress all elements from D that do not satisfy f >= (s - epsilon) * N, + * where N is the total number of elements in the input. We emit the + * remaining elements with estimated frequency f/N. The LC paper proves + * that this algorithm finds all elements with true frequency at least s, + * and that no frequency is overestimated or is underestimated by more than + * epsilon. Furthermore, given reasonable assumptions about the input + * distribution, the required table size is no more than about 7 times w. + * + * In the absence of a principled basis for other particular values, we + * follow ts_typanalyze() and use parameters s = 0.07/K, epsilon = s/10. + * But we leave out the correction for stopwords, which do not apply to + * arrays. These parameters give bucket width w = K/0.007 and maximum + * expected hashtable size of about 1000 * K. + * + * Elements may repeat within an array. Since duplicates do not change the + * behavior of <@, && or @>, we want to count each element only once per + * array. Therefore, we store in the finished pg_statistic entry each + * element's frequency as the fraction of all non-null rows that contain it. + * We divide the raw counts by nonnull_cnt to get those figures. + */ +static void +compute_array_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc, + int samplerows, double totalrows) +{ + ArrayAnalyzeExtraData *extra_data; + int num_mcelem; + int null_elem_cnt = 0; + int analyzed_rows = 0; + + /* This is D from the LC algorithm. */ + HTAB *elements_tab; + HASHCTL elem_hash_ctl; + HASH_SEQ_STATUS scan_status; + + /* This is the current bucket number from the LC algorithm */ + int b_current; + + /* This is 'w' from the LC algorithm */ + int bucket_width; + int array_no; + int64 element_no; + TrackItem *item; + int slot_idx; + HTAB *count_tab; + HASHCTL count_hash_ctl; + DECountItem *count_item; + + extra_data = (ArrayAnalyzeExtraData *) stats->extra_data; + + /* + * Invoke analyze.c's standard analysis function to create scalar-style + * stats for the column. It will expect its own extra_data pointer, so + * temporarily install that. + */ + stats->extra_data = extra_data->std_extra_data; + extra_data->std_compute_stats(stats, fetchfunc, samplerows, totalrows); + stats->extra_data = extra_data; + + /* + * Set up static pointer for use by subroutines. We wait till here in + * case std_compute_stats somehow recursively invokes us (probably not + * possible, but ...) + */ + array_extra_data = extra_data; + + /* + * We want statistics_target * 10 elements in the MCELEM array. This + * multiplier is pretty arbitrary, but is meant to reflect the fact that + * the number of individual elements tracked in pg_statistic ought to be + * more than the number of values for a simple scalar column. + */ + num_mcelem = stats->attr->attstattarget * 10; + + /* + * We set bucket width equal to num_mcelem / 0.007 as per the comment + * above. + */ + bucket_width = num_mcelem * 1000 / 7; + + /* + * Create the hashtable. It will be in local memory, so we don't need to + * worry about overflowing the initial size. Also we don't need to pay any + * attention to locking and memory management. + */ + elem_hash_ctl.keysize = sizeof(Datum); + elem_hash_ctl.entrysize = sizeof(TrackItem); + elem_hash_ctl.hash = element_hash; + elem_hash_ctl.match = element_match; + elem_hash_ctl.hcxt = CurrentMemoryContext; + elements_tab = hash_create("Analyzed elements table", + num_mcelem, + &elem_hash_ctl, + HASH_ELEM | HASH_FUNCTION | HASH_COMPARE | HASH_CONTEXT); + + /* hashtable for array distinct elements counts */ + count_hash_ctl.keysize = sizeof(int); + count_hash_ctl.entrysize = sizeof(DECountItem); + count_hash_ctl.hcxt = CurrentMemoryContext; + count_tab = hash_create("Array distinct element count table", + 64, + &count_hash_ctl, + HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); + + /* Initialize counters. */ + b_current = 1; + element_no = 0; + + /* Loop over the arrays. */ + for (array_no = 0; array_no < samplerows; array_no++) + { + Datum value; + bool isnull; + ArrayType *array; + int num_elems; + Datum *elem_values; + bool *elem_nulls; + bool null_present; + int j; + int64 prev_element_no = element_no; + int distinct_count; + bool count_item_found; + + vacuum_delay_point(); + + value = fetchfunc(stats, array_no, &isnull); + if (isnull) + { + /* ignore arrays that are null overall */ + continue; + } + + /* Skip too-large values. */ + if (toast_raw_datum_size(value) > ARRAY_WIDTH_THRESHOLD) + continue; + else + analyzed_rows++; + + /* + * Now detoast the array if needed, and deconstruct into datums. + */ + array = DatumGetArrayTypeP(value); + + Assert(ARR_ELEMTYPE(array) == extra_data->type_id); + deconstruct_array(array, + extra_data->type_id, + extra_data->typlen, + extra_data->typbyval, + extra_data->typalign, + &elem_values, &elem_nulls, &num_elems); + + /* + * We loop through the elements in the array and add them to our + * tracking hashtable. + */ + null_present = false; + for (j = 0; j < num_elems; j++) + { + Datum elem_value; + bool found; + + /* No null element processing other than flag setting here */ + if (elem_nulls[j]) + { + null_present = true; + continue; + } + + /* Lookup current element in hashtable, adding it if new */ + elem_value = elem_values[j]; + item = (TrackItem *) hash_search(elements_tab, + &elem_value, + HASH_ENTER, &found); + + if (found) + { + /* The element value is already on the tracking list */ + + /* + * The operators we assist ignore duplicate array elements, so + * count a given distinct element only once per array. + */ + if (item->last_container == array_no) + continue; + + item->frequency++; + item->last_container = array_no; + } + else + { + /* Initialize new tracking list element */ + + /* + * If element type is pass-by-reference, we must copy it into + * palloc'd space, so that we can release the array below. (We + * do this so that the space needed for element values is + * limited by the size of the hashtable; if we kept all the + * array values around, it could be much more.) + */ + item->key = datumCopy(elem_value, + extra_data->typbyval, + extra_data->typlen); + + item->frequency = 1; + item->delta = b_current - 1; + item->last_container = array_no; + } + + /* element_no is the number of elements processed (ie N) */ + element_no++; + + /* We prune the D structure after processing each bucket */ + if (element_no % bucket_width == 0) + { + prune_element_hashtable(elements_tab, b_current); + b_current++; + } + } + + /* Count null element presence once per array. */ + if (null_present) + null_elem_cnt++; + + /* Update frequency of the particular array distinct element count. */ + distinct_count = (int) (element_no - prev_element_no); + count_item = (DECountItem *) hash_search(count_tab, &distinct_count, + HASH_ENTER, + &count_item_found); + + if (count_item_found) + count_item->frequency++; + else + count_item->frequency = 1; + + /* Free memory allocated while detoasting. */ + if (PointerGetDatum(array) != value) + pfree(array); + pfree(elem_values); + pfree(elem_nulls); + } + + /* Skip pg_statistic slots occupied by standard statistics */ + slot_idx = 0; + while (slot_idx < STATISTIC_NUM_SLOTS && stats->stakind[slot_idx] != 0) + slot_idx++; + if (slot_idx > STATISTIC_NUM_SLOTS - 2) + elog(ERROR, "insufficient pg_statistic slots for array stats"); + + /* We can only compute real stats if we found some non-null values. */ + if (analyzed_rows > 0) + { + int nonnull_cnt = analyzed_rows; + int count_items_count; + int i; + TrackItem **sort_table; + int track_len; + int64 cutoff_freq; + int64 minfreq, + maxfreq; + + /* + * We assume the standard stats code already took care of setting + * stats_valid, stanullfrac, stawidth, stadistinct. We'd have to + * re-compute those values if we wanted to not store the standard + * stats. + */ + + /* + * Construct an array of the interesting hashtable items, that is, + * those meeting the cutoff frequency (s - epsilon)*N. Also identify + * the minimum and maximum frequencies among these items. + * + * Since epsilon = s/10 and bucket_width = 1/epsilon, the cutoff + * frequency is 9*N / bucket_width. + */ + cutoff_freq = 9 * element_no / bucket_width; + + i = hash_get_num_entries(elements_tab); /* surely enough space */ + sort_table = (TrackItem **) palloc(sizeof(TrackItem *) * i); + + hash_seq_init(&scan_status, elements_tab); + track_len = 0; + minfreq = element_no; + maxfreq = 0; + while ((item = (TrackItem *) hash_seq_search(&scan_status)) != NULL) + { + if (item->frequency > cutoff_freq) + { + sort_table[track_len++] = item; + minfreq = Min(minfreq, item->frequency); + maxfreq = Max(maxfreq, item->frequency); + } + } + Assert(track_len <= i); + + /* emit some statistics for debug purposes */ + elog(DEBUG3, "compute_array_stats: target # mces = %d, " + "bucket width = %d, " + "# elements = " INT64_FORMAT ", hashtable size = %d, " + "usable entries = %d", + num_mcelem, bucket_width, element_no, i, track_len); + + /* + * If we obtained more elements than we really want, get rid of those + * with least frequencies. The easiest way is to qsort the array into + * descending frequency order and truncate the array. + */ + if (num_mcelem < track_len) + { + qsort_interruptible(sort_table, track_len, sizeof(TrackItem *), + trackitem_compare_frequencies_desc, NULL); + /* reset minfreq to the smallest frequency we're keeping */ + minfreq = sort_table[num_mcelem - 1]->frequency; + } + else + num_mcelem = track_len; + + /* Generate MCELEM slot entry */ + if (num_mcelem > 0) + { + MemoryContext old_context; + Datum *mcelem_values; + float4 *mcelem_freqs; + + /* + * We want to store statistics sorted on the element value using + * the element type's default comparison function. This permits + * fast binary searches in selectivity estimation functions. + */ + qsort_interruptible(sort_table, num_mcelem, sizeof(TrackItem *), + trackitem_compare_element, NULL); + + /* Must copy the target values into anl_context */ + old_context = MemoryContextSwitchTo(stats->anl_context); + + /* + * We sorted statistics on the element value, but we want to be + * able to find the minimal and maximal frequencies without going + * through all the values. We also want the frequency of null + * elements. Store these three values at the end of mcelem_freqs. + */ + mcelem_values = (Datum *) palloc(num_mcelem * sizeof(Datum)); + mcelem_freqs = (float4 *) palloc((num_mcelem + 3) * sizeof(float4)); + + /* + * See comments above about use of nonnull_cnt as the divisor for + * the final frequency estimates. + */ + for (i = 0; i < num_mcelem; i++) + { + TrackItem *titem = sort_table[i]; + + mcelem_values[i] = datumCopy(titem->key, + extra_data->typbyval, + extra_data->typlen); + mcelem_freqs[i] = (double) titem->frequency / + (double) nonnull_cnt; + } + mcelem_freqs[i++] = (double) minfreq / (double) nonnull_cnt; + mcelem_freqs[i++] = (double) maxfreq / (double) nonnull_cnt; + mcelem_freqs[i++] = (double) null_elem_cnt / (double) nonnull_cnt; + + MemoryContextSwitchTo(old_context); + + stats->stakind[slot_idx] = STATISTIC_KIND_MCELEM; + stats->staop[slot_idx] = extra_data->eq_opr; + stats->stacoll[slot_idx] = extra_data->coll_id; + stats->stanumbers[slot_idx] = mcelem_freqs; + /* See above comment about extra stanumber entries */ + stats->numnumbers[slot_idx] = num_mcelem + 3; + stats->stavalues[slot_idx] = mcelem_values; + stats->numvalues[slot_idx] = num_mcelem; + /* We are storing values of element type */ + stats->statypid[slot_idx] = extra_data->type_id; + stats->statyplen[slot_idx] = extra_data->typlen; + stats->statypbyval[slot_idx] = extra_data->typbyval; + stats->statypalign[slot_idx] = extra_data->typalign; + slot_idx++; + } + + /* Generate DECHIST slot entry */ + count_items_count = hash_get_num_entries(count_tab); + if (count_items_count > 0) + { + int num_hist = stats->attr->attstattarget; + DECountItem **sorted_count_items; + int j; + int delta; + int64 frac; + float4 *hist; + + /* num_hist must be at least 2 for the loop below to work */ + num_hist = Max(num_hist, 2); + + /* + * Create an array of DECountItem pointers, and sort them into + * increasing count order. + */ + sorted_count_items = (DECountItem **) + palloc(sizeof(DECountItem *) * count_items_count); + hash_seq_init(&scan_status, count_tab); + j = 0; + while ((count_item = (DECountItem *) hash_seq_search(&scan_status)) != NULL) + { + sorted_count_items[j++] = count_item; + } + qsort_interruptible(sorted_count_items, count_items_count, + sizeof(DECountItem *), + countitem_compare_count, NULL); + + /* + * Prepare to fill stanumbers with the histogram, followed by the + * average count. This array must be stored in anl_context. + */ + hist = (float4 *) + MemoryContextAlloc(stats->anl_context, + sizeof(float4) * (num_hist + 1)); + hist[num_hist] = (double) element_no / (double) nonnull_cnt; + + /*---------- + * Construct the histogram of distinct-element counts (DECs). + * + * The object of this loop is to copy the min and max DECs to + * hist[0] and hist[num_hist - 1], along with evenly-spaced DECs + * in between (where "evenly-spaced" is with reference to the + * whole input population of arrays). If we had a complete sorted + * array of DECs, one per analyzed row, the i'th hist value would + * come from DECs[i * (analyzed_rows - 1) / (num_hist - 1)] + * (compare the histogram-making loop in compute_scalar_stats()). + * But instead of that we have the sorted_count_items[] array, + * which holds unique DEC values with their frequencies (that is, + * a run-length-compressed version of the full array). So we + * control advancing through sorted_count_items[] with the + * variable "frac", which is defined as (x - y) * (num_hist - 1), + * where x is the index in the notional DECs array corresponding + * to the start of the next sorted_count_items[] element's run, + * and y is the index in DECs from which we should take the next + * histogram value. We have to advance whenever x <= y, that is + * frac <= 0. The x component is the sum of the frequencies seen + * so far (up through the current sorted_count_items[] element), + * and of course y * (num_hist - 1) = i * (analyzed_rows - 1), + * per the subscript calculation above. (The subscript calculation + * implies dropping any fractional part of y; in this formulation + * that's handled by not advancing until frac reaches 1.) + * + * Even though frac has a bounded range, it could overflow int32 + * when working with very large statistics targets, so we do that + * math in int64. + *---------- + */ + delta = analyzed_rows - 1; + j = 0; /* current index in sorted_count_items */ + /* Initialize frac for sorted_count_items[0]; y is initially 0 */ + frac = (int64) sorted_count_items[0]->frequency * (num_hist - 1); + for (i = 0; i < num_hist; i++) + { + while (frac <= 0) + { + /* Advance, and update x component of frac */ + j++; + frac += (int64) sorted_count_items[j]->frequency * (num_hist - 1); + } + hist[i] = sorted_count_items[j]->count; + frac -= delta; /* update y for upcoming i increment */ + } + Assert(j == count_items_count - 1); + + stats->stakind[slot_idx] = STATISTIC_KIND_DECHIST; + stats->staop[slot_idx] = extra_data->eq_opr; + stats->stacoll[slot_idx] = extra_data->coll_id; + stats->stanumbers[slot_idx] = hist; + stats->numnumbers[slot_idx] = num_hist + 1; + slot_idx++; + } + } + + /* + * We don't need to bother cleaning up any of our temporary palloc's. The + * hashtable should also go away, as it used a child memory context. + */ +} + +/* + * A function to prune the D structure from the Lossy Counting algorithm. + * Consult compute_tsvector_stats() for wider explanation. + */ +static void +prune_element_hashtable(HTAB *elements_tab, int b_current) +{ + HASH_SEQ_STATUS scan_status; + TrackItem *item; + + hash_seq_init(&scan_status, elements_tab); + while ((item = (TrackItem *) hash_seq_search(&scan_status)) != NULL) + { + if (item->frequency + item->delta <= b_current) + { + Datum value = item->key; + + if (hash_search(elements_tab, &item->key, + HASH_REMOVE, NULL) == NULL) + elog(ERROR, "hash table corrupted"); + /* We should free memory if element is not passed by value */ + if (!array_extra_data->typbyval) + pfree(DatumGetPointer(value)); + } + } +} + +/* + * Hash function for elements. + * + * We use the element type's default hash opclass, and the column collation + * if the type is collation-sensitive. + */ +static uint32 +element_hash(const void *key, Size keysize) +{ + Datum d = *((const Datum *) key); + Datum h; + + h = FunctionCall1Coll(array_extra_data->hash, + array_extra_data->coll_id, + d); + return DatumGetUInt32(h); +} + +/* + * Matching function for elements, to be used in hashtable lookups. + */ +static int +element_match(const void *key1, const void *key2, Size keysize) +{ + /* The keysize parameter is superfluous here */ + return element_compare(key1, key2); +} + +/* + * Comparison function for elements. + * + * We use the element type's default btree opclass, and the column collation + * if the type is collation-sensitive. + * + * XXX consider using SortSupport infrastructure + */ +static int +element_compare(const void *key1, const void *key2) +{ + Datum d1 = *((const Datum *) key1); + Datum d2 = *((const Datum *) key2); + Datum c; + + c = FunctionCall2Coll(array_extra_data->cmp, + array_extra_data->coll_id, + d1, d2); + return DatumGetInt32(c); +} + +/* + * Comparator for sorting TrackItems by frequencies (descending sort) + */ +static int +trackitem_compare_frequencies_desc(const void *e1, const void *e2, void *arg) +{ + const TrackItem *const *t1 = (const TrackItem *const *) e1; + const TrackItem *const *t2 = (const TrackItem *const *) e2; + + return (*t2)->frequency - (*t1)->frequency; +} + +/* + * Comparator for sorting TrackItems by element values + */ +static int +trackitem_compare_element(const void *e1, const void *e2, void *arg) +{ + const TrackItem *const *t1 = (const TrackItem *const *) e1; + const TrackItem *const *t2 = (const TrackItem *const *) e2; + + return element_compare(&(*t1)->key, &(*t2)->key); +} + +/* + * Comparator for sorting DECountItems by count + */ +static int +countitem_compare_count(const void *e1, const void *e2, void *arg) +{ + const DECountItem *const *t1 = (const DECountItem *const *) e1; + const DECountItem *const *t2 = (const DECountItem *const *) e2; + + if ((*t1)->count < (*t2)->count) + return -1; + else if ((*t1)->count == (*t2)->count) + return 0; + else + return 1; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/array_userfuncs.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/array_userfuncs.c new file mode 100644 index 00000000000..5c4fdcfba46 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/array_userfuncs.c @@ -0,0 +1,1701 @@ +/*------------------------------------------------------------------------- + * + * array_userfuncs.c + * Misc user-visible array support functions + * + * Copyright (c) 2003-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/utils/adt/array_userfuncs.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "catalog/pg_type.h" +#include "libpq/pqformat.h" +#include "common/int.h" +#include "common/pg_prng.h" +#include "port/pg_bitutils.h" +#include "utils/array.h" +#include "utils/datum.h" +#include "utils/builtins.h" +#include "utils/lsyscache.h" +#include "utils/typcache.h" + +/* + * SerialIOData + * Used for caching element-type data in array_agg_serialize + */ +typedef struct SerialIOData +{ + FmgrInfo typsend; +} SerialIOData; + +/* + * DeserialIOData + * Used for caching element-type data in array_agg_deserialize + */ +typedef struct DeserialIOData +{ + FmgrInfo typreceive; + Oid typioparam; +} DeserialIOData; + +static Datum array_position_common(FunctionCallInfo fcinfo); + + +/* + * fetch_array_arg_replace_nulls + * + * Fetch an array-valued argument in expanded form; if it's null, construct an + * empty array value of the proper data type. Also cache basic element type + * information in fn_extra. + * + * Caution: if the input is a read/write pointer, this returns the input + * argument; so callers must be sure that their changes are "safe", that is + * they cannot leave the array in a corrupt state. + * + * If we're being called as an aggregate function, make sure any newly-made + * expanded array is allocated in the aggregate state context, so as to save + * copying operations. + */ +static ExpandedArrayHeader * +fetch_array_arg_replace_nulls(FunctionCallInfo fcinfo, int argno) +{ + ExpandedArrayHeader *eah; + Oid element_type; + ArrayMetaState *my_extra; + MemoryContext resultcxt; + + /* If first time through, create datatype cache struct */ + my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra; + if (my_extra == NULL) + { + my_extra = (ArrayMetaState *) + MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, + sizeof(ArrayMetaState)); + my_extra->element_type = InvalidOid; + fcinfo->flinfo->fn_extra = my_extra; + } + + /* Figure out which context we want the result in */ + if (!AggCheckCallContext(fcinfo, &resultcxt)) + resultcxt = CurrentMemoryContext; + + /* Now collect the array value */ + if (!PG_ARGISNULL(argno)) + { + MemoryContext oldcxt = MemoryContextSwitchTo(resultcxt); + + eah = PG_GETARG_EXPANDED_ARRAYX(argno, my_extra); + MemoryContextSwitchTo(oldcxt); + } + else + { + /* We have to look up the array type and element type */ + Oid arr_typeid = get_fn_expr_argtype(fcinfo->flinfo, argno); + + if (!OidIsValid(arr_typeid)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("could not determine input data type"))); + element_type = get_element_type(arr_typeid); + if (!OidIsValid(element_type)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("input data type is not an array"))); + + eah = construct_empty_expanded_array(element_type, + resultcxt, + my_extra); + } + + return eah; +} + +/*----------------------------------------------------------------------------- + * array_append : + * push an element onto the end of a one-dimensional array + *---------------------------------------------------------------------------- + */ +Datum +array_append(PG_FUNCTION_ARGS) +{ + ExpandedArrayHeader *eah; + Datum newelem; + bool isNull; + Datum result; + int *dimv, + *lb; + int indx; + ArrayMetaState *my_extra; + + eah = fetch_array_arg_replace_nulls(fcinfo, 0); + isNull = PG_ARGISNULL(1); + if (isNull) + newelem = (Datum) 0; + else + newelem = PG_GETARG_DATUM(1); + + if (eah->ndims == 1) + { + /* append newelem */ + lb = eah->lbound; + dimv = eah->dims; + + /* index of added elem is at lb[0] + (dimv[0] - 1) + 1 */ + if (pg_add_s32_overflow(lb[0], dimv[0], &indx)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("integer out of range"))); + } + else if (eah->ndims == 0) + indx = 1; + else + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("argument must be empty or one-dimensional array"))); + + /* Perform element insertion */ + my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra; + + result = array_set_element(EOHPGetRWDatum(&eah->hdr), + 1, &indx, newelem, isNull, + -1, my_extra->typlen, my_extra->typbyval, my_extra->typalign); + + PG_RETURN_DATUM(result); +} + +/*----------------------------------------------------------------------------- + * array_prepend : + * push an element onto the front of a one-dimensional array + *---------------------------------------------------------------------------- + */ +Datum +array_prepend(PG_FUNCTION_ARGS) +{ + ExpandedArrayHeader *eah; + Datum newelem; + bool isNull; + Datum result; + int *lb; + int indx; + int lb0; + ArrayMetaState *my_extra; + + isNull = PG_ARGISNULL(0); + if (isNull) + newelem = (Datum) 0; + else + newelem = PG_GETARG_DATUM(0); + eah = fetch_array_arg_replace_nulls(fcinfo, 1); + + if (eah->ndims == 1) + { + /* prepend newelem */ + lb = eah->lbound; + lb0 = lb[0]; + + if (pg_sub_s32_overflow(lb0, 1, &indx)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("integer out of range"))); + } + else if (eah->ndims == 0) + { + indx = 1; + lb0 = 1; + } + else + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("argument must be empty or one-dimensional array"))); + + /* Perform element insertion */ + my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra; + + result = array_set_element(EOHPGetRWDatum(&eah->hdr), + 1, &indx, newelem, isNull, + -1, my_extra->typlen, my_extra->typbyval, my_extra->typalign); + + /* Readjust result's LB to match the input's, as expected for prepend */ + Assert(result == EOHPGetRWDatum(&eah->hdr)); + if (eah->ndims == 1) + { + /* This is ok whether we've deconstructed or not */ + eah->lbound[0] = lb0; + } + + PG_RETURN_DATUM(result); +} + +/*----------------------------------------------------------------------------- + * array_cat : + * concatenate two nD arrays to form an nD array, or + * push an (n-1)D array onto the end of an nD array + *---------------------------------------------------------------------------- + */ +Datum +array_cat(PG_FUNCTION_ARGS) +{ + ArrayType *v1, + *v2; + ArrayType *result; + int *dims, + *lbs, + ndims, + nitems, + ndatabytes, + nbytes; + int *dims1, + *lbs1, + ndims1, + nitems1, + ndatabytes1; + int *dims2, + *lbs2, + ndims2, + nitems2, + ndatabytes2; + int i; + char *dat1, + *dat2; + bits8 *bitmap1, + *bitmap2; + Oid element_type; + Oid element_type1; + Oid element_type2; + int32 dataoffset; + + /* Concatenating a null array is a no-op, just return the other input */ + if (PG_ARGISNULL(0)) + { + if (PG_ARGISNULL(1)) + PG_RETURN_NULL(); + result = PG_GETARG_ARRAYTYPE_P(1); + PG_RETURN_ARRAYTYPE_P(result); + } + if (PG_ARGISNULL(1)) + { + result = PG_GETARG_ARRAYTYPE_P(0); + PG_RETURN_ARRAYTYPE_P(result); + } + + v1 = PG_GETARG_ARRAYTYPE_P(0); + v2 = PG_GETARG_ARRAYTYPE_P(1); + + element_type1 = ARR_ELEMTYPE(v1); + element_type2 = ARR_ELEMTYPE(v2); + + /* Check we have matching element types */ + if (element_type1 != element_type2) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot concatenate incompatible arrays"), + errdetail("Arrays with element types %s and %s are not " + "compatible for concatenation.", + format_type_be(element_type1), + format_type_be(element_type2)))); + + /* OK, use it */ + element_type = element_type1; + + /*---------- + * We must have one of the following combinations of inputs: + * 1) one empty array, and one non-empty array + * 2) both arrays empty + * 3) two arrays with ndims1 == ndims2 + * 4) ndims1 == ndims2 - 1 + * 5) ndims1 == ndims2 + 1 + *---------- + */ + ndims1 = ARR_NDIM(v1); + ndims2 = ARR_NDIM(v2); + + /* + * short circuit - if one input array is empty, and the other is not, we + * return the non-empty one as the result + * + * if both are empty, return the first one + */ + if (ndims1 == 0 && ndims2 > 0) + PG_RETURN_ARRAYTYPE_P(v2); + + if (ndims2 == 0) + PG_RETURN_ARRAYTYPE_P(v1); + + /* the rest fall under rule 3, 4, or 5 */ + if (ndims1 != ndims2 && + ndims1 != ndims2 - 1 && + ndims1 != ndims2 + 1) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("cannot concatenate incompatible arrays"), + errdetail("Arrays of %d and %d dimensions are not " + "compatible for concatenation.", + ndims1, ndims2))); + + /* get argument array details */ + lbs1 = ARR_LBOUND(v1); + lbs2 = ARR_LBOUND(v2); + dims1 = ARR_DIMS(v1); + dims2 = ARR_DIMS(v2); + dat1 = ARR_DATA_PTR(v1); + dat2 = ARR_DATA_PTR(v2); + bitmap1 = ARR_NULLBITMAP(v1); + bitmap2 = ARR_NULLBITMAP(v2); + nitems1 = ArrayGetNItems(ndims1, dims1); + nitems2 = ArrayGetNItems(ndims2, dims2); + ndatabytes1 = ARR_SIZE(v1) - ARR_DATA_OFFSET(v1); + ndatabytes2 = ARR_SIZE(v2) - ARR_DATA_OFFSET(v2); + + if (ndims1 == ndims2) + { + /* + * resulting array is made up of the elements (possibly arrays + * themselves) of the input argument arrays + */ + ndims = ndims1; + dims = (int *) palloc(ndims * sizeof(int)); + lbs = (int *) palloc(ndims * sizeof(int)); + + dims[0] = dims1[0] + dims2[0]; + lbs[0] = lbs1[0]; + + for (i = 1; i < ndims; i++) + { + if (dims1[i] != dims2[i] || lbs1[i] != lbs2[i]) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("cannot concatenate incompatible arrays"), + errdetail("Arrays with differing element dimensions are " + "not compatible for concatenation."))); + + dims[i] = dims1[i]; + lbs[i] = lbs1[i]; + } + } + else if (ndims1 == ndims2 - 1) + { + /* + * resulting array has the second argument as the outer array, with + * the first argument inserted at the front of the outer dimension + */ + ndims = ndims2; + dims = (int *) palloc(ndims * sizeof(int)); + lbs = (int *) palloc(ndims * sizeof(int)); + memcpy(dims, dims2, ndims * sizeof(int)); + memcpy(lbs, lbs2, ndims * sizeof(int)); + + /* increment number of elements in outer array */ + dims[0] += 1; + + /* make sure the added element matches our existing elements */ + for (i = 0; i < ndims1; i++) + { + if (dims1[i] != dims[i + 1] || lbs1[i] != lbs[i + 1]) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("cannot concatenate incompatible arrays"), + errdetail("Arrays with differing dimensions are not " + "compatible for concatenation."))); + } + } + else + { + /* + * (ndims1 == ndims2 + 1) + * + * resulting array has the first argument as the outer array, with the + * second argument appended to the end of the outer dimension + */ + ndims = ndims1; + dims = (int *) palloc(ndims * sizeof(int)); + lbs = (int *) palloc(ndims * sizeof(int)); + memcpy(dims, dims1, ndims * sizeof(int)); + memcpy(lbs, lbs1, ndims * sizeof(int)); + + /* increment number of elements in outer array */ + dims[0] += 1; + + /* make sure the added element matches our existing elements */ + for (i = 0; i < ndims2; i++) + { + if (dims2[i] != dims[i + 1] || lbs2[i] != lbs[i + 1]) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("cannot concatenate incompatible arrays"), + errdetail("Arrays with differing dimensions are not " + "compatible for concatenation."))); + } + } + + /* Do this mainly for overflow checking */ + nitems = ArrayGetNItems(ndims, dims); + ArrayCheckBounds(ndims, dims, lbs); + + /* build the result array */ + ndatabytes = ndatabytes1 + ndatabytes2; + if (ARR_HASNULL(v1) || ARR_HASNULL(v2)) + { + dataoffset = ARR_OVERHEAD_WITHNULLS(ndims, nitems); + nbytes = ndatabytes + dataoffset; + } + else + { + dataoffset = 0; /* marker for no null bitmap */ + nbytes = ndatabytes + ARR_OVERHEAD_NONULLS(ndims); + } + result = (ArrayType *) palloc0(nbytes); + SET_VARSIZE(result, nbytes); + result->ndim = ndims; + result->dataoffset = dataoffset; + result->elemtype = element_type; + memcpy(ARR_DIMS(result), dims, ndims * sizeof(int)); + memcpy(ARR_LBOUND(result), lbs, ndims * sizeof(int)); + /* data area is arg1 then arg2 */ + memcpy(ARR_DATA_PTR(result), dat1, ndatabytes1); + memcpy(ARR_DATA_PTR(result) + ndatabytes1, dat2, ndatabytes2); + /* handle the null bitmap if needed */ + if (ARR_HASNULL(result)) + { + array_bitmap_copy(ARR_NULLBITMAP(result), 0, + bitmap1, 0, + nitems1); + array_bitmap_copy(ARR_NULLBITMAP(result), nitems1, + bitmap2, 0, + nitems2); + } + + PG_RETURN_ARRAYTYPE_P(result); +} + + +/* + * ARRAY_AGG(anynonarray) aggregate function + */ +Datum +array_agg_transfn(PG_FUNCTION_ARGS) +{ + Oid arg1_typeid = get_fn_expr_argtype(fcinfo->flinfo, 1); + MemoryContext aggcontext; + ArrayBuildState *state; + Datum elem; + + if (arg1_typeid == InvalidOid) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("could not determine input data type"))); + + /* + * Note: we do not need a run-time check about whether arg1_typeid is a + * valid array element type, because the parser would have verified that + * while resolving the input/result types of this polymorphic aggregate. + */ + + if (!AggCheckCallContext(fcinfo, &aggcontext)) + { + /* cannot be called directly because of internal-type argument */ + elog(ERROR, "array_agg_transfn called in non-aggregate context"); + } + + if (PG_ARGISNULL(0)) + state = initArrayResult(arg1_typeid, aggcontext, false); + else + state = (ArrayBuildState *) PG_GETARG_POINTER(0); + + elem = PG_ARGISNULL(1) ? (Datum) 0 : PG_GETARG_DATUM(1); + + state = accumArrayResult(state, + elem, + PG_ARGISNULL(1), + arg1_typeid, + aggcontext); + + /* + * The transition type for array_agg() is declared to be "internal", which + * is a pass-by-value type the same size as a pointer. So we can safely + * pass the ArrayBuildState pointer through nodeAgg.c's machinations. + */ + PG_RETURN_POINTER(state); +} + +Datum +array_agg_combine(PG_FUNCTION_ARGS) +{ + ArrayBuildState *state1; + ArrayBuildState *state2; + MemoryContext agg_context; + MemoryContext old_context; + + if (!AggCheckCallContext(fcinfo, &agg_context)) + elog(ERROR, "aggregate function called in non-aggregate context"); + + state1 = PG_ARGISNULL(0) ? NULL : (ArrayBuildState *) PG_GETARG_POINTER(0); + state2 = PG_ARGISNULL(1) ? NULL : (ArrayBuildState *) PG_GETARG_POINTER(1); + + if (state2 == NULL) + { + /* + * NULL state2 is easy, just return state1, which we know is already + * in the agg_context + */ + if (state1 == NULL) + PG_RETURN_NULL(); + PG_RETURN_POINTER(state1); + } + + if (state1 == NULL) + { + /* We must copy state2's data into the agg_context */ + state1 = initArrayResultWithSize(state2->element_type, agg_context, + false, state2->alen); + + old_context = MemoryContextSwitchTo(agg_context); + + for (int i = 0; i < state2->nelems; i++) + { + if (!state2->dnulls[i]) + state1->dvalues[i] = datumCopy(state2->dvalues[i], + state1->typbyval, + state1->typlen); + else + state1->dvalues[i] = (Datum) 0; + } + + MemoryContextSwitchTo(old_context); + + memcpy(state1->dnulls, state2->dnulls, sizeof(bool) * state2->nelems); + + state1->nelems = state2->nelems; + + PG_RETURN_POINTER(state1); + } + else if (state2->nelems > 0) + { + /* We only need to combine the two states if state2 has any elements */ + int reqsize = state1->nelems + state2->nelems; + MemoryContext oldContext = MemoryContextSwitchTo(state1->mcontext); + + Assert(state1->element_type == state2->element_type); + + /* Enlarge state1 arrays if needed */ + if (state1->alen < reqsize) + { + /* Use a power of 2 size rather than allocating just reqsize */ + state1->alen = pg_nextpower2_32(reqsize); + state1->dvalues = (Datum *) repalloc(state1->dvalues, + state1->alen * sizeof(Datum)); + state1->dnulls = (bool *) repalloc(state1->dnulls, + state1->alen * sizeof(bool)); + } + + /* Copy in the state2 elements to the end of the state1 arrays */ + for (int i = 0; i < state2->nelems; i++) + { + if (!state2->dnulls[i]) + state1->dvalues[i + state1->nelems] = + datumCopy(state2->dvalues[i], + state1->typbyval, + state1->typlen); + else + state1->dvalues[i + state1->nelems] = (Datum) 0; + } + + memcpy(&state1->dnulls[state1->nelems], state2->dnulls, + sizeof(bool) * state2->nelems); + + state1->nelems = reqsize; + + MemoryContextSwitchTo(oldContext); + } + + PG_RETURN_POINTER(state1); +} + +/* + * array_agg_serialize + * Serialize ArrayBuildState into bytea. + */ +Datum +array_agg_serialize(PG_FUNCTION_ARGS) +{ + ArrayBuildState *state; + StringInfoData buf; + bytea *result; + + /* cannot be called directly because of internal-type argument */ + Assert(AggCheckCallContext(fcinfo, NULL)); + + state = (ArrayBuildState *) PG_GETARG_POINTER(0); + + pq_begintypsend(&buf); + + /* + * element_type. Putting this first is more convenient in deserialization + */ + pq_sendint32(&buf, state->element_type); + + /* + * nelems -- send first so we know how large to make the dvalues and + * dnulls array during deserialization. + */ + pq_sendint64(&buf, state->nelems); + + /* alen can be decided during deserialization */ + + /* typlen */ + pq_sendint16(&buf, state->typlen); + + /* typbyval */ + pq_sendbyte(&buf, state->typbyval); + + /* typalign */ + pq_sendbyte(&buf, state->typalign); + + /* dnulls */ + pq_sendbytes(&buf, state->dnulls, sizeof(bool) * state->nelems); + + /* + * dvalues. By agreement with array_agg_deserialize, when the element + * type is byval, we just transmit the Datum array as-is, including any + * null elements. For by-ref types, we must invoke the element type's + * send function, and we skip null elements (which is why the nulls flags + * must be sent first). + */ + if (state->typbyval) + pq_sendbytes(&buf, state->dvalues, sizeof(Datum) * state->nelems); + else + { + SerialIOData *iodata; + int i; + + /* Avoid repeat catalog lookups for typsend function */ + iodata = (SerialIOData *) fcinfo->flinfo->fn_extra; + if (iodata == NULL) + { + Oid typsend; + bool typisvarlena; + + iodata = (SerialIOData *) + MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, + sizeof(SerialIOData)); + getTypeBinaryOutputInfo(state->element_type, &typsend, + &typisvarlena); + fmgr_info_cxt(typsend, &iodata->typsend, + fcinfo->flinfo->fn_mcxt); + fcinfo->flinfo->fn_extra = (void *) iodata; + } + + for (i = 0; i < state->nelems; i++) + { + bytea *outputbytes; + + if (state->dnulls[i]) + continue; + outputbytes = SendFunctionCall(&iodata->typsend, + state->dvalues[i]); + pq_sendint32(&buf, VARSIZE(outputbytes) - VARHDRSZ); + pq_sendbytes(&buf, VARDATA(outputbytes), + VARSIZE(outputbytes) - VARHDRSZ); + } + } + + result = pq_endtypsend(&buf); + + PG_RETURN_BYTEA_P(result); +} + +Datum +array_agg_deserialize(PG_FUNCTION_ARGS) +{ + bytea *sstate; + ArrayBuildState *result; + StringInfoData buf; + Oid element_type; + int64 nelems; + const char *temp; + + if (!AggCheckCallContext(fcinfo, NULL)) + elog(ERROR, "aggregate function called in non-aggregate context"); + + sstate = PG_GETARG_BYTEA_PP(0); + + /* + * Copy the bytea into a StringInfo so that we can "receive" it using the + * standard recv-function infrastructure. + */ + initStringInfo(&buf); + appendBinaryStringInfo(&buf, + VARDATA_ANY(sstate), VARSIZE_ANY_EXHDR(sstate)); + + /* element_type */ + element_type = pq_getmsgint(&buf, 4); + + /* nelems */ + nelems = pq_getmsgint64(&buf); + + /* Create output ArrayBuildState with the needed number of elements */ + result = initArrayResultWithSize(element_type, CurrentMemoryContext, + false, nelems); + result->nelems = nelems; + + /* typlen */ + result->typlen = pq_getmsgint(&buf, 2); + + /* typbyval */ + result->typbyval = pq_getmsgbyte(&buf); + + /* typalign */ + result->typalign = pq_getmsgbyte(&buf); + + /* dnulls */ + temp = pq_getmsgbytes(&buf, sizeof(bool) * nelems); + memcpy(result->dnulls, temp, sizeof(bool) * nelems); + + /* dvalues --- see comment in array_agg_serialize */ + if (result->typbyval) + { + temp = pq_getmsgbytes(&buf, sizeof(Datum) * nelems); + memcpy(result->dvalues, temp, sizeof(Datum) * nelems); + } + else + { + DeserialIOData *iodata; + + /* Avoid repeat catalog lookups for typreceive function */ + iodata = (DeserialIOData *) fcinfo->flinfo->fn_extra; + if (iodata == NULL) + { + Oid typreceive; + + iodata = (DeserialIOData *) + MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, + sizeof(DeserialIOData)); + getTypeBinaryInputInfo(element_type, &typreceive, + &iodata->typioparam); + fmgr_info_cxt(typreceive, &iodata->typreceive, + fcinfo->flinfo->fn_mcxt); + fcinfo->flinfo->fn_extra = (void *) iodata; + } + + for (int i = 0; i < nelems; i++) + { + int itemlen; + StringInfoData elem_buf; + char csave; + + if (result->dnulls[i]) + { + result->dvalues[i] = (Datum) 0; + continue; + } + + itemlen = pq_getmsgint(&buf, 4); + if (itemlen < 0 || itemlen > (buf.len - buf.cursor)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION), + errmsg("insufficient data left in message"))); + + /* + * Rather than copying data around, we just set up a phony + * StringInfo pointing to the correct portion of the input buffer. + * We assume we can scribble on the input buffer so as to maintain + * the convention that StringInfos have a trailing null. + */ + elem_buf.data = &buf.data[buf.cursor]; + elem_buf.maxlen = itemlen + 1; + elem_buf.len = itemlen; + elem_buf.cursor = 0; + + buf.cursor += itemlen; + + csave = buf.data[buf.cursor]; + buf.data[buf.cursor] = '\0'; + + /* Now call the element's receiveproc */ + result->dvalues[i] = ReceiveFunctionCall(&iodata->typreceive, + &elem_buf, + iodata->typioparam, + -1); + + buf.data[buf.cursor] = csave; + } + } + + pq_getmsgend(&buf); + pfree(buf.data); + + PG_RETURN_POINTER(result); +} + +Datum +array_agg_finalfn(PG_FUNCTION_ARGS) +{ + Datum result; + ArrayBuildState *state; + int dims[1]; + int lbs[1]; + + /* cannot be called directly because of internal-type argument */ + Assert(AggCheckCallContext(fcinfo, NULL)); + + state = PG_ARGISNULL(0) ? NULL : (ArrayBuildState *) PG_GETARG_POINTER(0); + + if (state == NULL) + PG_RETURN_NULL(); /* returns null iff no input values */ + + dims[0] = state->nelems; + lbs[0] = 1; + + /* + * Make the result. We cannot release the ArrayBuildState because + * sometimes aggregate final functions are re-executed. Rather, it is + * nodeAgg.c's responsibility to reset the aggcontext when it's safe to do + * so. + */ + result = makeMdArrayResult(state, 1, dims, lbs, + CurrentMemoryContext, + false); + + PG_RETURN_DATUM(result); +} + +/* + * ARRAY_AGG(anyarray) aggregate function + */ +Datum +array_agg_array_transfn(PG_FUNCTION_ARGS) +{ + Oid arg1_typeid = get_fn_expr_argtype(fcinfo->flinfo, 1); + MemoryContext aggcontext; + ArrayBuildStateArr *state; + + if (arg1_typeid == InvalidOid) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("could not determine input data type"))); + + /* + * Note: we do not need a run-time check about whether arg1_typeid is a + * valid array type, because the parser would have verified that while + * resolving the input/result types of this polymorphic aggregate. + */ + + if (!AggCheckCallContext(fcinfo, &aggcontext)) + { + /* cannot be called directly because of internal-type argument */ + elog(ERROR, "array_agg_array_transfn called in non-aggregate context"); + } + + + if (PG_ARGISNULL(0)) + state = initArrayResultArr(arg1_typeid, InvalidOid, aggcontext, false); + else + state = (ArrayBuildStateArr *) PG_GETARG_POINTER(0); + + state = accumArrayResultArr(state, + PG_GETARG_DATUM(1), + PG_ARGISNULL(1), + arg1_typeid, + aggcontext); + + /* + * The transition type for array_agg() is declared to be "internal", which + * is a pass-by-value type the same size as a pointer. So we can safely + * pass the ArrayBuildStateArr pointer through nodeAgg.c's machinations. + */ + PG_RETURN_POINTER(state); +} + +Datum +array_agg_array_combine(PG_FUNCTION_ARGS) +{ + ArrayBuildStateArr *state1; + ArrayBuildStateArr *state2; + MemoryContext agg_context; + MemoryContext old_context; + + if (!AggCheckCallContext(fcinfo, &agg_context)) + elog(ERROR, "aggregate function called in non-aggregate context"); + + state1 = PG_ARGISNULL(0) ? NULL : (ArrayBuildStateArr *) PG_GETARG_POINTER(0); + state2 = PG_ARGISNULL(1) ? NULL : (ArrayBuildStateArr *) PG_GETARG_POINTER(1); + + if (state2 == NULL) + { + /* + * NULL state2 is easy, just return state1, which we know is already + * in the agg_context + */ + if (state1 == NULL) + PG_RETURN_NULL(); + PG_RETURN_POINTER(state1); + } + + if (state1 == NULL) + { + /* We must copy state2's data into the agg_context */ + old_context = MemoryContextSwitchTo(agg_context); + + state1 = initArrayResultArr(state2->array_type, InvalidOid, + agg_context, false); + + state1->abytes = state2->abytes; + state1->data = (char *) palloc(state1->abytes); + + if (state2->nullbitmap) + { + int size = (state2->aitems + 7) / 8; + + state1->nullbitmap = (bits8 *) palloc(size); + memcpy(state1->nullbitmap, state2->nullbitmap, size); + } + + memcpy(state1->data, state2->data, state2->nbytes); + state1->nbytes = state2->nbytes; + state1->aitems = state2->aitems; + state1->nitems = state2->nitems; + state1->ndims = state2->ndims; + memcpy(state1->dims, state2->dims, sizeof(state2->dims)); + memcpy(state1->lbs, state2->lbs, sizeof(state2->lbs)); + state1->array_type = state2->array_type; + state1->element_type = state2->element_type; + + MemoryContextSwitchTo(old_context); + + PG_RETURN_POINTER(state1); + } + + /* We only need to combine the two states if state2 has any items */ + else if (state2->nitems > 0) + { + MemoryContext oldContext; + int reqsize = state1->nbytes + state2->nbytes; + int i; + + /* + * Check the states are compatible with each other. Ensure we use the + * same error messages that are listed in accumArrayResultArr so that + * the same error is shown as would have been if we'd not used the + * combine function for the aggregation. + */ + if (state1->ndims != state2->ndims) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("cannot accumulate arrays of different dimensionality"))); + + /* Check dimensions match ignoring the first dimension. */ + for (i = 1; i < state1->ndims; i++) + { + if (state1->dims[i] != state2->dims[i] || state1->lbs[i] != state2->lbs[i]) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("cannot accumulate arrays of different dimensionality"))); + } + + + oldContext = MemoryContextSwitchTo(state1->mcontext); + + /* + * If there's not enough space in state1 then we'll need to reallocate + * more. + */ + if (state1->abytes < reqsize) + { + /* use a power of 2 size rather than allocating just reqsize */ + state1->abytes = pg_nextpower2_32(reqsize); + state1->data = (char *) repalloc(state1->data, state1->abytes); + } + + if (state2->nullbitmap) + { + int newnitems = state1->nitems + state2->nitems; + + if (state1->nullbitmap == NULL) + { + /* + * First input with nulls; we must retrospectively handle any + * previous inputs by marking all their items non-null. + */ + state1->aitems = pg_nextpower2_32(Max(256, newnitems + 1)); + state1->nullbitmap = (bits8 *) palloc((state1->aitems + 7) / 8); + array_bitmap_copy(state1->nullbitmap, 0, + NULL, 0, + state1->nitems); + } + else if (newnitems > state1->aitems) + { + int newaitems = state1->aitems + state2->aitems; + + state1->aitems = pg_nextpower2_32(newaitems); + state1->nullbitmap = (bits8 *) + repalloc(state1->nullbitmap, (state1->aitems + 7) / 8); + } + array_bitmap_copy(state1->nullbitmap, state1->nitems, + state2->nullbitmap, 0, + state2->nitems); + } + + memcpy(state1->data + state1->nbytes, state2->data, state2->nbytes); + state1->nbytes += state2->nbytes; + state1->nitems += state2->nitems; + + state1->dims[0] += state2->dims[0]; + /* remaining dims already match, per test above */ + + Assert(state1->array_type == state2->array_type); + Assert(state1->element_type == state2->element_type); + + MemoryContextSwitchTo(oldContext); + } + + PG_RETURN_POINTER(state1); +} + +/* + * array_agg_array_serialize + * Serialize ArrayBuildStateArr into bytea. + */ +Datum +array_agg_array_serialize(PG_FUNCTION_ARGS) +{ + ArrayBuildStateArr *state; + StringInfoData buf; + bytea *result; + + /* cannot be called directly because of internal-type argument */ + Assert(AggCheckCallContext(fcinfo, NULL)); + + state = (ArrayBuildStateArr *) PG_GETARG_POINTER(0); + + pq_begintypsend(&buf); + + /* + * element_type. Putting this first is more convenient in deserialization + * so that we can init the new state sooner. + */ + pq_sendint32(&buf, state->element_type); + + /* array_type */ + pq_sendint32(&buf, state->array_type); + + /* nbytes */ + pq_sendint32(&buf, state->nbytes); + + /* data */ + pq_sendbytes(&buf, state->data, state->nbytes); + + /* abytes */ + pq_sendint32(&buf, state->abytes); + + /* aitems */ + pq_sendint32(&buf, state->aitems); + + /* nullbitmap */ + if (state->nullbitmap) + { + Assert(state->aitems > 0); + pq_sendbytes(&buf, state->nullbitmap, (state->aitems + 7) / 8); + } + + /* nitems */ + pq_sendint32(&buf, state->nitems); + + /* ndims */ + pq_sendint32(&buf, state->ndims); + + /* dims: XXX should we just send ndims elements? */ + pq_sendbytes(&buf, state->dims, sizeof(state->dims)); + + /* lbs */ + pq_sendbytes(&buf, state->lbs, sizeof(state->lbs)); + + result = pq_endtypsend(&buf); + + PG_RETURN_BYTEA_P(result); +} + +Datum +array_agg_array_deserialize(PG_FUNCTION_ARGS) +{ + bytea *sstate; + ArrayBuildStateArr *result; + StringInfoData buf; + Oid element_type; + Oid array_type; + int nbytes; + const char *temp; + + /* cannot be called directly because of internal-type argument */ + Assert(AggCheckCallContext(fcinfo, NULL)); + + sstate = PG_GETARG_BYTEA_PP(0); + + /* + * Copy the bytea into a StringInfo so that we can "receive" it using the + * standard recv-function infrastructure. + */ + initStringInfo(&buf); + appendBinaryStringInfo(&buf, + VARDATA_ANY(sstate), VARSIZE_ANY_EXHDR(sstate)); + + /* element_type */ + element_type = pq_getmsgint(&buf, 4); + + /* array_type */ + array_type = pq_getmsgint(&buf, 4); + + /* nbytes */ + nbytes = pq_getmsgint(&buf, 4); + + result = initArrayResultArr(array_type, element_type, + CurrentMemoryContext, false); + + result->abytes = 1024; + while (result->abytes < nbytes) + result->abytes *= 2; + + result->data = (char *) palloc(result->abytes); + + /* data */ + temp = pq_getmsgbytes(&buf, nbytes); + memcpy(result->data, temp, nbytes); + result->nbytes = nbytes; + + /* abytes */ + result->abytes = pq_getmsgint(&buf, 4); + + /* aitems: might be 0 */ + result->aitems = pq_getmsgint(&buf, 4); + + /* nullbitmap */ + if (result->aitems > 0) + { + int size = (result->aitems + 7) / 8; + + result->nullbitmap = (bits8 *) palloc(size); + temp = pq_getmsgbytes(&buf, size); + memcpy(result->nullbitmap, temp, size); + } + else + result->nullbitmap = NULL; + + /* nitems */ + result->nitems = pq_getmsgint(&buf, 4); + + /* ndims */ + result->ndims = pq_getmsgint(&buf, 4); + + /* dims */ + temp = pq_getmsgbytes(&buf, sizeof(result->dims)); + memcpy(result->dims, temp, sizeof(result->dims)); + + /* lbs */ + temp = pq_getmsgbytes(&buf, sizeof(result->lbs)); + memcpy(result->lbs, temp, sizeof(result->lbs)); + + pq_getmsgend(&buf); + pfree(buf.data); + + PG_RETURN_POINTER(result); +} + +Datum +array_agg_array_finalfn(PG_FUNCTION_ARGS) +{ + Datum result; + ArrayBuildStateArr *state; + + /* cannot be called directly because of internal-type argument */ + Assert(AggCheckCallContext(fcinfo, NULL)); + + state = PG_ARGISNULL(0) ? NULL : (ArrayBuildStateArr *) PG_GETARG_POINTER(0); + + if (state == NULL) + PG_RETURN_NULL(); /* returns null iff no input values */ + + /* + * Make the result. We cannot release the ArrayBuildStateArr because + * sometimes aggregate final functions are re-executed. Rather, it is + * nodeAgg.c's responsibility to reset the aggcontext when it's safe to do + * so. + */ + result = makeArrayResultArr(state, CurrentMemoryContext, false); + + PG_RETURN_DATUM(result); +} + +/*----------------------------------------------------------------------------- + * array_position, array_position_start : + * return the offset of a value in an array. + * + * IS NOT DISTINCT FROM semantics are used for comparisons. Return NULL when + * the value is not found. + *----------------------------------------------------------------------------- + */ +Datum +array_position(PG_FUNCTION_ARGS) +{ + return array_position_common(fcinfo); +} + +Datum +array_position_start(PG_FUNCTION_ARGS) +{ + return array_position_common(fcinfo); +} + +/* + * array_position_common + * Common code for array_position and array_position_start + * + * These are separate wrappers for the sake of opr_sanity regression test. + * They are not strict so we have to test for null inputs explicitly. + */ +static Datum +array_position_common(FunctionCallInfo fcinfo) +{ + ArrayType *array; + Oid collation = PG_GET_COLLATION(); + Oid element_type; + Datum searched_element, + value; + bool isnull; + int position, + position_min; + bool found = false; + TypeCacheEntry *typentry; + ArrayMetaState *my_extra; + bool null_search; + ArrayIterator array_iterator; + + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + + array = PG_GETARG_ARRAYTYPE_P(0); + + /* + * We refuse to search for elements in multi-dimensional arrays, since we + * have no good way to report the element's location in the array. + */ + if (ARR_NDIM(array) > 1) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("searching for elements in multidimensional arrays is not supported"))); + + /* Searching in an empty array is well-defined, though: it always fails */ + if (ARR_NDIM(array) < 1) + PG_RETURN_NULL(); + + if (PG_ARGISNULL(1)) + { + /* fast return when the array doesn't have nulls */ + if (!array_contains_nulls(array)) + PG_RETURN_NULL(); + searched_element = (Datum) 0; + null_search = true; + } + else + { + searched_element = PG_GETARG_DATUM(1); + null_search = false; + } + + element_type = ARR_ELEMTYPE(array); + position = (ARR_LBOUND(array))[0] - 1; + + /* figure out where to start */ + if (PG_NARGS() == 3) + { + if (PG_ARGISNULL(2)) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("initial position must not be null"))); + + position_min = PG_GETARG_INT32(2); + } + else + position_min = (ARR_LBOUND(array))[0]; + + /* + * We arrange to look up type info for array_create_iterator only once per + * series of calls, assuming the element type doesn't change underneath + * us. + */ + my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra; + if (my_extra == NULL) + { + fcinfo->flinfo->fn_extra = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, + sizeof(ArrayMetaState)); + my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra; + my_extra->element_type = ~element_type; + } + + if (my_extra->element_type != element_type) + { + get_typlenbyvalalign(element_type, + &my_extra->typlen, + &my_extra->typbyval, + &my_extra->typalign); + + typentry = lookup_type_cache(element_type, TYPECACHE_EQ_OPR_FINFO); + + if (!OidIsValid(typentry->eq_opr_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify an equality operator for type %s", + format_type_be(element_type)))); + + my_extra->element_type = element_type; + fmgr_info_cxt(typentry->eq_opr_finfo.fn_oid, &my_extra->proc, + fcinfo->flinfo->fn_mcxt); + } + + /* Examine each array element until we find a match. */ + array_iterator = array_create_iterator(array, 0, my_extra); + while (array_iterate(array_iterator, &value, &isnull)) + { + position++; + + /* skip initial elements if caller requested so */ + if (position < position_min) + continue; + + /* + * Can't look at the array element's value if it's null; but if we + * search for null, we have a hit and are done. + */ + if (isnull || null_search) + { + if (isnull && null_search) + { + found = true; + break; + } + else + continue; + } + + /* not nulls, so run the operator */ + if (DatumGetBool(FunctionCall2Coll(&my_extra->proc, collation, + searched_element, value))) + { + found = true; + break; + } + } + + array_free_iterator(array_iterator); + + /* Avoid leaking memory when handed toasted input */ + PG_FREE_IF_COPY(array, 0); + + if (!found) + PG_RETURN_NULL(); + + PG_RETURN_INT32(position); +} + +/*----------------------------------------------------------------------------- + * array_positions : + * return an array of positions of a value in an array. + * + * IS NOT DISTINCT FROM semantics are used for comparisons. Returns NULL when + * the input array is NULL. When the value is not found in the array, returns + * an empty array. + * + * This is not strict so we have to test for null inputs explicitly. + *----------------------------------------------------------------------------- + */ +Datum +array_positions(PG_FUNCTION_ARGS) +{ + ArrayType *array; + Oid collation = PG_GET_COLLATION(); + Oid element_type; + Datum searched_element, + value; + bool isnull; + int position; + TypeCacheEntry *typentry; + ArrayMetaState *my_extra; + bool null_search; + ArrayIterator array_iterator; + ArrayBuildState *astate = NULL; + + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + + array = PG_GETARG_ARRAYTYPE_P(0); + + /* + * We refuse to search for elements in multi-dimensional arrays, since we + * have no good way to report the element's location in the array. + */ + if (ARR_NDIM(array) > 1) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("searching for elements in multidimensional arrays is not supported"))); + + astate = initArrayResult(INT4OID, CurrentMemoryContext, false); + + /* Searching in an empty array is well-defined, though: it always fails */ + if (ARR_NDIM(array) < 1) + PG_RETURN_DATUM(makeArrayResult(astate, CurrentMemoryContext)); + + if (PG_ARGISNULL(1)) + { + /* fast return when the array doesn't have nulls */ + if (!array_contains_nulls(array)) + PG_RETURN_DATUM(makeArrayResult(astate, CurrentMemoryContext)); + searched_element = (Datum) 0; + null_search = true; + } + else + { + searched_element = PG_GETARG_DATUM(1); + null_search = false; + } + + element_type = ARR_ELEMTYPE(array); + position = (ARR_LBOUND(array))[0] - 1; + + /* + * We arrange to look up type info for array_create_iterator only once per + * series of calls, assuming the element type doesn't change underneath + * us. + */ + my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra; + if (my_extra == NULL) + { + fcinfo->flinfo->fn_extra = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, + sizeof(ArrayMetaState)); + my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra; + my_extra->element_type = ~element_type; + } + + if (my_extra->element_type != element_type) + { + get_typlenbyvalalign(element_type, + &my_extra->typlen, + &my_extra->typbyval, + &my_extra->typalign); + + typentry = lookup_type_cache(element_type, TYPECACHE_EQ_OPR_FINFO); + + if (!OidIsValid(typentry->eq_opr_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify an equality operator for type %s", + format_type_be(element_type)))); + + my_extra->element_type = element_type; + fmgr_info_cxt(typentry->eq_opr_finfo.fn_oid, &my_extra->proc, + fcinfo->flinfo->fn_mcxt); + } + + /* + * Accumulate each array position iff the element matches the given + * element. + */ + array_iterator = array_create_iterator(array, 0, my_extra); + while (array_iterate(array_iterator, &value, &isnull)) + { + position += 1; + + /* + * Can't look at the array element's value if it's null; but if we + * search for null, we have a hit. + */ + if (isnull || null_search) + { + if (isnull && null_search) + astate = + accumArrayResult(astate, Int32GetDatum(position), false, + INT4OID, CurrentMemoryContext); + + continue; + } + + /* not nulls, so run the operator */ + if (DatumGetBool(FunctionCall2Coll(&my_extra->proc, collation, + searched_element, value))) + astate = + accumArrayResult(astate, Int32GetDatum(position), false, + INT4OID, CurrentMemoryContext); + } + + array_free_iterator(array_iterator); + + /* Avoid leaking memory when handed toasted input */ + PG_FREE_IF_COPY(array, 0); + + PG_RETURN_DATUM(makeArrayResult(astate, CurrentMemoryContext)); +} + +/* + * array_shuffle_n + * Return a copy of array with n randomly chosen items. + * + * The number of items must not exceed the size of the first dimension of the + * array. We preserve the first dimension's lower bound if keep_lb, + * else it's set to 1. Lower-order dimensions are preserved in any case. + * + * NOTE: it would be cleaner to look up the elmlen/elmbval/elmalign info + * from the system catalogs, given only the elmtyp. However, the caller is + * in a better position to cache this info across multiple calls. + */ +static ArrayType * +array_shuffle_n(ArrayType *array, int n, bool keep_lb, + Oid elmtyp, TypeCacheEntry *typentry) +{ + ArrayType *result; + int ndim, + *dims, + *lbs, + nelm, + nitem, + rdims[MAXDIM], + rlbs[MAXDIM]; + int16 elmlen; + bool elmbyval; + char elmalign; + Datum *elms, + *ielms; + bool *nuls, + *inuls; + + ndim = ARR_NDIM(array); + dims = ARR_DIMS(array); + lbs = ARR_LBOUND(array); + + elmlen = typentry->typlen; + elmbyval = typentry->typbyval; + elmalign = typentry->typalign; + + /* If the target array is empty, exit fast */ + if (ndim < 1 || dims[0] < 1 || n < 1) + return construct_empty_array(elmtyp); + + deconstruct_array(array, elmtyp, elmlen, elmbyval, elmalign, + &elms, &nuls, &nelm); + + nitem = dims[0]; /* total number of items */ + nelm /= nitem; /* number of elements per item */ + + Assert(n <= nitem); /* else it's caller error */ + + /* + * Shuffle array using Fisher-Yates algorithm. Scan the array and swap + * current item (nelm datums starting at ielms) with a randomly chosen + * later item (nelm datums starting at jelms) in each iteration. We can + * stop once we've done n iterations; then first n items are the result. + */ + ielms = elms; + inuls = nuls; + for (int i = 0; i < n; i++) + { + int j = (int) pg_prng_uint64_range(&pg_global_prng_state, i, nitem - 1) * nelm; + Datum *jelms = elms + j; + bool *jnuls = nuls + j; + + /* Swap i'th and j'th items; advance ielms/inuls to next item */ + for (int k = 0; k < nelm; k++) + { + Datum elm = *ielms; + bool nul = *inuls; + + *ielms++ = *jelms; + *inuls++ = *jnuls; + *jelms++ = elm; + *jnuls++ = nul; + } + } + + /* Set up dimensions of the result */ + memcpy(rdims, dims, ndim * sizeof(int)); + memcpy(rlbs, lbs, ndim * sizeof(int)); + rdims[0] = n; + if (!keep_lb) + rlbs[0] = 1; + + result = construct_md_array(elms, nuls, ndim, rdims, rlbs, + elmtyp, elmlen, elmbyval, elmalign); + + pfree(elms); + pfree(nuls); + + return result; +} + +/* + * array_shuffle + * + * Returns an array with the same dimensions as the input array, with its + * first-dimension elements in random order. + */ +Datum +array_shuffle(PG_FUNCTION_ARGS) +{ + ArrayType *array = PG_GETARG_ARRAYTYPE_P(0); + ArrayType *result; + Oid elmtyp; + TypeCacheEntry *typentry; + + /* + * There is no point in shuffling empty arrays or arrays with less than + * two items. + */ + if (ARR_NDIM(array) < 1 || ARR_DIMS(array)[0] < 2) + PG_RETURN_ARRAYTYPE_P(array); + + elmtyp = ARR_ELEMTYPE(array); + typentry = (TypeCacheEntry *) fcinfo->flinfo->fn_extra; + if (typentry == NULL || typentry->type_id != elmtyp) + { + typentry = lookup_type_cache(elmtyp, 0); + fcinfo->flinfo->fn_extra = (void *) typentry; + } + + result = array_shuffle_n(array, ARR_DIMS(array)[0], true, elmtyp, typentry); + + PG_RETURN_ARRAYTYPE_P(result); +} + +/* + * array_sample + * + * Returns an array of n randomly chosen first-dimension elements + * from the input array. + */ +Datum +array_sample(PG_FUNCTION_ARGS) +{ + ArrayType *array = PG_GETARG_ARRAYTYPE_P(0); + int n = PG_GETARG_INT32(1); + ArrayType *result; + Oid elmtyp; + TypeCacheEntry *typentry; + int nitem; + + nitem = (ARR_NDIM(array) < 1) ? 0 : ARR_DIMS(array)[0]; + + if (n < 0 || n > nitem) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("sample size must be between 0 and %d", nitem))); + + elmtyp = ARR_ELEMTYPE(array); + typentry = (TypeCacheEntry *) fcinfo->flinfo->fn_extra; + if (typentry == NULL || typentry->type_id != elmtyp) + { + typentry = lookup_type_cache(elmtyp, 0); + fcinfo->flinfo->fn_extra = (void *) typentry; + } + + result = array_shuffle_n(array, n, false, elmtyp, typentry); + + PG_RETURN_ARRAYTYPE_P(result); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/arrayfuncs.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/arrayfuncs.c new file mode 100644 index 00000000000..807030da997 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/arrayfuncs.c @@ -0,0 +1,6961 @@ +/*------------------------------------------------------------------------- + * + * arrayfuncs.c + * Support functions for arrays. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/adt/arrayfuncs.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <ctype.h> +#include <math.h> + +#include "access/htup_details.h" +#include "catalog/pg_type.h" +#include "common/int.h" +#include "funcapi.h" +#include "libpq/pqformat.h" +#include "nodes/nodeFuncs.h" +#include "nodes/supportnodes.h" +#include "optimizer/optimizer.h" +#include "port/pg_bitutils.h" +#include "utils/array.h" +#include "utils/arrayaccess.h" +#include "utils/builtins.h" +#include "utils/datum.h" +#include "utils/fmgroids.h" +#include "utils/lsyscache.h" +#include "utils/memutils.h" +#include "utils/selfuncs.h" +#include "utils/typcache.h" + + +/* + * GUC parameter + */ +__thread bool Array_nulls = true; + +/* + * Local definitions + */ +#define ASSGN "=" + +#define AARR_FREE_IF_COPY(array,n) \ + do { \ + if (!VARATT_IS_EXPANDED_HEADER(array)) \ + PG_FREE_IF_COPY(array, n); \ + } while (0) + +typedef enum +{ + ARRAY_NO_LEVEL, + ARRAY_LEVEL_STARTED, + ARRAY_ELEM_STARTED, + ARRAY_ELEM_COMPLETED, + ARRAY_QUOTED_ELEM_STARTED, + ARRAY_QUOTED_ELEM_COMPLETED, + ARRAY_ELEM_DELIMITED, + ARRAY_LEVEL_COMPLETED, + ARRAY_LEVEL_DELIMITED +} ArrayParseState; + +/* Working state for array_iterate() */ +typedef struct ArrayIteratorData +{ + /* basic info about the array, set up during array_create_iterator() */ + ArrayType *arr; /* array we're iterating through */ + bits8 *nullbitmap; /* its null bitmap, if any */ + int nitems; /* total number of elements in array */ + int16 typlen; /* element type's length */ + bool typbyval; /* element type's byval property */ + char typalign; /* element type's align property */ + + /* information about the requested slice size */ + int slice_ndim; /* slice dimension, or 0 if not slicing */ + int slice_len; /* number of elements per slice */ + int *slice_dims; /* slice dims array */ + int *slice_lbound; /* slice lbound array */ + Datum *slice_values; /* workspace of length slice_len */ + bool *slice_nulls; /* workspace of length slice_len */ + + /* current position information, updated on each iteration */ + char *data_ptr; /* our current position in the array */ + int current_item; /* the item # we're at in the array */ +} ArrayIteratorData; + +static bool array_isspace(char ch); +static int ArrayCount(const char *str, int *dim, char typdelim, + Node *escontext); +static bool ReadArrayStr(char *arrayStr, const char *origStr, + int nitems, int ndim, int *dim, + FmgrInfo *inputproc, Oid typioparam, int32 typmod, + char typdelim, + int typlen, bool typbyval, char typalign, + Datum *values, bool *nulls, + bool *hasnulls, int32 *nbytes, Node *escontext); +static void ReadArrayBinary(StringInfo buf, int nitems, + FmgrInfo *receiveproc, Oid typioparam, int32 typmod, + int typlen, bool typbyval, char typalign, + Datum *values, bool *nulls, + bool *hasnulls, int32 *nbytes); +static Datum array_get_element_expanded(Datum arraydatum, + int nSubscripts, int *indx, + int arraytyplen, + int elmlen, bool elmbyval, char elmalign, + bool *isNull); +static Datum array_set_element_expanded(Datum arraydatum, + int nSubscripts, int *indx, + Datum dataValue, bool isNull, + int arraytyplen, + int elmlen, bool elmbyval, char elmalign); +static bool array_get_isnull(const bits8 *nullbitmap, int offset); +static void array_set_isnull(bits8 *nullbitmap, int offset, bool isNull); +static Datum ArrayCast(char *value, bool byval, int len); +static int ArrayCastAndSet(Datum src, + int typlen, bool typbyval, char typalign, + char *dest); +static char *array_seek(char *ptr, int offset, bits8 *nullbitmap, int nitems, + int typlen, bool typbyval, char typalign); +static int array_nelems_size(char *ptr, int offset, bits8 *nullbitmap, + int nitems, int typlen, bool typbyval, char typalign); +static int array_copy(char *destptr, int nitems, + char *srcptr, int offset, bits8 *nullbitmap, + int typlen, bool typbyval, char typalign); +static int array_slice_size(char *arraydataptr, bits8 *arraynullsptr, + int ndim, int *dim, int *lb, + int *st, int *endp, + int typlen, bool typbyval, char typalign); +static void array_extract_slice(ArrayType *newarray, + int ndim, int *dim, int *lb, + char *arraydataptr, bits8 *arraynullsptr, + int *st, int *endp, + int typlen, bool typbyval, char typalign); +static void array_insert_slice(ArrayType *destArray, ArrayType *origArray, + ArrayType *srcArray, + int ndim, int *dim, int *lb, + int *st, int *endp, + int typlen, bool typbyval, char typalign); +static int array_cmp(FunctionCallInfo fcinfo); +static ArrayType *create_array_envelope(int ndims, int *dimv, int *lbsv, int nbytes, + Oid elmtype, int dataoffset); +static ArrayType *array_fill_internal(ArrayType *dims, ArrayType *lbs, + Datum value, bool isnull, Oid elmtype, + FunctionCallInfo fcinfo); +static ArrayType *array_replace_internal(ArrayType *array, + Datum search, bool search_isnull, + Datum replace, bool replace_isnull, + bool remove, Oid collation, + FunctionCallInfo fcinfo); +static int width_bucket_array_float8(Datum operand, ArrayType *thresholds); +static int width_bucket_array_fixed(Datum operand, + ArrayType *thresholds, + Oid collation, + TypeCacheEntry *typentry); +static int width_bucket_array_variable(Datum operand, + ArrayType *thresholds, + Oid collation, + TypeCacheEntry *typentry); + + +/* + * array_in : + * converts an array from the external format in "string" to + * its internal format. + * + * return value : + * the internal representation of the input array + */ +Datum +array_in(PG_FUNCTION_ARGS) +{ + char *string = PG_GETARG_CSTRING(0); /* external form */ + Oid element_type = PG_GETARG_OID(1); /* type of an array + * element */ + int32 typmod = PG_GETARG_INT32(2); /* typmod for array elements */ + Node *escontext = fcinfo->context; + int typlen; + bool typbyval; + char typalign; + char typdelim; + Oid typioparam; + char *string_save, + *p; + int i, + nitems; + Datum *dataPtr; + bool *nullsPtr; + bool hasnulls; + int32 nbytes; + int32 dataoffset; + ArrayType *retval; + int ndim, + dim[MAXDIM], + lBound[MAXDIM]; + ArrayMetaState *my_extra; + + /* + * We arrange to look up info about element type, including its input + * conversion proc, only once per series of calls, assuming the element + * type doesn't change underneath us. + */ + my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra; + if (my_extra == NULL) + { + fcinfo->flinfo->fn_extra = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, + sizeof(ArrayMetaState)); + my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra; + my_extra->element_type = ~element_type; + } + + if (my_extra->element_type != element_type) + { + /* + * Get info about element type, including its input conversion proc + */ + get_type_io_data(element_type, IOFunc_input, + &my_extra->typlen, &my_extra->typbyval, + &my_extra->typalign, &my_extra->typdelim, + &my_extra->typioparam, &my_extra->typiofunc); + fmgr_info_cxt(my_extra->typiofunc, &my_extra->proc, + fcinfo->flinfo->fn_mcxt); + my_extra->element_type = element_type; + } + typlen = my_extra->typlen; + typbyval = my_extra->typbyval; + typalign = my_extra->typalign; + typdelim = my_extra->typdelim; + typioparam = my_extra->typioparam; + + /* Make a modifiable copy of the input */ + string_save = pstrdup(string); + + /* + * If the input string starts with dimension info, read and use that. + * Otherwise, we require the input to be in curly-brace style, and we + * prescan the input to determine dimensions. + * + * Dimension info takes the form of one or more [n] or [m:n] items. The + * outer loop iterates once per dimension item. + */ + p = string_save; + ndim = 0; + for (;;) + { + char *q; + int ub; + + /* + * Note: we currently allow whitespace between, but not within, + * dimension items. + */ + while (array_isspace(*p)) + p++; + if (*p != '[') + break; /* no more dimension items */ + p++; + if (ndim >= MAXDIM) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)", + ndim + 1, MAXDIM))); + + for (q = p; isdigit((unsigned char) *q) || (*q == '-') || (*q == '+'); q++) + /* skip */ ; + if (q == p) /* no digits? */ + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed array literal: \"%s\"", string), + errdetail("\"[\" must introduce explicitly-specified array dimensions."))); + + if (*q == ':') + { + /* [m:n] format */ + *q = '\0'; + lBound[ndim] = atoi(p); + p = q + 1; + for (q = p; isdigit((unsigned char) *q) || (*q == '-') || (*q == '+'); q++) + /* skip */ ; + if (q == p) /* no digits? */ + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed array literal: \"%s\"", string), + errdetail("Missing array dimension value."))); + } + else + { + /* [n] format */ + lBound[ndim] = 1; + } + if (*q != ']') + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed array literal: \"%s\"", string), + errdetail("Missing \"%s\" after array dimensions.", + "]"))); + + *q = '\0'; + ub = atoi(p); + p = q + 1; + if (ub < lBound[ndim]) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("upper bound cannot be less than lower bound"))); + + dim[ndim] = ub - lBound[ndim] + 1; + ndim++; + } + + if (ndim == 0) + { + /* No array dimensions, so intuit dimensions from brace structure */ + if (*p != '{') + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed array literal: \"%s\"", string), + errdetail("Array value must start with \"{\" or dimension information."))); + ndim = ArrayCount(p, dim, typdelim, escontext); + if (ndim < 0) + PG_RETURN_NULL(); + for (i = 0; i < ndim; i++) + lBound[i] = 1; + } + else + { + int ndim_braces, + dim_braces[MAXDIM]; + + /* If array dimensions are given, expect '=' operator */ + if (strncmp(p, ASSGN, strlen(ASSGN)) != 0) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed array literal: \"%s\"", string), + errdetail("Missing \"%s\" after array dimensions.", + ASSGN))); + p += strlen(ASSGN); + while (array_isspace(*p)) + p++; + + /* + * intuit dimensions from brace structure -- it better match what we + * were given + */ + if (*p != '{') + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed array literal: \"%s\"", string), + errdetail("Array contents must start with \"{\"."))); + ndim_braces = ArrayCount(p, dim_braces, typdelim, escontext); + if (ndim_braces < 0) + PG_RETURN_NULL(); + if (ndim_braces != ndim) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed array literal: \"%s\"", string), + errdetail("Specified array dimensions do not match array contents."))); + for (i = 0; i < ndim; ++i) + { + if (dim[i] != dim_braces[i]) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed array literal: \"%s\"", string), + errdetail("Specified array dimensions do not match array contents."))); + } + } + +#ifdef ARRAYDEBUG + printf("array_in- ndim %d (", ndim); + for (i = 0; i < ndim; i++) + { + printf(" %d", dim[i]); + }; + printf(") for %s\n", string); +#endif + + /* This checks for overflow of the array dimensions */ + nitems = ArrayGetNItemsSafe(ndim, dim, escontext); + if (nitems < 0) + PG_RETURN_NULL(); + if (!ArrayCheckBoundsSafe(ndim, dim, lBound, escontext)) + PG_RETURN_NULL(); + + /* Empty array? */ + if (nitems == 0) + PG_RETURN_ARRAYTYPE_P(construct_empty_array(element_type)); + + dataPtr = (Datum *) palloc(nitems * sizeof(Datum)); + nullsPtr = (bool *) palloc(nitems * sizeof(bool)); + if (!ReadArrayStr(p, string, + nitems, ndim, dim, + &my_extra->proc, typioparam, typmod, + typdelim, + typlen, typbyval, typalign, + dataPtr, nullsPtr, + &hasnulls, &nbytes, escontext)) + PG_RETURN_NULL(); + if (hasnulls) + { + dataoffset = ARR_OVERHEAD_WITHNULLS(ndim, nitems); + nbytes += dataoffset; + } + else + { + dataoffset = 0; /* marker for no null bitmap */ + nbytes += ARR_OVERHEAD_NONULLS(ndim); + } + retval = (ArrayType *) palloc0(nbytes); + SET_VARSIZE(retval, nbytes); + retval->ndim = ndim; + retval->dataoffset = dataoffset; + + /* + * This comes from the array's pg_type.typelem (which points to the base + * data type's pg_type.oid) and stores system oids in user tables. This + * oid must be preserved by binary upgrades. + */ + retval->elemtype = element_type; + memcpy(ARR_DIMS(retval), dim, ndim * sizeof(int)); + memcpy(ARR_LBOUND(retval), lBound, ndim * sizeof(int)); + + CopyArrayEls(retval, + dataPtr, nullsPtr, nitems, + typlen, typbyval, typalign, + true); + + pfree(dataPtr); + pfree(nullsPtr); + pfree(string_save); + + PG_RETURN_ARRAYTYPE_P(retval); +} + +/* + * array_isspace() --- a non-locale-dependent isspace() + * + * We used to use isspace() for parsing array values, but that has + * undesirable results: an array value might be silently interpreted + * differently depending on the locale setting. Now we just hard-wire + * the traditional ASCII definition of isspace(). + */ +static bool +array_isspace(char ch) +{ + if (ch == ' ' || + ch == '\t' || + ch == '\n' || + ch == '\r' || + ch == '\v' || + ch == '\f') + return true; + return false; +} + +/* + * ArrayCount + * Determines the dimensions for an array string. + * + * Returns number of dimensions as function result. The axis lengths are + * returned in dim[], which must be of size MAXDIM. + * + * If we detect an error, fill *escontext with error details and return -1 + * (unless escontext isn't provided, in which case errors will be thrown). + */ +static int +ArrayCount(const char *str, int *dim, char typdelim, Node *escontext) +{ + int nest_level = 0, + i; + int ndim = 1, + temp[MAXDIM], + nelems[MAXDIM], + nelems_last[MAXDIM]; + bool in_quotes = false; + bool eoArray = false; + bool empty_array = true; + const char *ptr; + ArrayParseState parse_state = ARRAY_NO_LEVEL; + + for (i = 0; i < MAXDIM; ++i) + { + temp[i] = dim[i] = nelems_last[i] = 0; + nelems[i] = 1; + } + + ptr = str; + while (!eoArray) + { + bool itemdone = false; + + while (!itemdone) + { + if (parse_state == ARRAY_ELEM_STARTED || + parse_state == ARRAY_QUOTED_ELEM_STARTED) + empty_array = false; + + switch (*ptr) + { + case '\0': + /* Signal a premature end of the string */ + ereturn(escontext, -1, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed array literal: \"%s\"", str), + errdetail("Unexpected end of input."))); + case '\\': + + /* + * An escape must be after a level start, after an element + * start, or after an element delimiter. In any case we + * now must be past an element start. + */ + if (parse_state != ARRAY_LEVEL_STARTED && + parse_state != ARRAY_ELEM_STARTED && + parse_state != ARRAY_QUOTED_ELEM_STARTED && + parse_state != ARRAY_ELEM_DELIMITED) + ereturn(escontext, -1, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed array literal: \"%s\"", str), + errdetail("Unexpected \"%c\" character.", + '\\'))); + if (parse_state != ARRAY_QUOTED_ELEM_STARTED) + parse_state = ARRAY_ELEM_STARTED; + /* skip the escaped character */ + if (*(ptr + 1)) + ptr++; + else + ereturn(escontext, -1, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed array literal: \"%s\"", str), + errdetail("Unexpected end of input."))); + break; + case '"': + + /* + * A quote must be after a level start, after a quoted + * element start, or after an element delimiter. In any + * case we now must be past an element start. + */ + if (parse_state != ARRAY_LEVEL_STARTED && + parse_state != ARRAY_QUOTED_ELEM_STARTED && + parse_state != ARRAY_ELEM_DELIMITED) + ereturn(escontext, -1, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed array literal: \"%s\"", str), + errdetail("Unexpected array element."))); + in_quotes = !in_quotes; + if (in_quotes) + parse_state = ARRAY_QUOTED_ELEM_STARTED; + else + parse_state = ARRAY_QUOTED_ELEM_COMPLETED; + break; + case '{': + if (!in_quotes) + { + /* + * A left brace can occur if no nesting has occurred + * yet, after a level start, or after a level + * delimiter. + */ + if (parse_state != ARRAY_NO_LEVEL && + parse_state != ARRAY_LEVEL_STARTED && + parse_state != ARRAY_LEVEL_DELIMITED) + ereturn(escontext, -1, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed array literal: \"%s\"", str), + errdetail("Unexpected \"%c\" character.", + '{'))); + parse_state = ARRAY_LEVEL_STARTED; + if (nest_level >= MAXDIM) + ereturn(escontext, -1, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)", + nest_level + 1, MAXDIM))); + temp[nest_level] = 0; + nest_level++; + if (ndim < nest_level) + ndim = nest_level; + } + break; + case '}': + if (!in_quotes) + { + /* + * A right brace can occur after an element start, an + * element completion, a quoted element completion, or + * a level completion. + */ + if (parse_state != ARRAY_ELEM_STARTED && + parse_state != ARRAY_ELEM_COMPLETED && + parse_state != ARRAY_QUOTED_ELEM_COMPLETED && + parse_state != ARRAY_LEVEL_COMPLETED && + !(nest_level == 1 && parse_state == ARRAY_LEVEL_STARTED)) + ereturn(escontext, -1, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed array literal: \"%s\"", str), + errdetail("Unexpected \"%c\" character.", + '}'))); + parse_state = ARRAY_LEVEL_COMPLETED; + if (nest_level == 0) + ereturn(escontext, -1, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed array literal: \"%s\"", str), + errdetail("Unmatched \"%c\" character.", '}'))); + nest_level--; + + if (nelems_last[nest_level] != 0 && + nelems[nest_level] != nelems_last[nest_level]) + ereturn(escontext, -1, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed array literal: \"%s\"", str), + errdetail("Multidimensional arrays must have " + "sub-arrays with matching " + "dimensions."))); + nelems_last[nest_level] = nelems[nest_level]; + nelems[nest_level] = 1; + if (nest_level == 0) + eoArray = itemdone = true; + else + { + /* + * We don't set itemdone here; see comments in + * ReadArrayStr + */ + temp[nest_level - 1]++; + } + } + break; + default: + if (!in_quotes) + { + if (*ptr == typdelim) + { + /* + * Delimiters can occur after an element start, an + * element completion, a quoted element + * completion, or a level completion. + */ + if (parse_state != ARRAY_ELEM_STARTED && + parse_state != ARRAY_ELEM_COMPLETED && + parse_state != ARRAY_QUOTED_ELEM_COMPLETED && + parse_state != ARRAY_LEVEL_COMPLETED) + ereturn(escontext, -1, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed array literal: \"%s\"", str), + errdetail("Unexpected \"%c\" character.", + typdelim))); + if (parse_state == ARRAY_LEVEL_COMPLETED) + parse_state = ARRAY_LEVEL_DELIMITED; + else + parse_state = ARRAY_ELEM_DELIMITED; + itemdone = true; + nelems[nest_level - 1]++; + } + else if (!array_isspace(*ptr)) + { + /* + * Other non-space characters must be after a + * level start, after an element start, or after + * an element delimiter. In any case we now must + * be past an element start. + */ + if (parse_state != ARRAY_LEVEL_STARTED && + parse_state != ARRAY_ELEM_STARTED && + parse_state != ARRAY_ELEM_DELIMITED) + ereturn(escontext, -1, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed array literal: \"%s\"", str), + errdetail("Unexpected array element."))); + parse_state = ARRAY_ELEM_STARTED; + } + } + break; + } + if (!itemdone) + ptr++; + } + temp[ndim - 1]++; + ptr++; + } + + /* only whitespace is allowed after the closing brace */ + while (*ptr) + { + if (!array_isspace(*ptr++)) + ereturn(escontext, -1, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed array literal: \"%s\"", str), + errdetail("Junk after closing right brace."))); + } + + /* special case for an empty array */ + if (empty_array) + return 0; + + for (i = 0; i < ndim; ++i) + dim[i] = temp[i]; + + return ndim; +} + +/* + * ReadArrayStr : + * parses the array string pointed to by "arrayStr" and converts the values + * to internal format. Unspecified elements are initialized to nulls. + * The array dimensions must already have been determined. + * + * Inputs: + * arrayStr: the string to parse. + * CAUTION: the contents of "arrayStr" will be modified! + * origStr: the unmodified input string, used only in error messages. + * nitems: total number of array elements, as already determined. + * ndim: number of array dimensions + * dim[]: array axis lengths + * inputproc: type-specific input procedure for element datatype. + * typioparam, typmod: auxiliary values to pass to inputproc. + * typdelim: the value delimiter (type-specific). + * typlen, typbyval, typalign: storage parameters of element datatype. + * + * Outputs: + * values[]: filled with converted data values. + * nulls[]: filled with is-null markers. + * *hasnulls: set true iff there are any null elements. + * *nbytes: set to total size of data area needed (including alignment + * padding but not including array header overhead). + * *escontext: if this points to an ErrorSaveContext, details of + * any error are reported there. + * + * Result: + * true for success, false for failure (if escontext is provided). + * + * Note that values[] and nulls[] are allocated by the caller, and must have + * nitems elements. + */ +static bool +ReadArrayStr(char *arrayStr, + const char *origStr, + int nitems, + int ndim, + int *dim, + FmgrInfo *inputproc, + Oid typioparam, + int32 typmod, + char typdelim, + int typlen, + bool typbyval, + char typalign, + Datum *values, + bool *nulls, + bool *hasnulls, + int32 *nbytes, + Node *escontext) +{ + int i, + nest_level = 0; + char *srcptr; + bool in_quotes = false; + bool eoArray = false; + bool hasnull; + int32 totbytes; + int indx[MAXDIM] = {0}, + prod[MAXDIM]; + + mda_get_prod(ndim, dim, prod); + + /* Initialize is-null markers to true */ + memset(nulls, true, nitems * sizeof(bool)); + + /* + * We have to remove " and \ characters to create a clean item value to + * pass to the datatype input routine. We overwrite each item value + * in-place within arrayStr to do this. srcptr is the current scan point, + * and dstptr is where we are copying to. + * + * We also want to suppress leading and trailing unquoted whitespace. We + * use the leadingspace flag to suppress leading space. Trailing space is + * tracked by using dstendptr to point to the last significant output + * character. + * + * The error checking in this routine is mostly pro-forma, since we expect + * that ArrayCount() already validated the string. So we don't bother + * with errdetail messages. + */ + srcptr = arrayStr; + while (!eoArray) + { + bool itemdone = false; + bool leadingspace = true; + bool hasquoting = false; + char *itemstart; + char *dstptr; + char *dstendptr; + + i = -1; + itemstart = dstptr = dstendptr = srcptr; + + while (!itemdone) + { + switch (*srcptr) + { + case '\0': + /* Signal a premature end of the string */ + ereturn(escontext, false, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed array literal: \"%s\"", + origStr))); + break; + case '\\': + /* Skip backslash, copy next character as-is. */ + srcptr++; + if (*srcptr == '\0') + ereturn(escontext, false, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed array literal: \"%s\"", + origStr))); + *dstptr++ = *srcptr++; + /* Treat the escaped character as non-whitespace */ + leadingspace = false; + dstendptr = dstptr; + hasquoting = true; /* can't be a NULL marker */ + break; + case '"': + in_quotes = !in_quotes; + if (in_quotes) + leadingspace = false; + else + { + /* + * Advance dstendptr when we exit in_quotes; this + * saves having to do it in all the other in_quotes + * cases. + */ + dstendptr = dstptr; + } + hasquoting = true; /* can't be a NULL marker */ + srcptr++; + break; + case '{': + if (!in_quotes) + { + if (nest_level >= ndim) + ereturn(escontext, false, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed array literal: \"%s\"", + origStr))); + nest_level++; + indx[nest_level - 1] = 0; + srcptr++; + } + else + *dstptr++ = *srcptr++; + break; + case '}': + if (!in_quotes) + { + if (nest_level == 0) + ereturn(escontext, false, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed array literal: \"%s\"", + origStr))); + if (i == -1) + i = ArrayGetOffset0(ndim, indx, prod); + indx[nest_level - 1] = 0; + nest_level--; + if (nest_level == 0) + eoArray = itemdone = true; + else + indx[nest_level - 1]++; + srcptr++; + } + else + *dstptr++ = *srcptr++; + break; + default: + if (in_quotes) + *dstptr++ = *srcptr++; + else if (*srcptr == typdelim) + { + if (i == -1) + i = ArrayGetOffset0(ndim, indx, prod); + itemdone = true; + indx[ndim - 1]++; + srcptr++; + } + else if (array_isspace(*srcptr)) + { + /* + * If leading space, drop it immediately. Else, copy + * but don't advance dstendptr. + */ + if (leadingspace) + srcptr++; + else + *dstptr++ = *srcptr++; + } + else + { + *dstptr++ = *srcptr++; + leadingspace = false; + dstendptr = dstptr; + } + break; + } + } + + Assert(dstptr < srcptr); + *dstendptr = '\0'; + + if (i < 0 || i >= nitems) + ereturn(escontext, false, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed array literal: \"%s\"", + origStr))); + + if (Array_nulls && !hasquoting && + pg_strcasecmp(itemstart, "NULL") == 0) + { + /* it's a NULL item */ + if (!InputFunctionCallSafe(inputproc, NULL, + typioparam, typmod, + escontext, + &values[i])) + return false; + nulls[i] = true; + } + else + { + if (!InputFunctionCallSafe(inputproc, itemstart, + typioparam, typmod, + escontext, + &values[i])) + return false; + nulls[i] = false; + } + } + + /* + * Check for nulls, compute total data space needed + */ + hasnull = false; + totbytes = 0; + for (i = 0; i < nitems; i++) + { + if (nulls[i]) + hasnull = true; + else + { + /* let's just make sure data is not toasted */ + if (typlen == -1) + values[i] = PointerGetDatum(PG_DETOAST_DATUM(values[i])); + totbytes = att_addlength_datum(totbytes, typlen, values[i]); + totbytes = att_align_nominal(totbytes, typalign); + /* check for overflow of total request */ + if (!AllocSizeIsValid(totbytes)) + ereturn(escontext, false, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("array size exceeds the maximum allowed (%d)", + (int) MaxAllocSize))); + } + } + *hasnulls = hasnull; + *nbytes = totbytes; + return true; +} + + +/* + * Copy data into an array object from a temporary array of Datums. + * + * array: array object (with header fields already filled in) + * values: array of Datums to be copied + * nulls: array of is-null flags (can be NULL if no nulls) + * nitems: number of Datums to be copied + * typbyval, typlen, typalign: info about element datatype + * freedata: if true and element type is pass-by-ref, pfree data values + * referenced by Datums after copying them. + * + * If the input data is of varlena type, the caller must have ensured that + * the values are not toasted. (Doing it here doesn't work since the + * caller has already allocated space for the array...) + */ +void +CopyArrayEls(ArrayType *array, + Datum *values, + bool *nulls, + int nitems, + int typlen, + bool typbyval, + char typalign, + bool freedata) +{ + char *p = ARR_DATA_PTR(array); + bits8 *bitmap = ARR_NULLBITMAP(array); + int bitval = 0; + int bitmask = 1; + int i; + + if (typbyval) + freedata = false; + + for (i = 0; i < nitems; i++) + { + if (nulls && nulls[i]) + { + if (!bitmap) /* shouldn't happen */ + elog(ERROR, "null array element where not supported"); + /* bitmap bit stays 0 */ + } + else + { + bitval |= bitmask; + p += ArrayCastAndSet(values[i], typlen, typbyval, typalign, p); + if (freedata) + pfree(DatumGetPointer(values[i])); + } + if (bitmap) + { + bitmask <<= 1; + if (bitmask == 0x100) + { + *bitmap++ = bitval; + bitval = 0; + bitmask = 1; + } + } + } + + if (bitmap && bitmask != 1) + *bitmap = bitval; +} + +/* + * array_out : + * takes the internal representation of an array and returns a string + * containing the array in its external format. + */ +Datum +array_out(PG_FUNCTION_ARGS) +{ + AnyArrayType *v = PG_GETARG_ANY_ARRAY_P(0); + Oid element_type = AARR_ELEMTYPE(v); + int typlen; + bool typbyval; + char typalign; + char typdelim; + char *p, + *tmp, + *retval, + **values, + dims_str[(MAXDIM * 33) + 2]; + + /* + * 33 per dim since we assume 15 digits per number + ':' +'[]' + * + * +2 allows for assignment operator + trailing null + */ + bool *needquotes, + needdims = false; + size_t overall_length; + int nitems, + i, + j, + k, + indx[MAXDIM]; + int ndim, + *dims, + *lb; + array_iter iter; + ArrayMetaState *my_extra; + + /* + * We arrange to look up info about element type, including its output + * conversion proc, only once per series of calls, assuming the element + * type doesn't change underneath us. + */ + my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra; + if (my_extra == NULL) + { + fcinfo->flinfo->fn_extra = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, + sizeof(ArrayMetaState)); + my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra; + my_extra->element_type = ~element_type; + } + + if (my_extra->element_type != element_type) + { + /* + * Get info about element type, including its output conversion proc + */ + get_type_io_data(element_type, IOFunc_output, + &my_extra->typlen, &my_extra->typbyval, + &my_extra->typalign, &my_extra->typdelim, + &my_extra->typioparam, &my_extra->typiofunc); + fmgr_info_cxt(my_extra->typiofunc, &my_extra->proc, + fcinfo->flinfo->fn_mcxt); + my_extra->element_type = element_type; + } + typlen = my_extra->typlen; + typbyval = my_extra->typbyval; + typalign = my_extra->typalign; + typdelim = my_extra->typdelim; + + ndim = AARR_NDIM(v); + dims = AARR_DIMS(v); + lb = AARR_LBOUND(v); + nitems = ArrayGetNItems(ndim, dims); + + if (nitems == 0) + { + retval = pstrdup("{}"); + PG_RETURN_CSTRING(retval); + } + + /* + * we will need to add explicit dimensions if any dimension has a lower + * bound other than one + */ + for (i = 0; i < ndim; i++) + { + if (lb[i] != 1) + { + needdims = true; + break; + } + } + + /* + * Convert all values to string form, count total space needed (including + * any overhead such as escaping backslashes), and detect whether each + * item needs double quotes. + */ + values = (char **) palloc(nitems * sizeof(char *)); + needquotes = (bool *) palloc(nitems * sizeof(bool)); + overall_length = 0; + + array_iter_setup(&iter, v); + + for (i = 0; i < nitems; i++) + { + Datum itemvalue; + bool isnull; + bool needquote; + + /* Get source element, checking for NULL */ + itemvalue = array_iter_next(&iter, &isnull, i, + typlen, typbyval, typalign); + + if (isnull) + { + values[i] = pstrdup("NULL"); + overall_length += 4; + needquote = false; + } + else + { + values[i] = OutputFunctionCall(&my_extra->proc, itemvalue); + + /* count data plus backslashes; detect chars needing quotes */ + if (values[i][0] == '\0') + needquote = true; /* force quotes for empty string */ + else if (pg_strcasecmp(values[i], "NULL") == 0) + needquote = true; /* force quotes for literal NULL */ + else + needquote = false; + + for (tmp = values[i]; *tmp != '\0'; tmp++) + { + char ch = *tmp; + + overall_length += 1; + if (ch == '"' || ch == '\\') + { + needquote = true; + overall_length += 1; + } + else if (ch == '{' || ch == '}' || ch == typdelim || + array_isspace(ch)) + needquote = true; + } + } + + needquotes[i] = needquote; + + /* Count the pair of double quotes, if needed */ + if (needquote) + overall_length += 2; + /* and the comma (or other typdelim delimiter) */ + overall_length += 1; + } + + /* + * The very last array element doesn't have a typdelim delimiter after it, + * but that's OK; that space is needed for the trailing '\0'. + * + * Now count total number of curly brace pairs in output string. + */ + for (i = j = 0, k = 1; i < ndim; i++) + { + j += k, k *= dims[i]; + } + overall_length += 2 * j; + + /* Format explicit dimensions if required */ + dims_str[0] = '\0'; + if (needdims) + { + char *ptr = dims_str; + + for (i = 0; i < ndim; i++) + { + sprintf(ptr, "[%d:%d]", lb[i], lb[i] + dims[i] - 1); + ptr += strlen(ptr); + } + *ptr++ = *ASSGN; + *ptr = '\0'; + overall_length += ptr - dims_str; + } + + /* Now construct the output string */ + retval = (char *) palloc(overall_length); + p = retval; + +#define APPENDSTR(str) (strcpy(p, (str)), p += strlen(p)) +#define APPENDCHAR(ch) (*p++ = (ch), *p = '\0') + + if (needdims) + APPENDSTR(dims_str); + APPENDCHAR('{'); + for (i = 0; i < ndim; i++) + indx[i] = 0; + j = 0; + k = 0; + do + { + for (i = j; i < ndim - 1; i++) + APPENDCHAR('{'); + + if (needquotes[k]) + { + APPENDCHAR('"'); + for (tmp = values[k]; *tmp; tmp++) + { + char ch = *tmp; + + if (ch == '"' || ch == '\\') + *p++ = '\\'; + *p++ = ch; + } + *p = '\0'; + APPENDCHAR('"'); + } + else + APPENDSTR(values[k]); + pfree(values[k++]); + + for (i = ndim - 1; i >= 0; i--) + { + if (++(indx[i]) < dims[i]) + { + APPENDCHAR(typdelim); + break; + } + else + { + indx[i] = 0; + APPENDCHAR('}'); + } + } + j = i; + } while (j != -1); + +#undef APPENDSTR +#undef APPENDCHAR + + /* Assert that we calculated the string length accurately */ + Assert(overall_length == (p - retval + 1)); + + pfree(values); + pfree(needquotes); + + PG_RETURN_CSTRING(retval); +} + +/* + * array_recv : + * converts an array from the external binary format to + * its internal format. + * + * return value : + * the internal representation of the input array + */ +Datum +array_recv(PG_FUNCTION_ARGS) +{ + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + Oid spec_element_type = PG_GETARG_OID(1); /* type of an array + * element */ + int32 typmod = PG_GETARG_INT32(2); /* typmod for array elements */ + Oid element_type; + int typlen; + bool typbyval; + char typalign; + Oid typioparam; + int i, + nitems; + Datum *dataPtr; + bool *nullsPtr; + bool hasnulls; + int32 nbytes; + int32 dataoffset; + ArrayType *retval; + int ndim, + flags, + dim[MAXDIM], + lBound[MAXDIM]; + ArrayMetaState *my_extra; + + /* Get the array header information */ + ndim = pq_getmsgint(buf, 4); + if (ndim < 0) /* we do allow zero-dimension arrays */ + ereport(ERROR, + (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION), + errmsg("invalid number of dimensions: %d", ndim))); + if (ndim > MAXDIM) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)", + ndim, MAXDIM))); + + flags = pq_getmsgint(buf, 4); + if (flags != 0 && flags != 1) + ereport(ERROR, + (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION), + errmsg("invalid array flags"))); + + /* Check element type recorded in the data */ + element_type = pq_getmsgint(buf, sizeof(Oid)); + + /* + * From a security standpoint, it doesn't matter whether the input's + * element type matches what we expect: the element type's receive + * function has to be robust enough to cope with invalid data. However, + * from a user-friendliness standpoint, it's nicer to complain about type + * mismatches than to throw "improper binary format" errors. But there's + * a problem: only built-in types have OIDs that are stable enough to + * believe that a mismatch is a real issue. So complain only if both OIDs + * are in the built-in range. Otherwise, carry on with the element type + * we "should" be getting. + */ + if (element_type != spec_element_type) + { + if (element_type < FirstGenbkiObjectId && + spec_element_type < FirstGenbkiObjectId) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("binary data has array element type %u (%s) instead of expected %u (%s)", + element_type, + format_type_extended(element_type, -1, + FORMAT_TYPE_ALLOW_INVALID), + spec_element_type, + format_type_extended(spec_element_type, -1, + FORMAT_TYPE_ALLOW_INVALID)))); + element_type = spec_element_type; + } + + for (i = 0; i < ndim; i++) + { + dim[i] = pq_getmsgint(buf, 4); + lBound[i] = pq_getmsgint(buf, 4); + } + + /* This checks for overflow of array dimensions */ + nitems = ArrayGetNItems(ndim, dim); + ArrayCheckBounds(ndim, dim, lBound); + + /* + * We arrange to look up info about element type, including its receive + * conversion proc, only once per series of calls, assuming the element + * type doesn't change underneath us. + */ + my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra; + if (my_extra == NULL) + { + fcinfo->flinfo->fn_extra = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, + sizeof(ArrayMetaState)); + my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra; + my_extra->element_type = ~element_type; + } + + if (my_extra->element_type != element_type) + { + /* Get info about element type, including its receive proc */ + get_type_io_data(element_type, IOFunc_receive, + &my_extra->typlen, &my_extra->typbyval, + &my_extra->typalign, &my_extra->typdelim, + &my_extra->typioparam, &my_extra->typiofunc); + if (!OidIsValid(my_extra->typiofunc)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("no binary input function available for type %s", + format_type_be(element_type)))); + fmgr_info_cxt(my_extra->typiofunc, &my_extra->proc, + fcinfo->flinfo->fn_mcxt); + my_extra->element_type = element_type; + } + + if (nitems == 0) + { + /* Return empty array ... but not till we've validated element_type */ + PG_RETURN_ARRAYTYPE_P(construct_empty_array(element_type)); + } + + typlen = my_extra->typlen; + typbyval = my_extra->typbyval; + typalign = my_extra->typalign; + typioparam = my_extra->typioparam; + + dataPtr = (Datum *) palloc(nitems * sizeof(Datum)); + nullsPtr = (bool *) palloc(nitems * sizeof(bool)); + ReadArrayBinary(buf, nitems, + &my_extra->proc, typioparam, typmod, + typlen, typbyval, typalign, + dataPtr, nullsPtr, + &hasnulls, &nbytes); + if (hasnulls) + { + dataoffset = ARR_OVERHEAD_WITHNULLS(ndim, nitems); + nbytes += dataoffset; + } + else + { + dataoffset = 0; /* marker for no null bitmap */ + nbytes += ARR_OVERHEAD_NONULLS(ndim); + } + retval = (ArrayType *) palloc0(nbytes); + SET_VARSIZE(retval, nbytes); + retval->ndim = ndim; + retval->dataoffset = dataoffset; + retval->elemtype = element_type; + memcpy(ARR_DIMS(retval), dim, ndim * sizeof(int)); + memcpy(ARR_LBOUND(retval), lBound, ndim * sizeof(int)); + + CopyArrayEls(retval, + dataPtr, nullsPtr, nitems, + typlen, typbyval, typalign, + true); + + pfree(dataPtr); + pfree(nullsPtr); + + PG_RETURN_ARRAYTYPE_P(retval); +} + +/* + * ReadArrayBinary: + * collect the data elements of an array being read in binary style. + * + * Inputs: + * buf: the data buffer to read from. + * nitems: total number of array elements (already read). + * receiveproc: type-specific receive procedure for element datatype. + * typioparam, typmod: auxiliary values to pass to receiveproc. + * typlen, typbyval, typalign: storage parameters of element datatype. + * + * Outputs: + * values[]: filled with converted data values. + * nulls[]: filled with is-null markers. + * *hasnulls: set true iff there are any null elements. + * *nbytes: set to total size of data area needed (including alignment + * padding but not including array header overhead). + * + * Note that values[] and nulls[] are allocated by the caller, and must have + * nitems elements. + */ +static void +ReadArrayBinary(StringInfo buf, + int nitems, + FmgrInfo *receiveproc, + Oid typioparam, + int32 typmod, + int typlen, + bool typbyval, + char typalign, + Datum *values, + bool *nulls, + bool *hasnulls, + int32 *nbytes) +{ + int i; + bool hasnull; + int32 totbytes; + + for (i = 0; i < nitems; i++) + { + int itemlen; + StringInfoData elem_buf; + + /* Get and check the item length */ + itemlen = pq_getmsgint(buf, 4); + if (itemlen < -1 || itemlen > (buf->len - buf->cursor)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION), + errmsg("insufficient data left in message"))); + + if (itemlen == -1) + { + /* -1 length means NULL */ + values[i] = ReceiveFunctionCall(receiveproc, NULL, + typioparam, typmod); + nulls[i] = true; + continue; + } + + /* + * Rather than copying data around, we just set up a phony StringInfo + * pointing to the correct portion of the input buffer. We assume we + * can scribble on the input buffer so as to maintain the convention + * that StringInfos have a trailing null. + */ + elem_buf.data = &buf->data[buf->cursor]; + elem_buf.maxlen = itemlen + 1; + elem_buf.len = itemlen; + elem_buf.cursor = 0; + + buf->cursor += itemlen; + + /* Now call the element's receiveproc */ + values[i] = ReceiveFunctionCall(receiveproc, &elem_buf, + typioparam, typmod); + nulls[i] = false; + + /* Trouble if it didn't eat the whole buffer */ + if (elem_buf.cursor != itemlen) + ereport(ERROR, + (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION), + errmsg("improper binary format in array element %d", + i + 1))); + } + + /* + * Check for nulls, compute total data space needed + */ + hasnull = false; + totbytes = 0; + for (i = 0; i < nitems; i++) + { + if (nulls[i]) + hasnull = true; + else + { + /* let's just make sure data is not toasted */ + if (typlen == -1) + values[i] = PointerGetDatum(PG_DETOAST_DATUM(values[i])); + totbytes = att_addlength_datum(totbytes, typlen, values[i]); + totbytes = att_align_nominal(totbytes, typalign); + /* check for overflow of total request */ + if (!AllocSizeIsValid(totbytes)) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("array size exceeds the maximum allowed (%d)", + (int) MaxAllocSize))); + } + } + *hasnulls = hasnull; + *nbytes = totbytes; +} + + +/* + * array_send : + * takes the internal representation of an array and returns a bytea + * containing the array in its external binary format. + */ +Datum +array_send(PG_FUNCTION_ARGS) +{ + AnyArrayType *v = PG_GETARG_ANY_ARRAY_P(0); + Oid element_type = AARR_ELEMTYPE(v); + int typlen; + bool typbyval; + char typalign; + int nitems, + i; + int ndim, + *dim, + *lb; + StringInfoData buf; + array_iter iter; + ArrayMetaState *my_extra; + + /* + * We arrange to look up info about element type, including its send + * conversion proc, only once per series of calls, assuming the element + * type doesn't change underneath us. + */ + my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra; + if (my_extra == NULL) + { + fcinfo->flinfo->fn_extra = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, + sizeof(ArrayMetaState)); + my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra; + my_extra->element_type = ~element_type; + } + + if (my_extra->element_type != element_type) + { + /* Get info about element type, including its send proc */ + get_type_io_data(element_type, IOFunc_send, + &my_extra->typlen, &my_extra->typbyval, + &my_extra->typalign, &my_extra->typdelim, + &my_extra->typioparam, &my_extra->typiofunc); + if (!OidIsValid(my_extra->typiofunc)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("no binary output function available for type %s", + format_type_be(element_type)))); + fmgr_info_cxt(my_extra->typiofunc, &my_extra->proc, + fcinfo->flinfo->fn_mcxt); + my_extra->element_type = element_type; + } + typlen = my_extra->typlen; + typbyval = my_extra->typbyval; + typalign = my_extra->typalign; + + ndim = AARR_NDIM(v); + dim = AARR_DIMS(v); + lb = AARR_LBOUND(v); + nitems = ArrayGetNItems(ndim, dim); + + pq_begintypsend(&buf); + + /* Send the array header information */ + pq_sendint32(&buf, ndim); + pq_sendint32(&buf, AARR_HASNULL(v) ? 1 : 0); + pq_sendint32(&buf, element_type); + for (i = 0; i < ndim; i++) + { + pq_sendint32(&buf, dim[i]); + pq_sendint32(&buf, lb[i]); + } + + /* Send the array elements using the element's own sendproc */ + array_iter_setup(&iter, v); + + for (i = 0; i < nitems; i++) + { + Datum itemvalue; + bool isnull; + + /* Get source element, checking for NULL */ + itemvalue = array_iter_next(&iter, &isnull, i, + typlen, typbyval, typalign); + + if (isnull) + { + /* -1 length means a NULL */ + pq_sendint32(&buf, -1); + } + else + { + bytea *outputbytes; + + outputbytes = SendFunctionCall(&my_extra->proc, itemvalue); + pq_sendint32(&buf, VARSIZE(outputbytes) - VARHDRSZ); + pq_sendbytes(&buf, VARDATA(outputbytes), + VARSIZE(outputbytes) - VARHDRSZ); + pfree(outputbytes); + } + } + + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} + +/* + * array_ndims : + * returns the number of dimensions of the array pointed to by "v" + */ +Datum +array_ndims(PG_FUNCTION_ARGS) +{ + AnyArrayType *v = PG_GETARG_ANY_ARRAY_P(0); + + /* Sanity check: does it look like an array at all? */ + if (AARR_NDIM(v) <= 0 || AARR_NDIM(v) > MAXDIM) + PG_RETURN_NULL(); + + PG_RETURN_INT32(AARR_NDIM(v)); +} + +/* + * array_dims : + * returns the dimensions of the array pointed to by "v", as a "text" + */ +Datum +array_dims(PG_FUNCTION_ARGS) +{ + AnyArrayType *v = PG_GETARG_ANY_ARRAY_P(0); + char *p; + int i; + int *dimv, + *lb; + + /* + * 33 since we assume 15 digits per number + ':' +'[]' + * + * +1 for trailing null + */ + char buf[MAXDIM * 33 + 1]; + + /* Sanity check: does it look like an array at all? */ + if (AARR_NDIM(v) <= 0 || AARR_NDIM(v) > MAXDIM) + PG_RETURN_NULL(); + + dimv = AARR_DIMS(v); + lb = AARR_LBOUND(v); + + p = buf; + for (i = 0; i < AARR_NDIM(v); i++) + { + sprintf(p, "[%d:%d]", lb[i], dimv[i] + lb[i] - 1); + p += strlen(p); + } + + PG_RETURN_TEXT_P(cstring_to_text(buf)); +} + +/* + * array_lower : + * returns the lower dimension, of the DIM requested, for + * the array pointed to by "v", as an int4 + */ +Datum +array_lower(PG_FUNCTION_ARGS) +{ + AnyArrayType *v = PG_GETARG_ANY_ARRAY_P(0); + int reqdim = PG_GETARG_INT32(1); + int *lb; + int result; + + /* Sanity check: does it look like an array at all? */ + if (AARR_NDIM(v) <= 0 || AARR_NDIM(v) > MAXDIM) + PG_RETURN_NULL(); + + /* Sanity check: was the requested dim valid */ + if (reqdim <= 0 || reqdim > AARR_NDIM(v)) + PG_RETURN_NULL(); + + lb = AARR_LBOUND(v); + result = lb[reqdim - 1]; + + PG_RETURN_INT32(result); +} + +/* + * array_upper : + * returns the upper dimension, of the DIM requested, for + * the array pointed to by "v", as an int4 + */ +Datum +array_upper(PG_FUNCTION_ARGS) +{ + AnyArrayType *v = PG_GETARG_ANY_ARRAY_P(0); + int reqdim = PG_GETARG_INT32(1); + int *dimv, + *lb; + int result; + + /* Sanity check: does it look like an array at all? */ + if (AARR_NDIM(v) <= 0 || AARR_NDIM(v) > MAXDIM) + PG_RETURN_NULL(); + + /* Sanity check: was the requested dim valid */ + if (reqdim <= 0 || reqdim > AARR_NDIM(v)) + PG_RETURN_NULL(); + + lb = AARR_LBOUND(v); + dimv = AARR_DIMS(v); + + result = dimv[reqdim - 1] + lb[reqdim - 1] - 1; + + PG_RETURN_INT32(result); +} + +/* + * array_length : + * returns the length, of the dimension requested, for + * the array pointed to by "v", as an int4 + */ +Datum +array_length(PG_FUNCTION_ARGS) +{ + AnyArrayType *v = PG_GETARG_ANY_ARRAY_P(0); + int reqdim = PG_GETARG_INT32(1); + int *dimv; + int result; + + /* Sanity check: does it look like an array at all? */ + if (AARR_NDIM(v) <= 0 || AARR_NDIM(v) > MAXDIM) + PG_RETURN_NULL(); + + /* Sanity check: was the requested dim valid */ + if (reqdim <= 0 || reqdim > AARR_NDIM(v)) + PG_RETURN_NULL(); + + dimv = AARR_DIMS(v); + + result = dimv[reqdim - 1]; + + PG_RETURN_INT32(result); +} + +/* + * array_cardinality: + * returns the total number of elements in an array + */ +Datum +array_cardinality(PG_FUNCTION_ARGS) +{ + AnyArrayType *v = PG_GETARG_ANY_ARRAY_P(0); + + PG_RETURN_INT32(ArrayGetNItems(AARR_NDIM(v), AARR_DIMS(v))); +} + + +/* + * array_get_element : + * This routine takes an array datum and a subscript array and returns + * the referenced item as a Datum. Note that for a pass-by-reference + * datatype, the returned Datum is a pointer into the array object. + * + * This handles both ordinary varlena arrays and fixed-length arrays. + * + * Inputs: + * arraydatum: the array object (mustn't be NULL) + * nSubscripts: number of subscripts supplied + * indx[]: the subscript values + * arraytyplen: pg_type.typlen for the array type + * elmlen: pg_type.typlen for the array's element type + * elmbyval: pg_type.typbyval for the array's element type + * elmalign: pg_type.typalign for the array's element type + * + * Outputs: + * The return value is the element Datum. + * *isNull is set to indicate whether the element is NULL. + */ +Datum +array_get_element(Datum arraydatum, + int nSubscripts, + int *indx, + int arraytyplen, + int elmlen, + bool elmbyval, + char elmalign, + bool *isNull) +{ + int i, + ndim, + *dim, + *lb, + offset, + fixedDim[1], + fixedLb[1]; + char *arraydataptr, + *retptr; + bits8 *arraynullsptr; + + if (arraytyplen > 0) + { + /* + * fixed-length arrays -- these are assumed to be 1-d, 0-based + */ + ndim = 1; + fixedDim[0] = arraytyplen / elmlen; + fixedLb[0] = 0; + dim = fixedDim; + lb = fixedLb; + arraydataptr = (char *) DatumGetPointer(arraydatum); + arraynullsptr = NULL; + } + else if (VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(arraydatum))) + { + /* expanded array: let's do this in a separate function */ + return array_get_element_expanded(arraydatum, + nSubscripts, + indx, + arraytyplen, + elmlen, + elmbyval, + elmalign, + isNull); + } + else + { + /* detoast array if necessary, producing normal varlena input */ + ArrayType *array = DatumGetArrayTypeP(arraydatum); + + ndim = ARR_NDIM(array); + dim = ARR_DIMS(array); + lb = ARR_LBOUND(array); + arraydataptr = ARR_DATA_PTR(array); + arraynullsptr = ARR_NULLBITMAP(array); + } + + /* + * Return NULL for invalid subscript + */ + if (ndim != nSubscripts || ndim <= 0 || ndim > MAXDIM) + { + *isNull = true; + return (Datum) 0; + } + for (i = 0; i < ndim; i++) + { + if (indx[i] < lb[i] || indx[i] >= (dim[i] + lb[i])) + { + *isNull = true; + return (Datum) 0; + } + } + + /* + * Calculate the element number + */ + offset = ArrayGetOffset(nSubscripts, dim, lb, indx); + + /* + * Check for NULL array element + */ + if (array_get_isnull(arraynullsptr, offset)) + { + *isNull = true; + return (Datum) 0; + } + + /* + * OK, get the element + */ + *isNull = false; + retptr = array_seek(arraydataptr, 0, arraynullsptr, offset, + elmlen, elmbyval, elmalign); + return ArrayCast(retptr, elmbyval, elmlen); +} + +/* + * Implementation of array_get_element() for an expanded array + */ +static Datum +array_get_element_expanded(Datum arraydatum, + int nSubscripts, int *indx, + int arraytyplen, + int elmlen, bool elmbyval, char elmalign, + bool *isNull) +{ + ExpandedArrayHeader *eah; + int i, + ndim, + *dim, + *lb, + offset; + Datum *dvalues; + bool *dnulls; + + eah = (ExpandedArrayHeader *) DatumGetEOHP(arraydatum); + Assert(eah->ea_magic == EA_MAGIC); + + /* sanity-check caller's info against object */ + Assert(arraytyplen == -1); + Assert(elmlen == eah->typlen); + Assert(elmbyval == eah->typbyval); + Assert(elmalign == eah->typalign); + + ndim = eah->ndims; + dim = eah->dims; + lb = eah->lbound; + + /* + * Return NULL for invalid subscript + */ + if (ndim != nSubscripts || ndim <= 0 || ndim > MAXDIM) + { + *isNull = true; + return (Datum) 0; + } + for (i = 0; i < ndim; i++) + { + if (indx[i] < lb[i] || indx[i] >= (dim[i] + lb[i])) + { + *isNull = true; + return (Datum) 0; + } + } + + /* + * Calculate the element number + */ + offset = ArrayGetOffset(nSubscripts, dim, lb, indx); + + /* + * Deconstruct array if we didn't already. Note that we apply this even + * if the input is nominally read-only: it should be safe enough. + */ + deconstruct_expanded_array(eah); + + dvalues = eah->dvalues; + dnulls = eah->dnulls; + + /* + * Check for NULL array element + */ + if (dnulls && dnulls[offset]) + { + *isNull = true; + return (Datum) 0; + } + + /* + * OK, get the element. It's OK to return a pass-by-ref value as a + * pointer into the expanded array, for the same reason that regular + * array_get_element can return a pointer into flat arrays: the value is + * assumed not to change for as long as the Datum reference can exist. + */ + *isNull = false; + return dvalues[offset]; +} + +/* + * array_get_slice : + * This routine takes an array and a range of indices (upperIndx and + * lowerIndx), creates a new array structure for the referred elements + * and returns a pointer to it. + * + * This handles both ordinary varlena arrays and fixed-length arrays. + * + * Inputs: + * arraydatum: the array object (mustn't be NULL) + * nSubscripts: number of subscripts supplied (must be same for upper/lower) + * upperIndx[]: the upper subscript values + * lowerIndx[]: the lower subscript values + * upperProvided[]: true for provided upper subscript values + * lowerProvided[]: true for provided lower subscript values + * arraytyplen: pg_type.typlen for the array type + * elmlen: pg_type.typlen for the array's element type + * elmbyval: pg_type.typbyval for the array's element type + * elmalign: pg_type.typalign for the array's element type + * + * Outputs: + * The return value is the new array Datum (it's never NULL) + * + * Omitted upper and lower subscript values are replaced by the corresponding + * array bound. + * + * NOTE: we assume it is OK to scribble on the provided subscript arrays + * lowerIndx[] and upperIndx[]; also, these arrays must be of size MAXDIM + * even when nSubscripts is less. These are generally just temporaries. + */ +Datum +array_get_slice(Datum arraydatum, + int nSubscripts, + int *upperIndx, + int *lowerIndx, + bool *upperProvided, + bool *lowerProvided, + int arraytyplen, + int elmlen, + bool elmbyval, + char elmalign) +{ + ArrayType *array; + ArrayType *newarray; + int i, + ndim, + *dim, + *lb, + *newlb; + int fixedDim[1], + fixedLb[1]; + Oid elemtype; + char *arraydataptr; + bits8 *arraynullsptr; + int32 dataoffset; + int bytes, + span[MAXDIM]; + + if (arraytyplen > 0) + { + /* + * fixed-length arrays -- currently, cannot slice these because parser + * labels output as being of the fixed-length array type! Code below + * shows how we could support it if the parser were changed to label + * output as a suitable varlena array type. + */ + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("slices of fixed-length arrays not implemented"))); + + /* + * fixed-length arrays -- these are assumed to be 1-d, 0-based + * + * XXX where would we get the correct ELEMTYPE from? + */ + ndim = 1; + fixedDim[0] = arraytyplen / elmlen; + fixedLb[0] = 0; + dim = fixedDim; + lb = fixedLb; + elemtype = InvalidOid; /* XXX */ + arraydataptr = (char *) DatumGetPointer(arraydatum); + arraynullsptr = NULL; + } + else + { + /* detoast input array if necessary */ + array = DatumGetArrayTypeP(arraydatum); + + ndim = ARR_NDIM(array); + dim = ARR_DIMS(array); + lb = ARR_LBOUND(array); + elemtype = ARR_ELEMTYPE(array); + arraydataptr = ARR_DATA_PTR(array); + arraynullsptr = ARR_NULLBITMAP(array); + } + + /* + * Check provided subscripts. A slice exceeding the current array limits + * is silently truncated to the array limits. If we end up with an empty + * slice, return an empty array. + */ + if (ndim < nSubscripts || ndim <= 0 || ndim > MAXDIM) + return PointerGetDatum(construct_empty_array(elemtype)); + + for (i = 0; i < nSubscripts; i++) + { + if (!lowerProvided[i] || lowerIndx[i] < lb[i]) + lowerIndx[i] = lb[i]; + if (!upperProvided[i] || upperIndx[i] >= (dim[i] + lb[i])) + upperIndx[i] = dim[i] + lb[i] - 1; + if (lowerIndx[i] > upperIndx[i]) + return PointerGetDatum(construct_empty_array(elemtype)); + } + /* fill any missing subscript positions with full array range */ + for (; i < ndim; i++) + { + lowerIndx[i] = lb[i]; + upperIndx[i] = dim[i] + lb[i] - 1; + if (lowerIndx[i] > upperIndx[i]) + return PointerGetDatum(construct_empty_array(elemtype)); + } + + mda_get_range(ndim, span, lowerIndx, upperIndx); + + bytes = array_slice_size(arraydataptr, arraynullsptr, + ndim, dim, lb, + lowerIndx, upperIndx, + elmlen, elmbyval, elmalign); + + /* + * Currently, we put a null bitmap in the result if the source has one; + * could be smarter ... + */ + if (arraynullsptr) + { + dataoffset = ARR_OVERHEAD_WITHNULLS(ndim, ArrayGetNItems(ndim, span)); + bytes += dataoffset; + } + else + { + dataoffset = 0; /* marker for no null bitmap */ + bytes += ARR_OVERHEAD_NONULLS(ndim); + } + + newarray = (ArrayType *) palloc0(bytes); + SET_VARSIZE(newarray, bytes); + newarray->ndim = ndim; + newarray->dataoffset = dataoffset; + newarray->elemtype = elemtype; + memcpy(ARR_DIMS(newarray), span, ndim * sizeof(int)); + + /* + * Lower bounds of the new array are set to 1. Formerly (before 7.3) we + * copied the given lowerIndx values ... but that seems confusing. + */ + newlb = ARR_LBOUND(newarray); + for (i = 0; i < ndim; i++) + newlb[i] = 1; + + array_extract_slice(newarray, + ndim, dim, lb, + arraydataptr, arraynullsptr, + lowerIndx, upperIndx, + elmlen, elmbyval, elmalign); + + return PointerGetDatum(newarray); +} + +/* + * array_set_element : + * This routine sets the value of one array element (specified by + * a subscript array) to a new value specified by "dataValue". + * + * This handles both ordinary varlena arrays and fixed-length arrays. + * + * Inputs: + * arraydatum: the initial array object (mustn't be NULL) + * nSubscripts: number of subscripts supplied + * indx[]: the subscript values + * dataValue: the datum to be inserted at the given position + * isNull: whether dataValue is NULL + * arraytyplen: pg_type.typlen for the array type + * elmlen: pg_type.typlen for the array's element type + * elmbyval: pg_type.typbyval for the array's element type + * elmalign: pg_type.typalign for the array's element type + * + * Result: + * A new array is returned, just like the old except for the one + * modified entry. The original array object is not changed, + * unless what is passed is a read-write reference to an expanded + * array object; in that case the expanded array is updated in-place. + * + * For one-dimensional arrays only, we allow the array to be extended + * by assigning to a position outside the existing subscript range; any + * positions between the existing elements and the new one are set to NULLs. + * (XXX TODO: allow a corresponding behavior for multidimensional arrays) + * + * NOTE: For assignments, we throw an error for invalid subscripts etc, + * rather than returning a NULL as the fetch operations do. + */ +Datum +array_set_element(Datum arraydatum, + int nSubscripts, + int *indx, + Datum dataValue, + bool isNull, + int arraytyplen, + int elmlen, + bool elmbyval, + char elmalign) +{ + ArrayType *array; + ArrayType *newarray; + int i, + ndim, + dim[MAXDIM], + lb[MAXDIM], + offset; + char *elt_ptr; + bool newhasnulls; + bits8 *oldnullbitmap; + int oldnitems, + newnitems, + olddatasize, + newsize, + olditemlen, + newitemlen, + overheadlen, + oldoverheadlen, + addedbefore, + addedafter, + lenbefore, + lenafter; + + if (arraytyplen > 0) + { + /* + * fixed-length arrays -- these are assumed to be 1-d, 0-based. We + * cannot extend them, either. + */ + char *resultarray; + + if (nSubscripts != 1) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("wrong number of array subscripts"))); + + if (indx[0] < 0 || indx[0] >= arraytyplen / elmlen) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("array subscript out of range"))); + + if (isNull) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("cannot assign null value to an element of a fixed-length array"))); + + resultarray = (char *) palloc(arraytyplen); + memcpy(resultarray, DatumGetPointer(arraydatum), arraytyplen); + elt_ptr = (char *) resultarray + indx[0] * elmlen; + ArrayCastAndSet(dataValue, elmlen, elmbyval, elmalign, elt_ptr); + return PointerGetDatum(resultarray); + } + + if (nSubscripts <= 0 || nSubscripts > MAXDIM) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("wrong number of array subscripts"))); + + /* make sure item to be inserted is not toasted */ + if (elmlen == -1 && !isNull) + dataValue = PointerGetDatum(PG_DETOAST_DATUM(dataValue)); + + if (VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(arraydatum))) + { + /* expanded array: let's do this in a separate function */ + return array_set_element_expanded(arraydatum, + nSubscripts, + indx, + dataValue, + isNull, + arraytyplen, + elmlen, + elmbyval, + elmalign); + } + + /* detoast input array if necessary */ + array = DatumGetArrayTypeP(arraydatum); + + ndim = ARR_NDIM(array); + + /* + * if number of dims is zero, i.e. an empty array, create an array with + * nSubscripts dimensions, and set the lower bounds to the supplied + * subscripts + */ + if (ndim == 0) + { + Oid elmtype = ARR_ELEMTYPE(array); + + for (i = 0; i < nSubscripts; i++) + { + dim[i] = 1; + lb[i] = indx[i]; + } + + return PointerGetDatum(construct_md_array(&dataValue, &isNull, + nSubscripts, dim, lb, + elmtype, + elmlen, elmbyval, elmalign)); + } + + if (ndim != nSubscripts) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("wrong number of array subscripts"))); + + /* copy dim/lb since we may modify them */ + memcpy(dim, ARR_DIMS(array), ndim * sizeof(int)); + memcpy(lb, ARR_LBOUND(array), ndim * sizeof(int)); + + newhasnulls = (ARR_HASNULL(array) || isNull); + addedbefore = addedafter = 0; + + /* + * Check subscripts. We assume the existing subscripts passed + * ArrayCheckBounds, so that dim[i] + lb[i] can be computed without + * overflow. But we must beware of other overflows in our calculations of + * new dim[] values. + */ + if (ndim == 1) + { + if (indx[0] < lb[0]) + { + /* addedbefore = lb[0] - indx[0]; */ + /* dim[0] += addedbefore; */ + if (pg_sub_s32_overflow(lb[0], indx[0], &addedbefore) || + pg_add_s32_overflow(dim[0], addedbefore, &dim[0])) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("array size exceeds the maximum allowed (%d)", + (int) MaxArraySize))); + lb[0] = indx[0]; + if (addedbefore > 1) + newhasnulls = true; /* will insert nulls */ + } + if (indx[0] >= (dim[0] + lb[0])) + { + /* addedafter = indx[0] - (dim[0] + lb[0]) + 1; */ + /* dim[0] += addedafter; */ + if (pg_sub_s32_overflow(indx[0], dim[0] + lb[0], &addedafter) || + pg_add_s32_overflow(addedafter, 1, &addedafter) || + pg_add_s32_overflow(dim[0], addedafter, &dim[0])) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("array size exceeds the maximum allowed (%d)", + (int) MaxArraySize))); + if (addedafter > 1) + newhasnulls = true; /* will insert nulls */ + } + } + else + { + /* + * XXX currently we do not support extending multi-dimensional arrays + * during assignment + */ + for (i = 0; i < ndim; i++) + { + if (indx[i] < lb[i] || + indx[i] >= (dim[i] + lb[i])) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("array subscript out of range"))); + } + } + + /* This checks for overflow of the array dimensions */ + newnitems = ArrayGetNItems(ndim, dim); + ArrayCheckBounds(ndim, dim, lb); + + /* + * Compute sizes of items and areas to copy + */ + if (newhasnulls) + overheadlen = ARR_OVERHEAD_WITHNULLS(ndim, newnitems); + else + overheadlen = ARR_OVERHEAD_NONULLS(ndim); + oldnitems = ArrayGetNItems(ndim, ARR_DIMS(array)); + oldnullbitmap = ARR_NULLBITMAP(array); + oldoverheadlen = ARR_DATA_OFFSET(array); + olddatasize = ARR_SIZE(array) - oldoverheadlen; + if (addedbefore) + { + offset = 0; + lenbefore = 0; + olditemlen = 0; + lenafter = olddatasize; + } + else if (addedafter) + { + offset = oldnitems; + lenbefore = olddatasize; + olditemlen = 0; + lenafter = 0; + } + else + { + offset = ArrayGetOffset(nSubscripts, dim, lb, indx); + elt_ptr = array_seek(ARR_DATA_PTR(array), 0, oldnullbitmap, offset, + elmlen, elmbyval, elmalign); + lenbefore = (int) (elt_ptr - ARR_DATA_PTR(array)); + if (array_get_isnull(oldnullbitmap, offset)) + olditemlen = 0; + else + { + olditemlen = att_addlength_pointer(0, elmlen, elt_ptr); + olditemlen = att_align_nominal(olditemlen, elmalign); + } + lenafter = (int) (olddatasize - lenbefore - olditemlen); + } + + if (isNull) + newitemlen = 0; + else + { + newitemlen = att_addlength_datum(0, elmlen, dataValue); + newitemlen = att_align_nominal(newitemlen, elmalign); + } + + newsize = overheadlen + lenbefore + newitemlen + lenafter; + + /* + * OK, create the new array and fill in header/dimensions + */ + newarray = (ArrayType *) palloc0(newsize); + SET_VARSIZE(newarray, newsize); + newarray->ndim = ndim; + newarray->dataoffset = newhasnulls ? overheadlen : 0; + newarray->elemtype = ARR_ELEMTYPE(array); + memcpy(ARR_DIMS(newarray), dim, ndim * sizeof(int)); + memcpy(ARR_LBOUND(newarray), lb, ndim * sizeof(int)); + + /* + * Fill in data + */ + memcpy((char *) newarray + overheadlen, + (char *) array + oldoverheadlen, + lenbefore); + if (!isNull) + ArrayCastAndSet(dataValue, elmlen, elmbyval, elmalign, + (char *) newarray + overheadlen + lenbefore); + memcpy((char *) newarray + overheadlen + lenbefore + newitemlen, + (char *) array + oldoverheadlen + lenbefore + olditemlen, + lenafter); + + /* + * Fill in nulls bitmap if needed + * + * Note: it's possible we just replaced the last NULL with a non-NULL, and + * could get rid of the bitmap. Seems not worth testing for though. + */ + if (newhasnulls) + { + bits8 *newnullbitmap = ARR_NULLBITMAP(newarray); + + /* palloc0 above already marked any inserted positions as nulls */ + /* Fix the inserted value */ + if (addedafter) + array_set_isnull(newnullbitmap, newnitems - 1, isNull); + else + array_set_isnull(newnullbitmap, offset, isNull); + /* Fix the copied range(s) */ + if (addedbefore) + array_bitmap_copy(newnullbitmap, addedbefore, + oldnullbitmap, 0, + oldnitems); + else + { + array_bitmap_copy(newnullbitmap, 0, + oldnullbitmap, 0, + offset); + if (addedafter == 0) + array_bitmap_copy(newnullbitmap, offset + 1, + oldnullbitmap, offset + 1, + oldnitems - offset - 1); + } + } + + return PointerGetDatum(newarray); +} + +/* + * Implementation of array_set_element() for an expanded array + * + * Note: as with any operation on a read/write expanded object, we must + * take pains not to leave the object in a corrupt state if we fail partway + * through. + */ +static Datum +array_set_element_expanded(Datum arraydatum, + int nSubscripts, int *indx, + Datum dataValue, bool isNull, + int arraytyplen, + int elmlen, bool elmbyval, char elmalign) +{ + ExpandedArrayHeader *eah; + Datum *dvalues; + bool *dnulls; + int i, + ndim, + dim[MAXDIM], + lb[MAXDIM], + offset; + bool dimschanged, + newhasnulls; + int addedbefore, + addedafter; + char *oldValue; + + /* Convert to R/W object if not so already */ + eah = DatumGetExpandedArray(arraydatum); + + /* Sanity-check caller's info against object; we don't use it otherwise */ + Assert(arraytyplen == -1); + Assert(elmlen == eah->typlen); + Assert(elmbyval == eah->typbyval); + Assert(elmalign == eah->typalign); + + /* + * Copy dimension info into local storage. This allows us to modify the + * dimensions if needed, while not messing up the expanded value if we + * fail partway through. + */ + ndim = eah->ndims; + Assert(ndim >= 0 && ndim <= MAXDIM); + memcpy(dim, eah->dims, ndim * sizeof(int)); + memcpy(lb, eah->lbound, ndim * sizeof(int)); + dimschanged = false; + + /* + * if number of dims is zero, i.e. an empty array, create an array with + * nSubscripts dimensions, and set the lower bounds to the supplied + * subscripts. + */ + if (ndim == 0) + { + /* + * Allocate adequate space for new dimension info. This is harmless + * if we fail later. + */ + Assert(nSubscripts > 0 && nSubscripts <= MAXDIM); + eah->dims = (int *) MemoryContextAllocZero(eah->hdr.eoh_context, + nSubscripts * sizeof(int)); + eah->lbound = (int *) MemoryContextAllocZero(eah->hdr.eoh_context, + nSubscripts * sizeof(int)); + + /* Update local copies of dimension info */ + ndim = nSubscripts; + for (i = 0; i < nSubscripts; i++) + { + dim[i] = 0; + lb[i] = indx[i]; + } + dimschanged = true; + } + else if (ndim != nSubscripts) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("wrong number of array subscripts"))); + + /* + * Deconstruct array if we didn't already. (Someday maybe add a special + * case path for fixed-length, no-nulls cases, where we can overwrite an + * element in place without ever deconstructing. But today is not that + * day.) + */ + deconstruct_expanded_array(eah); + + /* + * Copy new element into array's context, if needed (we assume it's + * already detoasted, so no junk should be created). Doing this before + * we've made any significant changes ensures that our behavior is sane + * even when the source is a reference to some element of this same array. + * If we fail further down, this memory is leaked, but that's reasonably + * harmless. + */ + if (!eah->typbyval && !isNull) + { + MemoryContext oldcxt = MemoryContextSwitchTo(eah->hdr.eoh_context); + + dataValue = datumCopy(dataValue, false, eah->typlen); + MemoryContextSwitchTo(oldcxt); + } + + dvalues = eah->dvalues; + dnulls = eah->dnulls; + + newhasnulls = ((dnulls != NULL) || isNull); + addedbefore = addedafter = 0; + + /* + * Check subscripts (this logic must match array_set_element). We assume + * the existing subscripts passed ArrayCheckBounds, so that dim[i] + lb[i] + * can be computed without overflow. But we must beware of other + * overflows in our calculations of new dim[] values. + */ + if (ndim == 1) + { + if (indx[0] < lb[0]) + { + /* addedbefore = lb[0] - indx[0]; */ + /* dim[0] += addedbefore; */ + if (pg_sub_s32_overflow(lb[0], indx[0], &addedbefore) || + pg_add_s32_overflow(dim[0], addedbefore, &dim[0])) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("array size exceeds the maximum allowed (%d)", + (int) MaxArraySize))); + lb[0] = indx[0]; + dimschanged = true; + if (addedbefore > 1) + newhasnulls = true; /* will insert nulls */ + } + if (indx[0] >= (dim[0] + lb[0])) + { + /* addedafter = indx[0] - (dim[0] + lb[0]) + 1; */ + /* dim[0] += addedafter; */ + if (pg_sub_s32_overflow(indx[0], dim[0] + lb[0], &addedafter) || + pg_add_s32_overflow(addedafter, 1, &addedafter) || + pg_add_s32_overflow(dim[0], addedafter, &dim[0])) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("array size exceeds the maximum allowed (%d)", + (int) MaxArraySize))); + dimschanged = true; + if (addedafter > 1) + newhasnulls = true; /* will insert nulls */ + } + } + else + { + /* + * XXX currently we do not support extending multi-dimensional arrays + * during assignment + */ + for (i = 0; i < ndim; i++) + { + if (indx[i] < lb[i] || + indx[i] >= (dim[i] + lb[i])) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("array subscript out of range"))); + } + } + + /* Check for overflow of the array dimensions */ + if (dimschanged) + { + (void) ArrayGetNItems(ndim, dim); + ArrayCheckBounds(ndim, dim, lb); + } + + /* Now we can calculate linear offset of target item in array */ + offset = ArrayGetOffset(nSubscripts, dim, lb, indx); + + /* Physically enlarge existing dvalues/dnulls arrays if needed */ + if (dim[0] > eah->dvalueslen) + { + /* We want some extra space if we're enlarging */ + int newlen = dim[0] + dim[0] / 8; + + newlen = Max(newlen, dim[0]); /* integer overflow guard */ + eah->dvalues = dvalues = (Datum *) + repalloc(dvalues, newlen * sizeof(Datum)); + if (dnulls) + eah->dnulls = dnulls = (bool *) + repalloc(dnulls, newlen * sizeof(bool)); + eah->dvalueslen = newlen; + } + + /* + * If we need a nulls bitmap and don't already have one, create it, being + * sure to mark all existing entries as not null. + */ + if (newhasnulls && dnulls == NULL) + eah->dnulls = dnulls = (bool *) + MemoryContextAllocZero(eah->hdr.eoh_context, + eah->dvalueslen * sizeof(bool)); + + /* + * We now have all the needed space allocated, so we're ready to make + * irreversible changes. Be very wary of allowing failure below here. + */ + + /* Flattened value will no longer represent array accurately */ + eah->fvalue = NULL; + /* And we don't know the flattened size either */ + eah->flat_size = 0; + + /* Update dimensionality info if needed */ + if (dimschanged) + { + eah->ndims = ndim; + memcpy(eah->dims, dim, ndim * sizeof(int)); + memcpy(eah->lbound, lb, ndim * sizeof(int)); + } + + /* Reposition items if needed, and fill addedbefore items with nulls */ + if (addedbefore > 0) + { + memmove(dvalues + addedbefore, dvalues, eah->nelems * sizeof(Datum)); + for (i = 0; i < addedbefore; i++) + dvalues[i] = (Datum) 0; + if (dnulls) + { + memmove(dnulls + addedbefore, dnulls, eah->nelems * sizeof(bool)); + for (i = 0; i < addedbefore; i++) + dnulls[i] = true; + } + eah->nelems += addedbefore; + } + + /* fill addedafter items with nulls */ + if (addedafter > 0) + { + for (i = 0; i < addedafter; i++) + dvalues[eah->nelems + i] = (Datum) 0; + if (dnulls) + { + for (i = 0; i < addedafter; i++) + dnulls[eah->nelems + i] = true; + } + eah->nelems += addedafter; + } + + /* Grab old element value for pfree'ing, if needed. */ + if (!eah->typbyval && (dnulls == NULL || !dnulls[offset])) + oldValue = (char *) DatumGetPointer(dvalues[offset]); + else + oldValue = NULL; + + /* And finally we can insert the new element. */ + dvalues[offset] = dataValue; + if (dnulls) + dnulls[offset] = isNull; + + /* + * Free old element if needed; this keeps repeated element replacements + * from bloating the array's storage. If the pfree somehow fails, it + * won't corrupt the array. + */ + if (oldValue) + { + /* Don't try to pfree a part of the original flat array */ + if (oldValue < eah->fstartptr || oldValue >= eah->fendptr) + pfree(oldValue); + } + + /* Done, return standard TOAST pointer for object */ + return EOHPGetRWDatum(&eah->hdr); +} + +/* + * array_set_slice : + * This routine sets the value of a range of array locations (specified + * by upper and lower subscript values) to new values passed as + * another array. + * + * This handles both ordinary varlena arrays and fixed-length arrays. + * + * Inputs: + * arraydatum: the initial array object (mustn't be NULL) + * nSubscripts: number of subscripts supplied (must be same for upper/lower) + * upperIndx[]: the upper subscript values + * lowerIndx[]: the lower subscript values + * upperProvided[]: true for provided upper subscript values + * lowerProvided[]: true for provided lower subscript values + * srcArrayDatum: the source for the inserted values + * isNull: indicates whether srcArrayDatum is NULL + * arraytyplen: pg_type.typlen for the array type + * elmlen: pg_type.typlen for the array's element type + * elmbyval: pg_type.typbyval for the array's element type + * elmalign: pg_type.typalign for the array's element type + * + * Result: + * A new array is returned, just like the old except for the + * modified range. The original array object is not changed. + * + * Omitted upper and lower subscript values are replaced by the corresponding + * array bound. + * + * For one-dimensional arrays only, we allow the array to be extended + * by assigning to positions outside the existing subscript range; any + * positions between the existing elements and the new ones are set to NULLs. + * (XXX TODO: allow a corresponding behavior for multidimensional arrays) + * + * NOTE: we assume it is OK to scribble on the provided index arrays + * lowerIndx[] and upperIndx[]; also, these arrays must be of size MAXDIM + * even when nSubscripts is less. These are generally just temporaries. + * + * NOTE: For assignments, we throw an error for silly subscripts etc, + * rather than returning a NULL or empty array as the fetch operations do. + */ +Datum +array_set_slice(Datum arraydatum, + int nSubscripts, + int *upperIndx, + int *lowerIndx, + bool *upperProvided, + bool *lowerProvided, + Datum srcArrayDatum, + bool isNull, + int arraytyplen, + int elmlen, + bool elmbyval, + char elmalign) +{ + ArrayType *array; + ArrayType *srcArray; + ArrayType *newarray; + int i, + ndim, + dim[MAXDIM], + lb[MAXDIM], + span[MAXDIM]; + bool newhasnulls; + int nitems, + nsrcitems, + olddatasize, + newsize, + olditemsize, + newitemsize, + overheadlen, + oldoverheadlen, + addedbefore, + addedafter, + lenbefore, + lenafter, + itemsbefore, + itemsafter, + nolditems; + + /* Currently, assignment from a NULL source array is a no-op */ + if (isNull) + return arraydatum; + + if (arraytyplen > 0) + { + /* + * fixed-length arrays -- not got round to doing this... + */ + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("updates on slices of fixed-length arrays not implemented"))); + } + + /* detoast arrays if necessary */ + array = DatumGetArrayTypeP(arraydatum); + srcArray = DatumGetArrayTypeP(srcArrayDatum); + + /* note: we assume srcArray contains no toasted elements */ + + ndim = ARR_NDIM(array); + + /* + * if number of dims is zero, i.e. an empty array, create an array with + * nSubscripts dimensions, and set the upper and lower bounds to the + * supplied subscripts + */ + if (ndim == 0) + { + Datum *dvalues; + bool *dnulls; + int nelems; + Oid elmtype = ARR_ELEMTYPE(array); + + deconstruct_array(srcArray, elmtype, elmlen, elmbyval, elmalign, + &dvalues, &dnulls, &nelems); + + for (i = 0; i < nSubscripts; i++) + { + if (!upperProvided[i] || !lowerProvided[i]) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("array slice subscript must provide both boundaries"), + errdetail("When assigning to a slice of an empty array value," + " slice boundaries must be fully specified."))); + + dim[i] = 1 + upperIndx[i] - lowerIndx[i]; + lb[i] = lowerIndx[i]; + } + + /* complain if too few source items; we ignore extras, however */ + if (nelems < ArrayGetNItems(nSubscripts, dim)) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("source array too small"))); + + return PointerGetDatum(construct_md_array(dvalues, dnulls, nSubscripts, + dim, lb, elmtype, + elmlen, elmbyval, elmalign)); + } + + if (ndim < nSubscripts || ndim <= 0 || ndim > MAXDIM) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("wrong number of array subscripts"))); + + /* copy dim/lb since we may modify them */ + memcpy(dim, ARR_DIMS(array), ndim * sizeof(int)); + memcpy(lb, ARR_LBOUND(array), ndim * sizeof(int)); + + newhasnulls = (ARR_HASNULL(array) || ARR_HASNULL(srcArray)); + addedbefore = addedafter = 0; + + /* + * Check subscripts. We assume the existing subscripts passed + * ArrayCheckBounds, so that dim[i] + lb[i] can be computed without + * overflow. But we must beware of other overflows in our calculations of + * new dim[] values. + */ + if (ndim == 1) + { + Assert(nSubscripts == 1); + if (!lowerProvided[0]) + lowerIndx[0] = lb[0]; + if (!upperProvided[0]) + upperIndx[0] = dim[0] + lb[0] - 1; + if (lowerIndx[0] > upperIndx[0]) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("upper bound cannot be less than lower bound"))); + if (lowerIndx[0] < lb[0]) + { + /* addedbefore = lb[0] - lowerIndx[0]; */ + /* dim[0] += addedbefore; */ + if (pg_sub_s32_overflow(lb[0], lowerIndx[0], &addedbefore) || + pg_add_s32_overflow(dim[0], addedbefore, &dim[0])) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("array size exceeds the maximum allowed (%d)", + (int) MaxArraySize))); + lb[0] = lowerIndx[0]; + if (addedbefore > 1) + newhasnulls = true; /* will insert nulls */ + } + if (upperIndx[0] >= (dim[0] + lb[0])) + { + /* addedafter = upperIndx[0] - (dim[0] + lb[0]) + 1; */ + /* dim[0] += addedafter; */ + if (pg_sub_s32_overflow(upperIndx[0], dim[0] + lb[0], &addedafter) || + pg_add_s32_overflow(addedafter, 1, &addedafter) || + pg_add_s32_overflow(dim[0], addedafter, &dim[0])) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("array size exceeds the maximum allowed (%d)", + (int) MaxArraySize))); + if (addedafter > 1) + newhasnulls = true; /* will insert nulls */ + } + } + else + { + /* + * XXX currently we do not support extending multi-dimensional arrays + * during assignment + */ + for (i = 0; i < nSubscripts; i++) + { + if (!lowerProvided[i]) + lowerIndx[i] = lb[i]; + if (!upperProvided[i]) + upperIndx[i] = dim[i] + lb[i] - 1; + if (lowerIndx[i] > upperIndx[i]) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("upper bound cannot be less than lower bound"))); + if (lowerIndx[i] < lb[i] || + upperIndx[i] >= (dim[i] + lb[i])) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("array subscript out of range"))); + } + /* fill any missing subscript positions with full array range */ + for (; i < ndim; i++) + { + lowerIndx[i] = lb[i]; + upperIndx[i] = dim[i] + lb[i] - 1; + if (lowerIndx[i] > upperIndx[i]) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("upper bound cannot be less than lower bound"))); + } + } + + /* Do this mainly to check for overflow */ + nitems = ArrayGetNItems(ndim, dim); + ArrayCheckBounds(ndim, dim, lb); + + /* + * Make sure source array has enough entries. Note we ignore the shape of + * the source array and just read entries serially. + */ + mda_get_range(ndim, span, lowerIndx, upperIndx); + nsrcitems = ArrayGetNItems(ndim, span); + if (nsrcitems > ArrayGetNItems(ARR_NDIM(srcArray), ARR_DIMS(srcArray))) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("source array too small"))); + + /* + * Compute space occupied by new entries, space occupied by replaced + * entries, and required space for new array. + */ + if (newhasnulls) + overheadlen = ARR_OVERHEAD_WITHNULLS(ndim, nitems); + else + overheadlen = ARR_OVERHEAD_NONULLS(ndim); + newitemsize = array_nelems_size(ARR_DATA_PTR(srcArray), 0, + ARR_NULLBITMAP(srcArray), nsrcitems, + elmlen, elmbyval, elmalign); + oldoverheadlen = ARR_DATA_OFFSET(array); + olddatasize = ARR_SIZE(array) - oldoverheadlen; + if (ndim > 1) + { + /* + * here we do not need to cope with extension of the array; it would + * be a lot more complicated if we had to do so... + */ + olditemsize = array_slice_size(ARR_DATA_PTR(array), + ARR_NULLBITMAP(array), + ndim, dim, lb, + lowerIndx, upperIndx, + elmlen, elmbyval, elmalign); + lenbefore = lenafter = 0; /* keep compiler quiet */ + itemsbefore = itemsafter = nolditems = 0; + } + else + { + /* + * here we must allow for possibility of slice larger than orig array + * and/or not adjacent to orig array subscripts + */ + int oldlb = ARR_LBOUND(array)[0]; + int oldub = oldlb + ARR_DIMS(array)[0] - 1; + int slicelb = Max(oldlb, lowerIndx[0]); + int sliceub = Min(oldub, upperIndx[0]); + char *oldarraydata = ARR_DATA_PTR(array); + bits8 *oldarraybitmap = ARR_NULLBITMAP(array); + + /* count/size of old array entries that will go before the slice */ + itemsbefore = Min(slicelb, oldub + 1) - oldlb; + lenbefore = array_nelems_size(oldarraydata, 0, oldarraybitmap, + itemsbefore, + elmlen, elmbyval, elmalign); + /* count/size of old array entries that will be replaced by slice */ + if (slicelb > sliceub) + { + nolditems = 0; + olditemsize = 0; + } + else + { + nolditems = sliceub - slicelb + 1; + olditemsize = array_nelems_size(oldarraydata + lenbefore, + itemsbefore, oldarraybitmap, + nolditems, + elmlen, elmbyval, elmalign); + } + /* count/size of old array entries that will go after the slice */ + itemsafter = oldub + 1 - Max(sliceub + 1, oldlb); + lenafter = olddatasize - lenbefore - olditemsize; + } + + newsize = overheadlen + olddatasize - olditemsize + newitemsize; + + newarray = (ArrayType *) palloc0(newsize); + SET_VARSIZE(newarray, newsize); + newarray->ndim = ndim; + newarray->dataoffset = newhasnulls ? overheadlen : 0; + newarray->elemtype = ARR_ELEMTYPE(array); + memcpy(ARR_DIMS(newarray), dim, ndim * sizeof(int)); + memcpy(ARR_LBOUND(newarray), lb, ndim * sizeof(int)); + + if (ndim > 1) + { + /* + * here we do not need to cope with extension of the array; it would + * be a lot more complicated if we had to do so... + */ + array_insert_slice(newarray, array, srcArray, + ndim, dim, lb, + lowerIndx, upperIndx, + elmlen, elmbyval, elmalign); + } + else + { + /* fill in data */ + memcpy((char *) newarray + overheadlen, + (char *) array + oldoverheadlen, + lenbefore); + memcpy((char *) newarray + overheadlen + lenbefore, + ARR_DATA_PTR(srcArray), + newitemsize); + memcpy((char *) newarray + overheadlen + lenbefore + newitemsize, + (char *) array + oldoverheadlen + lenbefore + olditemsize, + lenafter); + /* fill in nulls bitmap if needed */ + if (newhasnulls) + { + bits8 *newnullbitmap = ARR_NULLBITMAP(newarray); + bits8 *oldnullbitmap = ARR_NULLBITMAP(array); + + /* palloc0 above already marked any inserted positions as nulls */ + array_bitmap_copy(newnullbitmap, addedbefore, + oldnullbitmap, 0, + itemsbefore); + array_bitmap_copy(newnullbitmap, lowerIndx[0] - lb[0], + ARR_NULLBITMAP(srcArray), 0, + nsrcitems); + array_bitmap_copy(newnullbitmap, addedbefore + itemsbefore + nolditems, + oldnullbitmap, itemsbefore + nolditems, + itemsafter); + } + } + + return PointerGetDatum(newarray); +} + +/* + * array_ref : backwards compatibility wrapper for array_get_element + * + * This only works for detoasted/flattened varlena arrays, since the array + * argument is declared as "ArrayType *". However there's enough code like + * that to justify preserving this API. + */ +Datum +array_ref(ArrayType *array, int nSubscripts, int *indx, + int arraytyplen, int elmlen, bool elmbyval, char elmalign, + bool *isNull) +{ + return array_get_element(PointerGetDatum(array), nSubscripts, indx, + arraytyplen, elmlen, elmbyval, elmalign, + isNull); +} + +/* + * array_set : backwards compatibility wrapper for array_set_element + * + * This only works for detoasted/flattened varlena arrays, since the array + * argument and result are declared as "ArrayType *". However there's enough + * code like that to justify preserving this API. + */ +ArrayType * +array_set(ArrayType *array, int nSubscripts, int *indx, + Datum dataValue, bool isNull, + int arraytyplen, int elmlen, bool elmbyval, char elmalign) +{ + return DatumGetArrayTypeP(array_set_element(PointerGetDatum(array), + nSubscripts, indx, + dataValue, isNull, + arraytyplen, + elmlen, elmbyval, elmalign)); +} + +/* + * array_map() + * + * Map an array through an arbitrary expression. Return a new array with + * the same dimensions and each source element transformed by the given, + * already-compiled expression. Each source element is placed in the + * innermost_caseval/innermost_casenull fields of the ExprState. + * + * Parameters are: + * * arrayd: Datum representing array argument. + * * exprstate: ExprState representing the per-element transformation. + * * econtext: context for expression evaluation. + * * retType: OID of element type of output array. This must be the same as, + * or binary-compatible with, the result type of the expression. It might + * be different from the input array's element type. + * * amstate: workspace for array_map. Must be zeroed by caller before + * first call, and not touched after that. + * + * It is legitimate to pass a freshly-zeroed ArrayMapState on each call, + * but better performance can be had if the state can be preserved across + * a series of calls. + * + * NB: caller must assure that input array is not NULL. NULL elements in + * the array are OK however. + * NB: caller should be running in econtext's per-tuple memory context. + */ +Datum +array_map(Datum arrayd, + ExprState *exprstate, ExprContext *econtext, + Oid retType, ArrayMapState *amstate) +{ + AnyArrayType *v = DatumGetAnyArrayP(arrayd); + ArrayType *result; + Datum *values; + bool *nulls; + int *dim; + int ndim; + int nitems; + int i; + int32 nbytes = 0; + int32 dataoffset; + bool hasnulls; + Oid inpType; + int inp_typlen; + bool inp_typbyval; + char inp_typalign; + int typlen; + bool typbyval; + char typalign; + array_iter iter; + ArrayMetaState *inp_extra; + ArrayMetaState *ret_extra; + Datum *transform_source = exprstate->innermost_caseval; + bool *transform_source_isnull = exprstate->innermost_casenull; + + inpType = AARR_ELEMTYPE(v); + ndim = AARR_NDIM(v); + dim = AARR_DIMS(v); + nitems = ArrayGetNItems(ndim, dim); + + /* Check for empty array */ + if (nitems <= 0) + { + /* Return empty array */ + return PointerGetDatum(construct_empty_array(retType)); + } + + /* + * We arrange to look up info about input and return element types only + * once per series of calls, assuming the element type doesn't change + * underneath us. + */ + inp_extra = &amstate->inp_extra; + ret_extra = &amstate->ret_extra; + + if (inp_extra->element_type != inpType) + { + get_typlenbyvalalign(inpType, + &inp_extra->typlen, + &inp_extra->typbyval, + &inp_extra->typalign); + inp_extra->element_type = inpType; + } + inp_typlen = inp_extra->typlen; + inp_typbyval = inp_extra->typbyval; + inp_typalign = inp_extra->typalign; + + if (ret_extra->element_type != retType) + { + get_typlenbyvalalign(retType, + &ret_extra->typlen, + &ret_extra->typbyval, + &ret_extra->typalign); + ret_extra->element_type = retType; + } + typlen = ret_extra->typlen; + typbyval = ret_extra->typbyval; + typalign = ret_extra->typalign; + + /* Allocate temporary arrays for new values */ + values = (Datum *) palloc(nitems * sizeof(Datum)); + nulls = (bool *) palloc(nitems * sizeof(bool)); + + /* Loop over source data */ + array_iter_setup(&iter, v); + hasnulls = false; + + for (i = 0; i < nitems; i++) + { + /* Get source element, checking for NULL */ + *transform_source = + array_iter_next(&iter, transform_source_isnull, i, + inp_typlen, inp_typbyval, inp_typalign); + + /* Apply the given expression to source element */ + values[i] = ExecEvalExpr(exprstate, econtext, &nulls[i]); + + if (nulls[i]) + hasnulls = true; + else + { + /* Ensure data is not toasted */ + if (typlen == -1) + values[i] = PointerGetDatum(PG_DETOAST_DATUM(values[i])); + /* Update total result size */ + nbytes = att_addlength_datum(nbytes, typlen, values[i]); + nbytes = att_align_nominal(nbytes, typalign); + /* check for overflow of total request */ + if (!AllocSizeIsValid(nbytes)) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("array size exceeds the maximum allowed (%d)", + (int) MaxAllocSize))); + } + } + + /* Allocate and fill the result array */ + if (hasnulls) + { + dataoffset = ARR_OVERHEAD_WITHNULLS(ndim, nitems); + nbytes += dataoffset; + } + else + { + dataoffset = 0; /* marker for no null bitmap */ + nbytes += ARR_OVERHEAD_NONULLS(ndim); + } + result = (ArrayType *) palloc0(nbytes); + SET_VARSIZE(result, nbytes); + result->ndim = ndim; + result->dataoffset = dataoffset; + result->elemtype = retType; + memcpy(ARR_DIMS(result), AARR_DIMS(v), ndim * sizeof(int)); + memcpy(ARR_LBOUND(result), AARR_LBOUND(v), ndim * sizeof(int)); + + CopyArrayEls(result, + values, nulls, nitems, + typlen, typbyval, typalign, + false); + + /* + * Note: do not risk trying to pfree the results of the called expression + */ + pfree(values); + pfree(nulls); + + return PointerGetDatum(result); +} + +/* + * construct_array --- simple method for constructing an array object + * + * elems: array of Datum items to become the array contents + * (NULL element values are not supported). + * nelems: number of items + * elmtype, elmlen, elmbyval, elmalign: info for the datatype of the items + * + * A palloc'd 1-D array object is constructed and returned. Note that + * elem values will be copied into the object even if pass-by-ref type. + * Also note the result will be 0-D not 1-D if nelems = 0. + * + * NOTE: it would be cleaner to look up the elmlen/elmbval/elmalign info + * from the system catalogs, given the elmtype. However, the caller is + * in a better position to cache this info across multiple uses, or even + * to hard-wire values if the element type is hard-wired. + */ +ArrayType * +construct_array(Datum *elems, int nelems, + Oid elmtype, + int elmlen, bool elmbyval, char elmalign) +{ + int dims[1]; + int lbs[1]; + + dims[0] = nelems; + lbs[0] = 1; + + return construct_md_array(elems, NULL, 1, dims, lbs, + elmtype, elmlen, elmbyval, elmalign); +} + +/* + * Like construct_array(), where elmtype must be a built-in type, and + * elmlen/elmbyval/elmalign is looked up from hardcoded data. This is often + * useful when manipulating arrays from/for system catalogs. + */ +ArrayType * +construct_array_builtin(Datum *elems, int nelems, Oid elmtype) +{ + int elmlen; + bool elmbyval; + char elmalign; + + switch (elmtype) + { + case CHAROID: + elmlen = 1; + elmbyval = true; + elmalign = TYPALIGN_CHAR; + break; + + case CSTRINGOID: + elmlen = -2; + elmbyval = false; + elmalign = TYPALIGN_CHAR; + break; + + case FLOAT4OID: + elmlen = sizeof(float4); + elmbyval = true; + elmalign = TYPALIGN_INT; + break; + + case INT2OID: + elmlen = sizeof(int16); + elmbyval = true; + elmalign = TYPALIGN_SHORT; + break; + + case INT4OID: + elmlen = sizeof(int32); + elmbyval = true; + elmalign = TYPALIGN_INT; + break; + + case INT8OID: + elmlen = sizeof(int64); + elmbyval = FLOAT8PASSBYVAL; + elmalign = TYPALIGN_DOUBLE; + break; + + case NAMEOID: + elmlen = NAMEDATALEN; + elmbyval = false; + elmalign = TYPALIGN_CHAR; + break; + + case OIDOID: + case REGTYPEOID: + elmlen = sizeof(Oid); + elmbyval = true; + elmalign = TYPALIGN_INT; + break; + + case TEXTOID: + elmlen = -1; + elmbyval = false; + elmalign = TYPALIGN_INT; + break; + + case TIDOID: + elmlen = sizeof(ItemPointerData); + elmbyval = false; + elmalign = TYPALIGN_SHORT; + break; + + default: + elog(ERROR, "type %u not supported by construct_array_builtin()", elmtype); + /* keep compiler quiet */ + elmlen = 0; + elmbyval = false; + elmalign = 0; + } + + return construct_array(elems, nelems, elmtype, elmlen, elmbyval, elmalign); +} + +/* + * construct_md_array --- simple method for constructing an array object + * with arbitrary dimensions and possible NULLs + * + * elems: array of Datum items to become the array contents + * nulls: array of is-null flags (can be NULL if no nulls) + * ndims: number of dimensions + * dims: integer array with size of each dimension + * lbs: integer array with lower bound of each dimension + * elmtype, elmlen, elmbyval, elmalign: info for the datatype of the items + * + * A palloc'd ndims-D array object is constructed and returned. Note that + * elem values will be copied into the object even if pass-by-ref type. + * Also note the result will be 0-D not ndims-D if any dims[i] = 0. + * + * NOTE: it would be cleaner to look up the elmlen/elmbval/elmalign info + * from the system catalogs, given the elmtype. However, the caller is + * in a better position to cache this info across multiple uses, or even + * to hard-wire values if the element type is hard-wired. + */ +ArrayType * +construct_md_array(Datum *elems, + bool *nulls, + int ndims, + int *dims, + int *lbs, + Oid elmtype, int elmlen, bool elmbyval, char elmalign) +{ + ArrayType *result; + bool hasnulls; + int32 nbytes; + int32 dataoffset; + int i; + int nelems; + + if (ndims < 0) /* we do allow zero-dimension arrays */ + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid number of dimensions: %d", ndims))); + if (ndims > MAXDIM) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)", + ndims, MAXDIM))); + + /* This checks for overflow of the array dimensions */ + nelems = ArrayGetNItems(ndims, dims); + ArrayCheckBounds(ndims, dims, lbs); + + /* if ndims <= 0 or any dims[i] == 0, return empty array */ + if (nelems <= 0) + return construct_empty_array(elmtype); + + /* compute required space */ + nbytes = 0; + hasnulls = false; + for (i = 0; i < nelems; i++) + { + if (nulls && nulls[i]) + { + hasnulls = true; + continue; + } + /* make sure data is not toasted */ + if (elmlen == -1) + elems[i] = PointerGetDatum(PG_DETOAST_DATUM(elems[i])); + nbytes = att_addlength_datum(nbytes, elmlen, elems[i]); + nbytes = att_align_nominal(nbytes, elmalign); + /* check for overflow of total request */ + if (!AllocSizeIsValid(nbytes)) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("array size exceeds the maximum allowed (%d)", + (int) MaxAllocSize))); + } + + /* Allocate and initialize result array */ + if (hasnulls) + { + dataoffset = ARR_OVERHEAD_WITHNULLS(ndims, nelems); + nbytes += dataoffset; + } + else + { + dataoffset = 0; /* marker for no null bitmap */ + nbytes += ARR_OVERHEAD_NONULLS(ndims); + } + result = (ArrayType *) palloc0(nbytes); + SET_VARSIZE(result, nbytes); + result->ndim = ndims; + result->dataoffset = dataoffset; + result->elemtype = elmtype; + memcpy(ARR_DIMS(result), dims, ndims * sizeof(int)); + memcpy(ARR_LBOUND(result), lbs, ndims * sizeof(int)); + + CopyArrayEls(result, + elems, nulls, nelems, + elmlen, elmbyval, elmalign, + false); + + return result; +} + +/* + * construct_empty_array --- make a zero-dimensional array of given type + */ +ArrayType * +construct_empty_array(Oid elmtype) +{ + ArrayType *result; + + result = (ArrayType *) palloc0(sizeof(ArrayType)); + SET_VARSIZE(result, sizeof(ArrayType)); + result->ndim = 0; + result->dataoffset = 0; + result->elemtype = elmtype; + return result; +} + +/* + * construct_empty_expanded_array: make an empty expanded array + * given only type information. (metacache can be NULL if not needed.) + */ +ExpandedArrayHeader * +construct_empty_expanded_array(Oid element_type, + MemoryContext parentcontext, + ArrayMetaState *metacache) +{ + ArrayType *array = construct_empty_array(element_type); + Datum d; + + d = expand_array(PointerGetDatum(array), parentcontext, metacache); + pfree(array); + return (ExpandedArrayHeader *) DatumGetEOHP(d); +} + +/* + * deconstruct_array --- simple method for extracting data from an array + * + * array: array object to examine (must not be NULL) + * elmtype, elmlen, elmbyval, elmalign: info for the datatype of the items + * elemsp: return value, set to point to palloc'd array of Datum values + * nullsp: return value, set to point to palloc'd array of isnull markers + * nelemsp: return value, set to number of extracted values + * + * The caller may pass nullsp == NULL if it does not support NULLs in the + * array. Note that this produces a very uninformative error message, + * so do it only in cases where a NULL is really not expected. + * + * If array elements are pass-by-ref data type, the returned Datums will + * be pointers into the array object. + * + * NOTE: it would be cleaner to look up the elmlen/elmbval/elmalign info + * from the system catalogs, given the elmtype. However, the caller is + * in a better position to cache this info across multiple uses, or even + * to hard-wire values if the element type is hard-wired. + */ +void +deconstruct_array(ArrayType *array, + Oid elmtype, + int elmlen, bool elmbyval, char elmalign, + Datum **elemsp, bool **nullsp, int *nelemsp) +{ + Datum *elems; + bool *nulls; + int nelems; + char *p; + bits8 *bitmap; + int bitmask; + int i; + + Assert(ARR_ELEMTYPE(array) == elmtype); + + nelems = ArrayGetNItems(ARR_NDIM(array), ARR_DIMS(array)); + *elemsp = elems = (Datum *) palloc(nelems * sizeof(Datum)); + if (nullsp) + *nullsp = nulls = (bool *) palloc0(nelems * sizeof(bool)); + else + nulls = NULL; + *nelemsp = nelems; + + p = ARR_DATA_PTR(array); + bitmap = ARR_NULLBITMAP(array); + bitmask = 1; + + for (i = 0; i < nelems; i++) + { + /* Get source element, checking for NULL */ + if (bitmap && (*bitmap & bitmask) == 0) + { + elems[i] = (Datum) 0; + if (nulls) + nulls[i] = true; + else + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("null array element not allowed in this context"))); + } + else + { + elems[i] = fetch_att(p, elmbyval, elmlen); + p = att_addlength_pointer(p, elmlen, p); + p = (char *) att_align_nominal(p, elmalign); + } + + /* advance bitmap pointer if any */ + if (bitmap) + { + bitmask <<= 1; + if (bitmask == 0x100) + { + bitmap++; + bitmask = 1; + } + } + } +} + +/* + * Like deconstruct_array(), where elmtype must be a built-in type, and + * elmlen/elmbyval/elmalign is looked up from hardcoded data. This is often + * useful when manipulating arrays from/for system catalogs. + */ +void +deconstruct_array_builtin(ArrayType *array, + Oid elmtype, + Datum **elemsp, bool **nullsp, int *nelemsp) +{ + int elmlen; + bool elmbyval; + char elmalign; + + switch (elmtype) + { + case CHAROID: + elmlen = 1; + elmbyval = true; + elmalign = TYPALIGN_CHAR; + break; + + case CSTRINGOID: + elmlen = -2; + elmbyval = false; + elmalign = TYPALIGN_CHAR; + break; + + case FLOAT8OID: + elmlen = sizeof(float8); + elmbyval = FLOAT8PASSBYVAL; + elmalign = TYPALIGN_DOUBLE; + break; + + case INT2OID: + elmlen = sizeof(int16); + elmbyval = true; + elmalign = TYPALIGN_SHORT; + break; + + case OIDOID: + elmlen = sizeof(Oid); + elmbyval = true; + elmalign = TYPALIGN_INT; + break; + + case TEXTOID: + elmlen = -1; + elmbyval = false; + elmalign = TYPALIGN_INT; + break; + + case TIDOID: + elmlen = sizeof(ItemPointerData); + elmbyval = false; + elmalign = TYPALIGN_SHORT; + break; + + default: + elog(ERROR, "type %u not supported by deconstruct_array_builtin()", elmtype); + /* keep compiler quiet */ + elmlen = 0; + elmbyval = false; + elmalign = 0; + } + + deconstruct_array(array, elmtype, elmlen, elmbyval, elmalign, elemsp, nullsp, nelemsp); +} + +/* + * array_contains_nulls --- detect whether an array has any null elements + * + * This gives an accurate answer, whereas testing ARR_HASNULL only tells + * if the array *might* contain a null. + */ +bool +array_contains_nulls(ArrayType *array) +{ + int nelems; + bits8 *bitmap; + int bitmask; + + /* Easy answer if there's no null bitmap */ + if (!ARR_HASNULL(array)) + return false; + + nelems = ArrayGetNItems(ARR_NDIM(array), ARR_DIMS(array)); + + bitmap = ARR_NULLBITMAP(array); + + /* check whole bytes of the bitmap byte-at-a-time */ + while (nelems >= 8) + { + if (*bitmap != 0xFF) + return true; + bitmap++; + nelems -= 8; + } + + /* check last partial byte */ + bitmask = 1; + while (nelems > 0) + { + if ((*bitmap & bitmask) == 0) + return true; + bitmask <<= 1; + nelems--; + } + + return false; +} + + +/* + * array_eq : + * compares two arrays for equality + * result : + * returns true if the arrays are equal, false otherwise. + * + * Note: we do not use array_cmp here, since equality may be meaningful in + * datatypes that don't have a total ordering (and hence no btree support). + */ +Datum +array_eq(PG_FUNCTION_ARGS) +{ + LOCAL_FCINFO(locfcinfo, 2); + AnyArrayType *array1 = PG_GETARG_ANY_ARRAY_P(0); + AnyArrayType *array2 = PG_GETARG_ANY_ARRAY_P(1); + Oid collation = PG_GET_COLLATION(); + int ndims1 = AARR_NDIM(array1); + int ndims2 = AARR_NDIM(array2); + int *dims1 = AARR_DIMS(array1); + int *dims2 = AARR_DIMS(array2); + int *lbs1 = AARR_LBOUND(array1); + int *lbs2 = AARR_LBOUND(array2); + Oid element_type = AARR_ELEMTYPE(array1); + bool result = true; + int nitems; + TypeCacheEntry *typentry; + int typlen; + bool typbyval; + char typalign; + array_iter it1; + array_iter it2; + int i; + + if (element_type != AARR_ELEMTYPE(array2)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare arrays of different element types"))); + + /* fast path if the arrays do not have the same dimensionality */ + if (ndims1 != ndims2 || + memcmp(dims1, dims2, ndims1 * sizeof(int)) != 0 || + memcmp(lbs1, lbs2, ndims1 * sizeof(int)) != 0) + result = false; + else + { + /* + * We arrange to look up the equality function only once per series of + * calls, assuming the element type doesn't change underneath us. The + * typcache is used so that we have no memory leakage when being used + * as an index support function. + */ + typentry = (TypeCacheEntry *) fcinfo->flinfo->fn_extra; + if (typentry == NULL || + typentry->type_id != element_type) + { + typentry = lookup_type_cache(element_type, + TYPECACHE_EQ_OPR_FINFO); + if (!OidIsValid(typentry->eq_opr_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify an equality operator for type %s", + format_type_be(element_type)))); + fcinfo->flinfo->fn_extra = (void *) typentry; + } + typlen = typentry->typlen; + typbyval = typentry->typbyval; + typalign = typentry->typalign; + + /* + * apply the operator to each pair of array elements. + */ + InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2, + collation, NULL, NULL); + + /* Loop over source data */ + nitems = ArrayGetNItems(ndims1, dims1); + array_iter_setup(&it1, array1); + array_iter_setup(&it2, array2); + + for (i = 0; i < nitems; i++) + { + Datum elt1; + Datum elt2; + bool isnull1; + bool isnull2; + bool oprresult; + + /* Get elements, checking for NULL */ + elt1 = array_iter_next(&it1, &isnull1, i, + typlen, typbyval, typalign); + elt2 = array_iter_next(&it2, &isnull2, i, + typlen, typbyval, typalign); + + /* + * We consider two NULLs equal; NULL and not-NULL are unequal. + */ + if (isnull1 && isnull2) + continue; + if (isnull1 || isnull2) + { + result = false; + break; + } + + /* + * Apply the operator to the element pair; treat NULL as false + */ + locfcinfo->args[0].value = elt1; + locfcinfo->args[0].isnull = false; + locfcinfo->args[1].value = elt2; + locfcinfo->args[1].isnull = false; + locfcinfo->isnull = false; + oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo)); + if (locfcinfo->isnull || !oprresult) + { + result = false; + break; + } + } + } + + /* Avoid leaking memory when handed toasted input. */ + AARR_FREE_IF_COPY(array1, 0); + AARR_FREE_IF_COPY(array2, 1); + + PG_RETURN_BOOL(result); +} + + +/*----------------------------------------------------------------------------- + * array-array bool operators: + * Given two arrays, iterate comparison operators + * over the array. Uses logic similar to text comparison + * functions, except element-by-element instead of + * character-by-character. + *---------------------------------------------------------------------------- + */ + +Datum +array_ne(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(!DatumGetBool(array_eq(fcinfo))); +} + +Datum +array_lt(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(array_cmp(fcinfo) < 0); +} + +Datum +array_gt(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(array_cmp(fcinfo) > 0); +} + +Datum +array_le(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(array_cmp(fcinfo) <= 0); +} + +Datum +array_ge(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(array_cmp(fcinfo) >= 0); +} + +Datum +btarraycmp(PG_FUNCTION_ARGS) +{ + PG_RETURN_INT32(array_cmp(fcinfo)); +} + +/* + * array_cmp() + * Internal comparison function for arrays. + * + * Returns -1, 0 or 1 + */ +static int +array_cmp(FunctionCallInfo fcinfo) +{ + LOCAL_FCINFO(locfcinfo, 2); + AnyArrayType *array1 = PG_GETARG_ANY_ARRAY_P(0); + AnyArrayType *array2 = PG_GETARG_ANY_ARRAY_P(1); + Oid collation = PG_GET_COLLATION(); + int ndims1 = AARR_NDIM(array1); + int ndims2 = AARR_NDIM(array2); + int *dims1 = AARR_DIMS(array1); + int *dims2 = AARR_DIMS(array2); + int nitems1 = ArrayGetNItems(ndims1, dims1); + int nitems2 = ArrayGetNItems(ndims2, dims2); + Oid element_type = AARR_ELEMTYPE(array1); + int result = 0; + TypeCacheEntry *typentry; + int typlen; + bool typbyval; + char typalign; + int min_nitems; + array_iter it1; + array_iter it2; + int i; + + if (element_type != AARR_ELEMTYPE(array2)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare arrays of different element types"))); + + /* + * We arrange to look up the comparison function only once per series of + * calls, assuming the element type doesn't change underneath us. The + * typcache is used so that we have no memory leakage when being used as + * an index support function. + */ + typentry = (TypeCacheEntry *) fcinfo->flinfo->fn_extra; + if (typentry == NULL || + typentry->type_id != element_type) + { + typentry = lookup_type_cache(element_type, + TYPECACHE_CMP_PROC_FINFO); + if (!OidIsValid(typentry->cmp_proc_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify a comparison function for type %s", + format_type_be(element_type)))); + fcinfo->flinfo->fn_extra = (void *) typentry; + } + typlen = typentry->typlen; + typbyval = typentry->typbyval; + typalign = typentry->typalign; + + /* + * apply the operator to each pair of array elements. + */ + InitFunctionCallInfoData(*locfcinfo, &typentry->cmp_proc_finfo, 2, + collation, NULL, NULL); + + /* Loop over source data */ + min_nitems = Min(nitems1, nitems2); + array_iter_setup(&it1, array1); + array_iter_setup(&it2, array2); + + for (i = 0; i < min_nitems; i++) + { + Datum elt1; + Datum elt2; + bool isnull1; + bool isnull2; + int32 cmpresult; + + /* Get elements, checking for NULL */ + elt1 = array_iter_next(&it1, &isnull1, i, typlen, typbyval, typalign); + elt2 = array_iter_next(&it2, &isnull2, i, typlen, typbyval, typalign); + + /* + * We consider two NULLs equal; NULL > not-NULL. + */ + if (isnull1 && isnull2) + continue; + if (isnull1) + { + /* arg1 is greater than arg2 */ + result = 1; + break; + } + if (isnull2) + { + /* arg1 is less than arg2 */ + result = -1; + break; + } + + /* Compare the pair of elements */ + locfcinfo->args[0].value = elt1; + locfcinfo->args[0].isnull = false; + locfcinfo->args[1].value = elt2; + locfcinfo->args[1].isnull = false; + cmpresult = DatumGetInt32(FunctionCallInvoke(locfcinfo)); + + /* We don't expect comparison support functions to return null */ + Assert(!locfcinfo->isnull); + + if (cmpresult == 0) + continue; /* equal */ + + if (cmpresult < 0) + { + /* arg1 is less than arg2 */ + result = -1; + break; + } + else + { + /* arg1 is greater than arg2 */ + result = 1; + break; + } + } + + /* + * If arrays contain same data (up to end of shorter one), apply + * additional rules to sort by dimensionality. The relative significance + * of the different bits of information is historical; mainly we just care + * that we don't say "equal" for arrays of different dimensionality. + */ + if (result == 0) + { + if (nitems1 != nitems2) + result = (nitems1 < nitems2) ? -1 : 1; + else if (ndims1 != ndims2) + result = (ndims1 < ndims2) ? -1 : 1; + else + { + for (i = 0; i < ndims1; i++) + { + if (dims1[i] != dims2[i]) + { + result = (dims1[i] < dims2[i]) ? -1 : 1; + break; + } + } + if (result == 0) + { + int *lbound1 = AARR_LBOUND(array1); + int *lbound2 = AARR_LBOUND(array2); + + for (i = 0; i < ndims1; i++) + { + if (lbound1[i] != lbound2[i]) + { + result = (lbound1[i] < lbound2[i]) ? -1 : 1; + break; + } + } + } + } + } + + /* Avoid leaking memory when handed toasted input. */ + AARR_FREE_IF_COPY(array1, 0); + AARR_FREE_IF_COPY(array2, 1); + + return result; +} + + +/*----------------------------------------------------------------------------- + * array hashing + * Hash the elements and combine the results. + *---------------------------------------------------------------------------- + */ + +Datum +hash_array(PG_FUNCTION_ARGS) +{ + LOCAL_FCINFO(locfcinfo, 1); + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0); + int ndims = AARR_NDIM(array); + int *dims = AARR_DIMS(array); + Oid element_type = AARR_ELEMTYPE(array); + uint32 result = 1; + int nitems; + TypeCacheEntry *typentry; + int typlen; + bool typbyval; + char typalign; + int i; + array_iter iter; + + /* + * We arrange to look up the hash function only once per series of calls, + * assuming the element type doesn't change underneath us. The typcache + * is used so that we have no memory leakage when being used as an index + * support function. + */ + typentry = (TypeCacheEntry *) fcinfo->flinfo->fn_extra; + if (typentry == NULL || + typentry->type_id != element_type) + { + typentry = lookup_type_cache(element_type, + TYPECACHE_HASH_PROC_FINFO); + if (!OidIsValid(typentry->hash_proc_finfo.fn_oid) && element_type != RECORDOID) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify a hash function for type %s", + format_type_be(element_type)))); + + /* + * The type cache doesn't believe that record is hashable (see + * cache_record_field_properties()), but since we're here, we're + * committed to hashing, so we can assume it does. Worst case, if any + * components of the record don't support hashing, we will fail at + * execution. + */ + if (element_type == RECORDOID) + { + MemoryContext oldcontext; + TypeCacheEntry *record_typentry; + + oldcontext = MemoryContextSwitchTo(fcinfo->flinfo->fn_mcxt); + + /* + * Make fake type cache entry structure. Note that we can't just + * modify typentry, since that points directly into the type + * cache. + */ + record_typentry = palloc0(sizeof(*record_typentry)); + record_typentry->type_id = element_type; + + /* fill in what we need below */ + record_typentry->typlen = typentry->typlen; + record_typentry->typbyval = typentry->typbyval; + record_typentry->typalign = typentry->typalign; + fmgr_info(F_HASH_RECORD, &record_typentry->hash_proc_finfo); + + MemoryContextSwitchTo(oldcontext); + + typentry = record_typentry; + } + + fcinfo->flinfo->fn_extra = (void *) typentry; + } + + typlen = typentry->typlen; + typbyval = typentry->typbyval; + typalign = typentry->typalign; + + /* + * apply the hash function to each array element. + */ + InitFunctionCallInfoData(*locfcinfo, &typentry->hash_proc_finfo, 1, + PG_GET_COLLATION(), NULL, NULL); + + /* Loop over source data */ + nitems = ArrayGetNItems(ndims, dims); + array_iter_setup(&iter, array); + + for (i = 0; i < nitems; i++) + { + Datum elt; + bool isnull; + uint32 elthash; + + /* Get element, checking for NULL */ + elt = array_iter_next(&iter, &isnull, i, typlen, typbyval, typalign); + + if (isnull) + { + /* Treat nulls as having hashvalue 0 */ + elthash = 0; + } + else + { + /* Apply the hash function */ + locfcinfo->args[0].value = elt; + locfcinfo->args[0].isnull = false; + elthash = DatumGetUInt32(FunctionCallInvoke(locfcinfo)); + /* We don't expect hash functions to return null */ + Assert(!locfcinfo->isnull); + } + + /* + * Combine hash values of successive elements by multiplying the + * current value by 31 and adding on the new element's hash value. + * + * The result is a sum in which each element's hash value is + * multiplied by a different power of 31. This is modulo 2^32 + * arithmetic, and the powers of 31 modulo 2^32 form a cyclic group of + * order 2^27. So for arrays of up to 2^27 elements, each element's + * hash value is multiplied by a different (odd) number, resulting in + * a good mixing of all the elements' hash values. + */ + result = (result << 5) - result + elthash; + } + + /* Avoid leaking memory when handed toasted input. */ + AARR_FREE_IF_COPY(array, 0); + + PG_RETURN_UINT32(result); +} + +/* + * Returns 64-bit value by hashing a value to a 64-bit value, with a seed. + * Otherwise, similar to hash_array. + */ +Datum +hash_array_extended(PG_FUNCTION_ARGS) +{ + LOCAL_FCINFO(locfcinfo, 2); + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0); + uint64 seed = PG_GETARG_INT64(1); + int ndims = AARR_NDIM(array); + int *dims = AARR_DIMS(array); + Oid element_type = AARR_ELEMTYPE(array); + uint64 result = 1; + int nitems; + TypeCacheEntry *typentry; + int typlen; + bool typbyval; + char typalign; + int i; + array_iter iter; + + typentry = (TypeCacheEntry *) fcinfo->flinfo->fn_extra; + if (typentry == NULL || + typentry->type_id != element_type) + { + typentry = lookup_type_cache(element_type, + TYPECACHE_HASH_EXTENDED_PROC_FINFO); + if (!OidIsValid(typentry->hash_extended_proc_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify an extended hash function for type %s", + format_type_be(element_type)))); + fcinfo->flinfo->fn_extra = (void *) typentry; + } + typlen = typentry->typlen; + typbyval = typentry->typbyval; + typalign = typentry->typalign; + + InitFunctionCallInfoData(*locfcinfo, &typentry->hash_extended_proc_finfo, 2, + PG_GET_COLLATION(), NULL, NULL); + + /* Loop over source data */ + nitems = ArrayGetNItems(ndims, dims); + array_iter_setup(&iter, array); + + for (i = 0; i < nitems; i++) + { + Datum elt; + bool isnull; + uint64 elthash; + + /* Get element, checking for NULL */ + elt = array_iter_next(&iter, &isnull, i, typlen, typbyval, typalign); + + if (isnull) + { + elthash = 0; + } + else + { + /* Apply the hash function */ + locfcinfo->args[0].value = elt; + locfcinfo->args[0].isnull = false; + locfcinfo->args[1].value = Int64GetDatum(seed); + locfcinfo->args[1].isnull = false; + elthash = DatumGetUInt64(FunctionCallInvoke(locfcinfo)); + /* We don't expect hash functions to return null */ + Assert(!locfcinfo->isnull); + } + + result = (result << 5) - result + elthash; + } + + AARR_FREE_IF_COPY(array, 0); + + PG_RETURN_UINT64(result); +} + + +/*----------------------------------------------------------------------------- + * array overlap/containment comparisons + * These use the same methods of comparing array elements as array_eq. + * We consider only the elements of the arrays, ignoring dimensionality. + *---------------------------------------------------------------------------- + */ + +/* + * array_contain_compare : + * compares two arrays for overlap/containment + * + * When matchall is true, return true if all members of array1 are in array2. + * When matchall is false, return true if any members of array1 are in array2. + */ +static bool +array_contain_compare(AnyArrayType *array1, AnyArrayType *array2, Oid collation, + bool matchall, void **fn_extra) +{ + LOCAL_FCINFO(locfcinfo, 2); + bool result = matchall; + Oid element_type = AARR_ELEMTYPE(array1); + TypeCacheEntry *typentry; + int nelems1; + Datum *values2; + bool *nulls2; + int nelems2; + int typlen; + bool typbyval; + char typalign; + int i; + int j; + array_iter it1; + + if (element_type != AARR_ELEMTYPE(array2)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare arrays of different element types"))); + + /* + * We arrange to look up the equality function only once per series of + * calls, assuming the element type doesn't change underneath us. The + * typcache is used so that we have no memory leakage when being used as + * an index support function. + */ + typentry = (TypeCacheEntry *) *fn_extra; + if (typentry == NULL || + typentry->type_id != element_type) + { + typentry = lookup_type_cache(element_type, + TYPECACHE_EQ_OPR_FINFO); + if (!OidIsValid(typentry->eq_opr_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify an equality operator for type %s", + format_type_be(element_type)))); + *fn_extra = (void *) typentry; + } + typlen = typentry->typlen; + typbyval = typentry->typbyval; + typalign = typentry->typalign; + + /* + * Since we probably will need to scan array2 multiple times, it's + * worthwhile to use deconstruct_array on it. We scan array1 the hard way + * however, since we very likely won't need to look at all of it. + */ + if (VARATT_IS_EXPANDED_HEADER(array2)) + { + /* This should be safe even if input is read-only */ + deconstruct_expanded_array(&(array2->xpn)); + values2 = array2->xpn.dvalues; + nulls2 = array2->xpn.dnulls; + nelems2 = array2->xpn.nelems; + } + else + deconstruct_array((ArrayType *) array2, + element_type, typlen, typbyval, typalign, + &values2, &nulls2, &nelems2); + + /* + * Apply the comparison operator to each pair of array elements. + */ + InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2, + collation, NULL, NULL); + + /* Loop over source data */ + nelems1 = ArrayGetNItems(AARR_NDIM(array1), AARR_DIMS(array1)); + array_iter_setup(&it1, array1); + + for (i = 0; i < nelems1; i++) + { + Datum elt1; + bool isnull1; + + /* Get element, checking for NULL */ + elt1 = array_iter_next(&it1, &isnull1, i, typlen, typbyval, typalign); + + /* + * We assume that the comparison operator is strict, so a NULL can't + * match anything. XXX this diverges from the "NULL=NULL" behavior of + * array_eq, should we act like that? + */ + if (isnull1) + { + if (matchall) + { + result = false; + break; + } + continue; + } + + for (j = 0; j < nelems2; j++) + { + Datum elt2 = values2[j]; + bool isnull2 = nulls2 ? nulls2[j] : false; + bool oprresult; + + if (isnull2) + continue; /* can't match */ + + /* + * Apply the operator to the element pair; treat NULL as false + */ + locfcinfo->args[0].value = elt1; + locfcinfo->args[0].isnull = false; + locfcinfo->args[1].value = elt2; + locfcinfo->args[1].isnull = false; + locfcinfo->isnull = false; + oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo)); + if (!locfcinfo->isnull && oprresult) + break; + } + + if (j < nelems2) + { + /* found a match for elt1 */ + if (!matchall) + { + result = true; + break; + } + } + else + { + /* no match for elt1 */ + if (matchall) + { + result = false; + break; + } + } + } + + return result; +} + +Datum +arrayoverlap(PG_FUNCTION_ARGS) +{ + AnyArrayType *array1 = PG_GETARG_ANY_ARRAY_P(0); + AnyArrayType *array2 = PG_GETARG_ANY_ARRAY_P(1); + Oid collation = PG_GET_COLLATION(); + bool result; + + result = array_contain_compare(array1, array2, collation, false, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input. */ + AARR_FREE_IF_COPY(array1, 0); + AARR_FREE_IF_COPY(array2, 1); + + PG_RETURN_BOOL(result); +} + +Datum +arraycontains(PG_FUNCTION_ARGS) +{ + AnyArrayType *array1 = PG_GETARG_ANY_ARRAY_P(0); + AnyArrayType *array2 = PG_GETARG_ANY_ARRAY_P(1); + Oid collation = PG_GET_COLLATION(); + bool result; + + result = array_contain_compare(array2, array1, collation, true, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input. */ + AARR_FREE_IF_COPY(array1, 0); + AARR_FREE_IF_COPY(array2, 1); + + PG_RETURN_BOOL(result); +} + +Datum +arraycontained(PG_FUNCTION_ARGS) +{ + AnyArrayType *array1 = PG_GETARG_ANY_ARRAY_P(0); + AnyArrayType *array2 = PG_GETARG_ANY_ARRAY_P(1); + Oid collation = PG_GET_COLLATION(); + bool result; + + result = array_contain_compare(array1, array2, collation, true, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input. */ + AARR_FREE_IF_COPY(array1, 0); + AARR_FREE_IF_COPY(array2, 1); + + PG_RETURN_BOOL(result); +} + + +/*----------------------------------------------------------------------------- + * Array iteration functions + * These functions are used to iterate efficiently through arrays + *----------------------------------------------------------------------------- + */ + +/* + * array_create_iterator --- set up to iterate through an array + * + * If slice_ndim is zero, we will iterate element-by-element; the returned + * datums are of the array's element type. + * + * If slice_ndim is 1..ARR_NDIM(arr), we will iterate by slices: the + * returned datums are of the same array type as 'arr', but of size + * equal to the rightmost N dimensions of 'arr'. + * + * The passed-in array must remain valid for the lifetime of the iterator. + */ +ArrayIterator +array_create_iterator(ArrayType *arr, int slice_ndim, ArrayMetaState *mstate) +{ + ArrayIterator iterator = palloc0(sizeof(ArrayIteratorData)); + + /* + * Sanity-check inputs --- caller should have got this right already + */ + Assert(PointerIsValid(arr)); + if (slice_ndim < 0 || slice_ndim > ARR_NDIM(arr)) + elog(ERROR, "invalid arguments to array_create_iterator"); + + /* + * Remember basic info about the array and its element type + */ + iterator->arr = arr; + iterator->nullbitmap = ARR_NULLBITMAP(arr); + iterator->nitems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr)); + + if (mstate != NULL) + { + Assert(mstate->element_type == ARR_ELEMTYPE(arr)); + + iterator->typlen = mstate->typlen; + iterator->typbyval = mstate->typbyval; + iterator->typalign = mstate->typalign; + } + else + get_typlenbyvalalign(ARR_ELEMTYPE(arr), + &iterator->typlen, + &iterator->typbyval, + &iterator->typalign); + + /* + * Remember the slicing parameters. + */ + iterator->slice_ndim = slice_ndim; + + if (slice_ndim > 0) + { + /* + * Get pointers into the array's dims and lbound arrays to represent + * the dims/lbound arrays of a slice. These are the same as the + * rightmost N dimensions of the array. + */ + iterator->slice_dims = ARR_DIMS(arr) + ARR_NDIM(arr) - slice_ndim; + iterator->slice_lbound = ARR_LBOUND(arr) + ARR_NDIM(arr) - slice_ndim; + + /* + * Compute number of elements in a slice. + */ + iterator->slice_len = ArrayGetNItems(slice_ndim, + iterator->slice_dims); + + /* + * Create workspace for building sub-arrays. + */ + iterator->slice_values = (Datum *) + palloc(iterator->slice_len * sizeof(Datum)); + iterator->slice_nulls = (bool *) + palloc(iterator->slice_len * sizeof(bool)); + } + + /* + * Initialize our data pointer and linear element number. These will + * advance through the array during array_iterate(). + */ + iterator->data_ptr = ARR_DATA_PTR(arr); + iterator->current_item = 0; + + return iterator; +} + +/* + * Iterate through the array referenced by 'iterator'. + * + * As long as there is another element (or slice), return it into + * *value / *isnull, and return true. Return false when no more data. + */ +bool +array_iterate(ArrayIterator iterator, Datum *value, bool *isnull) +{ + /* Done if we have reached the end of the array */ + if (iterator->current_item >= iterator->nitems) + return false; + + if (iterator->slice_ndim == 0) + { + /* + * Scalar case: return one element. + */ + if (array_get_isnull(iterator->nullbitmap, iterator->current_item++)) + { + *isnull = true; + *value = (Datum) 0; + } + else + { + /* non-NULL, so fetch the individual Datum to return */ + char *p = iterator->data_ptr; + + *isnull = false; + *value = fetch_att(p, iterator->typbyval, iterator->typlen); + + /* Move our data pointer forward to the next element */ + p = att_addlength_pointer(p, iterator->typlen, p); + p = (char *) att_align_nominal(p, iterator->typalign); + iterator->data_ptr = p; + } + } + else + { + /* + * Slice case: build and return an array of the requested size. + */ + ArrayType *result; + Datum *values = iterator->slice_values; + bool *nulls = iterator->slice_nulls; + char *p = iterator->data_ptr; + int i; + + for (i = 0; i < iterator->slice_len; i++) + { + if (array_get_isnull(iterator->nullbitmap, + iterator->current_item++)) + { + nulls[i] = true; + values[i] = (Datum) 0; + } + else + { + nulls[i] = false; + values[i] = fetch_att(p, iterator->typbyval, iterator->typlen); + + /* Move our data pointer forward to the next element */ + p = att_addlength_pointer(p, iterator->typlen, p); + p = (char *) att_align_nominal(p, iterator->typalign); + } + } + + iterator->data_ptr = p; + + result = construct_md_array(values, + nulls, + iterator->slice_ndim, + iterator->slice_dims, + iterator->slice_lbound, + ARR_ELEMTYPE(iterator->arr), + iterator->typlen, + iterator->typbyval, + iterator->typalign); + + *isnull = false; + *value = PointerGetDatum(result); + } + + return true; +} + +/* + * Release an ArrayIterator data structure + */ +void +array_free_iterator(ArrayIterator iterator) +{ + if (iterator->slice_ndim > 0) + { + pfree(iterator->slice_values); + pfree(iterator->slice_nulls); + } + pfree(iterator); +} + + +/***************************************************************************/ +/******************| Support Routines |*****************/ +/***************************************************************************/ + +/* + * Check whether a specific array element is NULL + * + * nullbitmap: pointer to array's null bitmap (NULL if none) + * offset: 0-based linear element number of array element + */ +static bool +array_get_isnull(const bits8 *nullbitmap, int offset) +{ + if (nullbitmap == NULL) + return false; /* assume not null */ + if (nullbitmap[offset / 8] & (1 << (offset % 8))) + return false; /* not null */ + return true; +} + +/* + * Set a specific array element's null-bitmap entry + * + * nullbitmap: pointer to array's null bitmap (mustn't be NULL) + * offset: 0-based linear element number of array element + * isNull: null status to set + */ +static void +array_set_isnull(bits8 *nullbitmap, int offset, bool isNull) +{ + int bitmask; + + nullbitmap += offset / 8; + bitmask = 1 << (offset % 8); + if (isNull) + *nullbitmap &= ~bitmask; + else + *nullbitmap |= bitmask; +} + +/* + * Fetch array element at pointer, converted correctly to a Datum + * + * Caller must have handled case of NULL element + */ +static Datum +ArrayCast(char *value, bool byval, int len) +{ + return fetch_att(value, byval, len); +} + +/* + * Copy datum to *dest and return total space used (including align padding) + * + * Caller must have handled case of NULL element + */ +static int +ArrayCastAndSet(Datum src, + int typlen, + bool typbyval, + char typalign, + char *dest) +{ + int inc; + + if (typlen > 0) + { + if (typbyval) + store_att_byval(dest, src, typlen); + else + memmove(dest, DatumGetPointer(src), typlen); + inc = att_align_nominal(typlen, typalign); + } + else + { + Assert(!typbyval); + inc = att_addlength_datum(0, typlen, src); + memmove(dest, DatumGetPointer(src), inc); + inc = att_align_nominal(inc, typalign); + } + + return inc; +} + +/* + * Advance ptr over nitems array elements + * + * ptr: starting location in array + * offset: 0-based linear element number of first element (the one at *ptr) + * nullbitmap: start of array's null bitmap, or NULL if none + * nitems: number of array elements to advance over (>= 0) + * typlen, typbyval, typalign: storage parameters of array element datatype + * + * It is caller's responsibility to ensure that nitems is within range + */ +static char * +array_seek(char *ptr, int offset, bits8 *nullbitmap, int nitems, + int typlen, bool typbyval, char typalign) +{ + int bitmask; + int i; + + /* easy if fixed-size elements and no NULLs */ + if (typlen > 0 && !nullbitmap) + return ptr + nitems * ((Size) att_align_nominal(typlen, typalign)); + + /* seems worth having separate loops for NULL and no-NULLs cases */ + if (nullbitmap) + { + nullbitmap += offset / 8; + bitmask = 1 << (offset % 8); + + for (i = 0; i < nitems; i++) + { + if (*nullbitmap & bitmask) + { + ptr = att_addlength_pointer(ptr, typlen, ptr); + ptr = (char *) att_align_nominal(ptr, typalign); + } + bitmask <<= 1; + if (bitmask == 0x100) + { + nullbitmap++; + bitmask = 1; + } + } + } + else + { + for (i = 0; i < nitems; i++) + { + ptr = att_addlength_pointer(ptr, typlen, ptr); + ptr = (char *) att_align_nominal(ptr, typalign); + } + } + return ptr; +} + +/* + * Compute total size of the nitems array elements starting at *ptr + * + * Parameters same as for array_seek + */ +static int +array_nelems_size(char *ptr, int offset, bits8 *nullbitmap, int nitems, + int typlen, bool typbyval, char typalign) +{ + return array_seek(ptr, offset, nullbitmap, nitems, + typlen, typbyval, typalign) - ptr; +} + +/* + * Copy nitems array elements from srcptr to destptr + * + * destptr: starting destination location (must be enough room!) + * nitems: number of array elements to copy (>= 0) + * srcptr: starting location in source array + * offset: 0-based linear element number of first element (the one at *srcptr) + * nullbitmap: start of source array's null bitmap, or NULL if none + * typlen, typbyval, typalign: storage parameters of array element datatype + * + * Returns number of bytes copied + * + * NB: this does not take care of setting up the destination's null bitmap! + */ +static int +array_copy(char *destptr, int nitems, + char *srcptr, int offset, bits8 *nullbitmap, + int typlen, bool typbyval, char typalign) +{ + int numbytes; + + numbytes = array_nelems_size(srcptr, offset, nullbitmap, nitems, + typlen, typbyval, typalign); + memcpy(destptr, srcptr, numbytes); + return numbytes; +} + +/* + * Copy nitems null-bitmap bits from source to destination + * + * destbitmap: start of destination array's null bitmap (mustn't be NULL) + * destoffset: 0-based linear element number of first dest element + * srcbitmap: start of source array's null bitmap, or NULL if none + * srcoffset: 0-based linear element number of first source element + * nitems: number of bits to copy (>= 0) + * + * If srcbitmap is NULL then we assume the source is all-non-NULL and + * fill 1's into the destination bitmap. Note that only the specified + * bits in the destination map are changed, not any before or after. + * + * Note: this could certainly be optimized using standard bitblt methods. + * However, it's not clear that the typical Postgres array has enough elements + * to make it worth worrying too much. For the moment, KISS. + */ +void +array_bitmap_copy(bits8 *destbitmap, int destoffset, + const bits8 *srcbitmap, int srcoffset, + int nitems) +{ + int destbitmask, + destbitval, + srcbitmask, + srcbitval; + + Assert(destbitmap); + if (nitems <= 0) + return; /* don't risk fetch off end of memory */ + destbitmap += destoffset / 8; + destbitmask = 1 << (destoffset % 8); + destbitval = *destbitmap; + if (srcbitmap) + { + srcbitmap += srcoffset / 8; + srcbitmask = 1 << (srcoffset % 8); + srcbitval = *srcbitmap; + while (nitems-- > 0) + { + if (srcbitval & srcbitmask) + destbitval |= destbitmask; + else + destbitval &= ~destbitmask; + destbitmask <<= 1; + if (destbitmask == 0x100) + { + *destbitmap++ = destbitval; + destbitmask = 1; + if (nitems > 0) + destbitval = *destbitmap; + } + srcbitmask <<= 1; + if (srcbitmask == 0x100) + { + srcbitmap++; + srcbitmask = 1; + if (nitems > 0) + srcbitval = *srcbitmap; + } + } + if (destbitmask != 1) + *destbitmap = destbitval; + } + else + { + while (nitems-- > 0) + { + destbitval |= destbitmask; + destbitmask <<= 1; + if (destbitmask == 0x100) + { + *destbitmap++ = destbitval; + destbitmask = 1; + if (nitems > 0) + destbitval = *destbitmap; + } + } + if (destbitmask != 1) + *destbitmap = destbitval; + } +} + +/* + * Compute space needed for a slice of an array + * + * We assume the caller has verified that the slice coordinates are valid. + */ +static int +array_slice_size(char *arraydataptr, bits8 *arraynullsptr, + int ndim, int *dim, int *lb, + int *st, int *endp, + int typlen, bool typbyval, char typalign) +{ + int src_offset, + span[MAXDIM], + prod[MAXDIM], + dist[MAXDIM], + indx[MAXDIM]; + char *ptr; + int i, + j, + inc; + int count = 0; + + mda_get_range(ndim, span, st, endp); + + /* Pretty easy for fixed element length without nulls ... */ + if (typlen > 0 && !arraynullsptr) + return ArrayGetNItems(ndim, span) * att_align_nominal(typlen, typalign); + + /* Else gotta do it the hard way */ + src_offset = ArrayGetOffset(ndim, dim, lb, st); + ptr = array_seek(arraydataptr, 0, arraynullsptr, src_offset, + typlen, typbyval, typalign); + mda_get_prod(ndim, dim, prod); + mda_get_offset_values(ndim, dist, prod, span); + for (i = 0; i < ndim; i++) + indx[i] = 0; + j = ndim - 1; + do + { + if (dist[j]) + { + ptr = array_seek(ptr, src_offset, arraynullsptr, dist[j], + typlen, typbyval, typalign); + src_offset += dist[j]; + } + if (!array_get_isnull(arraynullsptr, src_offset)) + { + inc = att_addlength_pointer(0, typlen, ptr); + inc = att_align_nominal(inc, typalign); + ptr += inc; + count += inc; + } + src_offset++; + } while ((j = mda_next_tuple(ndim, indx, span)) != -1); + return count; +} + +/* + * Extract a slice of an array into consecutive elements in the destination + * array. + * + * We assume the caller has verified that the slice coordinates are valid, + * allocated enough storage for the result, and initialized the header + * of the new array. + */ +static void +array_extract_slice(ArrayType *newarray, + int ndim, + int *dim, + int *lb, + char *arraydataptr, + bits8 *arraynullsptr, + int *st, + int *endp, + int typlen, + bool typbyval, + char typalign) +{ + char *destdataptr = ARR_DATA_PTR(newarray); + bits8 *destnullsptr = ARR_NULLBITMAP(newarray); + char *srcdataptr; + int src_offset, + dest_offset, + prod[MAXDIM], + span[MAXDIM], + dist[MAXDIM], + indx[MAXDIM]; + int i, + j, + inc; + + src_offset = ArrayGetOffset(ndim, dim, lb, st); + srcdataptr = array_seek(arraydataptr, 0, arraynullsptr, src_offset, + typlen, typbyval, typalign); + mda_get_prod(ndim, dim, prod); + mda_get_range(ndim, span, st, endp); + mda_get_offset_values(ndim, dist, prod, span); + for (i = 0; i < ndim; i++) + indx[i] = 0; + dest_offset = 0; + j = ndim - 1; + do + { + if (dist[j]) + { + /* skip unwanted elements */ + srcdataptr = array_seek(srcdataptr, src_offset, arraynullsptr, + dist[j], + typlen, typbyval, typalign); + src_offset += dist[j]; + } + inc = array_copy(destdataptr, 1, + srcdataptr, src_offset, arraynullsptr, + typlen, typbyval, typalign); + if (destnullsptr) + array_bitmap_copy(destnullsptr, dest_offset, + arraynullsptr, src_offset, + 1); + destdataptr += inc; + srcdataptr += inc; + src_offset++; + dest_offset++; + } while ((j = mda_next_tuple(ndim, indx, span)) != -1); +} + +/* + * Insert a slice into an array. + * + * ndim/dim[]/lb[] are dimensions of the original array. A new array with + * those same dimensions is to be constructed. destArray must already + * have been allocated and its header initialized. + * + * st[]/endp[] identify the slice to be replaced. Elements within the slice + * volume are taken from consecutive elements of the srcArray; elements + * outside it are copied from origArray. + * + * We assume the caller has verified that the slice coordinates are valid. + */ +static void +array_insert_slice(ArrayType *destArray, + ArrayType *origArray, + ArrayType *srcArray, + int ndim, + int *dim, + int *lb, + int *st, + int *endp, + int typlen, + bool typbyval, + char typalign) +{ + char *destPtr = ARR_DATA_PTR(destArray); + char *origPtr = ARR_DATA_PTR(origArray); + char *srcPtr = ARR_DATA_PTR(srcArray); + bits8 *destBitmap = ARR_NULLBITMAP(destArray); + bits8 *origBitmap = ARR_NULLBITMAP(origArray); + bits8 *srcBitmap = ARR_NULLBITMAP(srcArray); + int orignitems = ArrayGetNItems(ARR_NDIM(origArray), + ARR_DIMS(origArray)); + int dest_offset, + orig_offset, + src_offset, + prod[MAXDIM], + span[MAXDIM], + dist[MAXDIM], + indx[MAXDIM]; + int i, + j, + inc; + + dest_offset = ArrayGetOffset(ndim, dim, lb, st); + /* copy items before the slice start */ + inc = array_copy(destPtr, dest_offset, + origPtr, 0, origBitmap, + typlen, typbyval, typalign); + destPtr += inc; + origPtr += inc; + if (destBitmap) + array_bitmap_copy(destBitmap, 0, origBitmap, 0, dest_offset); + orig_offset = dest_offset; + mda_get_prod(ndim, dim, prod); + mda_get_range(ndim, span, st, endp); + mda_get_offset_values(ndim, dist, prod, span); + for (i = 0; i < ndim; i++) + indx[i] = 0; + src_offset = 0; + j = ndim - 1; + do + { + /* Copy/advance over elements between here and next part of slice */ + if (dist[j]) + { + inc = array_copy(destPtr, dist[j], + origPtr, orig_offset, origBitmap, + typlen, typbyval, typalign); + destPtr += inc; + origPtr += inc; + if (destBitmap) + array_bitmap_copy(destBitmap, dest_offset, + origBitmap, orig_offset, + dist[j]); + dest_offset += dist[j]; + orig_offset += dist[j]; + } + /* Copy new element at this slice position */ + inc = array_copy(destPtr, 1, + srcPtr, src_offset, srcBitmap, + typlen, typbyval, typalign); + if (destBitmap) + array_bitmap_copy(destBitmap, dest_offset, + srcBitmap, src_offset, + 1); + destPtr += inc; + srcPtr += inc; + dest_offset++; + src_offset++; + /* Advance over old element at this slice position */ + origPtr = array_seek(origPtr, orig_offset, origBitmap, 1, + typlen, typbyval, typalign); + orig_offset++; + } while ((j = mda_next_tuple(ndim, indx, span)) != -1); + + /* don't miss any data at the end */ + array_copy(destPtr, orignitems - orig_offset, + origPtr, orig_offset, origBitmap, + typlen, typbyval, typalign); + if (destBitmap) + array_bitmap_copy(destBitmap, dest_offset, + origBitmap, orig_offset, + orignitems - orig_offset); +} + +/* + * initArrayResult - initialize an empty ArrayBuildState + * + * element_type is the array element type (must be a valid array element type) + * rcontext is where to keep working state + * subcontext is a flag determining whether to use a separate memory context + * + * Note: there are two common schemes for using accumArrayResult(). + * In the older scheme, you start with a NULL ArrayBuildState pointer, and + * call accumArrayResult once per element. In this scheme you end up with + * a NULL pointer if there were no elements, which you need to special-case. + * In the newer scheme, call initArrayResult and then call accumArrayResult + * once per element. In this scheme you always end with a non-NULL pointer + * that you can pass to makeArrayResult; you get an empty array if there + * were no elements. This is preferred if an empty array is what you want. + * + * It's possible to choose whether to create a separate memory context for the + * array build state, or whether to allocate it directly within rcontext. + * + * When there are many concurrent small states (e.g. array_agg() using hash + * aggregation of many small groups), using a separate memory context for each + * one may result in severe memory bloat. In such cases, use the same memory + * context to initialize all such array build states, and pass + * subcontext=false. + * + * In cases when the array build states have different lifetimes, using a + * single memory context is impractical. Instead, pass subcontext=true so that + * the array build states can be freed individually. + */ +ArrayBuildState * +initArrayResult(Oid element_type, MemoryContext rcontext, bool subcontext) +{ + /* + * When using a subcontext, we can afford to start with a somewhat larger + * initial array size. Without subcontexts, we'd better hope that most of + * the states stay small ... + */ + return initArrayResultWithSize(element_type, rcontext, subcontext, + subcontext ? 64 : 8); +} + +/* + * initArrayResultWithSize + * As initArrayResult, but allow the initial size of the allocated arrays + * to be specified. + */ +ArrayBuildState * +initArrayResultWithSize(Oid element_type, MemoryContext rcontext, + bool subcontext, int initsize) +{ + ArrayBuildState *astate; + MemoryContext arr_context = rcontext; + + /* Make a temporary context to hold all the junk */ + if (subcontext) + arr_context = AllocSetContextCreate(rcontext, + "accumArrayResult", + ALLOCSET_DEFAULT_SIZES); + + astate = (ArrayBuildState *) + MemoryContextAlloc(arr_context, sizeof(ArrayBuildState)); + astate->mcontext = arr_context; + astate->private_cxt = subcontext; + astate->alen = initsize; + astate->dvalues = (Datum *) + MemoryContextAlloc(arr_context, astate->alen * sizeof(Datum)); + astate->dnulls = (bool *) + MemoryContextAlloc(arr_context, astate->alen * sizeof(bool)); + astate->nelems = 0; + astate->element_type = element_type; + get_typlenbyvalalign(element_type, + &astate->typlen, + &astate->typbyval, + &astate->typalign); + + return astate; +} + +/* + * accumArrayResult - accumulate one (more) Datum for an array result + * + * astate is working state (can be NULL on first call) + * dvalue/disnull represent the new Datum to append to the array + * element_type is the Datum's type (must be a valid array element type) + * rcontext is where to keep working state + */ +ArrayBuildState * +accumArrayResult(ArrayBuildState *astate, + Datum dvalue, bool disnull, + Oid element_type, + MemoryContext rcontext) +{ + MemoryContext oldcontext; + + if (astate == NULL) + { + /* First time through --- initialize */ + astate = initArrayResult(element_type, rcontext, true); + } + else + { + Assert(astate->element_type == element_type); + } + + oldcontext = MemoryContextSwitchTo(astate->mcontext); + + /* enlarge dvalues[]/dnulls[] if needed */ + if (astate->nelems >= astate->alen) + { + astate->alen *= 2; + astate->dvalues = (Datum *) + repalloc(astate->dvalues, astate->alen * sizeof(Datum)); + astate->dnulls = (bool *) + repalloc(astate->dnulls, astate->alen * sizeof(bool)); + } + + /* + * Ensure pass-by-ref stuff is copied into mcontext; and detoast it too if + * it's varlena. (You might think that detoasting is not needed here + * because construct_md_array can detoast the array elements later. + * However, we must not let construct_md_array modify the ArrayBuildState + * because that would mean array_agg_finalfn damages its input, which is + * verboten. Also, this way frequently saves one copying step.) + */ + if (!disnull && !astate->typbyval) + { + if (astate->typlen == -1) + dvalue = PointerGetDatum(PG_DETOAST_DATUM_COPY(dvalue)); + else + dvalue = datumCopy(dvalue, astate->typbyval, astate->typlen); + } + + astate->dvalues[astate->nelems] = dvalue; + astate->dnulls[astate->nelems] = disnull; + astate->nelems++; + + MemoryContextSwitchTo(oldcontext); + + return astate; +} + +/* + * makeArrayResult - produce 1-D final result of accumArrayResult + * + * Note: only releases astate if it was initialized within a separate memory + * context (i.e. using subcontext=true when calling initArrayResult). + * + * astate is working state (must not be NULL) + * rcontext is where to construct result + */ +Datum +makeArrayResult(ArrayBuildState *astate, + MemoryContext rcontext) +{ + int ndims; + int dims[1]; + int lbs[1]; + + /* If no elements were presented, we want to create an empty array */ + ndims = (astate->nelems > 0) ? 1 : 0; + dims[0] = astate->nelems; + lbs[0] = 1; + + return makeMdArrayResult(astate, ndims, dims, lbs, rcontext, + astate->private_cxt); +} + +/* + * makeMdArrayResult - produce multi-D final result of accumArrayResult + * + * beware: no check that specified dimensions match the number of values + * accumulated. + * + * Note: if the astate was not initialized within a separate memory context + * (that is, initArrayResult was called with subcontext=false), then using + * release=true is illegal. Instead, release astate along with the rest of its + * context when appropriate. + * + * astate is working state (must not be NULL) + * rcontext is where to construct result + * release is true if okay to release working state + */ +Datum +makeMdArrayResult(ArrayBuildState *astate, + int ndims, + int *dims, + int *lbs, + MemoryContext rcontext, + bool release) +{ + ArrayType *result; + MemoryContext oldcontext; + + /* Build the final array result in rcontext */ + oldcontext = MemoryContextSwitchTo(rcontext); + + result = construct_md_array(astate->dvalues, + astate->dnulls, + ndims, + dims, + lbs, + astate->element_type, + astate->typlen, + astate->typbyval, + astate->typalign); + + MemoryContextSwitchTo(oldcontext); + + /* Clean up all the junk */ + if (release) + { + Assert(astate->private_cxt); + MemoryContextDelete(astate->mcontext); + } + + return PointerGetDatum(result); +} + +/* + * The following three functions provide essentially the same API as + * initArrayResult/accumArrayResult/makeArrayResult, but instead of accepting + * inputs that are array elements, they accept inputs that are arrays and + * produce an output array having N+1 dimensions. The inputs must all have + * identical dimensionality as well as element type. + */ + +/* + * initArrayResultArr - initialize an empty ArrayBuildStateArr + * + * array_type is the array type (must be a valid varlena array type) + * element_type is the type of the array's elements (lookup if InvalidOid) + * rcontext is where to keep working state + * subcontext is a flag determining whether to use a separate memory context + */ +ArrayBuildStateArr * +initArrayResultArr(Oid array_type, Oid element_type, MemoryContext rcontext, + bool subcontext) +{ + ArrayBuildStateArr *astate; + MemoryContext arr_context = rcontext; /* by default use the parent ctx */ + + /* Lookup element type, unless element_type already provided */ + if (!OidIsValid(element_type)) + { + element_type = get_element_type(array_type); + + if (!OidIsValid(element_type)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("data type %s is not an array type", + format_type_be(array_type)))); + } + + /* Make a temporary context to hold all the junk */ + if (subcontext) + arr_context = AllocSetContextCreate(rcontext, + "accumArrayResultArr", + ALLOCSET_DEFAULT_SIZES); + + /* Note we initialize all fields to zero */ + astate = (ArrayBuildStateArr *) + MemoryContextAllocZero(arr_context, sizeof(ArrayBuildStateArr)); + astate->mcontext = arr_context; + astate->private_cxt = subcontext; + + /* Save relevant datatype information */ + astate->array_type = array_type; + astate->element_type = element_type; + + return astate; +} + +/* + * accumArrayResultArr - accumulate one (more) sub-array for an array result + * + * astate is working state (can be NULL on first call) + * dvalue/disnull represent the new sub-array to append to the array + * array_type is the array type (must be a valid varlena array type) + * rcontext is where to keep working state + */ +ArrayBuildStateArr * +accumArrayResultArr(ArrayBuildStateArr *astate, + Datum dvalue, bool disnull, + Oid array_type, + MemoryContext rcontext) +{ + ArrayType *arg; + MemoryContext oldcontext; + int *dims, + *lbs, + ndims, + nitems, + ndatabytes; + char *data; + int i; + + /* + * We disallow accumulating null subarrays. Another plausible definition + * is to ignore them, but callers that want that can just skip calling + * this function. + */ + if (disnull) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("cannot accumulate null arrays"))); + + /* Detoast input array in caller's context */ + arg = DatumGetArrayTypeP(dvalue); + + if (astate == NULL) + astate = initArrayResultArr(array_type, InvalidOid, rcontext, true); + else + Assert(astate->array_type == array_type); + + oldcontext = MemoryContextSwitchTo(astate->mcontext); + + /* Collect this input's dimensions */ + ndims = ARR_NDIM(arg); + dims = ARR_DIMS(arg); + lbs = ARR_LBOUND(arg); + data = ARR_DATA_PTR(arg); + nitems = ArrayGetNItems(ndims, dims); + ndatabytes = ARR_SIZE(arg) - ARR_DATA_OFFSET(arg); + + if (astate->ndims == 0) + { + /* First input; check/save the dimensionality info */ + + /* Should we allow empty inputs and just produce an empty output? */ + if (ndims == 0) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("cannot accumulate empty arrays"))); + if (ndims + 1 > MAXDIM) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)", + ndims + 1, MAXDIM))); + + /* + * The output array will have n+1 dimensions, with the ones after the + * first matching the input's dimensions. + */ + astate->ndims = ndims + 1; + astate->dims[0] = 0; + memcpy(&astate->dims[1], dims, ndims * sizeof(int)); + astate->lbs[0] = 1; + memcpy(&astate->lbs[1], lbs, ndims * sizeof(int)); + + /* Allocate at least enough data space for this item */ + astate->abytes = pg_nextpower2_32(Max(1024, ndatabytes + 1)); + astate->data = (char *) palloc(astate->abytes); + } + else + { + /* Second or later input: must match first input's dimensionality */ + if (astate->ndims != ndims + 1) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("cannot accumulate arrays of different dimensionality"))); + for (i = 0; i < ndims; i++) + { + if (astate->dims[i + 1] != dims[i] || astate->lbs[i + 1] != lbs[i]) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("cannot accumulate arrays of different dimensionality"))); + } + + /* Enlarge data space if needed */ + if (astate->nbytes + ndatabytes > astate->abytes) + { + astate->abytes = Max(astate->abytes * 2, + astate->nbytes + ndatabytes); + astate->data = (char *) repalloc(astate->data, astate->abytes); + } + } + + /* + * Copy the data portion of the sub-array. Note we assume that the + * advertised data length of the sub-array is properly aligned. We do not + * have to worry about detoasting elements since whatever's in the + * sub-array should be OK already. + */ + memcpy(astate->data + astate->nbytes, data, ndatabytes); + astate->nbytes += ndatabytes; + + /* Deal with null bitmap if needed */ + if (astate->nullbitmap || ARR_HASNULL(arg)) + { + int newnitems = astate->nitems + nitems; + + if (astate->nullbitmap == NULL) + { + /* + * First input with nulls; we must retrospectively handle any + * previous inputs by marking all their items non-null. + */ + astate->aitems = pg_nextpower2_32(Max(256, newnitems + 1)); + astate->nullbitmap = (bits8 *) palloc((astate->aitems + 7) / 8); + array_bitmap_copy(astate->nullbitmap, 0, + NULL, 0, + astate->nitems); + } + else if (newnitems > astate->aitems) + { + astate->aitems = Max(astate->aitems * 2, newnitems); + astate->nullbitmap = (bits8 *) + repalloc(astate->nullbitmap, (astate->aitems + 7) / 8); + } + array_bitmap_copy(astate->nullbitmap, astate->nitems, + ARR_NULLBITMAP(arg), 0, + nitems); + } + + astate->nitems += nitems; + astate->dims[0] += 1; + + MemoryContextSwitchTo(oldcontext); + + /* Release detoasted copy if any */ + if ((Pointer) arg != DatumGetPointer(dvalue)) + pfree(arg); + + return astate; +} + +/* + * makeArrayResultArr - produce N+1-D final result of accumArrayResultArr + * + * astate is working state (must not be NULL) + * rcontext is where to construct result + * release is true if okay to release working state + */ +Datum +makeArrayResultArr(ArrayBuildStateArr *astate, + MemoryContext rcontext, + bool release) +{ + ArrayType *result; + MemoryContext oldcontext; + + /* Build the final array result in rcontext */ + oldcontext = MemoryContextSwitchTo(rcontext); + + if (astate->ndims == 0) + { + /* No inputs, return empty array */ + result = construct_empty_array(astate->element_type); + } + else + { + int dataoffset, + nbytes; + + /* Check for overflow of the array dimensions */ + (void) ArrayGetNItems(astate->ndims, astate->dims); + ArrayCheckBounds(astate->ndims, astate->dims, astate->lbs); + + /* Compute required space */ + nbytes = astate->nbytes; + if (astate->nullbitmap != NULL) + { + dataoffset = ARR_OVERHEAD_WITHNULLS(astate->ndims, astate->nitems); + nbytes += dataoffset; + } + else + { + dataoffset = 0; + nbytes += ARR_OVERHEAD_NONULLS(astate->ndims); + } + + result = (ArrayType *) palloc0(nbytes); + SET_VARSIZE(result, nbytes); + result->ndim = astate->ndims; + result->dataoffset = dataoffset; + result->elemtype = astate->element_type; + + memcpy(ARR_DIMS(result), astate->dims, astate->ndims * sizeof(int)); + memcpy(ARR_LBOUND(result), astate->lbs, astate->ndims * sizeof(int)); + memcpy(ARR_DATA_PTR(result), astate->data, astate->nbytes); + + if (astate->nullbitmap != NULL) + array_bitmap_copy(ARR_NULLBITMAP(result), 0, + astate->nullbitmap, 0, + astate->nitems); + } + + MemoryContextSwitchTo(oldcontext); + + /* Clean up all the junk */ + if (release) + { + Assert(astate->private_cxt); + MemoryContextDelete(astate->mcontext); + } + + return PointerGetDatum(result); +} + +/* + * The following three functions provide essentially the same API as + * initArrayResult/accumArrayResult/makeArrayResult, but can accept either + * scalar or array inputs, invoking the appropriate set of functions above. + */ + +/* + * initArrayResultAny - initialize an empty ArrayBuildStateAny + * + * input_type is the input datatype (either element or array type) + * rcontext is where to keep working state + * subcontext is a flag determining whether to use a separate memory context + */ +ArrayBuildStateAny * +initArrayResultAny(Oid input_type, MemoryContext rcontext, bool subcontext) +{ + ArrayBuildStateAny *astate; + Oid element_type = get_element_type(input_type); + + if (OidIsValid(element_type)) + { + /* Array case */ + ArrayBuildStateArr *arraystate; + + arraystate = initArrayResultArr(input_type, InvalidOid, rcontext, subcontext); + astate = (ArrayBuildStateAny *) + MemoryContextAlloc(arraystate->mcontext, + sizeof(ArrayBuildStateAny)); + astate->scalarstate = NULL; + astate->arraystate = arraystate; + } + else + { + /* Scalar case */ + ArrayBuildState *scalarstate; + + /* Let's just check that we have a type that can be put into arrays */ + Assert(OidIsValid(get_array_type(input_type))); + + scalarstate = initArrayResult(input_type, rcontext, subcontext); + astate = (ArrayBuildStateAny *) + MemoryContextAlloc(scalarstate->mcontext, + sizeof(ArrayBuildStateAny)); + astate->scalarstate = scalarstate; + astate->arraystate = NULL; + } + + return astate; +} + +/* + * accumArrayResultAny - accumulate one (more) input for an array result + * + * astate is working state (can be NULL on first call) + * dvalue/disnull represent the new input to append to the array + * input_type is the input datatype (either element or array type) + * rcontext is where to keep working state + */ +ArrayBuildStateAny * +accumArrayResultAny(ArrayBuildStateAny *astate, + Datum dvalue, bool disnull, + Oid input_type, + MemoryContext rcontext) +{ + if (astate == NULL) + astate = initArrayResultAny(input_type, rcontext, true); + + if (astate->scalarstate) + (void) accumArrayResult(astate->scalarstate, + dvalue, disnull, + input_type, rcontext); + else + (void) accumArrayResultArr(astate->arraystate, + dvalue, disnull, + input_type, rcontext); + + return astate; +} + +/* + * makeArrayResultAny - produce final result of accumArrayResultAny + * + * astate is working state (must not be NULL) + * rcontext is where to construct result + * release is true if okay to release working state + */ +Datum +makeArrayResultAny(ArrayBuildStateAny *astate, + MemoryContext rcontext, bool release) +{ + Datum result; + + if (astate->scalarstate) + { + /* Must use makeMdArrayResult to support "release" parameter */ + int ndims; + int dims[1]; + int lbs[1]; + + /* If no elements were presented, we want to create an empty array */ + ndims = (astate->scalarstate->nelems > 0) ? 1 : 0; + dims[0] = astate->scalarstate->nelems; + lbs[0] = 1; + + result = makeMdArrayResult(astate->scalarstate, ndims, dims, lbs, + rcontext, release); + } + else + { + result = makeArrayResultArr(astate->arraystate, + rcontext, release); + } + return result; +} + + +Datum +array_larger(PG_FUNCTION_ARGS) +{ + if (array_cmp(fcinfo) > 0) + PG_RETURN_DATUM(PG_GETARG_DATUM(0)); + else + PG_RETURN_DATUM(PG_GETARG_DATUM(1)); +} + +Datum +array_smaller(PG_FUNCTION_ARGS) +{ + if (array_cmp(fcinfo) < 0) + PG_RETURN_DATUM(PG_GETARG_DATUM(0)); + else + PG_RETURN_DATUM(PG_GETARG_DATUM(1)); +} + + +typedef struct generate_subscripts_fctx +{ + int32 lower; + int32 upper; + bool reverse; +} generate_subscripts_fctx; + +/* + * generate_subscripts(array anyarray, dim int [, reverse bool]) + * Returns all subscripts of the array for any dimension + */ +Datum +generate_subscripts(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + MemoryContext oldcontext; + generate_subscripts_fctx *fctx; + + /* stuff done only on the first call of the function */ + if (SRF_IS_FIRSTCALL()) + { + AnyArrayType *v = PG_GETARG_ANY_ARRAY_P(0); + int reqdim = PG_GETARG_INT32(1); + int *lb, + *dimv; + + /* create a function context for cross-call persistence */ + funcctx = SRF_FIRSTCALL_INIT(); + + /* Sanity check: does it look like an array at all? */ + if (AARR_NDIM(v) <= 0 || AARR_NDIM(v) > MAXDIM) + SRF_RETURN_DONE(funcctx); + + /* Sanity check: was the requested dim valid */ + if (reqdim <= 0 || reqdim > AARR_NDIM(v)) + SRF_RETURN_DONE(funcctx); + + /* + * switch to memory context appropriate for multiple function calls + */ + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + fctx = (generate_subscripts_fctx *) palloc(sizeof(generate_subscripts_fctx)); + + lb = AARR_LBOUND(v); + dimv = AARR_DIMS(v); + + fctx->lower = lb[reqdim - 1]; + fctx->upper = dimv[reqdim - 1] + lb[reqdim - 1] - 1; + fctx->reverse = (PG_NARGS() < 3) ? false : PG_GETARG_BOOL(2); + + funcctx->user_fctx = fctx; + + MemoryContextSwitchTo(oldcontext); + } + + funcctx = SRF_PERCALL_SETUP(); + + fctx = funcctx->user_fctx; + + if (fctx->lower <= fctx->upper) + { + if (!fctx->reverse) + SRF_RETURN_NEXT(funcctx, Int32GetDatum(fctx->lower++)); + else + SRF_RETURN_NEXT(funcctx, Int32GetDatum(fctx->upper--)); + } + else + /* done when there are no more elements left */ + SRF_RETURN_DONE(funcctx); +} + +/* + * generate_subscripts_nodir + * Implements the 2-argument version of generate_subscripts + */ +Datum +generate_subscripts_nodir(PG_FUNCTION_ARGS) +{ + /* just call the other one -- it can handle both cases */ + return generate_subscripts(fcinfo); +} + +/* + * array_fill_with_lower_bounds + * Create and fill array with defined lower bounds. + */ +Datum +array_fill_with_lower_bounds(PG_FUNCTION_ARGS) +{ + ArrayType *dims; + ArrayType *lbs; + ArrayType *result; + Oid elmtype; + Datum value; + bool isnull; + + if (PG_ARGISNULL(1) || PG_ARGISNULL(2)) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("dimension array or low bound array cannot be null"))); + + dims = PG_GETARG_ARRAYTYPE_P(1); + lbs = PG_GETARG_ARRAYTYPE_P(2); + + if (!PG_ARGISNULL(0)) + { + value = PG_GETARG_DATUM(0); + isnull = false; + } + else + { + value = 0; + isnull = true; + } + + elmtype = get_fn_expr_argtype(fcinfo->flinfo, 0); + if (!OidIsValid(elmtype)) + elog(ERROR, "could not determine data type of input"); + + result = array_fill_internal(dims, lbs, value, isnull, elmtype, fcinfo); + PG_RETURN_ARRAYTYPE_P(result); +} + +/* + * array_fill + * Create and fill array with default lower bounds. + */ +Datum +array_fill(PG_FUNCTION_ARGS) +{ + ArrayType *dims; + ArrayType *result; + Oid elmtype; + Datum value; + bool isnull; + + if (PG_ARGISNULL(1)) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("dimension array or low bound array cannot be null"))); + + dims = PG_GETARG_ARRAYTYPE_P(1); + + if (!PG_ARGISNULL(0)) + { + value = PG_GETARG_DATUM(0); + isnull = false; + } + else + { + value = 0; + isnull = true; + } + + elmtype = get_fn_expr_argtype(fcinfo->flinfo, 0); + if (!OidIsValid(elmtype)) + elog(ERROR, "could not determine data type of input"); + + result = array_fill_internal(dims, NULL, value, isnull, elmtype, fcinfo); + PG_RETURN_ARRAYTYPE_P(result); +} + +static ArrayType * +create_array_envelope(int ndims, int *dimv, int *lbsv, int nbytes, + Oid elmtype, int dataoffset) +{ + ArrayType *result; + + result = (ArrayType *) palloc0(nbytes); + SET_VARSIZE(result, nbytes); + result->ndim = ndims; + result->dataoffset = dataoffset; + result->elemtype = elmtype; + memcpy(ARR_DIMS(result), dimv, ndims * sizeof(int)); + memcpy(ARR_LBOUND(result), lbsv, ndims * sizeof(int)); + + return result; +} + +static ArrayType * +array_fill_internal(ArrayType *dims, ArrayType *lbs, + Datum value, bool isnull, Oid elmtype, + FunctionCallInfo fcinfo) +{ + ArrayType *result; + int *dimv; + int *lbsv; + int ndims; + int nitems; + int deflbs[MAXDIM]; + int16 elmlen; + bool elmbyval; + char elmalign; + ArrayMetaState *my_extra; + + /* + * Params checks + */ + if (ARR_NDIM(dims) > 1) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("wrong number of array subscripts"), + errdetail("Dimension array must be one dimensional."))); + + if (array_contains_nulls(dims)) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("dimension values cannot be null"))); + + dimv = (int *) ARR_DATA_PTR(dims); + ndims = (ARR_NDIM(dims) > 0) ? ARR_DIMS(dims)[0] : 0; + + if (ndims < 0) /* we do allow zero-dimension arrays */ + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid number of dimensions: %d", ndims))); + if (ndims > MAXDIM) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)", + ndims, MAXDIM))); + + if (lbs != NULL) + { + if (ARR_NDIM(lbs) > 1) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("wrong number of array subscripts"), + errdetail("Dimension array must be one dimensional."))); + + if (array_contains_nulls(lbs)) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("dimension values cannot be null"))); + + if (ndims != ((ARR_NDIM(lbs) > 0) ? ARR_DIMS(lbs)[0] : 0)) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("wrong number of array subscripts"), + errdetail("Low bound array has different size than dimensions array."))); + + lbsv = (int *) ARR_DATA_PTR(lbs); + } + else + { + int i; + + for (i = 0; i < MAXDIM; i++) + deflbs[i] = 1; + + lbsv = deflbs; + } + + /* This checks for overflow of the array dimensions */ + nitems = ArrayGetNItems(ndims, dimv); + ArrayCheckBounds(ndims, dimv, lbsv); + + /* fast track for empty array */ + if (nitems <= 0) + return construct_empty_array(elmtype); + + /* + * We arrange to look up info about element type only once per series of + * calls, assuming the element type doesn't change underneath us. + */ + my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra; + if (my_extra == NULL) + { + fcinfo->flinfo->fn_extra = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, + sizeof(ArrayMetaState)); + my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra; + my_extra->element_type = InvalidOid; + } + + if (my_extra->element_type != elmtype) + { + /* Get info about element type */ + get_typlenbyvalalign(elmtype, + &my_extra->typlen, + &my_extra->typbyval, + &my_extra->typalign); + my_extra->element_type = elmtype; + } + + elmlen = my_extra->typlen; + elmbyval = my_extra->typbyval; + elmalign = my_extra->typalign; + + /* compute required space */ + if (!isnull) + { + int i; + char *p; + int nbytes; + int totbytes; + + /* make sure data is not toasted */ + if (elmlen == -1) + value = PointerGetDatum(PG_DETOAST_DATUM(value)); + + nbytes = att_addlength_datum(0, elmlen, value); + nbytes = att_align_nominal(nbytes, elmalign); + Assert(nbytes > 0); + + totbytes = nbytes * nitems; + + /* check for overflow of multiplication or total request */ + if (totbytes / nbytes != nitems || + !AllocSizeIsValid(totbytes)) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("array size exceeds the maximum allowed (%d)", + (int) MaxAllocSize))); + + /* + * This addition can't overflow, but it might cause us to go past + * MaxAllocSize. We leave it to palloc to complain in that case. + */ + totbytes += ARR_OVERHEAD_NONULLS(ndims); + + result = create_array_envelope(ndims, dimv, lbsv, totbytes, + elmtype, 0); + + p = ARR_DATA_PTR(result); + for (i = 0; i < nitems; i++) + p += ArrayCastAndSet(value, elmlen, elmbyval, elmalign, p); + } + else + { + int nbytes; + int dataoffset; + + dataoffset = ARR_OVERHEAD_WITHNULLS(ndims, nitems); + nbytes = dataoffset; + + result = create_array_envelope(ndims, dimv, lbsv, nbytes, + elmtype, dataoffset); + + /* create_array_envelope already zeroed the bitmap, so we're done */ + } + + return result; +} + + +/* + * UNNEST + */ +Datum +array_unnest(PG_FUNCTION_ARGS) +{ + typedef struct + { + array_iter iter; + int nextelem; + int numelems; + int16 elmlen; + bool elmbyval; + char elmalign; + } array_unnest_fctx; + + FuncCallContext *funcctx; + array_unnest_fctx *fctx; + MemoryContext oldcontext; + + /* stuff done only on the first call of the function */ + if (SRF_IS_FIRSTCALL()) + { + AnyArrayType *arr; + + /* 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); + + /* + * Get the array value and detoast if needed. We can't do this + * earlier because if we have to detoast, we want the detoasted copy + * to be in multi_call_memory_ctx, so it will go away when we're done + * and not before. (If no detoast happens, we assume the originally + * passed array will stick around till then.) + */ + arr = PG_GETARG_ANY_ARRAY_P(0); + + /* allocate memory for user context */ + fctx = (array_unnest_fctx *) palloc(sizeof(array_unnest_fctx)); + + /* initialize state */ + array_iter_setup(&fctx->iter, arr); + fctx->nextelem = 0; + fctx->numelems = ArrayGetNItems(AARR_NDIM(arr), AARR_DIMS(arr)); + + if (VARATT_IS_EXPANDED_HEADER(arr)) + { + /* we can just grab the type data from expanded array */ + fctx->elmlen = arr->xpn.typlen; + fctx->elmbyval = arr->xpn.typbyval; + fctx->elmalign = arr->xpn.typalign; + } + else + get_typlenbyvalalign(AARR_ELEMTYPE(arr), + &fctx->elmlen, + &fctx->elmbyval, + &fctx->elmalign); + + funcctx->user_fctx = fctx; + MemoryContextSwitchTo(oldcontext); + } + + /* stuff done on every call of the function */ + funcctx = SRF_PERCALL_SETUP(); + fctx = funcctx->user_fctx; + + if (fctx->nextelem < fctx->numelems) + { + int offset = fctx->nextelem++; + Datum elem; + + elem = array_iter_next(&fctx->iter, &fcinfo->isnull, offset, + fctx->elmlen, fctx->elmbyval, fctx->elmalign); + + SRF_RETURN_NEXT(funcctx, elem); + } + else + { + /* do when there is no more left */ + SRF_RETURN_DONE(funcctx); + } +} + +/* + * Planner support function for array_unnest(anyarray) + */ +Datum +array_unnest_support(PG_FUNCTION_ARGS) +{ + Node *rawreq = (Node *) PG_GETARG_POINTER(0); + Node *ret = NULL; + + if (IsA(rawreq, SupportRequestRows)) + { + /* Try to estimate the number of rows returned */ + SupportRequestRows *req = (SupportRequestRows *) rawreq; + + if (is_funcclause(req->node)) /* be paranoid */ + { + List *args = ((FuncExpr *) req->node)->args; + Node *arg1; + + /* We can use estimated argument values here */ + arg1 = estimate_expression_value(req->root, linitial(args)); + + req->rows = estimate_array_length(arg1); + ret = (Node *) req; + } + } + + PG_RETURN_POINTER(ret); +} + + +/* + * array_replace/array_remove support + * + * Find all array entries matching (not distinct from) search/search_isnull, + * and delete them if remove is true, else replace them with + * replace/replace_isnull. Comparisons are done using the specified + * collation. fcinfo is passed only for caching purposes. + */ +static ArrayType * +array_replace_internal(ArrayType *array, + Datum search, bool search_isnull, + Datum replace, bool replace_isnull, + bool remove, Oid collation, + FunctionCallInfo fcinfo) +{ + LOCAL_FCINFO(locfcinfo, 2); + ArrayType *result; + Oid element_type; + Datum *values; + bool *nulls; + int *dim; + int ndim; + int nitems, + nresult; + int i; + int32 nbytes = 0; + int32 dataoffset; + bool hasnulls; + int typlen; + bool typbyval; + char typalign; + char *arraydataptr; + bits8 *bitmap; + int bitmask; + bool changed = false; + TypeCacheEntry *typentry; + + element_type = ARR_ELEMTYPE(array); + ndim = ARR_NDIM(array); + dim = ARR_DIMS(array); + nitems = ArrayGetNItems(ndim, dim); + + /* Return input array unmodified if it is empty */ + if (nitems <= 0) + return array; + + /* + * We can't remove elements from multi-dimensional arrays, since the + * result might not be rectangular. + */ + if (remove && ndim > 1) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("removing elements from multidimensional arrays is not supported"))); + + /* + * We arrange to look up the equality function only once per series of + * calls, assuming the element type doesn't change underneath us. + */ + typentry = (TypeCacheEntry *) fcinfo->flinfo->fn_extra; + if (typentry == NULL || + typentry->type_id != element_type) + { + typentry = lookup_type_cache(element_type, + TYPECACHE_EQ_OPR_FINFO); + if (!OidIsValid(typentry->eq_opr_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify an equality operator for type %s", + format_type_be(element_type)))); + fcinfo->flinfo->fn_extra = (void *) typentry; + } + typlen = typentry->typlen; + typbyval = typentry->typbyval; + typalign = typentry->typalign; + + /* + * Detoast values if they are toasted. The replacement value must be + * detoasted for insertion into the result array, while detoasting the + * search value only once saves cycles. + */ + if (typlen == -1) + { + if (!search_isnull) + search = PointerGetDatum(PG_DETOAST_DATUM(search)); + if (!replace_isnull) + replace = PointerGetDatum(PG_DETOAST_DATUM(replace)); + } + + /* Prepare to apply the comparison operator */ + InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2, + collation, NULL, NULL); + + /* Allocate temporary arrays for new values */ + values = (Datum *) palloc(nitems * sizeof(Datum)); + nulls = (bool *) palloc(nitems * sizeof(bool)); + + /* Loop over source data */ + arraydataptr = ARR_DATA_PTR(array); + bitmap = ARR_NULLBITMAP(array); + bitmask = 1; + hasnulls = false; + nresult = 0; + + for (i = 0; i < nitems; i++) + { + Datum elt; + bool isNull; + bool oprresult; + bool skip = false; + + /* Get source element, checking for NULL */ + if (bitmap && (*bitmap & bitmask) == 0) + { + isNull = true; + /* If searching for NULL, we have a match */ + if (search_isnull) + { + if (remove) + { + skip = true; + changed = true; + } + else if (!replace_isnull) + { + values[nresult] = replace; + isNull = false; + changed = true; + } + } + } + else + { + isNull = false; + elt = fetch_att(arraydataptr, typbyval, typlen); + arraydataptr = att_addlength_datum(arraydataptr, typlen, elt); + arraydataptr = (char *) att_align_nominal(arraydataptr, typalign); + + if (search_isnull) + { + /* no match possible, keep element */ + values[nresult] = elt; + } + else + { + /* + * Apply the operator to the element pair; treat NULL as false + */ + locfcinfo->args[0].value = elt; + locfcinfo->args[0].isnull = false; + locfcinfo->args[1].value = search; + locfcinfo->args[1].isnull = false; + locfcinfo->isnull = false; + oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo)); + if (locfcinfo->isnull || !oprresult) + { + /* no match, keep element */ + values[nresult] = elt; + } + else + { + /* match, so replace or delete */ + changed = true; + if (remove) + skip = true; + else + { + values[nresult] = replace; + isNull = replace_isnull; + } + } + } + } + + if (!skip) + { + nulls[nresult] = isNull; + if (isNull) + hasnulls = true; + else + { + /* Update total result size */ + nbytes = att_addlength_datum(nbytes, typlen, values[nresult]); + nbytes = att_align_nominal(nbytes, typalign); + /* check for overflow of total request */ + if (!AllocSizeIsValid(nbytes)) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("array size exceeds the maximum allowed (%d)", + (int) MaxAllocSize))); + } + nresult++; + } + + /* advance bitmap pointer if any */ + if (bitmap) + { + bitmask <<= 1; + if (bitmask == 0x100) + { + bitmap++; + bitmask = 1; + } + } + } + + /* + * If not changed just return the original array + */ + if (!changed) + { + pfree(values); + pfree(nulls); + return array; + } + + /* If all elements were removed return an empty array */ + if (nresult == 0) + { + pfree(values); + pfree(nulls); + return construct_empty_array(element_type); + } + + /* Allocate and initialize the result array */ + if (hasnulls) + { + dataoffset = ARR_OVERHEAD_WITHNULLS(ndim, nresult); + nbytes += dataoffset; + } + else + { + dataoffset = 0; /* marker for no null bitmap */ + nbytes += ARR_OVERHEAD_NONULLS(ndim); + } + result = (ArrayType *) palloc0(nbytes); + SET_VARSIZE(result, nbytes); + result->ndim = ndim; + result->dataoffset = dataoffset; + result->elemtype = element_type; + memcpy(ARR_DIMS(result), ARR_DIMS(array), ndim * sizeof(int)); + memcpy(ARR_LBOUND(result), ARR_LBOUND(array), ndim * sizeof(int)); + + if (remove) + { + /* Adjust the result length */ + ARR_DIMS(result)[0] = nresult; + } + + /* Insert data into result array */ + CopyArrayEls(result, + values, nulls, nresult, + typlen, typbyval, typalign, + false); + + pfree(values); + pfree(nulls); + + return result; +} + +/* + * Remove any occurrences of an element from an array + * + * If used on a multi-dimensional array this will raise an error. + */ +Datum +array_remove(PG_FUNCTION_ARGS) +{ + ArrayType *array; + Datum search = PG_GETARG_DATUM(1); + bool search_isnull = PG_ARGISNULL(1); + + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + array = PG_GETARG_ARRAYTYPE_P(0); + + array = array_replace_internal(array, + search, search_isnull, + (Datum) 0, true, + true, PG_GET_COLLATION(), + fcinfo); + PG_RETURN_ARRAYTYPE_P(array); +} + +/* + * Replace any occurrences of an element in an array + */ +Datum +array_replace(PG_FUNCTION_ARGS) +{ + ArrayType *array; + Datum search = PG_GETARG_DATUM(1); + bool search_isnull = PG_ARGISNULL(1); + Datum replace = PG_GETARG_DATUM(2); + bool replace_isnull = PG_ARGISNULL(2); + + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + array = PG_GETARG_ARRAYTYPE_P(0); + + array = array_replace_internal(array, + search, search_isnull, + replace, replace_isnull, + false, PG_GET_COLLATION(), + fcinfo); + PG_RETURN_ARRAYTYPE_P(array); +} + +/* + * Implements width_bucket(anyelement, anyarray). + * + * 'thresholds' is an array containing lower bound values for each bucket; + * these must be sorted from smallest to largest, or bogus results will be + * produced. If N thresholds are supplied, the output is from 0 to N: + * 0 is for inputs < first threshold, N is for inputs >= last threshold. + */ +Datum +width_bucket_array(PG_FUNCTION_ARGS) +{ + Datum operand = PG_GETARG_DATUM(0); + ArrayType *thresholds = PG_GETARG_ARRAYTYPE_P(1); + Oid collation = PG_GET_COLLATION(); + Oid element_type = ARR_ELEMTYPE(thresholds); + int result; + + /* Check input */ + if (ARR_NDIM(thresholds) > 1) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("thresholds must be one-dimensional array"))); + + if (array_contains_nulls(thresholds)) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("thresholds array must not contain NULLs"))); + + /* We have a dedicated implementation for float8 data */ + if (element_type == FLOAT8OID) + result = width_bucket_array_float8(operand, thresholds); + else + { + TypeCacheEntry *typentry; + + /* Cache information about the input type */ + typentry = (TypeCacheEntry *) fcinfo->flinfo->fn_extra; + if (typentry == NULL || + typentry->type_id != element_type) + { + typentry = lookup_type_cache(element_type, + TYPECACHE_CMP_PROC_FINFO); + if (!OidIsValid(typentry->cmp_proc_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify a comparison function for type %s", + format_type_be(element_type)))); + fcinfo->flinfo->fn_extra = (void *) typentry; + } + + /* + * We have separate implementation paths for fixed- and variable-width + * types, since indexing the array is a lot cheaper in the first case. + */ + if (typentry->typlen > 0) + result = width_bucket_array_fixed(operand, thresholds, + collation, typentry); + else + result = width_bucket_array_variable(operand, thresholds, + collation, typentry); + } + + /* Avoid leaking memory when handed toasted input. */ + PG_FREE_IF_COPY(thresholds, 1); + + PG_RETURN_INT32(result); +} + +/* + * width_bucket_array for float8 data. + */ +static int +width_bucket_array_float8(Datum operand, ArrayType *thresholds) +{ + float8 op = DatumGetFloat8(operand); + float8 *thresholds_data; + int left; + int right; + + /* + * Since we know the array contains no NULLs, we can just index it + * directly. + */ + thresholds_data = (float8 *) ARR_DATA_PTR(thresholds); + + left = 0; + right = ArrayGetNItems(ARR_NDIM(thresholds), ARR_DIMS(thresholds)); + + /* + * If the probe value is a NaN, it's greater than or equal to all possible + * threshold values (including other NaNs), so we need not search. Note + * that this would give the same result as searching even if the array + * contains multiple NaNs (as long as they're correctly sorted), since the + * loop logic will find the rightmost of multiple equal threshold values. + */ + if (isnan(op)) + return right; + + /* Find the bucket */ + while (left < right) + { + int mid = (left + right) / 2; + + if (isnan(thresholds_data[mid]) || op < thresholds_data[mid]) + right = mid; + else + left = mid + 1; + } + + return left; +} + +/* + * width_bucket_array for generic fixed-width data types. + */ +static int +width_bucket_array_fixed(Datum operand, + ArrayType *thresholds, + Oid collation, + TypeCacheEntry *typentry) +{ + LOCAL_FCINFO(locfcinfo, 2); + char *thresholds_data; + int typlen = typentry->typlen; + bool typbyval = typentry->typbyval; + int left; + int right; + + /* + * Since we know the array contains no NULLs, we can just index it + * directly. + */ + thresholds_data = (char *) ARR_DATA_PTR(thresholds); + + InitFunctionCallInfoData(*locfcinfo, &typentry->cmp_proc_finfo, 2, + collation, NULL, NULL); + + /* Find the bucket */ + left = 0; + right = ArrayGetNItems(ARR_NDIM(thresholds), ARR_DIMS(thresholds)); + while (left < right) + { + int mid = (left + right) / 2; + char *ptr; + int32 cmpresult; + + ptr = thresholds_data + mid * typlen; + + locfcinfo->args[0].value = operand; + locfcinfo->args[0].isnull = false; + locfcinfo->args[1].value = fetch_att(ptr, typbyval, typlen); + locfcinfo->args[1].isnull = false; + + cmpresult = DatumGetInt32(FunctionCallInvoke(locfcinfo)); + + /* We don't expect comparison support functions to return null */ + Assert(!locfcinfo->isnull); + + if (cmpresult < 0) + right = mid; + else + left = mid + 1; + } + + return left; +} + +/* + * width_bucket_array for generic variable-width data types. + */ +static int +width_bucket_array_variable(Datum operand, + ArrayType *thresholds, + Oid collation, + TypeCacheEntry *typentry) +{ + LOCAL_FCINFO(locfcinfo, 2); + char *thresholds_data; + int typlen = typentry->typlen; + bool typbyval = typentry->typbyval; + char typalign = typentry->typalign; + int left; + int right; + + thresholds_data = (char *) ARR_DATA_PTR(thresholds); + + InitFunctionCallInfoData(*locfcinfo, &typentry->cmp_proc_finfo, 2, + collation, NULL, NULL); + + /* Find the bucket */ + left = 0; + right = ArrayGetNItems(ARR_NDIM(thresholds), ARR_DIMS(thresholds)); + while (left < right) + { + int mid = (left + right) / 2; + char *ptr; + int i; + int32 cmpresult; + + /* Locate mid'th array element by advancing from left element */ + ptr = thresholds_data; + for (i = left; i < mid; i++) + { + ptr = att_addlength_pointer(ptr, typlen, ptr); + ptr = (char *) att_align_nominal(ptr, typalign); + } + + locfcinfo->args[0].value = operand; + locfcinfo->args[0].isnull = false; + locfcinfo->args[1].value = fetch_att(ptr, typbyval, typlen); + locfcinfo->args[1].isnull = false; + + cmpresult = DatumGetInt32(FunctionCallInvoke(locfcinfo)); + + /* We don't expect comparison support functions to return null */ + Assert(!locfcinfo->isnull); + + if (cmpresult < 0) + right = mid; + else + { + left = mid + 1; + + /* + * Move the thresholds pointer to match new "left" index, so we + * don't have to seek over those elements again. This trick + * ensures we do only O(N) array indexing work, not O(N^2). + */ + ptr = att_addlength_pointer(ptr, typlen, ptr); + thresholds_data = (char *) att_align_nominal(ptr, typalign); + } + } + + return left; +} + +/* + * Trim the last N elements from an array by building an appropriate slice. + * Only the first dimension is trimmed. + */ +Datum +trim_array(PG_FUNCTION_ARGS) +{ + ArrayType *v = PG_GETARG_ARRAYTYPE_P(0); + int n = PG_GETARG_INT32(1); + int array_length = (ARR_NDIM(v) > 0) ? ARR_DIMS(v)[0] : 0; + int16 elmlen; + bool elmbyval; + char elmalign; + int lower[MAXDIM]; + int upper[MAXDIM]; + bool lowerProvided[MAXDIM]; + bool upperProvided[MAXDIM]; + Datum result; + + /* Per spec, throw an error if out of bounds */ + if (n < 0 || n > array_length) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_ELEMENT_ERROR), + errmsg("number of elements to trim must be between 0 and %d", + array_length))); + + /* Set all the bounds as unprovided except the first upper bound */ + memset(lowerProvided, false, sizeof(lowerProvided)); + memset(upperProvided, false, sizeof(upperProvided)); + if (ARR_NDIM(v) > 0) + { + upper[0] = ARR_LBOUND(v)[0] + array_length - n - 1; + upperProvided[0] = true; + } + + /* Fetch the needed information about the element type */ + get_typlenbyvalalign(ARR_ELEMTYPE(v), &elmlen, &elmbyval, &elmalign); + + /* Get the slice */ + result = array_get_slice(PointerGetDatum(v), 1, + upper, lower, upperProvided, lowerProvided, + -1, elmlen, elmbyval, elmalign); + + PG_RETURN_DATUM(result); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/arraysubs.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/arraysubs.c new file mode 100644 index 00000000000..66666fea98e --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/arraysubs.c @@ -0,0 +1,577 @@ +/*------------------------------------------------------------------------- + * + * arraysubs.c + * Subscripting support functions for arrays. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/adt/arraysubs.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "executor/execExpr.h" +#include "nodes/makefuncs.h" +#include "nodes/nodeFuncs.h" +#include "nodes/subscripting.h" +#include "parser/parse_coerce.h" +#include "parser/parse_expr.h" +#include "utils/array.h" +#include "utils/builtins.h" +#include "utils/lsyscache.h" + + +/* SubscriptingRefState.workspace for array subscripting execution */ +typedef struct ArraySubWorkspace +{ + /* Values determined during expression compilation */ + Oid refelemtype; /* OID of the array element type */ + int16 refattrlength; /* typlen of array type */ + int16 refelemlength; /* typlen of the array element type */ + bool refelembyval; /* is the element type pass-by-value? */ + char refelemalign; /* typalign of the element type */ + + /* + * Subscript values converted to integers. Note that these arrays must be + * of length MAXDIM even when dealing with fewer subscripts, because + * array_get/set_slice may scribble on the extra entries. + */ + int upperindex[MAXDIM]; + int lowerindex[MAXDIM]; +} ArraySubWorkspace; + + +/* + * Finish parse analysis of a SubscriptingRef expression for an array. + * + * Transform the subscript expressions, coerce them to integers, + * and determine the result type of the SubscriptingRef node. + */ +static void +array_subscript_transform(SubscriptingRef *sbsref, + List *indirection, + ParseState *pstate, + bool isSlice, + bool isAssignment) +{ + List *upperIndexpr = NIL; + List *lowerIndexpr = NIL; + ListCell *idx; + + /* + * Transform the subscript expressions, and separate upper and lower + * bounds into two lists. + * + * If we have a container slice expression, we convert any non-slice + * indirection items to slices by treating the single subscript as the + * upper bound and supplying an assumed lower bound of 1. + */ + foreach(idx, indirection) + { + A_Indices *ai = lfirst_node(A_Indices, idx); + Node *subexpr; + + if (isSlice) + { + if (ai->lidx) + { + subexpr = transformExpr(pstate, ai->lidx, pstate->p_expr_kind); + /* If it's not int4 already, try to coerce */ + subexpr = coerce_to_target_type(pstate, + subexpr, exprType(subexpr), + INT4OID, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (subexpr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("array subscript must have type integer"), + parser_errposition(pstate, exprLocation(ai->lidx)))); + } + else if (!ai->is_slice) + { + /* Make a constant 1 */ + subexpr = (Node *) makeConst(INT4OID, + -1, + InvalidOid, + sizeof(int32), + Int32GetDatum(1), + false, + true); /* pass by value */ + } + else + { + /* Slice with omitted lower bound, put NULL into the list */ + subexpr = NULL; + } + lowerIndexpr = lappend(lowerIndexpr, subexpr); + } + else + Assert(ai->lidx == NULL && !ai->is_slice); + + if (ai->uidx) + { + subexpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind); + /* If it's not int4 already, try to coerce */ + subexpr = coerce_to_target_type(pstate, + subexpr, exprType(subexpr), + INT4OID, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (subexpr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("array subscript must have type integer"), + parser_errposition(pstate, exprLocation(ai->uidx)))); + } + else + { + /* Slice with omitted upper bound, put NULL into the list */ + Assert(isSlice && ai->is_slice); + subexpr = NULL; + } + upperIndexpr = lappend(upperIndexpr, subexpr); + } + + /* ... and store the transformed lists into the SubscriptRef node */ + sbsref->refupperindexpr = upperIndexpr; + sbsref->reflowerindexpr = lowerIndexpr; + + /* Verify subscript list lengths are within implementation limit */ + if (list_length(upperIndexpr) > MAXDIM) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)", + list_length(upperIndexpr), MAXDIM))); + /* We need not check lowerIndexpr separately */ + + /* + * Determine the result type of the subscripting operation. It's the same + * as the array type if we're slicing, else it's the element type. In + * either case, the typmod is the same as the array's, so we need not + * change reftypmod. + */ + if (isSlice) + sbsref->refrestype = sbsref->refcontainertype; + else + sbsref->refrestype = sbsref->refelemtype; +} + +/* + * During execution, process the subscripts in a SubscriptingRef expression. + * + * The subscript expressions are already evaluated in Datum form in the + * SubscriptingRefState's arrays. Check and convert them as necessary. + * + * If any subscript is NULL, we throw error in assignment cases, or in fetch + * cases set result to NULL and return false (instructing caller to skip the + * rest of the SubscriptingRef sequence). + * + * We convert all the subscripts to plain integers and save them in the + * sbsrefstate->workspace arrays. + */ +static bool +array_subscript_check_subscripts(ExprState *state, + ExprEvalStep *op, + ExprContext *econtext) +{ + SubscriptingRefState *sbsrefstate = op->d.sbsref_subscript.state; + ArraySubWorkspace *workspace = (ArraySubWorkspace *) sbsrefstate->workspace; + + /* Process upper subscripts */ + for (int i = 0; i < sbsrefstate->numupper; i++) + { + if (sbsrefstate->upperprovided[i]) + { + /* If any index expr yields NULL, result is NULL or error */ + if (sbsrefstate->upperindexnull[i]) + { + if (sbsrefstate->isassignment) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("array subscript in assignment must not be null"))); + *op->resnull = true; + return false; + } + workspace->upperindex[i] = DatumGetInt32(sbsrefstate->upperindex[i]); + } + } + + /* Likewise for lower subscripts */ + for (int i = 0; i < sbsrefstate->numlower; i++) + { + if (sbsrefstate->lowerprovided[i]) + { + /* If any index expr yields NULL, result is NULL or error */ + if (sbsrefstate->lowerindexnull[i]) + { + if (sbsrefstate->isassignment) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("array subscript in assignment must not be null"))); + *op->resnull = true; + return false; + } + workspace->lowerindex[i] = DatumGetInt32(sbsrefstate->lowerindex[i]); + } + } + + return true; +} + +/* + * Evaluate SubscriptingRef fetch for an array element. + * + * Source container is in step's result variable (it's known not NULL, since + * we set fetch_strict to true), and indexes have already been evaluated into + * workspace array. + */ +static void +array_subscript_fetch(ExprState *state, + ExprEvalStep *op, + ExprContext *econtext) +{ + SubscriptingRefState *sbsrefstate = op->d.sbsref.state; + ArraySubWorkspace *workspace = (ArraySubWorkspace *) sbsrefstate->workspace; + + /* Should not get here if source array (or any subscript) is null */ + Assert(!(*op->resnull)); + + *op->resvalue = array_get_element(*op->resvalue, + sbsrefstate->numupper, + workspace->upperindex, + workspace->refattrlength, + workspace->refelemlength, + workspace->refelembyval, + workspace->refelemalign, + op->resnull); +} + +/* + * Evaluate SubscriptingRef fetch for an array slice. + * + * Source container is in step's result variable (it's known not NULL, since + * we set fetch_strict to true), and indexes have already been evaluated into + * workspace array. + */ +static void +array_subscript_fetch_slice(ExprState *state, + ExprEvalStep *op, + ExprContext *econtext) +{ + SubscriptingRefState *sbsrefstate = op->d.sbsref.state; + ArraySubWorkspace *workspace = (ArraySubWorkspace *) sbsrefstate->workspace; + + /* Should not get here if source array (or any subscript) is null */ + Assert(!(*op->resnull)); + + *op->resvalue = array_get_slice(*op->resvalue, + sbsrefstate->numupper, + workspace->upperindex, + workspace->lowerindex, + sbsrefstate->upperprovided, + sbsrefstate->lowerprovided, + workspace->refattrlength, + workspace->refelemlength, + workspace->refelembyval, + workspace->refelemalign); + /* The slice is never NULL, so no need to change *op->resnull */ +} + +/* + * Evaluate SubscriptingRef assignment for an array element assignment. + * + * Input container (possibly null) is in result area, replacement value is in + * SubscriptingRefState's replacevalue/replacenull. + */ +static void +array_subscript_assign(ExprState *state, + ExprEvalStep *op, + ExprContext *econtext) +{ + SubscriptingRefState *sbsrefstate = op->d.sbsref.state; + ArraySubWorkspace *workspace = (ArraySubWorkspace *) sbsrefstate->workspace; + Datum arraySource = *op->resvalue; + + /* + * For an assignment to a fixed-length array type, both the original array + * and the value to be assigned into it must be non-NULL, else we punt and + * return the original array. + */ + if (workspace->refattrlength > 0) + { + if (*op->resnull || sbsrefstate->replacenull) + return; + } + + /* + * For assignment to varlena arrays, we handle a NULL original array by + * substituting an empty (zero-dimensional) array; insertion of the new + * element will result in a singleton array value. It does not matter + * whether the new element is NULL. + */ + if (*op->resnull) + { + arraySource = PointerGetDatum(construct_empty_array(workspace->refelemtype)); + *op->resnull = false; + } + + *op->resvalue = array_set_element(arraySource, + sbsrefstate->numupper, + workspace->upperindex, + sbsrefstate->replacevalue, + sbsrefstate->replacenull, + workspace->refattrlength, + workspace->refelemlength, + workspace->refelembyval, + workspace->refelemalign); + /* The result is never NULL, so no need to change *op->resnull */ +} + +/* + * Evaluate SubscriptingRef assignment for an array slice assignment. + * + * Input container (possibly null) is in result area, replacement value is in + * SubscriptingRefState's replacevalue/replacenull. + */ +static void +array_subscript_assign_slice(ExprState *state, + ExprEvalStep *op, + ExprContext *econtext) +{ + SubscriptingRefState *sbsrefstate = op->d.sbsref.state; + ArraySubWorkspace *workspace = (ArraySubWorkspace *) sbsrefstate->workspace; + Datum arraySource = *op->resvalue; + + /* + * For an assignment to a fixed-length array type, both the original array + * and the value to be assigned into it must be non-NULL, else we punt and + * return the original array. + */ + if (workspace->refattrlength > 0) + { + if (*op->resnull || sbsrefstate->replacenull) + return; + } + + /* + * For assignment to varlena arrays, we handle a NULL original array by + * substituting an empty (zero-dimensional) array; insertion of the new + * element will result in a singleton array value. It does not matter + * whether the new element is NULL. + */ + if (*op->resnull) + { + arraySource = PointerGetDatum(construct_empty_array(workspace->refelemtype)); + *op->resnull = false; + } + + *op->resvalue = array_set_slice(arraySource, + sbsrefstate->numupper, + workspace->upperindex, + workspace->lowerindex, + sbsrefstate->upperprovided, + sbsrefstate->lowerprovided, + sbsrefstate->replacevalue, + sbsrefstate->replacenull, + workspace->refattrlength, + workspace->refelemlength, + workspace->refelembyval, + workspace->refelemalign); + /* The result is never NULL, so no need to change *op->resnull */ +} + +/* + * Compute old array element value for a SubscriptingRef assignment + * expression. Will only be called if the new-value subexpression + * contains SubscriptingRef or FieldStore. This is the same as the + * regular fetch case, except that we have to handle a null array, + * and the value should be stored into the SubscriptingRefState's + * prevvalue/prevnull fields. + */ +static void +array_subscript_fetch_old(ExprState *state, + ExprEvalStep *op, + ExprContext *econtext) +{ + SubscriptingRefState *sbsrefstate = op->d.sbsref.state; + ArraySubWorkspace *workspace = (ArraySubWorkspace *) sbsrefstate->workspace; + + if (*op->resnull) + { + /* whole array is null, so any element is too */ + sbsrefstate->prevvalue = (Datum) 0; + sbsrefstate->prevnull = true; + } + else + sbsrefstate->prevvalue = array_get_element(*op->resvalue, + sbsrefstate->numupper, + workspace->upperindex, + workspace->refattrlength, + workspace->refelemlength, + workspace->refelembyval, + workspace->refelemalign, + &sbsrefstate->prevnull); +} + +/* + * Compute old array slice value for a SubscriptingRef assignment + * expression. Will only be called if the new-value subexpression + * contains SubscriptingRef or FieldStore. This is the same as the + * regular fetch case, except that we have to handle a null array, + * and the value should be stored into the SubscriptingRefState's + * prevvalue/prevnull fields. + * + * Note: this is presently dead code, because the new value for a + * slice would have to be an array, so it couldn't directly contain a + * FieldStore; nor could it contain a SubscriptingRef assignment, since + * we consider adjacent subscripts to index one multidimensional array + * not nested array types. Future generalizations might make this + * reachable, however. + */ +static void +array_subscript_fetch_old_slice(ExprState *state, + ExprEvalStep *op, + ExprContext *econtext) +{ + SubscriptingRefState *sbsrefstate = op->d.sbsref.state; + ArraySubWorkspace *workspace = (ArraySubWorkspace *) sbsrefstate->workspace; + + if (*op->resnull) + { + /* whole array is null, so any slice is too */ + sbsrefstate->prevvalue = (Datum) 0; + sbsrefstate->prevnull = true; + } + else + { + sbsrefstate->prevvalue = array_get_slice(*op->resvalue, + sbsrefstate->numupper, + workspace->upperindex, + workspace->lowerindex, + sbsrefstate->upperprovided, + sbsrefstate->lowerprovided, + workspace->refattrlength, + workspace->refelemlength, + workspace->refelembyval, + workspace->refelemalign); + /* slices of non-null arrays are never null */ + sbsrefstate->prevnull = false; + } +} + +/* + * Set up execution state for an array subscript operation. + */ +static void +array_exec_setup(const SubscriptingRef *sbsref, + SubscriptingRefState *sbsrefstate, + SubscriptExecSteps *methods) +{ + bool is_slice = (sbsrefstate->numlower != 0); + ArraySubWorkspace *workspace; + + /* + * Enforce the implementation limit on number of array subscripts. This + * check isn't entirely redundant with checking at parse time; conceivably + * the expression was stored by a backend with a different MAXDIM value. + */ + if (sbsrefstate->numupper > MAXDIM) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)", + sbsrefstate->numupper, MAXDIM))); + + /* Should be impossible if parser is sane, but check anyway: */ + if (sbsrefstate->numlower != 0 && + sbsrefstate->numupper != sbsrefstate->numlower) + elog(ERROR, "upper and lower index lists are not same length"); + + /* + * Allocate type-specific workspace. + */ + workspace = (ArraySubWorkspace *) palloc(sizeof(ArraySubWorkspace)); + sbsrefstate->workspace = workspace; + + /* + * Collect datatype details we'll need at execution. + */ + workspace->refelemtype = sbsref->refelemtype; + workspace->refattrlength = get_typlen(sbsref->refcontainertype); + get_typlenbyvalalign(sbsref->refelemtype, + &workspace->refelemlength, + &workspace->refelembyval, + &workspace->refelemalign); + + /* + * Pass back pointers to appropriate step execution functions. + */ + methods->sbs_check_subscripts = array_subscript_check_subscripts; + if (is_slice) + { + methods->sbs_fetch = array_subscript_fetch_slice; + methods->sbs_assign = array_subscript_assign_slice; + methods->sbs_fetch_old = array_subscript_fetch_old_slice; + } + else + { + methods->sbs_fetch = array_subscript_fetch; + methods->sbs_assign = array_subscript_assign; + methods->sbs_fetch_old = array_subscript_fetch_old; + } +} + +/* + * array_subscript_handler + * Subscripting handler for standard varlena arrays. + * + * This should be used only for "true" array types, which have array headers + * as understood by the varlena array routines, and are referenced by the + * element type's pg_type.typarray field. + */ +Datum +array_subscript_handler(PG_FUNCTION_ARGS) +{ + static const SubscriptRoutines sbsroutines = { + .transform = array_subscript_transform, + .exec_setup = array_exec_setup, + .fetch_strict = true, /* fetch returns NULL for NULL inputs */ + .fetch_leakproof = true, /* fetch returns NULL for bad subscript */ + .store_leakproof = false /* ... but assignment throws error */ + }; + + PG_RETURN_POINTER(&sbsroutines); +} + +/* + * raw_array_subscript_handler + * Subscripting handler for "raw" arrays. + * + * A "raw" array just contains N independent instances of the element type. + * Currently we require both the element type and the array type to be fixed + * length, but it wouldn't be too hard to relax that for the array type. + * + * As of now, all the support code is shared with standard varlena arrays. + * We may split those into separate code paths, but probably that would yield + * only marginal speedups. The main point of having a separate handler is + * so that pg_type.typsubscript clearly indicates the type's semantics. + */ +Datum +raw_array_subscript_handler(PG_FUNCTION_ARGS) +{ + static const SubscriptRoutines sbsroutines = { + .transform = array_subscript_transform, + .exec_setup = array_exec_setup, + .fetch_strict = true, /* fetch returns NULL for NULL inputs */ + .fetch_leakproof = true, /* fetch returns NULL for bad subscript */ + .store_leakproof = false /* ... but assignment throws error */ + }; + + PG_RETURN_POINTER(&sbsroutines); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/arrayutils.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/arrayutils.c new file mode 100644 index 00000000000..aed799234cd --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/arrayutils.c @@ -0,0 +1,279 @@ +/*------------------------------------------------------------------------- + * + * arrayutils.c + * This file contains some support routines required for array functions. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/adt/arrayutils.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "catalog/pg_type.h" +#include "common/int.h" +#include "utils/array.h" +#include "utils/builtins.h" +#include "utils/memutils.h" + + +/* + * Convert subscript list into linear element number (from 0) + * + * We assume caller has already range-checked the dimensions and subscripts, + * so no overflow is possible. + */ +int +ArrayGetOffset(int n, const int *dim, const int *lb, const int *indx) +{ + int i, + scale = 1, + offset = 0; + + for (i = n - 1; i >= 0; i--) + { + offset += (indx[i] - lb[i]) * scale; + scale *= dim[i]; + } + return offset; +} + +/* + * Same, but subscripts are assumed 0-based, and use a scale array + * instead of raw dimension data (see mda_get_prod to create scale array) + */ +int +ArrayGetOffset0(int n, const int *tup, const int *scale) +{ + int i, + lin = 0; + + for (i = 0; i < n; i++) + lin += tup[i] * scale[i]; + return lin; +} + +/* + * Convert array dimensions into number of elements + * + * This must do overflow checking, since it is used to validate that a user + * dimensionality request doesn't overflow what we can handle. + * + * The multiplication overflow check only works on machines that have int64 + * arithmetic, but that is nearly all platforms these days, and doing check + * divides for those that don't seems way too expensive. + */ +int +ArrayGetNItems(int ndim, const int *dims) +{ + return ArrayGetNItemsSafe(ndim, dims, NULL); +} + +/* + * This entry point can return the error into an ErrorSaveContext + * instead of throwing an exception. -1 is returned after an error. + */ +int +ArrayGetNItemsSafe(int ndim, const int *dims, struct Node *escontext) +{ + int32 ret; + int i; + + if (ndim <= 0) + return 0; + ret = 1; + for (i = 0; i < ndim; i++) + { + int64 prod; + + /* A negative dimension implies that UB-LB overflowed ... */ + if (dims[i] < 0) + ereturn(escontext, -1, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("array size exceeds the maximum allowed (%d)", + (int) MaxArraySize))); + + prod = (int64) ret * (int64) dims[i]; + + ret = (int32) prod; + if ((int64) ret != prod) + ereturn(escontext, -1, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("array size exceeds the maximum allowed (%d)", + (int) MaxArraySize))); + } + Assert(ret >= 0); + if ((Size) ret > MaxArraySize) + ereturn(escontext, -1, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("array size exceeds the maximum allowed (%d)", + (int) MaxArraySize))); + return (int) ret; +} + +/* + * Verify sanity of proposed lower-bound values for an array + * + * The lower-bound values must not be so large as to cause overflow when + * calculating subscripts, e.g. lower bound 2147483640 with length 10 + * must be disallowed. We actually insist that dims[i] + lb[i] be + * computable without overflow, meaning that an array with last subscript + * equal to INT_MAX will be disallowed. + * + * It is assumed that the caller already called ArrayGetNItems, so that + * overflowed (negative) dims[] values have been eliminated. + */ +void +ArrayCheckBounds(int ndim, const int *dims, const int *lb) +{ + (void) ArrayCheckBoundsSafe(ndim, dims, lb, NULL); +} + +/* + * This entry point can return the error into an ErrorSaveContext + * instead of throwing an exception. + */ +bool +ArrayCheckBoundsSafe(int ndim, const int *dims, const int *lb, + struct Node *escontext) +{ + int i; + + for (i = 0; i < ndim; i++) + { + /* PG_USED_FOR_ASSERTS_ONLY prevents variable-isn't-read warnings */ + int32 sum PG_USED_FOR_ASSERTS_ONLY; + + if (pg_add_s32_overflow(dims[i], lb[i], &sum)) + ereturn(escontext, false, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("array lower bound is too large: %d", + lb[i]))); + } + + return true; +} + +/* + * Compute ranges (sub-array dimensions) for an array slice + * + * We assume caller has validated slice endpoints, so overflow is impossible + */ +void +mda_get_range(int n, int *span, const int *st, const int *endp) +{ + int i; + + for (i = 0; i < n; i++) + span[i] = endp[i] - st[i] + 1; +} + +/* + * Compute products of array dimensions, ie, scale factors for subscripts + * + * We assume caller has validated dimensions, so overflow is impossible + */ +void +mda_get_prod(int n, const int *range, int *prod) +{ + int i; + + prod[n - 1] = 1; + for (i = n - 2; i >= 0; i--) + prod[i] = prod[i + 1] * range[i + 1]; +} + +/* + * From products of whole-array dimensions and spans of a sub-array, + * compute offset distances needed to step through subarray within array + * + * We assume caller has validated dimensions, so overflow is impossible + */ +void +mda_get_offset_values(int n, int *dist, const int *prod, const int *span) +{ + int i, + j; + + dist[n - 1] = 0; + for (j = n - 2; j >= 0; j--) + { + dist[j] = prod[j] - 1; + for (i = j + 1; i < n; i++) + dist[j] -= (span[i] - 1) * prod[i]; + } +} + +/* + * Generates the tuple that is lexicographically one greater than the current + * n-tuple in "curr", with the restriction that the i-th element of "curr" is + * less than the i-th element of "span". + * + * Returns -1 if no next tuple exists, else the subscript position (0..n-1) + * corresponding to the dimension to advance along. + * + * We assume caller has validated dimensions, so overflow is impossible + */ +int +mda_next_tuple(int n, int *curr, const int *span) +{ + int i; + + if (n <= 0) + return -1; + + curr[n - 1] = (curr[n - 1] + 1) % span[n - 1]; + for (i = n - 1; i && curr[i] == 0; i--) + curr[i - 1] = (curr[i - 1] + 1) % span[i - 1]; + + if (i) + return i; + if (curr[0]) + return 0; + + return -1; +} + +/* + * ArrayGetIntegerTypmods: verify that argument is a 1-D cstring array, + * and get the contents converted to integers. Returns a palloc'd array + * and places the length at *n. + */ +int32 * +ArrayGetIntegerTypmods(ArrayType *arr, int *n) +{ + int32 *result; + Datum *elem_values; + int i; + + if (ARR_ELEMTYPE(arr) != CSTRINGOID) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_ELEMENT_ERROR), + errmsg("typmod array must be type cstring[]"))); + + if (ARR_NDIM(arr) != 1) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("typmod array must be one-dimensional"))); + + if (array_contains_nulls(arr)) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("typmod array must not contain nulls"))); + + deconstruct_array_builtin(arr, CSTRINGOID, &elem_values, NULL, n); + + result = (int32 *) palloc(*n * sizeof(int32)); + + for (i = 0; i < *n; i++) + result[i] = pg_strtoint32(DatumGetCString(elem_values[i])); + + pfree(elem_values); + + return result; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/ascii.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/ascii.c new file mode 100644 index 00000000000..b6944d80934 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/ascii.c @@ -0,0 +1,199 @@ +/*----------------------------------------------------------------------- + * ascii.c + * The PostgreSQL routine for string to ascii conversion. + * + * Portions Copyright (c) 1999-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/utils/adt/ascii.c + * + *----------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "mb/pg_wchar.h" +#include "utils/ascii.h" +#include "utils/builtins.h" +#include "varatt.h" + +static void pg_to_ascii(unsigned char *src, unsigned char *src_end, + unsigned char *dest, int enc); +static text *encode_to_ascii(text *data, int enc); + + +/* ---------- + * to_ascii + * ---------- + */ +static void +pg_to_ascii(unsigned char *src, unsigned char *src_end, unsigned char *dest, int enc) +{ + unsigned char *x; + const unsigned char *ascii; + int range; + + /* + * relevant start for an encoding + */ +#define RANGE_128 128 +#define RANGE_160 160 + + if (enc == PG_LATIN1) + { + /* + * ISO-8859-1 <range: 160 -- 255> + */ + ascii = (const unsigned char *) " cL Y \"Ca -R 'u ., ?AAAAAAACEEEEIIII NOOOOOxOUUUUYTBaaaaaaaceeeeiiii nooooo/ouuuuyty"; + range = RANGE_160; + } + else if (enc == PG_LATIN2) + { + /* + * ISO-8859-2 <range: 160 -- 255> + */ + ascii = (const unsigned char *) " A L LS \"SSTZ-ZZ a,l'ls ,sstz\"zzRAAAALCCCEEEEIIDDNNOOOOxRUUUUYTBraaaalccceeeeiiddnnoooo/ruuuuyt."; + range = RANGE_160; + } + else if (enc == PG_LATIN9) + { + /* + * ISO-8859-15 <range: 160 -- 255> + */ + ascii = (const unsigned char *) " cL YS sCa -R Zu .z EeY?AAAAAAACEEEEIIII NOOOOOxOUUUUYTBaaaaaaaceeeeiiii nooooo/ouuuuyty"; + range = RANGE_160; + } + else if (enc == PG_WIN1250) + { + /* + * Window CP1250 <range: 128 -- 255> + */ + ascii = (const unsigned char *) " ' \" %S<STZZ `'\"\".-- s>stzz L A \"CS -RZ ,l'u .,as L\"lzRAAAALCCCEEEEIIDDNNOOOOxRUUUUYTBraaaalccceeeeiiddnnoooo/ruuuuyt "; + range = RANGE_128; + } + else + { + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("encoding conversion from %s to ASCII not supported", + pg_encoding_to_char(enc)))); + return; /* keep compiler quiet */ + } + + /* + * Encode + */ + for (x = src; x < src_end; x++) + { + if (*x < 128) + *dest++ = *x; + else if (*x < range) + *dest++ = ' '; /* bogus 128 to 'range' */ + else + *dest++ = ascii[*x - range]; + } +} + +/* ---------- + * encode text + * + * The text datum is overwritten in-place, therefore this coding method + * cannot support conversions that change the string length! + * ---------- + */ +static text * +encode_to_ascii(text *data, int enc) +{ + pg_to_ascii((unsigned char *) VARDATA(data), /* src */ + (unsigned char *) (data) + VARSIZE(data), /* src end */ + (unsigned char *) VARDATA(data), /* dest */ + enc); /* encoding */ + + return data; +} + +/* ---------- + * convert to ASCII - enc is set as 'name' arg. + * ---------- + */ +Datum +to_ascii_encname(PG_FUNCTION_ARGS) +{ + text *data = PG_GETARG_TEXT_P_COPY(0); + char *encname = NameStr(*PG_GETARG_NAME(1)); + int enc = pg_char_to_encoding(encname); + + if (enc < 0) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("%s is not a valid encoding name", encname))); + + PG_RETURN_TEXT_P(encode_to_ascii(data, enc)); +} + +/* ---------- + * convert to ASCII - enc is set as int4 + * ---------- + */ +Datum +to_ascii_enc(PG_FUNCTION_ARGS) +{ + text *data = PG_GETARG_TEXT_P_COPY(0); + int enc = PG_GETARG_INT32(1); + + if (!PG_VALID_ENCODING(enc)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("%d is not a valid encoding code", enc))); + + PG_RETURN_TEXT_P(encode_to_ascii(data, enc)); +} + +/* ---------- + * convert to ASCII - current enc is DatabaseEncoding + * ---------- + */ +Datum +to_ascii_default(PG_FUNCTION_ARGS) +{ + text *data = PG_GETARG_TEXT_P_COPY(0); + int enc = GetDatabaseEncoding(); + + PG_RETURN_TEXT_P(encode_to_ascii(data, enc)); +} + +/* ---------- + * Copy a string in an arbitrary backend-safe encoding, converting it to a + * valid ASCII string by replacing non-ASCII bytes with '?'. Otherwise the + * behavior is identical to strlcpy(), except that we don't bother with a + * return value. + * + * This must not trigger ereport(ERROR), as it is called in postmaster. + * ---------- + */ +void +ascii_safe_strlcpy(char *dest, const char *src, size_t destsiz) +{ + if (destsiz == 0) /* corner case: no room for trailing nul */ + return; + + while (--destsiz > 0) + { + /* use unsigned char here to avoid compiler warning */ + unsigned char ch = *src++; + + if (ch == '\0') + break; + /* Keep printable ASCII characters */ + if (32 <= ch && ch <= 127) + *dest = ch; + /* White-space is also OK */ + else if (ch == '\n' || ch == '\r' || ch == '\t') + *dest = ch; + /* Everything else is replaced with '?' */ + else + *dest = '?'; + dest++; + } + + *dest = '\0'; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/bool.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/bool.c new file mode 100644 index 00000000000..cc4bd550354 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/bool.c @@ -0,0 +1,401 @@ +/*------------------------------------------------------------------------- + * + * bool.c + * Functions for the built-in type "bool". + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/adt/bool.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include <ctype.h> + +#include "libpq/pqformat.h" +#include "utils/builtins.h" + +/* + * Try to interpret value as boolean value. Valid values are: true, + * false, yes, no, on, off, 1, 0; as well as unique prefixes thereof. + * If the string parses okay, return true, else false. + * If okay and result is not NULL, return the value in *result. + */ +bool +parse_bool(const char *value, bool *result) +{ + return parse_bool_with_len(value, strlen(value), result); +} + +bool +parse_bool_with_len(const char *value, size_t len, bool *result) +{ + switch (*value) + { + case 't': + case 'T': + if (pg_strncasecmp(value, "true", len) == 0) + { + if (result) + *result = true; + return true; + } + break; + case 'f': + case 'F': + if (pg_strncasecmp(value, "false", len) == 0) + { + if (result) + *result = false; + return true; + } + break; + case 'y': + case 'Y': + if (pg_strncasecmp(value, "yes", len) == 0) + { + if (result) + *result = true; + return true; + } + break; + case 'n': + case 'N': + if (pg_strncasecmp(value, "no", len) == 0) + { + if (result) + *result = false; + return true; + } + break; + case 'o': + case 'O': + /* 'o' is not unique enough */ + if (pg_strncasecmp(value, "on", (len > 2 ? len : 2)) == 0) + { + if (result) + *result = true; + return true; + } + else if (pg_strncasecmp(value, "off", (len > 2 ? len : 2)) == 0) + { + if (result) + *result = false; + return true; + } + break; + case '1': + if (len == 1) + { + if (result) + *result = true; + return true; + } + break; + case '0': + if (len == 1) + { + if (result) + *result = false; + return true; + } + break; + default: + break; + } + + if (result) + *result = false; /* suppress compiler warning */ + return false; +} + +/***************************************************************************** + * USER I/O ROUTINES * + *****************************************************************************/ + +/* + * boolin - converts "t" or "f" to 1 or 0 + * + * Check explicitly for "true/false" and TRUE/FALSE, 1/0, YES/NO, ON/OFF. + * Reject other values. + * + * In the switch statement, check the most-used possibilities first. + */ +Datum +boolin(PG_FUNCTION_ARGS) +{ + const char *in_str = PG_GETARG_CSTRING(0); + const char *str; + size_t len; + bool result; + + /* + * Skip leading and trailing whitespace + */ + str = in_str; + while (isspace((unsigned char) *str)) + str++; + + len = strlen(str); + while (len > 0 && isspace((unsigned char) str[len - 1])) + len--; + + if (parse_bool_with_len(str, len, &result)) + PG_RETURN_BOOL(result); + + ereturn(fcinfo->context, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s: \"%s\"", + "boolean", in_str))); +} + +/* + * boolout - converts 1 or 0 to "t" or "f" + */ +Datum +boolout(PG_FUNCTION_ARGS) +{ + bool b = PG_GETARG_BOOL(0); + char *result = (char *) palloc(2); + + result[0] = (b) ? 't' : 'f'; + result[1] = '\0'; + PG_RETURN_CSTRING(result); +} + +/* + * boolrecv - converts external binary format to bool + * + * The external representation is one byte. Any nonzero value is taken + * as "true". + */ +Datum +boolrecv(PG_FUNCTION_ARGS) +{ + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + int ext; + + ext = pq_getmsgbyte(buf); + PG_RETURN_BOOL(ext != 0); +} + +/* + * boolsend - converts bool to binary format + */ +Datum +boolsend(PG_FUNCTION_ARGS) +{ + bool arg1 = PG_GETARG_BOOL(0); + StringInfoData buf; + + pq_begintypsend(&buf); + pq_sendbyte(&buf, arg1 ? 1 : 0); + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} + +/* + * booltext - cast function for bool => text + * + * We need this because it's different from the behavior of boolout(); + * this function follows the SQL-spec result (except for producing lower case) + */ +Datum +booltext(PG_FUNCTION_ARGS) +{ + bool arg1 = PG_GETARG_BOOL(0); + const char *str; + + if (arg1) + str = "true"; + else + str = "false"; + + PG_RETURN_TEXT_P(cstring_to_text(str)); +} + + +/***************************************************************************** + * PUBLIC ROUTINES * + *****************************************************************************/ + +Datum +booleq(PG_FUNCTION_ARGS) +{ + bool arg1 = PG_GETARG_BOOL(0); + bool arg2 = PG_GETARG_BOOL(1); + + PG_RETURN_BOOL(arg1 == arg2); +} + +Datum +boolne(PG_FUNCTION_ARGS) +{ + bool arg1 = PG_GETARG_BOOL(0); + bool arg2 = PG_GETARG_BOOL(1); + + PG_RETURN_BOOL(arg1 != arg2); +} + +Datum +boollt(PG_FUNCTION_ARGS) +{ + bool arg1 = PG_GETARG_BOOL(0); + bool arg2 = PG_GETARG_BOOL(1); + + PG_RETURN_BOOL(arg1 < arg2); +} + +Datum +boolgt(PG_FUNCTION_ARGS) +{ + bool arg1 = PG_GETARG_BOOL(0); + bool arg2 = PG_GETARG_BOOL(1); + + PG_RETURN_BOOL(arg1 > arg2); +} + +Datum +boolle(PG_FUNCTION_ARGS) +{ + bool arg1 = PG_GETARG_BOOL(0); + bool arg2 = PG_GETARG_BOOL(1); + + PG_RETURN_BOOL(arg1 <= arg2); +} + +Datum +boolge(PG_FUNCTION_ARGS) +{ + bool arg1 = PG_GETARG_BOOL(0); + bool arg2 = PG_GETARG_BOOL(1); + + PG_RETURN_BOOL(arg1 >= arg2); +} + +/* + * boolean-and and boolean-or aggregates. + */ + +/* + * Function for standard EVERY aggregate conforming to SQL 2003. + * The aggregate is also named bool_and for consistency. + * + * Note: this is only used in plain aggregate mode, not moving-aggregate mode. + */ +Datum +booland_statefunc(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(PG_GETARG_BOOL(0) && PG_GETARG_BOOL(1)); +} + +/* + * Function for standard ANY/SOME aggregate conforming to SQL 2003. + * The aggregate is named bool_or, because ANY/SOME have parsing conflicts. + * + * Note: this is only used in plain aggregate mode, not moving-aggregate mode. + */ +Datum +boolor_statefunc(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(PG_GETARG_BOOL(0) || PG_GETARG_BOOL(1)); +} + +typedef struct BoolAggState +{ + int64 aggcount; /* number of non-null values aggregated */ + int64 aggtrue; /* number of values aggregated that are true */ +} BoolAggState; + +static BoolAggState * +makeBoolAggState(FunctionCallInfo fcinfo) +{ + BoolAggState *state; + MemoryContext agg_context; + + if (!AggCheckCallContext(fcinfo, &agg_context)) + elog(ERROR, "aggregate function called in non-aggregate context"); + + state = (BoolAggState *) MemoryContextAlloc(agg_context, + sizeof(BoolAggState)); + state->aggcount = 0; + state->aggtrue = 0; + + return state; +} + +Datum +bool_accum(PG_FUNCTION_ARGS) +{ + BoolAggState *state; + + state = PG_ARGISNULL(0) ? NULL : (BoolAggState *) PG_GETARG_POINTER(0); + + /* Create the state data on first call */ + if (state == NULL) + state = makeBoolAggState(fcinfo); + + if (!PG_ARGISNULL(1)) + { + state->aggcount++; + if (PG_GETARG_BOOL(1)) + state->aggtrue++; + } + + PG_RETURN_POINTER(state); +} + +Datum +bool_accum_inv(PG_FUNCTION_ARGS) +{ + BoolAggState *state; + + state = PG_ARGISNULL(0) ? NULL : (BoolAggState *) PG_GETARG_POINTER(0); + + /* bool_accum should have created the state data */ + if (state == NULL) + elog(ERROR, "bool_accum_inv called with NULL state"); + + if (!PG_ARGISNULL(1)) + { + state->aggcount--; + if (PG_GETARG_BOOL(1)) + state->aggtrue--; + } + + PG_RETURN_POINTER(state); +} + +Datum +bool_alltrue(PG_FUNCTION_ARGS) +{ + BoolAggState *state; + + state = PG_ARGISNULL(0) ? NULL : (BoolAggState *) PG_GETARG_POINTER(0); + + /* if there were no non-null values, return NULL */ + if (state == NULL || state->aggcount == 0) + PG_RETURN_NULL(); + + /* true if all non-null values are true */ + PG_RETURN_BOOL(state->aggtrue == state->aggcount); +} + +Datum +bool_anytrue(PG_FUNCTION_ARGS) +{ + BoolAggState *state; + + state = PG_ARGISNULL(0) ? NULL : (BoolAggState *) PG_GETARG_POINTER(0); + + /* if there were no non-null values, return NULL */ + if (state == NULL || state->aggcount == 0) + PG_RETURN_NULL(); + + /* true if any non-null value is true */ + PG_RETURN_BOOL(state->aggtrue > 0); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/cash.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/cash.c new file mode 100644 index 00000000000..f15448018fe --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/cash.c @@ -0,0 +1,1176 @@ +/* + * cash.c + * Written by D'Arcy J.M. Cain + * darcy@druid.net + * http://www.druid.net/darcy/ + * + * Functions to allow input and output of money normally but store + * and handle it as 64 bit ints + * + * A slightly modified version of this file and a discussion of the + * workings can be found in the book "Software Solutions in C" by + * Dale Schumacher, Academic Press, ISBN: 0-12-632360-7 except that + * this version handles 64 bit numbers and so can hold values up to + * $92,233,720,368,547,758.07. + * + * src/backend/utils/adt/cash.c + */ + +#include "postgres.h" + +#include <limits.h> +#include <ctype.h> +#include <math.h> + +#include "common/int.h" +#include "libpq/pqformat.h" +#include "utils/builtins.h" +#include "utils/cash.h" +#include "utils/numeric.h" +#include "utils/pg_locale.h" + + +/************************************************************************* + * Private routines + ************************************************************************/ + +static const char * +num_word(Cash value) +{ + static __thread char buf[128]; + static const char *const small[] = { + "zero", "one", "two", "three", "four", "five", "six", "seven", + "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen", + "fifteen", "sixteen", "seventeen", "eighteen", "nineteen", "twenty", + "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety" + }; + const char *const *big = small + 18; + int tu = value % 100; + + /* deal with the simple cases first */ + if (value <= 20) + return small[value]; + + /* is it an even multiple of 100? */ + if (!tu) + { + sprintf(buf, "%s hundred", small[value / 100]); + return buf; + } + + /* more than 99? */ + if (value > 99) + { + /* is it an even multiple of 10 other than 10? */ + if (value % 10 == 0 && tu > 10) + sprintf(buf, "%s hundred %s", + small[value / 100], big[tu / 10]); + else if (tu < 20) + sprintf(buf, "%s hundred and %s", + small[value / 100], small[tu]); + else + sprintf(buf, "%s hundred %s %s", + small[value / 100], big[tu / 10], small[tu % 10]); + } + else + { + /* is it an even multiple of 10 other than 10? */ + if (value % 10 == 0 && tu > 10) + sprintf(buf, "%s", big[tu / 10]); + else if (tu < 20) + sprintf(buf, "%s", small[tu]); + else + sprintf(buf, "%s %s", big[tu / 10], small[tu % 10]); + } + + return buf; +} /* num_word() */ + +/* cash_in() + * Convert a string to a cash data type. + * Format is [$]###[,]###[.##] + * Examples: 123.45 $123.45 $123,456.78 + * + */ +Datum +cash_in(PG_FUNCTION_ARGS) +{ + char *str = PG_GETARG_CSTRING(0); + Node *escontext = fcinfo->context; + Cash result; + Cash value = 0; + Cash dec = 0; + Cash sgn = 1; + bool seen_dot = false; + const char *s = str; + int fpoint; + char dsymbol; + const char *ssymbol, + *psymbol, + *nsymbol, + *csymbol; + struct lconv *lconvert = PGLC_localeconv(); + + /* + * frac_digits will be CHAR_MAX in some locales, notably C. However, just + * testing for == CHAR_MAX is risky, because of compilers like gcc that + * "helpfully" let you alter the platform-standard definition of whether + * char is signed or not. If we are so unfortunate as to get compiled + * with a nonstandard -fsigned-char or -funsigned-char switch, then our + * idea of CHAR_MAX will not agree with libc's. The safest course is not + * to test for CHAR_MAX at all, but to impose a range check for plausible + * frac_digits values. + */ + fpoint = lconvert->frac_digits; + if (fpoint < 0 || fpoint > 10) + fpoint = 2; /* best guess in this case, I think */ + + /* we restrict dsymbol to be a single byte, but not the other symbols */ + if (*lconvert->mon_decimal_point != '\0' && + lconvert->mon_decimal_point[1] == '\0') + dsymbol = *lconvert->mon_decimal_point; + else + dsymbol = '.'; + if (*lconvert->mon_thousands_sep != '\0') + ssymbol = lconvert->mon_thousands_sep; + else /* ssymbol should not equal dsymbol */ + ssymbol = (dsymbol != ',') ? "," : "."; + csymbol = (*lconvert->currency_symbol != '\0') ? lconvert->currency_symbol : "$"; + psymbol = (*lconvert->positive_sign != '\0') ? lconvert->positive_sign : "+"; + nsymbol = (*lconvert->negative_sign != '\0') ? lconvert->negative_sign : "-"; + +#ifdef CASHDEBUG + printf("cashin- precision '%d'; decimal '%c'; thousands '%s'; currency '%s'; positive '%s'; negative '%s'\n", + fpoint, dsymbol, ssymbol, csymbol, psymbol, nsymbol); +#endif + + /* we need to add all sorts of checking here. For now just */ + /* strip all leading whitespace and any leading currency symbol */ + while (isspace((unsigned char) *s)) + s++; + if (strncmp(s, csymbol, strlen(csymbol)) == 0) + s += strlen(csymbol); + while (isspace((unsigned char) *s)) + s++; + +#ifdef CASHDEBUG + printf("cashin- string is '%s'\n", s); +#endif + + /* a leading minus or paren signifies a negative number */ + /* again, better heuristics needed */ + /* XXX - doesn't properly check for balanced parens - djmc */ + if (strncmp(s, nsymbol, strlen(nsymbol)) == 0) + { + sgn = -1; + s += strlen(nsymbol); + } + else if (*s == '(') + { + sgn = -1; + s++; + } + else if (strncmp(s, psymbol, strlen(psymbol)) == 0) + s += strlen(psymbol); + +#ifdef CASHDEBUG + printf("cashin- string is '%s'\n", s); +#endif + + /* allow whitespace and currency symbol after the sign, too */ + while (isspace((unsigned char) *s)) + s++; + if (strncmp(s, csymbol, strlen(csymbol)) == 0) + s += strlen(csymbol); + while (isspace((unsigned char) *s)) + s++; + +#ifdef CASHDEBUG + printf("cashin- string is '%s'\n", s); +#endif + + /* + * We accumulate the absolute amount in "value" and then apply the sign at + * the end. (The sign can appear before or after the digits, so it would + * be more complicated to do otherwise.) Because of the larger range of + * negative signed integers, we build "value" in the negative and then + * flip the sign at the end, catching most-negative-number overflow if + * necessary. + */ + + for (; *s; s++) + { + /* + * We look for digits as long as we have found less than the required + * number of decimal places. + */ + if (isdigit((unsigned char) *s) && (!seen_dot || dec < fpoint)) + { + int8 digit = *s - '0'; + + if (pg_mul_s64_overflow(value, 10, &value) || + pg_sub_s64_overflow(value, digit, &value)) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("value \"%s\" is out of range for type %s", + str, "money"))); + + if (seen_dot) + dec++; + } + /* decimal point? then start counting fractions... */ + else if (*s == dsymbol && !seen_dot) + { + seen_dot = true; + } + /* ignore if "thousands" separator, else we're done */ + else if (strncmp(s, ssymbol, strlen(ssymbol)) == 0) + s += strlen(ssymbol) - 1; + else + break; + } + + /* round off if there's another digit */ + if (isdigit((unsigned char) *s) && *s >= '5') + { + /* remember we build the value in the negative */ + if (pg_sub_s64_overflow(value, 1, &value)) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("value \"%s\" is out of range for type %s", + str, "money"))); + } + + /* adjust for less than required decimal places */ + for (; dec < fpoint; dec++) + { + if (pg_mul_s64_overflow(value, 10, &value)) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("value \"%s\" is out of range for type %s", + str, "money"))); + } + + /* + * should only be trailing digits followed by whitespace, right paren, + * trailing sign, and/or trailing currency symbol + */ + while (isdigit((unsigned char) *s)) + s++; + + while (*s) + { + if (isspace((unsigned char) *s) || *s == ')') + s++; + else if (strncmp(s, nsymbol, strlen(nsymbol)) == 0) + { + sgn = -1; + s += strlen(nsymbol); + } + else if (strncmp(s, psymbol, strlen(psymbol)) == 0) + s += strlen(psymbol); + else if (strncmp(s, csymbol, strlen(csymbol)) == 0) + s += strlen(csymbol); + else + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s: \"%s\"", + "money", str))); + } + + /* + * If the value is supposed to be positive, flip the sign, but check for + * the most negative number. + */ + if (sgn > 0) + { + if (value == PG_INT64_MIN) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("value \"%s\" is out of range for type %s", + str, "money"))); + result = -value; + } + else + result = value; + +#ifdef CASHDEBUG + printf("cashin- result is " INT64_FORMAT "\n", result); +#endif + + PG_RETURN_CASH(result); +} + + +/* cash_out() + * Function to convert cash to a dollars and cents representation, using + * the lc_monetary locale's formatting. + */ +Datum +cash_out(PG_FUNCTION_ARGS) +{ + Cash value = PG_GETARG_CASH(0); + char *result; + char buf[128]; + char *bufptr; + int digit_pos; + int points, + mon_group; + char dsymbol; + const char *ssymbol, + *csymbol, + *signsymbol; + char sign_posn, + cs_precedes, + sep_by_space; + struct lconv *lconvert = PGLC_localeconv(); + + /* see comments about frac_digits in cash_in() */ + points = lconvert->frac_digits; + if (points < 0 || points > 10) + points = 2; /* best guess in this case, I think */ + + /* + * As with frac_digits, must apply a range check to mon_grouping to avoid + * being fooled by variant CHAR_MAX values. + */ + mon_group = *lconvert->mon_grouping; + if (mon_group <= 0 || mon_group > 6) + mon_group = 3; + + /* we restrict dsymbol to be a single byte, but not the other symbols */ + if (*lconvert->mon_decimal_point != '\0' && + lconvert->mon_decimal_point[1] == '\0') + dsymbol = *lconvert->mon_decimal_point; + else + dsymbol = '.'; + if (*lconvert->mon_thousands_sep != '\0') + ssymbol = lconvert->mon_thousands_sep; + else /* ssymbol should not equal dsymbol */ + ssymbol = (dsymbol != ',') ? "," : "."; + csymbol = (*lconvert->currency_symbol != '\0') ? lconvert->currency_symbol : "$"; + + if (value < 0) + { + /* make the amount positive for digit-reconstruction loop */ + value = -value; + /* set up formatting data */ + signsymbol = (*lconvert->negative_sign != '\0') ? lconvert->negative_sign : "-"; + sign_posn = lconvert->n_sign_posn; + cs_precedes = lconvert->n_cs_precedes; + sep_by_space = lconvert->n_sep_by_space; + } + else + { + signsymbol = lconvert->positive_sign; + sign_posn = lconvert->p_sign_posn; + cs_precedes = lconvert->p_cs_precedes; + sep_by_space = lconvert->p_sep_by_space; + } + + /* we build the digits+decimal-point+sep string right-to-left in buf[] */ + bufptr = buf + sizeof(buf) - 1; + *bufptr = '\0'; + + /* + * Generate digits till there are no non-zero digits left and we emitted + * at least one to the left of the decimal point. digit_pos is the + * current digit position, with zero as the digit just left of the decimal + * point, increasing to the right. + */ + digit_pos = points; + do + { + if (points && digit_pos == 0) + { + /* insert decimal point, but not if value cannot be fractional */ + *(--bufptr) = dsymbol; + } + else if (digit_pos < 0 && (digit_pos % mon_group) == 0) + { + /* insert thousands sep, but only to left of radix point */ + bufptr -= strlen(ssymbol); + memcpy(bufptr, ssymbol, strlen(ssymbol)); + } + + *(--bufptr) = ((uint64) value % 10) + '0'; + value = ((uint64) value) / 10; + digit_pos--; + } while (value || digit_pos >= 0); + + /*---------- + * Now, attach currency symbol and sign symbol in the correct order. + * + * The POSIX spec defines these values controlling this code: + * + * p/n_sign_posn: + * 0 Parentheses enclose the quantity and the currency_symbol. + * 1 The sign string precedes the quantity and the currency_symbol. + * 2 The sign string succeeds the quantity and the currency_symbol. + * 3 The sign string precedes the currency_symbol. + * 4 The sign string succeeds the currency_symbol. + * + * p/n_cs_precedes: 0 means currency symbol after value, else before it. + * + * p/n_sep_by_space: + * 0 No <space> separates the currency symbol and value. + * 1 If the currency symbol and sign string are adjacent, a <space> + * separates them from the value; otherwise, a <space> separates + * the currency symbol from the value. + * 2 If the currency symbol and sign string are adjacent, a <space> + * separates them; otherwise, a <space> separates the sign string + * from the value. + *---------- + */ + switch (sign_posn) + { + case 0: + if (cs_precedes) + result = psprintf("(%s%s%s)", + csymbol, + (sep_by_space == 1) ? " " : "", + bufptr); + else + result = psprintf("(%s%s%s)", + bufptr, + (sep_by_space == 1) ? " " : "", + csymbol); + break; + case 1: + default: + if (cs_precedes) + result = psprintf("%s%s%s%s%s", + signsymbol, + (sep_by_space == 2) ? " " : "", + csymbol, + (sep_by_space == 1) ? " " : "", + bufptr); + else + result = psprintf("%s%s%s%s%s", + signsymbol, + (sep_by_space == 2) ? " " : "", + bufptr, + (sep_by_space == 1) ? " " : "", + csymbol); + break; + case 2: + if (cs_precedes) + result = psprintf("%s%s%s%s%s", + csymbol, + (sep_by_space == 1) ? " " : "", + bufptr, + (sep_by_space == 2) ? " " : "", + signsymbol); + else + result = psprintf("%s%s%s%s%s", + bufptr, + (sep_by_space == 1) ? " " : "", + csymbol, + (sep_by_space == 2) ? " " : "", + signsymbol); + break; + case 3: + if (cs_precedes) + result = psprintf("%s%s%s%s%s", + signsymbol, + (sep_by_space == 2) ? " " : "", + csymbol, + (sep_by_space == 1) ? " " : "", + bufptr); + else + result = psprintf("%s%s%s%s%s", + bufptr, + (sep_by_space == 1) ? " " : "", + signsymbol, + (sep_by_space == 2) ? " " : "", + csymbol); + break; + case 4: + if (cs_precedes) + result = psprintf("%s%s%s%s%s", + csymbol, + (sep_by_space == 2) ? " " : "", + signsymbol, + (sep_by_space == 1) ? " " : "", + bufptr); + else + result = psprintf("%s%s%s%s%s", + bufptr, + (sep_by_space == 1) ? " " : "", + csymbol, + (sep_by_space == 2) ? " " : "", + signsymbol); + break; + } + + PG_RETURN_CSTRING(result); +} + +/* + * cash_recv - converts external binary format to cash + */ +Datum +cash_recv(PG_FUNCTION_ARGS) +{ + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + + PG_RETURN_CASH((Cash) pq_getmsgint64(buf)); +} + +/* + * cash_send - converts cash to binary format + */ +Datum +cash_send(PG_FUNCTION_ARGS) +{ + Cash arg1 = PG_GETARG_CASH(0); + StringInfoData buf; + + pq_begintypsend(&buf); + pq_sendint64(&buf, arg1); + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} + +/* + * Comparison functions + */ + +Datum +cash_eq(PG_FUNCTION_ARGS) +{ + Cash c1 = PG_GETARG_CASH(0); + Cash c2 = PG_GETARG_CASH(1); + + PG_RETURN_BOOL(c1 == c2); +} + +Datum +cash_ne(PG_FUNCTION_ARGS) +{ + Cash c1 = PG_GETARG_CASH(0); + Cash c2 = PG_GETARG_CASH(1); + + PG_RETURN_BOOL(c1 != c2); +} + +Datum +cash_lt(PG_FUNCTION_ARGS) +{ + Cash c1 = PG_GETARG_CASH(0); + Cash c2 = PG_GETARG_CASH(1); + + PG_RETURN_BOOL(c1 < c2); +} + +Datum +cash_le(PG_FUNCTION_ARGS) +{ + Cash c1 = PG_GETARG_CASH(0); + Cash c2 = PG_GETARG_CASH(1); + + PG_RETURN_BOOL(c1 <= c2); +} + +Datum +cash_gt(PG_FUNCTION_ARGS) +{ + Cash c1 = PG_GETARG_CASH(0); + Cash c2 = PG_GETARG_CASH(1); + + PG_RETURN_BOOL(c1 > c2); +} + +Datum +cash_ge(PG_FUNCTION_ARGS) +{ + Cash c1 = PG_GETARG_CASH(0); + Cash c2 = PG_GETARG_CASH(1); + + PG_RETURN_BOOL(c1 >= c2); +} + +Datum +cash_cmp(PG_FUNCTION_ARGS) +{ + Cash c1 = PG_GETARG_CASH(0); + Cash c2 = PG_GETARG_CASH(1); + + if (c1 > c2) + PG_RETURN_INT32(1); + else if (c1 == c2) + PG_RETURN_INT32(0); + else + PG_RETURN_INT32(-1); +} + + +/* cash_pl() + * Add two cash values. + */ +Datum +cash_pl(PG_FUNCTION_ARGS) +{ + Cash c1 = PG_GETARG_CASH(0); + Cash c2 = PG_GETARG_CASH(1); + Cash result; + + result = c1 + c2; + + PG_RETURN_CASH(result); +} + + +/* cash_mi() + * Subtract two cash values. + */ +Datum +cash_mi(PG_FUNCTION_ARGS) +{ + Cash c1 = PG_GETARG_CASH(0); + Cash c2 = PG_GETARG_CASH(1); + Cash result; + + result = c1 - c2; + + PG_RETURN_CASH(result); +} + + +/* cash_div_cash() + * Divide cash by cash, returning float8. + */ +Datum +cash_div_cash(PG_FUNCTION_ARGS) +{ + Cash dividend = PG_GETARG_CASH(0); + Cash divisor = PG_GETARG_CASH(1); + float8 quotient; + + if (divisor == 0) + ereport(ERROR, + (errcode(ERRCODE_DIVISION_BY_ZERO), + errmsg("division by zero"))); + + quotient = (float8) dividend / (float8) divisor; + PG_RETURN_FLOAT8(quotient); +} + + +/* cash_mul_flt8() + * Multiply cash by float8. + */ +Datum +cash_mul_flt8(PG_FUNCTION_ARGS) +{ + Cash c = PG_GETARG_CASH(0); + float8 f = PG_GETARG_FLOAT8(1); + Cash result; + + result = rint(c * f); + PG_RETURN_CASH(result); +} + + +/* flt8_mul_cash() + * Multiply float8 by cash. + */ +Datum +flt8_mul_cash(PG_FUNCTION_ARGS) +{ + float8 f = PG_GETARG_FLOAT8(0); + Cash c = PG_GETARG_CASH(1); + Cash result; + + result = rint(f * c); + PG_RETURN_CASH(result); +} + + +/* cash_div_flt8() + * Divide cash by float8. + */ +Datum +cash_div_flt8(PG_FUNCTION_ARGS) +{ + Cash c = PG_GETARG_CASH(0); + float8 f = PG_GETARG_FLOAT8(1); + Cash result; + + if (f == 0.0) + ereport(ERROR, + (errcode(ERRCODE_DIVISION_BY_ZERO), + errmsg("division by zero"))); + + result = rint(c / f); + PG_RETURN_CASH(result); +} + + +/* cash_mul_flt4() + * Multiply cash by float4. + */ +Datum +cash_mul_flt4(PG_FUNCTION_ARGS) +{ + Cash c = PG_GETARG_CASH(0); + float4 f = PG_GETARG_FLOAT4(1); + Cash result; + + result = rint(c * (float8) f); + PG_RETURN_CASH(result); +} + + +/* flt4_mul_cash() + * Multiply float4 by cash. + */ +Datum +flt4_mul_cash(PG_FUNCTION_ARGS) +{ + float4 f = PG_GETARG_FLOAT4(0); + Cash c = PG_GETARG_CASH(1); + Cash result; + + result = rint((float8) f * c); + PG_RETURN_CASH(result); +} + + +/* cash_div_flt4() + * Divide cash by float4. + * + */ +Datum +cash_div_flt4(PG_FUNCTION_ARGS) +{ + Cash c = PG_GETARG_CASH(0); + float4 f = PG_GETARG_FLOAT4(1); + Cash result; + + if (f == 0.0) + ereport(ERROR, + (errcode(ERRCODE_DIVISION_BY_ZERO), + errmsg("division by zero"))); + + result = rint(c / (float8) f); + PG_RETURN_CASH(result); +} + + +/* cash_mul_int8() + * Multiply cash by int8. + */ +Datum +cash_mul_int8(PG_FUNCTION_ARGS) +{ + Cash c = PG_GETARG_CASH(0); + int64 i = PG_GETARG_INT64(1); + Cash result; + + result = c * i; + PG_RETURN_CASH(result); +} + + +/* int8_mul_cash() + * Multiply int8 by cash. + */ +Datum +int8_mul_cash(PG_FUNCTION_ARGS) +{ + int64 i = PG_GETARG_INT64(0); + Cash c = PG_GETARG_CASH(1); + Cash result; + + result = i * c; + PG_RETURN_CASH(result); +} + +/* cash_div_int8() + * Divide cash by 8-byte integer. + */ +Datum +cash_div_int8(PG_FUNCTION_ARGS) +{ + Cash c = PG_GETARG_CASH(0); + int64 i = PG_GETARG_INT64(1); + Cash result; + + if (i == 0) + ereport(ERROR, + (errcode(ERRCODE_DIVISION_BY_ZERO), + errmsg("division by zero"))); + + result = c / i; + + PG_RETURN_CASH(result); +} + + +/* cash_mul_int4() + * Multiply cash by int4. + */ +Datum +cash_mul_int4(PG_FUNCTION_ARGS) +{ + Cash c = PG_GETARG_CASH(0); + int32 i = PG_GETARG_INT32(1); + Cash result; + + result = c * i; + PG_RETURN_CASH(result); +} + + +/* int4_mul_cash() + * Multiply int4 by cash. + */ +Datum +int4_mul_cash(PG_FUNCTION_ARGS) +{ + int32 i = PG_GETARG_INT32(0); + Cash c = PG_GETARG_CASH(1); + Cash result; + + result = i * c; + PG_RETURN_CASH(result); +} + + +/* cash_div_int4() + * Divide cash by 4-byte integer. + * + */ +Datum +cash_div_int4(PG_FUNCTION_ARGS) +{ + Cash c = PG_GETARG_CASH(0); + int32 i = PG_GETARG_INT32(1); + Cash result; + + if (i == 0) + ereport(ERROR, + (errcode(ERRCODE_DIVISION_BY_ZERO), + errmsg("division by zero"))); + + result = c / i; + + PG_RETURN_CASH(result); +} + + +/* cash_mul_int2() + * Multiply cash by int2. + */ +Datum +cash_mul_int2(PG_FUNCTION_ARGS) +{ + Cash c = PG_GETARG_CASH(0); + int16 s = PG_GETARG_INT16(1); + Cash result; + + result = c * s; + PG_RETURN_CASH(result); +} + +/* int2_mul_cash() + * Multiply int2 by cash. + */ +Datum +int2_mul_cash(PG_FUNCTION_ARGS) +{ + int16 s = PG_GETARG_INT16(0); + Cash c = PG_GETARG_CASH(1); + Cash result; + + result = s * c; + PG_RETURN_CASH(result); +} + +/* cash_div_int2() + * Divide cash by int2. + * + */ +Datum +cash_div_int2(PG_FUNCTION_ARGS) +{ + Cash c = PG_GETARG_CASH(0); + int16 s = PG_GETARG_INT16(1); + Cash result; + + if (s == 0) + ereport(ERROR, + (errcode(ERRCODE_DIVISION_BY_ZERO), + errmsg("division by zero"))); + + result = c / s; + PG_RETURN_CASH(result); +} + +/* cashlarger() + * Return larger of two cash values. + */ +Datum +cashlarger(PG_FUNCTION_ARGS) +{ + Cash c1 = PG_GETARG_CASH(0); + Cash c2 = PG_GETARG_CASH(1); + Cash result; + + result = (c1 > c2) ? c1 : c2; + + PG_RETURN_CASH(result); +} + +/* cashsmaller() + * Return smaller of two cash values. + */ +Datum +cashsmaller(PG_FUNCTION_ARGS) +{ + Cash c1 = PG_GETARG_CASH(0); + Cash c2 = PG_GETARG_CASH(1); + Cash result; + + result = (c1 < c2) ? c1 : c2; + + PG_RETURN_CASH(result); +} + +/* cash_words() + * This converts an int4 as well but to a representation using words + * Obviously way North American centric - sorry + */ +Datum +cash_words(PG_FUNCTION_ARGS) +{ + Cash value = PG_GETARG_CASH(0); + uint64 val; + char buf[256]; + char *p = buf; + Cash m0; + Cash m1; + Cash m2; + Cash m3; + Cash m4; + Cash m5; + Cash m6; + + /* work with positive numbers */ + if (value < 0) + { + value = -value; + strcpy(buf, "minus "); + p += 6; + } + else + buf[0] = '\0'; + + /* Now treat as unsigned, to avoid trouble at INT_MIN */ + val = (uint64) value; + + m0 = val % INT64CONST(100); /* cents */ + m1 = (val / INT64CONST(100)) % 1000; /* hundreds */ + m2 = (val / INT64CONST(100000)) % 1000; /* thousands */ + m3 = (val / INT64CONST(100000000)) % 1000; /* millions */ + m4 = (val / INT64CONST(100000000000)) % 1000; /* billions */ + m5 = (val / INT64CONST(100000000000000)) % 1000; /* trillions */ + m6 = (val / INT64CONST(100000000000000000)) % 1000; /* quadrillions */ + + if (m6) + { + strcat(buf, num_word(m6)); + strcat(buf, " quadrillion "); + } + + if (m5) + { + strcat(buf, num_word(m5)); + strcat(buf, " trillion "); + } + + if (m4) + { + strcat(buf, num_word(m4)); + strcat(buf, " billion "); + } + + if (m3) + { + strcat(buf, num_word(m3)); + strcat(buf, " million "); + } + + if (m2) + { + strcat(buf, num_word(m2)); + strcat(buf, " thousand "); + } + + if (m1) + strcat(buf, num_word(m1)); + + if (!*p) + strcat(buf, "zero"); + + strcat(buf, (val / 100) == 1 ? " dollar and " : " dollars and "); + strcat(buf, num_word(m0)); + strcat(buf, m0 == 1 ? " cent" : " cents"); + + /* capitalize output */ + buf[0] = pg_toupper((unsigned char) buf[0]); + + /* return as text datum */ + PG_RETURN_TEXT_P(cstring_to_text(buf)); +} + + +/* cash_numeric() + * Convert cash to numeric. + */ +Datum +cash_numeric(PG_FUNCTION_ARGS) +{ + Cash money = PG_GETARG_CASH(0); + Datum result; + int fpoint; + struct lconv *lconvert = PGLC_localeconv(); + + /* see comments about frac_digits in cash_in() */ + fpoint = lconvert->frac_digits; + if (fpoint < 0 || fpoint > 10) + fpoint = 2; + + /* convert the integral money value to numeric */ + result = NumericGetDatum(int64_to_numeric(money)); + + /* scale appropriately, if needed */ + if (fpoint > 0) + { + int64 scale; + int i; + Datum numeric_scale; + Datum quotient; + + /* compute required scale factor */ + scale = 1; + for (i = 0; i < fpoint; i++) + scale *= 10; + numeric_scale = NumericGetDatum(int64_to_numeric(scale)); + + /* + * Given integral inputs approaching INT64_MAX, select_div_scale() + * might choose a result scale of zero, causing loss of fractional + * digits in the quotient. We can ensure an exact result by setting + * the dscale of either input to be at least as large as the desired + * result scale. numeric_round() will do that for us. + */ + numeric_scale = DirectFunctionCall2(numeric_round, + numeric_scale, + Int32GetDatum(fpoint)); + + /* Now we can safely divide ... */ + quotient = DirectFunctionCall2(numeric_div, result, numeric_scale); + + /* ... and forcibly round to exactly the intended number of digits */ + result = DirectFunctionCall2(numeric_round, + quotient, + Int32GetDatum(fpoint)); + } + + PG_RETURN_DATUM(result); +} + +/* numeric_cash() + * Convert numeric to cash. + */ +Datum +numeric_cash(PG_FUNCTION_ARGS) +{ + Datum amount = PG_GETARG_DATUM(0); + Cash result; + int fpoint; + int64 scale; + int i; + Datum numeric_scale; + struct lconv *lconvert = PGLC_localeconv(); + + /* see comments about frac_digits in cash_in() */ + fpoint = lconvert->frac_digits; + if (fpoint < 0 || fpoint > 10) + fpoint = 2; + + /* compute required scale factor */ + scale = 1; + for (i = 0; i < fpoint; i++) + scale *= 10; + + /* multiply the input amount by scale factor */ + numeric_scale = NumericGetDatum(int64_to_numeric(scale)); + amount = DirectFunctionCall2(numeric_mul, amount, numeric_scale); + + /* note that numeric_int8 will round to nearest integer for us */ + result = DatumGetInt64(DirectFunctionCall1(numeric_int8, amount)); + + PG_RETURN_CASH(result); +} + +/* int4_cash() + * Convert int4 (int) to cash + */ +Datum +int4_cash(PG_FUNCTION_ARGS) +{ + int32 amount = PG_GETARG_INT32(0); + Cash result; + int fpoint; + int64 scale; + int i; + struct lconv *lconvert = PGLC_localeconv(); + + /* see comments about frac_digits in cash_in() */ + fpoint = lconvert->frac_digits; + if (fpoint < 0 || fpoint > 10) + fpoint = 2; + + /* compute required scale factor */ + scale = 1; + for (i = 0; i < fpoint; i++) + scale *= 10; + + /* compute amount * scale, checking for overflow */ + result = DatumGetInt64(DirectFunctionCall2(int8mul, Int64GetDatum(amount), + Int64GetDatum(scale))); + + PG_RETURN_CASH(result); +} + +/* int8_cash() + * Convert int8 (bigint) to cash + */ +Datum +int8_cash(PG_FUNCTION_ARGS) +{ + int64 amount = PG_GETARG_INT64(0); + Cash result; + int fpoint; + int64 scale; + int i; + struct lconv *lconvert = PGLC_localeconv(); + + /* see comments about frac_digits in cash_in() */ + fpoint = lconvert->frac_digits; + if (fpoint < 0 || fpoint > 10) + fpoint = 2; + + /* compute required scale factor */ + scale = 1; + for (i = 0; i < fpoint; i++) + scale *= 10; + + /* compute amount * scale, checking for overflow */ + result = DatumGetInt64(DirectFunctionCall2(int8mul, Int64GetDatum(amount), + Int64GetDatum(scale))); + + PG_RETURN_CASH(result); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/char.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/char.c new file mode 100644 index 00000000000..33662595398 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/char.c @@ -0,0 +1,254 @@ +/*------------------------------------------------------------------------- + * + * char.c + * Functions for the built-in type "char" (not to be confused with + * bpchar, which is the SQL CHAR(n) type). + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/adt/char.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <limits.h> + +#include "libpq/pqformat.h" +#include "utils/builtins.h" +#include "varatt.h" + +#define ISOCTAL(c) (((c) >= '0') && ((c) <= '7')) +#define TOOCTAL(c) ((c) + '0') +#define FROMOCTAL(c) ((unsigned char) (c) - '0') + + +/***************************************************************************** + * USER I/O ROUTINES * + *****************************************************************************/ + +/* + * charin - converts "x" to 'x' + * + * This accepts the formats charout produces. If we have multibyte input + * that is not in the form '\ooo', then we take its first byte as the value + * and silently discard the rest; this is a backwards-compatibility provision. + */ +Datum +charin(PG_FUNCTION_ARGS) +{ + char *ch = PG_GETARG_CSTRING(0); + + if (strlen(ch) == 4 && ch[0] == '\\' && + ISOCTAL(ch[1]) && ISOCTAL(ch[2]) && ISOCTAL(ch[3])) + PG_RETURN_CHAR((FROMOCTAL(ch[1]) << 6) + + (FROMOCTAL(ch[2]) << 3) + + FROMOCTAL(ch[3])); + /* This will do the right thing for a zero-length input string */ + PG_RETURN_CHAR(ch[0]); +} + +/* + * charout - converts 'x' to "x" + * + * The possible output formats are: + * 1. 0x00 is represented as an empty string. + * 2. 0x01..0x7F are represented as a single ASCII byte. + * 3. 0x80..0xFF are represented as \ooo (backslash and 3 octal digits). + * Case 3 is meant to match the traditional "escape" format of bytea. + */ +Datum +charout(PG_FUNCTION_ARGS) +{ + char ch = PG_GETARG_CHAR(0); + char *result = (char *) palloc(5); + + if (IS_HIGHBIT_SET(ch)) + { + result[0] = '\\'; + result[1] = TOOCTAL(((unsigned char) ch) >> 6); + result[2] = TOOCTAL((((unsigned char) ch) >> 3) & 07); + result[3] = TOOCTAL(((unsigned char) ch) & 07); + result[4] = '\0'; + } + else + { + /* This produces acceptable results for 0x00 as well */ + result[0] = ch; + result[1] = '\0'; + } + PG_RETURN_CSTRING(result); +} + +/* + * charrecv - converts external binary format to char + * + * The external representation is one byte, with no character set + * conversion. This is somewhat dubious, perhaps, but in many + * cases people use char for a 1-byte binary type. + */ +Datum +charrecv(PG_FUNCTION_ARGS) +{ + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + + PG_RETURN_CHAR(pq_getmsgbyte(buf)); +} + +/* + * charsend - converts char to binary format + */ +Datum +charsend(PG_FUNCTION_ARGS) +{ + char arg1 = PG_GETARG_CHAR(0); + StringInfoData buf; + + pq_begintypsend(&buf); + pq_sendbyte(&buf, arg1); + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} + +/***************************************************************************** + * PUBLIC ROUTINES * + *****************************************************************************/ + +/* + * NOTE: comparisons are done as though char is unsigned (uint8). + * Conversions to and from integer are done as though char is signed (int8). + * + * You wanted consistency? + */ + +Datum +chareq(PG_FUNCTION_ARGS) +{ + char arg1 = PG_GETARG_CHAR(0); + char arg2 = PG_GETARG_CHAR(1); + + PG_RETURN_BOOL(arg1 == arg2); +} + +Datum +charne(PG_FUNCTION_ARGS) +{ + char arg1 = PG_GETARG_CHAR(0); + char arg2 = PG_GETARG_CHAR(1); + + PG_RETURN_BOOL(arg1 != arg2); +} + +Datum +charlt(PG_FUNCTION_ARGS) +{ + char arg1 = PG_GETARG_CHAR(0); + char arg2 = PG_GETARG_CHAR(1); + + PG_RETURN_BOOL((uint8) arg1 < (uint8) arg2); +} + +Datum +charle(PG_FUNCTION_ARGS) +{ + char arg1 = PG_GETARG_CHAR(0); + char arg2 = PG_GETARG_CHAR(1); + + PG_RETURN_BOOL((uint8) arg1 <= (uint8) arg2); +} + +Datum +chargt(PG_FUNCTION_ARGS) +{ + char arg1 = PG_GETARG_CHAR(0); + char arg2 = PG_GETARG_CHAR(1); + + PG_RETURN_BOOL((uint8) arg1 > (uint8) arg2); +} + +Datum +charge(PG_FUNCTION_ARGS) +{ + char arg1 = PG_GETARG_CHAR(0); + char arg2 = PG_GETARG_CHAR(1); + + PG_RETURN_BOOL((uint8) arg1 >= (uint8) arg2); +} + + +Datum +chartoi4(PG_FUNCTION_ARGS) +{ + char arg1 = PG_GETARG_CHAR(0); + + PG_RETURN_INT32((int32) ((int8) arg1)); +} + +Datum +i4tochar(PG_FUNCTION_ARGS) +{ + int32 arg1 = PG_GETARG_INT32(0); + + if (arg1 < SCHAR_MIN || arg1 > SCHAR_MAX) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("\"char\" out of range"))); + + PG_RETURN_CHAR((int8) arg1); +} + + +Datum +text_char(PG_FUNCTION_ARGS) +{ + text *arg1 = PG_GETARG_TEXT_PP(0); + char *ch = VARDATA_ANY(arg1); + char result; + + /* + * Conversion rules are the same as in charin(), but here we need to + * handle the empty-string case honestly. + */ + if (VARSIZE_ANY_EXHDR(arg1) == 4 && ch[0] == '\\' && + ISOCTAL(ch[1]) && ISOCTAL(ch[2]) && ISOCTAL(ch[3])) + result = (FROMOCTAL(ch[1]) << 6) + + (FROMOCTAL(ch[2]) << 3) + + FROMOCTAL(ch[3]); + else if (VARSIZE_ANY_EXHDR(arg1) > 0) + result = ch[0]; + else + result = '\0'; + + PG_RETURN_CHAR(result); +} + +Datum +char_text(PG_FUNCTION_ARGS) +{ + char arg1 = PG_GETARG_CHAR(0); + text *result = palloc(VARHDRSZ + 4); + + /* + * Conversion rules are the same as in charout(), but here we need to be + * honest about converting 0x00 to an empty string. + */ + if (IS_HIGHBIT_SET(arg1)) + { + SET_VARSIZE(result, VARHDRSZ + 4); + (VARDATA(result))[0] = '\\'; + (VARDATA(result))[1] = TOOCTAL(((unsigned char) arg1) >> 6); + (VARDATA(result))[2] = TOOCTAL((((unsigned char) arg1) >> 3) & 07); + (VARDATA(result))[3] = TOOCTAL(((unsigned char) arg1) & 07); + } + else if (arg1 != '\0') + { + SET_VARSIZE(result, VARHDRSZ + 1); + *(VARDATA(result)) = arg1; + } + else + SET_VARSIZE(result, VARHDRSZ); + + PG_RETURN_TEXT_P(result); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/cryptohashfuncs.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/cryptohashfuncs.c new file mode 100644 index 00000000000..f9603279581 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/cryptohashfuncs.c @@ -0,0 +1,169 @@ +/*------------------------------------------------------------------------- + * + * cryptohashfuncs.c + * Cryptographic hash functions + * + * Portions Copyright (c) 2018-2023, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/utils/adt/cryptohashfuncs.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "common/cryptohash.h" +#include "common/md5.h" +#include "common/sha2.h" +#include "utils/builtins.h" +#include "varatt.h" + + +/* + * MD5 + */ + +/* MD5 produces a 16 byte (128 bit) hash; double it for hex */ +#define MD5_HASH_LEN 32 + +/* + * Create an MD5 hash of a text value and return it as hex string. + */ +Datum +md5_text(PG_FUNCTION_ARGS) +{ + text *in_text = PG_GETARG_TEXT_PP(0); + size_t len; + char hexsum[MD5_HASH_LEN + 1]; + const char *errstr = NULL; + + /* Calculate the length of the buffer using varlena metadata */ + len = VARSIZE_ANY_EXHDR(in_text); + + /* get the hash result */ + if (pg_md5_hash(VARDATA_ANY(in_text), len, hexsum, &errstr) == false) + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("could not compute %s hash: %s", "MD5", + errstr))); + + /* convert to text and return it */ + PG_RETURN_TEXT_P(cstring_to_text(hexsum)); +} + +/* + * Create an MD5 hash of a bytea value and return it as a hex string. + */ +Datum +md5_bytea(PG_FUNCTION_ARGS) +{ + bytea *in = PG_GETARG_BYTEA_PP(0); + size_t len; + char hexsum[MD5_HASH_LEN + 1]; + const char *errstr = NULL; + + len = VARSIZE_ANY_EXHDR(in); + if (pg_md5_hash(VARDATA_ANY(in), len, hexsum, &errstr) == false) + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("could not compute %s hash: %s", "MD5", + errstr))); + + PG_RETURN_TEXT_P(cstring_to_text(hexsum)); +} + +/* + * Internal routine to compute a cryptohash with the given bytea input. + */ +static inline bytea * +cryptohash_internal(pg_cryptohash_type type, bytea *input) +{ + const uint8 *data; + const char *typestr = NULL; + int digest_len = 0; + size_t len; + pg_cryptohash_ctx *ctx; + bytea *result; + + switch (type) + { + case PG_SHA224: + typestr = "SHA224"; + digest_len = PG_SHA224_DIGEST_LENGTH; + break; + case PG_SHA256: + typestr = "SHA256"; + digest_len = PG_SHA256_DIGEST_LENGTH; + break; + case PG_SHA384: + typestr = "SHA384"; + digest_len = PG_SHA384_DIGEST_LENGTH; + break; + case PG_SHA512: + typestr = "SHA512"; + digest_len = PG_SHA512_DIGEST_LENGTH; + break; + case PG_MD5: + case PG_SHA1: + elog(ERROR, "unsupported cryptohash type %d", type); + break; + } + + result = palloc0(digest_len + VARHDRSZ); + len = VARSIZE_ANY_EXHDR(input); + data = (unsigned char *) VARDATA_ANY(input); + + ctx = pg_cryptohash_create(type); + if (pg_cryptohash_init(ctx) < 0) + elog(ERROR, "could not initialize %s context: %s", typestr, + pg_cryptohash_error(ctx)); + if (pg_cryptohash_update(ctx, data, len) < 0) + elog(ERROR, "could not update %s context: %s", typestr, + pg_cryptohash_error(ctx)); + if (pg_cryptohash_final(ctx, (unsigned char *) VARDATA(result), + digest_len) < 0) + elog(ERROR, "could not finalize %s context: %s", typestr, + pg_cryptohash_error(ctx)); + pg_cryptohash_free(ctx); + + SET_VARSIZE(result, digest_len + VARHDRSZ); + + return result; +} + +/* + * SHA-2 variants + */ + +Datum +sha224_bytea(PG_FUNCTION_ARGS) +{ + bytea *result = cryptohash_internal(PG_SHA224, PG_GETARG_BYTEA_PP(0)); + + PG_RETURN_BYTEA_P(result); +} + +Datum +sha256_bytea(PG_FUNCTION_ARGS) +{ + bytea *result = cryptohash_internal(PG_SHA256, PG_GETARG_BYTEA_PP(0)); + + PG_RETURN_BYTEA_P(result); +} + +Datum +sha384_bytea(PG_FUNCTION_ARGS) +{ + bytea *result = cryptohash_internal(PG_SHA384, PG_GETARG_BYTEA_PP(0)); + + PG_RETURN_BYTEA_P(result); +} + +Datum +sha512_bytea(PG_FUNCTION_ARGS) +{ + bytea *result = cryptohash_internal(PG_SHA512, PG_GETARG_BYTEA_PP(0)); + + PG_RETURN_BYTEA_P(result); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/date.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/date.c new file mode 100644 index 00000000000..3f4b5791841 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/date.c @@ -0,0 +1,3129 @@ +/*------------------------------------------------------------------------- + * + * date.c + * implements DATE and TIME data types specified in SQL standard + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994-5, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/adt/date.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include <ctype.h> +#include <limits.h> +#include <float.h> +#include <math.h> +#include <time.h> + +#include "access/xact.h" +#include "catalog/pg_type.h" +#include "common/hashfn.h" +#include "libpq/pqformat.h" +#include "miscadmin.h" +#include "nodes/supportnodes.h" +#include "parser/scansup.h" +#include "utils/array.h" +#include "utils/builtins.h" +#include "utils/date.h" +#include "utils/datetime.h" +#include "utils/numeric.h" +#include "utils/sortsupport.h" + +/* + * gcc's -ffast-math switch breaks routines that expect exact results from + * expressions like timeval / SECS_PER_HOUR, where timeval is double. + */ +#ifdef __FAST_MATH__ +#error -ffast-math is known to break this code +#endif + + +/* common code for timetypmodin and timetztypmodin */ +static int32 +anytime_typmodin(bool istz, ArrayType *ta) +{ + int32 *tl; + int n; + + tl = ArrayGetIntegerTypmods(ta, &n); + + /* + * we're not too tense about good error message here because grammar + * shouldn't allow wrong number of modifiers for TIME + */ + if (n != 1) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid type modifier"))); + + return anytime_typmod_check(istz, tl[0]); +} + +/* exported so parse_expr.c can use it */ +int32 +anytime_typmod_check(bool istz, int32 typmod) +{ + if (typmod < 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("TIME(%d)%s precision must not be negative", + typmod, (istz ? " WITH TIME ZONE" : "")))); + if (typmod > MAX_TIME_PRECISION) + { + ereport(WARNING, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("TIME(%d)%s precision reduced to maximum allowed, %d", + typmod, (istz ? " WITH TIME ZONE" : ""), + MAX_TIME_PRECISION))); + typmod = MAX_TIME_PRECISION; + } + + return typmod; +} + +/* common code for timetypmodout and timetztypmodout */ +static char * +anytime_typmodout(bool istz, int32 typmod) +{ + const char *tz = istz ? " with time zone" : " without time zone"; + + if (typmod >= 0) + return psprintf("(%d)%s", (int) typmod, tz); + else + return pstrdup(tz); +} + + +/***************************************************************************** + * Date ADT + *****************************************************************************/ + + +/* date_in() + * Given date text string, convert to internal date format. + */ +Datum +date_in(PG_FUNCTION_ARGS) +{ + char *str = PG_GETARG_CSTRING(0); + Node *escontext = fcinfo->context; + DateADT date; + fsec_t fsec; + struct pg_tm tt, + *tm = &tt; + int tzp; + int dtype; + int nf; + int dterr; + char *field[MAXDATEFIELDS]; + int ftype[MAXDATEFIELDS]; + char workbuf[MAXDATELEN + 1]; + DateTimeErrorExtra extra; + + dterr = ParseDateTime(str, workbuf, sizeof(workbuf), + field, ftype, MAXDATEFIELDS, &nf); + if (dterr == 0) + dterr = DecodeDateTime(field, ftype, nf, + &dtype, tm, &fsec, &tzp, &extra); + if (dterr != 0) + { + DateTimeParseError(dterr, &extra, str, "date", escontext); + PG_RETURN_NULL(); + } + + switch (dtype) + { + case DTK_DATE: + break; + + case DTK_EPOCH: + GetEpochTime(tm); + break; + + case DTK_LATE: + DATE_NOEND(date); + PG_RETURN_DATEADT(date); + + case DTK_EARLY: + DATE_NOBEGIN(date); + PG_RETURN_DATEADT(date); + + default: + DateTimeParseError(DTERR_BAD_FORMAT, &extra, str, "date", escontext); + PG_RETURN_NULL(); + } + + /* Prevent overflow in Julian-day routines */ + if (!IS_VALID_JULIAN(tm->tm_year, tm->tm_mon, tm->tm_mday)) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("date out of range: \"%s\"", str))); + + date = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - POSTGRES_EPOCH_JDATE; + + /* Now check for just-out-of-range dates */ + if (!IS_VALID_DATE(date)) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("date out of range: \"%s\"", str))); + + PG_RETURN_DATEADT(date); +} + +/* date_out() + * Given internal format date, convert to text string. + */ +Datum +date_out(PG_FUNCTION_ARGS) +{ + DateADT date = PG_GETARG_DATEADT(0); + char *result; + struct pg_tm tt, + *tm = &tt; + char buf[MAXDATELEN + 1]; + + if (DATE_NOT_FINITE(date)) + EncodeSpecialDate(date, buf); + else + { + j2date(date + POSTGRES_EPOCH_JDATE, + &(tm->tm_year), &(tm->tm_mon), &(tm->tm_mday)); + EncodeDateOnly(tm, DateStyle, buf); + } + + result = pstrdup(buf); + PG_RETURN_CSTRING(result); +} + +/* + * date_recv - converts external binary format to date + */ +Datum +date_recv(PG_FUNCTION_ARGS) +{ + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + DateADT result; + + result = (DateADT) pq_getmsgint(buf, sizeof(DateADT)); + + /* Limit to the same range that date_in() accepts. */ + if (DATE_NOT_FINITE(result)) + /* ok */ ; + else if (!IS_VALID_DATE(result)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("date out of range"))); + + PG_RETURN_DATEADT(result); +} + +/* + * date_send - converts date to binary format + */ +Datum +date_send(PG_FUNCTION_ARGS) +{ + DateADT date = PG_GETARG_DATEADT(0); + StringInfoData buf; + + pq_begintypsend(&buf); + pq_sendint32(&buf, date); + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} + +/* + * make_date - date constructor + */ +Datum +make_date(PG_FUNCTION_ARGS) +{ + struct pg_tm tm; + DateADT date; + int dterr; + bool bc = false; + + tm.tm_year = PG_GETARG_INT32(0); + tm.tm_mon = PG_GETARG_INT32(1); + tm.tm_mday = PG_GETARG_INT32(2); + + /* Handle negative years as BC */ + if (tm.tm_year < 0) + { + bc = true; + tm.tm_year = -tm.tm_year; + } + + dterr = ValidateDate(DTK_DATE_M, false, false, bc, &tm); + + if (dterr != 0) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_FIELD_OVERFLOW), + errmsg("date field value out of range: %d-%02d-%02d", + tm.tm_year, tm.tm_mon, tm.tm_mday))); + + /* Prevent overflow in Julian-day routines */ + if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("date out of range: %d-%02d-%02d", + tm.tm_year, tm.tm_mon, tm.tm_mday))); + + date = date2j(tm.tm_year, tm.tm_mon, tm.tm_mday) - POSTGRES_EPOCH_JDATE; + + /* Now check for just-out-of-range dates */ + if (!IS_VALID_DATE(date)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("date out of range: %d-%02d-%02d", + tm.tm_year, tm.tm_mon, tm.tm_mday))); + + PG_RETURN_DATEADT(date); +} + +/* + * Convert reserved date values to string. + */ +void +EncodeSpecialDate(DateADT dt, char *str) +{ + if (DATE_IS_NOBEGIN(dt)) + strcpy(str, EARLY); + else if (DATE_IS_NOEND(dt)) + strcpy(str, LATE); + else /* shouldn't happen */ + elog(ERROR, "invalid argument for EncodeSpecialDate"); +} + + +/* + * GetSQLCurrentDate -- implements CURRENT_DATE + */ +DateADT +GetSQLCurrentDate(void) +{ + struct pg_tm tm; + + static __thread int cache_year = 0; + static __thread int cache_mon = 0; + static __thread int cache_mday = 0; + static __thread DateADT cache_date; + + GetCurrentDateTime(&tm); + + /* + * date2j involves several integer divisions; moreover, unless our session + * lives across local midnight, we don't really have to do it more than + * once. So it seems worth having a separate cache here. + */ + if (tm.tm_year != cache_year || + tm.tm_mon != cache_mon || + tm.tm_mday != cache_mday) + { + cache_date = date2j(tm.tm_year, tm.tm_mon, tm.tm_mday) - POSTGRES_EPOCH_JDATE; + cache_year = tm.tm_year; + cache_mon = tm.tm_mon; + cache_mday = tm.tm_mday; + } + + return cache_date; +} + +/* + * GetSQLCurrentTime -- implements CURRENT_TIME, CURRENT_TIME(n) + */ +TimeTzADT * +GetSQLCurrentTime(int32 typmod) +{ + TimeTzADT *result; + struct pg_tm tt, + *tm = &tt; + fsec_t fsec; + int tz; + + GetCurrentTimeUsec(tm, &fsec, &tz); + + result = (TimeTzADT *) palloc(sizeof(TimeTzADT)); + tm2timetz(tm, fsec, tz, result); + AdjustTimeForTypmod(&(result->time), typmod); + return result; +} + +/* + * GetSQLLocalTime -- implements LOCALTIME, LOCALTIME(n) + */ +TimeADT +GetSQLLocalTime(int32 typmod) +{ + TimeADT result; + struct pg_tm tt, + *tm = &tt; + fsec_t fsec; + int tz; + + GetCurrentTimeUsec(tm, &fsec, &tz); + + tm2time(tm, fsec, &result); + AdjustTimeForTypmod(&result, typmod); + return result; +} + + +/* + * Comparison functions for dates + */ + +Datum +date_eq(PG_FUNCTION_ARGS) +{ + DateADT dateVal1 = PG_GETARG_DATEADT(0); + DateADT dateVal2 = PG_GETARG_DATEADT(1); + + PG_RETURN_BOOL(dateVal1 == dateVal2); +} + +Datum +date_ne(PG_FUNCTION_ARGS) +{ + DateADT dateVal1 = PG_GETARG_DATEADT(0); + DateADT dateVal2 = PG_GETARG_DATEADT(1); + + PG_RETURN_BOOL(dateVal1 != dateVal2); +} + +Datum +date_lt(PG_FUNCTION_ARGS) +{ + DateADT dateVal1 = PG_GETARG_DATEADT(0); + DateADT dateVal2 = PG_GETARG_DATEADT(1); + + PG_RETURN_BOOL(dateVal1 < dateVal2); +} + +Datum +date_le(PG_FUNCTION_ARGS) +{ + DateADT dateVal1 = PG_GETARG_DATEADT(0); + DateADT dateVal2 = PG_GETARG_DATEADT(1); + + PG_RETURN_BOOL(dateVal1 <= dateVal2); +} + +Datum +date_gt(PG_FUNCTION_ARGS) +{ + DateADT dateVal1 = PG_GETARG_DATEADT(0); + DateADT dateVal2 = PG_GETARG_DATEADT(1); + + PG_RETURN_BOOL(dateVal1 > dateVal2); +} + +Datum +date_ge(PG_FUNCTION_ARGS) +{ + DateADT dateVal1 = PG_GETARG_DATEADT(0); + DateADT dateVal2 = PG_GETARG_DATEADT(1); + + PG_RETURN_BOOL(dateVal1 >= dateVal2); +} + +Datum +date_cmp(PG_FUNCTION_ARGS) +{ + DateADT dateVal1 = PG_GETARG_DATEADT(0); + DateADT dateVal2 = PG_GETARG_DATEADT(1); + + if (dateVal1 < dateVal2) + PG_RETURN_INT32(-1); + else if (dateVal1 > dateVal2) + PG_RETURN_INT32(1); + PG_RETURN_INT32(0); +} + +Datum +date_sortsupport(PG_FUNCTION_ARGS) +{ + SortSupport ssup = (SortSupport) PG_GETARG_POINTER(0); + + ssup->comparator = ssup_datum_int32_cmp; + PG_RETURN_VOID(); +} + +Datum +date_finite(PG_FUNCTION_ARGS) +{ + DateADT date = PG_GETARG_DATEADT(0); + + PG_RETURN_BOOL(!DATE_NOT_FINITE(date)); +} + +Datum +date_larger(PG_FUNCTION_ARGS) +{ + DateADT dateVal1 = PG_GETARG_DATEADT(0); + DateADT dateVal2 = PG_GETARG_DATEADT(1); + + PG_RETURN_DATEADT((dateVal1 > dateVal2) ? dateVal1 : dateVal2); +} + +Datum +date_smaller(PG_FUNCTION_ARGS) +{ + DateADT dateVal1 = PG_GETARG_DATEADT(0); + DateADT dateVal2 = PG_GETARG_DATEADT(1); + + PG_RETURN_DATEADT((dateVal1 < dateVal2) ? dateVal1 : dateVal2); +} + +/* Compute difference between two dates in days. + */ +Datum +date_mi(PG_FUNCTION_ARGS) +{ + DateADT dateVal1 = PG_GETARG_DATEADT(0); + DateADT dateVal2 = PG_GETARG_DATEADT(1); + + if (DATE_NOT_FINITE(dateVal1) || DATE_NOT_FINITE(dateVal2)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("cannot subtract infinite dates"))); + + PG_RETURN_INT32((int32) (dateVal1 - dateVal2)); +} + +/* Add a number of days to a date, giving a new date. + * Must handle both positive and negative numbers of days. + */ +Datum +date_pli(PG_FUNCTION_ARGS) +{ + DateADT dateVal = PG_GETARG_DATEADT(0); + int32 days = PG_GETARG_INT32(1); + DateADT result; + + if (DATE_NOT_FINITE(dateVal)) + PG_RETURN_DATEADT(dateVal); /* can't change infinity */ + + result = dateVal + days; + + /* Check for integer overflow and out-of-allowed-range */ + if ((days >= 0 ? (result < dateVal) : (result > dateVal)) || + !IS_VALID_DATE(result)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("date out of range"))); + + PG_RETURN_DATEADT(result); +} + +/* Subtract a number of days from a date, giving a new date. + */ +Datum +date_mii(PG_FUNCTION_ARGS) +{ + DateADT dateVal = PG_GETARG_DATEADT(0); + int32 days = PG_GETARG_INT32(1); + DateADT result; + + if (DATE_NOT_FINITE(dateVal)) + PG_RETURN_DATEADT(dateVal); /* can't change infinity */ + + result = dateVal - days; + + /* Check for integer overflow and out-of-allowed-range */ + if ((days >= 0 ? (result > dateVal) : (result < dateVal)) || + !IS_VALID_DATE(result)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("date out of range"))); + + PG_RETURN_DATEADT(result); +} + + +/* + * Promote date to timestamp. + * + * On successful conversion, *overflow is set to zero if it's not NULL. + * + * If the date is finite but out of the valid range for timestamp, then: + * if overflow is NULL, we throw an out-of-range error. + * if overflow is not NULL, we store +1 or -1 there to indicate the sign + * of the overflow, and return the appropriate timestamp infinity. + * + * Note: *overflow = -1 is actually not possible currently, since both + * datatypes have the same lower bound, Julian day zero. + */ +Timestamp +date2timestamp_opt_overflow(DateADT dateVal, int *overflow) +{ + Timestamp result; + + if (overflow) + *overflow = 0; + + if (DATE_IS_NOBEGIN(dateVal)) + TIMESTAMP_NOBEGIN(result); + else if (DATE_IS_NOEND(dateVal)) + TIMESTAMP_NOEND(result); + else + { + /* + * Since dates have the same minimum values as timestamps, only upper + * boundary need be checked for overflow. + */ + if (dateVal >= (TIMESTAMP_END_JULIAN - POSTGRES_EPOCH_JDATE)) + { + if (overflow) + { + *overflow = 1; + TIMESTAMP_NOEND(result); + return result; + } + else + { + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("date out of range for timestamp"))); + } + } + + /* date is days since 2000, timestamp is microseconds since same... */ + result = dateVal * USECS_PER_DAY; + } + + return result; +} + +/* + * Promote date to timestamp, throwing error for overflow. + */ +static TimestampTz +date2timestamp(DateADT dateVal) +{ + return date2timestamp_opt_overflow(dateVal, NULL); +} + +/* + * Promote date to timestamp with time zone. + * + * On successful conversion, *overflow is set to zero if it's not NULL. + * + * If the date is finite but out of the valid range for timestamptz, then: + * if overflow is NULL, we throw an out-of-range error. + * if overflow is not NULL, we store +1 or -1 there to indicate the sign + * of the overflow, and return the appropriate timestamptz infinity. + */ +TimestampTz +date2timestamptz_opt_overflow(DateADT dateVal, int *overflow) +{ + TimestampTz result; + struct pg_tm tt, + *tm = &tt; + int tz; + + if (overflow) + *overflow = 0; + + if (DATE_IS_NOBEGIN(dateVal)) + TIMESTAMP_NOBEGIN(result); + else if (DATE_IS_NOEND(dateVal)) + TIMESTAMP_NOEND(result); + else + { + /* + * Since dates have the same minimum values as timestamps, only upper + * boundary need be checked for overflow. + */ + if (dateVal >= (TIMESTAMP_END_JULIAN - POSTGRES_EPOCH_JDATE)) + { + if (overflow) + { + *overflow = 1; + TIMESTAMP_NOEND(result); + return result; + } + else + { + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("date out of range for timestamp"))); + } + } + + j2date(dateVal + POSTGRES_EPOCH_JDATE, + &(tm->tm_year), &(tm->tm_mon), &(tm->tm_mday)); + tm->tm_hour = 0; + tm->tm_min = 0; + tm->tm_sec = 0; + tz = DetermineTimeZoneOffset(tm, session_timezone); + + result = dateVal * USECS_PER_DAY + tz * USECS_PER_SEC; + + /* + * Since it is possible to go beyond allowed timestamptz range because + * of time zone, check for allowed timestamp range after adding tz. + */ + if (!IS_VALID_TIMESTAMP(result)) + { + if (overflow) + { + if (result < MIN_TIMESTAMP) + { + *overflow = -1; + TIMESTAMP_NOBEGIN(result); + } + else + { + *overflow = 1; + TIMESTAMP_NOEND(result); + } + } + else + { + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("date out of range for timestamp"))); + } + } + } + + return result; +} + +/* + * Promote date to timestamptz, throwing error for overflow. + */ +static TimestampTz +date2timestamptz(DateADT dateVal) +{ + return date2timestamptz_opt_overflow(dateVal, NULL); +} + +/* + * date2timestamp_no_overflow + * + * This is chartered to produce a double value that is numerically + * equivalent to the corresponding Timestamp value, if the date is in the + * valid range of Timestamps, but in any case not throw an overflow error. + * We can do this since the numerical range of double is greater than + * that of non-erroneous timestamps. The results are currently only + * used for statistical estimation purposes. + */ +double +date2timestamp_no_overflow(DateADT dateVal) +{ + double result; + + if (DATE_IS_NOBEGIN(dateVal)) + result = -DBL_MAX; + else if (DATE_IS_NOEND(dateVal)) + result = DBL_MAX; + else + { + /* date is days since 2000, timestamp is microseconds since same... */ + result = dateVal * (double) USECS_PER_DAY; + } + + return result; +} + + +/* + * Crosstype comparison functions for dates + */ + +int32 +date_cmp_timestamp_internal(DateADT dateVal, Timestamp dt2) +{ + Timestamp dt1; + int overflow; + + dt1 = date2timestamp_opt_overflow(dateVal, &overflow); + if (overflow > 0) + { + /* dt1 is larger than any finite timestamp, but less than infinity */ + return TIMESTAMP_IS_NOEND(dt2) ? -1 : +1; + } + Assert(overflow == 0); /* -1 case cannot occur */ + + return timestamp_cmp_internal(dt1, dt2); +} + +Datum +date_eq_timestamp(PG_FUNCTION_ARGS) +{ + DateADT dateVal = PG_GETARG_DATEADT(0); + Timestamp dt2 = PG_GETARG_TIMESTAMP(1); + + PG_RETURN_BOOL(date_cmp_timestamp_internal(dateVal, dt2) == 0); +} + +Datum +date_ne_timestamp(PG_FUNCTION_ARGS) +{ + DateADT dateVal = PG_GETARG_DATEADT(0); + Timestamp dt2 = PG_GETARG_TIMESTAMP(1); + + PG_RETURN_BOOL(date_cmp_timestamp_internal(dateVal, dt2) != 0); +} + +Datum +date_lt_timestamp(PG_FUNCTION_ARGS) +{ + DateADT dateVal = PG_GETARG_DATEADT(0); + Timestamp dt2 = PG_GETARG_TIMESTAMP(1); + + PG_RETURN_BOOL(date_cmp_timestamp_internal(dateVal, dt2) < 0); +} + +Datum +date_gt_timestamp(PG_FUNCTION_ARGS) +{ + DateADT dateVal = PG_GETARG_DATEADT(0); + Timestamp dt2 = PG_GETARG_TIMESTAMP(1); + + PG_RETURN_BOOL(date_cmp_timestamp_internal(dateVal, dt2) > 0); +} + +Datum +date_le_timestamp(PG_FUNCTION_ARGS) +{ + DateADT dateVal = PG_GETARG_DATEADT(0); + Timestamp dt2 = PG_GETARG_TIMESTAMP(1); + + PG_RETURN_BOOL(date_cmp_timestamp_internal(dateVal, dt2) <= 0); +} + +Datum +date_ge_timestamp(PG_FUNCTION_ARGS) +{ + DateADT dateVal = PG_GETARG_DATEADT(0); + Timestamp dt2 = PG_GETARG_TIMESTAMP(1); + + PG_RETURN_BOOL(date_cmp_timestamp_internal(dateVal, dt2) >= 0); +} + +Datum +date_cmp_timestamp(PG_FUNCTION_ARGS) +{ + DateADT dateVal = PG_GETARG_DATEADT(0); + Timestamp dt2 = PG_GETARG_TIMESTAMP(1); + + PG_RETURN_INT32(date_cmp_timestamp_internal(dateVal, dt2)); +} + +int32 +date_cmp_timestamptz_internal(DateADT dateVal, TimestampTz dt2) +{ + TimestampTz dt1; + int overflow; + + dt1 = date2timestamptz_opt_overflow(dateVal, &overflow); + if (overflow > 0) + { + /* dt1 is larger than any finite timestamp, but less than infinity */ + return TIMESTAMP_IS_NOEND(dt2) ? -1 : +1; + } + if (overflow < 0) + { + /* dt1 is less than any finite timestamp, but more than -infinity */ + return TIMESTAMP_IS_NOBEGIN(dt2) ? +1 : -1; + } + + return timestamptz_cmp_internal(dt1, dt2); +} + +Datum +date_eq_timestamptz(PG_FUNCTION_ARGS) +{ + DateADT dateVal = PG_GETARG_DATEADT(0); + TimestampTz dt2 = PG_GETARG_TIMESTAMPTZ(1); + + PG_RETURN_BOOL(date_cmp_timestamptz_internal(dateVal, dt2) == 0); +} + +Datum +date_ne_timestamptz(PG_FUNCTION_ARGS) +{ + DateADT dateVal = PG_GETARG_DATEADT(0); + TimestampTz dt2 = PG_GETARG_TIMESTAMPTZ(1); + + PG_RETURN_BOOL(date_cmp_timestamptz_internal(dateVal, dt2) != 0); +} + +Datum +date_lt_timestamptz(PG_FUNCTION_ARGS) +{ + DateADT dateVal = PG_GETARG_DATEADT(0); + TimestampTz dt2 = PG_GETARG_TIMESTAMPTZ(1); + + PG_RETURN_BOOL(date_cmp_timestamptz_internal(dateVal, dt2) < 0); +} + +Datum +date_gt_timestamptz(PG_FUNCTION_ARGS) +{ + DateADT dateVal = PG_GETARG_DATEADT(0); + TimestampTz dt2 = PG_GETARG_TIMESTAMPTZ(1); + + PG_RETURN_BOOL(date_cmp_timestamptz_internal(dateVal, dt2) > 0); +} + +Datum +date_le_timestamptz(PG_FUNCTION_ARGS) +{ + DateADT dateVal = PG_GETARG_DATEADT(0); + TimestampTz dt2 = PG_GETARG_TIMESTAMPTZ(1); + + PG_RETURN_BOOL(date_cmp_timestamptz_internal(dateVal, dt2) <= 0); +} + +Datum +date_ge_timestamptz(PG_FUNCTION_ARGS) +{ + DateADT dateVal = PG_GETARG_DATEADT(0); + TimestampTz dt2 = PG_GETARG_TIMESTAMPTZ(1); + + PG_RETURN_BOOL(date_cmp_timestamptz_internal(dateVal, dt2) >= 0); +} + +Datum +date_cmp_timestamptz(PG_FUNCTION_ARGS) +{ + DateADT dateVal = PG_GETARG_DATEADT(0); + TimestampTz dt2 = PG_GETARG_TIMESTAMPTZ(1); + + PG_RETURN_INT32(date_cmp_timestamptz_internal(dateVal, dt2)); +} + +Datum +timestamp_eq_date(PG_FUNCTION_ARGS) +{ + Timestamp dt1 = PG_GETARG_TIMESTAMP(0); + DateADT dateVal = PG_GETARG_DATEADT(1); + + PG_RETURN_BOOL(date_cmp_timestamp_internal(dateVal, dt1) == 0); +} + +Datum +timestamp_ne_date(PG_FUNCTION_ARGS) +{ + Timestamp dt1 = PG_GETARG_TIMESTAMP(0); + DateADT dateVal = PG_GETARG_DATEADT(1); + + PG_RETURN_BOOL(date_cmp_timestamp_internal(dateVal, dt1) != 0); +} + +Datum +timestamp_lt_date(PG_FUNCTION_ARGS) +{ + Timestamp dt1 = PG_GETARG_TIMESTAMP(0); + DateADT dateVal = PG_GETARG_DATEADT(1); + + PG_RETURN_BOOL(date_cmp_timestamp_internal(dateVal, dt1) > 0); +} + +Datum +timestamp_gt_date(PG_FUNCTION_ARGS) +{ + Timestamp dt1 = PG_GETARG_TIMESTAMP(0); + DateADT dateVal = PG_GETARG_DATEADT(1); + + PG_RETURN_BOOL(date_cmp_timestamp_internal(dateVal, dt1) < 0); +} + +Datum +timestamp_le_date(PG_FUNCTION_ARGS) +{ + Timestamp dt1 = PG_GETARG_TIMESTAMP(0); + DateADT dateVal = PG_GETARG_DATEADT(1); + + PG_RETURN_BOOL(date_cmp_timestamp_internal(dateVal, dt1) >= 0); +} + +Datum +timestamp_ge_date(PG_FUNCTION_ARGS) +{ + Timestamp dt1 = PG_GETARG_TIMESTAMP(0); + DateADT dateVal = PG_GETARG_DATEADT(1); + + PG_RETURN_BOOL(date_cmp_timestamp_internal(dateVal, dt1) <= 0); +} + +Datum +timestamp_cmp_date(PG_FUNCTION_ARGS) +{ + Timestamp dt1 = PG_GETARG_TIMESTAMP(0); + DateADT dateVal = PG_GETARG_DATEADT(1); + + PG_RETURN_INT32(-date_cmp_timestamp_internal(dateVal, dt1)); +} + +Datum +timestamptz_eq_date(PG_FUNCTION_ARGS) +{ + TimestampTz dt1 = PG_GETARG_TIMESTAMPTZ(0); + DateADT dateVal = PG_GETARG_DATEADT(1); + + PG_RETURN_BOOL(date_cmp_timestamptz_internal(dateVal, dt1) == 0); +} + +Datum +timestamptz_ne_date(PG_FUNCTION_ARGS) +{ + TimestampTz dt1 = PG_GETARG_TIMESTAMPTZ(0); + DateADT dateVal = PG_GETARG_DATEADT(1); + + PG_RETURN_BOOL(date_cmp_timestamptz_internal(dateVal, dt1) != 0); +} + +Datum +timestamptz_lt_date(PG_FUNCTION_ARGS) +{ + TimestampTz dt1 = PG_GETARG_TIMESTAMPTZ(0); + DateADT dateVal = PG_GETARG_DATEADT(1); + + PG_RETURN_BOOL(date_cmp_timestamptz_internal(dateVal, dt1) > 0); +} + +Datum +timestamptz_gt_date(PG_FUNCTION_ARGS) +{ + TimestampTz dt1 = PG_GETARG_TIMESTAMPTZ(0); + DateADT dateVal = PG_GETARG_DATEADT(1); + + PG_RETURN_BOOL(date_cmp_timestamptz_internal(dateVal, dt1) < 0); +} + +Datum +timestamptz_le_date(PG_FUNCTION_ARGS) +{ + TimestampTz dt1 = PG_GETARG_TIMESTAMPTZ(0); + DateADT dateVal = PG_GETARG_DATEADT(1); + + PG_RETURN_BOOL(date_cmp_timestamptz_internal(dateVal, dt1) >= 0); +} + +Datum +timestamptz_ge_date(PG_FUNCTION_ARGS) +{ + TimestampTz dt1 = PG_GETARG_TIMESTAMPTZ(0); + DateADT dateVal = PG_GETARG_DATEADT(1); + + PG_RETURN_BOOL(date_cmp_timestamptz_internal(dateVal, dt1) <= 0); +} + +Datum +timestamptz_cmp_date(PG_FUNCTION_ARGS) +{ + TimestampTz dt1 = PG_GETARG_TIMESTAMPTZ(0); + DateADT dateVal = PG_GETARG_DATEADT(1); + + PG_RETURN_INT32(-date_cmp_timestamptz_internal(dateVal, dt1)); +} + +/* + * in_range support function for date. + * + * We implement this by promoting the dates to timestamp (without time zone) + * and then using the timestamp-and-interval in_range function. + */ +Datum +in_range_date_interval(PG_FUNCTION_ARGS) +{ + DateADT val = PG_GETARG_DATEADT(0); + DateADT base = PG_GETARG_DATEADT(1); + Interval *offset = PG_GETARG_INTERVAL_P(2); + bool sub = PG_GETARG_BOOL(3); + bool less = PG_GETARG_BOOL(4); + Timestamp valStamp; + Timestamp baseStamp; + + /* XXX we could support out-of-range cases here, perhaps */ + valStamp = date2timestamp(val); + baseStamp = date2timestamp(base); + + return DirectFunctionCall5(in_range_timestamp_interval, + TimestampGetDatum(valStamp), + TimestampGetDatum(baseStamp), + IntervalPGetDatum(offset), + BoolGetDatum(sub), + BoolGetDatum(less)); +} + + +/* extract_date() + * Extract specified field from date type. + */ +Datum +extract_date(PG_FUNCTION_ARGS) +{ + text *units = PG_GETARG_TEXT_PP(0); + DateADT date = PG_GETARG_DATEADT(1); + int64 intresult; + int type, + val; + char *lowunits; + int year, + mon, + mday; + + lowunits = downcase_truncate_identifier(VARDATA_ANY(units), + VARSIZE_ANY_EXHDR(units), + false); + + type = DecodeUnits(0, lowunits, &val); + if (type == UNKNOWN_FIELD) + type = DecodeSpecial(0, lowunits, &val); + + if (DATE_NOT_FINITE(date) && (type == UNITS || type == RESERV)) + { + switch (val) + { + /* Oscillating units */ + case DTK_DAY: + case DTK_MONTH: + case DTK_QUARTER: + case DTK_WEEK: + case DTK_DOW: + case DTK_ISODOW: + case DTK_DOY: + PG_RETURN_NULL(); + break; + + /* Monotonically-increasing units */ + case DTK_YEAR: + case DTK_DECADE: + case DTK_CENTURY: + case DTK_MILLENNIUM: + case DTK_JULIAN: + case DTK_ISOYEAR: + case DTK_EPOCH: + if (DATE_IS_NOBEGIN(date)) + PG_RETURN_NUMERIC(DatumGetNumeric(DirectFunctionCall3(numeric_in, + CStringGetDatum("-Infinity"), + ObjectIdGetDatum(InvalidOid), + Int32GetDatum(-1)))); + else + PG_RETURN_NUMERIC(DatumGetNumeric(DirectFunctionCall3(numeric_in, + CStringGetDatum("Infinity"), + ObjectIdGetDatum(InvalidOid), + Int32GetDatum(-1)))); + default: + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("unit \"%s\" not supported for type %s", + lowunits, format_type_be(DATEOID)))); + } + } + else if (type == UNITS) + { + j2date(date + POSTGRES_EPOCH_JDATE, &year, &mon, &mday); + + switch (val) + { + case DTK_DAY: + intresult = mday; + break; + + case DTK_MONTH: + intresult = mon; + break; + + case DTK_QUARTER: + intresult = (mon - 1) / 3 + 1; + break; + + case DTK_WEEK: + intresult = date2isoweek(year, mon, mday); + break; + + case DTK_YEAR: + if (year > 0) + intresult = year; + else + /* there is no year 0, just 1 BC and 1 AD */ + intresult = year - 1; + break; + + case DTK_DECADE: + /* see comments in timestamp_part */ + if (year >= 0) + intresult = year / 10; + else + intresult = -((8 - (year - 1)) / 10); + break; + + case DTK_CENTURY: + /* see comments in timestamp_part */ + if (year > 0) + intresult = (year + 99) / 100; + else + intresult = -((99 - (year - 1)) / 100); + break; + + case DTK_MILLENNIUM: + /* see comments in timestamp_part */ + if (year > 0) + intresult = (year + 999) / 1000; + else + intresult = -((999 - (year - 1)) / 1000); + break; + + case DTK_JULIAN: + intresult = date + POSTGRES_EPOCH_JDATE; + break; + + case DTK_ISOYEAR: + intresult = date2isoyear(year, mon, mday); + /* Adjust BC years */ + if (intresult <= 0) + intresult -= 1; + break; + + case DTK_DOW: + case DTK_ISODOW: + intresult = j2day(date + POSTGRES_EPOCH_JDATE); + if (val == DTK_ISODOW && intresult == 0) + intresult = 7; + break; + + case DTK_DOY: + intresult = date2j(year, mon, mday) - date2j(year, 1, 1) + 1; + break; + + default: + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("unit \"%s\" not supported for type %s", + lowunits, format_type_be(DATEOID)))); + intresult = 0; + } + } + else if (type == RESERV) + { + switch (val) + { + case DTK_EPOCH: + intresult = ((int64) date + POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * SECS_PER_DAY; + break; + + default: + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("unit \"%s\" not supported for type %s", + lowunits, format_type_be(DATEOID)))); + intresult = 0; + } + } + else + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unit \"%s\" not recognized for type %s", + lowunits, format_type_be(DATEOID)))); + intresult = 0; + } + + PG_RETURN_NUMERIC(int64_to_numeric(intresult)); +} + + +/* Add an interval to a date, giving a new date. + * Must handle both positive and negative intervals. + * + * We implement this by promoting the date to timestamp (without time zone) + * and then using the timestamp plus interval function. + */ +Datum +date_pl_interval(PG_FUNCTION_ARGS) +{ + DateADT dateVal = PG_GETARG_DATEADT(0); + Interval *span = PG_GETARG_INTERVAL_P(1); + Timestamp dateStamp; + + dateStamp = date2timestamp(dateVal); + + return DirectFunctionCall2(timestamp_pl_interval, + TimestampGetDatum(dateStamp), + PointerGetDatum(span)); +} + +/* Subtract an interval from a date, giving a new date. + * Must handle both positive and negative intervals. + * + * We implement this by promoting the date to timestamp (without time zone) + * and then using the timestamp minus interval function. + */ +Datum +date_mi_interval(PG_FUNCTION_ARGS) +{ + DateADT dateVal = PG_GETARG_DATEADT(0); + Interval *span = PG_GETARG_INTERVAL_P(1); + Timestamp dateStamp; + + dateStamp = date2timestamp(dateVal); + + return DirectFunctionCall2(timestamp_mi_interval, + TimestampGetDatum(dateStamp), + PointerGetDatum(span)); +} + +/* date_timestamp() + * Convert date to timestamp data type. + */ +Datum +date_timestamp(PG_FUNCTION_ARGS) +{ + DateADT dateVal = PG_GETARG_DATEADT(0); + Timestamp result; + + result = date2timestamp(dateVal); + + PG_RETURN_TIMESTAMP(result); +} + +/* timestamp_date() + * Convert timestamp to date data type. + */ +Datum +timestamp_date(PG_FUNCTION_ARGS) +{ + Timestamp timestamp = PG_GETARG_TIMESTAMP(0); + DateADT result; + struct pg_tm tt, + *tm = &tt; + fsec_t fsec; + + if (TIMESTAMP_IS_NOBEGIN(timestamp)) + DATE_NOBEGIN(result); + else if (TIMESTAMP_IS_NOEND(timestamp)) + DATE_NOEND(result); + else + { + if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + + result = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - POSTGRES_EPOCH_JDATE; + } + + PG_RETURN_DATEADT(result); +} + + +/* date_timestamptz() + * Convert date to timestamp with time zone data type. + */ +Datum +date_timestamptz(PG_FUNCTION_ARGS) +{ + DateADT dateVal = PG_GETARG_DATEADT(0); + TimestampTz result; + + result = date2timestamptz(dateVal); + + PG_RETURN_TIMESTAMP(result); +} + + +/* timestamptz_date() + * Convert timestamp with time zone to date data type. + */ +Datum +timestamptz_date(PG_FUNCTION_ARGS) +{ + TimestampTz timestamp = PG_GETARG_TIMESTAMP(0); + DateADT result; + struct pg_tm tt, + *tm = &tt; + fsec_t fsec; + int tz; + + if (TIMESTAMP_IS_NOBEGIN(timestamp)) + DATE_NOBEGIN(result); + else if (TIMESTAMP_IS_NOEND(timestamp)) + DATE_NOEND(result); + else + { + if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + + result = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - POSTGRES_EPOCH_JDATE; + } + + PG_RETURN_DATEADT(result); +} + + +/***************************************************************************** + * Time ADT + *****************************************************************************/ + +Datum +time_in(PG_FUNCTION_ARGS) +{ + char *str = PG_GETARG_CSTRING(0); +#ifdef NOT_USED + Oid typelem = PG_GETARG_OID(1); +#endif + int32 typmod = PG_GETARG_INT32(2); + Node *escontext = fcinfo->context; + TimeADT result; + fsec_t fsec; + struct pg_tm tt, + *tm = &tt; + int tz; + int nf; + int dterr; + char workbuf[MAXDATELEN + 1]; + char *field[MAXDATEFIELDS]; + int dtype; + int ftype[MAXDATEFIELDS]; + DateTimeErrorExtra extra; + + dterr = ParseDateTime(str, workbuf, sizeof(workbuf), + field, ftype, MAXDATEFIELDS, &nf); + if (dterr == 0) + dterr = DecodeTimeOnly(field, ftype, nf, + &dtype, tm, &fsec, &tz, &extra); + if (dterr != 0) + { + DateTimeParseError(dterr, &extra, str, "time", escontext); + PG_RETURN_NULL(); + } + + tm2time(tm, fsec, &result); + AdjustTimeForTypmod(&result, typmod); + + PG_RETURN_TIMEADT(result); +} + +/* tm2time() + * Convert a tm structure to a time data type. + */ +int +tm2time(struct pg_tm *tm, fsec_t fsec, TimeADT *result) +{ + *result = ((((tm->tm_hour * MINS_PER_HOUR + tm->tm_min) * SECS_PER_MINUTE) + tm->tm_sec) + * USECS_PER_SEC) + fsec; + return 0; +} + +/* time_overflows() + * Check to see if a broken-down time-of-day is out of range. + */ +bool +time_overflows(int hour, int min, int sec, fsec_t fsec) +{ + /* Range-check the fields individually. */ + if (hour < 0 || hour > HOURS_PER_DAY || + min < 0 || min >= MINS_PER_HOUR || + sec < 0 || sec > SECS_PER_MINUTE || + fsec < 0 || fsec > USECS_PER_SEC) + return true; + + /* + * Because we allow, eg, hour = 24 or sec = 60, we must check separately + * that the total time value doesn't exceed 24:00:00. + */ + if ((((((hour * MINS_PER_HOUR + min) * SECS_PER_MINUTE) + + sec) * USECS_PER_SEC) + fsec) > USECS_PER_DAY) + return true; + + return false; +} + +/* float_time_overflows() + * Same, when we have seconds + fractional seconds as one "double" value. + */ +bool +float_time_overflows(int hour, int min, double sec) +{ + /* Range-check the fields individually. */ + if (hour < 0 || hour > HOURS_PER_DAY || + min < 0 || min >= MINS_PER_HOUR) + return true; + + /* + * "sec", being double, requires extra care. Cope with NaN, and round off + * before applying the range check to avoid unexpected errors due to + * imprecise input. (We assume rint() behaves sanely with infinities.) + */ + if (isnan(sec)) + return true; + sec = rint(sec * USECS_PER_SEC); + if (sec < 0 || sec > SECS_PER_MINUTE * USECS_PER_SEC) + return true; + + /* + * Because we allow, eg, hour = 24 or sec = 60, we must check separately + * that the total time value doesn't exceed 24:00:00. This must match the + * way that callers will convert the fields to a time. + */ + if (((((hour * MINS_PER_HOUR + min) * SECS_PER_MINUTE) + * USECS_PER_SEC) + (int64) sec) > USECS_PER_DAY) + return true; + + return false; +} + + +/* time2tm() + * Convert time data type to POSIX time structure. + * + * Note that only the hour/min/sec/fractional-sec fields are filled in. + */ +int +time2tm(TimeADT time, struct pg_tm *tm, fsec_t *fsec) +{ + tm->tm_hour = time / USECS_PER_HOUR; + time -= tm->tm_hour * USECS_PER_HOUR; + tm->tm_min = time / USECS_PER_MINUTE; + time -= tm->tm_min * USECS_PER_MINUTE; + tm->tm_sec = time / USECS_PER_SEC; + time -= tm->tm_sec * USECS_PER_SEC; + *fsec = time; + return 0; +} + +Datum +time_out(PG_FUNCTION_ARGS) +{ + TimeADT time = PG_GETARG_TIMEADT(0); + char *result; + struct pg_tm tt, + *tm = &tt; + fsec_t fsec; + char buf[MAXDATELEN + 1]; + + time2tm(time, tm, &fsec); + EncodeTimeOnly(tm, fsec, false, 0, DateStyle, buf); + + result = pstrdup(buf); + PG_RETURN_CSTRING(result); +} + +/* + * time_recv - converts external binary format to time + */ +Datum +time_recv(PG_FUNCTION_ARGS) +{ + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + +#ifdef NOT_USED + Oid typelem = PG_GETARG_OID(1); +#endif + int32 typmod = PG_GETARG_INT32(2); + TimeADT result; + + result = pq_getmsgint64(buf); + + if (result < INT64CONST(0) || result > USECS_PER_DAY) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("time out of range"))); + + AdjustTimeForTypmod(&result, typmod); + + PG_RETURN_TIMEADT(result); +} + +/* + * time_send - converts time to binary format + */ +Datum +time_send(PG_FUNCTION_ARGS) +{ + TimeADT time = PG_GETARG_TIMEADT(0); + StringInfoData buf; + + pq_begintypsend(&buf); + pq_sendint64(&buf, time); + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} + +Datum +timetypmodin(PG_FUNCTION_ARGS) +{ + ArrayType *ta = PG_GETARG_ARRAYTYPE_P(0); + + PG_RETURN_INT32(anytime_typmodin(false, ta)); +} + +Datum +timetypmodout(PG_FUNCTION_ARGS) +{ + int32 typmod = PG_GETARG_INT32(0); + + PG_RETURN_CSTRING(anytime_typmodout(false, typmod)); +} + +/* + * make_time - time constructor + */ +Datum +make_time(PG_FUNCTION_ARGS) +{ + int tm_hour = PG_GETARG_INT32(0); + int tm_min = PG_GETARG_INT32(1); + double sec = PG_GETARG_FLOAT8(2); + TimeADT time; + + /* Check for time overflow */ + if (float_time_overflows(tm_hour, tm_min, sec)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_FIELD_OVERFLOW), + errmsg("time field value out of range: %d:%02d:%02g", + tm_hour, tm_min, sec))); + + /* This should match tm2time */ + time = (((tm_hour * MINS_PER_HOUR + tm_min) * SECS_PER_MINUTE) + * USECS_PER_SEC) + (int64) rint(sec * USECS_PER_SEC); + + PG_RETURN_TIMEADT(time); +} + + +/* time_support() + * + * Planner support function for the time_scale() and timetz_scale() + * length coercion functions (we need not distinguish them here). + */ +Datum +time_support(PG_FUNCTION_ARGS) +{ + Node *rawreq = (Node *) PG_GETARG_POINTER(0); + Node *ret = NULL; + + if (IsA(rawreq, SupportRequestSimplify)) + { + SupportRequestSimplify *req = (SupportRequestSimplify *) rawreq; + + ret = TemporalSimplify(MAX_TIME_PRECISION, (Node *) req->fcall); + } + + PG_RETURN_POINTER(ret); +} + +/* time_scale() + * Adjust time type for specified scale factor. + * Used by PostgreSQL type system to stuff columns. + */ +Datum +time_scale(PG_FUNCTION_ARGS) +{ + TimeADT time = PG_GETARG_TIMEADT(0); + int32 typmod = PG_GETARG_INT32(1); + TimeADT result; + + result = time; + AdjustTimeForTypmod(&result, typmod); + + PG_RETURN_TIMEADT(result); +} + +/* AdjustTimeForTypmod() + * Force the precision of the time value to a specified value. + * Uses *exactly* the same code as in AdjustTimestampForTypmod() + * but we make a separate copy because those types do not + * have a fundamental tie together but rather a coincidence of + * implementation. - thomas + */ +void +AdjustTimeForTypmod(TimeADT *time, int32 typmod) +{ + static const int64 TimeScales[MAX_TIME_PRECISION + 1] = { + INT64CONST(1000000), + INT64CONST(100000), + INT64CONST(10000), + INT64CONST(1000), + INT64CONST(100), + INT64CONST(10), + INT64CONST(1) + }; + + static const int64 TimeOffsets[MAX_TIME_PRECISION + 1] = { + INT64CONST(500000), + INT64CONST(50000), + INT64CONST(5000), + INT64CONST(500), + INT64CONST(50), + INT64CONST(5), + INT64CONST(0) + }; + + if (typmod >= 0 && typmod <= MAX_TIME_PRECISION) + { + if (*time >= INT64CONST(0)) + *time = ((*time + TimeOffsets[typmod]) / TimeScales[typmod]) * + TimeScales[typmod]; + else + *time = -((((-*time) + TimeOffsets[typmod]) / TimeScales[typmod]) * + TimeScales[typmod]); + } +} + + +Datum +time_eq(PG_FUNCTION_ARGS) +{ + TimeADT time1 = PG_GETARG_TIMEADT(0); + TimeADT time2 = PG_GETARG_TIMEADT(1); + + PG_RETURN_BOOL(time1 == time2); +} + +Datum +time_ne(PG_FUNCTION_ARGS) +{ + TimeADT time1 = PG_GETARG_TIMEADT(0); + TimeADT time2 = PG_GETARG_TIMEADT(1); + + PG_RETURN_BOOL(time1 != time2); +} + +Datum +time_lt(PG_FUNCTION_ARGS) +{ + TimeADT time1 = PG_GETARG_TIMEADT(0); + TimeADT time2 = PG_GETARG_TIMEADT(1); + + PG_RETURN_BOOL(time1 < time2); +} + +Datum +time_le(PG_FUNCTION_ARGS) +{ + TimeADT time1 = PG_GETARG_TIMEADT(0); + TimeADT time2 = PG_GETARG_TIMEADT(1); + + PG_RETURN_BOOL(time1 <= time2); +} + +Datum +time_gt(PG_FUNCTION_ARGS) +{ + TimeADT time1 = PG_GETARG_TIMEADT(0); + TimeADT time2 = PG_GETARG_TIMEADT(1); + + PG_RETURN_BOOL(time1 > time2); +} + +Datum +time_ge(PG_FUNCTION_ARGS) +{ + TimeADT time1 = PG_GETARG_TIMEADT(0); + TimeADT time2 = PG_GETARG_TIMEADT(1); + + PG_RETURN_BOOL(time1 >= time2); +} + +Datum +time_cmp(PG_FUNCTION_ARGS) +{ + TimeADT time1 = PG_GETARG_TIMEADT(0); + TimeADT time2 = PG_GETARG_TIMEADT(1); + + if (time1 < time2) + PG_RETURN_INT32(-1); + if (time1 > time2) + PG_RETURN_INT32(1); + PG_RETURN_INT32(0); +} + +Datum +time_hash(PG_FUNCTION_ARGS) +{ + return hashint8(fcinfo); +} + +Datum +time_hash_extended(PG_FUNCTION_ARGS) +{ + return hashint8extended(fcinfo); +} + +Datum +time_larger(PG_FUNCTION_ARGS) +{ + TimeADT time1 = PG_GETARG_TIMEADT(0); + TimeADT time2 = PG_GETARG_TIMEADT(1); + + PG_RETURN_TIMEADT((time1 > time2) ? time1 : time2); +} + +Datum +time_smaller(PG_FUNCTION_ARGS) +{ + TimeADT time1 = PG_GETARG_TIMEADT(0); + TimeADT time2 = PG_GETARG_TIMEADT(1); + + PG_RETURN_TIMEADT((time1 < time2) ? time1 : time2); +} + +/* overlaps_time() --- implements the SQL OVERLAPS operator. + * + * Algorithm is per SQL spec. This is much harder than you'd think + * because the spec requires us to deliver a non-null answer in some cases + * where some of the inputs are null. + */ +Datum +overlaps_time(PG_FUNCTION_ARGS) +{ + /* + * The arguments are TimeADT, but we leave them as generic Datums to avoid + * dereferencing nulls (TimeADT is pass-by-reference!) + */ + Datum ts1 = PG_GETARG_DATUM(0); + Datum te1 = PG_GETARG_DATUM(1); + Datum ts2 = PG_GETARG_DATUM(2); + Datum te2 = PG_GETARG_DATUM(3); + bool ts1IsNull = PG_ARGISNULL(0); + bool te1IsNull = PG_ARGISNULL(1); + bool ts2IsNull = PG_ARGISNULL(2); + bool te2IsNull = PG_ARGISNULL(3); + +#define TIMEADT_GT(t1,t2) \ + (DatumGetTimeADT(t1) > DatumGetTimeADT(t2)) +#define TIMEADT_LT(t1,t2) \ + (DatumGetTimeADT(t1) < DatumGetTimeADT(t2)) + + /* + * If both endpoints of interval 1 are null, the result is null (unknown). + * If just one endpoint is null, take ts1 as the non-null one. Otherwise, + * take ts1 as the lesser endpoint. + */ + if (ts1IsNull) + { + if (te1IsNull) + PG_RETURN_NULL(); + /* swap null for non-null */ + ts1 = te1; + te1IsNull = true; + } + else if (!te1IsNull) + { + if (TIMEADT_GT(ts1, te1)) + { + Datum tt = ts1; + + ts1 = te1; + te1 = tt; + } + } + + /* Likewise for interval 2. */ + if (ts2IsNull) + { + if (te2IsNull) + PG_RETURN_NULL(); + /* swap null for non-null */ + ts2 = te2; + te2IsNull = true; + } + else if (!te2IsNull) + { + if (TIMEADT_GT(ts2, te2)) + { + Datum tt = ts2; + + ts2 = te2; + te2 = tt; + } + } + + /* + * At this point neither ts1 nor ts2 is null, so we can consider three + * cases: ts1 > ts2, ts1 < ts2, ts1 = ts2 + */ + if (TIMEADT_GT(ts1, ts2)) + { + /* + * This case is ts1 < te2 OR te1 < te2, which may look redundant but + * in the presence of nulls it's not quite completely so. + */ + if (te2IsNull) + PG_RETURN_NULL(); + if (TIMEADT_LT(ts1, te2)) + PG_RETURN_BOOL(true); + if (te1IsNull) + PG_RETURN_NULL(); + + /* + * If te1 is not null then we had ts1 <= te1 above, and we just found + * ts1 >= te2, hence te1 >= te2. + */ + PG_RETURN_BOOL(false); + } + else if (TIMEADT_LT(ts1, ts2)) + { + /* This case is ts2 < te1 OR te2 < te1 */ + if (te1IsNull) + PG_RETURN_NULL(); + if (TIMEADT_LT(ts2, te1)) + PG_RETURN_BOOL(true); + if (te2IsNull) + PG_RETURN_NULL(); + + /* + * If te2 is not null then we had ts2 <= te2 above, and we just found + * ts2 >= te1, hence te2 >= te1. + */ + PG_RETURN_BOOL(false); + } + else + { + /* + * For ts1 = ts2 the spec says te1 <> te2 OR te1 = te2, which is a + * rather silly way of saying "true if both are nonnull, else null". + */ + if (te1IsNull || te2IsNull) + PG_RETURN_NULL(); + PG_RETURN_BOOL(true); + } + +#undef TIMEADT_GT +#undef TIMEADT_LT +} + +/* timestamp_time() + * Convert timestamp to time data type. + */ +Datum +timestamp_time(PG_FUNCTION_ARGS) +{ + Timestamp timestamp = PG_GETARG_TIMESTAMP(0); + TimeADT result; + struct pg_tm tt, + *tm = &tt; + fsec_t fsec; + + if (TIMESTAMP_NOT_FINITE(timestamp)) + PG_RETURN_NULL(); + + if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + + /* + * Could also do this with time = (timestamp / USECS_PER_DAY * + * USECS_PER_DAY) - timestamp; + */ + result = ((((tm->tm_hour * MINS_PER_HOUR + tm->tm_min) * SECS_PER_MINUTE) + tm->tm_sec) * + USECS_PER_SEC) + fsec; + + PG_RETURN_TIMEADT(result); +} + +/* timestamptz_time() + * Convert timestamptz to time data type. + */ +Datum +timestamptz_time(PG_FUNCTION_ARGS) +{ + TimestampTz timestamp = PG_GETARG_TIMESTAMP(0); + TimeADT result; + struct pg_tm tt, + *tm = &tt; + int tz; + fsec_t fsec; + + if (TIMESTAMP_NOT_FINITE(timestamp)) + PG_RETURN_NULL(); + + if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + + /* + * Could also do this with time = (timestamp / USECS_PER_DAY * + * USECS_PER_DAY) - timestamp; + */ + result = ((((tm->tm_hour * MINS_PER_HOUR + tm->tm_min) * SECS_PER_MINUTE) + tm->tm_sec) * + USECS_PER_SEC) + fsec; + + PG_RETURN_TIMEADT(result); +} + +/* datetime_timestamp() + * Convert date and time to timestamp data type. + */ +Datum +datetime_timestamp(PG_FUNCTION_ARGS) +{ + DateADT date = PG_GETARG_DATEADT(0); + TimeADT time = PG_GETARG_TIMEADT(1); + Timestamp result; + + result = date2timestamp(date); + if (!TIMESTAMP_NOT_FINITE(result)) + { + result += time; + if (!IS_VALID_TIMESTAMP(result)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + } + + PG_RETURN_TIMESTAMP(result); +} + +/* time_interval() + * Convert time to interval data type. + */ +Datum +time_interval(PG_FUNCTION_ARGS) +{ + TimeADT time = PG_GETARG_TIMEADT(0); + Interval *result; + + result = (Interval *) palloc(sizeof(Interval)); + + result->time = time; + result->day = 0; + result->month = 0; + + PG_RETURN_INTERVAL_P(result); +} + +/* interval_time() + * Convert interval to time data type. + * + * This is defined as producing the fractional-day portion of the interval. + * Therefore, we can just ignore the months field. It is not real clear + * what to do with negative intervals, but we choose to subtract the floor, + * so that, say, '-2 hours' becomes '22:00:00'. + */ +Datum +interval_time(PG_FUNCTION_ARGS) +{ + Interval *span = PG_GETARG_INTERVAL_P(0); + TimeADT result; + int64 days; + + result = span->time; + if (result >= USECS_PER_DAY) + { + days = result / USECS_PER_DAY; + result -= days * USECS_PER_DAY; + } + else if (result < 0) + { + days = (-result + USECS_PER_DAY - 1) / USECS_PER_DAY; + result += days * USECS_PER_DAY; + } + + PG_RETURN_TIMEADT(result); +} + +/* time_mi_time() + * Subtract two times to produce an interval. + */ +Datum +time_mi_time(PG_FUNCTION_ARGS) +{ + TimeADT time1 = PG_GETARG_TIMEADT(0); + TimeADT time2 = PG_GETARG_TIMEADT(1); + Interval *result; + + result = (Interval *) palloc(sizeof(Interval)); + + result->month = 0; + result->day = 0; + result->time = time1 - time2; + + PG_RETURN_INTERVAL_P(result); +} + +/* time_pl_interval() + * Add interval to time. + */ +Datum +time_pl_interval(PG_FUNCTION_ARGS) +{ + TimeADT time = PG_GETARG_TIMEADT(0); + Interval *span = PG_GETARG_INTERVAL_P(1); + TimeADT result; + + result = time + span->time; + result -= result / USECS_PER_DAY * USECS_PER_DAY; + if (result < INT64CONST(0)) + result += USECS_PER_DAY; + + PG_RETURN_TIMEADT(result); +} + +/* time_mi_interval() + * Subtract interval from time. + */ +Datum +time_mi_interval(PG_FUNCTION_ARGS) +{ + TimeADT time = PG_GETARG_TIMEADT(0); + Interval *span = PG_GETARG_INTERVAL_P(1); + TimeADT result; + + result = time - span->time; + result -= result / USECS_PER_DAY * USECS_PER_DAY; + if (result < INT64CONST(0)) + result += USECS_PER_DAY; + + PG_RETURN_TIMEADT(result); +} + +/* + * in_range support function for time. + */ +Datum +in_range_time_interval(PG_FUNCTION_ARGS) +{ + TimeADT val = PG_GETARG_TIMEADT(0); + TimeADT base = PG_GETARG_TIMEADT(1); + Interval *offset = PG_GETARG_INTERVAL_P(2); + bool sub = PG_GETARG_BOOL(3); + bool less = PG_GETARG_BOOL(4); + TimeADT sum; + + /* + * Like time_pl_interval/time_mi_interval, we disregard the month and day + * fields of the offset. So our test for negative should too. + */ + if (offset->time < 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE), + errmsg("invalid preceding or following size in window function"))); + + /* + * We can't use time_pl_interval/time_mi_interval here, because their + * wraparound behavior would give wrong (or at least undesirable) answers. + * Fortunately the equivalent non-wrapping behavior is trivial, especially + * since we don't worry about integer overflow. + */ + if (sub) + sum = base - offset->time; + else + sum = base + offset->time; + + if (less) + PG_RETURN_BOOL(val <= sum); + else + PG_RETURN_BOOL(val >= sum); +} + + +/* time_part() and extract_time() + * Extract specified field from time type. + */ +static Datum +time_part_common(PG_FUNCTION_ARGS, bool retnumeric) +{ + text *units = PG_GETARG_TEXT_PP(0); + TimeADT time = PG_GETARG_TIMEADT(1); + int64 intresult; + int type, + val; + char *lowunits; + + lowunits = downcase_truncate_identifier(VARDATA_ANY(units), + VARSIZE_ANY_EXHDR(units), + false); + + type = DecodeUnits(0, lowunits, &val); + if (type == UNKNOWN_FIELD) + type = DecodeSpecial(0, lowunits, &val); + + if (type == UNITS) + { + fsec_t fsec; + struct pg_tm tt, + *tm = &tt; + + time2tm(time, tm, &fsec); + + switch (val) + { + case DTK_MICROSEC: + intresult = tm->tm_sec * INT64CONST(1000000) + fsec; + break; + + case DTK_MILLISEC: + if (retnumeric) + /*--- + * tm->tm_sec * 1000 + fsec / 1000 + * = (tm->tm_sec * 1'000'000 + fsec) / 1000 + */ + PG_RETURN_NUMERIC(int64_div_fast_to_numeric(tm->tm_sec * INT64CONST(1000000) + fsec, 3)); + else + PG_RETURN_FLOAT8(tm->tm_sec * 1000.0 + fsec / 1000.0); + break; + + case DTK_SECOND: + if (retnumeric) + /*--- + * tm->tm_sec + fsec / 1'000'000 + * = (tm->tm_sec * 1'000'000 + fsec) / 1'000'000 + */ + PG_RETURN_NUMERIC(int64_div_fast_to_numeric(tm->tm_sec * INT64CONST(1000000) + fsec, 6)); + else + PG_RETURN_FLOAT8(tm->tm_sec + fsec / 1000000.0); + break; + + case DTK_MINUTE: + intresult = tm->tm_min; + break; + + case DTK_HOUR: + intresult = tm->tm_hour; + break; + + case DTK_TZ: + case DTK_TZ_MINUTE: + case DTK_TZ_HOUR: + case DTK_DAY: + case DTK_MONTH: + case DTK_QUARTER: + case DTK_YEAR: + case DTK_DECADE: + case DTK_CENTURY: + case DTK_MILLENNIUM: + case DTK_ISOYEAR: + default: + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("unit \"%s\" not supported for type %s", + lowunits, format_type_be(TIMEOID)))); + intresult = 0; + } + } + else if (type == RESERV && val == DTK_EPOCH) + { + if (retnumeric) + PG_RETURN_NUMERIC(int64_div_fast_to_numeric(time, 6)); + else + PG_RETURN_FLOAT8(time / 1000000.0); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unit \"%s\" not recognized for type %s", + lowunits, format_type_be(TIMEOID)))); + intresult = 0; + } + + if (retnumeric) + PG_RETURN_NUMERIC(int64_to_numeric(intresult)); + else + PG_RETURN_FLOAT8(intresult); +} + +Datum +time_part(PG_FUNCTION_ARGS) +{ + return time_part_common(fcinfo, false); +} + +Datum +extract_time(PG_FUNCTION_ARGS) +{ + return time_part_common(fcinfo, true); +} + + +/***************************************************************************** + * Time With Time Zone ADT + *****************************************************************************/ + +/* tm2timetz() + * Convert a tm structure to a time data type. + */ +int +tm2timetz(struct pg_tm *tm, fsec_t fsec, int tz, TimeTzADT *result) +{ + result->time = ((((tm->tm_hour * MINS_PER_HOUR + tm->tm_min) * SECS_PER_MINUTE) + tm->tm_sec) * + USECS_PER_SEC) + fsec; + result->zone = tz; + + return 0; +} + +Datum +timetz_in(PG_FUNCTION_ARGS) +{ + char *str = PG_GETARG_CSTRING(0); +#ifdef NOT_USED + Oid typelem = PG_GETARG_OID(1); +#endif + int32 typmod = PG_GETARG_INT32(2); + Node *escontext = fcinfo->context; + TimeTzADT *result; + fsec_t fsec; + struct pg_tm tt, + *tm = &tt; + int tz; + int nf; + int dterr; + char workbuf[MAXDATELEN + 1]; + char *field[MAXDATEFIELDS]; + int dtype; + int ftype[MAXDATEFIELDS]; + DateTimeErrorExtra extra; + + dterr = ParseDateTime(str, workbuf, sizeof(workbuf), + field, ftype, MAXDATEFIELDS, &nf); + if (dterr == 0) + dterr = DecodeTimeOnly(field, ftype, nf, + &dtype, tm, &fsec, &tz, &extra); + if (dterr != 0) + { + DateTimeParseError(dterr, &extra, str, "time with time zone", + escontext); + PG_RETURN_NULL(); + } + + result = (TimeTzADT *) palloc(sizeof(TimeTzADT)); + tm2timetz(tm, fsec, tz, result); + AdjustTimeForTypmod(&(result->time), typmod); + + PG_RETURN_TIMETZADT_P(result); +} + +Datum +timetz_out(PG_FUNCTION_ARGS) +{ + TimeTzADT *time = PG_GETARG_TIMETZADT_P(0); + char *result; + struct pg_tm tt, + *tm = &tt; + fsec_t fsec; + int tz; + char buf[MAXDATELEN + 1]; + + timetz2tm(time, tm, &fsec, &tz); + EncodeTimeOnly(tm, fsec, true, tz, DateStyle, buf); + + result = pstrdup(buf); + PG_RETURN_CSTRING(result); +} + +/* + * timetz_recv - converts external binary format to timetz + */ +Datum +timetz_recv(PG_FUNCTION_ARGS) +{ + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + +#ifdef NOT_USED + Oid typelem = PG_GETARG_OID(1); +#endif + int32 typmod = PG_GETARG_INT32(2); + TimeTzADT *result; + + result = (TimeTzADT *) palloc(sizeof(TimeTzADT)); + + result->time = pq_getmsgint64(buf); + + if (result->time < INT64CONST(0) || result->time > USECS_PER_DAY) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("time out of range"))); + + result->zone = pq_getmsgint(buf, sizeof(result->zone)); + + /* Check for sane GMT displacement; see notes in datatype/timestamp.h */ + if (result->zone <= -TZDISP_LIMIT || result->zone >= TZDISP_LIMIT) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TIME_ZONE_DISPLACEMENT_VALUE), + errmsg("time zone displacement out of range"))); + + AdjustTimeForTypmod(&(result->time), typmod); + + PG_RETURN_TIMETZADT_P(result); +} + +/* + * timetz_send - converts timetz to binary format + */ +Datum +timetz_send(PG_FUNCTION_ARGS) +{ + TimeTzADT *time = PG_GETARG_TIMETZADT_P(0); + StringInfoData buf; + + pq_begintypsend(&buf); + pq_sendint64(&buf, time->time); + pq_sendint32(&buf, time->zone); + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} + +Datum +timetztypmodin(PG_FUNCTION_ARGS) +{ + ArrayType *ta = PG_GETARG_ARRAYTYPE_P(0); + + PG_RETURN_INT32(anytime_typmodin(true, ta)); +} + +Datum +timetztypmodout(PG_FUNCTION_ARGS) +{ + int32 typmod = PG_GETARG_INT32(0); + + PG_RETURN_CSTRING(anytime_typmodout(true, typmod)); +} + + +/* timetz2tm() + * Convert TIME WITH TIME ZONE data type to POSIX time structure. + */ +int +timetz2tm(TimeTzADT *time, struct pg_tm *tm, fsec_t *fsec, int *tzp) +{ + TimeOffset trem = time->time; + + tm->tm_hour = trem / USECS_PER_HOUR; + trem -= tm->tm_hour * USECS_PER_HOUR; + tm->tm_min = trem / USECS_PER_MINUTE; + trem -= tm->tm_min * USECS_PER_MINUTE; + tm->tm_sec = trem / USECS_PER_SEC; + *fsec = trem - tm->tm_sec * USECS_PER_SEC; + + if (tzp != NULL) + *tzp = time->zone; + + return 0; +} + +/* timetz_scale() + * Adjust time type for specified scale factor. + * Used by PostgreSQL type system to stuff columns. + */ +Datum +timetz_scale(PG_FUNCTION_ARGS) +{ + TimeTzADT *time = PG_GETARG_TIMETZADT_P(0); + int32 typmod = PG_GETARG_INT32(1); + TimeTzADT *result; + + result = (TimeTzADT *) palloc(sizeof(TimeTzADT)); + + result->time = time->time; + result->zone = time->zone; + + AdjustTimeForTypmod(&(result->time), typmod); + + PG_RETURN_TIMETZADT_P(result); +} + + +static int +timetz_cmp_internal(TimeTzADT *time1, TimeTzADT *time2) +{ + TimeOffset t1, + t2; + + /* Primary sort is by true (GMT-equivalent) time */ + t1 = time1->time + (time1->zone * USECS_PER_SEC); + t2 = time2->time + (time2->zone * USECS_PER_SEC); + + if (t1 > t2) + return 1; + if (t1 < t2) + return -1; + + /* + * If same GMT time, sort by timezone; we only want to say that two + * timetz's are equal if both the time and zone parts are equal. + */ + if (time1->zone > time2->zone) + return 1; + if (time1->zone < time2->zone) + return -1; + + return 0; +} + +Datum +timetz_eq(PG_FUNCTION_ARGS) +{ + TimeTzADT *time1 = PG_GETARG_TIMETZADT_P(0); + TimeTzADT *time2 = PG_GETARG_TIMETZADT_P(1); + + PG_RETURN_BOOL(timetz_cmp_internal(time1, time2) == 0); +} + +Datum +timetz_ne(PG_FUNCTION_ARGS) +{ + TimeTzADT *time1 = PG_GETARG_TIMETZADT_P(0); + TimeTzADT *time2 = PG_GETARG_TIMETZADT_P(1); + + PG_RETURN_BOOL(timetz_cmp_internal(time1, time2) != 0); +} + +Datum +timetz_lt(PG_FUNCTION_ARGS) +{ + TimeTzADT *time1 = PG_GETARG_TIMETZADT_P(0); + TimeTzADT *time2 = PG_GETARG_TIMETZADT_P(1); + + PG_RETURN_BOOL(timetz_cmp_internal(time1, time2) < 0); +} + +Datum +timetz_le(PG_FUNCTION_ARGS) +{ + TimeTzADT *time1 = PG_GETARG_TIMETZADT_P(0); + TimeTzADT *time2 = PG_GETARG_TIMETZADT_P(1); + + PG_RETURN_BOOL(timetz_cmp_internal(time1, time2) <= 0); +} + +Datum +timetz_gt(PG_FUNCTION_ARGS) +{ + TimeTzADT *time1 = PG_GETARG_TIMETZADT_P(0); + TimeTzADT *time2 = PG_GETARG_TIMETZADT_P(1); + + PG_RETURN_BOOL(timetz_cmp_internal(time1, time2) > 0); +} + +Datum +timetz_ge(PG_FUNCTION_ARGS) +{ + TimeTzADT *time1 = PG_GETARG_TIMETZADT_P(0); + TimeTzADT *time2 = PG_GETARG_TIMETZADT_P(1); + + PG_RETURN_BOOL(timetz_cmp_internal(time1, time2) >= 0); +} + +Datum +timetz_cmp(PG_FUNCTION_ARGS) +{ + TimeTzADT *time1 = PG_GETARG_TIMETZADT_P(0); + TimeTzADT *time2 = PG_GETARG_TIMETZADT_P(1); + + PG_RETURN_INT32(timetz_cmp_internal(time1, time2)); +} + +Datum +timetz_hash(PG_FUNCTION_ARGS) +{ + TimeTzADT *key = PG_GETARG_TIMETZADT_P(0); + uint32 thash; + + /* + * To avoid any problems with padding bytes in the struct, we figure the + * field hashes separately and XOR them. + */ + thash = DatumGetUInt32(DirectFunctionCall1(hashint8, + Int64GetDatumFast(key->time))); + thash ^= DatumGetUInt32(hash_uint32(key->zone)); + PG_RETURN_UINT32(thash); +} + +Datum +timetz_hash_extended(PG_FUNCTION_ARGS) +{ + TimeTzADT *key = PG_GETARG_TIMETZADT_P(0); + Datum seed = PG_GETARG_DATUM(1); + uint64 thash; + + /* Same approach as timetz_hash */ + thash = DatumGetUInt64(DirectFunctionCall2(hashint8extended, + Int64GetDatumFast(key->time), + seed)); + thash ^= DatumGetUInt64(hash_uint32_extended(key->zone, + DatumGetInt64(seed))); + PG_RETURN_UINT64(thash); +} + +Datum +timetz_larger(PG_FUNCTION_ARGS) +{ + TimeTzADT *time1 = PG_GETARG_TIMETZADT_P(0); + TimeTzADT *time2 = PG_GETARG_TIMETZADT_P(1); + TimeTzADT *result; + + if (timetz_cmp_internal(time1, time2) > 0) + result = time1; + else + result = time2; + PG_RETURN_TIMETZADT_P(result); +} + +Datum +timetz_smaller(PG_FUNCTION_ARGS) +{ + TimeTzADT *time1 = PG_GETARG_TIMETZADT_P(0); + TimeTzADT *time2 = PG_GETARG_TIMETZADT_P(1); + TimeTzADT *result; + + if (timetz_cmp_internal(time1, time2) < 0) + result = time1; + else + result = time2; + PG_RETURN_TIMETZADT_P(result); +} + +/* timetz_pl_interval() + * Add interval to timetz. + */ +Datum +timetz_pl_interval(PG_FUNCTION_ARGS) +{ + TimeTzADT *time = PG_GETARG_TIMETZADT_P(0); + Interval *span = PG_GETARG_INTERVAL_P(1); + TimeTzADT *result; + + result = (TimeTzADT *) palloc(sizeof(TimeTzADT)); + + result->time = time->time + span->time; + result->time -= result->time / USECS_PER_DAY * USECS_PER_DAY; + if (result->time < INT64CONST(0)) + result->time += USECS_PER_DAY; + + result->zone = time->zone; + + PG_RETURN_TIMETZADT_P(result); +} + +/* timetz_mi_interval() + * Subtract interval from timetz. + */ +Datum +timetz_mi_interval(PG_FUNCTION_ARGS) +{ + TimeTzADT *time = PG_GETARG_TIMETZADT_P(0); + Interval *span = PG_GETARG_INTERVAL_P(1); + TimeTzADT *result; + + result = (TimeTzADT *) palloc(sizeof(TimeTzADT)); + + result->time = time->time - span->time; + result->time -= result->time / USECS_PER_DAY * USECS_PER_DAY; + if (result->time < INT64CONST(0)) + result->time += USECS_PER_DAY; + + result->zone = time->zone; + + PG_RETURN_TIMETZADT_P(result); +} + +/* + * in_range support function for timetz. + */ +Datum +in_range_timetz_interval(PG_FUNCTION_ARGS) +{ + TimeTzADT *val = PG_GETARG_TIMETZADT_P(0); + TimeTzADT *base = PG_GETARG_TIMETZADT_P(1); + Interval *offset = PG_GETARG_INTERVAL_P(2); + bool sub = PG_GETARG_BOOL(3); + bool less = PG_GETARG_BOOL(4); + TimeTzADT sum; + + /* + * Like timetz_pl_interval/timetz_mi_interval, we disregard the month and + * day fields of the offset. So our test for negative should too. + */ + if (offset->time < 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE), + errmsg("invalid preceding or following size in window function"))); + + /* + * We can't use timetz_pl_interval/timetz_mi_interval here, because their + * wraparound behavior would give wrong (or at least undesirable) answers. + * Fortunately the equivalent non-wrapping behavior is trivial, especially + * since we don't worry about integer overflow. + */ + if (sub) + sum.time = base->time - offset->time; + else + sum.time = base->time + offset->time; + sum.zone = base->zone; + + if (less) + PG_RETURN_BOOL(timetz_cmp_internal(val, &sum) <= 0); + else + PG_RETURN_BOOL(timetz_cmp_internal(val, &sum) >= 0); +} + +/* overlaps_timetz() --- implements the SQL OVERLAPS operator. + * + * Algorithm is per SQL spec. This is much harder than you'd think + * because the spec requires us to deliver a non-null answer in some cases + * where some of the inputs are null. + */ +Datum +overlaps_timetz(PG_FUNCTION_ARGS) +{ + /* + * The arguments are TimeTzADT *, but we leave them as generic Datums for + * convenience of notation --- and to avoid dereferencing nulls. + */ + Datum ts1 = PG_GETARG_DATUM(0); + Datum te1 = PG_GETARG_DATUM(1); + Datum ts2 = PG_GETARG_DATUM(2); + Datum te2 = PG_GETARG_DATUM(3); + bool ts1IsNull = PG_ARGISNULL(0); + bool te1IsNull = PG_ARGISNULL(1); + bool ts2IsNull = PG_ARGISNULL(2); + bool te2IsNull = PG_ARGISNULL(3); + +#define TIMETZ_GT(t1,t2) \ + DatumGetBool(DirectFunctionCall2(timetz_gt,t1,t2)) +#define TIMETZ_LT(t1,t2) \ + DatumGetBool(DirectFunctionCall2(timetz_lt,t1,t2)) + + /* + * If both endpoints of interval 1 are null, the result is null (unknown). + * If just one endpoint is null, take ts1 as the non-null one. Otherwise, + * take ts1 as the lesser endpoint. + */ + if (ts1IsNull) + { + if (te1IsNull) + PG_RETURN_NULL(); + /* swap null for non-null */ + ts1 = te1; + te1IsNull = true; + } + else if (!te1IsNull) + { + if (TIMETZ_GT(ts1, te1)) + { + Datum tt = ts1; + + ts1 = te1; + te1 = tt; + } + } + + /* Likewise for interval 2. */ + if (ts2IsNull) + { + if (te2IsNull) + PG_RETURN_NULL(); + /* swap null for non-null */ + ts2 = te2; + te2IsNull = true; + } + else if (!te2IsNull) + { + if (TIMETZ_GT(ts2, te2)) + { + Datum tt = ts2; + + ts2 = te2; + te2 = tt; + } + } + + /* + * At this point neither ts1 nor ts2 is null, so we can consider three + * cases: ts1 > ts2, ts1 < ts2, ts1 = ts2 + */ + if (TIMETZ_GT(ts1, ts2)) + { + /* + * This case is ts1 < te2 OR te1 < te2, which may look redundant but + * in the presence of nulls it's not quite completely so. + */ + if (te2IsNull) + PG_RETURN_NULL(); + if (TIMETZ_LT(ts1, te2)) + PG_RETURN_BOOL(true); + if (te1IsNull) + PG_RETURN_NULL(); + + /* + * If te1 is not null then we had ts1 <= te1 above, and we just found + * ts1 >= te2, hence te1 >= te2. + */ + PG_RETURN_BOOL(false); + } + else if (TIMETZ_LT(ts1, ts2)) + { + /* This case is ts2 < te1 OR te2 < te1 */ + if (te1IsNull) + PG_RETURN_NULL(); + if (TIMETZ_LT(ts2, te1)) + PG_RETURN_BOOL(true); + if (te2IsNull) + PG_RETURN_NULL(); + + /* + * If te2 is not null then we had ts2 <= te2 above, and we just found + * ts2 >= te1, hence te2 >= te1. + */ + PG_RETURN_BOOL(false); + } + else + { + /* + * For ts1 = ts2 the spec says te1 <> te2 OR te1 = te2, which is a + * rather silly way of saying "true if both are nonnull, else null". + */ + if (te1IsNull || te2IsNull) + PG_RETURN_NULL(); + PG_RETURN_BOOL(true); + } + +#undef TIMETZ_GT +#undef TIMETZ_LT +} + + +Datum +timetz_time(PG_FUNCTION_ARGS) +{ + TimeTzADT *timetz = PG_GETARG_TIMETZADT_P(0); + TimeADT result; + + /* swallow the time zone and just return the time */ + result = timetz->time; + + PG_RETURN_TIMEADT(result); +} + + +Datum +time_timetz(PG_FUNCTION_ARGS) +{ + TimeADT time = PG_GETARG_TIMEADT(0); + TimeTzADT *result; + struct pg_tm tt, + *tm = &tt; + fsec_t fsec; + int tz; + + GetCurrentDateTime(tm); + time2tm(time, tm, &fsec); + tz = DetermineTimeZoneOffset(tm, session_timezone); + + result = (TimeTzADT *) palloc(sizeof(TimeTzADT)); + + result->time = time; + result->zone = tz; + + PG_RETURN_TIMETZADT_P(result); +} + + +/* timestamptz_timetz() + * Convert timestamp to timetz data type. + */ +Datum +timestamptz_timetz(PG_FUNCTION_ARGS) +{ + TimestampTz timestamp = PG_GETARG_TIMESTAMP(0); + TimeTzADT *result; + struct pg_tm tt, + *tm = &tt; + int tz; + fsec_t fsec; + + if (TIMESTAMP_NOT_FINITE(timestamp)) + PG_RETURN_NULL(); + + if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + + result = (TimeTzADT *) palloc(sizeof(TimeTzADT)); + + tm2timetz(tm, fsec, tz, result); + + PG_RETURN_TIMETZADT_P(result); +} + + +/* datetimetz_timestamptz() + * Convert date and timetz to timestamp with time zone data type. + * Timestamp is stored in GMT, so add the time zone + * stored with the timetz to the result. + * - thomas 2000-03-10 + */ +Datum +datetimetz_timestamptz(PG_FUNCTION_ARGS) +{ + DateADT date = PG_GETARG_DATEADT(0); + TimeTzADT *time = PG_GETARG_TIMETZADT_P(1); + TimestampTz result; + + if (DATE_IS_NOBEGIN(date)) + TIMESTAMP_NOBEGIN(result); + else if (DATE_IS_NOEND(date)) + TIMESTAMP_NOEND(result); + else + { + /* + * Date's range is wider than timestamp's, so check for boundaries. + * Since dates have the same minimum values as timestamps, only upper + * boundary need be checked for overflow. + */ + if (date >= (TIMESTAMP_END_JULIAN - POSTGRES_EPOCH_JDATE)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("date out of range for timestamp"))); + result = date * USECS_PER_DAY + time->time + time->zone * USECS_PER_SEC; + + /* + * Since it is possible to go beyond allowed timestamptz range because + * of time zone, check for allowed timestamp range after adding tz. + */ + if (!IS_VALID_TIMESTAMP(result)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("date out of range for timestamp"))); + } + + PG_RETURN_TIMESTAMP(result); +} + + +/* timetz_part() and extract_timetz() + * Extract specified field from time type. + */ +static Datum +timetz_part_common(PG_FUNCTION_ARGS, bool retnumeric) +{ + text *units = PG_GETARG_TEXT_PP(0); + TimeTzADT *time = PG_GETARG_TIMETZADT_P(1); + int64 intresult; + int type, + val; + char *lowunits; + + lowunits = downcase_truncate_identifier(VARDATA_ANY(units), + VARSIZE_ANY_EXHDR(units), + false); + + type = DecodeUnits(0, lowunits, &val); + if (type == UNKNOWN_FIELD) + type = DecodeSpecial(0, lowunits, &val); + + if (type == UNITS) + { + int tz; + fsec_t fsec; + struct pg_tm tt, + *tm = &tt; + + timetz2tm(time, tm, &fsec, &tz); + + switch (val) + { + case DTK_TZ: + intresult = -tz; + break; + + case DTK_TZ_MINUTE: + intresult = (-tz / SECS_PER_MINUTE) % MINS_PER_HOUR; + break; + + case DTK_TZ_HOUR: + intresult = -tz / SECS_PER_HOUR; + break; + + case DTK_MICROSEC: + intresult = tm->tm_sec * INT64CONST(1000000) + fsec; + break; + + case DTK_MILLISEC: + if (retnumeric) + /*--- + * tm->tm_sec * 1000 + fsec / 1000 + * = (tm->tm_sec * 1'000'000 + fsec) / 1000 + */ + PG_RETURN_NUMERIC(int64_div_fast_to_numeric(tm->tm_sec * INT64CONST(1000000) + fsec, 3)); + else + PG_RETURN_FLOAT8(tm->tm_sec * 1000.0 + fsec / 1000.0); + break; + + case DTK_SECOND: + if (retnumeric) + /*--- + * tm->tm_sec + fsec / 1'000'000 + * = (tm->tm_sec * 1'000'000 + fsec) / 1'000'000 + */ + PG_RETURN_NUMERIC(int64_div_fast_to_numeric(tm->tm_sec * INT64CONST(1000000) + fsec, 6)); + else + PG_RETURN_FLOAT8(tm->tm_sec + fsec / 1000000.0); + break; + + case DTK_MINUTE: + intresult = tm->tm_min; + break; + + case DTK_HOUR: + intresult = tm->tm_hour; + break; + + case DTK_DAY: + case DTK_MONTH: + case DTK_QUARTER: + case DTK_YEAR: + case DTK_DECADE: + case DTK_CENTURY: + case DTK_MILLENNIUM: + default: + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("unit \"%s\" not supported for type %s", + lowunits, format_type_be(TIMETZOID)))); + intresult = 0; + } + } + else if (type == RESERV && val == DTK_EPOCH) + { + if (retnumeric) + /*--- + * time->time / 1'000'000 + time->zone + * = (time->time + time->zone * 1'000'000) / 1'000'000 + */ + PG_RETURN_NUMERIC(int64_div_fast_to_numeric(time->time + time->zone * INT64CONST(1000000), 6)); + else + PG_RETURN_FLOAT8(time->time / 1000000.0 + time->zone); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unit \"%s\" not recognized for type %s", + lowunits, format_type_be(TIMETZOID)))); + intresult = 0; + } + + if (retnumeric) + PG_RETURN_NUMERIC(int64_to_numeric(intresult)); + else + PG_RETURN_FLOAT8(intresult); +} + + +Datum +timetz_part(PG_FUNCTION_ARGS) +{ + return timetz_part_common(fcinfo, false); +} + +Datum +extract_timetz(PG_FUNCTION_ARGS) +{ + return timetz_part_common(fcinfo, true); +} + +/* timetz_zone() + * Encode time with time zone type with specified time zone. + * Applies DST rules as of the transaction start time. + */ +Datum +timetz_zone(PG_FUNCTION_ARGS) +{ + text *zone = PG_GETARG_TEXT_PP(0); + TimeTzADT *t = PG_GETARG_TIMETZADT_P(1); + TimeTzADT *result; + int tz; + char tzname[TZ_STRLEN_MAX + 1]; + int type, + val; + pg_tz *tzp; + + /* + * Look up the requested timezone. + */ + text_to_cstring_buffer(zone, tzname, sizeof(tzname)); + + type = DecodeTimezoneName(tzname, &val, &tzp); + + if (type == TZNAME_FIXED_OFFSET) + { + /* fixed-offset abbreviation */ + tz = -val; + } + else if (type == TZNAME_DYNTZ) + { + /* dynamic-offset abbreviation, resolve using transaction start time */ + TimestampTz now = GetCurrentTransactionStartTimestamp(); + int isdst; + + tz = DetermineTimeZoneAbbrevOffsetTS(now, tzname, tzp, &isdst); + } + else + { + /* Get the offset-from-GMT that is valid now for the zone name */ + TimestampTz now = GetCurrentTransactionStartTimestamp(); + struct pg_tm tm; + fsec_t fsec; + + if (timestamp2tm(now, &tz, &tm, &fsec, NULL, tzp) != 0) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + } + + result = (TimeTzADT *) palloc(sizeof(TimeTzADT)); + + result->time = t->time + (t->zone - tz) * USECS_PER_SEC; + /* C99 modulo has the wrong sign convention for negative input */ + while (result->time < INT64CONST(0)) + result->time += USECS_PER_DAY; + if (result->time >= USECS_PER_DAY) + result->time %= USECS_PER_DAY; + + result->zone = tz; + + PG_RETURN_TIMETZADT_P(result); +} + +/* timetz_izone() + * Encode time with time zone type with specified time interval as time zone. + */ +Datum +timetz_izone(PG_FUNCTION_ARGS) +{ + Interval *zone = PG_GETARG_INTERVAL_P(0); + TimeTzADT *time = PG_GETARG_TIMETZADT_P(1); + TimeTzADT *result; + int tz; + + if (zone->month != 0 || zone->day != 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("interval time zone \"%s\" must not include months or days", + DatumGetCString(DirectFunctionCall1(interval_out, + PointerGetDatum(zone)))))); + + tz = -(zone->time / USECS_PER_SEC); + + result = (TimeTzADT *) palloc(sizeof(TimeTzADT)); + + result->time = time->time + (time->zone - tz) * USECS_PER_SEC; + /* C99 modulo has the wrong sign convention for negative input */ + while (result->time < INT64CONST(0)) + result->time += USECS_PER_DAY; + if (result->time >= USECS_PER_DAY) + result->time %= USECS_PER_DAY; + + result->zone = tz; + + PG_RETURN_TIMETZADT_P(result); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/datetime.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/datetime.c new file mode 100644 index 00000000000..8b0c8150eb7 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/datetime.c @@ -0,0 +1,5057 @@ +/*------------------------------------------------------------------------- + * + * datetime.c + * Support functions for date/time types. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/adt/datetime.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <ctype.h> +#include <limits.h> +#include <math.h> + +#include "access/htup_details.h" +#include "access/xact.h" +#include "catalog/pg_type.h" +#include "common/int.h" +#include "common/string.h" +#include "funcapi.h" +#include "miscadmin.h" +#include "nodes/nodeFuncs.h" +#include "parser/scansup.h" +#include "utils/builtins.h" +#include "utils/date.h" +#include "utils/datetime.h" +#include "utils/guc.h" +#include "utils/memutils.h" +#include "utils/tzparser.h" + +static int DecodeNumber(int flen, char *str, bool haveTextMonth, + int fmask, int *tmask, + struct pg_tm *tm, fsec_t *fsec, bool *is2digits); +static int DecodeNumberField(int len, char *str, + int fmask, int *tmask, + struct pg_tm *tm, fsec_t *fsec, bool *is2digits); +static int DecodeTimeCommon(char *str, int fmask, int range, + int *tmask, struct pg_itm *itm); +static int DecodeTime(char *str, int fmask, int range, + int *tmask, struct pg_tm *tm, fsec_t *fsec); +static int DecodeTimeForInterval(char *str, int fmask, int range, + int *tmask, struct pg_itm_in *itm_in); +static const datetkn *datebsearch(const char *key, const datetkn *base, int nel); +static int DecodeDate(char *str, int fmask, int *tmask, bool *is2digits, + struct pg_tm *tm); +static char *AppendSeconds(char *cp, int sec, fsec_t fsec, + int precision, bool fillzeros); +static bool int64_multiply_add(int64 val, int64 multiplier, int64 *sum); +static bool AdjustFractMicroseconds(double frac, int64 scale, + struct pg_itm_in *itm_in); +static bool AdjustFractDays(double frac, int scale, + struct pg_itm_in *itm_in); +static bool AdjustFractYears(double frac, int scale, + struct pg_itm_in *itm_in); +static bool AdjustMicroseconds(int64 val, double fval, int64 scale, + struct pg_itm_in *itm_in); +static bool AdjustDays(int64 val, int scale, + struct pg_itm_in *itm_in); +static bool AdjustMonths(int64 val, struct pg_itm_in *itm_in); +static bool AdjustYears(int64 val, int scale, + struct pg_itm_in *itm_in); +static int DetermineTimeZoneOffsetInternal(struct pg_tm *tm, pg_tz *tzp, + pg_time_t *tp); +static bool DetermineTimeZoneAbbrevOffsetInternal(pg_time_t t, + const char *abbr, pg_tz *tzp, + int *offset, int *isdst); +static pg_tz *FetchDynamicTimeZone(TimeZoneAbbrevTable *tbl, const datetkn *tp, + DateTimeErrorExtra *extra); + + +const int day_tab[2][13] = +{ + {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 0}, + {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 0} +}; + +const char *const months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", +"Jul", "Aug", "Sep", "Oct", "Nov", "Dec", NULL}; + +const char *const days[] = {"Sunday", "Monday", "Tuesday", "Wednesday", +"Thursday", "Friday", "Saturday", NULL}; + + +/***************************************************************************** + * PRIVATE ROUTINES * + *****************************************************************************/ + +/* + * datetktbl holds date/time keywords. + * + * Note that this table must be strictly alphabetically ordered to allow an + * O(ln(N)) search algorithm to be used. + * + * The token field must be NUL-terminated; we truncate entries to TOKMAXLEN + * characters to fit. + * + * The static table contains no TZ, DTZ, or DYNTZ entries; rather those + * are loaded from configuration files and stored in zoneabbrevtbl, whose + * abbrevs[] field has the same format as the static datetktbl. + */ +static const datetkn datetktbl[] = { + /* token, type, value */ + {"+infinity", RESERV, DTK_LATE}, /* same as "infinity" */ + {EARLY, RESERV, DTK_EARLY}, /* "-infinity" reserved for "early time" */ + {DA_D, ADBC, AD}, /* "ad" for years > 0 */ + {"allballs", RESERV, DTK_ZULU}, /* 00:00:00 */ + {"am", AMPM, AM}, + {"apr", MONTH, 4}, + {"april", MONTH, 4}, + {"at", IGNORE_DTF, 0}, /* "at" (throwaway) */ + {"aug", MONTH, 8}, + {"august", MONTH, 8}, + {DB_C, ADBC, BC}, /* "bc" for years <= 0 */ + {"d", UNITS, DTK_DAY}, /* "day of month" for ISO input */ + {"dec", MONTH, 12}, + {"december", MONTH, 12}, + {"dow", UNITS, DTK_DOW}, /* day of week */ + {"doy", UNITS, DTK_DOY}, /* day of year */ + {"dst", DTZMOD, SECS_PER_HOUR}, + {EPOCH, RESERV, DTK_EPOCH}, /* "epoch" reserved for system epoch time */ + {"feb", MONTH, 2}, + {"february", MONTH, 2}, + {"fri", DOW, 5}, + {"friday", DOW, 5}, + {"h", UNITS, DTK_HOUR}, /* "hour" */ + {LATE, RESERV, DTK_LATE}, /* "infinity" reserved for "late time" */ + {"isodow", UNITS, DTK_ISODOW}, /* ISO day of week, Sunday == 7 */ + {"isoyear", UNITS, DTK_ISOYEAR}, /* year in terms of the ISO week date */ + {"j", UNITS, DTK_JULIAN}, + {"jan", MONTH, 1}, + {"january", MONTH, 1}, + {"jd", UNITS, DTK_JULIAN}, + {"jul", MONTH, 7}, + {"julian", UNITS, DTK_JULIAN}, + {"july", MONTH, 7}, + {"jun", MONTH, 6}, + {"june", MONTH, 6}, + {"m", UNITS, DTK_MONTH}, /* "month" for ISO input */ + {"mar", MONTH, 3}, + {"march", MONTH, 3}, + {"may", MONTH, 5}, + {"mm", UNITS, DTK_MINUTE}, /* "minute" for ISO input */ + {"mon", DOW, 1}, + {"monday", DOW, 1}, + {"nov", MONTH, 11}, + {"november", MONTH, 11}, + {NOW, RESERV, DTK_NOW}, /* current transaction time */ + {"oct", MONTH, 10}, + {"october", MONTH, 10}, + {"on", IGNORE_DTF, 0}, /* "on" (throwaway) */ + {"pm", AMPM, PM}, + {"s", UNITS, DTK_SECOND}, /* "seconds" for ISO input */ + {"sat", DOW, 6}, + {"saturday", DOW, 6}, + {"sep", MONTH, 9}, + {"sept", MONTH, 9}, + {"september", MONTH, 9}, + {"sun", DOW, 0}, + {"sunday", DOW, 0}, + {"t", ISOTIME, DTK_TIME}, /* Filler for ISO time fields */ + {"thu", DOW, 4}, + {"thur", DOW, 4}, + {"thurs", DOW, 4}, + {"thursday", DOW, 4}, + {TODAY, RESERV, DTK_TODAY}, /* midnight */ + {TOMORROW, RESERV, DTK_TOMORROW}, /* tomorrow midnight */ + {"tue", DOW, 2}, + {"tues", DOW, 2}, + {"tuesday", DOW, 2}, + {"wed", DOW, 3}, + {"wednesday", DOW, 3}, + {"weds", DOW, 3}, + {"y", UNITS, DTK_YEAR}, /* "year" for ISO input */ + {YESTERDAY, RESERV, DTK_YESTERDAY} /* yesterday midnight */ +}; + +static const int szdatetktbl = sizeof datetktbl / sizeof datetktbl[0]; + +/* + * deltatktbl: same format as datetktbl, but holds keywords used to represent + * time units (eg, for intervals, and for EXTRACT). + */ +static const datetkn deltatktbl[] = { + /* token, type, value */ + {"@", IGNORE_DTF, 0}, /* postgres relative prefix */ + {DAGO, AGO, 0}, /* "ago" indicates negative time offset */ + {"c", UNITS, DTK_CENTURY}, /* "century" relative */ + {"cent", UNITS, DTK_CENTURY}, /* "century" relative */ + {"centuries", UNITS, DTK_CENTURY}, /* "centuries" relative */ + {DCENTURY, UNITS, DTK_CENTURY}, /* "century" relative */ + {"d", UNITS, DTK_DAY}, /* "day" relative */ + {DDAY, UNITS, DTK_DAY}, /* "day" relative */ + {"days", UNITS, DTK_DAY}, /* "days" relative */ + {"dec", UNITS, DTK_DECADE}, /* "decade" relative */ + {DDECADE, UNITS, DTK_DECADE}, /* "decade" relative */ + {"decades", UNITS, DTK_DECADE}, /* "decades" relative */ + {"decs", UNITS, DTK_DECADE}, /* "decades" relative */ + {"h", UNITS, DTK_HOUR}, /* "hour" relative */ + {DHOUR, UNITS, DTK_HOUR}, /* "hour" relative */ + {"hours", UNITS, DTK_HOUR}, /* "hours" relative */ + {"hr", UNITS, DTK_HOUR}, /* "hour" relative */ + {"hrs", UNITS, DTK_HOUR}, /* "hours" relative */ + {"m", UNITS, DTK_MINUTE}, /* "minute" relative */ + {"microsecon", UNITS, DTK_MICROSEC}, /* "microsecond" relative */ + {"mil", UNITS, DTK_MILLENNIUM}, /* "millennium" relative */ + {"millennia", UNITS, DTK_MILLENNIUM}, /* "millennia" relative */ + {DMILLENNIUM, UNITS, DTK_MILLENNIUM}, /* "millennium" relative */ + {"millisecon", UNITS, DTK_MILLISEC}, /* relative */ + {"mils", UNITS, DTK_MILLENNIUM}, /* "millennia" relative */ + {"min", UNITS, DTK_MINUTE}, /* "minute" relative */ + {"mins", UNITS, DTK_MINUTE}, /* "minutes" relative */ + {DMINUTE, UNITS, DTK_MINUTE}, /* "minute" relative */ + {"minutes", UNITS, DTK_MINUTE}, /* "minutes" relative */ + {"mon", UNITS, DTK_MONTH}, /* "months" relative */ + {"mons", UNITS, DTK_MONTH}, /* "months" relative */ + {DMONTH, UNITS, DTK_MONTH}, /* "month" relative */ + {"months", UNITS, DTK_MONTH}, + {"ms", UNITS, DTK_MILLISEC}, + {"msec", UNITS, DTK_MILLISEC}, + {DMILLISEC, UNITS, DTK_MILLISEC}, + {"mseconds", UNITS, DTK_MILLISEC}, + {"msecs", UNITS, DTK_MILLISEC}, + {"qtr", UNITS, DTK_QUARTER}, /* "quarter" relative */ + {DQUARTER, UNITS, DTK_QUARTER}, /* "quarter" relative */ + {"s", UNITS, DTK_SECOND}, + {"sec", UNITS, DTK_SECOND}, + {DSECOND, UNITS, DTK_SECOND}, + {"seconds", UNITS, DTK_SECOND}, + {"secs", UNITS, DTK_SECOND}, + {DTIMEZONE, UNITS, DTK_TZ}, /* "timezone" time offset */ + {"timezone_h", UNITS, DTK_TZ_HOUR}, /* timezone hour units */ + {"timezone_m", UNITS, DTK_TZ_MINUTE}, /* timezone minutes units */ + {"us", UNITS, DTK_MICROSEC}, /* "microsecond" relative */ + {"usec", UNITS, DTK_MICROSEC}, /* "microsecond" relative */ + {DMICROSEC, UNITS, DTK_MICROSEC}, /* "microsecond" relative */ + {"useconds", UNITS, DTK_MICROSEC}, /* "microseconds" relative */ + {"usecs", UNITS, DTK_MICROSEC}, /* "microseconds" relative */ + {"w", UNITS, DTK_WEEK}, /* "week" relative */ + {DWEEK, UNITS, DTK_WEEK}, /* "week" relative */ + {"weeks", UNITS, DTK_WEEK}, /* "weeks" relative */ + {"y", UNITS, DTK_YEAR}, /* "year" relative */ + {DYEAR, UNITS, DTK_YEAR}, /* "year" relative */ + {"years", UNITS, DTK_YEAR}, /* "years" relative */ + {"yr", UNITS, DTK_YEAR}, /* "year" relative */ + {"yrs", UNITS, DTK_YEAR} /* "years" relative */ +}; + +static const int szdeltatktbl = sizeof deltatktbl / sizeof deltatktbl[0]; + +static __thread TimeZoneAbbrevTable *zoneabbrevtbl = NULL; + +/* Caches of recent lookup results in the above tables */ + +static __thread const datetkn *datecache[MAXDATEFIELDS] = {NULL}; + +static __thread const datetkn *deltacache[MAXDATEFIELDS] = {NULL}; + +static __thread const datetkn *abbrevcache[MAXDATEFIELDS] = {NULL}; + + +/* + * Calendar time to Julian date conversions. + * Julian date is commonly used in astronomical applications, + * since it is numerically accurate and computationally simple. + * The algorithms here will accurately convert between Julian day + * and calendar date for all non-negative Julian days + * (i.e. from Nov 24, -4713 on). + * + * Rewritten to eliminate overflow problems. This now allows the + * routines to work correctly for all Julian day counts from + * 0 to 2147483647 (Nov 24, -4713 to Jun 3, 5874898) assuming + * a 32-bit integer. Longer types should also work to the limits + * of their precision. + * + * Actually, date2j() will work sanely, in the sense of producing + * valid negative Julian dates, significantly before Nov 24, -4713. + * We rely on it to do so back to Nov 1, -4713; see IS_VALID_JULIAN() + * and associated commentary in timestamp.h. + */ + +int +date2j(int year, int month, int day) +{ + int julian; + int century; + + if (month > 2) + { + month += 1; + year += 4800; + } + else + { + month += 13; + year += 4799; + } + + century = year / 100; + julian = year * 365 - 32167; + julian += year / 4 - century + century / 4; + julian += 7834 * month / 256 + day; + + return julian; +} /* date2j() */ + +void +j2date(int jd, int *year, int *month, int *day) +{ + unsigned int julian; + unsigned int quad; + unsigned int extra; + int y; + + julian = jd; + julian += 32044; + quad = julian / 146097; + extra = (julian - quad * 146097) * 4 + 3; + julian += 60 + quad * 3 + extra / 146097; + quad = julian / 1461; + julian -= quad * 1461; + y = julian * 4 / 1461; + julian = ((y != 0) ? ((julian + 305) % 365) : ((julian + 306) % 366)) + + 123; + y += quad * 4; + *year = y - 4800; + quad = julian * 2141 / 65536; + *day = julian - 7834 * quad / 256; + *month = (quad + 10) % MONTHS_PER_YEAR + 1; +} /* j2date() */ + + +/* + * j2day - convert Julian date to day-of-week (0..6 == Sun..Sat) + * + * Note: various places use the locution j2day(date - 1) to produce a + * result according to the convention 0..6 = Mon..Sun. This is a bit of + * a crock, but will work as long as the computation here is just a modulo. + */ +int +j2day(int date) +{ + date += 1; + date %= 7; + /* Cope if division truncates towards zero, as it probably does */ + if (date < 0) + date += 7; + + return date; +} /* j2day() */ + + +/* + * GetCurrentDateTime() + * + * Get the transaction start time ("now()") broken down as a struct pg_tm, + * converted according to the session timezone setting. + * + * This is just a convenience wrapper for GetCurrentTimeUsec, to cover the + * case where caller doesn't need either fractional seconds or tz offset. + */ +void +GetCurrentDateTime(struct pg_tm *tm) +{ + fsec_t fsec; + + GetCurrentTimeUsec(tm, &fsec, NULL); +} + +/* + * GetCurrentTimeUsec() + * + * Get the transaction start time ("now()") broken down as a struct pg_tm, + * including fractional seconds and timezone offset. The time is converted + * according to the session timezone setting. + * + * Callers may pass tzp = NULL if they don't need the offset, but this does + * not affect the conversion behavior (unlike timestamp2tm()). + * + * Internally, we cache the result, since this could be called many times + * in a transaction, within which now() doesn't change. + */ +void +GetCurrentTimeUsec(struct pg_tm *tm, fsec_t *fsec, int *tzp) +{ + TimestampTz cur_ts = GetCurrentTransactionStartTimestamp(); + + /* + * The cache key must include both current time and current timezone. By + * representing the timezone by just a pointer, we're assuming that + * distinct timezone settings could never have the same pointer value. + * This is true by virtue of the hashtable used inside pg_tzset(); + * however, it might need another look if we ever allow entries in that + * hash to be recycled. + */ + static __thread TimestampTz cache_ts = 0; + static __thread pg_tz *cache_timezone = NULL; + static __thread struct pg_tm cache_tm; + static __thread fsec_t cache_fsec; + static __thread int cache_tz; + + if (cur_ts != cache_ts || session_timezone != cache_timezone) + { + /* + * Make sure cache is marked invalid in case of error after partial + * update within timestamp2tm. + */ + cache_timezone = NULL; + + /* + * Perform the computation, storing results into cache. We do not + * really expect any error here, since current time surely ought to be + * within range, but check just for sanity's sake. + */ + if (timestamp2tm(cur_ts, &cache_tz, &cache_tm, &cache_fsec, + NULL, session_timezone) != 0) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + + /* OK, so mark the cache valid. */ + cache_ts = cur_ts; + cache_timezone = session_timezone; + } + + *tm = cache_tm; + *fsec = cache_fsec; + if (tzp != NULL) + *tzp = cache_tz; +} + + +/* + * Append seconds and fractional seconds (if any) at *cp. + * + * precision is the max number of fraction digits, fillzeros says to + * pad to two integral-seconds digits. + * + * Returns a pointer to the new end of string. No NUL terminator is put + * there; callers are responsible for NUL terminating str themselves. + * + * Note that any sign is stripped from the input sec and fsec values. + */ +static char * +AppendSeconds(char *cp, int sec, fsec_t fsec, int precision, bool fillzeros) +{ + Assert(precision >= 0); + + if (fillzeros) + cp = pg_ultostr_zeropad(cp, abs(sec), 2); + else + cp = pg_ultostr(cp, abs(sec)); + + /* fsec_t is just an int32 */ + if (fsec != 0) + { + int32 value = abs(fsec); + char *end = &cp[precision + 1]; + bool gotnonzero = false; + + *cp++ = '.'; + + /* + * Append the fractional seconds part. Note that we don't want any + * trailing zeros here, so since we're building the number in reverse + * we'll skip appending zeros until we've output a non-zero digit. + */ + while (precision--) + { + int32 oldval = value; + int32 remainder; + + value /= 10; + remainder = oldval - value * 10; + + /* check if we got a non-zero */ + if (remainder) + gotnonzero = true; + + if (gotnonzero) + cp[precision] = '0' + remainder; + else + end = &cp[precision]; + } + + /* + * If we still have a non-zero value then precision must have not been + * enough to print the number. We punt the problem to pg_ultostr(), + * which will generate a correct answer in the minimum valid width. + */ + if (value) + return pg_ultostr(cp, abs(fsec)); + + return end; + } + else + return cp; +} + + +/* + * Variant of above that's specialized to timestamp case. + * + * Returns a pointer to the new end of string. No NUL terminator is put + * there; callers are responsible for NUL terminating str themselves. + */ +static char * +AppendTimestampSeconds(char *cp, struct pg_tm *tm, fsec_t fsec) +{ + return AppendSeconds(cp, tm->tm_sec, fsec, MAX_TIMESTAMP_PRECISION, true); +} + + +/* + * Add val * multiplier to *sum. + * Returns true if successful, false on overflow. + */ +static bool +int64_multiply_add(int64 val, int64 multiplier, int64 *sum) +{ + int64 product; + + if (pg_mul_s64_overflow(val, multiplier, &product) || + pg_add_s64_overflow(*sum, product, sum)) + return false; + return true; +} + +/* + * Multiply frac by scale (to produce microseconds) and add to itm_in->tm_usec. + * Returns true if successful, false if itm_in overflows. + */ +static bool +AdjustFractMicroseconds(double frac, int64 scale, + struct pg_itm_in *itm_in) +{ + int64 usec; + + /* Fast path for common case */ + if (frac == 0) + return true; + + /* + * We assume the input frac has abs value less than 1, so overflow of frac + * or usec is not an issue for interesting values of scale. + */ + frac *= scale; + usec = (int64) frac; + + /* Round off any fractional microsecond */ + frac -= usec; + if (frac > 0.5) + usec++; + else if (frac < -0.5) + usec--; + + return !pg_add_s64_overflow(itm_in->tm_usec, usec, &itm_in->tm_usec); +} + +/* + * Multiply frac by scale (to produce days). Add the integral part of the + * result to itm_in->tm_mday, the fractional part to itm_in->tm_usec. + * Returns true if successful, false if itm_in overflows. + */ +static bool +AdjustFractDays(double frac, int scale, + struct pg_itm_in *itm_in) +{ + int extra_days; + + /* Fast path for common case */ + if (frac == 0) + return true; + + /* + * We assume the input frac has abs value less than 1, so overflow of frac + * or extra_days is not an issue. + */ + frac *= scale; + extra_days = (int) frac; + + /* ... but this could overflow, if tm_mday is already nonzero */ + if (pg_add_s32_overflow(itm_in->tm_mday, extra_days, &itm_in->tm_mday)) + return false; + + /* Handle any fractional day */ + frac -= extra_days; + return AdjustFractMicroseconds(frac, USECS_PER_DAY, itm_in); +} + +/* + * Multiply frac by scale (to produce years), then further scale up to months. + * Add the integral part of the result to itm_in->tm_mon, discarding any + * fractional part. + * Returns true if successful, false if itm_in overflows. + */ +static bool +AdjustFractYears(double frac, int scale, + struct pg_itm_in *itm_in) +{ + /* + * As above, we assume abs(frac) < 1, so this can't overflow for any + * interesting value of scale. + */ + int extra_months = (int) rint(frac * scale * MONTHS_PER_YEAR); + + return !pg_add_s32_overflow(itm_in->tm_mon, extra_months, &itm_in->tm_mon); +} + +/* + * Add (val + fval) * scale to itm_in->tm_usec. + * Returns true if successful, false if itm_in overflows. + */ +static bool +AdjustMicroseconds(int64 val, double fval, int64 scale, + struct pg_itm_in *itm_in) +{ + /* Handle the integer part */ + if (!int64_multiply_add(val, scale, &itm_in->tm_usec)) + return false; + /* Handle the float part */ + return AdjustFractMicroseconds(fval, scale, itm_in); +} + +/* + * Multiply val by scale (to produce days) and add to itm_in->tm_mday. + * Returns true if successful, false if itm_in overflows. + */ +static bool +AdjustDays(int64 val, int scale, struct pg_itm_in *itm_in) +{ + int days; + + if (val < INT_MIN || val > INT_MAX) + return false; + return !pg_mul_s32_overflow((int32) val, scale, &days) && + !pg_add_s32_overflow(itm_in->tm_mday, days, &itm_in->tm_mday); +} + +/* + * Add val to itm_in->tm_mon (no need for scale here, as val is always + * in months already). + * Returns true if successful, false if itm_in overflows. + */ +static bool +AdjustMonths(int64 val, struct pg_itm_in *itm_in) +{ + if (val < INT_MIN || val > INT_MAX) + return false; + return !pg_add_s32_overflow(itm_in->tm_mon, (int32) val, &itm_in->tm_mon); +} + +/* + * Multiply val by scale (to produce years) and add to itm_in->tm_year. + * Returns true if successful, false if itm_in overflows. + */ +static bool +AdjustYears(int64 val, int scale, + struct pg_itm_in *itm_in) +{ + int years; + + if (val < INT_MIN || val > INT_MAX) + return false; + return !pg_mul_s32_overflow((int32) val, scale, &years) && + !pg_add_s32_overflow(itm_in->tm_year, years, &itm_in->tm_year); +} + + +/* + * Parse the fractional part of a number (decimal point and optional digits, + * followed by end of string). Returns the fractional value into *frac. + * + * Returns 0 if successful, DTERR code if bogus input detected. + */ +static int +ParseFraction(char *cp, double *frac) +{ + /* Caller should always pass the start of the fraction part */ + Assert(*cp == '.'); + + /* + * We want to allow just "." with no digits, but some versions of strtod + * will report EINVAL for that, so special-case it. + */ + if (cp[1] == '\0') + { + *frac = 0; + } + else + { + errno = 0; + *frac = strtod(cp, &cp); + /* check for parse failure */ + if (*cp != '\0' || errno != 0) + return DTERR_BAD_FORMAT; + } + return 0; +} + +/* + * Fetch a fractional-second value with suitable error checking. + * Same as ParseFraction except we convert the result to integer microseconds. + */ +static int +ParseFractionalSecond(char *cp, fsec_t *fsec) +{ + double frac; + int dterr; + + dterr = ParseFraction(cp, &frac); + if (dterr) + return dterr; + *fsec = rint(frac * 1000000); + return 0; +} + + +/* ParseDateTime() + * Break string into tokens based on a date/time context. + * Returns 0 if successful, DTERR code if bogus input detected. + * + * timestr - the input string + * workbuf - workspace for field string storage. This must be + * larger than the largest legal input for this datetime type -- + * some additional space will be needed to NUL terminate fields. + * buflen - the size of workbuf + * field[] - pointers to field strings are returned in this array + * ftype[] - field type indicators are returned in this array + * maxfields - dimensions of the above two arrays + * *numfields - set to the actual number of fields detected + * + * The fields extracted from the input are stored as separate, + * null-terminated strings in the workspace at workbuf. Any text is + * converted to lower case. + * + * Several field types are assigned: + * DTK_NUMBER - digits and (possibly) a decimal point + * DTK_DATE - digits and two delimiters, or digits and text + * DTK_TIME - digits, colon delimiters, and possibly a decimal point + * DTK_STRING - text (no digits or punctuation) + * DTK_SPECIAL - leading "+" or "-" followed by text + * DTK_TZ - leading "+" or "-" followed by digits (also eats ':', '.', '-') + * + * Note that some field types can hold unexpected items: + * DTK_NUMBER can hold date fields (yy.ddd) + * DTK_STRING can hold months (January) and time zones (PST) + * DTK_DATE can hold time zone names (America/New_York, GMT-8) + */ +int +ParseDateTime(const char *timestr, char *workbuf, size_t buflen, + char **field, int *ftype, int maxfields, int *numfields) +{ + int nf = 0; + const char *cp = timestr; + char *bufp = workbuf; + const char *bufend = workbuf + buflen; + + /* + * Set the character pointed-to by "bufptr" to "newchar", and increment + * "bufptr". "end" gives the end of the buffer -- we return an error if + * there is no space left to append a character to the buffer. Note that + * "bufptr" is evaluated twice. + */ +#define APPEND_CHAR(bufptr, end, newchar) \ + do \ + { \ + if (((bufptr) + 1) >= (end)) \ + return DTERR_BAD_FORMAT; \ + *(bufptr)++ = newchar; \ + } while (0) + + /* outer loop through fields */ + while (*cp != '\0') + { + /* Ignore spaces between fields */ + if (isspace((unsigned char) *cp)) + { + cp++; + continue; + } + + /* Record start of current field */ + if (nf >= maxfields) + return DTERR_BAD_FORMAT; + field[nf] = bufp; + + /* leading digit? then date or time */ + if (isdigit((unsigned char) *cp)) + { + APPEND_CHAR(bufp, bufend, *cp++); + while (isdigit((unsigned char) *cp)) + APPEND_CHAR(bufp, bufend, *cp++); + + /* time field? */ + if (*cp == ':') + { + ftype[nf] = DTK_TIME; + APPEND_CHAR(bufp, bufend, *cp++); + while (isdigit((unsigned char) *cp) || + (*cp == ':') || (*cp == '.')) + APPEND_CHAR(bufp, bufend, *cp++); + } + /* date field? allow embedded text month */ + else if (*cp == '-' || *cp == '/' || *cp == '.') + { + /* save delimiting character to use later */ + char delim = *cp; + + APPEND_CHAR(bufp, bufend, *cp++); + /* second field is all digits? then no embedded text month */ + if (isdigit((unsigned char) *cp)) + { + ftype[nf] = ((delim == '.') ? DTK_NUMBER : DTK_DATE); + while (isdigit((unsigned char) *cp)) + APPEND_CHAR(bufp, bufend, *cp++); + + /* + * insist that the delimiters match to get a three-field + * date. + */ + if (*cp == delim) + { + ftype[nf] = DTK_DATE; + APPEND_CHAR(bufp, bufend, *cp++); + while (isdigit((unsigned char) *cp) || *cp == delim) + APPEND_CHAR(bufp, bufend, *cp++); + } + } + else + { + ftype[nf] = DTK_DATE; + while (isalnum((unsigned char) *cp) || *cp == delim) + APPEND_CHAR(bufp, bufend, pg_tolower((unsigned char) *cp++)); + } + } + + /* + * otherwise, number only and will determine year, month, day, or + * concatenated fields later... + */ + else + ftype[nf] = DTK_NUMBER; + } + /* Leading decimal point? Then fractional seconds... */ + else if (*cp == '.') + { + APPEND_CHAR(bufp, bufend, *cp++); + while (isdigit((unsigned char) *cp)) + APPEND_CHAR(bufp, bufend, *cp++); + + ftype[nf] = DTK_NUMBER; + } + + /* + * text? then date string, month, day of week, special, or timezone + */ + else if (isalpha((unsigned char) *cp)) + { + bool is_date; + + ftype[nf] = DTK_STRING; + APPEND_CHAR(bufp, bufend, pg_tolower((unsigned char) *cp++)); + while (isalpha((unsigned char) *cp)) + APPEND_CHAR(bufp, bufend, pg_tolower((unsigned char) *cp++)); + + /* + * Dates can have embedded '-', '/', or '.' separators. It could + * also be a timezone name containing embedded '/', '+', '-', '_', + * or ':' (but '_' or ':' can't be the first punctuation). If the + * next character is a digit or '+', we need to check whether what + * we have so far is a recognized non-timezone keyword --- if so, + * don't believe that this is the start of a timezone. + */ + is_date = false; + if (*cp == '-' || *cp == '/' || *cp == '.') + is_date = true; + else if (*cp == '+' || isdigit((unsigned char) *cp)) + { + *bufp = '\0'; /* null-terminate current field value */ + /* we need search only the core token table, not TZ names */ + if (datebsearch(field[nf], datetktbl, szdatetktbl) == NULL) + is_date = true; + } + if (is_date) + { + ftype[nf] = DTK_DATE; + do + { + APPEND_CHAR(bufp, bufend, pg_tolower((unsigned char) *cp++)); + } while (*cp == '+' || *cp == '-' || + *cp == '/' || *cp == '_' || + *cp == '.' || *cp == ':' || + isalnum((unsigned char) *cp)); + } + } + /* sign? then special or numeric timezone */ + else if (*cp == '+' || *cp == '-') + { + APPEND_CHAR(bufp, bufend, *cp++); + /* soak up leading whitespace */ + while (isspace((unsigned char) *cp)) + cp++; + /* numeric timezone? */ + /* note that "DTK_TZ" could also be a signed float or yyyy-mm */ + if (isdigit((unsigned char) *cp)) + { + ftype[nf] = DTK_TZ; + APPEND_CHAR(bufp, bufend, *cp++); + while (isdigit((unsigned char) *cp) || + *cp == ':' || *cp == '.' || *cp == '-') + APPEND_CHAR(bufp, bufend, *cp++); + } + /* special? */ + else if (isalpha((unsigned char) *cp)) + { + ftype[nf] = DTK_SPECIAL; + APPEND_CHAR(bufp, bufend, pg_tolower((unsigned char) *cp++)); + while (isalpha((unsigned char) *cp)) + APPEND_CHAR(bufp, bufend, pg_tolower((unsigned char) *cp++)); + } + /* otherwise something wrong... */ + else + return DTERR_BAD_FORMAT; + } + /* ignore other punctuation but use as delimiter */ + else if (ispunct((unsigned char) *cp)) + { + cp++; + continue; + } + /* otherwise, something is not right... */ + else + return DTERR_BAD_FORMAT; + + /* force in a delimiter after each field */ + *bufp++ = '\0'; + nf++; + } + + *numfields = nf; + + return 0; +} + + +/* DecodeDateTime() + * Interpret previously parsed fields for general date and time. + * Return 0 if full date, 1 if only time, and negative DTERR code if problems. + * (Currently, all callers treat 1 as an error return too.) + * + * Inputs are field[] and ftype[] arrays, of length nf. + * Other arguments are outputs. + * + * External format(s): + * "<weekday> <month>-<day>-<year> <hour>:<minute>:<second>" + * "Fri Feb-7-1997 15:23:27" + * "Feb-7-1997 15:23:27" + * "2-7-1997 15:23:27" + * "1997-2-7 15:23:27" + * "1997.038 15:23:27" (day of year 1-366) + * Also supports input in compact time: + * "970207 152327" + * "97038 152327" + * "20011225T040506.789-07" + * + * Use the system-provided functions to get the current time zone + * if not specified in the input string. + * + * If the date is outside the range of pg_time_t (in practice that could only + * happen if pg_time_t is just 32 bits), then assume UTC time zone - thomas + * 1997-05-27 + */ +int +DecodeDateTime(char **field, int *ftype, int nf, + int *dtype, struct pg_tm *tm, fsec_t *fsec, int *tzp, + DateTimeErrorExtra *extra) +{ + int fmask = 0, + tmask, + type; + int ptype = 0; /* "prefix type" for ISO and Julian formats */ + int i; + int val; + int dterr; + int mer = HR24; + bool haveTextMonth = false; + bool isjulian = false; + bool is2digits = false; + bool bc = false; + pg_tz *namedTz = NULL; + pg_tz *abbrevTz = NULL; + pg_tz *valtz; + char *abbrev = NULL; + struct pg_tm cur_tm; + + /* + * We'll insist on at least all of the date fields, but initialize the + * remaining fields in case they are not set later... + */ + *dtype = DTK_DATE; + tm->tm_hour = 0; + tm->tm_min = 0; + tm->tm_sec = 0; + *fsec = 0; + /* don't know daylight savings time status apriori */ + tm->tm_isdst = -1; + if (tzp != NULL) + *tzp = 0; + + for (i = 0; i < nf; i++) + { + switch (ftype[i]) + { + case DTK_DATE: + + /* + * Integral julian day with attached time zone? All other + * forms with JD will be separated into distinct fields, so we + * handle just this case here. + */ + if (ptype == DTK_JULIAN) + { + char *cp; + int jday; + + if (tzp == NULL) + return DTERR_BAD_FORMAT; + + errno = 0; + jday = strtoint(field[i], &cp, 10); + if (errno == ERANGE || jday < 0) + return DTERR_FIELD_OVERFLOW; + + j2date(jday, &tm->tm_year, &tm->tm_mon, &tm->tm_mday); + isjulian = true; + + /* Get the time zone from the end of the string */ + dterr = DecodeTimezone(cp, tzp); + if (dterr) + return dterr; + + tmask = DTK_DATE_M | DTK_TIME_M | DTK_M(TZ); + ptype = 0; + break; + } + + /* + * Already have a date? Then this might be a time zone name + * with embedded punctuation (e.g. "America/New_York") or a + * run-together time with trailing time zone (e.g. hhmmss-zz). + * - thomas 2001-12-25 + * + * We consider it a time zone if we already have month & day. + * This is to allow the form "mmm dd hhmmss tz year", which + * we've historically accepted. + */ + else if (ptype != 0 || + ((fmask & (DTK_M(MONTH) | DTK_M(DAY))) == + (DTK_M(MONTH) | DTK_M(DAY)))) + { + /* No time zone accepted? Then quit... */ + if (tzp == NULL) + return DTERR_BAD_FORMAT; + + if (isdigit((unsigned char) *field[i]) || ptype != 0) + { + char *cp; + + /* + * Allow a preceding "t" field, but no other units. + */ + if (ptype != 0) + { + /* Sanity check; should not fail this test */ + if (ptype != DTK_TIME) + return DTERR_BAD_FORMAT; + ptype = 0; + } + + /* + * Starts with a digit but we already have a time + * field? Then we are in trouble with a date and time + * already... + */ + if ((fmask & DTK_TIME_M) == DTK_TIME_M) + return DTERR_BAD_FORMAT; + + if ((cp = strchr(field[i], '-')) == NULL) + return DTERR_BAD_FORMAT; + + /* Get the time zone from the end of the string */ + dterr = DecodeTimezone(cp, tzp); + if (dterr) + return dterr; + *cp = '\0'; + + /* + * Then read the rest of the field as a concatenated + * time + */ + dterr = DecodeNumberField(strlen(field[i]), field[i], + fmask, + &tmask, tm, + fsec, &is2digits); + if (dterr < 0) + return dterr; + + /* + * modify tmask after returning from + * DecodeNumberField() + */ + tmask |= DTK_M(TZ); + } + else + { + namedTz = pg_tzset(field[i]); + if (!namedTz) + { + extra->dtee_timezone = field[i]; + return DTERR_BAD_TIMEZONE; + } + /* we'll apply the zone setting below */ + tmask = DTK_M(TZ); + } + } + else + { + dterr = DecodeDate(field[i], fmask, + &tmask, &is2digits, tm); + if (dterr) + return dterr; + } + break; + + case DTK_TIME: + + /* + * This might be an ISO time following a "t" field. + */ + if (ptype != 0) + { + /* Sanity check; should not fail this test */ + if (ptype != DTK_TIME) + return DTERR_BAD_FORMAT; + ptype = 0; + } + dterr = DecodeTime(field[i], fmask, INTERVAL_FULL_RANGE, + &tmask, tm, fsec); + if (dterr) + return dterr; + + /* check for time overflow */ + if (time_overflows(tm->tm_hour, tm->tm_min, tm->tm_sec, + *fsec)) + return DTERR_FIELD_OVERFLOW; + break; + + case DTK_TZ: + { + int tz; + + if (tzp == NULL) + return DTERR_BAD_FORMAT; + + dterr = DecodeTimezone(field[i], &tz); + if (dterr) + return dterr; + *tzp = tz; + tmask = DTK_M(TZ); + } + break; + + case DTK_NUMBER: + + /* + * Deal with cases where previous field labeled this one + */ + if (ptype != 0) + { + char *cp; + int value; + + errno = 0; + value = strtoint(field[i], &cp, 10); + if (errno == ERANGE) + return DTERR_FIELD_OVERFLOW; + if (*cp != '.' && *cp != '\0') + return DTERR_BAD_FORMAT; + + switch (ptype) + { + case DTK_JULIAN: + /* previous field was a label for "julian date" */ + if (value < 0) + return DTERR_FIELD_OVERFLOW; + tmask = DTK_DATE_M; + j2date(value, &tm->tm_year, &tm->tm_mon, &tm->tm_mday); + isjulian = true; + + /* fractional Julian Day? */ + if (*cp == '.') + { + double time; + + dterr = ParseFraction(cp, &time); + if (dterr) + return dterr; + time *= USECS_PER_DAY; + dt2time(time, + &tm->tm_hour, &tm->tm_min, + &tm->tm_sec, fsec); + tmask |= DTK_TIME_M; + } + break; + + case DTK_TIME: + /* previous field was "t" for ISO time */ + dterr = DecodeNumberField(strlen(field[i]), field[i], + (fmask | DTK_DATE_M), + &tmask, tm, + fsec, &is2digits); + if (dterr < 0) + return dterr; + if (tmask != DTK_TIME_M) + return DTERR_BAD_FORMAT; + break; + + default: + return DTERR_BAD_FORMAT; + break; + } + + ptype = 0; + *dtype = DTK_DATE; + } + else + { + char *cp; + int flen; + + flen = strlen(field[i]); + cp = strchr(field[i], '.'); + + /* Embedded decimal and no date yet? */ + if (cp != NULL && !(fmask & DTK_DATE_M)) + { + dterr = DecodeDate(field[i], fmask, + &tmask, &is2digits, tm); + if (dterr) + return dterr; + } + /* embedded decimal and several digits before? */ + else if (cp != NULL && flen - strlen(cp) > 2) + { + /* + * Interpret as a concatenated date or time Set the + * type field to allow decoding other fields later. + * Example: 20011223 or 040506 + */ + dterr = DecodeNumberField(flen, field[i], fmask, + &tmask, tm, + fsec, &is2digits); + if (dterr < 0) + return dterr; + } + + /* + * Is this a YMD or HMS specification, or a year number? + * YMD and HMS are required to be six digits or more, so + * if it is 5 digits, it is a year. If it is six or more + * digits, we assume it is YMD or HMS unless no date and + * no time values have been specified. This forces 6+ + * digit years to be at the end of the string, or to use + * the ISO date specification. + */ + else if (flen >= 6 && (!(fmask & DTK_DATE_M) || + !(fmask & DTK_TIME_M))) + { + dterr = DecodeNumberField(flen, field[i], fmask, + &tmask, tm, + fsec, &is2digits); + if (dterr < 0) + return dterr; + } + /* otherwise it is a single date/time field... */ + else + { + dterr = DecodeNumber(flen, field[i], + haveTextMonth, fmask, + &tmask, tm, + fsec, &is2digits); + if (dterr) + return dterr; + } + } + break; + + case DTK_STRING: + case DTK_SPECIAL: + /* timezone abbrevs take precedence over built-in tokens */ + dterr = DecodeTimezoneAbbrev(i, field[i], + &type, &val, &valtz, extra); + if (dterr) + return dterr; + if (type == UNKNOWN_FIELD) + type = DecodeSpecial(i, field[i], &val); + if (type == IGNORE_DTF) + continue; + + tmask = DTK_M(type); + switch (type) + { + case RESERV: + switch (val) + { + case DTK_NOW: + tmask = (DTK_DATE_M | DTK_TIME_M | DTK_M(TZ)); + *dtype = DTK_DATE; + GetCurrentTimeUsec(tm, fsec, tzp); + break; + + case DTK_YESTERDAY: + tmask = DTK_DATE_M; + *dtype = DTK_DATE; + GetCurrentDateTime(&cur_tm); + j2date(date2j(cur_tm.tm_year, cur_tm.tm_mon, cur_tm.tm_mday) - 1, + &tm->tm_year, &tm->tm_mon, &tm->tm_mday); + break; + + case DTK_TODAY: + tmask = DTK_DATE_M; + *dtype = DTK_DATE; + GetCurrentDateTime(&cur_tm); + tm->tm_year = cur_tm.tm_year; + tm->tm_mon = cur_tm.tm_mon; + tm->tm_mday = cur_tm.tm_mday; + break; + + case DTK_TOMORROW: + tmask = DTK_DATE_M; + *dtype = DTK_DATE; + GetCurrentDateTime(&cur_tm); + j2date(date2j(cur_tm.tm_year, cur_tm.tm_mon, cur_tm.tm_mday) + 1, + &tm->tm_year, &tm->tm_mon, &tm->tm_mday); + break; + + case DTK_ZULU: + tmask = (DTK_TIME_M | DTK_M(TZ)); + *dtype = DTK_DATE; + tm->tm_hour = 0; + tm->tm_min = 0; + tm->tm_sec = 0; + if (tzp != NULL) + *tzp = 0; + break; + + case DTK_EPOCH: + case DTK_LATE: + case DTK_EARLY: + tmask = (DTK_DATE_M | DTK_TIME_M | DTK_M(TZ)); + *dtype = val; + /* caller ignores tm for these dtype codes */ + break; + + default: + elog(ERROR, "unrecognized RESERV datetime token: %d", + val); + } + + break; + + case MONTH: + + /* + * already have a (numeric) month? then see if we can + * substitute... + */ + if ((fmask & DTK_M(MONTH)) && !haveTextMonth && + !(fmask & DTK_M(DAY)) && tm->tm_mon >= 1 && + tm->tm_mon <= 31) + { + tm->tm_mday = tm->tm_mon; + tmask = DTK_M(DAY); + } + haveTextMonth = true; + tm->tm_mon = val; + break; + + case DTZMOD: + + /* + * daylight savings time modifier (solves "MET DST" + * syntax) + */ + tmask |= DTK_M(DTZ); + tm->tm_isdst = 1; + if (tzp == NULL) + return DTERR_BAD_FORMAT; + *tzp -= val; + break; + + case DTZ: + + /* + * set mask for TZ here _or_ check for DTZ later when + * getting default timezone + */ + tmask |= DTK_M(TZ); + tm->tm_isdst = 1; + if (tzp == NULL) + return DTERR_BAD_FORMAT; + *tzp = -val; + break; + + case TZ: + tm->tm_isdst = 0; + if (tzp == NULL) + return DTERR_BAD_FORMAT; + *tzp = -val; + break; + + case DYNTZ: + tmask |= DTK_M(TZ); + if (tzp == NULL) + return DTERR_BAD_FORMAT; + /* we'll determine the actual offset later */ + abbrevTz = valtz; + abbrev = field[i]; + break; + + case AMPM: + mer = val; + break; + + case ADBC: + bc = (val == BC); + break; + + case DOW: + tm->tm_wday = val; + break; + + case UNITS: + tmask = 0; + /* reject consecutive unhandled units */ + if (ptype != 0) + return DTERR_BAD_FORMAT; + ptype = val; + break; + + case ISOTIME: + + /* + * This is a filler field "t" indicating that the next + * field is time. Try to verify that this is sensible. + */ + tmask = 0; + + /* No preceding date? Then quit... */ + if ((fmask & DTK_DATE_M) != DTK_DATE_M) + return DTERR_BAD_FORMAT; + + /* reject consecutive unhandled units */ + if (ptype != 0) + return DTERR_BAD_FORMAT; + ptype = val; + break; + + case UNKNOWN_FIELD: + + /* + * Before giving up and declaring error, check to see + * if it is an all-alpha timezone name. + */ + namedTz = pg_tzset(field[i]); + if (!namedTz) + return DTERR_BAD_FORMAT; + /* we'll apply the zone setting below */ + tmask = DTK_M(TZ); + break; + + default: + return DTERR_BAD_FORMAT; + } + break; + + default: + return DTERR_BAD_FORMAT; + } + + if (tmask & fmask) + return DTERR_BAD_FORMAT; + fmask |= tmask; + } /* end loop over fields */ + + /* reject if prefix type appeared and was never handled */ + if (ptype != 0) + return DTERR_BAD_FORMAT; + + /* do additional checking for normal date specs (but not "infinity" etc) */ + if (*dtype == DTK_DATE) + { + /* do final checking/adjustment of Y/M/D fields */ + dterr = ValidateDate(fmask, isjulian, is2digits, bc, tm); + if (dterr) + return dterr; + + /* handle AM/PM */ + if (mer != HR24 && tm->tm_hour > HOURS_PER_DAY / 2) + return DTERR_FIELD_OVERFLOW; + if (mer == AM && tm->tm_hour == HOURS_PER_DAY / 2) + tm->tm_hour = 0; + else if (mer == PM && tm->tm_hour != HOURS_PER_DAY / 2) + tm->tm_hour += HOURS_PER_DAY / 2; + + /* check for incomplete input */ + if ((fmask & DTK_DATE_M) != DTK_DATE_M) + { + if ((fmask & DTK_TIME_M) == DTK_TIME_M) + return 1; + return DTERR_BAD_FORMAT; + } + + /* + * If we had a full timezone spec, compute the offset (we could not do + * it before, because we need the date to resolve DST status). + */ + if (namedTz != NULL) + { + /* daylight savings time modifier disallowed with full TZ */ + if (fmask & DTK_M(DTZMOD)) + return DTERR_BAD_FORMAT; + + *tzp = DetermineTimeZoneOffset(tm, namedTz); + } + + /* + * Likewise, if we had a dynamic timezone abbreviation, resolve it + * now. + */ + if (abbrevTz != NULL) + { + /* daylight savings time modifier disallowed with dynamic TZ */ + if (fmask & DTK_M(DTZMOD)) + return DTERR_BAD_FORMAT; + + *tzp = DetermineTimeZoneAbbrevOffset(tm, abbrev, abbrevTz); + } + + /* timezone not specified? then use session timezone */ + if (tzp != NULL && !(fmask & DTK_M(TZ))) + { + /* + * daylight savings time modifier but no standard timezone? then + * error + */ + if (fmask & DTK_M(DTZMOD)) + return DTERR_BAD_FORMAT; + + *tzp = DetermineTimeZoneOffset(tm, session_timezone); + } + } + + return 0; +} + + +/* DetermineTimeZoneOffset() + * + * Given a struct pg_tm in which tm_year, tm_mon, tm_mday, tm_hour, tm_min, + * and tm_sec fields are set, and a zic-style time zone definition, determine + * the applicable GMT offset and daylight-savings status at that time. + * Set the struct pg_tm's tm_isdst field accordingly, and return the GMT + * offset as the function result. + * + * Note: if the date is out of the range we can deal with, we return zero + * as the GMT offset and set tm_isdst = 0. We don't throw an error here, + * though probably some higher-level code will. + */ +int +DetermineTimeZoneOffset(struct pg_tm *tm, pg_tz *tzp) +{ + pg_time_t t; + + return DetermineTimeZoneOffsetInternal(tm, tzp, &t); +} + + +/* DetermineTimeZoneOffsetInternal() + * + * As above, but also return the actual UTC time imputed to the date/time + * into *tp. + * + * In event of an out-of-range date, we punt by returning zero into *tp. + * This is okay for the immediate callers but is a good reason for not + * exposing this worker function globally. + * + * Note: it might seem that we should use mktime() for this, but bitter + * experience teaches otherwise. This code is much faster than most versions + * of mktime(), anyway. + */ +static int +DetermineTimeZoneOffsetInternal(struct pg_tm *tm, pg_tz *tzp, pg_time_t *tp) +{ + int date, + sec; + pg_time_t day, + mytime, + prevtime, + boundary, + beforetime, + aftertime; + long int before_gmtoff, + after_gmtoff; + int before_isdst, + after_isdst; + int res; + + /* + * First, generate the pg_time_t value corresponding to the given + * y/m/d/h/m/s taken as GMT time. If this overflows, punt and decide the + * timezone is GMT. (For a valid Julian date, integer overflow should be + * impossible with 64-bit pg_time_t, but let's check for safety.) + */ + if (!IS_VALID_JULIAN(tm->tm_year, tm->tm_mon, tm->tm_mday)) + goto overflow; + date = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - UNIX_EPOCH_JDATE; + + day = ((pg_time_t) date) * SECS_PER_DAY; + if (day / SECS_PER_DAY != date) + goto overflow; + sec = tm->tm_sec + (tm->tm_min + tm->tm_hour * MINS_PER_HOUR) * SECS_PER_MINUTE; + mytime = day + sec; + /* since sec >= 0, overflow could only be from +day to -mytime */ + if (mytime < 0 && day > 0) + goto overflow; + + /* + * Find the DST time boundary just before or following the target time. We + * assume that all zones have GMT offsets less than 24 hours, and that DST + * boundaries can't be closer together than 48 hours, so backing up 24 + * hours and finding the "next" boundary will work. + */ + prevtime = mytime - SECS_PER_DAY; + if (mytime < 0 && prevtime > 0) + goto overflow; + + res = pg_next_dst_boundary(&prevtime, + &before_gmtoff, &before_isdst, + &boundary, + &after_gmtoff, &after_isdst, + tzp); + if (res < 0) + goto overflow; /* failure? */ + + if (res == 0) + { + /* Non-DST zone, life is simple */ + tm->tm_isdst = before_isdst; + *tp = mytime - before_gmtoff; + return -(int) before_gmtoff; + } + + /* + * Form the candidate pg_time_t values with local-time adjustment + */ + beforetime = mytime - before_gmtoff; + if ((before_gmtoff > 0 && + mytime < 0 && beforetime > 0) || + (before_gmtoff <= 0 && + mytime > 0 && beforetime < 0)) + goto overflow; + aftertime = mytime - after_gmtoff; + if ((after_gmtoff > 0 && + mytime < 0 && aftertime > 0) || + (after_gmtoff <= 0 && + mytime > 0 && aftertime < 0)) + goto overflow; + + /* + * If both before or both after the boundary time, we know what to do. The + * boundary time itself is considered to be after the transition, which + * means we can accept aftertime == boundary in the second case. + */ + if (beforetime < boundary && aftertime < boundary) + { + tm->tm_isdst = before_isdst; + *tp = beforetime; + return -(int) before_gmtoff; + } + if (beforetime > boundary && aftertime >= boundary) + { + tm->tm_isdst = after_isdst; + *tp = aftertime; + return -(int) after_gmtoff; + } + + /* + * It's an invalid or ambiguous time due to timezone transition. In a + * spring-forward transition, prefer the "before" interpretation; in a + * fall-back transition, prefer "after". (We used to define and implement + * this test as "prefer the standard-time interpretation", but that rule + * does not help to resolve the behavior when both times are reported as + * standard time; which does happen, eg Europe/Moscow in Oct 2014. Also, + * in some zones such as Europe/Dublin, there is widespread confusion + * about which time offset is "standard" time, so it's fortunate that our + * behavior doesn't depend on that.) + */ + if (beforetime > aftertime) + { + tm->tm_isdst = before_isdst; + *tp = beforetime; + return -(int) before_gmtoff; + } + tm->tm_isdst = after_isdst; + *tp = aftertime; + return -(int) after_gmtoff; + +overflow: + /* Given date is out of range, so assume UTC */ + tm->tm_isdst = 0; + *tp = 0; + return 0; +} + + +/* DetermineTimeZoneAbbrevOffset() + * + * Determine the GMT offset and DST flag to be attributed to a dynamic + * time zone abbreviation, that is one whose meaning has changed over time. + * *tm contains the local time at which the meaning should be determined, + * and tm->tm_isdst receives the DST flag. + * + * This differs from the behavior of DetermineTimeZoneOffset() in that a + * standard-time or daylight-time abbreviation forces use of the corresponding + * GMT offset even when the zone was then in DS or standard time respectively. + * (However, that happens only if we can match the given abbreviation to some + * abbreviation that appears in the IANA timezone data. Otherwise, we fall + * back to doing DetermineTimeZoneOffset().) + */ +int +DetermineTimeZoneAbbrevOffset(struct pg_tm *tm, const char *abbr, pg_tz *tzp) +{ + pg_time_t t; + int zone_offset; + int abbr_offset; + int abbr_isdst; + + /* + * Compute the UTC time we want to probe at. (In event of overflow, we'll + * probe at the epoch, which is a bit random but probably doesn't matter.) + */ + zone_offset = DetermineTimeZoneOffsetInternal(tm, tzp, &t); + + /* + * Try to match the abbreviation to something in the zone definition. + */ + if (DetermineTimeZoneAbbrevOffsetInternal(t, abbr, tzp, + &abbr_offset, &abbr_isdst)) + { + /* Success, so use the abbrev-specific answers. */ + tm->tm_isdst = abbr_isdst; + return abbr_offset; + } + + /* + * No match, so use the answers we already got from + * DetermineTimeZoneOffsetInternal. + */ + return zone_offset; +} + + +/* DetermineTimeZoneAbbrevOffsetTS() + * + * As above but the probe time is specified as a TimestampTz (hence, UTC time), + * and DST status is returned into *isdst rather than into tm->tm_isdst. + */ +int +DetermineTimeZoneAbbrevOffsetTS(TimestampTz ts, const char *abbr, + pg_tz *tzp, int *isdst) +{ + pg_time_t t = timestamptz_to_time_t(ts); + int zone_offset; + int abbr_offset; + int tz; + struct pg_tm tm; + fsec_t fsec; + + /* + * If the abbrev matches anything in the zone data, this is pretty easy. + */ + if (DetermineTimeZoneAbbrevOffsetInternal(t, abbr, tzp, + &abbr_offset, isdst)) + return abbr_offset; + + /* + * Else, break down the timestamp so we can use DetermineTimeZoneOffset. + */ + if (timestamp2tm(ts, &tz, &tm, &fsec, NULL, tzp) != 0) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + + zone_offset = DetermineTimeZoneOffset(&tm, tzp); + *isdst = tm.tm_isdst; + return zone_offset; +} + + +/* DetermineTimeZoneAbbrevOffsetInternal() + * + * Workhorse for above two functions: work from a pg_time_t probe instant. + * On success, return GMT offset and DST status into *offset and *isdst. + */ +static bool +DetermineTimeZoneAbbrevOffsetInternal(pg_time_t t, const char *abbr, pg_tz *tzp, + int *offset, int *isdst) +{ + char upabbr[TZ_STRLEN_MAX + 1]; + unsigned char *p; + long int gmtoff; + + /* We need to force the abbrev to upper case */ + strlcpy(upabbr, abbr, sizeof(upabbr)); + for (p = (unsigned char *) upabbr; *p; p++) + *p = pg_toupper(*p); + + /* Look up the abbrev's meaning at this time in this zone */ + if (pg_interpret_timezone_abbrev(upabbr, + &t, + &gmtoff, + isdst, + tzp)) + { + /* Change sign to agree with DetermineTimeZoneOffset() */ + *offset = (int) -gmtoff; + return true; + } + return false; +} + + +/* DecodeTimeOnly() + * Interpret parsed string as time fields only. + * Returns 0 if successful, DTERR code if bogus input detected. + * + * Inputs are field[] and ftype[] arrays, of length nf. + * Other arguments are outputs. + * + * Note that support for time zone is here for + * SQL TIME WITH TIME ZONE, but it reveals + * bogosity with SQL date/time standards, since + * we must infer a time zone from current time. + * - thomas 2000-03-10 + * Allow specifying date to get a better time zone, + * if time zones are allowed. - thomas 2001-12-26 + */ +int +DecodeTimeOnly(char **field, int *ftype, int nf, + int *dtype, struct pg_tm *tm, fsec_t *fsec, int *tzp, + DateTimeErrorExtra *extra) +{ + int fmask = 0, + tmask, + type; + int ptype = 0; /* "prefix type" for ISO and Julian formats */ + int i; + int val; + int dterr; + bool isjulian = false; + bool is2digits = false; + bool bc = false; + int mer = HR24; + pg_tz *namedTz = NULL; + pg_tz *abbrevTz = NULL; + char *abbrev = NULL; + pg_tz *valtz; + + *dtype = DTK_TIME; + tm->tm_hour = 0; + tm->tm_min = 0; + tm->tm_sec = 0; + *fsec = 0; + /* don't know daylight savings time status apriori */ + tm->tm_isdst = -1; + + if (tzp != NULL) + *tzp = 0; + + for (i = 0; i < nf; i++) + { + switch (ftype[i]) + { + case DTK_DATE: + + /* + * Time zone not allowed? Then should not accept dates or time + * zones no matter what else! + */ + if (tzp == NULL) + return DTERR_BAD_FORMAT; + + /* Under limited circumstances, we will accept a date... */ + if (i == 0 && nf >= 2 && + (ftype[nf - 1] == DTK_DATE || ftype[1] == DTK_TIME)) + { + dterr = DecodeDate(field[i], fmask, + &tmask, &is2digits, tm); + if (dterr) + return dterr; + } + /* otherwise, this is a time and/or time zone */ + else + { + if (isdigit((unsigned char) *field[i])) + { + char *cp; + + /* + * Starts with a digit but we already have a time + * field? Then we are in trouble with time already... + */ + if ((fmask & DTK_TIME_M) == DTK_TIME_M) + return DTERR_BAD_FORMAT; + + /* + * Should not get here and fail. Sanity check only... + */ + if ((cp = strchr(field[i], '-')) == NULL) + return DTERR_BAD_FORMAT; + + /* Get the time zone from the end of the string */ + dterr = DecodeTimezone(cp, tzp); + if (dterr) + return dterr; + *cp = '\0'; + + /* + * Then read the rest of the field as a concatenated + * time + */ + dterr = DecodeNumberField(strlen(field[i]), field[i], + (fmask | DTK_DATE_M), + &tmask, tm, + fsec, &is2digits); + if (dterr < 0) + return dterr; + ftype[i] = dterr; + + tmask |= DTK_M(TZ); + } + else + { + namedTz = pg_tzset(field[i]); + if (!namedTz) + { + extra->dtee_timezone = field[i]; + return DTERR_BAD_TIMEZONE; + } + /* we'll apply the zone setting below */ + ftype[i] = DTK_TZ; + tmask = DTK_M(TZ); + } + } + break; + + case DTK_TIME: + dterr = DecodeTime(field[i], (fmask | DTK_DATE_M), + INTERVAL_FULL_RANGE, + &tmask, tm, fsec); + if (dterr) + return dterr; + break; + + case DTK_TZ: + { + int tz; + + if (tzp == NULL) + return DTERR_BAD_FORMAT; + + dterr = DecodeTimezone(field[i], &tz); + if (dterr) + return dterr; + *tzp = tz; + tmask = DTK_M(TZ); + } + break; + + case DTK_NUMBER: + + /* + * Deal with cases where previous field labeled this one + */ + if (ptype != 0) + { + char *cp; + int value; + + errno = 0; + value = strtoint(field[i], &cp, 10); + if (errno == ERANGE) + return DTERR_FIELD_OVERFLOW; + if (*cp != '.' && *cp != '\0') + return DTERR_BAD_FORMAT; + + switch (ptype) + { + case DTK_JULIAN: + /* previous field was a label for "julian date" */ + if (tzp == NULL) + return DTERR_BAD_FORMAT; + if (value < 0) + return DTERR_FIELD_OVERFLOW; + tmask = DTK_DATE_M; + j2date(value, &tm->tm_year, &tm->tm_mon, &tm->tm_mday); + isjulian = true; + + if (*cp == '.') + { + double time; + + dterr = ParseFraction(cp, &time); + if (dterr) + return dterr; + time *= USECS_PER_DAY; + dt2time(time, + &tm->tm_hour, &tm->tm_min, + &tm->tm_sec, fsec); + tmask |= DTK_TIME_M; + } + break; + + case DTK_TIME: + /* previous field was "t" for ISO time */ + dterr = DecodeNumberField(strlen(field[i]), field[i], + (fmask | DTK_DATE_M), + &tmask, tm, + fsec, &is2digits); + if (dterr < 0) + return dterr; + ftype[i] = dterr; + + if (tmask != DTK_TIME_M) + return DTERR_BAD_FORMAT; + break; + + default: + return DTERR_BAD_FORMAT; + break; + } + + ptype = 0; + *dtype = DTK_DATE; + } + else + { + char *cp; + int flen; + + flen = strlen(field[i]); + cp = strchr(field[i], '.'); + + /* Embedded decimal? */ + if (cp != NULL) + { + /* + * Under limited circumstances, we will accept a + * date... + */ + if (i == 0 && nf >= 2 && ftype[nf - 1] == DTK_DATE) + { + dterr = DecodeDate(field[i], fmask, + &tmask, &is2digits, tm); + if (dterr) + return dterr; + } + /* embedded decimal and several digits before? */ + else if (flen - strlen(cp) > 2) + { + /* + * Interpret as a concatenated date or time Set + * the type field to allow decoding other fields + * later. Example: 20011223 or 040506 + */ + dterr = DecodeNumberField(flen, field[i], + (fmask | DTK_DATE_M), + &tmask, tm, + fsec, &is2digits); + if (dterr < 0) + return dterr; + ftype[i] = dterr; + } + else + return DTERR_BAD_FORMAT; + } + else if (flen > 4) + { + dterr = DecodeNumberField(flen, field[i], + (fmask | DTK_DATE_M), + &tmask, tm, + fsec, &is2digits); + if (dterr < 0) + return dterr; + ftype[i] = dterr; + } + /* otherwise it is a single date/time field... */ + else + { + dterr = DecodeNumber(flen, field[i], + false, + (fmask | DTK_DATE_M), + &tmask, tm, + fsec, &is2digits); + if (dterr) + return dterr; + } + } + break; + + case DTK_STRING: + case DTK_SPECIAL: + /* timezone abbrevs take precedence over built-in tokens */ + dterr = DecodeTimezoneAbbrev(i, field[i], + &type, &val, &valtz, extra); + if (dterr) + return dterr; + if (type == UNKNOWN_FIELD) + type = DecodeSpecial(i, field[i], &val); + if (type == IGNORE_DTF) + continue; + + tmask = DTK_M(type); + switch (type) + { + case RESERV: + switch (val) + { + case DTK_NOW: + tmask = DTK_TIME_M; + *dtype = DTK_TIME; + GetCurrentTimeUsec(tm, fsec, NULL); + break; + + case DTK_ZULU: + tmask = (DTK_TIME_M | DTK_M(TZ)); + *dtype = DTK_TIME; + tm->tm_hour = 0; + tm->tm_min = 0; + tm->tm_sec = 0; + tm->tm_isdst = 0; + break; + + default: + return DTERR_BAD_FORMAT; + } + + break; + + case DTZMOD: + + /* + * daylight savings time modifier (solves "MET DST" + * syntax) + */ + tmask |= DTK_M(DTZ); + tm->tm_isdst = 1; + if (tzp == NULL) + return DTERR_BAD_FORMAT; + *tzp -= val; + break; + + case DTZ: + + /* + * set mask for TZ here _or_ check for DTZ later when + * getting default timezone + */ + tmask |= DTK_M(TZ); + tm->tm_isdst = 1; + if (tzp == NULL) + return DTERR_BAD_FORMAT; + *tzp = -val; + ftype[i] = DTK_TZ; + break; + + case TZ: + tm->tm_isdst = 0; + if (tzp == NULL) + return DTERR_BAD_FORMAT; + *tzp = -val; + ftype[i] = DTK_TZ; + break; + + case DYNTZ: + tmask |= DTK_M(TZ); + if (tzp == NULL) + return DTERR_BAD_FORMAT; + /* we'll determine the actual offset later */ + abbrevTz = valtz; + abbrev = field[i]; + ftype[i] = DTK_TZ; + break; + + case AMPM: + mer = val; + break; + + case ADBC: + bc = (val == BC); + break; + + case UNITS: + tmask = 0; + /* reject consecutive unhandled units */ + if (ptype != 0) + return DTERR_BAD_FORMAT; + ptype = val; + break; + + case ISOTIME: + tmask = 0; + /* reject consecutive unhandled units */ + if (ptype != 0) + return DTERR_BAD_FORMAT; + ptype = val; + break; + + case UNKNOWN_FIELD: + + /* + * Before giving up and declaring error, check to see + * if it is an all-alpha timezone name. + */ + namedTz = pg_tzset(field[i]); + if (!namedTz) + return DTERR_BAD_FORMAT; + /* we'll apply the zone setting below */ + tmask = DTK_M(TZ); + break; + + default: + return DTERR_BAD_FORMAT; + } + break; + + default: + return DTERR_BAD_FORMAT; + } + + if (tmask & fmask) + return DTERR_BAD_FORMAT; + fmask |= tmask; + } /* end loop over fields */ + + /* reject if prefix type appeared and was never handled */ + if (ptype != 0) + return DTERR_BAD_FORMAT; + + /* do final checking/adjustment of Y/M/D fields */ + dterr = ValidateDate(fmask, isjulian, is2digits, bc, tm); + if (dterr) + return dterr; + + /* handle AM/PM */ + if (mer != HR24 && tm->tm_hour > HOURS_PER_DAY / 2) + return DTERR_FIELD_OVERFLOW; + if (mer == AM && tm->tm_hour == HOURS_PER_DAY / 2) + tm->tm_hour = 0; + else if (mer == PM && tm->tm_hour != HOURS_PER_DAY / 2) + tm->tm_hour += HOURS_PER_DAY / 2; + + /* check for time overflow */ + if (time_overflows(tm->tm_hour, tm->tm_min, tm->tm_sec, *fsec)) + return DTERR_FIELD_OVERFLOW; + + if ((fmask & DTK_TIME_M) != DTK_TIME_M) + return DTERR_BAD_FORMAT; + + /* + * If we had a full timezone spec, compute the offset (we could not do it + * before, because we may need the date to resolve DST status). + */ + if (namedTz != NULL) + { + long int gmtoff; + + /* daylight savings time modifier disallowed with full TZ */ + if (fmask & DTK_M(DTZMOD)) + return DTERR_BAD_FORMAT; + + /* if non-DST zone, we do not need to know the date */ + if (pg_get_timezone_offset(namedTz, &gmtoff)) + { + *tzp = -(int) gmtoff; + } + else + { + /* a date has to be specified */ + if ((fmask & DTK_DATE_M) != DTK_DATE_M) + return DTERR_BAD_FORMAT; + *tzp = DetermineTimeZoneOffset(tm, namedTz); + } + } + + /* + * Likewise, if we had a dynamic timezone abbreviation, resolve it now. + */ + if (abbrevTz != NULL) + { + struct pg_tm tt, + *tmp = &tt; + + /* + * daylight savings time modifier but no standard timezone? then error + */ + if (fmask & DTK_M(DTZMOD)) + return DTERR_BAD_FORMAT; + + if ((fmask & DTK_DATE_M) == 0) + GetCurrentDateTime(tmp); + else + { + /* a date has to be specified */ + if ((fmask & DTK_DATE_M) != DTK_DATE_M) + return DTERR_BAD_FORMAT; + tmp->tm_year = tm->tm_year; + tmp->tm_mon = tm->tm_mon; + tmp->tm_mday = tm->tm_mday; + } + tmp->tm_hour = tm->tm_hour; + tmp->tm_min = tm->tm_min; + tmp->tm_sec = tm->tm_sec; + *tzp = DetermineTimeZoneAbbrevOffset(tmp, abbrev, abbrevTz); + tm->tm_isdst = tmp->tm_isdst; + } + + /* timezone not specified? then use session timezone */ + if (tzp != NULL && !(fmask & DTK_M(TZ))) + { + struct pg_tm tt, + *tmp = &tt; + + /* + * daylight savings time modifier but no standard timezone? then error + */ + if (fmask & DTK_M(DTZMOD)) + return DTERR_BAD_FORMAT; + + if ((fmask & DTK_DATE_M) == 0) + GetCurrentDateTime(tmp); + else + { + /* a date has to be specified */ + if ((fmask & DTK_DATE_M) != DTK_DATE_M) + return DTERR_BAD_FORMAT; + tmp->tm_year = tm->tm_year; + tmp->tm_mon = tm->tm_mon; + tmp->tm_mday = tm->tm_mday; + } + tmp->tm_hour = tm->tm_hour; + tmp->tm_min = tm->tm_min; + tmp->tm_sec = tm->tm_sec; + *tzp = DetermineTimeZoneOffset(tmp, session_timezone); + tm->tm_isdst = tmp->tm_isdst; + } + + return 0; +} + +/* DecodeDate() + * Decode date string which includes delimiters. + * Return 0 if okay, a DTERR code if not. + * + * str: field to be parsed + * fmask: bitmask for field types already seen + * *tmask: receives bitmask for fields found here + * *is2digits: set to true if we find 2-digit year + * *tm: field values are stored into appropriate members of this struct + */ +static int +DecodeDate(char *str, int fmask, int *tmask, bool *is2digits, + struct pg_tm *tm) +{ + fsec_t fsec; + int nf = 0; + int i, + len; + int dterr; + bool haveTextMonth = false; + int type, + val, + dmask = 0; + char *field[MAXDATEFIELDS]; + + *tmask = 0; + + /* parse this string... */ + while (*str != '\0' && nf < MAXDATEFIELDS) + { + /* skip field separators */ + while (*str != '\0' && !isalnum((unsigned char) *str)) + str++; + + if (*str == '\0') + return DTERR_BAD_FORMAT; /* end of string after separator */ + + field[nf] = str; + if (isdigit((unsigned char) *str)) + { + while (isdigit((unsigned char) *str)) + str++; + } + else if (isalpha((unsigned char) *str)) + { + while (isalpha((unsigned char) *str)) + str++; + } + + /* Just get rid of any non-digit, non-alpha characters... */ + if (*str != '\0') + *str++ = '\0'; + nf++; + } + + /* look first for text fields, since that will be unambiguous month */ + for (i = 0; i < nf; i++) + { + if (isalpha((unsigned char) *field[i])) + { + type = DecodeSpecial(i, field[i], &val); + if (type == IGNORE_DTF) + continue; + + dmask = DTK_M(type); + switch (type) + { + case MONTH: + tm->tm_mon = val; + haveTextMonth = true; + break; + + default: + return DTERR_BAD_FORMAT; + } + if (fmask & dmask) + return DTERR_BAD_FORMAT; + + fmask |= dmask; + *tmask |= dmask; + + /* mark this field as being completed */ + field[i] = NULL; + } + } + + /* now pick up remaining numeric fields */ + for (i = 0; i < nf; i++) + { + if (field[i] == NULL) + continue; + + if ((len = strlen(field[i])) <= 0) + return DTERR_BAD_FORMAT; + + dterr = DecodeNumber(len, field[i], haveTextMonth, fmask, + &dmask, tm, + &fsec, is2digits); + if (dterr) + return dterr; + + if (fmask & dmask) + return DTERR_BAD_FORMAT; + + fmask |= dmask; + *tmask |= dmask; + } + + if ((fmask & ~(DTK_M(DOY) | DTK_M(TZ))) != DTK_DATE_M) + return DTERR_BAD_FORMAT; + + /* validation of the field values must wait until ValidateDate() */ + + return 0; +} + +/* ValidateDate() + * Check valid year/month/day values, handle BC and DOY cases + * Return 0 if okay, a DTERR code if not. + */ +int +ValidateDate(int fmask, bool isjulian, bool is2digits, bool bc, + struct pg_tm *tm) +{ + if (fmask & DTK_M(YEAR)) + { + if (isjulian) + { + /* tm_year is correct and should not be touched */ + } + else if (bc) + { + /* there is no year zero in AD/BC notation */ + if (tm->tm_year <= 0) + return DTERR_FIELD_OVERFLOW; + /* internally, we represent 1 BC as year zero, 2 BC as -1, etc */ + tm->tm_year = -(tm->tm_year - 1); + } + else if (is2digits) + { + /* process 1 or 2-digit input as 1970-2069 AD, allow '0' and '00' */ + if (tm->tm_year < 0) /* just paranoia */ + return DTERR_FIELD_OVERFLOW; + if (tm->tm_year < 70) + tm->tm_year += 2000; + else if (tm->tm_year < 100) + tm->tm_year += 1900; + } + else + { + /* there is no year zero in AD/BC notation */ + if (tm->tm_year <= 0) + return DTERR_FIELD_OVERFLOW; + } + } + + /* now that we have correct year, decode DOY */ + if (fmask & DTK_M(DOY)) + { + j2date(date2j(tm->tm_year, 1, 1) + tm->tm_yday - 1, + &tm->tm_year, &tm->tm_mon, &tm->tm_mday); + } + + /* check for valid month */ + if (fmask & DTK_M(MONTH)) + { + if (tm->tm_mon < 1 || tm->tm_mon > MONTHS_PER_YEAR) + return DTERR_MD_FIELD_OVERFLOW; + } + + /* minimal check for valid day */ + if (fmask & DTK_M(DAY)) + { + if (tm->tm_mday < 1 || tm->tm_mday > 31) + return DTERR_MD_FIELD_OVERFLOW; + } + + if ((fmask & DTK_DATE_M) == DTK_DATE_M) + { + /* + * Check for valid day of month, now that we know for sure the month + * and year. Note we don't use MD_FIELD_OVERFLOW here, since it seems + * unlikely that "Feb 29" is a YMD-order error. + */ + if (tm->tm_mday > day_tab[isleap(tm->tm_year)][tm->tm_mon - 1]) + return DTERR_FIELD_OVERFLOW; + } + + return 0; +} + + +/* DecodeTimeCommon() + * Decode time string which includes delimiters. + * Return 0 if okay, a DTERR code if not. + * tmask and itm are output parameters. + * + * This code is shared between the timestamp and interval cases. + * We return a struct pg_itm (of which only the tm_usec, tm_sec, tm_min, + * and tm_hour fields are used) and let the wrapper functions below + * convert and range-check as necessary. + */ +static int +DecodeTimeCommon(char *str, int fmask, int range, + int *tmask, struct pg_itm *itm) +{ + char *cp; + int dterr; + fsec_t fsec = 0; + + *tmask = DTK_TIME_M; + + errno = 0; + itm->tm_hour = strtoi64(str, &cp, 10); + if (errno == ERANGE) + return DTERR_FIELD_OVERFLOW; + if (*cp != ':') + return DTERR_BAD_FORMAT; + errno = 0; + itm->tm_min = strtoint(cp + 1, &cp, 10); + if (errno == ERANGE) + return DTERR_FIELD_OVERFLOW; + if (*cp == '\0') + { + itm->tm_sec = 0; + /* If it's a MINUTE TO SECOND interval, take 2 fields as being mm:ss */ + if (range == (INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND))) + { + if (itm->tm_hour > INT_MAX || itm->tm_hour < INT_MIN) + return DTERR_FIELD_OVERFLOW; + itm->tm_sec = itm->tm_min; + itm->tm_min = (int) itm->tm_hour; + itm->tm_hour = 0; + } + } + else if (*cp == '.') + { + /* always assume mm:ss.sss is MINUTE TO SECOND */ + dterr = ParseFractionalSecond(cp, &fsec); + if (dterr) + return dterr; + if (itm->tm_hour > INT_MAX || itm->tm_hour < INT_MIN) + return DTERR_FIELD_OVERFLOW; + itm->tm_sec = itm->tm_min; + itm->tm_min = (int) itm->tm_hour; + itm->tm_hour = 0; + } + else if (*cp == ':') + { + errno = 0; + itm->tm_sec = strtoint(cp + 1, &cp, 10); + if (errno == ERANGE) + return DTERR_FIELD_OVERFLOW; + if (*cp == '.') + { + dterr = ParseFractionalSecond(cp, &fsec); + if (dterr) + return dterr; + } + else if (*cp != '\0') + return DTERR_BAD_FORMAT; + } + else + return DTERR_BAD_FORMAT; + + /* do a sanity check; but caller must check the range of tm_hour */ + if (itm->tm_hour < 0 || + itm->tm_min < 0 || itm->tm_min > MINS_PER_HOUR - 1 || + itm->tm_sec < 0 || itm->tm_sec > SECS_PER_MINUTE || + fsec < 0 || fsec > USECS_PER_SEC) + return DTERR_FIELD_OVERFLOW; + + itm->tm_usec = (int) fsec; + + return 0; +} + +/* DecodeTime() + * Decode time string which includes delimiters. + * Return 0 if okay, a DTERR code if not. + * + * This version is used for timestamps. The results are returned into + * the tm_hour/tm_min/tm_sec fields of *tm, and microseconds into *fsec. + */ +static int +DecodeTime(char *str, int fmask, int range, + int *tmask, struct pg_tm *tm, fsec_t *fsec) +{ + struct pg_itm itm; + int dterr; + + dterr = DecodeTimeCommon(str, fmask, range, + tmask, &itm); + if (dterr) + return dterr; + + if (itm.tm_hour > INT_MAX) + return DTERR_FIELD_OVERFLOW; + tm->tm_hour = (int) itm.tm_hour; + tm->tm_min = itm.tm_min; + tm->tm_sec = itm.tm_sec; + *fsec = itm.tm_usec; + + return 0; +} + +/* DecodeTimeForInterval() + * Decode time string which includes delimiters. + * Return 0 if okay, a DTERR code if not. + * + * This version is used for intervals. The results are returned into + * itm_in->tm_usec. + */ +static int +DecodeTimeForInterval(char *str, int fmask, int range, + int *tmask, struct pg_itm_in *itm_in) +{ + struct pg_itm itm; + int dterr; + + dterr = DecodeTimeCommon(str, fmask, range, + tmask, &itm); + if (dterr) + return dterr; + + itm_in->tm_usec = itm.tm_usec; + if (!int64_multiply_add(itm.tm_hour, USECS_PER_HOUR, &itm_in->tm_usec) || + !int64_multiply_add(itm.tm_min, USECS_PER_MINUTE, &itm_in->tm_usec) || + !int64_multiply_add(itm.tm_sec, USECS_PER_SEC, &itm_in->tm_usec)) + return DTERR_FIELD_OVERFLOW; + + return 0; +} + + +/* DecodeNumber() + * Interpret plain numeric field as a date value in context. + * Return 0 if okay, a DTERR code if not. + */ +static int +DecodeNumber(int flen, char *str, bool haveTextMonth, int fmask, + int *tmask, struct pg_tm *tm, fsec_t *fsec, bool *is2digits) +{ + int val; + char *cp; + int dterr; + + *tmask = 0; + + errno = 0; + val = strtoint(str, &cp, 10); + if (errno == ERANGE) + return DTERR_FIELD_OVERFLOW; + if (cp == str) + return DTERR_BAD_FORMAT; + + if (*cp == '.') + { + /* + * More than two digits before decimal point? Then could be a date or + * a run-together time: 2001.360 20011225 040506.789 + */ + if (cp - str > 2) + { + dterr = DecodeNumberField(flen, str, + (fmask | DTK_DATE_M), + tmask, tm, + fsec, is2digits); + if (dterr < 0) + return dterr; + return 0; + } + + dterr = ParseFractionalSecond(cp, fsec); + if (dterr) + return dterr; + } + else if (*cp != '\0') + return DTERR_BAD_FORMAT; + + /* Special case for day of year */ + if (flen == 3 && (fmask & DTK_DATE_M) == DTK_M(YEAR) && val >= 1 && + val <= 366) + { + *tmask = (DTK_M(DOY) | DTK_M(MONTH) | DTK_M(DAY)); + tm->tm_yday = val; + /* tm_mon and tm_mday can't actually be set yet ... */ + return 0; + } + + /* Switch based on what we have so far */ + switch (fmask & DTK_DATE_M) + { + case 0: + + /* + * Nothing so far; make a decision about what we think the input + * is. There used to be lots of heuristics here, but the + * consensus now is to be paranoid. It *must* be either + * YYYY-MM-DD (with a more-than-two-digit year field), or the + * field order defined by DateOrder. + */ + if (flen >= 3 || DateOrder == DATEORDER_YMD) + { + *tmask = DTK_M(YEAR); + tm->tm_year = val; + } + else if (DateOrder == DATEORDER_DMY) + { + *tmask = DTK_M(DAY); + tm->tm_mday = val; + } + else + { + *tmask = DTK_M(MONTH); + tm->tm_mon = val; + } + break; + + case (DTK_M(YEAR)): + /* Must be at second field of YY-MM-DD */ + *tmask = DTK_M(MONTH); + tm->tm_mon = val; + break; + + case (DTK_M(MONTH)): + if (haveTextMonth) + { + /* + * We are at the first numeric field of a date that included a + * textual month name. We want to support the variants + * MON-DD-YYYY, DD-MON-YYYY, and YYYY-MON-DD as unambiguous + * inputs. We will also accept MON-DD-YY or DD-MON-YY in + * either DMY or MDY modes, as well as YY-MON-DD in YMD mode. + */ + if (flen >= 3 || DateOrder == DATEORDER_YMD) + { + *tmask = DTK_M(YEAR); + tm->tm_year = val; + } + else + { + *tmask = DTK_M(DAY); + tm->tm_mday = val; + } + } + else + { + /* Must be at second field of MM-DD-YY */ + *tmask = DTK_M(DAY); + tm->tm_mday = val; + } + break; + + case (DTK_M(YEAR) | DTK_M(MONTH)): + if (haveTextMonth) + { + /* Need to accept DD-MON-YYYY even in YMD mode */ + if (flen >= 3 && *is2digits) + { + /* Guess that first numeric field is day was wrong */ + *tmask = DTK_M(DAY); /* YEAR is already set */ + tm->tm_mday = tm->tm_year; + tm->tm_year = val; + *is2digits = false; + } + else + { + *tmask = DTK_M(DAY); + tm->tm_mday = val; + } + } + else + { + /* Must be at third field of YY-MM-DD */ + *tmask = DTK_M(DAY); + tm->tm_mday = val; + } + break; + + case (DTK_M(DAY)): + /* Must be at second field of DD-MM-YY */ + *tmask = DTK_M(MONTH); + tm->tm_mon = val; + break; + + case (DTK_M(MONTH) | DTK_M(DAY)): + /* Must be at third field of DD-MM-YY or MM-DD-YY */ + *tmask = DTK_M(YEAR); + tm->tm_year = val; + break; + + case (DTK_M(YEAR) | DTK_M(MONTH) | DTK_M(DAY)): + /* we have all the date, so it must be a time field */ + dterr = DecodeNumberField(flen, str, fmask, + tmask, tm, + fsec, is2digits); + if (dterr < 0) + return dterr; + return 0; + + default: + /* Anything else is bogus input */ + return DTERR_BAD_FORMAT; + } + + /* + * When processing a year field, mark it for adjustment if it's only one + * or two digits. + */ + if (*tmask == DTK_M(YEAR)) + *is2digits = (flen <= 2); + + return 0; +} + + +/* DecodeNumberField() + * Interpret numeric string as a concatenated date or time field. + * Return a DTK token (>= 0) if successful, a DTERR code (< 0) if not. + * + * Use the context of previously decoded fields to help with + * the interpretation. + */ +static int +DecodeNumberField(int len, char *str, int fmask, + int *tmask, struct pg_tm *tm, fsec_t *fsec, bool *is2digits) +{ + char *cp; + + /* + * Have a decimal point? Then this is a date or something with a seconds + * field... + */ + if ((cp = strchr(str, '.')) != NULL) + { + /* + * Can we use ParseFractionalSecond here? Not clear whether trailing + * junk should be rejected ... + */ + if (cp[1] == '\0') + { + /* avoid assuming that strtod will accept "." */ + *fsec = 0; + } + else + { + double frac; + + errno = 0; + frac = strtod(cp, NULL); + if (errno != 0) + return DTERR_BAD_FORMAT; + *fsec = rint(frac * 1000000); + } + /* Now truncate off the fraction for further processing */ + *cp = '\0'; + len = strlen(str); + } + /* No decimal point and no complete date yet? */ + else if ((fmask & DTK_DATE_M) != DTK_DATE_M) + { + if (len >= 6) + { + *tmask = DTK_DATE_M; + + /* + * Start from end and consider first 2 as Day, next 2 as Month, + * and the rest as Year. + */ + tm->tm_mday = atoi(str + (len - 2)); + *(str + (len - 2)) = '\0'; + tm->tm_mon = atoi(str + (len - 4)); + *(str + (len - 4)) = '\0'; + tm->tm_year = atoi(str); + if ((len - 4) == 2) + *is2digits = true; + + return DTK_DATE; + } + } + + /* not all time fields are specified? */ + if ((fmask & DTK_TIME_M) != DTK_TIME_M) + { + /* hhmmss */ + if (len == 6) + { + *tmask = DTK_TIME_M; + tm->tm_sec = atoi(str + 4); + *(str + 4) = '\0'; + tm->tm_min = atoi(str + 2); + *(str + 2) = '\0'; + tm->tm_hour = atoi(str); + + return DTK_TIME; + } + /* hhmm? */ + else if (len == 4) + { + *tmask = DTK_TIME_M; + tm->tm_sec = 0; + tm->tm_min = atoi(str + 2); + *(str + 2) = '\0'; + tm->tm_hour = atoi(str); + + return DTK_TIME; + } + } + + return DTERR_BAD_FORMAT; +} + + +/* DecodeTimezone() + * Interpret string as a numeric timezone. + * + * Return 0 if okay (and set *tzp), a DTERR code if not okay. + */ +int +DecodeTimezone(const char *str, int *tzp) +{ + int tz; + int hr, + min, + sec = 0; + char *cp; + + /* leading character must be "+" or "-" */ + if (*str != '+' && *str != '-') + return DTERR_BAD_FORMAT; + + errno = 0; + hr = strtoint(str + 1, &cp, 10); + if (errno == ERANGE) + return DTERR_TZDISP_OVERFLOW; + + /* explicit delimiter? */ + if (*cp == ':') + { + errno = 0; + min = strtoint(cp + 1, &cp, 10); + if (errno == ERANGE) + return DTERR_TZDISP_OVERFLOW; + if (*cp == ':') + { + errno = 0; + sec = strtoint(cp + 1, &cp, 10); + if (errno == ERANGE) + return DTERR_TZDISP_OVERFLOW; + } + } + /* otherwise, might have run things together... */ + else if (*cp == '\0' && strlen(str) > 3) + { + min = hr % 100; + hr = hr / 100; + /* we could, but don't, support a run-together hhmmss format */ + } + else + min = 0; + + /* Range-check the values; see notes in datatype/timestamp.h */ + if (hr < 0 || hr > MAX_TZDISP_HOUR) + return DTERR_TZDISP_OVERFLOW; + if (min < 0 || min >= MINS_PER_HOUR) + return DTERR_TZDISP_OVERFLOW; + if (sec < 0 || sec >= SECS_PER_MINUTE) + return DTERR_TZDISP_OVERFLOW; + + tz = (hr * MINS_PER_HOUR + min) * SECS_PER_MINUTE + sec; + if (*str == '-') + tz = -tz; + + *tzp = -tz; + + if (*cp != '\0') + return DTERR_BAD_FORMAT; + + return 0; +} + + +/* DecodeTimezoneAbbrev() + * Interpret string as a timezone abbreviation, if possible. + * + * Sets *ftype to an abbreviation type (TZ, DTZ, or DYNTZ), or UNKNOWN_FIELD if + * string is not any known abbreviation. On success, set *offset and *tz to + * represent the UTC offset (for TZ or DTZ) or underlying zone (for DYNTZ). + * Note that full timezone names (such as America/New_York) are not handled + * here, mostly for historical reasons. + * + * The function result is 0 or a DTERR code; in the latter case, *extra + * is filled as needed. Note that unknown-abbreviation is not considered + * an error case. Also note that many callers assume that the DTERR code + * is one that DateTimeParseError does not require "str" or "datatype" + * strings for. + * + * Given string must be lowercased already. + * + * Implement a cache lookup since it is likely that dates + * will be related in format. + */ +int +DecodeTimezoneAbbrev(int field, const char *lowtoken, + int *ftype, int *offset, pg_tz **tz, + DateTimeErrorExtra *extra) +{ + const datetkn *tp; + + tp = abbrevcache[field]; + /* use strncmp so that we match truncated tokens */ + if (tp == NULL || strncmp(lowtoken, tp->token, TOKMAXLEN) != 0) + { + if (zoneabbrevtbl) + tp = datebsearch(lowtoken, zoneabbrevtbl->abbrevs, + zoneabbrevtbl->numabbrevs); + else + tp = NULL; + } + if (tp == NULL) + { + *ftype = UNKNOWN_FIELD; + *offset = 0; + *tz = NULL; + } + else + { + abbrevcache[field] = tp; + *ftype = tp->type; + if (tp->type == DYNTZ) + { + *offset = 0; + *tz = FetchDynamicTimeZone(zoneabbrevtbl, tp, extra); + if (*tz == NULL) + return DTERR_BAD_ZONE_ABBREV; + } + else + { + *offset = tp->value; + *tz = NULL; + } + } + + return 0; +} + + +/* DecodeSpecial() + * Decode text string using lookup table. + * + * Recognizes the keywords listed in datetktbl. + * Note: at one time this would also recognize timezone abbreviations, + * but no more; use DecodeTimezoneAbbrev for that. + * + * Given string must be lowercased already. + * + * Implement a cache lookup since it is likely that dates + * will be related in format. + */ +int +DecodeSpecial(int field, const char *lowtoken, int *val) +{ + int type; + const datetkn *tp; + + tp = datecache[field]; + /* use strncmp so that we match truncated tokens */ + if (tp == NULL || strncmp(lowtoken, tp->token, TOKMAXLEN) != 0) + { + tp = datebsearch(lowtoken, datetktbl, szdatetktbl); + } + if (tp == NULL) + { + type = UNKNOWN_FIELD; + *val = 0; + } + else + { + datecache[field] = tp; + type = tp->type; + *val = tp->value; + } + + return type; +} + + +/* DecodeTimezoneName() + * Interpret string as a timezone abbreviation or name. + * Throw error if the name is not recognized. + * + * The return value indicates what kind of zone identifier it is: + * TZNAME_FIXED_OFFSET: fixed offset from UTC + * TZNAME_DYNTZ: dynamic timezone abbreviation + * TZNAME_ZONE: full tzdb zone name + * + * For TZNAME_FIXED_OFFSET, *offset receives the UTC offset (in seconds, + * with ISO sign convention: positive is east of Greenwich). + * For the other two cases, *tz receives the timezone struct representing + * the zone name or the abbreviation's underlying zone. + */ +int +DecodeTimezoneName(const char *tzname, int *offset, pg_tz **tz) +{ + char *lowzone; + int dterr, + type; + DateTimeErrorExtra extra; + + /* + * First we look in the timezone abbreviation table (to handle cases like + * "EST"), and if that fails, we look in the timezone database (to handle + * cases like "America/New_York"). This matches the order in which + * timestamp input checks the cases; it's important because the timezone + * database unwisely uses a few zone names that are identical to offset + * abbreviations. + */ + + /* DecodeTimezoneAbbrev requires lowercase input */ + lowzone = downcase_truncate_identifier(tzname, + strlen(tzname), + false); + + dterr = DecodeTimezoneAbbrev(0, lowzone, &type, offset, tz, &extra); + if (dterr) + DateTimeParseError(dterr, &extra, NULL, NULL, NULL); + + if (type == TZ || type == DTZ) + { + /* fixed-offset abbreviation, return the offset */ + return TZNAME_FIXED_OFFSET; + } + else if (type == DYNTZ) + { + /* dynamic-offset abbreviation, return its referenced timezone */ + return TZNAME_DYNTZ; + } + else + { + /* try it as a full zone name */ + *tz = pg_tzset(tzname); + if (*tz == NULL) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("time zone \"%s\" not recognized", tzname))); + return TZNAME_ZONE; + } +} + +/* DecodeTimezoneNameToTz() + * Interpret string as a timezone abbreviation or name. + * Throw error if the name is not recognized. + * + * This is a simple wrapper for DecodeTimezoneName that produces a pg_tz * + * result in all cases. + */ +pg_tz * +DecodeTimezoneNameToTz(const char *tzname) +{ + pg_tz *result; + int offset; + + if (DecodeTimezoneName(tzname, &offset, &result) == TZNAME_FIXED_OFFSET) + { + /* fixed-offset abbreviation, get a pg_tz descriptor for that */ + result = pg_tzset_offset(-offset); /* flip to POSIX sign convention */ + } + return result; +} + + +/* ClearPgItmIn + * + * Zero out a pg_itm_in + */ +static inline void +ClearPgItmIn(struct pg_itm_in *itm_in) +{ + itm_in->tm_usec = 0; + itm_in->tm_mday = 0; + itm_in->tm_mon = 0; + itm_in->tm_year = 0; +} + + +/* DecodeInterval() + * Interpret previously parsed fields for general time interval. + * Returns 0 if successful, DTERR code if bogus input detected. + * dtype and itm_in are output parameters. + * + * Allow "date" field DTK_DATE since this could be just + * an unsigned floating point number. - thomas 1997-11-16 + * + * Allow ISO-style time span, with implicit units on number of days + * preceding an hh:mm:ss field. - thomas 1998-04-30 + */ +int +DecodeInterval(char **field, int *ftype, int nf, int range, + int *dtype, struct pg_itm_in *itm_in) +{ + bool force_negative = false; + bool is_before = false; + char *cp; + int fmask = 0, + tmask, + type, + uval; + int i; + int dterr; + int64 val; + double fval; + + *dtype = DTK_DELTA; + type = IGNORE_DTF; + ClearPgItmIn(itm_in); + + /*---------- + * The SQL standard defines the interval literal + * '-1 1:00:00' + * to mean "negative 1 days and negative 1 hours", while Postgres + * traditionally treats this as meaning "negative 1 days and positive + * 1 hours". In SQL_STANDARD intervalstyle, we apply the leading sign + * to all fields if there are no other explicit signs. + * + * We leave the signs alone if there are additional explicit signs. + * This protects us against misinterpreting postgres-style dump output, + * since the postgres-style output code has always put an explicit sign on + * all fields following a negative field. But note that SQL-spec output + * is ambiguous and can be misinterpreted on load! (So it's best practice + * to dump in postgres style, not SQL style.) + *---------- + */ + if (IntervalStyle == INTSTYLE_SQL_STANDARD && nf > 0 && *field[0] == '-') + { + force_negative = true; + /* Check for additional explicit signs */ + for (i = 1; i < nf; i++) + { + if (*field[i] == '-' || *field[i] == '+') + { + force_negative = false; + break; + } + } + } + + /* read through list backwards to pick up units before values */ + for (i = nf - 1; i >= 0; i--) + { + switch (ftype[i]) + { + case DTK_TIME: + dterr = DecodeTimeForInterval(field[i], fmask, range, + &tmask, itm_in); + if (dterr) + return dterr; + if (force_negative && + itm_in->tm_usec > 0) + itm_in->tm_usec = -itm_in->tm_usec; + type = DTK_DAY; + break; + + case DTK_TZ: + + /* + * Timezone means a token with a leading sign character and at + * least one digit; there could be ':', '.', '-' embedded in + * it as well. + */ + Assert(*field[i] == '-' || *field[i] == '+'); + + /* + * Check for signed hh:mm or hh:mm:ss. If so, process exactly + * like DTK_TIME case above, plus handling the sign. + */ + if (strchr(field[i] + 1, ':') != NULL && + DecodeTimeForInterval(field[i] + 1, fmask, range, + &tmask, itm_in) == 0) + { + if (*field[i] == '-') + { + /* flip the sign on time field */ + if (itm_in->tm_usec == PG_INT64_MIN) + return DTERR_FIELD_OVERFLOW; + itm_in->tm_usec = -itm_in->tm_usec; + } + + if (force_negative && + itm_in->tm_usec > 0) + itm_in->tm_usec = -itm_in->tm_usec; + + /* + * Set the next type to be a day, if units are not + * specified. This handles the case of '1 +02:03' since we + * are reading right to left. + */ + type = DTK_DAY; + break; + } + + /* + * Otherwise, fall through to DTK_NUMBER case, which can + * handle signed float numbers and signed year-month values. + */ + + /* FALLTHROUGH */ + + case DTK_DATE: + case DTK_NUMBER: + if (type == IGNORE_DTF) + { + /* use typmod to decide what rightmost field is */ + switch (range) + { + case INTERVAL_MASK(YEAR): + type = DTK_YEAR; + break; + case INTERVAL_MASK(MONTH): + case INTERVAL_MASK(YEAR) | INTERVAL_MASK(MONTH): + type = DTK_MONTH; + break; + case INTERVAL_MASK(DAY): + type = DTK_DAY; + break; + case INTERVAL_MASK(HOUR): + case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR): + type = DTK_HOUR; + break; + case INTERVAL_MASK(MINUTE): + case INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE): + case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE): + type = DTK_MINUTE; + break; + case INTERVAL_MASK(SECOND): + case INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND): + case INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND): + case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND): + type = DTK_SECOND; + break; + default: + type = DTK_SECOND; + break; + } + } + + errno = 0; + val = strtoi64(field[i], &cp, 10); + if (errno == ERANGE) + return DTERR_FIELD_OVERFLOW; + + if (*cp == '-') + { + /* SQL "years-months" syntax */ + int val2; + + val2 = strtoint(cp + 1, &cp, 10); + if (errno == ERANGE || val2 < 0 || val2 >= MONTHS_PER_YEAR) + return DTERR_FIELD_OVERFLOW; + if (*cp != '\0') + return DTERR_BAD_FORMAT; + type = DTK_MONTH; + if (*field[i] == '-') + val2 = -val2; + if (pg_mul_s64_overflow(val, MONTHS_PER_YEAR, &val)) + return DTERR_FIELD_OVERFLOW; + if (pg_add_s64_overflow(val, val2, &val)) + return DTERR_FIELD_OVERFLOW; + fval = 0; + } + else if (*cp == '.') + { + dterr = ParseFraction(cp, &fval); + if (dterr) + return dterr; + if (*field[i] == '-') + fval = -fval; + } + else if (*cp == '\0') + fval = 0; + else + return DTERR_BAD_FORMAT; + + tmask = 0; /* DTK_M(type); */ + + if (force_negative) + { + /* val and fval should be of same sign, but test anyway */ + if (val > 0) + val = -val; + if (fval > 0) + fval = -fval; + } + + switch (type) + { + case DTK_MICROSEC: + if (!AdjustMicroseconds(val, fval, 1, itm_in)) + return DTERR_FIELD_OVERFLOW; + tmask = DTK_M(MICROSECOND); + break; + + case DTK_MILLISEC: + if (!AdjustMicroseconds(val, fval, 1000, itm_in)) + return DTERR_FIELD_OVERFLOW; + tmask = DTK_M(MILLISECOND); + break; + + case DTK_SECOND: + if (!AdjustMicroseconds(val, fval, USECS_PER_SEC, itm_in)) + return DTERR_FIELD_OVERFLOW; + + /* + * If any subseconds were specified, consider this + * microsecond and millisecond input as well. + */ + if (fval == 0) + tmask = DTK_M(SECOND); + else + tmask = DTK_ALL_SECS_M; + break; + + case DTK_MINUTE: + if (!AdjustMicroseconds(val, fval, USECS_PER_MINUTE, itm_in)) + return DTERR_FIELD_OVERFLOW; + tmask = DTK_M(MINUTE); + break; + + case DTK_HOUR: + if (!AdjustMicroseconds(val, fval, USECS_PER_HOUR, itm_in)) + return DTERR_FIELD_OVERFLOW; + tmask = DTK_M(HOUR); + type = DTK_DAY; /* set for next field */ + break; + + case DTK_DAY: + if (!AdjustDays(val, 1, itm_in) || + !AdjustFractMicroseconds(fval, USECS_PER_DAY, itm_in)) + return DTERR_FIELD_OVERFLOW; + tmask = DTK_M(DAY); + break; + + case DTK_WEEK: + if (!AdjustDays(val, 7, itm_in) || + !AdjustFractDays(fval, 7, itm_in)) + return DTERR_FIELD_OVERFLOW; + tmask = DTK_M(WEEK); + break; + + case DTK_MONTH: + if (!AdjustMonths(val, itm_in) || + !AdjustFractDays(fval, DAYS_PER_MONTH, itm_in)) + return DTERR_FIELD_OVERFLOW; + tmask = DTK_M(MONTH); + break; + + case DTK_YEAR: + if (!AdjustYears(val, 1, itm_in) || + !AdjustFractYears(fval, 1, itm_in)) + return DTERR_FIELD_OVERFLOW; + tmask = DTK_M(YEAR); + break; + + case DTK_DECADE: + if (!AdjustYears(val, 10, itm_in) || + !AdjustFractYears(fval, 10, itm_in)) + return DTERR_FIELD_OVERFLOW; + tmask = DTK_M(DECADE); + break; + + case DTK_CENTURY: + if (!AdjustYears(val, 100, itm_in) || + !AdjustFractYears(fval, 100, itm_in)) + return DTERR_FIELD_OVERFLOW; + tmask = DTK_M(CENTURY); + break; + + case DTK_MILLENNIUM: + if (!AdjustYears(val, 1000, itm_in) || + !AdjustFractYears(fval, 1000, itm_in)) + return DTERR_FIELD_OVERFLOW; + tmask = DTK_M(MILLENNIUM); + break; + + default: + return DTERR_BAD_FORMAT; + } + break; + + case DTK_STRING: + case DTK_SPECIAL: + type = DecodeUnits(i, field[i], &uval); + if (type == IGNORE_DTF) + continue; + + tmask = 0; /* DTK_M(type); */ + switch (type) + { + case UNITS: + type = uval; + break; + + case AGO: + is_before = true; + type = uval; + break; + + case RESERV: + tmask = (DTK_DATE_M | DTK_TIME_M); + *dtype = uval; + break; + + default: + return DTERR_BAD_FORMAT; + } + break; + + default: + return DTERR_BAD_FORMAT; + } + + if (tmask & fmask) + return DTERR_BAD_FORMAT; + fmask |= tmask; + } + + /* ensure that at least one time field has been found */ + if (fmask == 0) + return DTERR_BAD_FORMAT; + + /* finally, AGO negates everything */ + if (is_before) + { + if (itm_in->tm_usec == PG_INT64_MIN || + itm_in->tm_mday == INT_MIN || + itm_in->tm_mon == INT_MIN || + itm_in->tm_year == INT_MIN) + return DTERR_FIELD_OVERFLOW; + + itm_in->tm_usec = -itm_in->tm_usec; + itm_in->tm_mday = -itm_in->tm_mday; + itm_in->tm_mon = -itm_in->tm_mon; + itm_in->tm_year = -itm_in->tm_year; + } + + return 0; +} + + +/* + * Helper functions to avoid duplicated code in DecodeISO8601Interval. + * + * Parse a decimal value and break it into integer and fractional parts. + * Set *endptr to end+1 of the parsed substring. + * Returns 0 or DTERR code. + */ +static int +ParseISO8601Number(char *str, char **endptr, int64 *ipart, double *fpart) +{ + double val; + + /* + * Historically this has accepted anything that strtod() would take, + * notably including "e" notation, so continue doing that. This is + * slightly annoying because the precision of double is less than that of + * int64, so we would lose accuracy for inputs larger than 2^53 or so. + * However, historically we rejected inputs outside the int32 range, + * making that concern moot. What we do now is reject abs(val) above + * 1.0e15 (a round number a bit less than 2^50), so that any accepted + * value will have an exact integer part, and thereby a fraction part with + * abs(*fpart) less than 1. In the absence of field complaints it doesn't + * seem worth working harder. + */ + if (!(isdigit((unsigned char) *str) || *str == '-' || *str == '.')) + return DTERR_BAD_FORMAT; + errno = 0; + val = strtod(str, endptr); + /* did we not see anything that looks like a double? */ + if (*endptr == str || errno != 0) + return DTERR_BAD_FORMAT; + /* watch out for overflow, including infinities; reject NaN too */ + if (isnan(val) || val < -1.0e15 || val > 1.0e15) + return DTERR_FIELD_OVERFLOW; + /* be very sure we truncate towards zero (cf dtrunc()) */ + if (val >= 0) + *ipart = (int64) floor(val); + else + *ipart = (int64) -floor(-val); + *fpart = val - *ipart; + /* Callers expect this to hold */ + Assert(*fpart > -1.0 && *fpart < 1.0); + return 0; +} + +/* + * Determine number of integral digits in a valid ISO 8601 number field + * (we should ignore sign and any fraction part) + */ +static int +ISO8601IntegerWidth(char *fieldstart) +{ + /* We might have had a leading '-' */ + if (*fieldstart == '-') + fieldstart++; + return strspn(fieldstart, "0123456789"); +} + + +/* DecodeISO8601Interval() + * Decode an ISO 8601 time interval of the "format with designators" + * (section 4.4.3.2) or "alternative format" (section 4.4.3.3) + * Examples: P1D for 1 day + * PT1H for 1 hour + * P2Y6M7DT1H30M for 2 years, 6 months, 7 days 1 hour 30 min + * P0002-06-07T01:30:00 the same value in alternative format + * + * Returns 0 if successful, DTERR code if bogus input detected. + * Note: error code should be DTERR_BAD_FORMAT if input doesn't look like + * ISO8601, otherwise this could cause unexpected error messages. + * dtype and itm_in are output parameters. + * + * A couple exceptions from the spec: + * - a week field ('W') may coexist with other units + * - allows decimals in fields other than the least significant unit. + */ +int +DecodeISO8601Interval(char *str, + int *dtype, struct pg_itm_in *itm_in) +{ + bool datepart = true; + bool havefield = false; + + *dtype = DTK_DELTA; + ClearPgItmIn(itm_in); + + if (strlen(str) < 2 || str[0] != 'P') + return DTERR_BAD_FORMAT; + + str++; + while (*str) + { + char *fieldstart; + int64 val; + double fval; + char unit; + int dterr; + + if (*str == 'T') /* T indicates the beginning of the time part */ + { + datepart = false; + havefield = false; + str++; + continue; + } + + fieldstart = str; + dterr = ParseISO8601Number(str, &str, &val, &fval); + if (dterr) + return dterr; + + /* + * Note: we could step off the end of the string here. Code below + * *must* exit the loop if unit == '\0'. + */ + unit = *str++; + + if (datepart) + { + switch (unit) /* before T: Y M W D */ + { + case 'Y': + if (!AdjustYears(val, 1, itm_in) || + !AdjustFractYears(fval, 1, itm_in)) + return DTERR_FIELD_OVERFLOW; + break; + case 'M': + if (!AdjustMonths(val, itm_in) || + !AdjustFractDays(fval, DAYS_PER_MONTH, itm_in)) + return DTERR_FIELD_OVERFLOW; + break; + case 'W': + if (!AdjustDays(val, 7, itm_in) || + !AdjustFractDays(fval, 7, itm_in)) + return DTERR_FIELD_OVERFLOW; + break; + case 'D': + if (!AdjustDays(val, 1, itm_in) || + !AdjustFractMicroseconds(fval, USECS_PER_DAY, itm_in)) + return DTERR_FIELD_OVERFLOW; + break; + case 'T': /* ISO 8601 4.4.3.3 Alternative Format / Basic */ + case '\0': + if (ISO8601IntegerWidth(fieldstart) == 8 && !havefield) + { + if (!AdjustYears(val / 10000, 1, itm_in) || + !AdjustMonths((val / 100) % 100, itm_in) || + !AdjustDays(val % 100, 1, itm_in) || + !AdjustFractMicroseconds(fval, USECS_PER_DAY, itm_in)) + return DTERR_FIELD_OVERFLOW; + if (unit == '\0') + return 0; + datepart = false; + havefield = false; + continue; + } + /* Else fall through to extended alternative format */ + /* FALLTHROUGH */ + case '-': /* ISO 8601 4.4.3.3 Alternative Format, + * Extended */ + if (havefield) + return DTERR_BAD_FORMAT; + + if (!AdjustYears(val, 1, itm_in) || + !AdjustFractYears(fval, 1, itm_in)) + return DTERR_FIELD_OVERFLOW; + if (unit == '\0') + return 0; + if (unit == 'T') + { + datepart = false; + havefield = false; + continue; + } + + dterr = ParseISO8601Number(str, &str, &val, &fval); + if (dterr) + return dterr; + if (!AdjustMonths(val, itm_in) || + !AdjustFractDays(fval, DAYS_PER_MONTH, itm_in)) + return DTERR_FIELD_OVERFLOW; + if (*str == '\0') + return 0; + if (*str == 'T') + { + datepart = false; + havefield = false; + continue; + } + if (*str != '-') + return DTERR_BAD_FORMAT; + str++; + + dterr = ParseISO8601Number(str, &str, &val, &fval); + if (dterr) + return dterr; + if (!AdjustDays(val, 1, itm_in) || + !AdjustFractMicroseconds(fval, USECS_PER_DAY, itm_in)) + return DTERR_FIELD_OVERFLOW; + if (*str == '\0') + return 0; + if (*str == 'T') + { + datepart = false; + havefield = false; + continue; + } + return DTERR_BAD_FORMAT; + default: + /* not a valid date unit suffix */ + return DTERR_BAD_FORMAT; + } + } + else + { + switch (unit) /* after T: H M S */ + { + case 'H': + if (!AdjustMicroseconds(val, fval, USECS_PER_HOUR, itm_in)) + return DTERR_FIELD_OVERFLOW; + break; + case 'M': + if (!AdjustMicroseconds(val, fval, USECS_PER_MINUTE, itm_in)) + return DTERR_FIELD_OVERFLOW; + break; + case 'S': + if (!AdjustMicroseconds(val, fval, USECS_PER_SEC, itm_in)) + return DTERR_FIELD_OVERFLOW; + break; + case '\0': /* ISO 8601 4.4.3.3 Alternative Format */ + if (ISO8601IntegerWidth(fieldstart) == 6 && !havefield) + { + if (!AdjustMicroseconds(val / 10000, 0, USECS_PER_HOUR, itm_in) || + !AdjustMicroseconds((val / 100) % 100, 0, USECS_PER_MINUTE, itm_in) || + !AdjustMicroseconds(val % 100, 0, USECS_PER_SEC, itm_in) || + !AdjustFractMicroseconds(fval, 1, itm_in)) + return DTERR_FIELD_OVERFLOW; + return 0; + } + /* Else fall through to extended alternative format */ + /* FALLTHROUGH */ + case ':': /* ISO 8601 4.4.3.3 Alternative Format, + * Extended */ + if (havefield) + return DTERR_BAD_FORMAT; + + if (!AdjustMicroseconds(val, fval, USECS_PER_HOUR, itm_in)) + return DTERR_FIELD_OVERFLOW; + if (unit == '\0') + return 0; + + dterr = ParseISO8601Number(str, &str, &val, &fval); + if (dterr) + return dterr; + if (!AdjustMicroseconds(val, fval, USECS_PER_MINUTE, itm_in)) + return DTERR_FIELD_OVERFLOW; + if (*str == '\0') + return 0; + if (*str != ':') + return DTERR_BAD_FORMAT; + str++; + + dterr = ParseISO8601Number(str, &str, &val, &fval); + if (dterr) + return dterr; + if (!AdjustMicroseconds(val, fval, USECS_PER_SEC, itm_in)) + return DTERR_FIELD_OVERFLOW; + if (*str == '\0') + return 0; + return DTERR_BAD_FORMAT; + + default: + /* not a valid time unit suffix */ + return DTERR_BAD_FORMAT; + } + } + + havefield = true; + } + + return 0; +} + + +/* DecodeUnits() + * Decode text string using lookup table. + * + * This routine recognizes keywords associated with time interval units. + * + * Given string must be lowercased already. + * + * Implement a cache lookup since it is likely that dates + * will be related in format. + */ +int +DecodeUnits(int field, const char *lowtoken, int *val) +{ + int type; + const datetkn *tp; + + tp = deltacache[field]; + /* use strncmp so that we match truncated tokens */ + if (tp == NULL || strncmp(lowtoken, tp->token, TOKMAXLEN) != 0) + { + tp = datebsearch(lowtoken, deltatktbl, szdeltatktbl); + } + if (tp == NULL) + { + type = UNKNOWN_FIELD; + *val = 0; + } + else + { + deltacache[field] = tp; + type = tp->type; + *val = tp->value; + } + + return type; +} /* DecodeUnits() */ + +/* + * Report an error detected by one of the datetime input processing routines. + * + * dterr is the error code, and *extra contains any auxiliary info we need + * for the error report. extra can be NULL if not needed for the particular + * dterr value. + * + * str is the original input string, and datatype is the name of the datatype + * we were trying to accept. (For some DTERR codes, these are not used and + * can be NULL.) + * + * If escontext points to an ErrorSaveContext node, that is filled instead + * of throwing an error. + * + * Note: it might seem useless to distinguish DTERR_INTERVAL_OVERFLOW and + * DTERR_TZDISP_OVERFLOW from DTERR_FIELD_OVERFLOW, but SQL99 mandates three + * separate SQLSTATE codes, so ... + */ +void +DateTimeParseError(int dterr, DateTimeErrorExtra *extra, + const char *str, const char *datatype, + Node *escontext) +{ + switch (dterr) + { + case DTERR_FIELD_OVERFLOW: + errsave(escontext, + (errcode(ERRCODE_DATETIME_FIELD_OVERFLOW), + errmsg("date/time field value out of range: \"%s\"", + str))); + break; + case DTERR_MD_FIELD_OVERFLOW: + /* <nanny>same as above, but add hint about DateStyle</nanny> */ + errsave(escontext, + (errcode(ERRCODE_DATETIME_FIELD_OVERFLOW), + errmsg("date/time field value out of range: \"%s\"", + str), + errhint("Perhaps you need a different \"datestyle\" setting."))); + break; + case DTERR_INTERVAL_OVERFLOW: + errsave(escontext, + (errcode(ERRCODE_INTERVAL_FIELD_OVERFLOW), + errmsg("interval field value out of range: \"%s\"", + str))); + break; + case DTERR_TZDISP_OVERFLOW: + errsave(escontext, + (errcode(ERRCODE_INVALID_TIME_ZONE_DISPLACEMENT_VALUE), + errmsg("time zone displacement out of range: \"%s\"", + str))); + break; + case DTERR_BAD_TIMEZONE: + errsave(escontext, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("time zone \"%s\" not recognized", + extra->dtee_timezone))); + break; + case DTERR_BAD_ZONE_ABBREV: + errsave(escontext, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("time zone \"%s\" not recognized", + extra->dtee_timezone), + errdetail("This time zone name appears in the configuration file for time zone abbreviation \"%s\".", + extra->dtee_abbrev))); + break; + case DTERR_BAD_FORMAT: + default: + errsave(escontext, + (errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("invalid input syntax for type %s: \"%s\"", + datatype, str))); + break; + } +} + +/* datebsearch() + * Binary search -- from Knuth (6.2.1) Algorithm B. Special case like this + * is WAY faster than the generic bsearch(). + */ +static const datetkn * +datebsearch(const char *key, const datetkn *base, int nel) +{ + if (nel > 0) + { + const datetkn *last = base + nel - 1, + *position; + int result; + + while (last >= base) + { + position = base + ((last - base) >> 1); + /* precheck the first character for a bit of extra speed */ + result = (int) key[0] - (int) position->token[0]; + if (result == 0) + { + /* use strncmp so that we match truncated tokens */ + result = strncmp(key, position->token, TOKMAXLEN); + if (result == 0) + return position; + } + if (result < 0) + last = position - 1; + else + base = position + 1; + } + } + return NULL; +} + +/* EncodeTimezone() + * Copies representation of a numeric timezone offset to str. + * + * Returns a pointer to the new end of string. No NUL terminator is put + * there; callers are responsible for NUL terminating str themselves. + */ +static char * +EncodeTimezone(char *str, int tz, int style) +{ + int hour, + min, + sec; + + sec = abs(tz); + min = sec / SECS_PER_MINUTE; + sec -= min * SECS_PER_MINUTE; + hour = min / MINS_PER_HOUR; + min -= hour * MINS_PER_HOUR; + + /* TZ is negated compared to sign we wish to display ... */ + *str++ = (tz <= 0 ? '+' : '-'); + + if (sec != 0) + { + str = pg_ultostr_zeropad(str, hour, 2); + *str++ = ':'; + str = pg_ultostr_zeropad(str, min, 2); + *str++ = ':'; + str = pg_ultostr_zeropad(str, sec, 2); + } + else if (min != 0 || style == USE_XSD_DATES) + { + str = pg_ultostr_zeropad(str, hour, 2); + *str++ = ':'; + str = pg_ultostr_zeropad(str, min, 2); + } + else + str = pg_ultostr_zeropad(str, hour, 2); + return str; +} + +/* EncodeDateOnly() + * Encode date as local time. + */ +void +EncodeDateOnly(struct pg_tm *tm, int style, char *str) +{ + Assert(tm->tm_mon >= 1 && tm->tm_mon <= MONTHS_PER_YEAR); + + switch (style) + { + case USE_ISO_DATES: + case USE_XSD_DATES: + /* compatible with ISO date formats */ + str = pg_ultostr_zeropad(str, + (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), 4); + *str++ = '-'; + str = pg_ultostr_zeropad(str, tm->tm_mon, 2); + *str++ = '-'; + str = pg_ultostr_zeropad(str, tm->tm_mday, 2); + break; + + case USE_SQL_DATES: + /* compatible with Oracle/Ingres date formats */ + if (DateOrder == DATEORDER_DMY) + { + str = pg_ultostr_zeropad(str, tm->tm_mday, 2); + *str++ = '/'; + str = pg_ultostr_zeropad(str, tm->tm_mon, 2); + } + else + { + str = pg_ultostr_zeropad(str, tm->tm_mon, 2); + *str++ = '/'; + str = pg_ultostr_zeropad(str, tm->tm_mday, 2); + } + *str++ = '/'; + str = pg_ultostr_zeropad(str, + (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), 4); + break; + + case USE_GERMAN_DATES: + /* German-style date format */ + str = pg_ultostr_zeropad(str, tm->tm_mday, 2); + *str++ = '.'; + str = pg_ultostr_zeropad(str, tm->tm_mon, 2); + *str++ = '.'; + str = pg_ultostr_zeropad(str, + (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), 4); + break; + + case USE_POSTGRES_DATES: + default: + /* traditional date-only style for Postgres */ + if (DateOrder == DATEORDER_DMY) + { + str = pg_ultostr_zeropad(str, tm->tm_mday, 2); + *str++ = '-'; + str = pg_ultostr_zeropad(str, tm->tm_mon, 2); + } + else + { + str = pg_ultostr_zeropad(str, tm->tm_mon, 2); + *str++ = '-'; + str = pg_ultostr_zeropad(str, tm->tm_mday, 2); + } + *str++ = '-'; + str = pg_ultostr_zeropad(str, + (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), 4); + break; + } + + if (tm->tm_year <= 0) + { + memcpy(str, " BC", 3); /* Don't copy NUL */ + str += 3; + } + *str = '\0'; +} + + +/* EncodeTimeOnly() + * Encode time fields only. + * + * tm and fsec are the value to encode, print_tz determines whether to include + * a time zone (the difference between time and timetz types), tz is the + * numeric time zone offset, style is the date style, str is where to write the + * output. + */ +void +EncodeTimeOnly(struct pg_tm *tm, fsec_t fsec, bool print_tz, int tz, int style, char *str) +{ + str = pg_ultostr_zeropad(str, tm->tm_hour, 2); + *str++ = ':'; + str = pg_ultostr_zeropad(str, tm->tm_min, 2); + *str++ = ':'; + str = AppendSeconds(str, tm->tm_sec, fsec, MAX_TIME_PRECISION, true); + if (print_tz) + str = EncodeTimezone(str, tz, style); + *str = '\0'; +} + + +/* EncodeDateTime() + * Encode date and time interpreted as local time. + * + * tm and fsec are the value to encode, print_tz determines whether to include + * a time zone (the difference between timestamp and timestamptz types), tz is + * the numeric time zone offset, tzn is the textual time zone, which if + * specified will be used instead of tz by some styles, style is the date + * style, str is where to write the output. + * + * Supported date styles: + * Postgres - day mon hh:mm:ss yyyy tz + * SQL - mm/dd/yyyy hh:mm:ss.ss tz + * ISO - yyyy-mm-dd hh:mm:ss+/-tz + * German - dd.mm.yyyy hh:mm:ss tz + * XSD - yyyy-mm-ddThh:mm:ss.ss+/-tz + */ +void +EncodeDateTime(struct pg_tm *tm, fsec_t fsec, bool print_tz, int tz, const char *tzn, int style, char *str) +{ + int day; + + Assert(tm->tm_mon >= 1 && tm->tm_mon <= MONTHS_PER_YEAR); + + /* + * Negative tm_isdst means we have no valid time zone translation. + */ + if (tm->tm_isdst < 0) + print_tz = false; + + switch (style) + { + case USE_ISO_DATES: + case USE_XSD_DATES: + /* Compatible with ISO-8601 date formats */ + str = pg_ultostr_zeropad(str, + (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), 4); + *str++ = '-'; + str = pg_ultostr_zeropad(str, tm->tm_mon, 2); + *str++ = '-'; + str = pg_ultostr_zeropad(str, tm->tm_mday, 2); + *str++ = (style == USE_ISO_DATES) ? ' ' : 'T'; + str = pg_ultostr_zeropad(str, tm->tm_hour, 2); + *str++ = ':'; + str = pg_ultostr_zeropad(str, tm->tm_min, 2); + *str++ = ':'; + str = AppendTimestampSeconds(str, tm, fsec); + if (print_tz) + str = EncodeTimezone(str, tz, style); + break; + + case USE_SQL_DATES: + /* Compatible with Oracle/Ingres date formats */ + if (DateOrder == DATEORDER_DMY) + { + str = pg_ultostr_zeropad(str, tm->tm_mday, 2); + *str++ = '/'; + str = pg_ultostr_zeropad(str, tm->tm_mon, 2); + } + else + { + str = pg_ultostr_zeropad(str, tm->tm_mon, 2); + *str++ = '/'; + str = pg_ultostr_zeropad(str, tm->tm_mday, 2); + } + *str++ = '/'; + str = pg_ultostr_zeropad(str, + (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), 4); + *str++ = ' '; + str = pg_ultostr_zeropad(str, tm->tm_hour, 2); + *str++ = ':'; + str = pg_ultostr_zeropad(str, tm->tm_min, 2); + *str++ = ':'; + str = AppendTimestampSeconds(str, tm, fsec); + + /* + * Note: the uses of %.*s in this function would be risky if the + * timezone names ever contain non-ASCII characters, since we are + * not being careful to do encoding-aware clipping. However, all + * TZ abbreviations in the IANA database are plain ASCII. + */ + if (print_tz) + { + if (tzn) + { + sprintf(str, " %.*s", MAXTZLEN, tzn); + str += strlen(str); + } + else + str = EncodeTimezone(str, tz, style); + } + break; + + case USE_GERMAN_DATES: + /* German variant on European style */ + str = pg_ultostr_zeropad(str, tm->tm_mday, 2); + *str++ = '.'; + str = pg_ultostr_zeropad(str, tm->tm_mon, 2); + *str++ = '.'; + str = pg_ultostr_zeropad(str, + (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), 4); + *str++ = ' '; + str = pg_ultostr_zeropad(str, tm->tm_hour, 2); + *str++ = ':'; + str = pg_ultostr_zeropad(str, tm->tm_min, 2); + *str++ = ':'; + str = AppendTimestampSeconds(str, tm, fsec); + + if (print_tz) + { + if (tzn) + { + sprintf(str, " %.*s", MAXTZLEN, tzn); + str += strlen(str); + } + else + str = EncodeTimezone(str, tz, style); + } + break; + + case USE_POSTGRES_DATES: + default: + /* Backward-compatible with traditional Postgres abstime dates */ + day = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday); + tm->tm_wday = j2day(day); + memcpy(str, days[tm->tm_wday], 3); + str += 3; + *str++ = ' '; + if (DateOrder == DATEORDER_DMY) + { + str = pg_ultostr_zeropad(str, tm->tm_mday, 2); + *str++ = ' '; + memcpy(str, months[tm->tm_mon - 1], 3); + str += 3; + } + else + { + memcpy(str, months[tm->tm_mon - 1], 3); + str += 3; + *str++ = ' '; + str = pg_ultostr_zeropad(str, tm->tm_mday, 2); + } + *str++ = ' '; + str = pg_ultostr_zeropad(str, tm->tm_hour, 2); + *str++ = ':'; + str = pg_ultostr_zeropad(str, tm->tm_min, 2); + *str++ = ':'; + str = AppendTimestampSeconds(str, tm, fsec); + *str++ = ' '; + str = pg_ultostr_zeropad(str, + (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), 4); + + if (print_tz) + { + if (tzn) + { + sprintf(str, " %.*s", MAXTZLEN, tzn); + str += strlen(str); + } + else + { + /* + * We have a time zone, but no string version. Use the + * numeric form, but be sure to include a leading space to + * avoid formatting something which would be rejected by + * the date/time parser later. - thomas 2001-10-19 + */ + *str++ = ' '; + str = EncodeTimezone(str, tz, style); + } + } + break; + } + + if (tm->tm_year <= 0) + { + memcpy(str, " BC", 3); /* Don't copy NUL */ + str += 3; + } + *str = '\0'; +} + + +/* + * Helper functions to avoid duplicated code in EncodeInterval. + */ + +/* Append an ISO-8601-style interval field, but only if value isn't zero */ +static char * +AddISO8601IntPart(char *cp, int64 value, char units) +{ + if (value == 0) + return cp; + sprintf(cp, "%lld%c", (long long) value, units); + return cp + strlen(cp); +} + +/* Append a postgres-style interval field, but only if value isn't zero */ +static char * +AddPostgresIntPart(char *cp, int64 value, const char *units, + bool *is_zero, bool *is_before) +{ + if (value == 0) + return cp; + sprintf(cp, "%s%s%lld %s%s", + (!*is_zero) ? " " : "", + (*is_before && value > 0) ? "+" : "", + (long long) value, + units, + (value != 1) ? "s" : ""); + + /* + * Each nonzero field sets is_before for (only) the next one. This is a + * tad bizarre but it's how it worked before... + */ + *is_before = (value < 0); + *is_zero = false; + return cp + strlen(cp); +} + +/* Append a verbose-style interval field, but only if value isn't zero */ +static char * +AddVerboseIntPart(char *cp, int64 value, const char *units, + bool *is_zero, bool *is_before) +{ + if (value == 0) + return cp; + /* first nonzero value sets is_before */ + if (*is_zero) + { + *is_before = (value < 0); + value = i64abs(value); + } + else if (*is_before) + value = -value; + sprintf(cp, " %lld %s%s", (long long) value, units, (value == 1) ? "" : "s"); + *is_zero = false; + return cp + strlen(cp); +} + + +/* EncodeInterval() + * Interpret time structure as a delta time and convert to string. + * + * Support "traditional Postgres" and ISO-8601 styles. + * Actually, afaik ISO does not address time interval formatting, + * but this looks similar to the spec for absolute date/time. + * - thomas 1998-04-30 + * + * Actually, afaik, ISO 8601 does specify formats for "time + * intervals...[of the]...format with time-unit designators", which + * are pretty ugly. The format looks something like + * P1Y1M1DT1H1M1.12345S + * but useful for exchanging data with computers instead of humans. + * - ron 2003-07-14 + * + * And ISO's SQL 2008 standard specifies standards for + * "year-month literal"s (that look like '2-3') and + * "day-time literal"s (that look like ('4 5:6:7') + */ +void +EncodeInterval(struct pg_itm *itm, int style, char *str) +{ + char *cp = str; + int year = itm->tm_year; + int mon = itm->tm_mon; + int64 mday = itm->tm_mday; /* tm_mday could be INT_MIN */ + int64 hour = itm->tm_hour; + int min = itm->tm_min; + int sec = itm->tm_sec; + int fsec = itm->tm_usec; + bool is_before = false; + bool is_zero = true; + + /* + * The sign of year and month are guaranteed to match, since they are + * stored internally as "month". But we'll need to check for is_before and + * is_zero when determining the signs of day and hour/minute/seconds + * fields. + */ + switch (style) + { + /* SQL Standard interval format */ + case INTSTYLE_SQL_STANDARD: + { + bool has_negative = year < 0 || mon < 0 || + mday < 0 || hour < 0 || + min < 0 || sec < 0 || fsec < 0; + bool has_positive = year > 0 || mon > 0 || + mday > 0 || hour > 0 || + min > 0 || sec > 0 || fsec > 0; + bool has_year_month = year != 0 || mon != 0; + bool has_day_time = mday != 0 || hour != 0 || + min != 0 || sec != 0 || fsec != 0; + bool has_day = mday != 0; + bool sql_standard_value = !(has_negative && has_positive) && + !(has_year_month && has_day_time); + + /* + * SQL Standard wants only 1 "<sign>" preceding the whole + * interval ... but can't do that if mixed signs. + */ + if (has_negative && sql_standard_value) + { + *cp++ = '-'; + year = -year; + mon = -mon; + mday = -mday; + hour = -hour; + min = -min; + sec = -sec; + fsec = -fsec; + } + + if (!has_negative && !has_positive) + { + sprintf(cp, "0"); + } + else if (!sql_standard_value) + { + /* + * For non sql-standard interval values, force outputting + * the signs to avoid ambiguities with intervals with + * mixed sign components. + */ + char year_sign = (year < 0 || mon < 0) ? '-' : '+'; + char day_sign = (mday < 0) ? '-' : '+'; + char sec_sign = (hour < 0 || min < 0 || + sec < 0 || fsec < 0) ? '-' : '+'; + + sprintf(cp, "%c%d-%d %c%lld %c%lld:%02d:", + year_sign, abs(year), abs(mon), + day_sign, (long long) i64abs(mday), + sec_sign, (long long) i64abs(hour), abs(min)); + cp += strlen(cp); + cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true); + *cp = '\0'; + } + else if (has_year_month) + { + sprintf(cp, "%d-%d", year, mon); + } + else if (has_day) + { + sprintf(cp, "%lld %lld:%02d:", + (long long) mday, (long long) hour, min); + cp += strlen(cp); + cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true); + *cp = '\0'; + } + else + { + sprintf(cp, "%lld:%02d:", (long long) hour, min); + cp += strlen(cp); + cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true); + *cp = '\0'; + } + } + break; + + /* ISO 8601 "time-intervals by duration only" */ + case INTSTYLE_ISO_8601: + /* special-case zero to avoid printing nothing */ + if (year == 0 && mon == 0 && mday == 0 && + hour == 0 && min == 0 && sec == 0 && fsec == 0) + { + sprintf(cp, "PT0S"); + break; + } + *cp++ = 'P'; + cp = AddISO8601IntPart(cp, year, 'Y'); + cp = AddISO8601IntPart(cp, mon, 'M'); + cp = AddISO8601IntPart(cp, mday, 'D'); + if (hour != 0 || min != 0 || sec != 0 || fsec != 0) + *cp++ = 'T'; + cp = AddISO8601IntPart(cp, hour, 'H'); + cp = AddISO8601IntPart(cp, min, 'M'); + if (sec != 0 || fsec != 0) + { + if (sec < 0 || fsec < 0) + *cp++ = '-'; + cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, false); + *cp++ = 'S'; + *cp++ = '\0'; + } + break; + + /* Compatible with postgresql < 8.4 when DateStyle = 'iso' */ + case INTSTYLE_POSTGRES: + cp = AddPostgresIntPart(cp, year, "year", &is_zero, &is_before); + + /* + * Ideally we should spell out "month" like we do for "year" and + * "day". However, for backward compatibility, we can't easily + * fix this. bjm 2011-05-24 + */ + cp = AddPostgresIntPart(cp, mon, "mon", &is_zero, &is_before); + cp = AddPostgresIntPart(cp, mday, "day", &is_zero, &is_before); + if (is_zero || hour != 0 || min != 0 || sec != 0 || fsec != 0) + { + bool minus = (hour < 0 || min < 0 || sec < 0 || fsec < 0); + + sprintf(cp, "%s%s%02lld:%02d:", + is_zero ? "" : " ", + (minus ? "-" : (is_before ? "+" : "")), + (long long) i64abs(hour), abs(min)); + cp += strlen(cp); + cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true); + *cp = '\0'; + } + break; + + /* Compatible with postgresql < 8.4 when DateStyle != 'iso' */ + case INTSTYLE_POSTGRES_VERBOSE: + default: + strcpy(cp, "@"); + cp++; + cp = AddVerboseIntPart(cp, year, "year", &is_zero, &is_before); + cp = AddVerboseIntPart(cp, mon, "mon", &is_zero, &is_before); + cp = AddVerboseIntPart(cp, mday, "day", &is_zero, &is_before); + cp = AddVerboseIntPart(cp, hour, "hour", &is_zero, &is_before); + cp = AddVerboseIntPart(cp, min, "min", &is_zero, &is_before); + if (sec != 0 || fsec != 0) + { + *cp++ = ' '; + if (sec < 0 || (sec == 0 && fsec < 0)) + { + if (is_zero) + is_before = true; + else if (!is_before) + *cp++ = '-'; + } + else if (is_before) + *cp++ = '-'; + cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, false); + /* We output "ago", not negatives, so use abs(). */ + sprintf(cp, " sec%s", + (abs(sec) != 1 || fsec != 0) ? "s" : ""); + is_zero = false; + } + /* identically zero? then put in a unitless zero... */ + if (is_zero) + strcat(cp, " 0"); + if (is_before) + strcat(cp, " ago"); + break; + } +} + + +/* + * We've been burnt by stupid errors in the ordering of the datetkn tables + * once too often. Arrange to check them during postmaster start. + */ +static bool +CheckDateTokenTable(const char *tablename, const datetkn *base, int nel) +{ + bool ok = true; + int i; + + for (i = 0; i < nel; i++) + { + /* check for token strings that don't fit */ + if (strlen(base[i].token) > TOKMAXLEN) + { + /* %.*s is safe since all our tokens are ASCII */ + elog(LOG, "token too long in %s table: \"%.*s\"", + tablename, + TOKMAXLEN + 1, base[i].token); + ok = false; + break; /* don't risk applying strcmp */ + } + /* check for out of order */ + if (i > 0 && + strcmp(base[i - 1].token, base[i].token) >= 0) + { + elog(LOG, "ordering error in %s table: \"%s\" >= \"%s\"", + tablename, + base[i - 1].token, + base[i].token); + ok = false; + } + } + return ok; +} + +bool +CheckDateTokenTables(void) +{ + bool ok = true; + + Assert(UNIX_EPOCH_JDATE == date2j(1970, 1, 1)); + Assert(POSTGRES_EPOCH_JDATE == date2j(2000, 1, 1)); + + ok &= CheckDateTokenTable("datetktbl", datetktbl, szdatetktbl); + ok &= CheckDateTokenTable("deltatktbl", deltatktbl, szdeltatktbl); + return ok; +} + +/* + * Common code for temporal prosupport functions: simplify, if possible, + * a call to a temporal type's length-coercion function. + * + * Types time, timetz, timestamp and timestamptz each have a range of allowed + * precisions. An unspecified precision is rigorously equivalent to the + * highest specifiable precision. We can replace the function call with a + * no-op RelabelType if it is coercing to the same or higher precision as the + * input is known to have. + * + * The input Node is always a FuncExpr, but to reduce the #include footprint + * of datetime.h, we declare it as Node *. + * + * Note: timestamp_scale throws an error when the typmod is out of range, but + * we can't get there from a cast: our typmodin will have caught it already. + */ +Node * +TemporalSimplify(int32 max_precis, Node *node) +{ + FuncExpr *expr = castNode(FuncExpr, node); + Node *ret = NULL; + Node *typmod; + + Assert(list_length(expr->args) >= 2); + + typmod = (Node *) lsecond(expr->args); + + if (IsA(typmod, Const) && !((Const *) typmod)->constisnull) + { + Node *source = (Node *) linitial(expr->args); + int32 old_precis = exprTypmod(source); + int32 new_precis = DatumGetInt32(((Const *) typmod)->constvalue); + + if (new_precis < 0 || new_precis == max_precis || + (old_precis >= 0 && new_precis >= old_precis)) + ret = relabel_to_typmod(source, new_precis); + } + + return ret; +} + +/* + * This function gets called during timezone config file load or reload + * to create the final array of timezone tokens. The argument array + * is already sorted in name order. + * + * The result is a TimeZoneAbbrevTable (which must be a single guc_malloc'd + * chunk) or NULL on alloc failure. No other error conditions are defined. + */ +TimeZoneAbbrevTable * +ConvertTimeZoneAbbrevs(struct tzEntry *abbrevs, int n) +{ + TimeZoneAbbrevTable *tbl; + Size tbl_size; + int i; + + /* Space for fixed fields and datetkn array */ + tbl_size = offsetof(TimeZoneAbbrevTable, abbrevs) + + n * sizeof(datetkn); + tbl_size = MAXALIGN(tbl_size); + /* Count up space for dynamic abbreviations */ + for (i = 0; i < n; i++) + { + struct tzEntry *abbr = abbrevs + i; + + if (abbr->zone != NULL) + { + Size dsize; + + dsize = offsetof(DynamicZoneAbbrev, zone) + + strlen(abbr->zone) + 1; + tbl_size += MAXALIGN(dsize); + } + } + + /* Alloc the result ... */ + tbl = guc_malloc(LOG, tbl_size); + if (!tbl) + return NULL; + + /* ... and fill it in */ + tbl->tblsize = tbl_size; + tbl->numabbrevs = n; + /* in this loop, tbl_size reprises the space calculation above */ + tbl_size = offsetof(TimeZoneAbbrevTable, abbrevs) + + n * sizeof(datetkn); + tbl_size = MAXALIGN(tbl_size); + for (i = 0; i < n; i++) + { + struct tzEntry *abbr = abbrevs + i; + datetkn *dtoken = tbl->abbrevs + i; + + /* use strlcpy to truncate name if necessary */ + strlcpy(dtoken->token, abbr->abbrev, TOKMAXLEN + 1); + if (abbr->zone != NULL) + { + /* Allocate a DynamicZoneAbbrev for this abbreviation */ + DynamicZoneAbbrev *dtza; + Size dsize; + + dtza = (DynamicZoneAbbrev *) ((char *) tbl + tbl_size); + dtza->tz = NULL; + strcpy(dtza->zone, abbr->zone); + + dtoken->type = DYNTZ; + /* value is offset from table start to DynamicZoneAbbrev */ + dtoken->value = (int32) tbl_size; + + dsize = offsetof(DynamicZoneAbbrev, zone) + + strlen(abbr->zone) + 1; + tbl_size += MAXALIGN(dsize); + } + else + { + dtoken->type = abbr->is_dst ? DTZ : TZ; + dtoken->value = abbr->offset; + } + } + + /* Assert the two loops above agreed on size calculations */ + Assert(tbl->tblsize == tbl_size); + + /* Check the ordering, if testing */ + Assert(CheckDateTokenTable("timezone abbreviations", tbl->abbrevs, n)); + + return tbl; +} + +/* + * Install a TimeZoneAbbrevTable as the active table. + * + * Caller is responsible that the passed table doesn't go away while in use. + */ +void +InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl) +{ + zoneabbrevtbl = tbl; + /* reset abbrevcache, which may contain pointers into old table */ + memset(abbrevcache, 0, sizeof(abbrevcache)); +} + +/* + * Helper subroutine to locate pg_tz timezone for a dynamic abbreviation. + * + * On failure, returns NULL and fills *extra for a DTERR_BAD_ZONE_ABBREV error. + */ +static pg_tz * +FetchDynamicTimeZone(TimeZoneAbbrevTable *tbl, const datetkn *tp, + DateTimeErrorExtra *extra) +{ + DynamicZoneAbbrev *dtza; + + /* Just some sanity checks to prevent indexing off into nowhere */ + Assert(tp->type == DYNTZ); + Assert(tp->value > 0 && tp->value < tbl->tblsize); + + dtza = (DynamicZoneAbbrev *) ((char *) tbl + tp->value); + + /* Look up the underlying zone if we haven't already */ + if (dtza->tz == NULL) + { + dtza->tz = pg_tzset(dtza->zone); + if (dtza->tz == NULL) + { + /* Ooops, bogus zone name in config file entry */ + extra->dtee_timezone = dtza->zone; + extra->dtee_abbrev = tp->token; + } + } + return dtza->tz; +} + + +/* + * This set-returning function reads all the available time zone abbreviations + * and returns a set of (abbrev, utc_offset, is_dst). + */ +Datum +pg_timezone_abbrevs(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + int *pindex; + Datum result; + HeapTuple tuple; + Datum values[3]; + bool nulls[3] = {0}; + const datetkn *tp; + char buffer[TOKMAXLEN + 1]; + int gmtoffset; + bool is_dst; + unsigned char *p; + struct pg_itm_in itm_in; + Interval *resInterval; + + /* stuff done only on the first call of the function */ + if (SRF_IS_FIRSTCALL()) + { + TupleDesc tupdesc; + MemoryContext oldcontext; + + /* 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 */ + pindex = (int *) palloc(sizeof(int)); + *pindex = 0; + funcctx->user_fctx = (void *) pindex; + + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + funcctx->tuple_desc = tupdesc; + + MemoryContextSwitchTo(oldcontext); + } + + /* stuff done on every call of the function */ + funcctx = SRF_PERCALL_SETUP(); + pindex = (int *) funcctx->user_fctx; + + if (zoneabbrevtbl == NULL || + *pindex >= zoneabbrevtbl->numabbrevs) + SRF_RETURN_DONE(funcctx); + + tp = zoneabbrevtbl->abbrevs + *pindex; + + switch (tp->type) + { + case TZ: + gmtoffset = tp->value; + is_dst = false; + break; + case DTZ: + gmtoffset = tp->value; + is_dst = true; + break; + case DYNTZ: + { + /* Determine the current meaning of the abbrev */ + pg_tz *tzp; + DateTimeErrorExtra extra; + TimestampTz now; + int isdst; + + tzp = FetchDynamicTimeZone(zoneabbrevtbl, tp, &extra); + if (tzp == NULL) + DateTimeParseError(DTERR_BAD_ZONE_ABBREV, &extra, + NULL, NULL, NULL); + now = GetCurrentTransactionStartTimestamp(); + gmtoffset = -DetermineTimeZoneAbbrevOffsetTS(now, + tp->token, + tzp, + &isdst); + is_dst = (bool) isdst; + break; + } + default: + elog(ERROR, "unrecognized timezone type %d", (int) tp->type); + gmtoffset = 0; /* keep compiler quiet */ + is_dst = false; + break; + } + + /* + * Convert name to text, using upcasing conversion that is the inverse of + * what ParseDateTime() uses. + */ + strlcpy(buffer, tp->token, sizeof(buffer)); + for (p = (unsigned char *) buffer; *p; p++) + *p = pg_toupper(*p); + + values[0] = CStringGetTextDatum(buffer); + + /* Convert offset (in seconds) to an interval; can't overflow */ + MemSet(&itm_in, 0, sizeof(struct pg_itm_in)); + itm_in.tm_usec = (int64) gmtoffset * USECS_PER_SEC; + resInterval = (Interval *) palloc(sizeof(Interval)); + (void) itmin2interval(&itm_in, resInterval); + values[1] = IntervalPGetDatum(resInterval); + + values[2] = BoolGetDatum(is_dst); + + (*pindex)++; + + tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); + result = HeapTupleGetDatum(tuple); + + SRF_RETURN_NEXT(funcctx, result); +} + +/* + * This set-returning function reads all the available full time zones + * and returns a set of (name, abbrev, utc_offset, is_dst). + */ +Datum +pg_timezone_names(PG_FUNCTION_ARGS) +{ + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + pg_tzenum *tzenum; + pg_tz *tz; + Datum values[4]; + bool nulls[4] = {0}; + int tzoff; + struct pg_tm tm; + fsec_t fsec; + const char *tzn; + Interval *resInterval; + struct pg_itm_in itm_in; + + InitMaterializedSRF(fcinfo, 0); + + /* initialize timezone scanning code */ + tzenum = pg_tzenumerate_start(); + + /* search for another zone to display */ + for (;;) + { + tz = pg_tzenumerate_next(tzenum); + if (!tz) + break; + + /* Convert now() to local time in this zone */ + if (timestamp2tm(GetCurrentTransactionStartTimestamp(), + &tzoff, &tm, &fsec, &tzn, tz) != 0) + continue; /* ignore if conversion fails */ + + /* + * IANA's rather silly "Factory" time zone used to emit ridiculously + * long "abbreviations" such as "Local time zone must be set--see zic + * manual page" or "Local time zone must be set--use tzsetup". While + * modern versions of tzdb emit the much saner "-00", it seems some + * benighted packagers are hacking the IANA data so that it continues + * to produce these strings. To prevent producing a weirdly wide + * abbrev column, reject ridiculously long abbreviations. + */ + if (tzn && strlen(tzn) > 31) + continue; + + values[0] = CStringGetTextDatum(pg_get_timezone_name(tz)); + values[1] = CStringGetTextDatum(tzn ? tzn : ""); + + /* Convert tzoff to an interval; can't overflow */ + MemSet(&itm_in, 0, sizeof(struct pg_itm_in)); + itm_in.tm_usec = (int64) -tzoff * USECS_PER_SEC; + resInterval = (Interval *) palloc(sizeof(Interval)); + (void) itmin2interval(&itm_in, resInterval); + values[2] = IntervalPGetDatum(resInterval); + + values[3] = BoolGetDatum(tm.tm_isdst > 0); + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + } + + pg_tzenumerate_end(tzenum); + return (Datum) 0; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/datum.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/datum.c new file mode 100644 index 00000000000..251dd23ca81 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/datum.c @@ -0,0 +1,554 @@ +/*------------------------------------------------------------------------- + * + * datum.c + * POSTGRES Datum (abstract data type) manipulation routines. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/adt/datum.c + * + *------------------------------------------------------------------------- + */ + +/* + * In the implementation of these routines we assume the following: + * + * A) if a type is "byVal" then all the information is stored in the + * Datum itself (i.e. no pointers involved!). In this case the + * length of the type is always greater than zero and not more than + * "sizeof(Datum)" + * + * B) if a type is not "byVal" and it has a fixed length (typlen > 0), + * then the "Datum" always contains a pointer to a stream of bytes. + * The number of significant bytes are always equal to the typlen. + * + * C) if a type is not "byVal" and has typlen == -1, + * then the "Datum" always points to a "struct varlena". + * This varlena structure has information about the actual length of this + * particular instance of the type and about its value. + * + * D) if a type is not "byVal" and has typlen == -2, + * then the "Datum" always points to a null-terminated C string. + * + * Note that we do not treat "toasted" datums specially; therefore what + * will be copied or compared is the compressed data or toast reference. + * An exception is made for datumCopy() of an expanded object, however, + * because most callers expect to get a simple contiguous (and pfree'able) + * result from datumCopy(). See also datumTransfer(). + */ + +#include "postgres.h" + +#include "access/detoast.h" +#include "catalog/pg_type_d.h" +#include "common/hashfn.h" +#include "fmgr.h" +#include "utils/builtins.h" +#include "utils/datum.h" +#include "utils/expandeddatum.h" + + +/*------------------------------------------------------------------------- + * datumGetSize + * + * Find the "real" size of a datum, given the datum value, + * whether it is a "by value", and the declared type length. + * (For TOAST pointer datums, this is the size of the pointer datum.) + * + * This is essentially an out-of-line version of the att_addlength_datum() + * macro in access/tupmacs.h. We do a tad more error checking though. + *------------------------------------------------------------------------- + */ +Size +datumGetSize(Datum value, bool typByVal, int typLen) +{ + Size size; + + if (typByVal) + { + /* Pass-by-value types are always fixed-length */ + Assert(typLen > 0 && typLen <= sizeof(Datum)); + size = (Size) typLen; + } + else + { + if (typLen > 0) + { + /* Fixed-length pass-by-ref type */ + size = (Size) typLen; + } + else if (typLen == -1) + { + /* It is a varlena datatype */ + struct varlena *s = (struct varlena *) DatumGetPointer(value); + + if (!PointerIsValid(s)) + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("invalid Datum pointer"))); + + size = (Size) VARSIZE_ANY(s); + } + else if (typLen == -2) + { + /* It is a cstring datatype */ + char *s = (char *) DatumGetPointer(value); + + if (!PointerIsValid(s)) + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("invalid Datum pointer"))); + + size = (Size) (strlen(s) + 1); + } + else + { + elog(ERROR, "invalid typLen: %d", typLen); + size = 0; /* keep compiler quiet */ + } + } + + return size; +} + +/*------------------------------------------------------------------------- + * datumCopy + * + * Make a copy of a non-NULL datum. + * + * If the datatype is pass-by-reference, memory is obtained with palloc(). + * + * If the value is a reference to an expanded object, we flatten into memory + * obtained with palloc(). We need to copy because one of the main uses of + * this function is to copy a datum out of a transient memory context that's + * about to be destroyed, and the expanded object is probably in a child + * context that will also go away. Moreover, many callers assume that the + * result is a single pfree-able chunk. + *------------------------------------------------------------------------- + */ +Datum +datumCopy(Datum value, bool typByVal, int typLen) +{ + Datum res; + + if (typByVal) + res = value; + else if (typLen == -1) + { + /* It is a varlena datatype */ + struct varlena *vl = (struct varlena *) DatumGetPointer(value); + + if (VARATT_IS_EXTERNAL_EXPANDED(vl)) + { + /* Flatten into the caller's memory context */ + ExpandedObjectHeader *eoh = DatumGetEOHP(value); + Size resultsize; + char *resultptr; + + resultsize = EOH_get_flat_size(eoh); + resultptr = (char *) palloc(resultsize); + EOH_flatten_into(eoh, (void *) resultptr, resultsize); + res = PointerGetDatum(resultptr); + } + else + { + /* Otherwise, just copy the varlena datum verbatim */ + Size realSize; + char *resultptr; + + realSize = (Size) VARSIZE_ANY(vl); + resultptr = (char *) palloc(realSize); + memcpy(resultptr, vl, realSize); + res = PointerGetDatum(resultptr); + } + } + else + { + /* Pass by reference, but not varlena, so not toasted */ + Size realSize; + char *resultptr; + + realSize = datumGetSize(value, typByVal, typLen); + + resultptr = (char *) palloc(realSize); + memcpy(resultptr, DatumGetPointer(value), realSize); + res = PointerGetDatum(resultptr); + } + return res; +} + +/*------------------------------------------------------------------------- + * datumTransfer + * + * Transfer a non-NULL datum into the current memory context. + * + * This is equivalent to datumCopy() except when the datum is a read-write + * pointer to an expanded object. In that case we merely reparent the object + * into the current context, and return its standard R/W pointer (in case the + * given one is a transient pointer of shorter lifespan). + *------------------------------------------------------------------------- + */ +Datum +datumTransfer(Datum value, bool typByVal, int typLen) +{ + if (!typByVal && typLen == -1 && + VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(value))) + value = TransferExpandedObject(value, CurrentMemoryContext); + else + value = datumCopy(value, typByVal, typLen); + return value; +} + +/*------------------------------------------------------------------------- + * datumIsEqual + * + * Return true if two datums are equal, false otherwise + * + * NOTE: XXX! + * We just compare the bytes of the two values, one by one. + * This routine will return false if there are 2 different + * representations of the same value (something along the lines + * of say the representation of zero in one's complement arithmetic). + * Also, it will probably not give the answer you want if either + * datum has been "toasted". + * + * Do not try to make this any smarter than it currently is with respect + * to "toasted" datums, because some of the callers could be working in the + * context of an aborted transaction. + *------------------------------------------------------------------------- + */ +bool +datumIsEqual(Datum value1, Datum value2, bool typByVal, int typLen) +{ + bool res; + + if (typByVal) + { + /* + * just compare the two datums. NOTE: just comparing "len" bytes will + * not do the work, because we do not know how these bytes are aligned + * inside the "Datum". We assume instead that any given datatype is + * consistent about how it fills extraneous bits in the Datum. + */ + res = (value1 == value2); + } + else + { + Size size1, + size2; + char *s1, + *s2; + + /* + * Compare the bytes pointed by the pointers stored in the datums. + */ + size1 = datumGetSize(value1, typByVal, typLen); + size2 = datumGetSize(value2, typByVal, typLen); + if (size1 != size2) + return false; + s1 = (char *) DatumGetPointer(value1); + s2 = (char *) DatumGetPointer(value2); + res = (memcmp(s1, s2, size1) == 0); + } + return res; +} + +/*------------------------------------------------------------------------- + * datum_image_eq + * + * Compares two datums for identical contents, based on byte images. Return + * true if the two datums are equal, false otherwise. + *------------------------------------------------------------------------- + */ +bool +datum_image_eq(Datum value1, Datum value2, bool typByVal, int typLen) +{ + Size len1, + len2; + bool result = true; + + if (typByVal) + { + result = (value1 == value2); + } + else if (typLen > 0) + { + result = (memcmp(DatumGetPointer(value1), + DatumGetPointer(value2), + typLen) == 0); + } + else if (typLen == -1) + { + len1 = toast_raw_datum_size(value1); + len2 = toast_raw_datum_size(value2); + /* No need to de-toast if lengths don't match. */ + if (len1 != len2) + result = false; + else + { + struct varlena *arg1val; + struct varlena *arg2val; + + arg1val = PG_DETOAST_DATUM_PACKED(value1); + arg2val = PG_DETOAST_DATUM_PACKED(value2); + + result = (memcmp(VARDATA_ANY(arg1val), + VARDATA_ANY(arg2val), + len1 - VARHDRSZ) == 0); + + /* Only free memory if it's a copy made here. */ + if ((Pointer) arg1val != (Pointer) value1) + pfree(arg1val); + if ((Pointer) arg2val != (Pointer) value2) + pfree(arg2val); + } + } + else if (typLen == -2) + { + char *s1, + *s2; + + /* Compare cstring datums */ + s1 = DatumGetCString(value1); + s2 = DatumGetCString(value2); + len1 = strlen(s1) + 1; + len2 = strlen(s2) + 1; + if (len1 != len2) + return false; + result = (memcmp(s1, s2, len1) == 0); + } + else + elog(ERROR, "unexpected typLen: %d", typLen); + + return result; +} + +/*------------------------------------------------------------------------- + * datum_image_hash + * + * Generate a hash value based on the binary representation of 'value'. Most + * use cases will want to use the hash function specific to the Datum's type, + * however, some corner cases require generating a hash value based on the + * actual bits rather than the logical value. + *------------------------------------------------------------------------- + */ +uint32 +datum_image_hash(Datum value, bool typByVal, int typLen) +{ + Size len; + uint32 result; + + if (typByVal) + result = hash_bytes((unsigned char *) &value, sizeof(Datum)); + else if (typLen > 0) + result = hash_bytes((unsigned char *) DatumGetPointer(value), typLen); + else if (typLen == -1) + { + struct varlena *val; + + len = toast_raw_datum_size(value); + + val = PG_DETOAST_DATUM_PACKED(value); + + result = hash_bytes((unsigned char *) VARDATA_ANY(val), len - VARHDRSZ); + + /* Only free memory if it's a copy made here. */ + if ((Pointer) val != (Pointer) value) + pfree(val); + } + else if (typLen == -2) + { + char *s; + + s = DatumGetCString(value); + len = strlen(s) + 1; + + result = hash_bytes((unsigned char *) s, len); + } + else + { + elog(ERROR, "unexpected typLen: %d", typLen); + result = 0; /* keep compiler quiet */ + } + + return result; +} + +/*------------------------------------------------------------------------- + * btequalimage + * + * Generic "equalimage" support function. + * + * B-Tree operator classes whose equality function could safely be replaced by + * datum_image_eq() in all cases can use this as their "equalimage" support + * function. + * + * Earlier minor releases erroneously associated this function with + * interval_ops. Detect that case to rescind deduplication support, without + * requiring initdb. + *------------------------------------------------------------------------- + */ +Datum +btequalimage(PG_FUNCTION_ARGS) +{ + Oid opcintype = PG_GETARG_OID(0); + + PG_RETURN_BOOL(opcintype != INTERVALOID); +} + +/*------------------------------------------------------------------------- + * datumEstimateSpace + * + * Compute the amount of space that datumSerialize will require for a + * particular Datum. + *------------------------------------------------------------------------- + */ +Size +datumEstimateSpace(Datum value, bool isnull, bool typByVal, int typLen) +{ + Size sz = sizeof(int); + + if (!isnull) + { + /* no need to use add_size, can't overflow */ + if (typByVal) + sz += sizeof(Datum); + else if (typLen == -1 && + VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(value))) + { + /* Expanded objects need to be flattened, see comment below */ + sz += EOH_get_flat_size(DatumGetEOHP(value)); + } + else + sz += datumGetSize(value, typByVal, typLen); + } + + return sz; +} + +/*------------------------------------------------------------------------- + * datumSerialize + * + * Serialize a possibly-NULL datum into caller-provided storage. + * + * Note: "expanded" objects are flattened so as to produce a self-contained + * representation, but other sorts of toast pointers are transferred as-is. + * This is because the intended use of this function is to pass the value + * to another process within the same database server. The other process + * could not access an "expanded" object within this process's memory, but + * we assume it can dereference the same TOAST pointers this one can. + * + * The format is as follows: first, we write a 4-byte header word, which + * is either the length of a pass-by-reference datum, -1 for a + * pass-by-value datum, or -2 for a NULL. If the value is NULL, nothing + * further is written. If it is pass-by-value, sizeof(Datum) bytes + * follow. Otherwise, the number of bytes indicated by the header word + * follow. The caller is responsible for ensuring that there is enough + * storage to store the number of bytes that will be written; use + * datumEstimateSpace() to find out how many will be needed. + * *start_address is updated to point to the byte immediately following + * those written. + *------------------------------------------------------------------------- + */ +void +datumSerialize(Datum value, bool isnull, bool typByVal, int typLen, + char **start_address) +{ + ExpandedObjectHeader *eoh = NULL; + int header; + + /* Write header word. */ + if (isnull) + header = -2; + else if (typByVal) + header = -1; + else if (typLen == -1 && + VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(value))) + { + eoh = DatumGetEOHP(value); + header = EOH_get_flat_size(eoh); + } + else + header = datumGetSize(value, typByVal, typLen); + memcpy(*start_address, &header, sizeof(int)); + *start_address += sizeof(int); + + /* If not null, write payload bytes. */ + if (!isnull) + { + if (typByVal) + { + memcpy(*start_address, &value, sizeof(Datum)); + *start_address += sizeof(Datum); + } + else if (eoh) + { + char *tmp; + + /* + * EOH_flatten_into expects the target address to be maxaligned, + * so we can't store directly to *start_address. + */ + tmp = (char *) palloc(header); + EOH_flatten_into(eoh, (void *) tmp, header); + memcpy(*start_address, tmp, header); + *start_address += header; + + /* be tidy. */ + pfree(tmp); + } + else + { + memcpy(*start_address, DatumGetPointer(value), header); + *start_address += header; + } + } +} + +/*------------------------------------------------------------------------- + * datumRestore + * + * Restore a possibly-NULL datum previously serialized by datumSerialize. + * *start_address is updated according to the number of bytes consumed. + *------------------------------------------------------------------------- + */ +Datum +datumRestore(char **start_address, bool *isnull) +{ + int header; + void *d; + + /* Read header word. */ + memcpy(&header, *start_address, sizeof(int)); + *start_address += sizeof(int); + + /* If this datum is NULL, we can stop here. */ + if (header == -2) + { + *isnull = true; + return (Datum) 0; + } + + /* OK, datum is not null. */ + *isnull = false; + + /* If this datum is pass-by-value, sizeof(Datum) bytes follow. */ + if (header == -1) + { + Datum val; + + memcpy(&val, *start_address, sizeof(Datum)); + *start_address += sizeof(Datum); + return val; + } + + /* Pass-by-reference case; copy indicated number of bytes. */ + Assert(header > 0); + d = palloc(header); + memcpy(d, *start_address, header); + *start_address += header; + return PointerGetDatum(d); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/dbsize.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/dbsize.c new file mode 100644 index 00000000000..c22837e48fb --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/dbsize.c @@ -0,0 +1,1028 @@ +/* + * dbsize.c + * Database object size functions, and related inquiries + * + * Copyright (c) 2002-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/utils/adt/dbsize.c + * + */ + +#include "postgres.h" + +#include <sys/stat.h> + +#include "access/htup_details.h" +#include "access/relation.h" +#include "catalog/catalog.h" +#include "catalog/namespace.h" +#include "catalog/pg_authid.h" +#include "catalog/pg_database.h" +#include "catalog/pg_tablespace.h" +#include "commands/dbcommands.h" +#include "commands/tablespace.h" +#include "miscadmin.h" +#include "storage/fd.h" +#include "utils/acl.h" +#include "utils/builtins.h" +#include "utils/numeric.h" +#include "utils/rel.h" +#include "utils/relfilenumbermap.h" +#include "utils/relmapper.h" +#include "utils/syscache.h" + +/* Divide by two and round away from zero */ +#define half_rounded(x) (((x) + ((x) < 0 ? -1 : 1)) / 2) + +/* Units used in pg_size_pretty functions. All units must be powers of 2 */ +struct size_pretty_unit +{ + const char *name; /* bytes, kB, MB, GB etc */ + uint32 limit; /* upper limit, prior to half rounding after + * converting to this unit. */ + bool round; /* do half rounding for this unit */ + uint8 unitbits; /* (1 << unitbits) bytes to make 1 of this + * unit */ +}; + +/* When adding units here also update the docs and the error message in pg_size_bytes */ +static const struct size_pretty_unit size_pretty_units[] = { + {"bytes", 10 * 1024, false, 0}, + {"kB", 20 * 1024 - 1, true, 10}, + {"MB", 20 * 1024 - 1, true, 20}, + {"GB", 20 * 1024 - 1, true, 30}, + {"TB", 20 * 1024 - 1, true, 40}, + {"PB", 20 * 1024 - 1, true, 50}, + {NULL, 0, false, 0} +}; + +/* Additional unit aliases accepted by pg_size_bytes */ +struct size_bytes_unit_alias +{ + const char *alias; + int unit_index; /* corresponding size_pretty_units element */ +}; + +/* When adding units here also update the docs and the error message in pg_size_bytes */ +static const struct size_bytes_unit_alias size_bytes_aliases[] = { + {"B", 0}, + {NULL} +}; + +/* Return physical size of directory contents, or 0 if dir doesn't exist */ +static int64 +db_dir_size(const char *path) +{ + int64 dirsize = 0; + struct dirent *direntry; + DIR *dirdesc; + char filename[MAXPGPATH * 2]; + + dirdesc = AllocateDir(path); + + if (!dirdesc) + return 0; + + while ((direntry = ReadDir(dirdesc, path)) != NULL) + { + struct stat fst; + + CHECK_FOR_INTERRUPTS(); + + if (strcmp(direntry->d_name, ".") == 0 || + strcmp(direntry->d_name, "..") == 0) + continue; + + snprintf(filename, sizeof(filename), "%s/%s", path, direntry->d_name); + + if (stat(filename, &fst) < 0) + { + if (errno == ENOENT) + continue; + else + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not stat file \"%s\": %m", filename))); + } + dirsize += fst.st_size; + } + + FreeDir(dirdesc); + return dirsize; +} + +/* + * calculate size of database in all tablespaces + */ +static int64 +calculate_database_size(Oid dbOid) +{ + int64 totalsize; + DIR *dirdesc; + struct dirent *direntry; + char dirpath[MAXPGPATH]; + char pathname[MAXPGPATH + 21 + sizeof(TABLESPACE_VERSION_DIRECTORY)]; + AclResult aclresult; + + /* + * User must have connect privilege for target database or have privileges + * of pg_read_all_stats + */ + aclresult = object_aclcheck(DatabaseRelationId, dbOid, GetUserId(), ACL_CONNECT); + if (aclresult != ACLCHECK_OK && + !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS)) + { + aclcheck_error(aclresult, OBJECT_DATABASE, + get_database_name(dbOid)); + } + + /* Shared storage in pg_global is not counted */ + + /* Include pg_default storage */ + snprintf(pathname, sizeof(pathname), "base/%u", dbOid); + totalsize = db_dir_size(pathname); + + /* Scan the non-default tablespaces */ + snprintf(dirpath, MAXPGPATH, "pg_tblspc"); + dirdesc = AllocateDir(dirpath); + + while ((direntry = ReadDir(dirdesc, dirpath)) != NULL) + { + CHECK_FOR_INTERRUPTS(); + + if (strcmp(direntry->d_name, ".") == 0 || + strcmp(direntry->d_name, "..") == 0) + continue; + + snprintf(pathname, sizeof(pathname), "pg_tblspc/%s/%s/%u", + direntry->d_name, TABLESPACE_VERSION_DIRECTORY, dbOid); + totalsize += db_dir_size(pathname); + } + + FreeDir(dirdesc); + + return totalsize; +} + +Datum +pg_database_size_oid(PG_FUNCTION_ARGS) +{ + Oid dbOid = PG_GETARG_OID(0); + int64 size; + + size = calculate_database_size(dbOid); + + if (size == 0) + PG_RETURN_NULL(); + + PG_RETURN_INT64(size); +} + +Datum +pg_database_size_name(PG_FUNCTION_ARGS) +{ + Name dbName = PG_GETARG_NAME(0); + Oid dbOid = get_database_oid(NameStr(*dbName), false); + int64 size; + + size = calculate_database_size(dbOid); + + if (size == 0) + PG_RETURN_NULL(); + + PG_RETURN_INT64(size); +} + + +/* + * Calculate total size of tablespace. Returns -1 if the tablespace directory + * cannot be found. + */ +static int64 +calculate_tablespace_size(Oid tblspcOid) +{ + char tblspcPath[MAXPGPATH]; + char pathname[MAXPGPATH * 2]; + int64 totalsize = 0; + DIR *dirdesc; + struct dirent *direntry; + AclResult aclresult; + + /* + * User must have privileges of pg_read_all_stats or have CREATE privilege + * for target tablespace, either explicitly granted or implicitly because + * it is default for current database. + */ + if (tblspcOid != MyDatabaseTableSpace && + !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS)) + { + aclresult = object_aclcheck(TableSpaceRelationId, tblspcOid, GetUserId(), ACL_CREATE); + if (aclresult != ACLCHECK_OK) + aclcheck_error(aclresult, OBJECT_TABLESPACE, + get_tablespace_name(tblspcOid)); + } + + if (tblspcOid == DEFAULTTABLESPACE_OID) + snprintf(tblspcPath, MAXPGPATH, "base"); + else if (tblspcOid == GLOBALTABLESPACE_OID) + snprintf(tblspcPath, MAXPGPATH, "global"); + else + snprintf(tblspcPath, MAXPGPATH, "pg_tblspc/%u/%s", tblspcOid, + TABLESPACE_VERSION_DIRECTORY); + + dirdesc = AllocateDir(tblspcPath); + + if (!dirdesc) + return -1; + + while ((direntry = ReadDir(dirdesc, tblspcPath)) != NULL) + { + struct stat fst; + + CHECK_FOR_INTERRUPTS(); + + if (strcmp(direntry->d_name, ".") == 0 || + strcmp(direntry->d_name, "..") == 0) + continue; + + snprintf(pathname, sizeof(pathname), "%s/%s", tblspcPath, direntry->d_name); + + if (stat(pathname, &fst) < 0) + { + if (errno == ENOENT) + continue; + else + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not stat file \"%s\": %m", pathname))); + } + + if (S_ISDIR(fst.st_mode)) + totalsize += db_dir_size(pathname); + + totalsize += fst.st_size; + } + + FreeDir(dirdesc); + + return totalsize; +} + +Datum +pg_tablespace_size_oid(PG_FUNCTION_ARGS) +{ + Oid tblspcOid = PG_GETARG_OID(0); + int64 size; + + size = calculate_tablespace_size(tblspcOid); + + if (size < 0) + PG_RETURN_NULL(); + + PG_RETURN_INT64(size); +} + +Datum +pg_tablespace_size_name(PG_FUNCTION_ARGS) +{ + Name tblspcName = PG_GETARG_NAME(0); + Oid tblspcOid = get_tablespace_oid(NameStr(*tblspcName), false); + int64 size; + + size = calculate_tablespace_size(tblspcOid); + + if (size < 0) + PG_RETURN_NULL(); + + PG_RETURN_INT64(size); +} + + +/* + * calculate size of (one fork of) a relation + * + * Note: we can safely apply this to temp tables of other sessions, so there + * is no check here or at the call sites for that. + */ +static int64 +calculate_relation_size(RelFileLocator *rfn, BackendId backend, ForkNumber forknum) +{ + int64 totalsize = 0; + char *relationpath; + char pathname[MAXPGPATH]; + unsigned int segcount = 0; + + relationpath = relpathbackend(*rfn, backend, forknum); + + for (segcount = 0;; segcount++) + { + struct stat fst; + + CHECK_FOR_INTERRUPTS(); + + if (segcount == 0) + snprintf(pathname, MAXPGPATH, "%s", + relationpath); + else + snprintf(pathname, MAXPGPATH, "%s.%u", + relationpath, segcount); + + if (stat(pathname, &fst) < 0) + { + if (errno == ENOENT) + break; + else + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not stat file \"%s\": %m", pathname))); + } + totalsize += fst.st_size; + } + + return totalsize; +} + +Datum +pg_relation_size(PG_FUNCTION_ARGS) +{ + Oid relOid = PG_GETARG_OID(0); + text *forkName = PG_GETARG_TEXT_PP(1); + Relation rel; + int64 size; + + rel = try_relation_open(relOid, AccessShareLock); + + /* + * Before 9.2, we used to throw an error if the relation didn't exist, but + * that makes queries like "SELECT pg_relation_size(oid) FROM pg_class" + * less robust, because while we scan pg_class with an MVCC snapshot, + * someone else might drop the table. It's better to return NULL for + * already-dropped tables than throw an error and abort the whole query. + */ + if (rel == NULL) + PG_RETURN_NULL(); + + size = calculate_relation_size(&(rel->rd_locator), rel->rd_backend, + forkname_to_number(text_to_cstring(forkName))); + + relation_close(rel, AccessShareLock); + + PG_RETURN_INT64(size); +} + +/* + * Calculate total on-disk size of a TOAST relation, including its indexes. + * Must not be applied to non-TOAST relations. + */ +static int64 +calculate_toast_table_size(Oid toastrelid) +{ + int64 size = 0; + Relation toastRel; + ForkNumber forkNum; + ListCell *lc; + List *indexlist; + + toastRel = relation_open(toastrelid, AccessShareLock); + + /* toast heap size, including FSM and VM size */ + for (forkNum = 0; forkNum <= MAX_FORKNUM; forkNum++) + size += calculate_relation_size(&(toastRel->rd_locator), + toastRel->rd_backend, forkNum); + + /* toast index size, including FSM and VM size */ + indexlist = RelationGetIndexList(toastRel); + + /* Size is calculated using all the indexes available */ + foreach(lc, indexlist) + { + Relation toastIdxRel; + + toastIdxRel = relation_open(lfirst_oid(lc), + AccessShareLock); + for (forkNum = 0; forkNum <= MAX_FORKNUM; forkNum++) + size += calculate_relation_size(&(toastIdxRel->rd_locator), + toastIdxRel->rd_backend, forkNum); + + relation_close(toastIdxRel, AccessShareLock); + } + list_free(indexlist); + relation_close(toastRel, AccessShareLock); + + return size; +} + +/* + * Calculate total on-disk size of a given table, + * including FSM and VM, plus TOAST table if any. + * Indexes other than the TOAST table's index are not included. + * + * Note that this also behaves sanely if applied to an index or toast table; + * those won't have attached toast tables, but they can have multiple forks. + */ +static int64 +calculate_table_size(Relation rel) +{ + int64 size = 0; + ForkNumber forkNum; + + /* + * heap size, including FSM and VM + */ + for (forkNum = 0; forkNum <= MAX_FORKNUM; forkNum++) + size += calculate_relation_size(&(rel->rd_locator), rel->rd_backend, + forkNum); + + /* + * Size of toast relation + */ + if (OidIsValid(rel->rd_rel->reltoastrelid)) + size += calculate_toast_table_size(rel->rd_rel->reltoastrelid); + + return size; +} + +/* + * Calculate total on-disk size of all indexes attached to the given table. + * + * Can be applied safely to an index, but you'll just get zero. + */ +static int64 +calculate_indexes_size(Relation rel) +{ + int64 size = 0; + + /* + * Aggregate all indexes on the given relation + */ + if (rel->rd_rel->relhasindex) + { + List *index_oids = RelationGetIndexList(rel); + ListCell *cell; + + foreach(cell, index_oids) + { + Oid idxOid = lfirst_oid(cell); + Relation idxRel; + ForkNumber forkNum; + + idxRel = relation_open(idxOid, AccessShareLock); + + for (forkNum = 0; forkNum <= MAX_FORKNUM; forkNum++) + size += calculate_relation_size(&(idxRel->rd_locator), + idxRel->rd_backend, + forkNum); + + relation_close(idxRel, AccessShareLock); + } + + list_free(index_oids); + } + + return size; +} + +Datum +pg_table_size(PG_FUNCTION_ARGS) +{ + Oid relOid = PG_GETARG_OID(0); + Relation rel; + int64 size; + + rel = try_relation_open(relOid, AccessShareLock); + + if (rel == NULL) + PG_RETURN_NULL(); + + size = calculate_table_size(rel); + + relation_close(rel, AccessShareLock); + + PG_RETURN_INT64(size); +} + +Datum +pg_indexes_size(PG_FUNCTION_ARGS) +{ + Oid relOid = PG_GETARG_OID(0); + Relation rel; + int64 size; + + rel = try_relation_open(relOid, AccessShareLock); + + if (rel == NULL) + PG_RETURN_NULL(); + + size = calculate_indexes_size(rel); + + relation_close(rel, AccessShareLock); + + PG_RETURN_INT64(size); +} + +/* + * Compute the on-disk size of all files for the relation, + * including heap data, index data, toast data, FSM, VM. + */ +static int64 +calculate_total_relation_size(Relation rel) +{ + int64 size; + + /* + * Aggregate the table size, this includes size of the heap, toast and + * toast index with free space and visibility map + */ + size = calculate_table_size(rel); + + /* + * Add size of all attached indexes as well + */ + size += calculate_indexes_size(rel); + + return size; +} + +Datum +pg_total_relation_size(PG_FUNCTION_ARGS) +{ + return (Datum)0; +} + +Datum +pg_total_relation_size_original(PG_FUNCTION_ARGS) +{ + Oid relOid = PG_GETARG_OID(0); + Relation rel; + int64 size; + + rel = try_relation_open(relOid, AccessShareLock); + + if (rel == NULL) + PG_RETURN_NULL(); + + size = calculate_total_relation_size(rel); + + relation_close(rel, AccessShareLock); + + PG_RETURN_INT64(size); +} + +/* + * formatting with size units + */ +Datum +pg_size_pretty(PG_FUNCTION_ARGS) +{ + int64 size = PG_GETARG_INT64(0); + char buf[64]; + const struct size_pretty_unit *unit; + + for (unit = size_pretty_units; unit->name != NULL; unit++) + { + uint8 bits; + + /* use this unit if there are no more units or we're below the limit */ + if (unit[1].name == NULL || i64abs(size) < unit->limit) + { + if (unit->round) + size = half_rounded(size); + + snprintf(buf, sizeof(buf), INT64_FORMAT " %s", size, unit->name); + break; + } + + /* + * Determine the number of bits to use to build the divisor. We may + * need to use 1 bit less than the difference between this and the + * next unit if the next unit uses half rounding. Or we may need to + * shift an extra bit if this unit uses half rounding and the next one + * does not. We use division rather than shifting right by this + * number of bits to ensure positive and negative values are rounded + * in the same way. + */ + bits = (unit[1].unitbits - unit->unitbits - (unit[1].round == true) + + (unit->round == true)); + size /= ((int64) 1) << bits; + } + + PG_RETURN_TEXT_P(cstring_to_text(buf)); +} + +static char * +numeric_to_cstring(Numeric n) +{ + Datum d = NumericGetDatum(n); + + return DatumGetCString(DirectFunctionCall1(numeric_out, d)); +} + +static bool +numeric_is_less(Numeric a, Numeric b) +{ + Datum da = NumericGetDatum(a); + Datum db = NumericGetDatum(b); + + return DatumGetBool(DirectFunctionCall2(numeric_lt, da, db)); +} + +static Numeric +numeric_absolute(Numeric n) +{ + Datum d = NumericGetDatum(n); + Datum result; + + result = DirectFunctionCall1(numeric_abs, d); + return DatumGetNumeric(result); +} + +static Numeric +numeric_half_rounded(Numeric n) +{ + Datum d = NumericGetDatum(n); + Datum zero; + Datum one; + Datum two; + Datum result; + + zero = NumericGetDatum(int64_to_numeric(0)); + one = NumericGetDatum(int64_to_numeric(1)); + two = NumericGetDatum(int64_to_numeric(2)); + + if (DatumGetBool(DirectFunctionCall2(numeric_ge, d, zero))) + d = DirectFunctionCall2(numeric_add, d, one); + else + d = DirectFunctionCall2(numeric_sub, d, one); + + result = DirectFunctionCall2(numeric_div_trunc, d, two); + return DatumGetNumeric(result); +} + +static Numeric +numeric_truncated_divide(Numeric n, int64 divisor) +{ + Datum d = NumericGetDatum(n); + Datum divisor_numeric; + Datum result; + + divisor_numeric = NumericGetDatum(int64_to_numeric(divisor)); + result = DirectFunctionCall2(numeric_div_trunc, d, divisor_numeric); + return DatumGetNumeric(result); +} + +Datum +pg_size_pretty_numeric(PG_FUNCTION_ARGS) +{ + Numeric size = PG_GETARG_NUMERIC(0); + char *result = NULL; + const struct size_pretty_unit *unit; + + for (unit = size_pretty_units; unit->name != NULL; unit++) + { + unsigned int shiftby; + + /* use this unit if there are no more units or we're below the limit */ + if (unit[1].name == NULL || + numeric_is_less(numeric_absolute(size), + int64_to_numeric(unit->limit))) + { + if (unit->round) + size = numeric_half_rounded(size); + + result = psprintf("%s %s", numeric_to_cstring(size), unit->name); + break; + } + + /* + * Determine the number of bits to use to build the divisor. We may + * need to use 1 bit less than the difference between this and the + * next unit if the next unit uses half rounding. Or we may need to + * shift an extra bit if this unit uses half rounding and the next one + * does not. + */ + shiftby = (unit[1].unitbits - unit->unitbits - (unit[1].round == true) + + (unit->round == true)); + size = numeric_truncated_divide(size, ((int64) 1) << shiftby); + } + + PG_RETURN_TEXT_P(cstring_to_text(result)); +} + +/* + * Convert a human-readable size to a size in bytes + */ +Datum +pg_size_bytes(PG_FUNCTION_ARGS) +{ + text *arg = PG_GETARG_TEXT_PP(0); + char *str, + *strptr, + *endptr; + char saved_char; + Numeric num; + int64 result; + bool have_digits = false; + + str = text_to_cstring(arg); + + /* Skip leading whitespace */ + strptr = str; + while (isspace((unsigned char) *strptr)) + strptr++; + + /* Check that we have a valid number and determine where it ends */ + endptr = strptr; + + /* Part (1): sign */ + if (*endptr == '-' || *endptr == '+') + endptr++; + + /* Part (2): main digit string */ + if (isdigit((unsigned char) *endptr)) + { + have_digits = true; + do + endptr++; + while (isdigit((unsigned char) *endptr)); + } + + /* Part (3): optional decimal point and fractional digits */ + if (*endptr == '.') + { + endptr++; + if (isdigit((unsigned char) *endptr)) + { + have_digits = true; + do + endptr++; + while (isdigit((unsigned char) *endptr)); + } + } + + /* Complain if we don't have a valid number at this point */ + if (!have_digits) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid size: \"%s\"", str))); + + /* Part (4): optional exponent */ + if (*endptr == 'e' || *endptr == 'E') + { + long exponent; + char *cp; + + /* + * Note we might one day support EB units, so if what follows 'E' + * isn't a number, just treat it all as a unit to be parsed. + */ + exponent = strtol(endptr + 1, &cp, 10); + (void) exponent; /* Silence -Wunused-result warnings */ + if (cp > endptr + 1) + endptr = cp; + } + + /* + * Parse the number, saving the next character, which may be the first + * character of the unit string. + */ + saved_char = *endptr; + *endptr = '\0'; + + num = DatumGetNumeric(DirectFunctionCall3(numeric_in, + CStringGetDatum(strptr), + ObjectIdGetDatum(InvalidOid), + Int32GetDatum(-1))); + + *endptr = saved_char; + + /* Skip whitespace between number and unit */ + strptr = endptr; + while (isspace((unsigned char) *strptr)) + strptr++; + + /* Handle possible unit */ + if (*strptr != '\0') + { + const struct size_pretty_unit *unit; + int64 multiplier = 0; + + /* Trim any trailing whitespace */ + endptr = str + VARSIZE_ANY_EXHDR(arg) - 1; + + while (isspace((unsigned char) *endptr)) + endptr--; + + endptr++; + *endptr = '\0'; + + for (unit = size_pretty_units; unit->name != NULL; unit++) + { + /* Parse the unit case-insensitively */ + if (pg_strcasecmp(strptr, unit->name) == 0) + break; + } + + /* If not found, look in table of aliases */ + if (unit->name == NULL) + { + for (const struct size_bytes_unit_alias *a = size_bytes_aliases; a->alias != NULL; a++) + { + if (pg_strcasecmp(strptr, a->alias) == 0) + { + unit = &size_pretty_units[a->unit_index]; + break; + } + } + } + + /* Verify we found a valid unit in the loop above */ + if (unit->name == NULL) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid size: \"%s\"", text_to_cstring(arg)), + errdetail("Invalid size unit: \"%s\".", strptr), + errhint("Valid units are \"bytes\", \"B\", \"kB\", \"MB\", \"GB\", \"TB\", and \"PB\"."))); + + multiplier = ((int64) 1) << unit->unitbits; + + if (multiplier > 1) + { + Numeric mul_num; + + mul_num = int64_to_numeric(multiplier); + + num = DatumGetNumeric(DirectFunctionCall2(numeric_mul, + NumericGetDatum(mul_num), + NumericGetDatum(num))); + } + } + + result = DatumGetInt64(DirectFunctionCall1(numeric_int8, + NumericGetDatum(num))); + + PG_RETURN_INT64(result); +} + +/* + * Get the filenode of a relation + * + * This is expected to be used in queries like + * SELECT pg_relation_filenode(oid) FROM pg_class; + * That leads to a couple of choices. We work from the pg_class row alone + * rather than actually opening each relation, for efficiency. We don't + * fail if we can't find the relation --- some rows might be visible in + * the query's MVCC snapshot even though the relations have been dropped. + * (Note: we could avoid using the catcache, but there's little point + * because the relation mapper also works "in the now".) We also don't + * fail if the relation doesn't have storage. In all these cases it + * seems better to quietly return NULL. + */ +Datum +pg_relation_filenode(PG_FUNCTION_ARGS) +{ + Oid relid = PG_GETARG_OID(0); + RelFileNumber result; + HeapTuple tuple; + Form_pg_class relform; + + tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(tuple)) + PG_RETURN_NULL(); + relform = (Form_pg_class) GETSTRUCT(tuple); + + if (RELKIND_HAS_STORAGE(relform->relkind)) + { + if (relform->relfilenode) + result = relform->relfilenode; + else /* Consult the relation mapper */ + result = RelationMapOidToFilenumber(relid, + relform->relisshared); + } + else + { + /* no storage, return NULL */ + result = InvalidRelFileNumber; + } + + ReleaseSysCache(tuple); + + if (!RelFileNumberIsValid(result)) + PG_RETURN_NULL(); + + PG_RETURN_OID(result); +} + +/* + * Get the relation via (reltablespace, relfilenumber) + * + * This is expected to be used when somebody wants to match an individual file + * on the filesystem back to its table. That's not trivially possible via + * pg_class, because that doesn't contain the relfilenumbers of shared and nailed + * tables. + * + * We don't fail but return NULL if we cannot find a mapping. + * + * InvalidOid can be passed instead of the current database's default + * tablespace. + */ +Datum +pg_filenode_relation(PG_FUNCTION_ARGS) +{ + Oid reltablespace = PG_GETARG_OID(0); + RelFileNumber relfilenumber = PG_GETARG_OID(1); + Oid heaprel; + + /* test needed so RelidByRelfilenumber doesn't misbehave */ + if (!RelFileNumberIsValid(relfilenumber)) + PG_RETURN_NULL(); + + heaprel = RelidByRelfilenumber(reltablespace, relfilenumber); + + if (!OidIsValid(heaprel)) + PG_RETURN_NULL(); + else + PG_RETURN_OID(heaprel); +} + +/* + * Get the pathname (relative to $PGDATA) of a relation + * + * See comments for pg_relation_filenode. + */ +Datum +pg_relation_filepath(PG_FUNCTION_ARGS) +{ + Oid relid = PG_GETARG_OID(0); + HeapTuple tuple; + Form_pg_class relform; + RelFileLocator rlocator; + BackendId backend; + char *path; + + tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(tuple)) + PG_RETURN_NULL(); + relform = (Form_pg_class) GETSTRUCT(tuple); + + if (RELKIND_HAS_STORAGE(relform->relkind)) + { + /* This logic should match RelationInitPhysicalAddr */ + if (relform->reltablespace) + rlocator.spcOid = relform->reltablespace; + else + rlocator.spcOid = MyDatabaseTableSpace; + if (rlocator.spcOid == GLOBALTABLESPACE_OID) + rlocator.dbOid = InvalidOid; + else + rlocator.dbOid = MyDatabaseId; + if (relform->relfilenode) + rlocator.relNumber = relform->relfilenode; + else /* Consult the relation mapper */ + rlocator.relNumber = RelationMapOidToFilenumber(relid, + relform->relisshared); + } + else + { + /* no storage, return NULL */ + rlocator.relNumber = InvalidRelFileNumber; + /* some compilers generate warnings without these next two lines */ + rlocator.dbOid = InvalidOid; + rlocator.spcOid = InvalidOid; + } + + if (!RelFileNumberIsValid(rlocator.relNumber)) + { + ReleaseSysCache(tuple); + PG_RETURN_NULL(); + } + + /* Determine owning backend. */ + switch (relform->relpersistence) + { + case RELPERSISTENCE_UNLOGGED: + case RELPERSISTENCE_PERMANENT: + backend = InvalidBackendId; + break; + case RELPERSISTENCE_TEMP: + if (isTempOrTempToastNamespace(relform->relnamespace)) + backend = BackendIdForTempRelations(); + else + { + /* Do it the hard way. */ + backend = GetTempNamespaceBackendId(relform->relnamespace); + Assert(backend != InvalidBackendId); + } + break; + default: + elog(ERROR, "invalid relpersistence: %c", relform->relpersistence); + backend = InvalidBackendId; /* placate compiler */ + break; + } + + ReleaseSysCache(tuple); + + path = relpathbackend(rlocator, backend, MAIN_FORKNUM); + + PG_RETURN_TEXT_P(cstring_to_text(path)); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/domains.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/domains.c new file mode 100644 index 00000000000..8d766f68e31 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/domains.c @@ -0,0 +1,406 @@ +/*------------------------------------------------------------------------- + * + * domains.c + * I/O functions for domain types. + * + * The output functions for a domain type are just the same ones provided + * by its underlying base type. The input functions, however, must be + * prepared to apply any constraints defined by the type. So, we create + * special input functions that invoke the base type's input function + * and then check the constraints. + * + * The overhead required for constraint checking can be high, since examining + * the catalogs to discover the constraints for a given domain is not cheap. + * We have three mechanisms for minimizing this cost: + * 1. We rely on the typcache to keep up-to-date copies of the constraints. + * 2. In a nest of domains, we flatten the checking of all the levels + * into just one operation (the typcache does this for us). + * 3. If there are CHECK constraints, we cache a standalone ExprContext + * to evaluate them in. + * + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/adt/domains.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/htup_details.h" +#include "catalog/pg_type.h" +#include "executor/executor.h" +#include "lib/stringinfo.h" +#include "utils/builtins.h" +#include "utils/expandeddatum.h" +#include "utils/lsyscache.h" +#include "utils/syscache.h" +#include "utils/typcache.h" + + +/* + * structure to cache state across multiple calls + */ +typedef struct DomainIOData +{ + Oid domain_type; + /* Data needed to call base type's input function */ + Oid typiofunc; + Oid typioparam; + int32 typtypmod; + FmgrInfo proc; + /* Reference to cached list of constraint items to check */ + DomainConstraintRef constraint_ref; + /* Context for evaluating CHECK constraints in */ + ExprContext *econtext; + /* Memory context this cache is in */ + MemoryContext mcxt; +} DomainIOData; + + +/* + * domain_state_setup - initialize the cache for a new domain type. + * + * Note: we can't re-use the same cache struct for a new domain type, + * since there's no provision for releasing the DomainConstraintRef. + * If a call site needs to deal with a new domain type, we just leak + * the old struct for the duration of the query. + */ +static DomainIOData * +domain_state_setup(Oid domainType, bool binary, MemoryContext mcxt) +{ + DomainIOData *my_extra; + TypeCacheEntry *typentry; + Oid baseType; + + my_extra = (DomainIOData *) MemoryContextAlloc(mcxt, sizeof(DomainIOData)); + + /* + * Verify that domainType represents a valid domain type. We need to be + * careful here because domain_in and domain_recv can be called from SQL, + * possibly with incorrect arguments. We use lookup_type_cache mainly + * because it will throw a clean user-facing error for a bad OID; but also + * it can cache the underlying base type info. + */ + typentry = lookup_type_cache(domainType, TYPECACHE_DOMAIN_BASE_INFO); + if (typentry->typtype != TYPTYPE_DOMAIN) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("type %s is not a domain", + format_type_be(domainType)))); + + /* Find out the base type */ + baseType = typentry->domainBaseType; + my_extra->typtypmod = typentry->domainBaseTypmod; + + /* Look up underlying I/O function */ + if (binary) + getTypeBinaryInputInfo(baseType, + &my_extra->typiofunc, + &my_extra->typioparam); + else + getTypeInputInfo(baseType, + &my_extra->typiofunc, + &my_extra->typioparam); + fmgr_info_cxt(my_extra->typiofunc, &my_extra->proc, mcxt); + + /* Look up constraints for domain */ + InitDomainConstraintRef(domainType, &my_extra->constraint_ref, mcxt, true); + + /* We don't make an ExprContext until needed */ + my_extra->econtext = NULL; + my_extra->mcxt = mcxt; + + /* Mark cache valid */ + my_extra->domain_type = domainType; + + return my_extra; +} + +/* + * domain_check_input - apply the cached checks. + * + * This is roughly similar to the handling of CoerceToDomain nodes in + * execExpr*.c, but we execute each constraint separately, rather than + * compiling them in-line within a larger expression. + * + * If escontext points to an ErrorSaveContext, any failures are reported + * there, otherwise they are ereport'ed. Note that we do not attempt to do + * soft reporting of errors raised during execution of CHECK constraints. + */ +static void +domain_check_input(Datum value, bool isnull, DomainIOData *my_extra, + Node *escontext) +{ + ExprContext *econtext = my_extra->econtext; + ListCell *l; + + /* Make sure we have up-to-date constraints */ + UpdateDomainConstraintRef(&my_extra->constraint_ref); + + foreach(l, my_extra->constraint_ref.constraints) + { + DomainConstraintState *con = (DomainConstraintState *) lfirst(l); + + switch (con->constrainttype) + { + case DOM_CONSTRAINT_NOTNULL: + if (isnull) + { + errsave(escontext, + (errcode(ERRCODE_NOT_NULL_VIOLATION), + errmsg("domain %s does not allow null values", + format_type_be(my_extra->domain_type)), + errdatatype(my_extra->domain_type))); + goto fail; + } + break; + case DOM_CONSTRAINT_CHECK: + { + /* Make the econtext if we didn't already */ + if (econtext == NULL) + { + MemoryContext oldcontext; + + oldcontext = MemoryContextSwitchTo(my_extra->mcxt); + econtext = CreateStandaloneExprContext(); + MemoryContextSwitchTo(oldcontext); + my_extra->econtext = econtext; + } + + /* + * Set up value to be returned by CoerceToDomainValue + * nodes. Unlike in the generic expression case, this + * econtext couldn't be shared with anything else, so no + * need to save and restore fields. But we do need to + * protect the passed-in value against being changed by + * called functions. (It couldn't be a R/W expanded + * object for most uses, but that seems possible for + * domain_check().) + */ + econtext->domainValue_datum = + MakeExpandedObjectReadOnly(value, isnull, + my_extra->constraint_ref.tcache->typlen); + econtext->domainValue_isNull = isnull; + + if (!ExecCheck(con->check_exprstate, econtext)) + { + errsave(escontext, + (errcode(ERRCODE_CHECK_VIOLATION), + errmsg("value for domain %s violates check constraint \"%s\"", + format_type_be(my_extra->domain_type), + con->name), + errdomainconstraint(my_extra->domain_type, + con->name))); + goto fail; + } + break; + } + default: + elog(ERROR, "unrecognized constraint type: %d", + (int) con->constrainttype); + break; + } + } + + /* + * Before exiting, call any shutdown callbacks and reset econtext's + * per-tuple memory. This avoids leaking non-memory resources, if + * anything in the expression(s) has any. + */ +fail: + if (econtext) + ReScanExprContext(econtext); +} + + +/* + * domain_in - input routine for any domain type. + */ +Datum +domain_in(PG_FUNCTION_ARGS) +{ + char *string; + Oid domainType; + Node *escontext = fcinfo->context; + DomainIOData *my_extra; + Datum value; + + /* + * Since domain_in is not strict, we have to check for null inputs. The + * typioparam argument should never be null in normal system usage, but it + * could be null in a manual invocation --- if so, just return null. + */ + if (PG_ARGISNULL(0)) + string = NULL; + else + string = PG_GETARG_CSTRING(0); + if (PG_ARGISNULL(1)) + PG_RETURN_NULL(); + domainType = PG_GETARG_OID(1); + + /* + * We arrange to look up the needed info just once per series of calls, + * assuming the domain type doesn't change underneath us (which really + * shouldn't happen, but cope if it does). + */ + my_extra = (DomainIOData *) fcinfo->flinfo->fn_extra; + if (my_extra == NULL || my_extra->domain_type != domainType) + { + my_extra = domain_state_setup(domainType, false, + fcinfo->flinfo->fn_mcxt); + fcinfo->flinfo->fn_extra = (void *) my_extra; + } + + /* + * Invoke the base type's typinput procedure to convert the data. + */ + if (!InputFunctionCallSafe(&my_extra->proc, + string, + my_extra->typioparam, + my_extra->typtypmod, + escontext, + &value)) + PG_RETURN_NULL(); + + /* + * Do the necessary checks to ensure it's a valid domain value. + */ + domain_check_input(value, (string == NULL), my_extra, escontext); + + if (string == NULL) + PG_RETURN_NULL(); + else + PG_RETURN_DATUM(value); +} + +/* + * domain_recv - binary input routine for any domain type. + */ +Datum +domain_recv(PG_FUNCTION_ARGS) +{ + StringInfo buf; + Oid domainType; + DomainIOData *my_extra; + Datum value; + + /* + * Since domain_recv is not strict, we have to check for null inputs. The + * typioparam argument should never be null in normal system usage, but it + * could be null in a manual invocation --- if so, just return null. + */ + if (PG_ARGISNULL(0)) + buf = NULL; + else + buf = (StringInfo) PG_GETARG_POINTER(0); + if (PG_ARGISNULL(1)) + PG_RETURN_NULL(); + domainType = PG_GETARG_OID(1); + + /* + * We arrange to look up the needed info just once per series of calls, + * assuming the domain type doesn't change underneath us (which really + * shouldn't happen, but cope if it does). + */ + my_extra = (DomainIOData *) fcinfo->flinfo->fn_extra; + if (my_extra == NULL || my_extra->domain_type != domainType) + { + my_extra = domain_state_setup(domainType, true, + fcinfo->flinfo->fn_mcxt); + fcinfo->flinfo->fn_extra = (void *) my_extra; + } + + /* + * Invoke the base type's typreceive procedure to convert the data. + */ + value = ReceiveFunctionCall(&my_extra->proc, + buf, + my_extra->typioparam, + my_extra->typtypmod); + + /* + * Do the necessary checks to ensure it's a valid domain value. + */ + domain_check_input(value, (buf == NULL), my_extra, NULL); + + if (buf == NULL) + PG_RETURN_NULL(); + else + PG_RETURN_DATUM(value); +} + +/* + * domain_check - check that a datum satisfies the constraints of a + * domain. extra and mcxt can be passed if they are available from, + * say, a FmgrInfo structure, or they can be NULL, in which case the + * setup is repeated for each call. + */ +void +domain_check(Datum value, bool isnull, Oid domainType, + void **extra, MemoryContext mcxt) +{ + DomainIOData *my_extra = NULL; + + if (mcxt == NULL) + mcxt = CurrentMemoryContext; + + /* + * We arrange to look up the needed info just once per series of calls, + * assuming the domain type doesn't change underneath us (which really + * shouldn't happen, but cope if it does). + */ + if (extra) + my_extra = (DomainIOData *) *extra; + if (my_extra == NULL || my_extra->domain_type != domainType) + { + my_extra = domain_state_setup(domainType, true, mcxt); + if (extra) + *extra = (void *) my_extra; + } + + /* + * Do the necessary checks to ensure it's a valid domain value. + */ + domain_check_input(value, isnull, my_extra, NULL); +} + +/* + * errdatatype --- stores schema_name and datatype_name of a datatype + * within the current errordata. + */ +int +errdatatype(Oid datatypeOid) +{ + HeapTuple tup; + Form_pg_type typtup; + + tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(datatypeOid)); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for type %u", datatypeOid); + typtup = (Form_pg_type) GETSTRUCT(tup); + + err_generic_string(PG_DIAG_SCHEMA_NAME, + get_namespace_name(typtup->typnamespace)); + err_generic_string(PG_DIAG_DATATYPE_NAME, NameStr(typtup->typname)); + + ReleaseSysCache(tup); + + return 0; /* return value does not matter */ +} + +/* + * errdomainconstraint --- stores schema_name, datatype_name and + * constraint_name of a domain-related constraint within the current errordata. + */ +int +errdomainconstraint(Oid datatypeOid, const char *conname) +{ + errdatatype(datatypeOid); + err_generic_string(PG_DIAG_CONSTRAINT_NAME, conname); + + return 0; /* return value does not matter */ +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/encode.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/encode.c new file mode 100644 index 00000000000..e5ac3ad23df --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/encode.c @@ -0,0 +1,612 @@ +/*------------------------------------------------------------------------- + * + * encode.c + * Various data encoding/decoding things. + * + * Copyright (c) 2001-2023, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/utils/adt/encode.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <ctype.h> + +#include "mb/pg_wchar.h" +#include "utils/builtins.h" +#include "utils/memutils.h" +#include "varatt.h" + + +/* + * Encoding conversion API. + * encode_len() and decode_len() compute the amount of space needed, while + * encode() and decode() perform the actual conversions. It is okay for + * the _len functions to return an overestimate, but not an underestimate. + * (Having said that, large overestimates could cause unnecessary errors, + * so it's better to get it right.) The conversion routines write to the + * buffer at *res and return the true length of their output. + */ +struct pg_encoding +{ + uint64 (*encode_len) (const char *data, size_t dlen); + uint64 (*decode_len) (const char *data, size_t dlen); + uint64 (*encode) (const char *data, size_t dlen, char *res); + uint64 (*decode) (const char *data, size_t dlen, char *res); +}; + +static const struct pg_encoding *pg_find_encoding(const char *name); + +/* + * SQL functions. + */ + +Datum +binary_encode(PG_FUNCTION_ARGS) +{ + bytea *data = PG_GETARG_BYTEA_PP(0); + Datum name = PG_GETARG_DATUM(1); + text *result; + char *namebuf; + char *dataptr; + size_t datalen; + uint64 resultlen; + uint64 res; + const struct pg_encoding *enc; + + namebuf = TextDatumGetCString(name); + + enc = pg_find_encoding(namebuf); + if (enc == NULL) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unrecognized encoding: \"%s\"", namebuf))); + + dataptr = VARDATA_ANY(data); + datalen = VARSIZE_ANY_EXHDR(data); + + resultlen = enc->encode_len(dataptr, datalen); + + /* + * resultlen possibly overflows uint32, therefore on 32-bit machines it's + * unsafe to rely on palloc's internal check. + */ + if (resultlen > MaxAllocSize - VARHDRSZ) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("result of encoding conversion is too large"))); + + result = palloc(VARHDRSZ + resultlen); + + res = enc->encode(dataptr, datalen, VARDATA(result)); + + /* Make this FATAL 'cause we've trodden on memory ... */ + if (res > resultlen) + elog(FATAL, "overflow - encode estimate too small"); + + SET_VARSIZE(result, VARHDRSZ + res); + + PG_RETURN_TEXT_P(result); +} + +Datum +binary_decode(PG_FUNCTION_ARGS) +{ + text *data = PG_GETARG_TEXT_PP(0); + Datum name = PG_GETARG_DATUM(1); + bytea *result; + char *namebuf; + char *dataptr; + size_t datalen; + uint64 resultlen; + uint64 res; + const struct pg_encoding *enc; + + namebuf = TextDatumGetCString(name); + + enc = pg_find_encoding(namebuf); + if (enc == NULL) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unrecognized encoding: \"%s\"", namebuf))); + + dataptr = VARDATA_ANY(data); + datalen = VARSIZE_ANY_EXHDR(data); + + resultlen = enc->decode_len(dataptr, datalen); + + /* + * resultlen possibly overflows uint32, therefore on 32-bit machines it's + * unsafe to rely on palloc's internal check. + */ + if (resultlen > MaxAllocSize - VARHDRSZ) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("result of decoding conversion is too large"))); + + result = palloc(VARHDRSZ + resultlen); + + res = enc->decode(dataptr, datalen, VARDATA(result)); + + /* Make this FATAL 'cause we've trodden on memory ... */ + if (res > resultlen) + elog(FATAL, "overflow - decode estimate too small"); + + SET_VARSIZE(result, VARHDRSZ + res); + + PG_RETURN_BYTEA_P(result); +} + + +/* + * HEX + */ + +static const char hextbl[] = "0123456789abcdef"; + +static const int8 hexlookup[128] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, +}; + +uint64 +hex_encode(const char *src, size_t len, char *dst) +{ + const char *end = src + len; + + while (src < end) + { + *dst++ = hextbl[(*src >> 4) & 0xF]; + *dst++ = hextbl[*src & 0xF]; + src++; + } + return (uint64) len * 2; +} + +static inline bool +get_hex(const char *cp, char *out) +{ + unsigned char c = (unsigned char) *cp; + int res = -1; + + if (c < 127) + res = hexlookup[c]; + + *out = (char) res; + + return (res >= 0); +} + +uint64 +hex_decode(const char *src, size_t len, char *dst) +{ + return hex_decode_safe(src, len, dst, NULL); +} + +uint64 +hex_decode_safe(const char *src, size_t len, char *dst, Node *escontext) +{ + const char *s, + *srcend; + char v1, + v2, + *p; + + srcend = src + len; + s = src; + p = dst; + while (s < srcend) + { + if (*s == ' ' || *s == '\n' || *s == '\t' || *s == '\r') + { + s++; + continue; + } + if (!get_hex(s, &v1)) + ereturn(escontext, 0, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid hexadecimal digit: \"%.*s\"", + pg_mblen(s), s))); + s++; + if (s >= srcend) + ereturn(escontext, 0, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid hexadecimal data: odd number of digits"))); + if (!get_hex(s, &v2)) + ereturn(escontext, 0, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid hexadecimal digit: \"%.*s\"", + pg_mblen(s), s))); + s++; + *p++ = (v1 << 4) | v2; + } + + return p - dst; +} + +static uint64 +hex_enc_len(const char *src, size_t srclen) +{ + return (uint64) srclen << 1; +} + +static uint64 +hex_dec_len(const char *src, size_t srclen) +{ + return (uint64) srclen >> 1; +} + +/* + * BASE64 + */ + +static const char _base64[] = +"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +static const int8 b64lookup[128] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, +}; + +static uint64 +pg_base64_encode(const char *src, size_t len, char *dst) +{ + char *p, + *lend = dst + 76; + const char *s, + *end = src + len; + int pos = 2; + uint32 buf = 0; + + s = src; + p = dst; + + while (s < end) + { + buf |= (unsigned char) *s << (pos << 3); + pos--; + s++; + + /* write it out */ + if (pos < 0) + { + *p++ = _base64[(buf >> 18) & 0x3f]; + *p++ = _base64[(buf >> 12) & 0x3f]; + *p++ = _base64[(buf >> 6) & 0x3f]; + *p++ = _base64[buf & 0x3f]; + + pos = 2; + buf = 0; + } + if (p >= lend) + { + *p++ = '\n'; + lend = p + 76; + } + } + if (pos != 2) + { + *p++ = _base64[(buf >> 18) & 0x3f]; + *p++ = _base64[(buf >> 12) & 0x3f]; + *p++ = (pos == 0) ? _base64[(buf >> 6) & 0x3f] : '='; + *p++ = '='; + } + + return p - dst; +} + +static uint64 +pg_base64_decode(const char *src, size_t len, char *dst) +{ + const char *srcend = src + len, + *s = src; + char *p = dst; + char c; + int b = 0; + uint32 buf = 0; + int pos = 0, + end = 0; + + while (s < srcend) + { + c = *s++; + + if (c == ' ' || c == '\t' || c == '\n' || c == '\r') + continue; + + if (c == '=') + { + /* end sequence */ + if (!end) + { + if (pos == 2) + end = 1; + else if (pos == 3) + end = 2; + else + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unexpected \"=\" while decoding base64 sequence"))); + } + b = 0; + } + else + { + b = -1; + if (c > 0 && c < 127) + b = b64lookup[(unsigned char) c]; + if (b < 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid symbol \"%.*s\" found while decoding base64 sequence", + pg_mblen(s - 1), s - 1))); + } + /* add it to buffer */ + buf = (buf << 6) + b; + pos++; + if (pos == 4) + { + *p++ = (buf >> 16) & 255; + if (end == 0 || end > 1) + *p++ = (buf >> 8) & 255; + if (end == 0 || end > 2) + *p++ = buf & 255; + buf = 0; + pos = 0; + } + } + + if (pos != 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid base64 end sequence"), + errhint("Input data is missing padding, is truncated, or is otherwise corrupted."))); + + return p - dst; +} + + +static uint64 +pg_base64_enc_len(const char *src, size_t srclen) +{ + /* 3 bytes will be converted to 4, linefeed after 76 chars */ + return ((uint64) srclen + 2) / 3 * 4 + (uint64) srclen / (76 * 3 / 4); +} + +static uint64 +pg_base64_dec_len(const char *src, size_t srclen) +{ + return ((uint64) srclen * 3) >> 2; +} + +/* + * Escape + * Minimally escape bytea to text. + * De-escape text to bytea. + * + * We must escape zero bytes and high-bit-set bytes to avoid generating + * text that might be invalid in the current encoding, or that might + * change to something else if passed through an encoding conversion + * (leading to failing to de-escape to the original bytea value). + * Also of course backslash itself has to be escaped. + * + * De-escaping processes \\ and any \### octal + */ + +#define VAL(CH) ((CH) - '0') +#define DIG(VAL) ((VAL) + '0') + +static uint64 +esc_encode(const char *src, size_t srclen, char *dst) +{ + const char *end = src + srclen; + char *rp = dst; + uint64 len = 0; + + while (src < end) + { + unsigned char c = (unsigned char) *src; + + if (c == '\0' || IS_HIGHBIT_SET(c)) + { + rp[0] = '\\'; + rp[1] = DIG(c >> 6); + rp[2] = DIG((c >> 3) & 7); + rp[3] = DIG(c & 7); + rp += 4; + len += 4; + } + else if (c == '\\') + { + rp[0] = '\\'; + rp[1] = '\\'; + rp += 2; + len += 2; + } + else + { + *rp++ = c; + len++; + } + + src++; + } + + return len; +} + +static uint64 +esc_decode(const char *src, size_t srclen, char *dst) +{ + const char *end = src + srclen; + char *rp = dst; + uint64 len = 0; + + while (src < end) + { + if (src[0] != '\\') + *rp++ = *src++; + else if (src + 3 < end && + (src[1] >= '0' && src[1] <= '3') && + (src[2] >= '0' && src[2] <= '7') && + (src[3] >= '0' && src[3] <= '7')) + { + int val; + + val = VAL(src[1]); + val <<= 3; + val += VAL(src[2]); + val <<= 3; + *rp++ = val + VAL(src[3]); + src += 4; + } + else if (src + 1 < end && + (src[1] == '\\')) + { + *rp++ = '\\'; + src += 2; + } + else + { + /* + * One backslash, not followed by ### valid octal. Should never + * get here, since esc_dec_len does same check. + */ + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s", "bytea"))); + } + + len++; + } + + return len; +} + +static uint64 +esc_enc_len(const char *src, size_t srclen) +{ + const char *end = src + srclen; + uint64 len = 0; + + while (src < end) + { + if (*src == '\0' || IS_HIGHBIT_SET(*src)) + len += 4; + else if (*src == '\\') + len += 2; + else + len++; + + src++; + } + + return len; +} + +static uint64 +esc_dec_len(const char *src, size_t srclen) +{ + const char *end = src + srclen; + uint64 len = 0; + + while (src < end) + { + if (src[0] != '\\') + src++; + else if (src + 3 < end && + (src[1] >= '0' && src[1] <= '3') && + (src[2] >= '0' && src[2] <= '7') && + (src[3] >= '0' && src[3] <= '7')) + { + /* + * backslash + valid octal + */ + src += 4; + } + else if (src + 1 < end && + (src[1] == '\\')) + { + /* + * two backslashes = backslash + */ + src += 2; + } + else + { + /* + * one backslash, not followed by ### valid octal + */ + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s", "bytea"))); + } + + len++; + } + return len; +} + +/* + * Common + */ + +static const struct +{ + const char *name; + struct pg_encoding enc; +} enclist[] = + +{ + { + "hex", + { + hex_enc_len, hex_dec_len, hex_encode, hex_decode + } + }, + { + "base64", + { + pg_base64_enc_len, pg_base64_dec_len, pg_base64_encode, pg_base64_decode + } + }, + { + "escape", + { + esc_enc_len, esc_dec_len, esc_encode, esc_decode + } + }, + { + NULL, + { + NULL, NULL, NULL, NULL + } + } +}; + +static const struct pg_encoding * +pg_find_encoding(const char *name) +{ + int i; + + for (i = 0; enclist[i].name; i++) + if (pg_strcasecmp(enclist[i].name, name) == 0) + return &enclist[i].enc; + + return NULL; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/enum.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/enum.c new file mode 100644 index 00000000000..fdfdf7d0d2c --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/enum.c @@ -0,0 +1,616 @@ +/*------------------------------------------------------------------------- + * + * enum.c + * I/O functions, operators, aggregates etc for enum types + * + * Copyright (c) 2006-2023, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/utils/adt/enum.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/genam.h" +#include "access/htup_details.h" +#include "access/table.h" +#include "catalog/pg_enum.h" +#include "libpq/pqformat.h" +#include "storage/procarray.h" +#include "utils/array.h" +#include "utils/builtins.h" +#include "utils/fmgroids.h" +#include "utils/snapmgr.h" +#include "utils/syscache.h" +#include "utils/typcache.h" + + +static Oid enum_endpoint(Oid enumtypoid, ScanDirection direction); +static ArrayType *enum_range_internal(Oid enumtypoid, Oid lower, Oid upper); + + +/* + * Disallow use of an uncommitted pg_enum tuple. + * + * We need to make sure that uncommitted enum values don't get into indexes. + * If they did, and if we then rolled back the pg_enum addition, we'd have + * broken the index because value comparisons will not work reliably without + * an underlying pg_enum entry. (Note that removal of the heap entry + * containing an enum value is not sufficient to ensure that it doesn't appear + * in upper levels of indexes.) To do this we prevent an uncommitted row from + * being used for any SQL-level purpose. This is stronger than necessary, + * since the value might not be getting inserted into a table or there might + * be no index on its column, but it's easy to enforce centrally. + * + * However, it's okay to allow use of uncommitted values belonging to enum + * types that were themselves created in the same transaction, because then + * any such index would also be new and would go away altogether on rollback. + * We don't implement that fully right now, but we do allow free use of enum + * values created during CREATE TYPE AS ENUM, which are surely of the same + * lifespan as the enum type. (This case is required by "pg_restore -1".) + * Values added by ALTER TYPE ADD VALUE are currently restricted, but could + * be allowed if the enum type could be proven to have been created earlier + * in the same transaction. (Note that comparing tuple xmins would not work + * for that, because the type tuple might have been updated in the current + * transaction. Subtransactions also create hazards to be accounted for.) + * + * This function needs to be called (directly or indirectly) in any of the + * functions below that could return an enum value to SQL operations. + */ +static void +check_safe_enum_use(HeapTuple enumval_tup) +{ + TransactionId xmin; + Form_pg_enum en = (Form_pg_enum) GETSTRUCT(enumval_tup); + + /* + * If the row is hinted as committed, it's surely safe. This provides a + * fast path for all normal use-cases. + */ + if (HeapTupleHeaderXminCommitted(enumval_tup->t_data)) + return; + + /* + * Usually, a row would get hinted as committed when it's read or loaded + * into syscache; but just in case not, let's check the xmin directly. + */ + xmin = HeapTupleHeaderGetXmin(enumval_tup->t_data); + if (!TransactionIdIsInProgress(xmin) && + TransactionIdDidCommit(xmin)) + return; + + /* + * Check if the enum value is uncommitted. If not, it's safe, because it + * was made during CREATE TYPE AS ENUM and can't be shorter-lived than its + * owning type. (This'd also be false for values made by other + * transactions; but the previous tests should have handled all of those.) + */ + if (!EnumUncommitted(en->oid)) + return; + + /* + * There might well be other tests we could do here to narrow down the + * unsafe conditions, but for now just raise an exception. + */ + ereport(ERROR, + (errcode(ERRCODE_UNSAFE_NEW_ENUM_VALUE_USAGE), + errmsg("unsafe use of new value \"%s\" of enum type %s", + NameStr(en->enumlabel), + format_type_be(en->enumtypid)), + errhint("New enum values must be committed before they can be used."))); +} + + +/* Basic I/O support */ + +Datum +enum_in(PG_FUNCTION_ARGS) +{ + char *name = PG_GETARG_CSTRING(0); + Oid enumtypoid = PG_GETARG_OID(1); + Node *escontext = fcinfo->context; + Oid enumoid; + HeapTuple tup; + + /* must check length to prevent Assert failure within SearchSysCache */ + if (strlen(name) >= NAMEDATALEN) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input value for enum %s: \"%s\"", + format_type_be(enumtypoid), + name))); + + tup = SearchSysCache2(ENUMTYPOIDNAME, + ObjectIdGetDatum(enumtypoid), + CStringGetDatum(name)); + if (!HeapTupleIsValid(tup)) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input value for enum %s: \"%s\"", + format_type_be(enumtypoid), + name))); + + /* + * Check it's safe to use in SQL. Perhaps we should take the trouble to + * report "unsafe use" softly; but it's unclear that it's worth the + * trouble, or indeed that that is a legitimate bad-input case at all + * rather than an implementation shortcoming. + */ + check_safe_enum_use(tup); + + /* + * This comes from pg_enum.oid and stores system oids in user tables. This + * oid must be preserved by binary upgrades. + */ + enumoid = ((Form_pg_enum) GETSTRUCT(tup))->oid; + + ReleaseSysCache(tup); + + PG_RETURN_OID(enumoid); +} + +Datum +enum_out(PG_FUNCTION_ARGS) +{ + Oid enumval = PG_GETARG_OID(0); + char *result; + HeapTuple tup; + Form_pg_enum en; + + tup = SearchSysCache1(ENUMOID, ObjectIdGetDatum(enumval)); + if (!HeapTupleIsValid(tup)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION), + errmsg("invalid internal value for enum: %u", + enumval))); + en = (Form_pg_enum) GETSTRUCT(tup); + + result = pstrdup(NameStr(en->enumlabel)); + + ReleaseSysCache(tup); + + PG_RETURN_CSTRING(result); +} + +/* Binary I/O support */ +Datum +enum_recv(PG_FUNCTION_ARGS) +{ + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + Oid enumtypoid = PG_GETARG_OID(1); + Oid enumoid; + HeapTuple tup; + char *name; + int nbytes; + + name = pq_getmsgtext(buf, buf->len - buf->cursor, &nbytes); + + /* must check length to prevent Assert failure within SearchSysCache */ + if (strlen(name) >= NAMEDATALEN) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input value for enum %s: \"%s\"", + format_type_be(enumtypoid), + name))); + + tup = SearchSysCache2(ENUMTYPOIDNAME, + ObjectIdGetDatum(enumtypoid), + CStringGetDatum(name)); + if (!HeapTupleIsValid(tup)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input value for enum %s: \"%s\"", + format_type_be(enumtypoid), + name))); + + /* check it's safe to use in SQL */ + check_safe_enum_use(tup); + + enumoid = ((Form_pg_enum) GETSTRUCT(tup))->oid; + + ReleaseSysCache(tup); + + pfree(name); + + PG_RETURN_OID(enumoid); +} + +Datum +enum_send(PG_FUNCTION_ARGS) +{ + Oid enumval = PG_GETARG_OID(0); + StringInfoData buf; + HeapTuple tup; + Form_pg_enum en; + + tup = SearchSysCache1(ENUMOID, ObjectIdGetDatum(enumval)); + if (!HeapTupleIsValid(tup)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION), + errmsg("invalid internal value for enum: %u", + enumval))); + en = (Form_pg_enum) GETSTRUCT(tup); + + pq_begintypsend(&buf); + pq_sendtext(&buf, NameStr(en->enumlabel), strlen(NameStr(en->enumlabel))); + + ReleaseSysCache(tup); + + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} + +/* Comparison functions and related */ + +/* + * enum_cmp_internal is the common engine for all the visible comparison + * functions, except for enum_eq and enum_ne which can just check for OID + * equality directly. + */ +static int +enum_cmp_internal(Oid arg1, Oid arg2, FunctionCallInfo fcinfo) +{ + TypeCacheEntry *tcache; + + /* + * We don't need the typcache except in the hopefully-uncommon case that + * one or both Oids are odd. This means that cursory testing of code that + * fails to pass flinfo to an enum comparison function might not disclose + * the oversight. To make such errors more obvious, Assert that we have a + * place to cache even when we take a fast-path exit. + */ + Assert(fcinfo->flinfo != NULL); + + /* Equal OIDs are equal no matter what */ + if (arg1 == arg2) + return 0; + + /* Fast path: even-numbered Oids are known to compare correctly */ + if ((arg1 & 1) == 0 && (arg2 & 1) == 0) + { + if (arg1 < arg2) + return -1; + else + return 1; + } + + /* Locate the typcache entry for the enum type */ + tcache = (TypeCacheEntry *) fcinfo->flinfo->fn_extra; + if (tcache == NULL) + { + HeapTuple enum_tup; + Form_pg_enum en; + Oid typeoid; + + /* Get the OID of the enum type containing arg1 */ + enum_tup = SearchSysCache1(ENUMOID, ObjectIdGetDatum(arg1)); + if (!HeapTupleIsValid(enum_tup)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION), + errmsg("invalid internal value for enum: %u", + arg1))); + en = (Form_pg_enum) GETSTRUCT(enum_tup); + typeoid = en->enumtypid; + ReleaseSysCache(enum_tup); + /* Now locate and remember the typcache entry */ + tcache = lookup_type_cache(typeoid, 0); + fcinfo->flinfo->fn_extra = (void *) tcache; + } + + /* The remaining comparison logic is in typcache.c */ + return compare_values_of_enum(tcache, arg1, arg2); +} + +Datum +enum_lt(PG_FUNCTION_ARGS) +{ + Oid a = PG_GETARG_OID(0); + Oid b = PG_GETARG_OID(1); + + PG_RETURN_BOOL(enum_cmp_internal(a, b, fcinfo) < 0); +} + +Datum +enum_le(PG_FUNCTION_ARGS) +{ + Oid a = PG_GETARG_OID(0); + Oid b = PG_GETARG_OID(1); + + PG_RETURN_BOOL(enum_cmp_internal(a, b, fcinfo) <= 0); +} + +Datum +enum_eq(PG_FUNCTION_ARGS) +{ + Oid a = PG_GETARG_OID(0); + Oid b = PG_GETARG_OID(1); + + PG_RETURN_BOOL(a == b); +} + +Datum +enum_ne(PG_FUNCTION_ARGS) +{ + Oid a = PG_GETARG_OID(0); + Oid b = PG_GETARG_OID(1); + + PG_RETURN_BOOL(a != b); +} + +Datum +enum_ge(PG_FUNCTION_ARGS) +{ + Oid a = PG_GETARG_OID(0); + Oid b = PG_GETARG_OID(1); + + PG_RETURN_BOOL(enum_cmp_internal(a, b, fcinfo) >= 0); +} + +Datum +enum_gt(PG_FUNCTION_ARGS) +{ + Oid a = PG_GETARG_OID(0); + Oid b = PG_GETARG_OID(1); + + PG_RETURN_BOOL(enum_cmp_internal(a, b, fcinfo) > 0); +} + +Datum +enum_smaller(PG_FUNCTION_ARGS) +{ + Oid a = PG_GETARG_OID(0); + Oid b = PG_GETARG_OID(1); + + PG_RETURN_OID(enum_cmp_internal(a, b, fcinfo) < 0 ? a : b); +} + +Datum +enum_larger(PG_FUNCTION_ARGS) +{ + Oid a = PG_GETARG_OID(0); + Oid b = PG_GETARG_OID(1); + + PG_RETURN_OID(enum_cmp_internal(a, b, fcinfo) > 0 ? a : b); +} + +Datum +enum_cmp(PG_FUNCTION_ARGS) +{ + Oid a = PG_GETARG_OID(0); + Oid b = PG_GETARG_OID(1); + + PG_RETURN_INT32(enum_cmp_internal(a, b, fcinfo)); +} + +/* Enum programming support functions */ + +/* + * enum_endpoint: common code for enum_first/enum_last + */ +static Oid +enum_endpoint(Oid enumtypoid, ScanDirection direction) +{ + Relation enum_rel; + Relation enum_idx; + SysScanDesc enum_scan; + HeapTuple enum_tuple; + ScanKeyData skey; + Oid minmax; + + /* + * Find the first/last enum member using pg_enum_typid_sortorder_index. + * Note we must not use the syscache. See comments for RenumberEnumType + * in catalog/pg_enum.c for more info. + */ + ScanKeyInit(&skey, + Anum_pg_enum_enumtypid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(enumtypoid)); + + enum_rel = table_open(EnumRelationId, AccessShareLock); + enum_idx = index_open(EnumTypIdSortOrderIndexId, AccessShareLock); + enum_scan = systable_beginscan_ordered(enum_rel, enum_idx, NULL, + 1, &skey); + + enum_tuple = systable_getnext_ordered(enum_scan, direction); + if (HeapTupleIsValid(enum_tuple)) + { + /* check it's safe to use in SQL */ + check_safe_enum_use(enum_tuple); + minmax = ((Form_pg_enum) GETSTRUCT(enum_tuple))->oid; + } + else + { + /* should only happen with an empty enum */ + minmax = InvalidOid; + } + + systable_endscan_ordered(enum_scan); + index_close(enum_idx, AccessShareLock); + table_close(enum_rel, AccessShareLock); + + return minmax; +} + +Datum +enum_first(PG_FUNCTION_ARGS) +{ + Oid enumtypoid; + Oid min; + + /* + * We rely on being able to get the specific enum type from the calling + * expression tree. Notice that the actual value of the argument isn't + * examined at all; in particular it might be NULL. + */ + enumtypoid = get_fn_expr_argtype(fcinfo->flinfo, 0); + if (enumtypoid == InvalidOid) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("could not determine actual enum type"))); + + /* Get the OID using the index */ + min = enum_endpoint(enumtypoid, ForwardScanDirection); + + if (!OidIsValid(min)) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("enum %s contains no values", + format_type_be(enumtypoid)))); + + PG_RETURN_OID(min); +} + +Datum +enum_last(PG_FUNCTION_ARGS) +{ + Oid enumtypoid; + Oid max; + + /* + * We rely on being able to get the specific enum type from the calling + * expression tree. Notice that the actual value of the argument isn't + * examined at all; in particular it might be NULL. + */ + enumtypoid = get_fn_expr_argtype(fcinfo->flinfo, 0); + if (enumtypoid == InvalidOid) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("could not determine actual enum type"))); + + /* Get the OID using the index */ + max = enum_endpoint(enumtypoid, BackwardScanDirection); + + if (!OidIsValid(max)) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("enum %s contains no values", + format_type_be(enumtypoid)))); + + PG_RETURN_OID(max); +} + +/* 2-argument variant of enum_range */ +Datum +enum_range_bounds(PG_FUNCTION_ARGS) +{ + Oid lower; + Oid upper; + Oid enumtypoid; + + if (PG_ARGISNULL(0)) + lower = InvalidOid; + else + lower = PG_GETARG_OID(0); + if (PG_ARGISNULL(1)) + upper = InvalidOid; + else + upper = PG_GETARG_OID(1); + + /* + * We rely on being able to get the specific enum type from the calling + * expression tree. The generic type mechanism should have ensured that + * both are of the same type. + */ + enumtypoid = get_fn_expr_argtype(fcinfo->flinfo, 0); + if (enumtypoid == InvalidOid) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("could not determine actual enum type"))); + + PG_RETURN_ARRAYTYPE_P(enum_range_internal(enumtypoid, lower, upper)); +} + +/* 1-argument variant of enum_range */ +Datum +enum_range_all(PG_FUNCTION_ARGS) +{ + Oid enumtypoid; + + /* + * We rely on being able to get the specific enum type from the calling + * expression tree. Notice that the actual value of the argument isn't + * examined at all; in particular it might be NULL. + */ + enumtypoid = get_fn_expr_argtype(fcinfo->flinfo, 0); + if (enumtypoid == InvalidOid) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("could not determine actual enum type"))); + + PG_RETURN_ARRAYTYPE_P(enum_range_internal(enumtypoid, + InvalidOid, InvalidOid)); +} + +static ArrayType * +enum_range_internal(Oid enumtypoid, Oid lower, Oid upper) +{ + ArrayType *result; + Relation enum_rel; + Relation enum_idx; + SysScanDesc enum_scan; + HeapTuple enum_tuple; + ScanKeyData skey; + Datum *elems; + int max, + cnt; + bool left_found; + + /* + * Scan the enum members in order using pg_enum_typid_sortorder_index. + * Note we must not use the syscache. See comments for RenumberEnumType + * in catalog/pg_enum.c for more info. + */ + ScanKeyInit(&skey, + Anum_pg_enum_enumtypid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(enumtypoid)); + + enum_rel = table_open(EnumRelationId, AccessShareLock); + enum_idx = index_open(EnumTypIdSortOrderIndexId, AccessShareLock); + enum_scan = systable_beginscan_ordered(enum_rel, enum_idx, NULL, 1, &skey); + + max = 64; + elems = (Datum *) palloc(max * sizeof(Datum)); + cnt = 0; + left_found = !OidIsValid(lower); + + while (HeapTupleIsValid(enum_tuple = systable_getnext_ordered(enum_scan, ForwardScanDirection))) + { + Oid enum_oid = ((Form_pg_enum) GETSTRUCT(enum_tuple))->oid; + + if (!left_found && lower == enum_oid) + left_found = true; + + if (left_found) + { + /* check it's safe to use in SQL */ + check_safe_enum_use(enum_tuple); + + if (cnt >= max) + { + max *= 2; + elems = (Datum *) repalloc(elems, max * sizeof(Datum)); + } + + elems[cnt++] = ObjectIdGetDatum(enum_oid); + } + + if (OidIsValid(upper) && upper == enum_oid) + break; + } + + systable_endscan_ordered(enum_scan); + index_close(enum_idx, AccessShareLock); + table_close(enum_rel, AccessShareLock); + + /* and build the result array */ + /* note this hardwires some details about the representation of Oid */ + result = construct_array(elems, cnt, enumtypoid, + sizeof(Oid), true, TYPALIGN_INT); + + pfree(elems); + + return result; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/expandeddatum.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/expandeddatum.c new file mode 100644 index 00000000000..24dc9473b42 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/expandeddatum.c @@ -0,0 +1,145 @@ +/*------------------------------------------------------------------------- + * + * expandeddatum.c + * Support functions for "expanded" value representations. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/adt/expandeddatum.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "utils/expandeddatum.h" +#include "utils/memutils.h" + +/* + * DatumGetEOHP + * + * Given a Datum that is an expanded-object reference, extract the pointer. + * + * This is a bit tedious since the pointer may not be properly aligned; + * compare VARATT_EXTERNAL_GET_POINTER(). + */ +ExpandedObjectHeader * +DatumGetEOHP(Datum d) +{ + varattrib_1b_e *datum = (varattrib_1b_e *) DatumGetPointer(d); + varatt_expanded ptr; + + Assert(VARATT_IS_EXTERNAL_EXPANDED(datum)); + memcpy(&ptr, VARDATA_EXTERNAL(datum), sizeof(ptr)); + Assert(VARATT_IS_EXPANDED_HEADER(ptr.eohptr)); + return ptr.eohptr; +} + +/* + * EOH_init_header + * + * Initialize the common header of an expanded object. + * + * The main thing this encapsulates is initializing the TOAST pointers. + */ +void +EOH_init_header(ExpandedObjectHeader *eohptr, + const ExpandedObjectMethods *methods, + MemoryContext obj_context) +{ + varatt_expanded ptr; + + eohptr->vl_len_ = EOH_HEADER_MAGIC; + eohptr->eoh_methods = methods; + eohptr->eoh_context = obj_context; + + ptr.eohptr = eohptr; + + SET_VARTAG_EXTERNAL(eohptr->eoh_rw_ptr, VARTAG_EXPANDED_RW); + memcpy(VARDATA_EXTERNAL(eohptr->eoh_rw_ptr), &ptr, sizeof(ptr)); + + SET_VARTAG_EXTERNAL(eohptr->eoh_ro_ptr, VARTAG_EXPANDED_RO); + memcpy(VARDATA_EXTERNAL(eohptr->eoh_ro_ptr), &ptr, sizeof(ptr)); +} + +/* + * EOH_get_flat_size + * EOH_flatten_into + * + * Convenience functions for invoking the "methods" of an expanded object. + */ + +Size +EOH_get_flat_size(ExpandedObjectHeader *eohptr) +{ + return eohptr->eoh_methods->get_flat_size(eohptr); +} + +void +EOH_flatten_into(ExpandedObjectHeader *eohptr, + void *result, Size allocated_size) +{ + eohptr->eoh_methods->flatten_into(eohptr, result, allocated_size); +} + +/* + * If the Datum represents a R/W expanded object, change it to R/O. + * Otherwise return the original Datum. + * + * Caller must ensure that the datum is a non-null varlena value. Typically + * this is invoked via MakeExpandedObjectReadOnly(), which checks that. + */ +Datum +MakeExpandedObjectReadOnlyInternal(Datum d) +{ + ExpandedObjectHeader *eohptr; + + /* Nothing to do if not a read-write expanded-object pointer */ + if (!VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(d))) + return d; + + /* Now safe to extract the object pointer */ + eohptr = DatumGetEOHP(d); + + /* Return the built-in read-only pointer instead of given pointer */ + return EOHPGetRODatum(eohptr); +} + +/* + * Transfer ownership of an expanded object to a new parent memory context. + * The object must be referenced by a R/W pointer, and what we return is + * always its "standard" R/W pointer, which is certain to have the same + * lifespan as the object itself. (The passed-in pointer might not, and + * in any case wouldn't provide a unique identifier if it's not that one.) + */ +Datum +TransferExpandedObject(Datum d, MemoryContext new_parent) +{ + ExpandedObjectHeader *eohptr = DatumGetEOHP(d); + + /* Assert caller gave a R/W pointer */ + Assert(VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(d))); + + /* Transfer ownership */ + MemoryContextSetParent(eohptr->eoh_context, new_parent); + + /* Return the object's standard read-write pointer */ + return EOHPGetRWDatum(eohptr); +} + +/* + * Delete an expanded object (must be referenced by a R/W pointer). + */ +void +DeleteExpandedObject(Datum d) +{ + ExpandedObjectHeader *eohptr = DatumGetEOHP(d); + + /* Assert caller gave a R/W pointer */ + Assert(VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(d))); + + /* Kill it */ + MemoryContextDelete(eohptr->eoh_context); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/expandedrecord.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/expandedrecord.c new file mode 100644 index 00000000000..c46e5aa36f2 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/expandedrecord.c @@ -0,0 +1,1633 @@ +/*------------------------------------------------------------------------- + * + * expandedrecord.c + * Functions for manipulating composite expanded objects. + * + * This module supports "expanded objects" (cf. expandeddatum.h) that can + * store values of named composite types, domains over named composite types, + * and record types (registered or anonymous). + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/adt/expandedrecord.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/detoast.h" +#include "access/heaptoast.h" +#include "access/htup_details.h" +#include "catalog/heap.h" +#include "catalog/pg_type.h" +#include "utils/builtins.h" +#include "utils/datum.h" +#include "utils/expandedrecord.h" +#include "utils/memutils.h" +#include "utils/typcache.h" + + +/* "Methods" required for an expanded object */ +static Size ER_get_flat_size(ExpandedObjectHeader *eohptr); +static void ER_flatten_into(ExpandedObjectHeader *eohptr, + void *result, Size allocated_size); + +static const ExpandedObjectMethods ER_methods = +{ + ER_get_flat_size, + ER_flatten_into +}; + +/* Other local functions */ +static void ER_mc_callback(void *arg); +static MemoryContext get_short_term_cxt(ExpandedRecordHeader *erh); +static void build_dummy_expanded_header(ExpandedRecordHeader *main_erh); +static pg_noinline void check_domain_for_new_field(ExpandedRecordHeader *erh, + int fnumber, + Datum newValue, bool isnull); +static pg_noinline void check_domain_for_new_tuple(ExpandedRecordHeader *erh, + HeapTuple tuple); + + +/* + * Build an expanded record of the specified composite type + * + * type_id can be RECORDOID, but only if a positive typmod is given. + * + * The expanded record is initially "empty", having a state logically + * equivalent to a NULL composite value (not ROW(NULL, NULL, ...)). + * Note that this might not be a valid state for a domain type; + * if the caller needs to check that, call + * expanded_record_set_tuple(erh, NULL, false, false). + * + * The expanded object will be a child of parentcontext. + */ +ExpandedRecordHeader * +make_expanded_record_from_typeid(Oid type_id, int32 typmod, + MemoryContext parentcontext) +{ + ExpandedRecordHeader *erh; + int flags = 0; + TupleDesc tupdesc; + uint64 tupdesc_id; + MemoryContext objcxt; + char *chunk; + + if (type_id != RECORDOID) + { + /* + * Consult the typcache to see if it's a domain over composite, and in + * any case to get the tupdesc and tupdesc identifier. + */ + TypeCacheEntry *typentry; + + typentry = lookup_type_cache(type_id, + TYPECACHE_TUPDESC | + TYPECACHE_DOMAIN_BASE_INFO); + if (typentry->typtype == TYPTYPE_DOMAIN) + { + flags |= ER_FLAG_IS_DOMAIN; + typentry = lookup_type_cache(typentry->domainBaseType, + TYPECACHE_TUPDESC); + } + if (typentry->tupDesc == NULL) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("type %s is not composite", + format_type_be(type_id)))); + tupdesc = typentry->tupDesc; + tupdesc_id = typentry->tupDesc_identifier; + } + else + { + /* + * For RECORD types, get the tupdesc and identifier from typcache. + */ + tupdesc = lookup_rowtype_tupdesc(type_id, typmod); + tupdesc_id = assign_record_type_identifier(type_id, typmod); + } + + /* + * Allocate private context for expanded object. We use a regular-size + * context, not a small one, to improve the odds that we can fit a tupdesc + * into it without needing an extra malloc block. (This code path doesn't + * ever need to copy a tupdesc into the expanded record, but let's be + * consistent with the other ways of making an expanded record.) + */ + objcxt = AllocSetContextCreate(parentcontext, + "expanded record", + ALLOCSET_DEFAULT_SIZES); + + /* + * Since we already know the number of fields in the tupdesc, we can + * allocate the dvalues/dnulls arrays along with the record header. This + * is useless if we never need those arrays, but it costs almost nothing, + * and it will save a palloc cycle if we do need them. + */ + erh = (ExpandedRecordHeader *) + MemoryContextAlloc(objcxt, MAXALIGN(sizeof(ExpandedRecordHeader)) + + tupdesc->natts * (sizeof(Datum) + sizeof(bool))); + + /* Ensure all header fields are initialized to 0/null */ + memset(erh, 0, sizeof(ExpandedRecordHeader)); + + EOH_init_header(&erh->hdr, &ER_methods, objcxt); + erh->er_magic = ER_MAGIC; + + /* Set up dvalues/dnulls, with no valid contents as yet */ + chunk = (char *) erh + MAXALIGN(sizeof(ExpandedRecordHeader)); + erh->dvalues = (Datum *) chunk; + erh->dnulls = (bool *) (chunk + tupdesc->natts * sizeof(Datum)); + erh->nfields = tupdesc->natts; + + /* Fill in composite-type identification info */ + erh->er_decltypeid = type_id; + erh->er_typeid = tupdesc->tdtypeid; + erh->er_typmod = tupdesc->tdtypmod; + erh->er_tupdesc_id = tupdesc_id; + + erh->flags = flags; + + /* + * If what we got from the typcache is a refcounted tupdesc, we need to + * acquire our own refcount on it. We manage the refcount with a memory + * context callback rather than assuming that the CurrentResourceOwner is + * longer-lived than this expanded object. + */ + if (tupdesc->tdrefcount >= 0) + { + /* Register callback to release the refcount */ + erh->er_mcb.func = ER_mc_callback; + erh->er_mcb.arg = (void *) erh; + MemoryContextRegisterResetCallback(erh->hdr.eoh_context, + &erh->er_mcb); + + /* And save the pointer */ + erh->er_tupdesc = tupdesc; + tupdesc->tdrefcount++; + + /* If we called lookup_rowtype_tupdesc, release the pin it took */ + if (type_id == RECORDOID) + ReleaseTupleDesc(tupdesc); + } + else + { + /* + * If it's not refcounted, just assume it will outlive the expanded + * object. (This can happen for shared record types, for instance.) + */ + erh->er_tupdesc = tupdesc; + } + + /* + * We don't set ER_FLAG_DVALUES_VALID or ER_FLAG_FVALUE_VALID, so the + * record remains logically empty. + */ + + return erh; +} + +/* + * Build an expanded record of the rowtype defined by the tupdesc + * + * The tupdesc is copied if necessary (i.e., if we can't just bump its + * reference count instead). + * + * The expanded record is initially "empty", having a state logically + * equivalent to a NULL composite value (not ROW(NULL, NULL, ...)). + * + * The expanded object will be a child of parentcontext. + */ +ExpandedRecordHeader * +make_expanded_record_from_tupdesc(TupleDesc tupdesc, + MemoryContext parentcontext) +{ + ExpandedRecordHeader *erh; + uint64 tupdesc_id; + MemoryContext objcxt; + MemoryContext oldcxt; + char *chunk; + + if (tupdesc->tdtypeid != RECORDOID) + { + /* + * If it's a named composite type (not RECORD), we prefer to reference + * the typcache's copy of the tupdesc, which is guaranteed to be + * refcounted (the given tupdesc might not be). In any case, we need + * to consult the typcache to get the correct tupdesc identifier. + * + * Note that tdtypeid couldn't be a domain type, so we need not + * consider that case here. + */ + TypeCacheEntry *typentry; + + typentry = lookup_type_cache(tupdesc->tdtypeid, TYPECACHE_TUPDESC); + if (typentry->tupDesc == NULL) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("type %s is not composite", + format_type_be(tupdesc->tdtypeid)))); + tupdesc = typentry->tupDesc; + tupdesc_id = typentry->tupDesc_identifier; + } + else + { + /* + * For RECORD types, get the appropriate unique identifier (possibly + * freshly assigned). + */ + tupdesc_id = assign_record_type_identifier(tupdesc->tdtypeid, + tupdesc->tdtypmod); + } + + /* + * Allocate private context for expanded object. We use a regular-size + * context, not a small one, to improve the odds that we can fit a tupdesc + * into it without needing an extra malloc block. + */ + objcxt = AllocSetContextCreate(parentcontext, + "expanded record", + ALLOCSET_DEFAULT_SIZES); + + /* + * Since we already know the number of fields in the tupdesc, we can + * allocate the dvalues/dnulls arrays along with the record header. This + * is useless if we never need those arrays, but it costs almost nothing, + * and it will save a palloc cycle if we do need them. + */ + erh = (ExpandedRecordHeader *) + MemoryContextAlloc(objcxt, MAXALIGN(sizeof(ExpandedRecordHeader)) + + tupdesc->natts * (sizeof(Datum) + sizeof(bool))); + + /* Ensure all header fields are initialized to 0/null */ + memset(erh, 0, sizeof(ExpandedRecordHeader)); + + EOH_init_header(&erh->hdr, &ER_methods, objcxt); + erh->er_magic = ER_MAGIC; + + /* Set up dvalues/dnulls, with no valid contents as yet */ + chunk = (char *) erh + MAXALIGN(sizeof(ExpandedRecordHeader)); + erh->dvalues = (Datum *) chunk; + erh->dnulls = (bool *) (chunk + tupdesc->natts * sizeof(Datum)); + erh->nfields = tupdesc->natts; + + /* Fill in composite-type identification info */ + erh->er_decltypeid = erh->er_typeid = tupdesc->tdtypeid; + erh->er_typmod = tupdesc->tdtypmod; + erh->er_tupdesc_id = tupdesc_id; + + /* + * Copy tupdesc if needed, but we prefer to bump its refcount if possible. + * We manage the refcount with a memory context callback rather than + * assuming that the CurrentResourceOwner is longer-lived than this + * expanded object. + */ + if (tupdesc->tdrefcount >= 0) + { + /* Register callback to release the refcount */ + erh->er_mcb.func = ER_mc_callback; + erh->er_mcb.arg = (void *) erh; + MemoryContextRegisterResetCallback(erh->hdr.eoh_context, + &erh->er_mcb); + + /* And save the pointer */ + erh->er_tupdesc = tupdesc; + tupdesc->tdrefcount++; + } + else + { + /* Just copy it */ + oldcxt = MemoryContextSwitchTo(objcxt); + erh->er_tupdesc = CreateTupleDescCopy(tupdesc); + erh->flags |= ER_FLAG_TUPDESC_ALLOCED; + MemoryContextSwitchTo(oldcxt); + } + + /* + * We don't set ER_FLAG_DVALUES_VALID or ER_FLAG_FVALUE_VALID, so the + * record remains logically empty. + */ + + return erh; +} + +/* + * Build an expanded record of the same rowtype as the given expanded record + * + * This is faster than either of the above routines because we can bypass + * typcache lookup(s). + * + * The expanded record is initially "empty" --- we do not copy whatever + * tuple might be in the source expanded record. + * + * The expanded object will be a child of parentcontext. + */ +ExpandedRecordHeader * +make_expanded_record_from_exprecord(ExpandedRecordHeader *olderh, + MemoryContext parentcontext) +{ + ExpandedRecordHeader *erh; + TupleDesc tupdesc = expanded_record_get_tupdesc(olderh); + MemoryContext objcxt; + MemoryContext oldcxt; + char *chunk; + + /* + * Allocate private context for expanded object. We use a regular-size + * context, not a small one, to improve the odds that we can fit a tupdesc + * into it without needing an extra malloc block. + */ + objcxt = AllocSetContextCreate(parentcontext, + "expanded record", + ALLOCSET_DEFAULT_SIZES); + + /* + * Since we already know the number of fields in the tupdesc, we can + * allocate the dvalues/dnulls arrays along with the record header. This + * is useless if we never need those arrays, but it costs almost nothing, + * and it will save a palloc cycle if we do need them. + */ + erh = (ExpandedRecordHeader *) + MemoryContextAlloc(objcxt, MAXALIGN(sizeof(ExpandedRecordHeader)) + + tupdesc->natts * (sizeof(Datum) + sizeof(bool))); + + /* Ensure all header fields are initialized to 0/null */ + memset(erh, 0, sizeof(ExpandedRecordHeader)); + + EOH_init_header(&erh->hdr, &ER_methods, objcxt); + erh->er_magic = ER_MAGIC; + + /* Set up dvalues/dnulls, with no valid contents as yet */ + chunk = (char *) erh + MAXALIGN(sizeof(ExpandedRecordHeader)); + erh->dvalues = (Datum *) chunk; + erh->dnulls = (bool *) (chunk + tupdesc->natts * sizeof(Datum)); + erh->nfields = tupdesc->natts; + + /* Fill in composite-type identification info */ + erh->er_decltypeid = olderh->er_decltypeid; + erh->er_typeid = olderh->er_typeid; + erh->er_typmod = olderh->er_typmod; + erh->er_tupdesc_id = olderh->er_tupdesc_id; + + /* The only flag bit that transfers over is IS_DOMAIN */ + erh->flags = olderh->flags & ER_FLAG_IS_DOMAIN; + + /* + * Copy tupdesc if needed, but we prefer to bump its refcount if possible. + * We manage the refcount with a memory context callback rather than + * assuming that the CurrentResourceOwner is longer-lived than this + * expanded object. + */ + if (tupdesc->tdrefcount >= 0) + { + /* Register callback to release the refcount */ + erh->er_mcb.func = ER_mc_callback; + erh->er_mcb.arg = (void *) erh; + MemoryContextRegisterResetCallback(erh->hdr.eoh_context, + &erh->er_mcb); + + /* And save the pointer */ + erh->er_tupdesc = tupdesc; + tupdesc->tdrefcount++; + } + else if (olderh->flags & ER_FLAG_TUPDESC_ALLOCED) + { + /* We need to make our own copy of the tupdesc */ + oldcxt = MemoryContextSwitchTo(objcxt); + erh->er_tupdesc = CreateTupleDescCopy(tupdesc); + erh->flags |= ER_FLAG_TUPDESC_ALLOCED; + MemoryContextSwitchTo(oldcxt); + } + else + { + /* + * Assume the tupdesc will outlive this expanded object, just like + * we're assuming it will outlive the source object. + */ + erh->er_tupdesc = tupdesc; + } + + /* + * We don't set ER_FLAG_DVALUES_VALID or ER_FLAG_FVALUE_VALID, so the + * record remains logically empty. + */ + + return erh; +} + +/* + * Insert given tuple as the value of the expanded record + * + * It is caller's responsibility that the tuple matches the record's + * previously-assigned rowtype. (However domain constraints, if any, + * will be checked here.) + * + * The tuple is physically copied into the expanded record's local storage + * if "copy" is true, otherwise it's caller's responsibility that the tuple + * will live as long as the expanded record does. + * + * Out-of-line field values in the tuple are automatically inlined if + * "expand_external" is true, otherwise not. (The combination copy = false, + * expand_external = true is not sensible and not supported.) + * + * Alternatively, tuple can be NULL, in which case we just set the expanded + * record to be empty. + */ +void +expanded_record_set_tuple(ExpandedRecordHeader *erh, + HeapTuple tuple, + bool copy, + bool expand_external) +{ + int oldflags; + HeapTuple oldtuple; + char *oldfstartptr; + char *oldfendptr; + int newflags; + HeapTuple newtuple; + MemoryContext oldcxt; + + /* Shouldn't ever be trying to assign new data to a dummy header */ + Assert(!(erh->flags & ER_FLAG_IS_DUMMY)); + + /* + * Before performing the assignment, see if result will satisfy domain. + */ + if (erh->flags & ER_FLAG_IS_DOMAIN) + check_domain_for_new_tuple(erh, tuple); + + /* + * If we need to get rid of out-of-line field values, do so, using the + * short-term context to avoid leaking whatever cruft the toast fetch + * might generate. + */ + if (expand_external && tuple) + { + /* Assert caller didn't ask for unsupported case */ + Assert(copy); + if (HeapTupleHasExternal(tuple)) + { + oldcxt = MemoryContextSwitchTo(get_short_term_cxt(erh)); + tuple = toast_flatten_tuple(tuple, erh->er_tupdesc); + MemoryContextSwitchTo(oldcxt); + } + else + expand_external = false; /* need not clean up below */ + } + + /* + * Initialize new flags, keeping only non-data status bits. + */ + oldflags = erh->flags; + newflags = oldflags & ER_FLAGS_NON_DATA; + + /* + * Copy tuple into local storage if needed. We must be sure this succeeds + * before we start to modify the expanded record's state. + */ + if (copy && tuple) + { + oldcxt = MemoryContextSwitchTo(erh->hdr.eoh_context); + newtuple = heap_copytuple(tuple); + newflags |= ER_FLAG_FVALUE_ALLOCED; + MemoryContextSwitchTo(oldcxt); + + /* We can now flush anything that detoasting might have leaked. */ + if (expand_external) + MemoryContextReset(erh->er_short_term_cxt); + } + else + newtuple = tuple; + + /* Make copies of fields we're about to overwrite */ + oldtuple = erh->fvalue; + oldfstartptr = erh->fstartptr; + oldfendptr = erh->fendptr; + + /* + * It's now safe to update the expanded record's state. + */ + if (newtuple) + { + /* Save flat representation */ + erh->fvalue = newtuple; + erh->fstartptr = (char *) newtuple->t_data; + erh->fendptr = ((char *) newtuple->t_data) + newtuple->t_len; + newflags |= ER_FLAG_FVALUE_VALID; + + /* Remember if we have any out-of-line field values */ + if (HeapTupleHasExternal(newtuple)) + newflags |= ER_FLAG_HAVE_EXTERNAL; + } + else + { + erh->fvalue = NULL; + erh->fstartptr = erh->fendptr = NULL; + } + + erh->flags = newflags; + + /* Reset flat-size info; we don't bother to make it valid now */ + erh->flat_size = 0; + + /* + * Now, release any storage belonging to old field values. It's safe to + * do this because ER_FLAG_DVALUES_VALID is no longer set in erh->flags; + * even if we fail partway through, the record is valid, and at worst + * we've failed to reclaim some space. + */ + if (oldflags & ER_FLAG_DVALUES_ALLOCED) + { + TupleDesc tupdesc = erh->er_tupdesc; + int i; + + for (i = 0; i < erh->nfields; i++) + { + if (!erh->dnulls[i] && + !(TupleDescAttr(tupdesc, i)->attbyval)) + { + char *oldValue = (char *) DatumGetPointer(erh->dvalues[i]); + + if (oldValue < oldfstartptr || oldValue >= oldfendptr) + pfree(oldValue); + } + } + } + + /* Likewise free the old tuple, if it was locally allocated */ + if (oldflags & ER_FLAG_FVALUE_ALLOCED) + heap_freetuple(oldtuple); + + /* We won't make a new deconstructed representation until/unless needed */ +} + +/* + * make_expanded_record_from_datum: build expanded record from composite Datum + * + * This combines the functions of make_expanded_record_from_typeid and + * expanded_record_set_tuple. However, we do not force a lookup of the + * tupdesc immediately, reasoning that it might never be needed. + * + * The expanded object will be a child of parentcontext. + * + * Note: a composite datum cannot self-identify as being of a domain type, + * so we need not consider domain cases here. + */ +Datum +make_expanded_record_from_datum(Datum recorddatum, MemoryContext parentcontext) +{ + ExpandedRecordHeader *erh; + HeapTupleHeader tuphdr; + HeapTupleData tmptup; + HeapTuple newtuple; + MemoryContext objcxt; + MemoryContext oldcxt; + + /* + * Allocate private context for expanded object. We use a regular-size + * context, not a small one, to improve the odds that we can fit a tupdesc + * into it without needing an extra malloc block. + */ + objcxt = AllocSetContextCreate(parentcontext, + "expanded record", + ALLOCSET_DEFAULT_SIZES); + + /* Set up expanded record header, initializing fields to 0/null */ + erh = (ExpandedRecordHeader *) + MemoryContextAllocZero(objcxt, sizeof(ExpandedRecordHeader)); + + EOH_init_header(&erh->hdr, &ER_methods, objcxt); + erh->er_magic = ER_MAGIC; + + /* + * Detoast and copy source record into private context, as a HeapTuple. + * (If we actually have to detoast the source, we'll leak some memory in + * the caller's context, but it doesn't seem worth worrying about.) + */ + tuphdr = DatumGetHeapTupleHeader(recorddatum); + + tmptup.t_len = HeapTupleHeaderGetDatumLength(tuphdr); + ItemPointerSetInvalid(&(tmptup.t_self)); + tmptup.t_tableOid = InvalidOid; + tmptup.t_data = tuphdr; + + oldcxt = MemoryContextSwitchTo(objcxt); + newtuple = heap_copytuple(&tmptup); + erh->flags |= ER_FLAG_FVALUE_ALLOCED; + MemoryContextSwitchTo(oldcxt); + + /* Fill in composite-type identification info */ + erh->er_decltypeid = erh->er_typeid = HeapTupleHeaderGetTypeId(tuphdr); + erh->er_typmod = HeapTupleHeaderGetTypMod(tuphdr); + + /* remember we have a flat representation */ + erh->fvalue = newtuple; + erh->fstartptr = (char *) newtuple->t_data; + erh->fendptr = ((char *) newtuple->t_data) + newtuple->t_len; + erh->flags |= ER_FLAG_FVALUE_VALID; + + /* Shouldn't need to set ER_FLAG_HAVE_EXTERNAL */ + Assert(!HeapTupleHeaderHasExternal(tuphdr)); + + /* + * We won't look up the tupdesc till we have to, nor make a deconstructed + * representation. We don't have enough info to fill flat_size and + * friends, either. + */ + + /* return a R/W pointer to the expanded record */ + return EOHPGetRWDatum(&erh->hdr); +} + +/* + * get_flat_size method for expanded records + * + * Note: call this in a reasonably short-lived memory context, in case of + * memory leaks from activities such as detoasting. + */ +static Size +ER_get_flat_size(ExpandedObjectHeader *eohptr) +{ + ExpandedRecordHeader *erh = (ExpandedRecordHeader *) eohptr; + TupleDesc tupdesc; + Size len; + Size data_len; + int hoff; + bool hasnull; + int i; + + Assert(erh->er_magic == ER_MAGIC); + + /* + * The flat representation has to be a valid composite datum. Make sure + * that we have a registered, not anonymous, RECORD type. + */ + if (erh->er_typeid == RECORDOID && + erh->er_typmod < 0) + { + tupdesc = expanded_record_get_tupdesc(erh); + assign_record_type_typmod(tupdesc); + erh->er_typmod = tupdesc->tdtypmod; + } + + /* + * If we have a valid flattened value without out-of-line fields, we can + * just use it as-is. + */ + if (erh->flags & ER_FLAG_FVALUE_VALID && + !(erh->flags & ER_FLAG_HAVE_EXTERNAL)) + return erh->fvalue->t_len; + + /* If we have a cached size value, believe that */ + if (erh->flat_size) + return erh->flat_size; + + /* If we haven't yet deconstructed the tuple, do that */ + if (!(erh->flags & ER_FLAG_DVALUES_VALID)) + deconstruct_expanded_record(erh); + + /* Tuple descriptor must be valid by now */ + tupdesc = erh->er_tupdesc; + + /* + * Composite datums mustn't contain any out-of-line values. + */ + if (erh->flags & ER_FLAG_HAVE_EXTERNAL) + { + for (i = 0; i < erh->nfields; i++) + { + Form_pg_attribute attr = TupleDescAttr(tupdesc, i); + + if (!erh->dnulls[i] && + !attr->attbyval && attr->attlen == -1 && + VARATT_IS_EXTERNAL(DatumGetPointer(erh->dvalues[i]))) + { + /* + * expanded_record_set_field_internal can do the actual work + * of detoasting. It needn't recheck domain constraints. + */ + expanded_record_set_field_internal(erh, i + 1, + erh->dvalues[i], false, + true, + false); + } + } + + /* + * We have now removed all external field values, so we can clear the + * flag about them. This won't cause ER_flatten_into() to mistakenly + * take the fast path, since expanded_record_set_field() will have + * cleared ER_FLAG_FVALUE_VALID. + */ + erh->flags &= ~ER_FLAG_HAVE_EXTERNAL; + } + + /* Test if we currently have any null values */ + hasnull = false; + for (i = 0; i < erh->nfields; i++) + { + if (erh->dnulls[i]) + { + hasnull = true; + break; + } + } + + /* Determine total space needed */ + len = offsetof(HeapTupleHeaderData, t_bits); + + if (hasnull) + len += BITMAPLEN(tupdesc->natts); + + hoff = len = MAXALIGN(len); /* align user data safely */ + + data_len = heap_compute_data_size(tupdesc, erh->dvalues, erh->dnulls); + + len += data_len; + + /* Cache for next time */ + erh->flat_size = len; + erh->data_len = data_len; + erh->hoff = hoff; + erh->hasnull = hasnull; + + return len; +} + +/* + * flatten_into method for expanded records + */ +static void +ER_flatten_into(ExpandedObjectHeader *eohptr, + void *result, Size allocated_size) +{ + ExpandedRecordHeader *erh = (ExpandedRecordHeader *) eohptr; + HeapTupleHeader tuphdr = (HeapTupleHeader) result; + TupleDesc tupdesc; + + Assert(erh->er_magic == ER_MAGIC); + + /* Easy if we have a valid flattened value without out-of-line fields */ + if (erh->flags & ER_FLAG_FVALUE_VALID && + !(erh->flags & ER_FLAG_HAVE_EXTERNAL)) + { + Assert(allocated_size == erh->fvalue->t_len); + memcpy(tuphdr, erh->fvalue->t_data, allocated_size); + /* The original flattened value might not have datum header fields */ + HeapTupleHeaderSetDatumLength(tuphdr, allocated_size); + HeapTupleHeaderSetTypeId(tuphdr, erh->er_typeid); + HeapTupleHeaderSetTypMod(tuphdr, erh->er_typmod); + return; + } + + /* Else allocation should match previous get_flat_size result */ + Assert(allocated_size == erh->flat_size); + + /* We'll need the tuple descriptor */ + tupdesc = expanded_record_get_tupdesc(erh); + + /* We must ensure that any pad space is zero-filled */ + memset(tuphdr, 0, allocated_size); + + /* Set up header fields of composite Datum */ + HeapTupleHeaderSetDatumLength(tuphdr, allocated_size); + HeapTupleHeaderSetTypeId(tuphdr, erh->er_typeid); + HeapTupleHeaderSetTypMod(tuphdr, erh->er_typmod); + /* We also make sure that t_ctid is invalid unless explicitly set */ + ItemPointerSetInvalid(&(tuphdr->t_ctid)); + + HeapTupleHeaderSetNatts(tuphdr, tupdesc->natts); + tuphdr->t_hoff = erh->hoff; + + /* And fill the data area from dvalues/dnulls */ + heap_fill_tuple(tupdesc, + erh->dvalues, + erh->dnulls, + (char *) tuphdr + erh->hoff, + erh->data_len, + &tuphdr->t_infomask, + (erh->hasnull ? tuphdr->t_bits : NULL)); +} + +/* + * Look up the tupdesc for the expanded record's actual type + * + * Note: code internal to this module is allowed to just fetch + * erh->er_tupdesc if ER_FLAG_DVALUES_VALID is set; otherwise it should call + * expanded_record_get_tupdesc. This function is the out-of-line portion + * of expanded_record_get_tupdesc. + */ +TupleDesc +expanded_record_fetch_tupdesc(ExpandedRecordHeader *erh) +{ + TupleDesc tupdesc; + + /* Easy if we already have it (but caller should have checked already) */ + if (erh->er_tupdesc) + return erh->er_tupdesc; + + /* Lookup the composite type's tupdesc using the typcache */ + tupdesc = lookup_rowtype_tupdesc(erh->er_typeid, erh->er_typmod); + + /* + * If it's a refcounted tupdesc rather than a statically allocated one, we + * want to manage the refcount with a memory context callback rather than + * assuming that the CurrentResourceOwner is longer-lived than this + * expanded object. + */ + if (tupdesc->tdrefcount >= 0) + { + /* Register callback if we didn't already */ + if (erh->er_mcb.arg == NULL) + { + erh->er_mcb.func = ER_mc_callback; + erh->er_mcb.arg = (void *) erh; + MemoryContextRegisterResetCallback(erh->hdr.eoh_context, + &erh->er_mcb); + } + + /* Remember our own pointer */ + erh->er_tupdesc = tupdesc; + tupdesc->tdrefcount++; + + /* Release the pin lookup_rowtype_tupdesc acquired */ + ReleaseTupleDesc(tupdesc); + } + else + { + /* Just remember the pointer */ + erh->er_tupdesc = tupdesc; + } + + /* In either case, fetch the process-global ID for this tupdesc */ + erh->er_tupdesc_id = assign_record_type_identifier(tupdesc->tdtypeid, + tupdesc->tdtypmod); + + return tupdesc; +} + +/* + * Get a HeapTuple representing the current value of the expanded record + * + * If valid, the originally stored tuple is returned, so caller must not + * scribble on it. Otherwise, we return a HeapTuple created in the current + * memory context. In either case, no attempt has been made to inline + * out-of-line toasted values, so the tuple isn't usable as a composite + * datum. + * + * Returns NULL if expanded record is empty. + */ +HeapTuple +expanded_record_get_tuple(ExpandedRecordHeader *erh) +{ + /* Easy case if we still have original tuple */ + if (erh->flags & ER_FLAG_FVALUE_VALID) + return erh->fvalue; + + /* Else just build a tuple from datums */ + if (erh->flags & ER_FLAG_DVALUES_VALID) + return heap_form_tuple(erh->er_tupdesc, erh->dvalues, erh->dnulls); + + /* Expanded record is empty */ + return NULL; +} + +/* + * Memory context reset callback for cleaning up external resources + */ +static void +ER_mc_callback(void *arg) +{ + ExpandedRecordHeader *erh = (ExpandedRecordHeader *) arg; + TupleDesc tupdesc = erh->er_tupdesc; + + /* Release our privately-managed tupdesc refcount, if any */ + if (tupdesc) + { + erh->er_tupdesc = NULL; /* just for luck */ + if (tupdesc->tdrefcount > 0) + { + if (--tupdesc->tdrefcount == 0) + FreeTupleDesc(tupdesc); + } + } +} + +/* + * DatumGetExpandedRecord: get a writable expanded record from an input argument + * + * Caution: if the input is a read/write pointer, this returns the input + * argument; so callers must be sure that their changes are "safe", that is + * they cannot leave the record in a corrupt state. + */ +ExpandedRecordHeader * +DatumGetExpandedRecord(Datum d) +{ + /* If it's a writable expanded record already, just return it */ + if (VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(d))) + { + ExpandedRecordHeader *erh = (ExpandedRecordHeader *) DatumGetEOHP(d); + + Assert(erh->er_magic == ER_MAGIC); + return erh; + } + + /* Else expand the hard way */ + d = make_expanded_record_from_datum(d, CurrentMemoryContext); + return (ExpandedRecordHeader *) DatumGetEOHP(d); +} + +/* + * Create the Datum/isnull representation of an expanded record object + * if we didn't do so already. After calling this, it's OK to read the + * dvalues/dnulls arrays directly, rather than going through get_field. + * + * Note that if the object is currently empty ("null"), this will change + * it to represent a row of nulls. + */ +void +deconstruct_expanded_record(ExpandedRecordHeader *erh) +{ + TupleDesc tupdesc; + Datum *dvalues; + bool *dnulls; + int nfields; + + if (erh->flags & ER_FLAG_DVALUES_VALID) + return; /* already valid, nothing to do */ + + /* We'll need the tuple descriptor */ + tupdesc = expanded_record_get_tupdesc(erh); + + /* + * Allocate arrays in private context, if we don't have them already. We + * don't expect to see a change in nfields here, so while we cope if it + * happens, we don't bother avoiding a leak of the old arrays (which might + * not be separately palloc'd, anyway). + */ + nfields = tupdesc->natts; + if (erh->dvalues == NULL || erh->nfields != nfields) + { + char *chunk; + + /* + * To save a palloc cycle, we allocate both the Datum and isnull + * arrays in one palloc chunk. + */ + chunk = MemoryContextAlloc(erh->hdr.eoh_context, + nfields * (sizeof(Datum) + sizeof(bool))); + dvalues = (Datum *) chunk; + dnulls = (bool *) (chunk + nfields * sizeof(Datum)); + erh->dvalues = dvalues; + erh->dnulls = dnulls; + erh->nfields = nfields; + } + else + { + dvalues = erh->dvalues; + dnulls = erh->dnulls; + } + + if (erh->flags & ER_FLAG_FVALUE_VALID) + { + /* Deconstruct tuple */ + heap_deform_tuple(erh->fvalue, tupdesc, dvalues, dnulls); + } + else + { + /* If record was empty, instantiate it as a row of nulls */ + memset(dvalues, 0, nfields * sizeof(Datum)); + memset(dnulls, true, nfields * sizeof(bool)); + } + + /* Mark the dvalues as valid */ + erh->flags |= ER_FLAG_DVALUES_VALID; +} + +/* + * Look up a record field by name + * + * If there is a field named "fieldname", fill in the contents of finfo + * and return "true". Else return "false" without changing *finfo. + */ +bool +expanded_record_lookup_field(ExpandedRecordHeader *erh, const char *fieldname, + ExpandedRecordFieldInfo *finfo) +{ + TupleDesc tupdesc; + int fno; + Form_pg_attribute attr; + const FormData_pg_attribute *sysattr; + + tupdesc = expanded_record_get_tupdesc(erh); + + /* First, check user-defined attributes */ + for (fno = 0; fno < tupdesc->natts; fno++) + { + attr = TupleDescAttr(tupdesc, fno); + if (namestrcmp(&attr->attname, fieldname) == 0 && + !attr->attisdropped) + { + finfo->fnumber = attr->attnum; + finfo->ftypeid = attr->atttypid; + finfo->ftypmod = attr->atttypmod; + finfo->fcollation = attr->attcollation; + return true; + } + } + + /* How about system attributes? */ + sysattr = SystemAttributeByName(fieldname); + if (sysattr != NULL) + { + finfo->fnumber = sysattr->attnum; + finfo->ftypeid = sysattr->atttypid; + finfo->ftypmod = sysattr->atttypmod; + finfo->fcollation = sysattr->attcollation; + return true; + } + + return false; +} + +/* + * Fetch value of record field + * + * expanded_record_get_field is the frontend for this; it handles the + * easy inline-able cases. + */ +Datum +expanded_record_fetch_field(ExpandedRecordHeader *erh, int fnumber, + bool *isnull) +{ + if (fnumber > 0) + { + /* Empty record has null fields */ + if (ExpandedRecordIsEmpty(erh)) + { + *isnull = true; + return (Datum) 0; + } + /* Make sure we have deconstructed form */ + deconstruct_expanded_record(erh); + /* Out-of-range field number reads as null */ + if (unlikely(fnumber > erh->nfields)) + { + *isnull = true; + return (Datum) 0; + } + *isnull = erh->dnulls[fnumber - 1]; + return erh->dvalues[fnumber - 1]; + } + else + { + /* System columns read as null if we haven't got flat tuple */ + if (erh->fvalue == NULL) + { + *isnull = true; + return (Datum) 0; + } + /* heap_getsysattr doesn't actually use tupdesc, so just pass null */ + return heap_getsysattr(erh->fvalue, fnumber, NULL, isnull); + } +} + +/* + * Set value of record field + * + * If the expanded record is of domain type, the assignment will be rejected + * (without changing the record's state) if the domain's constraints would + * be violated. + * + * If expand_external is true and newValue is an out-of-line value, we'll + * forcibly detoast it so that the record does not depend on external storage. + * + * Internal callers can pass check_constraints = false to skip application + * of domain constraints. External callers should never do that. + */ +void +expanded_record_set_field_internal(ExpandedRecordHeader *erh, int fnumber, + Datum newValue, bool isnull, + bool expand_external, + bool check_constraints) +{ + TupleDesc tupdesc; + Form_pg_attribute attr; + Datum *dvalues; + bool *dnulls; + char *oldValue; + + /* + * Shouldn't ever be trying to assign new data to a dummy header, except + * in the case of an internal call for field inlining. + */ + Assert(!(erh->flags & ER_FLAG_IS_DUMMY) || !check_constraints); + + /* Before performing the assignment, see if result will satisfy domain */ + if ((erh->flags & ER_FLAG_IS_DOMAIN) && check_constraints) + check_domain_for_new_field(erh, fnumber, newValue, isnull); + + /* If we haven't yet deconstructed the tuple, do that */ + if (!(erh->flags & ER_FLAG_DVALUES_VALID)) + deconstruct_expanded_record(erh); + + /* Tuple descriptor must be valid by now */ + tupdesc = erh->er_tupdesc; + Assert(erh->nfields == tupdesc->natts); + + /* Caller error if fnumber is system column or nonexistent column */ + if (unlikely(fnumber <= 0 || fnumber > erh->nfields)) + elog(ERROR, "cannot assign to field %d of expanded record", fnumber); + + /* + * Copy new field value into record's context, and deal with detoasting, + * if needed. + */ + attr = TupleDescAttr(tupdesc, fnumber - 1); + if (!isnull && !attr->attbyval) + { + MemoryContext oldcxt; + + /* If requested, detoast any external value */ + if (expand_external) + { + if (attr->attlen == -1 && + VARATT_IS_EXTERNAL(DatumGetPointer(newValue))) + { + /* Detoasting should be done in short-lived context. */ + oldcxt = MemoryContextSwitchTo(get_short_term_cxt(erh)); + newValue = PointerGetDatum(detoast_external_attr((struct varlena *) DatumGetPointer(newValue))); + MemoryContextSwitchTo(oldcxt); + } + else + expand_external = false; /* need not clean up below */ + } + + /* Copy value into record's context */ + oldcxt = MemoryContextSwitchTo(erh->hdr.eoh_context); + newValue = datumCopy(newValue, false, attr->attlen); + MemoryContextSwitchTo(oldcxt); + + /* We can now flush anything that detoasting might have leaked */ + if (expand_external) + MemoryContextReset(erh->er_short_term_cxt); + + /* Remember that we have field(s) that may need to be pfree'd */ + erh->flags |= ER_FLAG_DVALUES_ALLOCED; + + /* + * While we're here, note whether it's an external toasted value, + * because that could mean we need to inline it later. (Think not to + * merge this into the previous expand_external logic: datumCopy could + * by itself have made the value non-external.) + */ + if (attr->attlen == -1 && + VARATT_IS_EXTERNAL(DatumGetPointer(newValue))) + erh->flags |= ER_FLAG_HAVE_EXTERNAL; + } + + /* + * We're ready to make irreversible changes. + */ + dvalues = erh->dvalues; + dnulls = erh->dnulls; + + /* Flattened value will no longer represent record accurately */ + erh->flags &= ~ER_FLAG_FVALUE_VALID; + /* And we don't know the flattened size either */ + erh->flat_size = 0; + + /* Grab old field value for pfree'ing, if needed. */ + if (!attr->attbyval && !dnulls[fnumber - 1]) + oldValue = (char *) DatumGetPointer(dvalues[fnumber - 1]); + else + oldValue = NULL; + + /* And finally we can insert the new field. */ + dvalues[fnumber - 1] = newValue; + dnulls[fnumber - 1] = isnull; + + /* + * Free old field if needed; this keeps repeated field replacements from + * bloating the record's storage. If the pfree somehow fails, it won't + * corrupt the record. + * + * If we're updating a dummy header, we can't risk pfree'ing the old + * value, because most likely the expanded record's main header still has + * a pointer to it. This won't result in any sustained memory leak, since + * whatever we just allocated here is in the short-lived domain check + * context. + */ + if (oldValue && !(erh->flags & ER_FLAG_IS_DUMMY)) + { + /* Don't try to pfree a part of the original flat record */ + if (oldValue < erh->fstartptr || oldValue >= erh->fendptr) + pfree(oldValue); + } +} + +/* + * Set all record field(s) + * + * Caller must ensure that the provided datums are of the right types + * to match the record's previously assigned rowtype. + * + * If expand_external is true, we'll forcibly detoast out-of-line field values + * so that the record does not depend on external storage. + * + * Unlike repeated application of expanded_record_set_field(), this does not + * guarantee to leave the expanded record in a non-corrupt state in event + * of an error. Typically it would only be used for initializing a new + * expanded record. Also, because we expect this to be applied at most once + * in the lifespan of an expanded record, we do not worry about any cruft + * that detoasting might leak. + */ +void +expanded_record_set_fields(ExpandedRecordHeader *erh, + const Datum *newValues, const bool *isnulls, + bool expand_external) +{ + TupleDesc tupdesc; + Datum *dvalues; + bool *dnulls; + int fnumber; + MemoryContext oldcxt; + + /* Shouldn't ever be trying to assign new data to a dummy header */ + Assert(!(erh->flags & ER_FLAG_IS_DUMMY)); + + /* If we haven't yet deconstructed the tuple, do that */ + if (!(erh->flags & ER_FLAG_DVALUES_VALID)) + deconstruct_expanded_record(erh); + + /* Tuple descriptor must be valid by now */ + tupdesc = erh->er_tupdesc; + Assert(erh->nfields == tupdesc->natts); + + /* Flattened value will no longer represent record accurately */ + erh->flags &= ~ER_FLAG_FVALUE_VALID; + /* And we don't know the flattened size either */ + erh->flat_size = 0; + + oldcxt = MemoryContextSwitchTo(erh->hdr.eoh_context); + + dvalues = erh->dvalues; + dnulls = erh->dnulls; + + for (fnumber = 0; fnumber < erh->nfields; fnumber++) + { + Form_pg_attribute attr = TupleDescAttr(tupdesc, fnumber); + Datum newValue; + bool isnull; + + /* Ignore dropped columns */ + if (attr->attisdropped) + continue; + + newValue = newValues[fnumber]; + isnull = isnulls[fnumber]; + + if (!attr->attbyval) + { + /* + * Copy new field value into record's context, and deal with + * detoasting, if needed. + */ + if (!isnull) + { + /* Is it an external toasted value? */ + if (attr->attlen == -1 && + VARATT_IS_EXTERNAL(DatumGetPointer(newValue))) + { + if (expand_external) + { + /* Detoast as requested while copying the value */ + newValue = PointerGetDatum(detoast_external_attr((struct varlena *) DatumGetPointer(newValue))); + } + else + { + /* Just copy the value */ + newValue = datumCopy(newValue, false, -1); + /* If it's still external, remember that */ + if (VARATT_IS_EXTERNAL(DatumGetPointer(newValue))) + erh->flags |= ER_FLAG_HAVE_EXTERNAL; + } + } + else + { + /* Not an external value, just copy it */ + newValue = datumCopy(newValue, false, attr->attlen); + } + + /* Remember that we have field(s) that need to be pfree'd */ + erh->flags |= ER_FLAG_DVALUES_ALLOCED; + } + + /* + * Free old field value, if any (not likely, since really we ought + * to be inserting into an empty record). + */ + if (unlikely(!dnulls[fnumber])) + { + char *oldValue; + + oldValue = (char *) DatumGetPointer(dvalues[fnumber]); + /* Don't try to pfree a part of the original flat record */ + if (oldValue < erh->fstartptr || oldValue >= erh->fendptr) + pfree(oldValue); + } + } + + /* And finally we can insert the new field. */ + dvalues[fnumber] = newValue; + dnulls[fnumber] = isnull; + } + + /* + * Because we don't guarantee atomicity of set_fields(), we can just leave + * checking of domain constraints to occur as the final step; if it throws + * an error, too bad. + */ + if (erh->flags & ER_FLAG_IS_DOMAIN) + { + /* We run domain_check in a short-lived context to limit cruft */ + MemoryContextSwitchTo(get_short_term_cxt(erh)); + + domain_check(ExpandedRecordGetRODatum(erh), false, + erh->er_decltypeid, + &erh->er_domaininfo, + erh->hdr.eoh_context); + } + + MemoryContextSwitchTo(oldcxt); +} + +/* + * Construct (or reset) working memory context for short-term operations. + * + * This context is used for domain check evaluation and for detoasting. + * + * If we don't have a short-lived memory context, make one; if we have one, + * reset it to get rid of any leftover cruft. (It is a tad annoying to need a + * whole context for this, since it will often go unused --- but it's hard to + * avoid memory leaks otherwise. We can make the context small, at least.) + */ +static MemoryContext +get_short_term_cxt(ExpandedRecordHeader *erh) +{ + if (erh->er_short_term_cxt == NULL) + erh->er_short_term_cxt = + AllocSetContextCreate(erh->hdr.eoh_context, + "expanded record short-term context", + ALLOCSET_SMALL_SIZES); + else + MemoryContextReset(erh->er_short_term_cxt); + return erh->er_short_term_cxt; +} + +/* + * Construct "dummy header" for checking domain constraints. + * + * Since we don't want to modify the state of the expanded record until + * we've validated the constraints, our approach is to set up a dummy + * record header containing the new field value(s) and then pass that to + * domain_check. We retain the dummy header as part of the expanded + * record's state to save palloc cycles, but reinitialize (most of) + * its contents on each use. + */ +static void +build_dummy_expanded_header(ExpandedRecordHeader *main_erh) +{ + ExpandedRecordHeader *erh; + TupleDesc tupdesc = expanded_record_get_tupdesc(main_erh); + + /* Ensure we have a short-lived context */ + (void) get_short_term_cxt(main_erh); + + /* + * Allocate dummy header on first time through, or in the unlikely event + * that the number of fields changes (in which case we just leak the old + * one). Include space for its field values in the request. + */ + erh = main_erh->er_dummy_header; + if (erh == NULL || erh->nfields != tupdesc->natts) + { + char *chunk; + + erh = (ExpandedRecordHeader *) + MemoryContextAlloc(main_erh->hdr.eoh_context, + MAXALIGN(sizeof(ExpandedRecordHeader)) + + tupdesc->natts * (sizeof(Datum) + sizeof(bool))); + + /* Ensure all header fields are initialized to 0/null */ + memset(erh, 0, sizeof(ExpandedRecordHeader)); + + /* + * We set up the dummy header with an indication that its memory + * context is the short-lived context. This is so that, if any + * detoasting of out-of-line values happens due to an attempt to + * extract a composite datum from the dummy header, the detoasted + * stuff will end up in the short-lived context and not cause a leak. + * This is cheating a bit on the expanded-object protocol; but since + * we never pass a R/W pointer to the dummy object to any other code, + * nothing else is authorized to delete or transfer ownership of the + * object's context, so it should be safe enough. + */ + EOH_init_header(&erh->hdr, &ER_methods, main_erh->er_short_term_cxt); + erh->er_magic = ER_MAGIC; + + /* Set up dvalues/dnulls, with no valid contents as yet */ + chunk = (char *) erh + MAXALIGN(sizeof(ExpandedRecordHeader)); + erh->dvalues = (Datum *) chunk; + erh->dnulls = (bool *) (chunk + tupdesc->natts * sizeof(Datum)); + erh->nfields = tupdesc->natts; + + /* + * The fields we just set are assumed to remain constant through + * multiple uses of the dummy header to check domain constraints. All + * other dummy header fields should be explicitly reset below, to + * ensure there's not accidental effects of one check on the next one. + */ + + main_erh->er_dummy_header = erh; + } + + /* + * If anything inquires about the dummy header's declared type, it should + * report the composite base type, not the domain type (since the VALUE in + * a domain check constraint is of the base type not the domain). Hence + * we do not transfer over the IS_DOMAIN flag, nor indeed any of the main + * header's flags, since the dummy header is empty of data at this point. + * But don't forget to mark header as dummy. + */ + erh->flags = ER_FLAG_IS_DUMMY; + + /* Copy composite-type identification info */ + erh->er_decltypeid = erh->er_typeid = main_erh->er_typeid; + erh->er_typmod = main_erh->er_typmod; + + /* Dummy header does not need its own tupdesc refcount */ + erh->er_tupdesc = tupdesc; + erh->er_tupdesc_id = main_erh->er_tupdesc_id; + + /* + * It's tempting to copy over whatever we know about the flat size, but + * there's no point since we're surely about to modify the dummy record's + * field(s). Instead just clear anything left over from a previous usage + * cycle. + */ + erh->flat_size = 0; + + /* Copy over fvalue if we have it, so that system columns are available */ + erh->fvalue = main_erh->fvalue; + erh->fstartptr = main_erh->fstartptr; + erh->fendptr = main_erh->fendptr; +} + +/* + * Precheck domain constraints for a set_field operation + */ +static pg_noinline void +check_domain_for_new_field(ExpandedRecordHeader *erh, int fnumber, + Datum newValue, bool isnull) +{ + ExpandedRecordHeader *dummy_erh; + MemoryContext oldcxt; + + /* Construct dummy header to contain proposed new field set */ + build_dummy_expanded_header(erh); + dummy_erh = erh->er_dummy_header; + + /* + * If record isn't empty, just deconstruct it (if needed) and copy over + * the existing field values. If it is empty, just fill fields with nulls + * manually --- don't call deconstruct_expanded_record prematurely. + */ + if (!ExpandedRecordIsEmpty(erh)) + { + deconstruct_expanded_record(erh); + memcpy(dummy_erh->dvalues, erh->dvalues, + dummy_erh->nfields * sizeof(Datum)); + memcpy(dummy_erh->dnulls, erh->dnulls, + dummy_erh->nfields * sizeof(bool)); + /* There might be some external values in there... */ + dummy_erh->flags |= erh->flags & ER_FLAG_HAVE_EXTERNAL; + } + else + { + memset(dummy_erh->dvalues, 0, dummy_erh->nfields * sizeof(Datum)); + memset(dummy_erh->dnulls, true, dummy_erh->nfields * sizeof(bool)); + } + + /* Either way, we now have valid dvalues */ + dummy_erh->flags |= ER_FLAG_DVALUES_VALID; + + /* Caller error if fnumber is system column or nonexistent column */ + if (unlikely(fnumber <= 0 || fnumber > dummy_erh->nfields)) + elog(ERROR, "cannot assign to field %d of expanded record", fnumber); + + /* Insert proposed new value into dummy field array */ + dummy_erh->dvalues[fnumber - 1] = newValue; + dummy_erh->dnulls[fnumber - 1] = isnull; + + /* + * The proposed new value might be external, in which case we'd better set + * the flag for that in dummy_erh. (This matters in case something in the + * domain check expressions tries to extract a flat value from the dummy + * header.) + */ + if (!isnull) + { + Form_pg_attribute attr = TupleDescAttr(erh->er_tupdesc, fnumber - 1); + + if (!attr->attbyval && attr->attlen == -1 && + VARATT_IS_EXTERNAL(DatumGetPointer(newValue))) + dummy_erh->flags |= ER_FLAG_HAVE_EXTERNAL; + } + + /* + * We call domain_check in the short-lived context, so that any cruft + * leaked by expression evaluation can be reclaimed. + */ + oldcxt = MemoryContextSwitchTo(erh->er_short_term_cxt); + + /* + * And now we can apply the check. Note we use main header's domain cache + * space, so that caching carries across repeated uses. + */ + domain_check(ExpandedRecordGetRODatum(dummy_erh), false, + erh->er_decltypeid, + &erh->er_domaininfo, + erh->hdr.eoh_context); + + MemoryContextSwitchTo(oldcxt); + + /* We might as well clean up cruft immediately. */ + MemoryContextReset(erh->er_short_term_cxt); +} + +/* + * Precheck domain constraints for a set_tuple operation + */ +static pg_noinline void +check_domain_for_new_tuple(ExpandedRecordHeader *erh, HeapTuple tuple) +{ + ExpandedRecordHeader *dummy_erh; + MemoryContext oldcxt; + + /* If we're being told to set record to empty, just see if NULL is OK */ + if (tuple == NULL) + { + /* We run domain_check in a short-lived context to limit cruft */ + oldcxt = MemoryContextSwitchTo(get_short_term_cxt(erh)); + + domain_check((Datum) 0, true, + erh->er_decltypeid, + &erh->er_domaininfo, + erh->hdr.eoh_context); + + MemoryContextSwitchTo(oldcxt); + + /* We might as well clean up cruft immediately. */ + MemoryContextReset(erh->er_short_term_cxt); + + return; + } + + /* Construct dummy header to contain replacement tuple */ + build_dummy_expanded_header(erh); + dummy_erh = erh->er_dummy_header; + + /* Insert tuple, but don't bother to deconstruct its fields for now */ + dummy_erh->fvalue = tuple; + dummy_erh->fstartptr = (char *) tuple->t_data; + dummy_erh->fendptr = ((char *) tuple->t_data) + tuple->t_len; + dummy_erh->flags |= ER_FLAG_FVALUE_VALID; + + /* Remember if we have any out-of-line field values */ + if (HeapTupleHasExternal(tuple)) + dummy_erh->flags |= ER_FLAG_HAVE_EXTERNAL; + + /* + * We call domain_check in the short-lived context, so that any cruft + * leaked by expression evaluation can be reclaimed. + */ + oldcxt = MemoryContextSwitchTo(erh->er_short_term_cxt); + + /* + * And now we can apply the check. Note we use main header's domain cache + * space, so that caching carries across repeated uses. + */ + domain_check(ExpandedRecordGetRODatum(dummy_erh), false, + erh->er_decltypeid, + &erh->er_domaininfo, + erh->hdr.eoh_context); + + MemoryContextSwitchTo(oldcxt); + + /* We might as well clean up cruft immediately. */ + MemoryContextReset(erh->er_short_term_cxt); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/float.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/float.c new file mode 100644 index 00000000000..902d7912961 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/float.c @@ -0,0 +1,4177 @@ +/*------------------------------------------------------------------------- + * + * float.c + * Functions for the built-in floating-point types. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/adt/float.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <ctype.h> +#include <float.h> +#include <math.h> +#include <limits.h> + +#include "catalog/pg_type.h" +#include "common/int.h" +#include "common/pg_prng.h" +#include "common/shortest_dec.h" +#include "libpq/pqformat.h" +#include "miscadmin.h" +#include "utils/array.h" +#include "utils/float.h" +#include "utils/fmgrprotos.h" +#include "utils/sortsupport.h" +#include "utils/timestamp.h" + + +/* + * Configurable GUC parameter + * + * If >0, use shortest-decimal format for output; this is both the default and + * allows for compatibility with clients that explicitly set a value here to + * get round-trip-accurate results. If 0 or less, then use the old, slow, + * decimal rounding method. + */ +__thread int extra_float_digits = 1; + +/* Cached constants for degree-based trig functions */ +static __thread bool degree_consts_set = false; +static __thread float8 sin_30 = 0; +static __thread float8 one_minus_cos_60 = 0; +static __thread float8 asin_0_5 = 0; +static __thread float8 acos_0_5 = 0; +static __thread float8 atan_1_0 = 0; +static __thread float8 tan_45 = 0; +static __thread float8 cot_45 = 0; + +/* + * These are intentionally not static; don't "fix" them. They will never + * be referenced by other files, much less changed; but we don't want the + * compiler to know that, else it might try to precompute expressions + * involving them. See comments for init_degree_constants(). + */ +__thread float8 degree_c_thirty = 30.0; +__thread float8 degree_c_forty_five = 45.0; +__thread float8 degree_c_sixty = 60.0; +__thread float8 degree_c_one_half = 0.5; +__thread float8 degree_c_one = 1.0; + +/* State for drandom() and setseed() */ +static __thread bool drandom_seed_set = false; +static __thread pg_prng_state drandom_seed; + +/* Local function prototypes */ +static double sind_q1(double x); +static double cosd_q1(double x); +static void init_degree_constants(void); + + +/* + * We use these out-of-line ereport() calls to report float overflow, + * underflow, and zero-divide, because following our usual practice of + * repeating them at each call site would lead to a lot of code bloat. + * + * This does mean that you don't get a useful error location indicator. + */ +pg_noinline void +float_overflow_error(void) +{ + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("value out of range: overflow"))); +} + +pg_noinline void +float_underflow_error(void) +{ + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("value out of range: underflow"))); +} + +pg_noinline void +float_zero_divide_error(void) +{ + ereport(ERROR, + (errcode(ERRCODE_DIVISION_BY_ZERO), + errmsg("division by zero"))); +} + + +/* + * Returns -1 if 'val' represents negative infinity, 1 if 'val' + * represents (positive) infinity, and 0 otherwise. On some platforms, + * this is equivalent to the isinf() macro, but not everywhere: C99 + * does not specify that isinf() needs to distinguish between positive + * and negative infinity. + */ +int +is_infinite(double val) +{ + int inf = isinf(val); + + if (inf == 0) + return 0; + else if (val > 0) + return 1; + else + return -1; +} + + +/* ========== USER I/O ROUTINES ========== */ + + +/* + * float4in - converts "num" to float4 + * + * Note that this code now uses strtof(), where it used to use strtod(). + * + * The motivation for using strtof() is to avoid a double-rounding problem: + * for certain decimal inputs, if you round the input correctly to a double, + * and then round the double to a float, the result is incorrect in that it + * does not match the result of rounding the decimal value to float directly. + * + * One of the best examples is 7.038531e-26: + * + * 0xAE43FDp-107 = 7.03853069185120912085...e-26 + * midpoint 7.03853100000000022281...e-26 + * 0xAE43FEp-107 = 7.03853130814879132477...e-26 + * + * making 0xAE43FDp-107 the correct float result, but if you do the conversion + * via a double, you get + * + * 0xAE43FD.7FFFFFF8p-107 = 7.03853099999999907487...e-26 + * midpoint 7.03853099999999964884...e-26 + * 0xAE43FD.80000000p-107 = 7.03853100000000022281...e-26 + * 0xAE43FD.80000008p-107 = 7.03853100000000137076...e-26 + * + * so the value rounds to the double exactly on the midpoint between the two + * nearest floats, and then rounding again to a float gives the incorrect + * result of 0xAE43FEp-107. + * + */ +Datum +float4in(PG_FUNCTION_ARGS) +{ + char *num = PG_GETARG_CSTRING(0); + + PG_RETURN_FLOAT4(float4in_internal(num, NULL, "real", num, + fcinfo->context)); +} + +/* + * float4in_internal - guts of float4in() + * + * This is exposed for use by functions that want a reasonably + * platform-independent way of inputting floats. The behavior is + * essentially like strtof + ereturn on error. + * + * Uses the same API as float8in_internal below, so most of its + * comments also apply here, except regarding use in geometric types. + */ +float4 +float4in_internal(char *num, char **endptr_p, + const char *type_name, const char *orig_string, + struct Node *escontext) +{ + float val; + char *endptr; + + /* + * endptr points to the first character _after_ the sequence we recognized + * as a valid floating point number. orig_string points to the original + * input string. + */ + + /* skip leading whitespace */ + while (*num != '\0' && isspace((unsigned char) *num)) + num++; + + /* + * Check for an empty-string input to begin with, to avoid the vagaries of + * strtod() on different platforms. + */ + if (*num == '\0') + ereturn(escontext, 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s: \"%s\"", + type_name, orig_string))); + + errno = 0; + val = strtof(num, &endptr); + + /* did we not see anything that looks like a double? */ + if (endptr == num || errno != 0) + { + int save_errno = errno; + + /* + * C99 requires that strtof() accept NaN, [+-]Infinity, and [+-]Inf, + * but not all platforms support all of these (and some accept them + * but set ERANGE anyway...) Therefore, we check for these inputs + * ourselves if strtof() fails. + * + * Note: C99 also requires hexadecimal input as well as some extended + * forms of NaN, but we consider these forms unportable and don't try + * to support them. You can use 'em if your strtof() takes 'em. + */ + if (pg_strncasecmp(num, "NaN", 3) == 0) + { + val = get_float4_nan(); + endptr = num + 3; + } + else if (pg_strncasecmp(num, "Infinity", 8) == 0) + { + val = get_float4_infinity(); + endptr = num + 8; + } + else if (pg_strncasecmp(num, "+Infinity", 9) == 0) + { + val = get_float4_infinity(); + endptr = num + 9; + } + else if (pg_strncasecmp(num, "-Infinity", 9) == 0) + { + val = -get_float4_infinity(); + endptr = num + 9; + } + else if (pg_strncasecmp(num, "inf", 3) == 0) + { + val = get_float4_infinity(); + endptr = num + 3; + } + else if (pg_strncasecmp(num, "+inf", 4) == 0) + { + val = get_float4_infinity(); + endptr = num + 4; + } + else if (pg_strncasecmp(num, "-inf", 4) == 0) + { + val = -get_float4_infinity(); + endptr = num + 4; + } + else if (save_errno == ERANGE) + { + /* + * Some platforms return ERANGE for denormalized numbers (those + * that are not zero, but are too close to zero to have full + * precision). We'd prefer not to throw error for that, so try to + * detect whether it's a "real" out-of-range condition by checking + * to see if the result is zero or huge. + */ + if (val == 0.0 || +#if !defined(HUGE_VALF) + isinf(val) +#else + (val >= HUGE_VALF || val <= -HUGE_VALF) +#endif + ) + { + /* see comments in float8in_internal for rationale */ + char *errnumber = pstrdup(num); + + errnumber[endptr - num] = '\0'; + + ereturn(escontext, 0, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("\"%s\" is out of range for type real", + errnumber))); + } + } + else + ereturn(escontext, 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s: \"%s\"", + type_name, orig_string))); + } + + /* skip trailing whitespace */ + while (*endptr != '\0' && isspace((unsigned char) *endptr)) + endptr++; + + /* report stopping point if wanted, else complain if not end of string */ + if (endptr_p) + *endptr_p = endptr; + else if (*endptr != '\0') + ereturn(escontext, 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s: \"%s\"", + type_name, orig_string))); + + return val; +} + +/* + * float4out - converts a float4 number to a string + * using a standard output format + */ +Datum +float4out(PG_FUNCTION_ARGS) +{ + float4 num = PG_GETARG_FLOAT4(0); + char *ascii = (char *) palloc(32); + int ndig = FLT_DIG + extra_float_digits; + + if (extra_float_digits > 0) + { + float_to_shortest_decimal_buf(num, ascii); + PG_RETURN_CSTRING(ascii); + } + + (void) pg_strfromd(ascii, 32, ndig, num); + PG_RETURN_CSTRING(ascii); +} + +/* + * float4recv - converts external binary format to float4 + */ +Datum +float4recv(PG_FUNCTION_ARGS) +{ + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + + PG_RETURN_FLOAT4(pq_getmsgfloat4(buf)); +} + +/* + * float4send - converts float4 to binary format + */ +Datum +float4send(PG_FUNCTION_ARGS) +{ + float4 num = PG_GETARG_FLOAT4(0); + StringInfoData buf; + + pq_begintypsend(&buf); + pq_sendfloat4(&buf, num); + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} + +/* + * float8in - converts "num" to float8 + */ +Datum +float8in(PG_FUNCTION_ARGS) +{ + char *num = PG_GETARG_CSTRING(0); + + PG_RETURN_FLOAT8(float8in_internal(num, NULL, "double precision", num, + fcinfo->context)); +} + +/* + * float8in_internal - guts of float8in() + * + * This is exposed for use by functions that want a reasonably + * platform-independent way of inputting doubles. The behavior is + * essentially like strtod + ereturn on error, but note the following + * differences: + * 1. Both leading and trailing whitespace are skipped. + * 2. If endptr_p is NULL, we report error if there's trailing junk. + * Otherwise, it's up to the caller to complain about trailing junk. + * 3. In event of a syntax error, the report mentions the given type_name + * and prints orig_string as the input; this is meant to support use of + * this function with types such as "box" and "point", where what we are + * parsing here is just a substring of orig_string. + * + * If escontext points to an ErrorSaveContext node, that is filled instead + * of throwing an error; the caller must check SOFT_ERROR_OCCURRED() + * to detect errors. + * + * "num" could validly be declared "const char *", but that results in an + * unreasonable amount of extra casting both here and in callers, so we don't. + */ +float8 +float8in_internal(char *num, char **endptr_p, + const char *type_name, const char *orig_string, + struct Node *escontext) +{ + double val; + char *endptr; + + /* skip leading whitespace */ + while (*num != '\0' && isspace((unsigned char) *num)) + num++; + + /* + * Check for an empty-string input to begin with, to avoid the vagaries of + * strtod() on different platforms. + */ + if (*num == '\0') + ereturn(escontext, 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s: \"%s\"", + type_name, orig_string))); + + errno = 0; + val = strtod(num, &endptr); + + /* did we not see anything that looks like a double? */ + if (endptr == num || errno != 0) + { + int save_errno = errno; + + /* + * C99 requires that strtod() accept NaN, [+-]Infinity, and [+-]Inf, + * but not all platforms support all of these (and some accept them + * but set ERANGE anyway...) Therefore, we check for these inputs + * ourselves if strtod() fails. + * + * Note: C99 also requires hexadecimal input as well as some extended + * forms of NaN, but we consider these forms unportable and don't try + * to support them. You can use 'em if your strtod() takes 'em. + */ + if (pg_strncasecmp(num, "NaN", 3) == 0) + { + val = get_float8_nan(); + endptr = num + 3; + } + else if (pg_strncasecmp(num, "Infinity", 8) == 0) + { + val = get_float8_infinity(); + endptr = num + 8; + } + else if (pg_strncasecmp(num, "+Infinity", 9) == 0) + { + val = get_float8_infinity(); + endptr = num + 9; + } + else if (pg_strncasecmp(num, "-Infinity", 9) == 0) + { + val = -get_float8_infinity(); + endptr = num + 9; + } + else if (pg_strncasecmp(num, "inf", 3) == 0) + { + val = get_float8_infinity(); + endptr = num + 3; + } + else if (pg_strncasecmp(num, "+inf", 4) == 0) + { + val = get_float8_infinity(); + endptr = num + 4; + } + else if (pg_strncasecmp(num, "-inf", 4) == 0) + { + val = -get_float8_infinity(); + endptr = num + 4; + } + else if (save_errno == ERANGE) + { + /* + * Some platforms return ERANGE for denormalized numbers (those + * that are not zero, but are too close to zero to have full + * precision). We'd prefer not to throw error for that, so try to + * detect whether it's a "real" out-of-range condition by checking + * to see if the result is zero or huge. + * + * On error, we intentionally complain about double precision not + * the given type name, and we print only the part of the string + * that is the current number. + */ + if (val == 0.0 || val >= HUGE_VAL || val <= -HUGE_VAL) + { + char *errnumber = pstrdup(num); + + errnumber[endptr - num] = '\0'; + ereturn(escontext, 0, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("\"%s\" is out of range for type double precision", + errnumber))); + } + } + else + ereturn(escontext, 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s: \"%s\"", + type_name, orig_string))); + } + + /* skip trailing whitespace */ + while (*endptr != '\0' && isspace((unsigned char) *endptr)) + endptr++; + + /* report stopping point if wanted, else complain if not end of string */ + if (endptr_p) + *endptr_p = endptr; + else if (*endptr != '\0') + ereturn(escontext, 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s: \"%s\"", + type_name, orig_string))); + + return val; +} + + +/* + * float8out - converts float8 number to a string + * using a standard output format + */ +Datum +float8out(PG_FUNCTION_ARGS) +{ + float8 num = PG_GETARG_FLOAT8(0); + + PG_RETURN_CSTRING(float8out_internal(num)); +} + +/* + * float8out_internal - guts of float8out() + * + * This is exposed for use by functions that want a reasonably + * platform-independent way of outputting doubles. + * The result is always palloc'd. + */ +char * +float8out_internal(double num) +{ + char *ascii = (char *) palloc(32); + int ndig = DBL_DIG + extra_float_digits; + + if (extra_float_digits > 0) + { + double_to_shortest_decimal_buf(num, ascii); + return ascii; + } + + (void) pg_strfromd(ascii, 32, ndig, num); + return ascii; +} + +/* + * float8recv - converts external binary format to float8 + */ +Datum +float8recv(PG_FUNCTION_ARGS) +{ + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + + PG_RETURN_FLOAT8(pq_getmsgfloat8(buf)); +} + +/* + * float8send - converts float8 to binary format + */ +Datum +float8send(PG_FUNCTION_ARGS) +{ + float8 num = PG_GETARG_FLOAT8(0); + StringInfoData buf; + + pq_begintypsend(&buf); + pq_sendfloat8(&buf, num); + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} + + +/* ========== PUBLIC ROUTINES ========== */ + + +/* + * ====================== + * FLOAT4 BASE OPERATIONS + * ====================== + */ + +/* + * float4abs - returns |arg1| (absolute value) + */ +Datum +float4abs(PG_FUNCTION_ARGS) +{ + float4 arg1 = PG_GETARG_FLOAT4(0); + + PG_RETURN_FLOAT4(fabsf(arg1)); +} + +/* + * float4um - returns -arg1 (unary minus) + */ +Datum +float4um(PG_FUNCTION_ARGS) +{ + float4 arg1 = PG_GETARG_FLOAT4(0); + float4 result; + + result = -arg1; + PG_RETURN_FLOAT4(result); +} + +Datum +float4up(PG_FUNCTION_ARGS) +{ + float4 arg = PG_GETARG_FLOAT4(0); + + PG_RETURN_FLOAT4(arg); +} + +Datum +float4larger(PG_FUNCTION_ARGS) +{ + float4 arg1 = PG_GETARG_FLOAT4(0); + float4 arg2 = PG_GETARG_FLOAT4(1); + float4 result; + + if (float4_gt(arg1, arg2)) + result = arg1; + else + result = arg2; + PG_RETURN_FLOAT4(result); +} + +Datum +float4smaller(PG_FUNCTION_ARGS) +{ + float4 arg1 = PG_GETARG_FLOAT4(0); + float4 arg2 = PG_GETARG_FLOAT4(1); + float4 result; + + if (float4_lt(arg1, arg2)) + result = arg1; + else + result = arg2; + PG_RETURN_FLOAT4(result); +} + +/* + * ====================== + * FLOAT8 BASE OPERATIONS + * ====================== + */ + +/* + * float8abs - returns |arg1| (absolute value) + */ +Datum +float8abs(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + + PG_RETURN_FLOAT8(fabs(arg1)); +} + + +/* + * float8um - returns -arg1 (unary minus) + */ +Datum +float8um(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + float8 result; + + result = -arg1; + PG_RETURN_FLOAT8(result); +} + +Datum +float8up(PG_FUNCTION_ARGS) +{ + float8 arg = PG_GETARG_FLOAT8(0); + + PG_RETURN_FLOAT8(arg); +} + +Datum +float8larger(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + float8 arg2 = PG_GETARG_FLOAT8(1); + float8 result; + + if (float8_gt(arg1, arg2)) + result = arg1; + else + result = arg2; + PG_RETURN_FLOAT8(result); +} + +Datum +float8smaller(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + float8 arg2 = PG_GETARG_FLOAT8(1); + float8 result; + + if (float8_lt(arg1, arg2)) + result = arg1; + else + result = arg2; + PG_RETURN_FLOAT8(result); +} + + +/* + * ==================== + * ARITHMETIC OPERATORS + * ==================== + */ + +/* + * float4pl - returns arg1 + arg2 + * float4mi - returns arg1 - arg2 + * float4mul - returns arg1 * arg2 + * float4div - returns arg1 / arg2 + */ +Datum +float4pl(PG_FUNCTION_ARGS) +{ + float4 arg1 = PG_GETARG_FLOAT4(0); + float4 arg2 = PG_GETARG_FLOAT4(1); + + PG_RETURN_FLOAT4(float4_pl(arg1, arg2)); +} + +Datum +float4mi(PG_FUNCTION_ARGS) +{ + float4 arg1 = PG_GETARG_FLOAT4(0); + float4 arg2 = PG_GETARG_FLOAT4(1); + + PG_RETURN_FLOAT4(float4_mi(arg1, arg2)); +} + +Datum +float4mul(PG_FUNCTION_ARGS) +{ + float4 arg1 = PG_GETARG_FLOAT4(0); + float4 arg2 = PG_GETARG_FLOAT4(1); + + PG_RETURN_FLOAT4(float4_mul(arg1, arg2)); +} + +Datum +float4div(PG_FUNCTION_ARGS) +{ + float4 arg1 = PG_GETARG_FLOAT4(0); + float4 arg2 = PG_GETARG_FLOAT4(1); + + PG_RETURN_FLOAT4(float4_div(arg1, arg2)); +} + +/* + * float8pl - returns arg1 + arg2 + * float8mi - returns arg1 - arg2 + * float8mul - returns arg1 * arg2 + * float8div - returns arg1 / arg2 + */ +Datum +float8pl(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + float8 arg2 = PG_GETARG_FLOAT8(1); + + PG_RETURN_FLOAT8(float8_pl(arg1, arg2)); +} + +Datum +float8mi(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + float8 arg2 = PG_GETARG_FLOAT8(1); + + PG_RETURN_FLOAT8(float8_mi(arg1, arg2)); +} + +Datum +float8mul(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + float8 arg2 = PG_GETARG_FLOAT8(1); + + PG_RETURN_FLOAT8(float8_mul(arg1, arg2)); +} + +Datum +float8div(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + float8 arg2 = PG_GETARG_FLOAT8(1); + + PG_RETURN_FLOAT8(float8_div(arg1, arg2)); +} + + +/* + * ==================== + * COMPARISON OPERATORS + * ==================== + */ + +/* + * float4{eq,ne,lt,le,gt,ge} - float4/float4 comparison operations + */ +int +float4_cmp_internal(float4 a, float4 b) +{ + if (float4_gt(a, b)) + return 1; + if (float4_lt(a, b)) + return -1; + return 0; +} + +Datum +float4eq(PG_FUNCTION_ARGS) +{ + float4 arg1 = PG_GETARG_FLOAT4(0); + float4 arg2 = PG_GETARG_FLOAT4(1); + + PG_RETURN_BOOL(float4_eq(arg1, arg2)); +} + +Datum +float4ne(PG_FUNCTION_ARGS) +{ + float4 arg1 = PG_GETARG_FLOAT4(0); + float4 arg2 = PG_GETARG_FLOAT4(1); + + PG_RETURN_BOOL(float4_ne(arg1, arg2)); +} + +Datum +float4lt(PG_FUNCTION_ARGS) +{ + float4 arg1 = PG_GETARG_FLOAT4(0); + float4 arg2 = PG_GETARG_FLOAT4(1); + + PG_RETURN_BOOL(float4_lt(arg1, arg2)); +} + +Datum +float4le(PG_FUNCTION_ARGS) +{ + float4 arg1 = PG_GETARG_FLOAT4(0); + float4 arg2 = PG_GETARG_FLOAT4(1); + + PG_RETURN_BOOL(float4_le(arg1, arg2)); +} + +Datum +float4gt(PG_FUNCTION_ARGS) +{ + float4 arg1 = PG_GETARG_FLOAT4(0); + float4 arg2 = PG_GETARG_FLOAT4(1); + + PG_RETURN_BOOL(float4_gt(arg1, arg2)); +} + +Datum +float4ge(PG_FUNCTION_ARGS) +{ + float4 arg1 = PG_GETARG_FLOAT4(0); + float4 arg2 = PG_GETARG_FLOAT4(1); + + PG_RETURN_BOOL(float4_ge(arg1, arg2)); +} + +Datum +btfloat4cmp(PG_FUNCTION_ARGS) +{ + float4 arg1 = PG_GETARG_FLOAT4(0); + float4 arg2 = PG_GETARG_FLOAT4(1); + + PG_RETURN_INT32(float4_cmp_internal(arg1, arg2)); +} + +static int +btfloat4fastcmp(Datum x, Datum y, SortSupport ssup) +{ + float4 arg1 = DatumGetFloat4(x); + float4 arg2 = DatumGetFloat4(y); + + return float4_cmp_internal(arg1, arg2); +} + +Datum +btfloat4sortsupport(PG_FUNCTION_ARGS) +{ + SortSupport ssup = (SortSupport) PG_GETARG_POINTER(0); + + ssup->comparator = btfloat4fastcmp; + PG_RETURN_VOID(); +} + +/* + * float8{eq,ne,lt,le,gt,ge} - float8/float8 comparison operations + */ +int +float8_cmp_internal(float8 a, float8 b) +{ + if (float8_gt(a, b)) + return 1; + if (float8_lt(a, b)) + return -1; + return 0; +} + +Datum +float8eq(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + float8 arg2 = PG_GETARG_FLOAT8(1); + + PG_RETURN_BOOL(float8_eq(arg1, arg2)); +} + +Datum +float8ne(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + float8 arg2 = PG_GETARG_FLOAT8(1); + + PG_RETURN_BOOL(float8_ne(arg1, arg2)); +} + +Datum +float8lt(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + float8 arg2 = PG_GETARG_FLOAT8(1); + + PG_RETURN_BOOL(float8_lt(arg1, arg2)); +} + +Datum +float8le(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + float8 arg2 = PG_GETARG_FLOAT8(1); + + PG_RETURN_BOOL(float8_le(arg1, arg2)); +} + +Datum +float8gt(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + float8 arg2 = PG_GETARG_FLOAT8(1); + + PG_RETURN_BOOL(float8_gt(arg1, arg2)); +} + +Datum +float8ge(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + float8 arg2 = PG_GETARG_FLOAT8(1); + + PG_RETURN_BOOL(float8_ge(arg1, arg2)); +} + +Datum +btfloat8cmp(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + float8 arg2 = PG_GETARG_FLOAT8(1); + + PG_RETURN_INT32(float8_cmp_internal(arg1, arg2)); +} + +static int +btfloat8fastcmp(Datum x, Datum y, SortSupport ssup) +{ + float8 arg1 = DatumGetFloat8(x); + float8 arg2 = DatumGetFloat8(y); + + return float8_cmp_internal(arg1, arg2); +} + +Datum +btfloat8sortsupport(PG_FUNCTION_ARGS) +{ + SortSupport ssup = (SortSupport) PG_GETARG_POINTER(0); + + ssup->comparator = btfloat8fastcmp; + PG_RETURN_VOID(); +} + +Datum +btfloat48cmp(PG_FUNCTION_ARGS) +{ + float4 arg1 = PG_GETARG_FLOAT4(0); + float8 arg2 = PG_GETARG_FLOAT8(1); + + /* widen float4 to float8 and then compare */ + PG_RETURN_INT32(float8_cmp_internal(arg1, arg2)); +} + +Datum +btfloat84cmp(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + float4 arg2 = PG_GETARG_FLOAT4(1); + + /* widen float4 to float8 and then compare */ + PG_RETURN_INT32(float8_cmp_internal(arg1, arg2)); +} + +/* + * in_range support function for float8. + * + * Note: we needn't supply a float8_float4 variant, as implicit coercion + * of the offset value takes care of that scenario just as well. + */ +Datum +in_range_float8_float8(PG_FUNCTION_ARGS) +{ + float8 val = PG_GETARG_FLOAT8(0); + float8 base = PG_GETARG_FLOAT8(1); + float8 offset = PG_GETARG_FLOAT8(2); + bool sub = PG_GETARG_BOOL(3); + bool less = PG_GETARG_BOOL(4); + float8 sum; + + /* + * Reject negative or NaN offset. Negative is per spec, and NaN is + * because appropriate semantics for that seem non-obvious. + */ + if (isnan(offset) || offset < 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE), + errmsg("invalid preceding or following size in window function"))); + + /* + * Deal with cases where val and/or base is NaN, following the rule that + * NaN sorts after non-NaN (cf float8_cmp_internal). The offset cannot + * affect the conclusion. + */ + if (isnan(val)) + { + if (isnan(base)) + PG_RETURN_BOOL(true); /* NAN = NAN */ + else + PG_RETURN_BOOL(!less); /* NAN > non-NAN */ + } + else if (isnan(base)) + { + PG_RETURN_BOOL(less); /* non-NAN < NAN */ + } + + /* + * Deal with cases where both base and offset are infinite, and computing + * base +/- offset would produce NaN. This corresponds to a window frame + * whose boundary infinitely precedes +inf or infinitely follows -inf, + * which is not well-defined. For consistency with other cases involving + * infinities, such as the fact that +inf infinitely follows +inf, we + * choose to assume that +inf infinitely precedes +inf and -inf infinitely + * follows -inf, and therefore that all finite and infinite values are in + * such a window frame. + * + * offset is known positive, so we need only check the sign of base in + * this test. + */ + if (isinf(offset) && isinf(base) && + (sub ? base > 0 : base < 0)) + PG_RETURN_BOOL(true); + + /* + * Otherwise it should be safe to compute base +/- offset. We trust the + * FPU to cope if an input is +/-inf or the true sum would overflow, and + * produce a suitably signed infinity, which will compare properly against + * val whether or not that's infinity. + */ + if (sub) + sum = base - offset; + else + sum = base + offset; + + if (less) + PG_RETURN_BOOL(val <= sum); + else + PG_RETURN_BOOL(val >= sum); +} + +/* + * in_range support function for float4. + * + * We would need a float4_float8 variant in any case, so we supply that and + * let implicit coercion take care of the float4_float4 case. + */ +Datum +in_range_float4_float8(PG_FUNCTION_ARGS) +{ + float4 val = PG_GETARG_FLOAT4(0); + float4 base = PG_GETARG_FLOAT4(1); + float8 offset = PG_GETARG_FLOAT8(2); + bool sub = PG_GETARG_BOOL(3); + bool less = PG_GETARG_BOOL(4); + float8 sum; + + /* + * Reject negative or NaN offset. Negative is per spec, and NaN is + * because appropriate semantics for that seem non-obvious. + */ + if (isnan(offset) || offset < 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE), + errmsg("invalid preceding or following size in window function"))); + + /* + * Deal with cases where val and/or base is NaN, following the rule that + * NaN sorts after non-NaN (cf float8_cmp_internal). The offset cannot + * affect the conclusion. + */ + if (isnan(val)) + { + if (isnan(base)) + PG_RETURN_BOOL(true); /* NAN = NAN */ + else + PG_RETURN_BOOL(!less); /* NAN > non-NAN */ + } + else if (isnan(base)) + { + PG_RETURN_BOOL(less); /* non-NAN < NAN */ + } + + /* + * Deal with cases where both base and offset are infinite, and computing + * base +/- offset would produce NaN. This corresponds to a window frame + * whose boundary infinitely precedes +inf or infinitely follows -inf, + * which is not well-defined. For consistency with other cases involving + * infinities, such as the fact that +inf infinitely follows +inf, we + * choose to assume that +inf infinitely precedes +inf and -inf infinitely + * follows -inf, and therefore that all finite and infinite values are in + * such a window frame. + * + * offset is known positive, so we need only check the sign of base in + * this test. + */ + if (isinf(offset) && isinf(base) && + (sub ? base > 0 : base < 0)) + PG_RETURN_BOOL(true); + + /* + * Otherwise it should be safe to compute base +/- offset. We trust the + * FPU to cope if an input is +/-inf or the true sum would overflow, and + * produce a suitably signed infinity, which will compare properly against + * val whether or not that's infinity. + */ + if (sub) + sum = base - offset; + else + sum = base + offset; + + if (less) + PG_RETURN_BOOL(val <= sum); + else + PG_RETURN_BOOL(val >= sum); +} + + +/* + * =================== + * CONVERSION ROUTINES + * =================== + */ + +/* + * ftod - converts a float4 number to a float8 number + */ +Datum +ftod(PG_FUNCTION_ARGS) +{ + float4 num = PG_GETARG_FLOAT4(0); + + PG_RETURN_FLOAT8((float8) num); +} + + +/* + * dtof - converts a float8 number to a float4 number + */ +Datum +dtof(PG_FUNCTION_ARGS) +{ + float8 num = PG_GETARG_FLOAT8(0); + float4 result; + + result = (float4) num; + if (unlikely(isinf(result)) && !isinf(num)) + float_overflow_error(); + if (unlikely(result == 0.0f) && num != 0.0) + float_underflow_error(); + + PG_RETURN_FLOAT4(result); +} + + +/* + * dtoi4 - converts a float8 number to an int4 number + */ +Datum +dtoi4(PG_FUNCTION_ARGS) +{ + float8 num = PG_GETARG_FLOAT8(0); + + /* + * Get rid of any fractional part in the input. This is so we don't fail + * on just-out-of-range values that would round into range. Note + * assumption that rint() will pass through a NaN or Inf unchanged. + */ + num = rint(num); + + /* Range check */ + if (unlikely(isnan(num) || !FLOAT8_FITS_IN_INT32(num))) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("integer out of range"))); + + PG_RETURN_INT32((int32) num); +} + + +/* + * dtoi2 - converts a float8 number to an int2 number + */ +Datum +dtoi2(PG_FUNCTION_ARGS) +{ + float8 num = PG_GETARG_FLOAT8(0); + + /* + * Get rid of any fractional part in the input. This is so we don't fail + * on just-out-of-range values that would round into range. Note + * assumption that rint() will pass through a NaN or Inf unchanged. + */ + num = rint(num); + + /* Range check */ + if (unlikely(isnan(num) || !FLOAT8_FITS_IN_INT16(num))) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("smallint out of range"))); + + PG_RETURN_INT16((int16) num); +} + + +/* + * i4tod - converts an int4 number to a float8 number + */ +Datum +i4tod(PG_FUNCTION_ARGS) +{ + int32 num = PG_GETARG_INT32(0); + + PG_RETURN_FLOAT8((float8) num); +} + + +/* + * i2tod - converts an int2 number to a float8 number + */ +Datum +i2tod(PG_FUNCTION_ARGS) +{ + int16 num = PG_GETARG_INT16(0); + + PG_RETURN_FLOAT8((float8) num); +} + + +/* + * ftoi4 - converts a float4 number to an int4 number + */ +Datum +ftoi4(PG_FUNCTION_ARGS) +{ + float4 num = PG_GETARG_FLOAT4(0); + + /* + * Get rid of any fractional part in the input. This is so we don't fail + * on just-out-of-range values that would round into range. Note + * assumption that rint() will pass through a NaN or Inf unchanged. + */ + num = rint(num); + + /* Range check */ + if (unlikely(isnan(num) || !FLOAT4_FITS_IN_INT32(num))) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("integer out of range"))); + + PG_RETURN_INT32((int32) num); +} + + +/* + * ftoi2 - converts a float4 number to an int2 number + */ +Datum +ftoi2(PG_FUNCTION_ARGS) +{ + float4 num = PG_GETARG_FLOAT4(0); + + /* + * Get rid of any fractional part in the input. This is so we don't fail + * on just-out-of-range values that would round into range. Note + * assumption that rint() will pass through a NaN or Inf unchanged. + */ + num = rint(num); + + /* Range check */ + if (unlikely(isnan(num) || !FLOAT4_FITS_IN_INT16(num))) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("smallint out of range"))); + + PG_RETURN_INT16((int16) num); +} + + +/* + * i4tof - converts an int4 number to a float4 number + */ +Datum +i4tof(PG_FUNCTION_ARGS) +{ + int32 num = PG_GETARG_INT32(0); + + PG_RETURN_FLOAT4((float4) num); +} + + +/* + * i2tof - converts an int2 number to a float4 number + */ +Datum +i2tof(PG_FUNCTION_ARGS) +{ + int16 num = PG_GETARG_INT16(0); + + PG_RETURN_FLOAT4((float4) num); +} + + +/* + * ======================= + * RANDOM FLOAT8 OPERATORS + * ======================= + */ + +/* + * dround - returns ROUND(arg1) + */ +Datum +dround(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + + PG_RETURN_FLOAT8(rint(arg1)); +} + +/* + * dceil - returns the smallest integer greater than or + * equal to the specified float + */ +Datum +dceil(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + + PG_RETURN_FLOAT8(ceil(arg1)); +} + +/* + * dfloor - returns the largest integer lesser than or + * equal to the specified float + */ +Datum +dfloor(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + + PG_RETURN_FLOAT8(floor(arg1)); +} + +/* + * dsign - returns -1 if the argument is less than 0, 0 + * if the argument is equal to 0, and 1 if the + * argument is greater than zero. + */ +Datum +dsign(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + float8 result; + + if (arg1 > 0) + result = 1.0; + else if (arg1 < 0) + result = -1.0; + else + result = 0.0; + + PG_RETURN_FLOAT8(result); +} + +/* + * dtrunc - returns truncation-towards-zero of arg1, + * arg1 >= 0 ... the greatest integer less + * than or equal to arg1 + * arg1 < 0 ... the least integer greater + * than or equal to arg1 + */ +Datum +dtrunc(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + float8 result; + + if (arg1 >= 0) + result = floor(arg1); + else + result = -floor(-arg1); + + PG_RETURN_FLOAT8(result); +} + + +/* + * dsqrt - returns square root of arg1 + */ +Datum +dsqrt(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + float8 result; + + if (arg1 < 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_ARGUMENT_FOR_POWER_FUNCTION), + errmsg("cannot take square root of a negative number"))); + + result = sqrt(arg1); + if (unlikely(isinf(result)) && !isinf(arg1)) + float_overflow_error(); + if (unlikely(result == 0.0) && arg1 != 0.0) + float_underflow_error(); + + PG_RETURN_FLOAT8(result); +} + + +/* + * dcbrt - returns cube root of arg1 + */ +Datum +dcbrt(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + float8 result; + + result = cbrt(arg1); + if (unlikely(isinf(result)) && !isinf(arg1)) + float_overflow_error(); + if (unlikely(result == 0.0) && arg1 != 0.0) + float_underflow_error(); + + PG_RETURN_FLOAT8(result); +} + + +/* + * dpow - returns pow(arg1,arg2) + */ +Datum +dpow(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + float8 arg2 = PG_GETARG_FLOAT8(1); + float8 result; + + /* + * The POSIX spec says that NaN ^ 0 = 1, and 1 ^ NaN = 1, while all other + * cases with NaN inputs yield NaN (with no error). Many older platforms + * get one or more of these cases wrong, so deal with them via explicit + * logic rather than trusting pow(3). + */ + if (isnan(arg1)) + { + if (isnan(arg2) || arg2 != 0.0) + PG_RETURN_FLOAT8(get_float8_nan()); + PG_RETURN_FLOAT8(1.0); + } + if (isnan(arg2)) + { + if (arg1 != 1.0) + PG_RETURN_FLOAT8(get_float8_nan()); + PG_RETURN_FLOAT8(1.0); + } + + /* + * The SQL spec requires that we emit a particular SQLSTATE error code for + * certain error conditions. Specifically, we don't return a + * divide-by-zero error code for 0 ^ -1. + */ + if (arg1 == 0 && arg2 < 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_ARGUMENT_FOR_POWER_FUNCTION), + errmsg("zero raised to a negative power is undefined"))); + if (arg1 < 0 && floor(arg2) != arg2) + ereport(ERROR, + (errcode(ERRCODE_INVALID_ARGUMENT_FOR_POWER_FUNCTION), + errmsg("a negative number raised to a non-integer power yields a complex result"))); + + /* + * We don't trust the platform's pow() to handle infinity cases per POSIX + * spec either, so deal with those explicitly too. It's easier to handle + * infinite y first, so that it doesn't matter if x is also infinite. + */ + if (isinf(arg2)) + { + float8 absx = fabs(arg1); + + if (absx == 1.0) + result = 1.0; + else if (arg2 > 0.0) /* y = +Inf */ + { + if (absx > 1.0) + result = arg2; + else + result = 0.0; + } + else /* y = -Inf */ + { + if (absx > 1.0) + result = 0.0; + else + result = -arg2; + } + } + else if (isinf(arg1)) + { + if (arg2 == 0.0) + result = 1.0; + else if (arg1 > 0.0) /* x = +Inf */ + { + if (arg2 > 0.0) + result = arg1; + else + result = 0.0; + } + else /* x = -Inf */ + { + /* + * Per POSIX, the sign of the result depends on whether y is an + * odd integer. Since x < 0, we already know from the previous + * domain check that y is an integer. It is odd if y/2 is not + * also an integer. + */ + float8 halfy = arg2 / 2; /* should be computed exactly */ + bool yisoddinteger = (floor(halfy) != halfy); + + if (arg2 > 0.0) + result = yisoddinteger ? arg1 : -arg1; + else + result = yisoddinteger ? -0.0 : 0.0; + } + } + else + { + /* + * pow() sets errno on only some platforms, depending on whether it + * follows _IEEE_, _POSIX_, _XOPEN_, or _SVID_, so we must check both + * errno and invalid output values. (We can't rely on just the + * latter, either; some old platforms return a large-but-finite + * HUGE_VAL when reporting overflow.) + */ + errno = 0; + result = pow(arg1, arg2); + if (errno == EDOM || isnan(result)) + { + /* + * We handled all possible domain errors above, so this should be + * impossible. However, old glibc versions on x86 have a bug that + * causes them to fail this way for abs(y) greater than 2^63: + * + * https://sourceware.org/bugzilla/show_bug.cgi?id=3866 + * + * Hence, if we get here, assume y is finite but large (large + * enough to be certainly even). The result should be 0 if x == 0, + * 1.0 if abs(x) == 1.0, otherwise an overflow or underflow error. + */ + if (arg1 == 0.0) + result = 0.0; /* we already verified y is positive */ + else + { + float8 absx = fabs(arg1); + + if (absx == 1.0) + result = 1.0; + else if (arg2 >= 0.0 ? (absx > 1.0) : (absx < 1.0)) + float_overflow_error(); + else + float_underflow_error(); + } + } + else if (errno == ERANGE) + { + if (result != 0.0) + float_overflow_error(); + else + float_underflow_error(); + } + else + { + if (unlikely(isinf(result))) + float_overflow_error(); + if (unlikely(result == 0.0) && arg1 != 0.0) + float_underflow_error(); + } + } + + PG_RETURN_FLOAT8(result); +} + + +/* + * dexp - returns the exponential function of arg1 + */ +Datum +dexp(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + float8 result; + + /* + * Handle NaN and Inf cases explicitly. This avoids needing to assume + * that the platform's exp() conforms to POSIX for these cases, and it + * removes some edge cases for the overflow checks below. + */ + if (isnan(arg1)) + result = arg1; + else if (isinf(arg1)) + { + /* Per POSIX, exp(-Inf) is 0 */ + result = (arg1 > 0.0) ? arg1 : 0; + } + else + { + /* + * On some platforms, exp() will not set errno but just return Inf or + * zero to report overflow/underflow; therefore, test both cases. + */ + errno = 0; + result = exp(arg1); + if (unlikely(errno == ERANGE)) + { + if (result != 0.0) + float_overflow_error(); + else + float_underflow_error(); + } + else if (unlikely(isinf(result))) + float_overflow_error(); + else if (unlikely(result == 0.0)) + float_underflow_error(); + } + + PG_RETURN_FLOAT8(result); +} + + +/* + * dlog1 - returns the natural logarithm of arg1 + */ +Datum +dlog1(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + float8 result; + + /* + * Emit particular SQLSTATE error codes for ln(). This is required by the + * SQL standard. + */ + if (arg1 == 0.0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_ARGUMENT_FOR_LOG), + errmsg("cannot take logarithm of zero"))); + if (arg1 < 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_ARGUMENT_FOR_LOG), + errmsg("cannot take logarithm of a negative number"))); + + result = log(arg1); + if (unlikely(isinf(result)) && !isinf(arg1)) + float_overflow_error(); + if (unlikely(result == 0.0) && arg1 != 1.0) + float_underflow_error(); + + PG_RETURN_FLOAT8(result); +} + + +/* + * dlog10 - returns the base 10 logarithm of arg1 + */ +Datum +dlog10(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + float8 result; + + /* + * Emit particular SQLSTATE error codes for log(). The SQL spec doesn't + * define log(), but it does define ln(), so it makes sense to emit the + * same error code for an analogous error condition. + */ + if (arg1 == 0.0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_ARGUMENT_FOR_LOG), + errmsg("cannot take logarithm of zero"))); + if (arg1 < 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_ARGUMENT_FOR_LOG), + errmsg("cannot take logarithm of a negative number"))); + + result = log10(arg1); + if (unlikely(isinf(result)) && !isinf(arg1)) + float_overflow_error(); + if (unlikely(result == 0.0) && arg1 != 1.0) + float_underflow_error(); + + PG_RETURN_FLOAT8(result); +} + + +/* + * dacos - returns the arccos of arg1 (radians) + */ +Datum +dacos(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + float8 result; + + /* Per the POSIX spec, return NaN if the input is NaN */ + if (isnan(arg1)) + PG_RETURN_FLOAT8(get_float8_nan()); + + /* + * The principal branch of the inverse cosine function maps values in the + * range [-1, 1] to values in the range [0, Pi], so we should reject any + * inputs outside that range and the result will always be finite. + */ + if (arg1 < -1.0 || arg1 > 1.0) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("input is out of range"))); + + result = acos(arg1); + if (unlikely(isinf(result))) + float_overflow_error(); + + PG_RETURN_FLOAT8(result); +} + + +/* + * dasin - returns the arcsin of arg1 (radians) + */ +Datum +dasin(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + float8 result; + + /* Per the POSIX spec, return NaN if the input is NaN */ + if (isnan(arg1)) + PG_RETURN_FLOAT8(get_float8_nan()); + + /* + * The principal branch of the inverse sine function maps values in the + * range [-1, 1] to values in the range [-Pi/2, Pi/2], so we should reject + * any inputs outside that range and the result will always be finite. + */ + if (arg1 < -1.0 || arg1 > 1.0) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("input is out of range"))); + + result = asin(arg1); + if (unlikely(isinf(result))) + float_overflow_error(); + + PG_RETURN_FLOAT8(result); +} + + +/* + * datan - returns the arctan of arg1 (radians) + */ +Datum +datan(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + float8 result; + + /* Per the POSIX spec, return NaN if the input is NaN */ + if (isnan(arg1)) + PG_RETURN_FLOAT8(get_float8_nan()); + + /* + * The principal branch of the inverse tangent function maps all inputs to + * values in the range [-Pi/2, Pi/2], so the result should always be + * finite, even if the input is infinite. + */ + result = atan(arg1); + if (unlikely(isinf(result))) + float_overflow_error(); + + PG_RETURN_FLOAT8(result); +} + + +/* + * atan2 - returns the arctan of arg1/arg2 (radians) + */ +Datum +datan2(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + float8 arg2 = PG_GETARG_FLOAT8(1); + float8 result; + + /* Per the POSIX spec, return NaN if either input is NaN */ + if (isnan(arg1) || isnan(arg2)) + PG_RETURN_FLOAT8(get_float8_nan()); + + /* + * atan2 maps all inputs to values in the range [-Pi, Pi], so the result + * should always be finite, even if the inputs are infinite. + */ + result = atan2(arg1, arg2); + if (unlikely(isinf(result))) + float_overflow_error(); + + PG_RETURN_FLOAT8(result); +} + + +/* + * dcos - returns the cosine of arg1 (radians) + */ +Datum +dcos(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + float8 result; + + /* Per the POSIX spec, return NaN if the input is NaN */ + if (isnan(arg1)) + PG_RETURN_FLOAT8(get_float8_nan()); + + /* + * cos() is periodic and so theoretically can work for all finite inputs, + * but some implementations may choose to throw error if the input is so + * large that there are no significant digits in the result. So we should + * check for errors. POSIX allows an error to be reported either via + * errno or via fetestexcept(), but currently we only support checking + * errno. (fetestexcept() is rumored to report underflow unreasonably + * early on some platforms, so it's not clear that believing it would be a + * net improvement anyway.) + * + * For infinite inputs, POSIX specifies that the trigonometric functions + * should return a domain error; but we won't notice that unless the + * platform reports via errno, so also explicitly test for infinite + * inputs. + */ + errno = 0; + result = cos(arg1); + if (errno != 0 || isinf(arg1)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("input is out of range"))); + if (unlikely(isinf(result))) + float_overflow_error(); + + PG_RETURN_FLOAT8(result); +} + + +/* + * dcot - returns the cotangent of arg1 (radians) + */ +Datum +dcot(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + float8 result; + + /* Per the POSIX spec, return NaN if the input is NaN */ + if (isnan(arg1)) + PG_RETURN_FLOAT8(get_float8_nan()); + + /* Be sure to throw an error if the input is infinite --- see dcos() */ + errno = 0; + result = tan(arg1); + if (errno != 0 || isinf(arg1)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("input is out of range"))); + + result = 1.0 / result; + /* Not checking for overflow because cot(0) == Inf */ + + PG_RETURN_FLOAT8(result); +} + + +/* + * dsin - returns the sine of arg1 (radians) + */ +Datum +dsin(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + float8 result; + + /* Per the POSIX spec, return NaN if the input is NaN */ + if (isnan(arg1)) + PG_RETURN_FLOAT8(get_float8_nan()); + + /* Be sure to throw an error if the input is infinite --- see dcos() */ + errno = 0; + result = sin(arg1); + if (errno != 0 || isinf(arg1)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("input is out of range"))); + if (unlikely(isinf(result))) + float_overflow_error(); + + PG_RETURN_FLOAT8(result); +} + + +/* + * dtan - returns the tangent of arg1 (radians) + */ +Datum +dtan(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + float8 result; + + /* Per the POSIX spec, return NaN if the input is NaN */ + if (isnan(arg1)) + PG_RETURN_FLOAT8(get_float8_nan()); + + /* Be sure to throw an error if the input is infinite --- see dcos() */ + errno = 0; + result = tan(arg1); + if (errno != 0 || isinf(arg1)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("input is out of range"))); + /* Not checking for overflow because tan(pi/2) == Inf */ + + PG_RETURN_FLOAT8(result); +} + + +/* ========== DEGREE-BASED TRIGONOMETRIC FUNCTIONS ========== */ + + +/* + * Initialize the cached constants declared at the head of this file + * (sin_30 etc). The fact that we need those at all, let alone need this + * Rube-Goldberg-worthy method of initializing them, is because there are + * compilers out there that will precompute expressions such as sin(constant) + * using a sin() function different from what will be used at runtime. If we + * want exact results, we must ensure that none of the scaling constants used + * in the degree-based trig functions are computed that way. To do so, we + * compute them from the variables degree_c_thirty etc, which are also really + * constants, but the compiler cannot assume that. + * + * Other hazards we are trying to forestall with this kluge include the + * possibility that compilers will rearrange the expressions, or compute + * some intermediate results in registers wider than a standard double. + * + * In the places where we use these constants, the typical pattern is like + * volatile float8 sin_x = sin(x * RADIANS_PER_DEGREE); + * return (sin_x / sin_30); + * where we hope to get a value of exactly 1.0 from the division when x = 30. + * The volatile temporary variable is needed on machines with wide float + * registers, to ensure that the result of sin(x) is rounded to double width + * the same as the value of sin_30 has been. Experimentation with gcc shows + * that marking the temp variable volatile is necessary to make the store and + * reload actually happen; hopefully the same trick works for other compilers. + * (gcc's documentation suggests using the -ffloat-store compiler switch to + * ensure this, but that is compiler-specific and it also pessimizes code in + * many places where we don't care about this.) + */ +static void +init_degree_constants(void) +{ + sin_30 = sin(degree_c_thirty * RADIANS_PER_DEGREE); + one_minus_cos_60 = 1.0 - cos(degree_c_sixty * RADIANS_PER_DEGREE); + asin_0_5 = asin(degree_c_one_half); + acos_0_5 = acos(degree_c_one_half); + atan_1_0 = atan(degree_c_one); + tan_45 = sind_q1(degree_c_forty_five) / cosd_q1(degree_c_forty_five); + cot_45 = cosd_q1(degree_c_forty_five) / sind_q1(degree_c_forty_five); + degree_consts_set = true; +} + +#define INIT_DEGREE_CONSTANTS() \ +do { \ + if (!degree_consts_set) \ + init_degree_constants(); \ +} while(0) + + +/* + * asind_q1 - returns the inverse sine of x in degrees, for x in + * the range [0, 1]. The result is an angle in the + * first quadrant --- [0, 90] degrees. + * + * For the 3 special case inputs (0, 0.5 and 1), this + * function will return exact values (0, 30 and 90 + * degrees respectively). + */ +static double +asind_q1(double x) +{ + /* + * Stitch together inverse sine and cosine functions for the ranges [0, + * 0.5] and (0.5, 1]. Each expression below is guaranteed to return + * exactly 30 for x=0.5, so the result is a continuous monotonic function + * over the full range. + */ + if (x <= 0.5) + { + volatile float8 asin_x = asin(x); + + return (asin_x / asin_0_5) * 30.0; + } + else + { + volatile float8 acos_x = acos(x); + + return 90.0 - (acos_x / acos_0_5) * 60.0; + } +} + + +/* + * acosd_q1 - returns the inverse cosine of x in degrees, for x in + * the range [0, 1]. The result is an angle in the + * first quadrant --- [0, 90] degrees. + * + * For the 3 special case inputs (0, 0.5 and 1), this + * function will return exact values (0, 60 and 90 + * degrees respectively). + */ +static double +acosd_q1(double x) +{ + /* + * Stitch together inverse sine and cosine functions for the ranges [0, + * 0.5] and (0.5, 1]. Each expression below is guaranteed to return + * exactly 60 for x=0.5, so the result is a continuous monotonic function + * over the full range. + */ + if (x <= 0.5) + { + volatile float8 asin_x = asin(x); + + return 90.0 - (asin_x / asin_0_5) * 30.0; + } + else + { + volatile float8 acos_x = acos(x); + + return (acos_x / acos_0_5) * 60.0; + } +} + + +/* + * dacosd - returns the arccos of arg1 (degrees) + */ +Datum +dacosd(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + float8 result; + + /* Per the POSIX spec, return NaN if the input is NaN */ + if (isnan(arg1)) + PG_RETURN_FLOAT8(get_float8_nan()); + + INIT_DEGREE_CONSTANTS(); + + /* + * The principal branch of the inverse cosine function maps values in the + * range [-1, 1] to values in the range [0, 180], so we should reject any + * inputs outside that range and the result will always be finite. + */ + if (arg1 < -1.0 || arg1 > 1.0) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("input is out of range"))); + + if (arg1 >= 0.0) + result = acosd_q1(arg1); + else + result = 90.0 + asind_q1(-arg1); + + if (unlikely(isinf(result))) + float_overflow_error(); + + PG_RETURN_FLOAT8(result); +} + + +/* + * dasind - returns the arcsin of arg1 (degrees) + */ +Datum +dasind(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + float8 result; + + /* Per the POSIX spec, return NaN if the input is NaN */ + if (isnan(arg1)) + PG_RETURN_FLOAT8(get_float8_nan()); + + INIT_DEGREE_CONSTANTS(); + + /* + * The principal branch of the inverse sine function maps values in the + * range [-1, 1] to values in the range [-90, 90], so we should reject any + * inputs outside that range and the result will always be finite. + */ + if (arg1 < -1.0 || arg1 > 1.0) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("input is out of range"))); + + if (arg1 >= 0.0) + result = asind_q1(arg1); + else + result = -asind_q1(-arg1); + + if (unlikely(isinf(result))) + float_overflow_error(); + + PG_RETURN_FLOAT8(result); +} + + +/* + * datand - returns the arctan of arg1 (degrees) + */ +Datum +datand(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + float8 result; + volatile float8 atan_arg1; + + /* Per the POSIX spec, return NaN if the input is NaN */ + if (isnan(arg1)) + PG_RETURN_FLOAT8(get_float8_nan()); + + INIT_DEGREE_CONSTANTS(); + + /* + * The principal branch of the inverse tangent function maps all inputs to + * values in the range [-90, 90], so the result should always be finite, + * even if the input is infinite. Additionally, we take care to ensure + * than when arg1 is 1, the result is exactly 45. + */ + atan_arg1 = atan(arg1); + result = (atan_arg1 / atan_1_0) * 45.0; + + if (unlikely(isinf(result))) + float_overflow_error(); + + PG_RETURN_FLOAT8(result); +} + + +/* + * atan2d - returns the arctan of arg1/arg2 (degrees) + */ +Datum +datan2d(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + float8 arg2 = PG_GETARG_FLOAT8(1); + float8 result; + volatile float8 atan2_arg1_arg2; + + /* Per the POSIX spec, return NaN if either input is NaN */ + if (isnan(arg1) || isnan(arg2)) + PG_RETURN_FLOAT8(get_float8_nan()); + + INIT_DEGREE_CONSTANTS(); + + /* + * atan2d maps all inputs to values in the range [-180, 180], so the + * result should always be finite, even if the inputs are infinite. + * + * Note: this coding assumes that atan(1.0) is a suitable scaling constant + * to get an exact result from atan2(). This might well fail on us at + * some point, requiring us to decide exactly what inputs we think we're + * going to guarantee an exact result for. + */ + atan2_arg1_arg2 = atan2(arg1, arg2); + result = (atan2_arg1_arg2 / atan_1_0) * 45.0; + + if (unlikely(isinf(result))) + float_overflow_error(); + + PG_RETURN_FLOAT8(result); +} + + +/* + * sind_0_to_30 - returns the sine of an angle that lies between 0 and + * 30 degrees. This will return exactly 0 when x is 0, + * and exactly 0.5 when x is 30 degrees. + */ +static double +sind_0_to_30(double x) +{ + volatile float8 sin_x = sin(x * RADIANS_PER_DEGREE); + + return (sin_x / sin_30) / 2.0; +} + + +/* + * cosd_0_to_60 - returns the cosine of an angle that lies between 0 + * and 60 degrees. This will return exactly 1 when x + * is 0, and exactly 0.5 when x is 60 degrees. + */ +static double +cosd_0_to_60(double x) +{ + volatile float8 one_minus_cos_x = 1.0 - cos(x * RADIANS_PER_DEGREE); + + return 1.0 - (one_minus_cos_x / one_minus_cos_60) / 2.0; +} + + +/* + * sind_q1 - returns the sine of an angle in the first quadrant + * (0 to 90 degrees). + */ +static double +sind_q1(double x) +{ + /* + * Stitch together the sine and cosine functions for the ranges [0, 30] + * and (30, 90]. These guarantee to return exact answers at their + * endpoints, so the overall result is a continuous monotonic function + * that gives exact results when x = 0, 30 and 90 degrees. + */ + if (x <= 30.0) + return sind_0_to_30(x); + else + return cosd_0_to_60(90.0 - x); +} + + +/* + * cosd_q1 - returns the cosine of an angle in the first quadrant + * (0 to 90 degrees). + */ +static double +cosd_q1(double x) +{ + /* + * Stitch together the sine and cosine functions for the ranges [0, 60] + * and (60, 90]. These guarantee to return exact answers at their + * endpoints, so the overall result is a continuous monotonic function + * that gives exact results when x = 0, 60 and 90 degrees. + */ + if (x <= 60.0) + return cosd_0_to_60(x); + else + return sind_0_to_30(90.0 - x); +} + + +/* + * dcosd - returns the cosine of arg1 (degrees) + */ +Datum +dcosd(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + float8 result; + int sign = 1; + + /* + * Per the POSIX spec, return NaN if the input is NaN and throw an error + * if the input is infinite. + */ + if (isnan(arg1)) + PG_RETURN_FLOAT8(get_float8_nan()); + + if (isinf(arg1)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("input is out of range"))); + + INIT_DEGREE_CONSTANTS(); + + /* Reduce the range of the input to [0,90] degrees */ + arg1 = fmod(arg1, 360.0); + + if (arg1 < 0.0) + { + /* cosd(-x) = cosd(x) */ + arg1 = -arg1; + } + + if (arg1 > 180.0) + { + /* cosd(360-x) = cosd(x) */ + arg1 = 360.0 - arg1; + } + + if (arg1 > 90.0) + { + /* cosd(180-x) = -cosd(x) */ + arg1 = 180.0 - arg1; + sign = -sign; + } + + result = sign * cosd_q1(arg1); + + if (unlikely(isinf(result))) + float_overflow_error(); + + PG_RETURN_FLOAT8(result); +} + + +/* + * dcotd - returns the cotangent of arg1 (degrees) + */ +Datum +dcotd(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + float8 result; + volatile float8 cot_arg1; + int sign = 1; + + /* + * Per the POSIX spec, return NaN if the input is NaN and throw an error + * if the input is infinite. + */ + if (isnan(arg1)) + PG_RETURN_FLOAT8(get_float8_nan()); + + if (isinf(arg1)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("input is out of range"))); + + INIT_DEGREE_CONSTANTS(); + + /* Reduce the range of the input to [0,90] degrees */ + arg1 = fmod(arg1, 360.0); + + if (arg1 < 0.0) + { + /* cotd(-x) = -cotd(x) */ + arg1 = -arg1; + sign = -sign; + } + + if (arg1 > 180.0) + { + /* cotd(360-x) = -cotd(x) */ + arg1 = 360.0 - arg1; + sign = -sign; + } + + if (arg1 > 90.0) + { + /* cotd(180-x) = -cotd(x) */ + arg1 = 180.0 - arg1; + sign = -sign; + } + + cot_arg1 = cosd_q1(arg1) / sind_q1(arg1); + result = sign * (cot_arg1 / cot_45); + + /* + * On some machines we get cotd(270) = minus zero, but this isn't always + * true. For portability, and because the user constituency for this + * function probably doesn't want minus zero, force it to plain zero. + */ + if (result == 0.0) + result = 0.0; + + /* Not checking for overflow because cotd(0) == Inf */ + + PG_RETURN_FLOAT8(result); +} + + +/* + * dsind - returns the sine of arg1 (degrees) + */ +Datum +dsind(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + float8 result; + int sign = 1; + + /* + * Per the POSIX spec, return NaN if the input is NaN and throw an error + * if the input is infinite. + */ + if (isnan(arg1)) + PG_RETURN_FLOAT8(get_float8_nan()); + + if (isinf(arg1)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("input is out of range"))); + + INIT_DEGREE_CONSTANTS(); + + /* Reduce the range of the input to [0,90] degrees */ + arg1 = fmod(arg1, 360.0); + + if (arg1 < 0.0) + { + /* sind(-x) = -sind(x) */ + arg1 = -arg1; + sign = -sign; + } + + if (arg1 > 180.0) + { + /* sind(360-x) = -sind(x) */ + arg1 = 360.0 - arg1; + sign = -sign; + } + + if (arg1 > 90.0) + { + /* sind(180-x) = sind(x) */ + arg1 = 180.0 - arg1; + } + + result = sign * sind_q1(arg1); + + if (unlikely(isinf(result))) + float_overflow_error(); + + PG_RETURN_FLOAT8(result); +} + + +/* + * dtand - returns the tangent of arg1 (degrees) + */ +Datum +dtand(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + float8 result; + volatile float8 tan_arg1; + int sign = 1; + + /* + * Per the POSIX spec, return NaN if the input is NaN and throw an error + * if the input is infinite. + */ + if (isnan(arg1)) + PG_RETURN_FLOAT8(get_float8_nan()); + + if (isinf(arg1)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("input is out of range"))); + + INIT_DEGREE_CONSTANTS(); + + /* Reduce the range of the input to [0,90] degrees */ + arg1 = fmod(arg1, 360.0); + + if (arg1 < 0.0) + { + /* tand(-x) = -tand(x) */ + arg1 = -arg1; + sign = -sign; + } + + if (arg1 > 180.0) + { + /* tand(360-x) = -tand(x) */ + arg1 = 360.0 - arg1; + sign = -sign; + } + + if (arg1 > 90.0) + { + /* tand(180-x) = -tand(x) */ + arg1 = 180.0 - arg1; + sign = -sign; + } + + tan_arg1 = sind_q1(arg1) / cosd_q1(arg1); + result = sign * (tan_arg1 / tan_45); + + /* + * On some machines we get tand(180) = minus zero, but this isn't always + * true. For portability, and because the user constituency for this + * function probably doesn't want minus zero, force it to plain zero. + */ + if (result == 0.0) + result = 0.0; + + /* Not checking for overflow because tand(90) == Inf */ + + PG_RETURN_FLOAT8(result); +} + + +/* + * degrees - returns degrees converted from radians + */ +Datum +degrees(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + + PG_RETURN_FLOAT8(float8_div(arg1, RADIANS_PER_DEGREE)); +} + + +/* + * dpi - returns the constant PI + */ +Datum +dpi(PG_FUNCTION_ARGS) +{ + PG_RETURN_FLOAT8(M_PI); +} + + +/* + * radians - returns radians converted from degrees + */ +Datum +radians(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + + PG_RETURN_FLOAT8(float8_mul(arg1, RADIANS_PER_DEGREE)); +} + + +/* ========== HYPERBOLIC FUNCTIONS ========== */ + + +/* + * dsinh - returns the hyperbolic sine of arg1 + */ +Datum +dsinh(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + float8 result; + + errno = 0; + result = sinh(arg1); + + /* + * if an ERANGE error occurs, it means there is an overflow. For sinh, + * the result should be either -infinity or infinity, depending on the + * sign of arg1. + */ + if (errno == ERANGE) + { + if (arg1 < 0) + result = -get_float8_infinity(); + else + result = get_float8_infinity(); + } + + PG_RETURN_FLOAT8(result); +} + + +/* + * dcosh - returns the hyperbolic cosine of arg1 + */ +Datum +dcosh(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + float8 result; + + errno = 0; + result = cosh(arg1); + + /* + * if an ERANGE error occurs, it means there is an overflow. As cosh is + * always positive, it always means the result is positive infinity. + */ + if (errno == ERANGE) + result = get_float8_infinity(); + + if (unlikely(result == 0.0)) + float_underflow_error(); + + PG_RETURN_FLOAT8(result); +} + +/* + * dtanh - returns the hyperbolic tangent of arg1 + */ +Datum +dtanh(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + float8 result; + + /* + * For tanh, we don't need an errno check because it never overflows. + */ + result = tanh(arg1); + + if (unlikely(isinf(result))) + float_overflow_error(); + + PG_RETURN_FLOAT8(result); +} + +/* + * dasinh - returns the inverse hyperbolic sine of arg1 + */ +Datum +dasinh(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + float8 result; + + /* + * For asinh, we don't need an errno check because it never overflows. + */ + result = asinh(arg1); + + PG_RETURN_FLOAT8(result); +} + +/* + * dacosh - returns the inverse hyperbolic cosine of arg1 + */ +Datum +dacosh(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + float8 result; + + /* + * acosh is only defined for inputs >= 1.0. By checking this ourselves, + * we need not worry about checking for an EDOM error, which is a good + * thing because some implementations will report that for NaN. Otherwise, + * no error is possible. + */ + if (arg1 < 1.0) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("input is out of range"))); + + result = acosh(arg1); + + PG_RETURN_FLOAT8(result); +} + +/* + * datanh - returns the inverse hyperbolic tangent of arg1 + */ +Datum +datanh(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + float8 result; + + /* + * atanh is only defined for inputs between -1 and 1. By checking this + * ourselves, we need not worry about checking for an EDOM error, which is + * a good thing because some implementations will report that for NaN. + */ + if (arg1 < -1.0 || arg1 > 1.0) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("input is out of range"))); + + /* + * Also handle the infinity cases ourselves; this is helpful because old + * glibc versions may produce the wrong errno for this. All other inputs + * cannot produce an error. + */ + if (arg1 == -1.0) + result = -get_float8_infinity(); + else if (arg1 == 1.0) + result = get_float8_infinity(); + else + result = atanh(arg1); + + PG_RETURN_FLOAT8(result); +} + + +/* ========== ERROR FUNCTIONS ========== */ + + +/* + * derf - returns the error function: erf(arg1) + */ +Datum +derf(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + float8 result; + + /* + * For erf, we don't need an errno check because it never overflows. + */ + result = erf(arg1); + + if (unlikely(isinf(result))) + float_overflow_error(); + + PG_RETURN_FLOAT8(result); +} + +/* + * derfc - returns the complementary error function: 1 - erf(arg1) + */ +Datum +derfc(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + float8 result; + + /* + * For erfc, we don't need an errno check because it never overflows. + */ + result = erfc(arg1); + + if (unlikely(isinf(result))) + float_overflow_error(); + + PG_RETURN_FLOAT8(result); +} + + +/* ========== RANDOM FUNCTIONS ========== */ + + +/* + * initialize_drandom_seed - initialize drandom_seed if not yet done + */ +static void +initialize_drandom_seed(void) +{ + /* Initialize random seed, if not done yet in this process */ + if (unlikely(!drandom_seed_set)) + { + /* + * If possible, initialize the seed using high-quality random bits. + * Should that fail for some reason, we fall back on a lower-quality + * seed based on current time and PID. + */ + if (unlikely(!pg_prng_strong_seed(&drandom_seed))) + { + TimestampTz now = GetCurrentTimestamp(); + uint64 iseed; + + /* Mix the PID with the most predictable bits of the timestamp */ + iseed = (uint64) now ^ ((uint64) MyProcPid << 32); + pg_prng_seed(&drandom_seed, iseed); + } + drandom_seed_set = true; + } +} + +/* + * drandom - returns a random number + */ +Datum +drandom(PG_FUNCTION_ARGS) +{ + float8 result; + + initialize_drandom_seed(); + + /* pg_prng_double produces desired result range [0.0 - 1.0) */ + result = pg_prng_double(&drandom_seed); + + PG_RETURN_FLOAT8(result); +} + +/* + * drandom_normal - returns a random number from a normal distribution + */ +Datum +drandom_normal(PG_FUNCTION_ARGS) +{ + float8 mean = PG_GETARG_FLOAT8(0); + float8 stddev = PG_GETARG_FLOAT8(1); + float8 result, + z; + + initialize_drandom_seed(); + + /* Get random value from standard normal(mean = 0.0, stddev = 1.0) */ + z = pg_prng_double_normal(&drandom_seed); + /* Transform the normal standard variable (z) */ + /* using the target normal distribution parameters */ + result = (stddev * z) + mean; + + PG_RETURN_FLOAT8(result); +} + +/* + * setseed - set seed for the random number generator + */ +Datum +setseed(PG_FUNCTION_ARGS) +{ + float8 seed = PG_GETARG_FLOAT8(0); + + if (seed < -1 || seed > 1 || isnan(seed)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("setseed parameter %g is out of allowed range [-1,1]", + seed))); + + pg_prng_fseed(&drandom_seed, seed); + drandom_seed_set = true; + + PG_RETURN_VOID(); +} + + + +/* + * ========================= + * FLOAT AGGREGATE OPERATORS + * ========================= + * + * float8_accum - accumulate for AVG(), variance aggregates, etc. + * float4_accum - same, but input data is float4 + * float8_avg - produce final result for float AVG() + * float8_var_samp - produce final result for float VAR_SAMP() + * float8_var_pop - produce final result for float VAR_POP() + * float8_stddev_samp - produce final result for float STDDEV_SAMP() + * float8_stddev_pop - produce final result for float STDDEV_POP() + * + * The naive schoolbook implementation of these aggregates works by + * accumulating sum(X) and sum(X^2). However, this approach suffers from + * large rounding errors in the final computation of quantities like the + * population variance (N*sum(X^2) - sum(X)^2) / N^2, since each of the + * intermediate terms is potentially very large, while the difference is often + * quite small. + * + * Instead we use the Youngs-Cramer algorithm [1] which works by accumulating + * Sx=sum(X) and Sxx=sum((X-Sx/N)^2), using a numerically stable algorithm to + * incrementally update those quantities. The final computations of each of + * the aggregate values is then trivial and gives more accurate results (for + * example, the population variance is just Sxx/N). This algorithm is also + * fairly easy to generalize to allow parallel execution without loss of + * precision (see, for example, [2]). For more details, and a comparison of + * this with other algorithms, see [3]. + * + * The transition datatype for all these aggregates is a 3-element array + * of float8, holding the values N, Sx, Sxx in that order. + * + * Note that we represent N as a float to avoid having to build a special + * datatype. Given a reasonable floating-point implementation, there should + * be no accuracy loss unless N exceeds 2 ^ 52 or so (by which time the + * user will have doubtless lost interest anyway...) + * + * [1] Some Results Relevant to Choice of Sum and Sum-of-Product Algorithms, + * E. A. Youngs and E. M. Cramer, Technometrics Vol 13, No 3, August 1971. + * + * [2] Updating Formulae and a Pairwise Algorithm for Computing Sample + * Variances, T. F. Chan, G. H. Golub & R. J. LeVeque, COMPSTAT 1982. + * + * [3] Numerically Stable Parallel Computation of (Co-)Variance, Erich + * Schubert and Michael Gertz, Proceedings of the 30th International + * Conference on Scientific and Statistical Database Management, 2018. + */ + +static float8 * +check_float8_array(ArrayType *transarray, const char *caller, int n) +{ + /* + * We expect the input to be an N-element float array; verify that. We + * don't need to use deconstruct_array() since the array data is just + * going to look like a C array of N float8 values. + */ + if (ARR_NDIM(transarray) != 1 || + ARR_DIMS(transarray)[0] != n || + ARR_HASNULL(transarray) || + ARR_ELEMTYPE(transarray) != FLOAT8OID) + elog(ERROR, "%s: expected %d-element float8 array", caller, n); + return (float8 *) ARR_DATA_PTR(transarray); +} + +/* + * float8_combine + * + * An aggregate combine function used to combine two 3 fields + * aggregate transition data into a single transition data. + * This function is used only in two stage aggregation and + * shouldn't be called outside aggregate context. + */ +Datum +float8_combine(PG_FUNCTION_ARGS) +{ + ArrayType *transarray1 = PG_GETARG_ARRAYTYPE_P(0); + ArrayType *transarray2 = PG_GETARG_ARRAYTYPE_P(1); + float8 *transvalues1; + float8 *transvalues2; + float8 N1, + Sx1, + Sxx1, + N2, + Sx2, + Sxx2, + tmp, + N, + Sx, + Sxx; + + transvalues1 = check_float8_array(transarray1, "float8_combine", 3); + transvalues2 = check_float8_array(transarray2, "float8_combine", 3); + + N1 = transvalues1[0]; + Sx1 = transvalues1[1]; + Sxx1 = transvalues1[2]; + + N2 = transvalues2[0]; + Sx2 = transvalues2[1]; + Sxx2 = transvalues2[2]; + + /*-------------------- + * The transition values combine using a generalization of the + * Youngs-Cramer algorithm as follows: + * + * N = N1 + N2 + * Sx = Sx1 + Sx2 + * Sxx = Sxx1 + Sxx2 + N1 * N2 * (Sx1/N1 - Sx2/N2)^2 / N; + * + * It's worth handling the special cases N1 = 0 and N2 = 0 separately + * since those cases are trivial, and we then don't need to worry about + * division-by-zero errors in the general case. + *-------------------- + */ + if (N1 == 0.0) + { + N = N2; + Sx = Sx2; + Sxx = Sxx2; + } + else if (N2 == 0.0) + { + N = N1; + Sx = Sx1; + Sxx = Sxx1; + } + else + { + N = N1 + N2; + Sx = float8_pl(Sx1, Sx2); + tmp = Sx1 / N1 - Sx2 / N2; + Sxx = Sxx1 + Sxx2 + N1 * N2 * tmp * tmp / N; + if (unlikely(isinf(Sxx)) && !isinf(Sxx1) && !isinf(Sxx2)) + float_overflow_error(); + } + + /* + * If we're invoked as an aggregate, we can cheat and modify our first + * parameter in-place to reduce palloc overhead. Otherwise we construct a + * new array with the updated transition data and return it. + */ + if (AggCheckCallContext(fcinfo, NULL)) + { + transvalues1[0] = N; + transvalues1[1] = Sx; + transvalues1[2] = Sxx; + + PG_RETURN_ARRAYTYPE_P(transarray1); + } + else + { + Datum transdatums[3]; + ArrayType *result; + + transdatums[0] = Float8GetDatumFast(N); + transdatums[1] = Float8GetDatumFast(Sx); + transdatums[2] = Float8GetDatumFast(Sxx); + + result = construct_array(transdatums, 3, + FLOAT8OID, + sizeof(float8), FLOAT8PASSBYVAL, TYPALIGN_DOUBLE); + + PG_RETURN_ARRAYTYPE_P(result); + } +} + +Datum +float8_accum(PG_FUNCTION_ARGS) +{ + ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0); + float8 newval = PG_GETARG_FLOAT8(1); + float8 *transvalues; + float8 N, + Sx, + Sxx, + tmp; + + transvalues = check_float8_array(transarray, "float8_accum", 3); + N = transvalues[0]; + Sx = transvalues[1]; + Sxx = transvalues[2]; + + /* + * Use the Youngs-Cramer algorithm to incorporate the new value into the + * transition values. + */ + N += 1.0; + Sx += newval; + if (transvalues[0] > 0.0) + { + tmp = newval * N - Sx; + Sxx += tmp * tmp / (N * transvalues[0]); + + /* + * Overflow check. We only report an overflow error when finite + * inputs lead to infinite results. Note also that Sxx should be NaN + * if any of the inputs are infinite, so we intentionally prevent Sxx + * from becoming infinite. + */ + if (isinf(Sx) || isinf(Sxx)) + { + if (!isinf(transvalues[1]) && !isinf(newval)) + float_overflow_error(); + + Sxx = get_float8_nan(); + } + } + else + { + /* + * At the first input, we normally can leave Sxx as 0. However, if + * the first input is Inf or NaN, we'd better force Sxx to NaN; + * otherwise we will falsely report variance zero when there are no + * more inputs. + */ + if (isnan(newval) || isinf(newval)) + Sxx = get_float8_nan(); + } + + /* + * If we're invoked as an aggregate, we can cheat and modify our first + * parameter in-place to reduce palloc overhead. Otherwise we construct a + * new array with the updated transition data and return it. + */ + if (AggCheckCallContext(fcinfo, NULL)) + { + transvalues[0] = N; + transvalues[1] = Sx; + transvalues[2] = Sxx; + + PG_RETURN_ARRAYTYPE_P(transarray); + } + else + { + Datum transdatums[3]; + ArrayType *result; + + transdatums[0] = Float8GetDatumFast(N); + transdatums[1] = Float8GetDatumFast(Sx); + transdatums[2] = Float8GetDatumFast(Sxx); + + result = construct_array(transdatums, 3, + FLOAT8OID, + sizeof(float8), FLOAT8PASSBYVAL, TYPALIGN_DOUBLE); + + PG_RETURN_ARRAYTYPE_P(result); + } +} + +Datum +float4_accum(PG_FUNCTION_ARGS) +{ + ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0); + + /* do computations as float8 */ + float8 newval = PG_GETARG_FLOAT4(1); + float8 *transvalues; + float8 N, + Sx, + Sxx, + tmp; + + transvalues = check_float8_array(transarray, "float4_accum", 3); + N = transvalues[0]; + Sx = transvalues[1]; + Sxx = transvalues[2]; + + /* + * Use the Youngs-Cramer algorithm to incorporate the new value into the + * transition values. + */ + N += 1.0; + Sx += newval; + if (transvalues[0] > 0.0) + { + tmp = newval * N - Sx; + Sxx += tmp * tmp / (N * transvalues[0]); + + /* + * Overflow check. We only report an overflow error when finite + * inputs lead to infinite results. Note also that Sxx should be NaN + * if any of the inputs are infinite, so we intentionally prevent Sxx + * from becoming infinite. + */ + if (isinf(Sx) || isinf(Sxx)) + { + if (!isinf(transvalues[1]) && !isinf(newval)) + float_overflow_error(); + + Sxx = get_float8_nan(); + } + } + else + { + /* + * At the first input, we normally can leave Sxx as 0. However, if + * the first input is Inf or NaN, we'd better force Sxx to NaN; + * otherwise we will falsely report variance zero when there are no + * more inputs. + */ + if (isnan(newval) || isinf(newval)) + Sxx = get_float8_nan(); + } + + /* + * If we're invoked as an aggregate, we can cheat and modify our first + * parameter in-place to reduce palloc overhead. Otherwise we construct a + * new array with the updated transition data and return it. + */ + if (AggCheckCallContext(fcinfo, NULL)) + { + transvalues[0] = N; + transvalues[1] = Sx; + transvalues[2] = Sxx; + + PG_RETURN_ARRAYTYPE_P(transarray); + } + else + { + Datum transdatums[3]; + ArrayType *result; + + transdatums[0] = Float8GetDatumFast(N); + transdatums[1] = Float8GetDatumFast(Sx); + transdatums[2] = Float8GetDatumFast(Sxx); + + result = construct_array(transdatums, 3, + FLOAT8OID, + sizeof(float8), FLOAT8PASSBYVAL, TYPALIGN_DOUBLE); + + PG_RETURN_ARRAYTYPE_P(result); + } +} + +Datum +float8_avg(PG_FUNCTION_ARGS) +{ + ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0); + float8 *transvalues; + float8 N, + Sx; + + transvalues = check_float8_array(transarray, "float8_avg", 3); + N = transvalues[0]; + Sx = transvalues[1]; + /* ignore Sxx */ + + /* SQL defines AVG of no values to be NULL */ + if (N == 0.0) + PG_RETURN_NULL(); + + PG_RETURN_FLOAT8(Sx / N); +} + +Datum +float8_var_pop(PG_FUNCTION_ARGS) +{ + ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0); + float8 *transvalues; + float8 N, + Sxx; + + transvalues = check_float8_array(transarray, "float8_var_pop", 3); + N = transvalues[0]; + /* ignore Sx */ + Sxx = transvalues[2]; + + /* Population variance is undefined when N is 0, so return NULL */ + if (N == 0.0) + PG_RETURN_NULL(); + + /* Note that Sxx is guaranteed to be non-negative */ + + PG_RETURN_FLOAT8(Sxx / N); +} + +Datum +float8_var_samp(PG_FUNCTION_ARGS) +{ + ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0); + float8 *transvalues; + float8 N, + Sxx; + + transvalues = check_float8_array(transarray, "float8_var_samp", 3); + N = transvalues[0]; + /* ignore Sx */ + Sxx = transvalues[2]; + + /* Sample variance is undefined when N is 0 or 1, so return NULL */ + if (N <= 1.0) + PG_RETURN_NULL(); + + /* Note that Sxx is guaranteed to be non-negative */ + + PG_RETURN_FLOAT8(Sxx / (N - 1.0)); +} + +Datum +float8_stddev_pop(PG_FUNCTION_ARGS) +{ + ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0); + float8 *transvalues; + float8 N, + Sxx; + + transvalues = check_float8_array(transarray, "float8_stddev_pop", 3); + N = transvalues[0]; + /* ignore Sx */ + Sxx = transvalues[2]; + + /* Population stddev is undefined when N is 0, so return NULL */ + if (N == 0.0) + PG_RETURN_NULL(); + + /* Note that Sxx is guaranteed to be non-negative */ + + PG_RETURN_FLOAT8(sqrt(Sxx / N)); +} + +Datum +float8_stddev_samp(PG_FUNCTION_ARGS) +{ + ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0); + float8 *transvalues; + float8 N, + Sxx; + + transvalues = check_float8_array(transarray, "float8_stddev_samp", 3); + N = transvalues[0]; + /* ignore Sx */ + Sxx = transvalues[2]; + + /* Sample stddev is undefined when N is 0 or 1, so return NULL */ + if (N <= 1.0) + PG_RETURN_NULL(); + + /* Note that Sxx is guaranteed to be non-negative */ + + PG_RETURN_FLOAT8(sqrt(Sxx / (N - 1.0))); +} + +/* + * ========================= + * SQL2003 BINARY AGGREGATES + * ========================= + * + * As with the preceding aggregates, we use the Youngs-Cramer algorithm to + * reduce rounding errors in the aggregate final functions. + * + * The transition datatype for all these aggregates is a 6-element array of + * float8, holding the values N, Sx=sum(X), Sxx=sum((X-Sx/N)^2), Sy=sum(Y), + * Syy=sum((Y-Sy/N)^2), Sxy=sum((X-Sx/N)*(Y-Sy/N)) in that order. + * + * Note that Y is the first argument to all these aggregates! + * + * It might seem attractive to optimize this by having multiple accumulator + * functions that only calculate the sums actually needed. But on most + * modern machines, a couple of extra floating-point multiplies will be + * insignificant compared to the other per-tuple overhead, so I've chosen + * to minimize code space instead. + */ + +Datum +float8_regr_accum(PG_FUNCTION_ARGS) +{ + ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0); + float8 newvalY = PG_GETARG_FLOAT8(1); + float8 newvalX = PG_GETARG_FLOAT8(2); + float8 *transvalues; + float8 N, + Sx, + Sxx, + Sy, + Syy, + Sxy, + tmpX, + tmpY, + scale; + + transvalues = check_float8_array(transarray, "float8_regr_accum", 6); + N = transvalues[0]; + Sx = transvalues[1]; + Sxx = transvalues[2]; + Sy = transvalues[3]; + Syy = transvalues[4]; + Sxy = transvalues[5]; + + /* + * Use the Youngs-Cramer algorithm to incorporate the new values into the + * transition values. + */ + N += 1.0; + Sx += newvalX; + Sy += newvalY; + if (transvalues[0] > 0.0) + { + tmpX = newvalX * N - Sx; + tmpY = newvalY * N - Sy; + scale = 1.0 / (N * transvalues[0]); + Sxx += tmpX * tmpX * scale; + Syy += tmpY * tmpY * scale; + Sxy += tmpX * tmpY * scale; + + /* + * Overflow check. We only report an overflow error when finite + * inputs lead to infinite results. Note also that Sxx, Syy and Sxy + * should be NaN if any of the relevant inputs are infinite, so we + * intentionally prevent them from becoming infinite. + */ + if (isinf(Sx) || isinf(Sxx) || isinf(Sy) || isinf(Syy) || isinf(Sxy)) + { + if (((isinf(Sx) || isinf(Sxx)) && + !isinf(transvalues[1]) && !isinf(newvalX)) || + ((isinf(Sy) || isinf(Syy)) && + !isinf(transvalues[3]) && !isinf(newvalY)) || + (isinf(Sxy) && + !isinf(transvalues[1]) && !isinf(newvalX) && + !isinf(transvalues[3]) && !isinf(newvalY))) + float_overflow_error(); + + if (isinf(Sxx)) + Sxx = get_float8_nan(); + if (isinf(Syy)) + Syy = get_float8_nan(); + if (isinf(Sxy)) + Sxy = get_float8_nan(); + } + } + else + { + /* + * At the first input, we normally can leave Sxx et al as 0. However, + * if the first input is Inf or NaN, we'd better force the dependent + * sums to NaN; otherwise we will falsely report variance zero when + * there are no more inputs. + */ + if (isnan(newvalX) || isinf(newvalX)) + Sxx = Sxy = get_float8_nan(); + if (isnan(newvalY) || isinf(newvalY)) + Syy = Sxy = get_float8_nan(); + } + + /* + * If we're invoked as an aggregate, we can cheat and modify our first + * parameter in-place to reduce palloc overhead. Otherwise we construct a + * new array with the updated transition data and return it. + */ + if (AggCheckCallContext(fcinfo, NULL)) + { + transvalues[0] = N; + transvalues[1] = Sx; + transvalues[2] = Sxx; + transvalues[3] = Sy; + transvalues[4] = Syy; + transvalues[5] = Sxy; + + PG_RETURN_ARRAYTYPE_P(transarray); + } + else + { + Datum transdatums[6]; + ArrayType *result; + + transdatums[0] = Float8GetDatumFast(N); + transdatums[1] = Float8GetDatumFast(Sx); + transdatums[2] = Float8GetDatumFast(Sxx); + transdatums[3] = Float8GetDatumFast(Sy); + transdatums[4] = Float8GetDatumFast(Syy); + transdatums[5] = Float8GetDatumFast(Sxy); + + result = construct_array(transdatums, 6, + FLOAT8OID, + sizeof(float8), FLOAT8PASSBYVAL, TYPALIGN_DOUBLE); + + PG_RETURN_ARRAYTYPE_P(result); + } +} + +/* + * float8_regr_combine + * + * An aggregate combine function used to combine two 6 fields + * aggregate transition data into a single transition data. + * This function is used only in two stage aggregation and + * shouldn't be called outside aggregate context. + */ +Datum +float8_regr_combine(PG_FUNCTION_ARGS) +{ + ArrayType *transarray1 = PG_GETARG_ARRAYTYPE_P(0); + ArrayType *transarray2 = PG_GETARG_ARRAYTYPE_P(1); + float8 *transvalues1; + float8 *transvalues2; + float8 N1, + Sx1, + Sxx1, + Sy1, + Syy1, + Sxy1, + N2, + Sx2, + Sxx2, + Sy2, + Syy2, + Sxy2, + tmp1, + tmp2, + N, + Sx, + Sxx, + Sy, + Syy, + Sxy; + + transvalues1 = check_float8_array(transarray1, "float8_regr_combine", 6); + transvalues2 = check_float8_array(transarray2, "float8_regr_combine", 6); + + N1 = transvalues1[0]; + Sx1 = transvalues1[1]; + Sxx1 = transvalues1[2]; + Sy1 = transvalues1[3]; + Syy1 = transvalues1[4]; + Sxy1 = transvalues1[5]; + + N2 = transvalues2[0]; + Sx2 = transvalues2[1]; + Sxx2 = transvalues2[2]; + Sy2 = transvalues2[3]; + Syy2 = transvalues2[4]; + Sxy2 = transvalues2[5]; + + /*-------------------- + * The transition values combine using a generalization of the + * Youngs-Cramer algorithm as follows: + * + * N = N1 + N2 + * Sx = Sx1 + Sx2 + * Sxx = Sxx1 + Sxx2 + N1 * N2 * (Sx1/N1 - Sx2/N2)^2 / N + * Sy = Sy1 + Sy2 + * Syy = Syy1 + Syy2 + N1 * N2 * (Sy1/N1 - Sy2/N2)^2 / N + * Sxy = Sxy1 + Sxy2 + N1 * N2 * (Sx1/N1 - Sx2/N2) * (Sy1/N1 - Sy2/N2) / N + * + * It's worth handling the special cases N1 = 0 and N2 = 0 separately + * since those cases are trivial, and we then don't need to worry about + * division-by-zero errors in the general case. + *-------------------- + */ + if (N1 == 0.0) + { + N = N2; + Sx = Sx2; + Sxx = Sxx2; + Sy = Sy2; + Syy = Syy2; + Sxy = Sxy2; + } + else if (N2 == 0.0) + { + N = N1; + Sx = Sx1; + Sxx = Sxx1; + Sy = Sy1; + Syy = Syy1; + Sxy = Sxy1; + } + else + { + N = N1 + N2; + Sx = float8_pl(Sx1, Sx2); + tmp1 = Sx1 / N1 - Sx2 / N2; + Sxx = Sxx1 + Sxx2 + N1 * N2 * tmp1 * tmp1 / N; + if (unlikely(isinf(Sxx)) && !isinf(Sxx1) && !isinf(Sxx2)) + float_overflow_error(); + Sy = float8_pl(Sy1, Sy2); + tmp2 = Sy1 / N1 - Sy2 / N2; + Syy = Syy1 + Syy2 + N1 * N2 * tmp2 * tmp2 / N; + if (unlikely(isinf(Syy)) && !isinf(Syy1) && !isinf(Syy2)) + float_overflow_error(); + Sxy = Sxy1 + Sxy2 + N1 * N2 * tmp1 * tmp2 / N; + if (unlikely(isinf(Sxy)) && !isinf(Sxy1) && !isinf(Sxy2)) + float_overflow_error(); + } + + /* + * If we're invoked as an aggregate, we can cheat and modify our first + * parameter in-place to reduce palloc overhead. Otherwise we construct a + * new array with the updated transition data and return it. + */ + if (AggCheckCallContext(fcinfo, NULL)) + { + transvalues1[0] = N; + transvalues1[1] = Sx; + transvalues1[2] = Sxx; + transvalues1[3] = Sy; + transvalues1[4] = Syy; + transvalues1[5] = Sxy; + + PG_RETURN_ARRAYTYPE_P(transarray1); + } + else + { + Datum transdatums[6]; + ArrayType *result; + + transdatums[0] = Float8GetDatumFast(N); + transdatums[1] = Float8GetDatumFast(Sx); + transdatums[2] = Float8GetDatumFast(Sxx); + transdatums[3] = Float8GetDatumFast(Sy); + transdatums[4] = Float8GetDatumFast(Syy); + transdatums[5] = Float8GetDatumFast(Sxy); + + result = construct_array(transdatums, 6, + FLOAT8OID, + sizeof(float8), FLOAT8PASSBYVAL, TYPALIGN_DOUBLE); + + PG_RETURN_ARRAYTYPE_P(result); + } +} + + +Datum +float8_regr_sxx(PG_FUNCTION_ARGS) +{ + ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0); + float8 *transvalues; + float8 N, + Sxx; + + transvalues = check_float8_array(transarray, "float8_regr_sxx", 6); + N = transvalues[0]; + Sxx = transvalues[2]; + + /* if N is 0 we should return NULL */ + if (N < 1.0) + PG_RETURN_NULL(); + + /* Note that Sxx is guaranteed to be non-negative */ + + PG_RETURN_FLOAT8(Sxx); +} + +Datum +float8_regr_syy(PG_FUNCTION_ARGS) +{ + ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0); + float8 *transvalues; + float8 N, + Syy; + + transvalues = check_float8_array(transarray, "float8_regr_syy", 6); + N = transvalues[0]; + Syy = transvalues[4]; + + /* if N is 0 we should return NULL */ + if (N < 1.0) + PG_RETURN_NULL(); + + /* Note that Syy is guaranteed to be non-negative */ + + PG_RETURN_FLOAT8(Syy); +} + +Datum +float8_regr_sxy(PG_FUNCTION_ARGS) +{ + ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0); + float8 *transvalues; + float8 N, + Sxy; + + transvalues = check_float8_array(transarray, "float8_regr_sxy", 6); + N = transvalues[0]; + Sxy = transvalues[5]; + + /* if N is 0 we should return NULL */ + if (N < 1.0) + PG_RETURN_NULL(); + + /* A negative result is valid here */ + + PG_RETURN_FLOAT8(Sxy); +} + +Datum +float8_regr_avgx(PG_FUNCTION_ARGS) +{ + ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0); + float8 *transvalues; + float8 N, + Sx; + + transvalues = check_float8_array(transarray, "float8_regr_avgx", 6); + N = transvalues[0]; + Sx = transvalues[1]; + + /* if N is 0 we should return NULL */ + if (N < 1.0) + PG_RETURN_NULL(); + + PG_RETURN_FLOAT8(Sx / N); +} + +Datum +float8_regr_avgy(PG_FUNCTION_ARGS) +{ + ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0); + float8 *transvalues; + float8 N, + Sy; + + transvalues = check_float8_array(transarray, "float8_regr_avgy", 6); + N = transvalues[0]; + Sy = transvalues[3]; + + /* if N is 0 we should return NULL */ + if (N < 1.0) + PG_RETURN_NULL(); + + PG_RETURN_FLOAT8(Sy / N); +} + +Datum +float8_covar_pop(PG_FUNCTION_ARGS) +{ + ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0); + float8 *transvalues; + float8 N, + Sxy; + + transvalues = check_float8_array(transarray, "float8_covar_pop", 6); + N = transvalues[0]; + Sxy = transvalues[5]; + + /* if N is 0 we should return NULL */ + if (N < 1.0) + PG_RETURN_NULL(); + + PG_RETURN_FLOAT8(Sxy / N); +} + +Datum +float8_covar_samp(PG_FUNCTION_ARGS) +{ + ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0); + float8 *transvalues; + float8 N, + Sxy; + + transvalues = check_float8_array(transarray, "float8_covar_samp", 6); + N = transvalues[0]; + Sxy = transvalues[5]; + + /* if N is <= 1 we should return NULL */ + if (N < 2.0) + PG_RETURN_NULL(); + + PG_RETURN_FLOAT8(Sxy / (N - 1.0)); +} + +Datum +float8_corr(PG_FUNCTION_ARGS) +{ + ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0); + float8 *transvalues; + float8 N, + Sxx, + Syy, + Sxy; + + transvalues = check_float8_array(transarray, "float8_corr", 6); + N = transvalues[0]; + Sxx = transvalues[2]; + Syy = transvalues[4]; + Sxy = transvalues[5]; + + /* if N is 0 we should return NULL */ + if (N < 1.0) + PG_RETURN_NULL(); + + /* Note that Sxx and Syy are guaranteed to be non-negative */ + + /* per spec, return NULL for horizontal and vertical lines */ + if (Sxx == 0 || Syy == 0) + PG_RETURN_NULL(); + + PG_RETURN_FLOAT8(Sxy / sqrt(Sxx * Syy)); +} + +Datum +float8_regr_r2(PG_FUNCTION_ARGS) +{ + ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0); + float8 *transvalues; + float8 N, + Sxx, + Syy, + Sxy; + + transvalues = check_float8_array(transarray, "float8_regr_r2", 6); + N = transvalues[0]; + Sxx = transvalues[2]; + Syy = transvalues[4]; + Sxy = transvalues[5]; + + /* if N is 0 we should return NULL */ + if (N < 1.0) + PG_RETURN_NULL(); + + /* Note that Sxx and Syy are guaranteed to be non-negative */ + + /* per spec, return NULL for a vertical line */ + if (Sxx == 0) + PG_RETURN_NULL(); + + /* per spec, return 1.0 for a horizontal line */ + if (Syy == 0) + PG_RETURN_FLOAT8(1.0); + + PG_RETURN_FLOAT8((Sxy * Sxy) / (Sxx * Syy)); +} + +Datum +float8_regr_slope(PG_FUNCTION_ARGS) +{ + ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0); + float8 *transvalues; + float8 N, + Sxx, + Sxy; + + transvalues = check_float8_array(transarray, "float8_regr_slope", 6); + N = transvalues[0]; + Sxx = transvalues[2]; + Sxy = transvalues[5]; + + /* if N is 0 we should return NULL */ + if (N < 1.0) + PG_RETURN_NULL(); + + /* Note that Sxx is guaranteed to be non-negative */ + + /* per spec, return NULL for a vertical line */ + if (Sxx == 0) + PG_RETURN_NULL(); + + PG_RETURN_FLOAT8(Sxy / Sxx); +} + +Datum +float8_regr_intercept(PG_FUNCTION_ARGS) +{ + ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0); + float8 *transvalues; + float8 N, + Sx, + Sxx, + Sy, + Sxy; + + transvalues = check_float8_array(transarray, "float8_regr_intercept", 6); + N = transvalues[0]; + Sx = transvalues[1]; + Sxx = transvalues[2]; + Sy = transvalues[3]; + Sxy = transvalues[5]; + + /* if N is 0 we should return NULL */ + if (N < 1.0) + PG_RETURN_NULL(); + + /* Note that Sxx is guaranteed to be non-negative */ + + /* per spec, return NULL for a vertical line */ + if (Sxx == 0) + PG_RETURN_NULL(); + + PG_RETURN_FLOAT8((Sy - Sx * Sxy / Sxx) / N); +} + + +/* + * ==================================== + * MIXED-PRECISION ARITHMETIC OPERATORS + * ==================================== + */ + +/* + * float48pl - returns arg1 + arg2 + * float48mi - returns arg1 - arg2 + * float48mul - returns arg1 * arg2 + * float48div - returns arg1 / arg2 + */ +Datum +float48pl(PG_FUNCTION_ARGS) +{ + float4 arg1 = PG_GETARG_FLOAT4(0); + float8 arg2 = PG_GETARG_FLOAT8(1); + + PG_RETURN_FLOAT8(float8_pl((float8) arg1, arg2)); +} + +Datum +float48mi(PG_FUNCTION_ARGS) +{ + float4 arg1 = PG_GETARG_FLOAT4(0); + float8 arg2 = PG_GETARG_FLOAT8(1); + + PG_RETURN_FLOAT8(float8_mi((float8) arg1, arg2)); +} + +Datum +float48mul(PG_FUNCTION_ARGS) +{ + float4 arg1 = PG_GETARG_FLOAT4(0); + float8 arg2 = PG_GETARG_FLOAT8(1); + + PG_RETURN_FLOAT8(float8_mul((float8) arg1, arg2)); +} + +Datum +float48div(PG_FUNCTION_ARGS) +{ + float4 arg1 = PG_GETARG_FLOAT4(0); + float8 arg2 = PG_GETARG_FLOAT8(1); + + PG_RETURN_FLOAT8(float8_div((float8) arg1, arg2)); +} + +/* + * float84pl - returns arg1 + arg2 + * float84mi - returns arg1 - arg2 + * float84mul - returns arg1 * arg2 + * float84div - returns arg1 / arg2 + */ +Datum +float84pl(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + float4 arg2 = PG_GETARG_FLOAT4(1); + + PG_RETURN_FLOAT8(float8_pl(arg1, (float8) arg2)); +} + +Datum +float84mi(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + float4 arg2 = PG_GETARG_FLOAT4(1); + + PG_RETURN_FLOAT8(float8_mi(arg1, (float8) arg2)); +} + +Datum +float84mul(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + float4 arg2 = PG_GETARG_FLOAT4(1); + + PG_RETURN_FLOAT8(float8_mul(arg1, (float8) arg2)); +} + +Datum +float84div(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + float4 arg2 = PG_GETARG_FLOAT4(1); + + PG_RETURN_FLOAT8(float8_div(arg1, (float8) arg2)); +} + +/* + * ==================== + * COMPARISON OPERATORS + * ==================== + */ + +/* + * float48{eq,ne,lt,le,gt,ge} - float4/float8 comparison operations + */ +Datum +float48eq(PG_FUNCTION_ARGS) +{ + float4 arg1 = PG_GETARG_FLOAT4(0); + float8 arg2 = PG_GETARG_FLOAT8(1); + + PG_RETURN_BOOL(float8_eq((float8) arg1, arg2)); +} + +Datum +float48ne(PG_FUNCTION_ARGS) +{ + float4 arg1 = PG_GETARG_FLOAT4(0); + float8 arg2 = PG_GETARG_FLOAT8(1); + + PG_RETURN_BOOL(float8_ne((float8) arg1, arg2)); +} + +Datum +float48lt(PG_FUNCTION_ARGS) +{ + float4 arg1 = PG_GETARG_FLOAT4(0); + float8 arg2 = PG_GETARG_FLOAT8(1); + + PG_RETURN_BOOL(float8_lt((float8) arg1, arg2)); +} + +Datum +float48le(PG_FUNCTION_ARGS) +{ + float4 arg1 = PG_GETARG_FLOAT4(0); + float8 arg2 = PG_GETARG_FLOAT8(1); + + PG_RETURN_BOOL(float8_le((float8) arg1, arg2)); +} + +Datum +float48gt(PG_FUNCTION_ARGS) +{ + float4 arg1 = PG_GETARG_FLOAT4(0); + float8 arg2 = PG_GETARG_FLOAT8(1); + + PG_RETURN_BOOL(float8_gt((float8) arg1, arg2)); +} + +Datum +float48ge(PG_FUNCTION_ARGS) +{ + float4 arg1 = PG_GETARG_FLOAT4(0); + float8 arg2 = PG_GETARG_FLOAT8(1); + + PG_RETURN_BOOL(float8_ge((float8) arg1, arg2)); +} + +/* + * float84{eq,ne,lt,le,gt,ge} - float8/float4 comparison operations + */ +Datum +float84eq(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + float4 arg2 = PG_GETARG_FLOAT4(1); + + PG_RETURN_BOOL(float8_eq(arg1, (float8) arg2)); +} + +Datum +float84ne(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + float4 arg2 = PG_GETARG_FLOAT4(1); + + PG_RETURN_BOOL(float8_ne(arg1, (float8) arg2)); +} + +Datum +float84lt(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + float4 arg2 = PG_GETARG_FLOAT4(1); + + PG_RETURN_BOOL(float8_lt(arg1, (float8) arg2)); +} + +Datum +float84le(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + float4 arg2 = PG_GETARG_FLOAT4(1); + + PG_RETURN_BOOL(float8_le(arg1, (float8) arg2)); +} + +Datum +float84gt(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + float4 arg2 = PG_GETARG_FLOAT4(1); + + PG_RETURN_BOOL(float8_gt(arg1, (float8) arg2)); +} + +Datum +float84ge(PG_FUNCTION_ARGS) +{ + float8 arg1 = PG_GETARG_FLOAT8(0); + float4 arg2 = PG_GETARG_FLOAT4(1); + + PG_RETURN_BOOL(float8_ge(arg1, (float8) arg2)); +} + +/* + * Implements the float8 version of the width_bucket() function + * defined by SQL2003. See also width_bucket_numeric(). + * + * 'bound1' and 'bound2' are the lower and upper bounds of the + * histogram's range, respectively. 'count' is the number of buckets + * in the histogram. width_bucket() returns an integer indicating the + * bucket number that 'operand' belongs to in an equiwidth histogram + * with the specified characteristics. An operand smaller than the + * lower bound is assigned to bucket 0. An operand greater than the + * upper bound is assigned to an additional bucket (with number + * count+1). We don't allow "NaN" for any of the float8 inputs, and we + * don't allow either of the histogram bounds to be +/- infinity. + */ +Datum +width_bucket_float8(PG_FUNCTION_ARGS) +{ + float8 operand = PG_GETARG_FLOAT8(0); + float8 bound1 = PG_GETARG_FLOAT8(1); + float8 bound2 = PG_GETARG_FLOAT8(2); + int32 count = PG_GETARG_INT32(3); + int32 result; + + if (count <= 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_ARGUMENT_FOR_WIDTH_BUCKET_FUNCTION), + errmsg("count must be greater than zero"))); + + if (isnan(operand) || isnan(bound1) || isnan(bound2)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_ARGUMENT_FOR_WIDTH_BUCKET_FUNCTION), + errmsg("operand, lower bound, and upper bound cannot be NaN"))); + + /* Note that we allow "operand" to be infinite */ + if (isinf(bound1) || isinf(bound2)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_ARGUMENT_FOR_WIDTH_BUCKET_FUNCTION), + errmsg("lower and upper bounds must be finite"))); + + if (bound1 < bound2) + { + if (operand < bound1) + result = 0; + else if (operand >= bound2) + { + if (pg_add_s32_overflow(count, 1, &result)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("integer out of range"))); + } + else + { + if (!isinf(bound2 - bound1)) + { + /* The quotient is surely in [0,1], so this can't overflow */ + result = count * ((operand - bound1) / (bound2 - bound1)); + } + else + { + /* + * We get here if bound2 - bound1 overflows DBL_MAX. Since + * both bounds are finite, their difference can't exceed twice + * DBL_MAX; so we can perform the computation without overflow + * by dividing all the inputs by 2. That should be exact too, + * except in the case where a very small operand underflows to + * zero, which would have negligible impact on the result + * given such large bounds. + */ + result = count * ((operand / 2 - bound1 / 2) / (bound2 / 2 - bound1 / 2)); + } + /* The quotient could round to 1.0, which would be a lie */ + if (result >= count) + result = count - 1; + /* Having done that, we can add 1 without fear of overflow */ + result++; + } + } + else if (bound1 > bound2) + { + if (operand > bound1) + result = 0; + else if (operand <= bound2) + { + if (pg_add_s32_overflow(count, 1, &result)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("integer out of range"))); + } + else + { + if (!isinf(bound1 - bound2)) + result = count * ((bound1 - operand) / (bound1 - bound2)); + else + result = count * ((bound1 / 2 - operand / 2) / (bound1 / 2 - bound2 / 2)); + if (result >= count) + result = count - 1; + result++; + } + } + else + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_ARGUMENT_FOR_WIDTH_BUCKET_FUNCTION), + errmsg("lower bound cannot equal upper bound"))); + result = 0; /* keep the compiler quiet */ + } + + PG_RETURN_INT32(result); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/format_type.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/format_type.c new file mode 100644 index 00000000000..12402a06379 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/format_type.c @@ -0,0 +1,480 @@ +/*------------------------------------------------------------------------- + * + * format_type.c + * Display type names "nicely". + * + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/utils/adt/format_type.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include <ctype.h> + +#include "access/htup_details.h" +#include "catalog/namespace.h" +#include "catalog/pg_type.h" +#include "mb/pg_wchar.h" +#include "utils/builtins.h" +#include "utils/fmgroids.h" +#include "utils/lsyscache.h" +#include "utils/numeric.h" +#include "utils/syscache.h" + +static char *printTypmod(const char *typname, int32 typmod, Oid typmodout); + + +/* + * SQL function: format_type(type_oid, typemod) + * + * `type_oid' is from pg_type.oid, `typemod' is from + * pg_attribute.atttypmod. This function will get the type name and + * format it and the modifier to canonical SQL format, if the type is + * a standard type. Otherwise you just get pg_type.typname back, + * double quoted if it contains funny characters or matches a keyword. + * + * If typemod is NULL then we are formatting a type name in a context where + * no typemod is available, eg a function argument or result type. This + * yields a slightly different result from specifying typemod = -1 in some + * cases. Given typemod = -1 we feel compelled to produce an output that + * the parser will interpret as having typemod -1, so that pg_dump will + * produce CREATE TABLE commands that recreate the original state. But + * given NULL typemod, we assume that the parser's interpretation of + * typemod doesn't matter, and so we are willing to output a slightly + * "prettier" representation of the same type. For example, type = bpchar + * and typemod = NULL gets you "character", whereas typemod = -1 gets you + * "bpchar" --- the former will be interpreted as character(1) by the + * parser, which does not yield typemod -1. + * + * XXX encoding a meaning in typemod = NULL is ugly; it'd have been + * cleaner to make two functions of one and two arguments respectively. + * Not worth changing it now, however. + */ +Datum +format_type(PG_FUNCTION_ARGS) +{ + Oid type_oid; + int32 typemod; + char *result; + bits16 flags = FORMAT_TYPE_ALLOW_INVALID; + + /* Since this function is not strict, we must test for null args */ + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + + type_oid = PG_GETARG_OID(0); + + if (PG_ARGISNULL(1)) + typemod = -1; + else + { + typemod = PG_GETARG_INT32(1); + flags |= FORMAT_TYPE_TYPEMOD_GIVEN; + } + + result = format_type_extended(type_oid, typemod, flags); + + PG_RETURN_TEXT_P(cstring_to_text(result)); +} + +/* + * format_type_extended + * Generate a possibly-qualified type name. + * + * The default behavior is to only qualify if the type is not in the search + * path, to ignore the given typmod, and to raise an error if a non-existent + * type_oid is given. + * + * The following bits in 'flags' modify the behavior: + * - FORMAT_TYPE_TYPEMOD_GIVEN + * include the typmod in the output (typmod could still be -1 though) + * - FORMAT_TYPE_ALLOW_INVALID + * if the type OID is invalid or unknown, return ??? or such instead + * of failing + * - FORMAT_TYPE_INVALID_AS_NULL + * if the type OID is invalid or unknown, return NULL instead of ??? + * or such + * - FORMAT_TYPE_FORCE_QUALIFY + * always schema-qualify type names, regardless of search_path + * + * Note that TYPEMOD_GIVEN is not interchangeable with "typemod == -1"; + * see the comments above for format_type(). + * + * Returns a palloc'd string, or NULL. + */ +char * +format_type_extended(Oid type_oid, int32 typemod, bits16 flags) +{ + HeapTuple tuple; + Form_pg_type typeform; + Oid array_base_type; + bool is_array; + char *buf; + bool with_typemod; + + if (type_oid == InvalidOid) + { + if ((flags & FORMAT_TYPE_INVALID_AS_NULL) != 0) + return NULL; + else if ((flags & FORMAT_TYPE_ALLOW_INVALID) != 0) + return pstrdup("-"); + } + + tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type_oid)); + if (!HeapTupleIsValid(tuple)) + { + if ((flags & FORMAT_TYPE_INVALID_AS_NULL) != 0) + return NULL; + else if ((flags & FORMAT_TYPE_ALLOW_INVALID) != 0) + return pstrdup("???"); + else + elog(ERROR, "cache lookup failed for type %u", type_oid); + } + typeform = (Form_pg_type) GETSTRUCT(tuple); + + /* + * Check if it's a "true" array type. Pseudo-array types such as "name" + * shouldn't get deconstructed. Also check the toast property, and don't + * deconstruct "plain storage" array types --- this is because we don't + * want to show oidvector as oid[]. + */ + array_base_type = typeform->typelem; + + if (IsTrueArrayType(typeform) && + typeform->typstorage != TYPSTORAGE_PLAIN) + { + /* Switch our attention to the array element type */ + ReleaseSysCache(tuple); + tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(array_base_type)); + if (!HeapTupleIsValid(tuple)) + { + if ((flags & FORMAT_TYPE_INVALID_AS_NULL) != 0) + return NULL; + else if ((flags & FORMAT_TYPE_ALLOW_INVALID) != 0) + return pstrdup("???[]"); + else + elog(ERROR, "cache lookup failed for type %u", type_oid); + } + typeform = (Form_pg_type) GETSTRUCT(tuple); + type_oid = array_base_type; + is_array = true; + } + else + is_array = false; + + with_typemod = (flags & FORMAT_TYPE_TYPEMOD_GIVEN) != 0 && (typemod >= 0); + + /* + * See if we want to special-case the output for certain built-in types. + * Note that these special cases should all correspond to special + * productions in gram.y, to ensure that the type name will be taken as a + * system type, not a user type of the same name. + * + * If we do not provide a special-case output here, the type name will be + * handled the same way as a user type name --- in particular, it will be + * double-quoted if it matches any lexer keyword. This behavior is + * essential for some cases, such as types "bit" and "char". + */ + buf = NULL; /* flag for no special case */ + + switch (type_oid) + { + case BITOID: + if (with_typemod) + buf = printTypmod("bit", typemod, typeform->typmodout); + else if ((flags & FORMAT_TYPE_TYPEMOD_GIVEN) != 0) + { + /* + * bit with typmod -1 is not the same as BIT, which means + * BIT(1) per SQL spec. Report it as the quoted typename so + * that parser will not assign a bogus typmod. + */ + } + else + buf = pstrdup("bit"); + break; + + case BOOLOID: + buf = pstrdup("boolean"); + break; + + case BPCHAROID: + if (with_typemod) + buf = printTypmod("character", typemod, typeform->typmodout); + else if ((flags & FORMAT_TYPE_TYPEMOD_GIVEN) != 0) + { + /* + * bpchar with typmod -1 is not the same as CHARACTER, which + * means CHARACTER(1) per SQL spec. Report it as bpchar so + * that parser will not assign a bogus typmod. + */ + } + else + buf = pstrdup("character"); + break; + + case FLOAT4OID: + buf = pstrdup("real"); + break; + + case FLOAT8OID: + buf = pstrdup("double precision"); + break; + + case INT2OID: + buf = pstrdup("smallint"); + break; + + case INT4OID: + buf = pstrdup("integer"); + break; + + case INT8OID: + buf = pstrdup("bigint"); + break; + + case NUMERICOID: + if (with_typemod) + buf = printTypmod("numeric", typemod, typeform->typmodout); + else + buf = pstrdup("numeric"); + break; + + case INTERVALOID: + if (with_typemod) + buf = printTypmod("interval", typemod, typeform->typmodout); + else + buf = pstrdup("interval"); + break; + + case TIMEOID: + if (with_typemod) + buf = printTypmod("time", typemod, typeform->typmodout); + else + buf = pstrdup("time without time zone"); + break; + + case TIMETZOID: + if (with_typemod) + buf = printTypmod("time", typemod, typeform->typmodout); + else + buf = pstrdup("time with time zone"); + break; + + case TIMESTAMPOID: + if (with_typemod) + buf = printTypmod("timestamp", typemod, typeform->typmodout); + else + buf = pstrdup("timestamp without time zone"); + break; + + case TIMESTAMPTZOID: + if (with_typemod) + buf = printTypmod("timestamp", typemod, typeform->typmodout); + else + buf = pstrdup("timestamp with time zone"); + break; + + case VARBITOID: + if (with_typemod) + buf = printTypmod("bit varying", typemod, typeform->typmodout); + else + buf = pstrdup("bit varying"); + break; + + case VARCHAROID: + if (with_typemod) + buf = printTypmod("character varying", typemod, typeform->typmodout); + else + buf = pstrdup("character varying"); + break; + } + + if (buf == NULL) + { + /* + * Default handling: report the name as it appears in the catalog. + * Here, we must qualify the name if it is not visible in the search + * path or if caller requests it; and we must double-quote it if it's + * not a standard identifier or if it matches any keyword. + */ + char *nspname; + char *typname; + + if ((flags & FORMAT_TYPE_FORCE_QUALIFY) == 0 && + TypeIsVisible(type_oid)) + nspname = NULL; + else + nspname = get_namespace_name_or_temp(typeform->typnamespace); + + typname = NameStr(typeform->typname); + + buf = quote_qualified_identifier(nspname, typname); + + if (with_typemod) + buf = printTypmod(buf, typemod, typeform->typmodout); + } + + if (is_array) + buf = psprintf("%s[]", buf); + + ReleaseSysCache(tuple); + + return buf; +} + +/* + * This version is for use within the backend in error messages, etc. + * One difference is that it will fail for an invalid type. + * + * The result is always a palloc'd string. + */ +char * +format_type_be(Oid type_oid) +{ + return format_type_extended(type_oid, -1, 0); +} + +/* + * This version returns a name that is always qualified (unless it's one + * of the SQL-keyword type names, such as TIMESTAMP WITH TIME ZONE). + */ +char * +format_type_be_qualified(Oid type_oid) +{ + return format_type_extended(type_oid, -1, FORMAT_TYPE_FORCE_QUALIFY); +} + +/* + * This version allows a nondefault typemod to be specified. + */ +char * +format_type_with_typemod(Oid type_oid, int32 typemod) +{ + return format_type_extended(type_oid, typemod, FORMAT_TYPE_TYPEMOD_GIVEN); +} + +/* + * Add typmod decoration to the basic type name + */ +static char * +printTypmod(const char *typname, int32 typmod, Oid typmodout) +{ + char *res; + + /* Shouldn't be called if typmod is -1 */ + Assert(typmod >= 0); + + if (typmodout == InvalidOid) + { + /* Default behavior: just print the integer typmod with parens */ + res = psprintf("%s(%d)", typname, (int) typmod); + } + else + { + /* Use the type-specific typmodout procedure */ + char *tmstr; + + tmstr = DatumGetCString(OidFunctionCall1(typmodout, + Int32GetDatum(typmod))); + res = psprintf("%s%s", typname, tmstr); + } + + return res; +} + + +/* + * type_maximum_size --- determine maximum width of a variable-width column + * + * If the max width is indeterminate, return -1. In particular, we return + * -1 for any type not known to this routine. We assume the caller has + * already determined that the type is a variable-width type, so it's not + * necessary to look up the type's pg_type tuple here. + * + * This may appear unrelated to format_type(), but in fact the two routines + * share knowledge of the encoding of typmod for different types, so it's + * convenient to keep them together. (XXX now that most of this knowledge + * has been pushed out of format_type into the typmodout functions, it's + * interesting to wonder if it's worth trying to factor this code too...) + */ +int32 +type_maximum_size(Oid type_oid, int32 typemod) +{ + if (typemod < 0) + return -1; + + switch (type_oid) + { + case BPCHAROID: + case VARCHAROID: + /* typemod includes varlena header */ + + /* typemod is in characters not bytes */ + return (typemod - VARHDRSZ) * + pg_encoding_max_length(GetDatabaseEncoding()) + + VARHDRSZ; + + case NUMERICOID: + return numeric_maximum_size(typemod); + + case VARBITOID: + case BITOID: + /* typemod is the (max) number of bits */ + return (typemod + (BITS_PER_BYTE - 1)) / BITS_PER_BYTE + + 2 * sizeof(int32); + } + + /* Unknown type, or unlimited-width type such as 'text' */ + return -1; +} + + +/* + * oidvectortypes - converts a vector of type OIDs to "typname" list + */ +Datum +oidvectortypes(PG_FUNCTION_ARGS) +{ + oidvector *oidArray = (oidvector *) PG_GETARG_POINTER(0); + char *result; + int numargs = oidArray->dim1; + int num; + size_t total; + size_t left; + + total = 20 * numargs + 1; + result = palloc(total); + result[0] = '\0'; + left = total - 1; + + for (num = 0; num < numargs; num++) + { + char *typename = format_type_extended(oidArray->values[num], -1, + FORMAT_TYPE_ALLOW_INVALID); + size_t slen = strlen(typename); + + if (left < (slen + 2)) + { + total += slen + 2; + result = repalloc(result, total); + left += slen + 2; + } + + if (num > 0) + { + strcat(result, ", "); + left -= 2; + } + strcat(result, typename); + left -= slen; + } + + PG_RETURN_TEXT_P(cstring_to_text(result)); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/formatting.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/formatting.c new file mode 100644 index 00000000000..7c6202ddbcc --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/formatting.c @@ -0,0 +1,6693 @@ +/* ----------------------------------------------------------------------- + * formatting.c + * + * src/backend/utils/adt/formatting.c + * + * + * Portions Copyright (c) 1999-2023, PostgreSQL Global Development Group + * + * + * TO_CHAR(); TO_TIMESTAMP(); TO_DATE(); TO_NUMBER(); + * + * The PostgreSQL routines for a timestamp/int/float/numeric formatting, + * inspired by the Oracle TO_CHAR() / TO_DATE() / TO_NUMBER() routines. + * + * + * Cache & Memory: + * Routines use (itself) internal cache for format pictures. + * + * The cache uses a static buffer and is persistent across transactions. If + * the format-picture is bigger than the cache buffer, the parser is called + * always. + * + * NOTE for Number version: + * All in this version is implemented as keywords ( => not used + * suffixes), because a format picture is for *one* item (number) + * only. It not is as a timestamp version, where each keyword (can) + * has suffix. + * + * NOTE for Timestamp routines: + * In this module the POSIX 'struct tm' type is *not* used, but rather + * PgSQL type, which has tm_mon based on one (*non* zero) and + * year *not* based on 1900, but is used full year number. + * Module supports AD / BC / AM / PM. + * + * Supported types for to_char(): + * + * Timestamp, Numeric, int4, int8, float4, float8 + * + * Supported types for reverse conversion: + * + * Timestamp - to_timestamp() + * Date - to_date() + * Numeric - to_number() + * + * + * Karel Zak + * + * TODO + * - better number building (formatting) / parsing, now it isn't + * ideal code + * - use Assert() + * - add support for roman number to standard number conversion + * - add support for number spelling + * - add support for string to string formatting (we must be better + * than Oracle :-), + * to_char('Hello', 'X X X X X') -> 'H e l l o' + * + * ----------------------------------------------------------------------- + */ + +#ifdef DEBUG_TO_FROM_CHAR +#define DEBUG_elog_output DEBUG3 +#endif + +#include "postgres.h" + +#include <ctype.h> +#include <unistd.h> +#include <math.h> +#include <float.h> +#include <limits.h> +#include <wctype.h> + +#ifdef USE_ICU +#include <unicode/ustring.h> +#endif + +#include "catalog/pg_collation.h" +#include "catalog/pg_type.h" +#include "mb/pg_wchar.h" +#include "nodes/miscnodes.h" +#include "parser/scansup.h" +#include "utils/builtins.h" +#include "utils/date.h" +#include "utils/datetime.h" +#include "utils/float.h" +#include "utils/formatting.h" +#include "utils/memutils.h" +#include "utils/numeric.h" +#include "utils/pg_locale.h" +#include "varatt.h" + + +/* ---------- + * Routines flags + * ---------- + */ +#define DCH_FLAG 0x1 /* DATE-TIME flag */ +#define NUM_FLAG 0x2 /* NUMBER flag */ +#define STD_FLAG 0x4 /* STANDARD flag */ + +/* ---------- + * KeyWord Index (ascii from position 32 (' ') to 126 (~)) + * ---------- + */ +#define KeyWord_INDEX_SIZE ('~' - ' ') +#define KeyWord_INDEX_FILTER(_c) ((_c) <= ' ' || (_c) >= '~' ? 0 : 1) + +/* ---------- + * Maximal length of one node + * ---------- + */ +#define DCH_MAX_ITEM_SIZ 12 /* max localized day name */ +#define NUM_MAX_ITEM_SIZ 8 /* roman number (RN has 15 chars) */ + + +/* ---------- + * Format parser structs + * ---------- + */ +typedef struct +{ + const char *name; /* suffix string */ + int len, /* suffix length */ + id, /* used in node->suffix */ + type; /* prefix / postfix */ +} KeySuffix; + +/* ---------- + * FromCharDateMode + * ---------- + * + * This value is used to nominate one of several distinct (and mutually + * exclusive) date conventions that a keyword can belong to. + */ +typedef enum +{ + FROM_CHAR_DATE_NONE = 0, /* Value does not affect date mode. */ + FROM_CHAR_DATE_GREGORIAN, /* Gregorian (day, month, year) style date */ + FROM_CHAR_DATE_ISOWEEK /* ISO 8601 week date */ +} FromCharDateMode; + +typedef struct +{ + const char *name; + int len; + int id; + bool is_digit; + FromCharDateMode date_mode; +} KeyWord; + +typedef struct +{ + uint8 type; /* NODE_TYPE_XXX, see below */ + char character[MAX_MULTIBYTE_CHAR_LEN + 1]; /* if type is CHAR */ + uint8 suffix; /* keyword prefix/suffix code, if any */ + const KeyWord *key; /* if type is ACTION */ +} FormatNode; + +#define NODE_TYPE_END 1 +#define NODE_TYPE_ACTION 2 +#define NODE_TYPE_CHAR 3 +#define NODE_TYPE_SEPARATOR 4 +#define NODE_TYPE_SPACE 5 + +#define SUFFTYPE_PREFIX 1 +#define SUFFTYPE_POSTFIX 2 + +#define CLOCK_24_HOUR 0 +#define CLOCK_12_HOUR 1 + + +/* ---------- + * Full months + * ---------- + */ +static const char *const months_full[] = { + "January", "February", "March", "April", "May", "June", "July", + "August", "September", "October", "November", "December", NULL +}; + +static const char *const days_short[] = { + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", NULL +}; + +/* ---------- + * AD / BC + * ---------- + * There is no 0 AD. Years go from 1 BC to 1 AD, so we make it + * positive and map year == -1 to year zero, and shift all negative + * years up one. For interval years, we just return the year. + */ +#define ADJUST_YEAR(year, is_interval) ((is_interval) ? (year) : ((year) <= 0 ? -((year) - 1) : (year))) + +#define A_D_STR "A.D." +#define a_d_STR "a.d." +#define AD_STR "AD" +#define ad_STR "ad" + +#define B_C_STR "B.C." +#define b_c_STR "b.c." +#define BC_STR "BC" +#define bc_STR "bc" + +/* + * AD / BC strings for seq_search. + * + * These are given in two variants, a long form with periods and a standard + * form without. + * + * The array is laid out such that matches for AD have an even index, and + * matches for BC have an odd index. So the boolean value for BC is given by + * taking the array index of the match, modulo 2. + */ +static const char *const adbc_strings[] = {ad_STR, bc_STR, AD_STR, BC_STR, NULL}; +static const char *const adbc_strings_long[] = {a_d_STR, b_c_STR, A_D_STR, B_C_STR, NULL}; + +/* ---------- + * AM / PM + * ---------- + */ +#define A_M_STR "A.M." +#define a_m_STR "a.m." +#define AM_STR "AM" +#define am_STR "am" + +#define P_M_STR "P.M." +#define p_m_STR "p.m." +#define PM_STR "PM" +#define pm_STR "pm" + +/* + * AM / PM strings for seq_search. + * + * These are given in two variants, a long form with periods and a standard + * form without. + * + * The array is laid out such that matches for AM have an even index, and + * matches for PM have an odd index. So the boolean value for PM is given by + * taking the array index of the match, modulo 2. + */ +static const char *const ampm_strings[] = {am_STR, pm_STR, AM_STR, PM_STR, NULL}; +static const char *const ampm_strings_long[] = {a_m_STR, p_m_STR, A_M_STR, P_M_STR, NULL}; + +/* ---------- + * Months in roman-numeral + * (Must be in reverse order for seq_search (in FROM_CHAR), because + * 'VIII' must have higher precedence than 'V') + * ---------- + */ +static const char *const rm_months_upper[] = +{"XII", "XI", "X", "IX", "VIII", "VII", "VI", "V", "IV", "III", "II", "I", NULL}; + +static const char *const rm_months_lower[] = +{"xii", "xi", "x", "ix", "viii", "vii", "vi", "v", "iv", "iii", "ii", "i", NULL}; + +/* ---------- + * Roman numbers + * ---------- + */ +static const char *const rm1[] = {"I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", NULL}; +static const char *const rm10[] = {"X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC", NULL}; +static const char *const rm100[] = {"C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM", NULL}; + +/* ---------- + * Ordinal postfixes + * ---------- + */ +static const char *const numTH[] = {"ST", "ND", "RD", "TH", NULL}; +static const char *const numth[] = {"st", "nd", "rd", "th", NULL}; + +/* ---------- + * Flags & Options: + * ---------- + */ +#define TH_UPPER 1 +#define TH_LOWER 2 + +/* ---------- + * Number description struct + * ---------- + */ +typedef struct +{ + int pre, /* (count) numbers before decimal */ + post, /* (count) numbers after decimal */ + lsign, /* want locales sign */ + flag, /* number parameters */ + pre_lsign_num, /* tmp value for lsign */ + multi, /* multiplier for 'V' */ + zero_start, /* position of first zero */ + zero_end, /* position of last zero */ + need_locale; /* needs it locale */ +} NUMDesc; + +/* ---------- + * Flags for NUMBER version + * ---------- + */ +#define NUM_F_DECIMAL (1 << 1) +#define NUM_F_LDECIMAL (1 << 2) +#define NUM_F_ZERO (1 << 3) +#define NUM_F_BLANK (1 << 4) +#define NUM_F_FILLMODE (1 << 5) +#define NUM_F_LSIGN (1 << 6) +#define NUM_F_BRACKET (1 << 7) +#define NUM_F_MINUS (1 << 8) +#define NUM_F_PLUS (1 << 9) +#define NUM_F_ROMAN (1 << 10) +#define NUM_F_MULTI (1 << 11) +#define NUM_F_PLUS_POST (1 << 12) +#define NUM_F_MINUS_POST (1 << 13) +#define NUM_F_EEEE (1 << 14) + +#define NUM_LSIGN_PRE (-1) +#define NUM_LSIGN_POST 1 +#define NUM_LSIGN_NONE 0 + +/* ---------- + * Tests + * ---------- + */ +#define IS_DECIMAL(_f) ((_f)->flag & NUM_F_DECIMAL) +#define IS_LDECIMAL(_f) ((_f)->flag & NUM_F_LDECIMAL) +#define IS_ZERO(_f) ((_f)->flag & NUM_F_ZERO) +#define IS_BLANK(_f) ((_f)->flag & NUM_F_BLANK) +#define IS_FILLMODE(_f) ((_f)->flag & NUM_F_FILLMODE) +#define IS_BRACKET(_f) ((_f)->flag & NUM_F_BRACKET) +#define IS_MINUS(_f) ((_f)->flag & NUM_F_MINUS) +#define IS_LSIGN(_f) ((_f)->flag & NUM_F_LSIGN) +#define IS_PLUS(_f) ((_f)->flag & NUM_F_PLUS) +#define IS_ROMAN(_f) ((_f)->flag & NUM_F_ROMAN) +#define IS_MULTI(_f) ((_f)->flag & NUM_F_MULTI) +#define IS_EEEE(_f) ((_f)->flag & NUM_F_EEEE) + +/* ---------- + * Format picture cache + * + * We will cache datetime format pictures up to DCH_CACHE_SIZE bytes long; + * likewise number format pictures up to NUM_CACHE_SIZE bytes long. + * + * For simplicity, the cache entries are fixed-size, so they allow for the + * worst case of a FormatNode for each byte in the picture string. + * + * The CACHE_SIZE constants are computed to make sizeof(DCHCacheEntry) and + * sizeof(NUMCacheEntry) be powers of 2, or just less than that, so that + * we don't waste too much space by palloc'ing them individually. Be sure + * to adjust those macros if you add fields to those structs. + * + * The max number of entries in each cache is DCH_CACHE_ENTRIES + * resp. NUM_CACHE_ENTRIES. + * ---------- + */ +#define DCH_CACHE_OVERHEAD \ + MAXALIGN(sizeof(bool) + sizeof(int)) +#define NUM_CACHE_OVERHEAD \ + MAXALIGN(sizeof(bool) + sizeof(int) + sizeof(NUMDesc)) + +#define DCH_CACHE_SIZE \ + ((2048 - DCH_CACHE_OVERHEAD) / (sizeof(FormatNode) + sizeof(char)) - 1) +#define NUM_CACHE_SIZE \ + ((1024 - NUM_CACHE_OVERHEAD) / (sizeof(FormatNode) + sizeof(char)) - 1) + +#define DCH_CACHE_ENTRIES 20 +#define NUM_CACHE_ENTRIES 20 + +typedef struct +{ + FormatNode format[DCH_CACHE_SIZE + 1]; + char str[DCH_CACHE_SIZE + 1]; + bool std; + bool valid; + int age; +} DCHCacheEntry; + +typedef struct +{ + FormatNode format[NUM_CACHE_SIZE + 1]; + char str[NUM_CACHE_SIZE + 1]; + bool valid; + int age; + NUMDesc Num; +} NUMCacheEntry; + +/* global cache for date/time format pictures */ +static __thread DCHCacheEntry *DCHCache[DCH_CACHE_ENTRIES]; +static __thread int n_DCHCache = 0; /* current number of entries */ +static __thread int DCHCounter = 0; /* aging-event counter */ + +/* global cache for number format pictures */ +static __thread NUMCacheEntry *NUMCache[NUM_CACHE_ENTRIES]; +static __thread int n_NUMCache = 0; /* current number of entries */ +static __thread int NUMCounter = 0; /* aging-event counter */ + +/* ---------- + * For char->date/time conversion + * ---------- + */ +typedef struct +{ + FromCharDateMode mode; + int hh, + pm, + mi, + ss, + ssss, + d, /* stored as 1-7, Sunday = 1, 0 means missing */ + dd, + ddd, + mm, + ms, + year, + bc, + ww, + w, + cc, + j, + us, + yysz, /* is it YY or YYYY ? */ + clock, /* 12 or 24 hour clock? */ + tzsign, /* +1, -1 or 0 if timezone info is absent */ + tzh, + tzm, + ff; /* fractional precision */ +} TmFromChar; + +#define ZERO_tmfc(_X) memset(_X, 0, sizeof(TmFromChar)) + +/* ---------- + * Debug + * ---------- + */ +#ifdef DEBUG_TO_FROM_CHAR +#define DEBUG_TMFC(_X) \ + 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", \ + (_X)->mode, (_X)->hh, (_X)->pm, (_X)->mi, (_X)->ss, (_X)->ssss, \ + (_X)->d, (_X)->dd, (_X)->ddd, (_X)->mm, (_X)->ms, (_X)->year, \ + (_X)->bc, (_X)->ww, (_X)->w, (_X)->cc, (_X)->j, (_X)->us, \ + (_X)->yysz, (_X)->clock) +#define DEBUG_TM(_X) \ + elog(DEBUG_elog_output, "TM:\nsec %d\nyear %d\nmin %d\nwday %d\nhour %d\nyday %d\nmday %d\nnisdst %d\nmon %d\n",\ + (_X)->tm_sec, (_X)->tm_year,\ + (_X)->tm_min, (_X)->tm_wday, (_X)->tm_hour, (_X)->tm_yday,\ + (_X)->tm_mday, (_X)->tm_isdst, (_X)->tm_mon) +#else +#define DEBUG_TMFC(_X) +#define DEBUG_TM(_X) +#endif + +/* ---------- + * Datetime to char conversion + * + * To support intervals as well as timestamps, we use a custom "tm" struct + * that is almost like struct pg_tm, but has a 64-bit tm_hour field. + * We omit the tm_isdst and tm_zone fields, which are not used here. + * ---------- + */ +struct fmt_tm +{ + int tm_sec; + int tm_min; + int64 tm_hour; + int tm_mday; + int tm_mon; + int tm_year; + int tm_wday; + int tm_yday; + long int tm_gmtoff; +}; + +typedef struct TmToChar +{ + struct fmt_tm tm; /* almost the classic 'tm' struct */ + fsec_t fsec; /* fractional seconds */ + const char *tzn; /* timezone */ +} TmToChar; + +#define tmtcTm(_X) (&(_X)->tm) +#define tmtcTzn(_X) ((_X)->tzn) +#define tmtcFsec(_X) ((_X)->fsec) + +/* Note: this is used to copy pg_tm to fmt_tm, so not quite a bitwise copy */ +#define COPY_tm(_DST, _SRC) \ +do { \ + (_DST)->tm_sec = (_SRC)->tm_sec; \ + (_DST)->tm_min = (_SRC)->tm_min; \ + (_DST)->tm_hour = (_SRC)->tm_hour; \ + (_DST)->tm_mday = (_SRC)->tm_mday; \ + (_DST)->tm_mon = (_SRC)->tm_mon; \ + (_DST)->tm_year = (_SRC)->tm_year; \ + (_DST)->tm_wday = (_SRC)->tm_wday; \ + (_DST)->tm_yday = (_SRC)->tm_yday; \ + (_DST)->tm_gmtoff = (_SRC)->tm_gmtoff; \ +} while(0) + +/* Caution: this is used to zero both pg_tm and fmt_tm structs */ +#define ZERO_tm(_X) \ +do { \ + memset(_X, 0, sizeof(*(_X))); \ + (_X)->tm_mday = (_X)->tm_mon = 1; \ +} while(0) + +#define ZERO_tmtc(_X) \ +do { \ + ZERO_tm( tmtcTm(_X) ); \ + tmtcFsec(_X) = 0; \ + tmtcTzn(_X) = NULL; \ +} while(0) + +/* + * to_char(time) appears to to_char() as an interval, so this check + * is really for interval and time data types. + */ +#define INVALID_FOR_INTERVAL \ +do { \ + if (is_interval) \ + ereport(ERROR, \ + (errcode(ERRCODE_INVALID_DATETIME_FORMAT), \ + errmsg("invalid format specification for an interval value"), \ + errhint("Intervals are not tied to specific calendar dates."))); \ +} while(0) + +/***************************************************************************** + * KeyWord definitions + *****************************************************************************/ + +/* ---------- + * Suffixes (FormatNode.suffix is an OR of these codes) + * ---------- + */ +#define DCH_S_FM 0x01 +#define DCH_S_TH 0x02 +#define DCH_S_th 0x04 +#define DCH_S_SP 0x08 +#define DCH_S_TM 0x10 + +/* ---------- + * Suffix tests + * ---------- + */ +#define S_THth(_s) ((((_s) & DCH_S_TH) || ((_s) & DCH_S_th)) ? 1 : 0) +#define S_TH(_s) (((_s) & DCH_S_TH) ? 1 : 0) +#define S_th(_s) (((_s) & DCH_S_th) ? 1 : 0) +#define S_TH_TYPE(_s) (((_s) & DCH_S_TH) ? TH_UPPER : TH_LOWER) + +/* Oracle toggles FM behavior, we don't; see docs. */ +#define S_FM(_s) (((_s) & DCH_S_FM) ? 1 : 0) +#define S_SP(_s) (((_s) & DCH_S_SP) ? 1 : 0) +#define S_TM(_s) (((_s) & DCH_S_TM) ? 1 : 0) + +/* ---------- + * Suffixes definition for DATE-TIME TO/FROM CHAR + * ---------- + */ +#define TM_SUFFIX_LEN 2 + +static const KeySuffix DCH_suff[] = { + {"FM", 2, DCH_S_FM, SUFFTYPE_PREFIX}, + {"fm", 2, DCH_S_FM, SUFFTYPE_PREFIX}, + {"TM", TM_SUFFIX_LEN, DCH_S_TM, SUFFTYPE_PREFIX}, + {"tm", 2, DCH_S_TM, SUFFTYPE_PREFIX}, + {"TH", 2, DCH_S_TH, SUFFTYPE_POSTFIX}, + {"th", 2, DCH_S_th, SUFFTYPE_POSTFIX}, + {"SP", 2, DCH_S_SP, SUFFTYPE_POSTFIX}, + /* last */ + {NULL, 0, 0, 0} +}; + + +/* ---------- + * Format-pictures (KeyWord). + * + * The KeyWord field; alphabetic sorted, *BUT* strings alike is sorted + * complicated -to-> easy: + * + * (example: "DDD","DD","Day","D" ) + * + * (this specific sort needs the algorithm for sequential search for strings, + * which not has exact end; -> How keyword is in "HH12blabla" ? - "HH" + * or "HH12"? You must first try "HH12", because "HH" is in string, but + * it is not good. + * + * (!) + * - Position for the keyword is similar as position in the enum DCH/NUM_poz. + * (!) + * + * For fast search is used the 'int index[]', index is ascii table from position + * 32 (' ') to 126 (~), in this index is DCH_ / NUM_ enums for each ASCII + * position or -1 if char is not used in the KeyWord. Search example for + * string "MM": + * 1) see in index to index['M' - 32], + * 2) take keywords position (enum DCH_MI) from index + * 3) run sequential search in keywords[] from this position + * + * ---------- + */ + +typedef enum +{ + DCH_A_D, + DCH_A_M, + DCH_AD, + DCH_AM, + DCH_B_C, + DCH_BC, + DCH_CC, + DCH_DAY, + DCH_DDD, + DCH_DD, + DCH_DY, + DCH_Day, + DCH_Dy, + DCH_D, + DCH_FF1, + DCH_FF2, + DCH_FF3, + DCH_FF4, + DCH_FF5, + DCH_FF6, + DCH_FX, /* global suffix */ + DCH_HH24, + DCH_HH12, + DCH_HH, + DCH_IDDD, + DCH_ID, + DCH_IW, + DCH_IYYY, + DCH_IYY, + DCH_IY, + DCH_I, + DCH_J, + DCH_MI, + DCH_MM, + DCH_MONTH, + DCH_MON, + DCH_MS, + DCH_Month, + DCH_Mon, + DCH_OF, + DCH_P_M, + DCH_PM, + DCH_Q, + DCH_RM, + DCH_SSSSS, + DCH_SSSS, + DCH_SS, + DCH_TZH, + DCH_TZM, + DCH_TZ, + DCH_US, + DCH_WW, + DCH_W, + DCH_Y_YYY, + DCH_YYYY, + DCH_YYY, + DCH_YY, + DCH_Y, + DCH_a_d, + DCH_a_m, + DCH_ad, + DCH_am, + DCH_b_c, + DCH_bc, + DCH_cc, + DCH_day, + DCH_ddd, + DCH_dd, + DCH_dy, + DCH_d, + DCH_ff1, + DCH_ff2, + DCH_ff3, + DCH_ff4, + DCH_ff5, + DCH_ff6, + DCH_fx, + DCH_hh24, + DCH_hh12, + DCH_hh, + DCH_iddd, + DCH_id, + DCH_iw, + DCH_iyyy, + DCH_iyy, + DCH_iy, + DCH_i, + DCH_j, + DCH_mi, + DCH_mm, + DCH_month, + DCH_mon, + DCH_ms, + DCH_of, + DCH_p_m, + DCH_pm, + DCH_q, + DCH_rm, + DCH_sssss, + DCH_ssss, + DCH_ss, + DCH_tzh, + DCH_tzm, + DCH_tz, + DCH_us, + DCH_ww, + DCH_w, + DCH_y_yyy, + DCH_yyyy, + DCH_yyy, + DCH_yy, + DCH_y, + + /* last */ + _DCH_last_ +} DCH_poz; + +typedef enum +{ + NUM_COMMA, + NUM_DEC, + NUM_0, + NUM_9, + NUM_B, + NUM_C, + NUM_D, + NUM_E, + NUM_FM, + NUM_G, + NUM_L, + NUM_MI, + NUM_PL, + NUM_PR, + NUM_RN, + NUM_SG, + NUM_SP, + NUM_S, + NUM_TH, + NUM_V, + NUM_b, + NUM_c, + NUM_d, + NUM_e, + NUM_fm, + NUM_g, + NUM_l, + NUM_mi, + NUM_pl, + NUM_pr, + NUM_rn, + NUM_sg, + NUM_sp, + NUM_s, + NUM_th, + NUM_v, + + /* last */ + _NUM_last_ +} NUM_poz; + +/* ---------- + * KeyWords for DATE-TIME version + * ---------- + */ +static const KeyWord DCH_keywords[] = { +/* name, len, id, is_digit, date_mode */ + {"A.D.", 4, DCH_A_D, false, FROM_CHAR_DATE_NONE}, /* A */ + {"A.M.", 4, DCH_A_M, false, FROM_CHAR_DATE_NONE}, + {"AD", 2, DCH_AD, false, FROM_CHAR_DATE_NONE}, + {"AM", 2, DCH_AM, false, FROM_CHAR_DATE_NONE}, + {"B.C.", 4, DCH_B_C, false, FROM_CHAR_DATE_NONE}, /* B */ + {"BC", 2, DCH_BC, false, FROM_CHAR_DATE_NONE}, + {"CC", 2, DCH_CC, true, FROM_CHAR_DATE_NONE}, /* C */ + {"DAY", 3, DCH_DAY, false, FROM_CHAR_DATE_NONE}, /* D */ + {"DDD", 3, DCH_DDD, true, FROM_CHAR_DATE_GREGORIAN}, + {"DD", 2, DCH_DD, true, FROM_CHAR_DATE_GREGORIAN}, + {"DY", 2, DCH_DY, false, FROM_CHAR_DATE_NONE}, + {"Day", 3, DCH_Day, false, FROM_CHAR_DATE_NONE}, + {"Dy", 2, DCH_Dy, false, FROM_CHAR_DATE_NONE}, + {"D", 1, DCH_D, true, FROM_CHAR_DATE_GREGORIAN}, + {"FF1", 3, DCH_FF1, false, FROM_CHAR_DATE_NONE}, /* F */ + {"FF2", 3, DCH_FF2, false, FROM_CHAR_DATE_NONE}, + {"FF3", 3, DCH_FF3, false, FROM_CHAR_DATE_NONE}, + {"FF4", 3, DCH_FF4, false, FROM_CHAR_DATE_NONE}, + {"FF5", 3, DCH_FF5, false, FROM_CHAR_DATE_NONE}, + {"FF6", 3, DCH_FF6, false, FROM_CHAR_DATE_NONE}, + {"FX", 2, DCH_FX, false, FROM_CHAR_DATE_NONE}, + {"HH24", 4, DCH_HH24, true, FROM_CHAR_DATE_NONE}, /* H */ + {"HH12", 4, DCH_HH12, true, FROM_CHAR_DATE_NONE}, + {"HH", 2, DCH_HH, true, FROM_CHAR_DATE_NONE}, + {"IDDD", 4, DCH_IDDD, true, FROM_CHAR_DATE_ISOWEEK}, /* I */ + {"ID", 2, DCH_ID, true, FROM_CHAR_DATE_ISOWEEK}, + {"IW", 2, DCH_IW, true, FROM_CHAR_DATE_ISOWEEK}, + {"IYYY", 4, DCH_IYYY, true, FROM_CHAR_DATE_ISOWEEK}, + {"IYY", 3, DCH_IYY, true, FROM_CHAR_DATE_ISOWEEK}, + {"IY", 2, DCH_IY, true, FROM_CHAR_DATE_ISOWEEK}, + {"I", 1, DCH_I, true, FROM_CHAR_DATE_ISOWEEK}, + {"J", 1, DCH_J, true, FROM_CHAR_DATE_NONE}, /* J */ + {"MI", 2, DCH_MI, true, FROM_CHAR_DATE_NONE}, /* M */ + {"MM", 2, DCH_MM, true, FROM_CHAR_DATE_GREGORIAN}, + {"MONTH", 5, DCH_MONTH, false, FROM_CHAR_DATE_GREGORIAN}, + {"MON", 3, DCH_MON, false, FROM_CHAR_DATE_GREGORIAN}, + {"MS", 2, DCH_MS, true, FROM_CHAR_DATE_NONE}, + {"Month", 5, DCH_Month, false, FROM_CHAR_DATE_GREGORIAN}, + {"Mon", 3, DCH_Mon, false, FROM_CHAR_DATE_GREGORIAN}, + {"OF", 2, DCH_OF, false, FROM_CHAR_DATE_NONE}, /* O */ + {"P.M.", 4, DCH_P_M, false, FROM_CHAR_DATE_NONE}, /* P */ + {"PM", 2, DCH_PM, false, FROM_CHAR_DATE_NONE}, + {"Q", 1, DCH_Q, true, FROM_CHAR_DATE_NONE}, /* Q */ + {"RM", 2, DCH_RM, false, FROM_CHAR_DATE_GREGORIAN}, /* R */ + {"SSSSS", 5, DCH_SSSS, true, FROM_CHAR_DATE_NONE}, /* S */ + {"SSSS", 4, DCH_SSSS, true, FROM_CHAR_DATE_NONE}, + {"SS", 2, DCH_SS, true, FROM_CHAR_DATE_NONE}, + {"TZH", 3, DCH_TZH, false, FROM_CHAR_DATE_NONE}, /* T */ + {"TZM", 3, DCH_TZM, true, FROM_CHAR_DATE_NONE}, + {"TZ", 2, DCH_TZ, false, FROM_CHAR_DATE_NONE}, + {"US", 2, DCH_US, true, FROM_CHAR_DATE_NONE}, /* U */ + {"WW", 2, DCH_WW, true, FROM_CHAR_DATE_GREGORIAN}, /* W */ + {"W", 1, DCH_W, true, FROM_CHAR_DATE_GREGORIAN}, + {"Y,YYY", 5, DCH_Y_YYY, true, FROM_CHAR_DATE_GREGORIAN}, /* Y */ + {"YYYY", 4, DCH_YYYY, true, FROM_CHAR_DATE_GREGORIAN}, + {"YYY", 3, DCH_YYY, true, FROM_CHAR_DATE_GREGORIAN}, + {"YY", 2, DCH_YY, true, FROM_CHAR_DATE_GREGORIAN}, + {"Y", 1, DCH_Y, true, FROM_CHAR_DATE_GREGORIAN}, + {"a.d.", 4, DCH_a_d, false, FROM_CHAR_DATE_NONE}, /* a */ + {"a.m.", 4, DCH_a_m, false, FROM_CHAR_DATE_NONE}, + {"ad", 2, DCH_ad, false, FROM_CHAR_DATE_NONE}, + {"am", 2, DCH_am, false, FROM_CHAR_DATE_NONE}, + {"b.c.", 4, DCH_b_c, false, FROM_CHAR_DATE_NONE}, /* b */ + {"bc", 2, DCH_bc, false, FROM_CHAR_DATE_NONE}, + {"cc", 2, DCH_CC, true, FROM_CHAR_DATE_NONE}, /* c */ + {"day", 3, DCH_day, false, FROM_CHAR_DATE_NONE}, /* d */ + {"ddd", 3, DCH_DDD, true, FROM_CHAR_DATE_GREGORIAN}, + {"dd", 2, DCH_DD, true, FROM_CHAR_DATE_GREGORIAN}, + {"dy", 2, DCH_dy, false, FROM_CHAR_DATE_NONE}, + {"d", 1, DCH_D, true, FROM_CHAR_DATE_GREGORIAN}, + {"ff1", 3, DCH_FF1, false, FROM_CHAR_DATE_NONE}, /* f */ + {"ff2", 3, DCH_FF2, false, FROM_CHAR_DATE_NONE}, + {"ff3", 3, DCH_FF3, false, FROM_CHAR_DATE_NONE}, + {"ff4", 3, DCH_FF4, false, FROM_CHAR_DATE_NONE}, + {"ff5", 3, DCH_FF5, false, FROM_CHAR_DATE_NONE}, + {"ff6", 3, DCH_FF6, false, FROM_CHAR_DATE_NONE}, + {"fx", 2, DCH_FX, false, FROM_CHAR_DATE_NONE}, + {"hh24", 4, DCH_HH24, true, FROM_CHAR_DATE_NONE}, /* h */ + {"hh12", 4, DCH_HH12, true, FROM_CHAR_DATE_NONE}, + {"hh", 2, DCH_HH, true, FROM_CHAR_DATE_NONE}, + {"iddd", 4, DCH_IDDD, true, FROM_CHAR_DATE_ISOWEEK}, /* i */ + {"id", 2, DCH_ID, true, FROM_CHAR_DATE_ISOWEEK}, + {"iw", 2, DCH_IW, true, FROM_CHAR_DATE_ISOWEEK}, + {"iyyy", 4, DCH_IYYY, true, FROM_CHAR_DATE_ISOWEEK}, + {"iyy", 3, DCH_IYY, true, FROM_CHAR_DATE_ISOWEEK}, + {"iy", 2, DCH_IY, true, FROM_CHAR_DATE_ISOWEEK}, + {"i", 1, DCH_I, true, FROM_CHAR_DATE_ISOWEEK}, + {"j", 1, DCH_J, true, FROM_CHAR_DATE_NONE}, /* j */ + {"mi", 2, DCH_MI, true, FROM_CHAR_DATE_NONE}, /* m */ + {"mm", 2, DCH_MM, true, FROM_CHAR_DATE_GREGORIAN}, + {"month", 5, DCH_month, false, FROM_CHAR_DATE_GREGORIAN}, + {"mon", 3, DCH_mon, false, FROM_CHAR_DATE_GREGORIAN}, + {"ms", 2, DCH_MS, true, FROM_CHAR_DATE_NONE}, + {"of", 2, DCH_OF, false, FROM_CHAR_DATE_NONE}, /* o */ + {"p.m.", 4, DCH_p_m, false, FROM_CHAR_DATE_NONE}, /* p */ + {"pm", 2, DCH_pm, false, FROM_CHAR_DATE_NONE}, + {"q", 1, DCH_Q, true, FROM_CHAR_DATE_NONE}, /* q */ + {"rm", 2, DCH_rm, false, FROM_CHAR_DATE_GREGORIAN}, /* r */ + {"sssss", 5, DCH_SSSS, true, FROM_CHAR_DATE_NONE}, /* s */ + {"ssss", 4, DCH_SSSS, true, FROM_CHAR_DATE_NONE}, + {"ss", 2, DCH_SS, true, FROM_CHAR_DATE_NONE}, + {"tzh", 3, DCH_TZH, false, FROM_CHAR_DATE_NONE}, /* t */ + {"tzm", 3, DCH_TZM, true, FROM_CHAR_DATE_NONE}, + {"tz", 2, DCH_tz, false, FROM_CHAR_DATE_NONE}, + {"us", 2, DCH_US, true, FROM_CHAR_DATE_NONE}, /* u */ + {"ww", 2, DCH_WW, true, FROM_CHAR_DATE_GREGORIAN}, /* w */ + {"w", 1, DCH_W, true, FROM_CHAR_DATE_GREGORIAN}, + {"y,yyy", 5, DCH_Y_YYY, true, FROM_CHAR_DATE_GREGORIAN}, /* y */ + {"yyyy", 4, DCH_YYYY, true, FROM_CHAR_DATE_GREGORIAN}, + {"yyy", 3, DCH_YYY, true, FROM_CHAR_DATE_GREGORIAN}, + {"yy", 2, DCH_YY, true, FROM_CHAR_DATE_GREGORIAN}, + {"y", 1, DCH_Y, true, FROM_CHAR_DATE_GREGORIAN}, + + /* last */ + {NULL, 0, 0, 0, 0} +}; + +/* ---------- + * KeyWords for NUMBER version + * + * The is_digit and date_mode fields are not relevant here. + * ---------- + */ +static const KeyWord NUM_keywords[] = { +/* name, len, id is in Index */ + {",", 1, NUM_COMMA}, /* , */ + {".", 1, NUM_DEC}, /* . */ + {"0", 1, NUM_0}, /* 0 */ + {"9", 1, NUM_9}, /* 9 */ + {"B", 1, NUM_B}, /* B */ + {"C", 1, NUM_C}, /* C */ + {"D", 1, NUM_D}, /* D */ + {"EEEE", 4, NUM_E}, /* E */ + {"FM", 2, NUM_FM}, /* F */ + {"G", 1, NUM_G}, /* G */ + {"L", 1, NUM_L}, /* L */ + {"MI", 2, NUM_MI}, /* M */ + {"PL", 2, NUM_PL}, /* P */ + {"PR", 2, NUM_PR}, + {"RN", 2, NUM_RN}, /* R */ + {"SG", 2, NUM_SG}, /* S */ + {"SP", 2, NUM_SP}, + {"S", 1, NUM_S}, + {"TH", 2, NUM_TH}, /* T */ + {"V", 1, NUM_V}, /* V */ + {"b", 1, NUM_B}, /* b */ + {"c", 1, NUM_C}, /* c */ + {"d", 1, NUM_D}, /* d */ + {"eeee", 4, NUM_E}, /* e */ + {"fm", 2, NUM_FM}, /* f */ + {"g", 1, NUM_G}, /* g */ + {"l", 1, NUM_L}, /* l */ + {"mi", 2, NUM_MI}, /* m */ + {"pl", 2, NUM_PL}, /* p */ + {"pr", 2, NUM_PR}, + {"rn", 2, NUM_rn}, /* r */ + {"sg", 2, NUM_SG}, /* s */ + {"sp", 2, NUM_SP}, + {"s", 1, NUM_S}, + {"th", 2, NUM_th}, /* t */ + {"v", 1, NUM_V}, /* v */ + + /* last */ + {NULL, 0, 0} +}; + + +/* ---------- + * KeyWords index for DATE-TIME version + * ---------- + */ +static const int DCH_index[KeyWord_INDEX_SIZE] = { +/* +0 1 2 3 4 5 6 7 8 9 +*/ + /*---- first 0..31 chars are skipped ----*/ + + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, DCH_A_D, DCH_B_C, DCH_CC, DCH_DAY, -1, + DCH_FF1, -1, DCH_HH24, DCH_IDDD, DCH_J, -1, -1, DCH_MI, -1, DCH_OF, + DCH_P_M, DCH_Q, DCH_RM, DCH_SSSSS, DCH_TZH, DCH_US, -1, DCH_WW, -1, DCH_Y_YYY, + -1, -1, -1, -1, -1, -1, -1, DCH_a_d, DCH_b_c, DCH_cc, + DCH_day, -1, DCH_ff1, -1, DCH_hh24, DCH_iddd, DCH_j, -1, -1, DCH_mi, + -1, DCH_of, DCH_p_m, DCH_q, DCH_rm, DCH_sssss, DCH_tzh, DCH_us, -1, DCH_ww, + -1, DCH_y_yyy, -1, -1, -1, -1 + + /*---- chars over 126 are skipped ----*/ +}; + +/* ---------- + * KeyWords index for NUMBER version + * ---------- + */ +static const int NUM_index[KeyWord_INDEX_SIZE] = { +/* +0 1 2 3 4 5 6 7 8 9 +*/ + /*---- first 0..31 chars are skipped ----*/ + + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, NUM_COMMA, -1, NUM_DEC, -1, NUM_0, -1, + -1, -1, -1, -1, -1, -1, -1, NUM_9, -1, -1, + -1, -1, -1, -1, -1, -1, NUM_B, NUM_C, NUM_D, NUM_E, + NUM_FM, NUM_G, -1, -1, -1, -1, NUM_L, NUM_MI, -1, -1, + NUM_PL, -1, NUM_RN, NUM_SG, NUM_TH, -1, NUM_V, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, NUM_b, NUM_c, + NUM_d, NUM_e, NUM_fm, NUM_g, -1, -1, -1, -1, NUM_l, NUM_mi, + -1, -1, NUM_pl, -1, NUM_rn, NUM_sg, NUM_th, -1, NUM_v, -1, + -1, -1, -1, -1, -1, -1 + + /*---- chars over 126 are skipped ----*/ +}; + +/* ---------- + * Number processor struct + * ---------- + */ +typedef struct NUMProc +{ + bool is_to_char; + NUMDesc *Num; /* number description */ + + int sign, /* '-' or '+' */ + sign_wrote, /* was sign write */ + num_count, /* number of write digits */ + num_in, /* is inside number */ + num_curr, /* current position in number */ + out_pre_spaces, /* spaces before first digit */ + + read_dec, /* to_number - was read dec. point */ + read_post, /* to_number - number of dec. digit */ + read_pre; /* to_number - number non-dec. digit */ + + char *number, /* string with number */ + *number_p, /* pointer to current number position */ + *inout, /* in / out buffer */ + *inout_p, /* pointer to current inout position */ + *last_relevant, /* last relevant number after decimal point */ + + *L_negative_sign, /* Locale */ + *L_positive_sign, + *decimal, + *L_thousands_sep, + *L_currency_symbol; +} NUMProc; + +/* Return flags for DCH_from_char() */ +#define DCH_DATED 0x01 +#define DCH_TIMED 0x02 +#define DCH_ZONED 0x04 + +/* ---------- + * Functions + * ---------- + */ +static const KeyWord *index_seq_search(const char *str, const KeyWord *kw, + const int *index); +static const KeySuffix *suff_search(const char *str, const KeySuffix *suf, int type); +static bool is_separator_char(const char *str); +static void NUMDesc_prepare(NUMDesc *num, FormatNode *n); +static void parse_format(FormatNode *node, const char *str, const KeyWord *kw, + const KeySuffix *suf, const int *index, uint32 flags, NUMDesc *Num); + +static void DCH_to_char(FormatNode *node, bool is_interval, + TmToChar *in, char *out, Oid collid); +static void DCH_from_char(FormatNode *node, const char *in, TmFromChar *out, + Oid collid, bool std, Node *escontext); + +#ifdef DEBUG_TO_FROM_CHAR +static void dump_index(const KeyWord *k, const int *index); +static void dump_node(FormatNode *node, int max); +#endif + +static const char *get_th(char *num, int type); +static char *str_numth(char *dest, char *num, int type); +static int adjust_partial_year_to_2020(int year); +static int strspace_len(const char *str); +static bool from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode, + Node *escontext); +static bool from_char_set_int(int *dest, const int value, const FormatNode *node, + Node *escontext); +static int from_char_parse_int_len(int *dest, const char **src, const int len, + FormatNode *node, Node *escontext); +static int from_char_parse_int(int *dest, const char **src, FormatNode *node, + Node *escontext); +static int seq_search_ascii(const char *name, const char *const *array, int *len); +static int seq_search_localized(const char *name, char **array, int *len, + Oid collid); +static bool from_char_seq_search(int *dest, const char **src, + const char *const *array, + char **localized_array, Oid collid, + FormatNode *node, Node *escontext); +static bool do_to_timestamp(text *date_txt, text *fmt, Oid collid, bool std, + struct pg_tm *tm, fsec_t *fsec, int *fprec, + uint32 *flags, Node *escontext); +static char *fill_str(char *str, int c, int max); +static FormatNode *NUM_cache(int len, NUMDesc *Num, text *pars_str, bool *shouldFree); +static char *int_to_roman(int number); +static void NUM_prepare_locale(NUMProc *Np); +static char *get_last_relevant_decnum(char *num); +static void NUM_numpart_from_char(NUMProc *Np, int id, int input_len); +static void NUM_numpart_to_char(NUMProc *Np, int id); +static char *NUM_processor(FormatNode *node, NUMDesc *Num, char *inout, + char *number, int input_len, int to_char_out_pre_spaces, + int sign, bool is_to_char, Oid collid); +static DCHCacheEntry *DCH_cache_getnew(const char *str, bool std); +static DCHCacheEntry *DCH_cache_search(const char *str, bool std); +static DCHCacheEntry *DCH_cache_fetch(const char *str, bool std); +static NUMCacheEntry *NUM_cache_getnew(const char *str); +static NUMCacheEntry *NUM_cache_search(const char *str); +static NUMCacheEntry *NUM_cache_fetch(const char *str); + + +/* ---------- + * Fast sequential search, use index for data selection which + * go to seq. cycle (it is very fast for unwanted strings) + * (can't be used binary search in format parsing) + * ---------- + */ +static const KeyWord * +index_seq_search(const char *str, const KeyWord *kw, const int *index) +{ + int poz; + + if (!KeyWord_INDEX_FILTER(*str)) + return NULL; + + if ((poz = *(index + (*str - ' '))) > -1) + { + const KeyWord *k = kw + poz; + + do + { + if (strncmp(str, k->name, k->len) == 0) + return k; + k++; + if (!k->name) + return NULL; + } while (*str == *k->name); + } + return NULL; +} + +static const KeySuffix * +suff_search(const char *str, const KeySuffix *suf, int type) +{ + const KeySuffix *s; + + for (s = suf; s->name != NULL; s++) + { + if (s->type != type) + continue; + + if (strncmp(str, s->name, s->len) == 0) + return s; + } + return NULL; +} + +static bool +is_separator_char(const char *str) +{ + /* ASCII printable character, but not letter or digit */ + return (*str > 0x20 && *str < 0x7F && + !(*str >= 'A' && *str <= 'Z') && + !(*str >= 'a' && *str <= 'z') && + !(*str >= '0' && *str <= '9')); +} + +/* ---------- + * Prepare NUMDesc (number description struct) via FormatNode struct + * ---------- + */ +static void +NUMDesc_prepare(NUMDesc *num, FormatNode *n) +{ + if (n->type != NODE_TYPE_ACTION) + return; + + if (IS_EEEE(num) && n->key->id != NUM_E) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("\"EEEE\" must be the last pattern used"))); + + switch (n->key->id) + { + case NUM_9: + if (IS_BRACKET(num)) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("\"9\" must be ahead of \"PR\""))); + if (IS_MULTI(num)) + { + ++num->multi; + break; + } + if (IS_DECIMAL(num)) + ++num->post; + else + ++num->pre; + break; + + case NUM_0: + if (IS_BRACKET(num)) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("\"0\" must be ahead of \"PR\""))); + if (!IS_ZERO(num) && !IS_DECIMAL(num)) + { + num->flag |= NUM_F_ZERO; + num->zero_start = num->pre + 1; + } + if (!IS_DECIMAL(num)) + ++num->pre; + else + ++num->post; + + num->zero_end = num->pre + num->post; + break; + + case NUM_B: + if (num->pre == 0 && num->post == 0 && (!IS_ZERO(num))) + num->flag |= NUM_F_BLANK; + break; + + case NUM_D: + num->flag |= NUM_F_LDECIMAL; + num->need_locale = true; + /* FALLTHROUGH */ + case NUM_DEC: + if (IS_DECIMAL(num)) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("multiple decimal points"))); + if (IS_MULTI(num)) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("cannot use \"V\" and decimal point together"))); + num->flag |= NUM_F_DECIMAL; + break; + + case NUM_FM: + num->flag |= NUM_F_FILLMODE; + break; + + case NUM_S: + if (IS_LSIGN(num)) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("cannot use \"S\" twice"))); + if (IS_PLUS(num) || IS_MINUS(num) || IS_BRACKET(num)) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("cannot use \"S\" and \"PL\"/\"MI\"/\"SG\"/\"PR\" together"))); + if (!IS_DECIMAL(num)) + { + num->lsign = NUM_LSIGN_PRE; + num->pre_lsign_num = num->pre; + num->need_locale = true; + num->flag |= NUM_F_LSIGN; + } + else if (num->lsign == NUM_LSIGN_NONE) + { + num->lsign = NUM_LSIGN_POST; + num->need_locale = true; + num->flag |= NUM_F_LSIGN; + } + break; + + case NUM_MI: + if (IS_LSIGN(num)) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("cannot use \"S\" and \"MI\" together"))); + num->flag |= NUM_F_MINUS; + if (IS_DECIMAL(num)) + num->flag |= NUM_F_MINUS_POST; + break; + + case NUM_PL: + if (IS_LSIGN(num)) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("cannot use \"S\" and \"PL\" together"))); + num->flag |= NUM_F_PLUS; + if (IS_DECIMAL(num)) + num->flag |= NUM_F_PLUS_POST; + break; + + case NUM_SG: + if (IS_LSIGN(num)) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("cannot use \"S\" and \"SG\" together"))); + num->flag |= NUM_F_MINUS; + num->flag |= NUM_F_PLUS; + break; + + case NUM_PR: + if (IS_LSIGN(num) || IS_PLUS(num) || IS_MINUS(num)) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("cannot use \"PR\" and \"S\"/\"PL\"/\"MI\"/\"SG\" together"))); + num->flag |= NUM_F_BRACKET; + break; + + case NUM_rn: + case NUM_RN: + num->flag |= NUM_F_ROMAN; + break; + + case NUM_L: + case NUM_G: + num->need_locale = true; + break; + + case NUM_V: + if (IS_DECIMAL(num)) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("cannot use \"V\" and decimal point together"))); + num->flag |= NUM_F_MULTI; + break; + + case NUM_E: + if (IS_EEEE(num)) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("cannot use \"EEEE\" twice"))); + if (IS_BLANK(num) || IS_FILLMODE(num) || IS_LSIGN(num) || + IS_BRACKET(num) || IS_MINUS(num) || IS_PLUS(num) || + IS_ROMAN(num) || IS_MULTI(num)) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("\"EEEE\" is incompatible with other formats"), + errdetail("\"EEEE\" may only be used together with digit and decimal point patterns."))); + num->flag |= NUM_F_EEEE; + break; + } +} + +/* ---------- + * Format parser, search small keywords and keyword's suffixes, and make + * format-node tree. + * + * for DATE-TIME & NUMBER version + * ---------- + */ +static void +parse_format(FormatNode *node, const char *str, const KeyWord *kw, + const KeySuffix *suf, const int *index, uint32 flags, NUMDesc *Num) +{ + FormatNode *n; + +#ifdef DEBUG_TO_FROM_CHAR + elog(DEBUG_elog_output, "to_char/number(): run parser"); +#endif + + n = node; + + while (*str) + { + int suffix = 0; + const KeySuffix *s; + + /* + * Prefix + */ + if ((flags & DCH_FLAG) && + (s = suff_search(str, suf, SUFFTYPE_PREFIX)) != NULL) + { + suffix |= s->id; + if (s->len) + str += s->len; + } + + /* + * Keyword + */ + if (*str && (n->key = index_seq_search(str, kw, index)) != NULL) + { + n->type = NODE_TYPE_ACTION; + n->suffix = suffix; + if (n->key->len) + str += n->key->len; + + /* + * NUM version: Prepare global NUMDesc struct + */ + if (flags & NUM_FLAG) + NUMDesc_prepare(Num, n); + + /* + * Postfix + */ + if ((flags & DCH_FLAG) && *str && + (s = suff_search(str, suf, SUFFTYPE_POSTFIX)) != NULL) + { + n->suffix |= s->id; + if (s->len) + str += s->len; + } + + n++; + } + else if (*str) + { + int chlen; + + if ((flags & STD_FLAG) && *str != '"') + { + /* + * Standard mode, allow only following separators: "-./,':; ". + * However, we support double quotes even in standard mode + * (see below). This is our extension of standard mode. + */ + if (strchr("-./,':; ", *str) == NULL) + ereport(ERROR, + (errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("invalid datetime format separator: \"%s\"", + pnstrdup(str, pg_mblen(str))))); + + if (*str == ' ') + n->type = NODE_TYPE_SPACE; + else + n->type = NODE_TYPE_SEPARATOR; + + n->character[0] = *str; + n->character[1] = '\0'; + n->key = NULL; + n->suffix = 0; + n++; + str++; + } + else if (*str == '"') + { + /* + * Process double-quoted literal string, if any + */ + str++; + while (*str) + { + if (*str == '"') + { + str++; + break; + } + /* backslash quotes the next character, if any */ + if (*str == '\\' && *(str + 1)) + str++; + chlen = pg_mblen(str); + n->type = NODE_TYPE_CHAR; + memcpy(n->character, str, chlen); + n->character[chlen] = '\0'; + n->key = NULL; + n->suffix = 0; + n++; + str += chlen; + } + } + else + { + /* + * Outside double-quoted strings, backslash is only special if + * it immediately precedes a double quote. + */ + if (*str == '\\' && *(str + 1) == '"') + str++; + chlen = pg_mblen(str); + + if ((flags & DCH_FLAG) && is_separator_char(str)) + n->type = NODE_TYPE_SEPARATOR; + else if (isspace((unsigned char) *str)) + n->type = NODE_TYPE_SPACE; + else + n->type = NODE_TYPE_CHAR; + + memcpy(n->character, str, chlen); + n->character[chlen] = '\0'; + n->key = NULL; + n->suffix = 0; + n++; + str += chlen; + } + } + } + + n->type = NODE_TYPE_END; + n->suffix = 0; +} + +/* ---------- + * DEBUG: Dump the FormatNode Tree (debug) + * ---------- + */ +#ifdef DEBUG_TO_FROM_CHAR + +#define DUMP_THth(_suf) (S_TH(_suf) ? "TH" : (S_th(_suf) ? "th" : " ")) +#define DUMP_FM(_suf) (S_FM(_suf) ? "FM" : " ") + +static void +dump_node(FormatNode *node, int max) +{ + FormatNode *n; + int a; + + elog(DEBUG_elog_output, "to_from-char(): DUMP FORMAT"); + + for (a = 0, n = node; a <= max; n++, a++) + { + if (n->type == NODE_TYPE_ACTION) + elog(DEBUG_elog_output, "%d:\t NODE_TYPE_ACTION '%s'\t(%s,%s)", + a, n->key->name, DUMP_THth(n->suffix), DUMP_FM(n->suffix)); + else if (n->type == NODE_TYPE_CHAR) + elog(DEBUG_elog_output, "%d:\t NODE_TYPE_CHAR '%s'", + a, n->character); + else if (n->type == NODE_TYPE_END) + { + elog(DEBUG_elog_output, "%d:\t NODE_TYPE_END", a); + return; + } + else + elog(DEBUG_elog_output, "%d:\t unknown NODE!", a); + } +} +#endif /* DEBUG */ + +/***************************************************************************** + * Private utils + *****************************************************************************/ + +/* ---------- + * Return ST/ND/RD/TH for simple (1..9) numbers + * type --> 0 upper, 1 lower + * ---------- + */ +static const char * +get_th(char *num, int type) +{ + int len = strlen(num), + last; + + last = *(num + (len - 1)); + if (!isdigit((unsigned char) last)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("\"%s\" is not a number", num))); + + /* + * All "teens" (<x>1[0-9]) get 'TH/th', while <x>[02-9][123] still get + * 'ST/st', 'ND/nd', 'RD/rd', respectively + */ + if ((len > 1) && (num[len - 2] == '1')) + last = 0; + + switch (last) + { + case '1': + if (type == TH_UPPER) + return numTH[0]; + return numth[0]; + case '2': + if (type == TH_UPPER) + return numTH[1]; + return numth[1]; + case '3': + if (type == TH_UPPER) + return numTH[2]; + return numth[2]; + default: + if (type == TH_UPPER) + return numTH[3]; + return numth[3]; + } +} + +/* ---------- + * Convert string-number to ordinal string-number + * type --> 0 upper, 1 lower + * ---------- + */ +static char * +str_numth(char *dest, char *num, int type) +{ + if (dest != num) + strcpy(dest, num); + strcat(dest, get_th(num, type)); + return dest; +} + +/***************************************************************************** + * upper/lower/initcap functions + *****************************************************************************/ + +#ifdef USE_ICU + +typedef int32_t (*ICU_Convert_Func) (UChar *dest, int32_t destCapacity, + const UChar *src, int32_t srcLength, + const char *locale, + UErrorCode *pErrorCode); + +static int32_t +icu_convert_case(ICU_Convert_Func func, pg_locale_t mylocale, + UChar **buff_dest, UChar *buff_source, int32_t len_source) +{ + UErrorCode status; + int32_t len_dest; + + len_dest = len_source; /* try first with same length */ + *buff_dest = palloc(len_dest * sizeof(**buff_dest)); + status = U_ZERO_ERROR; + len_dest = func(*buff_dest, len_dest, buff_source, len_source, + mylocale->info.icu.locale, &status); + if (status == U_BUFFER_OVERFLOW_ERROR) + { + /* try again with adjusted length */ + pfree(*buff_dest); + *buff_dest = palloc(len_dest * sizeof(**buff_dest)); + status = U_ZERO_ERROR; + len_dest = func(*buff_dest, len_dest, buff_source, len_source, + mylocale->info.icu.locale, &status); + } + if (U_FAILURE(status)) + ereport(ERROR, + (errmsg("case conversion failed: %s", u_errorName(status)))); + return len_dest; +} + +static int32_t +u_strToTitle_default_BI(UChar *dest, int32_t destCapacity, + const UChar *src, int32_t srcLength, + const char *locale, + UErrorCode *pErrorCode) +{ + return u_strToTitle(dest, destCapacity, src, srcLength, + NULL, locale, pErrorCode); +} + +#endif /* USE_ICU */ + +/* + * If the system provides the needed functions for wide-character manipulation + * (which are all standardized by C99), then we implement upper/lower/initcap + * using wide-character functions, if necessary. Otherwise we use the + * traditional <ctype.h> functions, which of course will not work as desired + * in multibyte character sets. Note that in either case we are effectively + * assuming that the database character encoding matches the encoding implied + * by LC_CTYPE. + * + * If the system provides locale_t and associated functions (which are + * standardized by Open Group's XBD), we can support collations that are + * neither default nor C. The code is written to handle both combinations + * of have-wide-characters and have-locale_t, though it's rather unlikely + * a platform would have the latter without the former. + */ + +/* + * collation-aware, wide-character-aware lower function + * + * We pass the number of bytes so we can pass varlena and char* + * to this function. The result is a palloc'd, null-terminated string. + */ +char * +str_tolower(const char *buff, size_t nbytes, Oid collid) +{ + char *result; + + if (!buff) + return NULL; + + if (!OidIsValid(collid)) + { + /* + * This typically means that the parser could not resolve a conflict + * of implicit collations, so report it that way. + */ + ereport(ERROR, + (errcode(ERRCODE_INDETERMINATE_COLLATION), + errmsg("could not determine which collation to use for %s function", + "lower()"), + errhint("Use the COLLATE clause to set the collation explicitly."))); + } + + /* C/POSIX collations use this path regardless of database encoding */ + if (lc_ctype_is_c(collid)) + { + result = asc_tolower(buff, nbytes); + } + else + { + pg_locale_t mylocale; + + mylocale = pg_newlocale_from_collation(collid); + +#ifdef USE_ICU + if (mylocale && mylocale->provider == COLLPROVIDER_ICU) + { + int32_t len_uchar; + int32_t len_conv; + UChar *buff_uchar; + UChar *buff_conv; + + len_uchar = icu_to_uchar(&buff_uchar, buff, nbytes); + len_conv = icu_convert_case(u_strToLower, mylocale, + &buff_conv, buff_uchar, len_uchar); + icu_from_uchar(&result, buff_conv, len_conv); + pfree(buff_uchar); + pfree(buff_conv); + } + else +#endif + { + if (pg_database_encoding_max_length() > 1) + { + wchar_t *workspace; + size_t curr_char; + size_t result_size; + + /* Overflow paranoia */ + if ((nbytes + 1) > (INT_MAX / sizeof(wchar_t))) + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"))); + + /* Output workspace cannot have more codes than input bytes */ + workspace = (wchar_t *) palloc((nbytes + 1) * sizeof(wchar_t)); + + char2wchar(workspace, nbytes + 1, buff, nbytes, mylocale); + + for (curr_char = 0; workspace[curr_char] != 0; curr_char++) + { +#ifdef HAVE_LOCALE_T + if (mylocale) + workspace[curr_char] = towlower_l(workspace[curr_char], mylocale->info.lt); + else +#endif + workspace[curr_char] = towlower(workspace[curr_char]); + } + + /* + * Make result large enough; case change might change number + * of bytes + */ + result_size = curr_char * pg_database_encoding_max_length() + 1; + result = palloc(result_size); + + wchar2char(result, workspace, result_size, mylocale); + pfree(workspace); + } + else + { + char *p; + + result = pnstrdup(buff, nbytes); + + /* + * Note: we assume that tolower_l() will not be so broken as + * to need an isupper_l() guard test. When using the default + * collation, we apply the traditional Postgres behavior that + * forces ASCII-style treatment of I/i, but in non-default + * collations you get exactly what the collation says. + */ + for (p = result; *p; p++) + { +#ifdef HAVE_LOCALE_T + if (mylocale) + *p = tolower_l((unsigned char) *p, mylocale->info.lt); + else +#endif + *p = pg_tolower((unsigned char) *p); + } + } + } + } + + return result; +} + +/* + * collation-aware, wide-character-aware upper function + * + * We pass the number of bytes so we can pass varlena and char* + * to this function. The result is a palloc'd, null-terminated string. + */ +char * +str_toupper(const char *buff, size_t nbytes, Oid collid) +{ + char *result; + + if (!buff) + return NULL; + + if (!OidIsValid(collid)) + { + /* + * This typically means that the parser could not resolve a conflict + * of implicit collations, so report it that way. + */ + ereport(ERROR, + (errcode(ERRCODE_INDETERMINATE_COLLATION), + errmsg("could not determine which collation to use for %s function", + "upper()"), + errhint("Use the COLLATE clause to set the collation explicitly."))); + } + + /* C/POSIX collations use this path regardless of database encoding */ + if (lc_ctype_is_c(collid)) + { + result = asc_toupper(buff, nbytes); + } + else + { + pg_locale_t mylocale; + + mylocale = pg_newlocale_from_collation(collid); + +#ifdef USE_ICU + if (mylocale && mylocale->provider == COLLPROVIDER_ICU) + { + int32_t len_uchar, + len_conv; + UChar *buff_uchar; + UChar *buff_conv; + + len_uchar = icu_to_uchar(&buff_uchar, buff, nbytes); + len_conv = icu_convert_case(u_strToUpper, mylocale, + &buff_conv, buff_uchar, len_uchar); + icu_from_uchar(&result, buff_conv, len_conv); + pfree(buff_uchar); + pfree(buff_conv); + } + else +#endif + { + if (pg_database_encoding_max_length() > 1) + { + wchar_t *workspace; + size_t curr_char; + size_t result_size; + + /* Overflow paranoia */ + if ((nbytes + 1) > (INT_MAX / sizeof(wchar_t))) + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"))); + + /* Output workspace cannot have more codes than input bytes */ + workspace = (wchar_t *) palloc((nbytes + 1) * sizeof(wchar_t)); + + char2wchar(workspace, nbytes + 1, buff, nbytes, mylocale); + + for (curr_char = 0; workspace[curr_char] != 0; curr_char++) + { +#ifdef HAVE_LOCALE_T + if (mylocale) + workspace[curr_char] = towupper_l(workspace[curr_char], mylocale->info.lt); + else +#endif + workspace[curr_char] = towupper(workspace[curr_char]); + } + + /* + * Make result large enough; case change might change number + * of bytes + */ + result_size = curr_char * pg_database_encoding_max_length() + 1; + result = palloc(result_size); + + wchar2char(result, workspace, result_size, mylocale); + pfree(workspace); + } + else + { + char *p; + + result = pnstrdup(buff, nbytes); + + /* + * Note: we assume that toupper_l() will not be so broken as + * to need an islower_l() guard test. When using the default + * collation, we apply the traditional Postgres behavior that + * forces ASCII-style treatment of I/i, but in non-default + * collations you get exactly what the collation says. + */ + for (p = result; *p; p++) + { +#ifdef HAVE_LOCALE_T + if (mylocale) + *p = toupper_l((unsigned char) *p, mylocale->info.lt); + else +#endif + *p = pg_toupper((unsigned char) *p); + } + } + } + } + + return result; +} + +/* + * collation-aware, wide-character-aware initcap function + * + * We pass the number of bytes so we can pass varlena and char* + * to this function. The result is a palloc'd, null-terminated string. + */ +char * +str_initcap(const char *buff, size_t nbytes, Oid collid) +{ + char *result; + int wasalnum = false; + + if (!buff) + return NULL; + + if (!OidIsValid(collid)) + { + /* + * This typically means that the parser could not resolve a conflict + * of implicit collations, so report it that way. + */ + ereport(ERROR, + (errcode(ERRCODE_INDETERMINATE_COLLATION), + errmsg("could not determine which collation to use for %s function", + "initcap()"), + errhint("Use the COLLATE clause to set the collation explicitly."))); + } + + /* C/POSIX collations use this path regardless of database encoding */ + if (lc_ctype_is_c(collid)) + { + result = asc_initcap(buff, nbytes); + } + else + { + pg_locale_t mylocale; + + mylocale = pg_newlocale_from_collation(collid); + +#ifdef USE_ICU + if (mylocale && mylocale->provider == COLLPROVIDER_ICU) + { + int32_t len_uchar, + len_conv; + UChar *buff_uchar; + UChar *buff_conv; + + len_uchar = icu_to_uchar(&buff_uchar, buff, nbytes); + len_conv = icu_convert_case(u_strToTitle_default_BI, mylocale, + &buff_conv, buff_uchar, len_uchar); + icu_from_uchar(&result, buff_conv, len_conv); + pfree(buff_uchar); + pfree(buff_conv); + } + else +#endif + { + if (pg_database_encoding_max_length() > 1) + { + wchar_t *workspace; + size_t curr_char; + size_t result_size; + + /* Overflow paranoia */ + if ((nbytes + 1) > (INT_MAX / sizeof(wchar_t))) + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"))); + + /* Output workspace cannot have more codes than input bytes */ + workspace = (wchar_t *) palloc((nbytes + 1) * sizeof(wchar_t)); + + char2wchar(workspace, nbytes + 1, buff, nbytes, mylocale); + + for (curr_char = 0; workspace[curr_char] != 0; curr_char++) + { +#ifdef HAVE_LOCALE_T + if (mylocale) + { + if (wasalnum) + workspace[curr_char] = towlower_l(workspace[curr_char], mylocale->info.lt); + else + workspace[curr_char] = towupper_l(workspace[curr_char], mylocale->info.lt); + wasalnum = iswalnum_l(workspace[curr_char], mylocale->info.lt); + } + else +#endif + { + if (wasalnum) + workspace[curr_char] = towlower(workspace[curr_char]); + else + workspace[curr_char] = towupper(workspace[curr_char]); + wasalnum = iswalnum(workspace[curr_char]); + } + } + + /* + * Make result large enough; case change might change number + * of bytes + */ + result_size = curr_char * pg_database_encoding_max_length() + 1; + result = palloc(result_size); + + wchar2char(result, workspace, result_size, mylocale); + pfree(workspace); + } + else + { + char *p; + + result = pnstrdup(buff, nbytes); + + /* + * Note: we assume that toupper_l()/tolower_l() will not be so + * broken as to need guard tests. When using the default + * collation, we apply the traditional Postgres behavior that + * forces ASCII-style treatment of I/i, but in non-default + * collations you get exactly what the collation says. + */ + for (p = result; *p; p++) + { +#ifdef HAVE_LOCALE_T + if (mylocale) + { + if (wasalnum) + *p = tolower_l((unsigned char) *p, mylocale->info.lt); + else + *p = toupper_l((unsigned char) *p, mylocale->info.lt); + wasalnum = isalnum_l((unsigned char) *p, mylocale->info.lt); + } + else +#endif + { + if (wasalnum) + *p = pg_tolower((unsigned char) *p); + else + *p = pg_toupper((unsigned char) *p); + wasalnum = isalnum((unsigned char) *p); + } + } + } + } + } + + return result; +} + +/* + * ASCII-only lower function + * + * We pass the number of bytes so we can pass varlena and char* + * to this function. The result is a palloc'd, null-terminated string. + */ +char * +asc_tolower(const char *buff, size_t nbytes) +{ + char *result; + char *p; + + if (!buff) + return NULL; + + result = pnstrdup(buff, nbytes); + + for (p = result; *p; p++) + *p = pg_ascii_tolower((unsigned char) *p); + + return result; +} + +/* + * ASCII-only upper function + * + * We pass the number of bytes so we can pass varlena and char* + * to this function. The result is a palloc'd, null-terminated string. + */ +char * +asc_toupper(const char *buff, size_t nbytes) +{ + char *result; + char *p; + + if (!buff) + return NULL; + + result = pnstrdup(buff, nbytes); + + for (p = result; *p; p++) + *p = pg_ascii_toupper((unsigned char) *p); + + return result; +} + +/* + * ASCII-only initcap function + * + * We pass the number of bytes so we can pass varlena and char* + * to this function. The result is a palloc'd, null-terminated string. + */ +char * +asc_initcap(const char *buff, size_t nbytes) +{ + char *result; + char *p; + int wasalnum = false; + + if (!buff) + return NULL; + + result = pnstrdup(buff, nbytes); + + for (p = result; *p; p++) + { + char c; + + if (wasalnum) + *p = c = pg_ascii_tolower((unsigned char) *p); + else + *p = c = pg_ascii_toupper((unsigned char) *p); + /* we don't trust isalnum() here */ + wasalnum = ((c >= 'A' && c <= 'Z') || + (c >= 'a' && c <= 'z') || + (c >= '0' && c <= '9')); + } + + return result; +} + +/* convenience routines for when the input is null-terminated */ + +static char * +str_tolower_z(const char *buff, Oid collid) +{ + return str_tolower(buff, strlen(buff), collid); +} + +static char * +str_toupper_z(const char *buff, Oid collid) +{ + return str_toupper(buff, strlen(buff), collid); +} + +static char * +str_initcap_z(const char *buff, Oid collid) +{ + return str_initcap(buff, strlen(buff), collid); +} + +static char * +asc_tolower_z(const char *buff) +{ + return asc_tolower(buff, strlen(buff)); +} + +static char * +asc_toupper_z(const char *buff) +{ + return asc_toupper(buff, strlen(buff)); +} + +/* asc_initcap_z is not currently needed */ + + +/* ---------- + * Skip TM / th in FROM_CHAR + * + * If S_THth is on, skip two chars, assuming there are two available + * ---------- + */ +#define SKIP_THth(ptr, _suf) \ + do { \ + if (S_THth(_suf)) \ + { \ + if (*(ptr)) (ptr) += pg_mblen(ptr); \ + if (*(ptr)) (ptr) += pg_mblen(ptr); \ + } \ + } while (0) + + +#ifdef DEBUG_TO_FROM_CHAR +/* ----------- + * DEBUG: Call for debug and for index checking; (Show ASCII char + * and defined keyword for each used position + * ---------- + */ +static void +dump_index(const KeyWord *k, const int *index) +{ + int i, + count = 0, + free_i = 0; + + elog(DEBUG_elog_output, "TO-FROM_CHAR: Dump KeyWord Index:"); + + for (i = 0; i < KeyWord_INDEX_SIZE; i++) + { + if (index[i] != -1) + { + elog(DEBUG_elog_output, "\t%c: %s, ", i + 32, k[index[i]].name); + count++; + } + else + { + free_i++; + elog(DEBUG_elog_output, "\t(%d) %c %d", i, i + 32, index[i]); + } + } + elog(DEBUG_elog_output, "\n\t\tUsed positions: %d,\n\t\tFree positions: %d", + count, free_i); +} +#endif /* DEBUG */ + +/* ---------- + * Return true if next format picture is not digit value + * ---------- + */ +static bool +is_next_separator(FormatNode *n) +{ + if (n->type == NODE_TYPE_END) + return false; + + if (n->type == NODE_TYPE_ACTION && S_THth(n->suffix)) + return true; + + /* + * Next node + */ + n++; + + /* end of format string is treated like a non-digit separator */ + if (n->type == NODE_TYPE_END) + return true; + + if (n->type == NODE_TYPE_ACTION) + { + if (n->key->is_digit) + return false; + + return true; + } + else if (n->character[1] == '\0' && + isdigit((unsigned char) n->character[0])) + return false; + + return true; /* some non-digit input (separator) */ +} + + +static int +adjust_partial_year_to_2020(int year) +{ + /* + * Adjust all dates toward 2020; this is effectively what happens when we + * assume '70' is 1970 and '69' is 2069. + */ + /* Force 0-69 into the 2000's */ + if (year < 70) + return year + 2000; + /* Force 70-99 into the 1900's */ + else if (year < 100) + return year + 1900; + /* Force 100-519 into the 2000's */ + else if (year < 520) + return year + 2000; + /* Force 520-999 into the 1000's */ + else if (year < 1000) + return year + 1000; + else + return year; +} + + +static int +strspace_len(const char *str) +{ + int len = 0; + + while (*str && isspace((unsigned char) *str)) + { + str++; + len++; + } + return len; +} + +/* + * Set the date mode of a from-char conversion. + * + * Puke if the date mode has already been set, and the caller attempts to set + * it to a conflicting mode. + * + * Returns true on success, false on failure (if escontext points to an + * ErrorSaveContext; otherwise errors are thrown). + */ +static bool +from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode, + Node *escontext) +{ + if (mode != FROM_CHAR_DATE_NONE) + { + if (tmfc->mode == FROM_CHAR_DATE_NONE) + tmfc->mode = mode; + else if (tmfc->mode != mode) + ereturn(escontext, false, + (errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("invalid combination of date conventions"), + errhint("Do not mix Gregorian and ISO week date " + "conventions in a formatting template."))); + } + return true; +} + +/* + * Set the integer pointed to by 'dest' to the given value. + * + * Puke if the destination integer has previously been set to some other + * non-zero value. + * + * Returns true on success, false on failure (if escontext points to an + * ErrorSaveContext; otherwise errors are thrown). + */ +static bool +from_char_set_int(int *dest, const int value, const FormatNode *node, + Node *escontext) +{ + if (*dest != 0 && *dest != value) + ereturn(escontext, false, + (errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("conflicting values for \"%s\" field in formatting string", + node->key->name), + errdetail("This value contradicts a previous setting " + "for the same field type."))); + *dest = value; + return true; +} + +/* + * Read a single integer from the source string, into the int pointed to by + * 'dest'. If 'dest' is NULL, the result is discarded. + * + * In fixed-width mode (the node does not have the FM suffix), consume at most + * 'len' characters. However, any leading whitespace isn't counted in 'len'. + * + * We use strtol() to recover the integer value from the source string, in + * accordance with the given FormatNode. + * + * If the conversion completes successfully, src will have been advanced to + * point at the character immediately following the last character used in the + * conversion. + * + * Returns the number of characters consumed, or -1 on error (if escontext + * points to an ErrorSaveContext; otherwise errors are thrown). + * + * Note that from_char_parse_int() provides a more convenient wrapper where + * the length of the field is the same as the length of the format keyword (as + * with DD and MI). + */ +static int +from_char_parse_int_len(int *dest, const char **src, const int len, FormatNode *node, + Node *escontext) +{ + long result; + char copy[DCH_MAX_ITEM_SIZ + 1]; + const char *init = *src; + int used; + + /* + * Skip any whitespace before parsing the integer. + */ + *src += strspace_len(*src); + + Assert(len <= DCH_MAX_ITEM_SIZ); + used = (int) strlcpy(copy, *src, len + 1); + + if (S_FM(node->suffix) || is_next_separator(node)) + { + /* + * This node is in Fill Mode, or the next node is known to be a + * non-digit value, so we just slurp as many characters as we can get. + */ + char *endptr; + + errno = 0; + result = strtol(init, &endptr, 10); + *src = endptr; + } + else + { + /* + * We need to pull exactly the number of characters given in 'len' out + * of the string, and convert those. + */ + char *last; + + if (used < len) + ereturn(escontext, -1, + (errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("source string too short for \"%s\" formatting field", + node->key->name), + errdetail("Field requires %d characters, but only %d remain.", + len, used), + errhint("If your source string is not fixed-width, " + "try using the \"FM\" modifier."))); + + errno = 0; + result = strtol(copy, &last, 10); + used = last - copy; + + if (used > 0 && used < len) + ereturn(escontext, -1, + (errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("invalid value \"%s\" for \"%s\"", + copy, node->key->name), + errdetail("Field requires %d characters, but only %d could be parsed.", + len, used), + errhint("If your source string is not fixed-width, " + "try using the \"FM\" modifier."))); + + *src += used; + } + + if (*src == init) + ereturn(escontext, -1, + (errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("invalid value \"%s\" for \"%s\"", + copy, node->key->name), + errdetail("Value must be an integer."))); + + if (errno == ERANGE || result < INT_MIN || result > INT_MAX) + ereturn(escontext, -1, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("value for \"%s\" in source string is out of range", + node->key->name), + errdetail("Value must be in the range %d to %d.", + INT_MIN, INT_MAX))); + + if (dest != NULL) + { + if (!from_char_set_int(dest, (int) result, node, escontext)) + return -1; + } + + return *src - init; +} + +/* + * Call from_char_parse_int_len(), using the length of the format keyword as + * the expected length of the field. + * + * Don't call this function if the field differs in length from the format + * keyword (as with HH24; the keyword length is 4, but the field length is 2). + * In such cases, call from_char_parse_int_len() instead to specify the + * required length explicitly. + */ +static int +from_char_parse_int(int *dest, const char **src, FormatNode *node, + Node *escontext) +{ + return from_char_parse_int_len(dest, src, node->key->len, node, escontext); +} + +/* + * Sequentially search null-terminated "array" for a case-insensitive match + * to the initial character(s) of "name". + * + * Returns array index of match, or -1 for no match. + * + * *len is set to the length of the match, or 0 for no match. + * + * Case-insensitivity is defined per pg_ascii_tolower, so this is only + * suitable for comparisons to ASCII strings. + */ +static int +seq_search_ascii(const char *name, const char *const *array, int *len) +{ + unsigned char firstc; + const char *const *a; + + *len = 0; + + /* empty string can't match anything */ + if (!*name) + return -1; + + /* we handle first char specially to gain some speed */ + firstc = pg_ascii_tolower((unsigned char) *name); + + for (a = array; *a != NULL; a++) + { + const char *p; + const char *n; + + /* compare first chars */ + if (pg_ascii_tolower((unsigned char) **a) != firstc) + continue; + + /* compare rest of string */ + for (p = *a + 1, n = name + 1;; p++, n++) + { + /* return success if we matched whole array entry */ + if (*p == '\0') + { + *len = n - name; + return a - array; + } + /* else, must have another character in "name" ... */ + if (*n == '\0') + break; + /* ... and it must match */ + if (pg_ascii_tolower((unsigned char) *p) != + pg_ascii_tolower((unsigned char) *n)) + break; + } + } + + return -1; +} + +/* + * Sequentially search an array of possibly non-English words for + * a case-insensitive match to the initial character(s) of "name". + * + * This has the same API as seq_search_ascii(), but we use a more general + * case-folding transformation to achieve case-insensitivity. Case folding + * is done per the rules of the collation identified by "collid". + * + * The array is treated as const, but we don't declare it that way because + * the arrays exported by pg_locale.c aren't const. + */ +static int +seq_search_localized(const char *name, char **array, int *len, Oid collid) +{ + char **a; + char *upper_name; + char *lower_name; + + *len = 0; + + /* empty string can't match anything */ + if (!*name) + return -1; + + /* + * The case-folding processing done below is fairly expensive, so before + * doing that, make a quick pass to see if there is an exact match. + */ + for (a = array; *a != NULL; a++) + { + int element_len = strlen(*a); + + if (strncmp(name, *a, element_len) == 0) + { + *len = element_len; + return a - array; + } + } + + /* + * Fold to upper case, then to lower case, so that we can match reliably + * even in languages in which case conversions are not injective. + */ + upper_name = str_toupper(unconstify(char *, name), strlen(name), collid); + lower_name = str_tolower(upper_name, strlen(upper_name), collid); + pfree(upper_name); + + for (a = array; *a != NULL; a++) + { + char *upper_element; + char *lower_element; + int element_len; + + /* Likewise upper/lower-case array element */ + upper_element = str_toupper(*a, strlen(*a), collid); + lower_element = str_tolower(upper_element, strlen(upper_element), + collid); + pfree(upper_element); + element_len = strlen(lower_element); + + /* Match? */ + if (strncmp(lower_name, lower_element, element_len) == 0) + { + *len = element_len; + pfree(lower_element); + pfree(lower_name); + return a - array; + } + pfree(lower_element); + } + + pfree(lower_name); + return -1; +} + +/* + * Perform a sequential search in 'array' (or 'localized_array', if that's + * not NULL) for an entry matching the first character(s) of the 'src' + * string case-insensitively. + * + * The 'array' is presumed to be English words (all-ASCII), but + * if 'localized_array' is supplied, that might be non-English + * so we need a more expensive case-folding transformation + * (which will follow the rules of the collation 'collid'). + * + * If a match is found, copy the array index of the match into the integer + * pointed to by 'dest' and advance 'src' to the end of the part of the string + * which matched. + * + * Returns true on match, false on failure (if escontext points to an + * ErrorSaveContext; otherwise errors are thrown). + * + * 'node' is used only for error reports: node->key->name identifies the + * field type we were searching for. + */ +static bool +from_char_seq_search(int *dest, const char **src, const char *const *array, + char **localized_array, Oid collid, + FormatNode *node, Node *escontext) +{ + int len; + + if (localized_array == NULL) + *dest = seq_search_ascii(*src, array, &len); + else + *dest = seq_search_localized(*src, localized_array, &len, collid); + + if (len <= 0) + { + /* + * In the error report, truncate the string at the next whitespace (if + * any) to avoid including irrelevant data. + */ + char *copy = pstrdup(*src); + char *c; + + for (c = copy; *c; c++) + { + if (scanner_isspace(*c)) + { + *c = '\0'; + break; + } + } + + ereturn(escontext, false, + (errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("invalid value \"%s\" for \"%s\"", + copy, node->key->name), + errdetail("The given value did not match any of " + "the allowed values for this field."))); + } + *src += len; + return true; +} + +/* ---------- + * Process a TmToChar struct as denoted by a list of FormatNodes. + * The formatted data is written to the string pointed to by 'out'. + * ---------- + */ +static void +DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid collid) +{ + FormatNode *n; + char *s; + struct fmt_tm *tm = &in->tm; + int i; + + /* cache localized days and months */ + cache_locale_time(); + + s = out; + for (n = node; n->type != NODE_TYPE_END; n++) + { + if (n->type != NODE_TYPE_ACTION) + { + strcpy(s, n->character); + s += strlen(s); + continue; + } + + switch (n->key->id) + { + case DCH_A_M: + case DCH_P_M: + strcpy(s, (tm->tm_hour % HOURS_PER_DAY >= HOURS_PER_DAY / 2) + ? P_M_STR : A_M_STR); + s += strlen(s); + break; + case DCH_AM: + case DCH_PM: + strcpy(s, (tm->tm_hour % HOURS_PER_DAY >= HOURS_PER_DAY / 2) + ? PM_STR : AM_STR); + s += strlen(s); + break; + case DCH_a_m: + case DCH_p_m: + strcpy(s, (tm->tm_hour % HOURS_PER_DAY >= HOURS_PER_DAY / 2) + ? p_m_STR : a_m_STR); + s += strlen(s); + break; + case DCH_am: + case DCH_pm: + strcpy(s, (tm->tm_hour % HOURS_PER_DAY >= HOURS_PER_DAY / 2) + ? pm_STR : am_STR); + s += strlen(s); + break; + case DCH_HH: + case DCH_HH12: + + /* + * display time as shown on a 12-hour clock, even for + * intervals + */ + sprintf(s, "%0*lld", S_FM(n->suffix) ? 0 : (tm->tm_hour >= 0) ? 2 : 3, + tm->tm_hour % (HOURS_PER_DAY / 2) == 0 ? + (long long) (HOURS_PER_DAY / 2) : + (long long) (tm->tm_hour % (HOURS_PER_DAY / 2))); + if (S_THth(n->suffix)) + str_numth(s, s, S_TH_TYPE(n->suffix)); + s += strlen(s); + break; + case DCH_HH24: + sprintf(s, "%0*lld", S_FM(n->suffix) ? 0 : (tm->tm_hour >= 0) ? 2 : 3, + (long long) tm->tm_hour); + if (S_THth(n->suffix)) + str_numth(s, s, S_TH_TYPE(n->suffix)); + s += strlen(s); + break; + case DCH_MI: + sprintf(s, "%0*d", S_FM(n->suffix) ? 0 : (tm->tm_min >= 0) ? 2 : 3, + tm->tm_min); + if (S_THth(n->suffix)) + str_numth(s, s, S_TH_TYPE(n->suffix)); + s += strlen(s); + break; + case DCH_SS: + sprintf(s, "%0*d", S_FM(n->suffix) ? 0 : (tm->tm_sec >= 0) ? 2 : 3, + tm->tm_sec); + if (S_THth(n->suffix)) + str_numth(s, s, S_TH_TYPE(n->suffix)); + s += strlen(s); + break; + +#define DCH_to_char_fsec(frac_fmt, frac_val) \ + sprintf(s, frac_fmt, (int) (frac_val)); \ + if (S_THth(n->suffix)) \ + str_numth(s, s, S_TH_TYPE(n->suffix)); \ + s += strlen(s) + + case DCH_FF1: /* tenth of second */ + DCH_to_char_fsec("%01d", in->fsec / 100000); + break; + case DCH_FF2: /* hundredth of second */ + DCH_to_char_fsec("%02d", in->fsec / 10000); + break; + case DCH_FF3: + case DCH_MS: /* millisecond */ + DCH_to_char_fsec("%03d", in->fsec / 1000); + break; + case DCH_FF4: /* tenth of a millisecond */ + DCH_to_char_fsec("%04d", in->fsec / 100); + break; + case DCH_FF5: /* hundredth of a millisecond */ + DCH_to_char_fsec("%05d", in->fsec / 10); + break; + case DCH_FF6: + case DCH_US: /* microsecond */ + DCH_to_char_fsec("%06d", in->fsec); + break; +#undef DCH_to_char_fsec + case DCH_SSSS: + sprintf(s, "%lld", + (long long) (tm->tm_hour * SECS_PER_HOUR + + tm->tm_min * SECS_PER_MINUTE + + tm->tm_sec)); + if (S_THth(n->suffix)) + str_numth(s, s, S_TH_TYPE(n->suffix)); + s += strlen(s); + break; + case DCH_tz: + INVALID_FOR_INTERVAL; + if (tmtcTzn(in)) + { + /* We assume here that timezone names aren't localized */ + char *p = asc_tolower_z(tmtcTzn(in)); + + strcpy(s, p); + pfree(p); + s += strlen(s); + } + break; + case DCH_TZ: + INVALID_FOR_INTERVAL; + if (tmtcTzn(in)) + { + strcpy(s, tmtcTzn(in)); + s += strlen(s); + } + break; + case DCH_TZH: + INVALID_FOR_INTERVAL; + sprintf(s, "%c%02d", + (tm->tm_gmtoff >= 0) ? '+' : '-', + abs((int) tm->tm_gmtoff) / SECS_PER_HOUR); + s += strlen(s); + break; + case DCH_TZM: + INVALID_FOR_INTERVAL; + sprintf(s, "%02d", + (abs((int) tm->tm_gmtoff) % SECS_PER_HOUR) / SECS_PER_MINUTE); + s += strlen(s); + break; + case DCH_OF: + INVALID_FOR_INTERVAL; + sprintf(s, "%c%0*d", + (tm->tm_gmtoff >= 0) ? '+' : '-', + S_FM(n->suffix) ? 0 : 2, + abs((int) tm->tm_gmtoff) / SECS_PER_HOUR); + s += strlen(s); + if (abs((int) tm->tm_gmtoff) % SECS_PER_HOUR != 0) + { + sprintf(s, ":%02d", + (abs((int) tm->tm_gmtoff) % SECS_PER_HOUR) / SECS_PER_MINUTE); + s += strlen(s); + } + break; + case DCH_A_D: + case DCH_B_C: + INVALID_FOR_INTERVAL; + strcpy(s, (tm->tm_year <= 0 ? B_C_STR : A_D_STR)); + s += strlen(s); + break; + case DCH_AD: + case DCH_BC: + INVALID_FOR_INTERVAL; + strcpy(s, (tm->tm_year <= 0 ? BC_STR : AD_STR)); + s += strlen(s); + break; + case DCH_a_d: + case DCH_b_c: + INVALID_FOR_INTERVAL; + strcpy(s, (tm->tm_year <= 0 ? b_c_STR : a_d_STR)); + s += strlen(s); + break; + case DCH_ad: + case DCH_bc: + INVALID_FOR_INTERVAL; + strcpy(s, (tm->tm_year <= 0 ? bc_STR : ad_STR)); + s += strlen(s); + break; + case DCH_MONTH: + INVALID_FOR_INTERVAL; + if (!tm->tm_mon) + break; + if (S_TM(n->suffix)) + { + char *str = str_toupper_z(localized_full_months[tm->tm_mon - 1], collid); + + if (strlen(str) <= (n->key->len + TM_SUFFIX_LEN) * DCH_MAX_ITEM_SIZ) + strcpy(s, str); + else + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("localized string format value too long"))); + } + else + sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -9, + asc_toupper_z(months_full[tm->tm_mon - 1])); + s += strlen(s); + break; + case DCH_Month: + INVALID_FOR_INTERVAL; + if (!tm->tm_mon) + break; + if (S_TM(n->suffix)) + { + char *str = str_initcap_z(localized_full_months[tm->tm_mon - 1], collid); + + if (strlen(str) <= (n->key->len + TM_SUFFIX_LEN) * DCH_MAX_ITEM_SIZ) + strcpy(s, str); + else + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("localized string format value too long"))); + } + else + sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -9, + months_full[tm->tm_mon - 1]); + s += strlen(s); + break; + case DCH_month: + INVALID_FOR_INTERVAL; + if (!tm->tm_mon) + break; + if (S_TM(n->suffix)) + { + char *str = str_tolower_z(localized_full_months[tm->tm_mon - 1], collid); + + if (strlen(str) <= (n->key->len + TM_SUFFIX_LEN) * DCH_MAX_ITEM_SIZ) + strcpy(s, str); + else + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("localized string format value too long"))); + } + else + sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -9, + asc_tolower_z(months_full[tm->tm_mon - 1])); + s += strlen(s); + break; + case DCH_MON: + INVALID_FOR_INTERVAL; + if (!tm->tm_mon) + break; + if (S_TM(n->suffix)) + { + char *str = str_toupper_z(localized_abbrev_months[tm->tm_mon - 1], collid); + + if (strlen(str) <= (n->key->len + TM_SUFFIX_LEN) * DCH_MAX_ITEM_SIZ) + strcpy(s, str); + else + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("localized string format value too long"))); + } + else + strcpy(s, asc_toupper_z(months[tm->tm_mon - 1])); + s += strlen(s); + break; + case DCH_Mon: + INVALID_FOR_INTERVAL; + if (!tm->tm_mon) + break; + if (S_TM(n->suffix)) + { + char *str = str_initcap_z(localized_abbrev_months[tm->tm_mon - 1], collid); + + if (strlen(str) <= (n->key->len + TM_SUFFIX_LEN) * DCH_MAX_ITEM_SIZ) + strcpy(s, str); + else + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("localized string format value too long"))); + } + else + strcpy(s, months[tm->tm_mon - 1]); + s += strlen(s); + break; + case DCH_mon: + INVALID_FOR_INTERVAL; + if (!tm->tm_mon) + break; + if (S_TM(n->suffix)) + { + char *str = str_tolower_z(localized_abbrev_months[tm->tm_mon - 1], collid); + + if (strlen(str) <= (n->key->len + TM_SUFFIX_LEN) * DCH_MAX_ITEM_SIZ) + strcpy(s, str); + else + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("localized string format value too long"))); + } + else + strcpy(s, asc_tolower_z(months[tm->tm_mon - 1])); + s += strlen(s); + break; + case DCH_MM: + sprintf(s, "%0*d", S_FM(n->suffix) ? 0 : (tm->tm_mon >= 0) ? 2 : 3, + tm->tm_mon); + if (S_THth(n->suffix)) + str_numth(s, s, S_TH_TYPE(n->suffix)); + s += strlen(s); + break; + case DCH_DAY: + INVALID_FOR_INTERVAL; + if (S_TM(n->suffix)) + { + char *str = str_toupper_z(localized_full_days[tm->tm_wday], collid); + + if (strlen(str) <= (n->key->len + TM_SUFFIX_LEN) * DCH_MAX_ITEM_SIZ) + strcpy(s, str); + else + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("localized string format value too long"))); + } + else + sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -9, + asc_toupper_z(days[tm->tm_wday])); + s += strlen(s); + break; + case DCH_Day: + INVALID_FOR_INTERVAL; + if (S_TM(n->suffix)) + { + char *str = str_initcap_z(localized_full_days[tm->tm_wday], collid); + + if (strlen(str) <= (n->key->len + TM_SUFFIX_LEN) * DCH_MAX_ITEM_SIZ) + strcpy(s, str); + else + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("localized string format value too long"))); + } + else + sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -9, + days[tm->tm_wday]); + s += strlen(s); + break; + case DCH_day: + INVALID_FOR_INTERVAL; + if (S_TM(n->suffix)) + { + char *str = str_tolower_z(localized_full_days[tm->tm_wday], collid); + + if (strlen(str) <= (n->key->len + TM_SUFFIX_LEN) * DCH_MAX_ITEM_SIZ) + strcpy(s, str); + else + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("localized string format value too long"))); + } + else + sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -9, + asc_tolower_z(days[tm->tm_wday])); + s += strlen(s); + break; + case DCH_DY: + INVALID_FOR_INTERVAL; + if (S_TM(n->suffix)) + { + char *str = str_toupper_z(localized_abbrev_days[tm->tm_wday], collid); + + if (strlen(str) <= (n->key->len + TM_SUFFIX_LEN) * DCH_MAX_ITEM_SIZ) + strcpy(s, str); + else + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("localized string format value too long"))); + } + else + strcpy(s, asc_toupper_z(days_short[tm->tm_wday])); + s += strlen(s); + break; + case DCH_Dy: + INVALID_FOR_INTERVAL; + if (S_TM(n->suffix)) + { + char *str = str_initcap_z(localized_abbrev_days[tm->tm_wday], collid); + + if (strlen(str) <= (n->key->len + TM_SUFFIX_LEN) * DCH_MAX_ITEM_SIZ) + strcpy(s, str); + else + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("localized string format value too long"))); + } + else + strcpy(s, days_short[tm->tm_wday]); + s += strlen(s); + break; + case DCH_dy: + INVALID_FOR_INTERVAL; + if (S_TM(n->suffix)) + { + char *str = str_tolower_z(localized_abbrev_days[tm->tm_wday], collid); + + if (strlen(str) <= (n->key->len + TM_SUFFIX_LEN) * DCH_MAX_ITEM_SIZ) + strcpy(s, str); + else + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("localized string format value too long"))); + } + else + strcpy(s, asc_tolower_z(days_short[tm->tm_wday])); + s += strlen(s); + break; + case DCH_DDD: + case DCH_IDDD: + sprintf(s, "%0*d", S_FM(n->suffix) ? 0 : 3, + (n->key->id == DCH_DDD) ? + tm->tm_yday : + date2isoyearday(tm->tm_year, tm->tm_mon, tm->tm_mday)); + if (S_THth(n->suffix)) + str_numth(s, s, S_TH_TYPE(n->suffix)); + s += strlen(s); + break; + case DCH_DD: + sprintf(s, "%0*d", S_FM(n->suffix) ? 0 : 2, tm->tm_mday); + if (S_THth(n->suffix)) + str_numth(s, s, S_TH_TYPE(n->suffix)); + s += strlen(s); + break; + case DCH_D: + INVALID_FOR_INTERVAL; + sprintf(s, "%d", tm->tm_wday + 1); + if (S_THth(n->suffix)) + str_numth(s, s, S_TH_TYPE(n->suffix)); + s += strlen(s); + break; + case DCH_ID: + INVALID_FOR_INTERVAL; + sprintf(s, "%d", (tm->tm_wday == 0) ? 7 : tm->tm_wday); + if (S_THth(n->suffix)) + str_numth(s, s, S_TH_TYPE(n->suffix)); + s += strlen(s); + break; + case DCH_WW: + sprintf(s, "%0*d", S_FM(n->suffix) ? 0 : 2, + (tm->tm_yday - 1) / 7 + 1); + if (S_THth(n->suffix)) + str_numth(s, s, S_TH_TYPE(n->suffix)); + s += strlen(s); + break; + case DCH_IW: + sprintf(s, "%0*d", S_FM(n->suffix) ? 0 : 2, + date2isoweek(tm->tm_year, tm->tm_mon, tm->tm_mday)); + if (S_THth(n->suffix)) + str_numth(s, s, S_TH_TYPE(n->suffix)); + s += strlen(s); + break; + case DCH_Q: + if (!tm->tm_mon) + break; + sprintf(s, "%d", (tm->tm_mon - 1) / 3 + 1); + if (S_THth(n->suffix)) + str_numth(s, s, S_TH_TYPE(n->suffix)); + s += strlen(s); + break; + case DCH_CC: + if (is_interval) /* straight calculation */ + i = tm->tm_year / 100; + else + { + if (tm->tm_year > 0) + /* Century 20 == 1901 - 2000 */ + i = (tm->tm_year - 1) / 100 + 1; + else + /* Century 6BC == 600BC - 501BC */ + i = tm->tm_year / 100 - 1; + } + if (i <= 99 && i >= -99) + sprintf(s, "%0*d", S_FM(n->suffix) ? 0 : (i >= 0) ? 2 : 3, i); + else + sprintf(s, "%d", i); + if (S_THth(n->suffix)) + str_numth(s, s, S_TH_TYPE(n->suffix)); + s += strlen(s); + break; + case DCH_Y_YYY: + i = ADJUST_YEAR(tm->tm_year, is_interval) / 1000; + sprintf(s, "%d,%03d", i, + ADJUST_YEAR(tm->tm_year, is_interval) - (i * 1000)); + if (S_THth(n->suffix)) + str_numth(s, s, S_TH_TYPE(n->suffix)); + s += strlen(s); + break; + case DCH_YYYY: + case DCH_IYYY: + sprintf(s, "%0*d", + S_FM(n->suffix) ? 0 : + (ADJUST_YEAR(tm->tm_year, is_interval) >= 0) ? 4 : 5, + (n->key->id == DCH_YYYY ? + ADJUST_YEAR(tm->tm_year, is_interval) : + ADJUST_YEAR(date2isoyear(tm->tm_year, + tm->tm_mon, + tm->tm_mday), + is_interval))); + if (S_THth(n->suffix)) + str_numth(s, s, S_TH_TYPE(n->suffix)); + s += strlen(s); + break; + case DCH_YYY: + case DCH_IYY: + sprintf(s, "%0*d", + S_FM(n->suffix) ? 0 : + (ADJUST_YEAR(tm->tm_year, is_interval) >= 0) ? 3 : 4, + (n->key->id == DCH_YYY ? + ADJUST_YEAR(tm->tm_year, is_interval) : + ADJUST_YEAR(date2isoyear(tm->tm_year, + tm->tm_mon, + tm->tm_mday), + is_interval)) % 1000); + if (S_THth(n->suffix)) + str_numth(s, s, S_TH_TYPE(n->suffix)); + s += strlen(s); + break; + case DCH_YY: + case DCH_IY: + sprintf(s, "%0*d", + S_FM(n->suffix) ? 0 : + (ADJUST_YEAR(tm->tm_year, is_interval) >= 0) ? 2 : 3, + (n->key->id == DCH_YY ? + ADJUST_YEAR(tm->tm_year, is_interval) : + ADJUST_YEAR(date2isoyear(tm->tm_year, + tm->tm_mon, + tm->tm_mday), + is_interval)) % 100); + if (S_THth(n->suffix)) + str_numth(s, s, S_TH_TYPE(n->suffix)); + s += strlen(s); + break; + case DCH_Y: + case DCH_I: + sprintf(s, "%1d", + (n->key->id == DCH_Y ? + ADJUST_YEAR(tm->tm_year, is_interval) : + ADJUST_YEAR(date2isoyear(tm->tm_year, + tm->tm_mon, + tm->tm_mday), + is_interval)) % 10); + if (S_THth(n->suffix)) + str_numth(s, s, S_TH_TYPE(n->suffix)); + s += strlen(s); + break; + case DCH_RM: + /* FALLTHROUGH */ + case DCH_rm: + + /* + * For intervals, values like '12 month' will be reduced to 0 + * month and some years. These should be processed. + */ + if (!tm->tm_mon && !tm->tm_year) + break; + else + { + int mon = 0; + const char *const *months; + + if (n->key->id == DCH_RM) + months = rm_months_upper; + else + months = rm_months_lower; + + /* + * Compute the position in the roman-numeral array. Note + * that the contents of the array are reversed, December + * being first and January last. + */ + if (tm->tm_mon == 0) + { + /* + * This case is special, and tracks the case of full + * interval years. + */ + mon = tm->tm_year >= 0 ? 0 : MONTHS_PER_YEAR - 1; + } + else if (tm->tm_mon < 0) + { + /* + * Negative case. In this case, the calculation is + * reversed, where -1 means December, -2 November, + * etc. + */ + mon = -1 * (tm->tm_mon + 1); + } + else + { + /* + * Common case, with a strictly positive value. The + * position in the array matches with the value of + * tm_mon. + */ + mon = MONTHS_PER_YEAR - tm->tm_mon; + } + + sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -4, + months[mon]); + s += strlen(s); + } + break; + case DCH_W: + sprintf(s, "%d", (tm->tm_mday - 1) / 7 + 1); + if (S_THth(n->suffix)) + str_numth(s, s, S_TH_TYPE(n->suffix)); + s += strlen(s); + break; + case DCH_J: + sprintf(s, "%d", date2j(tm->tm_year, tm->tm_mon, tm->tm_mday)); + if (S_THth(n->suffix)) + str_numth(s, s, S_TH_TYPE(n->suffix)); + s += strlen(s); + break; + } + } + + *s = '\0'; +} + +/* + * Process the string 'in' as denoted by the array of FormatNodes 'node[]'. + * The TmFromChar struct pointed to by 'out' is populated with the results. + * + * 'collid' identifies the collation to use, if needed. + * 'std' specifies standard parsing mode. + * + * If escontext points to an ErrorSaveContext, data errors will be reported + * by filling that struct; the caller must test SOFT_ERROR_OCCURRED() to see + * whether an error occurred. Otherwise, errors are thrown. + * + * Note: we currently don't have any to_interval() function, so there + * is no need here for INVALID_FOR_INTERVAL checks. + */ +static void +DCH_from_char(FormatNode *node, const char *in, TmFromChar *out, + Oid collid, bool std, Node *escontext) +{ + FormatNode *n; + const char *s; + int len, + value; + bool fx_mode = std; + + /* number of extra skipped characters (more than given in format string) */ + int extra_skip = 0; + + /* cache localized days and months */ + cache_locale_time(); + + for (n = node, s = in; n->type != NODE_TYPE_END && *s != '\0'; n++) + { + /* + * Ignore spaces at the beginning of the string and before fields when + * not in FX (fixed width) mode. + */ + if (!fx_mode && (n->type != NODE_TYPE_ACTION || n->key->id != DCH_FX) && + (n->type == NODE_TYPE_ACTION || n == node)) + { + while (*s != '\0' && isspace((unsigned char) *s)) + { + s++; + extra_skip++; + } + } + + if (n->type == NODE_TYPE_SPACE || n->type == NODE_TYPE_SEPARATOR) + { + if (std) + { + /* + * Standard mode requires strict matching between format + * string separators/spaces and input string. + */ + Assert(n->character[0] && !n->character[1]); + + if (*s == n->character[0]) + s++; + else + ereturn(escontext,, + (errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("unmatched format separator \"%c\"", + n->character[0]))); + } + else if (!fx_mode) + { + /* + * In non FX (fixed format) mode one format string space or + * separator match to one space or separator in input string. + * Or match nothing if there is no space or separator in the + * current position of input string. + */ + extra_skip--; + if (isspace((unsigned char) *s) || is_separator_char(s)) + { + s++; + extra_skip++; + } + } + else + { + /* + * In FX mode, on format string space or separator we consume + * exactly one character from input string. Notice we don't + * insist that the consumed character match the format's + * character. + */ + s += pg_mblen(s); + } + continue; + } + else if (n->type != NODE_TYPE_ACTION) + { + /* + * Text character, so consume one character from input string. + * Notice we don't insist that the consumed character match the + * format's character. + */ + if (!fx_mode) + { + /* + * In non FX mode we might have skipped some extra characters + * (more than specified in format string) before. In this + * case we don't skip input string character, because it might + * be part of field. + */ + if (extra_skip > 0) + extra_skip--; + else + s += pg_mblen(s); + } + else + { + int chlen = pg_mblen(s); + + /* + * Standard mode requires strict match of format characters. + */ + if (std && n->type == NODE_TYPE_CHAR && + strncmp(s, n->character, chlen) != 0) + ereturn(escontext,, + (errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("unmatched format character \"%s\"", + n->character))); + + s += chlen; + } + continue; + } + + if (!from_char_set_mode(out, n->key->date_mode, escontext)) + return; + + switch (n->key->id) + { + case DCH_FX: + fx_mode = true; + break; + case DCH_A_M: + case DCH_P_M: + case DCH_a_m: + case DCH_p_m: + if (!from_char_seq_search(&value, &s, ampm_strings_long, + NULL, InvalidOid, + n, escontext)) + return; + if (!from_char_set_int(&out->pm, value % 2, n, escontext)) + return; + out->clock = CLOCK_12_HOUR; + break; + case DCH_AM: + case DCH_PM: + case DCH_am: + case DCH_pm: + if (!from_char_seq_search(&value, &s, ampm_strings, + NULL, InvalidOid, + n, escontext)) + return; + if (!from_char_set_int(&out->pm, value % 2, n, escontext)) + return; + out->clock = CLOCK_12_HOUR; + break; + case DCH_HH: + case DCH_HH12: + if (from_char_parse_int_len(&out->hh, &s, 2, n, escontext) < 0) + return; + out->clock = CLOCK_12_HOUR; + SKIP_THth(s, n->suffix); + break; + case DCH_HH24: + if (from_char_parse_int_len(&out->hh, &s, 2, n, escontext) < 0) + return; + SKIP_THth(s, n->suffix); + break; + case DCH_MI: + if (from_char_parse_int(&out->mi, &s, n, escontext) < 0) + return; + SKIP_THth(s, n->suffix); + break; + case DCH_SS: + if (from_char_parse_int(&out->ss, &s, n, escontext) < 0) + return; + SKIP_THth(s, n->suffix); + break; + case DCH_MS: /* millisecond */ + len = from_char_parse_int_len(&out->ms, &s, 3, n, escontext); + if (len < 0) + return; + + /* + * 25 is 0.25 and 250 is 0.25 too; 025 is 0.025 and not 0.25 + */ + out->ms *= len == 1 ? 100 : + len == 2 ? 10 : 1; + + SKIP_THth(s, n->suffix); + break; + case DCH_FF1: + case DCH_FF2: + case DCH_FF3: + case DCH_FF4: + case DCH_FF5: + case DCH_FF6: + out->ff = n->key->id - DCH_FF1 + 1; + /* fall through */ + case DCH_US: /* microsecond */ + len = from_char_parse_int_len(&out->us, &s, + n->key->id == DCH_US ? 6 : + out->ff, n, escontext); + if (len < 0) + return; + + out->us *= len == 1 ? 100000 : + len == 2 ? 10000 : + len == 3 ? 1000 : + len == 4 ? 100 : + len == 5 ? 10 : 1; + + SKIP_THth(s, n->suffix); + break; + case DCH_SSSS: + if (from_char_parse_int(&out->ssss, &s, n, escontext) < 0) + return; + SKIP_THth(s, n->suffix); + break; + case DCH_tz: + case DCH_TZ: + case DCH_OF: + ereturn(escontext,, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("formatting field \"%s\" is only supported in to_char", + n->key->name))); + break; + case DCH_TZH: + + /* + * Value of TZH might be negative. And the issue is that we + * might swallow minus sign as the separator. So, if we have + * skipped more characters than specified in the format + * string, then we consider prepending last skipped minus to + * TZH. + */ + if (*s == '+' || *s == '-' || *s == ' ') + { + out->tzsign = *s == '-' ? -1 : +1; + s++; + } + else + { + if (extra_skip > 0 && *(s - 1) == '-') + out->tzsign = -1; + else + out->tzsign = +1; + } + + if (from_char_parse_int_len(&out->tzh, &s, 2, n, escontext) < 0) + return; + break; + case DCH_TZM: + /* assign positive timezone sign if TZH was not seen before */ + if (!out->tzsign) + out->tzsign = +1; + if (from_char_parse_int_len(&out->tzm, &s, 2, n, escontext) < 0) + return; + break; + case DCH_A_D: + case DCH_B_C: + case DCH_a_d: + case DCH_b_c: + if (!from_char_seq_search(&value, &s, adbc_strings_long, + NULL, InvalidOid, + n, escontext)) + return; + if (!from_char_set_int(&out->bc, value % 2, n, escontext)) + return; + break; + case DCH_AD: + case DCH_BC: + case DCH_ad: + case DCH_bc: + if (!from_char_seq_search(&value, &s, adbc_strings, + NULL, InvalidOid, + n, escontext)) + return; + if (!from_char_set_int(&out->bc, value % 2, n, escontext)) + return; + break; + case DCH_MONTH: + case DCH_Month: + case DCH_month: + if (!from_char_seq_search(&value, &s, months_full, + S_TM(n->suffix) ? localized_full_months : NULL, + collid, + n, escontext)) + return; + if (!from_char_set_int(&out->mm, value + 1, n, escontext)) + return; + break; + case DCH_MON: + case DCH_Mon: + case DCH_mon: + if (!from_char_seq_search(&value, &s, months, + S_TM(n->suffix) ? localized_abbrev_months : NULL, + collid, + n, escontext)) + return; + if (!from_char_set_int(&out->mm, value + 1, n, escontext)) + return; + break; + case DCH_MM: + if (from_char_parse_int(&out->mm, &s, n, escontext) < 0) + return; + SKIP_THth(s, n->suffix); + break; + case DCH_DAY: + case DCH_Day: + case DCH_day: + if (!from_char_seq_search(&value, &s, days, + S_TM(n->suffix) ? localized_full_days : NULL, + collid, + n, escontext)) + return; + if (!from_char_set_int(&out->d, value, n, escontext)) + return; + out->d++; + break; + case DCH_DY: + case DCH_Dy: + case DCH_dy: + if (!from_char_seq_search(&value, &s, days_short, + S_TM(n->suffix) ? localized_abbrev_days : NULL, + collid, + n, escontext)) + return; + if (!from_char_set_int(&out->d, value, n, escontext)) + return; + out->d++; + break; + case DCH_DDD: + if (from_char_parse_int(&out->ddd, &s, n, escontext) < 0) + return; + SKIP_THth(s, n->suffix); + break; + case DCH_IDDD: + if (from_char_parse_int_len(&out->ddd, &s, 3, n, escontext) < 0) + return; + SKIP_THth(s, n->suffix); + break; + case DCH_DD: + if (from_char_parse_int(&out->dd, &s, n, escontext) < 0) + return; + SKIP_THth(s, n->suffix); + break; + case DCH_D: + if (from_char_parse_int(&out->d, &s, n, escontext) < 0) + return; + SKIP_THth(s, n->suffix); + break; + case DCH_ID: + if (from_char_parse_int_len(&out->d, &s, 1, n, escontext) < 0) + return; + /* Shift numbering to match Gregorian where Sunday = 1 */ + if (++out->d > 7) + out->d = 1; + SKIP_THth(s, n->suffix); + break; + case DCH_WW: + case DCH_IW: + if (from_char_parse_int(&out->ww, &s, n, escontext) < 0) + return; + SKIP_THth(s, n->suffix); + break; + case DCH_Q: + + /* + * We ignore 'Q' when converting to date because it is unclear + * which date in the quarter to use, and some people specify + * both quarter and month, so if it was honored it might + * conflict with the supplied month. That is also why we don't + * throw an error. + * + * We still parse the source string for an integer, but it + * isn't stored anywhere in 'out'. + */ + if (from_char_parse_int((int *) NULL, &s, n, escontext) < 0) + return; + SKIP_THth(s, n->suffix); + break; + case DCH_CC: + if (from_char_parse_int(&out->cc, &s, n, escontext) < 0) + return; + SKIP_THth(s, n->suffix); + break; + case DCH_Y_YYY: + { + int matched, + years, + millennia, + nch; + + matched = sscanf(s, "%d,%03d%n", &millennia, &years, &nch); + if (matched < 2) + ereturn(escontext,, + (errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("invalid input string for \"Y,YYY\""))); + years += (millennia * 1000); + if (!from_char_set_int(&out->year, years, n, escontext)) + return; + out->yysz = 4; + s += nch; + SKIP_THth(s, n->suffix); + } + break; + case DCH_YYYY: + case DCH_IYYY: + if (from_char_parse_int(&out->year, &s, n, escontext) < 0) + return; + out->yysz = 4; + SKIP_THth(s, n->suffix); + break; + case DCH_YYY: + case DCH_IYY: + len = from_char_parse_int(&out->year, &s, n, escontext); + if (len < 0) + return; + if (len < 4) + out->year = adjust_partial_year_to_2020(out->year); + out->yysz = 3; + SKIP_THth(s, n->suffix); + break; + case DCH_YY: + case DCH_IY: + len = from_char_parse_int(&out->year, &s, n, escontext); + if (len < 0) + return; + if (len < 4) + out->year = adjust_partial_year_to_2020(out->year); + out->yysz = 2; + SKIP_THth(s, n->suffix); + break; + case DCH_Y: + case DCH_I: + len = from_char_parse_int(&out->year, &s, n, escontext); + if (len < 0) + return; + if (len < 4) + out->year = adjust_partial_year_to_2020(out->year); + out->yysz = 1; + SKIP_THth(s, n->suffix); + break; + case DCH_RM: + case DCH_rm: + if (!from_char_seq_search(&value, &s, rm_months_lower, + NULL, InvalidOid, + n, escontext)) + return; + if (!from_char_set_int(&out->mm, MONTHS_PER_YEAR - value, n, + escontext)) + return; + break; + case DCH_W: + if (from_char_parse_int(&out->w, &s, n, escontext) < 0) + return; + SKIP_THth(s, n->suffix); + break; + case DCH_J: + if (from_char_parse_int(&out->j, &s, n, escontext) < 0) + return; + SKIP_THth(s, n->suffix); + break; + } + + /* Ignore all spaces after fields */ + if (!fx_mode) + { + extra_skip = 0; + while (*s != '\0' && isspace((unsigned char) *s)) + { + s++; + extra_skip++; + } + } + } + + /* + * Standard parsing mode doesn't allow unmatched format patterns or + * trailing characters in the input string. + */ + if (std) + { + if (n->type != NODE_TYPE_END) + ereturn(escontext,, + (errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("input string is too short for datetime format"))); + + while (*s != '\0' && isspace((unsigned char) *s)) + s++; + + if (*s != '\0') + ereturn(escontext,, + (errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("trailing characters remain in input string after datetime format"))); + } +} + +/* + * The invariant for DCH cache entry management is that DCHCounter is equal + * to the maximum age value among the existing entries, and we increment it + * whenever an access occurs. If we approach overflow, deal with that by + * halving all the age values, so that we retain a fairly accurate idea of + * which entries are oldest. + */ +static inline void +DCH_prevent_counter_overflow(void) +{ + if (DCHCounter >= (INT_MAX - 1)) + { + for (int i = 0; i < n_DCHCache; i++) + DCHCache[i]->age >>= 1; + DCHCounter >>= 1; + } +} + +/* + * Get mask of date/time/zone components present in format nodes. + */ +static int +DCH_datetime_type(FormatNode *node) +{ + FormatNode *n; + int flags = 0; + + for (n = node; n->type != NODE_TYPE_END; n++) + { + if (n->type != NODE_TYPE_ACTION) + continue; + + switch (n->key->id) + { + case DCH_FX: + break; + case DCH_A_M: + case DCH_P_M: + case DCH_a_m: + case DCH_p_m: + case DCH_AM: + case DCH_PM: + case DCH_am: + case DCH_pm: + case DCH_HH: + case DCH_HH12: + case DCH_HH24: + case DCH_MI: + case DCH_SS: + case DCH_MS: /* millisecond */ + case DCH_US: /* microsecond */ + case DCH_FF1: + case DCH_FF2: + case DCH_FF3: + case DCH_FF4: + case DCH_FF5: + case DCH_FF6: + case DCH_SSSS: + flags |= DCH_TIMED; + break; + case DCH_tz: + case DCH_TZ: + case DCH_OF: + case DCH_TZH: + case DCH_TZM: + flags |= DCH_ZONED; + break; + case DCH_A_D: + case DCH_B_C: + case DCH_a_d: + case DCH_b_c: + case DCH_AD: + case DCH_BC: + case DCH_ad: + case DCH_bc: + case DCH_MONTH: + case DCH_Month: + case DCH_month: + case DCH_MON: + case DCH_Mon: + case DCH_mon: + case DCH_MM: + case DCH_DAY: + case DCH_Day: + case DCH_day: + case DCH_DY: + case DCH_Dy: + case DCH_dy: + case DCH_DDD: + case DCH_IDDD: + case DCH_DD: + case DCH_D: + case DCH_ID: + case DCH_WW: + case DCH_Q: + case DCH_CC: + case DCH_Y_YYY: + case DCH_YYYY: + case DCH_IYYY: + case DCH_YYY: + case DCH_IYY: + case DCH_YY: + case DCH_IY: + case DCH_Y: + case DCH_I: + case DCH_RM: + case DCH_rm: + case DCH_W: + case DCH_J: + flags |= DCH_DATED; + break; + } + } + + return flags; +} + +/* select a DCHCacheEntry to hold the given format picture */ +static DCHCacheEntry * +DCH_cache_getnew(const char *str, bool std) +{ + DCHCacheEntry *ent; + + /* Ensure we can advance DCHCounter below */ + DCH_prevent_counter_overflow(); + + /* + * If cache is full, remove oldest entry (or recycle first not-valid one) + */ + if (n_DCHCache >= DCH_CACHE_ENTRIES) + { + DCHCacheEntry *old = DCHCache[0]; + +#ifdef DEBUG_TO_FROM_CHAR + elog(DEBUG_elog_output, "cache is full (%d)", n_DCHCache); +#endif + if (old->valid) + { + for (int i = 1; i < DCH_CACHE_ENTRIES; i++) + { + ent = DCHCache[i]; + if (!ent->valid) + { + old = ent; + break; + } + if (ent->age < old->age) + old = ent; + } + } +#ifdef DEBUG_TO_FROM_CHAR + elog(DEBUG_elog_output, "OLD: '%s' AGE: %d", old->str, old->age); +#endif + old->valid = false; + strlcpy(old->str, str, DCH_CACHE_SIZE + 1); + old->age = (++DCHCounter); + /* caller is expected to fill format, then set valid */ + return old; + } + else + { +#ifdef DEBUG_TO_FROM_CHAR + elog(DEBUG_elog_output, "NEW (%d)", n_DCHCache); +#endif + Assert(DCHCache[n_DCHCache] == NULL); + DCHCache[n_DCHCache] = ent = (DCHCacheEntry *) + MemoryContextAllocZero(TopMemoryContext, sizeof(DCHCacheEntry)); + ent->valid = false; + strlcpy(ent->str, str, DCH_CACHE_SIZE + 1); + ent->std = std; + ent->age = (++DCHCounter); + /* caller is expected to fill format, then set valid */ + ++n_DCHCache; + return ent; + } +} + +/* look for an existing DCHCacheEntry matching the given format picture */ +static DCHCacheEntry * +DCH_cache_search(const char *str, bool std) +{ + /* Ensure we can advance DCHCounter below */ + DCH_prevent_counter_overflow(); + + for (int i = 0; i < n_DCHCache; i++) + { + DCHCacheEntry *ent = DCHCache[i]; + + if (ent->valid && strcmp(ent->str, str) == 0 && ent->std == std) + { + ent->age = (++DCHCounter); + return ent; + } + } + + return NULL; +} + +/* Find or create a DCHCacheEntry for the given format picture */ +static DCHCacheEntry * +DCH_cache_fetch(const char *str, bool std) +{ + DCHCacheEntry *ent; + + if ((ent = DCH_cache_search(str, std)) == NULL) + { + /* + * Not in the cache, must run parser and save a new format-picture to + * the cache. Do not mark the cache entry valid until parsing + * succeeds. + */ + ent = DCH_cache_getnew(str, std); + + parse_format(ent->format, str, DCH_keywords, DCH_suff, DCH_index, + DCH_FLAG | (std ? STD_FLAG : 0), NULL); + + ent->valid = true; + } + return ent; +} + +/* + * Format a date/time or interval into a string according to fmt. + * We parse fmt into a list of FormatNodes. This is then passed to DCH_to_char + * for formatting. + */ +static text * +datetime_to_char_body(TmToChar *tmtc, text *fmt, bool is_interval, Oid collid) +{ + FormatNode *format; + char *fmt_str, + *result; + bool incache; + int fmt_len; + text *res; + + /* + * Convert fmt to C string + */ + fmt_str = text_to_cstring(fmt); + fmt_len = strlen(fmt_str); + + /* + * Allocate workspace for result as C string + */ + result = palloc((fmt_len * DCH_MAX_ITEM_SIZ) + 1); + *result = '\0'; + + if (fmt_len > DCH_CACHE_SIZE) + { + /* + * Allocate new memory if format picture is bigger than static cache + * and do not use cache (call parser always) + */ + incache = false; + + format = (FormatNode *) palloc((fmt_len + 1) * sizeof(FormatNode)); + + parse_format(format, fmt_str, DCH_keywords, + DCH_suff, DCH_index, DCH_FLAG, NULL); + } + else + { + /* + * Use cache buffers + */ + DCHCacheEntry *ent = DCH_cache_fetch(fmt_str, false); + + incache = true; + format = ent->format; + } + + /* The real work is here */ + DCH_to_char(format, is_interval, tmtc, result, collid); + + if (!incache) + pfree(format); + + pfree(fmt_str); + + /* convert C-string result to TEXT format */ + res = cstring_to_text(result); + + pfree(result); + return res; +} + +/**************************************************************************** + * Public routines + ***************************************************************************/ + +/* ------------------- + * TIMESTAMP to_char() + * ------------------- + */ +Datum +timestamp_to_char(PG_FUNCTION_ARGS) +{ + Timestamp dt = PG_GETARG_TIMESTAMP(0); + text *fmt = PG_GETARG_TEXT_PP(1), + *res; + TmToChar tmtc; + struct pg_tm tt; + struct fmt_tm *tm; + int thisdate; + + if (VARSIZE_ANY_EXHDR(fmt) <= 0 || TIMESTAMP_NOT_FINITE(dt)) + PG_RETURN_NULL(); + + ZERO_tmtc(&tmtc); + tm = tmtcTm(&tmtc); + + if (timestamp2tm(dt, NULL, &tt, &tmtcFsec(&tmtc), NULL, NULL) != 0) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + + /* calculate wday and yday, because timestamp2tm doesn't */ + thisdate = date2j(tt.tm_year, tt.tm_mon, tt.tm_mday); + tt.tm_wday = (thisdate + 1) % 7; + tt.tm_yday = thisdate - date2j(tt.tm_year, 1, 1) + 1; + + COPY_tm(tm, &tt); + + if (!(res = datetime_to_char_body(&tmtc, fmt, false, PG_GET_COLLATION()))) + PG_RETURN_NULL(); + + PG_RETURN_TEXT_P(res); +} + +Datum +timestamptz_to_char(PG_FUNCTION_ARGS) +{ + TimestampTz dt = PG_GETARG_TIMESTAMP(0); + text *fmt = PG_GETARG_TEXT_PP(1), + *res; + TmToChar tmtc; + int tz; + struct pg_tm tt; + struct fmt_tm *tm; + int thisdate; + + if (VARSIZE_ANY_EXHDR(fmt) <= 0 || TIMESTAMP_NOT_FINITE(dt)) + PG_RETURN_NULL(); + + ZERO_tmtc(&tmtc); + tm = tmtcTm(&tmtc); + + if (timestamp2tm(dt, &tz, &tt, &tmtcFsec(&tmtc), &tmtcTzn(&tmtc), NULL) != 0) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + + /* calculate wday and yday, because timestamp2tm doesn't */ + thisdate = date2j(tt.tm_year, tt.tm_mon, tt.tm_mday); + tt.tm_wday = (thisdate + 1) % 7; + tt.tm_yday = thisdate - date2j(tt.tm_year, 1, 1) + 1; + + COPY_tm(tm, &tt); + + if (!(res = datetime_to_char_body(&tmtc, fmt, false, PG_GET_COLLATION()))) + PG_RETURN_NULL(); + + PG_RETURN_TEXT_P(res); +} + + +/* ------------------- + * INTERVAL to_char() + * ------------------- + */ +Datum +interval_to_char(PG_FUNCTION_ARGS) +{ + Interval *it = PG_GETARG_INTERVAL_P(0); + text *fmt = PG_GETARG_TEXT_PP(1), + *res; + TmToChar tmtc; + struct fmt_tm *tm; + struct pg_itm tt, + *itm = &tt; + + if (VARSIZE_ANY_EXHDR(fmt) <= 0) + PG_RETURN_NULL(); + + ZERO_tmtc(&tmtc); + tm = tmtcTm(&tmtc); + + interval2itm(*it, itm); + tmtc.fsec = itm->tm_usec; + tm->tm_sec = itm->tm_sec; + tm->tm_min = itm->tm_min; + tm->tm_hour = itm->tm_hour; + tm->tm_mday = itm->tm_mday; + tm->tm_mon = itm->tm_mon; + tm->tm_year = itm->tm_year; + + /* wday is meaningless, yday approximates the total span in days */ + tm->tm_yday = (tm->tm_year * MONTHS_PER_YEAR + tm->tm_mon) * DAYS_PER_MONTH + tm->tm_mday; + + if (!(res = datetime_to_char_body(&tmtc, fmt, true, PG_GET_COLLATION()))) + PG_RETURN_NULL(); + + PG_RETURN_TEXT_P(res); +} + +/* --------------------- + * TO_TIMESTAMP() + * + * Make Timestamp from date_str which is formatted at argument 'fmt' + * ( to_timestamp is reverse to_char() ) + * --------------------- + */ +Datum +to_timestamp(PG_FUNCTION_ARGS) +{ + text *date_txt = PG_GETARG_TEXT_PP(0); + text *fmt = PG_GETARG_TEXT_PP(1); + Oid collid = PG_GET_COLLATION(); + Timestamp result; + int tz; + struct pg_tm tm; + fsec_t fsec; + int fprec; + + do_to_timestamp(date_txt, fmt, collid, false, + &tm, &fsec, &fprec, NULL, NULL); + + /* Use the specified time zone, if any. */ + if (tm.tm_zone) + { + DateTimeErrorExtra extra; + int dterr = DecodeTimezone(tm.tm_zone, &tz); + + if (dterr) + DateTimeParseError(dterr, &extra, text_to_cstring(date_txt), + "timestamptz", NULL); + } + else + tz = DetermineTimeZoneOffset(&tm, session_timezone); + + if (tm2timestamp(&tm, fsec, &tz, &result) != 0) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + + /* Use the specified fractional precision, if any. */ + if (fprec) + AdjustTimestampForTypmod(&result, fprec, NULL); + + PG_RETURN_TIMESTAMP(result); +} + +/* ---------- + * TO_DATE + * Make Date from date_str which is formatted at argument 'fmt' + * ---------- + */ +Datum +to_date(PG_FUNCTION_ARGS) +{ + text *date_txt = PG_GETARG_TEXT_PP(0); + text *fmt = PG_GETARG_TEXT_PP(1); + Oid collid = PG_GET_COLLATION(); + DateADT result; + struct pg_tm tm; + fsec_t fsec; + + do_to_timestamp(date_txt, fmt, collid, false, + &tm, &fsec, NULL, NULL, NULL); + + /* Prevent overflow in Julian-day routines */ + if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("date out of range: \"%s\"", + text_to_cstring(date_txt)))); + + result = date2j(tm.tm_year, tm.tm_mon, tm.tm_mday) - POSTGRES_EPOCH_JDATE; + + /* Now check for just-out-of-range dates */ + if (!IS_VALID_DATE(result)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("date out of range: \"%s\"", + text_to_cstring(date_txt)))); + + PG_RETURN_DATEADT(result); +} + +/* + * Convert the 'date_txt' input to a datetime type using argument 'fmt' + * as a format string. The collation 'collid' may be used for case-folding + * rules in some cases. 'strict' specifies standard parsing mode. + * + * The actual data type (returned in 'typid', 'typmod') is determined by + * the presence of date/time/zone components in the format string. + * + * When a timezone component is present, the corresponding offset is + * returned in '*tz'. + * + * If escontext points to an ErrorSaveContext, data errors will be reported + * by filling that struct; the caller must test SOFT_ERROR_OCCURRED() to see + * whether an error occurred. Otherwise, errors are thrown. + */ +Datum +parse_datetime(text *date_txt, text *fmt, Oid collid, bool strict, + Oid *typid, int32 *typmod, int *tz, + Node *escontext) +{ + struct pg_tm tm; + fsec_t fsec; + int fprec; + uint32 flags; + + if (!do_to_timestamp(date_txt, fmt, collid, strict, + &tm, &fsec, &fprec, &flags, escontext)) + return (Datum) 0; + + *typmod = fprec ? fprec : -1; /* fractional part precision */ + + if (flags & DCH_DATED) + { + if (flags & DCH_TIMED) + { + if (flags & DCH_ZONED) + { + TimestampTz result; + + if (tm.tm_zone) + { + DateTimeErrorExtra extra; + int dterr = DecodeTimezone(tm.tm_zone, tz); + + if (dterr) + { + DateTimeParseError(dterr, &extra, + text_to_cstring(date_txt), + "timestamptz", escontext); + return (Datum) 0; + } + } + else + { + /* + * Time zone is present in format string, but not in input + * string. Assuming do_to_timestamp() triggers no error + * this should be possible only in non-strict case. + */ + Assert(!strict); + + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("missing time zone in input string for type timestamptz"))); + } + + if (tm2timestamp(&tm, fsec, tz, &result) != 0) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamptz out of range"))); + + AdjustTimestampForTypmod(&result, *typmod, escontext); + + *typid = TIMESTAMPTZOID; + return TimestampTzGetDatum(result); + } + else + { + Timestamp result; + + if (tm2timestamp(&tm, fsec, NULL, &result) != 0) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + + AdjustTimestampForTypmod(&result, *typmod, escontext); + + *typid = TIMESTAMPOID; + return TimestampGetDatum(result); + } + } + else + { + if (flags & DCH_ZONED) + { + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("datetime format is zoned but not timed"))); + } + else + { + DateADT result; + + /* Prevent overflow in Julian-day routines */ + if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday)) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("date out of range: \"%s\"", + text_to_cstring(date_txt)))); + + result = date2j(tm.tm_year, tm.tm_mon, tm.tm_mday) - + POSTGRES_EPOCH_JDATE; + + /* Now check for just-out-of-range dates */ + if (!IS_VALID_DATE(result)) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("date out of range: \"%s\"", + text_to_cstring(date_txt)))); + + *typid = DATEOID; + return DateADTGetDatum(result); + } + } + } + else if (flags & DCH_TIMED) + { + if (flags & DCH_ZONED) + { + TimeTzADT *result = palloc(sizeof(TimeTzADT)); + + if (tm.tm_zone) + { + DateTimeErrorExtra extra; + int dterr = DecodeTimezone(tm.tm_zone, tz); + + if (dterr) + { + DateTimeParseError(dterr, &extra, + text_to_cstring(date_txt), + "timetz", escontext); + return (Datum) 0; + } + } + else + { + /* + * Time zone is present in format string, but not in input + * string. Assuming do_to_timestamp() triggers no error this + * should be possible only in non-strict case. + */ + Assert(!strict); + + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("missing time zone in input string for type timetz"))); + } + + if (tm2timetz(&tm, fsec, *tz, result) != 0) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timetz out of range"))); + + AdjustTimeForTypmod(&result->time, *typmod); + + *typid = TIMETZOID; + return TimeTzADTPGetDatum(result); + } + else + { + TimeADT result; + + if (tm2time(&tm, fsec, &result) != 0) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("time out of range"))); + + AdjustTimeForTypmod(&result, *typmod); + + *typid = TIMEOID; + return TimeADTGetDatum(result); + } + } + else + { + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("datetime format is not dated and not timed"))); + } +} + +/* + * do_to_timestamp: shared code for to_timestamp and to_date + * + * Parse the 'date_txt' according to 'fmt', return results as a struct pg_tm, + * fractional seconds, and fractional precision. + * + * 'collid' identifies the collation to use, if needed. + * 'std' specifies standard parsing mode. + * + * Bit mask of date/time/zone components found in 'fmt' is returned in 'flags', + * if that is not NULL. + * + * Returns true on success, false on failure (if escontext points to an + * ErrorSaveContext; otherwise errors are thrown). Note that currently, + * soft-error behavior is provided for bad data but not bad format. + * + * We parse 'fmt' into a list of FormatNodes, which is then passed to + * DCH_from_char to populate a TmFromChar with the parsed contents of + * 'date_txt'. + * + * The TmFromChar is then analysed and converted into the final results in + * struct 'tm', 'fsec', and 'fprec'. + */ +static bool +do_to_timestamp(text *date_txt, text *fmt, Oid collid, bool std, + struct pg_tm *tm, fsec_t *fsec, int *fprec, + uint32 *flags, Node *escontext) +{ + FormatNode *format = NULL; + TmFromChar tmfc; + int fmt_len; + char *date_str; + int fmask; + bool incache = false; + + Assert(tm != NULL); + Assert(fsec != NULL); + + date_str = text_to_cstring(date_txt); + + ZERO_tmfc(&tmfc); + ZERO_tm(tm); + *fsec = 0; + if (fprec) + *fprec = 0; + if (flags) + *flags = 0; + fmask = 0; /* bit mask for ValidateDate() */ + + fmt_len = VARSIZE_ANY_EXHDR(fmt); + + if (fmt_len) + { + char *fmt_str; + + fmt_str = text_to_cstring(fmt); + + if (fmt_len > DCH_CACHE_SIZE) + { + /* + * Allocate new memory if format picture is bigger than static + * cache and do not use cache (call parser always) + */ + format = (FormatNode *) palloc((fmt_len + 1) * sizeof(FormatNode)); + + parse_format(format, fmt_str, DCH_keywords, DCH_suff, DCH_index, + DCH_FLAG | (std ? STD_FLAG : 0), NULL); + } + else + { + /* + * Use cache buffers + */ + DCHCacheEntry *ent = DCH_cache_fetch(fmt_str, std); + + incache = true; + format = ent->format; + } + +#ifdef DEBUG_TO_FROM_CHAR + /* dump_node(format, fmt_len); */ + /* dump_index(DCH_keywords, DCH_index); */ +#endif + + DCH_from_char(format, date_str, &tmfc, collid, std, escontext); + pfree(fmt_str); + if (SOFT_ERROR_OCCURRED(escontext)) + goto fail; + + if (flags) + *flags = DCH_datetime_type(format); + + if (!incache) + { + pfree(format); + format = NULL; + } + } + + DEBUG_TMFC(&tmfc); + + /* + * Convert to_date/to_timestamp input fields to standard 'tm' + */ + if (tmfc.ssss) + { + int x = tmfc.ssss; + + tm->tm_hour = x / SECS_PER_HOUR; + x %= SECS_PER_HOUR; + tm->tm_min = x / SECS_PER_MINUTE; + x %= SECS_PER_MINUTE; + tm->tm_sec = x; + } + + if (tmfc.ss) + tm->tm_sec = tmfc.ss; + if (tmfc.mi) + tm->tm_min = tmfc.mi; + if (tmfc.hh) + tm->tm_hour = tmfc.hh; + + if (tmfc.clock == CLOCK_12_HOUR) + { + if (tm->tm_hour < 1 || tm->tm_hour > HOURS_PER_DAY / 2) + { + errsave(escontext, + (errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("hour \"%d\" is invalid for the 12-hour clock", + tm->tm_hour), + errhint("Use the 24-hour clock, or give an hour between 1 and 12."))); + goto fail; + } + + if (tmfc.pm && tm->tm_hour < HOURS_PER_DAY / 2) + tm->tm_hour += HOURS_PER_DAY / 2; + else if (!tmfc.pm && tm->tm_hour == HOURS_PER_DAY / 2) + tm->tm_hour = 0; + } + + if (tmfc.year) + { + /* + * If CC and YY (or Y) are provided, use YY as 2 low-order digits for + * the year in the given century. Keep in mind that the 21st century + * AD runs from 2001-2100, not 2000-2099; 6th century BC runs from + * 600BC to 501BC. + */ + if (tmfc.cc && tmfc.yysz <= 2) + { + if (tmfc.bc) + tmfc.cc = -tmfc.cc; + tm->tm_year = tmfc.year % 100; + if (tm->tm_year) + { + if (tmfc.cc >= 0) + tm->tm_year += (tmfc.cc - 1) * 100; + else + tm->tm_year = (tmfc.cc + 1) * 100 - tm->tm_year + 1; + } + else + { + /* find century year for dates ending in "00" */ + tm->tm_year = tmfc.cc * 100 + ((tmfc.cc >= 0) ? 0 : 1); + } + } + else + { + /* If a 4-digit year is provided, we use that and ignore CC. */ + tm->tm_year = tmfc.year; + if (tmfc.bc) + tm->tm_year = -tm->tm_year; + /* correct for our representation of BC years */ + if (tm->tm_year < 0) + tm->tm_year++; + } + fmask |= DTK_M(YEAR); + } + else if (tmfc.cc) + { + /* use first year of century */ + if (tmfc.bc) + tmfc.cc = -tmfc.cc; + if (tmfc.cc >= 0) + /* +1 because 21st century started in 2001 */ + tm->tm_year = (tmfc.cc - 1) * 100 + 1; + else + /* +1 because year == 599 is 600 BC */ + tm->tm_year = tmfc.cc * 100 + 1; + fmask |= DTK_M(YEAR); + } + + if (tmfc.j) + { + j2date(tmfc.j, &tm->tm_year, &tm->tm_mon, &tm->tm_mday); + fmask |= DTK_DATE_M; + } + + if (tmfc.ww) + { + if (tmfc.mode == FROM_CHAR_DATE_ISOWEEK) + { + /* + * If tmfc.d is not set, then the date is left at the beginning of + * the ISO week (Monday). + */ + if (tmfc.d) + isoweekdate2date(tmfc.ww, tmfc.d, &tm->tm_year, &tm->tm_mon, &tm->tm_mday); + else + isoweek2date(tmfc.ww, &tm->tm_year, &tm->tm_mon, &tm->tm_mday); + fmask |= DTK_DATE_M; + } + else + tmfc.ddd = (tmfc.ww - 1) * 7 + 1; + } + + if (tmfc.w) + tmfc.dd = (tmfc.w - 1) * 7 + 1; + if (tmfc.dd) + { + tm->tm_mday = tmfc.dd; + fmask |= DTK_M(DAY); + } + if (tmfc.mm) + { + tm->tm_mon = tmfc.mm; + fmask |= DTK_M(MONTH); + } + + if (tmfc.ddd && (tm->tm_mon <= 1 || tm->tm_mday <= 1)) + { + /* + * The month and day field have not been set, so we use the + * day-of-year field to populate them. Depending on the date mode, + * this field may be interpreted as a Gregorian day-of-year, or an ISO + * week date day-of-year. + */ + + if (!tm->tm_year && !tmfc.bc) + { + errsave(escontext, + (errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("cannot calculate day of year without year information"))); + goto fail; + } + + if (tmfc.mode == FROM_CHAR_DATE_ISOWEEK) + { + int j0; /* zeroth day of the ISO year, in Julian */ + + j0 = isoweek2j(tm->tm_year, 1) - 1; + + j2date(j0 + tmfc.ddd, &tm->tm_year, &tm->tm_mon, &tm->tm_mday); + fmask |= DTK_DATE_M; + } + else + { + const int *y; + int i; + + static const int ysum[2][13] = { + {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365}, + {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366}}; + + y = ysum[isleap(tm->tm_year)]; + + for (i = 1; i <= MONTHS_PER_YEAR; i++) + { + if (tmfc.ddd <= y[i]) + break; + } + if (tm->tm_mon <= 1) + tm->tm_mon = i; + + if (tm->tm_mday <= 1) + tm->tm_mday = tmfc.ddd - y[i - 1]; + + fmask |= DTK_M(MONTH) | DTK_M(DAY); + } + } + + if (tmfc.ms) + *fsec += tmfc.ms * 1000; + if (tmfc.us) + *fsec += tmfc.us; + if (fprec) + *fprec = tmfc.ff; /* fractional precision, if specified */ + + /* Range-check date fields according to bit mask computed above */ + if (fmask != 0) + { + /* We already dealt with AD/BC, so pass isjulian = true */ + int dterr = ValidateDate(fmask, true, false, false, tm); + + if (dterr != 0) + { + /* + * Force the error to be DTERR_FIELD_OVERFLOW even if ValidateDate + * said DTERR_MD_FIELD_OVERFLOW, because we don't want to print an + * irrelevant hint about datestyle. + */ + DateTimeParseError(DTERR_FIELD_OVERFLOW, NULL, + date_str, "timestamp", escontext); + goto fail; + } + } + + /* Range-check time fields too */ + if (tm->tm_hour < 0 || tm->tm_hour >= HOURS_PER_DAY || + tm->tm_min < 0 || tm->tm_min >= MINS_PER_HOUR || + tm->tm_sec < 0 || tm->tm_sec >= SECS_PER_MINUTE || + *fsec < INT64CONST(0) || *fsec >= USECS_PER_SEC) + { + DateTimeParseError(DTERR_FIELD_OVERFLOW, NULL, + date_str, "timestamp", escontext); + goto fail; + } + + /* Save parsed time-zone into tm->tm_zone if it was specified */ + if (tmfc.tzsign) + { + char *tz; + + if (tmfc.tzh < 0 || tmfc.tzh > MAX_TZDISP_HOUR || + tmfc.tzm < 0 || tmfc.tzm >= MINS_PER_HOUR) + { + DateTimeParseError(DTERR_TZDISP_OVERFLOW, NULL, + date_str, "timestamp", escontext); + goto fail; + } + + tz = psprintf("%c%02d:%02d", + tmfc.tzsign > 0 ? '+' : '-', tmfc.tzh, tmfc.tzm); + + tm->tm_zone = tz; + } + + DEBUG_TM(tm); + + if (format && !incache) + pfree(format); + pfree(date_str); + + return true; + +fail: + if (format && !incache) + pfree(format); + pfree(date_str); + + return false; +} + + +/********************************************************************** + * the NUMBER version part + *********************************************************************/ + + +static char * +fill_str(char *str, int c, int max) +{ + memset(str, c, max); + *(str + max) = '\0'; + return str; +} + +#define zeroize_NUM(_n) \ +do { \ + (_n)->flag = 0; \ + (_n)->lsign = 0; \ + (_n)->pre = 0; \ + (_n)->post = 0; \ + (_n)->pre_lsign_num = 0; \ + (_n)->need_locale = 0; \ + (_n)->multi = 0; \ + (_n)->zero_start = 0; \ + (_n)->zero_end = 0; \ +} while(0) + +/* This works the same as DCH_prevent_counter_overflow */ +static inline void +NUM_prevent_counter_overflow(void) +{ + if (NUMCounter >= (INT_MAX - 1)) + { + for (int i = 0; i < n_NUMCache; i++) + NUMCache[i]->age >>= 1; + NUMCounter >>= 1; + } +} + +/* select a NUMCacheEntry to hold the given format picture */ +static NUMCacheEntry * +NUM_cache_getnew(const char *str) +{ + NUMCacheEntry *ent; + + /* Ensure we can advance NUMCounter below */ + NUM_prevent_counter_overflow(); + + /* + * If cache is full, remove oldest entry (or recycle first not-valid one) + */ + if (n_NUMCache >= NUM_CACHE_ENTRIES) + { + NUMCacheEntry *old = NUMCache[0]; + +#ifdef DEBUG_TO_FROM_CHAR + elog(DEBUG_elog_output, "Cache is full (%d)", n_NUMCache); +#endif + if (old->valid) + { + for (int i = 1; i < NUM_CACHE_ENTRIES; i++) + { + ent = NUMCache[i]; + if (!ent->valid) + { + old = ent; + break; + } + if (ent->age < old->age) + old = ent; + } + } +#ifdef DEBUG_TO_FROM_CHAR + elog(DEBUG_elog_output, "OLD: \"%s\" AGE: %d", old->str, old->age); +#endif + old->valid = false; + strlcpy(old->str, str, NUM_CACHE_SIZE + 1); + old->age = (++NUMCounter); + /* caller is expected to fill format and Num, then set valid */ + return old; + } + else + { +#ifdef DEBUG_TO_FROM_CHAR + elog(DEBUG_elog_output, "NEW (%d)", n_NUMCache); +#endif + Assert(NUMCache[n_NUMCache] == NULL); + NUMCache[n_NUMCache] = ent = (NUMCacheEntry *) + MemoryContextAllocZero(TopMemoryContext, sizeof(NUMCacheEntry)); + ent->valid = false; + strlcpy(ent->str, str, NUM_CACHE_SIZE + 1); + ent->age = (++NUMCounter); + /* caller is expected to fill format and Num, then set valid */ + ++n_NUMCache; + return ent; + } +} + +/* look for an existing NUMCacheEntry matching the given format picture */ +static NUMCacheEntry * +NUM_cache_search(const char *str) +{ + /* Ensure we can advance NUMCounter below */ + NUM_prevent_counter_overflow(); + + for (int i = 0; i < n_NUMCache; i++) + { + NUMCacheEntry *ent = NUMCache[i]; + + if (ent->valid && strcmp(ent->str, str) == 0) + { + ent->age = (++NUMCounter); + return ent; + } + } + + return NULL; +} + +/* Find or create a NUMCacheEntry for the given format picture */ +static NUMCacheEntry * +NUM_cache_fetch(const char *str) +{ + NUMCacheEntry *ent; + + if ((ent = NUM_cache_search(str)) == NULL) + { + /* + * Not in the cache, must run parser and save a new format-picture to + * the cache. Do not mark the cache entry valid until parsing + * succeeds. + */ + ent = NUM_cache_getnew(str); + + zeroize_NUM(&ent->Num); + + parse_format(ent->format, str, NUM_keywords, + NULL, NUM_index, NUM_FLAG, &ent->Num); + + ent->valid = true; + } + return ent; +} + +/* ---------- + * Cache routine for NUM to_char version + * ---------- + */ +static FormatNode * +NUM_cache(int len, NUMDesc *Num, text *pars_str, bool *shouldFree) +{ + FormatNode *format = NULL; + char *str; + + str = text_to_cstring(pars_str); + + if (len > NUM_CACHE_SIZE) + { + /* + * Allocate new memory if format picture is bigger than static cache + * and do not use cache (call parser always) + */ + format = (FormatNode *) palloc((len + 1) * sizeof(FormatNode)); + + *shouldFree = true; + + zeroize_NUM(Num); + + parse_format(format, str, NUM_keywords, + NULL, NUM_index, NUM_FLAG, Num); + } + else + { + /* + * Use cache buffers + */ + NUMCacheEntry *ent = NUM_cache_fetch(str); + + *shouldFree = false; + + format = ent->format; + + /* + * Copy cache to used struct + */ + Num->flag = ent->Num.flag; + Num->lsign = ent->Num.lsign; + Num->pre = ent->Num.pre; + Num->post = ent->Num.post; + Num->pre_lsign_num = ent->Num.pre_lsign_num; + Num->need_locale = ent->Num.need_locale; + Num->multi = ent->Num.multi; + Num->zero_start = ent->Num.zero_start; + Num->zero_end = ent->Num.zero_end; + } + +#ifdef DEBUG_TO_FROM_CHAR + /* dump_node(format, len); */ + dump_index(NUM_keywords, NUM_index); +#endif + + pfree(str); + return format; +} + + +static char * +int_to_roman(int number) +{ + int len, + num; + char *p, + *result, + numstr[12]; + + result = (char *) palloc(16); + *result = '\0'; + + if (number > 3999 || number < 1) + { + fill_str(result, '#', 15); + return result; + } + len = snprintf(numstr, sizeof(numstr), "%d", number); + + for (p = numstr; *p != '\0'; p++, --len) + { + num = *p - ('0' + 1); + if (num < 0) + continue; + + if (len > 3) + { + while (num-- != -1) + strcat(result, "M"); + } + else + { + if (len == 3) + strcat(result, rm100[num]); + else if (len == 2) + strcat(result, rm10[num]); + else if (len == 1) + strcat(result, rm1[num]); + } + } + return result; +} + + + +/* ---------- + * Locale + * ---------- + */ +static void +NUM_prepare_locale(NUMProc *Np) +{ + if (Np->Num->need_locale) + { + struct lconv *lconv; + + /* + * Get locales + */ + lconv = PGLC_localeconv(); + + /* + * Positive / Negative number sign + */ + if (lconv->negative_sign && *lconv->negative_sign) + Np->L_negative_sign = lconv->negative_sign; + else + Np->L_negative_sign = "-"; + + if (lconv->positive_sign && *lconv->positive_sign) + Np->L_positive_sign = lconv->positive_sign; + else + Np->L_positive_sign = "+"; + + /* + * Number decimal point + */ + if (lconv->decimal_point && *lconv->decimal_point) + Np->decimal = lconv->decimal_point; + + else + Np->decimal = "."; + + if (!IS_LDECIMAL(Np->Num)) + Np->decimal = "."; + + /* + * Number thousands separator + * + * Some locales (e.g. broken glibc pt_BR), have a comma for decimal, + * but "" for thousands_sep, so we set the thousands_sep too. + * http://archives.postgresql.org/pgsql-hackers/2007-11/msg00772.php + */ + if (lconv->thousands_sep && *lconv->thousands_sep) + Np->L_thousands_sep = lconv->thousands_sep; + /* Make sure thousands separator doesn't match decimal point symbol. */ + else if (strcmp(Np->decimal, ",") != 0) + Np->L_thousands_sep = ","; + else + Np->L_thousands_sep = "."; + + /* + * Currency symbol + */ + if (lconv->currency_symbol && *lconv->currency_symbol) + Np->L_currency_symbol = lconv->currency_symbol; + else + Np->L_currency_symbol = " "; + } + else + { + /* + * Default values + */ + Np->L_negative_sign = "-"; + Np->L_positive_sign = "+"; + Np->decimal = "."; + + Np->L_thousands_sep = ","; + Np->L_currency_symbol = " "; + } +} + +/* ---------- + * Return pointer of last relevant number after decimal point + * 12.0500 --> last relevant is '5' + * 12.0000 --> last relevant is '.' + * If there is no decimal point, return NULL (which will result in same + * behavior as if FM hadn't been specified). + * ---------- + */ +static char * +get_last_relevant_decnum(char *num) +{ + char *result, + *p = strchr(num, '.'); + +#ifdef DEBUG_TO_FROM_CHAR + elog(DEBUG_elog_output, "get_last_relevant_decnum()"); +#endif + + if (!p) + return NULL; + + result = p; + + while (*(++p)) + { + if (*p != '0') + result = p; + } + + return result; +} + +/* + * These macros are used in NUM_processor() and its subsidiary routines. + * OVERLOAD_TEST: true if we've reached end of input string + * AMOUNT_TEST(s): true if at least s bytes remain in string + */ +#define OVERLOAD_TEST (Np->inout_p >= Np->inout + input_len) +#define AMOUNT_TEST(s) (Np->inout_p <= Np->inout + (input_len - (s))) + +/* ---------- + * Number extraction for TO_NUMBER() + * ---------- + */ +static void +NUM_numpart_from_char(NUMProc *Np, int id, int input_len) +{ + bool isread = false; + +#ifdef DEBUG_TO_FROM_CHAR + elog(DEBUG_elog_output, " --- scan start --- id=%s", + (id == NUM_0 || id == NUM_9) ? "NUM_0/9" : id == NUM_DEC ? "NUM_DEC" : "???"); +#endif + + if (OVERLOAD_TEST) + return; + + if (*Np->inout_p == ' ') + Np->inout_p++; + + if (OVERLOAD_TEST) + return; + + /* + * read sign before number + */ + if (*Np->number == ' ' && (id == NUM_0 || id == NUM_9) && + (Np->read_pre + Np->read_post) == 0) + { +#ifdef DEBUG_TO_FROM_CHAR + elog(DEBUG_elog_output, "Try read sign (%c), locale positive: %s, negative: %s", + *Np->inout_p, Np->L_positive_sign, Np->L_negative_sign); +#endif + + /* + * locale sign + */ + if (IS_LSIGN(Np->Num) && Np->Num->lsign == NUM_LSIGN_PRE) + { + int x = 0; + +#ifdef DEBUG_TO_FROM_CHAR + elog(DEBUG_elog_output, "Try read locale pre-sign (%c)", *Np->inout_p); +#endif + if ((x = strlen(Np->L_negative_sign)) && + AMOUNT_TEST(x) && + strncmp(Np->inout_p, Np->L_negative_sign, x) == 0) + { + Np->inout_p += x; + *Np->number = '-'; + } + else if ((x = strlen(Np->L_positive_sign)) && + AMOUNT_TEST(x) && + strncmp(Np->inout_p, Np->L_positive_sign, x) == 0) + { + Np->inout_p += x; + *Np->number = '+'; + } + } + else + { +#ifdef DEBUG_TO_FROM_CHAR + elog(DEBUG_elog_output, "Try read simple sign (%c)", *Np->inout_p); +#endif + + /* + * simple + - < > + */ + if (*Np->inout_p == '-' || (IS_BRACKET(Np->Num) && + *Np->inout_p == '<')) + { + *Np->number = '-'; /* set - */ + Np->inout_p++; + } + else if (*Np->inout_p == '+') + { + *Np->number = '+'; /* set + */ + Np->inout_p++; + } + } + } + + if (OVERLOAD_TEST) + return; + +#ifdef DEBUG_TO_FROM_CHAR + elog(DEBUG_elog_output, "Scan for numbers (%c), current number: '%s'", *Np->inout_p, Np->number); +#endif + + /* + * read digit or decimal point + */ + if (isdigit((unsigned char) *Np->inout_p)) + { + if (Np->read_dec && Np->read_post == Np->Num->post) + return; + + *Np->number_p = *Np->inout_p; + Np->number_p++; + + if (Np->read_dec) + Np->read_post++; + else + Np->read_pre++; + + isread = true; + +#ifdef DEBUG_TO_FROM_CHAR + elog(DEBUG_elog_output, "Read digit (%c)", *Np->inout_p); +#endif + } + else if (IS_DECIMAL(Np->Num) && Np->read_dec == false) + { + /* + * We need not test IS_LDECIMAL(Np->Num) explicitly here, because + * Np->decimal is always just "." if we don't have a D format token. + * So we just unconditionally match to Np->decimal. + */ + int x = strlen(Np->decimal); + +#ifdef DEBUG_TO_FROM_CHAR + elog(DEBUG_elog_output, "Try read decimal point (%c)", + *Np->inout_p); +#endif + if (x && AMOUNT_TEST(x) && strncmp(Np->inout_p, Np->decimal, x) == 0) + { + Np->inout_p += x - 1; + *Np->number_p = '.'; + Np->number_p++; + Np->read_dec = true; + isread = true; + } + } + + if (OVERLOAD_TEST) + return; + + /* + * Read sign behind "last" number + * + * We need sign detection because determine exact position of post-sign is + * difficult: + * + * FM9999.9999999S -> 123.001- 9.9S -> .5- FM9.999999MI -> + * 5.01- + */ + if (*Np->number == ' ' && Np->read_pre + Np->read_post > 0) + { + /* + * locale sign (NUM_S) is always anchored behind a last number, if: - + * locale sign expected - last read char was NUM_0/9 or NUM_DEC - and + * next char is not digit + */ + if (IS_LSIGN(Np->Num) && isread && + (Np->inout_p + 1) < Np->inout + input_len && + !isdigit((unsigned char) *(Np->inout_p + 1))) + { + int x; + char *tmp = Np->inout_p++; + +#ifdef DEBUG_TO_FROM_CHAR + elog(DEBUG_elog_output, "Try read locale post-sign (%c)", *Np->inout_p); +#endif + if ((x = strlen(Np->L_negative_sign)) && + AMOUNT_TEST(x) && + strncmp(Np->inout_p, Np->L_negative_sign, x) == 0) + { + Np->inout_p += x - 1; /* -1 .. NUM_processor() do inout_p++ */ + *Np->number = '-'; + } + else if ((x = strlen(Np->L_positive_sign)) && + AMOUNT_TEST(x) && + strncmp(Np->inout_p, Np->L_positive_sign, x) == 0) + { + Np->inout_p += x - 1; /* -1 .. NUM_processor() do inout_p++ */ + *Np->number = '+'; + } + if (*Np->number == ' ') + /* no sign read */ + Np->inout_p = tmp; + } + + /* + * try read non-locale sign, it's happen only if format is not exact + * and we cannot determine sign position of MI/PL/SG, an example: + * + * FM9.999999MI -> 5.01- + * + * if (.... && IS_LSIGN(Np->Num)==false) prevents read wrong formats + * like to_number('1 -', '9S') where sign is not anchored to last + * number. + */ + else if (isread == false && IS_LSIGN(Np->Num) == false && + (IS_PLUS(Np->Num) || IS_MINUS(Np->Num))) + { +#ifdef DEBUG_TO_FROM_CHAR + elog(DEBUG_elog_output, "Try read simple post-sign (%c)", *Np->inout_p); +#endif + + /* + * simple + - + */ + if (*Np->inout_p == '-' || *Np->inout_p == '+') + /* NUM_processor() do inout_p++ */ + *Np->number = *Np->inout_p; + } + } +} + +#define IS_PREDEC_SPACE(_n) \ + (IS_ZERO((_n)->Num)==false && \ + (_n)->number == (_n)->number_p && \ + *(_n)->number == '0' && \ + (_n)->Num->post != 0) + +/* ---------- + * Add digit or sign to number-string + * ---------- + */ +static void +NUM_numpart_to_char(NUMProc *Np, int id) +{ + int end; + + if (IS_ROMAN(Np->Num)) + return; + + /* Note: in this elog() output not set '\0' in 'inout' */ + +#ifdef DEBUG_TO_FROM_CHAR + + /* + * Np->num_curr is number of current item in format-picture, it is not + * current position in inout! + */ + elog(DEBUG_elog_output, + "SIGN_WROTE: %d, CURRENT: %d, NUMBER_P: \"%s\", INOUT: \"%s\"", + Np->sign_wrote, + Np->num_curr, + Np->number_p, + Np->inout); +#endif + Np->num_in = false; + + /* + * Write sign if real number will write to output Note: IS_PREDEC_SPACE() + * handle "9.9" --> " .1" + */ + if (Np->sign_wrote == false && + (Np->num_curr >= Np->out_pre_spaces || (IS_ZERO(Np->Num) && Np->Num->zero_start == Np->num_curr)) && + (IS_PREDEC_SPACE(Np) == false || (Np->last_relevant && *Np->last_relevant == '.'))) + { + if (IS_LSIGN(Np->Num)) + { + if (Np->Num->lsign == NUM_LSIGN_PRE) + { + if (Np->sign == '-') + strcpy(Np->inout_p, Np->L_negative_sign); + else + strcpy(Np->inout_p, Np->L_positive_sign); + Np->inout_p += strlen(Np->inout_p); + Np->sign_wrote = true; + } + } + else if (IS_BRACKET(Np->Num)) + { + *Np->inout_p = Np->sign == '+' ? ' ' : '<'; + ++Np->inout_p; + Np->sign_wrote = true; + } + else if (Np->sign == '+') + { + if (!IS_FILLMODE(Np->Num)) + { + *Np->inout_p = ' '; /* Write + */ + ++Np->inout_p; + } + Np->sign_wrote = true; + } + else if (Np->sign == '-') + { /* Write - */ + *Np->inout_p = '-'; + ++Np->inout_p; + Np->sign_wrote = true; + } + } + + + /* + * digits / FM / Zero / Dec. point + */ + if (id == NUM_9 || id == NUM_0 || id == NUM_D || id == NUM_DEC) + { + if (Np->num_curr < Np->out_pre_spaces && + (Np->Num->zero_start > Np->num_curr || !IS_ZERO(Np->Num))) + { + /* + * Write blank space + */ + if (!IS_FILLMODE(Np->Num)) + { + *Np->inout_p = ' '; /* Write ' ' */ + ++Np->inout_p; + } + } + else if (IS_ZERO(Np->Num) && + Np->num_curr < Np->out_pre_spaces && + Np->Num->zero_start <= Np->num_curr) + { + /* + * Write ZERO + */ + *Np->inout_p = '0'; /* Write '0' */ + ++Np->inout_p; + Np->num_in = true; + } + else + { + /* + * Write Decimal point + */ + if (*Np->number_p == '.') + { + if (!Np->last_relevant || *Np->last_relevant != '.') + { + strcpy(Np->inout_p, Np->decimal); /* Write DEC/D */ + Np->inout_p += strlen(Np->inout_p); + } + + /* + * Ora 'n' -- FM9.9 --> 'n.' + */ + else if (IS_FILLMODE(Np->Num) && + Np->last_relevant && *Np->last_relevant == '.') + { + strcpy(Np->inout_p, Np->decimal); /* Write DEC/D */ + Np->inout_p += strlen(Np->inout_p); + } + } + else + { + /* + * Write Digits + */ + if (Np->last_relevant && Np->number_p > Np->last_relevant && + id != NUM_0) + ; + + /* + * '0.1' -- 9.9 --> ' .1' + */ + else if (IS_PREDEC_SPACE(Np)) + { + if (!IS_FILLMODE(Np->Num)) + { + *Np->inout_p = ' '; + ++Np->inout_p; + } + + /* + * '0' -- FM9.9 --> '0.' + */ + else if (Np->last_relevant && *Np->last_relevant == '.') + { + *Np->inout_p = '0'; + ++Np->inout_p; + } + } + else + { + *Np->inout_p = *Np->number_p; /* Write DIGIT */ + ++Np->inout_p; + Np->num_in = true; + } + } + /* do no exceed string length */ + if (*Np->number_p) + ++Np->number_p; + } + + end = Np->num_count + (Np->out_pre_spaces ? 1 : 0) + (IS_DECIMAL(Np->Num) ? 1 : 0); + + if (Np->last_relevant && Np->last_relevant == Np->number_p) + end = Np->num_curr; + + if (Np->num_curr + 1 == end) + { + if (Np->sign_wrote == true && IS_BRACKET(Np->Num)) + { + *Np->inout_p = Np->sign == '+' ? ' ' : '>'; + ++Np->inout_p; + } + else if (IS_LSIGN(Np->Num) && Np->Num->lsign == NUM_LSIGN_POST) + { + if (Np->sign == '-') + strcpy(Np->inout_p, Np->L_negative_sign); + else + strcpy(Np->inout_p, Np->L_positive_sign); + Np->inout_p += strlen(Np->inout_p); + } + } + } + + ++Np->num_curr; +} + +/* + * Skip over "n" input characters, but only if they aren't numeric data + */ +static void +NUM_eat_non_data_chars(NUMProc *Np, int n, int input_len) +{ + while (n-- > 0) + { + if (OVERLOAD_TEST) + break; /* end of input */ + if (strchr("0123456789.,+-", *Np->inout_p) != NULL) + break; /* it's a data character */ + Np->inout_p += pg_mblen(Np->inout_p); + } +} + +static char * +NUM_processor(FormatNode *node, NUMDesc *Num, char *inout, + char *number, int input_len, int to_char_out_pre_spaces, + int sign, bool is_to_char, Oid collid) +{ + FormatNode *n; + NUMProc _Np, + *Np = &_Np; + const char *pattern; + int pattern_len; + + MemSet(Np, 0, sizeof(NUMProc)); + + Np->Num = Num; + Np->is_to_char = is_to_char; + Np->number = number; + Np->inout = inout; + Np->last_relevant = NULL; + Np->read_post = 0; + Np->read_pre = 0; + Np->read_dec = false; + + if (Np->Num->zero_start) + --Np->Num->zero_start; + + if (IS_EEEE(Np->Num)) + { + if (!Np->is_to_char) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("\"EEEE\" not supported for input"))); + return strcpy(inout, number); + } + + /* + * Roman correction + */ + if (IS_ROMAN(Np->Num)) + { + if (!Np->is_to_char) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("\"RN\" not supported for input"))); + + Np->Num->lsign = Np->Num->pre_lsign_num = Np->Num->post = + Np->Num->pre = Np->out_pre_spaces = Np->sign = 0; + + if (IS_FILLMODE(Np->Num)) + { + Np->Num->flag = 0; + Np->Num->flag |= NUM_F_FILLMODE; + } + else + Np->Num->flag = 0; + Np->Num->flag |= NUM_F_ROMAN; + } + + /* + * Sign + */ + if (is_to_char) + { + Np->sign = sign; + + /* MI/PL/SG - write sign itself and not in number */ + if (IS_PLUS(Np->Num) || IS_MINUS(Np->Num)) + { + if (IS_PLUS(Np->Num) && IS_MINUS(Np->Num) == false) + Np->sign_wrote = false; /* need sign */ + else + Np->sign_wrote = true; /* needn't sign */ + } + else + { + if (Np->sign != '-') + { + if (IS_FILLMODE(Np->Num)) + Np->Num->flag &= ~NUM_F_BRACKET; + } + + if (Np->sign == '+' && IS_FILLMODE(Np->Num) && IS_LSIGN(Np->Num) == false) + Np->sign_wrote = true; /* needn't sign */ + else + Np->sign_wrote = false; /* need sign */ + + if (Np->Num->lsign == NUM_LSIGN_PRE && Np->Num->pre == Np->Num->pre_lsign_num) + Np->Num->lsign = NUM_LSIGN_POST; + } + } + else + Np->sign = false; + + /* + * Count + */ + Np->num_count = Np->Num->post + Np->Num->pre - 1; + + if (is_to_char) + { + Np->out_pre_spaces = to_char_out_pre_spaces; + + if (IS_FILLMODE(Np->Num) && IS_DECIMAL(Np->Num)) + { + Np->last_relevant = get_last_relevant_decnum(Np->number); + + /* + * If any '0' specifiers are present, make sure we don't strip + * those digits. But don't advance last_relevant beyond the last + * character of the Np->number string, which is a hazard if the + * number got shortened due to precision limitations. + */ + if (Np->last_relevant && Np->Num->zero_end > Np->out_pre_spaces) + { + int last_zero_pos; + char *last_zero; + + /* note that Np->number cannot be zero-length here */ + last_zero_pos = strlen(Np->number) - 1; + last_zero_pos = Min(last_zero_pos, + Np->Num->zero_end - Np->out_pre_spaces); + last_zero = Np->number + last_zero_pos; + if (Np->last_relevant < last_zero) + Np->last_relevant = last_zero; + } + } + + if (Np->sign_wrote == false && Np->out_pre_spaces == 0) + ++Np->num_count; + } + else + { + Np->out_pre_spaces = 0; + *Np->number = ' '; /* sign space */ + *(Np->number + 1) = '\0'; + } + + Np->num_in = 0; + Np->num_curr = 0; + +#ifdef DEBUG_TO_FROM_CHAR + elog(DEBUG_elog_output, + "\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", + Np->sign, + Np->number, + Np->Num->pre, + Np->Num->post, + Np->num_count, + Np->out_pre_spaces, + Np->sign_wrote ? "Yes" : "No", + IS_ZERO(Np->Num) ? "Yes" : "No", + Np->Num->zero_start, + Np->Num->zero_end, + Np->last_relevant ? Np->last_relevant : "<not set>", + IS_BRACKET(Np->Num) ? "Yes" : "No", + IS_PLUS(Np->Num) ? "Yes" : "No", + IS_MINUS(Np->Num) ? "Yes" : "No", + IS_FILLMODE(Np->Num) ? "Yes" : "No", + IS_ROMAN(Np->Num) ? "Yes" : "No", + IS_EEEE(Np->Num) ? "Yes" : "No" + ); +#endif + + /* + * Locale + */ + NUM_prepare_locale(Np); + + /* + * Processor direct cycle + */ + if (Np->is_to_char) + Np->number_p = Np->number; + else + Np->number_p = Np->number + 1; /* first char is space for sign */ + + for (n = node, Np->inout_p = Np->inout; n->type != NODE_TYPE_END; n++) + { + if (!Np->is_to_char) + { + /* + * Check at least one byte remains to be scanned. (In actions + * below, must use AMOUNT_TEST if we want to read more bytes than + * that.) + */ + if (OVERLOAD_TEST) + break; + } + + /* + * Format pictures actions + */ + if (n->type == NODE_TYPE_ACTION) + { + /* + * Create/read digit/zero/blank/sign/special-case + * + * 'NUM_S' note: The locale sign is anchored to number and we + * read/write it when we work with first or last number + * (NUM_0/NUM_9). This is why NUM_S is missing in switch(). + * + * Notice the "Np->inout_p++" at the bottom of the loop. This is + * why most of the actions advance inout_p one less than you might + * expect. In cases where we don't want that increment to happen, + * a switch case ends with "continue" not "break". + */ + switch (n->key->id) + { + case NUM_9: + case NUM_0: + case NUM_DEC: + case NUM_D: + if (Np->is_to_char) + { + NUM_numpart_to_char(Np, n->key->id); + continue; /* for() */ + } + else + { + NUM_numpart_from_char(Np, n->key->id, input_len); + break; /* switch() case: */ + } + + case NUM_COMMA: + if (Np->is_to_char) + { + if (!Np->num_in) + { + if (IS_FILLMODE(Np->Num)) + continue; + else + *Np->inout_p = ' '; + } + else + *Np->inout_p = ','; + } + else + { + if (!Np->num_in) + { + if (IS_FILLMODE(Np->Num)) + continue; + } + if (*Np->inout_p != ',') + continue; + } + break; + + case NUM_G: + pattern = Np->L_thousands_sep; + pattern_len = strlen(pattern); + if (Np->is_to_char) + { + if (!Np->num_in) + { + if (IS_FILLMODE(Np->Num)) + continue; + else + { + /* just in case there are MB chars */ + pattern_len = pg_mbstrlen(pattern); + memset(Np->inout_p, ' ', pattern_len); + Np->inout_p += pattern_len - 1; + } + } + else + { + strcpy(Np->inout_p, pattern); + Np->inout_p += pattern_len - 1; + } + } + else + { + if (!Np->num_in) + { + if (IS_FILLMODE(Np->Num)) + continue; + } + + /* + * Because L_thousands_sep typically contains data + * characters (either '.' or ','), we can't use + * NUM_eat_non_data_chars here. Instead skip only if + * the input matches L_thousands_sep. + */ + if (AMOUNT_TEST(pattern_len) && + strncmp(Np->inout_p, pattern, pattern_len) == 0) + Np->inout_p += pattern_len - 1; + else + continue; + } + break; + + case NUM_L: + pattern = Np->L_currency_symbol; + if (Np->is_to_char) + { + strcpy(Np->inout_p, pattern); + Np->inout_p += strlen(pattern) - 1; + } + else + { + NUM_eat_non_data_chars(Np, pg_mbstrlen(pattern), input_len); + continue; + } + break; + + case NUM_RN: + if (IS_FILLMODE(Np->Num)) + { + strcpy(Np->inout_p, Np->number_p); + Np->inout_p += strlen(Np->inout_p) - 1; + } + else + { + sprintf(Np->inout_p, "%15s", Np->number_p); + Np->inout_p += strlen(Np->inout_p) - 1; + } + break; + + case NUM_rn: + if (IS_FILLMODE(Np->Num)) + { + strcpy(Np->inout_p, asc_tolower_z(Np->number_p)); + Np->inout_p += strlen(Np->inout_p) - 1; + } + else + { + sprintf(Np->inout_p, "%15s", asc_tolower_z(Np->number_p)); + Np->inout_p += strlen(Np->inout_p) - 1; + } + break; + + case NUM_th: + if (IS_ROMAN(Np->Num) || *Np->number == '#' || + Np->sign == '-' || IS_DECIMAL(Np->Num)) + continue; + + if (Np->is_to_char) + { + strcpy(Np->inout_p, get_th(Np->number, TH_LOWER)); + Np->inout_p += 1; + } + else + { + /* All variants of 'th' occupy 2 characters */ + NUM_eat_non_data_chars(Np, 2, input_len); + continue; + } + break; + + case NUM_TH: + if (IS_ROMAN(Np->Num) || *Np->number == '#' || + Np->sign == '-' || IS_DECIMAL(Np->Num)) + continue; + + if (Np->is_to_char) + { + strcpy(Np->inout_p, get_th(Np->number, TH_UPPER)); + Np->inout_p += 1; + } + else + { + /* All variants of 'TH' occupy 2 characters */ + NUM_eat_non_data_chars(Np, 2, input_len); + continue; + } + break; + + case NUM_MI: + if (Np->is_to_char) + { + if (Np->sign == '-') + *Np->inout_p = '-'; + else if (IS_FILLMODE(Np->Num)) + continue; + else + *Np->inout_p = ' '; + } + else + { + if (*Np->inout_p == '-') + *Np->number = '-'; + else + { + NUM_eat_non_data_chars(Np, 1, input_len); + continue; + } + } + break; + + case NUM_PL: + if (Np->is_to_char) + { + if (Np->sign == '+') + *Np->inout_p = '+'; + else if (IS_FILLMODE(Np->Num)) + continue; + else + *Np->inout_p = ' '; + } + else + { + if (*Np->inout_p == '+') + *Np->number = '+'; + else + { + NUM_eat_non_data_chars(Np, 1, input_len); + continue; + } + } + break; + + case NUM_SG: + if (Np->is_to_char) + *Np->inout_p = Np->sign; + else + { + if (*Np->inout_p == '-') + *Np->number = '-'; + else if (*Np->inout_p == '+') + *Np->number = '+'; + else + { + NUM_eat_non_data_chars(Np, 1, input_len); + continue; + } + } + break; + + default: + continue; + break; + } + } + else + { + /* + * In TO_CHAR, non-pattern characters in the format are copied to + * the output. In TO_NUMBER, we skip one input character for each + * non-pattern format character, whether or not it matches the + * format character. + */ + if (Np->is_to_char) + { + strcpy(Np->inout_p, n->character); + Np->inout_p += strlen(Np->inout_p); + } + else + { + Np->inout_p += pg_mblen(Np->inout_p); + } + continue; + } + Np->inout_p++; + } + + if (Np->is_to_char) + { + *Np->inout_p = '\0'; + return Np->inout; + } + else + { + if (*(Np->number_p - 1) == '.') + *(Np->number_p - 1) = '\0'; + else + *Np->number_p = '\0'; + + /* + * Correction - precision of dec. number + */ + Np->Num->post = Np->read_post; + +#ifdef DEBUG_TO_FROM_CHAR + elog(DEBUG_elog_output, "TO_NUMBER (number): '%s'", Np->number); +#endif + return Np->number; + } +} + +/* ---------- + * MACRO: Start part of NUM - for all NUM's to_char variants + * (sorry, but I hate copy same code - macro is better..) + * ---------- + */ +#define NUM_TOCHAR_prepare \ +do { \ + int len = VARSIZE_ANY_EXHDR(fmt); \ + if (len <= 0 || len >= (INT_MAX-VARHDRSZ)/NUM_MAX_ITEM_SIZ) \ + PG_RETURN_TEXT_P(cstring_to_text("")); \ + result = (text *) palloc0((len * NUM_MAX_ITEM_SIZ) + 1 + VARHDRSZ); \ + format = NUM_cache(len, &Num, fmt, &shouldFree); \ +} while (0) + +/* ---------- + * MACRO: Finish part of NUM + * ---------- + */ +#define NUM_TOCHAR_finish \ +do { \ + int len; \ + \ + NUM_processor(format, &Num, VARDATA(result), numstr, 0, out_pre_spaces, sign, true, PG_GET_COLLATION()); \ + \ + if (shouldFree) \ + pfree(format); \ + \ + /* \ + * Convert null-terminated representation of result to standard text. \ + * The result is usually much bigger than it needs to be, but there \ + * seems little point in realloc'ing it smaller. \ + */ \ + len = strlen(VARDATA(result)); \ + SET_VARSIZE(result, len + VARHDRSZ); \ +} while (0) + +/* ------------------- + * NUMERIC to_number() (convert string to numeric) + * ------------------- + */ +Datum +numeric_to_number(PG_FUNCTION_ARGS) +{ + text *value = PG_GETARG_TEXT_PP(0); + text *fmt = PG_GETARG_TEXT_PP(1); + NUMDesc Num; + Datum result; + FormatNode *format; + char *numstr; + bool shouldFree; + int len = 0; + int scale, + precision; + + len = VARSIZE_ANY_EXHDR(fmt); + + if (len <= 0 || len >= INT_MAX / NUM_MAX_ITEM_SIZ) + PG_RETURN_NULL(); + + format = NUM_cache(len, &Num, fmt, &shouldFree); + + numstr = (char *) palloc((len * NUM_MAX_ITEM_SIZ) + 1); + + NUM_processor(format, &Num, VARDATA_ANY(value), numstr, + VARSIZE_ANY_EXHDR(value), 0, 0, false, PG_GET_COLLATION()); + + scale = Num.post; + precision = Num.pre + Num.multi + scale; + + if (shouldFree) + pfree(format); + + result = DirectFunctionCall3(numeric_in, + CStringGetDatum(numstr), + ObjectIdGetDatum(InvalidOid), + Int32GetDatum(((precision << 16) | scale) + VARHDRSZ)); + + if (IS_MULTI(&Num)) + { + Numeric x; + Numeric a = int64_to_numeric(10); + Numeric b = int64_to_numeric(-Num.multi); + + x = DatumGetNumeric(DirectFunctionCall2(numeric_power, + NumericGetDatum(a), + NumericGetDatum(b))); + result = DirectFunctionCall2(numeric_mul, + result, + NumericGetDatum(x)); + } + + pfree(numstr); + return result; +} + +/* ------------------ + * NUMERIC to_char() + * ------------------ + */ +Datum +numeric_to_char(PG_FUNCTION_ARGS) +{ + Numeric value = PG_GETARG_NUMERIC(0); + text *fmt = PG_GETARG_TEXT_PP(1); + NUMDesc Num; + FormatNode *format; + text *result; + bool shouldFree; + int out_pre_spaces = 0, + sign = 0; + char *numstr, + *orgnum, + *p; + Numeric x; + + NUM_TOCHAR_prepare; + + /* + * On DateType depend part (numeric) + */ + if (IS_ROMAN(&Num)) + { + x = DatumGetNumeric(DirectFunctionCall2(numeric_round, + NumericGetDatum(value), + Int32GetDatum(0))); + numstr = + int_to_roman(DatumGetInt32(DirectFunctionCall1(numeric_int4, + NumericGetDatum(x)))); + } + else if (IS_EEEE(&Num)) + { + orgnum = numeric_out_sci(value, Num.post); + + /* + * numeric_out_sci() does not emit a sign for positive numbers. We + * need to add a space in this case so that positive and negative + * numbers are aligned. Also must check for NaN/infinity cases, which + * we handle the same way as in float8_to_char. + */ + if (strcmp(orgnum, "NaN") == 0 || + strcmp(orgnum, "Infinity") == 0 || + strcmp(orgnum, "-Infinity") == 0) + { + /* + * Allow 6 characters for the leading sign, the decimal point, + * "e", the exponent's sign and two exponent digits. + */ + numstr = (char *) palloc(Num.pre + Num.post + 7); + fill_str(numstr, '#', Num.pre + Num.post + 6); + *numstr = ' '; + *(numstr + Num.pre + 1) = '.'; + } + else if (*orgnum != '-') + { + numstr = (char *) palloc(strlen(orgnum) + 2); + *numstr = ' '; + strcpy(numstr + 1, orgnum); + } + else + { + numstr = orgnum; + } + } + else + { + int numstr_pre_len; + Numeric val = value; + + if (IS_MULTI(&Num)) + { + Numeric a = int64_to_numeric(10); + Numeric b = int64_to_numeric(Num.multi); + + x = DatumGetNumeric(DirectFunctionCall2(numeric_power, + NumericGetDatum(a), + NumericGetDatum(b))); + val = DatumGetNumeric(DirectFunctionCall2(numeric_mul, + NumericGetDatum(value), + NumericGetDatum(x))); + Num.pre += Num.multi; + } + + x = DatumGetNumeric(DirectFunctionCall2(numeric_round, + NumericGetDatum(val), + Int32GetDatum(Num.post))); + orgnum = DatumGetCString(DirectFunctionCall1(numeric_out, + NumericGetDatum(x))); + + if (*orgnum == '-') + { + sign = '-'; + numstr = orgnum + 1; + } + else + { + sign = '+'; + numstr = orgnum; + } + + if ((p = strchr(numstr, '.'))) + numstr_pre_len = p - numstr; + else + numstr_pre_len = strlen(numstr); + + /* needs padding? */ + if (numstr_pre_len < Num.pre) + out_pre_spaces = Num.pre - numstr_pre_len; + /* overflowed prefix digit format? */ + else if (numstr_pre_len > Num.pre) + { + numstr = (char *) palloc(Num.pre + Num.post + 2); + fill_str(numstr, '#', Num.pre + Num.post + 1); + *(numstr + Num.pre) = '.'; + } + } + + NUM_TOCHAR_finish; + PG_RETURN_TEXT_P(result); +} + +/* --------------- + * INT4 to_char() + * --------------- + */ +Datum +int4_to_char(PG_FUNCTION_ARGS) +{ + int32 value = PG_GETARG_INT32(0); + text *fmt = PG_GETARG_TEXT_PP(1); + NUMDesc Num; + FormatNode *format; + text *result; + bool shouldFree; + int out_pre_spaces = 0, + sign = 0; + char *numstr, + *orgnum; + + NUM_TOCHAR_prepare; + + /* + * On DateType depend part (int32) + */ + if (IS_ROMAN(&Num)) + numstr = int_to_roman(value); + else if (IS_EEEE(&Num)) + { + /* we can do it easily because float8 won't lose any precision */ + float8 val = (float8) value; + + orgnum = (char *) psprintf("%+.*e", Num.post, val); + + /* + * Swap a leading positive sign for a space. + */ + if (*orgnum == '+') + *orgnum = ' '; + + numstr = orgnum; + } + else + { + int numstr_pre_len; + + if (IS_MULTI(&Num)) + { + orgnum = DatumGetCString(DirectFunctionCall1(int4out, + Int32GetDatum(value * ((int32) pow((double) 10, (double) Num.multi))))); + Num.pre += Num.multi; + } + else + { + orgnum = DatumGetCString(DirectFunctionCall1(int4out, + Int32GetDatum(value))); + } + + if (*orgnum == '-') + { + sign = '-'; + orgnum++; + } + else + sign = '+'; + + numstr_pre_len = strlen(orgnum); + + /* post-decimal digits? Pad out with zeros. */ + if (Num.post) + { + numstr = (char *) palloc(numstr_pre_len + Num.post + 2); + strcpy(numstr, orgnum); + *(numstr + numstr_pre_len) = '.'; + memset(numstr + numstr_pre_len + 1, '0', Num.post); + *(numstr + numstr_pre_len + Num.post + 1) = '\0'; + } + else + numstr = orgnum; + + /* needs padding? */ + if (numstr_pre_len < Num.pre) + out_pre_spaces = Num.pre - numstr_pre_len; + /* overflowed prefix digit format? */ + else if (numstr_pre_len > Num.pre) + { + numstr = (char *) palloc(Num.pre + Num.post + 2); + fill_str(numstr, '#', Num.pre + Num.post + 1); + *(numstr + Num.pre) = '.'; + } + } + + NUM_TOCHAR_finish; + PG_RETURN_TEXT_P(result); +} + +/* --------------- + * INT8 to_char() + * --------------- + */ +Datum +int8_to_char(PG_FUNCTION_ARGS) +{ + int64 value = PG_GETARG_INT64(0); + text *fmt = PG_GETARG_TEXT_PP(1); + NUMDesc Num; + FormatNode *format; + text *result; + bool shouldFree; + int out_pre_spaces = 0, + sign = 0; + char *numstr, + *orgnum; + + NUM_TOCHAR_prepare; + + /* + * On DateType depend part (int32) + */ + if (IS_ROMAN(&Num)) + { + /* Currently don't support int8 conversion to roman... */ + numstr = int_to_roman(DatumGetInt32(DirectFunctionCall1(int84, Int64GetDatum(value)))); + } + else if (IS_EEEE(&Num)) + { + /* to avoid loss of precision, must go via numeric not float8 */ + orgnum = numeric_out_sci(int64_to_numeric(value), + Num.post); + + /* + * numeric_out_sci() does not emit a sign for positive numbers. We + * need to add a space in this case so that positive and negative + * numbers are aligned. We don't have to worry about NaN/inf here. + */ + if (*orgnum != '-') + { + numstr = (char *) palloc(strlen(orgnum) + 2); + *numstr = ' '; + strcpy(numstr + 1, orgnum); + } + else + { + numstr = orgnum; + } + } + else + { + int numstr_pre_len; + + if (IS_MULTI(&Num)) + { + double multi = pow((double) 10, (double) Num.multi); + + value = DatumGetInt64(DirectFunctionCall2(int8mul, + Int64GetDatum(value), + DirectFunctionCall1(dtoi8, + Float8GetDatum(multi)))); + Num.pre += Num.multi; + } + + orgnum = DatumGetCString(DirectFunctionCall1(int8out, + Int64GetDatum(value))); + + if (*orgnum == '-') + { + sign = '-'; + orgnum++; + } + else + sign = '+'; + + numstr_pre_len = strlen(orgnum); + + /* post-decimal digits? Pad out with zeros. */ + if (Num.post) + { + numstr = (char *) palloc(numstr_pre_len + Num.post + 2); + strcpy(numstr, orgnum); + *(numstr + numstr_pre_len) = '.'; + memset(numstr + numstr_pre_len + 1, '0', Num.post); + *(numstr + numstr_pre_len + Num.post + 1) = '\0'; + } + else + numstr = orgnum; + + /* needs padding? */ + if (numstr_pre_len < Num.pre) + out_pre_spaces = Num.pre - numstr_pre_len; + /* overflowed prefix digit format? */ + else if (numstr_pre_len > Num.pre) + { + numstr = (char *) palloc(Num.pre + Num.post + 2); + fill_str(numstr, '#', Num.pre + Num.post + 1); + *(numstr + Num.pre) = '.'; + } + } + + NUM_TOCHAR_finish; + PG_RETURN_TEXT_P(result); +} + +/* ----------------- + * FLOAT4 to_char() + * ----------------- + */ +Datum +float4_to_char(PG_FUNCTION_ARGS) +{ + float4 value = PG_GETARG_FLOAT4(0); + text *fmt = PG_GETARG_TEXT_PP(1); + NUMDesc Num; + FormatNode *format; + text *result; + bool shouldFree; + int out_pre_spaces = 0, + sign = 0; + char *numstr, + *p; + + NUM_TOCHAR_prepare; + + if (IS_ROMAN(&Num)) + numstr = int_to_roman((int) rint(value)); + else if (IS_EEEE(&Num)) + { + if (isnan(value) || isinf(value)) + { + /* + * Allow 6 characters for the leading sign, the decimal point, + * "e", the exponent's sign and two exponent digits. + */ + numstr = (char *) palloc(Num.pre + Num.post + 7); + fill_str(numstr, '#', Num.pre + Num.post + 6); + *numstr = ' '; + *(numstr + Num.pre + 1) = '.'; + } + else + { + numstr = psprintf("%+.*e", Num.post, value); + + /* + * Swap a leading positive sign for a space. + */ + if (*numstr == '+') + *numstr = ' '; + } + } + else + { + float4 val = value; + char *orgnum; + int numstr_pre_len; + + if (IS_MULTI(&Num)) + { + float multi = pow((double) 10, (double) Num.multi); + + val = value * multi; + Num.pre += Num.multi; + } + + orgnum = psprintf("%.0f", fabs(val)); + numstr_pre_len = strlen(orgnum); + + /* adjust post digits to fit max float digits */ + if (numstr_pre_len >= FLT_DIG) + Num.post = 0; + else if (numstr_pre_len + Num.post > FLT_DIG) + Num.post = FLT_DIG - numstr_pre_len; + orgnum = psprintf("%.*f", Num.post, val); + + if (*orgnum == '-') + { /* < 0 */ + sign = '-'; + numstr = orgnum + 1; + } + else + { + sign = '+'; + numstr = orgnum; + } + + if ((p = strchr(numstr, '.'))) + numstr_pre_len = p - numstr; + else + numstr_pre_len = strlen(numstr); + + /* needs padding? */ + if (numstr_pre_len < Num.pre) + out_pre_spaces = Num.pre - numstr_pre_len; + /* overflowed prefix digit format? */ + else if (numstr_pre_len > Num.pre) + { + numstr = (char *) palloc(Num.pre + Num.post + 2); + fill_str(numstr, '#', Num.pre + Num.post + 1); + *(numstr + Num.pre) = '.'; + } + } + + NUM_TOCHAR_finish; + PG_RETURN_TEXT_P(result); +} + +/* ----------------- + * FLOAT8 to_char() + * ----------------- + */ +Datum +float8_to_char(PG_FUNCTION_ARGS) +{ + float8 value = PG_GETARG_FLOAT8(0); + text *fmt = PG_GETARG_TEXT_PP(1); + NUMDesc Num; + FormatNode *format; + text *result; + bool shouldFree; + int out_pre_spaces = 0, + sign = 0; + char *numstr, + *p; + + NUM_TOCHAR_prepare; + + if (IS_ROMAN(&Num)) + numstr = int_to_roman((int) rint(value)); + else if (IS_EEEE(&Num)) + { + if (isnan(value) || isinf(value)) + { + /* + * Allow 6 characters for the leading sign, the decimal point, + * "e", the exponent's sign and two exponent digits. + */ + numstr = (char *) palloc(Num.pre + Num.post + 7); + fill_str(numstr, '#', Num.pre + Num.post + 6); + *numstr = ' '; + *(numstr + Num.pre + 1) = '.'; + } + else + { + numstr = psprintf("%+.*e", Num.post, value); + + /* + * Swap a leading positive sign for a space. + */ + if (*numstr == '+') + *numstr = ' '; + } + } + else + { + float8 val = value; + char *orgnum; + int numstr_pre_len; + + if (IS_MULTI(&Num)) + { + double multi = pow((double) 10, (double) Num.multi); + + val = value * multi; + Num.pre += Num.multi; + } + + orgnum = psprintf("%.0f", fabs(val)); + numstr_pre_len = strlen(orgnum); + + /* adjust post digits to fit max double digits */ + if (numstr_pre_len >= DBL_DIG) + Num.post = 0; + else if (numstr_pre_len + Num.post > DBL_DIG) + Num.post = DBL_DIG - numstr_pre_len; + orgnum = psprintf("%.*f", Num.post, val); + + if (*orgnum == '-') + { /* < 0 */ + sign = '-'; + numstr = orgnum + 1; + } + else + { + sign = '+'; + numstr = orgnum; + } + + if ((p = strchr(numstr, '.'))) + numstr_pre_len = p - numstr; + else + numstr_pre_len = strlen(numstr); + + /* needs padding? */ + if (numstr_pre_len < Num.pre) + out_pre_spaces = Num.pre - numstr_pre_len; + /* overflowed prefix digit format? */ + else if (numstr_pre_len > Num.pre) + { + numstr = (char *) palloc(Num.pre + Num.post + 2); + fill_str(numstr, '#', Num.pre + Num.post + 1); + *(numstr + Num.pre) = '.'; + } + } + + NUM_TOCHAR_finish; + PG_RETURN_TEXT_P(result); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/genfile.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/genfile.c new file mode 100644 index 00000000000..f281ce98068 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/genfile.c @@ -0,0 +1,779 @@ +/*------------------------------------------------------------------------- + * + * genfile.c + * Functions for direct access to files + * + * + * Copyright (c) 2004-2023, PostgreSQL Global Development Group + * + * Author: Andreas Pflug <pgadmin@pse-consulting.de> + * + * IDENTIFICATION + * src/backend/utils/adt/genfile.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <sys/file.h> +#include <sys/stat.h> +#include <unistd.h> +#include <dirent.h> + +#include "access/htup_details.h" +#include "access/xlog_internal.h" +#include "catalog/pg_authid.h" +#include "catalog/pg_tablespace_d.h" +#include "catalog/pg_type.h" +#include "funcapi.h" +#include "mb/pg_wchar.h" +#include "miscadmin.h" +#include "postmaster/syslogger.h" +#include "replication/slot.h" +#include "storage/fd.h" +#include "utils/acl.h" +#include "utils/builtins.h" +#include "utils/memutils.h" +#include "utils/syscache.h" +#include "utils/timestamp.h" + + +/* + * Convert a "text" filename argument to C string, and check it's allowable. + * + * Filename may be absolute or relative to the DataDir, but we only allow + * absolute paths that match DataDir or Log_directory. + * + * This does a privilege check against the 'pg_read_server_files' role, so + * this function is really only appropriate for callers who are only checking + * 'read' access. Do not use this function if you are looking for a check + * for 'write' or 'program' access without updating it to access the type + * of check as an argument and checking the appropriate role membership. + */ +static char * +convert_and_check_filename(text *arg) +{ + char *filename; + + filename = text_to_cstring(arg); + canonicalize_path(filename); /* filename can change length here */ + + /* + * Roles with privileges of the 'pg_read_server_files' role are allowed to + * access any files on the server as the PG user, so no need to do any + * further checks here. + */ + if (has_privs_of_role(GetUserId(), ROLE_PG_READ_SERVER_FILES)) + return filename; + + /* + * User isn't a member of the pg_read_server_files role, so check if it's + * allowable + */ + if (is_absolute_path(filename)) + { + /* + * Allow absolute paths if within DataDir or Log_directory, even + * though Log_directory might be outside DataDir. + */ + if (!path_is_prefix_of_path(DataDir, filename) && + (!is_absolute_path(Log_directory) || + !path_is_prefix_of_path(Log_directory, filename))) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("absolute path not allowed"))); + } + else if (!path_is_relative_and_below_cwd(filename)) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("path must be in or below the data directory"))); + + return filename; +} + + +/* + * Read a section of a file, returning it as bytea + * + * Caller is responsible for all permissions checking. + * + * We read the whole of the file when bytes_to_read is negative. + */ +static bytea * +read_binary_file(const char *filename, int64 seek_offset, int64 bytes_to_read, + bool missing_ok) +{ + bytea *buf; + size_t nbytes = 0; + FILE *file; + + /* clamp request size to what we can actually deliver */ + if (bytes_to_read > (int64) (MaxAllocSize - VARHDRSZ)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("requested length too large"))); + + if ((file = AllocateFile(filename, PG_BINARY_R)) == NULL) + { + if (missing_ok && errno == ENOENT) + return NULL; + else + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not open file \"%s\" for reading: %m", + filename))); + } + + if (fseeko(file, (off_t) seek_offset, + (seek_offset >= 0) ? SEEK_SET : SEEK_END) != 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not seek in file \"%s\": %m", filename))); + + if (bytes_to_read >= 0) + { + /* If passed explicit read size just do it */ + buf = (bytea *) palloc((Size) bytes_to_read + VARHDRSZ); + + nbytes = fread(VARDATA(buf), 1, (size_t) bytes_to_read, file); + } + else + { + /* Negative read size, read rest of file */ + StringInfoData sbuf; + + initStringInfo(&sbuf); + /* Leave room in the buffer for the varlena length word */ + sbuf.len += VARHDRSZ; + Assert(sbuf.len < sbuf.maxlen); + + while (!(feof(file) || ferror(file))) + { + size_t rbytes; + + /* Minimum amount to read at a time */ +#define MIN_READ_SIZE 4096 + + /* + * If not at end of file, and sbuf.len is equal to MaxAllocSize - + * 1, then either the file is too large, or there is nothing left + * to read. Attempt to read one more byte to see if the end of + * file has been reached. If not, the file is too large; we'd + * rather give the error message for that ourselves. + */ + if (sbuf.len == MaxAllocSize - 1) + { + char rbuf[1]; + + if (fread(rbuf, 1, 1, file) != 0 || !feof(file)) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("file length too large"))); + else + break; + } + + /* OK, ensure that we can read at least MIN_READ_SIZE */ + enlargeStringInfo(&sbuf, MIN_READ_SIZE); + + /* + * stringinfo.c likes to allocate in powers of 2, so it's likely + * that much more space is available than we asked for. Use all + * of it, rather than making more fread calls than necessary. + */ + rbytes = fread(sbuf.data + sbuf.len, 1, + (size_t) (sbuf.maxlen - sbuf.len - 1), file); + sbuf.len += rbytes; + nbytes += rbytes; + } + + /* Now we can commandeer the stringinfo's buffer as the result */ + buf = (bytea *) sbuf.data; + } + + if (ferror(file)) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not read file \"%s\": %m", filename))); + + SET_VARSIZE(buf, nbytes + VARHDRSZ); + + FreeFile(file); + + return buf; +} + +/* + * Similar to read_binary_file, but we verify that the contents are valid + * in the database encoding. + */ +static text * +read_text_file(const char *filename, int64 seek_offset, int64 bytes_to_read, + bool missing_ok) +{ + bytea *buf; + + buf = read_binary_file(filename, seek_offset, bytes_to_read, missing_ok); + + if (buf != NULL) + { + /* Make sure the input is valid */ + pg_verifymbstr(VARDATA(buf), VARSIZE(buf) - VARHDRSZ, false); + + /* OK, we can cast it to text safely */ + return (text *) buf; + } + else + return NULL; +} + +/* + * Read a section of a file, returning it as text + * + * This function is kept to support adminpack 1.0. + */ +Datum +pg_read_file(PG_FUNCTION_ARGS) +{ + text *filename_t = PG_GETARG_TEXT_PP(0); + int64 seek_offset = 0; + int64 bytes_to_read = -1; + bool missing_ok = false; + char *filename; + text *result; + + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to read files with adminpack 1.0"), + /* translator: %s is a SQL function name */ + errhint("Consider using %s, which is part of core, instead.", + "pg_read_file()"))); + + /* handle optional arguments */ + if (PG_NARGS() >= 3) + { + seek_offset = PG_GETARG_INT64(1); + bytes_to_read = PG_GETARG_INT64(2); + + if (bytes_to_read < 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("requested length cannot be negative"))); + } + if (PG_NARGS() >= 4) + missing_ok = PG_GETARG_BOOL(3); + + filename = convert_and_check_filename(filename_t); + + result = read_text_file(filename, seek_offset, bytes_to_read, missing_ok); + if (result) + PG_RETURN_TEXT_P(result); + else + PG_RETURN_NULL(); +} + +/* + * Read a section of a file, returning it as text + * + * No superuser check done here- instead privileges are handled by the + * GRANT system. + * + * If read_to_eof is true, bytes_to_read must be -1, otherwise negative values + * are not allowed for bytes_to_read. + */ +static text * +pg_read_file_common(text *filename_t, int64 seek_offset, int64 bytes_to_read, + bool read_to_eof, bool missing_ok) +{ + if (read_to_eof) + Assert(bytes_to_read == -1); + else if (bytes_to_read < 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("requested length cannot be negative"))); + + return read_text_file(convert_and_check_filename(filename_t), + seek_offset, bytes_to_read, missing_ok); +} + +/* + * Read a section of a file, returning it as bytea + * + * Parameters are interpreted the same as pg_read_file_common(). + */ +static bytea * +pg_read_binary_file_common(text *filename_t, + int64 seek_offset, int64 bytes_to_read, + bool read_to_eof, bool missing_ok) +{ + if (read_to_eof) + Assert(bytes_to_read == -1); + else if (bytes_to_read < 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("requested length cannot be negative"))); + + return read_binary_file(convert_and_check_filename(filename_t), + seek_offset, bytes_to_read, missing_ok); +} + + +/* + * Wrapper functions for the variants of SQL functions pg_read_file() and + * pg_read_binary_file(). + * + * These are necessary to pass the sanity check in opr_sanity, which checks + * that all built-in functions that share the implementing C function take + * the same number of arguments. + */ +Datum +pg_read_file_off_len(PG_FUNCTION_ARGS) +{ + text *filename_t = PG_GETARG_TEXT_PP(0); + int64 seek_offset = PG_GETARG_INT64(1); + int64 bytes_to_read = PG_GETARG_INT64(2); + text *ret; + + ret = pg_read_file_common(filename_t, seek_offset, bytes_to_read, + false, false); + if (!ret) + PG_RETURN_NULL(); + + PG_RETURN_TEXT_P(ret); +} + +Datum +pg_read_file_off_len_missing(PG_FUNCTION_ARGS) +{ + text *filename_t = PG_GETARG_TEXT_PP(0); + int64 seek_offset = PG_GETARG_INT64(1); + int64 bytes_to_read = PG_GETARG_INT64(2); + bool missing_ok = PG_GETARG_BOOL(3); + text *ret; + + ret = pg_read_file_common(filename_t, seek_offset, bytes_to_read, + false, missing_ok); + + if (!ret) + PG_RETURN_NULL(); + + PG_RETURN_TEXT_P(ret); +} + +Datum +pg_read_file_all(PG_FUNCTION_ARGS) +{ + text *filename_t = PG_GETARG_TEXT_PP(0); + text *ret; + + ret = pg_read_file_common(filename_t, 0, -1, true, false); + + if (!ret) + PG_RETURN_NULL(); + + PG_RETURN_TEXT_P(ret); +} + +Datum +pg_read_file_all_missing(PG_FUNCTION_ARGS) +{ + text *filename_t = PG_GETARG_TEXT_PP(0); + bool missing_ok = PG_GETARG_BOOL(1); + text *ret; + + ret = pg_read_file_common(filename_t, 0, -1, true, missing_ok); + + if (!ret) + PG_RETURN_NULL(); + + PG_RETURN_TEXT_P(ret); +} + +Datum +pg_read_binary_file_off_len(PG_FUNCTION_ARGS) +{ + text *filename_t = PG_GETARG_TEXT_PP(0); + int64 seek_offset = PG_GETARG_INT64(1); + int64 bytes_to_read = PG_GETARG_INT64(2); + text *ret; + + ret = pg_read_binary_file_common(filename_t, seek_offset, bytes_to_read, + false, false); + if (!ret) + PG_RETURN_NULL(); + + PG_RETURN_BYTEA_P(ret); +} + +Datum +pg_read_binary_file_off_len_missing(PG_FUNCTION_ARGS) +{ + text *filename_t = PG_GETARG_TEXT_PP(0); + int64 seek_offset = PG_GETARG_INT64(1); + int64 bytes_to_read = PG_GETARG_INT64(2); + bool missing_ok = PG_GETARG_BOOL(3); + text *ret; + + ret = pg_read_binary_file_common(filename_t, seek_offset, bytes_to_read, + false, missing_ok); + if (!ret) + PG_RETURN_NULL(); + + PG_RETURN_BYTEA_P(ret); +} + +Datum +pg_read_binary_file_all(PG_FUNCTION_ARGS) +{ + text *filename_t = PG_GETARG_TEXT_PP(0); + text *ret; + + ret = pg_read_binary_file_common(filename_t, 0, -1, true, false); + + if (!ret) + PG_RETURN_NULL(); + + PG_RETURN_BYTEA_P(ret); +} + +Datum +pg_read_binary_file_all_missing(PG_FUNCTION_ARGS) +{ + text *filename_t = PG_GETARG_TEXT_PP(0); + bool missing_ok = PG_GETARG_BOOL(1); + text *ret; + + ret = pg_read_binary_file_common(filename_t, 0, -1, true, missing_ok); + + if (!ret) + PG_RETURN_NULL(); + + PG_RETURN_BYTEA_P(ret); +} + +/* + * stat a file + */ +Datum +pg_stat_file(PG_FUNCTION_ARGS) +{ + text *filename_t = PG_GETARG_TEXT_PP(0); + char *filename; + struct stat fst; + Datum values[6]; + bool isnull[6]; + HeapTuple tuple; + TupleDesc tupdesc; + bool missing_ok = false; + + /* check the optional argument */ + if (PG_NARGS() == 2) + missing_ok = PG_GETARG_BOOL(1); + + filename = convert_and_check_filename(filename_t); + + if (stat(filename, &fst) < 0) + { + if (missing_ok && errno == ENOENT) + PG_RETURN_NULL(); + else + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not stat file \"%s\": %m", filename))); + } + + /* + * This record type had better match the output parameters declared for me + * in pg_proc.h. + */ + tupdesc = CreateTemplateTupleDesc(6); + TupleDescInitEntry(tupdesc, (AttrNumber) 1, + "size", INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 2, + "access", TIMESTAMPTZOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 3, + "modification", TIMESTAMPTZOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 4, + "change", TIMESTAMPTZOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 5, + "creation", TIMESTAMPTZOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 6, + "isdir", BOOLOID, -1, 0); + BlessTupleDesc(tupdesc); + + memset(isnull, false, sizeof(isnull)); + + values[0] = Int64GetDatum((int64) fst.st_size); + values[1] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_atime)); + values[2] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_mtime)); + /* Unix has file status change time, while Win32 has creation time */ +#if !defined(WIN32) && !defined(__CYGWIN__) + values[3] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime)); + isnull[4] = true; +#else + isnull[3] = true; + values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime)); +#endif + values[5] = BoolGetDatum(S_ISDIR(fst.st_mode)); + + tuple = heap_form_tuple(tupdesc, values, isnull); + + pfree(filename); + + PG_RETURN_DATUM(HeapTupleGetDatum(tuple)); +} + +/* + * stat a file (1 argument version) + * + * note: this wrapper is necessary to pass the sanity check in opr_sanity, + * which checks that all built-in functions that share the implementing C + * function take the same number of arguments + */ +Datum +pg_stat_file_1arg(PG_FUNCTION_ARGS) +{ + return pg_stat_file(fcinfo); +} + +/* + * List a directory (returns the filenames only) + */ +Datum +pg_ls_dir(PG_FUNCTION_ARGS) +{ + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + char *location; + bool missing_ok = false; + bool include_dot_dirs = false; + DIR *dirdesc; + struct dirent *de; + + location = convert_and_check_filename(PG_GETARG_TEXT_PP(0)); + + /* check the optional arguments */ + if (PG_NARGS() == 3) + { + if (!PG_ARGISNULL(1)) + missing_ok = PG_GETARG_BOOL(1); + if (!PG_ARGISNULL(2)) + include_dot_dirs = PG_GETARG_BOOL(2); + } + + InitMaterializedSRF(fcinfo, MAT_SRF_USE_EXPECTED_DESC); + + dirdesc = AllocateDir(location); + if (!dirdesc) + { + /* Return empty tuplestore if appropriate */ + if (missing_ok && errno == ENOENT) + return (Datum) 0; + /* Otherwise, we can let ReadDir() throw the error */ + } + + while ((de = ReadDir(dirdesc, location)) != NULL) + { + Datum values[1]; + bool nulls[1]; + + if (!include_dot_dirs && + (strcmp(de->d_name, ".") == 0 || + strcmp(de->d_name, "..") == 0)) + continue; + + values[0] = CStringGetTextDatum(de->d_name); + nulls[0] = false; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, + values, nulls); + } + + FreeDir(dirdesc); + return (Datum) 0; +} + +/* + * List a directory (1 argument version) + * + * note: this wrapper is necessary to pass the sanity check in opr_sanity, + * which checks that all built-in functions that share the implementing C + * function take the same number of arguments. + */ +Datum +pg_ls_dir_1arg(PG_FUNCTION_ARGS) +{ + return pg_ls_dir(fcinfo); +} + +/* + * Generic function to return a directory listing of files. + * + * If the directory isn't there, silently return an empty set if missing_ok. + * Other unreadable-directory cases throw an error. + */ +static Datum +pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok) +{ + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + DIR *dirdesc; + struct dirent *de; + + InitMaterializedSRF(fcinfo, 0); + + /* + * Now walk the directory. Note that we must do this within a single SRF + * call, not leave the directory open across multiple calls, since we + * can't count on the SRF being run to completion. + */ + dirdesc = AllocateDir(dir); + if (!dirdesc) + { + /* Return empty tuplestore if appropriate */ + if (missing_ok && errno == ENOENT) + return (Datum) 0; + /* Otherwise, we can let ReadDir() throw the error */ + } + + while ((de = ReadDir(dirdesc, dir)) != NULL) + { + Datum values[3]; + bool nulls[3]; + char path[MAXPGPATH * 2]; + struct stat attrib; + + /* Skip hidden files */ + if (de->d_name[0] == '.') + continue; + + /* Get the file info */ + snprintf(path, sizeof(path), "%s/%s", dir, de->d_name); + if (stat(path, &attrib) < 0) + { + /* Ignore concurrently-deleted files, else complain */ + if (errno == ENOENT) + continue; + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not stat file \"%s\": %m", path))); + } + + /* Ignore anything but regular files */ + if (!S_ISREG(attrib.st_mode)) + continue; + + values[0] = CStringGetTextDatum(de->d_name); + values[1] = Int64GetDatum((int64) attrib.st_size); + values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime)); + memset(nulls, 0, sizeof(nulls)); + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + } + + FreeDir(dirdesc); + return (Datum) 0; +} + +/* Function to return the list of files in the log directory */ +Datum +pg_ls_logdir(PG_FUNCTION_ARGS) +{ + return pg_ls_dir_files(fcinfo, Log_directory, false); +} + +/* Function to return the list of files in the WAL directory */ +Datum +pg_ls_waldir(PG_FUNCTION_ARGS) +{ + return pg_ls_dir_files(fcinfo, XLOGDIR, false); +} + +/* + * Generic function to return the list of files in pgsql_tmp + */ +static Datum +pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc) +{ + char path[MAXPGPATH]; + + if (!SearchSysCacheExists1(TABLESPACEOID, ObjectIdGetDatum(tblspc))) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("tablespace with OID %u does not exist", + tblspc))); + + TempTablespacePath(path, tblspc); + return pg_ls_dir_files(fcinfo, path, true); +} + +/* + * Function to return the list of temporary files in the pg_default tablespace's + * pgsql_tmp directory + */ +Datum +pg_ls_tmpdir_noargs(PG_FUNCTION_ARGS) +{ + return pg_ls_tmpdir(fcinfo, DEFAULTTABLESPACE_OID); +} + +/* + * Function to return the list of temporary files in the specified tablespace's + * pgsql_tmp directory + */ +Datum +pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS) +{ + return pg_ls_tmpdir(fcinfo, PG_GETARG_OID(0)); +} + +/* + * Function to return the list of files in the WAL archive status directory. + */ +Datum +pg_ls_archive_statusdir(PG_FUNCTION_ARGS) +{ + return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true); +} + +/* + * Function to return the list of files in the pg_logical/snapshots directory. + */ +Datum +pg_ls_logicalsnapdir(PG_FUNCTION_ARGS) +{ + return pg_ls_dir_files(fcinfo, "pg_logical/snapshots", false); +} + +/* + * Function to return the list of files in the pg_logical/mappings directory. + */ +Datum +pg_ls_logicalmapdir(PG_FUNCTION_ARGS) +{ + return pg_ls_dir_files(fcinfo, "pg_logical/mappings", false); +} + +/* + * Function to return the list of files in the pg_replslot/<replication_slot> + * directory. + */ +Datum +pg_ls_replslotdir(PG_FUNCTION_ARGS) +{ + text *slotname_t; + char path[MAXPGPATH]; + char *slotname; + + slotname_t = PG_GETARG_TEXT_PP(0); + + slotname = text_to_cstring(slotname_t); + + if (!SearchNamedReplicationSlot(slotname, true)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("replication slot \"%s\" does not exist", + slotname))); + + snprintf(path, sizeof(path), "pg_replslot/%s", slotname); + return pg_ls_dir_files(fcinfo, path, false); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/geo_ops.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/geo_ops.c new file mode 100644 index 00000000000..53ee4b6f9cb --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/geo_ops.c @@ -0,0 +1,5562 @@ +/*------------------------------------------------------------------------- + * + * geo_ops.c + * 2D geometric operations + * + * This module implements the geometric functions and operators. The + * geometric types are (from simple to more complicated): + * + * - point + * - line + * - line segment + * - box + * - circle + * - polygon + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/adt/geo_ops.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <math.h> +#include <limits.h> +#include <float.h> +#include <ctype.h> + +#include "libpq/pqformat.h" +#include "miscadmin.h" +#include "nodes/miscnodes.h" +#include "utils/float.h" +#include "utils/fmgrprotos.h" +#include "utils/geo_decls.h" +#include "varatt.h" + +/* + * * Type constructors have this form: + * void type_construct(Type *result, ...); + * + * * Operators commonly have signatures such as + * void type1_operator_type2(Type *result, Type1 *obj1, Type2 *obj2); + * + * Common operators are: + * * Intersection point: + * bool type1_interpt_type2(Point *result, Type1 *obj1, Type2 *obj2); + * Return whether the two objects intersect. If *result is not NULL, + * it is set to the intersection point. + * + * * Containment: + * bool type1_contain_type2(Type1 *obj1, Type2 *obj2); + * Return whether obj1 contains obj2. + * bool type1_contain_type2(Type1 *contains_obj, Type1 *contained_obj); + * Return whether obj1 contains obj2 (used when types are the same) + * + * * Distance of closest point in or on obj1 to obj2: + * float8 type1_closept_type2(Point *result, Type1 *obj1, Type2 *obj2); + * Returns the shortest distance between two objects. If *result is not + * NULL, it is set to the closest point in or on obj1 to obj2. + * + * These functions may be used to implement multiple SQL-level operators. For + * example, determining whether two lines are parallel is done by checking + * whether they don't intersect. + */ + +/* + * Internal routines + */ + +enum path_delim +{ + PATH_NONE, PATH_OPEN, PATH_CLOSED +}; + +/* Routines for points */ +static inline void point_construct(Point *result, float8 x, float8 y); +static inline void point_add_point(Point *result, Point *pt1, Point *pt2); +static inline void point_sub_point(Point *result, Point *pt1, Point *pt2); +static inline void point_mul_point(Point *result, Point *pt1, Point *pt2); +static inline void point_div_point(Point *result, Point *pt1, Point *pt2); +static inline bool point_eq_point(Point *pt1, Point *pt2); +static inline float8 point_dt(Point *pt1, Point *pt2); +static inline float8 point_sl(Point *pt1, Point *pt2); +static int point_inside(Point *p, int npts, Point *plist); + +/* Routines for lines */ +static inline void line_construct(LINE *result, Point *pt, float8 m); +static inline float8 line_sl(LINE *line); +static inline float8 line_invsl(LINE *line); +static bool line_interpt_line(Point *result, LINE *l1, LINE *l2); +static bool line_contain_point(LINE *line, Point *point); +static float8 line_closept_point(Point *result, LINE *line, Point *point); + +/* Routines for line segments */ +static inline void statlseg_construct(LSEG *lseg, Point *pt1, Point *pt2); +static inline float8 lseg_sl(LSEG *lseg); +static inline float8 lseg_invsl(LSEG *lseg); +static bool lseg_interpt_line(Point *result, LSEG *lseg, LINE *line); +static bool lseg_interpt_lseg(Point *result, LSEG *l1, LSEG *l2); +static int lseg_crossing(float8 x, float8 y, float8 prev_x, float8 prev_y); +static bool lseg_contain_point(LSEG *lseg, Point *pt); +static float8 lseg_closept_point(Point *result, LSEG *lseg, Point *pt); +static float8 lseg_closept_line(Point *result, LSEG *lseg, LINE *line); +static float8 lseg_closept_lseg(Point *result, LSEG *on_lseg, LSEG *to_lseg); + +/* Routines for boxes */ +static inline void box_construct(BOX *result, Point *pt1, Point *pt2); +static void box_cn(Point *center, BOX *box); +static bool box_ov(BOX *box1, BOX *box2); +static float8 box_ar(BOX *box); +static float8 box_ht(BOX *box); +static float8 box_wd(BOX *box); +static bool box_contain_point(BOX *box, Point *point); +static bool box_contain_box(BOX *contains_box, BOX *contained_box); +static bool box_contain_lseg(BOX *box, LSEG *lseg); +static bool box_interpt_lseg(Point *result, BOX *box, LSEG *lseg); +static float8 box_closept_point(Point *result, BOX *box, Point *pt); +static float8 box_closept_lseg(Point *result, BOX *box, LSEG *lseg); + +/* Routines for circles */ +static float8 circle_ar(CIRCLE *circle); + +/* Routines for polygons */ +static void make_bound_box(POLYGON *poly); +static void poly_to_circle(CIRCLE *result, POLYGON *poly); +static bool lseg_inside_poly(Point *a, Point *b, POLYGON *poly, int start); +static bool poly_contain_poly(POLYGON *contains_poly, POLYGON *contained_poly); +static bool plist_same(int npts, Point *p1, Point *p2); +static float8 dist_ppoly_internal(Point *pt, POLYGON *poly); + +/* Routines for encoding and decoding */ +static bool single_decode(char *num, float8 *x, char **endptr_p, + const char *type_name, const char *orig_string, + Node *escontext); +static void single_encode(float8 x, StringInfo str); +static bool pair_decode(char *str, float8 *x, float8 *y, char **endptr_p, + const char *type_name, const char *orig_string, + Node *escontext); +static void pair_encode(float8 x, float8 y, StringInfo str); +static int pair_count(char *s, char delim); +static bool path_decode(char *str, bool opentype, int npts, Point *p, + bool *isopen, char **endptr_p, + const char *type_name, const char *orig_string, + Node *escontext); +static char *path_encode(enum path_delim path_delim, int npts, Point *pt); + + +/* + * Delimiters for input and output strings. + * LDELIM, RDELIM, and DELIM are left, right, and separator delimiters, respectively. + * LDELIM_EP, RDELIM_EP are left and right delimiters for paths with endpoints. + */ + +#define LDELIM '(' +#define RDELIM ')' +#define DELIM ',' +#define LDELIM_EP '[' +#define RDELIM_EP ']' +#define LDELIM_C '<' +#define RDELIM_C '>' +#define LDELIM_L '{' +#define RDELIM_L '}' + + +/* + * Geometric data types are composed of points. + * This code tries to support a common format throughout the data types, + * to allow for more predictable usage and data type conversion. + * The fundamental unit is the point. Other units are line segments, + * open paths, boxes, closed paths, and polygons (which should be considered + * non-intersecting closed paths). + * + * Data representation is as follows: + * point: (x,y) + * line segment: [(x1,y1),(x2,y2)] + * box: (x1,y1),(x2,y2) + * open path: [(x1,y1),...,(xn,yn)] + * closed path: ((x1,y1),...,(xn,yn)) + * polygon: ((x1,y1),...,(xn,yn)) + * + * For boxes, the points are opposite corners with the first point at the top right. + * For closed paths and polygons, the points should be reordered to allow + * fast and correct equality comparisons. + * + * XXX perhaps points in complex shapes should be reordered internally + * to allow faster internal operations, but should keep track of input order + * and restore that order for text output - tgl 97/01/16 + */ + +static bool +single_decode(char *num, float8 *x, char **endptr_p, + const char *type_name, const char *orig_string, + Node *escontext) +{ + *x = float8in_internal(num, endptr_p, type_name, orig_string, escontext); + return (!SOFT_ERROR_OCCURRED(escontext)); +} /* single_decode() */ + +static void +single_encode(float8 x, StringInfo str) +{ + char *xstr = float8out_internal(x); + + appendStringInfoString(str, xstr); + pfree(xstr); +} /* single_encode() */ + +static bool +pair_decode(char *str, float8 *x, float8 *y, char **endptr_p, + const char *type_name, const char *orig_string, + Node *escontext) +{ + bool has_delim; + + while (isspace((unsigned char) *str)) + str++; + if ((has_delim = (*str == LDELIM))) + str++; + + if (!single_decode(str, x, &str, type_name, orig_string, escontext)) + return false; + + if (*str++ != DELIM) + goto fail; + + if (!single_decode(str, y, &str, type_name, orig_string, escontext)) + return false; + + if (has_delim) + { + if (*str++ != RDELIM) + goto fail; + while (isspace((unsigned char) *str)) + str++; + } + + /* report stopping point if wanted, else complain if not end of string */ + if (endptr_p) + *endptr_p = str; + else if (*str != '\0') + goto fail; + return true; + +fail: + ereturn(escontext, false, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s: \"%s\"", + type_name, orig_string))); +} + +static void +pair_encode(float8 x, float8 y, StringInfo str) +{ + char *xstr = float8out_internal(x); + char *ystr = float8out_internal(y); + + appendStringInfo(str, "%s,%s", xstr, ystr); + pfree(xstr); + pfree(ystr); +} + +static bool +path_decode(char *str, bool opentype, int npts, Point *p, + bool *isopen, char **endptr_p, + const char *type_name, const char *orig_string, + Node *escontext) +{ + int depth = 0; + char *cp; + int i; + + while (isspace((unsigned char) *str)) + str++; + if ((*isopen = (*str == LDELIM_EP))) + { + /* no open delimiter allowed? */ + if (!opentype) + goto fail; + depth++; + str++; + } + else if (*str == LDELIM) + { + cp = (str + 1); + while (isspace((unsigned char) *cp)) + cp++; + if (*cp == LDELIM) + { + depth++; + str = cp; + } + else if (strrchr(str, LDELIM) == str) + { + depth++; + str = cp; + } + } + + for (i = 0; i < npts; i++) + { + if (!pair_decode(str, &(p->x), &(p->y), &str, type_name, orig_string, + escontext)) + return false; + if (*str == DELIM) + str++; + p++; + } + + while (depth > 0) + { + if (*str == RDELIM || (*str == RDELIM_EP && *isopen && depth == 1)) + { + depth--; + str++; + while (isspace((unsigned char) *str)) + str++; + } + else + goto fail; + } + + /* report stopping point if wanted, else complain if not end of string */ + if (endptr_p) + *endptr_p = str; + else if (*str != '\0') + goto fail; + return true; + +fail: + ereturn(escontext, false, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s: \"%s\"", + type_name, orig_string))); +} /* path_decode() */ + +static char * +path_encode(enum path_delim path_delim, int npts, Point *pt) +{ + StringInfoData str; + int i; + + initStringInfo(&str); + + switch (path_delim) + { + case PATH_CLOSED: + appendStringInfoChar(&str, LDELIM); + break; + case PATH_OPEN: + appendStringInfoChar(&str, LDELIM_EP); + break; + case PATH_NONE: + break; + } + + for (i = 0; i < npts; i++) + { + if (i > 0) + appendStringInfoChar(&str, DELIM); + appendStringInfoChar(&str, LDELIM); + pair_encode(pt->x, pt->y, &str); + appendStringInfoChar(&str, RDELIM); + pt++; + } + + switch (path_delim) + { + case PATH_CLOSED: + appendStringInfoChar(&str, RDELIM); + break; + case PATH_OPEN: + appendStringInfoChar(&str, RDELIM_EP); + break; + case PATH_NONE: + break; + } + + return str.data; +} /* path_encode() */ + +/*------------------------------------------------------------- + * pair_count - count the number of points + * allow the following notation: + * '((1,2),(3,4))' + * '(1,3,2,4)' + * require an odd number of delim characters in the string + *-------------------------------------------------------------*/ +static int +pair_count(char *s, char delim) +{ + int ndelim = 0; + + while ((s = strchr(s, delim)) != NULL) + { + ndelim++; + s++; + } + return (ndelim % 2) ? ((ndelim + 1) / 2) : -1; +} + + +/*********************************************************************** + ** + ** Routines for two-dimensional boxes. + ** + ***********************************************************************/ + +/*---------------------------------------------------------- + * Formatting and conversion routines. + *---------------------------------------------------------*/ + +/* box_in - convert a string to internal form. + * + * External format: (two corners of box) + * "(f8, f8), (f8, f8)" + * also supports the older style "(f8, f8, f8, f8)" + */ +Datum +box_in(PG_FUNCTION_ARGS) +{ + char *str = PG_GETARG_CSTRING(0); + Node *escontext = fcinfo->context; + BOX *box = (BOX *) palloc(sizeof(BOX)); + bool isopen; + float8 x, + y; + + if (!path_decode(str, false, 2, &(box->high), &isopen, NULL, "box", str, + escontext)) + PG_RETURN_NULL(); + + /* reorder corners if necessary... */ + if (float8_lt(box->high.x, box->low.x)) + { + x = box->high.x; + box->high.x = box->low.x; + box->low.x = x; + } + if (float8_lt(box->high.y, box->low.y)) + { + y = box->high.y; + box->high.y = box->low.y; + box->low.y = y; + } + + PG_RETURN_BOX_P(box); +} + +/* box_out - convert a box to external form. + */ +Datum +box_out(PG_FUNCTION_ARGS) +{ + BOX *box = PG_GETARG_BOX_P(0); + + PG_RETURN_CSTRING(path_encode(PATH_NONE, 2, &(box->high))); +} + +/* + * box_recv - converts external binary format to box + */ +Datum +box_recv(PG_FUNCTION_ARGS) +{ + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + BOX *box; + float8 x, + y; + + box = (BOX *) palloc(sizeof(BOX)); + + box->high.x = pq_getmsgfloat8(buf); + box->high.y = pq_getmsgfloat8(buf); + box->low.x = pq_getmsgfloat8(buf); + box->low.y = pq_getmsgfloat8(buf); + + /* reorder corners if necessary... */ + if (float8_lt(box->high.x, box->low.x)) + { + x = box->high.x; + box->high.x = box->low.x; + box->low.x = x; + } + if (float8_lt(box->high.y, box->low.y)) + { + y = box->high.y; + box->high.y = box->low.y; + box->low.y = y; + } + + PG_RETURN_BOX_P(box); +} + +/* + * box_send - converts box to binary format + */ +Datum +box_send(PG_FUNCTION_ARGS) +{ + BOX *box = PG_GETARG_BOX_P(0); + StringInfoData buf; + + pq_begintypsend(&buf); + pq_sendfloat8(&buf, box->high.x); + pq_sendfloat8(&buf, box->high.y); + pq_sendfloat8(&buf, box->low.x); + pq_sendfloat8(&buf, box->low.y); + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} + + +/* box_construct - fill in a new box. + */ +static inline void +box_construct(BOX *result, Point *pt1, Point *pt2) +{ + if (float8_gt(pt1->x, pt2->x)) + { + result->high.x = pt1->x; + result->low.x = pt2->x; + } + else + { + result->high.x = pt2->x; + result->low.x = pt1->x; + } + if (float8_gt(pt1->y, pt2->y)) + { + result->high.y = pt1->y; + result->low.y = pt2->y; + } + else + { + result->high.y = pt2->y; + result->low.y = pt1->y; + } +} + + +/*---------------------------------------------------------- + * Relational operators for BOXes. + * <, >, <=, >=, and == are based on box area. + *---------------------------------------------------------*/ + +/* box_same - are two boxes identical? + */ +Datum +box_same(PG_FUNCTION_ARGS) +{ + BOX *box1 = PG_GETARG_BOX_P(0); + BOX *box2 = PG_GETARG_BOX_P(1); + + PG_RETURN_BOOL(point_eq_point(&box1->high, &box2->high) && + point_eq_point(&box1->low, &box2->low)); +} + +/* box_overlap - does box1 overlap box2? + */ +Datum +box_overlap(PG_FUNCTION_ARGS) +{ + BOX *box1 = PG_GETARG_BOX_P(0); + BOX *box2 = PG_GETARG_BOX_P(1); + + PG_RETURN_BOOL(box_ov(box1, box2)); +} + +static bool +box_ov(BOX *box1, BOX *box2) +{ + return (FPle(box1->low.x, box2->high.x) && + FPle(box2->low.x, box1->high.x) && + FPle(box1->low.y, box2->high.y) && + FPle(box2->low.y, box1->high.y)); +} + +/* box_left - is box1 strictly left of box2? + */ +Datum +box_left(PG_FUNCTION_ARGS) +{ + BOX *box1 = PG_GETARG_BOX_P(0); + BOX *box2 = PG_GETARG_BOX_P(1); + + PG_RETURN_BOOL(FPlt(box1->high.x, box2->low.x)); +} + +/* box_overleft - is the right edge of box1 at or left of + * the right edge of box2? + * + * This is "less than or equal" for the end of a time range, + * when time ranges are stored as rectangles. + */ +Datum +box_overleft(PG_FUNCTION_ARGS) +{ + BOX *box1 = PG_GETARG_BOX_P(0); + BOX *box2 = PG_GETARG_BOX_P(1); + + PG_RETURN_BOOL(FPle(box1->high.x, box2->high.x)); +} + +/* box_right - is box1 strictly right of box2? + */ +Datum +box_right(PG_FUNCTION_ARGS) +{ + BOX *box1 = PG_GETARG_BOX_P(0); + BOX *box2 = PG_GETARG_BOX_P(1); + + PG_RETURN_BOOL(FPgt(box1->low.x, box2->high.x)); +} + +/* box_overright - is the left edge of box1 at or right of + * the left edge of box2? + * + * This is "greater than or equal" for time ranges, when time ranges + * are stored as rectangles. + */ +Datum +box_overright(PG_FUNCTION_ARGS) +{ + BOX *box1 = PG_GETARG_BOX_P(0); + BOX *box2 = PG_GETARG_BOX_P(1); + + PG_RETURN_BOOL(FPge(box1->low.x, box2->low.x)); +} + +/* box_below - is box1 strictly below box2? + */ +Datum +box_below(PG_FUNCTION_ARGS) +{ + BOX *box1 = PG_GETARG_BOX_P(0); + BOX *box2 = PG_GETARG_BOX_P(1); + + PG_RETURN_BOOL(FPlt(box1->high.y, box2->low.y)); +} + +/* box_overbelow - is the upper edge of box1 at or below + * the upper edge of box2? + */ +Datum +box_overbelow(PG_FUNCTION_ARGS) +{ + BOX *box1 = PG_GETARG_BOX_P(0); + BOX *box2 = PG_GETARG_BOX_P(1); + + PG_RETURN_BOOL(FPle(box1->high.y, box2->high.y)); +} + +/* box_above - is box1 strictly above box2? + */ +Datum +box_above(PG_FUNCTION_ARGS) +{ + BOX *box1 = PG_GETARG_BOX_P(0); + BOX *box2 = PG_GETARG_BOX_P(1); + + PG_RETURN_BOOL(FPgt(box1->low.y, box2->high.y)); +} + +/* box_overabove - is the lower edge of box1 at or above + * the lower edge of box2? + */ +Datum +box_overabove(PG_FUNCTION_ARGS) +{ + BOX *box1 = PG_GETARG_BOX_P(0); + BOX *box2 = PG_GETARG_BOX_P(1); + + PG_RETURN_BOOL(FPge(box1->low.y, box2->low.y)); +} + +/* box_contained - is box1 contained by box2? + */ +Datum +box_contained(PG_FUNCTION_ARGS) +{ + BOX *box1 = PG_GETARG_BOX_P(0); + BOX *box2 = PG_GETARG_BOX_P(1); + + PG_RETURN_BOOL(box_contain_box(box2, box1)); +} + +/* box_contain - does box1 contain box2? + */ +Datum +box_contain(PG_FUNCTION_ARGS) +{ + BOX *box1 = PG_GETARG_BOX_P(0); + BOX *box2 = PG_GETARG_BOX_P(1); + + PG_RETURN_BOOL(box_contain_box(box1, box2)); +} + +/* + * Check whether the second box is in the first box or on its border + */ +static bool +box_contain_box(BOX *contains_box, BOX *contained_box) +{ + return FPge(contains_box->high.x, contained_box->high.x) && + FPle(contains_box->low.x, contained_box->low.x) && + FPge(contains_box->high.y, contained_box->high.y) && + FPle(contains_box->low.y, contained_box->low.y); +} + + +/* box_positionop - + * is box1 entirely {above,below} box2? + * + * box_below_eq and box_above_eq are obsolete versions that (probably + * erroneously) accept the equal-boundaries case. Since these are not + * in sync with the box_left and box_right code, they are deprecated and + * not supported in the PG 8.1 rtree operator class extension. + */ +Datum +box_below_eq(PG_FUNCTION_ARGS) +{ + BOX *box1 = PG_GETARG_BOX_P(0); + BOX *box2 = PG_GETARG_BOX_P(1); + + PG_RETURN_BOOL(FPle(box1->high.y, box2->low.y)); +} + +Datum +box_above_eq(PG_FUNCTION_ARGS) +{ + BOX *box1 = PG_GETARG_BOX_P(0); + BOX *box2 = PG_GETARG_BOX_P(1); + + PG_RETURN_BOOL(FPge(box1->low.y, box2->high.y)); +} + + +/* box_relop - is area(box1) relop area(box2), within + * our accuracy constraint? + */ +Datum +box_lt(PG_FUNCTION_ARGS) +{ + BOX *box1 = PG_GETARG_BOX_P(0); + BOX *box2 = PG_GETARG_BOX_P(1); + + PG_RETURN_BOOL(FPlt(box_ar(box1), box_ar(box2))); +} + +Datum +box_gt(PG_FUNCTION_ARGS) +{ + BOX *box1 = PG_GETARG_BOX_P(0); + BOX *box2 = PG_GETARG_BOX_P(1); + + PG_RETURN_BOOL(FPgt(box_ar(box1), box_ar(box2))); +} + +Datum +box_eq(PG_FUNCTION_ARGS) +{ + BOX *box1 = PG_GETARG_BOX_P(0); + BOX *box2 = PG_GETARG_BOX_P(1); + + PG_RETURN_BOOL(FPeq(box_ar(box1), box_ar(box2))); +} + +Datum +box_le(PG_FUNCTION_ARGS) +{ + BOX *box1 = PG_GETARG_BOX_P(0); + BOX *box2 = PG_GETARG_BOX_P(1); + + PG_RETURN_BOOL(FPle(box_ar(box1), box_ar(box2))); +} + +Datum +box_ge(PG_FUNCTION_ARGS) +{ + BOX *box1 = PG_GETARG_BOX_P(0); + BOX *box2 = PG_GETARG_BOX_P(1); + + PG_RETURN_BOOL(FPge(box_ar(box1), box_ar(box2))); +} + + +/*---------------------------------------------------------- + * "Arithmetic" operators on boxes. + *---------------------------------------------------------*/ + +/* box_area - returns the area of the box. + */ +Datum +box_area(PG_FUNCTION_ARGS) +{ + BOX *box = PG_GETARG_BOX_P(0); + + PG_RETURN_FLOAT8(box_ar(box)); +} + + +/* box_width - returns the width of the box + * (horizontal magnitude). + */ +Datum +box_width(PG_FUNCTION_ARGS) +{ + BOX *box = PG_GETARG_BOX_P(0); + + PG_RETURN_FLOAT8(box_wd(box)); +} + + +/* box_height - returns the height of the box + * (vertical magnitude). + */ +Datum +box_height(PG_FUNCTION_ARGS) +{ + BOX *box = PG_GETARG_BOX_P(0); + + PG_RETURN_FLOAT8(box_ht(box)); +} + + +/* box_distance - returns the distance between the + * center points of two boxes. + */ +Datum +box_distance(PG_FUNCTION_ARGS) +{ + BOX *box1 = PG_GETARG_BOX_P(0); + BOX *box2 = PG_GETARG_BOX_P(1); + Point a, + b; + + box_cn(&a, box1); + box_cn(&b, box2); + + PG_RETURN_FLOAT8(point_dt(&a, &b)); +} + + +/* box_center - returns the center point of the box. + */ +Datum +box_center(PG_FUNCTION_ARGS) +{ + BOX *box = PG_GETARG_BOX_P(0); + Point *result = (Point *) palloc(sizeof(Point)); + + box_cn(result, box); + + PG_RETURN_POINT_P(result); +} + + +/* box_ar - returns the area of the box. + */ +static float8 +box_ar(BOX *box) +{ + return float8_mul(box_wd(box), box_ht(box)); +} + + +/* box_cn - stores the centerpoint of the box into *center. + */ +static void +box_cn(Point *center, BOX *box) +{ + center->x = float8_div(float8_pl(box->high.x, box->low.x), 2.0); + center->y = float8_div(float8_pl(box->high.y, box->low.y), 2.0); +} + + +/* box_wd - returns the width (length) of the box + * (horizontal magnitude). + */ +static float8 +box_wd(BOX *box) +{ + return float8_mi(box->high.x, box->low.x); +} + + +/* box_ht - returns the height of the box + * (vertical magnitude). + */ +static float8 +box_ht(BOX *box) +{ + return float8_mi(box->high.y, box->low.y); +} + + +/*---------------------------------------------------------- + * Funky operations. + *---------------------------------------------------------*/ + +/* box_intersect - + * returns the overlapping portion of two boxes, + * or NULL if they do not intersect. + */ +Datum +box_intersect(PG_FUNCTION_ARGS) +{ + BOX *box1 = PG_GETARG_BOX_P(0); + BOX *box2 = PG_GETARG_BOX_P(1); + BOX *result; + + if (!box_ov(box1, box2)) + PG_RETURN_NULL(); + + result = (BOX *) palloc(sizeof(BOX)); + + result->high.x = float8_min(box1->high.x, box2->high.x); + result->low.x = float8_max(box1->low.x, box2->low.x); + result->high.y = float8_min(box1->high.y, box2->high.y); + result->low.y = float8_max(box1->low.y, box2->low.y); + + PG_RETURN_BOX_P(result); +} + + +/* box_diagonal - + * returns a line segment which happens to be the + * positive-slope diagonal of "box". + */ +Datum +box_diagonal(PG_FUNCTION_ARGS) +{ + BOX *box = PG_GETARG_BOX_P(0); + LSEG *result = (LSEG *) palloc(sizeof(LSEG)); + + statlseg_construct(result, &box->high, &box->low); + + PG_RETURN_LSEG_P(result); +} + +/*********************************************************************** + ** + ** Routines for 2D lines. + ** + ***********************************************************************/ + +static bool +line_decode(char *s, const char *str, LINE *line, Node *escontext) +{ + /* s was already advanced over leading '{' */ + if (!single_decode(s, &line->A, &s, "line", str, escontext)) + return false; + if (*s++ != DELIM) + goto fail; + if (!single_decode(s, &line->B, &s, "line", str, escontext)) + return false; + if (*s++ != DELIM) + goto fail; + if (!single_decode(s, &line->C, &s, "line", str, escontext)) + return false; + if (*s++ != RDELIM_L) + goto fail; + while (isspace((unsigned char) *s)) + s++; + if (*s != '\0') + goto fail; + return true; + +fail: + ereturn(escontext, false, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s: \"%s\"", + "line", str))); +} + +Datum +line_in(PG_FUNCTION_ARGS) +{ + char *str = PG_GETARG_CSTRING(0); + Node *escontext = fcinfo->context; + LINE *line = (LINE *) palloc(sizeof(LINE)); + LSEG lseg; + bool isopen; + char *s; + + s = str; + while (isspace((unsigned char) *s)) + s++; + if (*s == LDELIM_L) + { + if (!line_decode(s + 1, str, line, escontext)) + PG_RETURN_NULL(); + if (FPzero(line->A) && FPzero(line->B)) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid line specification: A and B cannot both be zero"))); + } + else + { + if (!path_decode(s, true, 2, &lseg.p[0], &isopen, NULL, "line", str, + escontext)) + PG_RETURN_NULL(); + if (point_eq_point(&lseg.p[0], &lseg.p[1])) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid line specification: must be two distinct points"))); + + /* + * XXX lseg_sl() and line_construct() can throw overflow/underflow + * errors. Eventually we should allow those to be soft, but the + * notational pain seems to outweigh the value for now. + */ + line_construct(line, &lseg.p[0], lseg_sl(&lseg)); + } + + PG_RETURN_LINE_P(line); +} + + +Datum +line_out(PG_FUNCTION_ARGS) +{ + LINE *line = PG_GETARG_LINE_P(0); + char *astr = float8out_internal(line->A); + char *bstr = float8out_internal(line->B); + char *cstr = float8out_internal(line->C); + + PG_RETURN_CSTRING(psprintf("%c%s%c%s%c%s%c", LDELIM_L, astr, DELIM, bstr, + DELIM, cstr, RDELIM_L)); +} + +/* + * line_recv - converts external binary format to line + */ +Datum +line_recv(PG_FUNCTION_ARGS) +{ + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + LINE *line; + + line = (LINE *) palloc(sizeof(LINE)); + + line->A = pq_getmsgfloat8(buf); + line->B = pq_getmsgfloat8(buf); + line->C = pq_getmsgfloat8(buf); + + if (FPzero(line->A) && FPzero(line->B)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION), + errmsg("invalid line specification: A and B cannot both be zero"))); + + PG_RETURN_LINE_P(line); +} + +/* + * line_send - converts line to binary format + */ +Datum +line_send(PG_FUNCTION_ARGS) +{ + LINE *line = PG_GETARG_LINE_P(0); + StringInfoData buf; + + pq_begintypsend(&buf); + pq_sendfloat8(&buf, line->A); + pq_sendfloat8(&buf, line->B); + pq_sendfloat8(&buf, line->C); + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} + + +/*---------------------------------------------------------- + * Conversion routines from one line formula to internal. + * Internal form: Ax+By+C=0 + *---------------------------------------------------------*/ + +/* + * Fill already-allocated LINE struct from the point and the slope + */ +static inline void +line_construct(LINE *result, Point *pt, float8 m) +{ + if (isinf(m)) + { + /* vertical - use "x = C" */ + result->A = -1.0; + result->B = 0.0; + result->C = pt->x; + } + else if (m == 0) + { + /* horizontal - use "y = C" */ + result->A = 0.0; + result->B = -1.0; + result->C = pt->y; + } + else + { + /* use "mx - y + yinter = 0" */ + result->A = m; + result->B = -1.0; + result->C = float8_mi(pt->y, float8_mul(m, pt->x)); + /* on some platforms, the preceding expression tends to produce -0 */ + if (result->C == 0.0) + result->C = 0.0; + } +} + +/* line_construct_pp() + * two points + */ +Datum +line_construct_pp(PG_FUNCTION_ARGS) +{ + Point *pt1 = PG_GETARG_POINT_P(0); + Point *pt2 = PG_GETARG_POINT_P(1); + LINE *result = (LINE *) palloc(sizeof(LINE)); + + if (point_eq_point(pt1, pt2)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid line specification: must be two distinct points"))); + + line_construct(result, pt1, point_sl(pt1, pt2)); + + PG_RETURN_LINE_P(result); +} + + +/*---------------------------------------------------------- + * Relative position routines. + *---------------------------------------------------------*/ + +Datum +line_intersect(PG_FUNCTION_ARGS) +{ + LINE *l1 = PG_GETARG_LINE_P(0); + LINE *l2 = PG_GETARG_LINE_P(1); + + PG_RETURN_BOOL(line_interpt_line(NULL, l1, l2)); +} + +Datum +line_parallel(PG_FUNCTION_ARGS) +{ + LINE *l1 = PG_GETARG_LINE_P(0); + LINE *l2 = PG_GETARG_LINE_P(1); + + PG_RETURN_BOOL(!line_interpt_line(NULL, l1, l2)); +} + +Datum +line_perp(PG_FUNCTION_ARGS) +{ + LINE *l1 = PG_GETARG_LINE_P(0); + LINE *l2 = PG_GETARG_LINE_P(1); + + if (FPzero(l1->A)) + PG_RETURN_BOOL(FPzero(l2->B)); + if (FPzero(l2->A)) + PG_RETURN_BOOL(FPzero(l1->B)); + if (FPzero(l1->B)) + PG_RETURN_BOOL(FPzero(l2->A)); + if (FPzero(l2->B)) + PG_RETURN_BOOL(FPzero(l1->A)); + + PG_RETURN_BOOL(FPeq(float8_div(float8_mul(l1->A, l2->A), + float8_mul(l1->B, l2->B)), -1.0)); +} + +Datum +line_vertical(PG_FUNCTION_ARGS) +{ + LINE *line = PG_GETARG_LINE_P(0); + + PG_RETURN_BOOL(FPzero(line->B)); +} + +Datum +line_horizontal(PG_FUNCTION_ARGS) +{ + LINE *line = PG_GETARG_LINE_P(0); + + PG_RETURN_BOOL(FPzero(line->A)); +} + + +/* + * Check whether the two lines are the same + */ +Datum +line_eq(PG_FUNCTION_ARGS) +{ + LINE *l1 = PG_GETARG_LINE_P(0); + LINE *l2 = PG_GETARG_LINE_P(1); + float8 ratio; + + /* If any NaNs are involved, insist on exact equality */ + if (unlikely(isnan(l1->A) || isnan(l1->B) || isnan(l1->C) || + isnan(l2->A) || isnan(l2->B) || isnan(l2->C))) + { + PG_RETURN_BOOL(float8_eq(l1->A, l2->A) && + float8_eq(l1->B, l2->B) && + float8_eq(l1->C, l2->C)); + } + + /* Otherwise, lines whose parameters are proportional are the same */ + if (!FPzero(l2->A)) + ratio = float8_div(l1->A, l2->A); + else if (!FPzero(l2->B)) + ratio = float8_div(l1->B, l2->B); + else if (!FPzero(l2->C)) + ratio = float8_div(l1->C, l2->C); + else + ratio = 1.0; + + PG_RETURN_BOOL(FPeq(l1->A, float8_mul(ratio, l2->A)) && + FPeq(l1->B, float8_mul(ratio, l2->B)) && + FPeq(l1->C, float8_mul(ratio, l2->C))); +} + + +/*---------------------------------------------------------- + * Line arithmetic routines. + *---------------------------------------------------------*/ + +/* + * Return slope of the line + */ +static inline float8 +line_sl(LINE *line) +{ + if (FPzero(line->A)) + return 0.0; + if (FPzero(line->B)) + return get_float8_infinity(); + return float8_div(line->A, -line->B); +} + + +/* + * Return inverse slope of the line + */ +static inline float8 +line_invsl(LINE *line) +{ + if (FPzero(line->A)) + return get_float8_infinity(); + if (FPzero(line->B)) + return 0.0; + return float8_div(line->B, line->A); +} + + +/* line_distance() + * Distance between two lines. + */ +Datum +line_distance(PG_FUNCTION_ARGS) +{ + LINE *l1 = PG_GETARG_LINE_P(0); + LINE *l2 = PG_GETARG_LINE_P(1); + float8 ratio; + + if (line_interpt_line(NULL, l1, l2)) /* intersecting? */ + PG_RETURN_FLOAT8(0.0); + + if (!FPzero(l1->A) && !isnan(l1->A) && !FPzero(l2->A) && !isnan(l2->A)) + ratio = float8_div(l1->A, l2->A); + else if (!FPzero(l1->B) && !isnan(l1->B) && !FPzero(l2->B) && !isnan(l2->B)) + ratio = float8_div(l1->B, l2->B); + else + ratio = 1.0; + + PG_RETURN_FLOAT8(float8_div(fabs(float8_mi(l1->C, + float8_mul(ratio, l2->C))), + HYPOT(l1->A, l1->B))); +} + +/* line_interpt() + * Point where two lines l1, l2 intersect (if any) + */ +Datum +line_interpt(PG_FUNCTION_ARGS) +{ + LINE *l1 = PG_GETARG_LINE_P(0); + LINE *l2 = PG_GETARG_LINE_P(1); + Point *result; + + result = (Point *) palloc(sizeof(Point)); + + if (!line_interpt_line(result, l1, l2)) + PG_RETURN_NULL(); + PG_RETURN_POINT_P(result); +} + +/* + * Internal version of line_interpt + * + * Return whether two lines intersect. If *result is not NULL, it is set to + * the intersection point. + * + * NOTE: If the lines are identical then we will find they are parallel + * and report "no intersection". This is a little weird, but since + * there's no *unique* intersection, maybe it's appropriate behavior. + * + * If the lines have NaN constants, we will return true, and the intersection + * point would have NaN coordinates. We shouldn't return false in this case + * because that would mean the lines are parallel. + */ +static bool +line_interpt_line(Point *result, LINE *l1, LINE *l2) +{ + float8 x, + y; + + if (!FPzero(l1->B)) + { + if (FPeq(l2->A, float8_mul(l1->A, float8_div(l2->B, l1->B)))) + return false; + + x = float8_div(float8_mi(float8_mul(l1->B, l2->C), + float8_mul(l2->B, l1->C)), + float8_mi(float8_mul(l1->A, l2->B), + float8_mul(l2->A, l1->B))); + y = float8_div(-float8_pl(float8_mul(l1->A, x), l1->C), l1->B); + } + else if (!FPzero(l2->B)) + { + if (FPeq(l1->A, float8_mul(l2->A, float8_div(l1->B, l2->B)))) + return false; + + x = float8_div(float8_mi(float8_mul(l2->B, l1->C), + float8_mul(l1->B, l2->C)), + float8_mi(float8_mul(l2->A, l1->B), + float8_mul(l1->A, l2->B))); + y = float8_div(-float8_pl(float8_mul(l2->A, x), l2->C), l2->B); + } + else + return false; + + /* On some platforms, the preceding expressions tend to produce -0. */ + if (x == 0.0) + x = 0.0; + if (y == 0.0) + y = 0.0; + + if (result != NULL) + point_construct(result, x, y); + + return true; +} + + +/*********************************************************************** + ** + ** Routines for 2D paths (sequences of line segments, also + ** called `polylines'). + ** + ** This is not a general package for geometric paths, + ** which of course include polygons; the emphasis here + ** is on (for example) usefulness in wire layout. + ** + ***********************************************************************/ + +/*---------------------------------------------------------- + * String to path / path to string conversion. + * External format: + * "((xcoord, ycoord),... )" + * "[(xcoord, ycoord),... ]" + * "(xcoord, ycoord),... " + * "[xcoord, ycoord,... ]" + * Also support older format: + * "(closed, npts, xcoord, ycoord,... )" + *---------------------------------------------------------*/ + +Datum +path_area(PG_FUNCTION_ARGS) +{ + PATH *path = PG_GETARG_PATH_P(0); + float8 area = 0.0; + int i, + j; + + if (!path->closed) + PG_RETURN_NULL(); + + for (i = 0; i < path->npts; i++) + { + j = (i + 1) % path->npts; + area = float8_pl(area, float8_mul(path->p[i].x, path->p[j].y)); + area = float8_mi(area, float8_mul(path->p[i].y, path->p[j].x)); + } + + PG_RETURN_FLOAT8(float8_div(fabs(area), 2.0)); +} + + +Datum +path_in(PG_FUNCTION_ARGS) +{ + char *str = PG_GETARG_CSTRING(0); + Node *escontext = fcinfo->context; + PATH *path; + bool isopen; + char *s; + int npts; + int size; + int base_size; + int depth = 0; + + if ((npts = pair_count(str, ',')) <= 0) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s: \"%s\"", + "path", str))); + + s = str; + while (isspace((unsigned char) *s)) + s++; + + /* skip single leading paren */ + if ((*s == LDELIM) && (strrchr(s, LDELIM) == s)) + { + s++; + depth++; + } + + base_size = sizeof(path->p[0]) * npts; + size = offsetof(PATH, p) + base_size; + + /* Check for integer overflow */ + if (base_size / npts != sizeof(path->p[0]) || size <= base_size) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("too many points requested"))); + + path = (PATH *) palloc(size); + + SET_VARSIZE(path, size); + path->npts = npts; + + if (!path_decode(s, true, npts, &(path->p[0]), &isopen, &s, "path", str, + escontext)) + PG_RETURN_NULL(); + + if (depth >= 1) + { + if (*s++ != RDELIM) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s: \"%s\"", + "path", str))); + while (isspace((unsigned char) *s)) + s++; + } + if (*s != '\0') + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s: \"%s\"", + "path", str))); + + path->closed = (!isopen); + /* prevent instability in unused pad bytes */ + path->dummy = 0; + + PG_RETURN_PATH_P(path); +} + + +Datum +path_out(PG_FUNCTION_ARGS) +{ + PATH *path = PG_GETARG_PATH_P(0); + + PG_RETURN_CSTRING(path_encode(path->closed ? PATH_CLOSED : PATH_OPEN, path->npts, path->p)); +} + +/* + * path_recv - converts external binary format to path + * + * External representation is closed flag (a boolean byte), int32 number + * of points, and the points. + */ +Datum +path_recv(PG_FUNCTION_ARGS) +{ + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + PATH *path; + int closed; + int32 npts; + int32 i; + int size; + + closed = pq_getmsgbyte(buf); + npts = pq_getmsgint(buf, sizeof(int32)); + if (npts <= 0 || npts >= (int32) ((INT_MAX - offsetof(PATH, p)) / sizeof(Point))) + ereport(ERROR, + (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION), + errmsg("invalid number of points in external \"path\" value"))); + + size = offsetof(PATH, p) + sizeof(path->p[0]) * npts; + path = (PATH *) palloc(size); + + SET_VARSIZE(path, size); + path->npts = npts; + path->closed = (closed ? 1 : 0); + /* prevent instability in unused pad bytes */ + path->dummy = 0; + + for (i = 0; i < npts; i++) + { + path->p[i].x = pq_getmsgfloat8(buf); + path->p[i].y = pq_getmsgfloat8(buf); + } + + PG_RETURN_PATH_P(path); +} + +/* + * path_send - converts path to binary format + */ +Datum +path_send(PG_FUNCTION_ARGS) +{ + PATH *path = PG_GETARG_PATH_P(0); + StringInfoData buf; + int32 i; + + pq_begintypsend(&buf); + pq_sendbyte(&buf, path->closed ? 1 : 0); + pq_sendint32(&buf, path->npts); + for (i = 0; i < path->npts; i++) + { + pq_sendfloat8(&buf, path->p[i].x); + pq_sendfloat8(&buf, path->p[i].y); + } + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} + + +/*---------------------------------------------------------- + * Relational operators. + * These are based on the path cardinality, + * as stupid as that sounds. + * + * Better relops and access methods coming soon. + *---------------------------------------------------------*/ + +Datum +path_n_lt(PG_FUNCTION_ARGS) +{ + PATH *p1 = PG_GETARG_PATH_P(0); + PATH *p2 = PG_GETARG_PATH_P(1); + + PG_RETURN_BOOL(p1->npts < p2->npts); +} + +Datum +path_n_gt(PG_FUNCTION_ARGS) +{ + PATH *p1 = PG_GETARG_PATH_P(0); + PATH *p2 = PG_GETARG_PATH_P(1); + + PG_RETURN_BOOL(p1->npts > p2->npts); +} + +Datum +path_n_eq(PG_FUNCTION_ARGS) +{ + PATH *p1 = PG_GETARG_PATH_P(0); + PATH *p2 = PG_GETARG_PATH_P(1); + + PG_RETURN_BOOL(p1->npts == p2->npts); +} + +Datum +path_n_le(PG_FUNCTION_ARGS) +{ + PATH *p1 = PG_GETARG_PATH_P(0); + PATH *p2 = PG_GETARG_PATH_P(1); + + PG_RETURN_BOOL(p1->npts <= p2->npts); +} + +Datum +path_n_ge(PG_FUNCTION_ARGS) +{ + PATH *p1 = PG_GETARG_PATH_P(0); + PATH *p2 = PG_GETARG_PATH_P(1); + + PG_RETURN_BOOL(p1->npts >= p2->npts); +} + +/*---------------------------------------------------------- + * Conversion operators. + *---------------------------------------------------------*/ + +Datum +path_isclosed(PG_FUNCTION_ARGS) +{ + PATH *path = PG_GETARG_PATH_P(0); + + PG_RETURN_BOOL(path->closed); +} + +Datum +path_isopen(PG_FUNCTION_ARGS) +{ + PATH *path = PG_GETARG_PATH_P(0); + + PG_RETURN_BOOL(!path->closed); +} + +Datum +path_npoints(PG_FUNCTION_ARGS) +{ + PATH *path = PG_GETARG_PATH_P(0); + + PG_RETURN_INT32(path->npts); +} + + +Datum +path_close(PG_FUNCTION_ARGS) +{ + PATH *path = PG_GETARG_PATH_P_COPY(0); + + path->closed = true; + + PG_RETURN_PATH_P(path); +} + +Datum +path_open(PG_FUNCTION_ARGS) +{ + PATH *path = PG_GETARG_PATH_P_COPY(0); + + path->closed = false; + + PG_RETURN_PATH_P(path); +} + + +/* path_inter - + * Does p1 intersect p2 at any point? + * Use bounding boxes for a quick (O(n)) check, then do a + * O(n^2) iterative edge check. + */ +Datum +path_inter(PG_FUNCTION_ARGS) +{ + PATH *p1 = PG_GETARG_PATH_P(0); + PATH *p2 = PG_GETARG_PATH_P(1); + BOX b1, + b2; + int i, + j; + LSEG seg1, + seg2; + + Assert(p1->npts > 0 && p2->npts > 0); + + b1.high.x = b1.low.x = p1->p[0].x; + b1.high.y = b1.low.y = p1->p[0].y; + for (i = 1; i < p1->npts; i++) + { + b1.high.x = float8_max(p1->p[i].x, b1.high.x); + b1.high.y = float8_max(p1->p[i].y, b1.high.y); + b1.low.x = float8_min(p1->p[i].x, b1.low.x); + b1.low.y = float8_min(p1->p[i].y, b1.low.y); + } + b2.high.x = b2.low.x = p2->p[0].x; + b2.high.y = b2.low.y = p2->p[0].y; + for (i = 1; i < p2->npts; i++) + { + b2.high.x = float8_max(p2->p[i].x, b2.high.x); + b2.high.y = float8_max(p2->p[i].y, b2.high.y); + b2.low.x = float8_min(p2->p[i].x, b2.low.x); + b2.low.y = float8_min(p2->p[i].y, b2.low.y); + } + if (!box_ov(&b1, &b2)) + PG_RETURN_BOOL(false); + + /* pairwise check lseg intersections */ + for (i = 0; i < p1->npts; i++) + { + int iprev; + + if (i > 0) + iprev = i - 1; + else + { + if (!p1->closed) + continue; + iprev = p1->npts - 1; /* include the closure segment */ + } + + for (j = 0; j < p2->npts; j++) + { + int jprev; + + if (j > 0) + jprev = j - 1; + else + { + if (!p2->closed) + continue; + jprev = p2->npts - 1; /* include the closure segment */ + } + + statlseg_construct(&seg1, &p1->p[iprev], &p1->p[i]); + statlseg_construct(&seg2, &p2->p[jprev], &p2->p[j]); + if (lseg_interpt_lseg(NULL, &seg1, &seg2)) + PG_RETURN_BOOL(true); + } + } + + /* if we dropped through, no two segs intersected */ + PG_RETURN_BOOL(false); +} + +/* path_distance() + * This essentially does a cartesian product of the lsegs in the + * two paths, and finds the min distance between any two lsegs + */ +Datum +path_distance(PG_FUNCTION_ARGS) +{ + PATH *p1 = PG_GETARG_PATH_P(0); + PATH *p2 = PG_GETARG_PATH_P(1); + float8 min = 0.0; /* initialize to keep compiler quiet */ + bool have_min = false; + float8 tmp; + int i, + j; + LSEG seg1, + seg2; + + for (i = 0; i < p1->npts; i++) + { + int iprev; + + if (i > 0) + iprev = i - 1; + else + { + if (!p1->closed) + continue; + iprev = p1->npts - 1; /* include the closure segment */ + } + + for (j = 0; j < p2->npts; j++) + { + int jprev; + + if (j > 0) + jprev = j - 1; + else + { + if (!p2->closed) + continue; + jprev = p2->npts - 1; /* include the closure segment */ + } + + statlseg_construct(&seg1, &p1->p[iprev], &p1->p[i]); + statlseg_construct(&seg2, &p2->p[jprev], &p2->p[j]); + + tmp = lseg_closept_lseg(NULL, &seg1, &seg2); + if (!have_min || float8_lt(tmp, min)) + { + min = tmp; + have_min = true; + } + } + } + + if (!have_min) + PG_RETURN_NULL(); + + PG_RETURN_FLOAT8(min); +} + + +/*---------------------------------------------------------- + * "Arithmetic" operations. + *---------------------------------------------------------*/ + +Datum +path_length(PG_FUNCTION_ARGS) +{ + PATH *path = PG_GETARG_PATH_P(0); + float8 result = 0.0; + int i; + + for (i = 0; i < path->npts; i++) + { + int iprev; + + if (i > 0) + iprev = i - 1; + else + { + if (!path->closed) + continue; + iprev = path->npts - 1; /* include the closure segment */ + } + + result = float8_pl(result, point_dt(&path->p[iprev], &path->p[i])); + } + + PG_RETURN_FLOAT8(result); +} + +/*********************************************************************** + ** + ** Routines for 2D points. + ** + ***********************************************************************/ + +/*---------------------------------------------------------- + * String to point, point to string conversion. + * External format: + * "(x,y)" + * "x,y" + *---------------------------------------------------------*/ + +Datum +point_in(PG_FUNCTION_ARGS) +{ + char *str = PG_GETARG_CSTRING(0); + Point *point = (Point *) palloc(sizeof(Point)); + + /* Ignore failure from pair_decode, since our return value won't matter */ + pair_decode(str, &point->x, &point->y, NULL, "point", str, fcinfo->context); + PG_RETURN_POINT_P(point); +} + +Datum +point_out(PG_FUNCTION_ARGS) +{ + Point *pt = PG_GETARG_POINT_P(0); + + PG_RETURN_CSTRING(path_encode(PATH_NONE, 1, pt)); +} + +/* + * point_recv - converts external binary format to point + */ +Datum +point_recv(PG_FUNCTION_ARGS) +{ + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + Point *point; + + point = (Point *) palloc(sizeof(Point)); + point->x = pq_getmsgfloat8(buf); + point->y = pq_getmsgfloat8(buf); + PG_RETURN_POINT_P(point); +} + +/* + * point_send - converts point to binary format + */ +Datum +point_send(PG_FUNCTION_ARGS) +{ + Point *pt = PG_GETARG_POINT_P(0); + StringInfoData buf; + + pq_begintypsend(&buf); + pq_sendfloat8(&buf, pt->x); + pq_sendfloat8(&buf, pt->y); + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} + + +/* + * Initialize a point + */ +static inline void +point_construct(Point *result, float8 x, float8 y) +{ + result->x = x; + result->y = y; +} + + +/*---------------------------------------------------------- + * Relational operators for Points. + * Since we do have a sense of coordinates being + * "equal" to a given accuracy (point_vert, point_horiz), + * the other ops must preserve that sense. This means + * that results may, strictly speaking, be a lie (unless + * EPSILON = 0.0). + *---------------------------------------------------------*/ + +Datum +point_left(PG_FUNCTION_ARGS) +{ + Point *pt1 = PG_GETARG_POINT_P(0); + Point *pt2 = PG_GETARG_POINT_P(1); + + PG_RETURN_BOOL(FPlt(pt1->x, pt2->x)); +} + +Datum +point_right(PG_FUNCTION_ARGS) +{ + Point *pt1 = PG_GETARG_POINT_P(0); + Point *pt2 = PG_GETARG_POINT_P(1); + + PG_RETURN_BOOL(FPgt(pt1->x, pt2->x)); +} + +Datum +point_above(PG_FUNCTION_ARGS) +{ + Point *pt1 = PG_GETARG_POINT_P(0); + Point *pt2 = PG_GETARG_POINT_P(1); + + PG_RETURN_BOOL(FPgt(pt1->y, pt2->y)); +} + +Datum +point_below(PG_FUNCTION_ARGS) +{ + Point *pt1 = PG_GETARG_POINT_P(0); + Point *pt2 = PG_GETARG_POINT_P(1); + + PG_RETURN_BOOL(FPlt(pt1->y, pt2->y)); +} + +Datum +point_vert(PG_FUNCTION_ARGS) +{ + Point *pt1 = PG_GETARG_POINT_P(0); + Point *pt2 = PG_GETARG_POINT_P(1); + + PG_RETURN_BOOL(FPeq(pt1->x, pt2->x)); +} + +Datum +point_horiz(PG_FUNCTION_ARGS) +{ + Point *pt1 = PG_GETARG_POINT_P(0); + Point *pt2 = PG_GETARG_POINT_P(1); + + PG_RETURN_BOOL(FPeq(pt1->y, pt2->y)); +} + +Datum +point_eq(PG_FUNCTION_ARGS) +{ + Point *pt1 = PG_GETARG_POINT_P(0); + Point *pt2 = PG_GETARG_POINT_P(1); + + PG_RETURN_BOOL(point_eq_point(pt1, pt2)); +} + +Datum +point_ne(PG_FUNCTION_ARGS) +{ + Point *pt1 = PG_GETARG_POINT_P(0); + Point *pt2 = PG_GETARG_POINT_P(1); + + PG_RETURN_BOOL(!point_eq_point(pt1, pt2)); +} + + +/* + * Check whether the two points are the same + */ +static inline bool +point_eq_point(Point *pt1, Point *pt2) +{ + /* If any NaNs are involved, insist on exact equality */ + if (unlikely(isnan(pt1->x) || isnan(pt1->y) || + isnan(pt2->x) || isnan(pt2->y))) + return (float8_eq(pt1->x, pt2->x) && float8_eq(pt1->y, pt2->y)); + + return (FPeq(pt1->x, pt2->x) && FPeq(pt1->y, pt2->y)); +} + + +/*---------------------------------------------------------- + * "Arithmetic" operators on points. + *---------------------------------------------------------*/ + +Datum +point_distance(PG_FUNCTION_ARGS) +{ + Point *pt1 = PG_GETARG_POINT_P(0); + Point *pt2 = PG_GETARG_POINT_P(1); + + PG_RETURN_FLOAT8(point_dt(pt1, pt2)); +} + +static inline float8 +point_dt(Point *pt1, Point *pt2) +{ + return HYPOT(float8_mi(pt1->x, pt2->x), float8_mi(pt1->y, pt2->y)); +} + +Datum +point_slope(PG_FUNCTION_ARGS) +{ + Point *pt1 = PG_GETARG_POINT_P(0); + Point *pt2 = PG_GETARG_POINT_P(1); + + PG_RETURN_FLOAT8(point_sl(pt1, pt2)); +} + + +/* + * Return slope of two points + * + * Note that this function returns Inf when the points are the same. + */ +static inline float8 +point_sl(Point *pt1, Point *pt2) +{ + if (FPeq(pt1->x, pt2->x)) + return get_float8_infinity(); + if (FPeq(pt1->y, pt2->y)) + return 0.0; + return float8_div(float8_mi(pt1->y, pt2->y), float8_mi(pt1->x, pt2->x)); +} + + +/* + * Return inverse slope of two points + * + * Note that this function returns 0.0 when the points are the same. + */ +static inline float8 +point_invsl(Point *pt1, Point *pt2) +{ + if (FPeq(pt1->x, pt2->x)) + return 0.0; + if (FPeq(pt1->y, pt2->y)) + return get_float8_infinity(); + return float8_div(float8_mi(pt1->x, pt2->x), float8_mi(pt2->y, pt1->y)); +} + + +/*********************************************************************** + ** + ** Routines for 2D line segments. + ** + ***********************************************************************/ + +/*---------------------------------------------------------- + * String to lseg, lseg to string conversion. + * External forms: "[(x1, y1), (x2, y2)]" + * "(x1, y1), (x2, y2)" + * "x1, y1, x2, y2" + * closed form ok "((x1, y1), (x2, y2))" + * (old form) "(x1, y1, x2, y2)" + *---------------------------------------------------------*/ + +Datum +lseg_in(PG_FUNCTION_ARGS) +{ + char *str = PG_GETARG_CSTRING(0); + Node *escontext = fcinfo->context; + LSEG *lseg = (LSEG *) palloc(sizeof(LSEG)); + bool isopen; + + if (!path_decode(str, true, 2, &lseg->p[0], &isopen, NULL, "lseg", str, + escontext)) + PG_RETURN_NULL(); + + PG_RETURN_LSEG_P(lseg); +} + + +Datum +lseg_out(PG_FUNCTION_ARGS) +{ + LSEG *ls = PG_GETARG_LSEG_P(0); + + PG_RETURN_CSTRING(path_encode(PATH_OPEN, 2, &ls->p[0])); +} + +/* + * lseg_recv - converts external binary format to lseg + */ +Datum +lseg_recv(PG_FUNCTION_ARGS) +{ + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + LSEG *lseg; + + lseg = (LSEG *) palloc(sizeof(LSEG)); + + lseg->p[0].x = pq_getmsgfloat8(buf); + lseg->p[0].y = pq_getmsgfloat8(buf); + lseg->p[1].x = pq_getmsgfloat8(buf); + lseg->p[1].y = pq_getmsgfloat8(buf); + + PG_RETURN_LSEG_P(lseg); +} + +/* + * lseg_send - converts lseg to binary format + */ +Datum +lseg_send(PG_FUNCTION_ARGS) +{ + LSEG *ls = PG_GETARG_LSEG_P(0); + StringInfoData buf; + + pq_begintypsend(&buf); + pq_sendfloat8(&buf, ls->p[0].x); + pq_sendfloat8(&buf, ls->p[0].y); + pq_sendfloat8(&buf, ls->p[1].x); + pq_sendfloat8(&buf, ls->p[1].y); + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} + + +/* lseg_construct - + * form a LSEG from two Points. + */ +Datum +lseg_construct(PG_FUNCTION_ARGS) +{ + Point *pt1 = PG_GETARG_POINT_P(0); + Point *pt2 = PG_GETARG_POINT_P(1); + LSEG *result = (LSEG *) palloc(sizeof(LSEG)); + + statlseg_construct(result, pt1, pt2); + + PG_RETURN_LSEG_P(result); +} + +/* like lseg_construct, but assume space already allocated */ +static inline void +statlseg_construct(LSEG *lseg, Point *pt1, Point *pt2) +{ + lseg->p[0].x = pt1->x; + lseg->p[0].y = pt1->y; + lseg->p[1].x = pt2->x; + lseg->p[1].y = pt2->y; +} + + +/* + * Return slope of the line segment + */ +static inline float8 +lseg_sl(LSEG *lseg) +{ + return point_sl(&lseg->p[0], &lseg->p[1]); +} + + +/* + * Return inverse slope of the line segment + */ +static inline float8 +lseg_invsl(LSEG *lseg) +{ + return point_invsl(&lseg->p[0], &lseg->p[1]); +} + + +Datum +lseg_length(PG_FUNCTION_ARGS) +{ + LSEG *lseg = PG_GETARG_LSEG_P(0); + + PG_RETURN_FLOAT8(point_dt(&lseg->p[0], &lseg->p[1])); +} + +/*---------------------------------------------------------- + * Relative position routines. + *---------------------------------------------------------*/ + +/* + ** find intersection of the two lines, and see if it falls on + ** both segments. + */ +Datum +lseg_intersect(PG_FUNCTION_ARGS) +{ + LSEG *l1 = PG_GETARG_LSEG_P(0); + LSEG *l2 = PG_GETARG_LSEG_P(1); + + PG_RETURN_BOOL(lseg_interpt_lseg(NULL, l1, l2)); +} + + +Datum +lseg_parallel(PG_FUNCTION_ARGS) +{ + LSEG *l1 = PG_GETARG_LSEG_P(0); + LSEG *l2 = PG_GETARG_LSEG_P(1); + + PG_RETURN_BOOL(FPeq(lseg_sl(l1), lseg_sl(l2))); +} + +/* + * Determine if two line segments are perpendicular. + */ +Datum +lseg_perp(PG_FUNCTION_ARGS) +{ + LSEG *l1 = PG_GETARG_LSEG_P(0); + LSEG *l2 = PG_GETARG_LSEG_P(1); + + PG_RETURN_BOOL(FPeq(lseg_sl(l1), lseg_invsl(l2))); +} + +Datum +lseg_vertical(PG_FUNCTION_ARGS) +{ + LSEG *lseg = PG_GETARG_LSEG_P(0); + + PG_RETURN_BOOL(FPeq(lseg->p[0].x, lseg->p[1].x)); +} + +Datum +lseg_horizontal(PG_FUNCTION_ARGS) +{ + LSEG *lseg = PG_GETARG_LSEG_P(0); + + PG_RETURN_BOOL(FPeq(lseg->p[0].y, lseg->p[1].y)); +} + + +Datum +lseg_eq(PG_FUNCTION_ARGS) +{ + LSEG *l1 = PG_GETARG_LSEG_P(0); + LSEG *l2 = PG_GETARG_LSEG_P(1); + + PG_RETURN_BOOL(point_eq_point(&l1->p[0], &l2->p[0]) && + point_eq_point(&l1->p[1], &l2->p[1])); +} + +Datum +lseg_ne(PG_FUNCTION_ARGS) +{ + LSEG *l1 = PG_GETARG_LSEG_P(0); + LSEG *l2 = PG_GETARG_LSEG_P(1); + + PG_RETURN_BOOL(!point_eq_point(&l1->p[0], &l2->p[0]) || + !point_eq_point(&l1->p[1], &l2->p[1])); +} + +Datum +lseg_lt(PG_FUNCTION_ARGS) +{ + LSEG *l1 = PG_GETARG_LSEG_P(0); + LSEG *l2 = PG_GETARG_LSEG_P(1); + + PG_RETURN_BOOL(FPlt(point_dt(&l1->p[0], &l1->p[1]), + point_dt(&l2->p[0], &l2->p[1]))); +} + +Datum +lseg_le(PG_FUNCTION_ARGS) +{ + LSEG *l1 = PG_GETARG_LSEG_P(0); + LSEG *l2 = PG_GETARG_LSEG_P(1); + + PG_RETURN_BOOL(FPle(point_dt(&l1->p[0], &l1->p[1]), + point_dt(&l2->p[0], &l2->p[1]))); +} + +Datum +lseg_gt(PG_FUNCTION_ARGS) +{ + LSEG *l1 = PG_GETARG_LSEG_P(0); + LSEG *l2 = PG_GETARG_LSEG_P(1); + + PG_RETURN_BOOL(FPgt(point_dt(&l1->p[0], &l1->p[1]), + point_dt(&l2->p[0], &l2->p[1]))); +} + +Datum +lseg_ge(PG_FUNCTION_ARGS) +{ + LSEG *l1 = PG_GETARG_LSEG_P(0); + LSEG *l2 = PG_GETARG_LSEG_P(1); + + PG_RETURN_BOOL(FPge(point_dt(&l1->p[0], &l1->p[1]), + point_dt(&l2->p[0], &l2->p[1]))); +} + + +/*---------------------------------------------------------- + * Line arithmetic routines. + *---------------------------------------------------------*/ + +/* lseg_distance - + * If two segments don't intersect, then the closest + * point will be from one of the endpoints to the other + * segment. + */ +Datum +lseg_distance(PG_FUNCTION_ARGS) +{ + LSEG *l1 = PG_GETARG_LSEG_P(0); + LSEG *l2 = PG_GETARG_LSEG_P(1); + + PG_RETURN_FLOAT8(lseg_closept_lseg(NULL, l1, l2)); +} + + +Datum +lseg_center(PG_FUNCTION_ARGS) +{ + LSEG *lseg = PG_GETARG_LSEG_P(0); + Point *result; + + result = (Point *) palloc(sizeof(Point)); + + result->x = float8_div(float8_pl(lseg->p[0].x, lseg->p[1].x), 2.0); + result->y = float8_div(float8_pl(lseg->p[0].y, lseg->p[1].y), 2.0); + + PG_RETURN_POINT_P(result); +} + + +/* + * Return whether the two segments intersect. If *result is not NULL, + * it is set to the intersection point. + * + * This function is almost perfectly symmetric, even though it doesn't look + * like it. See lseg_interpt_line() for the other half of it. + */ +static bool +lseg_interpt_lseg(Point *result, LSEG *l1, LSEG *l2) +{ + Point interpt; + LINE tmp; + + line_construct(&tmp, &l2->p[0], lseg_sl(l2)); + if (!lseg_interpt_line(&interpt, l1, &tmp)) + return false; + + /* + * If the line intersection point isn't within l2, there is no valid + * segment intersection point at all. + */ + if (!lseg_contain_point(l2, &interpt)) + return false; + + if (result != NULL) + *result = interpt; + + return true; +} + +Datum +lseg_interpt(PG_FUNCTION_ARGS) +{ + LSEG *l1 = PG_GETARG_LSEG_P(0); + LSEG *l2 = PG_GETARG_LSEG_P(1); + Point *result; + + result = (Point *) palloc(sizeof(Point)); + + if (!lseg_interpt_lseg(result, l1, l2)) + PG_RETURN_NULL(); + PG_RETURN_POINT_P(result); +} + +/*********************************************************************** + ** + ** Routines for position comparisons of differently-typed + ** 2D objects. + ** + ***********************************************************************/ + +/*--------------------------------------------------------------------- + * dist_ + * Minimum distance from one object to another. + *-------------------------------------------------------------------*/ + +/* + * Distance from a point to a line + */ +Datum +dist_pl(PG_FUNCTION_ARGS) +{ + Point *pt = PG_GETARG_POINT_P(0); + LINE *line = PG_GETARG_LINE_P(1); + + PG_RETURN_FLOAT8(line_closept_point(NULL, line, pt)); +} + +/* + * Distance from a line to a point + */ +Datum +dist_lp(PG_FUNCTION_ARGS) +{ + LINE *line = PG_GETARG_LINE_P(0); + Point *pt = PG_GETARG_POINT_P(1); + + PG_RETURN_FLOAT8(line_closept_point(NULL, line, pt)); +} + +/* + * Distance from a point to a lseg + */ +Datum +dist_ps(PG_FUNCTION_ARGS) +{ + Point *pt = PG_GETARG_POINT_P(0); + LSEG *lseg = PG_GETARG_LSEG_P(1); + + PG_RETURN_FLOAT8(lseg_closept_point(NULL, lseg, pt)); +} + +/* + * Distance from a lseg to a point + */ +Datum +dist_sp(PG_FUNCTION_ARGS) +{ + LSEG *lseg = PG_GETARG_LSEG_P(0); + Point *pt = PG_GETARG_POINT_P(1); + + PG_RETURN_FLOAT8(lseg_closept_point(NULL, lseg, pt)); +} + +static float8 +dist_ppath_internal(Point *pt, PATH *path) +{ + float8 result = 0.0; /* keep compiler quiet */ + bool have_min = false; + float8 tmp; + int i; + LSEG lseg; + + Assert(path->npts > 0); + + /* + * The distance from a point to a path is the smallest distance from the + * point to any of its constituent segments. + */ + for (i = 0; i < path->npts; i++) + { + int iprev; + + if (i > 0) + iprev = i - 1; + else + { + if (!path->closed) + continue; + iprev = path->npts - 1; /* Include the closure segment */ + } + + statlseg_construct(&lseg, &path->p[iprev], &path->p[i]); + tmp = lseg_closept_point(NULL, &lseg, pt); + if (!have_min || float8_lt(tmp, result)) + { + result = tmp; + have_min = true; + } + } + + return result; +} + +/* + * Distance from a point to a path + */ +Datum +dist_ppath(PG_FUNCTION_ARGS) +{ + Point *pt = PG_GETARG_POINT_P(0); + PATH *path = PG_GETARG_PATH_P(1); + + PG_RETURN_FLOAT8(dist_ppath_internal(pt, path)); +} + +/* + * Distance from a path to a point + */ +Datum +dist_pathp(PG_FUNCTION_ARGS) +{ + PATH *path = PG_GETARG_PATH_P(0); + Point *pt = PG_GETARG_POINT_P(1); + + PG_RETURN_FLOAT8(dist_ppath_internal(pt, path)); +} + +/* + * Distance from a point to a box + */ +Datum +dist_pb(PG_FUNCTION_ARGS) +{ + Point *pt = PG_GETARG_POINT_P(0); + BOX *box = PG_GETARG_BOX_P(1); + + PG_RETURN_FLOAT8(box_closept_point(NULL, box, pt)); +} + +/* + * Distance from a box to a point + */ +Datum +dist_bp(PG_FUNCTION_ARGS) +{ + BOX *box = PG_GETARG_BOX_P(0); + Point *pt = PG_GETARG_POINT_P(1); + + PG_RETURN_FLOAT8(box_closept_point(NULL, box, pt)); +} + +/* + * Distance from a lseg to a line + */ +Datum +dist_sl(PG_FUNCTION_ARGS) +{ + LSEG *lseg = PG_GETARG_LSEG_P(0); + LINE *line = PG_GETARG_LINE_P(1); + + PG_RETURN_FLOAT8(lseg_closept_line(NULL, lseg, line)); +} + +/* + * Distance from a line to a lseg + */ +Datum +dist_ls(PG_FUNCTION_ARGS) +{ + LINE *line = PG_GETARG_LINE_P(0); + LSEG *lseg = PG_GETARG_LSEG_P(1); + + PG_RETURN_FLOAT8(lseg_closept_line(NULL, lseg, line)); +} + +/* + * Distance from a lseg to a box + */ +Datum +dist_sb(PG_FUNCTION_ARGS) +{ + LSEG *lseg = PG_GETARG_LSEG_P(0); + BOX *box = PG_GETARG_BOX_P(1); + + PG_RETURN_FLOAT8(box_closept_lseg(NULL, box, lseg)); +} + +/* + * Distance from a box to a lseg + */ +Datum +dist_bs(PG_FUNCTION_ARGS) +{ + BOX *box = PG_GETARG_BOX_P(0); + LSEG *lseg = PG_GETARG_LSEG_P(1); + + PG_RETURN_FLOAT8(box_closept_lseg(NULL, box, lseg)); +} + +static float8 +dist_cpoly_internal(CIRCLE *circle, POLYGON *poly) +{ + float8 result; + + /* calculate distance to center, and subtract radius */ + result = float8_mi(dist_ppoly_internal(&circle->center, poly), + circle->radius); + if (result < 0.0) + result = 0.0; + + return result; +} + +/* + * Distance from a circle to a polygon + */ +Datum +dist_cpoly(PG_FUNCTION_ARGS) +{ + CIRCLE *circle = PG_GETARG_CIRCLE_P(0); + POLYGON *poly = PG_GETARG_POLYGON_P(1); + + PG_RETURN_FLOAT8(dist_cpoly_internal(circle, poly)); +} + +/* + * Distance from a polygon to a circle + */ +Datum +dist_polyc(PG_FUNCTION_ARGS) +{ + POLYGON *poly = PG_GETARG_POLYGON_P(0); + CIRCLE *circle = PG_GETARG_CIRCLE_P(1); + + PG_RETURN_FLOAT8(dist_cpoly_internal(circle, poly)); +} + +/* + * Distance from a point to a polygon + */ +Datum +dist_ppoly(PG_FUNCTION_ARGS) +{ + Point *point = PG_GETARG_POINT_P(0); + POLYGON *poly = PG_GETARG_POLYGON_P(1); + + PG_RETURN_FLOAT8(dist_ppoly_internal(point, poly)); +} + +Datum +dist_polyp(PG_FUNCTION_ARGS) +{ + POLYGON *poly = PG_GETARG_POLYGON_P(0); + Point *point = PG_GETARG_POINT_P(1); + + PG_RETURN_FLOAT8(dist_ppoly_internal(point, poly)); +} + +static float8 +dist_ppoly_internal(Point *pt, POLYGON *poly) +{ + float8 result; + float8 d; + int i; + LSEG seg; + + if (point_inside(pt, poly->npts, poly->p) != 0) + return 0.0; + + /* initialize distance with segment between first and last points */ + seg.p[0].x = poly->p[0].x; + seg.p[0].y = poly->p[0].y; + seg.p[1].x = poly->p[poly->npts - 1].x; + seg.p[1].y = poly->p[poly->npts - 1].y; + result = lseg_closept_point(NULL, &seg, pt); + + /* check distances for other segments */ + for (i = 0; i < poly->npts - 1; i++) + { + seg.p[0].x = poly->p[i].x; + seg.p[0].y = poly->p[i].y; + seg.p[1].x = poly->p[i + 1].x; + seg.p[1].y = poly->p[i + 1].y; + d = lseg_closept_point(NULL, &seg, pt); + if (float8_lt(d, result)) + result = d; + } + + return result; +} + + +/*--------------------------------------------------------------------- + * interpt_ + * Intersection point of objects. + * We choose to ignore the "point" of intersection between + * lines and boxes, since there are typically two. + *-------------------------------------------------------------------*/ + +/* + * Return whether the line segment intersect with the line. If *result is not + * NULL, it is set to the intersection point. + */ +static bool +lseg_interpt_line(Point *result, LSEG *lseg, LINE *line) +{ + Point interpt; + LINE tmp; + + /* + * First, we promote the line segment to a line, because we know how to + * find the intersection point of two lines. If they don't have an + * intersection point, we are done. + */ + line_construct(&tmp, &lseg->p[0], lseg_sl(lseg)); + if (!line_interpt_line(&interpt, &tmp, line)) + return false; + + /* + * Then, we check whether the intersection point is actually on the line + * segment. + */ + if (!lseg_contain_point(lseg, &interpt)) + return false; + if (result != NULL) + { + /* + * If there is an intersection, then check explicitly for matching + * endpoints since there may be rounding effects with annoying LSB + * residue. + */ + if (point_eq_point(&lseg->p[0], &interpt)) + *result = lseg->p[0]; + else if (point_eq_point(&lseg->p[1], &interpt)) + *result = lseg->p[1]; + else + *result = interpt; + } + + return true; +} + +/*--------------------------------------------------------------------- + * close_ + * Point of closest proximity between objects. + *-------------------------------------------------------------------*/ + +/* + * If *result is not NULL, it is set to the intersection point of a + * perpendicular of the line through the point. Returns the distance + * of those two points. + */ +static float8 +line_closept_point(Point *result, LINE *line, Point *point) +{ + Point closept; + LINE tmp; + + /* + * We drop a perpendicular to find the intersection point. Ordinarily we + * should always find it, but that can fail in the presence of NaN + * coordinates, and perhaps even from simple roundoff issues. + */ + line_construct(&tmp, point, line_invsl(line)); + if (!line_interpt_line(&closept, &tmp, line)) + { + if (result != NULL) + *result = *point; + + return get_float8_nan(); + } + + if (result != NULL) + *result = closept; + + return point_dt(&closept, point); +} + +Datum +close_pl(PG_FUNCTION_ARGS) +{ + Point *pt = PG_GETARG_POINT_P(0); + LINE *line = PG_GETARG_LINE_P(1); + Point *result; + + result = (Point *) palloc(sizeof(Point)); + + if (isnan(line_closept_point(result, line, pt))) + PG_RETURN_NULL(); + + PG_RETURN_POINT_P(result); +} + + +/* + * Closest point on line segment to specified point. + * + * If *result is not NULL, set it to the closest point on the line segment + * to the point. Returns the distance of the two points. + */ +static float8 +lseg_closept_point(Point *result, LSEG *lseg, Point *pt) +{ + Point closept; + LINE tmp; + + /* + * To find the closest point, we draw a perpendicular line from the point + * to the line segment. + */ + line_construct(&tmp, pt, point_invsl(&lseg->p[0], &lseg->p[1])); + lseg_closept_line(&closept, lseg, &tmp); + + if (result != NULL) + *result = closept; + + return point_dt(&closept, pt); +} + +Datum +close_ps(PG_FUNCTION_ARGS) +{ + Point *pt = PG_GETARG_POINT_P(0); + LSEG *lseg = PG_GETARG_LSEG_P(1); + Point *result; + + result = (Point *) palloc(sizeof(Point)); + + if (isnan(lseg_closept_point(result, lseg, pt))) + PG_RETURN_NULL(); + + PG_RETURN_POINT_P(result); +} + + +/* + * Closest point on line segment to line segment + */ +static float8 +lseg_closept_lseg(Point *result, LSEG *on_lseg, LSEG *to_lseg) +{ + Point point; + float8 dist, + d; + + /* First, we handle the case when the line segments are intersecting. */ + if (lseg_interpt_lseg(result, on_lseg, to_lseg)) + return 0.0; + + /* + * Then, we find the closest points from the endpoints of the second line + * segment, and keep the closest one. + */ + dist = lseg_closept_point(result, on_lseg, &to_lseg->p[0]); + d = lseg_closept_point(&point, on_lseg, &to_lseg->p[1]); + if (float8_lt(d, dist)) + { + dist = d; + if (result != NULL) + *result = point; + } + + /* The closest point can still be one of the endpoints, so we test them. */ + d = lseg_closept_point(NULL, to_lseg, &on_lseg->p[0]); + if (float8_lt(d, dist)) + { + dist = d; + if (result != NULL) + *result = on_lseg->p[0]; + } + d = lseg_closept_point(NULL, to_lseg, &on_lseg->p[1]); + if (float8_lt(d, dist)) + { + dist = d; + if (result != NULL) + *result = on_lseg->p[1]; + } + + return dist; +} + +Datum +close_lseg(PG_FUNCTION_ARGS) +{ + LSEG *l1 = PG_GETARG_LSEG_P(0); + LSEG *l2 = PG_GETARG_LSEG_P(1); + Point *result; + + if (lseg_sl(l1) == lseg_sl(l2)) + PG_RETURN_NULL(); + + result = (Point *) palloc(sizeof(Point)); + + if (isnan(lseg_closept_lseg(result, l2, l1))) + PG_RETURN_NULL(); + + PG_RETURN_POINT_P(result); +} + + +/* + * Closest point on or in box to specified point. + * + * If *result is not NULL, set it to the closest point on the box to the + * given point, and return the distance of the two points. + */ +static float8 +box_closept_point(Point *result, BOX *box, Point *pt) +{ + float8 dist, + d; + Point point, + closept; + LSEG lseg; + + if (box_contain_point(box, pt)) + { + if (result != NULL) + *result = *pt; + + return 0.0; + } + + /* pairwise check lseg distances */ + point.x = box->low.x; + point.y = box->high.y; + statlseg_construct(&lseg, &box->low, &point); + dist = lseg_closept_point(result, &lseg, pt); + + statlseg_construct(&lseg, &box->high, &point); + d = lseg_closept_point(&closept, &lseg, pt); + if (float8_lt(d, dist)) + { + dist = d; + if (result != NULL) + *result = closept; + } + + point.x = box->high.x; + point.y = box->low.y; + statlseg_construct(&lseg, &box->low, &point); + d = lseg_closept_point(&closept, &lseg, pt); + if (float8_lt(d, dist)) + { + dist = d; + if (result != NULL) + *result = closept; + } + + statlseg_construct(&lseg, &box->high, &point); + d = lseg_closept_point(&closept, &lseg, pt); + if (float8_lt(d, dist)) + { + dist = d; + if (result != NULL) + *result = closept; + } + + return dist; +} + +Datum +close_pb(PG_FUNCTION_ARGS) +{ + Point *pt = PG_GETARG_POINT_P(0); + BOX *box = PG_GETARG_BOX_P(1); + Point *result; + + result = (Point *) palloc(sizeof(Point)); + + if (isnan(box_closept_point(result, box, pt))) + PG_RETURN_NULL(); + + PG_RETURN_POINT_P(result); +} + +/* + * Closest point on line segment to line. + * + * Return the distance between the line and the closest point of the line + * segment to the line. If *result is not NULL, set it to that point. + * + * NOTE: When the lines are parallel, endpoints of one of the line segment + * are FPeq(), in presence of NaN or Infinite coordinates, or perhaps = + * even because of simple roundoff issues, there may not be a single closest + * point. We are likely to set the result to the second endpoint in these + * cases. + */ +static float8 +lseg_closept_line(Point *result, LSEG *lseg, LINE *line) +{ + float8 dist1, + dist2; + + if (lseg_interpt_line(result, lseg, line)) + return 0.0; + + dist1 = line_closept_point(NULL, line, &lseg->p[0]); + dist2 = line_closept_point(NULL, line, &lseg->p[1]); + + if (dist1 < dist2) + { + if (result != NULL) + *result = lseg->p[0]; + + return dist1; + } + else + { + if (result != NULL) + *result = lseg->p[1]; + + return dist2; + } +} + +Datum +close_ls(PG_FUNCTION_ARGS) +{ + LINE *line = PG_GETARG_LINE_P(0); + LSEG *lseg = PG_GETARG_LSEG_P(1); + Point *result; + + if (lseg_sl(lseg) == line_sl(line)) + PG_RETURN_NULL(); + + result = (Point *) palloc(sizeof(Point)); + + if (isnan(lseg_closept_line(result, lseg, line))) + PG_RETURN_NULL(); + + PG_RETURN_POINT_P(result); +} + + +/* + * Closest point on or in box to line segment. + * + * Returns the distance between the closest point on or in the box to + * the line segment. If *result is not NULL, it is set to that point. + */ +static float8 +box_closept_lseg(Point *result, BOX *box, LSEG *lseg) +{ + float8 dist, + d; + Point point, + closept; + LSEG bseg; + + if (box_interpt_lseg(result, box, lseg)) + return 0.0; + + /* pairwise check lseg distances */ + point.x = box->low.x; + point.y = box->high.y; + statlseg_construct(&bseg, &box->low, &point); + dist = lseg_closept_lseg(result, &bseg, lseg); + + statlseg_construct(&bseg, &box->high, &point); + d = lseg_closept_lseg(&closept, &bseg, lseg); + if (float8_lt(d, dist)) + { + dist = d; + if (result != NULL) + *result = closept; + } + + point.x = box->high.x; + point.y = box->low.y; + statlseg_construct(&bseg, &box->low, &point); + d = lseg_closept_lseg(&closept, &bseg, lseg); + if (float8_lt(d, dist)) + { + dist = d; + if (result != NULL) + *result = closept; + } + + statlseg_construct(&bseg, &box->high, &point); + d = lseg_closept_lseg(&closept, &bseg, lseg); + if (float8_lt(d, dist)) + { + dist = d; + if (result != NULL) + *result = closept; + } + + return dist; +} + +Datum +close_sb(PG_FUNCTION_ARGS) +{ + LSEG *lseg = PG_GETARG_LSEG_P(0); + BOX *box = PG_GETARG_BOX_P(1); + Point *result; + + result = (Point *) palloc(sizeof(Point)); + + if (isnan(box_closept_lseg(result, box, lseg))) + PG_RETURN_NULL(); + + PG_RETURN_POINT_P(result); +} + + +/*--------------------------------------------------------------------- + * on_ + * Whether one object lies completely within another. + *-------------------------------------------------------------------*/ + +/* + * Does the point satisfy the equation? + */ +static bool +line_contain_point(LINE *line, Point *point) +{ + return FPzero(float8_pl(float8_pl(float8_mul(line->A, point->x), + float8_mul(line->B, point->y)), + line->C)); +} + +Datum +on_pl(PG_FUNCTION_ARGS) +{ + Point *pt = PG_GETARG_POINT_P(0); + LINE *line = PG_GETARG_LINE_P(1); + + PG_RETURN_BOOL(line_contain_point(line, pt)); +} + + +/* + * Determine colinearity by detecting a triangle inequality. + * This algorithm seems to behave nicely even with lsb residues - tgl 1997-07-09 + */ +static bool +lseg_contain_point(LSEG *lseg, Point *pt) +{ + return FPeq(point_dt(pt, &lseg->p[0]) + + point_dt(pt, &lseg->p[1]), + point_dt(&lseg->p[0], &lseg->p[1])); +} + +Datum +on_ps(PG_FUNCTION_ARGS) +{ + Point *pt = PG_GETARG_POINT_P(0); + LSEG *lseg = PG_GETARG_LSEG_P(1); + + PG_RETURN_BOOL(lseg_contain_point(lseg, pt)); +} + + +/* + * Check whether the point is in the box or on its border + */ +static bool +box_contain_point(BOX *box, Point *point) +{ + return box->high.x >= point->x && box->low.x <= point->x && + box->high.y >= point->y && box->low.y <= point->y; +} + +Datum +on_pb(PG_FUNCTION_ARGS) +{ + Point *pt = PG_GETARG_POINT_P(0); + BOX *box = PG_GETARG_BOX_P(1); + + PG_RETURN_BOOL(box_contain_point(box, pt)); +} + +Datum +box_contain_pt(PG_FUNCTION_ARGS) +{ + BOX *box = PG_GETARG_BOX_P(0); + Point *pt = PG_GETARG_POINT_P(1); + + PG_RETURN_BOOL(box_contain_point(box, pt)); +} + +/* on_ppath - + * Whether a point lies within (on) a polyline. + * If open, we have to (groan) check each segment. + * (uses same algorithm as for point intersecting segment - tgl 1997-07-09) + * If closed, we use the old O(n) ray method for point-in-polygon. + * The ray is horizontal, from pt out to the right. + * Each segment that crosses the ray counts as an + * intersection; note that an endpoint or edge may touch + * but not cross. + * (we can do p-in-p in lg(n), but it takes preprocessing) + */ +Datum +on_ppath(PG_FUNCTION_ARGS) +{ + Point *pt = PG_GETARG_POINT_P(0); + PATH *path = PG_GETARG_PATH_P(1); + int i, + n; + float8 a, + b; + + /*-- OPEN --*/ + if (!path->closed) + { + n = path->npts - 1; + a = point_dt(pt, &path->p[0]); + for (i = 0; i < n; i++) + { + b = point_dt(pt, &path->p[i + 1]); + if (FPeq(float8_pl(a, b), point_dt(&path->p[i], &path->p[i + 1]))) + PG_RETURN_BOOL(true); + a = b; + } + PG_RETURN_BOOL(false); + } + + /*-- CLOSED --*/ + PG_RETURN_BOOL(point_inside(pt, path->npts, path->p) != 0); +} + + +/* + * Check whether the line segment is on the line or close enough + * + * It is, if both of its points are on the line or close enough. + */ +Datum +on_sl(PG_FUNCTION_ARGS) +{ + LSEG *lseg = PG_GETARG_LSEG_P(0); + LINE *line = PG_GETARG_LINE_P(1); + + PG_RETURN_BOOL(line_contain_point(line, &lseg->p[0]) && + line_contain_point(line, &lseg->p[1])); +} + + +/* + * Check whether the line segment is in the box or on its border + * + * It is, if both of its points are in the box or on its border. + */ +static bool +box_contain_lseg(BOX *box, LSEG *lseg) +{ + return box_contain_point(box, &lseg->p[0]) && + box_contain_point(box, &lseg->p[1]); +} + +Datum +on_sb(PG_FUNCTION_ARGS) +{ + LSEG *lseg = PG_GETARG_LSEG_P(0); + BOX *box = PG_GETARG_BOX_P(1); + + PG_RETURN_BOOL(box_contain_lseg(box, lseg)); +} + +/*--------------------------------------------------------------------- + * inter_ + * Whether one object intersects another. + *-------------------------------------------------------------------*/ + +Datum +inter_sl(PG_FUNCTION_ARGS) +{ + LSEG *lseg = PG_GETARG_LSEG_P(0); + LINE *line = PG_GETARG_LINE_P(1); + + PG_RETURN_BOOL(lseg_interpt_line(NULL, lseg, line)); +} + + +/* + * Do line segment and box intersect? + * + * Segment completely inside box counts as intersection. + * If you want only segments crossing box boundaries, + * try converting box to path first. + * + * This function also sets the *result to the closest point on the line + * segment to the center of the box when they overlap and the result is + * not NULL. It is somewhat arbitrary, but maybe the best we can do as + * there are typically two points they intersect. + * + * Optimize for non-intersection by checking for box intersection first. + * - thomas 1998-01-30 + */ +static bool +box_interpt_lseg(Point *result, BOX *box, LSEG *lseg) +{ + BOX lbox; + LSEG bseg; + Point point; + + lbox.low.x = float8_min(lseg->p[0].x, lseg->p[1].x); + lbox.low.y = float8_min(lseg->p[0].y, lseg->p[1].y); + lbox.high.x = float8_max(lseg->p[0].x, lseg->p[1].x); + lbox.high.y = float8_max(lseg->p[0].y, lseg->p[1].y); + + /* nothing close to overlap? then not going to intersect */ + if (!box_ov(&lbox, box)) + return false; + + if (result != NULL) + { + box_cn(&point, box); + lseg_closept_point(result, lseg, &point); + } + + /* an endpoint of segment is inside box? then clearly intersects */ + if (box_contain_point(box, &lseg->p[0]) || + box_contain_point(box, &lseg->p[1])) + return true; + + /* pairwise check lseg intersections */ + point.x = box->low.x; + point.y = box->high.y; + statlseg_construct(&bseg, &box->low, &point); + if (lseg_interpt_lseg(NULL, &bseg, lseg)) + return true; + + statlseg_construct(&bseg, &box->high, &point); + if (lseg_interpt_lseg(NULL, &bseg, lseg)) + return true; + + point.x = box->high.x; + point.y = box->low.y; + statlseg_construct(&bseg, &box->low, &point); + if (lseg_interpt_lseg(NULL, &bseg, lseg)) + return true; + + statlseg_construct(&bseg, &box->high, &point); + if (lseg_interpt_lseg(NULL, &bseg, lseg)) + return true; + + /* if we dropped through, no two segs intersected */ + return false; +} + +Datum +inter_sb(PG_FUNCTION_ARGS) +{ + LSEG *lseg = PG_GETARG_LSEG_P(0); + BOX *box = PG_GETARG_BOX_P(1); + + PG_RETURN_BOOL(box_interpt_lseg(NULL, box, lseg)); +} + + +/* inter_lb() + * Do line and box intersect? + */ +Datum +inter_lb(PG_FUNCTION_ARGS) +{ + LINE *line = PG_GETARG_LINE_P(0); + BOX *box = PG_GETARG_BOX_P(1); + LSEG bseg; + Point p1, + p2; + + /* pairwise check lseg intersections */ + p1.x = box->low.x; + p1.y = box->low.y; + p2.x = box->low.x; + p2.y = box->high.y; + statlseg_construct(&bseg, &p1, &p2); + if (lseg_interpt_line(NULL, &bseg, line)) + PG_RETURN_BOOL(true); + p1.x = box->high.x; + p1.y = box->high.y; + statlseg_construct(&bseg, &p1, &p2); + if (lseg_interpt_line(NULL, &bseg, line)) + PG_RETURN_BOOL(true); + p2.x = box->high.x; + p2.y = box->low.y; + statlseg_construct(&bseg, &p1, &p2); + if (lseg_interpt_line(NULL, &bseg, line)) + PG_RETURN_BOOL(true); + p1.x = box->low.x; + p1.y = box->low.y; + statlseg_construct(&bseg, &p1, &p2); + if (lseg_interpt_line(NULL, &bseg, line)) + PG_RETURN_BOOL(true); + + /* if we dropped through, no intersection */ + PG_RETURN_BOOL(false); +} + +/*------------------------------------------------------------------ + * The following routines define a data type and operator class for + * POLYGONS .... Part of which (the polygon's bounding box) is built on + * top of the BOX data type. + * + * make_bound_box - create the bounding box for the input polygon + *------------------------------------------------------------------*/ + +/*--------------------------------------------------------------------- + * Make the smallest bounding box for the given polygon. + *---------------------------------------------------------------------*/ +static void +make_bound_box(POLYGON *poly) +{ + int i; + float8 x1, + y1, + x2, + y2; + + Assert(poly->npts > 0); + + x1 = x2 = poly->p[0].x; + y2 = y1 = poly->p[0].y; + for (i = 1; i < poly->npts; i++) + { + if (float8_lt(poly->p[i].x, x1)) + x1 = poly->p[i].x; + if (float8_gt(poly->p[i].x, x2)) + x2 = poly->p[i].x; + if (float8_lt(poly->p[i].y, y1)) + y1 = poly->p[i].y; + if (float8_gt(poly->p[i].y, y2)) + y2 = poly->p[i].y; + } + + poly->boundbox.low.x = x1; + poly->boundbox.high.x = x2; + poly->boundbox.low.y = y1; + poly->boundbox.high.y = y2; +} + +/*------------------------------------------------------------------ + * poly_in - read in the polygon from a string specification + * + * External format: + * "((x0,y0),...,(xn,yn))" + * "x0,y0,...,xn,yn" + * also supports the older style "(x1,...,xn,y1,...yn)" + *------------------------------------------------------------------*/ +Datum +poly_in(PG_FUNCTION_ARGS) +{ + char *str = PG_GETARG_CSTRING(0); + Node *escontext = fcinfo->context; + POLYGON *poly; + int npts; + int size; + int base_size; + bool isopen; + + if ((npts = pair_count(str, ',')) <= 0) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s: \"%s\"", + "polygon", str))); + + base_size = sizeof(poly->p[0]) * npts; + size = offsetof(POLYGON, p) + base_size; + + /* Check for integer overflow */ + if (base_size / npts != sizeof(poly->p[0]) || size <= base_size) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("too many points requested"))); + + poly = (POLYGON *) palloc0(size); /* zero any holes */ + + SET_VARSIZE(poly, size); + poly->npts = npts; + + if (!path_decode(str, false, npts, &(poly->p[0]), &isopen, NULL, "polygon", + str, escontext)) + PG_RETURN_NULL(); + + make_bound_box(poly); + + PG_RETURN_POLYGON_P(poly); +} + +/*--------------------------------------------------------------- + * poly_out - convert internal POLYGON representation to the + * character string format "((f8,f8),...,(f8,f8))" + *---------------------------------------------------------------*/ +Datum +poly_out(PG_FUNCTION_ARGS) +{ + POLYGON *poly = PG_GETARG_POLYGON_P(0); + + PG_RETURN_CSTRING(path_encode(PATH_CLOSED, poly->npts, poly->p)); +} + +/* + * poly_recv - converts external binary format to polygon + * + * External representation is int32 number of points, and the points. + * We recompute the bounding box on read, instead of trusting it to + * be valid. (Checking it would take just as long, so may as well + * omit it from external representation.) + */ +Datum +poly_recv(PG_FUNCTION_ARGS) +{ + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + POLYGON *poly; + int32 npts; + int32 i; + int size; + + npts = pq_getmsgint(buf, sizeof(int32)); + if (npts <= 0 || npts >= (int32) ((INT_MAX - offsetof(POLYGON, p)) / sizeof(Point))) + ereport(ERROR, + (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION), + errmsg("invalid number of points in external \"polygon\" value"))); + + size = offsetof(POLYGON, p) + sizeof(poly->p[0]) * npts; + poly = (POLYGON *) palloc0(size); /* zero any holes */ + + SET_VARSIZE(poly, size); + poly->npts = npts; + + for (i = 0; i < npts; i++) + { + poly->p[i].x = pq_getmsgfloat8(buf); + poly->p[i].y = pq_getmsgfloat8(buf); + } + + make_bound_box(poly); + + PG_RETURN_POLYGON_P(poly); +} + +/* + * poly_send - converts polygon to binary format + */ +Datum +poly_send(PG_FUNCTION_ARGS) +{ + POLYGON *poly = PG_GETARG_POLYGON_P(0); + StringInfoData buf; + int32 i; + + pq_begintypsend(&buf); + pq_sendint32(&buf, poly->npts); + for (i = 0; i < poly->npts; i++) + { + pq_sendfloat8(&buf, poly->p[i].x); + pq_sendfloat8(&buf, poly->p[i].y); + } + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} + + +/*------------------------------------------------------- + * Is polygon A strictly left of polygon B? i.e. is + * the right most point of A left of the left most point + * of B? + *-------------------------------------------------------*/ +Datum +poly_left(PG_FUNCTION_ARGS) +{ + POLYGON *polya = PG_GETARG_POLYGON_P(0); + POLYGON *polyb = PG_GETARG_POLYGON_P(1); + bool result; + + result = polya->boundbox.high.x < polyb->boundbox.low.x; + + /* + * Avoid leaking memory for toasted inputs ... needed for rtree indexes + */ + PG_FREE_IF_COPY(polya, 0); + PG_FREE_IF_COPY(polyb, 1); + + PG_RETURN_BOOL(result); +} + +/*------------------------------------------------------- + * Is polygon A overlapping or left of polygon B? i.e. is + * the right most point of A at or left of the right most point + * of B? + *-------------------------------------------------------*/ +Datum +poly_overleft(PG_FUNCTION_ARGS) +{ + POLYGON *polya = PG_GETARG_POLYGON_P(0); + POLYGON *polyb = PG_GETARG_POLYGON_P(1); + bool result; + + result = polya->boundbox.high.x <= polyb->boundbox.high.x; + + /* + * Avoid leaking memory for toasted inputs ... needed for rtree indexes + */ + PG_FREE_IF_COPY(polya, 0); + PG_FREE_IF_COPY(polyb, 1); + + PG_RETURN_BOOL(result); +} + +/*------------------------------------------------------- + * Is polygon A strictly right of polygon B? i.e. is + * the left most point of A right of the right most point + * of B? + *-------------------------------------------------------*/ +Datum +poly_right(PG_FUNCTION_ARGS) +{ + POLYGON *polya = PG_GETARG_POLYGON_P(0); + POLYGON *polyb = PG_GETARG_POLYGON_P(1); + bool result; + + result = polya->boundbox.low.x > polyb->boundbox.high.x; + + /* + * Avoid leaking memory for toasted inputs ... needed for rtree indexes + */ + PG_FREE_IF_COPY(polya, 0); + PG_FREE_IF_COPY(polyb, 1); + + PG_RETURN_BOOL(result); +} + +/*------------------------------------------------------- + * Is polygon A overlapping or right of polygon B? i.e. is + * the left most point of A at or right of the left most point + * of B? + *-------------------------------------------------------*/ +Datum +poly_overright(PG_FUNCTION_ARGS) +{ + POLYGON *polya = PG_GETARG_POLYGON_P(0); + POLYGON *polyb = PG_GETARG_POLYGON_P(1); + bool result; + + result = polya->boundbox.low.x >= polyb->boundbox.low.x; + + /* + * Avoid leaking memory for toasted inputs ... needed for rtree indexes + */ + PG_FREE_IF_COPY(polya, 0); + PG_FREE_IF_COPY(polyb, 1); + + PG_RETURN_BOOL(result); +} + +/*------------------------------------------------------- + * Is polygon A strictly below polygon B? i.e. is + * the upper most point of A below the lower most point + * of B? + *-------------------------------------------------------*/ +Datum +poly_below(PG_FUNCTION_ARGS) +{ + POLYGON *polya = PG_GETARG_POLYGON_P(0); + POLYGON *polyb = PG_GETARG_POLYGON_P(1); + bool result; + + result = polya->boundbox.high.y < polyb->boundbox.low.y; + + /* + * Avoid leaking memory for toasted inputs ... needed for rtree indexes + */ + PG_FREE_IF_COPY(polya, 0); + PG_FREE_IF_COPY(polyb, 1); + + PG_RETURN_BOOL(result); +} + +/*------------------------------------------------------- + * Is polygon A overlapping or below polygon B? i.e. is + * the upper most point of A at or below the upper most point + * of B? + *-------------------------------------------------------*/ +Datum +poly_overbelow(PG_FUNCTION_ARGS) +{ + POLYGON *polya = PG_GETARG_POLYGON_P(0); + POLYGON *polyb = PG_GETARG_POLYGON_P(1); + bool result; + + result = polya->boundbox.high.y <= polyb->boundbox.high.y; + + /* + * Avoid leaking memory for toasted inputs ... needed for rtree indexes + */ + PG_FREE_IF_COPY(polya, 0); + PG_FREE_IF_COPY(polyb, 1); + + PG_RETURN_BOOL(result); +} + +/*------------------------------------------------------- + * Is polygon A strictly above polygon B? i.e. is + * the lower most point of A above the upper most point + * of B? + *-------------------------------------------------------*/ +Datum +poly_above(PG_FUNCTION_ARGS) +{ + POLYGON *polya = PG_GETARG_POLYGON_P(0); + POLYGON *polyb = PG_GETARG_POLYGON_P(1); + bool result; + + result = polya->boundbox.low.y > polyb->boundbox.high.y; + + /* + * Avoid leaking memory for toasted inputs ... needed for rtree indexes + */ + PG_FREE_IF_COPY(polya, 0); + PG_FREE_IF_COPY(polyb, 1); + + PG_RETURN_BOOL(result); +} + +/*------------------------------------------------------- + * Is polygon A overlapping or above polygon B? i.e. is + * the lower most point of A at or above the lower most point + * of B? + *-------------------------------------------------------*/ +Datum +poly_overabove(PG_FUNCTION_ARGS) +{ + POLYGON *polya = PG_GETARG_POLYGON_P(0); + POLYGON *polyb = PG_GETARG_POLYGON_P(1); + bool result; + + result = polya->boundbox.low.y >= polyb->boundbox.low.y; + + /* + * Avoid leaking memory for toasted inputs ... needed for rtree indexes + */ + PG_FREE_IF_COPY(polya, 0); + PG_FREE_IF_COPY(polyb, 1); + + PG_RETURN_BOOL(result); +} + + +/*------------------------------------------------------- + * Is polygon A the same as polygon B? i.e. are all the + * points the same? + * Check all points for matches in both forward and reverse + * direction since polygons are non-directional and are + * closed shapes. + *-------------------------------------------------------*/ +Datum +poly_same(PG_FUNCTION_ARGS) +{ + POLYGON *polya = PG_GETARG_POLYGON_P(0); + POLYGON *polyb = PG_GETARG_POLYGON_P(1); + bool result; + + if (polya->npts != polyb->npts) + result = false; + else + result = plist_same(polya->npts, polya->p, polyb->p); + + /* + * Avoid leaking memory for toasted inputs ... needed for rtree indexes + */ + PG_FREE_IF_COPY(polya, 0); + PG_FREE_IF_COPY(polyb, 1); + + PG_RETURN_BOOL(result); +} + +/*----------------------------------------------------------------- + * Determine if polygon A overlaps polygon B + *-----------------------------------------------------------------*/ +static bool +poly_overlap_internal(POLYGON *polya, POLYGON *polyb) +{ + bool result; + + Assert(polya->npts > 0 && polyb->npts > 0); + + /* Quick check by bounding box */ + result = box_ov(&polya->boundbox, &polyb->boundbox); + + /* + * Brute-force algorithm - try to find intersected edges, if so then + * polygons are overlapped else check is one polygon inside other or not + * by testing single point of them. + */ + if (result) + { + int ia, + ib; + LSEG sa, + sb; + + /* Init first of polya's edge with last point */ + sa.p[0] = polya->p[polya->npts - 1]; + result = false; + + for (ia = 0; ia < polya->npts && !result; ia++) + { + /* Second point of polya's edge is a current one */ + sa.p[1] = polya->p[ia]; + + /* Init first of polyb's edge with last point */ + sb.p[0] = polyb->p[polyb->npts - 1]; + + for (ib = 0; ib < polyb->npts && !result; ib++) + { + sb.p[1] = polyb->p[ib]; + result = lseg_interpt_lseg(NULL, &sa, &sb); + sb.p[0] = sb.p[1]; + } + + /* + * move current endpoint to the first point of next edge + */ + sa.p[0] = sa.p[1]; + } + + if (!result) + { + result = (point_inside(polya->p, polyb->npts, polyb->p) || + point_inside(polyb->p, polya->npts, polya->p)); + } + } + + return result; +} + +Datum +poly_overlap(PG_FUNCTION_ARGS) +{ + POLYGON *polya = PG_GETARG_POLYGON_P(0); + POLYGON *polyb = PG_GETARG_POLYGON_P(1); + bool result; + + result = poly_overlap_internal(polya, polyb); + + /* + * Avoid leaking memory for toasted inputs ... needed for rtree indexes + */ + PG_FREE_IF_COPY(polya, 0); + PG_FREE_IF_COPY(polyb, 1); + + PG_RETURN_BOOL(result); +} + +/* + * Tests special kind of segment for in/out of polygon. + * Special kind means: + * - point a should be on segment s + * - segment (a,b) should not be contained by s + * Returns true if: + * - segment (a,b) is collinear to s and (a,b) is in polygon + * - segment (a,b) s not collinear to s. Note: that doesn't + * mean that segment is in polygon! + */ + +static bool +touched_lseg_inside_poly(Point *a, Point *b, LSEG *s, POLYGON *poly, int start) +{ + /* point a is on s, b is not */ + LSEG t; + + t.p[0] = *a; + t.p[1] = *b; + + if (point_eq_point(a, s->p)) + { + if (lseg_contain_point(&t, s->p + 1)) + return lseg_inside_poly(b, s->p + 1, poly, start); + } + else if (point_eq_point(a, s->p + 1)) + { + if (lseg_contain_point(&t, s->p)) + return lseg_inside_poly(b, s->p, poly, start); + } + else if (lseg_contain_point(&t, s->p)) + { + return lseg_inside_poly(b, s->p, poly, start); + } + else if (lseg_contain_point(&t, s->p + 1)) + { + return lseg_inside_poly(b, s->p + 1, poly, start); + } + + return true; /* may be not true, but that will check later */ +} + +/* + * Returns true if segment (a,b) is in polygon, option + * start is used for optimization - function checks + * polygon's edges starting from start + */ +static bool +lseg_inside_poly(Point *a, Point *b, POLYGON *poly, int start) +{ + LSEG s, + t; + int i; + bool res = true, + intersection = false; + + /* since this function recurses, it could be driven to stack overflow */ + check_stack_depth(); + + t.p[0] = *a; + t.p[1] = *b; + s.p[0] = poly->p[(start == 0) ? (poly->npts - 1) : (start - 1)]; + + for (i = start; i < poly->npts && res; i++) + { + Point interpt; + + CHECK_FOR_INTERRUPTS(); + + s.p[1] = poly->p[i]; + + if (lseg_contain_point(&s, t.p)) + { + if (lseg_contain_point(&s, t.p + 1)) + return true; /* t is contained by s */ + + /* Y-cross */ + res = touched_lseg_inside_poly(t.p, t.p + 1, &s, poly, i + 1); + } + else if (lseg_contain_point(&s, t.p + 1)) + { + /* Y-cross */ + res = touched_lseg_inside_poly(t.p + 1, t.p, &s, poly, i + 1); + } + else if (lseg_interpt_lseg(&interpt, &t, &s)) + { + /* + * segments are X-crossing, go to check each subsegment + */ + + intersection = true; + res = lseg_inside_poly(t.p, &interpt, poly, i + 1); + if (res) + res = lseg_inside_poly(t.p + 1, &interpt, poly, i + 1); + } + + s.p[0] = s.p[1]; + } + + if (res && !intersection) + { + Point p; + + /* + * if X-intersection wasn't found, then check central point of tested + * segment. In opposite case we already check all subsegments + */ + p.x = float8_div(float8_pl(t.p[0].x, t.p[1].x), 2.0); + p.y = float8_div(float8_pl(t.p[0].y, t.p[1].y), 2.0); + + res = point_inside(&p, poly->npts, poly->p); + } + + return res; +} + +/* + * Check whether the first polygon contains the second + */ +static bool +poly_contain_poly(POLYGON *contains_poly, POLYGON *contained_poly) +{ + int i; + LSEG s; + + Assert(contains_poly->npts > 0 && contained_poly->npts > 0); + + /* + * Quick check to see if contained's bounding box is contained in + * contains' bb. + */ + if (!box_contain_box(&contains_poly->boundbox, &contained_poly->boundbox)) + return false; + + s.p[0] = contained_poly->p[contained_poly->npts - 1]; + + for (i = 0; i < contained_poly->npts; i++) + { + s.p[1] = contained_poly->p[i]; + if (!lseg_inside_poly(s.p, s.p + 1, contains_poly, 0)) + return false; + s.p[0] = s.p[1]; + } + + return true; +} + +Datum +poly_contain(PG_FUNCTION_ARGS) +{ + POLYGON *polya = PG_GETARG_POLYGON_P(0); + POLYGON *polyb = PG_GETARG_POLYGON_P(1); + bool result; + + result = poly_contain_poly(polya, polyb); + + /* + * Avoid leaking memory for toasted inputs ... needed for rtree indexes + */ + PG_FREE_IF_COPY(polya, 0); + PG_FREE_IF_COPY(polyb, 1); + + PG_RETURN_BOOL(result); +} + + +/*----------------------------------------------------------------- + * Determine if polygon A is contained by polygon B + *-----------------------------------------------------------------*/ +Datum +poly_contained(PG_FUNCTION_ARGS) +{ + POLYGON *polya = PG_GETARG_POLYGON_P(0); + POLYGON *polyb = PG_GETARG_POLYGON_P(1); + bool result; + + /* Just switch the arguments and pass it off to poly_contain */ + result = poly_contain_poly(polyb, polya); + + /* + * Avoid leaking memory for toasted inputs ... needed for rtree indexes + */ + PG_FREE_IF_COPY(polya, 0); + PG_FREE_IF_COPY(polyb, 1); + + PG_RETURN_BOOL(result); +} + + +Datum +poly_contain_pt(PG_FUNCTION_ARGS) +{ + POLYGON *poly = PG_GETARG_POLYGON_P(0); + Point *p = PG_GETARG_POINT_P(1); + + PG_RETURN_BOOL(point_inside(p, poly->npts, poly->p) != 0); +} + +Datum +pt_contained_poly(PG_FUNCTION_ARGS) +{ + Point *p = PG_GETARG_POINT_P(0); + POLYGON *poly = PG_GETARG_POLYGON_P(1); + + PG_RETURN_BOOL(point_inside(p, poly->npts, poly->p) != 0); +} + + +Datum +poly_distance(PG_FUNCTION_ARGS) +{ + POLYGON *polya = PG_GETARG_POLYGON_P(0); + POLYGON *polyb = PG_GETARG_POLYGON_P(1); + float8 min = 0.0; /* initialize to keep compiler quiet */ + bool have_min = false; + float8 tmp; + int i, + j; + LSEG seg1, + seg2; + + /* + * Distance is zero if polygons overlap. We must check this because the + * path distance will not give the right answer if one poly is entirely + * within the other. + */ + if (poly_overlap_internal(polya, polyb)) + PG_RETURN_FLOAT8(0.0); + + /* + * When they don't overlap, the distance calculation is identical to that + * for closed paths (i.e., we needn't care about the fact that polygons + * include their contained areas). See path_distance(). + */ + for (i = 0; i < polya->npts; i++) + { + int iprev; + + if (i > 0) + iprev = i - 1; + else + iprev = polya->npts - 1; + + for (j = 0; j < polyb->npts; j++) + { + int jprev; + + if (j > 0) + jprev = j - 1; + else + jprev = polyb->npts - 1; + + statlseg_construct(&seg1, &polya->p[iprev], &polya->p[i]); + statlseg_construct(&seg2, &polyb->p[jprev], &polyb->p[j]); + + tmp = lseg_closept_lseg(NULL, &seg1, &seg2); + if (!have_min || float8_lt(tmp, min)) + { + min = tmp; + have_min = true; + } + } + } + + if (!have_min) + PG_RETURN_NULL(); + + PG_RETURN_FLOAT8(min); +} + + +/*********************************************************************** + ** + ** Routines for 2D points. + ** + ***********************************************************************/ + +Datum +construct_point(PG_FUNCTION_ARGS) +{ + float8 x = PG_GETARG_FLOAT8(0); + float8 y = PG_GETARG_FLOAT8(1); + Point *result; + + result = (Point *) palloc(sizeof(Point)); + + point_construct(result, x, y); + + PG_RETURN_POINT_P(result); +} + + +static inline void +point_add_point(Point *result, Point *pt1, Point *pt2) +{ + point_construct(result, + float8_pl(pt1->x, pt2->x), + float8_pl(pt1->y, pt2->y)); +} + +Datum +point_add(PG_FUNCTION_ARGS) +{ + Point *p1 = PG_GETARG_POINT_P(0); + Point *p2 = PG_GETARG_POINT_P(1); + Point *result; + + result = (Point *) palloc(sizeof(Point)); + + point_add_point(result, p1, p2); + + PG_RETURN_POINT_P(result); +} + + +static inline void +point_sub_point(Point *result, Point *pt1, Point *pt2) +{ + point_construct(result, + float8_mi(pt1->x, pt2->x), + float8_mi(pt1->y, pt2->y)); +} + +Datum +point_sub(PG_FUNCTION_ARGS) +{ + Point *p1 = PG_GETARG_POINT_P(0); + Point *p2 = PG_GETARG_POINT_P(1); + Point *result; + + result = (Point *) palloc(sizeof(Point)); + + point_sub_point(result, p1, p2); + + PG_RETURN_POINT_P(result); +} + + +static inline void +point_mul_point(Point *result, Point *pt1, Point *pt2) +{ + point_construct(result, + float8_mi(float8_mul(pt1->x, pt2->x), + float8_mul(pt1->y, pt2->y)), + float8_pl(float8_mul(pt1->x, pt2->y), + float8_mul(pt1->y, pt2->x))); +} + +Datum +point_mul(PG_FUNCTION_ARGS) +{ + Point *p1 = PG_GETARG_POINT_P(0); + Point *p2 = PG_GETARG_POINT_P(1); + Point *result; + + result = (Point *) palloc(sizeof(Point)); + + point_mul_point(result, p1, p2); + + PG_RETURN_POINT_P(result); +} + + +static inline void +point_div_point(Point *result, Point *pt1, Point *pt2) +{ + float8 div; + + div = float8_pl(float8_mul(pt2->x, pt2->x), float8_mul(pt2->y, pt2->y)); + + point_construct(result, + float8_div(float8_pl(float8_mul(pt1->x, pt2->x), + float8_mul(pt1->y, pt2->y)), div), + float8_div(float8_mi(float8_mul(pt1->y, pt2->x), + float8_mul(pt1->x, pt2->y)), div)); +} + +Datum +point_div(PG_FUNCTION_ARGS) +{ + Point *p1 = PG_GETARG_POINT_P(0); + Point *p2 = PG_GETARG_POINT_P(1); + Point *result; + + result = (Point *) palloc(sizeof(Point)); + + point_div_point(result, p1, p2); + + PG_RETURN_POINT_P(result); +} + + +/*********************************************************************** + ** + ** Routines for 2D boxes. + ** + ***********************************************************************/ + +Datum +points_box(PG_FUNCTION_ARGS) +{ + Point *p1 = PG_GETARG_POINT_P(0); + Point *p2 = PG_GETARG_POINT_P(1); + BOX *result; + + result = (BOX *) palloc(sizeof(BOX)); + + box_construct(result, p1, p2); + + PG_RETURN_BOX_P(result); +} + +Datum +box_add(PG_FUNCTION_ARGS) +{ + BOX *box = PG_GETARG_BOX_P(0); + Point *p = PG_GETARG_POINT_P(1); + BOX *result; + + result = (BOX *) palloc(sizeof(BOX)); + + point_add_point(&result->high, &box->high, p); + point_add_point(&result->low, &box->low, p); + + PG_RETURN_BOX_P(result); +} + +Datum +box_sub(PG_FUNCTION_ARGS) +{ + BOX *box = PG_GETARG_BOX_P(0); + Point *p = PG_GETARG_POINT_P(1); + BOX *result; + + result = (BOX *) palloc(sizeof(BOX)); + + point_sub_point(&result->high, &box->high, p); + point_sub_point(&result->low, &box->low, p); + + PG_RETURN_BOX_P(result); +} + +Datum +box_mul(PG_FUNCTION_ARGS) +{ + BOX *box = PG_GETARG_BOX_P(0); + Point *p = PG_GETARG_POINT_P(1); + BOX *result; + Point high, + low; + + result = (BOX *) palloc(sizeof(BOX)); + + point_mul_point(&high, &box->high, p); + point_mul_point(&low, &box->low, p); + + box_construct(result, &high, &low); + + PG_RETURN_BOX_P(result); +} + +Datum +box_div(PG_FUNCTION_ARGS) +{ + BOX *box = PG_GETARG_BOX_P(0); + Point *p = PG_GETARG_POINT_P(1); + BOX *result; + Point high, + low; + + result = (BOX *) palloc(sizeof(BOX)); + + point_div_point(&high, &box->high, p); + point_div_point(&low, &box->low, p); + + box_construct(result, &high, &low); + + PG_RETURN_BOX_P(result); +} + +/* + * Convert point to empty box + */ +Datum +point_box(PG_FUNCTION_ARGS) +{ + Point *pt = PG_GETARG_POINT_P(0); + BOX *box; + + box = (BOX *) palloc(sizeof(BOX)); + + box->high.x = pt->x; + box->low.x = pt->x; + box->high.y = pt->y; + box->low.y = pt->y; + + PG_RETURN_BOX_P(box); +} + +/* + * Smallest bounding box that includes both of the given boxes + */ +Datum +boxes_bound_box(PG_FUNCTION_ARGS) +{ + BOX *box1 = PG_GETARG_BOX_P(0), + *box2 = PG_GETARG_BOX_P(1), + *container; + + container = (BOX *) palloc(sizeof(BOX)); + + container->high.x = float8_max(box1->high.x, box2->high.x); + container->low.x = float8_min(box1->low.x, box2->low.x); + container->high.y = float8_max(box1->high.y, box2->high.y); + container->low.y = float8_min(box1->low.y, box2->low.y); + + PG_RETURN_BOX_P(container); +} + + +/*********************************************************************** + ** + ** Routines for 2D paths. + ** + ***********************************************************************/ + +/* path_add() + * Concatenate two paths (only if they are both open). + */ +Datum +path_add(PG_FUNCTION_ARGS) +{ + PATH *p1 = PG_GETARG_PATH_P(0); + PATH *p2 = PG_GETARG_PATH_P(1); + PATH *result; + int size, + base_size; + int i; + + if (p1->closed || p2->closed) + PG_RETURN_NULL(); + + base_size = sizeof(p1->p[0]) * (p1->npts + p2->npts); + size = offsetof(PATH, p) + base_size; + + /* Check for integer overflow */ + if (base_size / sizeof(p1->p[0]) != (p1->npts + p2->npts) || + size <= base_size) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("too many points requested"))); + + result = (PATH *) palloc(size); + + SET_VARSIZE(result, size); + result->npts = (p1->npts + p2->npts); + result->closed = p1->closed; + /* prevent instability in unused pad bytes */ + result->dummy = 0; + + for (i = 0; i < p1->npts; i++) + { + result->p[i].x = p1->p[i].x; + result->p[i].y = p1->p[i].y; + } + for (i = 0; i < p2->npts; i++) + { + result->p[i + p1->npts].x = p2->p[i].x; + result->p[i + p1->npts].y = p2->p[i].y; + } + + PG_RETURN_PATH_P(result); +} + +/* path_add_pt() + * Translation operators. + */ +Datum +path_add_pt(PG_FUNCTION_ARGS) +{ + PATH *path = PG_GETARG_PATH_P_COPY(0); + Point *point = PG_GETARG_POINT_P(1); + int i; + + for (i = 0; i < path->npts; i++) + point_add_point(&path->p[i], &path->p[i], point); + + PG_RETURN_PATH_P(path); +} + +Datum +path_sub_pt(PG_FUNCTION_ARGS) +{ + PATH *path = PG_GETARG_PATH_P_COPY(0); + Point *point = PG_GETARG_POINT_P(1); + int i; + + for (i = 0; i < path->npts; i++) + point_sub_point(&path->p[i], &path->p[i], point); + + PG_RETURN_PATH_P(path); +} + +/* path_mul_pt() + * Rotation and scaling operators. + */ +Datum +path_mul_pt(PG_FUNCTION_ARGS) +{ + PATH *path = PG_GETARG_PATH_P_COPY(0); + Point *point = PG_GETARG_POINT_P(1); + int i; + + for (i = 0; i < path->npts; i++) + point_mul_point(&path->p[i], &path->p[i], point); + + PG_RETURN_PATH_P(path); +} + +Datum +path_div_pt(PG_FUNCTION_ARGS) +{ + PATH *path = PG_GETARG_PATH_P_COPY(0); + Point *point = PG_GETARG_POINT_P(1); + int i; + + for (i = 0; i < path->npts; i++) + point_div_point(&path->p[i], &path->p[i], point); + + PG_RETURN_PATH_P(path); +} + + +Datum +path_poly(PG_FUNCTION_ARGS) +{ + PATH *path = PG_GETARG_PATH_P(0); + POLYGON *poly; + int size; + int i; + + /* This is not very consistent --- other similar cases return NULL ... */ + if (!path->closed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("open path cannot be converted to polygon"))); + + /* + * Never overflows: the old size fit in MaxAllocSize, and the new size is + * just a small constant larger. + */ + size = offsetof(POLYGON, p) + sizeof(poly->p[0]) * path->npts; + poly = (POLYGON *) palloc(size); + + SET_VARSIZE(poly, size); + poly->npts = path->npts; + + for (i = 0; i < path->npts; i++) + { + poly->p[i].x = path->p[i].x; + poly->p[i].y = path->p[i].y; + } + + make_bound_box(poly); + + PG_RETURN_POLYGON_P(poly); +} + + +/*********************************************************************** + ** + ** Routines for 2D polygons. + ** + ***********************************************************************/ + +Datum +poly_npoints(PG_FUNCTION_ARGS) +{ + POLYGON *poly = PG_GETARG_POLYGON_P(0); + + PG_RETURN_INT32(poly->npts); +} + + +Datum +poly_center(PG_FUNCTION_ARGS) +{ + POLYGON *poly = PG_GETARG_POLYGON_P(0); + Point *result; + CIRCLE circle; + + result = (Point *) palloc(sizeof(Point)); + + poly_to_circle(&circle, poly); + *result = circle.center; + + PG_RETURN_POINT_P(result); +} + + +Datum +poly_box(PG_FUNCTION_ARGS) +{ + POLYGON *poly = PG_GETARG_POLYGON_P(0); + BOX *box; + + box = (BOX *) palloc(sizeof(BOX)); + *box = poly->boundbox; + + PG_RETURN_BOX_P(box); +} + + +/* box_poly() + * Convert a box to a polygon. + */ +Datum +box_poly(PG_FUNCTION_ARGS) +{ + BOX *box = PG_GETARG_BOX_P(0); + POLYGON *poly; + int size; + + /* map four corners of the box to a polygon */ + size = offsetof(POLYGON, p) + sizeof(poly->p[0]) * 4; + poly = (POLYGON *) palloc(size); + + SET_VARSIZE(poly, size); + poly->npts = 4; + + poly->p[0].x = box->low.x; + poly->p[0].y = box->low.y; + poly->p[1].x = box->low.x; + poly->p[1].y = box->high.y; + poly->p[2].x = box->high.x; + poly->p[2].y = box->high.y; + poly->p[3].x = box->high.x; + poly->p[3].y = box->low.y; + + box_construct(&poly->boundbox, &box->high, &box->low); + + PG_RETURN_POLYGON_P(poly); +} + + +Datum +poly_path(PG_FUNCTION_ARGS) +{ + POLYGON *poly = PG_GETARG_POLYGON_P(0); + PATH *path; + int size; + int i; + + /* + * Never overflows: the old size fit in MaxAllocSize, and the new size is + * smaller by a small constant. + */ + size = offsetof(PATH, p) + sizeof(path->p[0]) * poly->npts; + path = (PATH *) palloc(size); + + SET_VARSIZE(path, size); + path->npts = poly->npts; + path->closed = true; + /* prevent instability in unused pad bytes */ + path->dummy = 0; + + for (i = 0; i < poly->npts; i++) + { + path->p[i].x = poly->p[i].x; + path->p[i].y = poly->p[i].y; + } + + PG_RETURN_PATH_P(path); +} + + +/*********************************************************************** + ** + ** Routines for circles. + ** + ***********************************************************************/ + +/*---------------------------------------------------------- + * Formatting and conversion routines. + *---------------------------------------------------------*/ + +/* circle_in - convert a string to internal form. + * + * External format: (center and radius of circle) + * "<(f8,f8),f8>" + * also supports quick entry style "f8,f8,f8" + */ +Datum +circle_in(PG_FUNCTION_ARGS) +{ + char *str = PG_GETARG_CSTRING(0); + Node *escontext = fcinfo->context; + CIRCLE *circle = (CIRCLE *) palloc(sizeof(CIRCLE)); + char *s, + *cp; + int depth = 0; + + s = str; + while (isspace((unsigned char) *s)) + s++; + if (*s == LDELIM_C) + depth++, s++; + else if (*s == LDELIM) + { + /* If there are two left parens, consume the first one */ + cp = (s + 1); + while (isspace((unsigned char) *cp)) + cp++; + if (*cp == LDELIM) + depth++, s = cp; + } + + /* pair_decode will consume parens around the pair, if any */ + if (!pair_decode(s, &circle->center.x, &circle->center.y, &s, "circle", str, + escontext)) + PG_RETURN_NULL(); + + if (*s == DELIM) + s++; + + if (!single_decode(s, &circle->radius, &s, "circle", str, escontext)) + PG_RETURN_NULL(); + + /* We have to accept NaN. */ + if (circle->radius < 0.0) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s: \"%s\"", + "circle", str))); + + while (depth > 0) + { + if ((*s == RDELIM) || ((*s == RDELIM_C) && (depth == 1))) + { + depth--; + s++; + while (isspace((unsigned char) *s)) + s++; + } + else + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s: \"%s\"", + "circle", str))); + } + + if (*s != '\0') + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s: \"%s\"", + "circle", str))); + + PG_RETURN_CIRCLE_P(circle); +} + +/* circle_out - convert a circle to external form. + */ +Datum +circle_out(PG_FUNCTION_ARGS) +{ + CIRCLE *circle = PG_GETARG_CIRCLE_P(0); + StringInfoData str; + + initStringInfo(&str); + + appendStringInfoChar(&str, LDELIM_C); + appendStringInfoChar(&str, LDELIM); + pair_encode(circle->center.x, circle->center.y, &str); + appendStringInfoChar(&str, RDELIM); + appendStringInfoChar(&str, DELIM); + single_encode(circle->radius, &str); + appendStringInfoChar(&str, RDELIM_C); + + PG_RETURN_CSTRING(str.data); +} + +/* + * circle_recv - converts external binary format to circle + */ +Datum +circle_recv(PG_FUNCTION_ARGS) +{ + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + CIRCLE *circle; + + circle = (CIRCLE *) palloc(sizeof(CIRCLE)); + + circle->center.x = pq_getmsgfloat8(buf); + circle->center.y = pq_getmsgfloat8(buf); + circle->radius = pq_getmsgfloat8(buf); + + /* We have to accept NaN. */ + if (circle->radius < 0.0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION), + errmsg("invalid radius in external \"circle\" value"))); + + PG_RETURN_CIRCLE_P(circle); +} + +/* + * circle_send - converts circle to binary format + */ +Datum +circle_send(PG_FUNCTION_ARGS) +{ + CIRCLE *circle = PG_GETARG_CIRCLE_P(0); + StringInfoData buf; + + pq_begintypsend(&buf); + pq_sendfloat8(&buf, circle->center.x); + pq_sendfloat8(&buf, circle->center.y); + pq_sendfloat8(&buf, circle->radius); + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} + + +/*---------------------------------------------------------- + * Relational operators for CIRCLEs. + * <, >, <=, >=, and == are based on circle area. + *---------------------------------------------------------*/ + +/* circles identical? + * + * We consider NaNs values to be equal to each other to let those circles + * to be found. + */ +Datum +circle_same(PG_FUNCTION_ARGS) +{ + CIRCLE *circle1 = PG_GETARG_CIRCLE_P(0); + CIRCLE *circle2 = PG_GETARG_CIRCLE_P(1); + + PG_RETURN_BOOL(((isnan(circle1->radius) && isnan(circle2->radius)) || + FPeq(circle1->radius, circle2->radius)) && + point_eq_point(&circle1->center, &circle2->center)); +} + +/* circle_overlap - does circle1 overlap circle2? + */ +Datum +circle_overlap(PG_FUNCTION_ARGS) +{ + CIRCLE *circle1 = PG_GETARG_CIRCLE_P(0); + CIRCLE *circle2 = PG_GETARG_CIRCLE_P(1); + + PG_RETURN_BOOL(FPle(point_dt(&circle1->center, &circle2->center), + float8_pl(circle1->radius, circle2->radius))); +} + +/* circle_overleft - is the right edge of circle1 at or left of + * the right edge of circle2? + */ +Datum +circle_overleft(PG_FUNCTION_ARGS) +{ + CIRCLE *circle1 = PG_GETARG_CIRCLE_P(0); + CIRCLE *circle2 = PG_GETARG_CIRCLE_P(1); + + PG_RETURN_BOOL(FPle(float8_pl(circle1->center.x, circle1->radius), + float8_pl(circle2->center.x, circle2->radius))); +} + +/* circle_left - is circle1 strictly left of circle2? + */ +Datum +circle_left(PG_FUNCTION_ARGS) +{ + CIRCLE *circle1 = PG_GETARG_CIRCLE_P(0); + CIRCLE *circle2 = PG_GETARG_CIRCLE_P(1); + + PG_RETURN_BOOL(FPlt(float8_pl(circle1->center.x, circle1->radius), + float8_mi(circle2->center.x, circle2->radius))); +} + +/* circle_right - is circle1 strictly right of circle2? + */ +Datum +circle_right(PG_FUNCTION_ARGS) +{ + CIRCLE *circle1 = PG_GETARG_CIRCLE_P(0); + CIRCLE *circle2 = PG_GETARG_CIRCLE_P(1); + + PG_RETURN_BOOL(FPgt(float8_mi(circle1->center.x, circle1->radius), + float8_pl(circle2->center.x, circle2->radius))); +} + +/* circle_overright - is the left edge of circle1 at or right of + * the left edge of circle2? + */ +Datum +circle_overright(PG_FUNCTION_ARGS) +{ + CIRCLE *circle1 = PG_GETARG_CIRCLE_P(0); + CIRCLE *circle2 = PG_GETARG_CIRCLE_P(1); + + PG_RETURN_BOOL(FPge(float8_mi(circle1->center.x, circle1->radius), + float8_mi(circle2->center.x, circle2->radius))); +} + +/* circle_contained - is circle1 contained by circle2? + */ +Datum +circle_contained(PG_FUNCTION_ARGS) +{ + CIRCLE *circle1 = PG_GETARG_CIRCLE_P(0); + CIRCLE *circle2 = PG_GETARG_CIRCLE_P(1); + + PG_RETURN_BOOL(FPle(point_dt(&circle1->center, &circle2->center), + float8_mi(circle2->radius, circle1->radius))); +} + +/* circle_contain - does circle1 contain circle2? + */ +Datum +circle_contain(PG_FUNCTION_ARGS) +{ + CIRCLE *circle1 = PG_GETARG_CIRCLE_P(0); + CIRCLE *circle2 = PG_GETARG_CIRCLE_P(1); + + PG_RETURN_BOOL(FPle(point_dt(&circle1->center, &circle2->center), + float8_mi(circle1->radius, circle2->radius))); +} + + +/* circle_below - is circle1 strictly below circle2? + */ +Datum +circle_below(PG_FUNCTION_ARGS) +{ + CIRCLE *circle1 = PG_GETARG_CIRCLE_P(0); + CIRCLE *circle2 = PG_GETARG_CIRCLE_P(1); + + PG_RETURN_BOOL(FPlt(float8_pl(circle1->center.y, circle1->radius), + float8_mi(circle2->center.y, circle2->radius))); +} + +/* circle_above - is circle1 strictly above circle2? + */ +Datum +circle_above(PG_FUNCTION_ARGS) +{ + CIRCLE *circle1 = PG_GETARG_CIRCLE_P(0); + CIRCLE *circle2 = PG_GETARG_CIRCLE_P(1); + + PG_RETURN_BOOL(FPgt(float8_mi(circle1->center.y, circle1->radius), + float8_pl(circle2->center.y, circle2->radius))); +} + +/* circle_overbelow - is the upper edge of circle1 at or below + * the upper edge of circle2? + */ +Datum +circle_overbelow(PG_FUNCTION_ARGS) +{ + CIRCLE *circle1 = PG_GETARG_CIRCLE_P(0); + CIRCLE *circle2 = PG_GETARG_CIRCLE_P(1); + + PG_RETURN_BOOL(FPle(float8_pl(circle1->center.y, circle1->radius), + float8_pl(circle2->center.y, circle2->radius))); +} + +/* circle_overabove - is the lower edge of circle1 at or above + * the lower edge of circle2? + */ +Datum +circle_overabove(PG_FUNCTION_ARGS) +{ + CIRCLE *circle1 = PG_GETARG_CIRCLE_P(0); + CIRCLE *circle2 = PG_GETARG_CIRCLE_P(1); + + PG_RETURN_BOOL(FPge(float8_mi(circle1->center.y, circle1->radius), + float8_mi(circle2->center.y, circle2->radius))); +} + + +/* circle_relop - is area(circle1) relop area(circle2), within + * our accuracy constraint? + */ +Datum +circle_eq(PG_FUNCTION_ARGS) +{ + CIRCLE *circle1 = PG_GETARG_CIRCLE_P(0); + CIRCLE *circle2 = PG_GETARG_CIRCLE_P(1); + + PG_RETURN_BOOL(FPeq(circle_ar(circle1), circle_ar(circle2))); +} + +Datum +circle_ne(PG_FUNCTION_ARGS) +{ + CIRCLE *circle1 = PG_GETARG_CIRCLE_P(0); + CIRCLE *circle2 = PG_GETARG_CIRCLE_P(1); + + PG_RETURN_BOOL(FPne(circle_ar(circle1), circle_ar(circle2))); +} + +Datum +circle_lt(PG_FUNCTION_ARGS) +{ + CIRCLE *circle1 = PG_GETARG_CIRCLE_P(0); + CIRCLE *circle2 = PG_GETARG_CIRCLE_P(1); + + PG_RETURN_BOOL(FPlt(circle_ar(circle1), circle_ar(circle2))); +} + +Datum +circle_gt(PG_FUNCTION_ARGS) +{ + CIRCLE *circle1 = PG_GETARG_CIRCLE_P(0); + CIRCLE *circle2 = PG_GETARG_CIRCLE_P(1); + + PG_RETURN_BOOL(FPgt(circle_ar(circle1), circle_ar(circle2))); +} + +Datum +circle_le(PG_FUNCTION_ARGS) +{ + CIRCLE *circle1 = PG_GETARG_CIRCLE_P(0); + CIRCLE *circle2 = PG_GETARG_CIRCLE_P(1); + + PG_RETURN_BOOL(FPle(circle_ar(circle1), circle_ar(circle2))); +} + +Datum +circle_ge(PG_FUNCTION_ARGS) +{ + CIRCLE *circle1 = PG_GETARG_CIRCLE_P(0); + CIRCLE *circle2 = PG_GETARG_CIRCLE_P(1); + + PG_RETURN_BOOL(FPge(circle_ar(circle1), circle_ar(circle2))); +} + + +/*---------------------------------------------------------- + * "Arithmetic" operators on circles. + *---------------------------------------------------------*/ + +/* circle_add_pt() + * Translation operator. + */ +Datum +circle_add_pt(PG_FUNCTION_ARGS) +{ + CIRCLE *circle = PG_GETARG_CIRCLE_P(0); + Point *point = PG_GETARG_POINT_P(1); + CIRCLE *result; + + result = (CIRCLE *) palloc(sizeof(CIRCLE)); + + point_add_point(&result->center, &circle->center, point); + result->radius = circle->radius; + + PG_RETURN_CIRCLE_P(result); +} + +Datum +circle_sub_pt(PG_FUNCTION_ARGS) +{ + CIRCLE *circle = PG_GETARG_CIRCLE_P(0); + Point *point = PG_GETARG_POINT_P(1); + CIRCLE *result; + + result = (CIRCLE *) palloc(sizeof(CIRCLE)); + + point_sub_point(&result->center, &circle->center, point); + result->radius = circle->radius; + + PG_RETURN_CIRCLE_P(result); +} + + +/* circle_mul_pt() + * Rotation and scaling operators. + */ +Datum +circle_mul_pt(PG_FUNCTION_ARGS) +{ + CIRCLE *circle = PG_GETARG_CIRCLE_P(0); + Point *point = PG_GETARG_POINT_P(1); + CIRCLE *result; + + result = (CIRCLE *) palloc(sizeof(CIRCLE)); + + point_mul_point(&result->center, &circle->center, point); + result->radius = float8_mul(circle->radius, HYPOT(point->x, point->y)); + + PG_RETURN_CIRCLE_P(result); +} + +Datum +circle_div_pt(PG_FUNCTION_ARGS) +{ + CIRCLE *circle = PG_GETARG_CIRCLE_P(0); + Point *point = PG_GETARG_POINT_P(1); + CIRCLE *result; + + result = (CIRCLE *) palloc(sizeof(CIRCLE)); + + point_div_point(&result->center, &circle->center, point); + result->radius = float8_div(circle->radius, HYPOT(point->x, point->y)); + + PG_RETURN_CIRCLE_P(result); +} + + +/* circle_area - returns the area of the circle. + */ +Datum +circle_area(PG_FUNCTION_ARGS) +{ + CIRCLE *circle = PG_GETARG_CIRCLE_P(0); + + PG_RETURN_FLOAT8(circle_ar(circle)); +} + + +/* circle_diameter - returns the diameter of the circle. + */ +Datum +circle_diameter(PG_FUNCTION_ARGS) +{ + CIRCLE *circle = PG_GETARG_CIRCLE_P(0); + + PG_RETURN_FLOAT8(float8_mul(circle->radius, 2.0)); +} + + +/* circle_radius - returns the radius of the circle. + */ +Datum +circle_radius(PG_FUNCTION_ARGS) +{ + CIRCLE *circle = PG_GETARG_CIRCLE_P(0); + + PG_RETURN_FLOAT8(circle->radius); +} + + +/* circle_distance - returns the distance between + * two circles. + */ +Datum +circle_distance(PG_FUNCTION_ARGS) +{ + CIRCLE *circle1 = PG_GETARG_CIRCLE_P(0); + CIRCLE *circle2 = PG_GETARG_CIRCLE_P(1); + float8 result; + + result = float8_mi(point_dt(&circle1->center, &circle2->center), + float8_pl(circle1->radius, circle2->radius)); + if (result < 0.0) + result = 0.0; + + PG_RETURN_FLOAT8(result); +} + + +Datum +circle_contain_pt(PG_FUNCTION_ARGS) +{ + CIRCLE *circle = PG_GETARG_CIRCLE_P(0); + Point *point = PG_GETARG_POINT_P(1); + float8 d; + + d = point_dt(&circle->center, point); + PG_RETURN_BOOL(d <= circle->radius); +} + + +Datum +pt_contained_circle(PG_FUNCTION_ARGS) +{ + Point *point = PG_GETARG_POINT_P(0); + CIRCLE *circle = PG_GETARG_CIRCLE_P(1); + float8 d; + + d = point_dt(&circle->center, point); + PG_RETURN_BOOL(d <= circle->radius); +} + + +/* dist_pc - returns the distance between + * a point and a circle. + */ +Datum +dist_pc(PG_FUNCTION_ARGS) +{ + Point *point = PG_GETARG_POINT_P(0); + CIRCLE *circle = PG_GETARG_CIRCLE_P(1); + float8 result; + + result = float8_mi(point_dt(point, &circle->center), + circle->radius); + if (result < 0.0) + result = 0.0; + + PG_RETURN_FLOAT8(result); +} + +/* + * Distance from a circle to a point + */ +Datum +dist_cpoint(PG_FUNCTION_ARGS) +{ + CIRCLE *circle = PG_GETARG_CIRCLE_P(0); + Point *point = PG_GETARG_POINT_P(1); + float8 result; + + result = float8_mi(point_dt(point, &circle->center), circle->radius); + if (result < 0.0) + result = 0.0; + + PG_RETURN_FLOAT8(result); +} + +/* circle_center - returns the center point of the circle. + */ +Datum +circle_center(PG_FUNCTION_ARGS) +{ + CIRCLE *circle = PG_GETARG_CIRCLE_P(0); + Point *result; + + result = (Point *) palloc(sizeof(Point)); + result->x = circle->center.x; + result->y = circle->center.y; + + PG_RETURN_POINT_P(result); +} + + +/* circle_ar - returns the area of the circle. + */ +static float8 +circle_ar(CIRCLE *circle) +{ + return float8_mul(float8_mul(circle->radius, circle->radius), M_PI); +} + + +/*---------------------------------------------------------- + * Conversion operators. + *---------------------------------------------------------*/ + +Datum +cr_circle(PG_FUNCTION_ARGS) +{ + Point *center = PG_GETARG_POINT_P(0); + float8 radius = PG_GETARG_FLOAT8(1); + CIRCLE *result; + + result = (CIRCLE *) palloc(sizeof(CIRCLE)); + + result->center.x = center->x; + result->center.y = center->y; + result->radius = radius; + + PG_RETURN_CIRCLE_P(result); +} + +Datum +circle_box(PG_FUNCTION_ARGS) +{ + CIRCLE *circle = PG_GETARG_CIRCLE_P(0); + BOX *box; + float8 delta; + + box = (BOX *) palloc(sizeof(BOX)); + + delta = float8_div(circle->radius, sqrt(2.0)); + + box->high.x = float8_pl(circle->center.x, delta); + box->low.x = float8_mi(circle->center.x, delta); + box->high.y = float8_pl(circle->center.y, delta); + box->low.y = float8_mi(circle->center.y, delta); + + PG_RETURN_BOX_P(box); +} + +/* box_circle() + * Convert a box to a circle. + */ +Datum +box_circle(PG_FUNCTION_ARGS) +{ + BOX *box = PG_GETARG_BOX_P(0); + CIRCLE *circle; + + circle = (CIRCLE *) palloc(sizeof(CIRCLE)); + + circle->center.x = float8_div(float8_pl(box->high.x, box->low.x), 2.0); + circle->center.y = float8_div(float8_pl(box->high.y, box->low.y), 2.0); + + circle->radius = point_dt(&circle->center, &box->high); + + PG_RETURN_CIRCLE_P(circle); +} + + +Datum +circle_poly(PG_FUNCTION_ARGS) +{ + int32 npts = PG_GETARG_INT32(0); + CIRCLE *circle = PG_GETARG_CIRCLE_P(1); + POLYGON *poly; + int base_size, + size; + int i; + float8 angle; + float8 anglestep; + + if (FPzero(circle->radius)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot convert circle with radius zero to polygon"))); + + if (npts < 2) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("must request at least 2 points"))); + + base_size = sizeof(poly->p[0]) * npts; + size = offsetof(POLYGON, p) + base_size; + + /* Check for integer overflow */ + if (base_size / npts != sizeof(poly->p[0]) || size <= base_size) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("too many points requested"))); + + poly = (POLYGON *) palloc0(size); /* zero any holes */ + SET_VARSIZE(poly, size); + poly->npts = npts; + + anglestep = float8_div(2.0 * M_PI, npts); + + for (i = 0; i < npts; i++) + { + angle = float8_mul(anglestep, i); + + poly->p[i].x = float8_mi(circle->center.x, + float8_mul(circle->radius, cos(angle))); + poly->p[i].y = float8_pl(circle->center.y, + float8_mul(circle->radius, sin(angle))); + } + + make_bound_box(poly); + + PG_RETURN_POLYGON_P(poly); +} + +/* + * Convert polygon to circle + * + * The result must be preallocated. + * + * XXX This algorithm should use weighted means of line segments + * rather than straight average values of points - tgl 97/01/21. + */ +static void +poly_to_circle(CIRCLE *result, POLYGON *poly) +{ + int i; + + Assert(poly->npts > 0); + + result->center.x = 0; + result->center.y = 0; + result->radius = 0; + + for (i = 0; i < poly->npts; i++) + point_add_point(&result->center, &result->center, &poly->p[i]); + result->center.x = float8_div(result->center.x, poly->npts); + result->center.y = float8_div(result->center.y, poly->npts); + + for (i = 0; i < poly->npts; i++) + result->radius = float8_pl(result->radius, + point_dt(&poly->p[i], &result->center)); + result->radius = float8_div(result->radius, poly->npts); +} + +Datum +poly_circle(PG_FUNCTION_ARGS) +{ + POLYGON *poly = PG_GETARG_POLYGON_P(0); + CIRCLE *result; + + result = (CIRCLE *) palloc(sizeof(CIRCLE)); + + poly_to_circle(result, poly); + + PG_RETURN_CIRCLE_P(result); +} + + +/*********************************************************************** + ** + ** Private routines for multiple types. + ** + ***********************************************************************/ + +/* + * Test to see if the point is inside the polygon, returns 1/0, or 2 if + * the point is on the polygon. + * Code adapted but not copied from integer-based routines in WN: A + * Server for the HTTP + * version 1.15.1, file wn/image.c + * http://hopf.math.northwestern.edu/index.html + * Description of algorithm: http://www.linuxjournal.com/article/2197 + * http://www.linuxjournal.com/article/2029 + */ + +#define POINT_ON_POLYGON INT_MAX + +static int +point_inside(Point *p, int npts, Point *plist) +{ + float8 x0, + y0; + float8 prev_x, + prev_y; + int i = 0; + float8 x, + y; + int cross, + total_cross = 0; + + Assert(npts > 0); + + /* compute first polygon point relative to single point */ + x0 = float8_mi(plist[0].x, p->x); + y0 = float8_mi(plist[0].y, p->y); + + prev_x = x0; + prev_y = y0; + /* loop over polygon points and aggregate total_cross */ + for (i = 1; i < npts; i++) + { + /* compute next polygon point relative to single point */ + x = float8_mi(plist[i].x, p->x); + y = float8_mi(plist[i].y, p->y); + + /* compute previous to current point crossing */ + if ((cross = lseg_crossing(x, y, prev_x, prev_y)) == POINT_ON_POLYGON) + return 2; + total_cross += cross; + + prev_x = x; + prev_y = y; + } + + /* now do the first point */ + if ((cross = lseg_crossing(x0, y0, prev_x, prev_y)) == POINT_ON_POLYGON) + return 2; + total_cross += cross; + + if (total_cross != 0) + return 1; + return 0; +} + + +/* lseg_crossing() + * Returns +/-2 if line segment crosses the positive X-axis in a +/- direction. + * Returns +/-1 if one point is on the positive X-axis. + * Returns 0 if both points are on the positive X-axis, or there is no crossing. + * Returns POINT_ON_POLYGON if the segment contains (0,0). + * Wow, that is one confusing API, but it is used above, and when summed, + * can tell is if a point is in a polygon. + */ + +static int +lseg_crossing(float8 x, float8 y, float8 prev_x, float8 prev_y) +{ + float8 z; + int y_sign; + + if (FPzero(y)) + { /* y == 0, on X axis */ + if (FPzero(x)) /* (x,y) is (0,0)? */ + return POINT_ON_POLYGON; + else if (FPgt(x, 0)) + { /* x > 0 */ + if (FPzero(prev_y)) /* y and prev_y are zero */ + /* prev_x > 0? */ + return FPgt(prev_x, 0.0) ? 0 : POINT_ON_POLYGON; + return FPlt(prev_y, 0.0) ? 1 : -1; + } + else + { /* x < 0, x not on positive X axis */ + if (FPzero(prev_y)) + /* prev_x < 0? */ + return FPlt(prev_x, 0.0) ? 0 : POINT_ON_POLYGON; + return 0; + } + } + else + { /* y != 0 */ + /* compute y crossing direction from previous point */ + y_sign = FPgt(y, 0.0) ? 1 : -1; + + if (FPzero(prev_y)) + /* previous point was on X axis, so new point is either off or on */ + return FPlt(prev_x, 0.0) ? 0 : y_sign; + else if ((y_sign < 0 && FPlt(prev_y, 0.0)) || + (y_sign > 0 && FPgt(prev_y, 0.0))) + /* both above or below X axis */ + return 0; /* same sign */ + else + { /* y and prev_y cross X-axis */ + if (FPge(x, 0.0) && FPgt(prev_x, 0.0)) + /* both non-negative so cross positive X-axis */ + return 2 * y_sign; + if (FPlt(x, 0.0) && FPle(prev_x, 0.0)) + /* both non-positive so do not cross positive X-axis */ + return 0; + + /* x and y cross axes, see URL above point_inside() */ + z = float8_mi(float8_mul(float8_mi(x, prev_x), y), + float8_mul(float8_mi(y, prev_y), x)); + if (FPzero(z)) + return POINT_ON_POLYGON; + if ((y_sign < 0 && FPlt(z, 0.0)) || + (y_sign > 0 && FPgt(z, 0.0))) + return 0; + return 2 * y_sign; + } + } +} + + +static bool +plist_same(int npts, Point *p1, Point *p2) +{ + int i, + ii, + j; + + /* find match for first point */ + for (i = 0; i < npts; i++) + { + if (point_eq_point(&p2[i], &p1[0])) + { + + /* match found? then look forward through remaining points */ + for (ii = 1, j = i + 1; ii < npts; ii++, j++) + { + if (j >= npts) + j = 0; + if (!point_eq_point(&p2[j], &p1[ii])) + break; + } + if (ii == npts) + return true; + + /* match not found forwards? then look backwards */ + for (ii = 1, j = i - 1; ii < npts; ii++, j--) + { + if (j < 0) + j = (npts - 1); + if (!point_eq_point(&p2[j], &p1[ii])) + break; + } + if (ii == npts) + return true; + } + } + + return false; +} + + +/*------------------------------------------------------------------------- + * Determine the hypotenuse. + * + * If required, x and y are swapped to make x the larger number. The + * traditional formula of x^2+y^2 is rearranged to factor x outside the + * sqrt. This allows computation of the hypotenuse for significantly + * larger values, and with a higher precision than when using the naive + * formula. In particular, this cannot overflow unless the final result + * would be out-of-range. + * + * sqrt( x^2 + y^2 ) = sqrt( x^2( 1 + y^2/x^2) ) + * = x * sqrt( 1 + y^2/x^2 ) + * = x * sqrt( 1 + y/x * y/x ) + * + * It is expected that this routine will eventually be replaced with the + * C99 hypot() function. + * + * This implementation conforms to IEEE Std 1003.1 and GLIBC, in that the + * case of hypot(inf,nan) results in INF, and not NAN. + *----------------------------------------------------------------------- + */ +float8 +pg_hypot(float8 x, float8 y) +{ + float8 yx, + result; + + /* Handle INF and NaN properly */ + if (isinf(x) || isinf(y)) + return get_float8_infinity(); + + if (isnan(x) || isnan(y)) + return get_float8_nan(); + + /* Else, drop any minus signs */ + x = fabs(x); + y = fabs(y); + + /* Swap x and y if needed to make x the larger one */ + if (x < y) + { + float8 temp = x; + + x = y; + y = temp; + } + + /* + * If y is zero, the hypotenuse is x. This test saves a few cycles in + * such cases, but more importantly it also protects against + * divide-by-zero errors, since now x >= y. + */ + if (y == 0.0) + return x; + + /* Determine the hypotenuse */ + yx = y / x; + result = x * sqrt(1.0 + (yx * yx)); + + if (unlikely(isinf(result))) + float_overflow_error(); + if (unlikely(result == 0.0)) + float_underflow_error(); + + return result; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/geo_selfuncs.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/geo_selfuncs.c new file mode 100644 index 00000000000..f9f40922e03 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/geo_selfuncs.c @@ -0,0 +1,96 @@ +/*------------------------------------------------------------------------- + * + * geo_selfuncs.c + * Selectivity routines registered in the operator catalog in the + * "oprrest" and "oprjoin" attributes. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/adt/geo_selfuncs.c + * + * XXX These are totally bogus. Perhaps someone will make them do + * something reasonable, someday. + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "utils/builtins.h" +#include "utils/geo_decls.h" + + +/* + * Selectivity functions for geometric operators. These are bogus -- unless + * we know the actual key distribution in the index, we can't make a good + * prediction of the selectivity of these operators. + * + * Note: the values used here may look unreasonably small. Perhaps they + * are. For now, we want to make sure that the optimizer will make use + * of a geometric index if one is available, so the selectivity had better + * be fairly small. + * + * In general, GiST needs to search multiple subtrees in order to guarantee + * that all occurrences of the same key have been found. Because of this, + * the estimated cost for scanning the index ought to be higher than the + * output selectivity would indicate. gistcostestimate(), over in selfuncs.c, + * ought to be adjusted accordingly --- but until we can generate somewhat + * realistic numbers here, it hardly matters... + */ + + +/* + * Selectivity for operators that depend on area, such as "overlap". + */ + +Datum +areasel(PG_FUNCTION_ARGS) +{ + PG_RETURN_FLOAT8(0.005); +} + +Datum +areajoinsel(PG_FUNCTION_ARGS) +{ + PG_RETURN_FLOAT8(0.005); +} + +/* + * positionsel + * + * How likely is a box to be strictly left of (right of, above, below) + * a given box? + */ + +Datum +positionsel(PG_FUNCTION_ARGS) +{ + PG_RETURN_FLOAT8(0.1); +} + +Datum +positionjoinsel(PG_FUNCTION_ARGS) +{ + PG_RETURN_FLOAT8(0.1); +} + +/* + * contsel -- How likely is a box to contain (be contained by) a given box? + * + * This is a tighter constraint than "overlap", so produce a smaller + * estimate than areasel does. + */ + +Datum +contsel(PG_FUNCTION_ARGS) +{ + PG_RETURN_FLOAT8(0.001); +} + +Datum +contjoinsel(PG_FUNCTION_ARGS) +{ + PG_RETURN_FLOAT8(0.001); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/geo_spgist.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/geo_spgist.c new file mode 100644 index 00000000000..b708a805477 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/geo_spgist.c @@ -0,0 +1,885 @@ +/*------------------------------------------------------------------------- + * + * geo_spgist.c + * SP-GiST implementation of 4-dimensional quad tree over boxes + * + * This module provides SP-GiST implementation for boxes using quad tree + * analogy in 4-dimensional space. SP-GiST doesn't allow indexing of + * overlapping objects. We are making 2D objects never-overlapping in + * 4D space. This technique has some benefits compared to traditional + * R-Tree which is implemented as GiST. The performance tests reveal + * that this technique especially beneficial with too much overlapping + * objects, so called "spaghetti data". + * + * Unlike the original quad tree, we are splitting the tree into 16 + * quadrants in 4D space. It is easier to imagine it as splitting space + * two times into 4: + * + * | | + * | | + * | -----+----- + * | | + * | | + * -------------+------------- + * | + * | + * | + * | + * | + * + * We are using box datatype as the prefix, but we are treating them + * as points in 4-dimensional space, because 2D boxes are not enough + * to represent the quadrant boundaries in 4D space. They however are + * sufficient to point out the additional boundaries of the next + * quadrant. + * + * We are using traversal values provided by SP-GiST to calculate and + * to store the bounds of the quadrants, while traversing into the tree. + * Traversal value has all the boundaries in the 4D space, and is capable + * of transferring the required boundaries to the following traversal + * values. In conclusion, three things are necessary to calculate the + * next traversal value: + * + * (1) the traversal value of the parent + * (2) the quadrant of the current node + * (3) the prefix of the current node + * + * If we visualize them on our simplified drawing (see the drawing above); + * transferred boundaries of (1) would be the outer axis, relevant part + * of (2) would be the up right part of the other axis, and (3) would be + * the inner axis. + * + * For example, consider the case of overlapping. When recursion + * descends deeper and deeper down the tree, all quadrants in + * the current node will be checked for overlapping. The boundaries + * will be re-calculated for all quadrants. Overlap check answers + * the question: can any box from this quadrant overlap with the given + * box? If yes, then this quadrant will be walked. If no, then this + * quadrant will be skipped. + * + * This method provides restrictions for minimum and maximum values of + * every dimension of every corner of the box on every level of the tree + * except the root. For the root node, we are setting the boundaries + * that we don't yet have as infinity. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/utils/adt/geo_spgist.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/spgist.h" +#include "access/spgist_private.h" +#include "access/stratnum.h" +#include "catalog/pg_type.h" +#include "utils/float.h" +#include "utils/fmgroids.h" +#include "utils/fmgrprotos.h" +#include "utils/geo_decls.h" + +/* + * Comparator for qsort + * + * We don't need to use the floating point macros in here, because this + * is only going to be used in a place to effect the performance + * of the index, not the correctness. + */ +static int +compareDoubles(const void *a, const void *b) +{ + float8 x = *(float8 *) a; + float8 y = *(float8 *) b; + + if (x == y) + return 0; + return (x > y) ? 1 : -1; +} + +typedef struct +{ + float8 low; + float8 high; +} Range; + +typedef struct +{ + Range left; + Range right; +} RangeBox; + +typedef struct +{ + RangeBox range_box_x; + RangeBox range_box_y; +} RectBox; + +/* + * Calculate the quadrant + * + * The quadrant is 8 bit unsigned integer with 4 least bits in use. + * This function accepts BOXes as input. They are not casted to + * RangeBoxes, yet. All 4 bits are set by comparing a corner of the box. + * This makes 16 quadrants in total. + */ +static uint8 +getQuadrant(BOX *centroid, BOX *inBox) +{ + uint8 quadrant = 0; + + if (inBox->low.x > centroid->low.x) + quadrant |= 0x8; + + if (inBox->high.x > centroid->high.x) + quadrant |= 0x4; + + if (inBox->low.y > centroid->low.y) + quadrant |= 0x2; + + if (inBox->high.y > centroid->high.y) + quadrant |= 0x1; + + return quadrant; +} + +/* + * Get RangeBox using BOX + * + * We are turning the BOX to our structures to emphasize their function + * of representing points in 4D space. It also is more convenient to + * access the values with this structure. + */ +static RangeBox * +getRangeBox(BOX *box) +{ + RangeBox *range_box = (RangeBox *) palloc(sizeof(RangeBox)); + + range_box->left.low = box->low.x; + range_box->left.high = box->high.x; + + range_box->right.low = box->low.y; + range_box->right.high = box->high.y; + + return range_box; +} + +/* + * Initialize the traversal value + * + * In the beginning, we don't have any restrictions. We have to + * initialize the struct to cover the whole 4D space. + */ +static RectBox * +initRectBox(void) +{ + RectBox *rect_box = (RectBox *) palloc(sizeof(RectBox)); + float8 infinity = get_float8_infinity(); + + rect_box->range_box_x.left.low = -infinity; + rect_box->range_box_x.left.high = infinity; + + rect_box->range_box_x.right.low = -infinity; + rect_box->range_box_x.right.high = infinity; + + rect_box->range_box_y.left.low = -infinity; + rect_box->range_box_y.left.high = infinity; + + rect_box->range_box_y.right.low = -infinity; + rect_box->range_box_y.right.high = infinity; + + return rect_box; +} + +/* + * Calculate the next traversal value + * + * All centroids are bounded by RectBox, but SP-GiST only keeps + * boxes. When we are traversing the tree, we must calculate RectBox, + * using centroid and quadrant. + */ +static RectBox * +nextRectBox(RectBox *rect_box, RangeBox *centroid, uint8 quadrant) +{ + RectBox *next_rect_box = (RectBox *) palloc(sizeof(RectBox)); + + memcpy(next_rect_box, rect_box, sizeof(RectBox)); + + if (quadrant & 0x8) + next_rect_box->range_box_x.left.low = centroid->left.low; + else + next_rect_box->range_box_x.left.high = centroid->left.low; + + if (quadrant & 0x4) + next_rect_box->range_box_x.right.low = centroid->left.high; + else + next_rect_box->range_box_x.right.high = centroid->left.high; + + if (quadrant & 0x2) + next_rect_box->range_box_y.left.low = centroid->right.low; + else + next_rect_box->range_box_y.left.high = centroid->right.low; + + if (quadrant & 0x1) + next_rect_box->range_box_y.right.low = centroid->right.high; + else + next_rect_box->range_box_y.right.high = centroid->right.high; + + return next_rect_box; +} + +/* Can any range from range_box overlap with this argument? */ +static bool +overlap2D(RangeBox *range_box, Range *query) +{ + return FPge(range_box->right.high, query->low) && + FPle(range_box->left.low, query->high); +} + +/* Can any rectangle from rect_box overlap with this argument? */ +static bool +overlap4D(RectBox *rect_box, RangeBox *query) +{ + return overlap2D(&rect_box->range_box_x, &query->left) && + overlap2D(&rect_box->range_box_y, &query->right); +} + +/* Can any range from range_box contain this argument? */ +static bool +contain2D(RangeBox *range_box, Range *query) +{ + return FPge(range_box->right.high, query->high) && + FPle(range_box->left.low, query->low); +} + +/* Can any rectangle from rect_box contain this argument? */ +static bool +contain4D(RectBox *rect_box, RangeBox *query) +{ + return contain2D(&rect_box->range_box_x, &query->left) && + contain2D(&rect_box->range_box_y, &query->right); +} + +/* Can any range from range_box be contained by this argument? */ +static bool +contained2D(RangeBox *range_box, Range *query) +{ + return FPle(range_box->left.low, query->high) && + FPge(range_box->left.high, query->low) && + FPle(range_box->right.low, query->high) && + FPge(range_box->right.high, query->low); +} + +/* Can any rectangle from rect_box be contained by this argument? */ +static bool +contained4D(RectBox *rect_box, RangeBox *query) +{ + return contained2D(&rect_box->range_box_x, &query->left) && + contained2D(&rect_box->range_box_y, &query->right); +} + +/* Can any range from range_box to be lower than this argument? */ +static bool +lower2D(RangeBox *range_box, Range *query) +{ + return FPlt(range_box->left.low, query->low) && + FPlt(range_box->right.low, query->low); +} + +/* Can any range from range_box not extend to the right side of the query? */ +static bool +overLower2D(RangeBox *range_box, Range *query) +{ + return FPle(range_box->left.low, query->high) && + FPle(range_box->right.low, query->high); +} + +/* Can any range from range_box to be higher than this argument? */ +static bool +higher2D(RangeBox *range_box, Range *query) +{ + return FPgt(range_box->left.high, query->high) && + FPgt(range_box->right.high, query->high); +} + +/* Can any range from range_box not extend to the left side of the query? */ +static bool +overHigher2D(RangeBox *range_box, Range *query) +{ + return FPge(range_box->left.high, query->low) && + FPge(range_box->right.high, query->low); +} + +/* Can any rectangle from rect_box be left of this argument? */ +static bool +left4D(RectBox *rect_box, RangeBox *query) +{ + return lower2D(&rect_box->range_box_x, &query->left); +} + +/* Can any rectangle from rect_box does not extend the right of this argument? */ +static bool +overLeft4D(RectBox *rect_box, RangeBox *query) +{ + return overLower2D(&rect_box->range_box_x, &query->left); +} + +/* Can any rectangle from rect_box be right of this argument? */ +static bool +right4D(RectBox *rect_box, RangeBox *query) +{ + return higher2D(&rect_box->range_box_x, &query->left); +} + +/* Can any rectangle from rect_box does not extend the left of this argument? */ +static bool +overRight4D(RectBox *rect_box, RangeBox *query) +{ + return overHigher2D(&rect_box->range_box_x, &query->left); +} + +/* Can any rectangle from rect_box be below of this argument? */ +static bool +below4D(RectBox *rect_box, RangeBox *query) +{ + return lower2D(&rect_box->range_box_y, &query->right); +} + +/* Can any rectangle from rect_box does not extend above this argument? */ +static bool +overBelow4D(RectBox *rect_box, RangeBox *query) +{ + return overLower2D(&rect_box->range_box_y, &query->right); +} + +/* Can any rectangle from rect_box be above of this argument? */ +static bool +above4D(RectBox *rect_box, RangeBox *query) +{ + return higher2D(&rect_box->range_box_y, &query->right); +} + +/* Can any rectangle from rect_box does not extend below of this argument? */ +static bool +overAbove4D(RectBox *rect_box, RangeBox *query) +{ + return overHigher2D(&rect_box->range_box_y, &query->right); +} + +/* Lower bound for the distance between point and rect_box */ +static double +pointToRectBoxDistance(Point *point, RectBox *rect_box) +{ + double dx; + double dy; + + if (point->x < rect_box->range_box_x.left.low) + dx = rect_box->range_box_x.left.low - point->x; + else if (point->x > rect_box->range_box_x.right.high) + dx = point->x - rect_box->range_box_x.right.high; + else + dx = 0; + + if (point->y < rect_box->range_box_y.left.low) + dy = rect_box->range_box_y.left.low - point->y; + else if (point->y > rect_box->range_box_y.right.high) + dy = point->y - rect_box->range_box_y.right.high; + else + dy = 0; + + return HYPOT(dx, dy); +} + + +/* + * SP-GiST config function + */ +Datum +spg_box_quad_config(PG_FUNCTION_ARGS) +{ + spgConfigOut *cfg = (spgConfigOut *) PG_GETARG_POINTER(1); + + cfg->prefixType = BOXOID; + cfg->labelType = VOIDOID; /* We don't need node labels. */ + cfg->canReturnData = true; + cfg->longValuesOK = false; + + PG_RETURN_VOID(); +} + +/* + * SP-GiST choose function + */ +Datum +spg_box_quad_choose(PG_FUNCTION_ARGS) +{ + spgChooseIn *in = (spgChooseIn *) PG_GETARG_POINTER(0); + spgChooseOut *out = (spgChooseOut *) PG_GETARG_POINTER(1); + BOX *centroid = DatumGetBoxP(in->prefixDatum), + *box = DatumGetBoxP(in->leafDatum); + + out->resultType = spgMatchNode; + out->result.matchNode.restDatum = BoxPGetDatum(box); + + /* nodeN will be set by core, when allTheSame. */ + if (!in->allTheSame) + out->result.matchNode.nodeN = getQuadrant(centroid, box); + + PG_RETURN_VOID(); +} + +/* + * SP-GiST pick-split function + * + * It splits a list of boxes into quadrants by choosing a central 4D + * point as the median of the coordinates of the boxes. + */ +Datum +spg_box_quad_picksplit(PG_FUNCTION_ARGS) +{ + spgPickSplitIn *in = (spgPickSplitIn *) PG_GETARG_POINTER(0); + spgPickSplitOut *out = (spgPickSplitOut *) PG_GETARG_POINTER(1); + BOX *centroid; + int median, + i; + float8 *lowXs = palloc(sizeof(float8) * in->nTuples); + float8 *highXs = palloc(sizeof(float8) * in->nTuples); + float8 *lowYs = palloc(sizeof(float8) * in->nTuples); + float8 *highYs = palloc(sizeof(float8) * in->nTuples); + + /* Calculate median of all 4D coordinates */ + for (i = 0; i < in->nTuples; i++) + { + BOX *box = DatumGetBoxP(in->datums[i]); + + lowXs[i] = box->low.x; + highXs[i] = box->high.x; + lowYs[i] = box->low.y; + highYs[i] = box->high.y; + } + + qsort(lowXs, in->nTuples, sizeof(float8), compareDoubles); + qsort(highXs, in->nTuples, sizeof(float8), compareDoubles); + qsort(lowYs, in->nTuples, sizeof(float8), compareDoubles); + qsort(highYs, in->nTuples, sizeof(float8), compareDoubles); + + median = in->nTuples / 2; + + centroid = palloc(sizeof(BOX)); + + centroid->low.x = lowXs[median]; + centroid->high.x = highXs[median]; + centroid->low.y = lowYs[median]; + centroid->high.y = highYs[median]; + + /* Fill the output */ + out->hasPrefix = true; + out->prefixDatum = BoxPGetDatum(centroid); + + out->nNodes = 16; + out->nodeLabels = NULL; /* We don't need node labels. */ + + out->mapTuplesToNodes = palloc(sizeof(int) * in->nTuples); + out->leafTupleDatums = palloc(sizeof(Datum) * in->nTuples); + + /* + * Assign ranges to corresponding nodes according to quadrants relative to + * the "centroid" range + */ + for (i = 0; i < in->nTuples; i++) + { + BOX *box = DatumGetBoxP(in->datums[i]); + uint8 quadrant = getQuadrant(centroid, box); + + out->leafTupleDatums[i] = BoxPGetDatum(box); + out->mapTuplesToNodes[i] = quadrant; + } + + PG_RETURN_VOID(); +} + +/* + * Check if result of consistent method based on bounding box is exact. + */ +static bool +is_bounding_box_test_exact(StrategyNumber strategy) +{ + switch (strategy) + { + case RTLeftStrategyNumber: + case RTOverLeftStrategyNumber: + case RTOverRightStrategyNumber: + case RTRightStrategyNumber: + case RTOverBelowStrategyNumber: + case RTBelowStrategyNumber: + case RTAboveStrategyNumber: + case RTOverAboveStrategyNumber: + return true; + + default: + return false; + } +} + +/* + * Get bounding box for ScanKey. + */ +static BOX * +spg_box_quad_get_scankey_bbox(ScanKey sk, bool *recheck) +{ + switch (sk->sk_subtype) + { + case BOXOID: + return DatumGetBoxP(sk->sk_argument); + + case POLYGONOID: + if (recheck && !is_bounding_box_test_exact(sk->sk_strategy)) + *recheck = true; + return &DatumGetPolygonP(sk->sk_argument)->boundbox; + + default: + elog(ERROR, "unrecognized scankey subtype: %d", sk->sk_subtype); + return NULL; + } +} + +/* + * SP-GiST inner consistent function + */ +Datum +spg_box_quad_inner_consistent(PG_FUNCTION_ARGS) +{ + spgInnerConsistentIn *in = (spgInnerConsistentIn *) PG_GETARG_POINTER(0); + spgInnerConsistentOut *out = (spgInnerConsistentOut *) PG_GETARG_POINTER(1); + int i; + MemoryContext old_ctx; + RectBox *rect_box; + uint8 quadrant; + RangeBox *centroid, + **queries; + + /* + * We are saving the traversal value or initialize it an unbounded one, if + * we have just begun to walk the tree. + */ + if (in->traversalValue) + rect_box = in->traversalValue; + else + rect_box = initRectBox(); + + if (in->allTheSame) + { + /* Report that all nodes should be visited */ + out->nNodes = in->nNodes; + out->nodeNumbers = (int *) palloc(sizeof(int) * in->nNodes); + for (i = 0; i < in->nNodes; i++) + out->nodeNumbers[i] = i; + + if (in->norderbys > 0 && in->nNodes > 0) + { + double *distances = palloc(sizeof(double) * in->norderbys); + int j; + + for (j = 0; j < in->norderbys; j++) + { + Point *pt = DatumGetPointP(in->orderbys[j].sk_argument); + + distances[j] = pointToRectBoxDistance(pt, rect_box); + } + + out->distances = (double **) palloc(sizeof(double *) * in->nNodes); + out->distances[0] = distances; + + for (i = 1; i < in->nNodes; i++) + { + out->distances[i] = palloc(sizeof(double) * in->norderbys); + memcpy(out->distances[i], distances, + sizeof(double) * in->norderbys); + } + } + + PG_RETURN_VOID(); + } + + /* + * We are casting the prefix and queries to RangeBoxes for ease of the + * following operations. + */ + centroid = getRangeBox(DatumGetBoxP(in->prefixDatum)); + queries = (RangeBox **) palloc(in->nkeys * sizeof(RangeBox *)); + for (i = 0; i < in->nkeys; i++) + { + BOX *box = spg_box_quad_get_scankey_bbox(&in->scankeys[i], NULL); + + queries[i] = getRangeBox(box); + } + + /* Allocate enough memory for nodes */ + out->nNodes = 0; + out->nodeNumbers = (int *) palloc(sizeof(int) * in->nNodes); + out->traversalValues = (void **) palloc(sizeof(void *) * in->nNodes); + if (in->norderbys > 0) + out->distances = (double **) palloc(sizeof(double *) * in->nNodes); + + /* + * We switch memory context, because we want to allocate memory for new + * traversal values (next_rect_box) and pass these pieces of memory to + * further call of this function. + */ + old_ctx = MemoryContextSwitchTo(in->traversalMemoryContext); + + for (quadrant = 0; quadrant < in->nNodes; quadrant++) + { + RectBox *next_rect_box = nextRectBox(rect_box, centroid, quadrant); + bool flag = true; + + for (i = 0; i < in->nkeys; i++) + { + StrategyNumber strategy = in->scankeys[i].sk_strategy; + + switch (strategy) + { + case RTOverlapStrategyNumber: + flag = overlap4D(next_rect_box, queries[i]); + break; + + case RTContainsStrategyNumber: + flag = contain4D(next_rect_box, queries[i]); + break; + + case RTSameStrategyNumber: + case RTContainedByStrategyNumber: + flag = contained4D(next_rect_box, queries[i]); + break; + + case RTLeftStrategyNumber: + flag = left4D(next_rect_box, queries[i]); + break; + + case RTOverLeftStrategyNumber: + flag = overLeft4D(next_rect_box, queries[i]); + break; + + case RTRightStrategyNumber: + flag = right4D(next_rect_box, queries[i]); + break; + + case RTOverRightStrategyNumber: + flag = overRight4D(next_rect_box, queries[i]); + break; + + case RTAboveStrategyNumber: + flag = above4D(next_rect_box, queries[i]); + break; + + case RTOverAboveStrategyNumber: + flag = overAbove4D(next_rect_box, queries[i]); + break; + + case RTBelowStrategyNumber: + flag = below4D(next_rect_box, queries[i]); + break; + + case RTOverBelowStrategyNumber: + flag = overBelow4D(next_rect_box, queries[i]); + break; + + default: + elog(ERROR, "unrecognized strategy: %d", strategy); + } + + /* If any check is failed, we have found our answer. */ + if (!flag) + break; + } + + if (flag) + { + out->traversalValues[out->nNodes] = next_rect_box; + out->nodeNumbers[out->nNodes] = quadrant; + + if (in->norderbys > 0) + { + double *distances = palloc(sizeof(double) * in->norderbys); + int j; + + out->distances[out->nNodes] = distances; + + for (j = 0; j < in->norderbys; j++) + { + Point *pt = DatumGetPointP(in->orderbys[j].sk_argument); + + distances[j] = pointToRectBoxDistance(pt, next_rect_box); + } + } + + out->nNodes++; + } + else + { + /* + * If this node is not selected, we don't need to keep the next + * traversal value in the memory context. + */ + pfree(next_rect_box); + } + } + + /* Switch back */ + MemoryContextSwitchTo(old_ctx); + + PG_RETURN_VOID(); +} + +/* + * SP-GiST inner consistent function + */ +Datum +spg_box_quad_leaf_consistent(PG_FUNCTION_ARGS) +{ + spgLeafConsistentIn *in = (spgLeafConsistentIn *) PG_GETARG_POINTER(0); + spgLeafConsistentOut *out = (spgLeafConsistentOut *) PG_GETARG_POINTER(1); + Datum leaf = in->leafDatum; + bool flag = true; + int i; + + /* All tests are exact. */ + out->recheck = false; + + /* + * Don't return leafValue unless told to; this is used for both box and + * polygon opclasses, and in the latter case the leaf datum is not even of + * the right type to return. + */ + if (in->returnData) + out->leafValue = leaf; + + /* Perform the required comparison(s) */ + for (i = 0; i < in->nkeys; i++) + { + StrategyNumber strategy = in->scankeys[i].sk_strategy; + BOX *box = spg_box_quad_get_scankey_bbox(&in->scankeys[i], + &out->recheck); + Datum query = BoxPGetDatum(box); + + switch (strategy) + { + case RTOverlapStrategyNumber: + flag = DatumGetBool(DirectFunctionCall2(box_overlap, leaf, + query)); + break; + + case RTContainsStrategyNumber: + flag = DatumGetBool(DirectFunctionCall2(box_contain, leaf, + query)); + break; + + case RTContainedByStrategyNumber: + flag = DatumGetBool(DirectFunctionCall2(box_contained, leaf, + query)); + break; + + case RTSameStrategyNumber: + flag = DatumGetBool(DirectFunctionCall2(box_same, leaf, + query)); + break; + + case RTLeftStrategyNumber: + flag = DatumGetBool(DirectFunctionCall2(box_left, leaf, + query)); + break; + + case RTOverLeftStrategyNumber: + flag = DatumGetBool(DirectFunctionCall2(box_overleft, leaf, + query)); + break; + + case RTRightStrategyNumber: + flag = DatumGetBool(DirectFunctionCall2(box_right, leaf, + query)); + break; + + case RTOverRightStrategyNumber: + flag = DatumGetBool(DirectFunctionCall2(box_overright, leaf, + query)); + break; + + case RTAboveStrategyNumber: + flag = DatumGetBool(DirectFunctionCall2(box_above, leaf, + query)); + break; + + case RTOverAboveStrategyNumber: + flag = DatumGetBool(DirectFunctionCall2(box_overabove, leaf, + query)); + break; + + case RTBelowStrategyNumber: + flag = DatumGetBool(DirectFunctionCall2(box_below, leaf, + query)); + break; + + case RTOverBelowStrategyNumber: + flag = DatumGetBool(DirectFunctionCall2(box_overbelow, leaf, + query)); + break; + + default: + elog(ERROR, "unrecognized strategy: %d", strategy); + } + + /* If any check is failed, we have found our answer. */ + if (!flag) + break; + } + + if (flag && in->norderbys > 0) + { + Oid distfnoid = in->orderbys[0].sk_func.fn_oid; + + out->distances = spg_key_orderbys_distances(leaf, false, + in->orderbys, in->norderbys); + + /* Recheck is necessary when computing distance to polygon */ + out->recheckDistances = distfnoid == F_DIST_POLYP; + } + + PG_RETURN_BOOL(flag); +} + + +/* + * SP-GiST config function for 2-D types that are lossy represented by their + * bounding boxes + */ +Datum +spg_bbox_quad_config(PG_FUNCTION_ARGS) +{ + spgConfigOut *cfg = (spgConfigOut *) PG_GETARG_POINTER(1); + + cfg->prefixType = BOXOID; /* A type represented by its bounding box */ + cfg->labelType = VOIDOID; /* We don't need node labels. */ + cfg->leafType = BOXOID; + cfg->canReturnData = false; + cfg->longValuesOK = false; + + PG_RETURN_VOID(); +} + +/* + * SP-GiST compress function for polygons + */ +Datum +spg_poly_quad_compress(PG_FUNCTION_ARGS) +{ + POLYGON *polygon = PG_GETARG_POLYGON_P(0); + BOX *box; + + box = (BOX *) palloc(sizeof(BOX)); + *box = polygon->boundbox; + + PG_RETURN_BOX_P(box); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/hbafuncs.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/hbafuncs.c new file mode 100644 index 00000000000..73d3ad1dadc --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/hbafuncs.c @@ -0,0 +1,588 @@ +/*------------------------------------------------------------------------- + * + * hbafuncs.c + * Support functions for SQL views of authentication files. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/adt/hbafuncs.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "catalog/objectaddress.h" +#include "common/ip.h" +#include "funcapi.h" +#include "libpq/hba.h" +#include "miscadmin.h" +#include "utils/array.h" +#include "utils/builtins.h" +#include "utils/guc.h" + + +static ArrayType *get_hba_options(HbaLine *hba); +static void fill_hba_line(Tuplestorestate *tuple_store, TupleDesc tupdesc, + int rule_number, char *filename, int lineno, + HbaLine *hba, const char *err_msg); +static void fill_hba_view(Tuplestorestate *tuple_store, TupleDesc tupdesc); +static void fill_ident_line(Tuplestorestate *tuple_store, TupleDesc tupdesc, + int map_number, char *filename, int lineno, + IdentLine *ident, const char *err_msg); +static void fill_ident_view(Tuplestorestate *tuple_store, TupleDesc tupdesc); + + +/* + * This macro specifies the maximum number of authentication options + * that are possible with any given authentication method that is supported. + * Currently LDAP supports 11, and there are 3 that are not dependent on + * the auth method here. It may not actually be possible to set all of them + * at the same time, but we'll set the macro value high enough to be + * conservative and avoid warnings from static analysis tools. + */ +#define MAX_HBA_OPTIONS 14 + +/* + * Create a text array listing the options specified in the HBA line. + * Return NULL if no options are specified. + */ +static ArrayType * +get_hba_options(HbaLine *hba) +{ + int noptions; + Datum options[MAX_HBA_OPTIONS]; + + noptions = 0; + + if (hba->auth_method == uaGSS || hba->auth_method == uaSSPI) + { + if (hba->include_realm) + options[noptions++] = + CStringGetTextDatum("include_realm=true"); + + if (hba->krb_realm) + options[noptions++] = + CStringGetTextDatum(psprintf("krb_realm=%s", hba->krb_realm)); + } + + if (hba->usermap) + options[noptions++] = + CStringGetTextDatum(psprintf("map=%s", hba->usermap)); + + if (hba->clientcert != clientCertOff) + options[noptions++] = + CStringGetTextDatum(psprintf("clientcert=%s", (hba->clientcert == clientCertCA) ? "verify-ca" : "verify-full")); + + if (hba->pamservice) + options[noptions++] = + CStringGetTextDatum(psprintf("pamservice=%s", hba->pamservice)); + + if (hba->auth_method == uaLDAP) + { + if (hba->ldapserver) + options[noptions++] = + CStringGetTextDatum(psprintf("ldapserver=%s", hba->ldapserver)); + + if (hba->ldapport) + options[noptions++] = + CStringGetTextDatum(psprintf("ldapport=%d", hba->ldapport)); + + if (hba->ldaptls) + options[noptions++] = + CStringGetTextDatum("ldaptls=true"); + + if (hba->ldapprefix) + options[noptions++] = + CStringGetTextDatum(psprintf("ldapprefix=%s", hba->ldapprefix)); + + if (hba->ldapsuffix) + options[noptions++] = + CStringGetTextDatum(psprintf("ldapsuffix=%s", hba->ldapsuffix)); + + if (hba->ldapbasedn) + options[noptions++] = + CStringGetTextDatum(psprintf("ldapbasedn=%s", hba->ldapbasedn)); + + if (hba->ldapbinddn) + options[noptions++] = + CStringGetTextDatum(psprintf("ldapbinddn=%s", hba->ldapbinddn)); + + if (hba->ldapbindpasswd) + options[noptions++] = + CStringGetTextDatum(psprintf("ldapbindpasswd=%s", + hba->ldapbindpasswd)); + + if (hba->ldapsearchattribute) + options[noptions++] = + CStringGetTextDatum(psprintf("ldapsearchattribute=%s", + hba->ldapsearchattribute)); + + if (hba->ldapsearchfilter) + options[noptions++] = + CStringGetTextDatum(psprintf("ldapsearchfilter=%s", + hba->ldapsearchfilter)); + + if (hba->ldapscope) + options[noptions++] = + CStringGetTextDatum(psprintf("ldapscope=%d", hba->ldapscope)); + } + + if (hba->auth_method == uaRADIUS) + { + if (hba->radiusservers_s) + options[noptions++] = + CStringGetTextDatum(psprintf("radiusservers=%s", hba->radiusservers_s)); + + if (hba->radiussecrets_s) + options[noptions++] = + CStringGetTextDatum(psprintf("radiussecrets=%s", hba->radiussecrets_s)); + + if (hba->radiusidentifiers_s) + options[noptions++] = + CStringGetTextDatum(psprintf("radiusidentifiers=%s", hba->radiusidentifiers_s)); + + if (hba->radiusports_s) + options[noptions++] = + CStringGetTextDatum(psprintf("radiusports=%s", hba->radiusports_s)); + } + + /* If you add more options, consider increasing MAX_HBA_OPTIONS. */ + Assert(noptions <= MAX_HBA_OPTIONS); + + if (noptions > 0) + return construct_array_builtin(options, noptions, TEXTOID); + else + return NULL; +} + +/* Number of columns in pg_hba_file_rules view */ +#define NUM_PG_HBA_FILE_RULES_ATTS 11 + +/* + * fill_hba_line + * Build one row of pg_hba_file_rules view, add it to tuplestore. + * + * tuple_store: where to store data + * tupdesc: tuple descriptor for the view + * rule_number: unique identifier among all valid rules + * filename: configuration file name (must always be valid) + * lineno: line number of configuration file (must always be valid) + * hba: parsed line data (can be NULL, in which case err_msg should be set) + * err_msg: error message (NULL if none) + * + * Note: leaks memory, but we don't care since this is run in a short-lived + * memory context. + */ +static void +fill_hba_line(Tuplestorestate *tuple_store, TupleDesc tupdesc, + int rule_number, char *filename, int lineno, HbaLine *hba, + const char *err_msg) +{ + Datum values[NUM_PG_HBA_FILE_RULES_ATTS]; + bool nulls[NUM_PG_HBA_FILE_RULES_ATTS]; + char buffer[NI_MAXHOST]; + HeapTuple tuple; + int index; + ListCell *lc; + const char *typestr; + const char *addrstr; + const char *maskstr; + ArrayType *options; + + Assert(tupdesc->natts == NUM_PG_HBA_FILE_RULES_ATTS); + + memset(values, 0, sizeof(values)); + memset(nulls, 0, sizeof(nulls)); + index = 0; + + /* rule_number, nothing on error */ + if (err_msg) + nulls[index++] = true; + else + values[index++] = Int32GetDatum(rule_number); + + /* file_name */ + values[index++] = CStringGetTextDatum(filename); + + /* line_number */ + values[index++] = Int32GetDatum(lineno); + + if (hba != NULL) + { + /* type */ + /* Avoid a default: case so compiler will warn about missing cases */ + typestr = NULL; + switch (hba->conntype) + { + case ctLocal: + typestr = "local"; + break; + case ctHost: + typestr = "host"; + break; + case ctHostSSL: + typestr = "hostssl"; + break; + case ctHostNoSSL: + typestr = "hostnossl"; + break; + case ctHostGSS: + typestr = "hostgssenc"; + break; + case ctHostNoGSS: + typestr = "hostnogssenc"; + break; + } + if (typestr) + values[index++] = CStringGetTextDatum(typestr); + else + nulls[index++] = true; + + /* database */ + if (hba->databases) + { + /* + * Flatten AuthToken list to string list. It might seem that we + * should re-quote any quoted tokens, but that has been rejected + * on the grounds that it makes it harder to compare the array + * elements to other system catalogs. That makes entries like + * "all" or "samerole" formally ambiguous ... but users who name + * databases/roles that way are inflicting their own pain. + */ + List *names = NIL; + + foreach(lc, hba->databases) + { + AuthToken *tok = lfirst(lc); + + names = lappend(names, tok->string); + } + values[index++] = PointerGetDatum(strlist_to_textarray(names)); + } + else + nulls[index++] = true; + + /* user */ + if (hba->roles) + { + /* Flatten AuthToken list to string list; see comment above */ + List *roles = NIL; + + foreach(lc, hba->roles) + { + AuthToken *tok = lfirst(lc); + + roles = lappend(roles, tok->string); + } + values[index++] = PointerGetDatum(strlist_to_textarray(roles)); + } + else + nulls[index++] = true; + + /* address and netmask */ + /* Avoid a default: case so compiler will warn about missing cases */ + addrstr = maskstr = NULL; + switch (hba->ip_cmp_method) + { + case ipCmpMask: + if (hba->hostname) + { + addrstr = hba->hostname; + } + else + { + /* + * Note: if pg_getnameinfo_all fails, it'll set buffer to + * "???", which we want to return. + */ + if (hba->addrlen > 0) + { + if (pg_getnameinfo_all(&hba->addr, hba->addrlen, + buffer, sizeof(buffer), + NULL, 0, + NI_NUMERICHOST) == 0) + clean_ipv6_addr(hba->addr.ss_family, buffer); + addrstr = pstrdup(buffer); + } + if (hba->masklen > 0) + { + if (pg_getnameinfo_all(&hba->mask, hba->masklen, + buffer, sizeof(buffer), + NULL, 0, + NI_NUMERICHOST) == 0) + clean_ipv6_addr(hba->mask.ss_family, buffer); + maskstr = pstrdup(buffer); + } + } + break; + case ipCmpAll: + addrstr = "all"; + break; + case ipCmpSameHost: + addrstr = "samehost"; + break; + case ipCmpSameNet: + addrstr = "samenet"; + break; + } + if (addrstr) + values[index++] = CStringGetTextDatum(addrstr); + else + nulls[index++] = true; + if (maskstr) + values[index++] = CStringGetTextDatum(maskstr); + else + nulls[index++] = true; + + /* auth_method */ + values[index++] = CStringGetTextDatum(hba_authname(hba->auth_method)); + + /* options */ + options = get_hba_options(hba); + if (options) + values[index++] = PointerGetDatum(options); + else + nulls[index++] = true; + } + else + { + /* no parsing result, so set relevant fields to nulls */ + memset(&nulls[3], true, (NUM_PG_HBA_FILE_RULES_ATTS - 4) * sizeof(bool)); + } + + /* error */ + if (err_msg) + values[NUM_PG_HBA_FILE_RULES_ATTS - 1] = CStringGetTextDatum(err_msg); + else + nulls[NUM_PG_HBA_FILE_RULES_ATTS - 1] = true; + + tuple = heap_form_tuple(tupdesc, values, nulls); + tuplestore_puttuple(tuple_store, tuple); +} + +/* + * fill_hba_view + * Read the pg_hba.conf file and fill the tuplestore with view records. + */ +static void +fill_hba_view(Tuplestorestate *tuple_store, TupleDesc tupdesc) +{ + FILE *file; + List *hba_lines = NIL; + ListCell *line; + int rule_number = 0; + MemoryContext hbacxt; + MemoryContext oldcxt; + + /* + * In the unlikely event that we can't open pg_hba.conf, we throw an + * error, rather than trying to report it via some sort of view entry. + * (Most other error conditions should result in a message in a view + * entry.) + */ + file = open_auth_file(HbaFileName, ERROR, 0, NULL); + + tokenize_auth_file(HbaFileName, file, &hba_lines, DEBUG3, 0); + + /* Now parse all the lines */ + hbacxt = AllocSetContextCreate(CurrentMemoryContext, + "hba parser context", + ALLOCSET_SMALL_SIZES); + oldcxt = MemoryContextSwitchTo(hbacxt); + foreach(line, hba_lines) + { + TokenizedAuthLine *tok_line = (TokenizedAuthLine *) lfirst(line); + HbaLine *hbaline = NULL; + + /* don't parse lines that already have errors */ + if (tok_line->err_msg == NULL) + hbaline = parse_hba_line(tok_line, DEBUG3); + + /* No error, set a new rule number */ + if (tok_line->err_msg == NULL) + rule_number++; + + fill_hba_line(tuple_store, tupdesc, rule_number, + tok_line->file_name, tok_line->line_num, hbaline, + tok_line->err_msg); + } + + /* Free tokenizer memory */ + free_auth_file(file, 0); + /* Free parse_hba_line memory */ + MemoryContextSwitchTo(oldcxt); + MemoryContextDelete(hbacxt); +} + +/* + * pg_hba_file_rules + * + * SQL-accessible set-returning function to return all the entries in the + * pg_hba.conf file. + */ +Datum +pg_hba_file_rules(PG_FUNCTION_ARGS) +{ + ReturnSetInfo *rsi; + + /* + * Build tuplestore to hold the result rows. We must use the Materialize + * mode to be safe against HBA file changes while the cursor is open. It's + * also more efficient than having to look up our current position in the + * parsed list every time. + */ + InitMaterializedSRF(fcinfo, 0); + + /* Fill the tuplestore */ + rsi = (ReturnSetInfo *) fcinfo->resultinfo; + fill_hba_view(rsi->setResult, rsi->setDesc); + + PG_RETURN_NULL(); +} + +/* Number of columns in pg_ident_file_mappings view */ +#define NUM_PG_IDENT_FILE_MAPPINGS_ATTS 7 + +/* + * fill_ident_line: build one row of pg_ident_file_mappings view, add it to + * tuplestore + * + * tuple_store: where to store data + * tupdesc: tuple descriptor for the view + * map_number: unique identifier among all valid maps + * filename: configuration file name (must always be valid) + * lineno: line number of configuration file (must always be valid) + * ident: parsed line data (can be NULL, in which case err_msg should be set) + * err_msg: error message (NULL if none) + * + * Note: leaks memory, but we don't care since this is run in a short-lived + * memory context. + */ +static void +fill_ident_line(Tuplestorestate *tuple_store, TupleDesc tupdesc, + int map_number, char *filename, int lineno, IdentLine *ident, + const char *err_msg) +{ + Datum values[NUM_PG_IDENT_FILE_MAPPINGS_ATTS]; + bool nulls[NUM_PG_IDENT_FILE_MAPPINGS_ATTS]; + HeapTuple tuple; + int index; + + Assert(tupdesc->natts == NUM_PG_IDENT_FILE_MAPPINGS_ATTS); + + memset(values, 0, sizeof(values)); + memset(nulls, 0, sizeof(nulls)); + index = 0; + + /* map_number, nothing on error */ + if (err_msg) + nulls[index++] = true; + else + values[index++] = Int32GetDatum(map_number); + + /* file_name */ + values[index++] = CStringGetTextDatum(filename); + + /* line_number */ + values[index++] = Int32GetDatum(lineno); + + if (ident != NULL) + { + values[index++] = CStringGetTextDatum(ident->usermap); + values[index++] = CStringGetTextDatum(ident->system_user->string); + values[index++] = CStringGetTextDatum(ident->pg_user->string); + } + else + { + /* no parsing result, so set relevant fields to nulls */ + memset(&nulls[3], true, (NUM_PG_IDENT_FILE_MAPPINGS_ATTS - 4) * sizeof(bool)); + } + + /* error */ + if (err_msg) + values[NUM_PG_IDENT_FILE_MAPPINGS_ATTS - 1] = CStringGetTextDatum(err_msg); + else + nulls[NUM_PG_IDENT_FILE_MAPPINGS_ATTS - 1] = true; + + tuple = heap_form_tuple(tupdesc, values, nulls); + tuplestore_puttuple(tuple_store, tuple); +} + +/* + * Read the pg_ident.conf file and fill the tuplestore with view records. + */ +static void +fill_ident_view(Tuplestorestate *tuple_store, TupleDesc tupdesc) +{ + FILE *file; + List *ident_lines = NIL; + ListCell *line; + int map_number = 0; + MemoryContext identcxt; + MemoryContext oldcxt; + + /* + * In the unlikely event that we can't open pg_ident.conf, we throw an + * error, rather than trying to report it via some sort of view entry. + * (Most other error conditions should result in a message in a view + * entry.) + */ + file = open_auth_file(IdentFileName, ERROR, 0, NULL); + + tokenize_auth_file(IdentFileName, file, &ident_lines, DEBUG3, 0); + + /* Now parse all the lines */ + identcxt = AllocSetContextCreate(CurrentMemoryContext, + "ident parser context", + ALLOCSET_SMALL_SIZES); + oldcxt = MemoryContextSwitchTo(identcxt); + foreach(line, ident_lines) + { + TokenizedAuthLine *tok_line = (TokenizedAuthLine *) lfirst(line); + IdentLine *identline = NULL; + + /* don't parse lines that already have errors */ + if (tok_line->err_msg == NULL) + identline = parse_ident_line(tok_line, DEBUG3); + + /* no error, set a new mapping number */ + if (tok_line->err_msg == NULL) + map_number++; + + fill_ident_line(tuple_store, tupdesc, map_number, + tok_line->file_name, tok_line->line_num, + identline, tok_line->err_msg); + } + + /* Free tokenizer memory */ + free_auth_file(file, 0); + /* Free parse_ident_line memory */ + MemoryContextSwitchTo(oldcxt); + MemoryContextDelete(identcxt); +} + +/* + * SQL-accessible SRF to return all the entries in the pg_ident.conf file. + */ +Datum +pg_ident_file_mappings(PG_FUNCTION_ARGS) +{ + ReturnSetInfo *rsi; + + /* + * Build tuplestore to hold the result rows. We must use the Materialize + * mode to be safe against HBA file changes while the cursor is open. It's + * also more efficient than having to look up our current position in the + * parsed list every time. + */ + InitMaterializedSRF(fcinfo, 0); + + /* Fill the tuplestore */ + rsi = (ReturnSetInfo *) fcinfo->resultinfo; + fill_ident_view(rsi->setResult, rsi->setDesc); + + PG_RETURN_NULL(); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/inet_cidr_ntop.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/inet_cidr_ntop.c new file mode 100644 index 00000000000..5f74c05a65d --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/inet_cidr_ntop.c @@ -0,0 +1,294 @@ +/* + * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1996,1999 by Internet Software Consortium. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * src/backend/utils/adt/inet_cidr_ntop.c + */ + +#if defined(LIBC_SCCS) && !defined(lint) +static const char rcsid[] = "Id: inet_net_ntop.c,v 1.1.2.2 2004/03/09 09:17:27 marka Exp $"; +#endif + +#include "postgres.h" + +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#include "utils/builtins.h" +#include "utils/inet.h" + + +#ifdef SPRINTF_CHAR +#define SPRINTF(x) strlen(sprintf/**/x) +#else +#define SPRINTF(x) ((size_t)sprintf x) +#endif + +static char *inet_cidr_ntop_ipv4(const u_char *src, int bits, + char *dst, size_t size); +static char *inet_cidr_ntop_ipv6(const u_char *src, int bits, + char *dst, size_t size); + +/* + * char * + * pg_inet_cidr_ntop(af, src, bits, dst, size) + * convert network number from network to presentation format. + * generates CIDR style result always. + * return: + * pointer to dst, or NULL if an error occurred (check errno). + * author: + * Paul Vixie (ISC), July 1996 + */ +char * +pg_inet_cidr_ntop(int af, const void *src, int bits, char *dst, size_t size) +{ + switch (af) + { + case PGSQL_AF_INET: + return inet_cidr_ntop_ipv4(src, bits, dst, size); + case PGSQL_AF_INET6: + return inet_cidr_ntop_ipv6(src, bits, dst, size); + default: + errno = EAFNOSUPPORT; + return NULL; + } +} + + +/* + * static char * + * inet_cidr_ntop_ipv4(src, bits, dst, size) + * convert IPv4 network number from network to presentation format. + * generates CIDR style result always. + * return: + * pointer to dst, or NULL if an error occurred (check errno). + * note: + * network byte order assumed. this means 192.5.5.240/28 has + * 0b11110000 in its fourth octet. + * author: + * Paul Vixie (ISC), July 1996 + */ +static char * +inet_cidr_ntop_ipv4(const u_char *src, int bits, char *dst, size_t size) +{ + char *odst = dst; + char *t; + u_int m; + int b; + + if (bits < 0 || bits > 32) + { + errno = EINVAL; + return NULL; + } + + if (bits == 0) + { + if (size < sizeof "0") + goto emsgsize; + *dst++ = '0'; + size--; + *dst = '\0'; + } + + /* Format whole octets. */ + for (b = bits / 8; b > 0; b--) + { + if (size <= sizeof "255.") + goto emsgsize; + t = dst; + dst += SPRINTF((dst, "%u", *src++)); + if (b > 1) + { + *dst++ = '.'; + *dst = '\0'; + } + size -= (size_t) (dst - t); + } + + /* Format partial octet. */ + b = bits % 8; + if (b > 0) + { + if (size <= sizeof ".255") + goto emsgsize; + t = dst; + if (dst != odst) + *dst++ = '.'; + m = ((1 << b) - 1) << (8 - b); + dst += SPRINTF((dst, "%u", *src & m)); + size -= (size_t) (dst - t); + } + + /* Format CIDR /width. */ + if (size <= sizeof "/32") + goto emsgsize; + dst += SPRINTF((dst, "/%u", bits)); + return odst; + +emsgsize: + errno = EMSGSIZE; + return NULL; +} + +/* + * static char * + * inet_cidr_ntop_ipv6(src, bits, dst, size) + * convert IPv6 network number from network to presentation format. + * generates CIDR style result always. Picks the shortest representation + * unless the IP is really IPv4. + * always prints specified number of bits (bits). + * return: + * pointer to dst, or NULL if an error occurred (check errno). + * note: + * network byte order assumed. this means 192.5.5.240/28 has + * 0x11110000 in its fourth octet. + * author: + * Vadim Kogan (UCB), June 2001 + * Original version (IPv4) by Paul Vixie (ISC), July 1996 + */ + +static char * +inet_cidr_ntop_ipv6(const u_char *src, int bits, char *dst, size_t size) +{ + u_int m; + int b; + int p; + int zero_s, + zero_l, + tmp_zero_s, + tmp_zero_l; + int i; + int is_ipv4 = 0; + unsigned char inbuf[16]; + char outbuf[sizeof("xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:255.255.255.255/128")]; + char *cp; + int words; + u_char *s; + + if (bits < 0 || bits > 128) + { + errno = EINVAL; + return NULL; + } + + cp = outbuf; + + if (bits == 0) + { + *cp++ = ':'; + *cp++ = ':'; + *cp = '\0'; + } + else + { + /* Copy src to private buffer. Zero host part. */ + p = (bits + 7) / 8; + memcpy(inbuf, src, p); + memset(inbuf + p, 0, 16 - p); + b = bits % 8; + if (b != 0) + { + m = ((u_int) ~0) << (8 - b); + inbuf[p - 1] &= m; + } + + s = inbuf; + + /* how many words need to be displayed in output */ + words = (bits + 15) / 16; + if (words == 1) + words = 2; + + /* Find the longest substring of zero's */ + zero_s = zero_l = tmp_zero_s = tmp_zero_l = 0; + for (i = 0; i < (words * 2); i += 2) + { + if ((s[i] | s[i + 1]) == 0) + { + if (tmp_zero_l == 0) + tmp_zero_s = i / 2; + tmp_zero_l++; + } + else + { + if (tmp_zero_l && zero_l < tmp_zero_l) + { + zero_s = tmp_zero_s; + zero_l = tmp_zero_l; + tmp_zero_l = 0; + } + } + } + + if (tmp_zero_l && zero_l < tmp_zero_l) + { + zero_s = tmp_zero_s; + zero_l = tmp_zero_l; + } + + if (zero_l != words && zero_s == 0 && ((zero_l == 6) || + ((zero_l == 5 && s[10] == 0xff && s[11] == 0xff) || + ((zero_l == 7 && s[14] != 0 && s[15] != 1))))) + is_ipv4 = 1; + + /* Format whole words. */ + for (p = 0; p < words; p++) + { + if (zero_l != 0 && p >= zero_s && p < zero_s + zero_l) + { + /* Time to skip some zeros */ + if (p == zero_s) + *cp++ = ':'; + if (p == words - 1) + *cp++ = ':'; + s++; + s++; + continue; + } + + if (is_ipv4 && p > 5) + { + *cp++ = (p == 6) ? ':' : '.'; + cp += SPRINTF((cp, "%u", *s++)); + /* we can potentially drop the last octet */ + if (p != 7 || bits > 120) + { + *cp++ = '.'; + cp += SPRINTF((cp, "%u", *s++)); + } + } + else + { + if (cp != outbuf) + *cp++ = ':'; + cp += SPRINTF((cp, "%x", *s * 256 + s[1])); + s += 2; + } + } + } + /* Format CIDR /width. */ + (void) SPRINTF((cp, "/%u", bits)); + if (strlen(outbuf) + 1 > size) + goto emsgsize; + strcpy(dst, outbuf); + + return dst; + +emsgsize: + errno = EMSGSIZE; + return NULL; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/inet_net_pton.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/inet_net_pton.c new file mode 100644 index 00000000000..d3221a13139 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/inet_net_pton.c @@ -0,0 +1,564 @@ +/* + * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1996,1999 by Internet Software Consortium. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * src/backend/utils/adt/inet_net_pton.c + */ + +#if defined(LIBC_SCCS) && !defined(lint) +static const char rcsid[] = "Id: inet_net_pton.c,v 1.4.2.3 2004/03/17 00:40:11 marka Exp $"; +#endif + +#include "postgres.h" + +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <assert.h> +#include <ctype.h> + +#include "utils/builtins.h" /* pgrminclude ignore */ /* needed on some + * platforms */ +#include "utils/inet.h" + + +static int inet_net_pton_ipv4(const char *src, u_char *dst); +static int inet_cidr_pton_ipv4(const char *src, u_char *dst, size_t size); +static int inet_net_pton_ipv6(const char *src, u_char *dst); +static int inet_cidr_pton_ipv6(const char *src, u_char *dst, size_t size); + + +/* + * int + * pg_inet_net_pton(af, src, dst, size) + * convert network number from presentation to network format. + * accepts hex octets, hex strings, decimal octets, and /CIDR. + * "size" is in bytes and describes "dst". + * return: + * number of bits, either imputed classfully or specified with /CIDR, + * or -1 if some failure occurred (check errno). ENOENT means it was + * not a valid network specification. + * author: + * Paul Vixie (ISC), June 1996 + * + * Changes: + * I added the inet_cidr_pton function (also from Paul) and changed + * the names to reflect their current use. + * + */ +int +pg_inet_net_pton(int af, const char *src, void *dst, size_t size) +{ + switch (af) + { + case PGSQL_AF_INET: + return size == -1 ? + inet_net_pton_ipv4(src, dst) : + inet_cidr_pton_ipv4(src, dst, size); + case PGSQL_AF_INET6: + return size == -1 ? + inet_net_pton_ipv6(src, dst) : + inet_cidr_pton_ipv6(src, dst, size); + default: + errno = EAFNOSUPPORT; + return -1; + } +} + +/* + * static int + * inet_cidr_pton_ipv4(src, dst, size) + * convert IPv4 network number from presentation to network format. + * accepts hex octets, hex strings, decimal octets, and /CIDR. + * "size" is in bytes and describes "dst". + * return: + * number of bits, either imputed classfully or specified with /CIDR, + * or -1 if some failure occurred (check errno). ENOENT means it was + * not an IPv4 network specification. + * note: + * network byte order assumed. this means 192.5.5.240/28 has + * 0b11110000 in its fourth octet. + * author: + * Paul Vixie (ISC), June 1996 + */ +static int +inet_cidr_pton_ipv4(const char *src, u_char *dst, size_t size) +{ + static const char xdigits[] = "0123456789abcdef"; + static const char digits[] = "0123456789"; + int n, + ch, + tmp = 0, + dirty, + bits; + const u_char *odst = dst; + + ch = *src++; + if (ch == '0' && (src[0] == 'x' || src[0] == 'X') + && isxdigit((unsigned char) src[1])) + { + /* Hexadecimal: Eat nybble string. */ + if (size <= 0U) + goto emsgsize; + dirty = 0; + src++; /* skip x or X. */ + while ((ch = *src++) != '\0' && isxdigit((unsigned char) ch)) + { + if (isupper((unsigned char) ch)) + ch = tolower((unsigned char) ch); + n = strchr(xdigits, ch) - xdigits; + assert(n >= 0 && n <= 15); + if (dirty == 0) + tmp = n; + else + tmp = (tmp << 4) | n; + if (++dirty == 2) + { + if (size-- <= 0U) + goto emsgsize; + *dst++ = (u_char) tmp; + dirty = 0; + } + } + if (dirty) + { /* Odd trailing nybble? */ + if (size-- <= 0U) + goto emsgsize; + *dst++ = (u_char) (tmp << 4); + } + } + else if (isdigit((unsigned char) ch)) + { + /* Decimal: eat dotted digit string. */ + for (;;) + { + tmp = 0; + do + { + n = strchr(digits, ch) - digits; + assert(n >= 0 && n <= 9); + tmp *= 10; + tmp += n; + if (tmp > 255) + goto enoent; + } while ((ch = *src++) != '\0' && + isdigit((unsigned char) ch)); + if (size-- <= 0U) + goto emsgsize; + *dst++ = (u_char) tmp; + if (ch == '\0' || ch == '/') + break; + if (ch != '.') + goto enoent; + ch = *src++; + if (!isdigit((unsigned char) ch)) + goto enoent; + } + } + else + goto enoent; + + bits = -1; + if (ch == '/' && isdigit((unsigned char) src[0]) && dst > odst) + { + /* CIDR width specifier. Nothing can follow it. */ + ch = *src++; /* Skip over the /. */ + bits = 0; + do + { + n = strchr(digits, ch) - digits; + assert(n >= 0 && n <= 9); + bits *= 10; + bits += n; + } while ((ch = *src++) != '\0' && isdigit((unsigned char) ch)); + if (ch != '\0') + goto enoent; + if (bits > 32) + goto emsgsize; + } + + /* Fiery death and destruction unless we prefetched EOS. */ + if (ch != '\0') + goto enoent; + + /* If nothing was written to the destination, we found no address. */ + if (dst == odst) + goto enoent; + /* If no CIDR spec was given, infer width from net class. */ + if (bits == -1) + { + if (*odst >= 240) /* Class E */ + bits = 32; + else if (*odst >= 224) /* Class D */ + bits = 8; + else if (*odst >= 192) /* Class C */ + bits = 24; + else if (*odst >= 128) /* Class B */ + bits = 16; + else + /* Class A */ + bits = 8; + /* If imputed mask is narrower than specified octets, widen. */ + if (bits < ((dst - odst) * 8)) + bits = (dst - odst) * 8; + + /* + * If there are no additional bits specified for a class D address + * adjust bits to 4. + */ + if (bits == 8 && *odst == 224) + bits = 4; + } + /* Extend network to cover the actual mask. */ + while (bits > ((dst - odst) * 8)) + { + if (size-- <= 0U) + goto emsgsize; + *dst++ = '\0'; + } + return bits; + +enoent: + errno = ENOENT; + return -1; + +emsgsize: + errno = EMSGSIZE; + return -1; +} + +/* + * int + * inet_net_pton_ipv4(af, src, dst, *bits) + * convert network address from presentation to network format. + * accepts inet_pton()'s input for this "af" plus trailing "/CIDR". + * "dst" is assumed large enough for its "af". "bits" is set to the + * /CIDR prefix length, which can have defaults (like /32 for IPv4). + * return: + * -1 if an error occurred (inspect errno; ENOENT means bad format). + * 0 if successful conversion occurred. + * note: + * 192.5.5.1/28 has a nonzero host part, which means it isn't a network + * as called for by inet_cidr_pton() but it can be a host address with + * an included netmask. + * author: + * Paul Vixie (ISC), October 1998 + */ +static int +inet_net_pton_ipv4(const char *src, u_char *dst) +{ + static const char digits[] = "0123456789"; + const u_char *odst = dst; + int n, + ch, + tmp, + bits; + size_t size = 4; + + /* Get the mantissa. */ + while (ch = *src++, isdigit((unsigned char) ch)) + { + tmp = 0; + do + { + n = strchr(digits, ch) - digits; + assert(n >= 0 && n <= 9); + tmp *= 10; + tmp += n; + if (tmp > 255) + goto enoent; + } while ((ch = *src++) != '\0' && isdigit((unsigned char) ch)); + if (size-- == 0) + goto emsgsize; + *dst++ = (u_char) tmp; + if (ch == '\0' || ch == '/') + break; + if (ch != '.') + goto enoent; + } + + /* Get the prefix length if any. */ + bits = -1; + if (ch == '/' && isdigit((unsigned char) src[0]) && dst > odst) + { + /* CIDR width specifier. Nothing can follow it. */ + ch = *src++; /* Skip over the /. */ + bits = 0; + do + { + n = strchr(digits, ch) - digits; + assert(n >= 0 && n <= 9); + bits *= 10; + bits += n; + } while ((ch = *src++) != '\0' && isdigit((unsigned char) ch)); + if (ch != '\0') + goto enoent; + if (bits > 32) + goto emsgsize; + } + + /* Fiery death and destruction unless we prefetched EOS. */ + if (ch != '\0') + goto enoent; + + /* Prefix length can default to /32 only if all four octets spec'd. */ + if (bits == -1) + { + if (dst - odst == 4) + bits = 32; + else + goto enoent; + } + + /* If nothing was written to the destination, we found no address. */ + if (dst == odst) + goto enoent; + + /* If prefix length overspecifies mantissa, life is bad. */ + if ((bits / 8) > (dst - odst)) + goto enoent; + + /* Extend address to four octets. */ + while (size-- > 0) + *dst++ = 0; + + return bits; + +enoent: + errno = ENOENT; + return -1; + +emsgsize: + errno = EMSGSIZE; + return -1; +} + +static int +getbits(const char *src, int *bitsp) +{ + static const char digits[] = "0123456789"; + int n; + int val; + char ch; + + val = 0; + n = 0; + while ((ch = *src++) != '\0') + { + const char *pch; + + pch = strchr(digits, ch); + if (pch != NULL) + { + if (n++ != 0 && val == 0) /* no leading zeros */ + return 0; + val *= 10; + val += (pch - digits); + if (val > 128) /* range */ + return 0; + continue; + } + return 0; + } + if (n == 0) + return 0; + *bitsp = val; + return 1; +} + +static int +getv4(const char *src, u_char *dst, int *bitsp) +{ + static const char digits[] = "0123456789"; + u_char *odst = dst; + int n; + u_int val; + char ch; + + val = 0; + n = 0; + while ((ch = *src++) != '\0') + { + const char *pch; + + pch = strchr(digits, ch); + if (pch != NULL) + { + if (n++ != 0 && val == 0) /* no leading zeros */ + return 0; + val *= 10; + val += (pch - digits); + if (val > 255) /* range */ + return 0; + continue; + } + if (ch == '.' || ch == '/') + { + if (dst - odst > 3) /* too many octets? */ + return 0; + *dst++ = val; + if (ch == '/') + return getbits(src, bitsp); + val = 0; + n = 0; + continue; + } + return 0; + } + if (n == 0) + return 0; + if (dst - odst > 3) /* too many octets? */ + return 0; + *dst++ = val; + return 1; +} + +static int +inet_net_pton_ipv6(const char *src, u_char *dst) +{ + return inet_cidr_pton_ipv6(src, dst, 16); +} + +#define NS_IN6ADDRSZ 16 +#define NS_INT16SZ 2 +#define NS_INADDRSZ 4 + +static int +inet_cidr_pton_ipv6(const char *src, u_char *dst, size_t size) +{ + static const char xdigits_l[] = "0123456789abcdef", + xdigits_u[] = "0123456789ABCDEF"; + u_char tmp[NS_IN6ADDRSZ], + *tp, + *endp, + *colonp; + const char *xdigits, + *curtok; + int ch, + saw_xdigit; + u_int val; + int digits; + int bits; + + if (size < NS_IN6ADDRSZ) + goto emsgsize; + + memset((tp = tmp), '\0', NS_IN6ADDRSZ); + endp = tp + NS_IN6ADDRSZ; + colonp = NULL; + /* Leading :: requires some special handling. */ + if (*src == ':') + if (*++src != ':') + goto enoent; + curtok = src; + saw_xdigit = 0; + val = 0; + digits = 0; + bits = -1; + while ((ch = *src++) != '\0') + { + const char *pch; + + if ((pch = strchr((xdigits = xdigits_l), ch)) == NULL) + pch = strchr((xdigits = xdigits_u), ch); + if (pch != NULL) + { + val <<= 4; + val |= (pch - xdigits); + if (++digits > 4) + goto enoent; + saw_xdigit = 1; + continue; + } + if (ch == ':') + { + curtok = src; + if (!saw_xdigit) + { + if (colonp) + goto enoent; + colonp = tp; + continue; + } + else if (*src == '\0') + goto enoent; + if (tp + NS_INT16SZ > endp) + goto enoent; + *tp++ = (u_char) (val >> 8) & 0xff; + *tp++ = (u_char) val & 0xff; + saw_xdigit = 0; + digits = 0; + val = 0; + continue; + } + if (ch == '.' && ((tp + NS_INADDRSZ) <= endp) && + getv4(curtok, tp, &bits) > 0) + { + tp += NS_INADDRSZ; + saw_xdigit = 0; + break; /* '\0' was seen by inet_pton4(). */ + } + if (ch == '/' && getbits(src, &bits) > 0) + break; + goto enoent; + } + if (saw_xdigit) + { + if (tp + NS_INT16SZ > endp) + goto enoent; + *tp++ = (u_char) (val >> 8) & 0xff; + *tp++ = (u_char) val & 0xff; + } + if (bits == -1) + bits = 128; + + endp = tmp + 16; + + if (colonp != NULL) + { + /* + * Since some memmove()'s erroneously fail to handle overlapping + * regions, we'll do the shift by hand. + */ + const int n = tp - colonp; + int i; + + if (tp == endp) + goto enoent; + for (i = 1; i <= n; i++) + { + endp[-i] = colonp[n - i]; + colonp[n - i] = 0; + } + tp = endp; + } + if (tp != endp) + goto enoent; + + /* + * Copy out the result. + */ + memcpy(dst, tmp, NS_IN6ADDRSZ); + + return bits; + +enoent: + errno = ENOENT; + return -1; + +emsgsize: + errno = EMSGSIZE; + return -1; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/int.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/int.c new file mode 100644 index 00000000000..44d1c7ad0c4 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/int.c @@ -0,0 +1,1649 @@ +/*------------------------------------------------------------------------- + * + * int.c + * Functions for the built-in integer types (except int8). + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/adt/int.c + * + *------------------------------------------------------------------------- + */ +/* + * OLD COMMENTS + * I/O routines: + * int2in, int2out, int2recv, int2send + * int4in, int4out, int4recv, int4send + * int2vectorin, int2vectorout, int2vectorrecv, int2vectorsend + * Boolean operators: + * inteq, intne, intlt, intle, intgt, intge + * Arithmetic operators: + * intpl, intmi, int4mul, intdiv + * + * Arithmetic operators: + * intmod + */ +#include "postgres.h" + +#include <ctype.h> +#include <limits.h> +#include <math.h> + +#include "catalog/pg_type.h" +#include "common/int.h" +#include "funcapi.h" +#include "libpq/pqformat.h" +#include "nodes/nodeFuncs.h" +#include "nodes/supportnodes.h" +#include "optimizer/optimizer.h" +#include "utils/array.h" +#include "utils/builtins.h" + +#define Int2VectorSize(n) (offsetof(int2vector, values) + (n) * sizeof(int16)) + +typedef struct +{ + int32 current; + int32 finish; + int32 step; +} generate_series_fctx; + + +/***************************************************************************** + * USER I/O ROUTINES * + *****************************************************************************/ + +/* + * int2in - converts "num" to short + */ +Datum +int2in(PG_FUNCTION_ARGS) +{ + char *num = PG_GETARG_CSTRING(0); + + PG_RETURN_INT16(pg_strtoint16_safe(num, fcinfo->context)); +} + +/* + * int2out - converts short to "num" + */ +Datum +int2out(PG_FUNCTION_ARGS) +{ + int16 arg1 = PG_GETARG_INT16(0); + char *result = (char *) palloc(7); /* sign, 5 digits, '\0' */ + + pg_itoa(arg1, result); + PG_RETURN_CSTRING(result); +} + +/* + * int2recv - converts external binary format to int2 + */ +Datum +int2recv(PG_FUNCTION_ARGS) +{ + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + + PG_RETURN_INT16((int16) pq_getmsgint(buf, sizeof(int16))); +} + +/* + * int2send - converts int2 to binary format + */ +Datum +int2send(PG_FUNCTION_ARGS) +{ + int16 arg1 = PG_GETARG_INT16(0); + StringInfoData buf; + + pq_begintypsend(&buf); + pq_sendint16(&buf, arg1); + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} + +/* + * construct int2vector given a raw array of int2s + * + * If int2s is NULL then caller must fill values[] afterward + */ +int2vector * +buildint2vector(const int16 *int2s, int n) +{ + int2vector *result; + + result = (int2vector *) palloc0(Int2VectorSize(n)); + + if (n > 0 && int2s) + memcpy(result->values, int2s, n * sizeof(int16)); + + /* + * Attach standard array header. For historical reasons, we set the index + * lower bound to 0 not 1. + */ + SET_VARSIZE(result, Int2VectorSize(n)); + result->ndim = 1; + result->dataoffset = 0; /* never any nulls */ + result->elemtype = INT2OID; + result->dim1 = n; + result->lbound1 = 0; + + return result; +} + +/* + * int2vectorin - converts "num num ..." to internal form + */ +Datum +int2vectorin(PG_FUNCTION_ARGS) +{ + char *intString = PG_GETARG_CSTRING(0); + Node *escontext = fcinfo->context; + int2vector *result; + int nalloc; + int n; + + nalloc = 32; /* arbitrary initial size guess */ + result = (int2vector *) palloc0(Int2VectorSize(nalloc)); + + for (n = 0;; n++) + { + long l; + char *endp; + + while (*intString && isspace((unsigned char) *intString)) + intString++; + if (*intString == '\0') + break; + + if (n >= nalloc) + { + nalloc *= 2; + result = (int2vector *) repalloc(result, Int2VectorSize(nalloc)); + } + + errno = 0; + l = strtol(intString, &endp, 10); + + if (intString == endp) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s: \"%s\"", + "smallint", intString))); + + if (errno == ERANGE || l < SHRT_MIN || l > SHRT_MAX) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("value \"%s\" is out of range for type %s", intString, + "smallint"))); + + if (*endp && *endp != ' ') + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s: \"%s\"", + "smallint", intString))); + + result->values[n] = l; + intString = endp; + } + + SET_VARSIZE(result, Int2VectorSize(n)); + result->ndim = 1; + result->dataoffset = 0; /* never any nulls */ + result->elemtype = INT2OID; + result->dim1 = n; + result->lbound1 = 0; + + PG_RETURN_POINTER(result); +} + +/* + * int2vectorout - converts internal form to "num num ..." + */ +Datum +int2vectorout(PG_FUNCTION_ARGS) +{ + int2vector *int2Array = (int2vector *) PG_GETARG_POINTER(0); + int num, + nnums = int2Array->dim1; + char *rp; + char *result; + + /* assumes sign, 5 digits, ' ' */ + rp = result = (char *) palloc(nnums * 7 + 1); + for (num = 0; num < nnums; num++) + { + if (num != 0) + *rp++ = ' '; + rp += pg_itoa(int2Array->values[num], rp); + } + *rp = '\0'; + PG_RETURN_CSTRING(result); +} + +/* + * int2vectorrecv - converts external binary format to int2vector + */ +Datum +int2vectorrecv(PG_FUNCTION_ARGS) +{ + LOCAL_FCINFO(locfcinfo, 3); + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + int2vector *result; + + /* + * Normally one would call array_recv() using DirectFunctionCall3, but + * that does not work since array_recv wants to cache some data using + * fcinfo->flinfo->fn_extra. So we need to pass it our own flinfo + * parameter. + */ + InitFunctionCallInfoData(*locfcinfo, fcinfo->flinfo, 3, + InvalidOid, NULL, NULL); + + locfcinfo->args[0].value = PointerGetDatum(buf); + locfcinfo->args[0].isnull = false; + locfcinfo->args[1].value = ObjectIdGetDatum(INT2OID); + locfcinfo->args[1].isnull = false; + locfcinfo->args[2].value = Int32GetDatum(-1); + locfcinfo->args[2].isnull = false; + + result = (int2vector *) DatumGetPointer(array_recv(locfcinfo)); + + Assert(!locfcinfo->isnull); + + /* sanity checks: int2vector must be 1-D, 0-based, no nulls */ + if (ARR_NDIM(result) != 1 || + ARR_HASNULL(result) || + ARR_ELEMTYPE(result) != INT2OID || + ARR_LBOUND(result)[0] != 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION), + errmsg("invalid int2vector data"))); + + PG_RETURN_POINTER(result); +} + +/* + * int2vectorsend - converts int2vector to binary format + */ +Datum +int2vectorsend(PG_FUNCTION_ARGS) +{ + return array_send(fcinfo); +} + + +/***************************************************************************** + * PUBLIC ROUTINES * + *****************************************************************************/ + +/* + * int4in - converts "num" to int4 + */ +Datum +int4in(PG_FUNCTION_ARGS) +{ + char *num = PG_GETARG_CSTRING(0); + + PG_RETURN_INT32(pg_strtoint32_safe(num, fcinfo->context)); +} + +/* + * int4out - converts int4 to "num" + */ +Datum +int4out(PG_FUNCTION_ARGS) +{ + int32 arg1 = PG_GETARG_INT32(0); + char *result = (char *) palloc(12); /* sign, 10 digits, '\0' */ + + pg_ltoa(arg1, result); + PG_RETURN_CSTRING(result); +} + +/* + * int4recv - converts external binary format to int4 + */ +Datum +int4recv(PG_FUNCTION_ARGS) +{ + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + + PG_RETURN_INT32((int32) pq_getmsgint(buf, sizeof(int32))); +} + +/* + * int4send - converts int4 to binary format + */ +Datum +int4send(PG_FUNCTION_ARGS) +{ + int32 arg1 = PG_GETARG_INT32(0); + StringInfoData buf; + + pq_begintypsend(&buf); + pq_sendint32(&buf, arg1); + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} + + +/* + * =================== + * CONVERSION ROUTINES + * =================== + */ + +Datum +i2toi4(PG_FUNCTION_ARGS) +{ + int16 arg1 = PG_GETARG_INT16(0); + + PG_RETURN_INT32((int32) arg1); +} + +Datum +i4toi2(PG_FUNCTION_ARGS) +{ + int32 arg1 = PG_GETARG_INT32(0); + + if (unlikely(arg1 < SHRT_MIN) || unlikely(arg1 > SHRT_MAX)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("smallint out of range"))); + + PG_RETURN_INT16((int16) arg1); +} + +/* Cast int4 -> bool */ +Datum +int4_bool(PG_FUNCTION_ARGS) +{ + if (PG_GETARG_INT32(0) == 0) + PG_RETURN_BOOL(false); + else + PG_RETURN_BOOL(true); +} + +/* Cast bool -> int4 */ +Datum +bool_int4(PG_FUNCTION_ARGS) +{ + if (PG_GETARG_BOOL(0) == false) + PG_RETURN_INT32(0); + else + PG_RETURN_INT32(1); +} + +/* + * ============================ + * COMPARISON OPERATOR ROUTINES + * ============================ + */ + +/* + * inteq - returns 1 iff arg1 == arg2 + * intne - returns 1 iff arg1 != arg2 + * intlt - returns 1 iff arg1 < arg2 + * intle - returns 1 iff arg1 <= arg2 + * intgt - returns 1 iff arg1 > arg2 + * intge - returns 1 iff arg1 >= arg2 + */ + +Datum +int4eq(PG_FUNCTION_ARGS) +{ + int32 arg1 = PG_GETARG_INT32(0); + int32 arg2 = PG_GETARG_INT32(1); + + PG_RETURN_BOOL(arg1 == arg2); +} + +Datum +int4ne(PG_FUNCTION_ARGS) +{ + int32 arg1 = PG_GETARG_INT32(0); + int32 arg2 = PG_GETARG_INT32(1); + + PG_RETURN_BOOL(arg1 != arg2); +} + +Datum +int4lt(PG_FUNCTION_ARGS) +{ + int32 arg1 = PG_GETARG_INT32(0); + int32 arg2 = PG_GETARG_INT32(1); + + PG_RETURN_BOOL(arg1 < arg2); +} + +Datum +int4le(PG_FUNCTION_ARGS) +{ + int32 arg1 = PG_GETARG_INT32(0); + int32 arg2 = PG_GETARG_INT32(1); + + PG_RETURN_BOOL(arg1 <= arg2); +} + +Datum +int4gt(PG_FUNCTION_ARGS) +{ + int32 arg1 = PG_GETARG_INT32(0); + int32 arg2 = PG_GETARG_INT32(1); + + PG_RETURN_BOOL(arg1 > arg2); +} + +Datum +int4ge(PG_FUNCTION_ARGS) +{ + int32 arg1 = PG_GETARG_INT32(0); + int32 arg2 = PG_GETARG_INT32(1); + + PG_RETURN_BOOL(arg1 >= arg2); +} + +Datum +int2eq(PG_FUNCTION_ARGS) +{ + int16 arg1 = PG_GETARG_INT16(0); + int16 arg2 = PG_GETARG_INT16(1); + + PG_RETURN_BOOL(arg1 == arg2); +} + +Datum +int2ne(PG_FUNCTION_ARGS) +{ + int16 arg1 = PG_GETARG_INT16(0); + int16 arg2 = PG_GETARG_INT16(1); + + PG_RETURN_BOOL(arg1 != arg2); +} + +Datum +int2lt(PG_FUNCTION_ARGS) +{ + int16 arg1 = PG_GETARG_INT16(0); + int16 arg2 = PG_GETARG_INT16(1); + + PG_RETURN_BOOL(arg1 < arg2); +} + +Datum +int2le(PG_FUNCTION_ARGS) +{ + int16 arg1 = PG_GETARG_INT16(0); + int16 arg2 = PG_GETARG_INT16(1); + + PG_RETURN_BOOL(arg1 <= arg2); +} + +Datum +int2gt(PG_FUNCTION_ARGS) +{ + int16 arg1 = PG_GETARG_INT16(0); + int16 arg2 = PG_GETARG_INT16(1); + + PG_RETURN_BOOL(arg1 > arg2); +} + +Datum +int2ge(PG_FUNCTION_ARGS) +{ + int16 arg1 = PG_GETARG_INT16(0); + int16 arg2 = PG_GETARG_INT16(1); + + PG_RETURN_BOOL(arg1 >= arg2); +} + +Datum +int24eq(PG_FUNCTION_ARGS) +{ + int16 arg1 = PG_GETARG_INT16(0); + int32 arg2 = PG_GETARG_INT32(1); + + PG_RETURN_BOOL(arg1 == arg2); +} + +Datum +int24ne(PG_FUNCTION_ARGS) +{ + int16 arg1 = PG_GETARG_INT16(0); + int32 arg2 = PG_GETARG_INT32(1); + + PG_RETURN_BOOL(arg1 != arg2); +} + +Datum +int24lt(PG_FUNCTION_ARGS) +{ + int16 arg1 = PG_GETARG_INT16(0); + int32 arg2 = PG_GETARG_INT32(1); + + PG_RETURN_BOOL(arg1 < arg2); +} + +Datum +int24le(PG_FUNCTION_ARGS) +{ + int16 arg1 = PG_GETARG_INT16(0); + int32 arg2 = PG_GETARG_INT32(1); + + PG_RETURN_BOOL(arg1 <= arg2); +} + +Datum +int24gt(PG_FUNCTION_ARGS) +{ + int16 arg1 = PG_GETARG_INT16(0); + int32 arg2 = PG_GETARG_INT32(1); + + PG_RETURN_BOOL(arg1 > arg2); +} + +Datum +int24ge(PG_FUNCTION_ARGS) +{ + int16 arg1 = PG_GETARG_INT16(0); + int32 arg2 = PG_GETARG_INT32(1); + + PG_RETURN_BOOL(arg1 >= arg2); +} + +Datum +int42eq(PG_FUNCTION_ARGS) +{ + int32 arg1 = PG_GETARG_INT32(0); + int16 arg2 = PG_GETARG_INT16(1); + + PG_RETURN_BOOL(arg1 == arg2); +} + +Datum +int42ne(PG_FUNCTION_ARGS) +{ + int32 arg1 = PG_GETARG_INT32(0); + int16 arg2 = PG_GETARG_INT16(1); + + PG_RETURN_BOOL(arg1 != arg2); +} + +Datum +int42lt(PG_FUNCTION_ARGS) +{ + int32 arg1 = PG_GETARG_INT32(0); + int16 arg2 = PG_GETARG_INT16(1); + + PG_RETURN_BOOL(arg1 < arg2); +} + +Datum +int42le(PG_FUNCTION_ARGS) +{ + int32 arg1 = PG_GETARG_INT32(0); + int16 arg2 = PG_GETARG_INT16(1); + + PG_RETURN_BOOL(arg1 <= arg2); +} + +Datum +int42gt(PG_FUNCTION_ARGS) +{ + int32 arg1 = PG_GETARG_INT32(0); + int16 arg2 = PG_GETARG_INT16(1); + + PG_RETURN_BOOL(arg1 > arg2); +} + +Datum +int42ge(PG_FUNCTION_ARGS) +{ + int32 arg1 = PG_GETARG_INT32(0); + int16 arg2 = PG_GETARG_INT16(1); + + PG_RETURN_BOOL(arg1 >= arg2); +} + + +/*---------------------------------------------------------- + * in_range functions for int4 and int2, + * including cross-data-type comparisons. + * + * Note: we provide separate intN_int8 functions for performance + * reasons. This forces also providing intN_int2, else cases with a + * smallint offset value would fail to resolve which function to use. + * But that's an unlikely situation, so don't duplicate code for it. + *---------------------------------------------------------*/ + +Datum +in_range_int4_int4(PG_FUNCTION_ARGS) +{ + int32 val = PG_GETARG_INT32(0); + int32 base = PG_GETARG_INT32(1); + int32 offset = PG_GETARG_INT32(2); + bool sub = PG_GETARG_BOOL(3); + bool less = PG_GETARG_BOOL(4); + int32 sum; + + if (offset < 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE), + errmsg("invalid preceding or following size in window function"))); + + if (sub) + offset = -offset; /* cannot overflow */ + + if (unlikely(pg_add_s32_overflow(base, offset, &sum))) + { + /* + * If sub is false, the true sum is surely more than val, so correct + * answer is the same as "less". If sub is true, the true sum is + * surely less than val, so the answer is "!less". + */ + PG_RETURN_BOOL(sub ? !less : less); + } + + if (less) + PG_RETURN_BOOL(val <= sum); + else + PG_RETURN_BOOL(val >= sum); +} + +Datum +in_range_int4_int2(PG_FUNCTION_ARGS) +{ + /* Doesn't seem worth duplicating code for, so just invoke int4_int4 */ + return DirectFunctionCall5(in_range_int4_int4, + PG_GETARG_DATUM(0), + PG_GETARG_DATUM(1), + Int32GetDatum((int32) PG_GETARG_INT16(2)), + PG_GETARG_DATUM(3), + PG_GETARG_DATUM(4)); +} + +Datum +in_range_int4_int8(PG_FUNCTION_ARGS) +{ + /* We must do all the math in int64 */ + int64 val = (int64) PG_GETARG_INT32(0); + int64 base = (int64) PG_GETARG_INT32(1); + int64 offset = PG_GETARG_INT64(2); + bool sub = PG_GETARG_BOOL(3); + bool less = PG_GETARG_BOOL(4); + int64 sum; + + if (offset < 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE), + errmsg("invalid preceding or following size in window function"))); + + if (sub) + offset = -offset; /* cannot overflow */ + + if (unlikely(pg_add_s64_overflow(base, offset, &sum))) + { + /* + * If sub is false, the true sum is surely more than val, so correct + * answer is the same as "less". If sub is true, the true sum is + * surely less than val, so the answer is "!less". + */ + PG_RETURN_BOOL(sub ? !less : less); + } + + if (less) + PG_RETURN_BOOL(val <= sum); + else + PG_RETURN_BOOL(val >= sum); +} + +Datum +in_range_int2_int4(PG_FUNCTION_ARGS) +{ + /* We must do all the math in int32 */ + int32 val = (int32) PG_GETARG_INT16(0); + int32 base = (int32) PG_GETARG_INT16(1); + int32 offset = PG_GETARG_INT32(2); + bool sub = PG_GETARG_BOOL(3); + bool less = PG_GETARG_BOOL(4); + int32 sum; + + if (offset < 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE), + errmsg("invalid preceding or following size in window function"))); + + if (sub) + offset = -offset; /* cannot overflow */ + + if (unlikely(pg_add_s32_overflow(base, offset, &sum))) + { + /* + * If sub is false, the true sum is surely more than val, so correct + * answer is the same as "less". If sub is true, the true sum is + * surely less than val, so the answer is "!less". + */ + PG_RETURN_BOOL(sub ? !less : less); + } + + if (less) + PG_RETURN_BOOL(val <= sum); + else + PG_RETURN_BOOL(val >= sum); +} + +Datum +in_range_int2_int2(PG_FUNCTION_ARGS) +{ + /* Doesn't seem worth duplicating code for, so just invoke int2_int4 */ + return DirectFunctionCall5(in_range_int2_int4, + PG_GETARG_DATUM(0), + PG_GETARG_DATUM(1), + Int32GetDatum((int32) PG_GETARG_INT16(2)), + PG_GETARG_DATUM(3), + PG_GETARG_DATUM(4)); +} + +Datum +in_range_int2_int8(PG_FUNCTION_ARGS) +{ + /* Doesn't seem worth duplicating code for, so just invoke int4_int8 */ + return DirectFunctionCall5(in_range_int4_int8, + Int32GetDatum((int32) PG_GETARG_INT16(0)), + Int32GetDatum((int32) PG_GETARG_INT16(1)), + PG_GETARG_DATUM(2), + PG_GETARG_DATUM(3), + PG_GETARG_DATUM(4)); +} + + +/* + * int[24]pl - returns arg1 + arg2 + * int[24]mi - returns arg1 - arg2 + * int[24]mul - returns arg1 * arg2 + * int[24]div - returns arg1 / arg2 + */ + +Datum +int4um(PG_FUNCTION_ARGS) +{ + int32 arg = PG_GETARG_INT32(0); + + if (unlikely(arg == PG_INT32_MIN)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("integer out of range"))); + PG_RETURN_INT32(-arg); +} + +Datum +int4up(PG_FUNCTION_ARGS) +{ + int32 arg = PG_GETARG_INT32(0); + + PG_RETURN_INT32(arg); +} + +Datum +int4pl(PG_FUNCTION_ARGS) +{ + int32 arg1 = PG_GETARG_INT32(0); + int32 arg2 = PG_GETARG_INT32(1); + int32 result; + + if (unlikely(pg_add_s32_overflow(arg1, arg2, &result))) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("integer out of range"))); + PG_RETURN_INT32(result); +} + +Datum +int4mi(PG_FUNCTION_ARGS) +{ + int32 arg1 = PG_GETARG_INT32(0); + int32 arg2 = PG_GETARG_INT32(1); + int32 result; + + if (unlikely(pg_sub_s32_overflow(arg1, arg2, &result))) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("integer out of range"))); + PG_RETURN_INT32(result); +} + +Datum +int4mul(PG_FUNCTION_ARGS) +{ + int32 arg1 = PG_GETARG_INT32(0); + int32 arg2 = PG_GETARG_INT32(1); + int32 result; + + if (unlikely(pg_mul_s32_overflow(arg1, arg2, &result))) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("integer out of range"))); + PG_RETURN_INT32(result); +} + +Datum +int4div(PG_FUNCTION_ARGS) +{ + int32 arg1 = PG_GETARG_INT32(0); + int32 arg2 = PG_GETARG_INT32(1); + int32 result; + + if (arg2 == 0) + { + ereport(ERROR, + (errcode(ERRCODE_DIVISION_BY_ZERO), + errmsg("division by zero"))); + /* ensure compiler realizes we mustn't reach the division (gcc bug) */ + PG_RETURN_NULL(); + } + + /* + * INT_MIN / -1 is problematic, since the result can't be represented on a + * two's-complement machine. Some machines produce INT_MIN, some produce + * zero, some throw an exception. We can dodge the problem by recognizing + * that division by -1 is the same as negation. + */ + if (arg2 == -1) + { + if (unlikely(arg1 == PG_INT32_MIN)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("integer out of range"))); + result = -arg1; + PG_RETURN_INT32(result); + } + + /* No overflow is possible */ + + result = arg1 / arg2; + + PG_RETURN_INT32(result); +} + +Datum +int4inc(PG_FUNCTION_ARGS) +{ + int32 arg = PG_GETARG_INT32(0); + int32 result; + + if (unlikely(pg_add_s32_overflow(arg, 1, &result))) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("integer out of range"))); + + PG_RETURN_INT32(result); +} + +Datum +int2um(PG_FUNCTION_ARGS) +{ + int16 arg = PG_GETARG_INT16(0); + + if (unlikely(arg == PG_INT16_MIN)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("smallint out of range"))); + PG_RETURN_INT16(-arg); +} + +Datum +int2up(PG_FUNCTION_ARGS) +{ + int16 arg = PG_GETARG_INT16(0); + + PG_RETURN_INT16(arg); +} + +Datum +int2pl(PG_FUNCTION_ARGS) +{ + int16 arg1 = PG_GETARG_INT16(0); + int16 arg2 = PG_GETARG_INT16(1); + int16 result; + + if (unlikely(pg_add_s16_overflow(arg1, arg2, &result))) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("smallint out of range"))); + PG_RETURN_INT16(result); +} + +Datum +int2mi(PG_FUNCTION_ARGS) +{ + int16 arg1 = PG_GETARG_INT16(0); + int16 arg2 = PG_GETARG_INT16(1); + int16 result; + + if (unlikely(pg_sub_s16_overflow(arg1, arg2, &result))) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("smallint out of range"))); + PG_RETURN_INT16(result); +} + +Datum +int2mul(PG_FUNCTION_ARGS) +{ + int16 arg1 = PG_GETARG_INT16(0); + int16 arg2 = PG_GETARG_INT16(1); + int16 result; + + if (unlikely(pg_mul_s16_overflow(arg1, arg2, &result))) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("smallint out of range"))); + + PG_RETURN_INT16(result); +} + +Datum +int2div(PG_FUNCTION_ARGS) +{ + int16 arg1 = PG_GETARG_INT16(0); + int16 arg2 = PG_GETARG_INT16(1); + int16 result; + + if (arg2 == 0) + { + ereport(ERROR, + (errcode(ERRCODE_DIVISION_BY_ZERO), + errmsg("division by zero"))); + /* ensure compiler realizes we mustn't reach the division (gcc bug) */ + PG_RETURN_NULL(); + } + + /* + * SHRT_MIN / -1 is problematic, since the result can't be represented on + * a two's-complement machine. Some machines produce SHRT_MIN, some + * produce zero, some throw an exception. We can dodge the problem by + * recognizing that division by -1 is the same as negation. + */ + if (arg2 == -1) + { + if (unlikely(arg1 == PG_INT16_MIN)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("smallint out of range"))); + result = -arg1; + PG_RETURN_INT16(result); + } + + /* No overflow is possible */ + + result = arg1 / arg2; + + PG_RETURN_INT16(result); +} + +Datum +int24pl(PG_FUNCTION_ARGS) +{ + int16 arg1 = PG_GETARG_INT16(0); + int32 arg2 = PG_GETARG_INT32(1); + int32 result; + + if (unlikely(pg_add_s32_overflow((int32) arg1, arg2, &result))) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("integer out of range"))); + PG_RETURN_INT32(result); +} + +Datum +int24mi(PG_FUNCTION_ARGS) +{ + int16 arg1 = PG_GETARG_INT16(0); + int32 arg2 = PG_GETARG_INT32(1); + int32 result; + + if (unlikely(pg_sub_s32_overflow((int32) arg1, arg2, &result))) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("integer out of range"))); + PG_RETURN_INT32(result); +} + +Datum +int24mul(PG_FUNCTION_ARGS) +{ + int16 arg1 = PG_GETARG_INT16(0); + int32 arg2 = PG_GETARG_INT32(1); + int32 result; + + if (unlikely(pg_mul_s32_overflow((int32) arg1, arg2, &result))) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("integer out of range"))); + PG_RETURN_INT32(result); +} + +Datum +int24div(PG_FUNCTION_ARGS) +{ + int16 arg1 = PG_GETARG_INT16(0); + int32 arg2 = PG_GETARG_INT32(1); + + if (unlikely(arg2 == 0)) + { + ereport(ERROR, + (errcode(ERRCODE_DIVISION_BY_ZERO), + errmsg("division by zero"))); + /* ensure compiler realizes we mustn't reach the division (gcc bug) */ + PG_RETURN_NULL(); + } + + /* No overflow is possible */ + PG_RETURN_INT32((int32) arg1 / arg2); +} + +Datum +int42pl(PG_FUNCTION_ARGS) +{ + int32 arg1 = PG_GETARG_INT32(0); + int16 arg2 = PG_GETARG_INT16(1); + int32 result; + + if (unlikely(pg_add_s32_overflow(arg1, (int32) arg2, &result))) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("integer out of range"))); + PG_RETURN_INT32(result); +} + +Datum +int42mi(PG_FUNCTION_ARGS) +{ + int32 arg1 = PG_GETARG_INT32(0); + int16 arg2 = PG_GETARG_INT16(1); + int32 result; + + if (unlikely(pg_sub_s32_overflow(arg1, (int32) arg2, &result))) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("integer out of range"))); + PG_RETURN_INT32(result); +} + +Datum +int42mul(PG_FUNCTION_ARGS) +{ + int32 arg1 = PG_GETARG_INT32(0); + int16 arg2 = PG_GETARG_INT16(1); + int32 result; + + if (unlikely(pg_mul_s32_overflow(arg1, (int32) arg2, &result))) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("integer out of range"))); + PG_RETURN_INT32(result); +} + +Datum +int42div(PG_FUNCTION_ARGS) +{ + int32 arg1 = PG_GETARG_INT32(0); + int16 arg2 = PG_GETARG_INT16(1); + int32 result; + + if (unlikely(arg2 == 0)) + { + ereport(ERROR, + (errcode(ERRCODE_DIVISION_BY_ZERO), + errmsg("division by zero"))); + /* ensure compiler realizes we mustn't reach the division (gcc bug) */ + PG_RETURN_NULL(); + } + + /* + * INT_MIN / -1 is problematic, since the result can't be represented on a + * two's-complement machine. Some machines produce INT_MIN, some produce + * zero, some throw an exception. We can dodge the problem by recognizing + * that division by -1 is the same as negation. + */ + if (arg2 == -1) + { + if (unlikely(arg1 == PG_INT32_MIN)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("integer out of range"))); + result = -arg1; + PG_RETURN_INT32(result); + } + + /* No overflow is possible */ + + result = arg1 / arg2; + + PG_RETURN_INT32(result); +} + +Datum +int4mod(PG_FUNCTION_ARGS) +{ + int32 arg1 = PG_GETARG_INT32(0); + int32 arg2 = PG_GETARG_INT32(1); + + if (unlikely(arg2 == 0)) + { + ereport(ERROR, + (errcode(ERRCODE_DIVISION_BY_ZERO), + errmsg("division by zero"))); + /* ensure compiler realizes we mustn't reach the division (gcc bug) */ + PG_RETURN_NULL(); + } + + /* + * Some machines throw a floating-point exception for INT_MIN % -1, which + * is a bit silly since the correct answer is perfectly well-defined, + * namely zero. + */ + if (arg2 == -1) + PG_RETURN_INT32(0); + + /* No overflow is possible */ + + PG_RETURN_INT32(arg1 % arg2); +} + +Datum +int2mod(PG_FUNCTION_ARGS) +{ + int16 arg1 = PG_GETARG_INT16(0); + int16 arg2 = PG_GETARG_INT16(1); + + if (unlikely(arg2 == 0)) + { + ereport(ERROR, + (errcode(ERRCODE_DIVISION_BY_ZERO), + errmsg("division by zero"))); + /* ensure compiler realizes we mustn't reach the division (gcc bug) */ + PG_RETURN_NULL(); + } + + /* + * Some machines throw a floating-point exception for INT_MIN % -1, which + * is a bit silly since the correct answer is perfectly well-defined, + * namely zero. (It's not clear this ever happens when dealing with + * int16, but we might as well have the test for safety.) + */ + if (arg2 == -1) + PG_RETURN_INT16(0); + + /* No overflow is possible */ + + PG_RETURN_INT16(arg1 % arg2); +} + + +/* int[24]abs() + * Absolute value + */ +Datum +int4abs(PG_FUNCTION_ARGS) +{ + int32 arg1 = PG_GETARG_INT32(0); + int32 result; + + if (unlikely(arg1 == PG_INT32_MIN)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("integer out of range"))); + result = (arg1 < 0) ? -arg1 : arg1; + PG_RETURN_INT32(result); +} + +Datum +int2abs(PG_FUNCTION_ARGS) +{ + int16 arg1 = PG_GETARG_INT16(0); + int16 result; + + if (unlikely(arg1 == PG_INT16_MIN)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("smallint out of range"))); + result = (arg1 < 0) ? -arg1 : arg1; + PG_RETURN_INT16(result); +} + +/* + * Greatest Common Divisor + * + * Returns the largest positive integer that exactly divides both inputs. + * Special cases: + * - gcd(x, 0) = gcd(0, x) = abs(x) + * because 0 is divisible by anything + * - gcd(0, 0) = 0 + * complies with the previous definition and is a common convention + * + * Special care must be taken if either input is INT_MIN --- gcd(0, INT_MIN), + * gcd(INT_MIN, 0) and gcd(INT_MIN, INT_MIN) are all equal to abs(INT_MIN), + * which cannot be represented as a 32-bit signed integer. + */ +static int32 +int4gcd_internal(int32 arg1, int32 arg2) +{ + int32 swap; + int32 a1, + a2; + + /* + * Put the greater absolute value in arg1. + * + * This would happen automatically in the loop below, but avoids an + * expensive modulo operation, and simplifies the special-case handling + * for INT_MIN below. + * + * We do this in negative space in order to handle INT_MIN. + */ + a1 = (arg1 < 0) ? arg1 : -arg1; + a2 = (arg2 < 0) ? arg2 : -arg2; + if (a1 > a2) + { + swap = arg1; + arg1 = arg2; + arg2 = swap; + } + + /* Special care needs to be taken with INT_MIN. See comments above. */ + if (arg1 == PG_INT32_MIN) + { + if (arg2 == 0 || arg2 == PG_INT32_MIN) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("integer out of range"))); + + /* + * Some machines throw a floating-point exception for INT_MIN % -1, + * which is a bit silly since the correct answer is perfectly + * well-defined, namely zero. Guard against this and just return the + * result, gcd(INT_MIN, -1) = 1. + */ + if (arg2 == -1) + return 1; + } + + /* Use the Euclidean algorithm to find the GCD */ + while (arg2 != 0) + { + swap = arg2; + arg2 = arg1 % arg2; + arg1 = swap; + } + + /* + * Make sure the result is positive. (We know we don't have INT_MIN + * anymore). + */ + if (arg1 < 0) + arg1 = -arg1; + + return arg1; +} + +Datum +int4gcd(PG_FUNCTION_ARGS) +{ + int32 arg1 = PG_GETARG_INT32(0); + int32 arg2 = PG_GETARG_INT32(1); + int32 result; + + result = int4gcd_internal(arg1, arg2); + + PG_RETURN_INT32(result); +} + +/* + * Least Common Multiple + */ +Datum +int4lcm(PG_FUNCTION_ARGS) +{ + int32 arg1 = PG_GETARG_INT32(0); + int32 arg2 = PG_GETARG_INT32(1); + int32 gcd; + int32 result; + + /* + * Handle lcm(x, 0) = lcm(0, x) = 0 as a special case. This prevents a + * division-by-zero error below when x is zero, and an overflow error from + * the GCD computation when x = INT_MIN. + */ + if (arg1 == 0 || arg2 == 0) + PG_RETURN_INT32(0); + + /* lcm(x, y) = abs(x / gcd(x, y) * y) */ + gcd = int4gcd_internal(arg1, arg2); + arg1 = arg1 / gcd; + + if (unlikely(pg_mul_s32_overflow(arg1, arg2, &result))) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("integer out of range"))); + + /* If the result is INT_MIN, it cannot be represented. */ + if (unlikely(result == PG_INT32_MIN)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("integer out of range"))); + + if (result < 0) + result = -result; + + PG_RETURN_INT32(result); +} + +Datum +int2larger(PG_FUNCTION_ARGS) +{ + int16 arg1 = PG_GETARG_INT16(0); + int16 arg2 = PG_GETARG_INT16(1); + + PG_RETURN_INT16((arg1 > arg2) ? arg1 : arg2); +} + +Datum +int2smaller(PG_FUNCTION_ARGS) +{ + int16 arg1 = PG_GETARG_INT16(0); + int16 arg2 = PG_GETARG_INT16(1); + + PG_RETURN_INT16((arg1 < arg2) ? arg1 : arg2); +} + +Datum +int4larger(PG_FUNCTION_ARGS) +{ + int32 arg1 = PG_GETARG_INT32(0); + int32 arg2 = PG_GETARG_INT32(1); + + PG_RETURN_INT32((arg1 > arg2) ? arg1 : arg2); +} + +Datum +int4smaller(PG_FUNCTION_ARGS) +{ + int32 arg1 = PG_GETARG_INT32(0); + int32 arg2 = PG_GETARG_INT32(1); + + PG_RETURN_INT32((arg1 < arg2) ? arg1 : arg2); +} + +/* + * Bit-pushing operators + * + * int[24]and - returns arg1 & arg2 + * int[24]or - returns arg1 | arg2 + * int[24]xor - returns arg1 # arg2 + * int[24]not - returns ~arg1 + * int[24]shl - returns arg1 << arg2 + * int[24]shr - returns arg1 >> arg2 + */ + +Datum +int4and(PG_FUNCTION_ARGS) +{ + int32 arg1 = PG_GETARG_INT32(0); + int32 arg2 = PG_GETARG_INT32(1); + + PG_RETURN_INT32(arg1 & arg2); +} + +Datum +int4or(PG_FUNCTION_ARGS) +{ + int32 arg1 = PG_GETARG_INT32(0); + int32 arg2 = PG_GETARG_INT32(1); + + PG_RETURN_INT32(arg1 | arg2); +} + +Datum +int4xor(PG_FUNCTION_ARGS) +{ + int32 arg1 = PG_GETARG_INT32(0); + int32 arg2 = PG_GETARG_INT32(1); + + PG_RETURN_INT32(arg1 ^ arg2); +} + +Datum +int4shl(PG_FUNCTION_ARGS) +{ + int32 arg1 = PG_GETARG_INT32(0); + int32 arg2 = PG_GETARG_INT32(1); + + PG_RETURN_INT32(arg1 << arg2); +} + +Datum +int4shr(PG_FUNCTION_ARGS) +{ + int32 arg1 = PG_GETARG_INT32(0); + int32 arg2 = PG_GETARG_INT32(1); + + PG_RETURN_INT32(arg1 >> arg2); +} + +Datum +int4not(PG_FUNCTION_ARGS) +{ + int32 arg1 = PG_GETARG_INT32(0); + + PG_RETURN_INT32(~arg1); +} + +Datum +int2and(PG_FUNCTION_ARGS) +{ + int16 arg1 = PG_GETARG_INT16(0); + int16 arg2 = PG_GETARG_INT16(1); + + PG_RETURN_INT16(arg1 & arg2); +} + +Datum +int2or(PG_FUNCTION_ARGS) +{ + int16 arg1 = PG_GETARG_INT16(0); + int16 arg2 = PG_GETARG_INT16(1); + + PG_RETURN_INT16(arg1 | arg2); +} + +Datum +int2xor(PG_FUNCTION_ARGS) +{ + int16 arg1 = PG_GETARG_INT16(0); + int16 arg2 = PG_GETARG_INT16(1); + + PG_RETURN_INT16(arg1 ^ arg2); +} + +Datum +int2not(PG_FUNCTION_ARGS) +{ + int16 arg1 = PG_GETARG_INT16(0); + + PG_RETURN_INT16(~arg1); +} + + +Datum +int2shl(PG_FUNCTION_ARGS) +{ + int16 arg1 = PG_GETARG_INT16(0); + int32 arg2 = PG_GETARG_INT32(1); + + PG_RETURN_INT16(arg1 << arg2); +} + +Datum +int2shr(PG_FUNCTION_ARGS) +{ + int16 arg1 = PG_GETARG_INT16(0); + int32 arg2 = PG_GETARG_INT32(1); + + PG_RETURN_INT16(arg1 >> arg2); +} + +/* + * non-persistent numeric series generator + */ +Datum +generate_series_int4(PG_FUNCTION_ARGS) +{ + return generate_series_step_int4(fcinfo); +} + +Datum +generate_series_step_int4(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + generate_series_fctx *fctx; + int32 result; + MemoryContext oldcontext; + + /* stuff done only on the first call of the function */ + if (SRF_IS_FIRSTCALL()) + { + int32 start = PG_GETARG_INT32(0); + int32 finish = PG_GETARG_INT32(1); + int32 step = 1; + + /* see if we were given an explicit step size */ + if (PG_NARGS() == 3) + step = PG_GETARG_INT32(2); + if (step == 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("step size cannot equal zero"))); + + /* 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 */ + fctx = (generate_series_fctx *) palloc(sizeof(generate_series_fctx)); + + /* + * Use fctx to keep state from call to call. Seed current with the + * original start value + */ + fctx->current = start; + fctx->finish = finish; + fctx->step = step; + + funcctx->user_fctx = fctx; + MemoryContextSwitchTo(oldcontext); + } + + /* stuff done on every call of the function */ + funcctx = SRF_PERCALL_SETUP(); + + /* + * get the saved state and use current as the result for this iteration + */ + fctx = funcctx->user_fctx; + result = fctx->current; + + if ((fctx->step > 0 && fctx->current <= fctx->finish) || + (fctx->step < 0 && fctx->current >= fctx->finish)) + { + /* + * Increment current in preparation for next iteration. If next-value + * computation overflows, this is the final result. + */ + if (pg_add_s32_overflow(fctx->current, fctx->step, &fctx->current)) + fctx->step = 0; + + /* do when there is more left to send */ + SRF_RETURN_NEXT(funcctx, Int32GetDatum(result)); + } + else + /* do when there is no more left */ + SRF_RETURN_DONE(funcctx); +} + +/* + * Planner support function for generate_series(int4, int4 [, int4]) + */ +Datum +generate_series_int4_support(PG_FUNCTION_ARGS) +{ + Node *rawreq = (Node *) PG_GETARG_POINTER(0); + Node *ret = NULL; + + if (IsA(rawreq, SupportRequestRows)) + { + /* Try to estimate the number of rows returned */ + SupportRequestRows *req = (SupportRequestRows *) rawreq; + + if (is_funcclause(req->node)) /* be paranoid */ + { + List *args = ((FuncExpr *) req->node)->args; + Node *arg1, + *arg2, + *arg3; + + /* We can use estimated argument values here */ + arg1 = estimate_expression_value(req->root, linitial(args)); + arg2 = estimate_expression_value(req->root, lsecond(args)); + if (list_length(args) >= 3) + arg3 = estimate_expression_value(req->root, lthird(args)); + else + arg3 = NULL; + + /* + * If any argument is constant NULL, we can safely assume that + * zero rows are returned. Otherwise, if they're all non-NULL + * constants, we can calculate the number of rows that will be + * returned. Use double arithmetic to avoid overflow hazards. + */ + if ((IsA(arg1, Const) && + ((Const *) arg1)->constisnull) || + (IsA(arg2, Const) && + ((Const *) arg2)->constisnull) || + (arg3 != NULL && IsA(arg3, Const) && + ((Const *) arg3)->constisnull)) + { + req->rows = 0; + ret = (Node *) req; + } + else if (IsA(arg1, Const) && + IsA(arg2, Const) && + (arg3 == NULL || IsA(arg3, Const))) + { + double start, + finish, + step; + + start = DatumGetInt32(((Const *) arg1)->constvalue); + finish = DatumGetInt32(((Const *) arg2)->constvalue); + step = arg3 ? DatumGetInt32(((Const *) arg3)->constvalue) : 1; + + /* This equation works for either sign of step */ + if (step != 0) + { + req->rows = floor((finish - start + step) / step); + ret = (Node *) req; + } + } + } + } + + PG_RETURN_POINTER(ret); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/int8.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/int8.c new file mode 100644 index 00000000000..41fbeec8fd7 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/int8.c @@ -0,0 +1,1539 @@ +/*------------------------------------------------------------------------- + * + * int8.c + * Internal 64-bit integer operations + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/utils/adt/int8.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <ctype.h> +#include <limits.h> +#include <math.h> + +#include "common/int.h" +#include "funcapi.h" +#include "libpq/pqformat.h" +#include "nodes/nodeFuncs.h" +#include "nodes/supportnodes.h" +#include "optimizer/optimizer.h" +#include "utils/builtins.h" +#include "utils/lsyscache.h" + + +typedef struct +{ + int64 current; + int64 finish; + int64 step; +} generate_series_fctx; + + +/*********************************************************************** + ** + ** Routines for 64-bit integers. + ** + ***********************************************************************/ + +/*---------------------------------------------------------- + * Formatting and conversion routines. + *---------------------------------------------------------*/ + +/* int8in() + */ +Datum +int8in(PG_FUNCTION_ARGS) +{ + char *num = PG_GETARG_CSTRING(0); + + PG_RETURN_INT64(pg_strtoint64_safe(num, fcinfo->context)); +} + + +/* int8out() + */ +Datum +int8out(PG_FUNCTION_ARGS) +{ + int64 val = PG_GETARG_INT64(0); + char buf[MAXINT8LEN + 1]; + char *result; + int len; + + len = pg_lltoa(val, buf) + 1; + + /* + * Since the length is already known, we do a manual palloc() and memcpy() + * to avoid the strlen() call that would otherwise be done in pstrdup(). + */ + result = palloc(len); + memcpy(result, buf, len); + PG_RETURN_CSTRING(result); +} + +/* + * int8recv - converts external binary format to int8 + */ +Datum +int8recv(PG_FUNCTION_ARGS) +{ + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + + PG_RETURN_INT64(pq_getmsgint64(buf)); +} + +/* + * int8send - converts int8 to binary format + */ +Datum +int8send(PG_FUNCTION_ARGS) +{ + int64 arg1 = PG_GETARG_INT64(0); + StringInfoData buf; + + pq_begintypsend(&buf); + pq_sendint64(&buf, arg1); + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} + + +/*---------------------------------------------------------- + * Relational operators for int8s, including cross-data-type comparisons. + *---------------------------------------------------------*/ + +/* int8relop() + * Is val1 relop val2? + */ +Datum +int8eq(PG_FUNCTION_ARGS) +{ + int64 val1 = PG_GETARG_INT64(0); + int64 val2 = PG_GETARG_INT64(1); + + PG_RETURN_BOOL(val1 == val2); +} + +Datum +int8ne(PG_FUNCTION_ARGS) +{ + int64 val1 = PG_GETARG_INT64(0); + int64 val2 = PG_GETARG_INT64(1); + + PG_RETURN_BOOL(val1 != val2); +} + +Datum +int8lt(PG_FUNCTION_ARGS) +{ + int64 val1 = PG_GETARG_INT64(0); + int64 val2 = PG_GETARG_INT64(1); + + PG_RETURN_BOOL(val1 < val2); +} + +Datum +int8gt(PG_FUNCTION_ARGS) +{ + int64 val1 = PG_GETARG_INT64(0); + int64 val2 = PG_GETARG_INT64(1); + + PG_RETURN_BOOL(val1 > val2); +} + +Datum +int8le(PG_FUNCTION_ARGS) +{ + int64 val1 = PG_GETARG_INT64(0); + int64 val2 = PG_GETARG_INT64(1); + + PG_RETURN_BOOL(val1 <= val2); +} + +Datum +int8ge(PG_FUNCTION_ARGS) +{ + int64 val1 = PG_GETARG_INT64(0); + int64 val2 = PG_GETARG_INT64(1); + + PG_RETURN_BOOL(val1 >= val2); +} + +/* int84relop() + * Is 64-bit val1 relop 32-bit val2? + */ +Datum +int84eq(PG_FUNCTION_ARGS) +{ + int64 val1 = PG_GETARG_INT64(0); + int32 val2 = PG_GETARG_INT32(1); + + PG_RETURN_BOOL(val1 == val2); +} + +Datum +int84ne(PG_FUNCTION_ARGS) +{ + int64 val1 = PG_GETARG_INT64(0); + int32 val2 = PG_GETARG_INT32(1); + + PG_RETURN_BOOL(val1 != val2); +} + +Datum +int84lt(PG_FUNCTION_ARGS) +{ + int64 val1 = PG_GETARG_INT64(0); + int32 val2 = PG_GETARG_INT32(1); + + PG_RETURN_BOOL(val1 < val2); +} + +Datum +int84gt(PG_FUNCTION_ARGS) +{ + int64 val1 = PG_GETARG_INT64(0); + int32 val2 = PG_GETARG_INT32(1); + + PG_RETURN_BOOL(val1 > val2); +} + +Datum +int84le(PG_FUNCTION_ARGS) +{ + int64 val1 = PG_GETARG_INT64(0); + int32 val2 = PG_GETARG_INT32(1); + + PG_RETURN_BOOL(val1 <= val2); +} + +Datum +int84ge(PG_FUNCTION_ARGS) +{ + int64 val1 = PG_GETARG_INT64(0); + int32 val2 = PG_GETARG_INT32(1); + + PG_RETURN_BOOL(val1 >= val2); +} + +/* int48relop() + * Is 32-bit val1 relop 64-bit val2? + */ +Datum +int48eq(PG_FUNCTION_ARGS) +{ + int32 val1 = PG_GETARG_INT32(0); + int64 val2 = PG_GETARG_INT64(1); + + PG_RETURN_BOOL(val1 == val2); +} + +Datum +int48ne(PG_FUNCTION_ARGS) +{ + int32 val1 = PG_GETARG_INT32(0); + int64 val2 = PG_GETARG_INT64(1); + + PG_RETURN_BOOL(val1 != val2); +} + +Datum +int48lt(PG_FUNCTION_ARGS) +{ + int32 val1 = PG_GETARG_INT32(0); + int64 val2 = PG_GETARG_INT64(1); + + PG_RETURN_BOOL(val1 < val2); +} + +Datum +int48gt(PG_FUNCTION_ARGS) +{ + int32 val1 = PG_GETARG_INT32(0); + int64 val2 = PG_GETARG_INT64(1); + + PG_RETURN_BOOL(val1 > val2); +} + +Datum +int48le(PG_FUNCTION_ARGS) +{ + int32 val1 = PG_GETARG_INT32(0); + int64 val2 = PG_GETARG_INT64(1); + + PG_RETURN_BOOL(val1 <= val2); +} + +Datum +int48ge(PG_FUNCTION_ARGS) +{ + int32 val1 = PG_GETARG_INT32(0); + int64 val2 = PG_GETARG_INT64(1); + + PG_RETURN_BOOL(val1 >= val2); +} + +/* int82relop() + * Is 64-bit val1 relop 16-bit val2? + */ +Datum +int82eq(PG_FUNCTION_ARGS) +{ + int64 val1 = PG_GETARG_INT64(0); + int16 val2 = PG_GETARG_INT16(1); + + PG_RETURN_BOOL(val1 == val2); +} + +Datum +int82ne(PG_FUNCTION_ARGS) +{ + int64 val1 = PG_GETARG_INT64(0); + int16 val2 = PG_GETARG_INT16(1); + + PG_RETURN_BOOL(val1 != val2); +} + +Datum +int82lt(PG_FUNCTION_ARGS) +{ + int64 val1 = PG_GETARG_INT64(0); + int16 val2 = PG_GETARG_INT16(1); + + PG_RETURN_BOOL(val1 < val2); +} + +Datum +int82gt(PG_FUNCTION_ARGS) +{ + int64 val1 = PG_GETARG_INT64(0); + int16 val2 = PG_GETARG_INT16(1); + + PG_RETURN_BOOL(val1 > val2); +} + +Datum +int82le(PG_FUNCTION_ARGS) +{ + int64 val1 = PG_GETARG_INT64(0); + int16 val2 = PG_GETARG_INT16(1); + + PG_RETURN_BOOL(val1 <= val2); +} + +Datum +int82ge(PG_FUNCTION_ARGS) +{ + int64 val1 = PG_GETARG_INT64(0); + int16 val2 = PG_GETARG_INT16(1); + + PG_RETURN_BOOL(val1 >= val2); +} + +/* int28relop() + * Is 16-bit val1 relop 64-bit val2? + */ +Datum +int28eq(PG_FUNCTION_ARGS) +{ + int16 val1 = PG_GETARG_INT16(0); + int64 val2 = PG_GETARG_INT64(1); + + PG_RETURN_BOOL(val1 == val2); +} + +Datum +int28ne(PG_FUNCTION_ARGS) +{ + int16 val1 = PG_GETARG_INT16(0); + int64 val2 = PG_GETARG_INT64(1); + + PG_RETURN_BOOL(val1 != val2); +} + +Datum +int28lt(PG_FUNCTION_ARGS) +{ + int16 val1 = PG_GETARG_INT16(0); + int64 val2 = PG_GETARG_INT64(1); + + PG_RETURN_BOOL(val1 < val2); +} + +Datum +int28gt(PG_FUNCTION_ARGS) +{ + int16 val1 = PG_GETARG_INT16(0); + int64 val2 = PG_GETARG_INT64(1); + + PG_RETURN_BOOL(val1 > val2); +} + +Datum +int28le(PG_FUNCTION_ARGS) +{ + int16 val1 = PG_GETARG_INT16(0); + int64 val2 = PG_GETARG_INT64(1); + + PG_RETURN_BOOL(val1 <= val2); +} + +Datum +int28ge(PG_FUNCTION_ARGS) +{ + int16 val1 = PG_GETARG_INT16(0); + int64 val2 = PG_GETARG_INT64(1); + + PG_RETURN_BOOL(val1 >= val2); +} + +/* + * in_range support function for int8. + * + * Note: we needn't supply int8_int4 or int8_int2 variants, as implicit + * coercion of the offset value takes care of those scenarios just as well. + */ +Datum +in_range_int8_int8(PG_FUNCTION_ARGS) +{ + int64 val = PG_GETARG_INT64(0); + int64 base = PG_GETARG_INT64(1); + int64 offset = PG_GETARG_INT64(2); + bool sub = PG_GETARG_BOOL(3); + bool less = PG_GETARG_BOOL(4); + int64 sum; + + if (offset < 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE), + errmsg("invalid preceding or following size in window function"))); + + if (sub) + offset = -offset; /* cannot overflow */ + + if (unlikely(pg_add_s64_overflow(base, offset, &sum))) + { + /* + * If sub is false, the true sum is surely more than val, so correct + * answer is the same as "less". If sub is true, the true sum is + * surely less than val, so the answer is "!less". + */ + PG_RETURN_BOOL(sub ? !less : less); + } + + if (less) + PG_RETURN_BOOL(val <= sum); + else + PG_RETURN_BOOL(val >= sum); +} + + +/*---------------------------------------------------------- + * Arithmetic operators on 64-bit integers. + *---------------------------------------------------------*/ + +Datum +int8um(PG_FUNCTION_ARGS) +{ + int64 arg = PG_GETARG_INT64(0); + int64 result; + + if (unlikely(arg == PG_INT64_MIN)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("bigint out of range"))); + result = -arg; + PG_RETURN_INT64(result); +} + +Datum +int8up(PG_FUNCTION_ARGS) +{ + int64 arg = PG_GETARG_INT64(0); + + PG_RETURN_INT64(arg); +} + +Datum +int8pl(PG_FUNCTION_ARGS) +{ + int64 arg1 = PG_GETARG_INT64(0); + int64 arg2 = PG_GETARG_INT64(1); + int64 result; + + if (unlikely(pg_add_s64_overflow(arg1, arg2, &result))) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("bigint out of range"))); + PG_RETURN_INT64(result); +} + +Datum +int8mi(PG_FUNCTION_ARGS) +{ + int64 arg1 = PG_GETARG_INT64(0); + int64 arg2 = PG_GETARG_INT64(1); + int64 result; + + if (unlikely(pg_sub_s64_overflow(arg1, arg2, &result))) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("bigint out of range"))); + PG_RETURN_INT64(result); +} + +Datum +int8mul(PG_FUNCTION_ARGS) +{ + int64 arg1 = PG_GETARG_INT64(0); + int64 arg2 = PG_GETARG_INT64(1); + int64 result; + + if (unlikely(pg_mul_s64_overflow(arg1, arg2, &result))) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("bigint out of range"))); + PG_RETURN_INT64(result); +} + +Datum +int8div(PG_FUNCTION_ARGS) +{ + int64 arg1 = PG_GETARG_INT64(0); + int64 arg2 = PG_GETARG_INT64(1); + int64 result; + + if (arg2 == 0) + { + ereport(ERROR, + (errcode(ERRCODE_DIVISION_BY_ZERO), + errmsg("division by zero"))); + /* ensure compiler realizes we mustn't reach the division (gcc bug) */ + PG_RETURN_NULL(); + } + + /* + * INT64_MIN / -1 is problematic, since the result can't be represented on + * a two's-complement machine. Some machines produce INT64_MIN, some + * produce zero, some throw an exception. We can dodge the problem by + * recognizing that division by -1 is the same as negation. + */ + if (arg2 == -1) + { + if (unlikely(arg1 == PG_INT64_MIN)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("bigint out of range"))); + result = -arg1; + PG_RETURN_INT64(result); + } + + /* No overflow is possible */ + + result = arg1 / arg2; + + PG_RETURN_INT64(result); +} + +/* int8abs() + * Absolute value + */ +Datum +int8abs(PG_FUNCTION_ARGS) +{ + int64 arg1 = PG_GETARG_INT64(0); + int64 result; + + if (unlikely(arg1 == PG_INT64_MIN)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("bigint out of range"))); + result = (arg1 < 0) ? -arg1 : arg1; + PG_RETURN_INT64(result); +} + +/* int8mod() + * Modulo operation. + */ +Datum +int8mod(PG_FUNCTION_ARGS) +{ + int64 arg1 = PG_GETARG_INT64(0); + int64 arg2 = PG_GETARG_INT64(1); + + if (unlikely(arg2 == 0)) + { + ereport(ERROR, + (errcode(ERRCODE_DIVISION_BY_ZERO), + errmsg("division by zero"))); + /* ensure compiler realizes we mustn't reach the division (gcc bug) */ + PG_RETURN_NULL(); + } + + /* + * Some machines throw a floating-point exception for INT64_MIN % -1, + * which is a bit silly since the correct answer is perfectly + * well-defined, namely zero. + */ + if (arg2 == -1) + PG_RETURN_INT64(0); + + /* No overflow is possible */ + + PG_RETURN_INT64(arg1 % arg2); +} + +/* + * Greatest Common Divisor + * + * Returns the largest positive integer that exactly divides both inputs. + * Special cases: + * - gcd(x, 0) = gcd(0, x) = abs(x) + * because 0 is divisible by anything + * - gcd(0, 0) = 0 + * complies with the previous definition and is a common convention + * + * Special care must be taken if either input is INT64_MIN --- + * gcd(0, INT64_MIN), gcd(INT64_MIN, 0) and gcd(INT64_MIN, INT64_MIN) are + * all equal to abs(INT64_MIN), which cannot be represented as a 64-bit signed + * integer. + */ +static int64 +int8gcd_internal(int64 arg1, int64 arg2) +{ + int64 swap; + int64 a1, + a2; + + /* + * Put the greater absolute value in arg1. + * + * This would happen automatically in the loop below, but avoids an + * expensive modulo operation, and simplifies the special-case handling + * for INT64_MIN below. + * + * We do this in negative space in order to handle INT64_MIN. + */ + a1 = (arg1 < 0) ? arg1 : -arg1; + a2 = (arg2 < 0) ? arg2 : -arg2; + if (a1 > a2) + { + swap = arg1; + arg1 = arg2; + arg2 = swap; + } + + /* Special care needs to be taken with INT64_MIN. See comments above. */ + if (arg1 == PG_INT64_MIN) + { + if (arg2 == 0 || arg2 == PG_INT64_MIN) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("bigint out of range"))); + + /* + * Some machines throw a floating-point exception for INT64_MIN % -1, + * which is a bit silly since the correct answer is perfectly + * well-defined, namely zero. Guard against this and just return the + * result, gcd(INT64_MIN, -1) = 1. + */ + if (arg2 == -1) + return 1; + } + + /* Use the Euclidean algorithm to find the GCD */ + while (arg2 != 0) + { + swap = arg2; + arg2 = arg1 % arg2; + arg1 = swap; + } + + /* + * Make sure the result is positive. (We know we don't have INT64_MIN + * anymore). + */ + if (arg1 < 0) + arg1 = -arg1; + + return arg1; +} + +Datum +int8gcd(PG_FUNCTION_ARGS) +{ + int64 arg1 = PG_GETARG_INT64(0); + int64 arg2 = PG_GETARG_INT64(1); + int64 result; + + result = int8gcd_internal(arg1, arg2); + + PG_RETURN_INT64(result); +} + +/* + * Least Common Multiple + */ +Datum +int8lcm(PG_FUNCTION_ARGS) +{ + int64 arg1 = PG_GETARG_INT64(0); + int64 arg2 = PG_GETARG_INT64(1); + int64 gcd; + int64 result; + + /* + * Handle lcm(x, 0) = lcm(0, x) = 0 as a special case. This prevents a + * division-by-zero error below when x is zero, and an overflow error from + * the GCD computation when x = INT64_MIN. + */ + if (arg1 == 0 || arg2 == 0) + PG_RETURN_INT64(0); + + /* lcm(x, y) = abs(x / gcd(x, y) * y) */ + gcd = int8gcd_internal(arg1, arg2); + arg1 = arg1 / gcd; + + if (unlikely(pg_mul_s64_overflow(arg1, arg2, &result))) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("bigint out of range"))); + + /* If the result is INT64_MIN, it cannot be represented. */ + if (unlikely(result == PG_INT64_MIN)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("bigint out of range"))); + + if (result < 0) + result = -result; + + PG_RETURN_INT64(result); +} + +Datum +int8inc(PG_FUNCTION_ARGS) +{ + /* + * When int8 is pass-by-reference, we provide this special case to avoid + * palloc overhead for COUNT(): when called as an aggregate, we know that + * the argument is modifiable local storage, so just update it in-place. + * (If int8 is pass-by-value, then of course this is useless as well as + * incorrect, so just ifdef it out.) + */ +#ifndef USE_FLOAT8_BYVAL /* controls int8 too */ + if (AggCheckCallContext(fcinfo, NULL)) + { + int64 *arg = (int64 *) PG_GETARG_POINTER(0); + + if (unlikely(pg_add_s64_overflow(*arg, 1, arg))) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("bigint out of range"))); + + PG_RETURN_POINTER(arg); + } + else +#endif + { + /* Not called as an aggregate, so just do it the dumb way */ + int64 arg = PG_GETARG_INT64(0); + int64 result; + + if (unlikely(pg_add_s64_overflow(arg, 1, &result))) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("bigint out of range"))); + + PG_RETURN_INT64(result); + } +} + +Datum +int8dec(PG_FUNCTION_ARGS) +{ + /* + * When int8 is pass-by-reference, we provide this special case to avoid + * palloc overhead for COUNT(): when called as an aggregate, we know that + * the argument is modifiable local storage, so just update it in-place. + * (If int8 is pass-by-value, then of course this is useless as well as + * incorrect, so just ifdef it out.) + */ +#ifndef USE_FLOAT8_BYVAL /* controls int8 too */ + if (AggCheckCallContext(fcinfo, NULL)) + { + int64 *arg = (int64 *) PG_GETARG_POINTER(0); + + if (unlikely(pg_sub_s64_overflow(*arg, 1, arg))) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("bigint out of range"))); + PG_RETURN_POINTER(arg); + } + else +#endif + { + /* Not called as an aggregate, so just do it the dumb way */ + int64 arg = PG_GETARG_INT64(0); + int64 result; + + if (unlikely(pg_sub_s64_overflow(arg, 1, &result))) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("bigint out of range"))); + + PG_RETURN_INT64(result); + } +} + + +/* + * These functions are exactly like int8inc/int8dec but are used for + * aggregates that count only non-null values. Since the functions are + * declared strict, the null checks happen before we ever get here, and all we + * need do is increment the state value. We could actually make these pg_proc + * entries point right at int8inc/int8dec, but then the opr_sanity regression + * test would complain about mismatched entries for a built-in function. + */ + +Datum +int8inc_any(PG_FUNCTION_ARGS) +{ + return int8inc(fcinfo); +} + +Datum +int8inc_float8_float8(PG_FUNCTION_ARGS) +{ + return int8inc(fcinfo); +} + +Datum +int8dec_any(PG_FUNCTION_ARGS) +{ + return int8dec(fcinfo); +} + +/* + * int8inc_support + * prosupport function for int8inc() and int8inc_any() + */ +Datum +int8inc_support(PG_FUNCTION_ARGS) +{ + Node *rawreq = (Node *) PG_GETARG_POINTER(0); + + if (IsA(rawreq, SupportRequestWFuncMonotonic)) + { + SupportRequestWFuncMonotonic *req = (SupportRequestWFuncMonotonic *) rawreq; + MonotonicFunction monotonic = MONOTONICFUNC_NONE; + int frameOptions = req->window_clause->frameOptions; + WindowFunc *wfunc = req->window_func; + + if (list_length(wfunc->args) == 1) + { + Node *expr = eval_const_expressions(NULL, linitial(wfunc->args)); + + /* + * Due to the Node representation of WindowClause runConditions in + * version prior to v17, we need to insist that the count arg is + * Const to allow safe application of the runCondition + * optimization. + */ + if (!IsA(expr, Const)) + PG_RETURN_POINTER(NULL); + } + + /* No ORDER BY clause then all rows are peers */ + if (req->window_clause->orderClause == NIL) + monotonic = MONOTONICFUNC_BOTH; + else + { + /* + * Otherwise take into account the frame options. When the frame + * bound is the start of the window then the resulting value can + * never decrease, therefore is monotonically increasing + */ + if (frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING) + monotonic |= MONOTONICFUNC_INCREASING; + + /* + * Likewise, if the frame bound is the end of the window then the + * resulting value can never decrease. + */ + if (frameOptions & FRAMEOPTION_END_UNBOUNDED_FOLLOWING) + monotonic |= MONOTONICFUNC_DECREASING; + } + + req->monotonic = monotonic; + PG_RETURN_POINTER(req); + } + + PG_RETURN_POINTER(NULL); +} + + +Datum +int8larger(PG_FUNCTION_ARGS) +{ + int64 arg1 = PG_GETARG_INT64(0); + int64 arg2 = PG_GETARG_INT64(1); + int64 result; + + result = ((arg1 > arg2) ? arg1 : arg2); + + PG_RETURN_INT64(result); +} + +Datum +int8smaller(PG_FUNCTION_ARGS) +{ + int64 arg1 = PG_GETARG_INT64(0); + int64 arg2 = PG_GETARG_INT64(1); + int64 result; + + result = ((arg1 < arg2) ? arg1 : arg2); + + PG_RETURN_INT64(result); +} + +Datum +int84pl(PG_FUNCTION_ARGS) +{ + int64 arg1 = PG_GETARG_INT64(0); + int32 arg2 = PG_GETARG_INT32(1); + int64 result; + + if (unlikely(pg_add_s64_overflow(arg1, (int64) arg2, &result))) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("bigint out of range"))); + PG_RETURN_INT64(result); +} + +Datum +int84mi(PG_FUNCTION_ARGS) +{ + int64 arg1 = PG_GETARG_INT64(0); + int32 arg2 = PG_GETARG_INT32(1); + int64 result; + + if (unlikely(pg_sub_s64_overflow(arg1, (int64) arg2, &result))) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("bigint out of range"))); + PG_RETURN_INT64(result); +} + +Datum +int84mul(PG_FUNCTION_ARGS) +{ + int64 arg1 = PG_GETARG_INT64(0); + int32 arg2 = PG_GETARG_INT32(1); + int64 result; + + if (unlikely(pg_mul_s64_overflow(arg1, (int64) arg2, &result))) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("bigint out of range"))); + PG_RETURN_INT64(result); +} + +Datum +int84div(PG_FUNCTION_ARGS) +{ + int64 arg1 = PG_GETARG_INT64(0); + int32 arg2 = PG_GETARG_INT32(1); + int64 result; + + if (arg2 == 0) + { + ereport(ERROR, + (errcode(ERRCODE_DIVISION_BY_ZERO), + errmsg("division by zero"))); + /* ensure compiler realizes we mustn't reach the division (gcc bug) */ + PG_RETURN_NULL(); + } + + /* + * INT64_MIN / -1 is problematic, since the result can't be represented on + * a two's-complement machine. Some machines produce INT64_MIN, some + * produce zero, some throw an exception. We can dodge the problem by + * recognizing that division by -1 is the same as negation. + */ + if (arg2 == -1) + { + if (unlikely(arg1 == PG_INT64_MIN)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("bigint out of range"))); + result = -arg1; + PG_RETURN_INT64(result); + } + + /* No overflow is possible */ + + result = arg1 / arg2; + + PG_RETURN_INT64(result); +} + +Datum +int48pl(PG_FUNCTION_ARGS) +{ + int32 arg1 = PG_GETARG_INT32(0); + int64 arg2 = PG_GETARG_INT64(1); + int64 result; + + if (unlikely(pg_add_s64_overflow((int64) arg1, arg2, &result))) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("bigint out of range"))); + PG_RETURN_INT64(result); +} + +Datum +int48mi(PG_FUNCTION_ARGS) +{ + int32 arg1 = PG_GETARG_INT32(0); + int64 arg2 = PG_GETARG_INT64(1); + int64 result; + + if (unlikely(pg_sub_s64_overflow((int64) arg1, arg2, &result))) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("bigint out of range"))); + PG_RETURN_INT64(result); +} + +Datum +int48mul(PG_FUNCTION_ARGS) +{ + int32 arg1 = PG_GETARG_INT32(0); + int64 arg2 = PG_GETARG_INT64(1); + int64 result; + + if (unlikely(pg_mul_s64_overflow((int64) arg1, arg2, &result))) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("bigint out of range"))); + PG_RETURN_INT64(result); +} + +Datum +int48div(PG_FUNCTION_ARGS) +{ + int32 arg1 = PG_GETARG_INT32(0); + int64 arg2 = PG_GETARG_INT64(1); + + if (unlikely(arg2 == 0)) + { + ereport(ERROR, + (errcode(ERRCODE_DIVISION_BY_ZERO), + errmsg("division by zero"))); + /* ensure compiler realizes we mustn't reach the division (gcc bug) */ + PG_RETURN_NULL(); + } + + /* No overflow is possible */ + PG_RETURN_INT64((int64) arg1 / arg2); +} + +Datum +int82pl(PG_FUNCTION_ARGS) +{ + int64 arg1 = PG_GETARG_INT64(0); + int16 arg2 = PG_GETARG_INT16(1); + int64 result; + + if (unlikely(pg_add_s64_overflow(arg1, (int64) arg2, &result))) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("bigint out of range"))); + PG_RETURN_INT64(result); +} + +Datum +int82mi(PG_FUNCTION_ARGS) +{ + int64 arg1 = PG_GETARG_INT64(0); + int16 arg2 = PG_GETARG_INT16(1); + int64 result; + + if (unlikely(pg_sub_s64_overflow(arg1, (int64) arg2, &result))) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("bigint out of range"))); + PG_RETURN_INT64(result); +} + +Datum +int82mul(PG_FUNCTION_ARGS) +{ + int64 arg1 = PG_GETARG_INT64(0); + int16 arg2 = PG_GETARG_INT16(1); + int64 result; + + if (unlikely(pg_mul_s64_overflow(arg1, (int64) arg2, &result))) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("bigint out of range"))); + PG_RETURN_INT64(result); +} + +Datum +int82div(PG_FUNCTION_ARGS) +{ + int64 arg1 = PG_GETARG_INT64(0); + int16 arg2 = PG_GETARG_INT16(1); + int64 result; + + if (unlikely(arg2 == 0)) + { + ereport(ERROR, + (errcode(ERRCODE_DIVISION_BY_ZERO), + errmsg("division by zero"))); + /* ensure compiler realizes we mustn't reach the division (gcc bug) */ + PG_RETURN_NULL(); + } + + /* + * INT64_MIN / -1 is problematic, since the result can't be represented on + * a two's-complement machine. Some machines produce INT64_MIN, some + * produce zero, some throw an exception. We can dodge the problem by + * recognizing that division by -1 is the same as negation. + */ + if (arg2 == -1) + { + if (unlikely(arg1 == PG_INT64_MIN)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("bigint out of range"))); + result = -arg1; + PG_RETURN_INT64(result); + } + + /* No overflow is possible */ + + result = arg1 / arg2; + + PG_RETURN_INT64(result); +} + +Datum +int28pl(PG_FUNCTION_ARGS) +{ + int16 arg1 = PG_GETARG_INT16(0); + int64 arg2 = PG_GETARG_INT64(1); + int64 result; + + if (unlikely(pg_add_s64_overflow((int64) arg1, arg2, &result))) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("bigint out of range"))); + PG_RETURN_INT64(result); +} + +Datum +int28mi(PG_FUNCTION_ARGS) +{ + int16 arg1 = PG_GETARG_INT16(0); + int64 arg2 = PG_GETARG_INT64(1); + int64 result; + + if (unlikely(pg_sub_s64_overflow((int64) arg1, arg2, &result))) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("bigint out of range"))); + PG_RETURN_INT64(result); +} + +Datum +int28mul(PG_FUNCTION_ARGS) +{ + int16 arg1 = PG_GETARG_INT16(0); + int64 arg2 = PG_GETARG_INT64(1); + int64 result; + + if (unlikely(pg_mul_s64_overflow((int64) arg1, arg2, &result))) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("bigint out of range"))); + PG_RETURN_INT64(result); +} + +Datum +int28div(PG_FUNCTION_ARGS) +{ + int16 arg1 = PG_GETARG_INT16(0); + int64 arg2 = PG_GETARG_INT64(1); + + if (unlikely(arg2 == 0)) + { + ereport(ERROR, + (errcode(ERRCODE_DIVISION_BY_ZERO), + errmsg("division by zero"))); + /* ensure compiler realizes we mustn't reach the division (gcc bug) */ + PG_RETURN_NULL(); + } + + /* No overflow is possible */ + PG_RETURN_INT64((int64) arg1 / arg2); +} + +/* Binary arithmetics + * + * int8and - returns arg1 & arg2 + * int8or - returns arg1 | arg2 + * int8xor - returns arg1 # arg2 + * int8not - returns ~arg1 + * int8shl - returns arg1 << arg2 + * int8shr - returns arg1 >> arg2 + */ + +Datum +int8and(PG_FUNCTION_ARGS) +{ + int64 arg1 = PG_GETARG_INT64(0); + int64 arg2 = PG_GETARG_INT64(1); + + PG_RETURN_INT64(arg1 & arg2); +} + +Datum +int8or(PG_FUNCTION_ARGS) +{ + int64 arg1 = PG_GETARG_INT64(0); + int64 arg2 = PG_GETARG_INT64(1); + + PG_RETURN_INT64(arg1 | arg2); +} + +Datum +int8xor(PG_FUNCTION_ARGS) +{ + int64 arg1 = PG_GETARG_INT64(0); + int64 arg2 = PG_GETARG_INT64(1); + + PG_RETURN_INT64(arg1 ^ arg2); +} + +Datum +int8not(PG_FUNCTION_ARGS) +{ + int64 arg1 = PG_GETARG_INT64(0); + + PG_RETURN_INT64(~arg1); +} + +Datum +int8shl(PG_FUNCTION_ARGS) +{ + int64 arg1 = PG_GETARG_INT64(0); + int32 arg2 = PG_GETARG_INT32(1); + + PG_RETURN_INT64(arg1 << arg2); +} + +Datum +int8shr(PG_FUNCTION_ARGS) +{ + int64 arg1 = PG_GETARG_INT64(0); + int32 arg2 = PG_GETARG_INT32(1); + + PG_RETURN_INT64(arg1 >> arg2); +} + +/*---------------------------------------------------------- + * Conversion operators. + *---------------------------------------------------------*/ + +Datum +int48(PG_FUNCTION_ARGS) +{ + int32 arg = PG_GETARG_INT32(0); + + PG_RETURN_INT64((int64) arg); +} + +Datum +int84(PG_FUNCTION_ARGS) +{ + int64 arg = PG_GETARG_INT64(0); + + if (unlikely(arg < PG_INT32_MIN) || unlikely(arg > PG_INT32_MAX)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("integer out of range"))); + + PG_RETURN_INT32((int32) arg); +} + +Datum +int28(PG_FUNCTION_ARGS) +{ + int16 arg = PG_GETARG_INT16(0); + + PG_RETURN_INT64((int64) arg); +} + +Datum +int82(PG_FUNCTION_ARGS) +{ + int64 arg = PG_GETARG_INT64(0); + + if (unlikely(arg < PG_INT16_MIN) || unlikely(arg > PG_INT16_MAX)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("smallint out of range"))); + + PG_RETURN_INT16((int16) arg); +} + +Datum +i8tod(PG_FUNCTION_ARGS) +{ + int64 arg = PG_GETARG_INT64(0); + float8 result; + + result = arg; + + PG_RETURN_FLOAT8(result); +} + +/* dtoi8() + * Convert float8 to 8-byte integer. + */ +Datum +dtoi8(PG_FUNCTION_ARGS) +{ + float8 num = PG_GETARG_FLOAT8(0); + + /* + * Get rid of any fractional part in the input. This is so we don't fail + * on just-out-of-range values that would round into range. Note + * assumption that rint() will pass through a NaN or Inf unchanged. + */ + num = rint(num); + + /* Range check */ + if (unlikely(isnan(num) || !FLOAT8_FITS_IN_INT64(num))) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("bigint out of range"))); + + PG_RETURN_INT64((int64) num); +} + +Datum +i8tof(PG_FUNCTION_ARGS) +{ + int64 arg = PG_GETARG_INT64(0); + float4 result; + + result = arg; + + PG_RETURN_FLOAT4(result); +} + +/* ftoi8() + * Convert float4 to 8-byte integer. + */ +Datum +ftoi8(PG_FUNCTION_ARGS) +{ + float4 num = PG_GETARG_FLOAT4(0); + + /* + * Get rid of any fractional part in the input. This is so we don't fail + * on just-out-of-range values that would round into range. Note + * assumption that rint() will pass through a NaN or Inf unchanged. + */ + num = rint(num); + + /* Range check */ + if (unlikely(isnan(num) || !FLOAT4_FITS_IN_INT64(num))) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("bigint out of range"))); + + PG_RETURN_INT64((int64) num); +} + +Datum +i8tooid(PG_FUNCTION_ARGS) +{ + int64 arg = PG_GETARG_INT64(0); + + if (unlikely(arg < 0) || unlikely(arg > PG_UINT32_MAX)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("OID out of range"))); + + PG_RETURN_OID((Oid) arg); +} + +Datum +oidtoi8(PG_FUNCTION_ARGS) +{ + Oid arg = PG_GETARG_OID(0); + + PG_RETURN_INT64((int64) arg); +} + +/* + * non-persistent numeric series generator + */ +Datum +generate_series_int8(PG_FUNCTION_ARGS) +{ + return generate_series_step_int8(fcinfo); +} + +Datum +generate_series_step_int8(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + generate_series_fctx *fctx; + int64 result; + MemoryContext oldcontext; + + /* stuff done only on the first call of the function */ + if (SRF_IS_FIRSTCALL()) + { + int64 start = PG_GETARG_INT64(0); + int64 finish = PG_GETARG_INT64(1); + int64 step = 1; + + /* see if we were given an explicit step size */ + if (PG_NARGS() == 3) + step = PG_GETARG_INT64(2); + if (step == 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("step size cannot equal zero"))); + + /* 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 */ + fctx = (generate_series_fctx *) palloc(sizeof(generate_series_fctx)); + + /* + * Use fctx to keep state from call to call. Seed current with the + * original start value + */ + fctx->current = start; + fctx->finish = finish; + fctx->step = step; + + funcctx->user_fctx = fctx; + MemoryContextSwitchTo(oldcontext); + } + + /* stuff done on every call of the function */ + funcctx = SRF_PERCALL_SETUP(); + + /* + * get the saved state and use current as the result for this iteration + */ + fctx = funcctx->user_fctx; + result = fctx->current; + + if ((fctx->step > 0 && fctx->current <= fctx->finish) || + (fctx->step < 0 && fctx->current >= fctx->finish)) + { + /* + * Increment current in preparation for next iteration. If next-value + * computation overflows, this is the final result. + */ + if (pg_add_s64_overflow(fctx->current, fctx->step, &fctx->current)) + fctx->step = 0; + + /* do when there is more left to send */ + SRF_RETURN_NEXT(funcctx, Int64GetDatum(result)); + } + else + /* do when there is no more left */ + SRF_RETURN_DONE(funcctx); +} + +/* + * Planner support function for generate_series(int8, int8 [, int8]) + */ +Datum +generate_series_int8_support(PG_FUNCTION_ARGS) +{ + Node *rawreq = (Node *) PG_GETARG_POINTER(0); + Node *ret = NULL; + + if (IsA(rawreq, SupportRequestRows)) + { + /* Try to estimate the number of rows returned */ + SupportRequestRows *req = (SupportRequestRows *) rawreq; + + if (is_funcclause(req->node)) /* be paranoid */ + { + List *args = ((FuncExpr *) req->node)->args; + Node *arg1, + *arg2, + *arg3; + + /* We can use estimated argument values here */ + arg1 = estimate_expression_value(req->root, linitial(args)); + arg2 = estimate_expression_value(req->root, lsecond(args)); + if (list_length(args) >= 3) + arg3 = estimate_expression_value(req->root, lthird(args)); + else + arg3 = NULL; + + /* + * If any argument is constant NULL, we can safely assume that + * zero rows are returned. Otherwise, if they're all non-NULL + * constants, we can calculate the number of rows that will be + * returned. Use double arithmetic to avoid overflow hazards. + */ + if ((IsA(arg1, Const) && + ((Const *) arg1)->constisnull) || + (IsA(arg2, Const) && + ((Const *) arg2)->constisnull) || + (arg3 != NULL && IsA(arg3, Const) && + ((Const *) arg3)->constisnull)) + { + req->rows = 0; + ret = (Node *) req; + } + else if (IsA(arg1, Const) && + IsA(arg2, Const) && + (arg3 == NULL || IsA(arg3, Const))) + { + double start, + finish, + step; + + start = DatumGetInt64(((Const *) arg1)->constvalue); + finish = DatumGetInt64(((Const *) arg2)->constvalue); + step = arg3 ? DatumGetInt64(((Const *) arg3)->constvalue) : 1; + + /* This equation works for either sign of step */ + if (step != 0) + { + req->rows = floor((finish - start + step) / step); + ret = (Node *) req; + } + } + } + } + + PG_RETURN_POINTER(ret); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/json.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/json.c new file mode 100644 index 00000000000..7205f4adca8 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/json.c @@ -0,0 +1,1825 @@ +/*------------------------------------------------------------------------- + * + * json.c + * JSON data type support. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/utils/adt/json.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "catalog/pg_proc.h" +#include "catalog/pg_type.h" +#include "common/hashfn.h" +#include "funcapi.h" +#include "libpq/pqformat.h" +#include "miscadmin.h" +#include "parser/parse_coerce.h" +#include "utils/array.h" +#include "utils/builtins.h" +#include "utils/date.h" +#include "utils/datetime.h" +#include "utils/json.h" +#include "utils/jsonfuncs.h" +#include "utils/lsyscache.h" +#include "utils/typcache.h" + +typedef enum /* type categories for datum_to_json */ +{ + JSONTYPE_NULL, /* null, so we didn't bother to identify */ + JSONTYPE_BOOL, /* boolean (built-in types only) */ + JSONTYPE_NUMERIC, /* numeric (ditto) */ + JSONTYPE_DATE, /* we use special formatting for datetimes */ + JSONTYPE_TIMESTAMP, + JSONTYPE_TIMESTAMPTZ, + JSONTYPE_JSON, /* JSON itself (and JSONB) */ + JSONTYPE_ARRAY, /* array */ + JSONTYPE_COMPOSITE, /* composite */ + JSONTYPE_CAST, /* something with an explicit cast to JSON */ + JSONTYPE_OTHER /* all else */ +} JsonTypeCategory; + + +/* + * Support for fast key uniqueness checking. + * + * We maintain a hash table of used keys in JSON objects for fast detection + * of duplicates. + */ +/* Common context for key uniqueness check */ +typedef struct HTAB *JsonUniqueCheckState; /* hash table for key names */ + +/* Hash entry for JsonUniqueCheckState */ +typedef struct JsonUniqueHashEntry +{ + const char *key; + int key_len; + int object_id; +} JsonUniqueHashEntry; + +/* Stack element for key uniqueness check during JSON parsing */ +typedef struct JsonUniqueStackEntry +{ + struct JsonUniqueStackEntry *parent; + int object_id; +} JsonUniqueStackEntry; + +/* Context struct for key uniqueness check during JSON parsing */ +typedef struct JsonUniqueParsingState +{ + JsonLexContext *lex; + JsonUniqueCheckState check; + JsonUniqueStackEntry *stack; + int id_counter; + bool unique; +} JsonUniqueParsingState; + +/* Context struct for key uniqueness check during JSON building */ +typedef struct JsonUniqueBuilderState +{ + JsonUniqueCheckState check; /* unique check */ + StringInfoData skipped_keys; /* skipped keys with NULL values */ + MemoryContext mcxt; /* context for saving skipped keys */ +} JsonUniqueBuilderState; + + +/* State struct for JSON aggregation */ +typedef struct JsonAggState +{ + StringInfo str; + JsonTypeCategory key_category; + Oid key_output_func; + JsonTypeCategory val_category; + Oid val_output_func; + JsonUniqueBuilderState unique_check; +} JsonAggState; + +static void composite_to_json(Datum composite, StringInfo result, + bool use_line_feeds); +static void array_dim_to_json(StringInfo result, int dim, int ndims, int *dims, + Datum *vals, bool *nulls, int *valcount, + JsonTypeCategory tcategory, Oid outfuncoid, + bool use_line_feeds); +static void array_to_json_internal(Datum array, StringInfo result, + bool use_line_feeds); +static void json_categorize_type(Oid typoid, + JsonTypeCategory *tcategory, + Oid *outfuncoid); +static void datum_to_json(Datum val, bool is_null, StringInfo result, + JsonTypeCategory tcategory, Oid outfuncoid, + bool key_scalar); +static void add_json(Datum val, bool is_null, StringInfo result, + Oid val_type, bool key_scalar); +static text *catenate_stringinfo_string(StringInfo buffer, const char *addon); + +/* + * Input. + */ +Datum +json_in(PG_FUNCTION_ARGS) +{ + char *json = PG_GETARG_CSTRING(0); + text *result = cstring_to_text(json); + JsonLexContext *lex; + + /* validate it */ + lex = makeJsonLexContext(result, false); + if (!pg_parse_json_or_errsave(lex, &nullSemAction, fcinfo->context)) + PG_RETURN_NULL(); + + /* Internal representation is the same as text */ + PG_RETURN_TEXT_P(result); +} + +/* + * Output. + */ +Datum +json_out(PG_FUNCTION_ARGS) +{ + /* we needn't detoast because text_to_cstring will handle that */ + Datum txt = PG_GETARG_DATUM(0); + + PG_RETURN_CSTRING(TextDatumGetCString(txt)); +} + +/* + * Binary send. + */ +Datum +json_send(PG_FUNCTION_ARGS) +{ + text *t = PG_GETARG_TEXT_PP(0); + StringInfoData buf; + + pq_begintypsend(&buf); + pq_sendtext(&buf, VARDATA_ANY(t), VARSIZE_ANY_EXHDR(t)); + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} + +/* + * Binary receive. + */ +Datum +json_recv(PG_FUNCTION_ARGS) +{ + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + char *str; + int nbytes; + JsonLexContext *lex; + + str = pq_getmsgtext(buf, buf->len - buf->cursor, &nbytes); + + /* Validate it. */ + lex = makeJsonLexContextCstringLen(str, nbytes, GetDatabaseEncoding(), false); + pg_parse_json_or_ereport(lex, &nullSemAction); + + PG_RETURN_TEXT_P(cstring_to_text_with_len(str, nbytes)); +} + +/* + * Determine how we want to print values of a given type in datum_to_json. + * + * Given the datatype OID, return its JsonTypeCategory, as well as the type's + * output function OID. If the returned category is JSONTYPE_CAST, we + * return the OID of the type->JSON cast function instead. + */ +static void +json_categorize_type(Oid typoid, + JsonTypeCategory *tcategory, + Oid *outfuncoid) +{ + bool typisvarlena; + + /* Look through any domain */ + typoid = getBaseType(typoid); + + *outfuncoid = InvalidOid; + + /* + * We need to get the output function for everything except date and + * timestamp types, array and composite types, booleans, and non-builtin + * types where there's a cast to json. + */ + + switch (typoid) + { + case BOOLOID: + *tcategory = JSONTYPE_BOOL; + break; + + case INT2OID: + case INT4OID: + case INT8OID: + case FLOAT4OID: + case FLOAT8OID: + case NUMERICOID: + getTypeOutputInfo(typoid, outfuncoid, &typisvarlena); + *tcategory = JSONTYPE_NUMERIC; + break; + + case DATEOID: + *tcategory = JSONTYPE_DATE; + break; + + case TIMESTAMPOID: + *tcategory = JSONTYPE_TIMESTAMP; + break; + + case TIMESTAMPTZOID: + *tcategory = JSONTYPE_TIMESTAMPTZ; + break; + + case JSONOID: + case JSONBOID: + getTypeOutputInfo(typoid, outfuncoid, &typisvarlena); + *tcategory = JSONTYPE_JSON; + break; + + default: + /* Check for arrays and composites */ + if (OidIsValid(get_element_type(typoid)) || typoid == ANYARRAYOID + || typoid == ANYCOMPATIBLEARRAYOID || typoid == RECORDARRAYOID) + *tcategory = JSONTYPE_ARRAY; + else if (type_is_rowtype(typoid)) /* includes RECORDOID */ + *tcategory = JSONTYPE_COMPOSITE; + else + { + /* It's probably the general case ... */ + *tcategory = JSONTYPE_OTHER; + /* but let's look for a cast to json, if it's not built-in */ + if (typoid >= FirstNormalObjectId) + { + Oid castfunc; + CoercionPathType ctype; + + ctype = find_coercion_pathway(JSONOID, typoid, + COERCION_EXPLICIT, + &castfunc); + if (ctype == COERCION_PATH_FUNC && OidIsValid(castfunc)) + { + *tcategory = JSONTYPE_CAST; + *outfuncoid = castfunc; + } + else + { + /* non builtin type with no cast */ + getTypeOutputInfo(typoid, outfuncoid, &typisvarlena); + } + } + else + { + /* any other builtin type */ + getTypeOutputInfo(typoid, outfuncoid, &typisvarlena); + } + } + break; + } +} + +/* + * Turn a Datum into JSON text, appending the string to "result". + * + * tcategory and outfuncoid are from a previous call to json_categorize_type, + * except that if is_null is true then they can be invalid. + * + * If key_scalar is true, the value is being printed as a key, so insist + * it's of an acceptable type, and force it to be quoted. + */ +static void +datum_to_json(Datum val, bool is_null, StringInfo result, + JsonTypeCategory tcategory, Oid outfuncoid, + bool key_scalar) +{ + char *outputstr; + text *jsontext; + + check_stack_depth(); + + /* callers are expected to ensure that null keys are not passed in */ + Assert(!(key_scalar && is_null)); + + if (is_null) + { + appendStringInfoString(result, "null"); + return; + } + + if (key_scalar && + (tcategory == JSONTYPE_ARRAY || + tcategory == JSONTYPE_COMPOSITE || + tcategory == JSONTYPE_JSON || + tcategory == JSONTYPE_CAST)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("key value must be scalar, not array, composite, or json"))); + + switch (tcategory) + { + case JSONTYPE_ARRAY: + array_to_json_internal(val, result, false); + break; + case JSONTYPE_COMPOSITE: + composite_to_json(val, result, false); + break; + case JSONTYPE_BOOL: + outputstr = DatumGetBool(val) ? "true" : "false"; + if (key_scalar) + escape_json(result, outputstr); + else + appendStringInfoString(result, outputstr); + break; + case JSONTYPE_NUMERIC: + outputstr = OidOutputFunctionCall(outfuncoid, val); + + /* + * Don't call escape_json for a non-key if it's a valid JSON + * number. + */ + if (!key_scalar && IsValidJsonNumber(outputstr, strlen(outputstr))) + appendStringInfoString(result, outputstr); + else + escape_json(result, outputstr); + pfree(outputstr); + break; + case JSONTYPE_DATE: + { + char buf[MAXDATELEN + 1]; + + JsonEncodeDateTime(buf, val, DATEOID, NULL); + appendStringInfo(result, "\"%s\"", buf); + } + break; + case JSONTYPE_TIMESTAMP: + { + char buf[MAXDATELEN + 1]; + + JsonEncodeDateTime(buf, val, TIMESTAMPOID, NULL); + appendStringInfo(result, "\"%s\"", buf); + } + break; + case JSONTYPE_TIMESTAMPTZ: + { + char buf[MAXDATELEN + 1]; + + JsonEncodeDateTime(buf, val, TIMESTAMPTZOID, NULL); + appendStringInfo(result, "\"%s\"", buf); + } + break; + case JSONTYPE_JSON: + /* JSON and JSONB output will already be escaped */ + outputstr = OidOutputFunctionCall(outfuncoid, val); + appendStringInfoString(result, outputstr); + pfree(outputstr); + break; + case JSONTYPE_CAST: + /* outfuncoid refers to a cast function, not an output function */ + jsontext = DatumGetTextPP(OidFunctionCall1(outfuncoid, val)); + outputstr = text_to_cstring(jsontext); + appendStringInfoString(result, outputstr); + pfree(outputstr); + pfree(jsontext); + break; + default: + outputstr = OidOutputFunctionCall(outfuncoid, val); + escape_json(result, outputstr); + pfree(outputstr); + break; + } +} + +/* + * Encode 'value' of datetime type 'typid' into JSON string in ISO format using + * optionally preallocated buffer 'buf'. Optional 'tzp' determines time-zone + * offset (in seconds) in which we want to show timestamptz. + */ +char * +JsonEncodeDateTime(char *buf, Datum value, Oid typid, const int *tzp) +{ + if (!buf) + buf = palloc(MAXDATELEN + 1); + + switch (typid) + { + case DATEOID: + { + DateADT date; + struct pg_tm tm; + + date = DatumGetDateADT(value); + + /* Same as date_out(), but forcing DateStyle */ + if (DATE_NOT_FINITE(date)) + EncodeSpecialDate(date, buf); + else + { + j2date(date + POSTGRES_EPOCH_JDATE, + &(tm.tm_year), &(tm.tm_mon), &(tm.tm_mday)); + EncodeDateOnly(&tm, USE_XSD_DATES, buf); + } + } + break; + case TIMEOID: + { + TimeADT time = DatumGetTimeADT(value); + struct pg_tm tt, + *tm = &tt; + fsec_t fsec; + + /* Same as time_out(), but forcing DateStyle */ + time2tm(time, tm, &fsec); + EncodeTimeOnly(tm, fsec, false, 0, USE_XSD_DATES, buf); + } + break; + case TIMETZOID: + { + TimeTzADT *time = DatumGetTimeTzADTP(value); + struct pg_tm tt, + *tm = &tt; + fsec_t fsec; + int tz; + + /* Same as timetz_out(), but forcing DateStyle */ + timetz2tm(time, tm, &fsec, &tz); + EncodeTimeOnly(tm, fsec, true, tz, USE_XSD_DATES, buf); + } + break; + case TIMESTAMPOID: + { + Timestamp timestamp; + struct pg_tm tm; + fsec_t fsec; + + timestamp = DatumGetTimestamp(value); + /* Same as timestamp_out(), but forcing DateStyle */ + if (TIMESTAMP_NOT_FINITE(timestamp)) + EncodeSpecialTimestamp(timestamp, buf); + else if (timestamp2tm(timestamp, NULL, &tm, &fsec, NULL, NULL) == 0) + EncodeDateTime(&tm, fsec, false, 0, NULL, USE_XSD_DATES, buf); + else + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + } + break; + case TIMESTAMPTZOID: + { + TimestampTz timestamp; + struct pg_tm tm; + int tz; + fsec_t fsec; + const char *tzn = NULL; + + timestamp = DatumGetTimestampTz(value); + + /* + * If a time zone is specified, we apply the time-zone shift, + * convert timestamptz to pg_tm as if it were without a time + * zone, and then use the specified time zone for converting + * the timestamp into a string. + */ + if (tzp) + { + tz = *tzp; + timestamp -= (TimestampTz) tz * USECS_PER_SEC; + } + + /* Same as timestamptz_out(), but forcing DateStyle */ + if (TIMESTAMP_NOT_FINITE(timestamp)) + EncodeSpecialTimestamp(timestamp, buf); + else if (timestamp2tm(timestamp, tzp ? NULL : &tz, &tm, &fsec, + tzp ? NULL : &tzn, NULL) == 0) + { + if (tzp) + tm.tm_isdst = 1; /* set time-zone presence flag */ + + EncodeDateTime(&tm, fsec, true, tz, tzn, USE_XSD_DATES, buf); + } + else + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + } + break; + default: + elog(ERROR, "unknown jsonb value datetime type oid %u", typid); + return NULL; + } + + return buf; +} + +/* + * Process a single dimension of an array. + * If it's the innermost dimension, output the values, otherwise call + * ourselves recursively to process the next dimension. + */ +static void +array_dim_to_json(StringInfo result, int dim, int ndims, int *dims, Datum *vals, + bool *nulls, int *valcount, JsonTypeCategory tcategory, + Oid outfuncoid, bool use_line_feeds) +{ + int i; + const char *sep; + + Assert(dim < ndims); + + sep = use_line_feeds ? ",\n " : ","; + + appendStringInfoChar(result, '['); + + for (i = 1; i <= dims[dim]; i++) + { + if (i > 1) + appendStringInfoString(result, sep); + + if (dim + 1 == ndims) + { + datum_to_json(vals[*valcount], nulls[*valcount], result, tcategory, + outfuncoid, false); + (*valcount)++; + } + else + { + /* + * Do we want line feeds on inner dimensions of arrays? For now + * we'll say no. + */ + array_dim_to_json(result, dim + 1, ndims, dims, vals, nulls, + valcount, tcategory, outfuncoid, false); + } + } + + appendStringInfoChar(result, ']'); +} + +/* + * Turn an array into JSON. + */ +static void +array_to_json_internal(Datum array, StringInfo result, bool use_line_feeds) +{ + ArrayType *v = DatumGetArrayTypeP(array); + Oid element_type = ARR_ELEMTYPE(v); + int *dim; + int ndim; + int nitems; + int count = 0; + Datum *elements; + bool *nulls; + int16 typlen; + bool typbyval; + char typalign; + JsonTypeCategory tcategory; + Oid outfuncoid; + + ndim = ARR_NDIM(v); + dim = ARR_DIMS(v); + nitems = ArrayGetNItems(ndim, dim); + + if (nitems <= 0) + { + appendStringInfoString(result, "[]"); + return; + } + + get_typlenbyvalalign(element_type, + &typlen, &typbyval, &typalign); + + json_categorize_type(element_type, + &tcategory, &outfuncoid); + + deconstruct_array(v, element_type, typlen, typbyval, + typalign, &elements, &nulls, + &nitems); + + array_dim_to_json(result, 0, ndim, dim, elements, nulls, &count, tcategory, + outfuncoid, use_line_feeds); + + pfree(elements); + pfree(nulls); +} + +/* + * Turn a composite / record into JSON. + */ +static void +composite_to_json(Datum composite, StringInfo result, bool use_line_feeds) +{ + HeapTupleHeader td; + Oid tupType; + int32 tupTypmod; + TupleDesc tupdesc; + HeapTupleData tmptup, + *tuple; + int i; + bool needsep = false; + const char *sep; + + sep = use_line_feeds ? ",\n " : ","; + + td = DatumGetHeapTupleHeader(composite); + + /* Extract rowtype info and find a tupdesc */ + tupType = HeapTupleHeaderGetTypeId(td); + tupTypmod = HeapTupleHeaderGetTypMod(td); + tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); + + /* Build a temporary HeapTuple control structure */ + tmptup.t_len = HeapTupleHeaderGetDatumLength(td); + tmptup.t_data = td; + tuple = &tmptup; + + appendStringInfoChar(result, '{'); + + for (i = 0; i < tupdesc->natts; i++) + { + Datum val; + bool isnull; + char *attname; + JsonTypeCategory tcategory; + Oid outfuncoid; + Form_pg_attribute att = TupleDescAttr(tupdesc, i); + + if (att->attisdropped) + continue; + + if (needsep) + appendStringInfoString(result, sep); + needsep = true; + + attname = NameStr(att->attname); + escape_json(result, attname); + appendStringInfoChar(result, ':'); + + val = heap_getattr(tuple, i + 1, tupdesc, &isnull); + + if (isnull) + { + tcategory = JSONTYPE_NULL; + outfuncoid = InvalidOid; + } + else + json_categorize_type(att->atttypid, &tcategory, &outfuncoid); + + datum_to_json(val, isnull, result, tcategory, outfuncoid, false); + } + + appendStringInfoChar(result, '}'); + ReleaseTupleDesc(tupdesc); +} + +/* + * Append JSON text for "val" to "result". + * + * This is just a thin wrapper around datum_to_json. If the same type will be + * printed many times, avoid using this; better to do the json_categorize_type + * lookups only once. + */ +static void +add_json(Datum val, bool is_null, StringInfo result, + Oid val_type, bool key_scalar) +{ + JsonTypeCategory tcategory; + Oid outfuncoid; + + if (val_type == InvalidOid) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("could not determine input data type"))); + + if (is_null) + { + tcategory = JSONTYPE_NULL; + outfuncoid = InvalidOid; + } + else + json_categorize_type(val_type, + &tcategory, &outfuncoid); + + datum_to_json(val, is_null, result, tcategory, outfuncoid, key_scalar); +} + +/* + * SQL function array_to_json(row) + */ +Datum +array_to_json(PG_FUNCTION_ARGS) +{ + Datum array = PG_GETARG_DATUM(0); + StringInfo result; + + result = makeStringInfo(); + + array_to_json_internal(array, result, false); + + PG_RETURN_TEXT_P(cstring_to_text_with_len(result->data, result->len)); +} + +/* + * SQL function array_to_json(row, prettybool) + */ +Datum +array_to_json_pretty(PG_FUNCTION_ARGS) +{ + Datum array = PG_GETARG_DATUM(0); + bool use_line_feeds = PG_GETARG_BOOL(1); + StringInfo result; + + result = makeStringInfo(); + + array_to_json_internal(array, result, use_line_feeds); + + PG_RETURN_TEXT_P(cstring_to_text_with_len(result->data, result->len)); +} + +/* + * SQL function row_to_json(row) + */ +Datum +row_to_json(PG_FUNCTION_ARGS) +{ + Datum array = PG_GETARG_DATUM(0); + StringInfo result; + + result = makeStringInfo(); + + composite_to_json(array, result, false); + + PG_RETURN_TEXT_P(cstring_to_text_with_len(result->data, result->len)); +} + +/* + * SQL function row_to_json(row, prettybool) + */ +Datum +row_to_json_pretty(PG_FUNCTION_ARGS) +{ + Datum array = PG_GETARG_DATUM(0); + bool use_line_feeds = PG_GETARG_BOOL(1); + StringInfo result; + + result = makeStringInfo(); + + composite_to_json(array, result, use_line_feeds); + + PG_RETURN_TEXT_P(cstring_to_text_with_len(result->data, result->len)); +} + +/* + * Is the given type immutable when coming out of a JSON context? + * + * At present, datetimes are all considered mutable, because they + * depend on timezone. XXX we should also drill down into objects + * and arrays, but do not. + */ +bool +to_json_is_immutable(Oid typoid) +{ + JsonTypeCategory tcategory; + Oid outfuncoid; + + json_categorize_type(typoid, &tcategory, &outfuncoid); + + switch (tcategory) + { + case JSONTYPE_BOOL: + case JSONTYPE_JSON: + case JSONTYPE_NULL: + return true; + + case JSONTYPE_DATE: + case JSONTYPE_TIMESTAMP: + case JSONTYPE_TIMESTAMPTZ: + return false; + + case JSONTYPE_ARRAY: + return false; /* TODO recurse into elements */ + + case JSONTYPE_COMPOSITE: + return false; /* TODO recurse into fields */ + + case JSONTYPE_NUMERIC: + case JSONTYPE_CAST: + case JSONTYPE_OTHER: + return func_volatile(outfuncoid) == PROVOLATILE_IMMUTABLE; + } + + return false; /* not reached */ +} + +/* + * SQL function to_json(anyvalue) + */ +Datum +to_json(PG_FUNCTION_ARGS) +{ + Datum val = PG_GETARG_DATUM(0); + Oid val_type = get_fn_expr_argtype(fcinfo->flinfo, 0); + StringInfo result; + JsonTypeCategory tcategory; + Oid outfuncoid; + + if (val_type == InvalidOid) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("could not determine input data type"))); + + json_categorize_type(val_type, + &tcategory, &outfuncoid); + + result = makeStringInfo(); + + datum_to_json(val, false, result, tcategory, outfuncoid, false); + + PG_RETURN_TEXT_P(cstring_to_text_with_len(result->data, result->len)); +} + +/* + * json_agg transition function + * + * aggregate input column as a json array value. + */ +static Datum +json_agg_transfn_worker(FunctionCallInfo fcinfo, bool absent_on_null) +{ + MemoryContext aggcontext, + oldcontext; + JsonAggState *state; + Datum val; + + if (!AggCheckCallContext(fcinfo, &aggcontext)) + { + /* cannot be called directly because of internal-type argument */ + elog(ERROR, "json_agg_transfn called in non-aggregate context"); + } + + if (PG_ARGISNULL(0)) + { + Oid arg_type = get_fn_expr_argtype(fcinfo->flinfo, 1); + + if (arg_type == InvalidOid) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("could not determine input data type"))); + + /* + * Make this state object in a context where it will persist for the + * duration of the aggregate call. MemoryContextSwitchTo is only + * needed the first time, as the StringInfo routines make sure they + * use the right context to enlarge the object if necessary. + */ + oldcontext = MemoryContextSwitchTo(aggcontext); + state = (JsonAggState *) palloc(sizeof(JsonAggState)); + state->str = makeStringInfo(); + MemoryContextSwitchTo(oldcontext); + + appendStringInfoChar(state->str, '['); + json_categorize_type(arg_type, &state->val_category, + &state->val_output_func); + } + else + { + state = (JsonAggState *) PG_GETARG_POINTER(0); + } + + if (absent_on_null && PG_ARGISNULL(1)) + PG_RETURN_POINTER(state); + + if (state->str->len > 1) + appendStringInfoString(state->str, ", "); + + /* fast path for NULLs */ + if (PG_ARGISNULL(1)) + { + datum_to_json((Datum) 0, true, state->str, JSONTYPE_NULL, + InvalidOid, false); + PG_RETURN_POINTER(state); + } + + val = PG_GETARG_DATUM(1); + + /* add some whitespace if structured type and not first item */ + if (!PG_ARGISNULL(0) && state->str->len > 1 && + (state->val_category == JSONTYPE_ARRAY || + state->val_category == JSONTYPE_COMPOSITE)) + { + appendStringInfoString(state->str, "\n "); + } + + datum_to_json(val, false, state->str, state->val_category, + state->val_output_func, false); + + /* + * The transition type for json_agg() is declared to be "internal", which + * is a pass-by-value type the same size as a pointer. So we can safely + * pass the JsonAggState pointer through nodeAgg.c's machinations. + */ + PG_RETURN_POINTER(state); +} + + +/* + * json_agg aggregate function + */ +Datum +json_agg_transfn(PG_FUNCTION_ARGS) +{ + return json_agg_transfn_worker(fcinfo, false); +} + +/* + * json_agg_strict aggregate function + */ +Datum +json_agg_strict_transfn(PG_FUNCTION_ARGS) +{ + return json_agg_transfn_worker(fcinfo, true); +} + +/* + * json_agg final function + */ +Datum +json_agg_finalfn(PG_FUNCTION_ARGS) +{ + JsonAggState *state; + + /* cannot be called directly because of internal-type argument */ + Assert(AggCheckCallContext(fcinfo, NULL)); + + state = PG_ARGISNULL(0) ? + NULL : + (JsonAggState *) PG_GETARG_POINTER(0); + + /* NULL result for no rows in, as is standard with aggregates */ + if (state == NULL) + PG_RETURN_NULL(); + + /* Else return state with appropriate array terminator added */ + PG_RETURN_TEXT_P(catenate_stringinfo_string(state->str, "]")); +} + +/* Functions implementing hash table for key uniqueness check */ +static uint32 +json_unique_hash(const void *key, Size keysize) +{ + const JsonUniqueHashEntry *entry = (JsonUniqueHashEntry *) key; + uint32 hash = hash_bytes_uint32(entry->object_id); + + hash ^= hash_bytes((const unsigned char *) entry->key, entry->key_len); + + return DatumGetUInt32(hash); +} + +static int +json_unique_hash_match(const void *key1, const void *key2, Size keysize) +{ + const JsonUniqueHashEntry *entry1 = (const JsonUniqueHashEntry *) key1; + const JsonUniqueHashEntry *entry2 = (const JsonUniqueHashEntry *) key2; + + if (entry1->object_id != entry2->object_id) + return entry1->object_id > entry2->object_id ? 1 : -1; + + if (entry1->key_len != entry2->key_len) + return entry1->key_len > entry2->key_len ? 1 : -1; + + return strncmp(entry1->key, entry2->key, entry1->key_len); +} + +/* + * Uniqueness detection support. + * + * In order to detect uniqueness during building or parsing of a JSON + * object, we maintain a hash table of key names already seen. + */ +static void +json_unique_check_init(JsonUniqueCheckState *cxt) +{ + HASHCTL ctl; + + memset(&ctl, 0, sizeof(ctl)); + ctl.keysize = sizeof(JsonUniqueHashEntry); + ctl.entrysize = sizeof(JsonUniqueHashEntry); + ctl.hcxt = CurrentMemoryContext; + ctl.hash = json_unique_hash; + ctl.match = json_unique_hash_match; + + *cxt = hash_create("json object hashtable", + 32, + &ctl, + HASH_ELEM | HASH_CONTEXT | HASH_FUNCTION | HASH_COMPARE); +} + +static void +json_unique_builder_init(JsonUniqueBuilderState *cxt) +{ + json_unique_check_init(&cxt->check); + cxt->mcxt = CurrentMemoryContext; + cxt->skipped_keys.data = NULL; +} + +static bool +json_unique_check_key(JsonUniqueCheckState *cxt, const char *key, int object_id) +{ + JsonUniqueHashEntry entry; + bool found; + + entry.key = key; + entry.key_len = strlen(key); + entry.object_id = object_id; + + (void) hash_search(*cxt, &entry, HASH_ENTER, &found); + + return !found; +} + +/* + * On-demand initialization of a throwaway StringInfo. This is used to + * read a key name that we don't need to store in the output object, for + * duplicate key detection when the value is NULL. + */ +static StringInfo +json_unique_builder_get_throwawaybuf(JsonUniqueBuilderState *cxt) +{ + StringInfo out = &cxt->skipped_keys; + + if (!out->data) + { + MemoryContext oldcxt = MemoryContextSwitchTo(cxt->mcxt); + + initStringInfo(out); + MemoryContextSwitchTo(oldcxt); + } + else + /* Just reset the string to empty */ + out->len = 0; + + return out; +} + +/* + * json_object_agg transition function. + * + * aggregate two input columns as a single json object value. + */ +static Datum +json_object_agg_transfn_worker(FunctionCallInfo fcinfo, + bool absent_on_null, bool unique_keys) +{ + MemoryContext aggcontext, + oldcontext; + JsonAggState *state; + StringInfo out; + Datum arg; + bool skip; + int key_offset; + + if (!AggCheckCallContext(fcinfo, &aggcontext)) + { + /* cannot be called directly because of internal-type argument */ + elog(ERROR, "json_object_agg_transfn called in non-aggregate context"); + } + + if (PG_ARGISNULL(0)) + { + Oid arg_type; + + /* + * Make the StringInfo in a context where it will persist for the + * duration of the aggregate call. Switching context is only needed + * for this initial step, as the StringInfo and dynahash routines make + * sure they use the right context to enlarge the object if necessary. + */ + oldcontext = MemoryContextSwitchTo(aggcontext); + state = (JsonAggState *) palloc(sizeof(JsonAggState)); + state->str = makeStringInfo(); + if (unique_keys) + json_unique_builder_init(&state->unique_check); + else + memset(&state->unique_check, 0, sizeof(state->unique_check)); + MemoryContextSwitchTo(oldcontext); + + arg_type = get_fn_expr_argtype(fcinfo->flinfo, 1); + + if (arg_type == InvalidOid) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("could not determine data type for argument %d", 1))); + + json_categorize_type(arg_type, &state->key_category, + &state->key_output_func); + + arg_type = get_fn_expr_argtype(fcinfo->flinfo, 2); + + if (arg_type == InvalidOid) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("could not determine data type for argument %d", 2))); + + json_categorize_type(arg_type, &state->val_category, + &state->val_output_func); + + appendStringInfoString(state->str, "{ "); + } + else + { + state = (JsonAggState *) PG_GETARG_POINTER(0); + } + + /* + * Note: since json_object_agg() is declared as taking type "any", the + * parser will not do any type conversion on unknown-type literals (that + * is, undecorated strings or NULLs). Such values will arrive here as + * type UNKNOWN, which fortunately does not matter to us, since + * unknownout() works fine. + */ + + if (PG_ARGISNULL(1)) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("null value not allowed for object key"))); + + /* Skip null values if absent_on_null */ + skip = absent_on_null && PG_ARGISNULL(2); + + if (skip) + { + /* + * We got a NULL value and we're not storing those; if we're not + * testing key uniqueness, we're done. If we are, use the throwaway + * buffer to store the key name so that we can check it. + */ + if (!unique_keys) + PG_RETURN_POINTER(state); + + out = json_unique_builder_get_throwawaybuf(&state->unique_check); + } + else + { + out = state->str; + + /* + * Append comma delimiter only if we have already output some fields + * after the initial string "{ ". + */ + if (out->len > 2) + appendStringInfoString(out, ", "); + } + + arg = PG_GETARG_DATUM(1); + + key_offset = out->len; + + datum_to_json(arg, false, out, state->key_category, + state->key_output_func, true); + + if (unique_keys) + { + const char *key = &out->data[key_offset]; + + if (!json_unique_check_key(&state->unique_check.check, key, 0)) + ereport(ERROR, + errcode(ERRCODE_DUPLICATE_JSON_OBJECT_KEY_VALUE), + errmsg("duplicate JSON object key value: %s", key)); + + if (skip) + PG_RETURN_POINTER(state); + } + + appendStringInfoString(state->str, " : "); + + if (PG_ARGISNULL(2)) + arg = (Datum) 0; + else + arg = PG_GETARG_DATUM(2); + + datum_to_json(arg, PG_ARGISNULL(2), state->str, state->val_category, + state->val_output_func, false); + + PG_RETURN_POINTER(state); +} + +/* + * json_object_agg aggregate function + */ +Datum +json_object_agg_transfn(PG_FUNCTION_ARGS) +{ + return json_object_agg_transfn_worker(fcinfo, false, false); +} + +/* + * json_object_agg_strict aggregate function + */ +Datum +json_object_agg_strict_transfn(PG_FUNCTION_ARGS) +{ + return json_object_agg_transfn_worker(fcinfo, true, false); +} + +/* + * json_object_agg_unique aggregate function + */ +Datum +json_object_agg_unique_transfn(PG_FUNCTION_ARGS) +{ + return json_object_agg_transfn_worker(fcinfo, false, true); +} + +/* + * json_object_agg_unique_strict aggregate function + */ +Datum +json_object_agg_unique_strict_transfn(PG_FUNCTION_ARGS) +{ + return json_object_agg_transfn_worker(fcinfo, true, true); +} + +/* + * json_object_agg final function. + */ +Datum +json_object_agg_finalfn(PG_FUNCTION_ARGS) +{ + JsonAggState *state; + + /* cannot be called directly because of internal-type argument */ + Assert(AggCheckCallContext(fcinfo, NULL)); + + state = PG_ARGISNULL(0) ? NULL : (JsonAggState *) PG_GETARG_POINTER(0); + + /* NULL result for no rows in, as is standard with aggregates */ + if (state == NULL) + PG_RETURN_NULL(); + + /* Else return state with appropriate object terminator added */ + PG_RETURN_TEXT_P(catenate_stringinfo_string(state->str, " }")); +} + +/* + * Helper function for aggregates: return given StringInfo's contents plus + * specified trailing string, as a text datum. We need this because aggregate + * final functions are not allowed to modify the aggregate state. + */ +static text * +catenate_stringinfo_string(StringInfo buffer, const char *addon) +{ + /* custom version of cstring_to_text_with_len */ + int buflen = buffer->len; + int addlen = strlen(addon); + text *result = (text *) palloc(buflen + addlen + VARHDRSZ); + + SET_VARSIZE(result, buflen + addlen + VARHDRSZ); + memcpy(VARDATA(result), buffer->data, buflen); + memcpy(VARDATA(result) + buflen, addon, addlen); + + return result; +} + +Datum +json_build_object_worker(int nargs, Datum *args, bool *nulls, Oid *types, + bool absent_on_null, bool unique_keys) +{ + int i; + const char *sep = ""; + StringInfo result; + JsonUniqueBuilderState unique_check; + + if (nargs % 2 != 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("argument list must have even number of elements"), + /* translator: %s is a SQL function name */ + errhint("The arguments of %s must consist of alternating keys and values.", + "json_build_object()"))); + + result = makeStringInfo(); + + appendStringInfoChar(result, '{'); + + if (unique_keys) + json_unique_builder_init(&unique_check); + + for (i = 0; i < nargs; i += 2) + { + StringInfo out; + bool skip; + int key_offset; + + /* Skip null values if absent_on_null */ + skip = absent_on_null && nulls[i + 1]; + + if (skip) + { + /* If key uniqueness check is needed we must save skipped keys */ + if (!unique_keys) + continue; + + out = json_unique_builder_get_throwawaybuf(&unique_check); + } + else + { + appendStringInfoString(result, sep); + sep = ", "; + out = result; + } + + /* process key */ + if (nulls[i]) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("null value not allowed for object key"))); + + /* save key offset before appending it */ + key_offset = out->len; + + add_json(args[i], false, out, types[i], true); + + if (unique_keys) + { + /* check key uniqueness after key appending */ + const char *key = &out->data[key_offset]; + + if (!json_unique_check_key(&unique_check.check, key, 0)) + ereport(ERROR, + errcode(ERRCODE_DUPLICATE_JSON_OBJECT_KEY_VALUE), + errmsg("duplicate JSON object key value: %s", key)); + + if (skip) + continue; + } + + appendStringInfoString(result, " : "); + + /* process value */ + add_json(args[i + 1], nulls[i + 1], result, types[i + 1], false); + } + + appendStringInfoChar(result, '}'); + + return PointerGetDatum(cstring_to_text_with_len(result->data, result->len)); +} + +/* + * SQL function json_build_object(variadic "any") + */ +Datum +json_build_object(PG_FUNCTION_ARGS) +{ + Datum *args; + bool *nulls; + Oid *types; + + /* build argument values to build the object */ + int nargs = extract_variadic_args(fcinfo, 0, true, + &args, &types, &nulls); + + if (nargs < 0) + PG_RETURN_NULL(); + + PG_RETURN_DATUM(json_build_object_worker(nargs, args, nulls, types, false, false)); +} + +/* + * degenerate case of json_build_object where it gets 0 arguments. + */ +Datum +json_build_object_noargs(PG_FUNCTION_ARGS) +{ + PG_RETURN_TEXT_P(cstring_to_text_with_len("{}", 2)); +} + +Datum +json_build_array_worker(int nargs, Datum *args, bool *nulls, Oid *types, + bool absent_on_null) +{ + int i; + const char *sep = ""; + StringInfo result; + + result = makeStringInfo(); + + appendStringInfoChar(result, '['); + + for (i = 0; i < nargs; i++) + { + if (absent_on_null && nulls[i]) + continue; + + appendStringInfoString(result, sep); + sep = ", "; + add_json(args[i], nulls[i], result, types[i], false); + } + + appendStringInfoChar(result, ']'); + + return PointerGetDatum(cstring_to_text_with_len(result->data, result->len)); +} + +/* + * SQL function json_build_array(variadic "any") + */ +Datum +json_build_array(PG_FUNCTION_ARGS) +{ + Datum *args; + bool *nulls; + Oid *types; + + /* build argument values to build the object */ + int nargs = extract_variadic_args(fcinfo, 0, true, + &args, &types, &nulls); + + if (nargs < 0) + PG_RETURN_NULL(); + + PG_RETURN_DATUM(json_build_array_worker(nargs, args, nulls, types, false)); +} + +/* + * degenerate case of json_build_array where it gets 0 arguments. + */ +Datum +json_build_array_noargs(PG_FUNCTION_ARGS) +{ + PG_RETURN_TEXT_P(cstring_to_text_with_len("[]", 2)); +} + +/* + * SQL function json_object(text[]) + * + * take a one or two dimensional array of text as key/value pairs + * for a json object. + */ +Datum +json_object(PG_FUNCTION_ARGS) +{ + ArrayType *in_array = PG_GETARG_ARRAYTYPE_P(0); + int ndims = ARR_NDIM(in_array); + StringInfoData result; + Datum *in_datums; + bool *in_nulls; + int in_count, + count, + i; + text *rval; + char *v; + + switch (ndims) + { + case 0: + PG_RETURN_DATUM(CStringGetTextDatum("{}")); + break; + + case 1: + if ((ARR_DIMS(in_array)[0]) % 2) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("array must have even number of elements"))); + break; + + case 2: + if ((ARR_DIMS(in_array)[1]) != 2) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("array must have two columns"))); + break; + + default: + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("wrong number of array subscripts"))); + } + + deconstruct_array_builtin(in_array, TEXTOID, &in_datums, &in_nulls, &in_count); + + count = in_count / 2; + + initStringInfo(&result); + + appendStringInfoChar(&result, '{'); + + for (i = 0; i < count; ++i) + { + if (in_nulls[i * 2]) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("null value not allowed for object key"))); + + v = TextDatumGetCString(in_datums[i * 2]); + if (i > 0) + appendStringInfoString(&result, ", "); + escape_json(&result, v); + appendStringInfoString(&result, " : "); + pfree(v); + if (in_nulls[i * 2 + 1]) + appendStringInfoString(&result, "null"); + else + { + v = TextDatumGetCString(in_datums[i * 2 + 1]); + escape_json(&result, v); + pfree(v); + } + } + + appendStringInfoChar(&result, '}'); + + pfree(in_datums); + pfree(in_nulls); + + rval = cstring_to_text_with_len(result.data, result.len); + pfree(result.data); + + PG_RETURN_TEXT_P(rval); +} + +/* + * SQL function json_object(text[], text[]) + * + * take separate key and value arrays of text to construct a json object + * pairwise. + */ +Datum +json_object_two_arg(PG_FUNCTION_ARGS) +{ + ArrayType *key_array = PG_GETARG_ARRAYTYPE_P(0); + ArrayType *val_array = PG_GETARG_ARRAYTYPE_P(1); + int nkdims = ARR_NDIM(key_array); + int nvdims = ARR_NDIM(val_array); + StringInfoData result; + Datum *key_datums, + *val_datums; + bool *key_nulls, + *val_nulls; + int key_count, + val_count, + i; + text *rval; + char *v; + + if (nkdims > 1 || nkdims != nvdims) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("wrong number of array subscripts"))); + + if (nkdims == 0) + PG_RETURN_DATUM(CStringGetTextDatum("{}")); + + deconstruct_array_builtin(key_array, TEXTOID, &key_datums, &key_nulls, &key_count); + deconstruct_array_builtin(val_array, TEXTOID, &val_datums, &val_nulls, &val_count); + + if (key_count != val_count) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("mismatched array dimensions"))); + + initStringInfo(&result); + + appendStringInfoChar(&result, '{'); + + for (i = 0; i < key_count; ++i) + { + if (key_nulls[i]) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("null value not allowed for object key"))); + + v = TextDatumGetCString(key_datums[i]); + if (i > 0) + appendStringInfoString(&result, ", "); + escape_json(&result, v); + appendStringInfoString(&result, " : "); + pfree(v); + if (val_nulls[i]) + appendStringInfoString(&result, "null"); + else + { + v = TextDatumGetCString(val_datums[i]); + escape_json(&result, v); + pfree(v); + } + } + + appendStringInfoChar(&result, '}'); + + pfree(key_datums); + pfree(key_nulls); + pfree(val_datums); + pfree(val_nulls); + + rval = cstring_to_text_with_len(result.data, result.len); + pfree(result.data); + + PG_RETURN_TEXT_P(rval); +} + + +/* + * Produce a JSON string literal, properly escaping characters in the text. + */ +void +escape_json(StringInfo buf, const char *str) +{ + const char *p; + + appendStringInfoCharMacro(buf, '"'); + for (p = str; *p; p++) + { + switch (*p) + { + case '\b': + appendStringInfoString(buf, "\\b"); + break; + case '\f': + appendStringInfoString(buf, "\\f"); + break; + case '\n': + appendStringInfoString(buf, "\\n"); + break; + case '\r': + appendStringInfoString(buf, "\\r"); + break; + case '\t': + appendStringInfoString(buf, "\\t"); + break; + case '"': + appendStringInfoString(buf, "\\\""); + break; + case '\\': + appendStringInfoString(buf, "\\\\"); + break; + default: + if ((unsigned char) *p < ' ') + appendStringInfo(buf, "\\u%04x", (int) *p); + else + appendStringInfoCharMacro(buf, *p); + break; + } + } + appendStringInfoCharMacro(buf, '"'); +} + +/* Semantic actions for key uniqueness check */ +static JsonParseErrorType +json_unique_object_start(void *_state) +{ + JsonUniqueParsingState *state = _state; + JsonUniqueStackEntry *entry; + + if (!state->unique) + return JSON_SUCCESS; + + /* push object entry to stack */ + entry = palloc(sizeof(*entry)); + entry->object_id = state->id_counter++; + entry->parent = state->stack; + state->stack = entry; + + return JSON_SUCCESS; +} + +static JsonParseErrorType +json_unique_object_end(void *_state) +{ + JsonUniqueParsingState *state = _state; + JsonUniqueStackEntry *entry; + + if (!state->unique) + return JSON_SUCCESS; + + entry = state->stack; + state->stack = entry->parent; /* pop object from stack */ + pfree(entry); + return JSON_SUCCESS; +} + +static JsonParseErrorType +json_unique_object_field_start(void *_state, char *field, bool isnull) +{ + JsonUniqueParsingState *state = _state; + JsonUniqueStackEntry *entry; + + if (!state->unique) + return JSON_SUCCESS; + + /* find key collision in the current object */ + if (json_unique_check_key(&state->check, field, state->stack->object_id)) + return JSON_SUCCESS; + + state->unique = false; + + /* pop all objects entries */ + while ((entry = state->stack)) + { + state->stack = entry->parent; + pfree(entry); + } + return JSON_SUCCESS; +} + +/* Validate JSON text and additionally check key uniqueness */ +bool +json_validate(text *json, bool check_unique_keys, bool throw_error) +{ + JsonLexContext *lex = makeJsonLexContext(json, check_unique_keys); + JsonSemAction uniqueSemAction = {0}; + JsonUniqueParsingState state; + JsonParseErrorType result; + + if (check_unique_keys) + { + state.lex = lex; + state.stack = NULL; + state.id_counter = 0; + state.unique = true; + json_unique_check_init(&state.check); + + uniqueSemAction.semstate = &state; + uniqueSemAction.object_start = json_unique_object_start; + uniqueSemAction.object_field_start = json_unique_object_field_start; + uniqueSemAction.object_end = json_unique_object_end; + } + + result = pg_parse_json(lex, check_unique_keys ? &uniqueSemAction : &nullSemAction); + + if (result != JSON_SUCCESS) + { + if (throw_error) + json_errsave_error(result, lex, NULL); + + return false; /* invalid json */ + } + + if (check_unique_keys && !state.unique) + { + if (throw_error) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_JSON_OBJECT_KEY_VALUE), + errmsg("duplicate JSON object key value"))); + + return false; /* not unique keys */ + } + + return true; /* ok */ +} + +/* + * SQL function json_typeof(json) -> text + * + * Returns the type of the outermost JSON value as TEXT. Possible types are + * "object", "array", "string", "number", "boolean", and "null". + * + * Performs a single call to json_lex() to get the first token of the supplied + * value. This initial token uniquely determines the value's type. As our + * input must already have been validated by json_in() or json_recv(), the + * initial token should never be JSON_TOKEN_OBJECT_END, JSON_TOKEN_ARRAY_END, + * JSON_TOKEN_COLON, JSON_TOKEN_COMMA, or JSON_TOKEN_END. + */ +Datum +json_typeof(PG_FUNCTION_ARGS) +{ + text *json = PG_GETARG_TEXT_PP(0); + JsonLexContext *lex = makeJsonLexContext(json, false); + char *type; + JsonTokenType tok; + JsonParseErrorType result; + + /* Lex exactly one token from the input and check its type. */ + result = json_lex(lex); + if (result != JSON_SUCCESS) + json_errsave_error(result, lex, NULL); + tok = lex->token_type; + + switch (tok) + { + case JSON_TOKEN_OBJECT_START: + type = "object"; + break; + case JSON_TOKEN_ARRAY_START: + type = "array"; + break; + case JSON_TOKEN_STRING: + type = "string"; + break; + case JSON_TOKEN_NUMBER: + type = "number"; + break; + case JSON_TOKEN_TRUE: + case JSON_TOKEN_FALSE: + type = "boolean"; + break; + case JSON_TOKEN_NULL: + type = "null"; + break; + default: + elog(ERROR, "unexpected json token: %d", tok); + } + + PG_RETURN_TEXT_P(cstring_to_text(type)); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/jsonb.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/jsonb.c new file mode 100644 index 00000000000..cf43c3f2ded --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/jsonb.c @@ -0,0 +1,2259 @@ +/*------------------------------------------------------------------------- + * + * jsonb.c + * I/O routines for jsonb type + * + * Copyright (c) 2014-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/utils/adt/jsonb.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/htup_details.h" +#include "access/transam.h" +#include "catalog/pg_proc.h" +#include "catalog/pg_type.h" +#include "funcapi.h" +#include "libpq/pqformat.h" +#include "miscadmin.h" +#include "parser/parse_coerce.h" +#include "utils/builtins.h" +#include "utils/date.h" +#include "utils/datetime.h" +#include "utils/json.h" +#include "utils/jsonb.h" +#include "utils/jsonfuncs.h" +#include "utils/lsyscache.h" +#include "utils/syscache.h" +#include "utils/typcache.h" + +typedef struct JsonbInState +{ + JsonbParseState *parseState; + JsonbValue *res; + Node *escontext; +} JsonbInState; + +/* unlike with json categories, we need to treat json and jsonb differently */ +typedef enum /* type categories for datum_to_jsonb */ +{ + JSONBTYPE_NULL, /* null, so we didn't bother to identify */ + JSONBTYPE_BOOL, /* boolean (built-in types only) */ + JSONBTYPE_NUMERIC, /* numeric (ditto) */ + JSONBTYPE_DATE, /* we use special formatting for datetimes */ + JSONBTYPE_TIMESTAMP, /* we use special formatting for timestamp */ + JSONBTYPE_TIMESTAMPTZ, /* ... and timestamptz */ + JSONBTYPE_JSON, /* JSON */ + JSONBTYPE_JSONB, /* JSONB */ + JSONBTYPE_ARRAY, /* array */ + JSONBTYPE_COMPOSITE, /* composite */ + JSONBTYPE_JSONCAST, /* something with an explicit cast to JSON */ + JSONBTYPE_OTHER /* all else */ +} JsonbTypeCategory; + +typedef struct JsonbAggState +{ + JsonbInState *res; + JsonbTypeCategory key_category; + Oid key_output_func; + JsonbTypeCategory val_category; + Oid val_output_func; +} JsonbAggState; + +static inline Datum jsonb_from_cstring(char *json, int len, Node *escontext); +static bool checkStringLen(size_t len, Node *escontext); +static JsonParseErrorType jsonb_in_object_start(void *pstate); +static JsonParseErrorType jsonb_in_object_end(void *pstate); +static JsonParseErrorType jsonb_in_array_start(void *pstate); +static JsonParseErrorType jsonb_in_array_end(void *pstate); +static JsonParseErrorType jsonb_in_object_field_start(void *pstate, char *fname, bool isnull); +static void jsonb_put_escaped_value(StringInfo out, JsonbValue *scalarVal); +static JsonParseErrorType jsonb_in_scalar(void *pstate, char *token, JsonTokenType tokentype); +static void jsonb_categorize_type(Oid typoid, + JsonbTypeCategory *tcategory, + Oid *outfuncoid); +static void composite_to_jsonb(Datum composite, JsonbInState *result); +static void array_dim_to_jsonb(JsonbInState *result, int dim, int ndims, int *dims, + Datum *vals, bool *nulls, int *valcount, + JsonbTypeCategory tcategory, Oid outfuncoid); +static void array_to_jsonb_internal(Datum array, JsonbInState *result); +static void jsonb_categorize_type(Oid typoid, + JsonbTypeCategory *tcategory, + Oid *outfuncoid); +static void datum_to_jsonb(Datum val, bool is_null, JsonbInState *result, + JsonbTypeCategory tcategory, Oid outfuncoid, + bool key_scalar); +static void add_jsonb(Datum val, bool is_null, JsonbInState *result, + Oid val_type, bool key_scalar); +static JsonbParseState *clone_parse_state(JsonbParseState *state); +static char *JsonbToCStringWorker(StringInfo out, JsonbContainer *in, int estimated_len, bool indent); +static void add_indent(StringInfo out, bool indent, int level); + +/* + * jsonb type input function + */ +Datum +jsonb_in(PG_FUNCTION_ARGS) +{ + char *json = PG_GETARG_CSTRING(0); + + return jsonb_from_cstring(json, strlen(json), fcinfo->context); +} + +/* + * jsonb type recv function + * + * The type is sent as text in binary mode, so this is almost the same + * as the input function, but it's prefixed with a version number so we + * can change the binary format sent in future if necessary. For now, + * only version 1 is supported. + */ +Datum +jsonb_recv(PG_FUNCTION_ARGS) +{ + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + int version = pq_getmsgint(buf, 1); + char *str; + int nbytes; + + if (version == 1) + str = pq_getmsgtext(buf, buf->len - buf->cursor, &nbytes); + else + elog(ERROR, "unsupported jsonb version number %d", version); + + return jsonb_from_cstring(str, nbytes, NULL); +} + +/* + * jsonb type output function + */ +Datum +jsonb_out(PG_FUNCTION_ARGS) +{ + Jsonb *jb = PG_GETARG_JSONB_P(0); + char *out; + + out = JsonbToCString(NULL, &jb->root, VARSIZE(jb)); + + PG_RETURN_CSTRING(out); +} + +/* + * jsonb type send function + * + * Just send jsonb as a version number, then a string of text + */ +Datum +jsonb_send(PG_FUNCTION_ARGS) +{ + Jsonb *jb = PG_GETARG_JSONB_P(0); + StringInfoData buf; + StringInfo jtext = makeStringInfo(); + int version = 1; + + (void) JsonbToCString(jtext, &jb->root, VARSIZE(jb)); + + pq_begintypsend(&buf); + pq_sendint8(&buf, version); + pq_sendtext(&buf, jtext->data, jtext->len); + pfree(jtext->data); + pfree(jtext); + + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} + +/* + * Get the type name of a jsonb container. + */ +static const char * +JsonbContainerTypeName(JsonbContainer *jbc) +{ + JsonbValue scalar; + + if (JsonbExtractScalar(jbc, &scalar)) + return JsonbTypeName(&scalar); + else if (JsonContainerIsArray(jbc)) + return "array"; + else if (JsonContainerIsObject(jbc)) + return "object"; + else + { + elog(ERROR, "invalid jsonb container type: 0x%08x", jbc->header); + return "unknown"; + } +} + +/* + * Get the type name of a jsonb value. + */ +const char * +JsonbTypeName(JsonbValue *val) +{ + switch (val->type) + { + case jbvBinary: + return JsonbContainerTypeName(val->val.binary.data); + case jbvObject: + return "object"; + case jbvArray: + return "array"; + case jbvNumeric: + return "number"; + case jbvString: + return "string"; + case jbvBool: + return "boolean"; + case jbvNull: + return "null"; + case jbvDatetime: + switch (val->val.datetime.typid) + { + case DATEOID: + return "date"; + case TIMEOID: + return "time without time zone"; + case TIMETZOID: + return "time with time zone"; + case TIMESTAMPOID: + return "timestamp without time zone"; + case TIMESTAMPTZOID: + return "timestamp with time zone"; + default: + elog(ERROR, "unrecognized jsonb value datetime type: %d", + val->val.datetime.typid); + } + return "unknown"; + default: + elog(ERROR, "unrecognized jsonb value type: %d", val->type); + return "unknown"; + } +} + +/* + * SQL function jsonb_typeof(jsonb) -> text + * + * This function is here because the analog json function is in json.c, since + * it uses the json parser internals not exposed elsewhere. + */ +Datum +jsonb_typeof(PG_FUNCTION_ARGS) +{ + Jsonb *in = PG_GETARG_JSONB_P(0); + const char *result = JsonbContainerTypeName(&in->root); + + PG_RETURN_TEXT_P(cstring_to_text(result)); +} + +/* + * jsonb_from_cstring + * + * Turns json string into a jsonb Datum. + * + * Uses the json parser (with hooks) to construct a jsonb. + * + * If escontext points to an ErrorSaveContext, errors are reported there + * instead of being thrown. + */ +static inline Datum +jsonb_from_cstring(char *json, int len, Node *escontext) +{ + JsonLexContext *lex; + JsonbInState state; + JsonSemAction sem; + + memset(&state, 0, sizeof(state)); + memset(&sem, 0, sizeof(sem)); + lex = makeJsonLexContextCstringLen(json, len, GetDatabaseEncoding(), true); + + state.escontext = escontext; + sem.semstate = (void *) &state; + + sem.object_start = jsonb_in_object_start; + sem.array_start = jsonb_in_array_start; + sem.object_end = jsonb_in_object_end; + sem.array_end = jsonb_in_array_end; + sem.scalar = jsonb_in_scalar; + sem.object_field_start = jsonb_in_object_field_start; + + if (!pg_parse_json_or_errsave(lex, &sem, escontext)) + return (Datum) 0; + + /* after parsing, the item member has the composed jsonb structure */ + PG_RETURN_POINTER(JsonbValueToJsonb(state.res)); +} + +static bool +checkStringLen(size_t len, Node *escontext) +{ + if (len > JENTRY_OFFLENMASK) + ereturn(escontext, false, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("string too long to represent as jsonb string"), + errdetail("Due to an implementation restriction, jsonb strings cannot exceed %d bytes.", + JENTRY_OFFLENMASK))); + + return true; +} + +static JsonParseErrorType +jsonb_in_object_start(void *pstate) +{ + JsonbInState *_state = (JsonbInState *) pstate; + + _state->res = pushJsonbValue(&_state->parseState, WJB_BEGIN_OBJECT, NULL); + + return JSON_SUCCESS; +} + +static JsonParseErrorType +jsonb_in_object_end(void *pstate) +{ + JsonbInState *_state = (JsonbInState *) pstate; + + _state->res = pushJsonbValue(&_state->parseState, WJB_END_OBJECT, NULL); + + return JSON_SUCCESS; +} + +static JsonParseErrorType +jsonb_in_array_start(void *pstate) +{ + JsonbInState *_state = (JsonbInState *) pstate; + + _state->res = pushJsonbValue(&_state->parseState, WJB_BEGIN_ARRAY, NULL); + + return JSON_SUCCESS; +} + +static JsonParseErrorType +jsonb_in_array_end(void *pstate) +{ + JsonbInState *_state = (JsonbInState *) pstate; + + _state->res = pushJsonbValue(&_state->parseState, WJB_END_ARRAY, NULL); + + return JSON_SUCCESS; +} + +static JsonParseErrorType +jsonb_in_object_field_start(void *pstate, char *fname, bool isnull) +{ + JsonbInState *_state = (JsonbInState *) pstate; + JsonbValue v; + + Assert(fname != NULL); + v.type = jbvString; + v.val.string.len = strlen(fname); + if (!checkStringLen(v.val.string.len, _state->escontext)) + return JSON_SEM_ACTION_FAILED; + v.val.string.val = fname; + + _state->res = pushJsonbValue(&_state->parseState, WJB_KEY, &v); + + return JSON_SUCCESS; +} + +static void +jsonb_put_escaped_value(StringInfo out, JsonbValue *scalarVal) +{ + switch (scalarVal->type) + { + case jbvNull: + appendBinaryStringInfo(out, "null", 4); + break; + case jbvString: + escape_json(out, pnstrdup(scalarVal->val.string.val, scalarVal->val.string.len)); + break; + case jbvNumeric: + appendStringInfoString(out, + DatumGetCString(DirectFunctionCall1(numeric_out, + PointerGetDatum(scalarVal->val.numeric)))); + break; + case jbvBool: + if (scalarVal->val.boolean) + appendBinaryStringInfo(out, "true", 4); + else + appendBinaryStringInfo(out, "false", 5); + break; + default: + elog(ERROR, "unknown jsonb scalar type"); + } +} + +/* + * For jsonb we always want the de-escaped value - that's what's in token + */ +static JsonParseErrorType +jsonb_in_scalar(void *pstate, char *token, JsonTokenType tokentype) +{ + JsonbInState *_state = (JsonbInState *) pstate; + JsonbValue v; + Datum numd; + + switch (tokentype) + { + + case JSON_TOKEN_STRING: + Assert(token != NULL); + v.type = jbvString; + v.val.string.len = strlen(token); + if (!checkStringLen(v.val.string.len, _state->escontext)) + return JSON_SEM_ACTION_FAILED; + v.val.string.val = token; + break; + case JSON_TOKEN_NUMBER: + + /* + * No need to check size of numeric values, because maximum + * numeric size is well below the JsonbValue restriction + */ + Assert(token != NULL); + v.type = jbvNumeric; + if (!DirectInputFunctionCallSafe(numeric_in, token, + InvalidOid, -1, + _state->escontext, + &numd)) + return JSON_SEM_ACTION_FAILED; + v.val.numeric = DatumGetNumeric(numd); + break; + case JSON_TOKEN_TRUE: + v.type = jbvBool; + v.val.boolean = true; + break; + case JSON_TOKEN_FALSE: + v.type = jbvBool; + v.val.boolean = false; + break; + case JSON_TOKEN_NULL: + v.type = jbvNull; + break; + default: + /* should not be possible */ + elog(ERROR, "invalid json token type"); + break; + } + + if (_state->parseState == NULL) + { + /* single scalar */ + JsonbValue va; + + va.type = jbvArray; + va.val.array.rawScalar = true; + va.val.array.nElems = 1; + + _state->res = pushJsonbValue(&_state->parseState, WJB_BEGIN_ARRAY, &va); + _state->res = pushJsonbValue(&_state->parseState, WJB_ELEM, &v); + _state->res = pushJsonbValue(&_state->parseState, WJB_END_ARRAY, NULL); + } + else + { + JsonbValue *o = &_state->parseState->contVal; + + switch (o->type) + { + case jbvArray: + _state->res = pushJsonbValue(&_state->parseState, WJB_ELEM, &v); + break; + case jbvObject: + _state->res = pushJsonbValue(&_state->parseState, WJB_VALUE, &v); + break; + default: + elog(ERROR, "unexpected parent of nested structure"); + } + } + + return JSON_SUCCESS; +} + +/* + * JsonbToCString + * Converts jsonb value to a C-string. + * + * If 'out' argument is non-null, the resulting C-string is stored inside the + * StringBuffer. The resulting string is always returned. + * + * A typical case for passing the StringInfo in rather than NULL is where the + * caller wants access to the len attribute without having to call strlen, e.g. + * if they are converting it to a text* object. + */ +char * +JsonbToCString(StringInfo out, JsonbContainer *in, int estimated_len) +{ + return JsonbToCStringWorker(out, in, estimated_len, false); +} + +/* + * same thing but with indentation turned on + */ +char * +JsonbToCStringIndent(StringInfo out, JsonbContainer *in, int estimated_len) +{ + return JsonbToCStringWorker(out, in, estimated_len, true); +} + +/* + * common worker for above two functions + */ +static char * +JsonbToCStringWorker(StringInfo out, JsonbContainer *in, int estimated_len, bool indent) +{ + bool first = true; + JsonbIterator *it; + JsonbValue v; + JsonbIteratorToken type = WJB_DONE; + int level = 0; + bool redo_switch = false; + + /* If we are indenting, don't add a space after a comma */ + int ispaces = indent ? 1 : 2; + + /* + * Don't indent the very first item. This gets set to the indent flag at + * the bottom of the loop. + */ + bool use_indent = false; + bool raw_scalar = false; + bool last_was_key = false; + + if (out == NULL) + out = makeStringInfo(); + + enlargeStringInfo(out, (estimated_len >= 0) ? estimated_len : 64); + + it = JsonbIteratorInit(in); + + while (redo_switch || + ((type = JsonbIteratorNext(&it, &v, false)) != WJB_DONE)) + { + redo_switch = false; + switch (type) + { + case WJB_BEGIN_ARRAY: + if (!first) + appendBinaryStringInfo(out, ", ", ispaces); + + if (!v.val.array.rawScalar) + { + add_indent(out, use_indent && !last_was_key, level); + appendStringInfoCharMacro(out, '['); + } + else + raw_scalar = true; + + first = true; + level++; + break; + case WJB_BEGIN_OBJECT: + if (!first) + appendBinaryStringInfo(out, ", ", ispaces); + + add_indent(out, use_indent && !last_was_key, level); + appendStringInfoCharMacro(out, '{'); + + first = true; + level++; + break; + case WJB_KEY: + if (!first) + appendBinaryStringInfo(out, ", ", ispaces); + first = true; + + add_indent(out, use_indent, level); + + /* json rules guarantee this is a string */ + jsonb_put_escaped_value(out, &v); + appendBinaryStringInfo(out, ": ", 2); + + type = JsonbIteratorNext(&it, &v, false); + if (type == WJB_VALUE) + { + first = false; + jsonb_put_escaped_value(out, &v); + } + else + { + Assert(type == WJB_BEGIN_OBJECT || type == WJB_BEGIN_ARRAY); + + /* + * We need to rerun the current switch() since we need to + * output the object which we just got from the iterator + * before calling the iterator again. + */ + redo_switch = true; + } + break; + case WJB_ELEM: + if (!first) + appendBinaryStringInfo(out, ", ", ispaces); + first = false; + + if (!raw_scalar) + add_indent(out, use_indent, level); + jsonb_put_escaped_value(out, &v); + break; + case WJB_END_ARRAY: + level--; + if (!raw_scalar) + { + add_indent(out, use_indent, level); + appendStringInfoCharMacro(out, ']'); + } + first = false; + break; + case WJB_END_OBJECT: + level--; + add_indent(out, use_indent, level); + appendStringInfoCharMacro(out, '}'); + first = false; + break; + default: + elog(ERROR, "unknown jsonb iterator token type"); + } + use_indent = indent; + last_was_key = redo_switch; + } + + Assert(level == 0); + + return out->data; +} + +static void +add_indent(StringInfo out, bool indent, int level) +{ + if (indent) + { + appendStringInfoCharMacro(out, '\n'); + appendStringInfoSpaces(out, level * 4); + } +} + + +/* + * Determine how we want to render values of a given type in datum_to_jsonb. + * + * Given the datatype OID, return its JsonbTypeCategory, as well as the type's + * output function OID. If the returned category is JSONBTYPE_JSONCAST, + * we return the OID of the relevant cast function instead. + */ +static void +jsonb_categorize_type(Oid typoid, + JsonbTypeCategory *tcategory, + Oid *outfuncoid) +{ + bool typisvarlena; + + /* Look through any domain */ + typoid = getBaseType(typoid); + + *outfuncoid = InvalidOid; + + /* + * We need to get the output function for everything except date and + * timestamp types, booleans, array and composite types, json and jsonb, + * and non-builtin types where there's a cast to json. In this last case + * we return the oid of the cast function instead. + */ + + switch (typoid) + { + case BOOLOID: + *tcategory = JSONBTYPE_BOOL; + break; + + case INT2OID: + case INT4OID: + case INT8OID: + case FLOAT4OID: + case FLOAT8OID: + case NUMERICOID: + getTypeOutputInfo(typoid, outfuncoid, &typisvarlena); + *tcategory = JSONBTYPE_NUMERIC; + break; + + case DATEOID: + *tcategory = JSONBTYPE_DATE; + break; + + case TIMESTAMPOID: + *tcategory = JSONBTYPE_TIMESTAMP; + break; + + case TIMESTAMPTZOID: + *tcategory = JSONBTYPE_TIMESTAMPTZ; + break; + + case JSONBOID: + *tcategory = JSONBTYPE_JSONB; + break; + + case JSONOID: + *tcategory = JSONBTYPE_JSON; + break; + + default: + /* Check for arrays and composites */ + if (OidIsValid(get_element_type(typoid)) || typoid == ANYARRAYOID + || typoid == ANYCOMPATIBLEARRAYOID || typoid == RECORDARRAYOID) + *tcategory = JSONBTYPE_ARRAY; + else if (type_is_rowtype(typoid)) /* includes RECORDOID */ + *tcategory = JSONBTYPE_COMPOSITE; + else + { + /* It's probably the general case ... */ + *tcategory = JSONBTYPE_OTHER; + + /* + * but first let's look for a cast to json (note: not to + * jsonb) if it's not built-in. + */ + if (typoid >= FirstNormalObjectId) + { + Oid castfunc; + CoercionPathType ctype; + + ctype = find_coercion_pathway(JSONOID, typoid, + COERCION_EXPLICIT, &castfunc); + if (ctype == COERCION_PATH_FUNC && OidIsValid(castfunc)) + { + *tcategory = JSONBTYPE_JSONCAST; + *outfuncoid = castfunc; + } + else + { + /* not a cast type, so just get the usual output func */ + getTypeOutputInfo(typoid, outfuncoid, &typisvarlena); + } + } + else + { + /* any other builtin type */ + getTypeOutputInfo(typoid, outfuncoid, &typisvarlena); + } + break; + } + } +} + +/* + * Turn a Datum into jsonb, adding it to the result JsonbInState. + * + * tcategory and outfuncoid are from a previous call to json_categorize_type, + * except that if is_null is true then they can be invalid. + * + * If key_scalar is true, the value is stored as a key, so insist + * it's of an acceptable type, and force it to be a jbvString. + * + * Note: currently, we assume that result->escontext is NULL and errors + * will be thrown. + */ +static void +datum_to_jsonb(Datum val, bool is_null, JsonbInState *result, + JsonbTypeCategory tcategory, Oid outfuncoid, + bool key_scalar) +{ + char *outputstr; + bool numeric_error; + JsonbValue jb; + bool scalar_jsonb = false; + + check_stack_depth(); + + /* Convert val to a JsonbValue in jb (in most cases) */ + if (is_null) + { + Assert(!key_scalar); + jb.type = jbvNull; + } + else if (key_scalar && + (tcategory == JSONBTYPE_ARRAY || + tcategory == JSONBTYPE_COMPOSITE || + tcategory == JSONBTYPE_JSON || + tcategory == JSONBTYPE_JSONB || + tcategory == JSONBTYPE_JSONCAST)) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("key value must be scalar, not array, composite, or json"))); + } + else + { + if (tcategory == JSONBTYPE_JSONCAST) + val = OidFunctionCall1(outfuncoid, val); + + switch (tcategory) + { + case JSONBTYPE_ARRAY: + array_to_jsonb_internal(val, result); + break; + case JSONBTYPE_COMPOSITE: + composite_to_jsonb(val, result); + break; + case JSONBTYPE_BOOL: + if (key_scalar) + { + outputstr = DatumGetBool(val) ? "true" : "false"; + jb.type = jbvString; + jb.val.string.len = strlen(outputstr); + jb.val.string.val = outputstr; + } + else + { + jb.type = jbvBool; + jb.val.boolean = DatumGetBool(val); + } + break; + case JSONBTYPE_NUMERIC: + outputstr = OidOutputFunctionCall(outfuncoid, val); + if (key_scalar) + { + /* always quote keys */ + jb.type = jbvString; + jb.val.string.len = strlen(outputstr); + jb.val.string.val = outputstr; + } + else + { + /* + * Make it numeric if it's a valid JSON number, otherwise + * a string. Invalid numeric output will always have an + * 'N' or 'n' in it (I think). + */ + numeric_error = (strchr(outputstr, 'N') != NULL || + strchr(outputstr, 'n') != NULL); + if (!numeric_error) + { + Datum numd; + + jb.type = jbvNumeric; + numd = DirectFunctionCall3(numeric_in, + CStringGetDatum(outputstr), + ObjectIdGetDatum(InvalidOid), + Int32GetDatum(-1)); + jb.val.numeric = DatumGetNumeric(numd); + pfree(outputstr); + } + else + { + jb.type = jbvString; + jb.val.string.len = strlen(outputstr); + jb.val.string.val = outputstr; + } + } + break; + case JSONBTYPE_DATE: + jb.type = jbvString; + jb.val.string.val = JsonEncodeDateTime(NULL, val, + DATEOID, NULL); + jb.val.string.len = strlen(jb.val.string.val); + break; + case JSONBTYPE_TIMESTAMP: + jb.type = jbvString; + jb.val.string.val = JsonEncodeDateTime(NULL, val, + TIMESTAMPOID, NULL); + jb.val.string.len = strlen(jb.val.string.val); + break; + case JSONBTYPE_TIMESTAMPTZ: + jb.type = jbvString; + jb.val.string.val = JsonEncodeDateTime(NULL, val, + TIMESTAMPTZOID, NULL); + jb.val.string.len = strlen(jb.val.string.val); + break; + case JSONBTYPE_JSONCAST: + case JSONBTYPE_JSON: + { + /* parse the json right into the existing result object */ + JsonLexContext *lex; + JsonSemAction sem; + text *json = DatumGetTextPP(val); + + lex = makeJsonLexContext(json, true); + + memset(&sem, 0, sizeof(sem)); + + sem.semstate = (void *) result; + + sem.object_start = jsonb_in_object_start; + sem.array_start = jsonb_in_array_start; + sem.object_end = jsonb_in_object_end; + sem.array_end = jsonb_in_array_end; + sem.scalar = jsonb_in_scalar; + sem.object_field_start = jsonb_in_object_field_start; + + pg_parse_json_or_ereport(lex, &sem); + } + break; + case JSONBTYPE_JSONB: + { + Jsonb *jsonb = DatumGetJsonbP(val); + JsonbIterator *it; + + it = JsonbIteratorInit(&jsonb->root); + + if (JB_ROOT_IS_SCALAR(jsonb)) + { + (void) JsonbIteratorNext(&it, &jb, true); + Assert(jb.type == jbvArray); + (void) JsonbIteratorNext(&it, &jb, true); + scalar_jsonb = true; + } + else + { + JsonbIteratorToken type; + + while ((type = JsonbIteratorNext(&it, &jb, false)) + != WJB_DONE) + { + if (type == WJB_END_ARRAY || type == WJB_END_OBJECT || + type == WJB_BEGIN_ARRAY || type == WJB_BEGIN_OBJECT) + result->res = pushJsonbValue(&result->parseState, + type, NULL); + else + result->res = pushJsonbValue(&result->parseState, + type, &jb); + } + } + } + break; + default: + outputstr = OidOutputFunctionCall(outfuncoid, val); + jb.type = jbvString; + jb.val.string.len = strlen(outputstr); + (void) checkStringLen(jb.val.string.len, NULL); + jb.val.string.val = outputstr; + break; + } + } + + /* Now insert jb into result, unless we did it recursively */ + if (!is_null && !scalar_jsonb && + tcategory >= JSONBTYPE_JSON && tcategory <= JSONBTYPE_JSONCAST) + { + /* work has been done recursively */ + return; + } + else if (result->parseState == NULL) + { + /* single root scalar */ + JsonbValue va; + + va.type = jbvArray; + va.val.array.rawScalar = true; + va.val.array.nElems = 1; + + result->res = pushJsonbValue(&result->parseState, WJB_BEGIN_ARRAY, &va); + result->res = pushJsonbValue(&result->parseState, WJB_ELEM, &jb); + result->res = pushJsonbValue(&result->parseState, WJB_END_ARRAY, NULL); + } + else + { + JsonbValue *o = &result->parseState->contVal; + + switch (o->type) + { + case jbvArray: + result->res = pushJsonbValue(&result->parseState, WJB_ELEM, &jb); + break; + case jbvObject: + result->res = pushJsonbValue(&result->parseState, + key_scalar ? WJB_KEY : WJB_VALUE, + &jb); + break; + default: + elog(ERROR, "unexpected parent of nested structure"); + } + } +} + +/* + * Process a single dimension of an array. + * If it's the innermost dimension, output the values, otherwise call + * ourselves recursively to process the next dimension. + */ +static void +array_dim_to_jsonb(JsonbInState *result, int dim, int ndims, int *dims, Datum *vals, + bool *nulls, int *valcount, JsonbTypeCategory tcategory, + Oid outfuncoid) +{ + int i; + + Assert(dim < ndims); + + result->res = pushJsonbValue(&result->parseState, WJB_BEGIN_ARRAY, NULL); + + for (i = 1; i <= dims[dim]; i++) + { + if (dim + 1 == ndims) + { + datum_to_jsonb(vals[*valcount], nulls[*valcount], result, tcategory, + outfuncoid, false); + (*valcount)++; + } + else + { + array_dim_to_jsonb(result, dim + 1, ndims, dims, vals, nulls, + valcount, tcategory, outfuncoid); + } + } + + result->res = pushJsonbValue(&result->parseState, WJB_END_ARRAY, NULL); +} + +/* + * Turn an array into JSON. + */ +static void +array_to_jsonb_internal(Datum array, JsonbInState *result) +{ + ArrayType *v = DatumGetArrayTypeP(array); + Oid element_type = ARR_ELEMTYPE(v); + int *dim; + int ndim; + int nitems; + int count = 0; + Datum *elements; + bool *nulls; + int16 typlen; + bool typbyval; + char typalign; + JsonbTypeCategory tcategory; + Oid outfuncoid; + + ndim = ARR_NDIM(v); + dim = ARR_DIMS(v); + nitems = ArrayGetNItems(ndim, dim); + + if (nitems <= 0) + { + result->res = pushJsonbValue(&result->parseState, WJB_BEGIN_ARRAY, NULL); + result->res = pushJsonbValue(&result->parseState, WJB_END_ARRAY, NULL); + return; + } + + get_typlenbyvalalign(element_type, + &typlen, &typbyval, &typalign); + + jsonb_categorize_type(element_type, + &tcategory, &outfuncoid); + + deconstruct_array(v, element_type, typlen, typbyval, + typalign, &elements, &nulls, + &nitems); + + array_dim_to_jsonb(result, 0, ndim, dim, elements, nulls, &count, tcategory, + outfuncoid); + + pfree(elements); + pfree(nulls); +} + +/* + * Turn a composite / record into JSON. + */ +static void +composite_to_jsonb(Datum composite, JsonbInState *result) +{ + HeapTupleHeader td; + Oid tupType; + int32 tupTypmod; + TupleDesc tupdesc; + HeapTupleData tmptup, + *tuple; + int i; + + td = DatumGetHeapTupleHeader(composite); + + /* Extract rowtype info and find a tupdesc */ + tupType = HeapTupleHeaderGetTypeId(td); + tupTypmod = HeapTupleHeaderGetTypMod(td); + tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); + + /* Build a temporary HeapTuple control structure */ + tmptup.t_len = HeapTupleHeaderGetDatumLength(td); + tmptup.t_data = td; + tuple = &tmptup; + + result->res = pushJsonbValue(&result->parseState, WJB_BEGIN_OBJECT, NULL); + + for (i = 0; i < tupdesc->natts; i++) + { + Datum val; + bool isnull; + char *attname; + JsonbTypeCategory tcategory; + Oid outfuncoid; + JsonbValue v; + Form_pg_attribute att = TupleDescAttr(tupdesc, i); + + if (att->attisdropped) + continue; + + attname = NameStr(att->attname); + + v.type = jbvString; + /* don't need checkStringLen here - can't exceed maximum name length */ + v.val.string.len = strlen(attname); + v.val.string.val = attname; + + result->res = pushJsonbValue(&result->parseState, WJB_KEY, &v); + + val = heap_getattr(tuple, i + 1, tupdesc, &isnull); + + if (isnull) + { + tcategory = JSONBTYPE_NULL; + outfuncoid = InvalidOid; + } + else + jsonb_categorize_type(att->atttypid, &tcategory, &outfuncoid); + + datum_to_jsonb(val, isnull, result, tcategory, outfuncoid, false); + } + + result->res = pushJsonbValue(&result->parseState, WJB_END_OBJECT, NULL); + ReleaseTupleDesc(tupdesc); +} + +/* + * Append JSON text for "val" to "result". + * + * This is just a thin wrapper around datum_to_jsonb. If the same type will be + * printed many times, avoid using this; better to do the jsonb_categorize_type + * lookups only once. + */ + +static void +add_jsonb(Datum val, bool is_null, JsonbInState *result, + Oid val_type, bool key_scalar) +{ + JsonbTypeCategory tcategory; + Oid outfuncoid; + + if (val_type == InvalidOid) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("could not determine input data type"))); + + if (is_null) + { + tcategory = JSONBTYPE_NULL; + outfuncoid = InvalidOid; + } + else + jsonb_categorize_type(val_type, + &tcategory, &outfuncoid); + + datum_to_jsonb(val, is_null, result, tcategory, outfuncoid, key_scalar); +} + +/* + * Is the given type immutable when coming out of a JSONB context? + * + * At present, datetimes are all considered mutable, because they + * depend on timezone. XXX we should also drill down into objects and + * arrays, but do not. + */ +bool +to_jsonb_is_immutable(Oid typoid) +{ + JsonbTypeCategory tcategory; + Oid outfuncoid; + + jsonb_categorize_type(typoid, &tcategory, &outfuncoid); + + switch (tcategory) + { + case JSONBTYPE_NULL: + case JSONBTYPE_BOOL: + case JSONBTYPE_JSON: + case JSONBTYPE_JSONB: + return true; + + case JSONBTYPE_DATE: + case JSONBTYPE_TIMESTAMP: + case JSONBTYPE_TIMESTAMPTZ: + return false; + + case JSONBTYPE_ARRAY: + return false; /* TODO recurse into elements */ + + case JSONBTYPE_COMPOSITE: + return false; /* TODO recurse into fields */ + + case JSONBTYPE_NUMERIC: + case JSONBTYPE_JSONCAST: + case JSONBTYPE_OTHER: + return func_volatile(outfuncoid) == PROVOLATILE_IMMUTABLE; + } + + return false; /* not reached */ +} + +/* + * SQL function to_jsonb(anyvalue) + */ +Datum +to_jsonb(PG_FUNCTION_ARGS) +{ + Datum val = PG_GETARG_DATUM(0); + Oid val_type = get_fn_expr_argtype(fcinfo->flinfo, 0); + JsonbInState result; + JsonbTypeCategory tcategory; + Oid outfuncoid; + + if (val_type == InvalidOid) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("could not determine input data type"))); + + jsonb_categorize_type(val_type, + &tcategory, &outfuncoid); + + memset(&result, 0, sizeof(JsonbInState)); + + datum_to_jsonb(val, false, &result, tcategory, outfuncoid, false); + + PG_RETURN_POINTER(JsonbValueToJsonb(result.res)); +} + +Datum +jsonb_build_object_worker(int nargs, Datum *args, bool *nulls, Oid *types, + bool absent_on_null, bool unique_keys) +{ + int i; + JsonbInState result; + + if (nargs % 2 != 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("argument list must have even number of elements"), + /* translator: %s is a SQL function name */ + errhint("The arguments of %s must consist of alternating keys and values.", + "jsonb_build_object()"))); + + memset(&result, 0, sizeof(JsonbInState)); + + result.res = pushJsonbValue(&result.parseState, WJB_BEGIN_OBJECT, NULL); + result.parseState->unique_keys = unique_keys; + result.parseState->skip_nulls = absent_on_null; + + for (i = 0; i < nargs; i += 2) + { + /* process key */ + bool skip; + + if (nulls[i]) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("argument %d: key must not be null", i + 1))); + + /* skip null values if absent_on_null */ + skip = absent_on_null && nulls[i + 1]; + + /* we need to save skipped keys for the key uniqueness check */ + if (skip && !unique_keys) + continue; + + add_jsonb(args[i], false, &result, types[i], true); + + /* process value */ + add_jsonb(args[i + 1], nulls[i + 1], &result, types[i + 1], false); + } + + result.res = pushJsonbValue(&result.parseState, WJB_END_OBJECT, NULL); + + return JsonbPGetDatum(JsonbValueToJsonb(result.res)); +} + +/* + * SQL function jsonb_build_object(variadic "any") + */ +Datum +jsonb_build_object(PG_FUNCTION_ARGS) +{ + Datum *args; + bool *nulls; + Oid *types; + + /* build argument values to build the object */ + int nargs = extract_variadic_args(fcinfo, 0, true, + &args, &types, &nulls); + + if (nargs < 0) + PG_RETURN_NULL(); + + PG_RETURN_DATUM(jsonb_build_object_worker(nargs, args, nulls, types, false, false)); +} + +/* + * degenerate case of jsonb_build_object where it gets 0 arguments. + */ +Datum +jsonb_build_object_noargs(PG_FUNCTION_ARGS) +{ + JsonbInState result; + + memset(&result, 0, sizeof(JsonbInState)); + + (void) pushJsonbValue(&result.parseState, WJB_BEGIN_OBJECT, NULL); + result.res = pushJsonbValue(&result.parseState, WJB_END_OBJECT, NULL); + + PG_RETURN_POINTER(JsonbValueToJsonb(result.res)); +} + +Datum +jsonb_build_array_worker(int nargs, Datum *args, bool *nulls, Oid *types, + bool absent_on_null) +{ + int i; + JsonbInState result; + + memset(&result, 0, sizeof(JsonbInState)); + + result.res = pushJsonbValue(&result.parseState, WJB_BEGIN_ARRAY, NULL); + + for (i = 0; i < nargs; i++) + { + if (absent_on_null && nulls[i]) + continue; + + add_jsonb(args[i], nulls[i], &result, types[i], false); + } + + result.res = pushJsonbValue(&result.parseState, WJB_END_ARRAY, NULL); + + return JsonbPGetDatum(JsonbValueToJsonb(result.res)); +} + +/* + * SQL function jsonb_build_array(variadic "any") + */ +Datum +jsonb_build_array(PG_FUNCTION_ARGS) +{ + Datum *args; + bool *nulls; + Oid *types; + + /* build argument values to build the object */ + int nargs = extract_variadic_args(fcinfo, 0, true, + &args, &types, &nulls); + + if (nargs < 0) + PG_RETURN_NULL(); + + PG_RETURN_DATUM(jsonb_build_array_worker(nargs, args, nulls, types, false)); +} + + +/* + * degenerate case of jsonb_build_array where it gets 0 arguments. + */ +Datum +jsonb_build_array_noargs(PG_FUNCTION_ARGS) +{ + JsonbInState result; + + memset(&result, 0, sizeof(JsonbInState)); + + (void) pushJsonbValue(&result.parseState, WJB_BEGIN_ARRAY, NULL); + result.res = pushJsonbValue(&result.parseState, WJB_END_ARRAY, NULL); + + PG_RETURN_POINTER(JsonbValueToJsonb(result.res)); +} + + +/* + * SQL function jsonb_object(text[]) + * + * take a one or two dimensional array of text as name value pairs + * for a jsonb object. + * + */ +Datum +jsonb_object(PG_FUNCTION_ARGS) +{ + ArrayType *in_array = PG_GETARG_ARRAYTYPE_P(0); + int ndims = ARR_NDIM(in_array); + Datum *in_datums; + bool *in_nulls; + int in_count, + count, + i; + JsonbInState result; + + memset(&result, 0, sizeof(JsonbInState)); + + (void) pushJsonbValue(&result.parseState, WJB_BEGIN_OBJECT, NULL); + + switch (ndims) + { + case 0: + goto close_object; + break; + + case 1: + if ((ARR_DIMS(in_array)[0]) % 2) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("array must have even number of elements"))); + break; + + case 2: + if ((ARR_DIMS(in_array)[1]) != 2) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("array must have two columns"))); + break; + + default: + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("wrong number of array subscripts"))); + } + + deconstruct_array_builtin(in_array, TEXTOID, &in_datums, &in_nulls, &in_count); + + count = in_count / 2; + + for (i = 0; i < count; ++i) + { + JsonbValue v; + char *str; + int len; + + if (in_nulls[i * 2]) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("null value not allowed for object key"))); + + str = TextDatumGetCString(in_datums[i * 2]); + len = strlen(str); + + v.type = jbvString; + + v.val.string.len = len; + v.val.string.val = str; + + (void) pushJsonbValue(&result.parseState, WJB_KEY, &v); + + if (in_nulls[i * 2 + 1]) + { + v.type = jbvNull; + } + else + { + str = TextDatumGetCString(in_datums[i * 2 + 1]); + len = strlen(str); + + v.type = jbvString; + + v.val.string.len = len; + v.val.string.val = str; + } + + (void) pushJsonbValue(&result.parseState, WJB_VALUE, &v); + } + + pfree(in_datums); + pfree(in_nulls); + +close_object: + result.res = pushJsonbValue(&result.parseState, WJB_END_OBJECT, NULL); + + PG_RETURN_POINTER(JsonbValueToJsonb(result.res)); +} + +/* + * SQL function jsonb_object(text[], text[]) + * + * take separate name and value arrays of text to construct a jsonb object + * pairwise. + */ +Datum +jsonb_object_two_arg(PG_FUNCTION_ARGS) +{ + ArrayType *key_array = PG_GETARG_ARRAYTYPE_P(0); + ArrayType *val_array = PG_GETARG_ARRAYTYPE_P(1); + int nkdims = ARR_NDIM(key_array); + int nvdims = ARR_NDIM(val_array); + Datum *key_datums, + *val_datums; + bool *key_nulls, + *val_nulls; + int key_count, + val_count, + i; + JsonbInState result; + + memset(&result, 0, sizeof(JsonbInState)); + + (void) pushJsonbValue(&result.parseState, WJB_BEGIN_OBJECT, NULL); + + if (nkdims > 1 || nkdims != nvdims) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("wrong number of array subscripts"))); + + if (nkdims == 0) + goto close_object; + + deconstruct_array_builtin(key_array, TEXTOID, &key_datums, &key_nulls, &key_count); + deconstruct_array_builtin(val_array, TEXTOID, &val_datums, &val_nulls, &val_count); + + if (key_count != val_count) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("mismatched array dimensions"))); + + for (i = 0; i < key_count; ++i) + { + JsonbValue v; + char *str; + int len; + + if (key_nulls[i]) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("null value not allowed for object key"))); + + str = TextDatumGetCString(key_datums[i]); + len = strlen(str); + + v.type = jbvString; + + v.val.string.len = len; + v.val.string.val = str; + + (void) pushJsonbValue(&result.parseState, WJB_KEY, &v); + + if (val_nulls[i]) + { + v.type = jbvNull; + } + else + { + str = TextDatumGetCString(val_datums[i]); + len = strlen(str); + + v.type = jbvString; + + v.val.string.len = len; + v.val.string.val = str; + } + + (void) pushJsonbValue(&result.parseState, WJB_VALUE, &v); + } + + pfree(key_datums); + pfree(key_nulls); + pfree(val_datums); + pfree(val_nulls); + +close_object: + result.res = pushJsonbValue(&result.parseState, WJB_END_OBJECT, NULL); + + PG_RETURN_POINTER(JsonbValueToJsonb(result.res)); +} + + +/* + * shallow clone of a parse state, suitable for use in aggregate + * final functions that will only append to the values rather than + * change them. + */ +static JsonbParseState * +clone_parse_state(JsonbParseState *state) +{ + JsonbParseState *result, + *icursor, + *ocursor; + + if (state == NULL) + return NULL; + + result = palloc(sizeof(JsonbParseState)); + icursor = state; + ocursor = result; + for (;;) + { + ocursor->contVal = icursor->contVal; + ocursor->size = icursor->size; + ocursor->unique_keys = icursor->unique_keys; + ocursor->skip_nulls = icursor->skip_nulls; + icursor = icursor->next; + if (icursor == NULL) + break; + ocursor->next = palloc(sizeof(JsonbParseState)); + ocursor = ocursor->next; + } + ocursor->next = NULL; + + return result; +} + +static Datum +jsonb_agg_transfn_worker(FunctionCallInfo fcinfo, bool absent_on_null) +{ + MemoryContext oldcontext, + aggcontext; + JsonbAggState *state; + JsonbInState elem; + Datum val; + JsonbInState *result; + bool single_scalar = false; + JsonbIterator *it; + Jsonb *jbelem; + JsonbValue v; + JsonbIteratorToken type; + + if (!AggCheckCallContext(fcinfo, &aggcontext)) + { + /* cannot be called directly because of internal-type argument */ + elog(ERROR, "jsonb_agg_transfn called in non-aggregate context"); + } + + /* set up the accumulator on the first go round */ + + if (PG_ARGISNULL(0)) + { + Oid arg_type = get_fn_expr_argtype(fcinfo->flinfo, 1); + + if (arg_type == InvalidOid) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("could not determine input data type"))); + + oldcontext = MemoryContextSwitchTo(aggcontext); + state = palloc(sizeof(JsonbAggState)); + result = palloc0(sizeof(JsonbInState)); + state->res = result; + result->res = pushJsonbValue(&result->parseState, + WJB_BEGIN_ARRAY, NULL); + MemoryContextSwitchTo(oldcontext); + + jsonb_categorize_type(arg_type, &state->val_category, + &state->val_output_func); + } + else + { + state = (JsonbAggState *) PG_GETARG_POINTER(0); + result = state->res; + } + + if (absent_on_null && PG_ARGISNULL(1)) + PG_RETURN_POINTER(state); + + /* turn the argument into jsonb in the normal function context */ + + val = PG_ARGISNULL(1) ? (Datum) 0 : PG_GETARG_DATUM(1); + + memset(&elem, 0, sizeof(JsonbInState)); + + datum_to_jsonb(val, PG_ARGISNULL(1), &elem, state->val_category, + state->val_output_func, false); + + jbelem = JsonbValueToJsonb(elem.res); + + /* switch to the aggregate context for accumulation operations */ + + oldcontext = MemoryContextSwitchTo(aggcontext); + + it = JsonbIteratorInit(&jbelem->root); + + while ((type = JsonbIteratorNext(&it, &v, false)) != WJB_DONE) + { + switch (type) + { + case WJB_BEGIN_ARRAY: + if (v.val.array.rawScalar) + single_scalar = true; + else + result->res = pushJsonbValue(&result->parseState, + type, NULL); + break; + case WJB_END_ARRAY: + if (!single_scalar) + result->res = pushJsonbValue(&result->parseState, + type, NULL); + break; + case WJB_BEGIN_OBJECT: + case WJB_END_OBJECT: + result->res = pushJsonbValue(&result->parseState, + type, NULL); + break; + case WJB_ELEM: + case WJB_KEY: + case WJB_VALUE: + if (v.type == jbvString) + { + /* copy string values in the aggregate context */ + char *buf = palloc(v.val.string.len + 1); + + snprintf(buf, v.val.string.len + 1, "%s", v.val.string.val); + v.val.string.val = buf; + } + else if (v.type == jbvNumeric) + { + /* same for numeric */ + v.val.numeric = + DatumGetNumeric(DirectFunctionCall1(numeric_uplus, + NumericGetDatum(v.val.numeric))); + } + result->res = pushJsonbValue(&result->parseState, + type, &v); + break; + default: + elog(ERROR, "unknown jsonb iterator token type"); + } + } + + MemoryContextSwitchTo(oldcontext); + + PG_RETURN_POINTER(state); +} + +/* + * jsonb_agg aggregate function + */ +Datum +jsonb_agg_transfn(PG_FUNCTION_ARGS) +{ + return jsonb_agg_transfn_worker(fcinfo, false); +} + +/* + * jsonb_agg_strict aggregate function + */ +Datum +jsonb_agg_strict_transfn(PG_FUNCTION_ARGS) +{ + return jsonb_agg_transfn_worker(fcinfo, true); +} + +Datum +jsonb_agg_finalfn(PG_FUNCTION_ARGS) +{ + JsonbAggState *arg; + JsonbInState result; + Jsonb *out; + + /* cannot be called directly because of internal-type argument */ + Assert(AggCheckCallContext(fcinfo, NULL)); + + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); /* returns null iff no input values */ + + arg = (JsonbAggState *) PG_GETARG_POINTER(0); + + /* + * We need to do a shallow clone of the argument in case the final + * function is called more than once, so we avoid changing the argument. A + * shallow clone is sufficient as we aren't going to change any of the + * values, just add the final array end marker. + */ + memset(&result, 0, sizeof(JsonbInState)); + + result.parseState = clone_parse_state(arg->res->parseState); + + result.res = pushJsonbValue(&result.parseState, + WJB_END_ARRAY, NULL); + + out = JsonbValueToJsonb(result.res); + + PG_RETURN_POINTER(out); +} + +static Datum +jsonb_object_agg_transfn_worker(FunctionCallInfo fcinfo, + bool absent_on_null, bool unique_keys) +{ + MemoryContext oldcontext, + aggcontext; + JsonbInState elem; + JsonbAggState *state; + Datum val; + JsonbInState *result; + bool single_scalar; + JsonbIterator *it; + Jsonb *jbkey, + *jbval; + JsonbValue v; + JsonbIteratorToken type; + bool skip; + + if (!AggCheckCallContext(fcinfo, &aggcontext)) + { + /* cannot be called directly because of internal-type argument */ + elog(ERROR, "jsonb_object_agg_transfn called in non-aggregate context"); + } + + /* set up the accumulator on the first go round */ + + if (PG_ARGISNULL(0)) + { + Oid arg_type; + + oldcontext = MemoryContextSwitchTo(aggcontext); + state = palloc(sizeof(JsonbAggState)); + result = palloc0(sizeof(JsonbInState)); + state->res = result; + result->res = pushJsonbValue(&result->parseState, + WJB_BEGIN_OBJECT, NULL); + result->parseState->unique_keys = unique_keys; + result->parseState->skip_nulls = absent_on_null; + + MemoryContextSwitchTo(oldcontext); + + arg_type = get_fn_expr_argtype(fcinfo->flinfo, 1); + + if (arg_type == InvalidOid) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("could not determine input data type"))); + + jsonb_categorize_type(arg_type, &state->key_category, + &state->key_output_func); + + arg_type = get_fn_expr_argtype(fcinfo->flinfo, 2); + + if (arg_type == InvalidOid) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("could not determine input data type"))); + + jsonb_categorize_type(arg_type, &state->val_category, + &state->val_output_func); + } + else + { + state = (JsonbAggState *) PG_GETARG_POINTER(0); + result = state->res; + } + + /* turn the argument into jsonb in the normal function context */ + + if (PG_ARGISNULL(1)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("field name must not be null"))); + + /* + * Skip null values if absent_on_null unless key uniqueness check is + * needed (because we must save keys in this case). + */ + skip = absent_on_null && PG_ARGISNULL(2); + + if (skip && !unique_keys) + PG_RETURN_POINTER(state); + + val = PG_GETARG_DATUM(1); + + memset(&elem, 0, sizeof(JsonbInState)); + + datum_to_jsonb(val, false, &elem, state->key_category, + state->key_output_func, true); + + jbkey = JsonbValueToJsonb(elem.res); + + val = PG_ARGISNULL(2) ? (Datum) 0 : PG_GETARG_DATUM(2); + + memset(&elem, 0, sizeof(JsonbInState)); + + datum_to_jsonb(val, PG_ARGISNULL(2), &elem, state->val_category, + state->val_output_func, false); + + jbval = JsonbValueToJsonb(elem.res); + + it = JsonbIteratorInit(&jbkey->root); + + /* switch to the aggregate context for accumulation operations */ + + oldcontext = MemoryContextSwitchTo(aggcontext); + + /* + * keys should be scalar, and we should have already checked for that + * above when calling datum_to_jsonb, so we only need to look for these + * things. + */ + + while ((type = JsonbIteratorNext(&it, &v, false)) != WJB_DONE) + { + switch (type) + { + case WJB_BEGIN_ARRAY: + if (!v.val.array.rawScalar) + elog(ERROR, "unexpected structure for key"); + break; + case WJB_ELEM: + if (v.type == jbvString) + { + /* copy string values in the aggregate context */ + char *buf = palloc(v.val.string.len + 1); + + snprintf(buf, v.val.string.len + 1, "%s", v.val.string.val); + v.val.string.val = buf; + } + else + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("object keys must be strings"))); + } + result->res = pushJsonbValue(&result->parseState, + WJB_KEY, &v); + + if (skip) + { + v.type = jbvNull; + result->res = pushJsonbValue(&result->parseState, + WJB_VALUE, &v); + MemoryContextSwitchTo(oldcontext); + PG_RETURN_POINTER(state); + } + + break; + case WJB_END_ARRAY: + break; + default: + elog(ERROR, "unexpected structure for key"); + break; + } + } + + it = JsonbIteratorInit(&jbval->root); + + single_scalar = false; + + /* + * values can be anything, including structured and null, so we treat them + * as in json_agg_transfn, except that single scalars are always pushed as + * WJB_VALUE items. + */ + + while ((type = JsonbIteratorNext(&it, &v, false)) != WJB_DONE) + { + switch (type) + { + case WJB_BEGIN_ARRAY: + if (v.val.array.rawScalar) + single_scalar = true; + else + result->res = pushJsonbValue(&result->parseState, + type, NULL); + break; + case WJB_END_ARRAY: + if (!single_scalar) + result->res = pushJsonbValue(&result->parseState, + type, NULL); + break; + case WJB_BEGIN_OBJECT: + case WJB_END_OBJECT: + result->res = pushJsonbValue(&result->parseState, + type, NULL); + break; + case WJB_ELEM: + case WJB_KEY: + case WJB_VALUE: + if (v.type == jbvString) + { + /* copy string values in the aggregate context */ + char *buf = palloc(v.val.string.len + 1); + + snprintf(buf, v.val.string.len + 1, "%s", v.val.string.val); + v.val.string.val = buf; + } + else if (v.type == jbvNumeric) + { + /* same for numeric */ + v.val.numeric = + DatumGetNumeric(DirectFunctionCall1(numeric_uplus, + NumericGetDatum(v.val.numeric))); + } + result->res = pushJsonbValue(&result->parseState, + single_scalar ? WJB_VALUE : type, + &v); + break; + default: + elog(ERROR, "unknown jsonb iterator token type"); + } + } + + MemoryContextSwitchTo(oldcontext); + + PG_RETURN_POINTER(state); +} + +/* + * jsonb_object_agg aggregate function + */ +Datum +jsonb_object_agg_transfn(PG_FUNCTION_ARGS) +{ + return jsonb_object_agg_transfn_worker(fcinfo, false, false); +} + + +/* + * jsonb_object_agg_strict aggregate function + */ +Datum +jsonb_object_agg_strict_transfn(PG_FUNCTION_ARGS) +{ + return jsonb_object_agg_transfn_worker(fcinfo, true, false); +} + +/* + * jsonb_object_agg_unique aggregate function + */ +Datum +jsonb_object_agg_unique_transfn(PG_FUNCTION_ARGS) +{ + return jsonb_object_agg_transfn_worker(fcinfo, false, true); +} + +/* + * jsonb_object_agg_unique_strict aggregate function + */ +Datum +jsonb_object_agg_unique_strict_transfn(PG_FUNCTION_ARGS) +{ + return jsonb_object_agg_transfn_worker(fcinfo, true, true); +} + +Datum +jsonb_object_agg_finalfn(PG_FUNCTION_ARGS) +{ + JsonbAggState *arg; + JsonbInState result; + Jsonb *out; + + /* cannot be called directly because of internal-type argument */ + Assert(AggCheckCallContext(fcinfo, NULL)); + + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); /* returns null iff no input values */ + + arg = (JsonbAggState *) PG_GETARG_POINTER(0); + + /* + * We need to do a shallow clone of the argument's res field in case the + * final function is called more than once, so we avoid changing the + * aggregate state value. A shallow clone is sufficient as we aren't + * going to change any of the values, just add the final object end + * marker. + */ + memset(&result, 0, sizeof(JsonbInState)); + + result.parseState = clone_parse_state(arg->res->parseState); + + result.res = pushJsonbValue(&result.parseState, + WJB_END_OBJECT, NULL); + + out = JsonbValueToJsonb(result.res); + + PG_RETURN_POINTER(out); +} + + +/* + * Extract scalar value from raw-scalar pseudo-array jsonb. + */ +bool +JsonbExtractScalar(JsonbContainer *jbc, JsonbValue *res) +{ + JsonbIterator *it; + JsonbIteratorToken tok PG_USED_FOR_ASSERTS_ONLY; + JsonbValue tmp; + + if (!JsonContainerIsArray(jbc) || !JsonContainerIsScalar(jbc)) + { + /* inform caller about actual type of container */ + res->type = (JsonContainerIsArray(jbc)) ? jbvArray : jbvObject; + return false; + } + + /* + * A root scalar is stored as an array of one element, so we get the array + * and then its first (and only) member. + */ + it = JsonbIteratorInit(jbc); + + tok = JsonbIteratorNext(&it, &tmp, true); + Assert(tok == WJB_BEGIN_ARRAY); + Assert(tmp.val.array.nElems == 1 && tmp.val.array.rawScalar); + + tok = JsonbIteratorNext(&it, res, true); + Assert(tok == WJB_ELEM); + Assert(IsAJsonbScalar(res)); + + tok = JsonbIteratorNext(&it, &tmp, true); + Assert(tok == WJB_END_ARRAY); + + tok = JsonbIteratorNext(&it, &tmp, true); + Assert(tok == WJB_DONE); + + return true; +} + +/* + * Emit correct, translatable cast error message + */ +static void +cannotCastJsonbValue(enum jbvType type, const char *sqltype) +{ + static const struct + { + enum jbvType type; + const char *msg; + } + messages[] = + { + {jbvNull, gettext_noop("cannot cast jsonb null to type %s")}, + {jbvString, gettext_noop("cannot cast jsonb string to type %s")}, + {jbvNumeric, gettext_noop("cannot cast jsonb numeric to type %s")}, + {jbvBool, gettext_noop("cannot cast jsonb boolean to type %s")}, + {jbvArray, gettext_noop("cannot cast jsonb array to type %s")}, + {jbvObject, gettext_noop("cannot cast jsonb object to type %s")}, + {jbvBinary, gettext_noop("cannot cast jsonb array or object to type %s")} + }; + int i; + + for (i = 0; i < lengthof(messages); i++) + if (messages[i].type == type) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg(messages[i].msg, sqltype))); + + /* should be unreachable */ + elog(ERROR, "unknown jsonb type: %d", (int) type); +} + +Datum +jsonb_bool(PG_FUNCTION_ARGS) +{ + Jsonb *in = PG_GETARG_JSONB_P(0); + JsonbValue v; + + if (!JsonbExtractScalar(&in->root, &v) || v.type != jbvBool) + cannotCastJsonbValue(v.type, "boolean"); + + PG_FREE_IF_COPY(in, 0); + + PG_RETURN_BOOL(v.val.boolean); +} + +Datum +jsonb_numeric(PG_FUNCTION_ARGS) +{ + Jsonb *in = PG_GETARG_JSONB_P(0); + JsonbValue v; + Numeric retValue; + + if (!JsonbExtractScalar(&in->root, &v) || v.type != jbvNumeric) + cannotCastJsonbValue(v.type, "numeric"); + + /* + * v.val.numeric points into jsonb body, so we need to make a copy to + * return + */ + retValue = DatumGetNumericCopy(NumericGetDatum(v.val.numeric)); + + PG_FREE_IF_COPY(in, 0); + + PG_RETURN_NUMERIC(retValue); +} + +Datum +jsonb_int2(PG_FUNCTION_ARGS) +{ + Jsonb *in = PG_GETARG_JSONB_P(0); + JsonbValue v; + Datum retValue; + + if (!JsonbExtractScalar(&in->root, &v) || v.type != jbvNumeric) + cannotCastJsonbValue(v.type, "smallint"); + + retValue = DirectFunctionCall1(numeric_int2, + NumericGetDatum(v.val.numeric)); + + PG_FREE_IF_COPY(in, 0); + + PG_RETURN_DATUM(retValue); +} + +Datum +jsonb_int4(PG_FUNCTION_ARGS) +{ + Jsonb *in = PG_GETARG_JSONB_P(0); + JsonbValue v; + Datum retValue; + + if (!JsonbExtractScalar(&in->root, &v) || v.type != jbvNumeric) + cannotCastJsonbValue(v.type, "integer"); + + retValue = DirectFunctionCall1(numeric_int4, + NumericGetDatum(v.val.numeric)); + + PG_FREE_IF_COPY(in, 0); + + PG_RETURN_DATUM(retValue); +} + +Datum +jsonb_int8(PG_FUNCTION_ARGS) +{ + Jsonb *in = PG_GETARG_JSONB_P(0); + JsonbValue v; + Datum retValue; + + if (!JsonbExtractScalar(&in->root, &v) || v.type != jbvNumeric) + cannotCastJsonbValue(v.type, "bigint"); + + retValue = DirectFunctionCall1(numeric_int8, + NumericGetDatum(v.val.numeric)); + + PG_FREE_IF_COPY(in, 0); + + PG_RETURN_DATUM(retValue); +} + +Datum +jsonb_float4(PG_FUNCTION_ARGS) +{ + Jsonb *in = PG_GETARG_JSONB_P(0); + JsonbValue v; + Datum retValue; + + if (!JsonbExtractScalar(&in->root, &v) || v.type != jbvNumeric) + cannotCastJsonbValue(v.type, "real"); + + retValue = DirectFunctionCall1(numeric_float4, + NumericGetDatum(v.val.numeric)); + + PG_FREE_IF_COPY(in, 0); + + PG_RETURN_DATUM(retValue); +} + +Datum +jsonb_float8(PG_FUNCTION_ARGS) +{ + Jsonb *in = PG_GETARG_JSONB_P(0); + JsonbValue v; + Datum retValue; + + if (!JsonbExtractScalar(&in->root, &v) || v.type != jbvNumeric) + cannotCastJsonbValue(v.type, "double precision"); + + retValue = DirectFunctionCall1(numeric_float8, + NumericGetDatum(v.val.numeric)); + + PG_FREE_IF_COPY(in, 0); + + PG_RETURN_DATUM(retValue); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/jsonb_gin.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/jsonb_gin.c new file mode 100644 index 00000000000..e941439d749 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/jsonb_gin.c @@ -0,0 +1,1409 @@ +/*------------------------------------------------------------------------- + * + * jsonb_gin.c + * GIN support functions for jsonb + * + * Copyright (c) 2014-2023, PostgreSQL Global Development Group + * + * We provide two opclasses for jsonb indexing: jsonb_ops and jsonb_path_ops. + * For their description see json.sgml and comments in jsonb.h. + * + * The operators support, among the others, "jsonb @? jsonpath" and + * "jsonb @@ jsonpath". Expressions containing these operators are easily + * expressed through each other. + * + * jb @? 'path' <=> jb @@ 'EXISTS(path)' + * jb @@ 'expr' <=> jb @? '$ ? (expr)' + * + * Thus, we're going to consider only @@ operator, while regarding @? operator + * the same is true for jb @@ 'EXISTS(path)'. + * + * Result of jsonpath query extraction is a tree, which leaf nodes are index + * entries and non-leaf nodes are AND/OR logical expressions. Basically we + * extract following statements out of jsonpath: + * + * 1) "accessors_chain = const", + * 2) "EXISTS(accessors_chain)". + * + * Accessors chain may consist of .key, [*] and [index] accessors. jsonb_ops + * additionally supports .* and .**. + * + * For now, both jsonb_ops and jsonb_path_ops supports only statements of + * the 1st find. jsonb_ops might also support statements of the 2nd kind, + * but given we have no statistics keys extracted from accessors chain + * are likely non-selective. Therefore, we choose to not confuse optimizer + * and skip statements of the 2nd kind altogether. In future versions that + * might be changed. + * + * In jsonb_ops statement of the 1st kind is split into expression of AND'ed + * keys and const. Sometimes const might be interpreted as both value or key + * in jsonb_ops. Then statement of 1st kind is decomposed into the expression + * below. + * + * key1 AND key2 AND ... AND keyN AND (const_as_value OR const_as_key) + * + * jsonb_path_ops transforms each statement of the 1st kind into single hash + * entry below. + * + * HASH(key1, key2, ... , keyN, const) + * + * Despite statements of the 2nd kind are not supported by both jsonb_ops and + * jsonb_path_ops, EXISTS(path) expressions might be still supported, + * when statements of 1st kind could be extracted out of their filters. + * + * IDENTIFICATION + * src/backend/utils/adt/jsonb_gin.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/gin.h" +#include "access/stratnum.h" +#include "catalog/pg_collation.h" +#include "catalog/pg_type.h" +#include "common/hashfn.h" +#include "miscadmin.h" +#include "utils/builtins.h" +#include "utils/jsonb.h" +#include "utils/jsonpath.h" +#include "utils/varlena.h" + +typedef struct PathHashStack +{ + uint32 hash; + struct PathHashStack *parent; +} PathHashStack; + +/* Buffer for GIN entries */ +typedef struct GinEntries +{ + Datum *buf; + int count; + int allocated; +} GinEntries; + +typedef enum JsonPathGinNodeType +{ + JSP_GIN_OR, + JSP_GIN_AND, + JSP_GIN_ENTRY +} JsonPathGinNodeType; + +typedef struct JsonPathGinNode JsonPathGinNode; + +/* Node in jsonpath expression tree */ +struct JsonPathGinNode +{ + JsonPathGinNodeType type; + union + { + int nargs; /* valid for OR and AND nodes */ + int entryIndex; /* index in GinEntries array, valid for ENTRY + * nodes after entries output */ + Datum entryDatum; /* path hash or key name/scalar, valid for + * ENTRY nodes before entries output */ + } val; + JsonPathGinNode *args[FLEXIBLE_ARRAY_MEMBER]; /* valid for OR and AND + * nodes */ +}; + +/* + * jsonb_ops entry extracted from jsonpath item. Corresponding path item + * may be: '.key', '.*', '.**', '[index]' or '[*]'. + * Entry type is stored in 'type' field. + */ +typedef struct JsonPathGinPathItem +{ + struct JsonPathGinPathItem *parent; + Datum keyName; /* key name (for '.key' path item) or NULL */ + JsonPathItemType type; /* type of jsonpath item */ +} JsonPathGinPathItem; + +/* GIN representation of the extracted json path */ +typedef union JsonPathGinPath +{ + JsonPathGinPathItem *items; /* list of path items (jsonb_ops) */ + uint32 hash; /* hash of the path (jsonb_path_ops) */ +} JsonPathGinPath; + +typedef struct JsonPathGinContext JsonPathGinContext; + +/* Callback, which stores information about path item into JsonPathGinPath */ +typedef bool (*JsonPathGinAddPathItemFunc) (JsonPathGinPath *path, + JsonPathItem *jsp); + +/* + * Callback, which extracts set of nodes from statement of 1st kind + * (scalar != NULL) or statement of 2nd kind (scalar == NULL). + */ +typedef List *(*JsonPathGinExtractNodesFunc) (JsonPathGinContext *cxt, + JsonPathGinPath path, + JsonbValue *scalar, + List *nodes); + +/* Context for jsonpath entries extraction */ +struct JsonPathGinContext +{ + JsonPathGinAddPathItemFunc add_path_item; + JsonPathGinExtractNodesFunc extract_nodes; + bool lax; +}; + +static Datum make_text_key(char flag, const char *str, int len); +static Datum make_scalar_key(const JsonbValue *scalarVal, bool is_key); + +static JsonPathGinNode *extract_jsp_bool_expr(JsonPathGinContext *cxt, + JsonPathGinPath path, JsonPathItem *jsp, bool not); + + +/* Initialize GinEntries struct */ +static void +init_gin_entries(GinEntries *entries, int preallocated) +{ + entries->allocated = preallocated; + entries->buf = preallocated ? palloc(sizeof(Datum) * preallocated) : NULL; + entries->count = 0; +} + +/* Add new entry to GinEntries */ +static int +add_gin_entry(GinEntries *entries, Datum entry) +{ + int id = entries->count; + + if (entries->count >= entries->allocated) + { + if (entries->allocated) + { + entries->allocated *= 2; + entries->buf = repalloc(entries->buf, + sizeof(Datum) * entries->allocated); + } + else + { + entries->allocated = 8; + entries->buf = palloc(sizeof(Datum) * entries->allocated); + } + } + + entries->buf[entries->count++] = entry; + + return id; +} + +/* + * + * jsonb_ops GIN opclass support functions + * + */ + +Datum +gin_compare_jsonb(PG_FUNCTION_ARGS) +{ + text *arg1 = PG_GETARG_TEXT_PP(0); + text *arg2 = PG_GETARG_TEXT_PP(1); + int32 result; + char *a1p, + *a2p; + int len1, + len2; + + a1p = VARDATA_ANY(arg1); + a2p = VARDATA_ANY(arg2); + + len1 = VARSIZE_ANY_EXHDR(arg1); + len2 = VARSIZE_ANY_EXHDR(arg2); + + /* Compare text as bttextcmp does, but always using C collation */ + result = varstr_cmp(a1p, len1, a2p, len2, C_COLLATION_OID); + + PG_FREE_IF_COPY(arg1, 0); + PG_FREE_IF_COPY(arg2, 1); + + PG_RETURN_INT32(result); +} + +Datum +gin_extract_jsonb(PG_FUNCTION_ARGS) +{ + Jsonb *jb = (Jsonb *) PG_GETARG_JSONB_P(0); + int32 *nentries = (int32 *) PG_GETARG_POINTER(1); + int total = JB_ROOT_COUNT(jb); + JsonbIterator *it; + JsonbValue v; + JsonbIteratorToken r; + GinEntries entries; + + /* If the root level is empty, we certainly have no keys */ + if (total == 0) + { + *nentries = 0; + PG_RETURN_POINTER(NULL); + } + + /* Otherwise, use 2 * root count as initial estimate of result size */ + init_gin_entries(&entries, 2 * total); + + it = JsonbIteratorInit(&jb->root); + + while ((r = JsonbIteratorNext(&it, &v, false)) != WJB_DONE) + { + switch (r) + { + case WJB_KEY: + add_gin_entry(&entries, make_scalar_key(&v, true)); + break; + case WJB_ELEM: + /* Pretend string array elements are keys, see jsonb.h */ + add_gin_entry(&entries, make_scalar_key(&v, v.type == jbvString)); + break; + case WJB_VALUE: + add_gin_entry(&entries, make_scalar_key(&v, false)); + break; + default: + /* we can ignore structural items */ + break; + } + } + + *nentries = entries.count; + + PG_RETURN_POINTER(entries.buf); +} + +/* Append JsonPathGinPathItem to JsonPathGinPath (jsonb_ops) */ +static bool +jsonb_ops__add_path_item(JsonPathGinPath *path, JsonPathItem *jsp) +{ + JsonPathGinPathItem *pentry; + Datum keyName; + + switch (jsp->type) + { + case jpiRoot: + path->items = NULL; /* reset path */ + return true; + + case jpiKey: + { + int len; + char *key = jspGetString(jsp, &len); + + keyName = make_text_key(JGINFLAG_KEY, key, len); + break; + } + + case jpiAny: + case jpiAnyKey: + case jpiAnyArray: + case jpiIndexArray: + keyName = PointerGetDatum(NULL); + break; + + default: + /* other path items like item methods are not supported */ + return false; + } + + pentry = palloc(sizeof(*pentry)); + + pentry->type = jsp->type; + pentry->keyName = keyName; + pentry->parent = path->items; + + path->items = pentry; + + return true; +} + +/* Combine existing path hash with next key hash (jsonb_path_ops) */ +static bool +jsonb_path_ops__add_path_item(JsonPathGinPath *path, JsonPathItem *jsp) +{ + switch (jsp->type) + { + case jpiRoot: + path->hash = 0; /* reset path hash */ + return true; + + case jpiKey: + { + JsonbValue jbv; + + jbv.type = jbvString; + jbv.val.string.val = jspGetString(jsp, &jbv.val.string.len); + + JsonbHashScalarValue(&jbv, &path->hash); + return true; + } + + case jpiIndexArray: + case jpiAnyArray: + return true; /* path hash is unchanged */ + + default: + /* other items (wildcard paths, item methods) are not supported */ + return false; + } +} + +static JsonPathGinNode * +make_jsp_entry_node(Datum entry) +{ + JsonPathGinNode *node = palloc(offsetof(JsonPathGinNode, args)); + + node->type = JSP_GIN_ENTRY; + node->val.entryDatum = entry; + + return node; +} + +static JsonPathGinNode * +make_jsp_entry_node_scalar(JsonbValue *scalar, bool iskey) +{ + return make_jsp_entry_node(make_scalar_key(scalar, iskey)); +} + +static JsonPathGinNode * +make_jsp_expr_node(JsonPathGinNodeType type, int nargs) +{ + JsonPathGinNode *node = palloc(offsetof(JsonPathGinNode, args) + + sizeof(node->args[0]) * nargs); + + node->type = type; + node->val.nargs = nargs; + + return node; +} + +static JsonPathGinNode * +make_jsp_expr_node_args(JsonPathGinNodeType type, List *args) +{ + JsonPathGinNode *node = make_jsp_expr_node(type, list_length(args)); + ListCell *lc; + int i = 0; + + foreach(lc, args) + node->args[i++] = lfirst(lc); + + return node; +} + +static JsonPathGinNode * +make_jsp_expr_node_binary(JsonPathGinNodeType type, + JsonPathGinNode *arg1, JsonPathGinNode *arg2) +{ + JsonPathGinNode *node = make_jsp_expr_node(type, 2); + + node->args[0] = arg1; + node->args[1] = arg2; + + return node; +} + +/* Append a list of nodes from the jsonpath (jsonb_ops). */ +static List * +jsonb_ops__extract_nodes(JsonPathGinContext *cxt, JsonPathGinPath path, + JsonbValue *scalar, List *nodes) +{ + JsonPathGinPathItem *pentry; + + if (scalar) + { + JsonPathGinNode *node; + + /* + * Append path entry nodes only if scalar is provided. See header + * comment for details. + */ + for (pentry = path.items; pentry; pentry = pentry->parent) + { + if (pentry->type == jpiKey) /* only keys are indexed */ + nodes = lappend(nodes, make_jsp_entry_node(pentry->keyName)); + } + + /* Append scalar node for equality queries. */ + if (scalar->type == jbvString) + { + JsonPathGinPathItem *last = path.items; + GinTernaryValue key_entry; + + /* + * Assuming that jsonb_ops interprets string array elements as + * keys, we may extract key or non-key entry or even both. In the + * latter case we create OR-node. It is possible in lax mode + * where arrays are automatically unwrapped, or in strict mode for + * jpiAny items. + */ + + if (cxt->lax) + key_entry = GIN_MAYBE; + else if (!last) /* root ($) */ + key_entry = GIN_FALSE; + else if (last->type == jpiAnyArray || last->type == jpiIndexArray) + key_entry = GIN_TRUE; + else if (last->type == jpiAny) + key_entry = GIN_MAYBE; + else + key_entry = GIN_FALSE; + + if (key_entry == GIN_MAYBE) + { + JsonPathGinNode *n1 = make_jsp_entry_node_scalar(scalar, true); + JsonPathGinNode *n2 = make_jsp_entry_node_scalar(scalar, false); + + node = make_jsp_expr_node_binary(JSP_GIN_OR, n1, n2); + } + else + { + node = make_jsp_entry_node_scalar(scalar, + key_entry == GIN_TRUE); + } + } + else + { + node = make_jsp_entry_node_scalar(scalar, false); + } + + nodes = lappend(nodes, node); + } + + return nodes; +} + +/* Append a list of nodes from the jsonpath (jsonb_path_ops). */ +static List * +jsonb_path_ops__extract_nodes(JsonPathGinContext *cxt, JsonPathGinPath path, + JsonbValue *scalar, List *nodes) +{ + if (scalar) + { + /* append path hash node for equality queries */ + uint32 hash = path.hash; + + JsonbHashScalarValue(scalar, &hash); + + return lappend(nodes, + make_jsp_entry_node(UInt32GetDatum(hash))); + } + else + { + /* jsonb_path_ops doesn't support EXISTS queries => nothing to append */ + return nodes; + } +} + +/* + * Extract a list of expression nodes that need to be AND-ed by the caller. + * Extracted expression is 'path == scalar' if 'scalar' is non-NULL, and + * 'EXISTS(path)' otherwise. + */ +static List * +extract_jsp_path_expr_nodes(JsonPathGinContext *cxt, JsonPathGinPath path, + JsonPathItem *jsp, JsonbValue *scalar) +{ + JsonPathItem next; + List *nodes = NIL; + + for (;;) + { + switch (jsp->type) + { + case jpiCurrent: + break; + + case jpiFilter: + { + JsonPathItem arg; + JsonPathGinNode *filter; + + jspGetArg(jsp, &arg); + + filter = extract_jsp_bool_expr(cxt, path, &arg, false); + + if (filter) + nodes = lappend(nodes, filter); + + break; + } + + default: + if (!cxt->add_path_item(&path, jsp)) + + /* + * Path is not supported by the index opclass, return only + * the extracted filter nodes. + */ + return nodes; + break; + } + + if (!jspGetNext(jsp, &next)) + break; + + jsp = &next; + } + + /* + * Append nodes from the path expression itself to the already extracted + * list of filter nodes. + */ + return cxt->extract_nodes(cxt, path, scalar, nodes); +} + +/* + * Extract an expression node from one of following jsonpath path expressions: + * EXISTS(jsp) (when 'scalar' is NULL) + * jsp == scalar (when 'scalar' is not NULL). + * + * The current path (@) is passed in 'path'. + */ +static JsonPathGinNode * +extract_jsp_path_expr(JsonPathGinContext *cxt, JsonPathGinPath path, + JsonPathItem *jsp, JsonbValue *scalar) +{ + /* extract a list of nodes to be AND-ed */ + List *nodes = extract_jsp_path_expr_nodes(cxt, path, jsp, scalar); + + if (nodes == NIL) + /* no nodes were extracted => full scan is needed for this path */ + return NULL; + + if (list_length(nodes) == 1) + return linitial(nodes); /* avoid extra AND-node */ + + /* construct AND-node for path with filters */ + return make_jsp_expr_node_args(JSP_GIN_AND, nodes); +} + +/* Recursively extract nodes from the boolean jsonpath expression. */ +static JsonPathGinNode * +extract_jsp_bool_expr(JsonPathGinContext *cxt, JsonPathGinPath path, + JsonPathItem *jsp, bool not) +{ + check_stack_depth(); + + switch (jsp->type) + { + case jpiAnd: /* expr && expr */ + case jpiOr: /* expr || expr */ + { + JsonPathItem arg; + JsonPathGinNode *larg; + JsonPathGinNode *rarg; + JsonPathGinNodeType type; + + jspGetLeftArg(jsp, &arg); + larg = extract_jsp_bool_expr(cxt, path, &arg, not); + + jspGetRightArg(jsp, &arg); + rarg = extract_jsp_bool_expr(cxt, path, &arg, not); + + if (!larg || !rarg) + { + if (jsp->type == jpiOr) + return NULL; + + return larg ? larg : rarg; + } + + type = not ^ (jsp->type == jpiAnd) ? JSP_GIN_AND : JSP_GIN_OR; + + return make_jsp_expr_node_binary(type, larg, rarg); + } + + case jpiNot: /* !expr */ + { + JsonPathItem arg; + + jspGetArg(jsp, &arg); + + /* extract child expression inverting 'not' flag */ + return extract_jsp_bool_expr(cxt, path, &arg, !not); + } + + case jpiExists: /* EXISTS(path) */ + { + JsonPathItem arg; + + if (not) + return NULL; /* NOT EXISTS is not supported */ + + jspGetArg(jsp, &arg); + + return extract_jsp_path_expr(cxt, path, &arg, NULL); + } + + case jpiNotEqual: + + /* + * 'not' == true case is not supported here because '!(path != + * scalar)' is not equivalent to 'path == scalar' in the general + * case because of sequence comparison semantics: 'path == scalar' + * === 'EXISTS (path, @ == scalar)', '!(path != scalar)' === + * 'FOR_ALL(path, @ == scalar)'. So, we should translate '!(path + * != scalar)' into GIN query 'path == scalar || EMPTY(path)', but + * 'EMPTY(path)' queries are not supported by the both jsonb + * opclasses. However in strict mode we could omit 'EMPTY(path)' + * part if the path can return exactly one item (it does not + * contain wildcard accessors or item methods like .keyvalue() + * etc.). + */ + return NULL; + + case jpiEqual: /* path == scalar */ + { + JsonPathItem left_item; + JsonPathItem right_item; + JsonPathItem *path_item; + JsonPathItem *scalar_item; + JsonbValue scalar; + + if (not) + return NULL; + + jspGetLeftArg(jsp, &left_item); + jspGetRightArg(jsp, &right_item); + + if (jspIsScalar(left_item.type)) + { + scalar_item = &left_item; + path_item = &right_item; + } + else if (jspIsScalar(right_item.type)) + { + scalar_item = &right_item; + path_item = &left_item; + } + else + return NULL; /* at least one operand should be a scalar */ + + switch (scalar_item->type) + { + case jpiNull: + scalar.type = jbvNull; + break; + case jpiBool: + scalar.type = jbvBool; + scalar.val.boolean = !!*scalar_item->content.value.data; + break; + case jpiNumeric: + scalar.type = jbvNumeric; + scalar.val.numeric = + (Numeric) scalar_item->content.value.data; + break; + case jpiString: + scalar.type = jbvString; + scalar.val.string.val = scalar_item->content.value.data; + scalar.val.string.len = + scalar_item->content.value.datalen; + break; + default: + elog(ERROR, "invalid scalar jsonpath item type: %d", + scalar_item->type); + return NULL; + } + + return extract_jsp_path_expr(cxt, path, path_item, &scalar); + } + + default: + return NULL; /* not a boolean expression */ + } +} + +/* Recursively emit all GIN entries found in the node tree */ +static void +emit_jsp_gin_entries(JsonPathGinNode *node, GinEntries *entries) +{ + check_stack_depth(); + + switch (node->type) + { + case JSP_GIN_ENTRY: + /* replace datum with its index in the array */ + node->val.entryIndex = add_gin_entry(entries, node->val.entryDatum); + break; + + case JSP_GIN_OR: + case JSP_GIN_AND: + { + int i; + + for (i = 0; i < node->val.nargs; i++) + emit_jsp_gin_entries(node->args[i], entries); + + break; + } + } +} + +/* + * Recursively extract GIN entries from jsonpath query. + * Root expression node is put into (*extra_data)[0]. + */ +static Datum * +extract_jsp_query(JsonPath *jp, StrategyNumber strat, bool pathOps, + int32 *nentries, Pointer **extra_data) +{ + JsonPathGinContext cxt; + JsonPathItem root; + JsonPathGinNode *node; + JsonPathGinPath path = {0}; + GinEntries entries = {0}; + + cxt.lax = (jp->header & JSONPATH_LAX) != 0; + + if (pathOps) + { + cxt.add_path_item = jsonb_path_ops__add_path_item; + cxt.extract_nodes = jsonb_path_ops__extract_nodes; + } + else + { + cxt.add_path_item = jsonb_ops__add_path_item; + cxt.extract_nodes = jsonb_ops__extract_nodes; + } + + jspInit(&root, jp); + + node = strat == JsonbJsonpathExistsStrategyNumber + ? extract_jsp_path_expr(&cxt, path, &root, NULL) + : extract_jsp_bool_expr(&cxt, path, &root, false); + + if (!node) + { + *nentries = 0; + return NULL; + } + + emit_jsp_gin_entries(node, &entries); + + *nentries = entries.count; + if (!*nentries) + return NULL; + + *extra_data = palloc0(sizeof(**extra_data) * entries.count); + **extra_data = (Pointer) node; + + return entries.buf; +} + +/* + * Recursively execute jsonpath expression. + * 'check' is a bool[] or a GinTernaryValue[] depending on 'ternary' flag. + */ +static GinTernaryValue +execute_jsp_gin_node(JsonPathGinNode *node, void *check, bool ternary) +{ + GinTernaryValue res; + GinTernaryValue v; + int i; + + switch (node->type) + { + case JSP_GIN_AND: + res = GIN_TRUE; + for (i = 0; i < node->val.nargs; i++) + { + v = execute_jsp_gin_node(node->args[i], check, ternary); + if (v == GIN_FALSE) + return GIN_FALSE; + else if (v == GIN_MAYBE) + res = GIN_MAYBE; + } + return res; + + case JSP_GIN_OR: + res = GIN_FALSE; + for (i = 0; i < node->val.nargs; i++) + { + v = execute_jsp_gin_node(node->args[i], check, ternary); + if (v == GIN_TRUE) + return GIN_TRUE; + else if (v == GIN_MAYBE) + res = GIN_MAYBE; + } + return res; + + case JSP_GIN_ENTRY: + { + int index = node->val.entryIndex; + + if (ternary) + return ((GinTernaryValue *) check)[index]; + else + return ((bool *) check)[index] ? GIN_TRUE : GIN_FALSE; + } + + default: + elog(ERROR, "invalid jsonpath gin node type: %d", node->type); + return GIN_FALSE; /* keep compiler quiet */ + } +} + +Datum +gin_extract_jsonb_query(PG_FUNCTION_ARGS) +{ + int32 *nentries = (int32 *) PG_GETARG_POINTER(1); + StrategyNumber strategy = PG_GETARG_UINT16(2); + int32 *searchMode = (int32 *) PG_GETARG_POINTER(6); + Datum *entries; + + if (strategy == JsonbContainsStrategyNumber) + { + /* Query is a jsonb, so just apply gin_extract_jsonb... */ + entries = (Datum *) + DatumGetPointer(DirectFunctionCall2(gin_extract_jsonb, + PG_GETARG_DATUM(0), + PointerGetDatum(nentries))); + /* ...although "contains {}" requires a full index scan */ + if (*nentries == 0) + *searchMode = GIN_SEARCH_MODE_ALL; + } + else if (strategy == JsonbExistsStrategyNumber) + { + /* Query is a text string, which we treat as a key */ + text *query = PG_GETARG_TEXT_PP(0); + + *nentries = 1; + entries = (Datum *) palloc(sizeof(Datum)); + entries[0] = make_text_key(JGINFLAG_KEY, + VARDATA_ANY(query), + VARSIZE_ANY_EXHDR(query)); + } + else if (strategy == JsonbExistsAnyStrategyNumber || + strategy == JsonbExistsAllStrategyNumber) + { + /* Query is a text array; each element is treated as a key */ + ArrayType *query = PG_GETARG_ARRAYTYPE_P(0); + Datum *key_datums; + bool *key_nulls; + int key_count; + int i, + j; + + deconstruct_array_builtin(query, TEXTOID, &key_datums, &key_nulls, &key_count); + + entries = (Datum *) palloc(sizeof(Datum) * key_count); + + for (i = 0, j = 0; i < key_count; i++) + { + /* Nulls in the array are ignored */ + if (key_nulls[i]) + continue; + /* We rely on the array elements not being toasted */ + entries[j++] = make_text_key(JGINFLAG_KEY, + VARDATA_ANY(key_datums[i]), + VARSIZE_ANY_EXHDR(key_datums[i])); + } + + *nentries = j; + /* ExistsAll with no keys should match everything */ + if (j == 0 && strategy == JsonbExistsAllStrategyNumber) + *searchMode = GIN_SEARCH_MODE_ALL; + } + else if (strategy == JsonbJsonpathPredicateStrategyNumber || + strategy == JsonbJsonpathExistsStrategyNumber) + { + JsonPath *jp = PG_GETARG_JSONPATH_P(0); + Pointer **extra_data = (Pointer **) PG_GETARG_POINTER(4); + + entries = extract_jsp_query(jp, strategy, false, nentries, extra_data); + + if (!entries) + *searchMode = GIN_SEARCH_MODE_ALL; + } + else + { + elog(ERROR, "unrecognized strategy number: %d", strategy); + entries = NULL; /* keep compiler quiet */ + } + + PG_RETURN_POINTER(entries); +} + +Datum +gin_consistent_jsonb(PG_FUNCTION_ARGS) +{ + bool *check = (bool *) PG_GETARG_POINTER(0); + StrategyNumber strategy = PG_GETARG_UINT16(1); + + /* Jsonb *query = PG_GETARG_JSONB_P(2); */ + int32 nkeys = PG_GETARG_INT32(3); + + Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); + bool *recheck = (bool *) PG_GETARG_POINTER(5); + bool res = true; + int32 i; + + if (strategy == JsonbContainsStrategyNumber) + { + /* + * We must always recheck, since we can't tell from the index whether + * the positions of the matched items match the structure of the query + * object. (Even if we could, we'd also have to worry about hashed + * keys and the index's failure to distinguish keys from string array + * elements.) However, the tuple certainly doesn't match unless it + * contains all the query keys. + */ + *recheck = true; + for (i = 0; i < nkeys; i++) + { + if (!check[i]) + { + res = false; + break; + } + } + } + else if (strategy == JsonbExistsStrategyNumber) + { + /* + * Although the key is certainly present in the index, we must recheck + * because (1) the key might be hashed, and (2) the index match might + * be for a key that's not at top level of the JSON object. For (1), + * we could look at the query key to see if it's hashed and not + * recheck if not, but the index lacks enough info to tell about (2). + */ + *recheck = true; + res = true; + } + else if (strategy == JsonbExistsAnyStrategyNumber) + { + /* As for plain exists, we must recheck */ + *recheck = true; + res = true; + } + else if (strategy == JsonbExistsAllStrategyNumber) + { + /* As for plain exists, we must recheck */ + *recheck = true; + /* ... but unless all the keys are present, we can say "false" */ + for (i = 0; i < nkeys; i++) + { + if (!check[i]) + { + res = false; + break; + } + } + } + else if (strategy == JsonbJsonpathPredicateStrategyNumber || + strategy == JsonbJsonpathExistsStrategyNumber) + { + *recheck = true; + + if (nkeys > 0) + { + Assert(extra_data && extra_data[0]); + res = execute_jsp_gin_node((JsonPathGinNode *) extra_data[0], check, + false) != GIN_FALSE; + } + } + else + elog(ERROR, "unrecognized strategy number: %d", strategy); + + PG_RETURN_BOOL(res); +} + +Datum +gin_triconsistent_jsonb(PG_FUNCTION_ARGS) +{ + GinTernaryValue *check = (GinTernaryValue *) PG_GETARG_POINTER(0); + StrategyNumber strategy = PG_GETARG_UINT16(1); + + /* Jsonb *query = PG_GETARG_JSONB_P(2); */ + int32 nkeys = PG_GETARG_INT32(3); + Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); + GinTernaryValue res = GIN_MAYBE; + int32 i; + + /* + * Note that we never return GIN_TRUE, only GIN_MAYBE or GIN_FALSE; this + * corresponds to always forcing recheck in the regular consistent + * function, for the reasons listed there. + */ + if (strategy == JsonbContainsStrategyNumber || + strategy == JsonbExistsAllStrategyNumber) + { + /* All extracted keys must be present */ + for (i = 0; i < nkeys; i++) + { + if (check[i] == GIN_FALSE) + { + res = GIN_FALSE; + break; + } + } + } + else if (strategy == JsonbExistsStrategyNumber || + strategy == JsonbExistsAnyStrategyNumber) + { + /* At least one extracted key must be present */ + res = GIN_FALSE; + for (i = 0; i < nkeys; i++) + { + if (check[i] == GIN_TRUE || + check[i] == GIN_MAYBE) + { + res = GIN_MAYBE; + break; + } + } + } + else if (strategy == JsonbJsonpathPredicateStrategyNumber || + strategy == JsonbJsonpathExistsStrategyNumber) + { + if (nkeys > 0) + { + Assert(extra_data && extra_data[0]); + res = execute_jsp_gin_node((JsonPathGinNode *) extra_data[0], check, + true); + + /* Should always recheck the result */ + if (res == GIN_TRUE) + res = GIN_MAYBE; + } + } + else + elog(ERROR, "unrecognized strategy number: %d", strategy); + + PG_RETURN_GIN_TERNARY_VALUE(res); +} + +/* + * + * jsonb_path_ops GIN opclass support functions + * + * In a jsonb_path_ops index, the GIN keys are uint32 hashes, one per JSON + * value; but the JSON key(s) leading to each value are also included in its + * hash computation. This means we can only support containment queries, + * but the index can distinguish, for example, {"foo": 42} from {"bar": 42} + * since different hashes will be generated. + * + */ + +Datum +gin_extract_jsonb_path(PG_FUNCTION_ARGS) +{ + Jsonb *jb = PG_GETARG_JSONB_P(0); + int32 *nentries = (int32 *) PG_GETARG_POINTER(1); + int total = JB_ROOT_COUNT(jb); + JsonbIterator *it; + JsonbValue v; + JsonbIteratorToken r; + PathHashStack tail; + PathHashStack *stack; + GinEntries entries; + + /* If the root level is empty, we certainly have no keys */ + if (total == 0) + { + *nentries = 0; + PG_RETURN_POINTER(NULL); + } + + /* Otherwise, use 2 * root count as initial estimate of result size */ + init_gin_entries(&entries, 2 * total); + + /* We keep a stack of partial hashes corresponding to parent key levels */ + tail.parent = NULL; + tail.hash = 0; + stack = &tail; + + it = JsonbIteratorInit(&jb->root); + + while ((r = JsonbIteratorNext(&it, &v, false)) != WJB_DONE) + { + PathHashStack *parent; + + switch (r) + { + case WJB_BEGIN_ARRAY: + case WJB_BEGIN_OBJECT: + /* Push a stack level for this object */ + parent = stack; + stack = (PathHashStack *) palloc(sizeof(PathHashStack)); + + /* + * We pass forward hashes from outer nesting levels so that + * the hashes for nested values will include outer keys as + * well as their own keys. + * + * Nesting an array within another array will not alter + * innermost scalar element hash values, but that seems + * inconsequential. + */ + stack->hash = parent->hash; + stack->parent = parent; + break; + case WJB_KEY: + /* mix this key into the current outer hash */ + JsonbHashScalarValue(&v, &stack->hash); + /* hash is now ready to incorporate the value */ + break; + case WJB_ELEM: + case WJB_VALUE: + /* mix the element or value's hash into the prepared hash */ + JsonbHashScalarValue(&v, &stack->hash); + /* and emit an index entry */ + add_gin_entry(&entries, UInt32GetDatum(stack->hash)); + /* reset hash for next key, value, or sub-object */ + stack->hash = stack->parent->hash; + break; + case WJB_END_ARRAY: + case WJB_END_OBJECT: + /* Pop the stack */ + parent = stack->parent; + pfree(stack); + stack = parent; + /* reset hash for next key, value, or sub-object */ + if (stack->parent) + stack->hash = stack->parent->hash; + else + stack->hash = 0; + break; + default: + elog(ERROR, "invalid JsonbIteratorNext rc: %d", (int) r); + } + } + + *nentries = entries.count; + + PG_RETURN_POINTER(entries.buf); +} + +Datum +gin_extract_jsonb_query_path(PG_FUNCTION_ARGS) +{ + int32 *nentries = (int32 *) PG_GETARG_POINTER(1); + StrategyNumber strategy = PG_GETARG_UINT16(2); + int32 *searchMode = (int32 *) PG_GETARG_POINTER(6); + Datum *entries; + + if (strategy == JsonbContainsStrategyNumber) + { + /* Query is a jsonb, so just apply gin_extract_jsonb_path ... */ + entries = (Datum *) + DatumGetPointer(DirectFunctionCall2(gin_extract_jsonb_path, + PG_GETARG_DATUM(0), + PointerGetDatum(nentries))); + + /* ... although "contains {}" requires a full index scan */ + if (*nentries == 0) + *searchMode = GIN_SEARCH_MODE_ALL; + } + else if (strategy == JsonbJsonpathPredicateStrategyNumber || + strategy == JsonbJsonpathExistsStrategyNumber) + { + JsonPath *jp = PG_GETARG_JSONPATH_P(0); + Pointer **extra_data = (Pointer **) PG_GETARG_POINTER(4); + + entries = extract_jsp_query(jp, strategy, true, nentries, extra_data); + + if (!entries) + *searchMode = GIN_SEARCH_MODE_ALL; + } + else + { + elog(ERROR, "unrecognized strategy number: %d", strategy); + entries = NULL; + } + + PG_RETURN_POINTER(entries); +} + +Datum +gin_consistent_jsonb_path(PG_FUNCTION_ARGS) +{ + bool *check = (bool *) PG_GETARG_POINTER(0); + StrategyNumber strategy = PG_GETARG_UINT16(1); + + /* Jsonb *query = PG_GETARG_JSONB_P(2); */ + int32 nkeys = PG_GETARG_INT32(3); + Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); + bool *recheck = (bool *) PG_GETARG_POINTER(5); + bool res = true; + int32 i; + + if (strategy == JsonbContainsStrategyNumber) + { + /* + * jsonb_path_ops is necessarily lossy, not only because of hash + * collisions but also because it doesn't preserve complete + * information about the structure of the JSON object. Besides, there + * are some special rules around the containment of raw scalars in + * arrays that are not handled here. So we must always recheck a + * match. However, if not all of the keys are present, the tuple + * certainly doesn't match. + */ + *recheck = true; + for (i = 0; i < nkeys; i++) + { + if (!check[i]) + { + res = false; + break; + } + } + } + else if (strategy == JsonbJsonpathPredicateStrategyNumber || + strategy == JsonbJsonpathExistsStrategyNumber) + { + *recheck = true; + + if (nkeys > 0) + { + Assert(extra_data && extra_data[0]); + res = execute_jsp_gin_node((JsonPathGinNode *) extra_data[0], check, + false) != GIN_FALSE; + } + } + else + elog(ERROR, "unrecognized strategy number: %d", strategy); + + PG_RETURN_BOOL(res); +} + +Datum +gin_triconsistent_jsonb_path(PG_FUNCTION_ARGS) +{ + GinTernaryValue *check = (GinTernaryValue *) PG_GETARG_POINTER(0); + StrategyNumber strategy = PG_GETARG_UINT16(1); + + /* Jsonb *query = PG_GETARG_JSONB_P(2); */ + int32 nkeys = PG_GETARG_INT32(3); + Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); + GinTernaryValue res = GIN_MAYBE; + int32 i; + + if (strategy == JsonbContainsStrategyNumber) + { + /* + * Note that we never return GIN_TRUE, only GIN_MAYBE or GIN_FALSE; + * this corresponds to always forcing recheck in the regular + * consistent function, for the reasons listed there. + */ + for (i = 0; i < nkeys; i++) + { + if (check[i] == GIN_FALSE) + { + res = GIN_FALSE; + break; + } + } + } + else if (strategy == JsonbJsonpathPredicateStrategyNumber || + strategy == JsonbJsonpathExistsStrategyNumber) + { + if (nkeys > 0) + { + Assert(extra_data && extra_data[0]); + res = execute_jsp_gin_node((JsonPathGinNode *) extra_data[0], check, + true); + + /* Should always recheck the result */ + if (res == GIN_TRUE) + res = GIN_MAYBE; + } + } + else + elog(ERROR, "unrecognized strategy number: %d", strategy); + + PG_RETURN_GIN_TERNARY_VALUE(res); +} + +/* + * Construct a jsonb_ops GIN key from a flag byte and a textual representation + * (which need not be null-terminated). This function is responsible + * for hashing overlength text representations; it will add the + * JGINFLAG_HASHED bit to the flag value if it does that. + */ +static Datum +make_text_key(char flag, const char *str, int len) +{ + text *item; + char hashbuf[10]; + + if (len > JGIN_MAXLENGTH) + { + uint32 hashval; + + hashval = DatumGetUInt32(hash_any((const unsigned char *) str, len)); + snprintf(hashbuf, sizeof(hashbuf), "%08x", hashval); + str = hashbuf; + len = 8; + flag |= JGINFLAG_HASHED; + } + + /* + * Now build the text Datum. For simplicity we build a 4-byte-header + * varlena text Datum here, but we expect it will get converted to short + * header format when stored in the index. + */ + item = (text *) palloc(VARHDRSZ + len + 1); + SET_VARSIZE(item, VARHDRSZ + len + 1); + + *VARDATA(item) = flag; + + memcpy(VARDATA(item) + 1, str, len); + + return PointerGetDatum(item); +} + +/* + * Create a textual representation of a JsonbValue that will serve as a GIN + * key in a jsonb_ops index. is_key is true if the JsonbValue is a key, + * or if it is a string array element (since we pretend those are keys, + * see jsonb.h). + */ +static Datum +make_scalar_key(const JsonbValue *scalarVal, bool is_key) +{ + Datum item; + char *cstr; + + switch (scalarVal->type) + { + case jbvNull: + Assert(!is_key); + item = make_text_key(JGINFLAG_NULL, "", 0); + break; + case jbvBool: + Assert(!is_key); + item = make_text_key(JGINFLAG_BOOL, + scalarVal->val.boolean ? "t" : "f", 1); + break; + case jbvNumeric: + Assert(!is_key); + + /* + * A normalized textual representation, free of trailing zeroes, + * is required so that numerically equal values will produce equal + * strings. + * + * It isn't ideal that numerics are stored in a relatively bulky + * textual format. However, it's a notationally convenient way of + * storing a "union" type in the GIN B-Tree, and indexing Jsonb + * strings takes precedence. + */ + cstr = numeric_normalize(scalarVal->val.numeric); + item = make_text_key(JGINFLAG_NUM, cstr, strlen(cstr)); + pfree(cstr); + break; + case jbvString: + item = make_text_key(is_key ? JGINFLAG_KEY : JGINFLAG_STR, + scalarVal->val.string.val, + scalarVal->val.string.len); + break; + default: + elog(ERROR, "unrecognized jsonb scalar type: %d", scalarVal->type); + item = 0; /* keep compiler quiet */ + break; + } + + return item; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/jsonb_op.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/jsonb_op.c new file mode 100644 index 00000000000..054351f0a31 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/jsonb_op.c @@ -0,0 +1,336 @@ +/*------------------------------------------------------------------------- + * + * jsonb_op.c + * Special operators for jsonb only, used by various index access methods + * + * Copyright (c) 2014-2023, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/utils/adt/jsonb_op.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "catalog/pg_type.h" +#include "miscadmin.h" +#include "utils/builtins.h" +#include "utils/jsonb.h" + +Datum +jsonb_exists(PG_FUNCTION_ARGS) +{ + Jsonb *jb = PG_GETARG_JSONB_P(0); + text *key = PG_GETARG_TEXT_PP(1); + JsonbValue kval; + JsonbValue *v = NULL; + + /* + * We only match Object keys (which are naturally always Strings), or + * string elements in arrays. In particular, we do not match non-string + * scalar elements. Existence of a key/element is only considered at the + * top level. No recursion occurs. + */ + kval.type = jbvString; + kval.val.string.val = VARDATA_ANY(key); + kval.val.string.len = VARSIZE_ANY_EXHDR(key); + + v = findJsonbValueFromContainer(&jb->root, + JB_FOBJECT | JB_FARRAY, + &kval); + + PG_RETURN_BOOL(v != NULL); +} + +Datum +jsonb_exists_any(PG_FUNCTION_ARGS) +{ + Jsonb *jb = PG_GETARG_JSONB_P(0); + ArrayType *keys = PG_GETARG_ARRAYTYPE_P(1); + int i; + Datum *key_datums; + bool *key_nulls; + int elem_count; + + deconstruct_array_builtin(keys, TEXTOID, &key_datums, &key_nulls, &elem_count); + + for (i = 0; i < elem_count; i++) + { + JsonbValue strVal; + + if (key_nulls[i]) + continue; + + strVal.type = jbvString; + /* We rely on the array elements not being toasted */ + strVal.val.string.val = VARDATA_ANY(key_datums[i]); + strVal.val.string.len = VARSIZE_ANY_EXHDR(key_datums[i]); + + if (findJsonbValueFromContainer(&jb->root, + JB_FOBJECT | JB_FARRAY, + &strVal) != NULL) + PG_RETURN_BOOL(true); + } + + PG_RETURN_BOOL(false); +} + +Datum +jsonb_exists_all(PG_FUNCTION_ARGS) +{ + Jsonb *jb = PG_GETARG_JSONB_P(0); + ArrayType *keys = PG_GETARG_ARRAYTYPE_P(1); + int i; + Datum *key_datums; + bool *key_nulls; + int elem_count; + + deconstruct_array_builtin(keys, TEXTOID, &key_datums, &key_nulls, &elem_count); + + for (i = 0; i < elem_count; i++) + { + JsonbValue strVal; + + if (key_nulls[i]) + continue; + + strVal.type = jbvString; + /* We rely on the array elements not being toasted */ + strVal.val.string.val = VARDATA_ANY(key_datums[i]); + strVal.val.string.len = VARSIZE_ANY_EXHDR(key_datums[i]); + + if (findJsonbValueFromContainer(&jb->root, + JB_FOBJECT | JB_FARRAY, + &strVal) == NULL) + PG_RETURN_BOOL(false); + } + + PG_RETURN_BOOL(true); +} + +Datum +jsonb_contains(PG_FUNCTION_ARGS) +{ + Jsonb *val = PG_GETARG_JSONB_P(0); + Jsonb *tmpl = PG_GETARG_JSONB_P(1); + + JsonbIterator *it1, + *it2; + + if (JB_ROOT_IS_OBJECT(val) != JB_ROOT_IS_OBJECT(tmpl)) + PG_RETURN_BOOL(false); + + it1 = JsonbIteratorInit(&val->root); + it2 = JsonbIteratorInit(&tmpl->root); + + PG_RETURN_BOOL(JsonbDeepContains(&it1, &it2)); +} + +Datum +jsonb_contained(PG_FUNCTION_ARGS) +{ + /* Commutator of "contains" */ + Jsonb *tmpl = PG_GETARG_JSONB_P(0); + Jsonb *val = PG_GETARG_JSONB_P(1); + + JsonbIterator *it1, + *it2; + + if (JB_ROOT_IS_OBJECT(val) != JB_ROOT_IS_OBJECT(tmpl)) + PG_RETURN_BOOL(false); + + it1 = JsonbIteratorInit(&val->root); + it2 = JsonbIteratorInit(&tmpl->root); + + PG_RETURN_BOOL(JsonbDeepContains(&it1, &it2)); +} + +Datum +jsonb_ne(PG_FUNCTION_ARGS) +{ + Jsonb *jba = PG_GETARG_JSONB_P(0); + Jsonb *jbb = PG_GETARG_JSONB_P(1); + bool res; + + res = (compareJsonbContainers(&jba->root, &jbb->root) != 0); + + PG_FREE_IF_COPY(jba, 0); + PG_FREE_IF_COPY(jbb, 1); + PG_RETURN_BOOL(res); +} + +/* + * B-Tree operator class operators, support function + */ +Datum +jsonb_lt(PG_FUNCTION_ARGS) +{ + Jsonb *jba = PG_GETARG_JSONB_P(0); + Jsonb *jbb = PG_GETARG_JSONB_P(1); + bool res; + + res = (compareJsonbContainers(&jba->root, &jbb->root) < 0); + + PG_FREE_IF_COPY(jba, 0); + PG_FREE_IF_COPY(jbb, 1); + PG_RETURN_BOOL(res); +} + +Datum +jsonb_gt(PG_FUNCTION_ARGS) +{ + Jsonb *jba = PG_GETARG_JSONB_P(0); + Jsonb *jbb = PG_GETARG_JSONB_P(1); + bool res; + + res = (compareJsonbContainers(&jba->root, &jbb->root) > 0); + + PG_FREE_IF_COPY(jba, 0); + PG_FREE_IF_COPY(jbb, 1); + PG_RETURN_BOOL(res); +} + +Datum +jsonb_le(PG_FUNCTION_ARGS) +{ + Jsonb *jba = PG_GETARG_JSONB_P(0); + Jsonb *jbb = PG_GETARG_JSONB_P(1); + bool res; + + res = (compareJsonbContainers(&jba->root, &jbb->root) <= 0); + + PG_FREE_IF_COPY(jba, 0); + PG_FREE_IF_COPY(jbb, 1); + PG_RETURN_BOOL(res); +} + +Datum +jsonb_ge(PG_FUNCTION_ARGS) +{ + Jsonb *jba = PG_GETARG_JSONB_P(0); + Jsonb *jbb = PG_GETARG_JSONB_P(1); + bool res; + + res = (compareJsonbContainers(&jba->root, &jbb->root) >= 0); + + PG_FREE_IF_COPY(jba, 0); + PG_FREE_IF_COPY(jbb, 1); + PG_RETURN_BOOL(res); +} + +Datum +jsonb_eq(PG_FUNCTION_ARGS) +{ + Jsonb *jba = PG_GETARG_JSONB_P(0); + Jsonb *jbb = PG_GETARG_JSONB_P(1); + bool res; + + res = (compareJsonbContainers(&jba->root, &jbb->root) == 0); + + PG_FREE_IF_COPY(jba, 0); + PG_FREE_IF_COPY(jbb, 1); + PG_RETURN_BOOL(res); +} + +Datum +jsonb_cmp(PG_FUNCTION_ARGS) +{ + Jsonb *jba = PG_GETARG_JSONB_P(0); + Jsonb *jbb = PG_GETARG_JSONB_P(1); + int res; + + res = compareJsonbContainers(&jba->root, &jbb->root); + + PG_FREE_IF_COPY(jba, 0); + PG_FREE_IF_COPY(jbb, 1); + PG_RETURN_INT32(res); +} + +/* + * Hash operator class jsonb hashing function + */ +Datum +jsonb_hash(PG_FUNCTION_ARGS) +{ + Jsonb *jb = PG_GETARG_JSONB_P(0); + JsonbIterator *it; + JsonbValue v; + JsonbIteratorToken r; + uint32 hash = 0; + + if (JB_ROOT_COUNT(jb) == 0) + PG_RETURN_INT32(0); + + it = JsonbIteratorInit(&jb->root); + + while ((r = JsonbIteratorNext(&it, &v, false)) != WJB_DONE) + { + switch (r) + { + /* Rotation is left to JsonbHashScalarValue() */ + case WJB_BEGIN_ARRAY: + hash ^= JB_FARRAY; + break; + case WJB_BEGIN_OBJECT: + hash ^= JB_FOBJECT; + break; + case WJB_KEY: + case WJB_VALUE: + case WJB_ELEM: + JsonbHashScalarValue(&v, &hash); + break; + case WJB_END_ARRAY: + case WJB_END_OBJECT: + break; + default: + elog(ERROR, "invalid JsonbIteratorNext rc: %d", (int) r); + } + } + + PG_FREE_IF_COPY(jb, 0); + PG_RETURN_INT32(hash); +} + +Datum +jsonb_hash_extended(PG_FUNCTION_ARGS) +{ + Jsonb *jb = PG_GETARG_JSONB_P(0); + uint64 seed = PG_GETARG_INT64(1); + JsonbIterator *it; + JsonbValue v; + JsonbIteratorToken r; + uint64 hash = 0; + + if (JB_ROOT_COUNT(jb) == 0) + PG_RETURN_UINT64(seed); + + it = JsonbIteratorInit(&jb->root); + + while ((r = JsonbIteratorNext(&it, &v, false)) != WJB_DONE) + { + switch (r) + { + /* Rotation is left to JsonbHashScalarValueExtended() */ + case WJB_BEGIN_ARRAY: + hash ^= ((uint64) JB_FARRAY) << 32 | JB_FARRAY; + break; + case WJB_BEGIN_OBJECT: + hash ^= ((uint64) JB_FOBJECT) << 32 | JB_FOBJECT; + break; + case WJB_KEY: + case WJB_VALUE: + case WJB_ELEM: + JsonbHashScalarValueExtended(&v, &hash, seed); + break; + case WJB_END_ARRAY: + case WJB_END_OBJECT: + break; + default: + elog(ERROR, "invalid JsonbIteratorNext rc: %d", (int) r); + } + } + + PG_FREE_IF_COPY(jb, 0); + PG_RETURN_UINT64(hash); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/jsonb_util.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/jsonb_util.c new file mode 100644 index 00000000000..9cc95b773db --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/jsonb_util.c @@ -0,0 +1,1994 @@ +/*------------------------------------------------------------------------- + * + * jsonb_util.c + * converting between Jsonb and JsonbValues, and iterating. + * + * Copyright (c) 2014-2023, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/utils/adt/jsonb_util.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "catalog/pg_collation.h" +#include "catalog/pg_type.h" +#include "common/hashfn.h" +#include "common/jsonapi.h" +#include "miscadmin.h" +#include "port/pg_bitutils.h" +#include "utils/builtins.h" +#include "utils/datetime.h" +#include "utils/json.h" +#include "utils/jsonb.h" +#include "utils/memutils.h" +#include "utils/varlena.h" + +/* + * Maximum number of elements in an array (or key/value pairs in an object). + * This is limited by two things: the size of the JEntry array must fit + * in MaxAllocSize, and the number of elements (or pairs) must fit in the bits + * reserved for that in the JsonbContainer.header field. + * + * (The total size of an array's or object's elements is also limited by + * JENTRY_OFFLENMASK, but we're not concerned about that here.) + */ +#define JSONB_MAX_ELEMS (Min(MaxAllocSize / sizeof(JsonbValue), JB_CMASK)) +#define JSONB_MAX_PAIRS (Min(MaxAllocSize / sizeof(JsonbPair), JB_CMASK)) + +static void fillJsonbValue(JsonbContainer *container, int index, + char *base_addr, uint32 offset, + JsonbValue *result); +static bool equalsJsonbScalarValue(JsonbValue *a, JsonbValue *b); +static int compareJsonbScalarValue(JsonbValue *a, JsonbValue *b); +static Jsonb *convertToJsonb(JsonbValue *val); +static void convertJsonbValue(StringInfo buffer, JEntry *header, JsonbValue *val, int level); +static void convertJsonbArray(StringInfo buffer, JEntry *header, JsonbValue *val, int level); +static void convertJsonbObject(StringInfo buffer, JEntry *header, JsonbValue *val, int level); +static void convertJsonbScalar(StringInfo buffer, JEntry *header, JsonbValue *scalarVal); + +static int reserveFromBuffer(StringInfo buffer, int len); +static void appendToBuffer(StringInfo buffer, const char *data, int len); +static void copyToBuffer(StringInfo buffer, int offset, const char *data, int len); +static short padBufferToInt(StringInfo buffer); + +static JsonbIterator *iteratorFromContainer(JsonbContainer *container, JsonbIterator *parent); +static JsonbIterator *freeAndGetParent(JsonbIterator *it); +static JsonbParseState *pushState(JsonbParseState **pstate); +static void appendKey(JsonbParseState *pstate, JsonbValue *string); +static void appendValue(JsonbParseState *pstate, JsonbValue *scalarVal); +static void appendElement(JsonbParseState *pstate, JsonbValue *scalarVal); +static int lengthCompareJsonbStringValue(const void *a, const void *b); +static int lengthCompareJsonbString(const char *val1, int len1, + const char *val2, int len2); +static int lengthCompareJsonbPair(const void *a, const void *b, void *binequal); +static void uniqueifyJsonbObject(JsonbValue *object, bool unique_keys, + bool skip_nulls); +static JsonbValue *pushJsonbValueScalar(JsonbParseState **pstate, + JsonbIteratorToken seq, + JsonbValue *scalarVal); + +void +JsonbToJsonbValue(Jsonb *jsonb, JsonbValue *val) +{ + val->type = jbvBinary; + val->val.binary.data = &jsonb->root; + val->val.binary.len = VARSIZE(jsonb) - VARHDRSZ; +} + +/* + * Turn an in-memory JsonbValue into a Jsonb for on-disk storage. + * + * Generally we find it more convenient to directly iterate through the Jsonb + * representation and only really convert nested scalar values. + * JsonbIteratorNext() does this, so that clients of the iteration code don't + * have to directly deal with the binary representation (JsonbDeepContains() is + * a notable exception, although all exceptions are internal to this module). + * In general, functions that accept a JsonbValue argument are concerned with + * the manipulation of scalar values, or simple containers of scalar values, + * where it would be inconvenient to deal with a great amount of other state. + */ +Jsonb * +JsonbValueToJsonb(JsonbValue *val) +{ + Jsonb *out; + + if (IsAJsonbScalar(val)) + { + /* Scalar value */ + JsonbParseState *pstate = NULL; + JsonbValue *res; + JsonbValue scalarArray; + + scalarArray.type = jbvArray; + scalarArray.val.array.rawScalar = true; + scalarArray.val.array.nElems = 1; + + pushJsonbValue(&pstate, WJB_BEGIN_ARRAY, &scalarArray); + pushJsonbValue(&pstate, WJB_ELEM, val); + res = pushJsonbValue(&pstate, WJB_END_ARRAY, NULL); + + out = convertToJsonb(res); + } + else if (val->type == jbvObject || val->type == jbvArray) + { + out = convertToJsonb(val); + } + else + { + Assert(val->type == jbvBinary); + out = palloc(VARHDRSZ + val->val.binary.len); + SET_VARSIZE(out, VARHDRSZ + val->val.binary.len); + memcpy(VARDATA(out), val->val.binary.data, val->val.binary.len); + } + + return out; +} + +/* + * Get the offset of the variable-length portion of a Jsonb node within + * the variable-length-data part of its container. The node is identified + * by index within the container's JEntry array. + */ +uint32 +getJsonbOffset(const JsonbContainer *jc, int index) +{ + uint32 offset = 0; + int i; + + /* + * Start offset of this entry is equal to the end offset of the previous + * entry. Walk backwards to the most recent entry stored as an end + * offset, returning that offset plus any lengths in between. + */ + for (i = index - 1; i >= 0; i--) + { + offset += JBE_OFFLENFLD(jc->children[i]); + if (JBE_HAS_OFF(jc->children[i])) + break; + } + + return offset; +} + +/* + * Get the length of the variable-length portion of a Jsonb node. + * The node is identified by index within the container's JEntry array. + */ +uint32 +getJsonbLength(const JsonbContainer *jc, int index) +{ + uint32 off; + uint32 len; + + /* + * If the length is stored directly in the JEntry, just return it. + * Otherwise, get the begin offset of the entry, and subtract that from + * the stored end+1 offset. + */ + if (JBE_HAS_OFF(jc->children[index])) + { + off = getJsonbOffset(jc, index); + len = JBE_OFFLENFLD(jc->children[index]) - off; + } + else + len = JBE_OFFLENFLD(jc->children[index]); + + return len; +} + +/* + * BT comparator worker function. Returns an integer less than, equal to, or + * greater than zero, indicating whether a is less than, equal to, or greater + * than b. Consistent with the requirements for a B-Tree operator class + * + * Strings are compared lexically, in contrast with other places where we use a + * much simpler comparator logic for searching through Strings. Since this is + * called from B-Tree support function 1, we're careful about not leaking + * memory here. + */ +int +compareJsonbContainers(JsonbContainer *a, JsonbContainer *b) +{ + JsonbIterator *ita, + *itb; + int res = 0; + + ita = JsonbIteratorInit(a); + itb = JsonbIteratorInit(b); + + do + { + JsonbValue va, + vb; + JsonbIteratorToken ra, + rb; + + ra = JsonbIteratorNext(&ita, &va, false); + rb = JsonbIteratorNext(&itb, &vb, false); + + if (ra == rb) + { + if (ra == WJB_DONE) + { + /* Decisively equal */ + break; + } + + if (ra == WJB_END_ARRAY || ra == WJB_END_OBJECT) + { + /* + * There is no array or object to compare at this stage of + * processing. jbvArray/jbvObject values are compared + * initially, at the WJB_BEGIN_ARRAY and WJB_BEGIN_OBJECT + * tokens. + */ + continue; + } + + if (va.type == vb.type) + { + switch (va.type) + { + case jbvString: + case jbvNull: + case jbvNumeric: + case jbvBool: + res = compareJsonbScalarValue(&va, &vb); + break; + case jbvArray: + + /* + * This could be a "raw scalar" pseudo array. That's + * a special case here though, since we still want the + * general type-based comparisons to apply, and as far + * as we're concerned a pseudo array is just a scalar. + */ + if (va.val.array.rawScalar != vb.val.array.rawScalar) + res = (va.val.array.rawScalar) ? -1 : 1; + if (va.val.array.nElems != vb.val.array.nElems) + res = (va.val.array.nElems > vb.val.array.nElems) ? 1 : -1; + break; + case jbvObject: + if (va.val.object.nPairs != vb.val.object.nPairs) + res = (va.val.object.nPairs > vb.val.object.nPairs) ? 1 : -1; + break; + case jbvBinary: + elog(ERROR, "unexpected jbvBinary value"); + break; + case jbvDatetime: + elog(ERROR, "unexpected jbvDatetime value"); + break; + } + } + else + { + /* Type-defined order */ + res = (va.type > vb.type) ? 1 : -1; + } + } + else + { + /* + * It's safe to assume that the types differed, and that the va + * and vb values passed were set. + * + * If the two values were of the same container type, then there'd + * have been a chance to observe the variation in the number of + * elements/pairs (when processing WJB_BEGIN_OBJECT, say). They're + * either two heterogeneously-typed containers, or a container and + * some scalar type. + * + * We don't have to consider the WJB_END_ARRAY and WJB_END_OBJECT + * cases here, because we would have seen the corresponding + * WJB_BEGIN_ARRAY and WJB_BEGIN_OBJECT tokens first, and + * concluded that they don't match. + */ + Assert(ra != WJB_END_ARRAY && ra != WJB_END_OBJECT); + Assert(rb != WJB_END_ARRAY && rb != WJB_END_OBJECT); + + Assert(va.type != vb.type); + Assert(va.type != jbvBinary); + Assert(vb.type != jbvBinary); + /* Type-defined order */ + res = (va.type > vb.type) ? 1 : -1; + } + } + while (res == 0); + + while (ita != NULL) + { + JsonbIterator *i = ita->parent; + + pfree(ita); + ita = i; + } + while (itb != NULL) + { + JsonbIterator *i = itb->parent; + + pfree(itb); + itb = i; + } + + return res; +} + +/* + * Find value in object (i.e. the "value" part of some key/value pair in an + * object), or find a matching element if we're looking through an array. Do + * so on the basis of equality of the object keys only, or alternatively + * element values only, with a caller-supplied value "key". The "flags" + * argument allows the caller to specify which container types are of interest. + * + * This exported utility function exists to facilitate various cases concerned + * with "containment". If asked to look through an object, the caller had + * better pass a Jsonb String, because their keys can only be strings. + * Otherwise, for an array, any type of JsonbValue will do. + * + * In order to proceed with the search, it is necessary for callers to have + * both specified an interest in exactly one particular container type with an + * appropriate flag, as well as having the pointed-to Jsonb container be of + * one of those same container types at the top level. (Actually, we just do + * whichever makes sense to save callers the trouble of figuring it out - at + * most one can make sense, because the container either points to an array + * (possibly a "raw scalar" pseudo array) or an object.) + * + * Note that we can return a jbvBinary JsonbValue if this is called on an + * object, but we never do so on an array. If the caller asks to look through + * a container type that is not of the type pointed to by the container, + * immediately fall through and return NULL. If we cannot find the value, + * return NULL. Otherwise, return palloc()'d copy of value. + */ +JsonbValue * +findJsonbValueFromContainer(JsonbContainer *container, uint32 flags, + JsonbValue *key) +{ + JEntry *children = container->children; + int count = JsonContainerSize(container); + + Assert((flags & ~(JB_FARRAY | JB_FOBJECT)) == 0); + + /* Quick out without a palloc cycle if object/array is empty */ + if (count <= 0) + return NULL; + + if ((flags & JB_FARRAY) && JsonContainerIsArray(container)) + { + JsonbValue *result = palloc(sizeof(JsonbValue)); + char *base_addr = (char *) (children + count); + uint32 offset = 0; + int i; + + for (i = 0; i < count; i++) + { + fillJsonbValue(container, i, base_addr, offset, result); + + if (key->type == result->type) + { + if (equalsJsonbScalarValue(key, result)) + return result; + } + + JBE_ADVANCE_OFFSET(offset, children[i]); + } + + pfree(result); + } + else if ((flags & JB_FOBJECT) && JsonContainerIsObject(container)) + { + /* Object key passed by caller must be a string */ + Assert(key->type == jbvString); + + return getKeyJsonValueFromContainer(container, key->val.string.val, + key->val.string.len, NULL); + } + + /* Not found */ + return NULL; +} + +/* + * Find value by key in Jsonb object and fetch it into 'res', which is also + * returned. + * + * 'res' can be passed in as NULL, in which case it's newly palloc'ed here. + */ +JsonbValue * +getKeyJsonValueFromContainer(JsonbContainer *container, + const char *keyVal, int keyLen, JsonbValue *res) +{ + JEntry *children = container->children; + int count = JsonContainerSize(container); + char *baseAddr; + uint32 stopLow, + stopHigh; + + Assert(JsonContainerIsObject(container)); + + /* Quick out without a palloc cycle if object is empty */ + if (count <= 0) + return NULL; + + /* + * Binary search the container. Since we know this is an object, account + * for *Pairs* of Jentrys + */ + baseAddr = (char *) (children + count * 2); + stopLow = 0; + stopHigh = count; + while (stopLow < stopHigh) + { + uint32 stopMiddle; + int difference; + const char *candidateVal; + int candidateLen; + + stopMiddle = stopLow + (stopHigh - stopLow) / 2; + + candidateVal = baseAddr + getJsonbOffset(container, stopMiddle); + candidateLen = getJsonbLength(container, stopMiddle); + + difference = lengthCompareJsonbString(candidateVal, candidateLen, + keyVal, keyLen); + + if (difference == 0) + { + /* Found our key, return corresponding value */ + int index = stopMiddle + count; + + if (!res) + res = palloc(sizeof(JsonbValue)); + + fillJsonbValue(container, index, baseAddr, + getJsonbOffset(container, index), + res); + + return res; + } + else + { + if (difference < 0) + stopLow = stopMiddle + 1; + else + stopHigh = stopMiddle; + } + } + + /* Not found */ + return NULL; +} + +/* + * Get i-th value of a Jsonb array. + * + * Returns palloc()'d copy of the value, or NULL if it does not exist. + */ +JsonbValue * +getIthJsonbValueFromContainer(JsonbContainer *container, uint32 i) +{ + JsonbValue *result; + char *base_addr; + uint32 nelements; + + if (!JsonContainerIsArray(container)) + elog(ERROR, "not a jsonb array"); + + nelements = JsonContainerSize(container); + base_addr = (char *) &container->children[nelements]; + + if (i >= nelements) + return NULL; + + result = palloc(sizeof(JsonbValue)); + + fillJsonbValue(container, i, base_addr, + getJsonbOffset(container, i), + result); + + return result; +} + +/* + * A helper function to fill in a JsonbValue to represent an element of an + * array, or a key or value of an object. + * + * The node's JEntry is at container->children[index], and its variable-length + * data is at base_addr + offset. We make the caller determine the offset + * since in many cases the caller can amortize that work across multiple + * children. When it can't, it can just call getJsonbOffset(). + * + * A nested array or object will be returned as jbvBinary, ie. it won't be + * expanded. + */ +static void +fillJsonbValue(JsonbContainer *container, int index, + char *base_addr, uint32 offset, + JsonbValue *result) +{ + JEntry entry = container->children[index]; + + if (JBE_ISNULL(entry)) + { + result->type = jbvNull; + } + else if (JBE_ISSTRING(entry)) + { + result->type = jbvString; + result->val.string.val = base_addr + offset; + result->val.string.len = getJsonbLength(container, index); + Assert(result->val.string.len >= 0); + } + else if (JBE_ISNUMERIC(entry)) + { + result->type = jbvNumeric; + result->val.numeric = (Numeric) (base_addr + INTALIGN(offset)); + } + else if (JBE_ISBOOL_TRUE(entry)) + { + result->type = jbvBool; + result->val.boolean = true; + } + else if (JBE_ISBOOL_FALSE(entry)) + { + result->type = jbvBool; + result->val.boolean = false; + } + else + { + Assert(JBE_ISCONTAINER(entry)); + result->type = jbvBinary; + /* Remove alignment padding from data pointer and length */ + result->val.binary.data = (JsonbContainer *) (base_addr + INTALIGN(offset)); + result->val.binary.len = getJsonbLength(container, index) - + (INTALIGN(offset) - offset); + } +} + +/* + * Push JsonbValue into JsonbParseState. + * + * Used when parsing JSON tokens to form Jsonb, or when converting an in-memory + * JsonbValue to a Jsonb. + * + * Initial state of *JsonbParseState is NULL, since it'll be allocated here + * originally (caller will get JsonbParseState back by reference). + * + * Only sequential tokens pertaining to non-container types should pass a + * JsonbValue. There is one exception -- WJB_BEGIN_ARRAY callers may pass a + * "raw scalar" pseudo array to append it - the actual scalar should be passed + * next and it will be added as the only member of the array. + * + * Values of type jbvBinary, which are rolled up arrays and objects, + * are unpacked before being added to the result. + */ +JsonbValue * +pushJsonbValue(JsonbParseState **pstate, JsonbIteratorToken seq, + JsonbValue *jbval) +{ + JsonbIterator *it; + JsonbValue *res = NULL; + JsonbValue v; + JsonbIteratorToken tok; + int i; + + if (jbval && (seq == WJB_ELEM || seq == WJB_VALUE) && jbval->type == jbvObject) + { + pushJsonbValue(pstate, WJB_BEGIN_OBJECT, NULL); + for (i = 0; i < jbval->val.object.nPairs; i++) + { + pushJsonbValue(pstate, WJB_KEY, &jbval->val.object.pairs[i].key); + pushJsonbValue(pstate, WJB_VALUE, &jbval->val.object.pairs[i].value); + } + + return pushJsonbValue(pstate, WJB_END_OBJECT, NULL); + } + + if (jbval && (seq == WJB_ELEM || seq == WJB_VALUE) && jbval->type == jbvArray) + { + pushJsonbValue(pstate, WJB_BEGIN_ARRAY, NULL); + for (i = 0; i < jbval->val.array.nElems; i++) + { + pushJsonbValue(pstate, WJB_ELEM, &jbval->val.array.elems[i]); + } + + return pushJsonbValue(pstate, WJB_END_ARRAY, NULL); + } + + if (!jbval || (seq != WJB_ELEM && seq != WJB_VALUE) || + jbval->type != jbvBinary) + { + /* drop through */ + return pushJsonbValueScalar(pstate, seq, jbval); + } + + /* unpack the binary and add each piece to the pstate */ + it = JsonbIteratorInit(jbval->val.binary.data); + + if ((jbval->val.binary.data->header & JB_FSCALAR) && *pstate) + { + tok = JsonbIteratorNext(&it, &v, true); + Assert(tok == WJB_BEGIN_ARRAY); + Assert(v.type == jbvArray && v.val.array.rawScalar); + + tok = JsonbIteratorNext(&it, &v, true); + Assert(tok == WJB_ELEM); + + res = pushJsonbValueScalar(pstate, seq, &v); + + tok = JsonbIteratorNext(&it, &v, true); + Assert(tok == WJB_END_ARRAY); + Assert(it == NULL); + + return res; + } + + while ((tok = JsonbIteratorNext(&it, &v, false)) != WJB_DONE) + res = pushJsonbValueScalar(pstate, tok, + tok < WJB_BEGIN_ARRAY || + (tok == WJB_BEGIN_ARRAY && + v.val.array.rawScalar) ? &v : NULL); + + return res; +} + +/* + * Do the actual pushing, with only scalar or pseudo-scalar-array values + * accepted. + */ +static JsonbValue * +pushJsonbValueScalar(JsonbParseState **pstate, JsonbIteratorToken seq, + JsonbValue *scalarVal) +{ + JsonbValue *result = NULL; + + switch (seq) + { + case WJB_BEGIN_ARRAY: + Assert(!scalarVal || scalarVal->val.array.rawScalar); + *pstate = pushState(pstate); + result = &(*pstate)->contVal; + (*pstate)->contVal.type = jbvArray; + (*pstate)->contVal.val.array.nElems = 0; + (*pstate)->contVal.val.array.rawScalar = (scalarVal && + scalarVal->val.array.rawScalar); + if (scalarVal && scalarVal->val.array.nElems > 0) + { + /* Assume that this array is still really a scalar */ + Assert(scalarVal->type == jbvArray); + (*pstate)->size = scalarVal->val.array.nElems; + } + else + { + (*pstate)->size = 4; + } + (*pstate)->contVal.val.array.elems = palloc(sizeof(JsonbValue) * + (*pstate)->size); + break; + case WJB_BEGIN_OBJECT: + Assert(!scalarVal); + *pstate = pushState(pstate); + result = &(*pstate)->contVal; + (*pstate)->contVal.type = jbvObject; + (*pstate)->contVal.val.object.nPairs = 0; + (*pstate)->size = 4; + (*pstate)->contVal.val.object.pairs = palloc(sizeof(JsonbPair) * + (*pstate)->size); + break; + case WJB_KEY: + Assert(scalarVal->type == jbvString); + appendKey(*pstate, scalarVal); + break; + case WJB_VALUE: + Assert(IsAJsonbScalar(scalarVal)); + appendValue(*pstate, scalarVal); + break; + case WJB_ELEM: + Assert(IsAJsonbScalar(scalarVal)); + appendElement(*pstate, scalarVal); + break; + case WJB_END_OBJECT: + uniqueifyJsonbObject(&(*pstate)->contVal, + (*pstate)->unique_keys, + (*pstate)->skip_nulls); + /* fall through! */ + case WJB_END_ARRAY: + /* Steps here common to WJB_END_OBJECT case */ + Assert(!scalarVal); + result = &(*pstate)->contVal; + + /* + * Pop stack and push current array/object as value in parent + * array/object + */ + *pstate = (*pstate)->next; + if (*pstate) + { + switch ((*pstate)->contVal.type) + { + case jbvArray: + appendElement(*pstate, result); + break; + case jbvObject: + appendValue(*pstate, result); + break; + default: + elog(ERROR, "invalid jsonb container type"); + } + } + break; + default: + elog(ERROR, "unrecognized jsonb sequential processing token"); + } + + return result; +} + +/* + * pushJsonbValue() worker: Iteration-like forming of Jsonb + */ +static JsonbParseState * +pushState(JsonbParseState **pstate) +{ + JsonbParseState *ns = palloc(sizeof(JsonbParseState)); + + ns->next = *pstate; + ns->unique_keys = false; + ns->skip_nulls = false; + + return ns; +} + +/* + * pushJsonbValue() worker: Append a pair key to state when generating a Jsonb + */ +static void +appendKey(JsonbParseState *pstate, JsonbValue *string) +{ + JsonbValue *object = &pstate->contVal; + + Assert(object->type == jbvObject); + Assert(string->type == jbvString); + + if (object->val.object.nPairs >= JSONB_MAX_PAIRS) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("number of jsonb object pairs exceeds the maximum allowed (%zu)", + JSONB_MAX_PAIRS))); + + if (object->val.object.nPairs >= pstate->size) + { + pstate->size *= 2; + object->val.object.pairs = repalloc(object->val.object.pairs, + sizeof(JsonbPair) * pstate->size); + } + + object->val.object.pairs[object->val.object.nPairs].key = *string; + object->val.object.pairs[object->val.object.nPairs].order = object->val.object.nPairs; +} + +/* + * pushJsonbValue() worker: Append a pair value to state when generating a + * Jsonb + */ +static void +appendValue(JsonbParseState *pstate, JsonbValue *scalarVal) +{ + JsonbValue *object = &pstate->contVal; + + Assert(object->type == jbvObject); + + object->val.object.pairs[object->val.object.nPairs++].value = *scalarVal; +} + +/* + * pushJsonbValue() worker: Append an element to state when generating a Jsonb + */ +static void +appendElement(JsonbParseState *pstate, JsonbValue *scalarVal) +{ + JsonbValue *array = &pstate->contVal; + + Assert(array->type == jbvArray); + + if (array->val.array.nElems >= JSONB_MAX_ELEMS) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("number of jsonb array elements exceeds the maximum allowed (%zu)", + JSONB_MAX_ELEMS))); + + if (array->val.array.nElems >= pstate->size) + { + pstate->size *= 2; + array->val.array.elems = repalloc(array->val.array.elems, + sizeof(JsonbValue) * pstate->size); + } + + array->val.array.elems[array->val.array.nElems++] = *scalarVal; +} + +/* + * Given a JsonbContainer, expand to JsonbIterator to iterate over items + * fully expanded to in-memory representation for manipulation. + * + * See JsonbIteratorNext() for notes on memory management. + */ +JsonbIterator * +JsonbIteratorInit(JsonbContainer *container) +{ + return iteratorFromContainer(container, NULL); +} + +/* + * Get next JsonbValue while iterating + * + * Caller should initially pass their own, original iterator. They may get + * back a child iterator palloc()'d here instead. The function can be relied + * on to free those child iterators, lest the memory allocated for highly + * nested objects become unreasonable, but only if callers don't end iteration + * early (by breaking upon having found something in a search, for example). + * + * Callers in such a scenario, that are particularly sensitive to leaking + * memory in a long-lived context may walk the ancestral tree from the final + * iterator we left them with to its oldest ancestor, pfree()ing as they go. + * They do not have to free any other memory previously allocated for iterators + * but not accessible as direct ancestors of the iterator they're last passed + * back. + * + * Returns "Jsonb sequential processing" token value. Iterator "state" + * reflects the current stage of the process in a less granular fashion, and is + * mostly used here to track things internally with respect to particular + * iterators. + * + * Clients of this function should not have to handle any jbvBinary values + * (since recursive calls will deal with this), provided skipNested is false. + * It is our job to expand the jbvBinary representation without bothering them + * with it. However, clients should not take it upon themselves to touch array + * or Object element/pair buffers, since their element/pair pointers are + * garbage. Also, *val will not be set when returning WJB_END_ARRAY or + * WJB_END_OBJECT, on the assumption that it's only useful to access values + * when recursing in. + */ +JsonbIteratorToken +JsonbIteratorNext(JsonbIterator **it, JsonbValue *val, bool skipNested) +{ + if (*it == NULL) + return WJB_DONE; + + /* + * When stepping into a nested container, we jump back here to start + * processing the child. We will not recurse further in one call, because + * processing the child will always begin in JBI_ARRAY_START or + * JBI_OBJECT_START state. + */ +recurse: + switch ((*it)->state) + { + case JBI_ARRAY_START: + /* Set v to array on first array call */ + val->type = jbvArray; + val->val.array.nElems = (*it)->nElems; + + /* + * v->val.array.elems is not actually set, because we aren't doing + * a full conversion + */ + val->val.array.rawScalar = (*it)->isScalar; + (*it)->curIndex = 0; + (*it)->curDataOffset = 0; + (*it)->curValueOffset = 0; /* not actually used */ + /* Set state for next call */ + (*it)->state = JBI_ARRAY_ELEM; + return WJB_BEGIN_ARRAY; + + case JBI_ARRAY_ELEM: + if ((*it)->curIndex >= (*it)->nElems) + { + /* + * All elements within array already processed. Report this + * to caller, and give it back original parent iterator (which + * independently tracks iteration progress at its level of + * nesting). + */ + *it = freeAndGetParent(*it); + return WJB_END_ARRAY; + } + + fillJsonbValue((*it)->container, (*it)->curIndex, + (*it)->dataProper, (*it)->curDataOffset, + val); + + JBE_ADVANCE_OFFSET((*it)->curDataOffset, + (*it)->children[(*it)->curIndex]); + (*it)->curIndex++; + + if (!IsAJsonbScalar(val) && !skipNested) + { + /* Recurse into container. */ + *it = iteratorFromContainer(val->val.binary.data, *it); + goto recurse; + } + else + { + /* + * Scalar item in array, or a container and caller didn't want + * us to recurse into it. + */ + return WJB_ELEM; + } + + case JBI_OBJECT_START: + /* Set v to object on first object call */ + val->type = jbvObject; + val->val.object.nPairs = (*it)->nElems; + + /* + * v->val.object.pairs is not actually set, because we aren't + * doing a full conversion + */ + (*it)->curIndex = 0; + (*it)->curDataOffset = 0; + (*it)->curValueOffset = getJsonbOffset((*it)->container, + (*it)->nElems); + /* Set state for next call */ + (*it)->state = JBI_OBJECT_KEY; + return WJB_BEGIN_OBJECT; + + case JBI_OBJECT_KEY: + if ((*it)->curIndex >= (*it)->nElems) + { + /* + * All pairs within object already processed. Report this to + * caller, and give it back original containing iterator + * (which independently tracks iteration progress at its level + * of nesting). + */ + *it = freeAndGetParent(*it); + return WJB_END_OBJECT; + } + else + { + /* Return key of a key/value pair. */ + fillJsonbValue((*it)->container, (*it)->curIndex, + (*it)->dataProper, (*it)->curDataOffset, + val); + if (val->type != jbvString) + elog(ERROR, "unexpected jsonb type as object key"); + + /* Set state for next call */ + (*it)->state = JBI_OBJECT_VALUE; + return WJB_KEY; + } + + case JBI_OBJECT_VALUE: + /* Set state for next call */ + (*it)->state = JBI_OBJECT_KEY; + + fillJsonbValue((*it)->container, (*it)->curIndex + (*it)->nElems, + (*it)->dataProper, (*it)->curValueOffset, + val); + + JBE_ADVANCE_OFFSET((*it)->curDataOffset, + (*it)->children[(*it)->curIndex]); + JBE_ADVANCE_OFFSET((*it)->curValueOffset, + (*it)->children[(*it)->curIndex + (*it)->nElems]); + (*it)->curIndex++; + + /* + * Value may be a container, in which case we recurse with new, + * child iterator (unless the caller asked not to, by passing + * skipNested). + */ + if (!IsAJsonbScalar(val) && !skipNested) + { + *it = iteratorFromContainer(val->val.binary.data, *it); + goto recurse; + } + else + return WJB_VALUE; + } + + elog(ERROR, "invalid iterator state"); + return -1; +} + +/* + * Initialize an iterator for iterating all elements in a container. + */ +static JsonbIterator * +iteratorFromContainer(JsonbContainer *container, JsonbIterator *parent) +{ + JsonbIterator *it; + + it = palloc0(sizeof(JsonbIterator)); + it->container = container; + it->parent = parent; + it->nElems = JsonContainerSize(container); + + /* Array starts just after header */ + it->children = container->children; + + switch (container->header & (JB_FARRAY | JB_FOBJECT)) + { + case JB_FARRAY: + it->dataProper = + (char *) it->children + it->nElems * sizeof(JEntry); + it->isScalar = JsonContainerIsScalar(container); + /* This is either a "raw scalar", or an array */ + Assert(!it->isScalar || it->nElems == 1); + + it->state = JBI_ARRAY_START; + break; + + case JB_FOBJECT: + it->dataProper = + (char *) it->children + it->nElems * sizeof(JEntry) * 2; + it->state = JBI_OBJECT_START; + break; + + default: + elog(ERROR, "unknown type of jsonb container"); + } + + return it; +} + +/* + * JsonbIteratorNext() worker: Return parent, while freeing memory for current + * iterator + */ +static JsonbIterator * +freeAndGetParent(JsonbIterator *it) +{ + JsonbIterator *v = it->parent; + + pfree(it); + return v; +} + +/* + * Worker for "contains" operator's function + * + * Formally speaking, containment is top-down, unordered subtree isomorphism. + * + * Takes iterators that belong to some container type. These iterators + * "belong" to those values in the sense that they've just been initialized in + * respect of them by the caller (perhaps in a nested fashion). + * + * "val" is lhs Jsonb, and mContained is rhs Jsonb when called from top level. + * We determine if mContained is contained within val. + */ +bool +JsonbDeepContains(JsonbIterator **val, JsonbIterator **mContained) +{ + JsonbValue vval, + vcontained; + JsonbIteratorToken rval, + rcont; + + /* + * Guard against stack overflow due to overly complex Jsonb. + * + * Functions called here independently take this precaution, but that + * might not be sufficient since this is also a recursive function. + */ + check_stack_depth(); + + rval = JsonbIteratorNext(val, &vval, false); + rcont = JsonbIteratorNext(mContained, &vcontained, false); + + if (rval != rcont) + { + /* + * The differing return values can immediately be taken as indicating + * two differing container types at this nesting level, which is + * sufficient reason to give up entirely (but it should be the case + * that they're both some container type). + */ + Assert(rval == WJB_BEGIN_OBJECT || rval == WJB_BEGIN_ARRAY); + Assert(rcont == WJB_BEGIN_OBJECT || rcont == WJB_BEGIN_ARRAY); + return false; + } + else if (rcont == WJB_BEGIN_OBJECT) + { + Assert(vval.type == jbvObject); + Assert(vcontained.type == jbvObject); + + /* + * If the lhs has fewer pairs than the rhs, it can't possibly contain + * the rhs. (This conclusion is safe only because we de-duplicate + * keys in all Jsonb objects; thus there can be no corresponding + * optimization in the array case.) The case probably won't arise + * often, but since it's such a cheap check we may as well make it. + */ + if (vval.val.object.nPairs < vcontained.val.object.nPairs) + return false; + + /* Work through rhs "is it contained within?" object */ + for (;;) + { + JsonbValue *lhsVal; /* lhsVal is from pair in lhs object */ + JsonbValue lhsValBuf; + + rcont = JsonbIteratorNext(mContained, &vcontained, false); + + /* + * When we get through caller's rhs "is it contained within?" + * object without failing to find one of its values, it's + * contained. + */ + if (rcont == WJB_END_OBJECT) + return true; + + Assert(rcont == WJB_KEY); + Assert(vcontained.type == jbvString); + + /* First, find value by key... */ + lhsVal = + getKeyJsonValueFromContainer((*val)->container, + vcontained.val.string.val, + vcontained.val.string.len, + &lhsValBuf); + if (!lhsVal) + return false; + + /* + * ...at this stage it is apparent that there is at least a key + * match for this rhs pair. + */ + rcont = JsonbIteratorNext(mContained, &vcontained, true); + + Assert(rcont == WJB_VALUE); + + /* + * Compare rhs pair's value with lhs pair's value just found using + * key + */ + if (lhsVal->type != vcontained.type) + { + return false; + } + else if (IsAJsonbScalar(lhsVal)) + { + if (!equalsJsonbScalarValue(lhsVal, &vcontained)) + return false; + } + else + { + /* Nested container value (object or array) */ + JsonbIterator *nestval, + *nestContained; + + Assert(lhsVal->type == jbvBinary); + Assert(vcontained.type == jbvBinary); + + nestval = JsonbIteratorInit(lhsVal->val.binary.data); + nestContained = JsonbIteratorInit(vcontained.val.binary.data); + + /* + * Match "value" side of rhs datum object's pair recursively. + * It's a nested structure. + * + * Note that nesting still has to "match up" at the right + * nesting sub-levels. However, there need only be zero or + * more matching pairs (or elements) at each nesting level + * (provided the *rhs* pairs/elements *all* match on each + * level), which enables searching nested structures for a + * single String or other primitive type sub-datum quite + * effectively (provided the user constructed the rhs nested + * structure such that we "know where to look"). + * + * In other words, the mapping of container nodes in the rhs + * "vcontained" Jsonb to internal nodes on the lhs is + * injective, and parent-child edges on the rhs must be mapped + * to parent-child edges on the lhs to satisfy the condition + * of containment (plus of course the mapped nodes must be + * equal). + */ + if (!JsonbDeepContains(&nestval, &nestContained)) + return false; + } + } + } + else if (rcont == WJB_BEGIN_ARRAY) + { + JsonbValue *lhsConts = NULL; + uint32 nLhsElems = vval.val.array.nElems; + + Assert(vval.type == jbvArray); + Assert(vcontained.type == jbvArray); + + /* + * Handle distinction between "raw scalar" pseudo arrays, and real + * arrays. + * + * A raw scalar may contain another raw scalar, and an array may + * contain a raw scalar, but a raw scalar may not contain an array. We + * don't do something like this for the object case, since objects can + * only contain pairs, never raw scalars (a pair is represented by an + * rhs object argument with a single contained pair). + */ + if (vval.val.array.rawScalar && !vcontained.val.array.rawScalar) + return false; + + /* Work through rhs "is it contained within?" array */ + for (;;) + { + rcont = JsonbIteratorNext(mContained, &vcontained, true); + + /* + * When we get through caller's rhs "is it contained within?" + * array without failing to find one of its values, it's + * contained. + */ + if (rcont == WJB_END_ARRAY) + return true; + + Assert(rcont == WJB_ELEM); + + if (IsAJsonbScalar(&vcontained)) + { + if (!findJsonbValueFromContainer((*val)->container, + JB_FARRAY, + &vcontained)) + return false; + } + else + { + uint32 i; + + /* + * If this is first container found in rhs array (at this + * depth), initialize temp lhs array of containers + */ + if (lhsConts == NULL) + { + uint32 j = 0; + + /* Make room for all possible values */ + lhsConts = palloc(sizeof(JsonbValue) * nLhsElems); + + for (i = 0; i < nLhsElems; i++) + { + /* Store all lhs elements in temp array */ + rcont = JsonbIteratorNext(val, &vval, true); + Assert(rcont == WJB_ELEM); + + if (vval.type == jbvBinary) + lhsConts[j++] = vval; + } + + /* No container elements in temp array, so give up now */ + if (j == 0) + return false; + + /* We may have only partially filled array */ + nLhsElems = j; + } + + /* XXX: Nested array containment is O(N^2) */ + for (i = 0; i < nLhsElems; i++) + { + /* Nested container value (object or array) */ + JsonbIterator *nestval, + *nestContained; + bool contains; + + nestval = JsonbIteratorInit(lhsConts[i].val.binary.data); + nestContained = JsonbIteratorInit(vcontained.val.binary.data); + + contains = JsonbDeepContains(&nestval, &nestContained); + + if (nestval) + pfree(nestval); + if (nestContained) + pfree(nestContained); + if (contains) + break; + } + + /* + * Report rhs container value is not contained if couldn't + * match rhs container to *some* lhs cont + */ + if (i == nLhsElems) + return false; + } + } + } + else + { + elog(ERROR, "invalid jsonb container type"); + } + + elog(ERROR, "unexpectedly fell off end of jsonb container"); + return false; +} + +/* + * Hash a JsonbValue scalar value, mixing the hash value into an existing + * hash provided by the caller. + * + * Some callers may wish to independently XOR in JB_FOBJECT and JB_FARRAY + * flags. + */ +void +JsonbHashScalarValue(const JsonbValue *scalarVal, uint32 *hash) +{ + uint32 tmp; + + /* Compute hash value for scalarVal */ + switch (scalarVal->type) + { + case jbvNull: + tmp = 0x01; + break; + case jbvString: + tmp = DatumGetUInt32(hash_any((const unsigned char *) scalarVal->val.string.val, + scalarVal->val.string.len)); + break; + case jbvNumeric: + /* Must hash equal numerics to equal hash codes */ + tmp = DatumGetUInt32(DirectFunctionCall1(hash_numeric, + NumericGetDatum(scalarVal->val.numeric))); + break; + case jbvBool: + tmp = scalarVal->val.boolean ? 0x02 : 0x04; + + break; + default: + elog(ERROR, "invalid jsonb scalar type"); + tmp = 0; /* keep compiler quiet */ + break; + } + + /* + * Combine hash values of successive keys, values and elements by rotating + * the previous value left 1 bit, then XOR'ing in the new + * key/value/element's hash value. + */ + *hash = pg_rotate_left32(*hash, 1); + *hash ^= tmp; +} + +/* + * Hash a value to a 64-bit value, with a seed. Otherwise, similar to + * JsonbHashScalarValue. + */ +void +JsonbHashScalarValueExtended(const JsonbValue *scalarVal, uint64 *hash, + uint64 seed) +{ + uint64 tmp; + + switch (scalarVal->type) + { + case jbvNull: + tmp = seed + 0x01; + break; + case jbvString: + tmp = DatumGetUInt64(hash_any_extended((const unsigned char *) scalarVal->val.string.val, + scalarVal->val.string.len, + seed)); + break; + case jbvNumeric: + tmp = DatumGetUInt64(DirectFunctionCall2(hash_numeric_extended, + NumericGetDatum(scalarVal->val.numeric), + UInt64GetDatum(seed))); + break; + case jbvBool: + if (seed) + tmp = DatumGetUInt64(DirectFunctionCall2(hashcharextended, + BoolGetDatum(scalarVal->val.boolean), + UInt64GetDatum(seed))); + else + tmp = scalarVal->val.boolean ? 0x02 : 0x04; + + break; + default: + elog(ERROR, "invalid jsonb scalar type"); + break; + } + + *hash = ROTATE_HIGH_AND_LOW_32BITS(*hash); + *hash ^= tmp; +} + +/* + * Are two scalar JsonbValues of the same type a and b equal? + */ +static bool +equalsJsonbScalarValue(JsonbValue *a, JsonbValue *b) +{ + if (a->type == b->type) + { + switch (a->type) + { + case jbvNull: + return true; + case jbvString: + return lengthCompareJsonbStringValue(a, b) == 0; + case jbvNumeric: + return DatumGetBool(DirectFunctionCall2(numeric_eq, + PointerGetDatum(a->val.numeric), + PointerGetDatum(b->val.numeric))); + case jbvBool: + return a->val.boolean == b->val.boolean; + + default: + elog(ERROR, "invalid jsonb scalar type"); + } + } + elog(ERROR, "jsonb scalar type mismatch"); + return false; +} + +/* + * Compare two scalar JsonbValues, returning -1, 0, or 1. + * + * Strings are compared using the default collation. Used by B-tree + * operators, where a lexical sort order is generally expected. + */ +static int +compareJsonbScalarValue(JsonbValue *a, JsonbValue *b) +{ + if (a->type == b->type) + { + switch (a->type) + { + case jbvNull: + return 0; + case jbvString: + return varstr_cmp(a->val.string.val, + a->val.string.len, + b->val.string.val, + b->val.string.len, + DEFAULT_COLLATION_OID); + case jbvNumeric: + return DatumGetInt32(DirectFunctionCall2(numeric_cmp, + PointerGetDatum(a->val.numeric), + PointerGetDatum(b->val.numeric))); + case jbvBool: + if (a->val.boolean == b->val.boolean) + return 0; + else if (a->val.boolean > b->val.boolean) + return 1; + else + return -1; + default: + elog(ERROR, "invalid jsonb scalar type"); + } + } + elog(ERROR, "jsonb scalar type mismatch"); + return -1; +} + + +/* + * Functions for manipulating the resizable buffer used by convertJsonb and + * its subroutines. + */ + +/* + * Reserve 'len' bytes, at the end of the buffer, enlarging it if necessary. + * Returns the offset to the reserved area. The caller is expected to fill + * the reserved area later with copyToBuffer(). + */ +static int +reserveFromBuffer(StringInfo buffer, int len) +{ + int offset; + + /* Make more room if needed */ + enlargeStringInfo(buffer, len); + + /* remember current offset */ + offset = buffer->len; + + /* reserve the space */ + buffer->len += len; + + /* + * Keep a trailing null in place, even though it's not useful for us; it + * seems best to preserve the invariants of StringInfos. + */ + buffer->data[buffer->len] = '\0'; + + return offset; +} + +/* + * Copy 'len' bytes to a previously reserved area in buffer. + */ +static void +copyToBuffer(StringInfo buffer, int offset, const char *data, int len) +{ + memcpy(buffer->data + offset, data, len); +} + +/* + * A shorthand for reserveFromBuffer + copyToBuffer. + */ +static void +appendToBuffer(StringInfo buffer, const char *data, int len) +{ + int offset; + + offset = reserveFromBuffer(buffer, len); + copyToBuffer(buffer, offset, data, len); +} + + +/* + * Append padding, so that the length of the StringInfo is int-aligned. + * Returns the number of padding bytes appended. + */ +static short +padBufferToInt(StringInfo buffer) +{ + int padlen, + p, + offset; + + padlen = INTALIGN(buffer->len) - buffer->len; + + offset = reserveFromBuffer(buffer, padlen); + + /* padlen must be small, so this is probably faster than a memset */ + for (p = 0; p < padlen; p++) + buffer->data[offset + p] = '\0'; + + return padlen; +} + +/* + * Given a JsonbValue, convert to Jsonb. The result is palloc'd. + */ +static Jsonb * +convertToJsonb(JsonbValue *val) +{ + StringInfoData buffer; + JEntry jentry; + Jsonb *res; + + /* Should not already have binary representation */ + Assert(val->type != jbvBinary); + + /* Allocate an output buffer. It will be enlarged as needed */ + initStringInfo(&buffer); + + /* Make room for the varlena header */ + reserveFromBuffer(&buffer, VARHDRSZ); + + convertJsonbValue(&buffer, &jentry, val, 0); + + /* + * Note: the JEntry of the root is discarded. Therefore the root + * JsonbContainer struct must contain enough information to tell what kind + * of value it is. + */ + + res = (Jsonb *) buffer.data; + + SET_VARSIZE(res, buffer.len); + + return res; +} + +/* + * Subroutine of convertJsonb: serialize a single JsonbValue into buffer. + * + * The JEntry header for this node is returned in *header. It is filled in + * with the length of this value and appropriate type bits. If we wish to + * store an end offset rather than a length, it is the caller's responsibility + * to adjust for that. + * + * If the value is an array or an object, this recurses. 'level' is only used + * for debugging purposes. + */ +static void +convertJsonbValue(StringInfo buffer, JEntry *header, JsonbValue *val, int level) +{ + check_stack_depth(); + + if (!val) + return; + + /* + * A JsonbValue passed as val should never have a type of jbvBinary, and + * neither should any of its sub-components. Those values will be produced + * by convertJsonbArray and convertJsonbObject, the results of which will + * not be passed back to this function as an argument. + */ + + if (IsAJsonbScalar(val)) + convertJsonbScalar(buffer, header, val); + else if (val->type == jbvArray) + convertJsonbArray(buffer, header, val, level); + else if (val->type == jbvObject) + convertJsonbObject(buffer, header, val, level); + else + elog(ERROR, "unknown type of jsonb container to convert"); +} + +static void +convertJsonbArray(StringInfo buffer, JEntry *header, JsonbValue *val, int level) +{ + int base_offset; + int jentry_offset; + int i; + int totallen; + uint32 containerhead; + int nElems = val->val.array.nElems; + + /* Remember where in the buffer this array starts. */ + base_offset = buffer->len; + + /* Align to 4-byte boundary (any padding counts as part of my data) */ + padBufferToInt(buffer); + + /* + * Construct the header Jentry and store it in the beginning of the + * variable-length payload. + */ + containerhead = nElems | JB_FARRAY; + if (val->val.array.rawScalar) + { + Assert(nElems == 1); + Assert(level == 0); + containerhead |= JB_FSCALAR; + } + + appendToBuffer(buffer, (char *) &containerhead, sizeof(uint32)); + + /* Reserve space for the JEntries of the elements. */ + jentry_offset = reserveFromBuffer(buffer, sizeof(JEntry) * nElems); + + totallen = 0; + for (i = 0; i < nElems; i++) + { + JsonbValue *elem = &val->val.array.elems[i]; + int len; + JEntry meta; + + /* + * Convert element, producing a JEntry and appending its + * variable-length data to buffer + */ + convertJsonbValue(buffer, &meta, elem, level + 1); + + len = JBE_OFFLENFLD(meta); + totallen += len; + + /* + * Bail out if total variable-length data exceeds what will fit in a + * JEntry length field. We check this in each iteration, not just + * once at the end, to forestall possible integer overflow. + */ + if (totallen > JENTRY_OFFLENMASK) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("total size of jsonb array elements exceeds the maximum of %d bytes", + JENTRY_OFFLENMASK))); + + /* + * Convert each JB_OFFSET_STRIDE'th length to an offset. + */ + if ((i % JB_OFFSET_STRIDE) == 0) + meta = (meta & JENTRY_TYPEMASK) | totallen | JENTRY_HAS_OFF; + + copyToBuffer(buffer, jentry_offset, (char *) &meta, sizeof(JEntry)); + jentry_offset += sizeof(JEntry); + } + + /* Total data size is everything we've appended to buffer */ + totallen = buffer->len - base_offset; + + /* Check length again, since we didn't include the metadata above */ + if (totallen > JENTRY_OFFLENMASK) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("total size of jsonb array elements exceeds the maximum of %d bytes", + JENTRY_OFFLENMASK))); + + /* Initialize the header of this node in the container's JEntry array */ + *header = JENTRY_ISCONTAINER | totallen; +} + +static void +convertJsonbObject(StringInfo buffer, JEntry *header, JsonbValue *val, int level) +{ + int base_offset; + int jentry_offset; + int i; + int totallen; + uint32 containerheader; + int nPairs = val->val.object.nPairs; + + /* Remember where in the buffer this object starts. */ + base_offset = buffer->len; + + /* Align to 4-byte boundary (any padding counts as part of my data) */ + padBufferToInt(buffer); + + /* + * Construct the header Jentry and store it in the beginning of the + * variable-length payload. + */ + containerheader = nPairs | JB_FOBJECT; + appendToBuffer(buffer, (char *) &containerheader, sizeof(uint32)); + + /* Reserve space for the JEntries of the keys and values. */ + jentry_offset = reserveFromBuffer(buffer, sizeof(JEntry) * nPairs * 2); + + /* + * Iterate over the keys, then over the values, since that is the ordering + * we want in the on-disk representation. + */ + totallen = 0; + for (i = 0; i < nPairs; i++) + { + JsonbPair *pair = &val->val.object.pairs[i]; + int len; + JEntry meta; + + /* + * Convert key, producing a JEntry and appending its variable-length + * data to buffer + */ + convertJsonbScalar(buffer, &meta, &pair->key); + + len = JBE_OFFLENFLD(meta); + totallen += len; + + /* + * Bail out if total variable-length data exceeds what will fit in a + * JEntry length field. We check this in each iteration, not just + * once at the end, to forestall possible integer overflow. + */ + if (totallen > JENTRY_OFFLENMASK) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("total size of jsonb object elements exceeds the maximum of %d bytes", + JENTRY_OFFLENMASK))); + + /* + * Convert each JB_OFFSET_STRIDE'th length to an offset. + */ + if ((i % JB_OFFSET_STRIDE) == 0) + meta = (meta & JENTRY_TYPEMASK) | totallen | JENTRY_HAS_OFF; + + copyToBuffer(buffer, jentry_offset, (char *) &meta, sizeof(JEntry)); + jentry_offset += sizeof(JEntry); + } + for (i = 0; i < nPairs; i++) + { + JsonbPair *pair = &val->val.object.pairs[i]; + int len; + JEntry meta; + + /* + * Convert value, producing a JEntry and appending its variable-length + * data to buffer + */ + convertJsonbValue(buffer, &meta, &pair->value, level + 1); + + len = JBE_OFFLENFLD(meta); + totallen += len; + + /* + * Bail out if total variable-length data exceeds what will fit in a + * JEntry length field. We check this in each iteration, not just + * once at the end, to forestall possible integer overflow. + */ + if (totallen > JENTRY_OFFLENMASK) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("total size of jsonb object elements exceeds the maximum of %d bytes", + JENTRY_OFFLENMASK))); + + /* + * Convert each JB_OFFSET_STRIDE'th length to an offset. + */ + if (((i + nPairs) % JB_OFFSET_STRIDE) == 0) + meta = (meta & JENTRY_TYPEMASK) | totallen | JENTRY_HAS_OFF; + + copyToBuffer(buffer, jentry_offset, (char *) &meta, sizeof(JEntry)); + jentry_offset += sizeof(JEntry); + } + + /* Total data size is everything we've appended to buffer */ + totallen = buffer->len - base_offset; + + /* Check length again, since we didn't include the metadata above */ + if (totallen > JENTRY_OFFLENMASK) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("total size of jsonb object elements exceeds the maximum of %d bytes", + JENTRY_OFFLENMASK))); + + /* Initialize the header of this node in the container's JEntry array */ + *header = JENTRY_ISCONTAINER | totallen; +} + +static void +convertJsonbScalar(StringInfo buffer, JEntry *header, JsonbValue *scalarVal) +{ + int numlen; + short padlen; + + switch (scalarVal->type) + { + case jbvNull: + *header = JENTRY_ISNULL; + break; + + case jbvString: + appendToBuffer(buffer, scalarVal->val.string.val, scalarVal->val.string.len); + + *header = scalarVal->val.string.len; + break; + + case jbvNumeric: + numlen = VARSIZE_ANY(scalarVal->val.numeric); + padlen = padBufferToInt(buffer); + + appendToBuffer(buffer, (char *) scalarVal->val.numeric, numlen); + + *header = JENTRY_ISNUMERIC | (padlen + numlen); + break; + + case jbvBool: + *header = (scalarVal->val.boolean) ? + JENTRY_ISBOOL_TRUE : JENTRY_ISBOOL_FALSE; + break; + + case jbvDatetime: + { + char buf[MAXDATELEN + 1]; + size_t len; + + JsonEncodeDateTime(buf, + scalarVal->val.datetime.value, + scalarVal->val.datetime.typid, + &scalarVal->val.datetime.tz); + len = strlen(buf); + appendToBuffer(buffer, buf, len); + + *header = len; + } + break; + + default: + elog(ERROR, "invalid jsonb scalar type"); + } +} + +/* + * Compare two jbvString JsonbValue values, a and b. + * + * This is a special qsort() comparator used to sort strings in certain + * internal contexts where it is sufficient to have a well-defined sort order. + * In particular, object pair keys are sorted according to this criteria to + * facilitate cheap binary searches where we don't care about lexical sort + * order. + * + * a and b are first sorted based on their length. If a tie-breaker is + * required, only then do we consider string binary equality. + */ +static int +lengthCompareJsonbStringValue(const void *a, const void *b) +{ + const JsonbValue *va = (const JsonbValue *) a; + const JsonbValue *vb = (const JsonbValue *) b; + + Assert(va->type == jbvString); + Assert(vb->type == jbvString); + + return lengthCompareJsonbString(va->val.string.val, va->val.string.len, + vb->val.string.val, vb->val.string.len); +} + +/* + * Subroutine for lengthCompareJsonbStringValue + * + * This is also useful separately to implement binary search on + * JsonbContainers. + */ +static int +lengthCompareJsonbString(const char *val1, int len1, const char *val2, int len2) +{ + if (len1 == len2) + return memcmp(val1, val2, len1); + else + return len1 > len2 ? 1 : -1; +} + +/* + * qsort_arg() comparator to compare JsonbPair values. + * + * Third argument 'binequal' may point to a bool. If it's set, *binequal is set + * to true iff a and b have full binary equality, since some callers have an + * interest in whether the two values are equal or merely equivalent. + * + * N.B: String comparisons here are "length-wise" + * + * Pairs with equals keys are ordered such that the order field is respected. + */ +static int +lengthCompareJsonbPair(const void *a, const void *b, void *binequal) +{ + const JsonbPair *pa = (const JsonbPair *) a; + const JsonbPair *pb = (const JsonbPair *) b; + int res; + + res = lengthCompareJsonbStringValue(&pa->key, &pb->key); + if (res == 0 && binequal) + *((bool *) binequal) = true; + + /* + * Guarantee keeping order of equal pair. Unique algorithm will prefer + * first element as value. + */ + if (res == 0) + res = (pa->order > pb->order) ? -1 : 1; + + return res; +} + +/* + * Sort and unique-ify pairs in JsonbValue object + */ +static void +uniqueifyJsonbObject(JsonbValue *object, bool unique_keys, bool skip_nulls) +{ + bool hasNonUniq = false; + + Assert(object->type == jbvObject); + + if (object->val.object.nPairs > 1) + qsort_arg(object->val.object.pairs, object->val.object.nPairs, sizeof(JsonbPair), + lengthCompareJsonbPair, &hasNonUniq); + + if (hasNonUniq && unique_keys) + ereport(ERROR, + errcode(ERRCODE_DUPLICATE_JSON_OBJECT_KEY_VALUE), + errmsg("duplicate JSON object key value")); + + if (hasNonUniq || skip_nulls) + { + JsonbPair *ptr, + *res; + + while (skip_nulls && object->val.object.nPairs > 0 && + object->val.object.pairs->value.type == jbvNull) + { + /* If skip_nulls is true, remove leading items with null */ + object->val.object.pairs++; + object->val.object.nPairs--; + } + + if (object->val.object.nPairs > 0) + { + ptr = object->val.object.pairs + 1; + res = object->val.object.pairs; + + while (ptr - object->val.object.pairs < object->val.object.nPairs) + { + /* Avoid copying over duplicate or null */ + if (lengthCompareJsonbStringValue(ptr, res) != 0 && + (!skip_nulls || ptr->value.type != jbvNull)) + { + res++; + if (ptr != res) + memcpy(res, ptr, sizeof(JsonbPair)); + } + ptr++; + } + + object->val.object.nPairs = res + 1 - object->val.object.pairs; + } + } +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/jsonbsubs.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/jsonbsubs.c new file mode 100644 index 00000000000..de0ae3604ff --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/jsonbsubs.c @@ -0,0 +1,416 @@ +/*------------------------------------------------------------------------- + * + * jsonbsubs.c + * Subscripting support functions for jsonb. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/adt/jsonbsubs.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "executor/execExpr.h" +#include "nodes/makefuncs.h" +#include "nodes/nodeFuncs.h" +#include "nodes/subscripting.h" +#include "parser/parse_coerce.h" +#include "parser/parse_expr.h" +#include "utils/jsonb.h" +#include "utils/jsonfuncs.h" +#include "utils/builtins.h" +#include "utils/lsyscache.h" + + +/* SubscriptingRefState.workspace for jsonb subscripting execution */ +typedef struct JsonbSubWorkspace +{ + bool expectArray; /* jsonb root is expected to be an array */ + Oid *indexOid; /* OID of coerced subscript expression, could + * be only integer or text */ + Datum *index; /* Subscript values in Datum format */ +} JsonbSubWorkspace; + + +/* + * Finish parse analysis of a SubscriptingRef expression for a jsonb. + * + * Transform the subscript expressions, coerce them to text, + * and determine the result type of the SubscriptingRef node. + */ +static void +jsonb_subscript_transform(SubscriptingRef *sbsref, + List *indirection, + ParseState *pstate, + bool isSlice, + bool isAssignment) +{ + List *upperIndexpr = NIL; + ListCell *idx; + + /* + * Transform and convert the subscript expressions. Jsonb subscripting + * does not support slices, look only and the upper index. + */ + foreach(idx, indirection) + { + A_Indices *ai = lfirst_node(A_Indices, idx); + Node *subExpr; + + if (isSlice) + { + Node *expr = ai->uidx ? ai->uidx : ai->lidx; + + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("jsonb subscript does not support slices"), + parser_errposition(pstate, exprLocation(expr)))); + } + + if (ai->uidx) + { + Oid subExprType = InvalidOid, + targetType = UNKNOWNOID; + + subExpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind); + subExprType = exprType(subExpr); + + if (subExprType != UNKNOWNOID) + { + Oid targets[2] = {INT4OID, TEXTOID}; + + /* + * Jsonb can handle multiple subscript types, but cases when a + * subscript could be coerced to multiple target types must be + * avoided, similar to overloaded functions. It could be + * possibly extend with jsonpath in the future. + */ + for (int i = 0; i < 2; i++) + { + if (can_coerce_type(1, &subExprType, &targets[i], COERCION_IMPLICIT)) + { + /* + * One type has already succeeded, it means there are + * two coercion targets possible, failure. + */ + if (targetType != UNKNOWNOID) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("subscript type %s is not supported", format_type_be(subExprType)), + errhint("jsonb subscript must be coercible to only one type, integer or text."), + parser_errposition(pstate, exprLocation(subExpr)))); + + targetType = targets[i]; + } + } + + /* + * No suitable types were found, failure. + */ + if (targetType == UNKNOWNOID) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("subscript type %s is not supported", format_type_be(subExprType)), + errhint("jsonb subscript must be coercible to either integer or text."), + parser_errposition(pstate, exprLocation(subExpr)))); + } + else + targetType = TEXTOID; + + /* + * We known from can_coerce_type that coercion will succeed, so + * coerce_type could be used. Note the implicit coercion context, + * which is required to handle subscripts of different types, + * similar to overloaded functions. + */ + subExpr = coerce_type(pstate, + subExpr, subExprType, + targetType, -1, + COERCION_IMPLICIT, + COERCE_IMPLICIT_CAST, + -1); + if (subExpr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("jsonb subscript must have text type"), + parser_errposition(pstate, exprLocation(subExpr)))); + } + else + { + /* + * Slice with omitted upper bound. Should not happen as we already + * errored out on slice earlier, but handle this just in case. + */ + Assert(isSlice && ai->is_slice); + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("jsonb subscript does not support slices"), + parser_errposition(pstate, exprLocation(ai->uidx)))); + } + + upperIndexpr = lappend(upperIndexpr, subExpr); + } + + /* store the transformed lists into the SubscriptRef node */ + sbsref->refupperindexpr = upperIndexpr; + sbsref->reflowerindexpr = NIL; + + /* Determine the result type of the subscripting operation; always jsonb */ + sbsref->refrestype = JSONBOID; + sbsref->reftypmod = -1; +} + +/* + * During execution, process the subscripts in a SubscriptingRef expression. + * + * The subscript expressions are already evaluated in Datum form in the + * SubscriptingRefState's arrays. Check and convert them as necessary. + * + * If any subscript is NULL, we throw error in assignment cases, or in fetch + * cases set result to NULL and return false (instructing caller to skip the + * rest of the SubscriptingRef sequence). + */ +static bool +jsonb_subscript_check_subscripts(ExprState *state, + ExprEvalStep *op, + ExprContext *econtext) +{ + SubscriptingRefState *sbsrefstate = op->d.sbsref_subscript.state; + JsonbSubWorkspace *workspace = (JsonbSubWorkspace *) sbsrefstate->workspace; + + /* + * In case if the first subscript is an integer, the source jsonb is + * expected to be an array. This information is not used directly, all + * such cases are handled within corresponding jsonb assign functions. But + * if the source jsonb is NULL the expected type will be used to construct + * an empty source. + */ + if (sbsrefstate->numupper > 0 && sbsrefstate->upperprovided[0] && + !sbsrefstate->upperindexnull[0] && workspace->indexOid[0] == INT4OID) + workspace->expectArray = true; + + /* Process upper subscripts */ + for (int i = 0; i < sbsrefstate->numupper; i++) + { + if (sbsrefstate->upperprovided[i]) + { + /* If any index expr yields NULL, result is NULL or error */ + if (sbsrefstate->upperindexnull[i]) + { + if (sbsrefstate->isassignment) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("jsonb subscript in assignment must not be null"))); + *op->resnull = true; + return false; + } + + /* + * For jsonb fetch and assign functions we need to provide path in + * text format. Convert if it's not already text. + */ + if (workspace->indexOid[i] == INT4OID) + { + Datum datum = sbsrefstate->upperindex[i]; + char *cs = DatumGetCString(DirectFunctionCall1(int4out, datum)); + + workspace->index[i] = CStringGetTextDatum(cs); + } + else + workspace->index[i] = sbsrefstate->upperindex[i]; + } + } + + return true; +} + +/* + * Evaluate SubscriptingRef fetch for a jsonb element. + * + * Source container is in step's result variable (it's known not NULL, since + * we set fetch_strict to true). + */ +static void +jsonb_subscript_fetch(ExprState *state, + ExprEvalStep *op, + ExprContext *econtext) +{ + SubscriptingRefState *sbsrefstate = op->d.sbsref.state; + JsonbSubWorkspace *workspace = (JsonbSubWorkspace *) sbsrefstate->workspace; + Jsonb *jsonbSource; + + /* Should not get here if source jsonb (or any subscript) is null */ + Assert(!(*op->resnull)); + + jsonbSource = DatumGetJsonbP(*op->resvalue); + *op->resvalue = jsonb_get_element(jsonbSource, + workspace->index, + sbsrefstate->numupper, + op->resnull, + false); +} + +/* + * Evaluate SubscriptingRef assignment for a jsonb element assignment. + * + * Input container (possibly null) is in result area, replacement value is in + * SubscriptingRefState's replacevalue/replacenull. + */ +static void +jsonb_subscript_assign(ExprState *state, + ExprEvalStep *op, + ExprContext *econtext) +{ + SubscriptingRefState *sbsrefstate = op->d.sbsref.state; + JsonbSubWorkspace *workspace = (JsonbSubWorkspace *) sbsrefstate->workspace; + Jsonb *jsonbSource; + JsonbValue replacevalue; + + if (sbsrefstate->replacenull) + replacevalue.type = jbvNull; + else + JsonbToJsonbValue(DatumGetJsonbP(sbsrefstate->replacevalue), + &replacevalue); + + /* + * In case if the input container is null, set up an empty jsonb and + * proceed with the assignment. + */ + if (*op->resnull) + { + JsonbValue newSource; + + /* + * To avoid any surprising results, set up an empty jsonb array in + * case of an array is expected (i.e. the first subscript is integer), + * otherwise jsonb object. + */ + if (workspace->expectArray) + { + newSource.type = jbvArray; + newSource.val.array.nElems = 0; + newSource.val.array.rawScalar = false; + } + else + { + newSource.type = jbvObject; + newSource.val.object.nPairs = 0; + } + + jsonbSource = JsonbValueToJsonb(&newSource); + *op->resnull = false; + } + else + jsonbSource = DatumGetJsonbP(*op->resvalue); + + *op->resvalue = jsonb_set_element(jsonbSource, + workspace->index, + sbsrefstate->numupper, + &replacevalue); + /* The result is never NULL, so no need to change *op->resnull */ +} + +/* + * Compute old jsonb element value for a SubscriptingRef assignment + * expression. Will only be called if the new-value subexpression + * contains SubscriptingRef or FieldStore. This is the same as the + * regular fetch case, except that we have to handle a null jsonb, + * and the value should be stored into the SubscriptingRefState's + * prevvalue/prevnull fields. + */ +static void +jsonb_subscript_fetch_old(ExprState *state, + ExprEvalStep *op, + ExprContext *econtext) +{ + SubscriptingRefState *sbsrefstate = op->d.sbsref.state; + + if (*op->resnull) + { + /* whole jsonb is null, so any element is too */ + sbsrefstate->prevvalue = (Datum) 0; + sbsrefstate->prevnull = true; + } + else + { + Jsonb *jsonbSource = DatumGetJsonbP(*op->resvalue); + + sbsrefstate->prevvalue = jsonb_get_element(jsonbSource, + sbsrefstate->upperindex, + sbsrefstate->numupper, + &sbsrefstate->prevnull, + false); + } +} + +/* + * Set up execution state for a jsonb subscript operation. Opposite to the + * arrays subscription, there is no limit for number of subscripts as jsonb + * type itself doesn't have nesting limits. + */ +static void +jsonb_exec_setup(const SubscriptingRef *sbsref, + SubscriptingRefState *sbsrefstate, + SubscriptExecSteps *methods) +{ + JsonbSubWorkspace *workspace; + ListCell *lc; + int nupper = sbsref->refupperindexpr->length; + char *ptr; + + /* Allocate type-specific workspace with space for per-subscript data */ + workspace = palloc0(MAXALIGN(sizeof(JsonbSubWorkspace)) + + nupper * (sizeof(Datum) + sizeof(Oid))); + workspace->expectArray = false; + ptr = ((char *) workspace) + MAXALIGN(sizeof(JsonbSubWorkspace)); + + /* + * This coding assumes sizeof(Datum) >= sizeof(Oid), else we might + * misalign the indexOid pointer + */ + workspace->index = (Datum *) ptr; + ptr += nupper * sizeof(Datum); + workspace->indexOid = (Oid *) ptr; + + sbsrefstate->workspace = workspace; + + /* Collect subscript data types necessary at execution time */ + foreach(lc, sbsref->refupperindexpr) + { + Node *expr = lfirst(lc); + int i = foreach_current_index(lc); + + workspace->indexOid[i] = exprType(expr); + } + + /* + * Pass back pointers to appropriate step execution functions. + */ + methods->sbs_check_subscripts = jsonb_subscript_check_subscripts; + methods->sbs_fetch = jsonb_subscript_fetch; + methods->sbs_assign = jsonb_subscript_assign; + methods->sbs_fetch_old = jsonb_subscript_fetch_old; +} + +/* + * jsonb_subscript_handler + * Subscripting handler for jsonb. + * + */ +Datum +jsonb_subscript_handler(PG_FUNCTION_ARGS) +{ + static const SubscriptRoutines sbsroutines = { + .transform = jsonb_subscript_transform, + .exec_setup = jsonb_exec_setup, + .fetch_strict = true, /* fetch returns NULL for NULL inputs */ + .fetch_leakproof = true, /* fetch returns NULL for bad subscript */ + .store_leakproof = false /* ... but assignment throws error */ + }; + + PG_RETURN_POINTER(&sbsroutines); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/jsonfuncs.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/jsonfuncs.c new file mode 100644 index 00000000000..70cb922e6b7 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/jsonfuncs.c @@ -0,0 +1,5687 @@ +/*------------------------------------------------------------------------- + * + * jsonfuncs.c + * Functions to process JSON data types. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/utils/adt/jsonfuncs.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include <limits.h> + +#include "access/htup_details.h" +#include "catalog/pg_type.h" +#include "common/jsonapi.h" +#include "common/string.h" +#include "fmgr.h" +#include "funcapi.h" +#include "lib/stringinfo.h" +#include "mb/pg_wchar.h" +#include "miscadmin.h" +#include "nodes/miscnodes.h" +#include "utils/array.h" +#include "utils/builtins.h" +#include "utils/fmgroids.h" +#include "utils/hsearch.h" +#include "utils/json.h" +#include "utils/jsonb.h" +#include "utils/jsonfuncs.h" +#include "utils/lsyscache.h" +#include "utils/memutils.h" +#include "utils/syscache.h" +#include "utils/typcache.h" + +/* Operations available for setPath */ +#define JB_PATH_CREATE 0x0001 +#define JB_PATH_DELETE 0x0002 +#define JB_PATH_REPLACE 0x0004 +#define JB_PATH_INSERT_BEFORE 0x0008 +#define JB_PATH_INSERT_AFTER 0x0010 +#define JB_PATH_CREATE_OR_INSERT \ + (JB_PATH_INSERT_BEFORE | JB_PATH_INSERT_AFTER | JB_PATH_CREATE) +#define JB_PATH_FILL_GAPS 0x0020 +#define JB_PATH_CONSISTENT_POSITION 0x0040 + +/* state for json_object_keys */ +typedef struct OkeysState +{ + JsonLexContext *lex; + char **result; + int result_size; + int result_count; + int sent_count; +} OkeysState; + +/* state for iterate_json_values function */ +typedef struct IterateJsonStringValuesState +{ + JsonLexContext *lex; + JsonIterateStringValuesAction action; /* an action that will be applied + * to each json value */ + void *action_state; /* any necessary context for iteration */ + uint32 flags; /* what kind of elements from a json we want + * to iterate */ +} IterateJsonStringValuesState; + +/* state for transform_json_string_values function */ +typedef struct TransformJsonStringValuesState +{ + JsonLexContext *lex; + StringInfo strval; /* resulting json */ + JsonTransformStringValuesAction action; /* an action that will be applied + * to each json value */ + void *action_state; /* any necessary context for transformation */ +} TransformJsonStringValuesState; + +/* state for json_get* functions */ +typedef struct GetState +{ + JsonLexContext *lex; + text *tresult; + char *result_start; + bool normalize_results; + bool next_scalar; + int npath; /* length of each path-related array */ + char **path_names; /* field name(s) being sought */ + int *path_indexes; /* array index(es) being sought */ + bool *pathok; /* is path matched to current depth? */ + int *array_cur_index; /* current element index at each path + * level */ +} GetState; + +/* state for json_array_length */ +typedef struct AlenState +{ + JsonLexContext *lex; + int count; +} AlenState; + +/* state for json_each */ +typedef struct EachState +{ + JsonLexContext *lex; + Tuplestorestate *tuple_store; + TupleDesc ret_tdesc; + MemoryContext tmp_cxt; + char *result_start; + bool normalize_results; + bool next_scalar; + char *normalized_scalar; +} EachState; + +/* state for json_array_elements */ +typedef struct ElementsState +{ + JsonLexContext *lex; + const char *function_name; + Tuplestorestate *tuple_store; + TupleDesc ret_tdesc; + MemoryContext tmp_cxt; + char *result_start; + bool normalize_results; + bool next_scalar; + char *normalized_scalar; +} ElementsState; + +/* state for get_json_object_as_hash */ +typedef struct JHashState +{ + JsonLexContext *lex; + const char *function_name; + HTAB *hash; + char *saved_scalar; + char *save_json_start; + JsonTokenType saved_token_type; +} JHashState; + +/* hashtable element */ +typedef struct JsonHashEntry +{ + char fname[NAMEDATALEN]; /* hash key (MUST BE FIRST) */ + char *val; + JsonTokenType type; +} JsonHashEntry; + +/* structure to cache type I/O metadata needed for populate_scalar() */ +typedef struct ScalarIOData +{ + Oid typioparam; + FmgrInfo typiofunc; +} ScalarIOData; + +/* these two structures are used recursively */ +typedef struct ColumnIOData ColumnIOData; +typedef struct RecordIOData RecordIOData; + +/* structure to cache metadata needed for populate_array() */ +typedef struct ArrayIOData +{ + ColumnIOData *element_info; /* metadata cache */ + Oid element_type; /* array element type id */ + int32 element_typmod; /* array element type modifier */ +} ArrayIOData; + +/* structure to cache metadata needed for populate_composite() */ +typedef struct CompositeIOData +{ + /* + * We use pointer to a RecordIOData here because variable-length struct + * RecordIOData can't be used directly in ColumnIOData.io union + */ + RecordIOData *record_io; /* metadata cache for populate_record() */ + TupleDesc tupdesc; /* cached tuple descriptor */ + /* these fields differ from target type only if domain over composite: */ + Oid base_typid; /* base type id */ + int32 base_typmod; /* base type modifier */ + /* this field is used only if target type is domain over composite: */ + void *domain_info; /* opaque cache for domain checks */ +} CompositeIOData; + +/* structure to cache metadata needed for populate_domain() */ +typedef struct DomainIOData +{ + ColumnIOData *base_io; /* metadata cache */ + Oid base_typid; /* base type id */ + int32 base_typmod; /* base type modifier */ + void *domain_info; /* opaque cache for domain checks */ +} DomainIOData; + +/* enumeration type categories */ +typedef enum TypeCat +{ + TYPECAT_SCALAR = 's', + TYPECAT_ARRAY = 'a', + TYPECAT_COMPOSITE = 'c', + TYPECAT_COMPOSITE_DOMAIN = 'C', + TYPECAT_DOMAIN = 'd' +} TypeCat; + +/* these two are stolen from hstore / record_out, used in populate_record* */ + +/* structure to cache record metadata needed for populate_record_field() */ +struct ColumnIOData +{ + Oid typid; /* column type id */ + int32 typmod; /* column type modifier */ + TypeCat typcat; /* column type category */ + ScalarIOData scalar_io; /* metadata cache for direct conversion + * through input function */ + union + { + ArrayIOData array; + CompositeIOData composite; + DomainIOData domain; + } io; /* metadata cache for various column type + * categories */ +}; + +/* structure to cache record metadata needed for populate_record() */ +struct RecordIOData +{ + Oid record_type; + int32 record_typmod; + int ncolumns; + ColumnIOData columns[FLEXIBLE_ARRAY_MEMBER]; +}; + +/* per-query cache for populate_record_worker and populate_recordset_worker */ +typedef struct PopulateRecordCache +{ + Oid argtype; /* declared type of the record argument */ + ColumnIOData c; /* metadata cache for populate_composite() */ + MemoryContext fn_mcxt; /* where this is stored */ +} PopulateRecordCache; + +/* per-call state for populate_recordset */ +typedef struct PopulateRecordsetState +{ + JsonLexContext *lex; + const char *function_name; + HTAB *json_hash; + char *saved_scalar; + char *save_json_start; + JsonTokenType saved_token_type; + Tuplestorestate *tuple_store; + HeapTupleHeader rec; + PopulateRecordCache *cache; +} PopulateRecordsetState; + +/* common data for populate_array_json() and populate_array_dim_jsonb() */ +typedef struct PopulateArrayContext +{ + ArrayBuildState *astate; /* array build state */ + ArrayIOData *aio; /* metadata cache */ + MemoryContext acxt; /* array build memory context */ + MemoryContext mcxt; /* cache memory context */ + const char *colname; /* for diagnostics only */ + int *dims; /* dimensions */ + int *sizes; /* current dimension counters */ + int ndims; /* number of dimensions */ +} PopulateArrayContext; + +/* state for populate_array_json() */ +typedef struct PopulateArrayState +{ + JsonLexContext *lex; /* json lexer */ + PopulateArrayContext *ctx; /* context */ + char *element_start; /* start of the current array element */ + char *element_scalar; /* current array element token if it is a + * scalar */ + JsonTokenType element_type; /* current array element type */ +} PopulateArrayState; + +/* state for json_strip_nulls */ +typedef struct StripnullState +{ + JsonLexContext *lex; + StringInfo strval; + bool skip_next_null; +} StripnullState; + +/* structure for generalized json/jsonb value passing */ +typedef struct JsValue +{ + bool is_json; /* json/jsonb */ + union + { + struct + { + char *str; /* json string */ + int len; /* json string length or -1 if null-terminated */ + JsonTokenType type; /* json type */ + } json; /* json value */ + + JsonbValue *jsonb; /* jsonb value */ + } val; +} JsValue; + +typedef struct JsObject +{ + bool is_json; /* json/jsonb */ + union + { + HTAB *json_hash; + JsonbContainer *jsonb_cont; + } val; +} JsObject; + +/* useful macros for testing JsValue properties */ +#define JsValueIsNull(jsv) \ + ((jsv)->is_json ? \ + (!(jsv)->val.json.str || (jsv)->val.json.type == JSON_TOKEN_NULL) : \ + (!(jsv)->val.jsonb || (jsv)->val.jsonb->type == jbvNull)) + +#define JsValueIsString(jsv) \ + ((jsv)->is_json ? (jsv)->val.json.type == JSON_TOKEN_STRING \ + : ((jsv)->val.jsonb && (jsv)->val.jsonb->type == jbvString)) + +#define JsObjectIsEmpty(jso) \ + ((jso)->is_json \ + ? hash_get_num_entries((jso)->val.json_hash) == 0 \ + : ((jso)->val.jsonb_cont == NULL || \ + JsonContainerSize((jso)->val.jsonb_cont) == 0)) + +#define JsObjectFree(jso) \ + do { \ + if ((jso)->is_json) \ + hash_destroy((jso)->val.json_hash); \ + } while (0) + +static int report_json_context(JsonLexContext *lex); + +/* semantic action functions for json_object_keys */ +static JsonParseErrorType okeys_object_field_start(void *state, char *fname, bool isnull); +static JsonParseErrorType okeys_array_start(void *state); +static JsonParseErrorType okeys_scalar(void *state, char *token, JsonTokenType tokentype); + +/* semantic action functions for json_get* functions */ +static JsonParseErrorType get_object_start(void *state); +static JsonParseErrorType get_object_end(void *state); +static JsonParseErrorType get_object_field_start(void *state, char *fname, bool isnull); +static JsonParseErrorType get_object_field_end(void *state, char *fname, bool isnull); +static JsonParseErrorType get_array_start(void *state); +static JsonParseErrorType get_array_end(void *state); +static JsonParseErrorType get_array_element_start(void *state, bool isnull); +static JsonParseErrorType get_array_element_end(void *state, bool isnull); +static JsonParseErrorType get_scalar(void *state, char *token, JsonTokenType tokentype); + +/* common worker function for json getter functions */ +static Datum get_path_all(FunctionCallInfo fcinfo, bool as_text); +static text *get_worker(text *json, char **tpath, int *ipath, int npath, + bool normalize_results); +static Datum get_jsonb_path_all(FunctionCallInfo fcinfo, bool as_text); +static text *JsonbValueAsText(JsonbValue *v); + +/* semantic action functions for json_array_length */ +static JsonParseErrorType alen_object_start(void *state); +static JsonParseErrorType alen_scalar(void *state, char *token, JsonTokenType tokentype); +static JsonParseErrorType alen_array_element_start(void *state, bool isnull); + +/* common workers for json{b}_each* functions */ +static Datum each_worker(FunctionCallInfo fcinfo, bool as_text); +static Datum each_worker_jsonb(FunctionCallInfo fcinfo, const char *funcname, + bool as_text); + +/* semantic action functions for json_each */ +static JsonParseErrorType each_object_field_start(void *state, char *fname, bool isnull); +static JsonParseErrorType each_object_field_end(void *state, char *fname, bool isnull); +static JsonParseErrorType each_array_start(void *state); +static JsonParseErrorType each_scalar(void *state, char *token, JsonTokenType tokentype); + +/* common workers for json{b}_array_elements_* functions */ +static Datum elements_worker(FunctionCallInfo fcinfo, const char *funcname, + bool as_text); +static Datum elements_worker_jsonb(FunctionCallInfo fcinfo, const char *funcname, + bool as_text); + +/* semantic action functions for json_array_elements */ +static JsonParseErrorType elements_object_start(void *state); +static JsonParseErrorType elements_array_element_start(void *state, bool isnull); +static JsonParseErrorType elements_array_element_end(void *state, bool isnull); +static JsonParseErrorType elements_scalar(void *state, char *token, JsonTokenType tokentype); + +/* turn a json object into a hash table */ +static HTAB *get_json_object_as_hash(char *json, int len, const char *funcname); + +/* semantic actions for populate_array_json */ +static JsonParseErrorType populate_array_object_start(void *_state); +static JsonParseErrorType populate_array_array_end(void *_state); +static JsonParseErrorType populate_array_element_start(void *_state, bool isnull); +static JsonParseErrorType populate_array_element_end(void *_state, bool isnull); +static JsonParseErrorType populate_array_scalar(void *_state, char *token, JsonTokenType tokentype); + +/* semantic action functions for get_json_object_as_hash */ +static JsonParseErrorType hash_object_field_start(void *state, char *fname, bool isnull); +static JsonParseErrorType hash_object_field_end(void *state, char *fname, bool isnull); +static JsonParseErrorType hash_array_start(void *state); +static JsonParseErrorType hash_scalar(void *state, char *token, JsonTokenType tokentype); + +/* semantic action functions for populate_recordset */ +static JsonParseErrorType populate_recordset_object_field_start(void *state, char *fname, bool isnull); +static JsonParseErrorType populate_recordset_object_field_end(void *state, char *fname, bool isnull); +static JsonParseErrorType populate_recordset_scalar(void *state, char *token, JsonTokenType tokentype); +static JsonParseErrorType populate_recordset_object_start(void *state); +static JsonParseErrorType populate_recordset_object_end(void *state); +static JsonParseErrorType populate_recordset_array_start(void *state); +static JsonParseErrorType populate_recordset_array_element_start(void *state, bool isnull); + +/* semantic action functions for json_strip_nulls */ +static JsonParseErrorType sn_object_start(void *state); +static JsonParseErrorType sn_object_end(void *state); +static JsonParseErrorType sn_array_start(void *state); +static JsonParseErrorType sn_array_end(void *state); +static JsonParseErrorType sn_object_field_start(void *state, char *fname, bool isnull); +static JsonParseErrorType sn_array_element_start(void *state, bool isnull); +static JsonParseErrorType sn_scalar(void *state, char *token, JsonTokenType tokentype); + +/* worker functions for populate_record, to_record, populate_recordset and to_recordset */ +static Datum populate_recordset_worker(FunctionCallInfo fcinfo, const char *funcname, + bool is_json, bool have_record_arg); +static Datum populate_record_worker(FunctionCallInfo fcinfo, const char *funcname, + bool is_json, bool have_record_arg); + +/* helper functions for populate_record[set] */ +static HeapTupleHeader populate_record(TupleDesc tupdesc, RecordIOData **record_p, + HeapTupleHeader defaultval, MemoryContext mcxt, + JsObject *obj); +static void get_record_type_from_argument(FunctionCallInfo fcinfo, + const char *funcname, + PopulateRecordCache *cache); +static void get_record_type_from_query(FunctionCallInfo fcinfo, + const char *funcname, + PopulateRecordCache *cache); +static void JsValueToJsObject(JsValue *jsv, JsObject *jso); +static Datum populate_composite(CompositeIOData *io, Oid typid, + const char *colname, MemoryContext mcxt, + HeapTupleHeader defaultval, JsValue *jsv, bool isnull); +static Datum populate_scalar(ScalarIOData *io, Oid typid, int32 typmod, JsValue *jsv); +static void prepare_column_cache(ColumnIOData *column, Oid typid, int32 typmod, + MemoryContext mcxt, bool need_scalar); +static Datum populate_record_field(ColumnIOData *col, Oid typid, int32 typmod, + const char *colname, MemoryContext mcxt, Datum defaultval, + JsValue *jsv, bool *isnull); +static RecordIOData *allocate_record_info(MemoryContext mcxt, int ncolumns); +static bool JsObjectGetField(JsObject *obj, char *field, JsValue *jsv); +static void populate_recordset_record(PopulateRecordsetState *state, JsObject *obj); +static void populate_array_json(PopulateArrayContext *ctx, char *json, int len); +static void populate_array_dim_jsonb(PopulateArrayContext *ctx, JsonbValue *jbv, + int ndim); +static void populate_array_report_expected_array(PopulateArrayContext *ctx, int ndim); +static void populate_array_assign_ndims(PopulateArrayContext *ctx, int ndims); +static void populate_array_check_dimension(PopulateArrayContext *ctx, int ndim); +static void populate_array_element(PopulateArrayContext *ctx, int ndim, JsValue *jsv); +static Datum populate_array(ArrayIOData *aio, const char *colname, + MemoryContext mcxt, JsValue *jsv); +static Datum populate_domain(DomainIOData *io, Oid typid, const char *colname, + MemoryContext mcxt, JsValue *jsv, bool isnull); + +/* functions supporting jsonb_delete, jsonb_set and jsonb_concat */ +static JsonbValue *IteratorConcat(JsonbIterator **it1, JsonbIterator **it2, + JsonbParseState **state); +static JsonbValue *setPath(JsonbIterator **it, Datum *path_elems, + bool *path_nulls, int path_len, + JsonbParseState **st, int level, JsonbValue *newval, + int op_type); +static void setPathObject(JsonbIterator **it, Datum *path_elems, + bool *path_nulls, int path_len, JsonbParseState **st, + int level, + JsonbValue *newval, uint32 npairs, int op_type); +static void setPathArray(JsonbIterator **it, Datum *path_elems, + bool *path_nulls, int path_len, JsonbParseState **st, + int level, + JsonbValue *newval, uint32 nelems, int op_type); + +/* function supporting iterate_json_values */ +static JsonParseErrorType iterate_values_scalar(void *state, char *token, JsonTokenType tokentype); +static JsonParseErrorType iterate_values_object_field_start(void *state, char *fname, bool isnull); + +/* functions supporting transform_json_string_values */ +static JsonParseErrorType transform_string_values_object_start(void *state); +static JsonParseErrorType transform_string_values_object_end(void *state); +static JsonParseErrorType transform_string_values_array_start(void *state); +static JsonParseErrorType transform_string_values_array_end(void *state); +static JsonParseErrorType transform_string_values_object_field_start(void *state, char *fname, bool isnull); +static JsonParseErrorType transform_string_values_array_element_start(void *state, bool isnull); +static JsonParseErrorType transform_string_values_scalar(void *state, char *token, JsonTokenType tokentype); + + +/* + * pg_parse_json_or_errsave + * + * This function is like pg_parse_json, except that it does not return a + * JsonParseErrorType. Instead, in case of any failure, this function will + * save error data into *escontext if that's an ErrorSaveContext, otherwise + * ereport(ERROR). + * + * Returns a boolean indicating success or failure (failure will only be + * returned when escontext is an ErrorSaveContext). + */ +bool +pg_parse_json_or_errsave(JsonLexContext *lex, JsonSemAction *sem, + Node *escontext) +{ + JsonParseErrorType result; + + result = pg_parse_json(lex, sem); + if (result != JSON_SUCCESS) + { + json_errsave_error(result, lex, escontext); + return false; + } + return true; +} + +/* + * makeJsonLexContext + * + * This is like makeJsonLexContextCstringLen, but it accepts a text value + * directly. + */ +JsonLexContext * +makeJsonLexContext(text *json, bool need_escapes) +{ + /* + * Most callers pass a detoasted datum, but it's not clear that they all + * do. pg_detoast_datum_packed() is cheap insurance. + */ + json = pg_detoast_datum_packed(json); + + return makeJsonLexContextCstringLen(VARDATA_ANY(json), + VARSIZE_ANY_EXHDR(json), + GetDatabaseEncoding(), + need_escapes); +} + +/* + * SQL function json_object_keys + * + * Returns the set of keys for the object argument. + * + * This SRF operates in value-per-call mode. It processes the + * object during the first call, and the keys are simply stashed + * in an array, whose size is expanded as necessary. This is probably + * safe enough for a list of keys of a single object, since they are + * limited in size to NAMEDATALEN and the number of keys is unlikely to + * be so huge that it has major memory implications. + */ +Datum +jsonb_object_keys(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + OkeysState *state; + + if (SRF_IS_FIRSTCALL()) + { + MemoryContext oldcontext; + Jsonb *jb = PG_GETARG_JSONB_P(0); + bool skipNested = false; + JsonbIterator *it; + JsonbValue v; + JsonbIteratorToken r; + + if (JB_ROOT_IS_SCALAR(jb)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot call %s on a scalar", + "jsonb_object_keys"))); + else if (JB_ROOT_IS_ARRAY(jb)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot call %s on an array", + "jsonb_object_keys"))); + + funcctx = SRF_FIRSTCALL_INIT(); + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + state = palloc(sizeof(OkeysState)); + + state->result_size = JB_ROOT_COUNT(jb); + state->result_count = 0; + state->sent_count = 0; + state->result = palloc(state->result_size * sizeof(char *)); + + it = JsonbIteratorInit(&jb->root); + + while ((r = JsonbIteratorNext(&it, &v, skipNested)) != WJB_DONE) + { + skipNested = true; + + if (r == WJB_KEY) + { + char *cstr; + + cstr = palloc(v.val.string.len + 1 * sizeof(char)); + memcpy(cstr, v.val.string.val, v.val.string.len); + cstr[v.val.string.len] = '\0'; + state->result[state->result_count++] = cstr; + } + } + + MemoryContextSwitchTo(oldcontext); + funcctx->user_fctx = (void *) state; + } + + funcctx = SRF_PERCALL_SETUP(); + state = (OkeysState *) funcctx->user_fctx; + + if (state->sent_count < state->result_count) + { + char *nxt = state->result[state->sent_count++]; + + SRF_RETURN_NEXT(funcctx, CStringGetTextDatum(nxt)); + } + + SRF_RETURN_DONE(funcctx); +} + +/* + * Report a JSON error. + */ +void +json_errsave_error(JsonParseErrorType error, JsonLexContext *lex, + Node *escontext) +{ + if (error == JSON_UNICODE_HIGH_ESCAPE || + error == JSON_UNICODE_UNTRANSLATABLE || + error == JSON_UNICODE_CODE_POINT_ZERO) + errsave(escontext, + (errcode(ERRCODE_UNTRANSLATABLE_CHARACTER), + errmsg("unsupported Unicode escape sequence"), + errdetail_internal("%s", json_errdetail(error, lex)), + report_json_context(lex))); + else if (error == JSON_SEM_ACTION_FAILED) + { + /* semantic action function had better have reported something */ + if (!SOFT_ERROR_OCCURRED(escontext)) + elog(ERROR, "JSON semantic action function did not provide error information"); + } + else + errsave(escontext, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s", "json"), + errdetail_internal("%s", json_errdetail(error, lex)), + report_json_context(lex))); +} + +/* + * Report a CONTEXT line for bogus JSON input. + * + * lex->token_terminator must be set to identify the spot where we detected + * the error. Note that lex->token_start might be NULL, in case we recognized + * error at EOF. + * + * The return value isn't meaningful, but we make it non-void so that this + * can be invoked inside ereport(). + */ +static int +report_json_context(JsonLexContext *lex) +{ + const char *context_start; + const char *context_end; + const char *line_start; + char *ctxt; + int ctxtlen; + const char *prefix; + const char *suffix; + + /* Choose boundaries for the part of the input we will display */ + line_start = lex->line_start; + context_start = line_start; + context_end = lex->token_terminator; + Assert(context_end >= context_start); + + /* Advance until we are close enough to context_end */ + while (context_end - context_start >= 50) + { + /* Advance to next multibyte character */ + if (IS_HIGHBIT_SET(*context_start)) + context_start += pg_mblen(context_start); + else + context_start++; + } + + /* + * We add "..." to indicate that the excerpt doesn't start at the + * beginning of the line ... but if we're within 3 characters of the + * beginning of the line, we might as well just show the whole line. + */ + if (context_start - line_start <= 3) + context_start = line_start; + + /* Get a null-terminated copy of the data to present */ + ctxtlen = context_end - context_start; + ctxt = palloc(ctxtlen + 1); + memcpy(ctxt, context_start, ctxtlen); + ctxt[ctxtlen] = '\0'; + + /* + * Show the context, prefixing "..." if not starting at start of line, and + * suffixing "..." if not ending at end of line. + */ + prefix = (context_start > line_start) ? "..." : ""; + suffix = (lex->token_type != JSON_TOKEN_END && + context_end - lex->input < lex->input_length && + *context_end != '\n' && *context_end != '\r') ? "..." : ""; + + return errcontext("JSON data, line %d: %s%s%s", + lex->line_number, prefix, ctxt, suffix); +} + + +Datum +json_object_keys(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + OkeysState *state; + + if (SRF_IS_FIRSTCALL()) + { + text *json = PG_GETARG_TEXT_PP(0); + JsonLexContext *lex = makeJsonLexContext(json, true); + JsonSemAction *sem; + MemoryContext oldcontext; + + funcctx = SRF_FIRSTCALL_INIT(); + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + state = palloc(sizeof(OkeysState)); + sem = palloc0(sizeof(JsonSemAction)); + + state->lex = lex; + state->result_size = 256; + state->result_count = 0; + state->sent_count = 0; + state->result = palloc(256 * sizeof(char *)); + + sem->semstate = (void *) state; + sem->array_start = okeys_array_start; + sem->scalar = okeys_scalar; + sem->object_field_start = okeys_object_field_start; + /* remainder are all NULL, courtesy of palloc0 above */ + + pg_parse_json_or_ereport(lex, sem); + /* keys are now in state->result */ + + pfree(lex->strval->data); + pfree(lex->strval); + pfree(lex); + pfree(sem); + + MemoryContextSwitchTo(oldcontext); + funcctx->user_fctx = (void *) state; + } + + funcctx = SRF_PERCALL_SETUP(); + state = (OkeysState *) funcctx->user_fctx; + + if (state->sent_count < state->result_count) + { + char *nxt = state->result[state->sent_count++]; + + SRF_RETURN_NEXT(funcctx, CStringGetTextDatum(nxt)); + } + + SRF_RETURN_DONE(funcctx); +} + +static JsonParseErrorType +okeys_object_field_start(void *state, char *fname, bool isnull) +{ + OkeysState *_state = (OkeysState *) state; + + /* only collecting keys for the top level object */ + if (_state->lex->lex_level != 1) + return JSON_SUCCESS; + + /* enlarge result array if necessary */ + if (_state->result_count >= _state->result_size) + { + _state->result_size *= 2; + _state->result = (char **) + repalloc(_state->result, sizeof(char *) * _state->result_size); + } + + /* save a copy of the field name */ + _state->result[_state->result_count++] = pstrdup(fname); + + return JSON_SUCCESS; +} + +static JsonParseErrorType +okeys_array_start(void *state) +{ + OkeysState *_state = (OkeysState *) state; + + /* top level must be a json object */ + if (_state->lex->lex_level == 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot call %s on an array", + "json_object_keys"))); + + return JSON_SUCCESS; +} + +static JsonParseErrorType +okeys_scalar(void *state, char *token, JsonTokenType tokentype) +{ + OkeysState *_state = (OkeysState *) state; + + /* top level must be a json object */ + if (_state->lex->lex_level == 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot call %s on a scalar", + "json_object_keys"))); + + return JSON_SUCCESS; +} + +/* + * json and jsonb getter functions + * these implement the -> ->> #> and #>> operators + * and the json{b?}_extract_path*(json, text, ...) functions + */ + + +Datum +json_object_field(PG_FUNCTION_ARGS) +{ + text *json = PG_GETARG_TEXT_PP(0); + text *fname = PG_GETARG_TEXT_PP(1); + char *fnamestr = text_to_cstring(fname); + text *result; + + result = get_worker(json, &fnamestr, NULL, 1, false); + + if (result != NULL) + PG_RETURN_TEXT_P(result); + else + PG_RETURN_NULL(); +} + +Datum +jsonb_object_field(PG_FUNCTION_ARGS) +{ + Jsonb *jb = PG_GETARG_JSONB_P(0); + text *key = PG_GETARG_TEXT_PP(1); + JsonbValue *v; + JsonbValue vbuf; + + if (!JB_ROOT_IS_OBJECT(jb)) + PG_RETURN_NULL(); + + v = getKeyJsonValueFromContainer(&jb->root, + VARDATA_ANY(key), + VARSIZE_ANY_EXHDR(key), + &vbuf); + + if (v != NULL) + PG_RETURN_JSONB_P(JsonbValueToJsonb(v)); + + PG_RETURN_NULL(); +} + +Datum +json_object_field_text(PG_FUNCTION_ARGS) +{ + text *json = PG_GETARG_TEXT_PP(0); + text *fname = PG_GETARG_TEXT_PP(1); + char *fnamestr = text_to_cstring(fname); + text *result; + + result = get_worker(json, &fnamestr, NULL, 1, true); + + if (result != NULL) + PG_RETURN_TEXT_P(result); + else + PG_RETURN_NULL(); +} + +Datum +jsonb_object_field_text(PG_FUNCTION_ARGS) +{ + Jsonb *jb = PG_GETARG_JSONB_P(0); + text *key = PG_GETARG_TEXT_PP(1); + JsonbValue *v; + JsonbValue vbuf; + + if (!JB_ROOT_IS_OBJECT(jb)) + PG_RETURN_NULL(); + + v = getKeyJsonValueFromContainer(&jb->root, + VARDATA_ANY(key), + VARSIZE_ANY_EXHDR(key), + &vbuf); + + if (v != NULL && v->type != jbvNull) + PG_RETURN_TEXT_P(JsonbValueAsText(v)); + + PG_RETURN_NULL(); +} + +Datum +json_array_element(PG_FUNCTION_ARGS) +{ + text *json = PG_GETARG_TEXT_PP(0); + int element = PG_GETARG_INT32(1); + text *result; + + result = get_worker(json, NULL, &element, 1, false); + + if (result != NULL) + PG_RETURN_TEXT_P(result); + else + PG_RETURN_NULL(); +} + +Datum +jsonb_array_element(PG_FUNCTION_ARGS) +{ + Jsonb *jb = PG_GETARG_JSONB_P(0); + int element = PG_GETARG_INT32(1); + JsonbValue *v; + + if (!JB_ROOT_IS_ARRAY(jb)) + PG_RETURN_NULL(); + + /* Handle negative subscript */ + if (element < 0) + { + uint32 nelements = JB_ROOT_COUNT(jb); + + if (-element > nelements) + PG_RETURN_NULL(); + else + element += nelements; + } + + v = getIthJsonbValueFromContainer(&jb->root, element); + if (v != NULL) + PG_RETURN_JSONB_P(JsonbValueToJsonb(v)); + + PG_RETURN_NULL(); +} + +Datum +json_array_element_text(PG_FUNCTION_ARGS) +{ + text *json = PG_GETARG_TEXT_PP(0); + int element = PG_GETARG_INT32(1); + text *result; + + result = get_worker(json, NULL, &element, 1, true); + + if (result != NULL) + PG_RETURN_TEXT_P(result); + else + PG_RETURN_NULL(); +} + +Datum +jsonb_array_element_text(PG_FUNCTION_ARGS) +{ + Jsonb *jb = PG_GETARG_JSONB_P(0); + int element = PG_GETARG_INT32(1); + JsonbValue *v; + + if (!JB_ROOT_IS_ARRAY(jb)) + PG_RETURN_NULL(); + + /* Handle negative subscript */ + if (element < 0) + { + uint32 nelements = JB_ROOT_COUNT(jb); + + if (-element > nelements) + PG_RETURN_NULL(); + else + element += nelements; + } + + v = getIthJsonbValueFromContainer(&jb->root, element); + + if (v != NULL && v->type != jbvNull) + PG_RETURN_TEXT_P(JsonbValueAsText(v)); + + PG_RETURN_NULL(); +} + +Datum +json_extract_path(PG_FUNCTION_ARGS) +{ + return get_path_all(fcinfo, false); +} + +Datum +json_extract_path_text(PG_FUNCTION_ARGS) +{ + return get_path_all(fcinfo, true); +} + +/* + * common routine for extract_path functions + */ +static Datum +get_path_all(FunctionCallInfo fcinfo, bool as_text) +{ + text *json = PG_GETARG_TEXT_PP(0); + ArrayType *path = PG_GETARG_ARRAYTYPE_P(1); + text *result; + Datum *pathtext; + bool *pathnulls; + int npath; + char **tpath; + int *ipath; + int i; + + /* + * If the array contains any null elements, return NULL, on the grounds + * that you'd have gotten NULL if any RHS value were NULL in a nested + * series of applications of the -> operator. (Note: because we also + * return NULL for error cases such as no-such-field, this is true + * regardless of the contents of the rest of the array.) + */ + if (array_contains_nulls(path)) + PG_RETURN_NULL(); + + deconstruct_array_builtin(path, TEXTOID, &pathtext, &pathnulls, &npath); + + tpath = palloc(npath * sizeof(char *)); + ipath = palloc(npath * sizeof(int)); + + for (i = 0; i < npath; i++) + { + Assert(!pathnulls[i]); + tpath[i] = TextDatumGetCString(pathtext[i]); + + /* + * we have no idea at this stage what structure the document is so + * just convert anything in the path that we can to an integer and set + * all the other integers to INT_MIN which will never match. + */ + if (*tpath[i] != '\0') + { + int ind; + char *endptr; + + errno = 0; + ind = strtoint(tpath[i], &endptr, 10); + if (endptr == tpath[i] || *endptr != '\0' || errno != 0) + ipath[i] = INT_MIN; + else + ipath[i] = ind; + } + else + ipath[i] = INT_MIN; + } + + result = get_worker(json, tpath, ipath, npath, as_text); + + if (result != NULL) + PG_RETURN_TEXT_P(result); + else + PG_RETURN_NULL(); +} + +/* + * get_worker + * + * common worker for all the json getter functions + * + * json: JSON object (in text form) + * tpath[]: field name(s) to extract + * ipath[]: array index(es) (zero-based) to extract, accepts negatives + * npath: length of tpath[] and/or ipath[] + * normalize_results: true to de-escape string and null scalars + * + * tpath can be NULL, or any one tpath[] entry can be NULL, if an object + * field is not to be matched at that nesting level. Similarly, ipath can + * be NULL, or any one ipath[] entry can be INT_MIN if an array element is + * not to be matched at that nesting level (a json datum should never be + * large enough to have -INT_MIN elements due to MaxAllocSize restriction). + */ +static text * +get_worker(text *json, + char **tpath, + int *ipath, + int npath, + bool normalize_results) +{ + JsonLexContext *lex = makeJsonLexContext(json, true); + JsonSemAction *sem = palloc0(sizeof(JsonSemAction)); + GetState *state = palloc0(sizeof(GetState)); + + Assert(npath >= 0); + + state->lex = lex; + /* is it "_as_text" variant? */ + state->normalize_results = normalize_results; + state->npath = npath; + state->path_names = tpath; + state->path_indexes = ipath; + state->pathok = palloc0(sizeof(bool) * npath); + state->array_cur_index = palloc(sizeof(int) * npath); + + if (npath > 0) + state->pathok[0] = true; + + sem->semstate = (void *) state; + + /* + * Not all variants need all the semantic routines. Only set the ones that + * are actually needed for maximum efficiency. + */ + sem->scalar = get_scalar; + if (npath == 0) + { + sem->object_start = get_object_start; + sem->object_end = get_object_end; + sem->array_start = get_array_start; + sem->array_end = get_array_end; + } + if (tpath != NULL) + { + sem->object_field_start = get_object_field_start; + sem->object_field_end = get_object_field_end; + } + if (ipath != NULL) + { + sem->array_start = get_array_start; + sem->array_element_start = get_array_element_start; + sem->array_element_end = get_array_element_end; + } + + pg_parse_json_or_ereport(lex, sem); + + return state->tresult; +} + +static JsonParseErrorType +get_object_start(void *state) +{ + GetState *_state = (GetState *) state; + int lex_level = _state->lex->lex_level; + + if (lex_level == 0 && _state->npath == 0) + { + /* + * Special case: we should match the entire object. We only need this + * at outermost level because at nested levels the match will have + * been started by the outer field or array element callback. + */ + _state->result_start = _state->lex->token_start; + } + + return JSON_SUCCESS; +} + +static JsonParseErrorType +get_object_end(void *state) +{ + GetState *_state = (GetState *) state; + int lex_level = _state->lex->lex_level; + + if (lex_level == 0 && _state->npath == 0) + { + /* Special case: return the entire object */ + char *start = _state->result_start; + int len = _state->lex->prev_token_terminator - start; + + _state->tresult = cstring_to_text_with_len(start, len); + } + + return JSON_SUCCESS; +} + +static JsonParseErrorType +get_object_field_start(void *state, char *fname, bool isnull) +{ + GetState *_state = (GetState *) state; + bool get_next = false; + int lex_level = _state->lex->lex_level; + + if (lex_level <= _state->npath && + _state->pathok[lex_level - 1] && + _state->path_names != NULL && + _state->path_names[lex_level - 1] != NULL && + strcmp(fname, _state->path_names[lex_level - 1]) == 0) + { + if (lex_level < _state->npath) + { + /* if not at end of path just mark path ok */ + _state->pathok[lex_level] = true; + } + else + { + /* end of path, so we want this value */ + get_next = true; + } + } + + if (get_next) + { + /* this object overrides any previous matching object */ + _state->tresult = NULL; + _state->result_start = NULL; + + if (_state->normalize_results && + _state->lex->token_type == JSON_TOKEN_STRING) + { + /* for as_text variants, tell get_scalar to set it for us */ + _state->next_scalar = true; + } + else + { + /* for non-as_text variants, just note the json starting point */ + _state->result_start = _state->lex->token_start; + } + } + + return JSON_SUCCESS; +} + +static JsonParseErrorType +get_object_field_end(void *state, char *fname, bool isnull) +{ + GetState *_state = (GetState *) state; + bool get_last = false; + int lex_level = _state->lex->lex_level; + + /* same tests as in get_object_field_start */ + if (lex_level <= _state->npath && + _state->pathok[lex_level - 1] && + _state->path_names != NULL && + _state->path_names[lex_level - 1] != NULL && + strcmp(fname, _state->path_names[lex_level - 1]) == 0) + { + if (lex_level < _state->npath) + { + /* done with this field so reset pathok */ + _state->pathok[lex_level] = false; + } + else + { + /* end of path, so we want this value */ + get_last = true; + } + } + + /* for as_text scalar case, our work is already done */ + if (get_last && _state->result_start != NULL) + { + /* + * make a text object from the string from the previously noted json + * start up to the end of the previous token (the lexer is by now + * ahead of us on whatever came after what we're interested in). + */ + if (isnull && _state->normalize_results) + _state->tresult = (text *) NULL; + else + { + char *start = _state->result_start; + int len = _state->lex->prev_token_terminator - start; + + _state->tresult = cstring_to_text_with_len(start, len); + } + + /* this should be unnecessary but let's do it for cleanliness: */ + _state->result_start = NULL; + } + + return JSON_SUCCESS; +} + +static JsonParseErrorType +get_array_start(void *state) +{ + GetState *_state = (GetState *) state; + int lex_level = _state->lex->lex_level; + + if (lex_level < _state->npath) + { + /* Initialize counting of elements in this array */ + _state->array_cur_index[lex_level] = -1; + + /* INT_MIN value is reserved to represent invalid subscript */ + if (_state->path_indexes[lex_level] < 0 && + _state->path_indexes[lex_level] != INT_MIN) + { + /* Negative subscript -- convert to positive-wise subscript */ + JsonParseErrorType error; + int nelements; + + error = json_count_array_elements(_state->lex, &nelements); + if (error != JSON_SUCCESS) + json_errsave_error(error, _state->lex, NULL); + + if (-_state->path_indexes[lex_level] <= nelements) + _state->path_indexes[lex_level] += nelements; + } + } + else if (lex_level == 0 && _state->npath == 0) + { + /* + * Special case: we should match the entire array. We only need this + * at the outermost level because at nested levels the match will have + * been started by the outer field or array element callback. + */ + _state->result_start = _state->lex->token_start; + } + + return JSON_SUCCESS; +} + +static JsonParseErrorType +get_array_end(void *state) +{ + GetState *_state = (GetState *) state; + int lex_level = _state->lex->lex_level; + + if (lex_level == 0 && _state->npath == 0) + { + /* Special case: return the entire array */ + char *start = _state->result_start; + int len = _state->lex->prev_token_terminator - start; + + _state->tresult = cstring_to_text_with_len(start, len); + } + + return JSON_SUCCESS; +} + +static JsonParseErrorType +get_array_element_start(void *state, bool isnull) +{ + GetState *_state = (GetState *) state; + bool get_next = false; + int lex_level = _state->lex->lex_level; + + /* Update array element counter */ + if (lex_level <= _state->npath) + _state->array_cur_index[lex_level - 1]++; + + if (lex_level <= _state->npath && + _state->pathok[lex_level - 1] && + _state->path_indexes != NULL && + _state->array_cur_index[lex_level - 1] == _state->path_indexes[lex_level - 1]) + { + if (lex_level < _state->npath) + { + /* if not at end of path just mark path ok */ + _state->pathok[lex_level] = true; + } + else + { + /* end of path, so we want this value */ + get_next = true; + } + } + + /* same logic as for objects */ + if (get_next) + { + _state->tresult = NULL; + _state->result_start = NULL; + + if (_state->normalize_results && + _state->lex->token_type == JSON_TOKEN_STRING) + { + _state->next_scalar = true; + } + else + { + _state->result_start = _state->lex->token_start; + } + } + + return JSON_SUCCESS; +} + +static JsonParseErrorType +get_array_element_end(void *state, bool isnull) +{ + GetState *_state = (GetState *) state; + bool get_last = false; + int lex_level = _state->lex->lex_level; + + /* same tests as in get_array_element_start */ + if (lex_level <= _state->npath && + _state->pathok[lex_level - 1] && + _state->path_indexes != NULL && + _state->array_cur_index[lex_level - 1] == _state->path_indexes[lex_level - 1]) + { + if (lex_level < _state->npath) + { + /* done with this element so reset pathok */ + _state->pathok[lex_level] = false; + } + else + { + /* end of path, so we want this value */ + get_last = true; + } + } + + /* same logic as for objects */ + if (get_last && _state->result_start != NULL) + { + if (isnull && _state->normalize_results) + _state->tresult = (text *) NULL; + else + { + char *start = _state->result_start; + int len = _state->lex->prev_token_terminator - start; + + _state->tresult = cstring_to_text_with_len(start, len); + } + + _state->result_start = NULL; + } + + return JSON_SUCCESS; +} + +static JsonParseErrorType +get_scalar(void *state, char *token, JsonTokenType tokentype) +{ + GetState *_state = (GetState *) state; + int lex_level = _state->lex->lex_level; + + /* Check for whole-object match */ + if (lex_level == 0 && _state->npath == 0) + { + if (_state->normalize_results && tokentype == JSON_TOKEN_STRING) + { + /* we want the de-escaped string */ + _state->next_scalar = true; + } + else if (_state->normalize_results && tokentype == JSON_TOKEN_NULL) + { + _state->tresult = (text *) NULL; + } + else + { + /* + * This is a bit hokey: we will suppress whitespace after the + * scalar token, but not whitespace before it. Probably not worth + * doing our own space-skipping to avoid that. + */ + char *start = _state->lex->input; + int len = _state->lex->prev_token_terminator - start; + + _state->tresult = cstring_to_text_with_len(start, len); + } + } + + if (_state->next_scalar) + { + /* a de-escaped text value is wanted, so supply it */ + _state->tresult = cstring_to_text(token); + /* make sure the next call to get_scalar doesn't overwrite it */ + _state->next_scalar = false; + } + + return JSON_SUCCESS; +} + +Datum +jsonb_extract_path(PG_FUNCTION_ARGS) +{ + return get_jsonb_path_all(fcinfo, false); +} + +Datum +jsonb_extract_path_text(PG_FUNCTION_ARGS) +{ + return get_jsonb_path_all(fcinfo, true); +} + +static Datum +get_jsonb_path_all(FunctionCallInfo fcinfo, bool as_text) +{ + Jsonb *jb = PG_GETARG_JSONB_P(0); + ArrayType *path = PG_GETARG_ARRAYTYPE_P(1); + Datum *pathtext; + bool *pathnulls; + bool isnull; + int npath; + Datum res; + + /* + * If the array contains any null elements, return NULL, on the grounds + * that you'd have gotten NULL if any RHS value were NULL in a nested + * series of applications of the -> operator. (Note: because we also + * return NULL for error cases such as no-such-field, this is true + * regardless of the contents of the rest of the array.) + */ + if (array_contains_nulls(path)) + PG_RETURN_NULL(); + + deconstruct_array_builtin(path, TEXTOID, &pathtext, &pathnulls, &npath); + + res = jsonb_get_element(jb, pathtext, npath, &isnull, as_text); + + if (isnull) + PG_RETURN_NULL(); + else + PG_RETURN_DATUM(res); +} + +Datum +jsonb_get_element(Jsonb *jb, Datum *path, int npath, bool *isnull, bool as_text) +{ + JsonbContainer *container = &jb->root; + JsonbValue *jbvp = NULL; + int i; + bool have_object = false, + have_array = false; + + *isnull = false; + + /* Identify whether we have object, array, or scalar at top-level */ + if (JB_ROOT_IS_OBJECT(jb)) + have_object = true; + else if (JB_ROOT_IS_ARRAY(jb) && !JB_ROOT_IS_SCALAR(jb)) + have_array = true; + else + { + Assert(JB_ROOT_IS_ARRAY(jb) && JB_ROOT_IS_SCALAR(jb)); + /* Extract the scalar value, if it is what we'll return */ + if (npath <= 0) + jbvp = getIthJsonbValueFromContainer(container, 0); + } + + /* + * If the array is empty, return the entire LHS object, on the grounds + * that we should do zero field or element extractions. For the + * non-scalar case we can just hand back the object without much work. For + * the scalar case, fall through and deal with the value below the loop. + * (This inconsistency arises because there's no easy way to generate a + * JsonbValue directly for root-level containers.) + */ + if (npath <= 0 && jbvp == NULL) + { + if (as_text) + { + return PointerGetDatum(cstring_to_text(JsonbToCString(NULL, + container, + VARSIZE(jb)))); + } + else + { + /* not text mode - just hand back the jsonb */ + PG_RETURN_JSONB_P(jb); + } + } + + for (i = 0; i < npath; i++) + { + if (have_object) + { + text *subscr = DatumGetTextPP(path[i]); + + jbvp = getKeyJsonValueFromContainer(container, + VARDATA_ANY(subscr), + VARSIZE_ANY_EXHDR(subscr), + NULL); + } + else if (have_array) + { + int lindex; + uint32 index; + char *indextext = TextDatumGetCString(path[i]); + char *endptr; + + errno = 0; + lindex = strtoint(indextext, &endptr, 10); + if (endptr == indextext || *endptr != '\0' || errno != 0) + { + *isnull = true; + return PointerGetDatum(NULL); + } + + if (lindex >= 0) + { + index = (uint32) lindex; + } + else + { + /* Handle negative subscript */ + uint32 nelements; + + /* Container must be array, but make sure */ + if (!JsonContainerIsArray(container)) + elog(ERROR, "not a jsonb array"); + + nelements = JsonContainerSize(container); + + if (lindex == INT_MIN || -lindex > nelements) + { + *isnull = true; + return PointerGetDatum(NULL); + } + else + index = nelements + lindex; + } + + jbvp = getIthJsonbValueFromContainer(container, index); + } + else + { + /* scalar, extraction yields a null */ + *isnull = true; + return PointerGetDatum(NULL); + } + + if (jbvp == NULL) + { + *isnull = true; + return PointerGetDatum(NULL); + } + else if (i == npath - 1) + break; + + if (jbvp->type == jbvBinary) + { + container = jbvp->val.binary.data; + have_object = JsonContainerIsObject(container); + have_array = JsonContainerIsArray(container); + Assert(!JsonContainerIsScalar(container)); + } + else + { + Assert(IsAJsonbScalar(jbvp)); + have_object = false; + have_array = false; + } + } + + if (as_text) + { + if (jbvp->type == jbvNull) + { + *isnull = true; + return PointerGetDatum(NULL); + } + + return PointerGetDatum(JsonbValueAsText(jbvp)); + } + else + { + Jsonb *res = JsonbValueToJsonb(jbvp); + + /* not text mode - just hand back the jsonb */ + PG_RETURN_JSONB_P(res); + } +} + +Datum +jsonb_set_element(Jsonb *jb, Datum *path, int path_len, + JsonbValue *newval) +{ + JsonbValue *res; + JsonbParseState *state = NULL; + JsonbIterator *it; + bool *path_nulls = palloc0(path_len * sizeof(bool)); + + if (newval->type == jbvArray && newval->val.array.rawScalar) + *newval = newval->val.array.elems[0]; + + it = JsonbIteratorInit(&jb->root); + + res = setPath(&it, path, path_nulls, path_len, &state, 0, newval, + JB_PATH_CREATE | JB_PATH_FILL_GAPS | + JB_PATH_CONSISTENT_POSITION); + + pfree(path_nulls); + + PG_RETURN_JSONB_P(JsonbValueToJsonb(res)); +} + +static void +push_null_elements(JsonbParseState **ps, int num) +{ + JsonbValue null; + + null.type = jbvNull; + + while (num-- > 0) + pushJsonbValue(ps, WJB_ELEM, &null); +} + +/* + * Prepare a new structure containing nested empty objects and arrays + * corresponding to the specified path, and assign a new value at the end of + * this path. E.g. the path [a][0][b] with the new value 1 will produce the + * structure {a: [{b: 1}]}. + * + * Caller is responsible to make sure such path does not exist yet. + */ +static void +push_path(JsonbParseState **st, int level, Datum *path_elems, + bool *path_nulls, int path_len, JsonbValue *newval) +{ + /* + * tpath contains expected type of an empty jsonb created at each level + * higher or equal than the current one, either jbvObject or jbvArray. + * Since it contains only information about path slice from level to the + * end, the access index must be normalized by level. + */ + enum jbvType *tpath = palloc0((path_len - level) * sizeof(enum jbvType)); + JsonbValue newkey; + + /* + * Create first part of the chain with beginning tokens. For the current + * level WJB_BEGIN_OBJECT/WJB_BEGIN_ARRAY was already created, so start + * with the next one. + */ + for (int i = level + 1; i < path_len; i++) + { + char *c, + *badp; + int lindex; + + if (path_nulls[i]) + break; + + /* + * Try to convert to an integer to find out the expected type, object + * or array. + */ + c = TextDatumGetCString(path_elems[i]); + errno = 0; + lindex = strtoint(c, &badp, 10); + if (badp == c || *badp != '\0' || errno != 0) + { + /* text, an object is expected */ + newkey.type = jbvString; + newkey.val.string.val = c; + newkey.val.string.len = strlen(c); + + (void) pushJsonbValue(st, WJB_BEGIN_OBJECT, NULL); + (void) pushJsonbValue(st, WJB_KEY, &newkey); + + tpath[i - level] = jbvObject; + } + else + { + /* integer, an array is expected */ + (void) pushJsonbValue(st, WJB_BEGIN_ARRAY, NULL); + + push_null_elements(st, lindex); + + tpath[i - level] = jbvArray; + } + } + + /* Insert an actual value for either an object or array */ + if (tpath[(path_len - level) - 1] == jbvArray) + { + (void) pushJsonbValue(st, WJB_ELEM, newval); + } + else + (void) pushJsonbValue(st, WJB_VALUE, newval); + + /* + * Close everything up to the last but one level. The last one will be + * closed outside of this function. + */ + for (int i = path_len - 1; i > level; i--) + { + if (path_nulls[i]) + break; + + if (tpath[i - level] == jbvObject) + (void) pushJsonbValue(st, WJB_END_OBJECT, NULL); + else + (void) pushJsonbValue(st, WJB_END_ARRAY, NULL); + } +} + +/* + * Return the text representation of the given JsonbValue. + */ +static text * +JsonbValueAsText(JsonbValue *v) +{ + switch (v->type) + { + case jbvNull: + return NULL; + + case jbvBool: + return v->val.boolean ? + cstring_to_text_with_len("true", 4) : + cstring_to_text_with_len("false", 5); + + case jbvString: + return cstring_to_text_with_len(v->val.string.val, + v->val.string.len); + + case jbvNumeric: + { + Datum cstr; + + cstr = DirectFunctionCall1(numeric_out, + PointerGetDatum(v->val.numeric)); + + return cstring_to_text(DatumGetCString(cstr)); + } + + case jbvBinary: + { + StringInfoData jtext; + + initStringInfo(&jtext); + (void) JsonbToCString(&jtext, v->val.binary.data, + v->val.binary.len); + + return cstring_to_text_with_len(jtext.data, jtext.len); + } + + default: + elog(ERROR, "unrecognized jsonb type: %d", (int) v->type); + return NULL; + } +} + +/* + * SQL function json_array_length(json) -> int + */ +Datum +json_array_length(PG_FUNCTION_ARGS) +{ + text *json = PG_GETARG_TEXT_PP(0); + AlenState *state; + JsonLexContext *lex; + JsonSemAction *sem; + + lex = makeJsonLexContext(json, false); + state = palloc0(sizeof(AlenState)); + sem = palloc0(sizeof(JsonSemAction)); + + /* palloc0 does this for us */ +#if 0 + state->count = 0; +#endif + state->lex = lex; + + sem->semstate = (void *) state; + sem->object_start = alen_object_start; + sem->scalar = alen_scalar; + sem->array_element_start = alen_array_element_start; + + pg_parse_json_or_ereport(lex, sem); + + PG_RETURN_INT32(state->count); +} + +Datum +jsonb_array_length(PG_FUNCTION_ARGS) +{ + Jsonb *jb = PG_GETARG_JSONB_P(0); + + if (JB_ROOT_IS_SCALAR(jb)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot get array length of a scalar"))); + else if (!JB_ROOT_IS_ARRAY(jb)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot get array length of a non-array"))); + + PG_RETURN_INT32(JB_ROOT_COUNT(jb)); +} + +/* + * These next two checks ensure that the json is an array (since it can't be + * a scalar or an object). + */ + +static JsonParseErrorType +alen_object_start(void *state) +{ + AlenState *_state = (AlenState *) state; + + /* json structure check */ + if (_state->lex->lex_level == 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot get array length of a non-array"))); + + return JSON_SUCCESS; +} + +static JsonParseErrorType +alen_scalar(void *state, char *token, JsonTokenType tokentype) +{ + AlenState *_state = (AlenState *) state; + + /* json structure check */ + if (_state->lex->lex_level == 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot get array length of a scalar"))); + + return JSON_SUCCESS; +} + +static JsonParseErrorType +alen_array_element_start(void *state, bool isnull) +{ + AlenState *_state = (AlenState *) state; + + /* just count up all the level 1 elements */ + if (_state->lex->lex_level == 1) + _state->count++; + + return JSON_SUCCESS; +} + +/* + * SQL function json_each and json_each_text + * + * decompose a json object into key value pairs. + * + * Unlike json_object_keys() these SRFs operate in materialize mode, + * stashing results into a Tuplestore object as they go. + * The construction of tuples is done using a temporary memory context + * that is cleared out after each tuple is built. + */ +Datum +json_each(PG_FUNCTION_ARGS) +{ + return each_worker(fcinfo, false); +} + +Datum +jsonb_each(PG_FUNCTION_ARGS) +{ + return each_worker_jsonb(fcinfo, "jsonb_each", false); +} + +Datum +json_each_text(PG_FUNCTION_ARGS) +{ + return each_worker(fcinfo, true); +} + +Datum +jsonb_each_text(PG_FUNCTION_ARGS) +{ + return each_worker_jsonb(fcinfo, "jsonb_each_text", true); +} + +static Datum +each_worker_jsonb(FunctionCallInfo fcinfo, const char *funcname, bool as_text) +{ + Jsonb *jb = PG_GETARG_JSONB_P(0); + ReturnSetInfo *rsi; + MemoryContext old_cxt, + tmp_cxt; + bool skipNested = false; + JsonbIterator *it; + JsonbValue v; + JsonbIteratorToken r; + + if (!JB_ROOT_IS_OBJECT(jb)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot call %s on a non-object", + funcname))); + + rsi = (ReturnSetInfo *) fcinfo->resultinfo; + InitMaterializedSRF(fcinfo, MAT_SRF_BLESS); + + tmp_cxt = AllocSetContextCreate(CurrentMemoryContext, + "jsonb_each temporary cxt", + ALLOCSET_DEFAULT_SIZES); + + it = JsonbIteratorInit(&jb->root); + + while ((r = JsonbIteratorNext(&it, &v, skipNested)) != WJB_DONE) + { + skipNested = true; + + if (r == WJB_KEY) + { + text *key; + Datum values[2]; + bool nulls[2] = {false, false}; + + /* Use the tmp context so we can clean up after each tuple is done */ + old_cxt = MemoryContextSwitchTo(tmp_cxt); + + key = cstring_to_text_with_len(v.val.string.val, v.val.string.len); + + /* + * The next thing the iterator fetches should be the value, no + * matter what shape it is. + */ + r = JsonbIteratorNext(&it, &v, skipNested); + Assert(r != WJB_DONE); + + values[0] = PointerGetDatum(key); + + if (as_text) + { + if (v.type == jbvNull) + { + /* a json null is an sql null in text mode */ + nulls[1] = true; + values[1] = (Datum) NULL; + } + else + values[1] = PointerGetDatum(JsonbValueAsText(&v)); + } + else + { + /* Not in text mode, just return the Jsonb */ + Jsonb *val = JsonbValueToJsonb(&v); + + values[1] = PointerGetDatum(val); + } + + tuplestore_putvalues(rsi->setResult, rsi->setDesc, values, nulls); + + /* clean up and switch back */ + MemoryContextSwitchTo(old_cxt); + MemoryContextReset(tmp_cxt); + } + } + + MemoryContextDelete(tmp_cxt); + + PG_RETURN_NULL(); +} + + +static Datum +each_worker(FunctionCallInfo fcinfo, bool as_text) +{ + text *json = PG_GETARG_TEXT_PP(0); + JsonLexContext *lex; + JsonSemAction *sem; + ReturnSetInfo *rsi; + EachState *state; + + lex = makeJsonLexContext(json, true); + state = palloc0(sizeof(EachState)); + sem = palloc0(sizeof(JsonSemAction)); + + rsi = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, MAT_SRF_BLESS); + state->tuple_store = rsi->setResult; + state->ret_tdesc = rsi->setDesc; + + sem->semstate = (void *) state; + sem->array_start = each_array_start; + sem->scalar = each_scalar; + sem->object_field_start = each_object_field_start; + sem->object_field_end = each_object_field_end; + + state->normalize_results = as_text; + state->next_scalar = false; + state->lex = lex; + state->tmp_cxt = AllocSetContextCreate(CurrentMemoryContext, + "json_each temporary cxt", + ALLOCSET_DEFAULT_SIZES); + + pg_parse_json_or_ereport(lex, sem); + + MemoryContextDelete(state->tmp_cxt); + + PG_RETURN_NULL(); +} + + +static JsonParseErrorType +each_object_field_start(void *state, char *fname, bool isnull) +{ + EachState *_state = (EachState *) state; + + /* save a pointer to where the value starts */ + if (_state->lex->lex_level == 1) + { + /* + * next_scalar will be reset in the object_field_end handler, and + * since we know the value is a scalar there is no danger of it being + * on while recursing down the tree. + */ + if (_state->normalize_results && _state->lex->token_type == JSON_TOKEN_STRING) + _state->next_scalar = true; + else + _state->result_start = _state->lex->token_start; + } + + return JSON_SUCCESS; +} + +static JsonParseErrorType +each_object_field_end(void *state, char *fname, bool isnull) +{ + EachState *_state = (EachState *) state; + MemoryContext old_cxt; + int len; + text *val; + HeapTuple tuple; + Datum values[2]; + bool nulls[2] = {false, false}; + + /* skip over nested objects */ + if (_state->lex->lex_level != 1) + return JSON_SUCCESS; + + /* use the tmp context so we can clean up after each tuple is done */ + old_cxt = MemoryContextSwitchTo(_state->tmp_cxt); + + values[0] = CStringGetTextDatum(fname); + + if (isnull && _state->normalize_results) + { + nulls[1] = true; + values[1] = (Datum) 0; + } + else if (_state->next_scalar) + { + values[1] = CStringGetTextDatum(_state->normalized_scalar); + _state->next_scalar = false; + } + else + { + len = _state->lex->prev_token_terminator - _state->result_start; + val = cstring_to_text_with_len(_state->result_start, len); + values[1] = PointerGetDatum(val); + } + + tuple = heap_form_tuple(_state->ret_tdesc, values, nulls); + + tuplestore_puttuple(_state->tuple_store, tuple); + + /* clean up and switch back */ + MemoryContextSwitchTo(old_cxt); + MemoryContextReset(_state->tmp_cxt); + + return JSON_SUCCESS; +} + +static JsonParseErrorType +each_array_start(void *state) +{ + EachState *_state = (EachState *) state; + + /* json structure check */ + if (_state->lex->lex_level == 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot deconstruct an array as an object"))); + + return JSON_SUCCESS; +} + +static JsonParseErrorType +each_scalar(void *state, char *token, JsonTokenType tokentype) +{ + EachState *_state = (EachState *) state; + + /* json structure check */ + if (_state->lex->lex_level == 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot deconstruct a scalar"))); + + /* supply de-escaped value if required */ + if (_state->next_scalar) + _state->normalized_scalar = token; + + return JSON_SUCCESS; +} + +/* + * SQL functions json_array_elements and json_array_elements_text + * + * get the elements from a json array + * + * a lot of this processing is similar to the json_each* functions + */ + +Datum +jsonb_array_elements(PG_FUNCTION_ARGS) +{ + return elements_worker_jsonb(fcinfo, "jsonb_array_elements", false); +} + +Datum +jsonb_array_elements_text(PG_FUNCTION_ARGS) +{ + return elements_worker_jsonb(fcinfo, "jsonb_array_elements_text", true); +} + +static Datum +elements_worker_jsonb(FunctionCallInfo fcinfo, const char *funcname, + bool as_text) +{ + Jsonb *jb = PG_GETARG_JSONB_P(0); + ReturnSetInfo *rsi; + MemoryContext old_cxt, + tmp_cxt; + bool skipNested = false; + JsonbIterator *it; + JsonbValue v; + JsonbIteratorToken r; + + if (JB_ROOT_IS_SCALAR(jb)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot extract elements from a scalar"))); + else if (!JB_ROOT_IS_ARRAY(jb)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot extract elements from an object"))); + + rsi = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, MAT_SRF_USE_EXPECTED_DESC | MAT_SRF_BLESS); + + tmp_cxt = AllocSetContextCreate(CurrentMemoryContext, + "jsonb_array_elements temporary cxt", + ALLOCSET_DEFAULT_SIZES); + + it = JsonbIteratorInit(&jb->root); + + while ((r = JsonbIteratorNext(&it, &v, skipNested)) != WJB_DONE) + { + skipNested = true; + + if (r == WJB_ELEM) + { + Datum values[1]; + bool nulls[1] = {false}; + + /* use the tmp context so we can clean up after each tuple is done */ + old_cxt = MemoryContextSwitchTo(tmp_cxt); + + if (as_text) + { + if (v.type == jbvNull) + { + /* a json null is an sql null in text mode */ + nulls[0] = true; + values[0] = (Datum) NULL; + } + else + values[0] = PointerGetDatum(JsonbValueAsText(&v)); + } + else + { + /* Not in text mode, just return the Jsonb */ + Jsonb *val = JsonbValueToJsonb(&v); + + values[0] = PointerGetDatum(val); + } + + tuplestore_putvalues(rsi->setResult, rsi->setDesc, values, nulls); + + /* clean up and switch back */ + MemoryContextSwitchTo(old_cxt); + MemoryContextReset(tmp_cxt); + } + } + + MemoryContextDelete(tmp_cxt); + + PG_RETURN_NULL(); +} + +Datum +json_array_elements(PG_FUNCTION_ARGS) +{ + return elements_worker(fcinfo, "json_array_elements", false); +} + +Datum +json_array_elements_text(PG_FUNCTION_ARGS) +{ + return elements_worker(fcinfo, "json_array_elements_text", true); +} + +static Datum +elements_worker(FunctionCallInfo fcinfo, const char *funcname, bool as_text) +{ + text *json = PG_GETARG_TEXT_PP(0); + + /* elements only needs escaped strings when as_text */ + JsonLexContext *lex = makeJsonLexContext(json, as_text); + JsonSemAction *sem; + ReturnSetInfo *rsi; + ElementsState *state; + + state = palloc0(sizeof(ElementsState)); + sem = palloc0(sizeof(JsonSemAction)); + + InitMaterializedSRF(fcinfo, MAT_SRF_USE_EXPECTED_DESC | MAT_SRF_BLESS); + rsi = (ReturnSetInfo *) fcinfo->resultinfo; + state->tuple_store = rsi->setResult; + state->ret_tdesc = rsi->setDesc; + + sem->semstate = (void *) state; + sem->object_start = elements_object_start; + sem->scalar = elements_scalar; + sem->array_element_start = elements_array_element_start; + sem->array_element_end = elements_array_element_end; + + state->function_name = funcname; + state->normalize_results = as_text; + state->next_scalar = false; + state->lex = lex; + state->tmp_cxt = AllocSetContextCreate(CurrentMemoryContext, + "json_array_elements temporary cxt", + ALLOCSET_DEFAULT_SIZES); + + pg_parse_json_or_ereport(lex, sem); + + MemoryContextDelete(state->tmp_cxt); + + PG_RETURN_NULL(); +} + +static JsonParseErrorType +elements_array_element_start(void *state, bool isnull) +{ + ElementsState *_state = (ElementsState *) state; + + /* save a pointer to where the value starts */ + if (_state->lex->lex_level == 1) + { + /* + * next_scalar will be reset in the array_element_end handler, and + * since we know the value is a scalar there is no danger of it being + * on while recursing down the tree. + */ + if (_state->normalize_results && _state->lex->token_type == JSON_TOKEN_STRING) + _state->next_scalar = true; + else + _state->result_start = _state->lex->token_start; + } + + return JSON_SUCCESS; +} + +static JsonParseErrorType +elements_array_element_end(void *state, bool isnull) +{ + ElementsState *_state = (ElementsState *) state; + MemoryContext old_cxt; + int len; + text *val; + HeapTuple tuple; + Datum values[1]; + bool nulls[1] = {false}; + + /* skip over nested objects */ + if (_state->lex->lex_level != 1) + return JSON_SUCCESS; + + /* use the tmp context so we can clean up after each tuple is done */ + old_cxt = MemoryContextSwitchTo(_state->tmp_cxt); + + if (isnull && _state->normalize_results) + { + nulls[0] = true; + values[0] = (Datum) NULL; + } + else if (_state->next_scalar) + { + values[0] = CStringGetTextDatum(_state->normalized_scalar); + _state->next_scalar = false; + } + else + { + len = _state->lex->prev_token_terminator - _state->result_start; + val = cstring_to_text_with_len(_state->result_start, len); + values[0] = PointerGetDatum(val); + } + + tuple = heap_form_tuple(_state->ret_tdesc, values, nulls); + + tuplestore_puttuple(_state->tuple_store, tuple); + + /* clean up and switch back */ + MemoryContextSwitchTo(old_cxt); + MemoryContextReset(_state->tmp_cxt); + + return JSON_SUCCESS; +} + +static JsonParseErrorType +elements_object_start(void *state) +{ + ElementsState *_state = (ElementsState *) state; + + /* json structure check */ + if (_state->lex->lex_level == 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot call %s on a non-array", + _state->function_name))); + + return JSON_SUCCESS; +} + +static JsonParseErrorType +elements_scalar(void *state, char *token, JsonTokenType tokentype) +{ + ElementsState *_state = (ElementsState *) state; + + /* json structure check */ + if (_state->lex->lex_level == 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot call %s on a scalar", + _state->function_name))); + + /* supply de-escaped value if required */ + if (_state->next_scalar) + _state->normalized_scalar = token; + + return JSON_SUCCESS; +} + +/* + * SQL function json_populate_record + * + * set fields in a record from the argument json + * + * Code adapted shamelessly from hstore's populate_record + * which is in turn partly adapted from record_out. + * + * The json is decomposed into a hash table, in which each + * field in the record is then looked up by name. For jsonb + * we fetch the values direct from the object. + */ +Datum +jsonb_populate_record(PG_FUNCTION_ARGS) +{ + return populate_record_worker(fcinfo, "jsonb_populate_record", + false, true); +} + +Datum +jsonb_to_record(PG_FUNCTION_ARGS) +{ + return populate_record_worker(fcinfo, "jsonb_to_record", + false, false); +} + +Datum +json_populate_record(PG_FUNCTION_ARGS) +{ + return populate_record_worker(fcinfo, "json_populate_record", + true, true); +} + +Datum +json_to_record(PG_FUNCTION_ARGS) +{ + return populate_record_worker(fcinfo, "json_to_record", + true, false); +} + +/* helper function for diagnostics */ +static void +populate_array_report_expected_array(PopulateArrayContext *ctx, int ndim) +{ + if (ndim <= 0) + { + if (ctx->colname) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("expected JSON array"), + errhint("See the value of key \"%s\".", ctx->colname))); + else + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("expected JSON array"))); + } + else + { + StringInfoData indices; + int i; + + initStringInfo(&indices); + + Assert(ctx->ndims > 0 && ndim < ctx->ndims); + + for (i = 0; i < ndim; i++) + appendStringInfo(&indices, "[%d]", ctx->sizes[i]); + + if (ctx->colname) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("expected JSON array"), + errhint("See the array element %s of key \"%s\".", + indices.data, ctx->colname))); + else + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("expected JSON array"), + errhint("See the array element %s.", + indices.data))); + } +} + +/* set the number of dimensions of the populated array when it becomes known */ +static void +populate_array_assign_ndims(PopulateArrayContext *ctx, int ndims) +{ + int i; + + Assert(ctx->ndims <= 0); + + if (ndims <= 0) + populate_array_report_expected_array(ctx, ndims); + + ctx->ndims = ndims; + ctx->dims = palloc(sizeof(int) * ndims); + ctx->sizes = palloc0(sizeof(int) * ndims); + + for (i = 0; i < ndims; i++) + ctx->dims[i] = -1; /* dimensions are unknown yet */ +} + +/* check the populated subarray dimension */ +static void +populate_array_check_dimension(PopulateArrayContext *ctx, int ndim) +{ + int dim = ctx->sizes[ndim]; /* current dimension counter */ + + if (ctx->dims[ndim] == -1) + ctx->dims[ndim] = dim; /* assign dimension if not yet known */ + else if (ctx->dims[ndim] != dim) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed JSON array"), + errdetail("Multidimensional arrays must have " + "sub-arrays with matching dimensions."))); + + /* reset the current array dimension size counter */ + ctx->sizes[ndim] = 0; + + /* increment the parent dimension counter if it is a nested sub-array */ + if (ndim > 0) + ctx->sizes[ndim - 1]++; +} + +static void +populate_array_element(PopulateArrayContext *ctx, int ndim, JsValue *jsv) +{ + Datum element; + bool element_isnull; + + /* populate the array element */ + element = populate_record_field(ctx->aio->element_info, + ctx->aio->element_type, + ctx->aio->element_typmod, + NULL, ctx->mcxt, PointerGetDatum(NULL), + jsv, &element_isnull); + + accumArrayResult(ctx->astate, element, element_isnull, + ctx->aio->element_type, ctx->acxt); + + Assert(ndim > 0); + ctx->sizes[ndim - 1]++; /* increment current dimension counter */ +} + +/* json object start handler for populate_array_json() */ +static JsonParseErrorType +populate_array_object_start(void *_state) +{ + PopulateArrayState *state = (PopulateArrayState *) _state; + int ndim = state->lex->lex_level; + + if (state->ctx->ndims <= 0) + populate_array_assign_ndims(state->ctx, ndim); + else if (ndim < state->ctx->ndims) + populate_array_report_expected_array(state->ctx, ndim); + + return JSON_SUCCESS; +} + +/* json array end handler for populate_array_json() */ +static JsonParseErrorType +populate_array_array_end(void *_state) +{ + PopulateArrayState *state = (PopulateArrayState *) _state; + PopulateArrayContext *ctx = state->ctx; + int ndim = state->lex->lex_level; + + if (ctx->ndims <= 0) + populate_array_assign_ndims(ctx, ndim + 1); + + if (ndim < ctx->ndims) + populate_array_check_dimension(ctx, ndim); + + return JSON_SUCCESS; +} + +/* json array element start handler for populate_array_json() */ +static JsonParseErrorType +populate_array_element_start(void *_state, bool isnull) +{ + PopulateArrayState *state = (PopulateArrayState *) _state; + int ndim = state->lex->lex_level; + + if (state->ctx->ndims <= 0 || ndim == state->ctx->ndims) + { + /* remember current array element start */ + state->element_start = state->lex->token_start; + state->element_type = state->lex->token_type; + state->element_scalar = NULL; + } + + return JSON_SUCCESS; +} + +/* json array element end handler for populate_array_json() */ +static JsonParseErrorType +populate_array_element_end(void *_state, bool isnull) +{ + PopulateArrayState *state = (PopulateArrayState *) _state; + PopulateArrayContext *ctx = state->ctx; + int ndim = state->lex->lex_level; + + Assert(ctx->ndims > 0); + + if (ndim == ctx->ndims) + { + JsValue jsv; + + jsv.is_json = true; + jsv.val.json.type = state->element_type; + + if (isnull) + { + Assert(jsv.val.json.type == JSON_TOKEN_NULL); + jsv.val.json.str = NULL; + jsv.val.json.len = 0; + } + else if (state->element_scalar) + { + jsv.val.json.str = state->element_scalar; + jsv.val.json.len = -1; /* null-terminated */ + } + else + { + jsv.val.json.str = state->element_start; + jsv.val.json.len = (state->lex->prev_token_terminator - + state->element_start) * sizeof(char); + } + + populate_array_element(ctx, ndim, &jsv); + } + + return JSON_SUCCESS; +} + +/* json scalar handler for populate_array_json() */ +static JsonParseErrorType +populate_array_scalar(void *_state, char *token, JsonTokenType tokentype) +{ + PopulateArrayState *state = (PopulateArrayState *) _state; + PopulateArrayContext *ctx = state->ctx; + int ndim = state->lex->lex_level; + + if (ctx->ndims <= 0) + populate_array_assign_ndims(ctx, ndim); + else if (ndim < ctx->ndims) + populate_array_report_expected_array(ctx, ndim); + + if (ndim == ctx->ndims) + { + /* remember the scalar element token */ + state->element_scalar = token; + /* element_type must already be set in populate_array_element_start() */ + Assert(state->element_type == tokentype); + } + + return JSON_SUCCESS; +} + +/* parse a json array and populate array */ +static void +populate_array_json(PopulateArrayContext *ctx, char *json, int len) +{ + PopulateArrayState state; + JsonSemAction sem; + + state.lex = makeJsonLexContextCstringLen(json, len, GetDatabaseEncoding(), true); + state.ctx = ctx; + + memset(&sem, 0, sizeof(sem)); + sem.semstate = (void *) &state; + sem.object_start = populate_array_object_start; + sem.array_end = populate_array_array_end; + sem.array_element_start = populate_array_element_start; + sem.array_element_end = populate_array_element_end; + sem.scalar = populate_array_scalar; + + pg_parse_json_or_ereport(state.lex, &sem); + + /* number of dimensions should be already known */ + Assert(ctx->ndims > 0 && ctx->dims); + + pfree(state.lex); +} + +/* + * populate_array_dim_jsonb() -- Iterate recursively through jsonb sub-array + * elements and accumulate result using given ArrayBuildState. + */ +static void +populate_array_dim_jsonb(PopulateArrayContext *ctx, /* context */ + JsonbValue *jbv, /* jsonb sub-array */ + int ndim) /* current dimension */ +{ + JsonbContainer *jbc = jbv->val.binary.data; + JsonbIterator *it; + JsonbIteratorToken tok; + JsonbValue val; + JsValue jsv; + + check_stack_depth(); + + if (jbv->type != jbvBinary || !JsonContainerIsArray(jbc)) + populate_array_report_expected_array(ctx, ndim - 1); + + Assert(!JsonContainerIsScalar(jbc)); + + it = JsonbIteratorInit(jbc); + + tok = JsonbIteratorNext(&it, &val, true); + Assert(tok == WJB_BEGIN_ARRAY); + + tok = JsonbIteratorNext(&it, &val, true); + + /* + * If the number of dimensions is not yet known and we have found end of + * the array, or the first child element is not an array, then assign the + * number of dimensions now. + */ + if (ctx->ndims <= 0 && + (tok == WJB_END_ARRAY || + (tok == WJB_ELEM && + (val.type != jbvBinary || + !JsonContainerIsArray(val.val.binary.data))))) + populate_array_assign_ndims(ctx, ndim); + + jsv.is_json = false; + jsv.val.jsonb = &val; + + /* process all the array elements */ + while (tok == WJB_ELEM) + { + /* + * Recurse only if the dimensions of dimensions is still unknown or if + * it is not the innermost dimension. + */ + if (ctx->ndims > 0 && ndim >= ctx->ndims) + populate_array_element(ctx, ndim, &jsv); + else + { + /* populate child sub-array */ + populate_array_dim_jsonb(ctx, &val, ndim + 1); + + /* number of dimensions should be already known */ + Assert(ctx->ndims > 0 && ctx->dims); + + populate_array_check_dimension(ctx, ndim); + } + + tok = JsonbIteratorNext(&it, &val, true); + } + + Assert(tok == WJB_END_ARRAY); + + /* free iterator, iterating until WJB_DONE */ + tok = JsonbIteratorNext(&it, &val, true); + Assert(tok == WJB_DONE && !it); +} + +/* recursively populate an array from json/jsonb */ +static Datum +populate_array(ArrayIOData *aio, + const char *colname, + MemoryContext mcxt, + JsValue *jsv) +{ + PopulateArrayContext ctx; + Datum result; + int *lbs; + int i; + + ctx.aio = aio; + ctx.mcxt = mcxt; + ctx.acxt = CurrentMemoryContext; + ctx.astate = initArrayResult(aio->element_type, ctx.acxt, true); + ctx.colname = colname; + ctx.ndims = 0; /* unknown yet */ + ctx.dims = NULL; + ctx.sizes = NULL; + + if (jsv->is_json) + populate_array_json(&ctx, jsv->val.json.str, + jsv->val.json.len >= 0 ? jsv->val.json.len + : strlen(jsv->val.json.str)); + else + { + populate_array_dim_jsonb(&ctx, jsv->val.jsonb, 1); + ctx.dims[0] = ctx.sizes[0]; + } + + Assert(ctx.ndims > 0); + + lbs = palloc(sizeof(int) * ctx.ndims); + + for (i = 0; i < ctx.ndims; i++) + lbs[i] = 1; + + result = makeMdArrayResult(ctx.astate, ctx.ndims, ctx.dims, lbs, + ctx.acxt, true); + + pfree(ctx.dims); + pfree(ctx.sizes); + pfree(lbs); + + return result; +} + +static void +JsValueToJsObject(JsValue *jsv, JsObject *jso) +{ + jso->is_json = jsv->is_json; + + if (jsv->is_json) + { + /* convert plain-text json into a hash table */ + jso->val.json_hash = + get_json_object_as_hash(jsv->val.json.str, + jsv->val.json.len >= 0 + ? jsv->val.json.len + : strlen(jsv->val.json.str), + "populate_composite"); + } + else + { + JsonbValue *jbv = jsv->val.jsonb; + + if (jbv->type == jbvBinary && + JsonContainerIsObject(jbv->val.binary.data)) + { + jso->val.jsonb_cont = jbv->val.binary.data; + } + else + { + bool is_scalar; + + is_scalar = IsAJsonbScalar(jbv) || + (jbv->type == jbvBinary && + JsonContainerIsScalar(jbv->val.binary.data)); + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + is_scalar + ? errmsg("cannot call %s on a scalar", + "populate_composite") + : errmsg("cannot call %s on an array", + "populate_composite"))); + } + } +} + +/* acquire or update cached tuple descriptor for a composite type */ +static void +update_cached_tupdesc(CompositeIOData *io, MemoryContext mcxt) +{ + if (!io->tupdesc || + io->tupdesc->tdtypeid != io->base_typid || + io->tupdesc->tdtypmod != io->base_typmod) + { + TupleDesc tupdesc = lookup_rowtype_tupdesc(io->base_typid, + io->base_typmod); + MemoryContext oldcxt; + + if (io->tupdesc) + FreeTupleDesc(io->tupdesc); + + /* copy tuple desc without constraints into cache memory context */ + oldcxt = MemoryContextSwitchTo(mcxt); + io->tupdesc = CreateTupleDescCopy(tupdesc); + MemoryContextSwitchTo(oldcxt); + + ReleaseTupleDesc(tupdesc); + } +} + +/* recursively populate a composite (row type) value from json/jsonb */ +static Datum +populate_composite(CompositeIOData *io, + Oid typid, + const char *colname, + MemoryContext mcxt, + HeapTupleHeader defaultval, + JsValue *jsv, + bool isnull) +{ + Datum result; + + /* acquire/update cached tuple descriptor */ + update_cached_tupdesc(io, mcxt); + + if (isnull) + result = (Datum) 0; + else + { + HeapTupleHeader tuple; + JsObject jso; + + /* prepare input value */ + JsValueToJsObject(jsv, &jso); + + /* populate resulting record tuple */ + tuple = populate_record(io->tupdesc, &io->record_io, + defaultval, mcxt, &jso); + result = HeapTupleHeaderGetDatum(tuple); + + JsObjectFree(&jso); + } + + /* + * If it's domain over composite, check domain constraints. (This should + * probably get refactored so that we can see the TYPECAT value, but for + * now, we can tell by comparing typid to base_typid.) + */ + if (typid != io->base_typid && typid != RECORDOID) + domain_check(result, isnull, typid, &io->domain_info, mcxt); + + return result; +} + +/* populate non-null scalar value from json/jsonb value */ +static Datum +populate_scalar(ScalarIOData *io, Oid typid, int32 typmod, JsValue *jsv) +{ + Datum res; + char *str = NULL; + char *json = NULL; + + if (jsv->is_json) + { + int len = jsv->val.json.len; + + json = jsv->val.json.str; + Assert(json); + if (len >= 0) + { + /* Need to copy non-null-terminated string */ + str = palloc(len + 1 * sizeof(char)); + memcpy(str, json, len); + str[len] = '\0'; + } + else + str = json; /* string is already null-terminated */ + + /* If converting to json/jsonb, make string into valid JSON literal */ + if ((typid == JSONOID || typid == JSONBOID) && + jsv->val.json.type == JSON_TOKEN_STRING) + { + StringInfoData buf; + + initStringInfo(&buf); + escape_json(&buf, str); + /* free temporary buffer */ + if (str != json) + pfree(str); + str = buf.data; + } + } + else + { + JsonbValue *jbv = jsv->val.jsonb; + + if (typid == JSONBOID) + { + Jsonb *jsonb = JsonbValueToJsonb(jbv); /* directly use jsonb */ + + return JsonbPGetDatum(jsonb); + } + /* convert jsonb to string for typio call */ + else if (typid == JSONOID && jbv->type != jbvBinary) + { + /* + * Convert scalar jsonb (non-scalars are passed here as jbvBinary) + * to json string, preserving quotes around top-level strings. + */ + Jsonb *jsonb = JsonbValueToJsonb(jbv); + + str = JsonbToCString(NULL, &jsonb->root, VARSIZE(jsonb)); + } + else if (jbv->type == jbvString) /* quotes are stripped */ + str = pnstrdup(jbv->val.string.val, jbv->val.string.len); + else if (jbv->type == jbvBool) + str = pstrdup(jbv->val.boolean ? "true" : "false"); + else if (jbv->type == jbvNumeric) + str = DatumGetCString(DirectFunctionCall1(numeric_out, + PointerGetDatum(jbv->val.numeric))); + else if (jbv->type == jbvBinary) + str = JsonbToCString(NULL, jbv->val.binary.data, + jbv->val.binary.len); + else + elog(ERROR, "unrecognized jsonb type: %d", (int) jbv->type); + } + + res = InputFunctionCall(&io->typiofunc, str, io->typioparam, typmod); + + /* free temporary buffer */ + if (str != json) + pfree(str); + + return res; +} + +static Datum +populate_domain(DomainIOData *io, + Oid typid, + const char *colname, + MemoryContext mcxt, + JsValue *jsv, + bool isnull) +{ + Datum res; + + if (isnull) + res = (Datum) 0; + else + { + res = populate_record_field(io->base_io, + io->base_typid, io->base_typmod, + colname, mcxt, PointerGetDatum(NULL), + jsv, &isnull); + Assert(!isnull); + } + + domain_check(res, isnull, typid, &io->domain_info, mcxt); + + return res; +} + +/* prepare column metadata cache for the given type */ +static void +prepare_column_cache(ColumnIOData *column, + Oid typid, + int32 typmod, + MemoryContext mcxt, + bool need_scalar) +{ + HeapTuple tup; + Form_pg_type type; + + column->typid = typid; + column->typmod = typmod; + + tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid)); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for type %u", typid); + + type = (Form_pg_type) GETSTRUCT(tup); + + if (type->typtype == TYPTYPE_DOMAIN) + { + /* + * We can move directly to the bottom base type; domain_check() will + * take care of checking all constraints for a stack of domains. + */ + Oid base_typid; + int32 base_typmod = typmod; + + base_typid = getBaseTypeAndTypmod(typid, &base_typmod); + if (get_typtype(base_typid) == TYPTYPE_COMPOSITE) + { + /* domain over composite has its own code path */ + column->typcat = TYPECAT_COMPOSITE_DOMAIN; + column->io.composite.record_io = NULL; + column->io.composite.tupdesc = NULL; + column->io.composite.base_typid = base_typid; + column->io.composite.base_typmod = base_typmod; + column->io.composite.domain_info = NULL; + } + else + { + /* domain over anything else */ + column->typcat = TYPECAT_DOMAIN; + column->io.domain.base_typid = base_typid; + column->io.domain.base_typmod = base_typmod; + column->io.domain.base_io = + MemoryContextAllocZero(mcxt, sizeof(ColumnIOData)); + column->io.domain.domain_info = NULL; + } + } + else if (type->typtype == TYPTYPE_COMPOSITE || typid == RECORDOID) + { + column->typcat = TYPECAT_COMPOSITE; + column->io.composite.record_io = NULL; + column->io.composite.tupdesc = NULL; + column->io.composite.base_typid = typid; + column->io.composite.base_typmod = typmod; + column->io.composite.domain_info = NULL; + } + else if (IsTrueArrayType(type)) + { + column->typcat = TYPECAT_ARRAY; + column->io.array.element_info = MemoryContextAllocZero(mcxt, + sizeof(ColumnIOData)); + column->io.array.element_type = type->typelem; + /* array element typemod stored in attribute's typmod */ + column->io.array.element_typmod = typmod; + } + else + { + column->typcat = TYPECAT_SCALAR; + need_scalar = true; + } + + /* caller can force us to look up scalar_io info even for non-scalars */ + if (need_scalar) + { + Oid typioproc; + + getTypeInputInfo(typid, &typioproc, &column->scalar_io.typioparam); + fmgr_info_cxt(typioproc, &column->scalar_io.typiofunc, mcxt); + } + + ReleaseSysCache(tup); +} + +/* recursively populate a record field or an array element from a json/jsonb value */ +static Datum +populate_record_field(ColumnIOData *col, + Oid typid, + int32 typmod, + const char *colname, + MemoryContext mcxt, + Datum defaultval, + JsValue *jsv, + bool *isnull) +{ + TypeCat typcat; + + check_stack_depth(); + + /* + * Prepare column metadata cache for the given type. Force lookup of the + * scalar_io data so that the json string hack below will work. + */ + if (col->typid != typid || col->typmod != typmod) + prepare_column_cache(col, typid, typmod, mcxt, true); + + *isnull = JsValueIsNull(jsv); + + typcat = col->typcat; + + /* try to convert json string to a non-scalar type through input function */ + if (JsValueIsString(jsv) && + (typcat == TYPECAT_ARRAY || + typcat == TYPECAT_COMPOSITE || + typcat == TYPECAT_COMPOSITE_DOMAIN)) + typcat = TYPECAT_SCALAR; + + /* we must perform domain checks for NULLs, otherwise exit immediately */ + if (*isnull && + typcat != TYPECAT_DOMAIN && + typcat != TYPECAT_COMPOSITE_DOMAIN) + return (Datum) 0; + + switch (typcat) + { + case TYPECAT_SCALAR: + return populate_scalar(&col->scalar_io, typid, typmod, jsv); + + case TYPECAT_ARRAY: + return populate_array(&col->io.array, colname, mcxt, jsv); + + case TYPECAT_COMPOSITE: + case TYPECAT_COMPOSITE_DOMAIN: + return populate_composite(&col->io.composite, typid, + colname, mcxt, + DatumGetPointer(defaultval) + ? DatumGetHeapTupleHeader(defaultval) + : NULL, + jsv, *isnull); + + case TYPECAT_DOMAIN: + return populate_domain(&col->io.domain, typid, colname, mcxt, + jsv, *isnull); + + default: + elog(ERROR, "unrecognized type category '%c'", typcat); + return (Datum) 0; + } +} + +static RecordIOData * +allocate_record_info(MemoryContext mcxt, int ncolumns) +{ + RecordIOData *data = (RecordIOData *) + MemoryContextAlloc(mcxt, + offsetof(RecordIOData, columns) + + ncolumns * sizeof(ColumnIOData)); + + data->record_type = InvalidOid; + data->record_typmod = 0; + data->ncolumns = ncolumns; + MemSet(data->columns, 0, sizeof(ColumnIOData) * ncolumns); + + return data; +} + +static bool +JsObjectGetField(JsObject *obj, char *field, JsValue *jsv) +{ + jsv->is_json = obj->is_json; + + if (jsv->is_json) + { + JsonHashEntry *hashentry = hash_search(obj->val.json_hash, field, + HASH_FIND, NULL); + + jsv->val.json.type = hashentry ? hashentry->type : JSON_TOKEN_NULL; + jsv->val.json.str = jsv->val.json.type == JSON_TOKEN_NULL ? NULL : + hashentry->val; + jsv->val.json.len = jsv->val.json.str ? -1 : 0; /* null-terminated */ + + return hashentry != NULL; + } + else + { + jsv->val.jsonb = !obj->val.jsonb_cont ? NULL : + getKeyJsonValueFromContainer(obj->val.jsonb_cont, field, strlen(field), + NULL); + + return jsv->val.jsonb != NULL; + } +} + +/* populate a record tuple from json/jsonb value */ +static HeapTupleHeader +populate_record(TupleDesc tupdesc, + RecordIOData **record_p, + HeapTupleHeader defaultval, + MemoryContext mcxt, + JsObject *obj) +{ + RecordIOData *record = *record_p; + Datum *values; + bool *nulls; + HeapTuple res; + int ncolumns = tupdesc->natts; + int i; + + /* + * if the input json is empty, we can only skip the rest if we were passed + * in a non-null record, since otherwise there may be issues with domain + * nulls. + */ + if (defaultval && JsObjectIsEmpty(obj)) + return defaultval; + + /* (re)allocate metadata cache */ + if (record == NULL || + record->ncolumns != ncolumns) + *record_p = record = allocate_record_info(mcxt, ncolumns); + + /* invalidate metadata cache if the record type has changed */ + if (record->record_type != tupdesc->tdtypeid || + record->record_typmod != tupdesc->tdtypmod) + { + MemSet(record, 0, offsetof(RecordIOData, columns) + + ncolumns * sizeof(ColumnIOData)); + record->record_type = tupdesc->tdtypeid; + record->record_typmod = tupdesc->tdtypmod; + record->ncolumns = ncolumns; + } + + values = (Datum *) palloc(ncolumns * sizeof(Datum)); + nulls = (bool *) palloc(ncolumns * sizeof(bool)); + + if (defaultval) + { + HeapTupleData tuple; + + /* Build a temporary HeapTuple control structure */ + tuple.t_len = HeapTupleHeaderGetDatumLength(defaultval); + ItemPointerSetInvalid(&(tuple.t_self)); + tuple.t_tableOid = InvalidOid; + tuple.t_data = defaultval; + + /* Break down the tuple into fields */ + heap_deform_tuple(&tuple, tupdesc, values, nulls); + } + else + { + for (i = 0; i < ncolumns; ++i) + { + values[i] = (Datum) 0; + nulls[i] = true; + } + } + + for (i = 0; i < ncolumns; ++i) + { + Form_pg_attribute att = TupleDescAttr(tupdesc, i); + char *colname = NameStr(att->attname); + JsValue field = {0}; + bool found; + + /* Ignore dropped columns in datatype */ + if (att->attisdropped) + { + nulls[i] = true; + continue; + } + + found = JsObjectGetField(obj, colname, &field); + + /* + * we can't just skip here if the key wasn't found since we might have + * a domain to deal with. If we were passed in a non-null record + * datum, we assume that the existing values are valid (if they're + * not, then it's not our fault), but if we were passed in a null, + * then every field which we don't populate needs to be run through + * the input function just in case it's a domain type. + */ + if (defaultval && !found) + continue; + + values[i] = populate_record_field(&record->columns[i], + att->atttypid, + att->atttypmod, + colname, + mcxt, + nulls[i] ? (Datum) 0 : values[i], + &field, + &nulls[i]); + } + + res = heap_form_tuple(tupdesc, values, nulls); + + pfree(values); + pfree(nulls); + + return res->t_data; +} + +/* + * Setup for json{b}_populate_record{set}: result type will be same as first + * argument's type --- unless first argument is "null::record", which we can't + * extract type info from; we handle that later. + */ +static void +get_record_type_from_argument(FunctionCallInfo fcinfo, + const char *funcname, + PopulateRecordCache *cache) +{ + cache->argtype = get_fn_expr_argtype(fcinfo->flinfo, 0); + prepare_column_cache(&cache->c, + cache->argtype, -1, + cache->fn_mcxt, false); + if (cache->c.typcat != TYPECAT_COMPOSITE && + cache->c.typcat != TYPECAT_COMPOSITE_DOMAIN) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + /* translator: %s is a function name, eg json_to_record */ + errmsg("first argument of %s must be a row type", + funcname))); +} + +/* + * Setup for json{b}_to_record{set}: result type is specified by calling + * query. We'll also use this code for json{b}_populate_record{set}, + * if we discover that the first argument is a null of type RECORD. + * + * Here it is syntactically impossible to specify the target type + * as domain-over-composite. + */ +static void +get_record_type_from_query(FunctionCallInfo fcinfo, + const char *funcname, + PopulateRecordCache *cache) +{ + TupleDesc tupdesc; + MemoryContext old_cxt; + + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + /* translator: %s is a function name, eg json_to_record */ + errmsg("could not determine row type for result of %s", + funcname), + errhint("Provide a non-null record argument, " + "or call the function in the FROM clause " + "using a column definition list."))); + + Assert(tupdesc); + cache->argtype = tupdesc->tdtypeid; + + /* If we go through this more than once, avoid memory leak */ + if (cache->c.io.composite.tupdesc) + FreeTupleDesc(cache->c.io.composite.tupdesc); + + /* Save identified tupdesc */ + old_cxt = MemoryContextSwitchTo(cache->fn_mcxt); + cache->c.io.composite.tupdesc = CreateTupleDescCopy(tupdesc); + cache->c.io.composite.base_typid = tupdesc->tdtypeid; + cache->c.io.composite.base_typmod = tupdesc->tdtypmod; + MemoryContextSwitchTo(old_cxt); +} + +/* + * common worker for json{b}_populate_record() and json{b}_to_record() + * is_json and have_record_arg identify the specific function + */ +static Datum +populate_record_worker(FunctionCallInfo fcinfo, const char *funcname, + bool is_json, bool have_record_arg) +{ + int json_arg_num = have_record_arg ? 1 : 0; + JsValue jsv = {0}; + HeapTupleHeader rec; + Datum rettuple; + JsonbValue jbv; + MemoryContext fnmcxt = fcinfo->flinfo->fn_mcxt; + PopulateRecordCache *cache = fcinfo->flinfo->fn_extra; + + /* + * If first time through, identify input/result record type. Note that + * this stanza looks only at fcinfo context, which can't change during the + * query; so we may not be able to fully resolve a RECORD input type yet. + */ + if (!cache) + { + fcinfo->flinfo->fn_extra = cache = + MemoryContextAllocZero(fnmcxt, sizeof(*cache)); + cache->fn_mcxt = fnmcxt; + + if (have_record_arg) + get_record_type_from_argument(fcinfo, funcname, cache); + else + get_record_type_from_query(fcinfo, funcname, cache); + } + + /* Collect record arg if we have one */ + if (!have_record_arg) + rec = NULL; /* it's json{b}_to_record() */ + else if (!PG_ARGISNULL(0)) + { + rec = PG_GETARG_HEAPTUPLEHEADER(0); + + /* + * When declared arg type is RECORD, identify actual record type from + * the tuple itself. + */ + if (cache->argtype == RECORDOID) + { + cache->c.io.composite.base_typid = HeapTupleHeaderGetTypeId(rec); + cache->c.io.composite.base_typmod = HeapTupleHeaderGetTypMod(rec); + } + } + else + { + rec = NULL; + + /* + * When declared arg type is RECORD, identify actual record type from + * calling query, or fail if we can't. + */ + if (cache->argtype == RECORDOID) + { + get_record_type_from_query(fcinfo, funcname, cache); + /* This can't change argtype, which is important for next time */ + Assert(cache->argtype == RECORDOID); + } + } + + /* If no JSON argument, just return the record (if any) unchanged */ + if (PG_ARGISNULL(json_arg_num)) + { + if (rec) + PG_RETURN_POINTER(rec); + else + PG_RETURN_NULL(); + } + + jsv.is_json = is_json; + + if (is_json) + { + text *json = PG_GETARG_TEXT_PP(json_arg_num); + + jsv.val.json.str = VARDATA_ANY(json); + jsv.val.json.len = VARSIZE_ANY_EXHDR(json); + jsv.val.json.type = JSON_TOKEN_INVALID; /* not used in + * populate_composite() */ + } + else + { + Jsonb *jb = PG_GETARG_JSONB_P(json_arg_num); + + jsv.val.jsonb = &jbv; + + /* fill binary jsonb value pointing to jb */ + jbv.type = jbvBinary; + jbv.val.binary.data = &jb->root; + jbv.val.binary.len = VARSIZE(jb) - VARHDRSZ; + } + + rettuple = populate_composite(&cache->c.io.composite, cache->argtype, + NULL, fnmcxt, rec, &jsv, false); + + PG_RETURN_DATUM(rettuple); +} + +/* + * get_json_object_as_hash + * + * decompose a json object into a hash table. + */ +static HTAB * +get_json_object_as_hash(char *json, int len, const char *funcname) +{ + HASHCTL ctl; + HTAB *tab; + JHashState *state; + JsonLexContext *lex = makeJsonLexContextCstringLen(json, len, GetDatabaseEncoding(), true); + JsonSemAction *sem; + + ctl.keysize = NAMEDATALEN; + ctl.entrysize = sizeof(JsonHashEntry); + ctl.hcxt = CurrentMemoryContext; + tab = hash_create("json object hashtable", + 100, + &ctl, + HASH_ELEM | HASH_STRINGS | HASH_CONTEXT); + + state = palloc0(sizeof(JHashState)); + sem = palloc0(sizeof(JsonSemAction)); + + state->function_name = funcname; + state->hash = tab; + state->lex = lex; + + sem->semstate = (void *) state; + sem->array_start = hash_array_start; + sem->scalar = hash_scalar; + sem->object_field_start = hash_object_field_start; + sem->object_field_end = hash_object_field_end; + + pg_parse_json_or_ereport(lex, sem); + + return tab; +} + +static JsonParseErrorType +hash_object_field_start(void *state, char *fname, bool isnull) +{ + JHashState *_state = (JHashState *) state; + + if (_state->lex->lex_level > 1) + return JSON_SUCCESS; + + /* remember token type */ + _state->saved_token_type = _state->lex->token_type; + + if (_state->lex->token_type == JSON_TOKEN_ARRAY_START || + _state->lex->token_type == JSON_TOKEN_OBJECT_START) + { + /* remember start position of the whole text of the subobject */ + _state->save_json_start = _state->lex->token_start; + } + else + { + /* must be a scalar */ + _state->save_json_start = NULL; + } + + return JSON_SUCCESS; +} + +static JsonParseErrorType +hash_object_field_end(void *state, char *fname, bool isnull) +{ + JHashState *_state = (JHashState *) state; + JsonHashEntry *hashentry; + bool found; + + /* + * Ignore nested fields. + */ + if (_state->lex->lex_level > 1) + return JSON_SUCCESS; + + /* + * Ignore field names >= NAMEDATALEN - they can't match a record field. + * (Note: without this test, the hash code would truncate the string at + * NAMEDATALEN-1, and could then match against a similarly-truncated + * record field name. That would be a reasonable behavior, but this code + * has previously insisted on exact equality, so we keep this behavior.) + */ + if (strlen(fname) >= NAMEDATALEN) + return JSON_SUCCESS; + + hashentry = hash_search(_state->hash, fname, HASH_ENTER, &found); + + /* + * found being true indicates a duplicate. We don't do anything about + * that, a later field with the same name overrides the earlier field. + */ + + hashentry->type = _state->saved_token_type; + Assert(isnull == (hashentry->type == JSON_TOKEN_NULL)); + + if (_state->save_json_start != NULL) + { + int len = _state->lex->prev_token_terminator - _state->save_json_start; + char *val = palloc((len + 1) * sizeof(char)); + + memcpy(val, _state->save_json_start, len); + val[len] = '\0'; + hashentry->val = val; + } + else + { + /* must have had a scalar instead */ + hashentry->val = _state->saved_scalar; + } + + return JSON_SUCCESS; +} + +static JsonParseErrorType +hash_array_start(void *state) +{ + JHashState *_state = (JHashState *) state; + + if (_state->lex->lex_level == 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot call %s on an array", _state->function_name))); + + return JSON_SUCCESS; +} + +static JsonParseErrorType +hash_scalar(void *state, char *token, JsonTokenType tokentype) +{ + JHashState *_state = (JHashState *) state; + + if (_state->lex->lex_level == 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot call %s on a scalar", _state->function_name))); + + if (_state->lex->lex_level == 1) + { + _state->saved_scalar = token; + /* saved_token_type must already be set in hash_object_field_start() */ + Assert(_state->saved_token_type == tokentype); + } + + return JSON_SUCCESS; +} + + +/* + * SQL function json_populate_recordset + * + * set fields in a set of records from the argument json, + * which must be an array of objects. + * + * similar to json_populate_record, but the tuple-building code + * is pushed down into the semantic action handlers so it's done + * per object in the array. + */ +Datum +jsonb_populate_recordset(PG_FUNCTION_ARGS) +{ + return populate_recordset_worker(fcinfo, "jsonb_populate_recordset", + false, true); +} + +Datum +jsonb_to_recordset(PG_FUNCTION_ARGS) +{ + return populate_recordset_worker(fcinfo, "jsonb_to_recordset", + false, false); +} + +Datum +json_populate_recordset(PG_FUNCTION_ARGS) +{ + return populate_recordset_worker(fcinfo, "json_populate_recordset", + true, true); +} + +Datum +json_to_recordset(PG_FUNCTION_ARGS) +{ + return populate_recordset_worker(fcinfo, "json_to_recordset", + true, false); +} + +static void +populate_recordset_record(PopulateRecordsetState *state, JsObject *obj) +{ + PopulateRecordCache *cache = state->cache; + HeapTupleHeader tuphead; + HeapTupleData tuple; + + /* acquire/update cached tuple descriptor */ + update_cached_tupdesc(&cache->c.io.composite, cache->fn_mcxt); + + /* replace record fields from json */ + tuphead = populate_record(cache->c.io.composite.tupdesc, + &cache->c.io.composite.record_io, + state->rec, + cache->fn_mcxt, + obj); + + /* if it's domain over composite, check domain constraints */ + if (cache->c.typcat == TYPECAT_COMPOSITE_DOMAIN) + domain_check(HeapTupleHeaderGetDatum(tuphead), false, + cache->argtype, + &cache->c.io.composite.domain_info, + cache->fn_mcxt); + + /* ok, save into tuplestore */ + tuple.t_len = HeapTupleHeaderGetDatumLength(tuphead); + ItemPointerSetInvalid(&(tuple.t_self)); + tuple.t_tableOid = InvalidOid; + tuple.t_data = tuphead; + + tuplestore_puttuple(state->tuple_store, &tuple); +} + +/* + * common worker for json{b}_populate_recordset() and json{b}_to_recordset() + * is_json and have_record_arg identify the specific function + */ +static Datum +populate_recordset_worker(FunctionCallInfo fcinfo, const char *funcname, + bool is_json, bool have_record_arg) +{ + int json_arg_num = have_record_arg ? 1 : 0; + ReturnSetInfo *rsi; + MemoryContext old_cxt; + HeapTupleHeader rec; + PopulateRecordCache *cache = fcinfo->flinfo->fn_extra; + PopulateRecordsetState *state; + + rsi = (ReturnSetInfo *) fcinfo->resultinfo; + + if (!rsi || !IsA(rsi, ReturnSetInfo)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("set-valued function called in context that cannot accept a set"))); + + if (!(rsi->allowedModes & SFRM_Materialize)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("materialize mode required, but it is not allowed in this context"))); + + rsi->returnMode = SFRM_Materialize; + + /* + * If first time through, identify input/result record type. Note that + * this stanza looks only at fcinfo context, which can't change during the + * query; so we may not be able to fully resolve a RECORD input type yet. + */ + if (!cache) + { + fcinfo->flinfo->fn_extra = cache = + MemoryContextAllocZero(fcinfo->flinfo->fn_mcxt, sizeof(*cache)); + cache->fn_mcxt = fcinfo->flinfo->fn_mcxt; + + if (have_record_arg) + get_record_type_from_argument(fcinfo, funcname, cache); + else + get_record_type_from_query(fcinfo, funcname, cache); + } + + /* Collect record arg if we have one */ + if (!have_record_arg) + rec = NULL; /* it's json{b}_to_recordset() */ + else if (!PG_ARGISNULL(0)) + { + rec = PG_GETARG_HEAPTUPLEHEADER(0); + + /* + * When declared arg type is RECORD, identify actual record type from + * the tuple itself. + */ + if (cache->argtype == RECORDOID) + { + cache->c.io.composite.base_typid = HeapTupleHeaderGetTypeId(rec); + cache->c.io.composite.base_typmod = HeapTupleHeaderGetTypMod(rec); + } + } + else + { + rec = NULL; + + /* + * When declared arg type is RECORD, identify actual record type from + * calling query, or fail if we can't. + */ + if (cache->argtype == RECORDOID) + { + get_record_type_from_query(fcinfo, funcname, cache); + /* This can't change argtype, which is important for next time */ + Assert(cache->argtype == RECORDOID); + } + } + + /* if the json is null send back an empty set */ + if (PG_ARGISNULL(json_arg_num)) + PG_RETURN_NULL(); + + /* + * Forcibly update the cached tupdesc, to ensure we have the right tupdesc + * to return even if the JSON contains no rows. + */ + update_cached_tupdesc(&cache->c.io.composite, cache->fn_mcxt); + + state = palloc0(sizeof(PopulateRecordsetState)); + + /* make tuplestore in a sufficiently long-lived memory context */ + old_cxt = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory); + state->tuple_store = tuplestore_begin_heap(rsi->allowedModes & + SFRM_Materialize_Random, + false, work_mem); + MemoryContextSwitchTo(old_cxt); + + state->function_name = funcname; + state->cache = cache; + state->rec = rec; + + if (is_json) + { + text *json = PG_GETARG_TEXT_PP(json_arg_num); + JsonLexContext *lex; + JsonSemAction *sem; + + sem = palloc0(sizeof(JsonSemAction)); + + lex = makeJsonLexContext(json, true); + + sem->semstate = (void *) state; + sem->array_start = populate_recordset_array_start; + sem->array_element_start = populate_recordset_array_element_start; + sem->scalar = populate_recordset_scalar; + sem->object_field_start = populate_recordset_object_field_start; + sem->object_field_end = populate_recordset_object_field_end; + sem->object_start = populate_recordset_object_start; + sem->object_end = populate_recordset_object_end; + + state->lex = lex; + + pg_parse_json_or_ereport(lex, sem); + } + else + { + Jsonb *jb = PG_GETARG_JSONB_P(json_arg_num); + JsonbIterator *it; + JsonbValue v; + bool skipNested = false; + JsonbIteratorToken r; + + if (JB_ROOT_IS_SCALAR(jb) || !JB_ROOT_IS_ARRAY(jb)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot call %s on a non-array", + funcname))); + + it = JsonbIteratorInit(&jb->root); + + while ((r = JsonbIteratorNext(&it, &v, skipNested)) != WJB_DONE) + { + skipNested = true; + + if (r == WJB_ELEM) + { + JsObject obj; + + if (v.type != jbvBinary || + !JsonContainerIsObject(v.val.binary.data)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("argument of %s must be an array of objects", + funcname))); + + obj.is_json = false; + obj.val.jsonb_cont = v.val.binary.data; + + populate_recordset_record(state, &obj); + } + } + } + + /* + * Note: we must copy the cached tupdesc because the executor will free + * the passed-back setDesc, but we want to hang onto the cache in case + * we're called again in the same query. + */ + rsi->setResult = state->tuple_store; + rsi->setDesc = CreateTupleDescCopy(cache->c.io.composite.tupdesc); + + PG_RETURN_NULL(); +} + +static JsonParseErrorType +populate_recordset_object_start(void *state) +{ + PopulateRecordsetState *_state = (PopulateRecordsetState *) state; + int lex_level = _state->lex->lex_level; + HASHCTL ctl; + + /* Reject object at top level: we must have an array at level 0 */ + if (lex_level == 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot call %s on an object", + _state->function_name))); + + /* Nested objects require no special processing */ + if (lex_level > 1) + return JSON_SUCCESS; + + /* Object at level 1: set up a new hash table for this object */ + ctl.keysize = NAMEDATALEN; + ctl.entrysize = sizeof(JsonHashEntry); + ctl.hcxt = CurrentMemoryContext; + _state->json_hash = hash_create("json object hashtable", + 100, + &ctl, + HASH_ELEM | HASH_STRINGS | HASH_CONTEXT); + + return JSON_SUCCESS; +} + +static JsonParseErrorType +populate_recordset_object_end(void *state) +{ + PopulateRecordsetState *_state = (PopulateRecordsetState *) state; + JsObject obj; + + /* Nested objects require no special processing */ + if (_state->lex->lex_level > 1) + return JSON_SUCCESS; + + obj.is_json = true; + obj.val.json_hash = _state->json_hash; + + /* Otherwise, construct and return a tuple based on this level-1 object */ + populate_recordset_record(_state, &obj); + + /* Done with hash for this object */ + hash_destroy(_state->json_hash); + _state->json_hash = NULL; + + return JSON_SUCCESS; +} + +static JsonParseErrorType +populate_recordset_array_element_start(void *state, bool isnull) +{ + PopulateRecordsetState *_state = (PopulateRecordsetState *) state; + + if (_state->lex->lex_level == 1 && + _state->lex->token_type != JSON_TOKEN_OBJECT_START) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("argument of %s must be an array of objects", + _state->function_name))); + + return JSON_SUCCESS; +} + +static JsonParseErrorType +populate_recordset_array_start(void *state) +{ + /* nothing to do */ + return JSON_SUCCESS; +} + +static JsonParseErrorType +populate_recordset_scalar(void *state, char *token, JsonTokenType tokentype) +{ + PopulateRecordsetState *_state = (PopulateRecordsetState *) state; + + if (_state->lex->lex_level == 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot call %s on a scalar", + _state->function_name))); + + if (_state->lex->lex_level == 2) + _state->saved_scalar = token; + + return JSON_SUCCESS; +} + +static JsonParseErrorType +populate_recordset_object_field_start(void *state, char *fname, bool isnull) +{ + PopulateRecordsetState *_state = (PopulateRecordsetState *) state; + + if (_state->lex->lex_level > 2) + return JSON_SUCCESS; + + _state->saved_token_type = _state->lex->token_type; + + if (_state->lex->token_type == JSON_TOKEN_ARRAY_START || + _state->lex->token_type == JSON_TOKEN_OBJECT_START) + { + _state->save_json_start = _state->lex->token_start; + } + else + { + _state->save_json_start = NULL; + } + + return JSON_SUCCESS; +} + +static JsonParseErrorType +populate_recordset_object_field_end(void *state, char *fname, bool isnull) +{ + PopulateRecordsetState *_state = (PopulateRecordsetState *) state; + JsonHashEntry *hashentry; + bool found; + + /* + * Ignore nested fields. + */ + if (_state->lex->lex_level > 2) + return JSON_SUCCESS; + + /* + * Ignore field names >= NAMEDATALEN - they can't match a record field. + * (Note: without this test, the hash code would truncate the string at + * NAMEDATALEN-1, and could then match against a similarly-truncated + * record field name. That would be a reasonable behavior, but this code + * has previously insisted on exact equality, so we keep this behavior.) + */ + if (strlen(fname) >= NAMEDATALEN) + return JSON_SUCCESS; + + hashentry = hash_search(_state->json_hash, fname, HASH_ENTER, &found); + + /* + * found being true indicates a duplicate. We don't do anything about + * that, a later field with the same name overrides the earlier field. + */ + + hashentry->type = _state->saved_token_type; + Assert(isnull == (hashentry->type == JSON_TOKEN_NULL)); + + if (_state->save_json_start != NULL) + { + int len = _state->lex->prev_token_terminator - _state->save_json_start; + char *val = palloc((len + 1) * sizeof(char)); + + memcpy(val, _state->save_json_start, len); + val[len] = '\0'; + hashentry->val = val; + } + else + { + /* must have had a scalar instead */ + hashentry->val = _state->saved_scalar; + } + + return JSON_SUCCESS; +} + +/* + * Semantic actions for json_strip_nulls. + * + * Simply repeat the input on the output unless we encounter + * a null object field. State for this is set when the field + * is started and reset when the scalar action (which must be next) + * is called. + */ + +static JsonParseErrorType +sn_object_start(void *state) +{ + StripnullState *_state = (StripnullState *) state; + + appendStringInfoCharMacro(_state->strval, '{'); + + return JSON_SUCCESS; +} + +static JsonParseErrorType +sn_object_end(void *state) +{ + StripnullState *_state = (StripnullState *) state; + + appendStringInfoCharMacro(_state->strval, '}'); + + return JSON_SUCCESS; +} + +static JsonParseErrorType +sn_array_start(void *state) +{ + StripnullState *_state = (StripnullState *) state; + + appendStringInfoCharMacro(_state->strval, '['); + + return JSON_SUCCESS; +} + +static JsonParseErrorType +sn_array_end(void *state) +{ + StripnullState *_state = (StripnullState *) state; + + appendStringInfoCharMacro(_state->strval, ']'); + + return JSON_SUCCESS; +} + +static JsonParseErrorType +sn_object_field_start(void *state, char *fname, bool isnull) +{ + StripnullState *_state = (StripnullState *) state; + + if (isnull) + { + /* + * The next thing must be a scalar or isnull couldn't be true, so + * there is no danger of this state being carried down into a nested + * object or array. The flag will be reset in the scalar action. + */ + _state->skip_next_null = true; + return JSON_SUCCESS; + } + + if (_state->strval->data[_state->strval->len - 1] != '{') + appendStringInfoCharMacro(_state->strval, ','); + + /* + * Unfortunately we don't have the quoted and escaped string any more, so + * we have to re-escape it. + */ + escape_json(_state->strval, fname); + + appendStringInfoCharMacro(_state->strval, ':'); + + return JSON_SUCCESS; +} + +static JsonParseErrorType +sn_array_element_start(void *state, bool isnull) +{ + StripnullState *_state = (StripnullState *) state; + + if (_state->strval->data[_state->strval->len - 1] != '[') + appendStringInfoCharMacro(_state->strval, ','); + + return JSON_SUCCESS; +} + +static JsonParseErrorType +sn_scalar(void *state, char *token, JsonTokenType tokentype) +{ + StripnullState *_state = (StripnullState *) state; + + if (_state->skip_next_null) + { + Assert(tokentype == JSON_TOKEN_NULL); + _state->skip_next_null = false; + return JSON_SUCCESS; + } + + if (tokentype == JSON_TOKEN_STRING) + escape_json(_state->strval, token); + else + appendStringInfoString(_state->strval, token); + + return JSON_SUCCESS; +} + +/* + * SQL function json_strip_nulls(json) -> json + */ +Datum +json_strip_nulls(PG_FUNCTION_ARGS) +{ + text *json = PG_GETARG_TEXT_PP(0); + StripnullState *state; + JsonLexContext *lex; + JsonSemAction *sem; + + lex = makeJsonLexContext(json, true); + state = palloc0(sizeof(StripnullState)); + sem = palloc0(sizeof(JsonSemAction)); + + state->strval = makeStringInfo(); + state->skip_next_null = false; + state->lex = lex; + + sem->semstate = (void *) state; + sem->object_start = sn_object_start; + sem->object_end = sn_object_end; + sem->array_start = sn_array_start; + sem->array_end = sn_array_end; + sem->scalar = sn_scalar; + sem->array_element_start = sn_array_element_start; + sem->object_field_start = sn_object_field_start; + + pg_parse_json_or_ereport(lex, sem); + + PG_RETURN_TEXT_P(cstring_to_text_with_len(state->strval->data, + state->strval->len)); +} + +/* + * SQL function jsonb_strip_nulls(jsonb) -> jsonb + */ +Datum +jsonb_strip_nulls(PG_FUNCTION_ARGS) +{ + Jsonb *jb = PG_GETARG_JSONB_P(0); + JsonbIterator *it; + JsonbParseState *parseState = NULL; + JsonbValue *res = NULL; + JsonbValue v, + k; + JsonbIteratorToken type; + bool last_was_key = false; + + if (JB_ROOT_IS_SCALAR(jb)) + PG_RETURN_POINTER(jb); + + it = JsonbIteratorInit(&jb->root); + + while ((type = JsonbIteratorNext(&it, &v, false)) != WJB_DONE) + { + Assert(!(type == WJB_KEY && last_was_key)); + + if (type == WJB_KEY) + { + /* stash the key until we know if it has a null value */ + k = v; + last_was_key = true; + continue; + } + + if (last_was_key) + { + /* if the last element was a key this one can't be */ + last_was_key = false; + + /* skip this field if value is null */ + if (type == WJB_VALUE && v.type == jbvNull) + continue; + + /* otherwise, do a delayed push of the key */ + (void) pushJsonbValue(&parseState, WJB_KEY, &k); + } + + if (type == WJB_VALUE || type == WJB_ELEM) + res = pushJsonbValue(&parseState, type, &v); + else + res = pushJsonbValue(&parseState, type, NULL); + } + + Assert(res != NULL); + + PG_RETURN_POINTER(JsonbValueToJsonb(res)); +} + +/* + * SQL function jsonb_pretty (jsonb) + * + * Pretty-printed text for the jsonb + */ +Datum +jsonb_pretty(PG_FUNCTION_ARGS) +{ + Jsonb *jb = PG_GETARG_JSONB_P(0); + StringInfo str = makeStringInfo(); + + JsonbToCStringIndent(str, &jb->root, VARSIZE(jb)); + + PG_RETURN_TEXT_P(cstring_to_text_with_len(str->data, str->len)); +} + +/* + * SQL function jsonb_concat (jsonb, jsonb) + * + * function for || operator + */ +Datum +jsonb_concat(PG_FUNCTION_ARGS) +{ + Jsonb *jb1 = PG_GETARG_JSONB_P(0); + Jsonb *jb2 = PG_GETARG_JSONB_P(1); + JsonbParseState *state = NULL; + JsonbValue *res; + JsonbIterator *it1, + *it2; + + /* + * If one of the jsonb is empty, just return the other if it's not scalar + * and both are of the same kind. If it's a scalar or they are of + * different kinds we need to perform the concatenation even if one is + * empty. + */ + if (JB_ROOT_IS_OBJECT(jb1) == JB_ROOT_IS_OBJECT(jb2)) + { + if (JB_ROOT_COUNT(jb1) == 0 && !JB_ROOT_IS_SCALAR(jb2)) + PG_RETURN_JSONB_P(jb2); + else if (JB_ROOT_COUNT(jb2) == 0 && !JB_ROOT_IS_SCALAR(jb1)) + PG_RETURN_JSONB_P(jb1); + } + + it1 = JsonbIteratorInit(&jb1->root); + it2 = JsonbIteratorInit(&jb2->root); + + res = IteratorConcat(&it1, &it2, &state); + + Assert(res != NULL); + + PG_RETURN_JSONB_P(JsonbValueToJsonb(res)); +} + + +/* + * SQL function jsonb_delete (jsonb, text) + * + * return a copy of the jsonb with the indicated item + * removed. + */ +Datum +jsonb_delete(PG_FUNCTION_ARGS) +{ + Jsonb *in = PG_GETARG_JSONB_P(0); + text *key = PG_GETARG_TEXT_PP(1); + char *keyptr = VARDATA_ANY(key); + int keylen = VARSIZE_ANY_EXHDR(key); + JsonbParseState *state = NULL; + JsonbIterator *it; + JsonbValue v, + *res = NULL; + bool skipNested = false; + JsonbIteratorToken r; + + if (JB_ROOT_IS_SCALAR(in)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot delete from scalar"))); + + if (JB_ROOT_COUNT(in) == 0) + PG_RETURN_JSONB_P(in); + + it = JsonbIteratorInit(&in->root); + + while ((r = JsonbIteratorNext(&it, &v, skipNested)) != WJB_DONE) + { + skipNested = true; + + if ((r == WJB_ELEM || r == WJB_KEY) && + (v.type == jbvString && keylen == v.val.string.len && + memcmp(keyptr, v.val.string.val, keylen) == 0)) + { + /* skip corresponding value as well */ + if (r == WJB_KEY) + (void) JsonbIteratorNext(&it, &v, true); + + continue; + } + + res = pushJsonbValue(&state, r, r < WJB_BEGIN_ARRAY ? &v : NULL); + } + + Assert(res != NULL); + + PG_RETURN_JSONB_P(JsonbValueToJsonb(res)); +} + +/* + * SQL function jsonb_delete (jsonb, variadic text[]) + * + * return a copy of the jsonb with the indicated items + * removed. + */ +Datum +jsonb_delete_array(PG_FUNCTION_ARGS) +{ + Jsonb *in = PG_GETARG_JSONB_P(0); + ArrayType *keys = PG_GETARG_ARRAYTYPE_P(1); + Datum *keys_elems; + bool *keys_nulls; + int keys_len; + JsonbParseState *state = NULL; + JsonbIterator *it; + JsonbValue v, + *res = NULL; + bool skipNested = false; + JsonbIteratorToken r; + + if (ARR_NDIM(keys) > 1) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("wrong number of array subscripts"))); + + if (JB_ROOT_IS_SCALAR(in)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot delete from scalar"))); + + if (JB_ROOT_COUNT(in) == 0) + PG_RETURN_JSONB_P(in); + + deconstruct_array_builtin(keys, TEXTOID, &keys_elems, &keys_nulls, &keys_len); + + if (keys_len == 0) + PG_RETURN_JSONB_P(in); + + it = JsonbIteratorInit(&in->root); + + while ((r = JsonbIteratorNext(&it, &v, skipNested)) != WJB_DONE) + { + skipNested = true; + + if ((r == WJB_ELEM || r == WJB_KEY) && v.type == jbvString) + { + int i; + bool found = false; + + for (i = 0; i < keys_len; i++) + { + char *keyptr; + int keylen; + + if (keys_nulls[i]) + continue; + + /* We rely on the array elements not being toasted */ + keyptr = VARDATA_ANY(keys_elems[i]); + keylen = VARSIZE_ANY_EXHDR(keys_elems[i]); + if (keylen == v.val.string.len && + memcmp(keyptr, v.val.string.val, keylen) == 0) + { + found = true; + break; + } + } + if (found) + { + /* skip corresponding value as well */ + if (r == WJB_KEY) + (void) JsonbIteratorNext(&it, &v, true); + + continue; + } + } + + res = pushJsonbValue(&state, r, r < WJB_BEGIN_ARRAY ? &v : NULL); + } + + Assert(res != NULL); + + PG_RETURN_JSONB_P(JsonbValueToJsonb(res)); +} + +/* + * SQL function jsonb_delete (jsonb, int) + * + * return a copy of the jsonb with the indicated item + * removed. Negative int means count back from the + * end of the items. + */ +Datum +jsonb_delete_idx(PG_FUNCTION_ARGS) +{ + Jsonb *in = PG_GETARG_JSONB_P(0); + int idx = PG_GETARG_INT32(1); + JsonbParseState *state = NULL; + JsonbIterator *it; + uint32 i = 0, + n; + JsonbValue v, + *res = NULL; + JsonbIteratorToken r; + + if (JB_ROOT_IS_SCALAR(in)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot delete from scalar"))); + + if (JB_ROOT_IS_OBJECT(in)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot delete from object using integer index"))); + + if (JB_ROOT_COUNT(in) == 0) + PG_RETURN_JSONB_P(in); + + it = JsonbIteratorInit(&in->root); + + r = JsonbIteratorNext(&it, &v, false); + Assert(r == WJB_BEGIN_ARRAY); + n = v.val.array.nElems; + + if (idx < 0) + { + if (-idx > n) + idx = n; + else + idx = n + idx; + } + + if (idx >= n) + PG_RETURN_JSONB_P(in); + + pushJsonbValue(&state, r, NULL); + + while ((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE) + { + if (r == WJB_ELEM) + { + if (i++ == idx) + continue; + } + + res = pushJsonbValue(&state, r, r < WJB_BEGIN_ARRAY ? &v : NULL); + } + + Assert(res != NULL); + + PG_RETURN_JSONB_P(JsonbValueToJsonb(res)); +} + +/* + * SQL function jsonb_set(jsonb, text[], jsonb, boolean) + */ +Datum +jsonb_set(PG_FUNCTION_ARGS) +{ + Jsonb *in = PG_GETARG_JSONB_P(0); + ArrayType *path = PG_GETARG_ARRAYTYPE_P(1); + Jsonb *newjsonb = PG_GETARG_JSONB_P(2); + JsonbValue newval; + bool create = PG_GETARG_BOOL(3); + JsonbValue *res = NULL; + Datum *path_elems; + bool *path_nulls; + int path_len; + JsonbIterator *it; + JsonbParseState *st = NULL; + + JsonbToJsonbValue(newjsonb, &newval); + + if (ARR_NDIM(path) > 1) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("wrong number of array subscripts"))); + + if (JB_ROOT_IS_SCALAR(in)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot set path in scalar"))); + + if (JB_ROOT_COUNT(in) == 0 && !create) + PG_RETURN_JSONB_P(in); + + deconstruct_array_builtin(path, TEXTOID, &path_elems, &path_nulls, &path_len); + + if (path_len == 0) + PG_RETURN_JSONB_P(in); + + it = JsonbIteratorInit(&in->root); + + res = setPath(&it, path_elems, path_nulls, path_len, &st, + 0, &newval, create ? JB_PATH_CREATE : JB_PATH_REPLACE); + + Assert(res != NULL); + + PG_RETURN_JSONB_P(JsonbValueToJsonb(res)); +} + + +/* + * SQL function jsonb_set_lax(jsonb, text[], jsonb, boolean, text) + */ +Datum +jsonb_set_lax(PG_FUNCTION_ARGS) +{ + /* Jsonb *in = PG_GETARG_JSONB_P(0); */ + /* ArrayType *path = PG_GETARG_ARRAYTYPE_P(1); */ + /* Jsonb *newval = PG_GETARG_JSONB_P(2); */ + /* bool create = PG_GETARG_BOOL(3); */ + text *handle_null; + char *handle_val; + + if (PG_ARGISNULL(0) || PG_ARGISNULL(1) || PG_ARGISNULL(3)) + PG_RETURN_NULL(); + + /* could happen if they pass in an explicit NULL */ + if (PG_ARGISNULL(4)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("null_value_treatment must be \"delete_key\", \"return_target\", \"use_json_null\", or \"raise_exception\""))); + + /* if the new value isn't an SQL NULL just call jsonb_set */ + if (!PG_ARGISNULL(2)) + return jsonb_set(fcinfo); + + handle_null = PG_GETARG_TEXT_P(4); + handle_val = text_to_cstring(handle_null); + + if (strcmp(handle_val, "raise_exception") == 0) + { + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("JSON value must not be null"), + errdetail("Exception was raised because null_value_treatment is \"raise_exception\"."), + errhint("To avoid, either change the null_value_treatment argument or ensure that an SQL NULL is not passed."))); + return (Datum) 0; /* silence stupider compilers */ + } + else if (strcmp(handle_val, "use_json_null") == 0) + { + Datum newval; + + newval = DirectFunctionCall1(jsonb_in, CStringGetDatum("null")); + + fcinfo->args[2].value = newval; + fcinfo->args[2].isnull = false; + return jsonb_set(fcinfo); + } + else if (strcmp(handle_val, "delete_key") == 0) + { + return jsonb_delete_path(fcinfo); + } + else if (strcmp(handle_val, "return_target") == 0) + { + Jsonb *in = PG_GETARG_JSONB_P(0); + + PG_RETURN_JSONB_P(in); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("null_value_treatment must be \"delete_key\", \"return_target\", \"use_json_null\", or \"raise_exception\""))); + return (Datum) 0; /* silence stupider compilers */ + } +} + +/* + * SQL function jsonb_delete_path(jsonb, text[]) + */ +Datum +jsonb_delete_path(PG_FUNCTION_ARGS) +{ + Jsonb *in = PG_GETARG_JSONB_P(0); + ArrayType *path = PG_GETARG_ARRAYTYPE_P(1); + JsonbValue *res = NULL; + Datum *path_elems; + bool *path_nulls; + int path_len; + JsonbIterator *it; + JsonbParseState *st = NULL; + + if (ARR_NDIM(path) > 1) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("wrong number of array subscripts"))); + + if (JB_ROOT_IS_SCALAR(in)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot delete path in scalar"))); + + if (JB_ROOT_COUNT(in) == 0) + PG_RETURN_JSONB_P(in); + + deconstruct_array_builtin(path, TEXTOID, &path_elems, &path_nulls, &path_len); + + if (path_len == 0) + PG_RETURN_JSONB_P(in); + + it = JsonbIteratorInit(&in->root); + + res = setPath(&it, path_elems, path_nulls, path_len, &st, + 0, NULL, JB_PATH_DELETE); + + Assert(res != NULL); + + PG_RETURN_JSONB_P(JsonbValueToJsonb(res)); +} + +/* + * SQL function jsonb_insert(jsonb, text[], jsonb, boolean) + */ +Datum +jsonb_insert(PG_FUNCTION_ARGS) +{ + Jsonb *in = PG_GETARG_JSONB_P(0); + ArrayType *path = PG_GETARG_ARRAYTYPE_P(1); + Jsonb *newjsonb = PG_GETARG_JSONB_P(2); + JsonbValue newval; + bool after = PG_GETARG_BOOL(3); + JsonbValue *res = NULL; + Datum *path_elems; + bool *path_nulls; + int path_len; + JsonbIterator *it; + JsonbParseState *st = NULL; + + JsonbToJsonbValue(newjsonb, &newval); + + if (ARR_NDIM(path) > 1) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("wrong number of array subscripts"))); + + if (JB_ROOT_IS_SCALAR(in)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot set path in scalar"))); + + deconstruct_array_builtin(path, TEXTOID, &path_elems, &path_nulls, &path_len); + + if (path_len == 0) + PG_RETURN_JSONB_P(in); + + it = JsonbIteratorInit(&in->root); + + res = setPath(&it, path_elems, path_nulls, path_len, &st, 0, &newval, + after ? JB_PATH_INSERT_AFTER : JB_PATH_INSERT_BEFORE); + + Assert(res != NULL); + + PG_RETURN_JSONB_P(JsonbValueToJsonb(res)); +} + +/* + * Iterate over all jsonb objects and merge them into one. + * The logic of this function copied from the same hstore function, + * except the case, when it1 & it2 represents jbvObject. + * In that case we just append the content of it2 to it1 without any + * verifications. + */ +static JsonbValue * +IteratorConcat(JsonbIterator **it1, JsonbIterator **it2, + JsonbParseState **state) +{ + JsonbValue v1, + v2, + *res = NULL; + JsonbIteratorToken r1, + r2, + rk1, + rk2; + + rk1 = JsonbIteratorNext(it1, &v1, false); + rk2 = JsonbIteratorNext(it2, &v2, false); + + /* + * JsonbIteratorNext reports raw scalars as if they were single-element + * arrays; hence we only need consider "object" and "array" cases here. + */ + if (rk1 == WJB_BEGIN_OBJECT && rk2 == WJB_BEGIN_OBJECT) + { + /* + * Both inputs are objects. + * + * Append all the tokens from v1 to res, except last WJB_END_OBJECT + * (because res will not be finished yet). + */ + pushJsonbValue(state, rk1, NULL); + while ((r1 = JsonbIteratorNext(it1, &v1, true)) != WJB_END_OBJECT) + pushJsonbValue(state, r1, &v1); + + /* + * Append all the tokens from v2 to res, including last WJB_END_OBJECT + * (the concatenation will be completed). Any duplicate keys will + * automatically override the value from the first object. + */ + while ((r2 = JsonbIteratorNext(it2, &v2, true)) != WJB_DONE) + res = pushJsonbValue(state, r2, r2 != WJB_END_OBJECT ? &v2 : NULL); + } + else if (rk1 == WJB_BEGIN_ARRAY && rk2 == WJB_BEGIN_ARRAY) + { + /* + * Both inputs are arrays. + */ + pushJsonbValue(state, rk1, NULL); + + while ((r1 = JsonbIteratorNext(it1, &v1, true)) != WJB_END_ARRAY) + { + Assert(r1 == WJB_ELEM); + pushJsonbValue(state, r1, &v1); + } + + while ((r2 = JsonbIteratorNext(it2, &v2, true)) != WJB_END_ARRAY) + { + Assert(r2 == WJB_ELEM); + pushJsonbValue(state, WJB_ELEM, &v2); + } + + res = pushJsonbValue(state, WJB_END_ARRAY, NULL /* signal to sort */ ); + } + else if (rk1 == WJB_BEGIN_OBJECT) + { + /* + * We have object || array. + */ + Assert(rk2 == WJB_BEGIN_ARRAY); + + pushJsonbValue(state, WJB_BEGIN_ARRAY, NULL); + + pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL); + while ((r1 = JsonbIteratorNext(it1, &v1, true)) != WJB_DONE) + pushJsonbValue(state, r1, r1 != WJB_END_OBJECT ? &v1 : NULL); + + while ((r2 = JsonbIteratorNext(it2, &v2, true)) != WJB_DONE) + res = pushJsonbValue(state, r2, r2 != WJB_END_ARRAY ? &v2 : NULL); + } + else + { + /* + * We have array || object. + */ + Assert(rk1 == WJB_BEGIN_ARRAY); + Assert(rk2 == WJB_BEGIN_OBJECT); + + pushJsonbValue(state, WJB_BEGIN_ARRAY, NULL); + + while ((r1 = JsonbIteratorNext(it1, &v1, true)) != WJB_END_ARRAY) + pushJsonbValue(state, r1, &v1); + + pushJsonbValue(state, WJB_BEGIN_OBJECT, NULL); + while ((r2 = JsonbIteratorNext(it2, &v2, true)) != WJB_DONE) + pushJsonbValue(state, r2, r2 != WJB_END_OBJECT ? &v2 : NULL); + + res = pushJsonbValue(state, WJB_END_ARRAY, NULL); + } + + return res; +} + +/* + * Do most of the heavy work for jsonb_set/jsonb_insert + * + * If JB_PATH_DELETE bit is set in op_type, the element is to be removed. + * + * If any bit mentioned in JB_PATH_CREATE_OR_INSERT is set in op_type, + * we create the new value if the key or array index does not exist. + * + * Bits JB_PATH_INSERT_BEFORE and JB_PATH_INSERT_AFTER in op_type + * behave as JB_PATH_CREATE if new value is inserted in JsonbObject. + * + * If JB_PATH_FILL_GAPS bit is set, this will change an assignment logic in + * case if target is an array. The assignment index will not be restricted by + * number of elements in the array, and if there are any empty slots between + * last element of the array and a new one they will be filled with nulls. If + * the index is negative, it still will be considered an index from the end + * of the array. Of a part of the path is not present and this part is more + * than just one last element, this flag will instruct to create the whole + * chain of corresponding objects and insert the value. + * + * JB_PATH_CONSISTENT_POSITION for an array indicates that the caller wants to + * keep values with fixed indices. Indices for existing elements could be + * changed (shifted forward) in case if the array is prepended with a new value + * and a negative index out of the range, so this behavior will be prevented + * and return an error. + * + * All path elements before the last must already exist + * whatever bits in op_type are set, or nothing is done. + */ +static JsonbValue * +setPath(JsonbIterator **it, Datum *path_elems, + bool *path_nulls, int path_len, + JsonbParseState **st, int level, JsonbValue *newval, int op_type) +{ + JsonbValue v; + JsonbIteratorToken r; + JsonbValue *res; + + check_stack_depth(); + + if (path_nulls[level]) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("path element at position %d is null", + level + 1))); + + r = JsonbIteratorNext(it, &v, false); + + switch (r) + { + case WJB_BEGIN_ARRAY: + + /* + * If instructed complain about attempts to replace within a raw + * scalar value. This happens even when current level is equal to + * path_len, because the last path key should also correspond to + * an object or an array, not raw scalar. + */ + if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1) && + v.val.array.rawScalar) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot replace existing key"), + errdetail("The path assumes key is a composite object, " + "but it is a scalar value."))); + + (void) pushJsonbValue(st, r, NULL); + setPathArray(it, path_elems, path_nulls, path_len, st, level, + newval, v.val.array.nElems, op_type); + r = JsonbIteratorNext(it, &v, false); + Assert(r == WJB_END_ARRAY); + res = pushJsonbValue(st, r, NULL); + break; + case WJB_BEGIN_OBJECT: + (void) pushJsonbValue(st, r, NULL); + setPathObject(it, path_elems, path_nulls, path_len, st, level, + newval, v.val.object.nPairs, op_type); + r = JsonbIteratorNext(it, &v, true); + Assert(r == WJB_END_OBJECT); + res = pushJsonbValue(st, r, NULL); + break; + case WJB_ELEM: + case WJB_VALUE: + + /* + * If instructed complain about attempts to replace within a + * scalar value. This happens even when current level is equal to + * path_len, because the last path key should also correspond to + * an object or an array, not an element or value. + */ + if ((op_type & JB_PATH_FILL_GAPS) && (level <= path_len - 1)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot replace existing key"), + errdetail("The path assumes key is a composite object, " + "but it is a scalar value."))); + + res = pushJsonbValue(st, r, &v); + break; + default: + elog(ERROR, "unrecognized iterator result: %d", (int) r); + res = NULL; /* keep compiler quiet */ + break; + } + + return res; +} + +/* + * Object walker for setPath + */ +static void +setPathObject(JsonbIterator **it, Datum *path_elems, bool *path_nulls, + int path_len, JsonbParseState **st, int level, + JsonbValue *newval, uint32 npairs, int op_type) +{ + text *pathelem = NULL; + int i; + JsonbValue k, + v; + bool done = false; + + if (level >= path_len || path_nulls[level]) + done = true; + else + { + /* The path Datum could be toasted, in which case we must detoast it */ + pathelem = DatumGetTextPP(path_elems[level]); + } + + /* empty object is a special case for create */ + if ((npairs == 0) && (op_type & JB_PATH_CREATE_OR_INSERT) && + (level == path_len - 1)) + { + JsonbValue newkey; + + newkey.type = jbvString; + newkey.val.string.val = VARDATA_ANY(pathelem); + newkey.val.string.len = VARSIZE_ANY_EXHDR(pathelem); + + (void) pushJsonbValue(st, WJB_KEY, &newkey); + (void) pushJsonbValue(st, WJB_VALUE, newval); + } + + for (i = 0; i < npairs; i++) + { + JsonbIteratorToken r = JsonbIteratorNext(it, &k, true); + + Assert(r == WJB_KEY); + + if (!done && + k.val.string.len == VARSIZE_ANY_EXHDR(pathelem) && + memcmp(k.val.string.val, VARDATA_ANY(pathelem), + k.val.string.len) == 0) + { + done = true; + + if (level == path_len - 1) + { + /* + * called from jsonb_insert(), it forbids redefining an + * existing value + */ + if (op_type & (JB_PATH_INSERT_BEFORE | JB_PATH_INSERT_AFTER)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot replace existing key"), + errhint("Try using the function jsonb_set " + "to replace key value."))); + + r = JsonbIteratorNext(it, &v, true); /* skip value */ + if (!(op_type & JB_PATH_DELETE)) + { + (void) pushJsonbValue(st, WJB_KEY, &k); + (void) pushJsonbValue(st, WJB_VALUE, newval); + } + } + else + { + (void) pushJsonbValue(st, r, &k); + setPath(it, path_elems, path_nulls, path_len, + st, level + 1, newval, op_type); + } + } + else + { + if ((op_type & JB_PATH_CREATE_OR_INSERT) && !done && + level == path_len - 1 && i == npairs - 1) + { + JsonbValue newkey; + + newkey.type = jbvString; + newkey.val.string.val = VARDATA_ANY(pathelem); + newkey.val.string.len = VARSIZE_ANY_EXHDR(pathelem); + + (void) pushJsonbValue(st, WJB_KEY, &newkey); + (void) pushJsonbValue(st, WJB_VALUE, newval); + } + + (void) pushJsonbValue(st, r, &k); + r = JsonbIteratorNext(it, &v, false); + (void) pushJsonbValue(st, r, r < WJB_BEGIN_ARRAY ? &v : NULL); + if (r == WJB_BEGIN_ARRAY || r == WJB_BEGIN_OBJECT) + { + int walking_level = 1; + + while (walking_level != 0) + { + r = JsonbIteratorNext(it, &v, false); + + if (r == WJB_BEGIN_ARRAY || r == WJB_BEGIN_OBJECT) + ++walking_level; + if (r == WJB_END_ARRAY || r == WJB_END_OBJECT) + --walking_level; + + (void) pushJsonbValue(st, r, r < WJB_BEGIN_ARRAY ? &v : NULL); + } + } + } + } + + /*-- + * If we got here there are only few possibilities: + * - no target path was found, and an open object with some keys/values was + * pushed into the state + * - an object is empty, only WJB_BEGIN_OBJECT is pushed + * + * In both cases if instructed to create the path when not present, + * generate the whole chain of empty objects and insert the new value + * there. + */ + if (!done && (op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1)) + { + JsonbValue newkey; + + newkey.type = jbvString; + newkey.val.string.val = VARDATA_ANY(pathelem); + newkey.val.string.len = VARSIZE_ANY_EXHDR(pathelem); + + (void) pushJsonbValue(st, WJB_KEY, &newkey); + (void) push_path(st, level, path_elems, path_nulls, + path_len, newval); + + /* Result is closed with WJB_END_OBJECT outside of this function */ + } +} + +/* + * Array walker for setPath + */ +static void +setPathArray(JsonbIterator **it, Datum *path_elems, bool *path_nulls, + int path_len, JsonbParseState **st, int level, + JsonbValue *newval, uint32 nelems, int op_type) +{ + JsonbValue v; + int idx, + i; + bool done = false; + + /* pick correct index */ + if (level < path_len && !path_nulls[level]) + { + char *c = TextDatumGetCString(path_elems[level]); + char *badp; + + errno = 0; + idx = strtoint(c, &badp, 10); + if (badp == c || *badp != '\0' || errno != 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("path element at position %d is not an integer: \"%s\"", + level + 1, c))); + } + else + idx = nelems; + + if (idx < 0) + { + if (-idx > nelems) + { + /* + * If asked to keep elements position consistent, it's not allowed + * to prepend the array. + */ + if (op_type & JB_PATH_CONSISTENT_POSITION) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("path element at position %d is out of range: %d", + level + 1, idx))); + else + idx = INT_MIN; + } + else + idx = nelems + idx; + } + + /* + * Filling the gaps means there are no limits on the positive index are + * imposed, we can set any element. Otherwise limit the index by nelems. + */ + if (!(op_type & JB_PATH_FILL_GAPS)) + { + if (idx > 0 && idx > nelems) + idx = nelems; + } + + /* + * if we're creating, and idx == INT_MIN, we prepend the new value to the + * array also if the array is empty - in which case we don't really care + * what the idx value is + */ + if ((idx == INT_MIN || nelems == 0) && (level == path_len - 1) && + (op_type & JB_PATH_CREATE_OR_INSERT)) + { + Assert(newval != NULL); + + if (op_type & JB_PATH_FILL_GAPS && nelems == 0 && idx > 0) + push_null_elements(st, idx); + + (void) pushJsonbValue(st, WJB_ELEM, newval); + + done = true; + } + + /* iterate over the array elements */ + for (i = 0; i < nelems; i++) + { + JsonbIteratorToken r; + + if (i == idx && level < path_len) + { + done = true; + + if (level == path_len - 1) + { + r = JsonbIteratorNext(it, &v, true); /* skip */ + + if (op_type & (JB_PATH_INSERT_BEFORE | JB_PATH_CREATE)) + (void) pushJsonbValue(st, WJB_ELEM, newval); + + /* + * We should keep current value only in case of + * JB_PATH_INSERT_BEFORE or JB_PATH_INSERT_AFTER because + * otherwise it should be deleted or replaced + */ + if (op_type & (JB_PATH_INSERT_AFTER | JB_PATH_INSERT_BEFORE)) + (void) pushJsonbValue(st, r, &v); + + if (op_type & (JB_PATH_INSERT_AFTER | JB_PATH_REPLACE)) + (void) pushJsonbValue(st, WJB_ELEM, newval); + } + else + (void) setPath(it, path_elems, path_nulls, path_len, + st, level + 1, newval, op_type); + } + else + { + r = JsonbIteratorNext(it, &v, false); + + (void) pushJsonbValue(st, r, r < WJB_BEGIN_ARRAY ? &v : NULL); + + if (r == WJB_BEGIN_ARRAY || r == WJB_BEGIN_OBJECT) + { + int walking_level = 1; + + while (walking_level != 0) + { + r = JsonbIteratorNext(it, &v, false); + + if (r == WJB_BEGIN_ARRAY || r == WJB_BEGIN_OBJECT) + ++walking_level; + if (r == WJB_END_ARRAY || r == WJB_END_OBJECT) + --walking_level; + + (void) pushJsonbValue(st, r, r < WJB_BEGIN_ARRAY ? &v : NULL); + } + } + } + } + + if ((op_type & JB_PATH_CREATE_OR_INSERT) && !done && level == path_len - 1) + { + /* + * If asked to fill the gaps, idx could be bigger than nelems, so + * prepend the new element with nulls if that's the case. + */ + if (op_type & JB_PATH_FILL_GAPS && idx > nelems) + push_null_elements(st, idx - nelems); + + (void) pushJsonbValue(st, WJB_ELEM, newval); + done = true; + } + + /*-- + * If we got here there are only few possibilities: + * - no target path was found, and an open array with some keys/values was + * pushed into the state + * - an array is empty, only WJB_BEGIN_ARRAY is pushed + * + * In both cases if instructed to create the path when not present, + * generate the whole chain of empty objects and insert the new value + * there. + */ + if (!done && (op_type & JB_PATH_FILL_GAPS) && (level < path_len - 1)) + { + if (idx > 0) + push_null_elements(st, idx - nelems); + + (void) push_path(st, level, path_elems, path_nulls, + path_len, newval); + + /* Result is closed with WJB_END_OBJECT outside of this function */ + } +} + +/* + * Parse information about what elements of a jsonb document we want to iterate + * in functions iterate_json(b)_values. This information is presented in jsonb + * format, so that it can be easily extended in the future. + */ +uint32 +parse_jsonb_index_flags(Jsonb *jb) +{ + JsonbIterator *it; + JsonbValue v; + JsonbIteratorToken type; + uint32 flags = 0; + + it = JsonbIteratorInit(&jb->root); + + type = JsonbIteratorNext(&it, &v, false); + + /* + * We iterate over array (scalar internally is represented as array, so, + * we will accept it too) to check all its elements. Flag names are + * chosen the same as jsonb_typeof uses. + */ + if (type != WJB_BEGIN_ARRAY) + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("wrong flag type, only arrays and scalars are allowed"))); + + while ((type = JsonbIteratorNext(&it, &v, false)) == WJB_ELEM) + { + if (v.type != jbvString) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("flag array element is not a string"), + errhint("Possible values are: \"string\", \"numeric\", \"boolean\", \"key\", and \"all\"."))); + + if (v.val.string.len == 3 && + pg_strncasecmp(v.val.string.val, "all", 3) == 0) + flags |= jtiAll; + else if (v.val.string.len == 3 && + pg_strncasecmp(v.val.string.val, "key", 3) == 0) + flags |= jtiKey; + else if (v.val.string.len == 6 && + pg_strncasecmp(v.val.string.val, "string", 6) == 0) + flags |= jtiString; + else if (v.val.string.len == 7 && + pg_strncasecmp(v.val.string.val, "numeric", 7) == 0) + flags |= jtiNumeric; + else if (v.val.string.len == 7 && + pg_strncasecmp(v.val.string.val, "boolean", 7) == 0) + flags |= jtiBool; + else + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("wrong flag in flag array: \"%s\"", + pnstrdup(v.val.string.val, v.val.string.len)), + errhint("Possible values are: \"string\", \"numeric\", \"boolean\", \"key\", and \"all\"."))); + } + + /* expect end of array now */ + if (type != WJB_END_ARRAY) + elog(ERROR, "unexpected end of flag array"); + + /* get final WJB_DONE and free iterator */ + type = JsonbIteratorNext(&it, &v, false); + if (type != WJB_DONE) + elog(ERROR, "unexpected end of flag array"); + + return flags; +} + +/* + * Iterate over jsonb values or elements, specified by flags, and pass them + * together with an iteration state to a specified JsonIterateStringValuesAction. + */ +void +iterate_jsonb_values(Jsonb *jb, uint32 flags, void *state, + JsonIterateStringValuesAction action) +{ + JsonbIterator *it; + JsonbValue v; + JsonbIteratorToken type; + + it = JsonbIteratorInit(&jb->root); + + /* + * Just recursively iterating over jsonb and call callback on all + * corresponding elements + */ + while ((type = JsonbIteratorNext(&it, &v, false)) != WJB_DONE) + { + if (type == WJB_KEY) + { + if (flags & jtiKey) + action(state, v.val.string.val, v.val.string.len); + + continue; + } + else if (!(type == WJB_VALUE || type == WJB_ELEM)) + { + /* do not call callback for composite JsonbValue */ + continue; + } + + /* JsonbValue is a value of object or element of array */ + switch (v.type) + { + case jbvString: + if (flags & jtiString) + action(state, v.val.string.val, v.val.string.len); + break; + case jbvNumeric: + if (flags & jtiNumeric) + { + char *val; + + val = DatumGetCString(DirectFunctionCall1(numeric_out, + NumericGetDatum(v.val.numeric))); + + action(state, val, strlen(val)); + pfree(val); + } + break; + case jbvBool: + if (flags & jtiBool) + { + if (v.val.boolean) + action(state, "true", 4); + else + action(state, "false", 5); + } + break; + default: + /* do not call callback for composite JsonbValue */ + break; + } + } +} + +/* + * Iterate over json values and elements, specified by flags, and pass them + * together with an iteration state to a specified JsonIterateStringValuesAction. + */ +void +iterate_json_values(text *json, uint32 flags, void *action_state, + JsonIterateStringValuesAction action) +{ + JsonLexContext *lex = makeJsonLexContext(json, true); + JsonSemAction *sem = palloc0(sizeof(JsonSemAction)); + IterateJsonStringValuesState *state = palloc0(sizeof(IterateJsonStringValuesState)); + + state->lex = lex; + state->action = action; + state->action_state = action_state; + state->flags = flags; + + sem->semstate = (void *) state; + sem->scalar = iterate_values_scalar; + sem->object_field_start = iterate_values_object_field_start; + + pg_parse_json_or_ereport(lex, sem); +} + +/* + * An auxiliary function for iterate_json_values to invoke a specified + * JsonIterateStringValuesAction for specified values. + */ +static JsonParseErrorType +iterate_values_scalar(void *state, char *token, JsonTokenType tokentype) +{ + IterateJsonStringValuesState *_state = (IterateJsonStringValuesState *) state; + + switch (tokentype) + { + case JSON_TOKEN_STRING: + if (_state->flags & jtiString) + _state->action(_state->action_state, token, strlen(token)); + break; + case JSON_TOKEN_NUMBER: + if (_state->flags & jtiNumeric) + _state->action(_state->action_state, token, strlen(token)); + break; + case JSON_TOKEN_TRUE: + case JSON_TOKEN_FALSE: + if (_state->flags & jtiBool) + _state->action(_state->action_state, token, strlen(token)); + break; + default: + /* do not call callback for any other token */ + break; + } + + return JSON_SUCCESS; +} + +static JsonParseErrorType +iterate_values_object_field_start(void *state, char *fname, bool isnull) +{ + IterateJsonStringValuesState *_state = (IterateJsonStringValuesState *) state; + + if (_state->flags & jtiKey) + { + char *val = pstrdup(fname); + + _state->action(_state->action_state, val, strlen(val)); + } + + return JSON_SUCCESS; +} + +/* + * Iterate over a jsonb, and apply a specified JsonTransformStringValuesAction + * to every string value or element. Any necessary context for a + * JsonTransformStringValuesAction can be passed in the action_state variable. + * Function returns a copy of an original jsonb object with transformed values. + */ +Jsonb * +transform_jsonb_string_values(Jsonb *jsonb, void *action_state, + JsonTransformStringValuesAction transform_action) +{ + JsonbIterator *it; + JsonbValue v, + *res = NULL; + JsonbIteratorToken type; + JsonbParseState *st = NULL; + text *out; + bool is_scalar = false; + + it = JsonbIteratorInit(&jsonb->root); + is_scalar = it->isScalar; + + while ((type = JsonbIteratorNext(&it, &v, false)) != WJB_DONE) + { + if ((type == WJB_VALUE || type == WJB_ELEM) && v.type == jbvString) + { + out = transform_action(action_state, v.val.string.val, v.val.string.len); + /* out is probably not toasted, but let's be sure */ + out = pg_detoast_datum_packed(out); + v.val.string.val = VARDATA_ANY(out); + v.val.string.len = VARSIZE_ANY_EXHDR(out); + res = pushJsonbValue(&st, type, type < WJB_BEGIN_ARRAY ? &v : NULL); + } + else + { + res = pushJsonbValue(&st, type, (type == WJB_KEY || + type == WJB_VALUE || + type == WJB_ELEM) ? &v : NULL); + } + } + + if (res->type == jbvArray) + res->val.array.rawScalar = is_scalar; + + return JsonbValueToJsonb(res); +} + +/* + * Iterate over a json, and apply a specified JsonTransformStringValuesAction + * to every string value or element. Any necessary context for a + * JsonTransformStringValuesAction can be passed in the action_state variable. + * Function returns a StringInfo, which is a copy of an original json with + * transformed values. + */ +text * +transform_json_string_values(text *json, void *action_state, + JsonTransformStringValuesAction transform_action) +{ + JsonLexContext *lex = makeJsonLexContext(json, true); + JsonSemAction *sem = palloc0(sizeof(JsonSemAction)); + TransformJsonStringValuesState *state = palloc0(sizeof(TransformJsonStringValuesState)); + + state->lex = lex; + state->strval = makeStringInfo(); + state->action = transform_action; + state->action_state = action_state; + + sem->semstate = (void *) state; + sem->object_start = transform_string_values_object_start; + sem->object_end = transform_string_values_object_end; + sem->array_start = transform_string_values_array_start; + sem->array_end = transform_string_values_array_end; + sem->scalar = transform_string_values_scalar; + sem->array_element_start = transform_string_values_array_element_start; + sem->object_field_start = transform_string_values_object_field_start; + + pg_parse_json_or_ereport(lex, sem); + + return cstring_to_text_with_len(state->strval->data, state->strval->len); +} + +/* + * Set of auxiliary functions for transform_json_string_values to invoke a + * specified JsonTransformStringValuesAction for all values and left everything + * else untouched. + */ +static JsonParseErrorType +transform_string_values_object_start(void *state) +{ + TransformJsonStringValuesState *_state = (TransformJsonStringValuesState *) state; + + appendStringInfoCharMacro(_state->strval, '{'); + + return JSON_SUCCESS; +} + +static JsonParseErrorType +transform_string_values_object_end(void *state) +{ + TransformJsonStringValuesState *_state = (TransformJsonStringValuesState *) state; + + appendStringInfoCharMacro(_state->strval, '}'); + + return JSON_SUCCESS; +} + +static JsonParseErrorType +transform_string_values_array_start(void *state) +{ + TransformJsonStringValuesState *_state = (TransformJsonStringValuesState *) state; + + appendStringInfoCharMacro(_state->strval, '['); + + return JSON_SUCCESS; +} + +static JsonParseErrorType +transform_string_values_array_end(void *state) +{ + TransformJsonStringValuesState *_state = (TransformJsonStringValuesState *) state; + + appendStringInfoCharMacro(_state->strval, ']'); + + return JSON_SUCCESS; +} + +static JsonParseErrorType +transform_string_values_object_field_start(void *state, char *fname, bool isnull) +{ + TransformJsonStringValuesState *_state = (TransformJsonStringValuesState *) state; + + if (_state->strval->data[_state->strval->len - 1] != '{') + appendStringInfoCharMacro(_state->strval, ','); + + /* + * Unfortunately we don't have the quoted and escaped string any more, so + * we have to re-escape it. + */ + escape_json(_state->strval, fname); + appendStringInfoCharMacro(_state->strval, ':'); + + return JSON_SUCCESS; +} + +static JsonParseErrorType +transform_string_values_array_element_start(void *state, bool isnull) +{ + TransformJsonStringValuesState *_state = (TransformJsonStringValuesState *) state; + + if (_state->strval->data[_state->strval->len - 1] != '[') + appendStringInfoCharMacro(_state->strval, ','); + + return JSON_SUCCESS; +} + +static JsonParseErrorType +transform_string_values_scalar(void *state, char *token, JsonTokenType tokentype) +{ + TransformJsonStringValuesState *_state = (TransformJsonStringValuesState *) state; + + if (tokentype == JSON_TOKEN_STRING) + { + text *out = _state->action(_state->action_state, token, strlen(token)); + + escape_json(_state->strval, text_to_cstring(out)); + } + else + appendStringInfoString(_state->strval, token); + + return JSON_SUCCESS; +} + +JsonTokenType +json_get_first_token(text *json, bool throw_error) +{ + JsonLexContext *lex; + JsonParseErrorType result; + + lex = makeJsonLexContext(json, false); + + /* Lex exactly one token from the input and check its type. */ + result = json_lex(lex); + + if (result == JSON_SUCCESS) + return lex->token_type; + + if (throw_error) + json_errsave_error(result, lex, NULL); + + return JSON_TOKEN_INVALID; /* invalid json */ +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/jsonpath.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/jsonpath.c new file mode 100644 index 00000000000..c5ba3b7f1d0 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/jsonpath.c @@ -0,0 +1,1112 @@ +/*------------------------------------------------------------------------- + * + * jsonpath.c + * Input/output and supporting routines for jsonpath + * + * jsonpath expression is a chain of path items. First path item is $, $var, + * literal or arithmetic expression. Subsequent path items are accessors + * (.key, .*, [subscripts], [*]), filters (? (predicate)) and methods (.type(), + * .size() etc). + * + * For instance, structure of path items for simple expression: + * + * $.a[*].type() + * + * is pretty evident: + * + * $ => .a => [*] => .type() + * + * Some path items such as arithmetic operations, predicates or array + * subscripts may comprise subtrees. For instance, more complex expression + * + * ($.a + $[1 to 5, 7] ? (@ > 3).double()).type() + * + * have following structure of path items: + * + * + => .type() + * ___/ \___ + * / \ + * $ => .a $ => [] => ? => .double() + * _||_ | + * / \ > + * to to / \ + * / \ / @ 3 + * 1 5 7 + * + * Binary encoding of jsonpath constitutes a sequence of 4-bytes aligned + * variable-length path items connected by links. Every item has a header + * consisting of item type (enum JsonPathItemType) and offset of next item + * (zero means no next item). After the header, item may have payload + * depending on item type. For instance, payload of '.key' accessor item is + * length of key name and key name itself. Payload of '>' arithmetic operator + * item is offsets of right and left operands. + * + * So, binary representation of sample expression above is: + * (bottom arrows are next links, top lines are argument links) + * + * _____ + * _____ ___/____ \ __ + * _ /_ \ _____/__/____ \ \ __ _ /_ \ + * / / \ \ / / / \ \ \ / \ / / \ \ + * +(LR) $ .a $ [](* to *, * to *) 1 5 7 ?(A) >(LR) @ 3 .double() .type() + * | | ^ | ^| ^| ^ ^ + * | |__| |__||________________________||___________________| | + * |_______________________________________________________________________| + * + * Copyright (c) 2019-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/utils/adt/jsonpath.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "funcapi.h" +#include "lib/stringinfo.h" +#include "libpq/pqformat.h" +#include "nodes/miscnodes.h" +#include "miscadmin.h" +#include "utils/builtins.h" +#include "utils/json.h" +#include "utils/jsonpath.h" + + +static Datum jsonPathFromCstring(char *in, int len, struct Node *escontext); +static char *jsonPathToCstring(StringInfo out, JsonPath *in, + int estimated_len); +static bool flattenJsonPathParseItem(StringInfo buf, int *result, + struct Node *escontext, + JsonPathParseItem *item, + int nestingLevel, bool insideArraySubscript); +static void alignStringInfoInt(StringInfo buf); +static int32 reserveSpaceForItemPointer(StringInfo buf); +static void printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, + bool printBracketes); +static int operationPriority(JsonPathItemType op); + + +/**************************** INPUT/OUTPUT ********************************/ + +/* + * jsonpath type input function + */ +Datum +jsonpath_in(PG_FUNCTION_ARGS) +{ + char *in = PG_GETARG_CSTRING(0); + int len = strlen(in); + + return jsonPathFromCstring(in, len, fcinfo->context); +} + +/* + * jsonpath type recv function + * + * The type is sent as text in binary mode, so this is almost the same + * as the input function, but it's prefixed with a version number so we + * can change the binary format sent in future if necessary. For now, + * only version 1 is supported. + */ +Datum +jsonpath_recv(PG_FUNCTION_ARGS) +{ + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + int version = pq_getmsgint(buf, 1); + char *str; + int nbytes; + + if (version == JSONPATH_VERSION) + str = pq_getmsgtext(buf, buf->len - buf->cursor, &nbytes); + else + elog(ERROR, "unsupported jsonpath version number: %d", version); + + return jsonPathFromCstring(str, nbytes, NULL); +} + +/* + * jsonpath type output function + */ +Datum +jsonpath_out(PG_FUNCTION_ARGS) +{ + JsonPath *in = PG_GETARG_JSONPATH_P(0); + + PG_RETURN_CSTRING(jsonPathToCstring(NULL, in, VARSIZE(in))); +} + +/* + * jsonpath type send function + * + * Just send jsonpath as a version number, then a string of text + */ +Datum +jsonpath_send(PG_FUNCTION_ARGS) +{ + JsonPath *in = PG_GETARG_JSONPATH_P(0); + StringInfoData buf; + StringInfoData jtext; + int version = JSONPATH_VERSION; + + initStringInfo(&jtext); + (void) jsonPathToCstring(&jtext, in, VARSIZE(in)); + + pq_begintypsend(&buf); + pq_sendint8(&buf, version); + pq_sendtext(&buf, jtext.data, jtext.len); + pfree(jtext.data); + + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} + +/* + * Converts C-string to a jsonpath value. + * + * Uses jsonpath parser to turn string into an AST, then + * flattenJsonPathParseItem() does second pass turning AST into binary + * representation of jsonpath. + */ +static Datum +jsonPathFromCstring(char *in, int len, struct Node *escontext) +{ + JsonPathParseResult *jsonpath = parsejsonpath(in, len, escontext); + JsonPath *res; + StringInfoData buf; + + if (SOFT_ERROR_OCCURRED(escontext)) + return (Datum) 0; + + if (!jsonpath) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s: \"%s\"", "jsonpath", + in))); + + initStringInfo(&buf); + enlargeStringInfo(&buf, 4 * len /* estimation */ ); + + appendStringInfoSpaces(&buf, JSONPATH_HDRSZ); + + if (!flattenJsonPathParseItem(&buf, NULL, escontext, + jsonpath->expr, 0, false)) + return (Datum) 0; + + res = (JsonPath *) buf.data; + SET_VARSIZE(res, buf.len); + res->header = JSONPATH_VERSION; + if (jsonpath->lax) + res->header |= JSONPATH_LAX; + + PG_RETURN_JSONPATH_P(res); +} + +/* + * Converts jsonpath value to a C-string. + * + * If 'out' argument is non-null, the resulting C-string is stored inside the + * StringBuffer. The resulting string is always returned. + */ +static char * +jsonPathToCstring(StringInfo out, JsonPath *in, int estimated_len) +{ + StringInfoData buf; + JsonPathItem v; + + if (!out) + { + out = &buf; + initStringInfo(out); + } + enlargeStringInfo(out, estimated_len); + + if (!(in->header & JSONPATH_LAX)) + appendStringInfoString(out, "strict "); + + jspInit(&v, in); + printJsonPathItem(out, &v, false, true); + + return out->data; +} + +/* + * Recursive function converting given jsonpath parse item and all its + * children into a binary representation. + */ +static bool +flattenJsonPathParseItem(StringInfo buf, int *result, struct Node *escontext, + JsonPathParseItem *item, int nestingLevel, + bool insideArraySubscript) +{ + /* position from beginning of jsonpath data */ + int32 pos = buf->len - JSONPATH_HDRSZ; + int32 chld; + int32 next; + int argNestingLevel = 0; + + check_stack_depth(); + CHECK_FOR_INTERRUPTS(); + + appendStringInfoChar(buf, (char) (item->type)); + + /* + * We align buffer to int32 because a series of int32 values often goes + * after the header, and we want to read them directly by dereferencing + * int32 pointer (see jspInitByBuffer()). + */ + alignStringInfoInt(buf); + + /* + * Reserve space for next item pointer. Actual value will be recorded + * later, after next and children items processing. + */ + next = reserveSpaceForItemPointer(buf); + + switch (item->type) + { + case jpiString: + case jpiVariable: + case jpiKey: + appendBinaryStringInfo(buf, &item->value.string.len, + sizeof(item->value.string.len)); + appendBinaryStringInfo(buf, item->value.string.val, + item->value.string.len); + appendStringInfoChar(buf, '\0'); + break; + case jpiNumeric: + appendBinaryStringInfo(buf, item->value.numeric, + VARSIZE(item->value.numeric)); + break; + case jpiBool: + appendBinaryStringInfo(buf, &item->value.boolean, + sizeof(item->value.boolean)); + break; + case jpiAnd: + case jpiOr: + case jpiEqual: + case jpiNotEqual: + case jpiLess: + case jpiGreater: + case jpiLessOrEqual: + case jpiGreaterOrEqual: + case jpiAdd: + case jpiSub: + case jpiMul: + case jpiDiv: + case jpiMod: + case jpiStartsWith: + { + /* + * First, reserve place for left/right arg's positions, then + * record both args and sets actual position in reserved + * places. + */ + int32 left = reserveSpaceForItemPointer(buf); + int32 right = reserveSpaceForItemPointer(buf); + + if (!item->value.args.left) + chld = pos; + else if (!flattenJsonPathParseItem(buf, &chld, escontext, + item->value.args.left, + nestingLevel + argNestingLevel, + insideArraySubscript)) + return false; + *(int32 *) (buf->data + left) = chld - pos; + + if (!item->value.args.right) + chld = pos; + else if (!flattenJsonPathParseItem(buf, &chld, escontext, + item->value.args.right, + nestingLevel + argNestingLevel, + insideArraySubscript)) + return false; + *(int32 *) (buf->data + right) = chld - pos; + } + break; + case jpiLikeRegex: + { + int32 offs; + + appendBinaryStringInfo(buf, + &item->value.like_regex.flags, + sizeof(item->value.like_regex.flags)); + offs = reserveSpaceForItemPointer(buf); + appendBinaryStringInfo(buf, + &item->value.like_regex.patternlen, + sizeof(item->value.like_regex.patternlen)); + appendBinaryStringInfo(buf, item->value.like_regex.pattern, + item->value.like_regex.patternlen); + appendStringInfoChar(buf, '\0'); + + if (!flattenJsonPathParseItem(buf, &chld, escontext, + item->value.like_regex.expr, + nestingLevel, + insideArraySubscript)) + return false; + *(int32 *) (buf->data + offs) = chld - pos; + } + break; + case jpiFilter: + argNestingLevel++; + /* FALLTHROUGH */ + case jpiIsUnknown: + case jpiNot: + case jpiPlus: + case jpiMinus: + case jpiExists: + case jpiDatetime: + { + int32 arg = reserveSpaceForItemPointer(buf); + + if (!item->value.arg) + chld = pos; + else if (!flattenJsonPathParseItem(buf, &chld, escontext, + item->value.arg, + nestingLevel + argNestingLevel, + insideArraySubscript)) + return false; + *(int32 *) (buf->data + arg) = chld - pos; + } + break; + case jpiNull: + break; + case jpiRoot: + break; + case jpiAnyArray: + case jpiAnyKey: + break; + case jpiCurrent: + if (nestingLevel <= 0) + ereturn(escontext, false, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("@ is not allowed in root expressions"))); + break; + case jpiLast: + if (!insideArraySubscript) + ereturn(escontext, false, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("LAST is allowed only in array subscripts"))); + break; + case jpiIndexArray: + { + int32 nelems = item->value.array.nelems; + int offset; + int i; + + appendBinaryStringInfo(buf, &nelems, sizeof(nelems)); + + offset = buf->len; + + appendStringInfoSpaces(buf, sizeof(int32) * 2 * nelems); + + for (i = 0; i < nelems; i++) + { + int32 *ppos; + int32 topos; + int32 frompos; + + if (!flattenJsonPathParseItem(buf, &frompos, escontext, + item->value.array.elems[i].from, + nestingLevel, true)) + return false; + frompos -= pos; + + if (item->value.array.elems[i].to) + { + if (!flattenJsonPathParseItem(buf, &topos, escontext, + item->value.array.elems[i].to, + nestingLevel, true)) + return false; + topos -= pos; + } + else + topos = 0; + + ppos = (int32 *) &buf->data[offset + i * 2 * sizeof(int32)]; + + ppos[0] = frompos; + ppos[1] = topos; + } + } + break; + case jpiAny: + appendBinaryStringInfo(buf, + &item->value.anybounds.first, + sizeof(item->value.anybounds.first)); + appendBinaryStringInfo(buf, + &item->value.anybounds.last, + sizeof(item->value.anybounds.last)); + break; + case jpiType: + case jpiSize: + case jpiAbs: + case jpiFloor: + case jpiCeiling: + case jpiDouble: + case jpiKeyValue: + break; + default: + elog(ERROR, "unrecognized jsonpath item type: %d", item->type); + } + + if (item->next) + { + if (!flattenJsonPathParseItem(buf, &chld, escontext, + item->next, nestingLevel, + insideArraySubscript)) + return false; + chld -= pos; + *(int32 *) (buf->data + next) = chld; + } + + if (result) + *result = pos; + return true; +} + +/* + * Align StringInfo to int by adding zero padding bytes + */ +static void +alignStringInfoInt(StringInfo buf) +{ + switch (INTALIGN(buf->len) - buf->len) + { + case 3: + appendStringInfoCharMacro(buf, 0); + /* FALLTHROUGH */ + case 2: + appendStringInfoCharMacro(buf, 0); + /* FALLTHROUGH */ + case 1: + appendStringInfoCharMacro(buf, 0); + /* FALLTHROUGH */ + default: + break; + } +} + +/* + * Reserve space for int32 JsonPathItem pointer. Now zero pointer is written, + * actual value will be recorded at '(int32 *) &buf->data[pos]' later. + */ +static int32 +reserveSpaceForItemPointer(StringInfo buf) +{ + int32 pos = buf->len; + int32 ptr = 0; + + appendBinaryStringInfo(buf, &ptr, sizeof(ptr)); + + return pos; +} + +/* + * Prints text representation of given jsonpath item and all its children. + */ +static void +printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, + bool printBracketes) +{ + JsonPathItem elem; + int i; + + check_stack_depth(); + CHECK_FOR_INTERRUPTS(); + + switch (v->type) + { + case jpiNull: + appendStringInfoString(buf, "null"); + break; + case jpiKey: + if (inKey) + appendStringInfoChar(buf, '.'); + escape_json(buf, jspGetString(v, NULL)); + break; + case jpiString: + escape_json(buf, jspGetString(v, NULL)); + break; + case jpiVariable: + appendStringInfoChar(buf, '$'); + escape_json(buf, jspGetString(v, NULL)); + break; + case jpiNumeric: + if (jspHasNext(v)) + appendStringInfoChar(buf, '('); + appendStringInfoString(buf, + DatumGetCString(DirectFunctionCall1(numeric_out, + NumericGetDatum(jspGetNumeric(v))))); + if (jspHasNext(v)) + appendStringInfoChar(buf, ')'); + break; + case jpiBool: + if (jspGetBool(v)) + appendStringInfoString(buf, "true"); + else + appendStringInfoString(buf, "false"); + break; + case jpiAnd: + case jpiOr: + case jpiEqual: + case jpiNotEqual: + case jpiLess: + case jpiGreater: + case jpiLessOrEqual: + case jpiGreaterOrEqual: + case jpiAdd: + case jpiSub: + case jpiMul: + case jpiDiv: + case jpiMod: + case jpiStartsWith: + if (printBracketes) + appendStringInfoChar(buf, '('); + jspGetLeftArg(v, &elem); + printJsonPathItem(buf, &elem, false, + operationPriority(elem.type) <= + operationPriority(v->type)); + appendStringInfoChar(buf, ' '); + appendStringInfoString(buf, jspOperationName(v->type)); + appendStringInfoChar(buf, ' '); + jspGetRightArg(v, &elem); + printJsonPathItem(buf, &elem, false, + operationPriority(elem.type) <= + operationPriority(v->type)); + if (printBracketes) + appendStringInfoChar(buf, ')'); + break; + case jpiLikeRegex: + if (printBracketes) + appendStringInfoChar(buf, '('); + + jspInitByBuffer(&elem, v->base, v->content.like_regex.expr); + printJsonPathItem(buf, &elem, false, + operationPriority(elem.type) <= + operationPriority(v->type)); + + appendStringInfoString(buf, " like_regex "); + + escape_json(buf, v->content.like_regex.pattern); + + if (v->content.like_regex.flags) + { + appendStringInfoString(buf, " flag \""); + + if (v->content.like_regex.flags & JSP_REGEX_ICASE) + appendStringInfoChar(buf, 'i'); + if (v->content.like_regex.flags & JSP_REGEX_DOTALL) + appendStringInfoChar(buf, 's'); + if (v->content.like_regex.flags & JSP_REGEX_MLINE) + appendStringInfoChar(buf, 'm'); + if (v->content.like_regex.flags & JSP_REGEX_WSPACE) + appendStringInfoChar(buf, 'x'); + if (v->content.like_regex.flags & JSP_REGEX_QUOTE) + appendStringInfoChar(buf, 'q'); + + appendStringInfoChar(buf, '"'); + } + + if (printBracketes) + appendStringInfoChar(buf, ')'); + break; + case jpiPlus: + case jpiMinus: + if (printBracketes) + appendStringInfoChar(buf, '('); + appendStringInfoChar(buf, v->type == jpiPlus ? '+' : '-'); + jspGetArg(v, &elem); + printJsonPathItem(buf, &elem, false, + operationPriority(elem.type) <= + operationPriority(v->type)); + if (printBracketes) + appendStringInfoChar(buf, ')'); + break; + case jpiFilter: + appendStringInfoString(buf, "?("); + jspGetArg(v, &elem); + printJsonPathItem(buf, &elem, false, false); + appendStringInfoChar(buf, ')'); + break; + case jpiNot: + appendStringInfoString(buf, "!("); + jspGetArg(v, &elem); + printJsonPathItem(buf, &elem, false, false); + appendStringInfoChar(buf, ')'); + break; + case jpiIsUnknown: + appendStringInfoChar(buf, '('); + jspGetArg(v, &elem); + printJsonPathItem(buf, &elem, false, false); + appendStringInfoString(buf, ") is unknown"); + break; + case jpiExists: + appendStringInfoString(buf, "exists ("); + jspGetArg(v, &elem); + printJsonPathItem(buf, &elem, false, false); + appendStringInfoChar(buf, ')'); + break; + case jpiCurrent: + Assert(!inKey); + appendStringInfoChar(buf, '@'); + break; + case jpiRoot: + Assert(!inKey); + appendStringInfoChar(buf, '$'); + break; + case jpiLast: + appendStringInfoString(buf, "last"); + break; + case jpiAnyArray: + appendStringInfoString(buf, "[*]"); + break; + case jpiAnyKey: + if (inKey) + appendStringInfoChar(buf, '.'); + appendStringInfoChar(buf, '*'); + break; + case jpiIndexArray: + appendStringInfoChar(buf, '['); + for (i = 0; i < v->content.array.nelems; i++) + { + JsonPathItem from; + JsonPathItem to; + bool range = jspGetArraySubscript(v, &from, &to, i); + + if (i) + appendStringInfoChar(buf, ','); + + printJsonPathItem(buf, &from, false, false); + + if (range) + { + appendStringInfoString(buf, " to "); + printJsonPathItem(buf, &to, false, false); + } + } + appendStringInfoChar(buf, ']'); + break; + case jpiAny: + if (inKey) + appendStringInfoChar(buf, '.'); + + if (v->content.anybounds.first == 0 && + v->content.anybounds.last == PG_UINT32_MAX) + appendStringInfoString(buf, "**"); + else if (v->content.anybounds.first == v->content.anybounds.last) + { + if (v->content.anybounds.first == PG_UINT32_MAX) + appendStringInfoString(buf, "**{last}"); + else + appendStringInfo(buf, "**{%u}", + v->content.anybounds.first); + } + else if (v->content.anybounds.first == PG_UINT32_MAX) + appendStringInfo(buf, "**{last to %u}", + v->content.anybounds.last); + else if (v->content.anybounds.last == PG_UINT32_MAX) + appendStringInfo(buf, "**{%u to last}", + v->content.anybounds.first); + else + appendStringInfo(buf, "**{%u to %u}", + v->content.anybounds.first, + v->content.anybounds.last); + break; + case jpiType: + appendStringInfoString(buf, ".type()"); + break; + case jpiSize: + appendStringInfoString(buf, ".size()"); + break; + case jpiAbs: + appendStringInfoString(buf, ".abs()"); + break; + case jpiFloor: + appendStringInfoString(buf, ".floor()"); + break; + case jpiCeiling: + appendStringInfoString(buf, ".ceiling()"); + break; + case jpiDouble: + appendStringInfoString(buf, ".double()"); + break; + case jpiDatetime: + appendStringInfoString(buf, ".datetime("); + if (v->content.arg) + { + jspGetArg(v, &elem); + printJsonPathItem(buf, &elem, false, false); + } + appendStringInfoChar(buf, ')'); + break; + case jpiKeyValue: + appendStringInfoString(buf, ".keyvalue()"); + break; + default: + elog(ERROR, "unrecognized jsonpath item type: %d", v->type); + } + + if (jspGetNext(v, &elem)) + printJsonPathItem(buf, &elem, true, true); +} + +const char * +jspOperationName(JsonPathItemType type) +{ + switch (type) + { + case jpiAnd: + return "&&"; + case jpiOr: + return "||"; + case jpiEqual: + return "=="; + case jpiNotEqual: + return "!="; + case jpiLess: + return "<"; + case jpiGreater: + return ">"; + case jpiLessOrEqual: + return "<="; + case jpiGreaterOrEqual: + return ">="; + case jpiPlus: + case jpiAdd: + return "+"; + case jpiMinus: + case jpiSub: + return "-"; + case jpiMul: + return "*"; + case jpiDiv: + return "/"; + case jpiMod: + return "%"; + case jpiStartsWith: + return "starts with"; + case jpiLikeRegex: + return "like_regex"; + case jpiType: + return "type"; + case jpiSize: + return "size"; + case jpiKeyValue: + return "keyvalue"; + case jpiDouble: + return "double"; + case jpiAbs: + return "abs"; + case jpiFloor: + return "floor"; + case jpiCeiling: + return "ceiling"; + case jpiDatetime: + return "datetime"; + default: + elog(ERROR, "unrecognized jsonpath item type: %d", type); + return NULL; + } +} + +static int +operationPriority(JsonPathItemType op) +{ + switch (op) + { + case jpiOr: + return 0; + case jpiAnd: + return 1; + case jpiEqual: + case jpiNotEqual: + case jpiLess: + case jpiGreater: + case jpiLessOrEqual: + case jpiGreaterOrEqual: + case jpiStartsWith: + return 2; + case jpiAdd: + case jpiSub: + return 3; + case jpiMul: + case jpiDiv: + case jpiMod: + return 4; + case jpiPlus: + case jpiMinus: + return 5; + default: + return 6; + } +} + +/******************* Support functions for JsonPath *************************/ + +/* + * Support macros to read stored values + */ + +#define read_byte(v, b, p) do { \ + (v) = *(uint8*)((b) + (p)); \ + (p) += 1; \ +} while(0) \ + +#define read_int32(v, b, p) do { \ + (v) = *(uint32*)((b) + (p)); \ + (p) += sizeof(int32); \ +} while(0) \ + +#define read_int32_n(v, b, p, n) do { \ + (v) = (void *)((b) + (p)); \ + (p) += sizeof(int32) * (n); \ +} while(0) \ + +/* + * Read root node and fill root node representation + */ +void +jspInit(JsonPathItem *v, JsonPath *js) +{ + Assert((js->header & ~JSONPATH_LAX) == JSONPATH_VERSION); + jspInitByBuffer(v, js->data, 0); +} + +/* + * Read node from buffer and fill its representation + */ +void +jspInitByBuffer(JsonPathItem *v, char *base, int32 pos) +{ + v->base = base + pos; + + read_byte(v->type, base, pos); + pos = INTALIGN((uintptr_t) (base + pos)) - (uintptr_t) base; + read_int32(v->nextPos, base, pos); + + switch (v->type) + { + case jpiNull: + case jpiRoot: + case jpiCurrent: + case jpiAnyArray: + case jpiAnyKey: + case jpiType: + case jpiSize: + case jpiAbs: + case jpiFloor: + case jpiCeiling: + case jpiDouble: + case jpiKeyValue: + case jpiLast: + break; + case jpiKey: + case jpiString: + case jpiVariable: + read_int32(v->content.value.datalen, base, pos); + /* FALLTHROUGH */ + case jpiNumeric: + case jpiBool: + v->content.value.data = base + pos; + break; + case jpiAnd: + case jpiOr: + case jpiAdd: + case jpiSub: + case jpiMul: + case jpiDiv: + case jpiMod: + case jpiEqual: + case jpiNotEqual: + case jpiLess: + case jpiGreater: + case jpiLessOrEqual: + case jpiGreaterOrEqual: + case jpiStartsWith: + read_int32(v->content.args.left, base, pos); + read_int32(v->content.args.right, base, pos); + break; + case jpiLikeRegex: + read_int32(v->content.like_regex.flags, base, pos); + read_int32(v->content.like_regex.expr, base, pos); + read_int32(v->content.like_regex.patternlen, base, pos); + v->content.like_regex.pattern = base + pos; + break; + case jpiNot: + case jpiExists: + case jpiIsUnknown: + case jpiPlus: + case jpiMinus: + case jpiFilter: + case jpiDatetime: + read_int32(v->content.arg, base, pos); + break; + case jpiIndexArray: + read_int32(v->content.array.nelems, base, pos); + read_int32_n(v->content.array.elems, base, pos, + v->content.array.nelems * 2); + break; + case jpiAny: + read_int32(v->content.anybounds.first, base, pos); + read_int32(v->content.anybounds.last, base, pos); + break; + default: + elog(ERROR, "unrecognized jsonpath item type: %d", v->type); + } +} + +void +jspGetArg(JsonPathItem *v, JsonPathItem *a) +{ + Assert(v->type == jpiFilter || + v->type == jpiNot || + v->type == jpiIsUnknown || + v->type == jpiExists || + v->type == jpiPlus || + v->type == jpiMinus || + v->type == jpiDatetime); + + jspInitByBuffer(a, v->base, v->content.arg); +} + +bool +jspGetNext(JsonPathItem *v, JsonPathItem *a) +{ + if (jspHasNext(v)) + { + Assert(v->type == jpiString || + v->type == jpiNumeric || + v->type == jpiBool || + v->type == jpiNull || + v->type == jpiKey || + v->type == jpiAny || + v->type == jpiAnyArray || + v->type == jpiAnyKey || + v->type == jpiIndexArray || + v->type == jpiFilter || + v->type == jpiCurrent || + v->type == jpiExists || + v->type == jpiRoot || + v->type == jpiVariable || + v->type == jpiLast || + v->type == jpiAdd || + v->type == jpiSub || + v->type == jpiMul || + v->type == jpiDiv || + v->type == jpiMod || + v->type == jpiPlus || + v->type == jpiMinus || + v->type == jpiEqual || + v->type == jpiNotEqual || + v->type == jpiGreater || + v->type == jpiGreaterOrEqual || + v->type == jpiLess || + v->type == jpiLessOrEqual || + v->type == jpiAnd || + v->type == jpiOr || + v->type == jpiNot || + v->type == jpiIsUnknown || + v->type == jpiType || + v->type == jpiSize || + v->type == jpiAbs || + v->type == jpiFloor || + v->type == jpiCeiling || + v->type == jpiDouble || + v->type == jpiDatetime || + v->type == jpiKeyValue || + v->type == jpiStartsWith || + v->type == jpiLikeRegex); + + if (a) + jspInitByBuffer(a, v->base, v->nextPos); + return true; + } + + return false; +} + +void +jspGetLeftArg(JsonPathItem *v, JsonPathItem *a) +{ + Assert(v->type == jpiAnd || + v->type == jpiOr || + v->type == jpiEqual || + v->type == jpiNotEqual || + v->type == jpiLess || + v->type == jpiGreater || + v->type == jpiLessOrEqual || + v->type == jpiGreaterOrEqual || + v->type == jpiAdd || + v->type == jpiSub || + v->type == jpiMul || + v->type == jpiDiv || + v->type == jpiMod || + v->type == jpiStartsWith); + + jspInitByBuffer(a, v->base, v->content.args.left); +} + +void +jspGetRightArg(JsonPathItem *v, JsonPathItem *a) +{ + Assert(v->type == jpiAnd || + v->type == jpiOr || + v->type == jpiEqual || + v->type == jpiNotEqual || + v->type == jpiLess || + v->type == jpiGreater || + v->type == jpiLessOrEqual || + v->type == jpiGreaterOrEqual || + v->type == jpiAdd || + v->type == jpiSub || + v->type == jpiMul || + v->type == jpiDiv || + v->type == jpiMod || + v->type == jpiStartsWith); + + jspInitByBuffer(a, v->base, v->content.args.right); +} + +bool +jspGetBool(JsonPathItem *v) +{ + Assert(v->type == jpiBool); + + return (bool) *v->content.value.data; +} + +Numeric +jspGetNumeric(JsonPathItem *v) +{ + Assert(v->type == jpiNumeric); + + return (Numeric) v->content.value.data; +} + +char * +jspGetString(JsonPathItem *v, int32 *len) +{ + Assert(v->type == jpiKey || + v->type == jpiString || + v->type == jpiVariable); + + if (len) + *len = v->content.value.datalen; + return v->content.value.data; +} + +bool +jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from, JsonPathItem *to, + int i) +{ + Assert(v->type == jpiIndexArray); + + jspInitByBuffer(from, v->base, v->content.array.elems[i].from); + + if (!v->content.array.elems[i].to) + return false; + + jspInitByBuffer(to, v->base, v->content.array.elems[i].to); + + return true; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/jsonpath_exec.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/jsonpath_exec.c new file mode 100644 index 00000000000..971979800bf --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/jsonpath_exec.c @@ -0,0 +1,2817 @@ +/*------------------------------------------------------------------------- + * + * jsonpath_exec.c + * Routines for SQL/JSON path execution. + * + * Jsonpath is executed in the global context stored in JsonPathExecContext, + * which is passed to almost every function involved into execution. Entry + * point for jsonpath execution is executeJsonPath() function, which + * initializes execution context including initial JsonPathItem and JsonbValue, + * flags, stack for calculation of @ in filters. + * + * The result of jsonpath query execution is enum JsonPathExecResult and + * if succeeded sequence of JsonbValue, written to JsonValueList *found, which + * is passed through the jsonpath items. When found == NULL, we're inside + * exists-query and we're interested only in whether result is empty. In this + * case execution is stopped once first result item is found, and the only + * execution result is JsonPathExecResult. The values of JsonPathExecResult + * are following: + * - jperOk -- result sequence is not empty + * - jperNotFound -- result sequence is empty + * - jperError -- error occurred during execution + * + * Jsonpath is executed recursively (see executeItem()) starting form the + * first path item (which in turn might be, for instance, an arithmetic + * expression evaluated separately). On each step single JsonbValue obtained + * from previous path item is processed. The result of processing is a + * sequence of JsonbValue (probably empty), which is passed to the next path + * item one by one. When there is no next path item, then JsonbValue is added + * to the 'found' list. When found == NULL, then execution functions just + * return jperOk (see executeNextItem()). + * + * Many of jsonpath operations require automatic unwrapping of arrays in lax + * mode. So, if input value is array, then corresponding operation is + * processed not on array itself, but on all of its members one by one. + * executeItemOptUnwrapTarget() function have 'unwrap' argument, which indicates + * whether unwrapping of array is needed. When unwrap == true, each of array + * members is passed to executeItemOptUnwrapTarget() again but with unwrap == false + * in order to avoid subsequent array unwrapping. + * + * All boolean expressions (predicates) are evaluated by executeBoolItem() + * function, which returns tri-state JsonPathBool. When error is occurred + * during predicate execution, it returns jpbUnknown. According to standard + * predicates can be only inside filters. But we support their usage as + * jsonpath expression. This helps us to implement @@ operator. In this case + * resulting JsonPathBool is transformed into jsonb bool or null. + * + * Arithmetic and boolean expression are evaluated recursively from expression + * tree top down to the leaves. Therefore, for binary arithmetic expressions + * we calculate operands first. Then we check that results are numeric + * singleton lists, calculate the result and pass it to the next path item. + * + * Copyright (c) 2019-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/utils/adt/jsonpath_exec.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "catalog/pg_collation.h" +#include "catalog/pg_type.h" +#include "funcapi.h" +#include "lib/stringinfo.h" +#include "miscadmin.h" +#include "nodes/miscnodes.h" +#include "regex/regex.h" +#include "utils/builtins.h" +#include "utils/date.h" +#include "utils/datetime.h" +#include "utils/datum.h" +#include "utils/float.h" +#include "utils/formatting.h" +#include "utils/guc.h" +#include "utils/json.h" +#include "utils/jsonpath.h" +#include "utils/timestamp.h" +#include "utils/varlena.h" + +/* + * Represents "base object" and it's "id" for .keyvalue() evaluation. + */ +typedef struct JsonBaseObjectInfo +{ + JsonbContainer *jbc; + int id; +} JsonBaseObjectInfo; + +/* + * Context of jsonpath execution. + */ +typedef struct JsonPathExecContext +{ + Jsonb *vars; /* variables to substitute into jsonpath */ + JsonbValue *root; /* for $ evaluation */ + JsonbValue *current; /* for @ evaluation */ + JsonBaseObjectInfo baseObject; /* "base object" for .keyvalue() + * evaluation */ + int lastGeneratedObjectId; /* "id" counter for .keyvalue() + * evaluation */ + int innermostArraySize; /* for LAST array index evaluation */ + bool laxMode; /* true for "lax" mode, false for "strict" + * mode */ + bool ignoreStructuralErrors; /* with "true" structural errors such + * as absence of required json item or + * unexpected json item type are + * ignored */ + bool throwErrors; /* with "false" all suppressible errors are + * suppressed */ + bool useTz; +} JsonPathExecContext; + +/* Context for LIKE_REGEX execution. */ +typedef struct JsonLikeRegexContext +{ + text *regex; + int cflags; +} JsonLikeRegexContext; + +/* Result of jsonpath predicate evaluation */ +typedef enum JsonPathBool +{ + jpbFalse = 0, + jpbTrue = 1, + jpbUnknown = 2 +} JsonPathBool; + +/* Result of jsonpath expression evaluation */ +typedef enum JsonPathExecResult +{ + jperOk = 0, + jperNotFound = 1, + jperError = 2 +} JsonPathExecResult; + +#define jperIsError(jper) ((jper) == jperError) + +/* + * List of jsonb values with shortcut for single-value list. + */ +typedef struct JsonValueList +{ + JsonbValue *singleton; + List *list; +} JsonValueList; + +typedef struct JsonValueListIterator +{ + JsonbValue *value; + List *list; + ListCell *next; +} JsonValueListIterator; + +/* strict/lax flags is decomposed into four [un]wrap/error flags */ +#define jspStrictAbsenseOfErrors(cxt) (!(cxt)->laxMode) +#define jspAutoUnwrap(cxt) ((cxt)->laxMode) +#define jspAutoWrap(cxt) ((cxt)->laxMode) +#define jspIgnoreStructuralErrors(cxt) ((cxt)->ignoreStructuralErrors) +#define jspThrowErrors(cxt) ((cxt)->throwErrors) + +/* Convenience macro: return or throw error depending on context */ +#define RETURN_ERROR(throw_error) \ +do { \ + if (jspThrowErrors(cxt)) \ + throw_error; \ + else \ + return jperError; \ +} while (0) + +typedef JsonPathBool (*JsonPathPredicateCallback) (JsonPathItem *jsp, + JsonbValue *larg, + JsonbValue *rarg, + void *param); +typedef Numeric (*BinaryArithmFunc) (Numeric num1, Numeric num2, bool *error); + +static JsonPathExecResult executeJsonPath(JsonPath *path, Jsonb *vars, + Jsonb *json, bool throwErrors, + JsonValueList *result, bool useTz); +static JsonPathExecResult executeItem(JsonPathExecContext *cxt, + JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found); +static JsonPathExecResult executeItemOptUnwrapTarget(JsonPathExecContext *cxt, + JsonPathItem *jsp, JsonbValue *jb, + JsonValueList *found, bool unwrap); +static JsonPathExecResult executeItemUnwrapTargetArray(JsonPathExecContext *cxt, + JsonPathItem *jsp, JsonbValue *jb, + JsonValueList *found, bool unwrapElements); +static JsonPathExecResult executeNextItem(JsonPathExecContext *cxt, + JsonPathItem *cur, JsonPathItem *next, + JsonbValue *v, JsonValueList *found, bool copy); +static JsonPathExecResult executeItemOptUnwrapResult(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, + bool unwrap, JsonValueList *found); +static JsonPathExecResult executeItemOptUnwrapResultNoThrow(JsonPathExecContext *cxt, JsonPathItem *jsp, + JsonbValue *jb, bool unwrap, JsonValueList *found); +static JsonPathBool executeBoolItem(JsonPathExecContext *cxt, + JsonPathItem *jsp, JsonbValue *jb, bool canHaveNext); +static JsonPathBool executeNestedBoolItem(JsonPathExecContext *cxt, + JsonPathItem *jsp, JsonbValue *jb); +static JsonPathExecResult executeAnyItem(JsonPathExecContext *cxt, + JsonPathItem *jsp, JsonbContainer *jbc, JsonValueList *found, + uint32 level, uint32 first, uint32 last, + bool ignoreStructuralErrors, bool unwrapNext); +static JsonPathBool executePredicate(JsonPathExecContext *cxt, + JsonPathItem *pred, JsonPathItem *larg, JsonPathItem *rarg, + JsonbValue *jb, bool unwrapRightArg, + JsonPathPredicateCallback exec, void *param); +static JsonPathExecResult executeBinaryArithmExpr(JsonPathExecContext *cxt, + JsonPathItem *jsp, JsonbValue *jb, + BinaryArithmFunc func, JsonValueList *found); +static JsonPathExecResult executeUnaryArithmExpr(JsonPathExecContext *cxt, + JsonPathItem *jsp, JsonbValue *jb, PGFunction func, + JsonValueList *found); +static JsonPathBool executeStartsWith(JsonPathItem *jsp, + JsonbValue *whole, JsonbValue *initial, void *param); +static JsonPathBool executeLikeRegex(JsonPathItem *jsp, JsonbValue *str, + JsonbValue *rarg, void *param); +static JsonPathExecResult executeNumericItemMethod(JsonPathExecContext *cxt, + JsonPathItem *jsp, JsonbValue *jb, bool unwrap, PGFunction func, + JsonValueList *found); +static JsonPathExecResult executeDateTimeMethod(JsonPathExecContext *cxt, JsonPathItem *jsp, + JsonbValue *jb, JsonValueList *found); +static JsonPathExecResult executeKeyValueMethod(JsonPathExecContext *cxt, + JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found); +static JsonPathExecResult appendBoolResult(JsonPathExecContext *cxt, + JsonPathItem *jsp, JsonValueList *found, JsonPathBool res); +static void getJsonPathItem(JsonPathExecContext *cxt, JsonPathItem *item, + JsonbValue *value); +static void getJsonPathVariable(JsonPathExecContext *cxt, + JsonPathItem *variable, Jsonb *vars, JsonbValue *value); +static int JsonbArraySize(JsonbValue *jb); +static JsonPathBool executeComparison(JsonPathItem *cmp, JsonbValue *lv, + JsonbValue *rv, void *p); +static JsonPathBool compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2, + bool useTz); +static int compareNumeric(Numeric a, Numeric b); +static JsonbValue *copyJsonbValue(JsonbValue *src); +static JsonPathExecResult getArrayIndex(JsonPathExecContext *cxt, + JsonPathItem *jsp, JsonbValue *jb, int32 *index); +static JsonBaseObjectInfo setBaseObject(JsonPathExecContext *cxt, + JsonbValue *jbv, int32 id); +static void JsonValueListAppend(JsonValueList *jvl, JsonbValue *jbv); +static int JsonValueListLength(const JsonValueList *jvl); +static bool JsonValueListIsEmpty(JsonValueList *jvl); +static JsonbValue *JsonValueListHead(JsonValueList *jvl); +static List *JsonValueListGetList(JsonValueList *jvl); +static void JsonValueListInitIterator(const JsonValueList *jvl, + JsonValueListIterator *it); +static JsonbValue *JsonValueListNext(const JsonValueList *jvl, + JsonValueListIterator *it); +static int JsonbType(JsonbValue *jb); +static JsonbValue *JsonbInitBinary(JsonbValue *jbv, Jsonb *jb); +static int JsonbType(JsonbValue *jb); +static JsonbValue *getScalar(JsonbValue *scalar, enum jbvType type); +static JsonbValue *wrapItemsInArray(const JsonValueList *items); +static int compareDatetime(Datum val1, Oid typid1, Datum val2, Oid typid2, + bool useTz, bool *cast_error); + +/****************** User interface to JsonPath executor ********************/ + +/* + * jsonb_path_exists + * Returns true if jsonpath returns at least one item for the specified + * jsonb value. This function and jsonb_path_match() are used to + * implement @? and @@ operators, which in turn are intended to have an + * index support. Thus, it's desirable to make it easier to achieve + * consistency between index scan results and sequential scan results. + * So, we throw as few errors as possible. Regarding this function, + * such behavior also matches behavior of JSON_EXISTS() clause of + * SQL/JSON. Regarding jsonb_path_match(), this function doesn't have + * an analogy in SQL/JSON, so we define its behavior on our own. + */ +static Datum +jsonb_path_exists_internal(FunctionCallInfo fcinfo, bool tz) +{ + Jsonb *jb = PG_GETARG_JSONB_P(0); + JsonPath *jp = PG_GETARG_JSONPATH_P(1); + JsonPathExecResult res; + Jsonb *vars = NULL; + bool silent = true; + + if (PG_NARGS() == 4) + { + vars = PG_GETARG_JSONB_P(2); + silent = PG_GETARG_BOOL(3); + } + + res = executeJsonPath(jp, vars, jb, !silent, NULL, tz); + + PG_FREE_IF_COPY(jb, 0); + PG_FREE_IF_COPY(jp, 1); + + if (jperIsError(res)) + PG_RETURN_NULL(); + + PG_RETURN_BOOL(res == jperOk); +} + +Datum +jsonb_path_exists(PG_FUNCTION_ARGS) +{ + return jsonb_path_exists_internal(fcinfo, false); +} + +Datum +jsonb_path_exists_tz(PG_FUNCTION_ARGS) +{ + return jsonb_path_exists_internal(fcinfo, true); +} + +/* + * jsonb_path_exists_opr + * Implementation of operator "jsonb @? jsonpath" (2-argument version of + * jsonb_path_exists()). + */ +Datum +jsonb_path_exists_opr(PG_FUNCTION_ARGS) +{ + /* just call the other one -- it can handle both cases */ + return jsonb_path_exists_internal(fcinfo, false); +} + +/* + * jsonb_path_match + * Returns jsonpath predicate result item for the specified jsonb value. + * See jsonb_path_exists() comment for details regarding error handling. + */ +static Datum +jsonb_path_match_internal(FunctionCallInfo fcinfo, bool tz) +{ + Jsonb *jb = PG_GETARG_JSONB_P(0); + JsonPath *jp = PG_GETARG_JSONPATH_P(1); + JsonValueList found = {0}; + Jsonb *vars = NULL; + bool silent = true; + + if (PG_NARGS() == 4) + { + vars = PG_GETARG_JSONB_P(2); + silent = PG_GETARG_BOOL(3); + } + + (void) executeJsonPath(jp, vars, jb, !silent, &found, tz); + + PG_FREE_IF_COPY(jb, 0); + PG_FREE_IF_COPY(jp, 1); + + if (JsonValueListLength(&found) == 1) + { + JsonbValue *jbv = JsonValueListHead(&found); + + if (jbv->type == jbvBool) + PG_RETURN_BOOL(jbv->val.boolean); + + if (jbv->type == jbvNull) + PG_RETURN_NULL(); + } + + if (!silent) + ereport(ERROR, + (errcode(ERRCODE_SINGLETON_SQL_JSON_ITEM_REQUIRED), + errmsg("single boolean result is expected"))); + + PG_RETURN_NULL(); +} + +Datum +jsonb_path_match(PG_FUNCTION_ARGS) +{ + return jsonb_path_match_internal(fcinfo, false); +} + +Datum +jsonb_path_match_tz(PG_FUNCTION_ARGS) +{ + return jsonb_path_match_internal(fcinfo, true); +} + +/* + * jsonb_path_match_opr + * Implementation of operator "jsonb @@ jsonpath" (2-argument version of + * jsonb_path_match()). + */ +Datum +jsonb_path_match_opr(PG_FUNCTION_ARGS) +{ + /* just call the other one -- it can handle both cases */ + return jsonb_path_match_internal(fcinfo, false); +} + +/* + * jsonb_path_query + * Executes jsonpath for given jsonb document and returns result as + * rowset. + */ +static Datum +jsonb_path_query_internal(FunctionCallInfo fcinfo, bool tz) +{ + FuncCallContext *funcctx; + List *found; + JsonbValue *v; + ListCell *c; + + if (SRF_IS_FIRSTCALL()) + { + JsonPath *jp; + Jsonb *jb; + MemoryContext oldcontext; + Jsonb *vars; + bool silent; + JsonValueList found = {0}; + + funcctx = SRF_FIRSTCALL_INIT(); + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + jb = PG_GETARG_JSONB_P_COPY(0); + jp = PG_GETARG_JSONPATH_P_COPY(1); + vars = PG_GETARG_JSONB_P_COPY(2); + silent = PG_GETARG_BOOL(3); + + (void) executeJsonPath(jp, vars, jb, !silent, &found, tz); + + funcctx->user_fctx = JsonValueListGetList(&found); + + MemoryContextSwitchTo(oldcontext); + } + + funcctx = SRF_PERCALL_SETUP(); + found = funcctx->user_fctx; + + c = list_head(found); + + if (c == NULL) + SRF_RETURN_DONE(funcctx); + + v = lfirst(c); + funcctx->user_fctx = list_delete_first(found); + + SRF_RETURN_NEXT(funcctx, JsonbPGetDatum(JsonbValueToJsonb(v))); +} + +Datum +jsonb_path_query(PG_FUNCTION_ARGS) +{ + return jsonb_path_query_internal(fcinfo, false); +} + +Datum +jsonb_path_query_tz(PG_FUNCTION_ARGS) +{ + return jsonb_path_query_internal(fcinfo, true); +} + +/* + * jsonb_path_query_array + * Executes jsonpath for given jsonb document and returns result as + * jsonb array. + */ +static Datum +jsonb_path_query_array_internal(FunctionCallInfo fcinfo, bool tz) +{ + Jsonb *jb = PG_GETARG_JSONB_P(0); + JsonPath *jp = PG_GETARG_JSONPATH_P(1); + JsonValueList found = {0}; + Jsonb *vars = PG_GETARG_JSONB_P(2); + bool silent = PG_GETARG_BOOL(3); + + (void) executeJsonPath(jp, vars, jb, !silent, &found, tz); + + PG_RETURN_JSONB_P(JsonbValueToJsonb(wrapItemsInArray(&found))); +} + +Datum +jsonb_path_query_array(PG_FUNCTION_ARGS) +{ + return jsonb_path_query_array_internal(fcinfo, false); +} + +Datum +jsonb_path_query_array_tz(PG_FUNCTION_ARGS) +{ + return jsonb_path_query_array_internal(fcinfo, true); +} + +/* + * jsonb_path_query_first + * Executes jsonpath for given jsonb document and returns first result + * item. If there are no items, NULL returned. + */ +static Datum +jsonb_path_query_first_internal(FunctionCallInfo fcinfo, bool tz) +{ + Jsonb *jb = PG_GETARG_JSONB_P(0); + JsonPath *jp = PG_GETARG_JSONPATH_P(1); + JsonValueList found = {0}; + Jsonb *vars = PG_GETARG_JSONB_P(2); + bool silent = PG_GETARG_BOOL(3); + + (void) executeJsonPath(jp, vars, jb, !silent, &found, tz); + + if (JsonValueListLength(&found) >= 1) + PG_RETURN_JSONB_P(JsonbValueToJsonb(JsonValueListHead(&found))); + else + PG_RETURN_NULL(); +} + +Datum +jsonb_path_query_first(PG_FUNCTION_ARGS) +{ + return jsonb_path_query_first_internal(fcinfo, false); +} + +Datum +jsonb_path_query_first_tz(PG_FUNCTION_ARGS) +{ + return jsonb_path_query_first_internal(fcinfo, true); +} + +/********************Execute functions for JsonPath**************************/ + +/* + * Interface to jsonpath executor + * + * 'path' - jsonpath to be executed + * 'vars' - variables to be substituted to jsonpath + * 'json' - target document for jsonpath evaluation + * 'throwErrors' - whether we should throw suppressible errors + * 'result' - list to store result items into + * + * Returns an error if a recoverable error happens during processing, or NULL + * on no error. + * + * Note, jsonb and jsonpath values should be available and untoasted during + * work because JsonPathItem, JsonbValue and result item could have pointers + * into input values. If caller needs to just check if document matches + * jsonpath, then it doesn't provide a result arg. In this case executor + * works till first positive result and does not check the rest if possible. + * In other case it tries to find all the satisfied result items. + */ +static JsonPathExecResult +executeJsonPath(JsonPath *path, Jsonb *vars, Jsonb *json, bool throwErrors, + JsonValueList *result, bool useTz) +{ + JsonPathExecContext cxt; + JsonPathExecResult res; + JsonPathItem jsp; + JsonbValue jbv; + + jspInit(&jsp, path); + + if (!JsonbExtractScalar(&json->root, &jbv)) + JsonbInitBinary(&jbv, json); + + if (vars && !JsonContainerIsObject(&vars->root)) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("\"vars\" argument is not an object"), + errdetail("Jsonpath parameters should be encoded as key-value pairs of \"vars\" object."))); + } + + cxt.vars = vars; + cxt.laxMode = (path->header & JSONPATH_LAX) != 0; + cxt.ignoreStructuralErrors = cxt.laxMode; + cxt.root = &jbv; + cxt.current = &jbv; + cxt.baseObject.jbc = NULL; + cxt.baseObject.id = 0; + cxt.lastGeneratedObjectId = vars ? 2 : 1; + cxt.innermostArraySize = -1; + cxt.throwErrors = throwErrors; + cxt.useTz = useTz; + + if (jspStrictAbsenseOfErrors(&cxt) && !result) + { + /* + * In strict mode we must get a complete list of values to check that + * there are no errors at all. + */ + JsonValueList vals = {0}; + + res = executeItem(&cxt, &jsp, &jbv, &vals); + + if (jperIsError(res)) + return res; + + return JsonValueListIsEmpty(&vals) ? jperNotFound : jperOk; + } + + res = executeItem(&cxt, &jsp, &jbv, result); + + Assert(!throwErrors || !jperIsError(res)); + + return res; +} + +/* + * Execute jsonpath with automatic unwrapping of current item in lax mode. + */ +static JsonPathExecResult +executeItem(JsonPathExecContext *cxt, JsonPathItem *jsp, + JsonbValue *jb, JsonValueList *found) +{ + return executeItemOptUnwrapTarget(cxt, jsp, jb, found, jspAutoUnwrap(cxt)); +} + +/* + * Main jsonpath executor function: walks on jsonpath structure, finds + * relevant parts of jsonb and evaluates expressions over them. + * When 'unwrap' is true current SQL/JSON item is unwrapped if it is an array. + */ +static JsonPathExecResult +executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, + JsonbValue *jb, JsonValueList *found, bool unwrap) +{ + JsonPathItem elem; + JsonPathExecResult res = jperNotFound; + JsonBaseObjectInfo baseObject; + + check_stack_depth(); + CHECK_FOR_INTERRUPTS(); + + switch (jsp->type) + { + /* all boolean item types: */ + case jpiAnd: + case jpiOr: + case jpiNot: + case jpiIsUnknown: + case jpiEqual: + case jpiNotEqual: + case jpiLess: + case jpiGreater: + case jpiLessOrEqual: + case jpiGreaterOrEqual: + case jpiExists: + case jpiStartsWith: + case jpiLikeRegex: + { + JsonPathBool st = executeBoolItem(cxt, jsp, jb, true); + + res = appendBoolResult(cxt, jsp, found, st); + break; + } + + case jpiKey: + if (JsonbType(jb) == jbvObject) + { + JsonbValue *v; + JsonbValue key; + + key.type = jbvString; + key.val.string.val = jspGetString(jsp, &key.val.string.len); + + v = findJsonbValueFromContainer(jb->val.binary.data, + JB_FOBJECT, &key); + + if (v != NULL) + { + res = executeNextItem(cxt, jsp, NULL, + v, found, false); + + /* free value if it was not added to found list */ + if (jspHasNext(jsp) || !found) + pfree(v); + } + else if (!jspIgnoreStructuralErrors(cxt)) + { + Assert(found); + + if (!jspThrowErrors(cxt)) + return jperError; + + ereport(ERROR, + (errcode(ERRCODE_SQL_JSON_MEMBER_NOT_FOUND), \ + errmsg("JSON object does not contain key \"%s\"", + pnstrdup(key.val.string.val, + key.val.string.len)))); + } + } + else if (unwrap && JsonbType(jb) == jbvArray) + return executeItemUnwrapTargetArray(cxt, jsp, jb, found, false); + else if (!jspIgnoreStructuralErrors(cxt)) + { + Assert(found); + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_SQL_JSON_MEMBER_NOT_FOUND), + errmsg("jsonpath member accessor can only be applied to an object")))); + } + break; + + case jpiRoot: + jb = cxt->root; + baseObject = setBaseObject(cxt, jb, 0); + res = executeNextItem(cxt, jsp, NULL, jb, found, true); + cxt->baseObject = baseObject; + break; + + case jpiCurrent: + res = executeNextItem(cxt, jsp, NULL, cxt->current, + found, true); + break; + + case jpiAnyArray: + if (JsonbType(jb) == jbvArray) + { + bool hasNext = jspGetNext(jsp, &elem); + + res = executeItemUnwrapTargetArray(cxt, hasNext ? &elem : NULL, + jb, found, jspAutoUnwrap(cxt)); + } + else if (jspAutoWrap(cxt)) + res = executeNextItem(cxt, jsp, NULL, jb, found, true); + else if (!jspIgnoreStructuralErrors(cxt)) + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_SQL_JSON_ARRAY_NOT_FOUND), + errmsg("jsonpath wildcard array accessor can only be applied to an array")))); + break; + + case jpiIndexArray: + if (JsonbType(jb) == jbvArray || jspAutoWrap(cxt)) + { + int innermostArraySize = cxt->innermostArraySize; + int i; + int size = JsonbArraySize(jb); + bool singleton = size < 0; + bool hasNext = jspGetNext(jsp, &elem); + + if (singleton) + size = 1; + + cxt->innermostArraySize = size; /* for LAST evaluation */ + + for (i = 0; i < jsp->content.array.nelems; i++) + { + JsonPathItem from; + JsonPathItem to; + int32 index; + int32 index_from; + int32 index_to; + bool range = jspGetArraySubscript(jsp, &from, + &to, i); + + res = getArrayIndex(cxt, &from, jb, &index_from); + + if (jperIsError(res)) + break; + + if (range) + { + res = getArrayIndex(cxt, &to, jb, &index_to); + + if (jperIsError(res)) + break; + } + else + index_to = index_from; + + if (!jspIgnoreStructuralErrors(cxt) && + (index_from < 0 || + index_from > index_to || + index_to >= size)) + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_INVALID_SQL_JSON_SUBSCRIPT), + errmsg("jsonpath array subscript is out of bounds")))); + + if (index_from < 0) + index_from = 0; + + if (index_to >= size) + index_to = size - 1; + + res = jperNotFound; + + for (index = index_from; index <= index_to; index++) + { + JsonbValue *v; + bool copy; + + if (singleton) + { + v = jb; + copy = true; + } + else + { + v = getIthJsonbValueFromContainer(jb->val.binary.data, + (uint32) index); + + if (v == NULL) + continue; + + copy = false; + } + + if (!hasNext && !found) + return jperOk; + + res = executeNextItem(cxt, jsp, &elem, v, found, + copy); + + if (jperIsError(res)) + break; + + if (res == jperOk && !found) + break; + } + + if (jperIsError(res)) + break; + + if (res == jperOk && !found) + break; + } + + cxt->innermostArraySize = innermostArraySize; + } + else if (!jspIgnoreStructuralErrors(cxt)) + { + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_SQL_JSON_ARRAY_NOT_FOUND), + errmsg("jsonpath array accessor can only be applied to an array")))); + } + break; + + case jpiLast: + { + JsonbValue tmpjbv; + JsonbValue *lastjbv; + int last; + bool hasNext = jspGetNext(jsp, &elem); + + if (cxt->innermostArraySize < 0) + elog(ERROR, "evaluating jsonpath LAST outside of array subscript"); + + if (!hasNext && !found) + { + res = jperOk; + break; + } + + last = cxt->innermostArraySize - 1; + + lastjbv = hasNext ? &tmpjbv : palloc(sizeof(*lastjbv)); + + lastjbv->type = jbvNumeric; + lastjbv->val.numeric = int64_to_numeric(last); + + res = executeNextItem(cxt, jsp, &elem, + lastjbv, found, hasNext); + } + break; + + case jpiAnyKey: + if (JsonbType(jb) == jbvObject) + { + bool hasNext = jspGetNext(jsp, &elem); + + if (jb->type != jbvBinary) + elog(ERROR, "invalid jsonb object type: %d", jb->type); + + return executeAnyItem + (cxt, hasNext ? &elem : NULL, + jb->val.binary.data, found, 1, 1, 1, + false, jspAutoUnwrap(cxt)); + } + else if (unwrap && JsonbType(jb) == jbvArray) + return executeItemUnwrapTargetArray(cxt, jsp, jb, found, false); + else if (!jspIgnoreStructuralErrors(cxt)) + { + Assert(found); + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_SQL_JSON_OBJECT_NOT_FOUND), + errmsg("jsonpath wildcard member accessor can only be applied to an object")))); + } + break; + + case jpiAdd: + return executeBinaryArithmExpr(cxt, jsp, jb, + numeric_add_opt_error, found); + + case jpiSub: + return executeBinaryArithmExpr(cxt, jsp, jb, + numeric_sub_opt_error, found); + + case jpiMul: + return executeBinaryArithmExpr(cxt, jsp, jb, + numeric_mul_opt_error, found); + + case jpiDiv: + return executeBinaryArithmExpr(cxt, jsp, jb, + numeric_div_opt_error, found); + + case jpiMod: + return executeBinaryArithmExpr(cxt, jsp, jb, + numeric_mod_opt_error, found); + + case jpiPlus: + return executeUnaryArithmExpr(cxt, jsp, jb, NULL, found); + + case jpiMinus: + return executeUnaryArithmExpr(cxt, jsp, jb, numeric_uminus, + found); + + case jpiFilter: + { + JsonPathBool st; + + if (unwrap && JsonbType(jb) == jbvArray) + return executeItemUnwrapTargetArray(cxt, jsp, jb, found, + false); + + jspGetArg(jsp, &elem); + st = executeNestedBoolItem(cxt, &elem, jb); + if (st != jpbTrue) + res = jperNotFound; + else + res = executeNextItem(cxt, jsp, NULL, + jb, found, true); + break; + } + + case jpiAny: + { + bool hasNext = jspGetNext(jsp, &elem); + + /* first try without any intermediate steps */ + if (jsp->content.anybounds.first == 0) + { + bool savedIgnoreStructuralErrors; + + savedIgnoreStructuralErrors = cxt->ignoreStructuralErrors; + cxt->ignoreStructuralErrors = true; + res = executeNextItem(cxt, jsp, &elem, + jb, found, true); + cxt->ignoreStructuralErrors = savedIgnoreStructuralErrors; + + if (res == jperOk && !found) + break; + } + + if (jb->type == jbvBinary) + res = executeAnyItem + (cxt, hasNext ? &elem : NULL, + jb->val.binary.data, found, + 1, + jsp->content.anybounds.first, + jsp->content.anybounds.last, + true, jspAutoUnwrap(cxt)); + break; + } + + case jpiNull: + case jpiBool: + case jpiNumeric: + case jpiString: + case jpiVariable: + { + JsonbValue vbuf; + JsonbValue *v; + bool hasNext = jspGetNext(jsp, &elem); + + if (!hasNext && !found && jsp->type != jpiVariable) + { + /* + * Skip evaluation, but not for variables. We must + * trigger an error for the missing variable. + */ + res = jperOk; + break; + } + + v = hasNext ? &vbuf : palloc(sizeof(*v)); + + baseObject = cxt->baseObject; + getJsonPathItem(cxt, jsp, v); + + res = executeNextItem(cxt, jsp, &elem, + v, found, hasNext); + cxt->baseObject = baseObject; + } + break; + + case jpiType: + { + JsonbValue *jbv = palloc(sizeof(*jbv)); + + jbv->type = jbvString; + jbv->val.string.val = pstrdup(JsonbTypeName(jb)); + jbv->val.string.len = strlen(jbv->val.string.val); + + res = executeNextItem(cxt, jsp, NULL, jbv, + found, false); + } + break; + + case jpiSize: + { + int size = JsonbArraySize(jb); + + if (size < 0) + { + if (!jspAutoWrap(cxt)) + { + if (!jspIgnoreStructuralErrors(cxt)) + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_SQL_JSON_ARRAY_NOT_FOUND), + errmsg("jsonpath item method .%s() can only be applied to an array", + jspOperationName(jsp->type))))); + break; + } + + size = 1; + } + + jb = palloc(sizeof(*jb)); + + jb->type = jbvNumeric; + jb->val.numeric = int64_to_numeric(size); + + res = executeNextItem(cxt, jsp, NULL, jb, found, false); + } + break; + + case jpiAbs: + return executeNumericItemMethod(cxt, jsp, jb, unwrap, numeric_abs, + found); + + case jpiFloor: + return executeNumericItemMethod(cxt, jsp, jb, unwrap, numeric_floor, + found); + + case jpiCeiling: + return executeNumericItemMethod(cxt, jsp, jb, unwrap, numeric_ceil, + found); + + case jpiDouble: + { + JsonbValue jbv; + + if (unwrap && JsonbType(jb) == jbvArray) + return executeItemUnwrapTargetArray(cxt, jsp, jb, found, + false); + + if (jb->type == jbvNumeric) + { + char *tmp = DatumGetCString(DirectFunctionCall1(numeric_out, + NumericGetDatum(jb->val.numeric))); + double val; + ErrorSaveContext escontext = {T_ErrorSaveContext}; + + val = float8in_internal(tmp, + NULL, + "double precision", + tmp, + (Node *) &escontext); + + if (escontext.error_occurred || isinf(val) || isnan(val)) + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM), + errmsg("numeric argument of jsonpath item method .%s() is out of range for type double precision", + jspOperationName(jsp->type))))); + res = jperOk; + } + else if (jb->type == jbvString) + { + /* cast string as double */ + double val; + char *tmp = pnstrdup(jb->val.string.val, + jb->val.string.len); + ErrorSaveContext escontext = {T_ErrorSaveContext}; + + val = float8in_internal(tmp, + NULL, + "double precision", + tmp, + (Node *) &escontext); + + if (escontext.error_occurred || isinf(val) || isnan(val)) + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM), + errmsg("string argument of jsonpath item method .%s() is not a valid representation of a double precision number", + jspOperationName(jsp->type))))); + + jb = &jbv; + jb->type = jbvNumeric; + jb->val.numeric = DatumGetNumeric(DirectFunctionCall1(float8_numeric, + Float8GetDatum(val))); + res = jperOk; + } + + if (res == jperNotFound) + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM), + errmsg("jsonpath item method .%s() can only be applied to a string or numeric value", + jspOperationName(jsp->type))))); + + res = executeNextItem(cxt, jsp, NULL, jb, found, true); + } + break; + + case jpiDatetime: + if (unwrap && JsonbType(jb) == jbvArray) + return executeItemUnwrapTargetArray(cxt, jsp, jb, found, false); + + return executeDateTimeMethod(cxt, jsp, jb, found); + + case jpiKeyValue: + if (unwrap && JsonbType(jb) == jbvArray) + return executeItemUnwrapTargetArray(cxt, jsp, jb, found, false); + + return executeKeyValueMethod(cxt, jsp, jb, found); + + default: + elog(ERROR, "unrecognized jsonpath item type: %d", jsp->type); + } + + return res; +} + +/* + * Unwrap current array item and execute jsonpath for each of its elements. + */ +static JsonPathExecResult +executeItemUnwrapTargetArray(JsonPathExecContext *cxt, JsonPathItem *jsp, + JsonbValue *jb, JsonValueList *found, + bool unwrapElements) +{ + if (jb->type != jbvBinary) + { + Assert(jb->type != jbvArray); + elog(ERROR, "invalid jsonb array value type: %d", jb->type); + } + + return executeAnyItem + (cxt, jsp, jb->val.binary.data, found, 1, 1, 1, + false, unwrapElements); +} + +/* + * Execute next jsonpath item if exists. Otherwise put "v" to the "found" + * list if provided. + */ +static JsonPathExecResult +executeNextItem(JsonPathExecContext *cxt, + JsonPathItem *cur, JsonPathItem *next, + JsonbValue *v, JsonValueList *found, bool copy) +{ + JsonPathItem elem; + bool hasNext; + + if (!cur) + hasNext = next != NULL; + else if (next) + hasNext = jspHasNext(cur); + else + { + next = &elem; + hasNext = jspGetNext(cur, next); + } + + if (hasNext) + return executeItem(cxt, next, v, found); + + if (found) + JsonValueListAppend(found, copy ? copyJsonbValue(v) : v); + + return jperOk; +} + +/* + * Same as executeItem(), but when "unwrap == true" automatically unwraps + * each array item from the resulting sequence in lax mode. + */ +static JsonPathExecResult +executeItemOptUnwrapResult(JsonPathExecContext *cxt, JsonPathItem *jsp, + JsonbValue *jb, bool unwrap, + JsonValueList *found) +{ + if (unwrap && jspAutoUnwrap(cxt)) + { + JsonValueList seq = {0}; + JsonValueListIterator it; + JsonPathExecResult res = executeItem(cxt, jsp, jb, &seq); + JsonbValue *item; + + if (jperIsError(res)) + return res; + + JsonValueListInitIterator(&seq, &it); + while ((item = JsonValueListNext(&seq, &it))) + { + Assert(item->type != jbvArray); + + if (JsonbType(item) == jbvArray) + executeItemUnwrapTargetArray(cxt, NULL, item, found, false); + else + JsonValueListAppend(found, item); + } + + return jperOk; + } + + return executeItem(cxt, jsp, jb, found); +} + +/* + * Same as executeItemOptUnwrapResult(), but with error suppression. + */ +static JsonPathExecResult +executeItemOptUnwrapResultNoThrow(JsonPathExecContext *cxt, + JsonPathItem *jsp, + JsonbValue *jb, bool unwrap, + JsonValueList *found) +{ + JsonPathExecResult res; + bool throwErrors = cxt->throwErrors; + + cxt->throwErrors = false; + res = executeItemOptUnwrapResult(cxt, jsp, jb, unwrap, found); + cxt->throwErrors = throwErrors; + + return res; +} + +/* Execute boolean-valued jsonpath expression. */ +static JsonPathBool +executeBoolItem(JsonPathExecContext *cxt, JsonPathItem *jsp, + JsonbValue *jb, bool canHaveNext) +{ + JsonPathItem larg; + JsonPathItem rarg; + JsonPathBool res; + JsonPathBool res2; + + /* since this function recurses, it could be driven to stack overflow */ + check_stack_depth(); + + if (!canHaveNext && jspHasNext(jsp)) + elog(ERROR, "boolean jsonpath item cannot have next item"); + + switch (jsp->type) + { + case jpiAnd: + jspGetLeftArg(jsp, &larg); + res = executeBoolItem(cxt, &larg, jb, false); + + if (res == jpbFalse) + return jpbFalse; + + /* + * SQL/JSON says that we should check second arg in case of + * jperError + */ + + jspGetRightArg(jsp, &rarg); + res2 = executeBoolItem(cxt, &rarg, jb, false); + + return res2 == jpbTrue ? res : res2; + + case jpiOr: + jspGetLeftArg(jsp, &larg); + res = executeBoolItem(cxt, &larg, jb, false); + + if (res == jpbTrue) + return jpbTrue; + + jspGetRightArg(jsp, &rarg); + res2 = executeBoolItem(cxt, &rarg, jb, false); + + return res2 == jpbFalse ? res : res2; + + case jpiNot: + jspGetArg(jsp, &larg); + + res = executeBoolItem(cxt, &larg, jb, false); + + if (res == jpbUnknown) + return jpbUnknown; + + return res == jpbTrue ? jpbFalse : jpbTrue; + + case jpiIsUnknown: + jspGetArg(jsp, &larg); + res = executeBoolItem(cxt, &larg, jb, false); + return res == jpbUnknown ? jpbTrue : jpbFalse; + + case jpiEqual: + case jpiNotEqual: + case jpiLess: + case jpiGreater: + case jpiLessOrEqual: + case jpiGreaterOrEqual: + jspGetLeftArg(jsp, &larg); + jspGetRightArg(jsp, &rarg); + return executePredicate(cxt, jsp, &larg, &rarg, jb, true, + executeComparison, cxt); + + case jpiStartsWith: /* 'whole STARTS WITH initial' */ + jspGetLeftArg(jsp, &larg); /* 'whole' */ + jspGetRightArg(jsp, &rarg); /* 'initial' */ + return executePredicate(cxt, jsp, &larg, &rarg, jb, false, + executeStartsWith, NULL); + + case jpiLikeRegex: /* 'expr LIKE_REGEX pattern FLAGS flags' */ + { + /* + * 'expr' is a sequence-returning expression. 'pattern' is a + * regex string literal. SQL/JSON standard requires XQuery + * regexes, but we use Postgres regexes here. 'flags' is a + * string literal converted to integer flags at compile-time. + */ + JsonLikeRegexContext lrcxt = {0}; + + jspInitByBuffer(&larg, jsp->base, + jsp->content.like_regex.expr); + + return executePredicate(cxt, jsp, &larg, NULL, jb, false, + executeLikeRegex, &lrcxt); + } + + case jpiExists: + jspGetArg(jsp, &larg); + + if (jspStrictAbsenseOfErrors(cxt)) + { + /* + * In strict mode we must get a complete list of values to + * check that there are no errors at all. + */ + JsonValueList vals = {0}; + JsonPathExecResult res = + executeItemOptUnwrapResultNoThrow(cxt, &larg, jb, + false, &vals); + + if (jperIsError(res)) + return jpbUnknown; + + return JsonValueListIsEmpty(&vals) ? jpbFalse : jpbTrue; + } + else + { + JsonPathExecResult res = + executeItemOptUnwrapResultNoThrow(cxt, &larg, jb, + false, NULL); + + if (jperIsError(res)) + return jpbUnknown; + + return res == jperOk ? jpbTrue : jpbFalse; + } + + default: + elog(ERROR, "invalid boolean jsonpath item type: %d", jsp->type); + return jpbUnknown; + } +} + +/* + * Execute nested (filters etc.) boolean expression pushing current SQL/JSON + * item onto the stack. + */ +static JsonPathBool +executeNestedBoolItem(JsonPathExecContext *cxt, JsonPathItem *jsp, + JsonbValue *jb) +{ + JsonbValue *prev; + JsonPathBool res; + + prev = cxt->current; + cxt->current = jb; + res = executeBoolItem(cxt, jsp, jb, false); + cxt->current = prev; + + return res; +} + +/* + * Implementation of several jsonpath nodes: + * - jpiAny (.** accessor), + * - jpiAnyKey (.* accessor), + * - jpiAnyArray ([*] accessor) + */ +static JsonPathExecResult +executeAnyItem(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbContainer *jbc, + JsonValueList *found, uint32 level, uint32 first, uint32 last, + bool ignoreStructuralErrors, bool unwrapNext) +{ + JsonPathExecResult res = jperNotFound; + JsonbIterator *it; + int32 r; + JsonbValue v; + + check_stack_depth(); + + if (level > last) + return res; + + it = JsonbIteratorInit(jbc); + + /* + * Recursively iterate over jsonb objects/arrays + */ + while ((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE) + { + if (r == WJB_KEY) + { + r = JsonbIteratorNext(&it, &v, true); + Assert(r == WJB_VALUE); + } + + if (r == WJB_VALUE || r == WJB_ELEM) + { + + if (level >= first || + (first == PG_UINT32_MAX && last == PG_UINT32_MAX && + v.type != jbvBinary)) /* leaves only requested */ + { + /* check expression */ + if (jsp) + { + if (ignoreStructuralErrors) + { + bool savedIgnoreStructuralErrors; + + savedIgnoreStructuralErrors = cxt->ignoreStructuralErrors; + cxt->ignoreStructuralErrors = true; + res = executeItemOptUnwrapTarget(cxt, jsp, &v, found, unwrapNext); + cxt->ignoreStructuralErrors = savedIgnoreStructuralErrors; + } + else + res = executeItemOptUnwrapTarget(cxt, jsp, &v, found, unwrapNext); + + if (jperIsError(res)) + break; + + if (res == jperOk && !found) + break; + } + else if (found) + JsonValueListAppend(found, copyJsonbValue(&v)); + else + return jperOk; + } + + if (level < last && v.type == jbvBinary) + { + res = executeAnyItem + (cxt, jsp, v.val.binary.data, found, + level + 1, first, last, + ignoreStructuralErrors, unwrapNext); + + if (jperIsError(res)) + break; + + if (res == jperOk && found == NULL) + break; + } + } + } + + return res; +} + +/* + * Execute unary or binary predicate. + * + * Predicates have existence semantics, because their operands are item + * sequences. Pairs of items from the left and right operand's sequences are + * checked. TRUE returned only if any pair satisfying the condition is found. + * In strict mode, even if the desired pair has already been found, all pairs + * still need to be examined to check the absence of errors. If any error + * occurs, UNKNOWN (analogous to SQL NULL) is returned. + */ +static JsonPathBool +executePredicate(JsonPathExecContext *cxt, JsonPathItem *pred, + JsonPathItem *larg, JsonPathItem *rarg, JsonbValue *jb, + bool unwrapRightArg, JsonPathPredicateCallback exec, + void *param) +{ + JsonPathExecResult res; + JsonValueListIterator lseqit; + JsonValueList lseq = {0}; + JsonValueList rseq = {0}; + JsonbValue *lval; + bool error = false; + bool found = false; + + /* Left argument is always auto-unwrapped. */ + res = executeItemOptUnwrapResultNoThrow(cxt, larg, jb, true, &lseq); + if (jperIsError(res)) + return jpbUnknown; + + if (rarg) + { + /* Right argument is conditionally auto-unwrapped. */ + res = executeItemOptUnwrapResultNoThrow(cxt, rarg, jb, + unwrapRightArg, &rseq); + if (jperIsError(res)) + return jpbUnknown; + } + + JsonValueListInitIterator(&lseq, &lseqit); + while ((lval = JsonValueListNext(&lseq, &lseqit))) + { + JsonValueListIterator rseqit; + JsonbValue *rval; + bool first = true; + + JsonValueListInitIterator(&rseq, &rseqit); + if (rarg) + rval = JsonValueListNext(&rseq, &rseqit); + else + rval = NULL; + + /* Loop over right arg sequence or do single pass otherwise */ + while (rarg ? (rval != NULL) : first) + { + JsonPathBool res = exec(pred, lval, rval, param); + + if (res == jpbUnknown) + { + if (jspStrictAbsenseOfErrors(cxt)) + return jpbUnknown; + + error = true; + } + else if (res == jpbTrue) + { + if (!jspStrictAbsenseOfErrors(cxt)) + return jpbTrue; + + found = true; + } + + first = false; + if (rarg) + rval = JsonValueListNext(&rseq, &rseqit); + } + } + + if (found) /* possible only in strict mode */ + return jpbTrue; + + if (error) /* possible only in lax mode */ + return jpbUnknown; + + return jpbFalse; +} + +/* + * Execute binary arithmetic expression on singleton numeric operands. + * Array operands are automatically unwrapped in lax mode. + */ +static JsonPathExecResult +executeBinaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, + JsonbValue *jb, BinaryArithmFunc func, + JsonValueList *found) +{ + JsonPathExecResult jper; + JsonPathItem elem; + JsonValueList lseq = {0}; + JsonValueList rseq = {0}; + JsonbValue *lval; + JsonbValue *rval; + Numeric res; + + jspGetLeftArg(jsp, &elem); + + /* + * XXX: By standard only operands of multiplicative expressions are + * unwrapped. We extend it to other binary arithmetic expressions too. + */ + jper = executeItemOptUnwrapResult(cxt, &elem, jb, true, &lseq); + if (jperIsError(jper)) + return jper; + + jspGetRightArg(jsp, &elem); + + jper = executeItemOptUnwrapResult(cxt, &elem, jb, true, &rseq); + if (jperIsError(jper)) + return jper; + + if (JsonValueListLength(&lseq) != 1 || + !(lval = getScalar(JsonValueListHead(&lseq), jbvNumeric))) + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_SINGLETON_SQL_JSON_ITEM_REQUIRED), + errmsg("left operand of jsonpath operator %s is not a single numeric value", + jspOperationName(jsp->type))))); + + if (JsonValueListLength(&rseq) != 1 || + !(rval = getScalar(JsonValueListHead(&rseq), jbvNumeric))) + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_SINGLETON_SQL_JSON_ITEM_REQUIRED), + errmsg("right operand of jsonpath operator %s is not a single numeric value", + jspOperationName(jsp->type))))); + + if (jspThrowErrors(cxt)) + { + res = func(lval->val.numeric, rval->val.numeric, NULL); + } + else + { + bool error = false; + + res = func(lval->val.numeric, rval->val.numeric, &error); + + if (error) + return jperError; + } + + if (!jspGetNext(jsp, &elem) && !found) + return jperOk; + + lval = palloc(sizeof(*lval)); + lval->type = jbvNumeric; + lval->val.numeric = res; + + return executeNextItem(cxt, jsp, &elem, lval, found, false); +} + +/* + * Execute unary arithmetic expression for each numeric item in its operand's + * sequence. Array operand is automatically unwrapped in lax mode. + */ +static JsonPathExecResult +executeUnaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, + JsonbValue *jb, PGFunction func, JsonValueList *found) +{ + JsonPathExecResult jper; + JsonPathExecResult jper2; + JsonPathItem elem; + JsonValueList seq = {0}; + JsonValueListIterator it; + JsonbValue *val; + bool hasNext; + + jspGetArg(jsp, &elem); + jper = executeItemOptUnwrapResult(cxt, &elem, jb, true, &seq); + + if (jperIsError(jper)) + return jper; + + jper = jperNotFound; + + hasNext = jspGetNext(jsp, &elem); + + JsonValueListInitIterator(&seq, &it); + while ((val = JsonValueListNext(&seq, &it))) + { + if ((val = getScalar(val, jbvNumeric))) + { + if (!found && !hasNext) + return jperOk; + } + else + { + if (!found && !hasNext) + continue; /* skip non-numerics processing */ + + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_SQL_JSON_NUMBER_NOT_FOUND), + errmsg("operand of unary jsonpath operator %s is not a numeric value", + jspOperationName(jsp->type))))); + } + + if (func) + val->val.numeric = + DatumGetNumeric(DirectFunctionCall1(func, + NumericGetDatum(val->val.numeric))); + + jper2 = executeNextItem(cxt, jsp, &elem, val, found, false); + + if (jperIsError(jper2)) + return jper2; + + if (jper2 == jperOk) + { + if (!found) + return jperOk; + jper = jperOk; + } + } + + return jper; +} + +/* + * STARTS_WITH predicate callback. + * + * Check if the 'whole' string starts from 'initial' string. + */ +static JsonPathBool +executeStartsWith(JsonPathItem *jsp, JsonbValue *whole, JsonbValue *initial, + void *param) +{ + if (!(whole = getScalar(whole, jbvString))) + return jpbUnknown; /* error */ + + if (!(initial = getScalar(initial, jbvString))) + return jpbUnknown; /* error */ + + if (whole->val.string.len >= initial->val.string.len && + !memcmp(whole->val.string.val, + initial->val.string.val, + initial->val.string.len)) + return jpbTrue; + + return jpbFalse; +} + +/* + * LIKE_REGEX predicate callback. + * + * Check if the string matches regex pattern. + */ +static JsonPathBool +executeLikeRegex(JsonPathItem *jsp, JsonbValue *str, JsonbValue *rarg, + void *param) +{ + JsonLikeRegexContext *cxt = param; + + if (!(str = getScalar(str, jbvString))) + return jpbUnknown; + + /* Cache regex text and converted flags. */ + if (!cxt->regex) + { + cxt->regex = + cstring_to_text_with_len(jsp->content.like_regex.pattern, + jsp->content.like_regex.patternlen); + (void) jspConvertRegexFlags(jsp->content.like_regex.flags, + &(cxt->cflags), NULL); + } + + if (RE_compile_and_execute(cxt->regex, str->val.string.val, + str->val.string.len, + cxt->cflags, DEFAULT_COLLATION_OID, 0, NULL)) + return jpbTrue; + + return jpbFalse; +} + +/* + * Execute numeric item methods (.abs(), .floor(), .ceil()) using the specified + * user function 'func'. + */ +static JsonPathExecResult +executeNumericItemMethod(JsonPathExecContext *cxt, JsonPathItem *jsp, + JsonbValue *jb, bool unwrap, PGFunction func, + JsonValueList *found) +{ + JsonPathItem next; + Datum datum; + + if (unwrap && JsonbType(jb) == jbvArray) + return executeItemUnwrapTargetArray(cxt, jsp, jb, found, false); + + if (!(jb = getScalar(jb, jbvNumeric))) + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM), + errmsg("jsonpath item method .%s() can only be applied to a numeric value", + jspOperationName(jsp->type))))); + + datum = DirectFunctionCall1(func, NumericGetDatum(jb->val.numeric)); + + if (!jspGetNext(jsp, &next) && !found) + return jperOk; + + jb = palloc(sizeof(*jb)); + jb->type = jbvNumeric; + jb->val.numeric = DatumGetNumeric(datum); + + return executeNextItem(cxt, jsp, &next, jb, found, false); +} + +/* + * Implementation of the .datetime() method. + * + * Converts a string into a date/time value. The actual type is determined at run time. + * If an argument is provided, this argument is used as a template string. + * Otherwise, the first fitting ISO format is selected. + */ +static JsonPathExecResult +executeDateTimeMethod(JsonPathExecContext *cxt, JsonPathItem *jsp, + JsonbValue *jb, JsonValueList *found) +{ + JsonbValue jbvbuf; + Datum value; + text *datetime; + Oid collid; + Oid typid; + int32 typmod = -1; + int tz = 0; + bool hasNext; + JsonPathExecResult res = jperNotFound; + JsonPathItem elem; + + if (!(jb = getScalar(jb, jbvString))) + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_INVALID_ARGUMENT_FOR_SQL_JSON_DATETIME_FUNCTION), + errmsg("jsonpath item method .%s() can only be applied to a string", + jspOperationName(jsp->type))))); + + datetime = cstring_to_text_with_len(jb->val.string.val, + jb->val.string.len); + + /* + * At some point we might wish to have callers supply the collation to + * use, but right now it's unclear that they'd be able to do better than + * DEFAULT_COLLATION_OID anyway. + */ + collid = DEFAULT_COLLATION_OID; + + if (jsp->content.arg) + { + text *template; + char *template_str; + int template_len; + ErrorSaveContext escontext = {T_ErrorSaveContext}; + + jspGetArg(jsp, &elem); + + if (elem.type != jpiString) + elog(ERROR, "invalid jsonpath item type for .datetime() argument"); + + template_str = jspGetString(&elem, &template_len); + + template = cstring_to_text_with_len(template_str, + template_len); + + value = parse_datetime(datetime, template, collid, true, + &typid, &typmod, &tz, + jspThrowErrors(cxt) ? NULL : (Node *) &escontext); + + if (escontext.error_occurred) + res = jperError; + else + res = jperOk; + } + else + { + /* + * According to SQL/JSON standard enumerate ISO formats for: date, + * timetz, time, timestamptz, timestamp. + * + * We also support ISO 8601 format (with "T") for timestamps, because + * to_json[b]() functions use this format. + */ + static __thread const char *fmt_str[] = + { + "yyyy-mm-dd", /* date */ + "HH24:MI:SS.USTZH:TZM", /* timetz */ + "HH24:MI:SS.USTZH", + "HH24:MI:SSTZH:TZM", + "HH24:MI:SSTZH", + "HH24:MI:SS.US", /* time without tz */ + "HH24:MI:SS", + "yyyy-mm-dd HH24:MI:SS.USTZH:TZM", /* timestamptz */ + "yyyy-mm-dd HH24:MI:SS.USTZH", + "yyyy-mm-dd HH24:MI:SSTZH:TZM", + "yyyy-mm-dd HH24:MI:SSTZH", + "yyyy-mm-dd\"T\"HH24:MI:SS.USTZH:TZM", + "yyyy-mm-dd\"T\"HH24:MI:SS.USTZH", + "yyyy-mm-dd\"T\"HH24:MI:SSTZH:TZM", + "yyyy-mm-dd\"T\"HH24:MI:SSTZH", + "yyyy-mm-dd HH24:MI:SS.US", /* timestamp without tz */ + "yyyy-mm-dd HH24:MI:SS", + "yyyy-mm-dd\"T\"HH24:MI:SS.US", + "yyyy-mm-dd\"T\"HH24:MI:SS" + }; + + /* cache for format texts */ + static __thread text *fmt_txt[lengthof(fmt_str)] = {0}; + int i; + + /* loop until datetime format fits */ + for (i = 0; i < lengthof(fmt_str); i++) + { + ErrorSaveContext escontext = {T_ErrorSaveContext}; + + if (!fmt_txt[i]) + { + MemoryContext oldcxt = + MemoryContextSwitchTo(TopMemoryContext); + + fmt_txt[i] = cstring_to_text(fmt_str[i]); + MemoryContextSwitchTo(oldcxt); + } + + value = parse_datetime(datetime, fmt_txt[i], collid, true, + &typid, &typmod, &tz, + (Node *) &escontext); + + if (!escontext.error_occurred) + { + res = jperOk; + break; + } + } + + if (res == jperNotFound) + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_INVALID_ARGUMENT_FOR_SQL_JSON_DATETIME_FUNCTION), + errmsg("datetime format is not recognized: \"%s\"", + text_to_cstring(datetime)), + errhint("Use a datetime template argument to specify the input data format.")))); + } + + pfree(datetime); + + if (jperIsError(res)) + return res; + + hasNext = jspGetNext(jsp, &elem); + + if (!hasNext && !found) + return res; + + jb = hasNext ? &jbvbuf : palloc(sizeof(*jb)); + + jb->type = jbvDatetime; + jb->val.datetime.value = value; + jb->val.datetime.typid = typid; + jb->val.datetime.typmod = typmod; + jb->val.datetime.tz = tz; + + return executeNextItem(cxt, jsp, &elem, jb, found, hasNext); +} + +/* + * Implementation of .keyvalue() method. + * + * .keyvalue() method returns a sequence of object's key-value pairs in the + * following format: '{ "key": key, "value": value, "id": id }'. + * + * "id" field is an object identifier which is constructed from the two parts: + * base object id and its binary offset in base object's jsonb: + * id = 10000000000 * base_object_id + obj_offset_in_base_object + * + * 10000000000 (10^10) -- is a first round decimal number greater than 2^32 + * (maximal offset in jsonb). Decimal multiplier is used here to improve the + * readability of identifiers. + * + * Base object is usually a root object of the path: context item '$' or path + * variable '$var', literals can't produce objects for now. But if the path + * contains generated objects (.keyvalue() itself, for example), then they + * become base object for the subsequent .keyvalue(). + * + * Id of '$' is 0. Id of '$var' is its ordinal (positive) number in the list + * of variables (see getJsonPathVariable()). Ids for generated objects + * are assigned using global counter JsonPathExecContext.lastGeneratedObjectId. + */ +static JsonPathExecResult +executeKeyValueMethod(JsonPathExecContext *cxt, JsonPathItem *jsp, + JsonbValue *jb, JsonValueList *found) +{ + JsonPathExecResult res = jperNotFound; + JsonPathItem next; + JsonbContainer *jbc; + JsonbValue key; + JsonbValue val; + JsonbValue idval; + JsonbValue keystr; + JsonbValue valstr; + JsonbValue idstr; + JsonbIterator *it; + JsonbIteratorToken tok; + int64 id; + bool hasNext; + + if (JsonbType(jb) != jbvObject || jb->type != jbvBinary) + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_SQL_JSON_OBJECT_NOT_FOUND), + errmsg("jsonpath item method .%s() can only be applied to an object", + jspOperationName(jsp->type))))); + + jbc = jb->val.binary.data; + + if (!JsonContainerSize(jbc)) + return jperNotFound; /* no key-value pairs */ + + hasNext = jspGetNext(jsp, &next); + + keystr.type = jbvString; + keystr.val.string.val = "key"; + keystr.val.string.len = 3; + + valstr.type = jbvString; + valstr.val.string.val = "value"; + valstr.val.string.len = 5; + + idstr.type = jbvString; + idstr.val.string.val = "id"; + idstr.val.string.len = 2; + + /* construct object id from its base object and offset inside that */ + id = jb->type != jbvBinary ? 0 : + (int64) ((char *) jbc - (char *) cxt->baseObject.jbc); + id += (int64) cxt->baseObject.id * INT64CONST(10000000000); + + idval.type = jbvNumeric; + idval.val.numeric = int64_to_numeric(id); + + it = JsonbIteratorInit(jbc); + + while ((tok = JsonbIteratorNext(&it, &key, true)) != WJB_DONE) + { + JsonBaseObjectInfo baseObject; + JsonbValue obj; + JsonbParseState *ps; + JsonbValue *keyval; + Jsonb *jsonb; + + if (tok != WJB_KEY) + continue; + + res = jperOk; + + if (!hasNext && !found) + break; + + tok = JsonbIteratorNext(&it, &val, true); + Assert(tok == WJB_VALUE); + + ps = NULL; + pushJsonbValue(&ps, WJB_BEGIN_OBJECT, NULL); + + pushJsonbValue(&ps, WJB_KEY, &keystr); + pushJsonbValue(&ps, WJB_VALUE, &key); + + pushJsonbValue(&ps, WJB_KEY, &valstr); + pushJsonbValue(&ps, WJB_VALUE, &val); + + pushJsonbValue(&ps, WJB_KEY, &idstr); + pushJsonbValue(&ps, WJB_VALUE, &idval); + + keyval = pushJsonbValue(&ps, WJB_END_OBJECT, NULL); + + jsonb = JsonbValueToJsonb(keyval); + + JsonbInitBinary(&obj, jsonb); + + baseObject = setBaseObject(cxt, &obj, cxt->lastGeneratedObjectId++); + + res = executeNextItem(cxt, jsp, &next, &obj, found, true); + + cxt->baseObject = baseObject; + + if (jperIsError(res)) + return res; + + if (res == jperOk && !found) + break; + } + + return res; +} + +/* + * Convert boolean execution status 'res' to a boolean JSON item and execute + * next jsonpath. + */ +static JsonPathExecResult +appendBoolResult(JsonPathExecContext *cxt, JsonPathItem *jsp, + JsonValueList *found, JsonPathBool res) +{ + JsonPathItem next; + JsonbValue jbv; + + if (!jspGetNext(jsp, &next) && !found) + return jperOk; /* found singleton boolean value */ + + if (res == jpbUnknown) + { + jbv.type = jbvNull; + } + else + { + jbv.type = jbvBool; + jbv.val.boolean = res == jpbTrue; + } + + return executeNextItem(cxt, jsp, &next, &jbv, found, true); +} + +/* + * Convert jsonpath's scalar or variable node to actual jsonb value. + * + * If node is a variable then its id returned, otherwise 0 returned. + */ +static void +getJsonPathItem(JsonPathExecContext *cxt, JsonPathItem *item, + JsonbValue *value) +{ + switch (item->type) + { + case jpiNull: + value->type = jbvNull; + break; + case jpiBool: + value->type = jbvBool; + value->val.boolean = jspGetBool(item); + break; + case jpiNumeric: + value->type = jbvNumeric; + value->val.numeric = jspGetNumeric(item); + break; + case jpiString: + value->type = jbvString; + value->val.string.val = jspGetString(item, + &value->val.string.len); + break; + case jpiVariable: + getJsonPathVariable(cxt, item, cxt->vars, value); + return; + default: + elog(ERROR, "unexpected jsonpath item type"); + } +} + +/* + * Get the value of variable passed to jsonpath executor + */ +static void +getJsonPathVariable(JsonPathExecContext *cxt, JsonPathItem *variable, + Jsonb *vars, JsonbValue *value) +{ + char *varName; + int varNameLength; + JsonbValue tmp; + JsonbValue *v; + + if (!vars) + { + value->type = jbvNull; + return; + } + + Assert(variable->type == jpiVariable); + varName = jspGetString(variable, &varNameLength); + tmp.type = jbvString; + tmp.val.string.val = varName; + tmp.val.string.len = varNameLength; + + v = findJsonbValueFromContainer(&vars->root, JB_FOBJECT, &tmp); + + if (v) + { + *value = *v; + pfree(v); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("could not find jsonpath variable \"%s\"", + pnstrdup(varName, varNameLength)))); + } + + JsonbInitBinary(&tmp, vars); + setBaseObject(cxt, &tmp, 1); +} + +/**************** Support functions for JsonPath execution *****************/ + +/* + * Returns the size of an array item, or -1 if item is not an array. + */ +static int +JsonbArraySize(JsonbValue *jb) +{ + Assert(jb->type != jbvArray); + + if (jb->type == jbvBinary) + { + JsonbContainer *jbc = jb->val.binary.data; + + if (JsonContainerIsArray(jbc) && !JsonContainerIsScalar(jbc)) + return JsonContainerSize(jbc); + } + + return -1; +} + +/* Comparison predicate callback. */ +static JsonPathBool +executeComparison(JsonPathItem *cmp, JsonbValue *lv, JsonbValue *rv, void *p) +{ + JsonPathExecContext *cxt = (JsonPathExecContext *) p; + + return compareItems(cmp->type, lv, rv, cxt->useTz); +} + +/* + * Perform per-byte comparison of two strings. + */ +static int +binaryCompareStrings(const char *s1, int len1, + const char *s2, int len2) +{ + int cmp; + + cmp = memcmp(s1, s2, Min(len1, len2)); + + if (cmp != 0) + return cmp; + + if (len1 == len2) + return 0; + + return len1 < len2 ? -1 : 1; +} + +/* + * Compare two strings in the current server encoding using Unicode codepoint + * collation. + */ +static int +compareStrings(const char *mbstr1, int mblen1, + const char *mbstr2, int mblen2) +{ + if (GetDatabaseEncoding() == PG_SQL_ASCII || + GetDatabaseEncoding() == PG_UTF8) + { + /* + * It's known property of UTF-8 strings that their per-byte comparison + * result matches codepoints comparison result. ASCII can be + * considered as special case of UTF-8. + */ + return binaryCompareStrings(mbstr1, mblen1, mbstr2, mblen2); + } + else + { + char *utf8str1, + *utf8str2; + int cmp, + utf8len1, + utf8len2; + + /* + * We have to convert other encodings to UTF-8 first, then compare. + * Input strings may be not null-terminated and pg_server_to_any() may + * return them "as is". So, use strlen() only if there is real + * conversion. + */ + utf8str1 = pg_server_to_any(mbstr1, mblen1, PG_UTF8); + utf8str2 = pg_server_to_any(mbstr2, mblen2, PG_UTF8); + utf8len1 = (mbstr1 == utf8str1) ? mblen1 : strlen(utf8str1); + utf8len2 = (mbstr2 == utf8str2) ? mblen2 : strlen(utf8str2); + + cmp = binaryCompareStrings(utf8str1, utf8len1, utf8str2, utf8len2); + + /* + * If pg_server_to_any() did no real conversion, then we actually + * compared original strings. So, we already done. + */ + if (mbstr1 == utf8str1 && mbstr2 == utf8str2) + return cmp; + + /* Free memory if needed */ + if (mbstr1 != utf8str1) + pfree(utf8str1); + if (mbstr2 != utf8str2) + pfree(utf8str2); + + /* + * When all Unicode codepoints are equal, return result of binary + * comparison. In some edge cases, same characters may have different + * representations in encoding. Then our behavior could diverge from + * standard. However, that allow us to do simple binary comparison + * for "==" operator, which is performance critical in typical cases. + * In future to implement strict standard conformance, we can do + * normalization of input JSON strings. + */ + if (cmp == 0) + return binaryCompareStrings(mbstr1, mblen1, mbstr2, mblen2); + else + return cmp; + } +} + +/* + * Compare two SQL/JSON items using comparison operation 'op'. + */ +static JsonPathBool +compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2, bool useTz) +{ + int cmp; + bool res; + + if (jb1->type != jb2->type) + { + if (jb1->type == jbvNull || jb2->type == jbvNull) + + /* + * Equality and order comparison of nulls to non-nulls returns + * always false, but inequality comparison returns true. + */ + return op == jpiNotEqual ? jpbTrue : jpbFalse; + + /* Non-null items of different types are not comparable. */ + return jpbUnknown; + } + + switch (jb1->type) + { + case jbvNull: + cmp = 0; + break; + case jbvBool: + cmp = jb1->val.boolean == jb2->val.boolean ? 0 : + jb1->val.boolean ? 1 : -1; + break; + case jbvNumeric: + cmp = compareNumeric(jb1->val.numeric, jb2->val.numeric); + break; + case jbvString: + if (op == jpiEqual) + return jb1->val.string.len != jb2->val.string.len || + memcmp(jb1->val.string.val, + jb2->val.string.val, + jb1->val.string.len) ? jpbFalse : jpbTrue; + + cmp = compareStrings(jb1->val.string.val, jb1->val.string.len, + jb2->val.string.val, jb2->val.string.len); + break; + case jbvDatetime: + { + bool cast_error; + + cmp = compareDatetime(jb1->val.datetime.value, + jb1->val.datetime.typid, + jb2->val.datetime.value, + jb2->val.datetime.typid, + useTz, + &cast_error); + + if (cast_error) + return jpbUnknown; + } + break; + + case jbvBinary: + case jbvArray: + case jbvObject: + return jpbUnknown; /* non-scalars are not comparable */ + + default: + elog(ERROR, "invalid jsonb value type %d", jb1->type); + } + + switch (op) + { + case jpiEqual: + res = (cmp == 0); + break; + case jpiNotEqual: + res = (cmp != 0); + break; + case jpiLess: + res = (cmp < 0); + break; + case jpiGreater: + res = (cmp > 0); + break; + case jpiLessOrEqual: + res = (cmp <= 0); + break; + case jpiGreaterOrEqual: + res = (cmp >= 0); + break; + default: + elog(ERROR, "unrecognized jsonpath operation: %d", op); + return jpbUnknown; + } + + return res ? jpbTrue : jpbFalse; +} + +/* Compare two numerics */ +static int +compareNumeric(Numeric a, Numeric b) +{ + return DatumGetInt32(DirectFunctionCall2(numeric_cmp, + NumericGetDatum(a), + NumericGetDatum(b))); +} + +static JsonbValue * +copyJsonbValue(JsonbValue *src) +{ + JsonbValue *dst = palloc(sizeof(*dst)); + + *dst = *src; + + return dst; +} + +/* + * Execute array subscript expression and convert resulting numeric item to + * the integer type with truncation. + */ +static JsonPathExecResult +getArrayIndex(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, + int32 *index) +{ + JsonbValue *jbv; + JsonValueList found = {0}; + JsonPathExecResult res = executeItem(cxt, jsp, jb, &found); + Datum numeric_index; + bool have_error = false; + + if (jperIsError(res)) + return res; + + if (JsonValueListLength(&found) != 1 || + !(jbv = getScalar(JsonValueListHead(&found), jbvNumeric))) + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_INVALID_SQL_JSON_SUBSCRIPT), + errmsg("jsonpath array subscript is not a single numeric value")))); + + numeric_index = DirectFunctionCall2(numeric_trunc, + NumericGetDatum(jbv->val.numeric), + Int32GetDatum(0)); + + *index = numeric_int4_opt_error(DatumGetNumeric(numeric_index), + &have_error); + + if (have_error) + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_INVALID_SQL_JSON_SUBSCRIPT), + errmsg("jsonpath array subscript is out of integer range")))); + + return jperOk; +} + +/* Save base object and its id needed for the execution of .keyvalue(). */ +static JsonBaseObjectInfo +setBaseObject(JsonPathExecContext *cxt, JsonbValue *jbv, int32 id) +{ + JsonBaseObjectInfo baseObject = cxt->baseObject; + + cxt->baseObject.jbc = jbv->type != jbvBinary ? NULL : + (JsonbContainer *) jbv->val.binary.data; + cxt->baseObject.id = id; + + return baseObject; +} + +static void +JsonValueListAppend(JsonValueList *jvl, JsonbValue *jbv) +{ + if (jvl->singleton) + { + jvl->list = list_make2(jvl->singleton, jbv); + jvl->singleton = NULL; + } + else if (!jvl->list) + jvl->singleton = jbv; + else + jvl->list = lappend(jvl->list, jbv); +} + +static int +JsonValueListLength(const JsonValueList *jvl) +{ + return jvl->singleton ? 1 : list_length(jvl->list); +} + +static bool +JsonValueListIsEmpty(JsonValueList *jvl) +{ + return !jvl->singleton && (jvl->list == NIL); +} + +static JsonbValue * +JsonValueListHead(JsonValueList *jvl) +{ + return jvl->singleton ? jvl->singleton : linitial(jvl->list); +} + +static List * +JsonValueListGetList(JsonValueList *jvl) +{ + if (jvl->singleton) + return list_make1(jvl->singleton); + + return jvl->list; +} + +static void +JsonValueListInitIterator(const JsonValueList *jvl, JsonValueListIterator *it) +{ + if (jvl->singleton) + { + it->value = jvl->singleton; + it->list = NIL; + it->next = NULL; + } + else if (jvl->list != NIL) + { + it->value = (JsonbValue *) linitial(jvl->list); + it->list = jvl->list; + it->next = list_second_cell(jvl->list); + } + else + { + it->value = NULL; + it->list = NIL; + it->next = NULL; + } +} + +/* + * Get the next item from the sequence advancing iterator. + */ +static JsonbValue * +JsonValueListNext(const JsonValueList *jvl, JsonValueListIterator *it) +{ + JsonbValue *result = it->value; + + if (it->next) + { + it->value = lfirst(it->next); + it->next = lnext(it->list, it->next); + } + else + { + it->value = NULL; + } + + return result; +} + +/* + * Initialize a binary JsonbValue with the given jsonb container. + */ +static JsonbValue * +JsonbInitBinary(JsonbValue *jbv, Jsonb *jb) +{ + jbv->type = jbvBinary; + jbv->val.binary.data = &jb->root; + jbv->val.binary.len = VARSIZE_ANY_EXHDR(jb); + + return jbv; +} + +/* + * Returns jbv* type of JsonbValue. Note, it never returns jbvBinary as is. + */ +static int +JsonbType(JsonbValue *jb) +{ + int type = jb->type; + + if (jb->type == jbvBinary) + { + JsonbContainer *jbc = (void *) jb->val.binary.data; + + /* Scalars should be always extracted during jsonpath execution. */ + Assert(!JsonContainerIsScalar(jbc)); + + if (JsonContainerIsObject(jbc)) + type = jbvObject; + else if (JsonContainerIsArray(jbc)) + type = jbvArray; + else + elog(ERROR, "invalid jsonb container type: 0x%08x", jbc->header); + } + + return type; +} + +/* Get scalar of given type or NULL on type mismatch */ +static JsonbValue * +getScalar(JsonbValue *scalar, enum jbvType type) +{ + /* Scalars should be always extracted during jsonpath execution. */ + Assert(scalar->type != jbvBinary || + !JsonContainerIsScalar(scalar->val.binary.data)); + + return scalar->type == type ? scalar : NULL; +} + +/* Construct a JSON array from the item list */ +static JsonbValue * +wrapItemsInArray(const JsonValueList *items) +{ + JsonbParseState *ps = NULL; + JsonValueListIterator it; + JsonbValue *jbv; + + pushJsonbValue(&ps, WJB_BEGIN_ARRAY, NULL); + + JsonValueListInitIterator(items, &it); + while ((jbv = JsonValueListNext(items, &it))) + pushJsonbValue(&ps, WJB_ELEM, jbv); + + return pushJsonbValue(&ps, WJB_END_ARRAY, NULL); +} + +/* Check if the timezone required for casting from type1 to type2 is used */ +static void +checkTimezoneIsUsedForCast(bool useTz, const char *type1, const char *type2) +{ + if (!useTz) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot convert value from %s to %s without time zone usage", + type1, type2), + errhint("Use *_tz() function for time zone support."))); +} + +/* Convert time datum to timetz datum */ +static Datum +castTimeToTimeTz(Datum time, bool useTz) +{ + checkTimezoneIsUsedForCast(useTz, "time", "timetz"); + + return DirectFunctionCall1(time_timetz, time); +} + +/* + * Compare date to timestamp. + * Note that this doesn't involve any timezone considerations. + */ +static int +cmpDateToTimestamp(DateADT date1, Timestamp ts2, bool useTz) +{ + return date_cmp_timestamp_internal(date1, ts2); +} + +/* + * Compare date to timestamptz. + */ +static int +cmpDateToTimestampTz(DateADT date1, TimestampTz tstz2, bool useTz) +{ + checkTimezoneIsUsedForCast(useTz, "date", "timestamptz"); + + return date_cmp_timestamptz_internal(date1, tstz2); +} + +/* + * Compare timestamp to timestamptz. + */ +static int +cmpTimestampToTimestampTz(Timestamp ts1, TimestampTz tstz2, bool useTz) +{ + checkTimezoneIsUsedForCast(useTz, "timestamp", "timestamptz"); + + return timestamp_cmp_timestamptz_internal(ts1, tstz2); +} + +/* + * Cross-type comparison of two datetime SQL/JSON items. If items are + * uncomparable *cast_error flag is set, otherwise *cast_error is unset. + * If the cast requires timezone and it is not used, then explicit error is thrown. + */ +static int +compareDatetime(Datum val1, Oid typid1, Datum val2, Oid typid2, + bool useTz, bool *cast_error) +{ + PGFunction cmpfunc; + + *cast_error = false; + + switch (typid1) + { + case DATEOID: + switch (typid2) + { + case DATEOID: + cmpfunc = date_cmp; + + break; + + case TIMESTAMPOID: + return cmpDateToTimestamp(DatumGetDateADT(val1), + DatumGetTimestamp(val2), + useTz); + + case TIMESTAMPTZOID: + return cmpDateToTimestampTz(DatumGetDateADT(val1), + DatumGetTimestampTz(val2), + useTz); + + case TIMEOID: + case TIMETZOID: + *cast_error = true; /* uncomparable types */ + return 0; + + default: + elog(ERROR, "unrecognized SQL/JSON datetime type oid: %u", + typid2); + } + break; + + case TIMEOID: + switch (typid2) + { + case TIMEOID: + cmpfunc = time_cmp; + + break; + + case TIMETZOID: + val1 = castTimeToTimeTz(val1, useTz); + cmpfunc = timetz_cmp; + + break; + + case DATEOID: + case TIMESTAMPOID: + case TIMESTAMPTZOID: + *cast_error = true; /* uncomparable types */ + return 0; + + default: + elog(ERROR, "unrecognized SQL/JSON datetime type oid: %u", + typid2); + } + break; + + case TIMETZOID: + switch (typid2) + { + case TIMEOID: + val2 = castTimeToTimeTz(val2, useTz); + cmpfunc = timetz_cmp; + + break; + + case TIMETZOID: + cmpfunc = timetz_cmp; + + break; + + case DATEOID: + case TIMESTAMPOID: + case TIMESTAMPTZOID: + *cast_error = true; /* uncomparable types */ + return 0; + + default: + elog(ERROR, "unrecognized SQL/JSON datetime type oid: %u", + typid2); + } + break; + + case TIMESTAMPOID: + switch (typid2) + { + case DATEOID: + return -cmpDateToTimestamp(DatumGetDateADT(val2), + DatumGetTimestamp(val1), + useTz); + + case TIMESTAMPOID: + cmpfunc = timestamp_cmp; + + break; + + case TIMESTAMPTZOID: + return cmpTimestampToTimestampTz(DatumGetTimestamp(val1), + DatumGetTimestampTz(val2), + useTz); + + case TIMEOID: + case TIMETZOID: + *cast_error = true; /* uncomparable types */ + return 0; + + default: + elog(ERROR, "unrecognized SQL/JSON datetime type oid: %u", + typid2); + } + break; + + case TIMESTAMPTZOID: + switch (typid2) + { + case DATEOID: + return -cmpDateToTimestampTz(DatumGetDateADT(val2), + DatumGetTimestampTz(val1), + useTz); + + case TIMESTAMPOID: + return -cmpTimestampToTimestampTz(DatumGetTimestamp(val2), + DatumGetTimestampTz(val1), + useTz); + + case TIMESTAMPTZOID: + cmpfunc = timestamp_cmp; + + break; + + case TIMEOID: + case TIMETZOID: + *cast_error = true; /* uncomparable types */ + return 0; + + default: + elog(ERROR, "unrecognized SQL/JSON datetime type oid: %u", + typid2); + } + break; + + default: + elog(ERROR, "unrecognized SQL/JSON datetime type oid: %u", typid1); + } + + if (*cast_error) + return 0; /* cast error */ + + return DatumGetInt32(DirectFunctionCall2(cmpfunc, val1, val2)); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/jsonpath_gram.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/jsonpath_gram.c new file mode 100644 index 00000000000..6c5892a0152 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/jsonpath_gram.c @@ -0,0 +1,2334 @@ +/* A Bison parser, made by GNU Bison 3.7.5. */ + +/* Bison implementation for Yacc-like parsers in C + + Copyright (C) 1984, 1989-1990, 2000-2015, 2018-2021 Free Software Foundation, + Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +/* As a special exception, you may create a larger work that contains + part or all of the Bison parser skeleton and distribute that work + under terms of your choice, so long as that work isn't itself a + parser generator using the skeleton or a modified version thereof + as a parser skeleton. Alternatively, if you modify or redistribute + the parser skeleton itself, you may (at your option) remove this + special exception, which will cause the skeleton and the resulting + Bison output files to be licensed under the GNU General Public + License without this special exception. + + This special exception was added by the Free Software Foundation in + version 2.2 of Bison. */ + +/* C LALR(1) parser skeleton written by Richard Stallman, by + simplifying the original so-called "semantic" parser. */ + +/* DO NOT RELY ON FEATURES THAT ARE NOT DOCUMENTED in the manual, + especially those whose name start with YY_ or yy_. They are + private implementation details that can be changed or removed. */ + +/* All symbols defined below should begin with yy or YY, to avoid + infringing on user name space. This should be done even for local + variables, as they might otherwise be expanded by user macros. + There are some unavoidable exceptions within include files to + define necessary library symbols; they are noted "INFRINGES ON + USER NAME SPACE" below. */ + +/* Identify Bison output, and Bison version. */ +#define YYBISON 30705 + +/* Bison version string. */ +#define YYBISON_VERSION "3.7.5" + +/* Skeleton name. */ +#define YYSKELETON_NAME "yacc.c" + +/* Pure parsers. */ +#define YYPURE 1 + +/* Push parsers. */ +#define YYPUSH 0 + +/* Pull parsers. */ +#define YYPULL 1 + + +/* Substitute the variable and function names. */ +#define yyparse jsonpath_yyparse +#define yylex jsonpath_yylex +#define yyerror jsonpath_yyerror +#define yydebug jsonpath_yydebug +#define yynerrs jsonpath_yynerrs + +/* First part of user prologue. */ +#line 1 "jsonpath_gram.y" + +/*------------------------------------------------------------------------- + * + * jsonpath_gram.y + * Grammar definitions for jsonpath datatype + * + * Transforms tokenized jsonpath into tree of JsonPathParseItem structs. + * + * Copyright (c) 2019-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/utils/adt/jsonpath_gram.y + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "catalog/pg_collation.h" +#include "fmgr.h" +#include "jsonpath_internal.h" +#include "miscadmin.h" +#include "nodes/pg_list.h" +#include "regex/regex.h" +#include "utils/builtins.h" + +static JsonPathParseItem *makeItemType(JsonPathItemType type); +static JsonPathParseItem *makeItemString(JsonPathString *s); +static JsonPathParseItem *makeItemVariable(JsonPathString *s); +static JsonPathParseItem *makeItemKey(JsonPathString *s); +static JsonPathParseItem *makeItemNumeric(JsonPathString *s); +static JsonPathParseItem *makeItemBool(bool val); +static JsonPathParseItem *makeItemBinary(JsonPathItemType type, + JsonPathParseItem *la, + JsonPathParseItem *ra); +static JsonPathParseItem *makeItemUnary(JsonPathItemType type, + JsonPathParseItem *a); +static JsonPathParseItem *makeItemList(List *list); +static JsonPathParseItem *makeIndexArray(List *list); +static JsonPathParseItem *makeAny(int first, int last); +static bool makeItemLikeRegex(JsonPathParseItem *expr, + JsonPathString *pattern, + JsonPathString *flags, + JsonPathParseItem ** result, + struct Node *escontext); + +/* + * Bison doesn't allocate anything that needs to live across parser calls, + * so we can easily have it use palloc instead of malloc. This prevents + * memory leaks if we error out during parsing. + */ +#define YYMALLOC palloc +#define YYFREE pfree + + +#line 132 "jsonpath_gram.c" + +# ifndef YY_CAST +# ifdef __cplusplus +# define YY_CAST(Type, Val) static_cast<Type> (Val) +# define YY_REINTERPRET_CAST(Type, Val) reinterpret_cast<Type> (Val) +# else +# define YY_CAST(Type, Val) ((Type) (Val)) +# define YY_REINTERPRET_CAST(Type, Val) ((Type) (Val)) +# endif +# endif +# ifndef YY_NULLPTR +# if defined __cplusplus +# if 201103L <= __cplusplus +# define YY_NULLPTR nullptr +# else +# define YY_NULLPTR 0 +# endif +# else +# define YY_NULLPTR ((void*)0) +# endif +# endif + +#include "jsonpath_gram.h" +/* Symbol kind. */ +enum yysymbol_kind_t +{ + YYSYMBOL_YYEMPTY = -2, + YYSYMBOL_YYEOF = 0, /* "end of file" */ + YYSYMBOL_YYerror = 1, /* error */ + YYSYMBOL_YYUNDEF = 2, /* "invalid token" */ + YYSYMBOL_TO_P = 3, /* TO_P */ + YYSYMBOL_NULL_P = 4, /* NULL_P */ + YYSYMBOL_TRUE_P = 5, /* TRUE_P */ + YYSYMBOL_FALSE_P = 6, /* FALSE_P */ + YYSYMBOL_IS_P = 7, /* IS_P */ + YYSYMBOL_UNKNOWN_P = 8, /* UNKNOWN_P */ + YYSYMBOL_EXISTS_P = 9, /* EXISTS_P */ + YYSYMBOL_IDENT_P = 10, /* IDENT_P */ + YYSYMBOL_STRING_P = 11, /* STRING_P */ + YYSYMBOL_NUMERIC_P = 12, /* NUMERIC_P */ + YYSYMBOL_INT_P = 13, /* INT_P */ + YYSYMBOL_VARIABLE_P = 14, /* VARIABLE_P */ + YYSYMBOL_OR_P = 15, /* OR_P */ + YYSYMBOL_AND_P = 16, /* AND_P */ + YYSYMBOL_NOT_P = 17, /* NOT_P */ + YYSYMBOL_LESS_P = 18, /* LESS_P */ + YYSYMBOL_LESSEQUAL_P = 19, /* LESSEQUAL_P */ + YYSYMBOL_EQUAL_P = 20, /* EQUAL_P */ + YYSYMBOL_NOTEQUAL_P = 21, /* NOTEQUAL_P */ + YYSYMBOL_GREATEREQUAL_P = 22, /* GREATEREQUAL_P */ + YYSYMBOL_GREATER_P = 23, /* GREATER_P */ + YYSYMBOL_ANY_P = 24, /* ANY_P */ + YYSYMBOL_STRICT_P = 25, /* STRICT_P */ + YYSYMBOL_LAX_P = 26, /* LAX_P */ + YYSYMBOL_LAST_P = 27, /* LAST_P */ + YYSYMBOL_STARTS_P = 28, /* STARTS_P */ + YYSYMBOL_WITH_P = 29, /* WITH_P */ + YYSYMBOL_LIKE_REGEX_P = 30, /* LIKE_REGEX_P */ + YYSYMBOL_FLAG_P = 31, /* FLAG_P */ + YYSYMBOL_ABS_P = 32, /* ABS_P */ + YYSYMBOL_SIZE_P = 33, /* SIZE_P */ + YYSYMBOL_TYPE_P = 34, /* TYPE_P */ + YYSYMBOL_FLOOR_P = 35, /* FLOOR_P */ + YYSYMBOL_DOUBLE_P = 36, /* DOUBLE_P */ + YYSYMBOL_CEILING_P = 37, /* CEILING_P */ + YYSYMBOL_KEYVALUE_P = 38, /* KEYVALUE_P */ + YYSYMBOL_DATETIME_P = 39, /* DATETIME_P */ + YYSYMBOL_40_ = 40, /* '+' */ + YYSYMBOL_41_ = 41, /* '-' */ + YYSYMBOL_42_ = 42, /* '*' */ + YYSYMBOL_43_ = 43, /* '/' */ + YYSYMBOL_44_ = 44, /* '%' */ + YYSYMBOL_UMINUS = 45, /* UMINUS */ + YYSYMBOL_46_ = 46, /* '(' */ + YYSYMBOL_47_ = 47, /* ')' */ + YYSYMBOL_48_ = 48, /* '$' */ + YYSYMBOL_49_ = 49, /* '@' */ + YYSYMBOL_50_ = 50, /* ',' */ + YYSYMBOL_51_ = 51, /* '[' */ + YYSYMBOL_52_ = 52, /* ']' */ + YYSYMBOL_53_ = 53, /* '{' */ + YYSYMBOL_54_ = 54, /* '}' */ + YYSYMBOL_55_ = 55, /* '.' */ + YYSYMBOL_56_ = 56, /* '?' */ + YYSYMBOL_YYACCEPT = 57, /* $accept */ + YYSYMBOL_result = 58, /* result */ + YYSYMBOL_expr_or_predicate = 59, /* expr_or_predicate */ + YYSYMBOL_mode = 60, /* mode */ + YYSYMBOL_scalar_value = 61, /* scalar_value */ + YYSYMBOL_comp_op = 62, /* comp_op */ + YYSYMBOL_delimited_predicate = 63, /* delimited_predicate */ + YYSYMBOL_predicate = 64, /* predicate */ + YYSYMBOL_starts_with_initial = 65, /* starts_with_initial */ + YYSYMBOL_path_primary = 66, /* path_primary */ + YYSYMBOL_accessor_expr = 67, /* accessor_expr */ + YYSYMBOL_expr = 68, /* expr */ + YYSYMBOL_index_elem = 69, /* index_elem */ + YYSYMBOL_index_list = 70, /* index_list */ + YYSYMBOL_array_accessor = 71, /* array_accessor */ + YYSYMBOL_any_level = 72, /* any_level */ + YYSYMBOL_any_path = 73, /* any_path */ + YYSYMBOL_accessor_op = 74, /* accessor_op */ + YYSYMBOL_datetime_template = 75, /* datetime_template */ + YYSYMBOL_opt_datetime_template = 76, /* opt_datetime_template */ + YYSYMBOL_key = 77, /* key */ + YYSYMBOL_key_name = 78, /* key_name */ + YYSYMBOL_method = 79 /* method */ +}; +typedef enum yysymbol_kind_t yysymbol_kind_t; + + + + +#ifdef short +# undef short +#endif + +/* On compilers that do not define __PTRDIFF_MAX__ etc., make sure + <limits.h> and (if available) <stdint.h> are included + so that the code can choose integer types of a good width. */ + +#ifndef __PTRDIFF_MAX__ +# include <limits.h> /* INFRINGES ON USER NAME SPACE */ +# if defined __STDC_VERSION__ && 199901 <= __STDC_VERSION__ +# include <stdint.h> /* INFRINGES ON USER NAME SPACE */ +# define YY_STDINT_H +# endif +#endif + +/* Narrow types that promote to a signed type and that can represent a + signed or unsigned integer of at least N bits. In tables they can + save space and decrease cache pressure. Promoting to a signed type + helps avoid bugs in integer arithmetic. */ + +#ifdef __INT_LEAST8_MAX__ +typedef __INT_LEAST8_TYPE__ yytype_int8; +#elif defined YY_STDINT_H +typedef int_least8_t yytype_int8; +#else +typedef signed char yytype_int8; +#endif + +#ifdef __INT_LEAST16_MAX__ +typedef __INT_LEAST16_TYPE__ yytype_int16; +#elif defined YY_STDINT_H +typedef int_least16_t yytype_int16; +#else +typedef short yytype_int16; +#endif + +/* Work around bug in HP-UX 11.23, which defines these macros + incorrectly for preprocessor constants. This workaround can likely + be removed in 2023, as HPE has promised support for HP-UX 11.23 + (aka HP-UX 11i v2) only through the end of 2022; see Table 2 of + <https://h20195.www2.hpe.com/V2/getpdf.aspx/4AA4-7673ENW.pdf>. */ +#ifdef __hpux +# undef UINT_LEAST8_MAX +# undef UINT_LEAST16_MAX +# define UINT_LEAST8_MAX 255 +# define UINT_LEAST16_MAX 65535 +#endif + +#if defined __UINT_LEAST8_MAX__ && __UINT_LEAST8_MAX__ <= __INT_MAX__ +typedef __UINT_LEAST8_TYPE__ yytype_uint8; +#elif (!defined __UINT_LEAST8_MAX__ && defined YY_STDINT_H \ + && UINT_LEAST8_MAX <= INT_MAX) +typedef uint_least8_t yytype_uint8; +#elif !defined __UINT_LEAST8_MAX__ && UCHAR_MAX <= INT_MAX +typedef unsigned char yytype_uint8; +#else +typedef short yytype_uint8; +#endif + +#if defined __UINT_LEAST16_MAX__ && __UINT_LEAST16_MAX__ <= __INT_MAX__ +typedef __UINT_LEAST16_TYPE__ yytype_uint16; +#elif (!defined __UINT_LEAST16_MAX__ && defined YY_STDINT_H \ + && UINT_LEAST16_MAX <= INT_MAX) +typedef uint_least16_t yytype_uint16; +#elif !defined __UINT_LEAST16_MAX__ && USHRT_MAX <= INT_MAX +typedef unsigned short yytype_uint16; +#else +typedef int yytype_uint16; +#endif + +#ifndef YYPTRDIFF_T +# if defined __PTRDIFF_TYPE__ && defined __PTRDIFF_MAX__ +# define YYPTRDIFF_T __PTRDIFF_TYPE__ +# define YYPTRDIFF_MAXIMUM __PTRDIFF_MAX__ +# elif defined PTRDIFF_MAX +# ifndef ptrdiff_t +# include <stddef.h> /* INFRINGES ON USER NAME SPACE */ +# endif +# define YYPTRDIFF_T ptrdiff_t +# define YYPTRDIFF_MAXIMUM PTRDIFF_MAX +# else +# define YYPTRDIFF_T long +# define YYPTRDIFF_MAXIMUM LONG_MAX +# endif +#endif + +#ifndef YYSIZE_T +# ifdef __SIZE_TYPE__ +# define YYSIZE_T __SIZE_TYPE__ +# elif defined size_t +# define YYSIZE_T size_t +# elif defined __STDC_VERSION__ && 199901 <= __STDC_VERSION__ +# include <stddef.h> /* INFRINGES ON USER NAME SPACE */ +# define YYSIZE_T size_t +# else +# define YYSIZE_T unsigned +# endif +#endif + +#define YYSIZE_MAXIMUM \ + YY_CAST (YYPTRDIFF_T, \ + (YYPTRDIFF_MAXIMUM < YY_CAST (YYSIZE_T, -1) \ + ? YYPTRDIFF_MAXIMUM \ + : YY_CAST (YYSIZE_T, -1))) + +#define YYSIZEOF(X) YY_CAST (YYPTRDIFF_T, sizeof (X)) + + +/* Stored state numbers (used for stacks). */ +typedef yytype_uint8 yy_state_t; + +/* State numbers in computations. */ +typedef int yy_state_fast_t; + +#ifndef YY_ +# if defined YYENABLE_NLS && YYENABLE_NLS +# if ENABLE_NLS +# include <libintl.h> /* INFRINGES ON USER NAME SPACE */ +# define YY_(Msgid) dgettext ("bison-runtime", Msgid) +# endif +# endif +# ifndef YY_ +# define YY_(Msgid) Msgid +# endif +#endif + + +#ifndef YY_ATTRIBUTE_PURE +# if defined __GNUC__ && 2 < __GNUC__ + (96 <= __GNUC_MINOR__) +# define YY_ATTRIBUTE_PURE __attribute__ ((__pure__)) +# else +# define YY_ATTRIBUTE_PURE +# endif +#endif + +#ifndef YY_ATTRIBUTE_UNUSED +# if defined __GNUC__ && 2 < __GNUC__ + (7 <= __GNUC_MINOR__) +# define YY_ATTRIBUTE_UNUSED __attribute__ ((__unused__)) +# else +# define YY_ATTRIBUTE_UNUSED +# endif +#endif + +/* Suppress unused-variable warnings by "using" E. */ +#if ! defined lint || defined __GNUC__ +# define YY_USE(E) ((void) (E)) +#else +# define YY_USE(E) /* empty */ +#endif + +#if defined __GNUC__ && ! defined __ICC && 407 <= __GNUC__ * 100 + __GNUC_MINOR__ +/* Suppress an incorrect diagnostic about yylval being uninitialized. */ +# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN \ + _Pragma ("GCC diagnostic push") \ + _Pragma ("GCC diagnostic ignored \"-Wuninitialized\"") \ + _Pragma ("GCC diagnostic ignored \"-Wmaybe-uninitialized\"") +# define YY_IGNORE_MAYBE_UNINITIALIZED_END \ + _Pragma ("GCC diagnostic pop") +#else +# define YY_INITIAL_VALUE(Value) Value +#endif +#ifndef YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN +# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN +# define YY_IGNORE_MAYBE_UNINITIALIZED_END +#endif +#ifndef YY_INITIAL_VALUE +# define YY_INITIAL_VALUE(Value) /* Nothing. */ +#endif + +#if defined __cplusplus && defined __GNUC__ && ! defined __ICC && 6 <= __GNUC__ +# define YY_IGNORE_USELESS_CAST_BEGIN \ + _Pragma ("GCC diagnostic push") \ + _Pragma ("GCC diagnostic ignored \"-Wuseless-cast\"") +# define YY_IGNORE_USELESS_CAST_END \ + _Pragma ("GCC diagnostic pop") +#endif +#ifndef YY_IGNORE_USELESS_CAST_BEGIN +# define YY_IGNORE_USELESS_CAST_BEGIN +# define YY_IGNORE_USELESS_CAST_END +#endif + + +#define YY_ASSERT(E) ((void) (0 && (E))) + +#if !defined yyoverflow + +/* The parser invokes alloca or malloc; define the necessary symbols. */ + +# ifdef YYSTACK_USE_ALLOCA +# if YYSTACK_USE_ALLOCA +# ifdef __GNUC__ +# define YYSTACK_ALLOC __builtin_alloca +# elif defined __BUILTIN_VA_ARG_INCR +# include <alloca.h> /* INFRINGES ON USER NAME SPACE */ +# elif defined _AIX +# define YYSTACK_ALLOC __alloca +# elif defined _MSC_VER +# include <malloc.h> /* INFRINGES ON USER NAME SPACE */ +# define alloca _alloca +# else +# define YYSTACK_ALLOC alloca +# if ! defined _ALLOCA_H && ! defined EXIT_SUCCESS +# include <stdlib.h> /* INFRINGES ON USER NAME SPACE */ + /* Use EXIT_SUCCESS as a witness for stdlib.h. */ +# ifndef EXIT_SUCCESS +# define EXIT_SUCCESS 0 +# endif +# endif +# endif +# endif +# endif + +# ifdef YYSTACK_ALLOC + /* Pacify GCC's 'empty if-body' warning. */ +# define YYSTACK_FREE(Ptr) do { /* empty */; } while (0) +# ifndef YYSTACK_ALLOC_MAXIMUM + /* The OS might guarantee only one guard page at the bottom of the stack, + and a page size can be as small as 4096 bytes. So we cannot safely + invoke alloca (N) if N exceeds 4096. Use a slightly smaller number + to allow for a few compiler-allocated temporary stack slots. */ +# define YYSTACK_ALLOC_MAXIMUM 4032 /* reasonable circa 2006 */ +# endif +# else +# define YYSTACK_ALLOC YYMALLOC +# define YYSTACK_FREE YYFREE +# ifndef YYSTACK_ALLOC_MAXIMUM +# define YYSTACK_ALLOC_MAXIMUM YYSIZE_MAXIMUM +# endif +# if (defined __cplusplus && ! defined EXIT_SUCCESS \ + && ! ((defined YYMALLOC || defined malloc) \ + && (defined YYFREE || defined free))) +# include <stdlib.h> /* INFRINGES ON USER NAME SPACE */ +# ifndef EXIT_SUCCESS +# define EXIT_SUCCESS 0 +# endif +# endif +# ifndef YYMALLOC +# define YYMALLOC malloc +# if ! defined malloc && ! defined EXIT_SUCCESS +void *malloc (YYSIZE_T); /* INFRINGES ON USER NAME SPACE */ +# endif +# endif +# ifndef YYFREE +# define YYFREE free +# if ! defined free && ! defined EXIT_SUCCESS +void free (void *); /* INFRINGES ON USER NAME SPACE */ +# endif +# endif +# endif +#endif /* !defined yyoverflow */ + +#if (! defined yyoverflow \ + && (! defined __cplusplus \ + || (defined YYSTYPE_IS_TRIVIAL && YYSTYPE_IS_TRIVIAL))) + +/* A type that is properly aligned for any stack member. */ +union yyalloc +{ + yy_state_t yyss_alloc; + YYSTYPE yyvs_alloc; +}; + +/* The size of the maximum gap between one aligned stack and the next. */ +# define YYSTACK_GAP_MAXIMUM (YYSIZEOF (union yyalloc) - 1) + +/* The size of an array large to enough to hold all stacks, each with + N elements. */ +# define YYSTACK_BYTES(N) \ + ((N) * (YYSIZEOF (yy_state_t) + YYSIZEOF (YYSTYPE)) \ + + YYSTACK_GAP_MAXIMUM) + +# define YYCOPY_NEEDED 1 + +/* Relocate STACK from its old location to the new one. The + local variables YYSIZE and YYSTACKSIZE give the old and new number of + elements in the stack, and YYPTR gives the new location of the + stack. Advance YYPTR to a properly aligned location for the next + stack. */ +# define YYSTACK_RELOCATE(Stack_alloc, Stack) \ + do \ + { \ + YYPTRDIFF_T yynewbytes; \ + YYCOPY (&yyptr->Stack_alloc, Stack, yysize); \ + Stack = &yyptr->Stack_alloc; \ + yynewbytes = yystacksize * YYSIZEOF (*Stack) + YYSTACK_GAP_MAXIMUM; \ + yyptr += yynewbytes / YYSIZEOF (*yyptr); \ + } \ + while (0) + +#endif + +#if defined YYCOPY_NEEDED && YYCOPY_NEEDED +/* Copy COUNT objects from SRC to DST. The source and destination do + not overlap. */ +# ifndef YYCOPY +# if defined __GNUC__ && 1 < __GNUC__ +# define YYCOPY(Dst, Src, Count) \ + __builtin_memcpy (Dst, Src, YY_CAST (YYSIZE_T, (Count)) * sizeof (*(Src))) +# else +# define YYCOPY(Dst, Src, Count) \ + do \ + { \ + YYPTRDIFF_T yyi; \ + for (yyi = 0; yyi < (Count); yyi++) \ + (Dst)[yyi] = (Src)[yyi]; \ + } \ + while (0) +# endif +# endif +#endif /* !YYCOPY_NEEDED */ + +/* YYFINAL -- State number of the termination state. */ +#define YYFINAL 5 +/* YYLAST -- Last index in YYTABLE. */ +#define YYLAST 239 + +/* YYNTOKENS -- Number of terminals. */ +#define YYNTOKENS 57 +/* YYNNTS -- Number of nonterminals. */ +#define YYNNTS 23 +/* YYNRULES -- Number of rules. */ +#define YYNRULES 104 +/* YYNSTATES -- Number of states. */ +#define YYNSTATES 143 + +/* YYMAXUTOK -- Last valid token kind. */ +#define YYMAXUTOK 295 + + +/* YYTRANSLATE(TOKEN-NUM) -- Symbol number corresponding to TOKEN-NUM + as returned by yylex, with out-of-bounds checking. */ +#define YYTRANSLATE(YYX) \ + (0 <= (YYX) && (YYX) <= YYMAXUTOK \ + ? YY_CAST (yysymbol_kind_t, yytranslate[YYX]) \ + : YYSYMBOL_YYUNDEF) + +/* YYTRANSLATE[TOKEN-NUM] -- Symbol number corresponding to TOKEN-NUM + as returned by yylex. */ +static const yytype_int8 yytranslate[] = +{ + 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 48, 44, 2, 2, + 46, 47, 42, 40, 50, 41, 55, 43, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 56, 49, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 51, 2, 52, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 53, 2, 54, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 1, 2, 3, 4, + 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, + 35, 36, 37, 38, 39, 45 +}; + +#if YYDEBUG + /* YYRLINE[YYN] -- Source line where rule number YYN was defined. */ +static const yytype_int16 yyrline[] = +{ + 0, 117, 117, 123, 127, 128, 132, 133, 134, 138, + 139, 140, 141, 142, 143, 144, 148, 149, 150, 151, + 152, 153, 157, 158, 162, 163, 164, 165, 166, 167, + 169, 171, 178, 188, 189, 193, 194, 195, 196, 200, + 201, 202, 203, 207, 208, 209, 210, 211, 212, 213, + 214, 215, 219, 220, 224, 225, 229, 230, 234, 235, + 239, 240, 241, 246, 247, 248, 249, 250, 251, 253, + 257, 261, 262, 266, 270, 271, 272, 273, 274, 275, + 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, + 286, 287, 288, 289, 290, 291, 292, 293, 297, 298, + 299, 300, 301, 302, 303 +}; +#endif + +/** Accessing symbol of state STATE. */ +#define YY_ACCESSING_SYMBOL(State) YY_CAST (yysymbol_kind_t, yystos[State]) + +#if YYDEBUG || 0 +/* The user-facing name of the symbol whose (internal) number is + YYSYMBOL. No bounds checking. */ +static const char *yysymbol_name (yysymbol_kind_t yysymbol) YY_ATTRIBUTE_UNUSED; + +/* YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM. + First, the terminals, then, starting at YYNTOKENS, nonterminals. */ +static const char *const yytname[] = +{ + "\"end of file\"", "error", "\"invalid token\"", "TO_P", "NULL_P", + "TRUE_P", "FALSE_P", "IS_P", "UNKNOWN_P", "EXISTS_P", "IDENT_P", + "STRING_P", "NUMERIC_P", "INT_P", "VARIABLE_P", "OR_P", "AND_P", "NOT_P", + "LESS_P", "LESSEQUAL_P", "EQUAL_P", "NOTEQUAL_P", "GREATEREQUAL_P", + "GREATER_P", "ANY_P", "STRICT_P", "LAX_P", "LAST_P", "STARTS_P", + "WITH_P", "LIKE_REGEX_P", "FLAG_P", "ABS_P", "SIZE_P", "TYPE_P", + "FLOOR_P", "DOUBLE_P", "CEILING_P", "KEYVALUE_P", "DATETIME_P", "'+'", + "'-'", "'*'", "'/'", "'%'", "UMINUS", "'('", "')'", "'$'", "'@'", "','", + "'['", "']'", "'{'", "'}'", "'.'", "'?'", "$accept", "result", + "expr_or_predicate", "mode", "scalar_value", "comp_op", + "delimited_predicate", "predicate", "starts_with_initial", + "path_primary", "accessor_expr", "expr", "index_elem", "index_list", + "array_accessor", "any_level", "any_path", "accessor_op", + "datetime_template", "opt_datetime_template", "key", "key_name", + "method", YY_NULLPTR +}; + +static const char * +yysymbol_name (yysymbol_kind_t yysymbol) +{ + return yytname[yysymbol]; +} +#endif + +#ifdef YYPRINT +/* YYTOKNUM[NUM] -- (External) token number corresponding to the + (internal) symbol number NUM (which must be that of a token). */ +static const yytype_int16 yytoknum[] = +{ + 0, 256, 257, 258, 259, 260, 261, 262, 263, 264, + 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, + 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, + 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, + 43, 45, 42, 47, 37, 295, 40, 41, 36, 64, + 44, 91, 93, 123, 125, 46, 63 +}; +#endif + +#define YYPACT_NINF (-44) + +#define yypact_value_is_default(Yyn) \ + ((Yyn) == YYPACT_NINF) + +#define YYTABLE_NINF (-105) + +#define yytable_value_is_error(Yyn) \ + 0 + + /* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing + STATE-NUM. */ +static const yytype_int16 yypact[] = +{ + 7, -44, -44, 18, 51, -44, -44, -44, -44, -43, + -44, -44, -44, -44, -3, -44, 114, 114, 51, -44, + -44, -44, -44, -44, 10, -44, -35, 195, 114, 51, + -44, 51, -44, -44, 14, 165, 51, 51, 68, 140, + -9, -44, -44, -44, -44, -44, -44, -44, -44, 37, + 60, 114, 114, 114, 114, 114, 114, 46, 20, 195, + 30, 3, -35, 59, -44, 24, -2, -44, -41, -44, + -44, -44, -44, -44, -44, -44, -44, -44, 31, -44, + -44, -44, -44, -44, -44, -44, 48, 50, 52, 61, + 67, 69, 78, 83, -44, -44, -44, -44, 84, 51, + 17, 100, 79, 79, -44, -44, -44, 62, -44, -44, + -35, 75, -44, -44, -44, 114, 114, -44, -8, 121, + 86, 54, -44, -44, -44, 123, -44, 62, -44, -44, + -44, -1, -44, -44, 88, -44, -44, -44, -8, -44, + -44, 82, -44 +}; + + /* YYDEFACT[STATE-NUM] -- Default reduction number in state STATE-NUM. + Performed when YYTABLE does not specify something else to do. Zero + means the default is an error. */ +static const yytype_int8 yydefact[] = +{ + 8, 6, 7, 0, 0, 1, 10, 11, 12, 0, + 9, 13, 14, 15, 0, 38, 0, 0, 0, 36, + 37, 2, 35, 24, 5, 39, 43, 4, 0, 0, + 28, 0, 45, 46, 0, 0, 0, 0, 0, 0, + 0, 65, 42, 18, 20, 16, 17, 21, 19, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 22, 44, 27, 26, 0, 52, 54, 0, 76, + 77, 78, 79, 80, 81, 82, 74, 75, 60, 83, + 84, 93, 94, 95, 96, 97, 85, 86, 87, 88, + 89, 90, 92, 91, 64, 66, 63, 73, 0, 0, + 0, 31, 47, 48, 49, 50, 51, 25, 23, 22, + 0, 0, 41, 40, 56, 0, 0, 57, 0, 72, + 0, 0, 33, 34, 30, 0, 29, 53, 55, 58, + 59, 0, 70, 71, 0, 67, 69, 32, 0, 61, + 68, 0, 62 +}; + + /* YYPGOTO[NTERM-NUM]. */ +static const yytype_int8 yypgoto[] = +{ + -44, -44, -44, -44, -44, -44, 124, -14, -44, -44, + -44, -4, 21, -44, -44, 1, -44, -18, -44, -44, + -44, -44, -44 +}; + + /* YYDEFGOTO[NTERM-NUM]. */ +static const yytype_uint8 yydefgoto[] = +{ + 0, 3, 21, 4, 22, 56, 23, 24, 124, 25, + 26, 59, 67, 68, 41, 131, 95, 112, 133, 134, + 96, 97, 98 +}; + + /* YYTABLE[YYPACT[STATE-NUM]] -- What to do in state STATE-NUM. If + positive, shift that token. If negative, reduce the rule whose + number is the opposite. If YYTABLE_NINF, syntax error. */ +static const yytype_int16 yytable[] = +{ + 27, 115, 138, 28, 34, 129, 9, -3, 42, 116, + 111, 117, 32, 33, 35, 58, 38, 60, 5, 130, + 39, 40, 63, 64, 57, 36, 37, 35, 122, 36, + 37, 123, 1, 2, 66, 36, 37, 99, 51, 52, + 53, 54, 55, 29, 113, 36, 37, 102, 103, 104, + 105, 106, 107, 139, 38, 6, 7, 8, 39, 40, + 9, 61, 10, 11, 12, 13, 100, 109, 14, 36, + 37, 101, 6, 7, 8, 37, 114, 110, 15, 10, + 11, 12, 13, 126, 118, 121, 51, 52, 53, 54, + 55, 16, 17, 108, -98, 15, -99, 18, -100, 19, + 20, 136, 51, 52, 53, 54, 55, -101, 16, 17, + 65, 127, 66, -102, 31, -103, 19, 20, 6, 7, + 8, 53, 54, 55, -104, 10, 11, 12, 13, 119, + 120, 125, 132, 135, 137, 140, 142, 128, 30, 141, + 0, 15, 0, 69, 70, 71, 72, 73, 74, 75, + 76, 77, 0, 0, 16, 17, 0, 0, 0, 0, + 31, 0, 19, 20, 78, 79, 80, 81, 82, 83, + 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, + 0, 0, 94, 43, 44, 45, 46, 47, 48, 0, + 0, 0, 0, 49, 0, 50, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 51, 52, 53, 54, 55, + 0, 0, 62, 43, 44, 45, 46, 47, 48, 0, + 0, 0, 0, 49, 0, 50, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 51, 52, 53, 54, 55 +}; + +static const yytype_int16 yycheck[] = +{ + 4, 3, 3, 46, 18, 13, 9, 0, 26, 50, + 7, 52, 16, 17, 18, 29, 51, 31, 0, 27, + 55, 56, 36, 37, 28, 15, 16, 31, 11, 15, + 16, 14, 25, 26, 38, 15, 16, 46, 40, 41, + 42, 43, 44, 46, 62, 15, 16, 51, 52, 53, + 54, 55, 56, 54, 51, 4, 5, 6, 55, 56, + 9, 47, 11, 12, 13, 14, 29, 47, 17, 15, + 16, 11, 4, 5, 6, 16, 52, 47, 27, 11, + 12, 13, 14, 8, 53, 99, 40, 41, 42, 43, + 44, 40, 41, 47, 46, 27, 46, 46, 46, 48, + 49, 47, 40, 41, 42, 43, 44, 46, 40, 41, + 42, 115, 116, 46, 46, 46, 48, 49, 4, 5, + 6, 42, 43, 44, 46, 11, 12, 13, 14, 46, + 46, 31, 11, 47, 11, 47, 54, 116, 14, 138, + -1, 27, -1, 3, 4, 5, 6, 7, 8, 9, + 10, 11, -1, -1, 40, 41, -1, -1, -1, -1, + 46, -1, 48, 49, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, + -1, -1, 42, 18, 19, 20, 21, 22, 23, -1, + -1, -1, -1, 28, -1, 30, -1, -1, -1, -1, + -1, -1, -1, -1, -1, 40, 41, 42, 43, 44, + -1, -1, 47, 18, 19, 20, 21, 22, 23, -1, + -1, -1, -1, 28, -1, 30, -1, -1, -1, -1, + -1, -1, -1, -1, -1, 40, 41, 42, 43, 44 +}; + + /* YYSTOS[STATE-NUM] -- The (internal number of the) accessing + symbol of state STATE-NUM. */ +static const yytype_int8 yystos[] = +{ + 0, 25, 26, 58, 60, 0, 4, 5, 6, 9, + 11, 12, 13, 14, 17, 27, 40, 41, 46, 48, + 49, 59, 61, 63, 64, 66, 67, 68, 46, 46, + 63, 46, 68, 68, 64, 68, 15, 16, 51, 55, + 56, 71, 74, 18, 19, 20, 21, 22, 23, 28, + 30, 40, 41, 42, 43, 44, 62, 68, 64, 68, + 64, 47, 47, 64, 64, 42, 68, 69, 70, 3, + 4, 5, 6, 7, 8, 9, 10, 11, 24, 25, + 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, + 36, 37, 38, 39, 42, 73, 77, 78, 79, 46, + 29, 11, 68, 68, 68, 68, 68, 68, 47, 47, + 47, 7, 74, 74, 52, 3, 50, 52, 53, 46, + 46, 64, 11, 14, 65, 31, 8, 68, 69, 13, + 27, 72, 11, 75, 76, 47, 47, 11, 3, 54, + 47, 72, 54 +}; + + /* YYR1[YYN] -- Symbol number of symbol that rule YYN derives. */ +static const yytype_int8 yyr1[] = +{ + 0, 57, 58, 58, 59, 59, 60, 60, 60, 61, + 61, 61, 61, 61, 61, 61, 62, 62, 62, 62, + 62, 62, 63, 63, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 65, 65, 66, 66, 66, 66, 67, + 67, 67, 67, 68, 68, 68, 68, 68, 68, 68, + 68, 68, 69, 69, 70, 70, 71, 71, 72, 72, + 73, 73, 73, 74, 74, 74, 74, 74, 74, 74, + 75, 76, 76, 77, 78, 78, 78, 78, 78, 78, + 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, + 78, 78, 78, 78, 78, 78, 78, 78, 79, 79, + 79, 79, 79, 79, 79 +}; + + /* YYR2[YYN] -- Number of symbols on the right hand side of rule YYN. */ +static const yytype_int8 yyr2[] = +{ + 0, 2, 2, 0, 1, 1, 1, 1, 0, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 3, 4, 1, 3, 3, 3, 2, 5, + 4, 3, 5, 1, 1, 1, 1, 1, 1, 1, + 4, 4, 2, 1, 3, 2, 2, 3, 3, 3, + 3, 3, 1, 3, 1, 3, 3, 3, 1, 1, + 1, 4, 6, 2, 2, 1, 2, 4, 5, 4, + 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1 +}; + + +enum { YYENOMEM = -2 }; + +#define yyerrok (yyerrstatus = 0) +#define yyclearin (yychar = YYEMPTY) + +#define YYACCEPT goto yyacceptlab +#define YYABORT goto yyabortlab +#define YYERROR goto yyerrorlab + + +#define YYRECOVERING() (!!yyerrstatus) + +#define YYBACKUP(Token, Value) \ + do \ + if (yychar == YYEMPTY) \ + { \ + yychar = (Token); \ + yylval = (Value); \ + YYPOPSTACK (yylen); \ + yystate = *yyssp; \ + goto yybackup; \ + } \ + else \ + { \ + yyerror (result, escontext, YY_("syntax error: cannot back up")); \ + YYERROR; \ + } \ + while (0) + +/* Backward compatibility with an undocumented macro. + Use YYerror or YYUNDEF. */ +#define YYERRCODE YYUNDEF + + +/* Enable debugging if requested. */ +#if YYDEBUG + +# ifndef YYFPRINTF +# include <stdio.h> /* INFRINGES ON USER NAME SPACE */ +# define YYFPRINTF fprintf +# endif + +# define YYDPRINTF(Args) \ +do { \ + if (yydebug) \ + YYFPRINTF Args; \ +} while (0) + +/* This macro is provided for backward compatibility. */ +# ifndef YY_LOCATION_PRINT +# define YY_LOCATION_PRINT(File, Loc) ((void) 0) +# endif + + +# define YY_SYMBOL_PRINT(Title, Kind, Value, Location) \ +do { \ + if (yydebug) \ + { \ + YYFPRINTF (stderr, "%s ", Title); \ + yy_symbol_print (stderr, \ + Kind, Value, result, escontext); \ + YYFPRINTF (stderr, "\n"); \ + } \ +} while (0) + + +/*-----------------------------------. +| Print this symbol's value on YYO. | +`-----------------------------------*/ + +static void +yy_symbol_value_print (FILE *yyo, + yysymbol_kind_t yykind, YYSTYPE const * const yyvaluep, JsonPathParseResult **result, struct Node *escontext) +{ + FILE *yyoutput = yyo; + YY_USE (yyoutput); + YY_USE (result); + YY_USE (escontext); + if (!yyvaluep) + return; +# ifdef YYPRINT + if (yykind < YYNTOKENS) + YYPRINT (yyo, yytoknum[yykind], *yyvaluep); +# endif + YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN + YY_USE (yykind); + YY_IGNORE_MAYBE_UNINITIALIZED_END +} + + +/*---------------------------. +| Print this symbol on YYO. | +`---------------------------*/ + +static void +yy_symbol_print (FILE *yyo, + yysymbol_kind_t yykind, YYSTYPE const * const yyvaluep, JsonPathParseResult **result, struct Node *escontext) +{ + YYFPRINTF (yyo, "%s %s (", + yykind < YYNTOKENS ? "token" : "nterm", yysymbol_name (yykind)); + + yy_symbol_value_print (yyo, yykind, yyvaluep, result, escontext); + YYFPRINTF (yyo, ")"); +} + +/*------------------------------------------------------------------. +| yy_stack_print -- Print the state stack from its BOTTOM up to its | +| TOP (included). | +`------------------------------------------------------------------*/ + +static void +yy_stack_print (yy_state_t *yybottom, yy_state_t *yytop) +{ + YYFPRINTF (stderr, "Stack now"); + for (; yybottom <= yytop; yybottom++) + { + int yybot = *yybottom; + YYFPRINTF (stderr, " %d", yybot); + } + YYFPRINTF (stderr, "\n"); +} + +# define YY_STACK_PRINT(Bottom, Top) \ +do { \ + if (yydebug) \ + yy_stack_print ((Bottom), (Top)); \ +} while (0) + + +/*------------------------------------------------. +| Report that the YYRULE is going to be reduced. | +`------------------------------------------------*/ + +static void +yy_reduce_print (yy_state_t *yyssp, YYSTYPE *yyvsp, + int yyrule, JsonPathParseResult **result, struct Node *escontext) +{ + int yylno = yyrline[yyrule]; + int yynrhs = yyr2[yyrule]; + int yyi; + YYFPRINTF (stderr, "Reducing stack by rule %d (line %d):\n", + yyrule - 1, yylno); + /* The symbols being reduced. */ + for (yyi = 0; yyi < yynrhs; yyi++) + { + YYFPRINTF (stderr, " $%d = ", yyi + 1); + yy_symbol_print (stderr, + YY_ACCESSING_SYMBOL (+yyssp[yyi + 1 - yynrhs]), + &yyvsp[(yyi + 1) - (yynrhs)], result, escontext); + YYFPRINTF (stderr, "\n"); + } +} + +# define YY_REDUCE_PRINT(Rule) \ +do { \ + if (yydebug) \ + yy_reduce_print (yyssp, yyvsp, Rule, result, escontext); \ +} while (0) + +/* Nonzero means print parse trace. It is left uninitialized so that + multiple parsers can coexist. */ +int yydebug; +#else /* !YYDEBUG */ +# define YYDPRINTF(Args) ((void) 0) +# define YY_SYMBOL_PRINT(Title, Kind, Value, Location) +# define YY_STACK_PRINT(Bottom, Top) +# define YY_REDUCE_PRINT(Rule) +#endif /* !YYDEBUG */ + + +/* YYINITDEPTH -- initial size of the parser's stacks. */ +#ifndef YYINITDEPTH +# define YYINITDEPTH 200 +#endif + +/* YYMAXDEPTH -- maximum size the stacks can grow to (effective only + if the built-in stack extension method is used). + + Do not make this value too large; the results are undefined if + YYSTACK_ALLOC_MAXIMUM < YYSTACK_BYTES (YYMAXDEPTH) + evaluated with infinite-precision integer arithmetic. */ + +#ifndef YYMAXDEPTH +# define YYMAXDEPTH 10000 +#endif + + + + + + +/*-----------------------------------------------. +| Release the memory associated to this symbol. | +`-----------------------------------------------*/ + +static void +yydestruct (const char *yymsg, + yysymbol_kind_t yykind, YYSTYPE *yyvaluep, JsonPathParseResult **result, struct Node *escontext) +{ + YY_USE (yyvaluep); + YY_USE (result); + YY_USE (escontext); + if (!yymsg) + yymsg = "Deleting"; + YY_SYMBOL_PRINT (yymsg, yykind, yyvaluep, yylocationp); + + YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN + YY_USE (yykind); + YY_IGNORE_MAYBE_UNINITIALIZED_END +} + + + + + + +/*----------. +| yyparse. | +`----------*/ + +int +yyparse (JsonPathParseResult **result, struct Node *escontext) +{ +/* Lookahead token kind. */ +int yychar; + + +/* The semantic value of the lookahead symbol. */ +/* Default value used for initialization, for pacifying older GCCs + or non-GCC compilers. */ +YY_INITIAL_VALUE (static __thread YYSTYPE yyval_default;) +YYSTYPE yylval YY_INITIAL_VALUE (= yyval_default); + + /* Number of syntax errors so far. */ + int yynerrs = 0; + + yy_state_fast_t yystate = 0; + /* Number of tokens to shift before error messages enabled. */ + int yyerrstatus = 0; + + /* Refer to the stacks through separate pointers, to allow yyoverflow + to reallocate them elsewhere. */ + + /* Their size. */ + YYPTRDIFF_T yystacksize = YYINITDEPTH; + + /* The state stack: array, bottom, top. */ + yy_state_t yyssa[YYINITDEPTH]; + yy_state_t *yyss = yyssa; + yy_state_t *yyssp = yyss; + + /* The semantic value stack: array, bottom, top. */ + YYSTYPE yyvsa[YYINITDEPTH]; + YYSTYPE *yyvs = yyvsa; + YYSTYPE *yyvsp = yyvs; + + int yyn; + /* The return value of yyparse. */ + int yyresult; + /* Lookahead symbol kind. */ + yysymbol_kind_t yytoken = YYSYMBOL_YYEMPTY; + /* The variables used to return semantic value and location from the + action routines. */ + YYSTYPE yyval; + + + +#define YYPOPSTACK(N) (yyvsp -= (N), yyssp -= (N)) + + /* The number of symbols on the RHS of the reduced rule. + Keep to zero when no symbol should be popped. */ + int yylen = 0; + + YYDPRINTF ((stderr, "Starting parse\n")); + + yychar = YYEMPTY; /* Cause a token to be read. */ + goto yysetstate; + + +/*------------------------------------------------------------. +| yynewstate -- push a new state, which is found in yystate. | +`------------------------------------------------------------*/ +yynewstate: + /* In all cases, when you get here, the value and location stacks + have just been pushed. So pushing a state here evens the stacks. */ + yyssp++; + + +/*--------------------------------------------------------------------. +| yysetstate -- set current state (the top of the stack) to yystate. | +`--------------------------------------------------------------------*/ +yysetstate: + YYDPRINTF ((stderr, "Entering state %d\n", yystate)); + YY_ASSERT (0 <= yystate && yystate < YYNSTATES); + YY_IGNORE_USELESS_CAST_BEGIN + *yyssp = YY_CAST (yy_state_t, yystate); + YY_IGNORE_USELESS_CAST_END + YY_STACK_PRINT (yyss, yyssp); + + if (yyss + yystacksize - 1 <= yyssp) +#if !defined yyoverflow && !defined YYSTACK_RELOCATE + goto yyexhaustedlab; +#else + { + /* Get the current used size of the three stacks, in elements. */ + YYPTRDIFF_T yysize = yyssp - yyss + 1; + +# if defined yyoverflow + { + /* Give user a chance to reallocate the stack. Use copies of + these so that the &'s don't force the real ones into + memory. */ + yy_state_t *yyss1 = yyss; + YYSTYPE *yyvs1 = yyvs; + + /* Each stack pointer address is followed by the size of the + data in use in that stack, in bytes. This used to be a + conditional around just the two extra args, but that might + be undefined if yyoverflow is a macro. */ + yyoverflow (YY_("memory exhausted"), + &yyss1, yysize * YYSIZEOF (*yyssp), + &yyvs1, yysize * YYSIZEOF (*yyvsp), + &yystacksize); + yyss = yyss1; + yyvs = yyvs1; + } +# else /* defined YYSTACK_RELOCATE */ + /* Extend the stack our own way. */ + if (YYMAXDEPTH <= yystacksize) + goto yyexhaustedlab; + yystacksize *= 2; + if (YYMAXDEPTH < yystacksize) + yystacksize = YYMAXDEPTH; + + { + yy_state_t *yyss1 = yyss; + union yyalloc *yyptr = + YY_CAST (union yyalloc *, + YYSTACK_ALLOC (YY_CAST (YYSIZE_T, YYSTACK_BYTES (yystacksize)))); + if (! yyptr) + goto yyexhaustedlab; + YYSTACK_RELOCATE (yyss_alloc, yyss); + YYSTACK_RELOCATE (yyvs_alloc, yyvs); +# undef YYSTACK_RELOCATE + if (yyss1 != yyssa) + YYSTACK_FREE (yyss1); + } +# endif + + yyssp = yyss + yysize - 1; + yyvsp = yyvs + yysize - 1; + + YY_IGNORE_USELESS_CAST_BEGIN + YYDPRINTF ((stderr, "Stack size increased to %ld\n", + YY_CAST (long, yystacksize))); + YY_IGNORE_USELESS_CAST_END + + if (yyss + yystacksize - 1 <= yyssp) + YYABORT; + } +#endif /* !defined yyoverflow && !defined YYSTACK_RELOCATE */ + + if (yystate == YYFINAL) + YYACCEPT; + + goto yybackup; + + +/*-----------. +| yybackup. | +`-----------*/ +yybackup: + /* Do appropriate processing given the current state. Read a + lookahead token if we need one and don't already have one. */ + + /* First try to decide what to do without reference to lookahead token. */ + yyn = yypact[yystate]; + if (yypact_value_is_default (yyn)) + goto yydefault; + + /* Not known => get a lookahead token if don't already have one. */ + + /* YYCHAR is either empty, or end-of-input, or a valid lookahead. */ + if (yychar == YYEMPTY) + { + YYDPRINTF ((stderr, "Reading a token\n")); + yychar = yylex (&yylval, result, escontext); + } + + if (yychar <= YYEOF) + { + yychar = YYEOF; + yytoken = YYSYMBOL_YYEOF; + YYDPRINTF ((stderr, "Now at end of input.\n")); + } + else if (yychar == YYerror) + { + /* The scanner already issued an error message, process directly + to error recovery. But do not keep the error token as + lookahead, it is too special and may lead us to an endless + loop in error recovery. */ + yychar = YYUNDEF; + yytoken = YYSYMBOL_YYerror; + goto yyerrlab1; + } + else + { + yytoken = YYTRANSLATE (yychar); + YY_SYMBOL_PRINT ("Next token is", yytoken, &yylval, &yylloc); + } + + /* If the proper action on seeing token YYTOKEN is to reduce or to + detect an error, take that action. */ + yyn += yytoken; + if (yyn < 0 || YYLAST < yyn || yycheck[yyn] != yytoken) + goto yydefault; + yyn = yytable[yyn]; + if (yyn <= 0) + { + if (yytable_value_is_error (yyn)) + goto yyerrlab; + yyn = -yyn; + goto yyreduce; + } + + /* Count tokens shifted since error; after three, turn off error + status. */ + if (yyerrstatus) + yyerrstatus--; + + /* Shift the lookahead token. */ + YY_SYMBOL_PRINT ("Shifting", yytoken, &yylval, &yylloc); + yystate = yyn; + YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN + *++yyvsp = yylval; + YY_IGNORE_MAYBE_UNINITIALIZED_END + + /* Discard the shifted token. */ + yychar = YYEMPTY; + goto yynewstate; + + +/*-----------------------------------------------------------. +| yydefault -- do the default action for the current state. | +`-----------------------------------------------------------*/ +yydefault: + yyn = yydefact[yystate]; + if (yyn == 0) + goto yyerrlab; + goto yyreduce; + + +/*-----------------------------. +| yyreduce -- do a reduction. | +`-----------------------------*/ +yyreduce: + /* yyn is the number of a rule to reduce with. */ + yylen = yyr2[yyn]; + + /* If YYLEN is nonzero, implement the default value of the action: + '$$ = $1'. + + Otherwise, the following line sets YYVAL to garbage. + This behavior is undocumented and Bison + users should not rely upon it. Assigning to YYVAL + unconditionally makes the parser a bit smaller, and it avoids a + GCC warning that YYVAL may be used uninitialized. */ + yyval = yyvsp[1-yylen]; + + + YY_REDUCE_PRINT (yyn); + switch (yyn) + { + case 2: /* result: mode expr_or_predicate */ +#line 117 "jsonpath_gram.y" + { + *result = palloc(sizeof(JsonPathParseResult)); + (*result)->expr = (yyvsp[0].value); + (*result)->lax = (yyvsp[-1].boolean); + (void) yynerrs; + } +#line 1350 "jsonpath_gram.c" + break; + + case 3: /* result: %empty */ +#line 123 "jsonpath_gram.y" + { *result = NULL; } +#line 1356 "jsonpath_gram.c" + break; + + case 4: /* expr_or_predicate: expr */ +#line 127 "jsonpath_gram.y" + { (yyval.value) = (yyvsp[0].value); } +#line 1362 "jsonpath_gram.c" + break; + + case 5: /* expr_or_predicate: predicate */ +#line 128 "jsonpath_gram.y" + { (yyval.value) = (yyvsp[0].value); } +#line 1368 "jsonpath_gram.c" + break; + + case 6: /* mode: STRICT_P */ +#line 132 "jsonpath_gram.y" + { (yyval.boolean) = false; } +#line 1374 "jsonpath_gram.c" + break; + + case 7: /* mode: LAX_P */ +#line 133 "jsonpath_gram.y" + { (yyval.boolean) = true; } +#line 1380 "jsonpath_gram.c" + break; + + case 8: /* mode: %empty */ +#line 134 "jsonpath_gram.y" + { (yyval.boolean) = true; } +#line 1386 "jsonpath_gram.c" + break; + + case 9: /* scalar_value: STRING_P */ +#line 138 "jsonpath_gram.y" + { (yyval.value) = makeItemString(&(yyvsp[0].str)); } +#line 1392 "jsonpath_gram.c" + break; + + case 10: /* scalar_value: NULL_P */ +#line 139 "jsonpath_gram.y" + { (yyval.value) = makeItemString(NULL); } +#line 1398 "jsonpath_gram.c" + break; + + case 11: /* scalar_value: TRUE_P */ +#line 140 "jsonpath_gram.y" + { (yyval.value) = makeItemBool(true); } +#line 1404 "jsonpath_gram.c" + break; + + case 12: /* scalar_value: FALSE_P */ +#line 141 "jsonpath_gram.y" + { (yyval.value) = makeItemBool(false); } +#line 1410 "jsonpath_gram.c" + break; + + case 13: /* scalar_value: NUMERIC_P */ +#line 142 "jsonpath_gram.y" + { (yyval.value) = makeItemNumeric(&(yyvsp[0].str)); } +#line 1416 "jsonpath_gram.c" + break; + + case 14: /* scalar_value: INT_P */ +#line 143 "jsonpath_gram.y" + { (yyval.value) = makeItemNumeric(&(yyvsp[0].str)); } +#line 1422 "jsonpath_gram.c" + break; + + case 15: /* scalar_value: VARIABLE_P */ +#line 144 "jsonpath_gram.y" + { (yyval.value) = makeItemVariable(&(yyvsp[0].str)); } +#line 1428 "jsonpath_gram.c" + break; + + case 16: /* comp_op: EQUAL_P */ +#line 148 "jsonpath_gram.y" + { (yyval.optype) = jpiEqual; } +#line 1434 "jsonpath_gram.c" + break; + + case 17: /* comp_op: NOTEQUAL_P */ +#line 149 "jsonpath_gram.y" + { (yyval.optype) = jpiNotEqual; } +#line 1440 "jsonpath_gram.c" + break; + + case 18: /* comp_op: LESS_P */ +#line 150 "jsonpath_gram.y" + { (yyval.optype) = jpiLess; } +#line 1446 "jsonpath_gram.c" + break; + + case 19: /* comp_op: GREATER_P */ +#line 151 "jsonpath_gram.y" + { (yyval.optype) = jpiGreater; } +#line 1452 "jsonpath_gram.c" + break; + + case 20: /* comp_op: LESSEQUAL_P */ +#line 152 "jsonpath_gram.y" + { (yyval.optype) = jpiLessOrEqual; } +#line 1458 "jsonpath_gram.c" + break; + + case 21: /* comp_op: GREATEREQUAL_P */ +#line 153 "jsonpath_gram.y" + { (yyval.optype) = jpiGreaterOrEqual; } +#line 1464 "jsonpath_gram.c" + break; + + case 22: /* delimited_predicate: '(' predicate ')' */ +#line 157 "jsonpath_gram.y" + { (yyval.value) = (yyvsp[-1].value); } +#line 1470 "jsonpath_gram.c" + break; + + case 23: /* delimited_predicate: EXISTS_P '(' expr ')' */ +#line 158 "jsonpath_gram.y" + { (yyval.value) = makeItemUnary(jpiExists, (yyvsp[-1].value)); } +#line 1476 "jsonpath_gram.c" + break; + + case 24: /* predicate: delimited_predicate */ +#line 162 "jsonpath_gram.y" + { (yyval.value) = (yyvsp[0].value); } +#line 1482 "jsonpath_gram.c" + break; + + case 25: /* predicate: expr comp_op expr */ +#line 163 "jsonpath_gram.y" + { (yyval.value) = makeItemBinary((yyvsp[-1].optype), (yyvsp[-2].value), (yyvsp[0].value)); } +#line 1488 "jsonpath_gram.c" + break; + + case 26: /* predicate: predicate AND_P predicate */ +#line 164 "jsonpath_gram.y" + { (yyval.value) = makeItemBinary(jpiAnd, (yyvsp[-2].value), (yyvsp[0].value)); } +#line 1494 "jsonpath_gram.c" + break; + + case 27: /* predicate: predicate OR_P predicate */ +#line 165 "jsonpath_gram.y" + { (yyval.value) = makeItemBinary(jpiOr, (yyvsp[-2].value), (yyvsp[0].value)); } +#line 1500 "jsonpath_gram.c" + break; + + case 28: /* predicate: NOT_P delimited_predicate */ +#line 166 "jsonpath_gram.y" + { (yyval.value) = makeItemUnary(jpiNot, (yyvsp[0].value)); } +#line 1506 "jsonpath_gram.c" + break; + + case 29: /* predicate: '(' predicate ')' IS_P UNKNOWN_P */ +#line 168 "jsonpath_gram.y" + { (yyval.value) = makeItemUnary(jpiIsUnknown, (yyvsp[-3].value)); } +#line 1512 "jsonpath_gram.c" + break; + + case 30: /* predicate: expr STARTS_P WITH_P starts_with_initial */ +#line 170 "jsonpath_gram.y" + { (yyval.value) = makeItemBinary(jpiStartsWith, (yyvsp[-3].value), (yyvsp[0].value)); } +#line 1518 "jsonpath_gram.c" + break; + + case 31: /* predicate: expr LIKE_REGEX_P STRING_P */ +#line 172 "jsonpath_gram.y" + { + JsonPathParseItem *jppitem; + if (! makeItemLikeRegex((yyvsp[-2].value), &(yyvsp[0].str), NULL, &jppitem, escontext)) + YYABORT; + (yyval.value) = jppitem; + } +#line 1529 "jsonpath_gram.c" + break; + + case 32: /* predicate: expr LIKE_REGEX_P STRING_P FLAG_P STRING_P */ +#line 179 "jsonpath_gram.y" + { + JsonPathParseItem *jppitem; + if (! makeItemLikeRegex((yyvsp[-4].value), &(yyvsp[-2].str), &(yyvsp[0].str), &jppitem, escontext)) + YYABORT; + (yyval.value) = jppitem; + } +#line 1540 "jsonpath_gram.c" + break; + + case 33: /* starts_with_initial: STRING_P */ +#line 188 "jsonpath_gram.y" + { (yyval.value) = makeItemString(&(yyvsp[0].str)); } +#line 1546 "jsonpath_gram.c" + break; + + case 34: /* starts_with_initial: VARIABLE_P */ +#line 189 "jsonpath_gram.y" + { (yyval.value) = makeItemVariable(&(yyvsp[0].str)); } +#line 1552 "jsonpath_gram.c" + break; + + case 35: /* path_primary: scalar_value */ +#line 193 "jsonpath_gram.y" + { (yyval.value) = (yyvsp[0].value); } +#line 1558 "jsonpath_gram.c" + break; + + case 36: /* path_primary: '$' */ +#line 194 "jsonpath_gram.y" + { (yyval.value) = makeItemType(jpiRoot); } +#line 1564 "jsonpath_gram.c" + break; + + case 37: /* path_primary: '@' */ +#line 195 "jsonpath_gram.y" + { (yyval.value) = makeItemType(jpiCurrent); } +#line 1570 "jsonpath_gram.c" + break; + + case 38: /* path_primary: LAST_P */ +#line 196 "jsonpath_gram.y" + { (yyval.value) = makeItemType(jpiLast); } +#line 1576 "jsonpath_gram.c" + break; + + case 39: /* accessor_expr: path_primary */ +#line 200 "jsonpath_gram.y" + { (yyval.elems) = list_make1((yyvsp[0].value)); } +#line 1582 "jsonpath_gram.c" + break; + + case 40: /* accessor_expr: '(' expr ')' accessor_op */ +#line 201 "jsonpath_gram.y" + { (yyval.elems) = list_make2((yyvsp[-2].value), (yyvsp[0].value)); } +#line 1588 "jsonpath_gram.c" + break; + + case 41: /* accessor_expr: '(' predicate ')' accessor_op */ +#line 202 "jsonpath_gram.y" + { (yyval.elems) = list_make2((yyvsp[-2].value), (yyvsp[0].value)); } +#line 1594 "jsonpath_gram.c" + break; + + case 42: /* accessor_expr: accessor_expr accessor_op */ +#line 203 "jsonpath_gram.y" + { (yyval.elems) = lappend((yyvsp[-1].elems), (yyvsp[0].value)); } +#line 1600 "jsonpath_gram.c" + break; + + case 43: /* expr: accessor_expr */ +#line 207 "jsonpath_gram.y" + { (yyval.value) = makeItemList((yyvsp[0].elems)); } +#line 1606 "jsonpath_gram.c" + break; + + case 44: /* expr: '(' expr ')' */ +#line 208 "jsonpath_gram.y" + { (yyval.value) = (yyvsp[-1].value); } +#line 1612 "jsonpath_gram.c" + break; + + case 45: /* expr: '+' expr */ +#line 209 "jsonpath_gram.y" + { (yyval.value) = makeItemUnary(jpiPlus, (yyvsp[0].value)); } +#line 1618 "jsonpath_gram.c" + break; + + case 46: /* expr: '-' expr */ +#line 210 "jsonpath_gram.y" + { (yyval.value) = makeItemUnary(jpiMinus, (yyvsp[0].value)); } +#line 1624 "jsonpath_gram.c" + break; + + case 47: /* expr: expr '+' expr */ +#line 211 "jsonpath_gram.y" + { (yyval.value) = makeItemBinary(jpiAdd, (yyvsp[-2].value), (yyvsp[0].value)); } +#line 1630 "jsonpath_gram.c" + break; + + case 48: /* expr: expr '-' expr */ +#line 212 "jsonpath_gram.y" + { (yyval.value) = makeItemBinary(jpiSub, (yyvsp[-2].value), (yyvsp[0].value)); } +#line 1636 "jsonpath_gram.c" + break; + + case 49: /* expr: expr '*' expr */ +#line 213 "jsonpath_gram.y" + { (yyval.value) = makeItemBinary(jpiMul, (yyvsp[-2].value), (yyvsp[0].value)); } +#line 1642 "jsonpath_gram.c" + break; + + case 50: /* expr: expr '/' expr */ +#line 214 "jsonpath_gram.y" + { (yyval.value) = makeItemBinary(jpiDiv, (yyvsp[-2].value), (yyvsp[0].value)); } +#line 1648 "jsonpath_gram.c" + break; + + case 51: /* expr: expr '%' expr */ +#line 215 "jsonpath_gram.y" + { (yyval.value) = makeItemBinary(jpiMod, (yyvsp[-2].value), (yyvsp[0].value)); } +#line 1654 "jsonpath_gram.c" + break; + + case 52: /* index_elem: expr */ +#line 219 "jsonpath_gram.y" + { (yyval.value) = makeItemBinary(jpiSubscript, (yyvsp[0].value), NULL); } +#line 1660 "jsonpath_gram.c" + break; + + case 53: /* index_elem: expr TO_P expr */ +#line 220 "jsonpath_gram.y" + { (yyval.value) = makeItemBinary(jpiSubscript, (yyvsp[-2].value), (yyvsp[0].value)); } +#line 1666 "jsonpath_gram.c" + break; + + case 54: /* index_list: index_elem */ +#line 224 "jsonpath_gram.y" + { (yyval.indexs) = list_make1((yyvsp[0].value)); } +#line 1672 "jsonpath_gram.c" + break; + + case 55: /* index_list: index_list ',' index_elem */ +#line 225 "jsonpath_gram.y" + { (yyval.indexs) = lappend((yyvsp[-2].indexs), (yyvsp[0].value)); } +#line 1678 "jsonpath_gram.c" + break; + + case 56: /* array_accessor: '[' '*' ']' */ +#line 229 "jsonpath_gram.y" + { (yyval.value) = makeItemType(jpiAnyArray); } +#line 1684 "jsonpath_gram.c" + break; + + case 57: /* array_accessor: '[' index_list ']' */ +#line 230 "jsonpath_gram.y" + { (yyval.value) = makeIndexArray((yyvsp[-1].indexs)); } +#line 1690 "jsonpath_gram.c" + break; + + case 58: /* any_level: INT_P */ +#line 234 "jsonpath_gram.y" + { (yyval.integer) = pg_strtoint32((yyvsp[0].str).val); } +#line 1696 "jsonpath_gram.c" + break; + + case 59: /* any_level: LAST_P */ +#line 235 "jsonpath_gram.y" + { (yyval.integer) = -1; } +#line 1702 "jsonpath_gram.c" + break; + + case 60: /* any_path: ANY_P */ +#line 239 "jsonpath_gram.y" + { (yyval.value) = makeAny(0, -1); } +#line 1708 "jsonpath_gram.c" + break; + + case 61: /* any_path: ANY_P '{' any_level '}' */ +#line 240 "jsonpath_gram.y" + { (yyval.value) = makeAny((yyvsp[-1].integer), (yyvsp[-1].integer)); } +#line 1714 "jsonpath_gram.c" + break; + + case 62: /* any_path: ANY_P '{' any_level TO_P any_level '}' */ +#line 242 "jsonpath_gram.y" + { (yyval.value) = makeAny((yyvsp[-3].integer), (yyvsp[-1].integer)); } +#line 1720 "jsonpath_gram.c" + break; + + case 63: /* accessor_op: '.' key */ +#line 246 "jsonpath_gram.y" + { (yyval.value) = (yyvsp[0].value); } +#line 1726 "jsonpath_gram.c" + break; + + case 64: /* accessor_op: '.' '*' */ +#line 247 "jsonpath_gram.y" + { (yyval.value) = makeItemType(jpiAnyKey); } +#line 1732 "jsonpath_gram.c" + break; + + case 65: /* accessor_op: array_accessor */ +#line 248 "jsonpath_gram.y" + { (yyval.value) = (yyvsp[0].value); } +#line 1738 "jsonpath_gram.c" + break; + + case 66: /* accessor_op: '.' any_path */ +#line 249 "jsonpath_gram.y" + { (yyval.value) = (yyvsp[0].value); } +#line 1744 "jsonpath_gram.c" + break; + + case 67: /* accessor_op: '.' method '(' ')' */ +#line 250 "jsonpath_gram.y" + { (yyval.value) = makeItemType((yyvsp[-2].optype)); } +#line 1750 "jsonpath_gram.c" + break; + + case 68: /* accessor_op: '.' DATETIME_P '(' opt_datetime_template ')' */ +#line 252 "jsonpath_gram.y" + { (yyval.value) = makeItemUnary(jpiDatetime, (yyvsp[-1].value)); } +#line 1756 "jsonpath_gram.c" + break; + + case 69: /* accessor_op: '?' '(' predicate ')' */ +#line 253 "jsonpath_gram.y" + { (yyval.value) = makeItemUnary(jpiFilter, (yyvsp[-1].value)); } +#line 1762 "jsonpath_gram.c" + break; + + case 70: /* datetime_template: STRING_P */ +#line 257 "jsonpath_gram.y" + { (yyval.value) = makeItemString(&(yyvsp[0].str)); } +#line 1768 "jsonpath_gram.c" + break; + + case 71: /* opt_datetime_template: datetime_template */ +#line 261 "jsonpath_gram.y" + { (yyval.value) = (yyvsp[0].value); } +#line 1774 "jsonpath_gram.c" + break; + + case 72: /* opt_datetime_template: %empty */ +#line 262 "jsonpath_gram.y" + { (yyval.value) = NULL; } +#line 1780 "jsonpath_gram.c" + break; + + case 73: /* key: key_name */ +#line 266 "jsonpath_gram.y" + { (yyval.value) = makeItemKey(&(yyvsp[0].str)); } +#line 1786 "jsonpath_gram.c" + break; + + case 98: /* method: ABS_P */ +#line 297 "jsonpath_gram.y" + { (yyval.optype) = jpiAbs; } +#line 1792 "jsonpath_gram.c" + break; + + case 99: /* method: SIZE_P */ +#line 298 "jsonpath_gram.y" + { (yyval.optype) = jpiSize; } +#line 1798 "jsonpath_gram.c" + break; + + case 100: /* method: TYPE_P */ +#line 299 "jsonpath_gram.y" + { (yyval.optype) = jpiType; } +#line 1804 "jsonpath_gram.c" + break; + + case 101: /* method: FLOOR_P */ +#line 300 "jsonpath_gram.y" + { (yyval.optype) = jpiFloor; } +#line 1810 "jsonpath_gram.c" + break; + + case 102: /* method: DOUBLE_P */ +#line 301 "jsonpath_gram.y" + { (yyval.optype) = jpiDouble; } +#line 1816 "jsonpath_gram.c" + break; + + case 103: /* method: CEILING_P */ +#line 302 "jsonpath_gram.y" + { (yyval.optype) = jpiCeiling; } +#line 1822 "jsonpath_gram.c" + break; + + case 104: /* method: KEYVALUE_P */ +#line 303 "jsonpath_gram.y" + { (yyval.optype) = jpiKeyValue; } +#line 1828 "jsonpath_gram.c" + break; + + +#line 1832 "jsonpath_gram.c" + + default: break; + } + /* User semantic actions sometimes alter yychar, and that requires + that yytoken be updated with the new translation. We take the + approach of translating immediately before every use of yytoken. + One alternative is translating here after every semantic action, + but that translation would be missed if the semantic action invokes + YYABORT, YYACCEPT, or YYERROR immediately after altering yychar or + if it invokes YYBACKUP. In the case of YYABORT or YYACCEPT, an + incorrect destructor might then be invoked immediately. In the + case of YYERROR or YYBACKUP, subsequent parser actions might lead + to an incorrect destructor call or verbose syntax error message + before the lookahead is translated. */ + YY_SYMBOL_PRINT ("-> $$ =", YY_CAST (yysymbol_kind_t, yyr1[yyn]), &yyval, &yyloc); + + YYPOPSTACK (yylen); + yylen = 0; + + *++yyvsp = yyval; + + /* Now 'shift' the result of the reduction. Determine what state + that goes to, based on the state we popped back to and the rule + number reduced by. */ + { + const int yylhs = yyr1[yyn] - YYNTOKENS; + const int yyi = yypgoto[yylhs] + *yyssp; + yystate = (0 <= yyi && yyi <= YYLAST && yycheck[yyi] == *yyssp + ? yytable[yyi] + : yydefgoto[yylhs]); + } + + goto yynewstate; + + +/*--------------------------------------. +| yyerrlab -- here on detecting error. | +`--------------------------------------*/ +yyerrlab: + /* Make sure we have latest lookahead translation. See comments at + user semantic actions for why this is necessary. */ + yytoken = yychar == YYEMPTY ? YYSYMBOL_YYEMPTY : YYTRANSLATE (yychar); + /* If not already recovering from an error, report this error. */ + if (!yyerrstatus) + { + ++yynerrs; + yyerror (result, escontext, YY_("syntax error")); + } + + if (yyerrstatus == 3) + { + /* If just tried and failed to reuse lookahead token after an + error, discard it. */ + + if (yychar <= YYEOF) + { + /* Return failure if at end of input. */ + if (yychar == YYEOF) + YYABORT; + } + else + { + yydestruct ("Error: discarding", + yytoken, &yylval, result, escontext); + yychar = YYEMPTY; + } + } + + /* Else will try to reuse lookahead token after shifting the error + token. */ + goto yyerrlab1; + + +/*---------------------------------------------------. +| yyerrorlab -- error raised explicitly by YYERROR. | +`---------------------------------------------------*/ +yyerrorlab: + /* Pacify compilers when the user code never invokes YYERROR and the + label yyerrorlab therefore never appears in user code. */ + if (0) + YYERROR; + + /* Do not reclaim the symbols of the rule whose action triggered + this YYERROR. */ + YYPOPSTACK (yylen); + yylen = 0; + YY_STACK_PRINT (yyss, yyssp); + yystate = *yyssp; + goto yyerrlab1; + + +/*-------------------------------------------------------------. +| yyerrlab1 -- common code for both syntax error and YYERROR. | +`-------------------------------------------------------------*/ +yyerrlab1: + yyerrstatus = 3; /* Each real token shifted decrements this. */ + + /* Pop stack until we find a state that shifts the error token. */ + for (;;) + { + yyn = yypact[yystate]; + if (!yypact_value_is_default (yyn)) + { + yyn += YYSYMBOL_YYerror; + if (0 <= yyn && yyn <= YYLAST && yycheck[yyn] == YYSYMBOL_YYerror) + { + yyn = yytable[yyn]; + if (0 < yyn) + break; + } + } + + /* Pop the current state because it cannot handle the error token. */ + if (yyssp == yyss) + YYABORT; + + + yydestruct ("Error: popping", + YY_ACCESSING_SYMBOL (yystate), yyvsp, result, escontext); + YYPOPSTACK (1); + yystate = *yyssp; + YY_STACK_PRINT (yyss, yyssp); + } + + YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN + *++yyvsp = yylval; + YY_IGNORE_MAYBE_UNINITIALIZED_END + + + /* Shift the error token. */ + YY_SYMBOL_PRINT ("Shifting", YY_ACCESSING_SYMBOL (yyn), yyvsp, yylsp); + + yystate = yyn; + goto yynewstate; + + +/*-------------------------------------. +| yyacceptlab -- YYACCEPT comes here. | +`-------------------------------------*/ +yyacceptlab: + yyresult = 0; + goto yyreturn; + + +/*-----------------------------------. +| yyabortlab -- YYABORT comes here. | +`-----------------------------------*/ +yyabortlab: + yyresult = 1; + goto yyreturn; + + +#if !defined yyoverflow +/*-------------------------------------------------. +| yyexhaustedlab -- memory exhaustion comes here. | +`-------------------------------------------------*/ +yyexhaustedlab: + yyerror (result, escontext, YY_("memory exhausted")); + yyresult = 2; + goto yyreturn; +#endif + + +/*-------------------------------------------------------. +| yyreturn -- parsing is finished, clean up and return. | +`-------------------------------------------------------*/ +yyreturn: + if (yychar != YYEMPTY) + { + /* Make sure we have latest lookahead translation. See comments at + user semantic actions for why this is necessary. */ + yytoken = YYTRANSLATE (yychar); + yydestruct ("Cleanup: discarding lookahead", + yytoken, &yylval, result, escontext); + } + /* Do not reclaim the symbols of the rule whose action triggered + this YYABORT or YYACCEPT. */ + YYPOPSTACK (yylen); + YY_STACK_PRINT (yyss, yyssp); + while (yyssp != yyss) + { + yydestruct ("Cleanup: popping", + YY_ACCESSING_SYMBOL (+*yyssp), yyvsp, result, escontext); + YYPOPSTACK (1); + } +#ifndef yyoverflow + if (yyss != yyssa) + YYSTACK_FREE (yyss); +#endif + + return yyresult; +} + +#line 305 "jsonpath_gram.y" + + +/* + * The helper functions below allocate and fill JsonPathParseItem's of various + * types. + */ + +static JsonPathParseItem * +makeItemType(JsonPathItemType type) +{ + JsonPathParseItem *v = palloc(sizeof(*v)); + + CHECK_FOR_INTERRUPTS(); + + v->type = type; + v->next = NULL; + + return v; +} + +static JsonPathParseItem * +makeItemString(JsonPathString *s) +{ + JsonPathParseItem *v; + + if (s == NULL) + { + v = makeItemType(jpiNull); + } + else + { + v = makeItemType(jpiString); + v->value.string.val = s->val; + v->value.string.len = s->len; + } + + return v; +} + +static JsonPathParseItem * +makeItemVariable(JsonPathString *s) +{ + JsonPathParseItem *v; + + v = makeItemType(jpiVariable); + v->value.string.val = s->val; + v->value.string.len = s->len; + + return v; +} + +static JsonPathParseItem * +makeItemKey(JsonPathString *s) +{ + JsonPathParseItem *v; + + v = makeItemString(s); + v->type = jpiKey; + + return v; +} + +static JsonPathParseItem * +makeItemNumeric(JsonPathString *s) +{ + JsonPathParseItem *v; + + v = makeItemType(jpiNumeric); + v->value.numeric = + DatumGetNumeric(DirectFunctionCall3(numeric_in, + CStringGetDatum(s->val), + ObjectIdGetDatum(InvalidOid), + Int32GetDatum(-1))); + + return v; +} + +static JsonPathParseItem * +makeItemBool(bool val) +{ + JsonPathParseItem *v = makeItemType(jpiBool); + + v->value.boolean = val; + + return v; +} + +static JsonPathParseItem * +makeItemBinary(JsonPathItemType type, JsonPathParseItem *la, JsonPathParseItem *ra) +{ + JsonPathParseItem *v = makeItemType(type); + + v->value.args.left = la; + v->value.args.right = ra; + + return v; +} + +static JsonPathParseItem * +makeItemUnary(JsonPathItemType type, JsonPathParseItem *a) +{ + JsonPathParseItem *v; + + if (type == jpiPlus && a->type == jpiNumeric && !a->next) + return a; + + if (type == jpiMinus && a->type == jpiNumeric && !a->next) + { + v = makeItemType(jpiNumeric); + v->value.numeric = + DatumGetNumeric(DirectFunctionCall1(numeric_uminus, + NumericGetDatum(a->value.numeric))); + return v; + } + + v = makeItemType(type); + + v->value.arg = a; + + return v; +} + +static JsonPathParseItem * +makeItemList(List *list) +{ + JsonPathParseItem *head, + *end; + ListCell *cell; + + head = end = (JsonPathParseItem *) linitial(list); + + if (list_length(list) == 1) + return head; + + /* append items to the end of already existing list */ + while (end->next) + end = end->next; + + for_each_from(cell, list, 1) + { + JsonPathParseItem *c = (JsonPathParseItem *) lfirst(cell); + + end->next = c; + end = c; + } + + return head; +} + +static JsonPathParseItem * +makeIndexArray(List *list) +{ + JsonPathParseItem *v = makeItemType(jpiIndexArray); + ListCell *cell; + int i = 0; + + Assert(list != NIL); + v->value.array.nelems = list_length(list); + + v->value.array.elems = palloc(sizeof(v->value.array.elems[0]) * + v->value.array.nelems); + + foreach(cell, list) + { + JsonPathParseItem *jpi = lfirst(cell); + + Assert(jpi->type == jpiSubscript); + + v->value.array.elems[i].from = jpi->value.args.left; + v->value.array.elems[i++].to = jpi->value.args.right; + } + + return v; +} + +static JsonPathParseItem * +makeAny(int first, int last) +{ + JsonPathParseItem *v = makeItemType(jpiAny); + + v->value.anybounds.first = (first >= 0) ? first : PG_UINT32_MAX; + v->value.anybounds.last = (last >= 0) ? last : PG_UINT32_MAX; + + return v; +} + +static bool +makeItemLikeRegex(JsonPathParseItem *expr, JsonPathString *pattern, + JsonPathString *flags, JsonPathParseItem ** result, + struct Node *escontext) +{ + JsonPathParseItem *v = makeItemType(jpiLikeRegex); + int i; + int cflags; + + v->value.like_regex.expr = expr; + v->value.like_regex.pattern = pattern->val; + v->value.like_regex.patternlen = pattern->len; + + /* Parse the flags string, convert to bitmask. Duplicate flags are OK. */ + v->value.like_regex.flags = 0; + for (i = 0; flags && i < flags->len; i++) + { + switch (flags->val[i]) + { + case 'i': + v->value.like_regex.flags |= JSP_REGEX_ICASE; + break; + case 's': + v->value.like_regex.flags |= JSP_REGEX_DOTALL; + break; + case 'm': + v->value.like_regex.flags |= JSP_REGEX_MLINE; + break; + case 'x': + v->value.like_regex.flags |= JSP_REGEX_WSPACE; + break; + case 'q': + v->value.like_regex.flags |= JSP_REGEX_QUOTE; + break; + default: + ereturn(escontext, false, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid input syntax for type %s", "jsonpath"), + errdetail("Unrecognized flag character \"%.*s\" in LIKE_REGEX predicate.", + pg_mblen(flags->val + i), flags->val + i))); + break; + } + } + + /* Convert flags to what pg_regcomp needs */ + if ( !jspConvertRegexFlags(v->value.like_regex.flags, &cflags, escontext)) + return false; + + /* check regex validity */ + { + regex_t re_tmp; + pg_wchar *wpattern; + int wpattern_len; + int re_result; + + wpattern = (pg_wchar *) palloc((pattern->len + 1) * sizeof(pg_wchar)); + wpattern_len = pg_mb2wchar_with_len(pattern->val, + wpattern, + pattern->len); + + if ((re_result = pg_regcomp(&re_tmp, wpattern, wpattern_len, cflags, + DEFAULT_COLLATION_OID)) != REG_OKAY) + { + char errMsg[100]; + + pg_regerror(re_result, &re_tmp, errMsg, sizeof(errMsg)); + ereturn(escontext, false, + (errcode(ERRCODE_INVALID_REGULAR_EXPRESSION), + errmsg("invalid regular expression: %s", errMsg))); + } + + pg_regfree(&re_tmp); + } + + *result = v; + + return true; +} + +/* + * Convert from XQuery regex flags to those recognized by our regex library. + */ +bool +jspConvertRegexFlags(uint32 xflags, int *result, struct Node *escontext) +{ + /* By default, XQuery is very nearly the same as Spencer's AREs */ + int cflags = REG_ADVANCED; + + /* Ignore-case means the same thing, too, modulo locale issues */ + if (xflags & JSP_REGEX_ICASE) + cflags |= REG_ICASE; + + /* Per XQuery spec, if 'q' is specified then 'm', 's', 'x' are ignored */ + if (xflags & JSP_REGEX_QUOTE) + { + cflags &= ~REG_ADVANCED; + cflags |= REG_QUOTE; + } + else + { + /* Note that dotall mode is the default in POSIX */ + if (!(xflags & JSP_REGEX_DOTALL)) + cflags |= REG_NLSTOP; + if (xflags & JSP_REGEX_MLINE) + cflags |= REG_NLANCH; + + /* + * XQuery's 'x' mode is related to Spencer's expanded mode, but it's + * not really enough alike to justify treating JSP_REGEX_WSPACE as + * REG_EXPANDED. For now we treat 'x' as unimplemented; perhaps in + * future we'll modify the regex library to have an option for + * XQuery-style ignore-whitespace mode. + */ + if (xflags & JSP_REGEX_WSPACE) + ereturn(escontext, false, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("XQuery \"x\" flag (expanded regular expressions) is not implemented"))); + } + + *result = cflags; + + return true; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/jsonpath_gram.h b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/jsonpath_gram.h new file mode 100644 index 00000000000..6931882fc78 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/jsonpath_gram.h @@ -0,0 +1,126 @@ +/* A Bison parser, made by GNU Bison 3.7.5. */ + +/* Bison interface for Yacc-like parsers in C + + Copyright (C) 1984, 1989-1990, 2000-2015, 2018-2021 Free Software Foundation, + Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +/* As a special exception, you may create a larger work that contains + part or all of the Bison parser skeleton and distribute that work + under terms of your choice, so long as that work isn't itself a + parser generator using the skeleton or a modified version thereof + as a parser skeleton. Alternatively, if you modify or redistribute + the parser skeleton itself, you may (at your option) remove this + special exception, which will cause the skeleton and the resulting + Bison output files to be licensed under the GNU General Public + License without this special exception. + + This special exception was added by the Free Software Foundation in + version 2.2 of Bison. */ + +/* DO NOT RELY ON FEATURES THAT ARE NOT DOCUMENTED in the manual, + especially those whose name start with YY_ or yy_. They are + private implementation details that can be changed or removed. */ + +#ifndef YY_JSONPATH_YY_JSONPATH_GRAM_H_INCLUDED +# define YY_JSONPATH_YY_JSONPATH_GRAM_H_INCLUDED +/* Debug traces. */ +#ifndef YYDEBUG +# define YYDEBUG 0 +#endif +#if YYDEBUG +extern int jsonpath_yydebug; +#endif + +/* Token kinds. */ +#ifndef YYTOKENTYPE +# define YYTOKENTYPE + enum yytokentype + { + YYEMPTY = -2, + YYEOF = 0, /* "end of file" */ + YYerror = 256, /* error */ + YYUNDEF = 257, /* "invalid token" */ + TO_P = 258, /* TO_P */ + NULL_P = 259, /* NULL_P */ + TRUE_P = 260, /* TRUE_P */ + FALSE_P = 261, /* FALSE_P */ + IS_P = 262, /* IS_P */ + UNKNOWN_P = 263, /* UNKNOWN_P */ + EXISTS_P = 264, /* EXISTS_P */ + IDENT_P = 265, /* IDENT_P */ + STRING_P = 266, /* STRING_P */ + NUMERIC_P = 267, /* NUMERIC_P */ + INT_P = 268, /* INT_P */ + VARIABLE_P = 269, /* VARIABLE_P */ + OR_P = 270, /* OR_P */ + AND_P = 271, /* AND_P */ + NOT_P = 272, /* NOT_P */ + LESS_P = 273, /* LESS_P */ + LESSEQUAL_P = 274, /* LESSEQUAL_P */ + EQUAL_P = 275, /* EQUAL_P */ + NOTEQUAL_P = 276, /* NOTEQUAL_P */ + GREATEREQUAL_P = 277, /* GREATEREQUAL_P */ + GREATER_P = 278, /* GREATER_P */ + ANY_P = 279, /* ANY_P */ + STRICT_P = 280, /* STRICT_P */ + LAX_P = 281, /* LAX_P */ + LAST_P = 282, /* LAST_P */ + STARTS_P = 283, /* STARTS_P */ + WITH_P = 284, /* WITH_P */ + LIKE_REGEX_P = 285, /* LIKE_REGEX_P */ + FLAG_P = 286, /* FLAG_P */ + ABS_P = 287, /* ABS_P */ + SIZE_P = 288, /* SIZE_P */ + TYPE_P = 289, /* TYPE_P */ + FLOOR_P = 290, /* FLOOR_P */ + DOUBLE_P = 291, /* DOUBLE_P */ + CEILING_P = 292, /* CEILING_P */ + KEYVALUE_P = 293, /* KEYVALUE_P */ + DATETIME_P = 294, /* DATETIME_P */ + UMINUS = 295 /* UMINUS */ + }; + typedef enum yytokentype yytoken_kind_t; +#endif + +/* Value type. */ +#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED +union YYSTYPE +{ +#line 67 "jsonpath_gram.y" + + JsonPathString str; + List *elems; /* list of JsonPathParseItem */ + List *indexs; /* list of integers */ + JsonPathParseItem *value; + JsonPathParseResult *result; + JsonPathItemType optype; + bool boolean; + int integer; + +#line 115 "jsonpath_gram.h" + +}; +typedef union YYSTYPE YYSTYPE; +# define YYSTYPE_IS_TRIVIAL 1 +# define YYSTYPE_IS_DECLARED 1 +#endif + + + +int jsonpath_yyparse (JsonPathParseResult **result, struct Node *escontext); + +#endif /* !YY_JSONPATH_YY_JSONPATH_GRAM_H_INCLUDED */ diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/jsonpath_internal.h b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/jsonpath_internal.h new file mode 100644 index 00000000000..90eea6e9616 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/jsonpath_internal.h @@ -0,0 +1,38 @@ +/*------------------------------------------------------------------------- + * + * jsonpath_internal.h + * Private definitions for jsonpath scanner & parser + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/backend/utils/adt/jsonpath_internal.h + * + *------------------------------------------------------------------------- + */ + +#ifndef JSONPATH_INTERNAL_H +#define JSONPATH_INTERNAL_H + +/* struct JsonPathString is shared between scan and gram */ +typedef struct JsonPathString +{ + char *val; + int len; + int total; +} JsonPathString; + +#include "utils/jsonpath.h" +#include "jsonpath_gram.h" + +#define YY_DECL extern int jsonpath_yylex(YYSTYPE *yylval_param, \ + JsonPathParseResult **result, \ + struct Node *escontext) +YY_DECL; +extern int jsonpath_yyparse(JsonPathParseResult **result, + struct Node *escontext); +extern void jsonpath_yyerror(JsonPathParseResult **result, + struct Node *escontext, + const char *message); + +#endif /* JSONPATH_INTERNAL_H */ diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/jsonpath_scan.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/jsonpath_scan.c new file mode 100644 index 00000000000..cddc313bb41 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/jsonpath_scan.c @@ -0,0 +1,6175 @@ +#line 2 "jsonpath_scan.c" +/*------------------------------------------------------------------------- + * + * jsonpath_scan.l + * Lexical parser for jsonpath datatype + * + * Splits jsonpath string into tokens represented as JsonPathString structs. + * Decodes unicode and hex escaped strings. + * + * Copyright (c) 2019-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/utils/adt/jsonpath_scan.l + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +/* + * NB: include jsonpath_gram.h only AFTER including jsonpath_internal.h, + * because jsonpath_internal.h contains the declaration for JsonPathString. + */ +#include "jsonpath_internal.h" +#include "jsonpath_gram.h" + +#include "mb/pg_wchar.h" +#include "nodes/miscnodes.h" +#include "nodes/pg_list.h" + +#line 32 "jsonpath_scan.c" + +#define YY_INT_ALIGNED short int + +/* A lexical scanner generated by flex */ + +#define yy_create_buffer jsonpath_yy_create_buffer +#define yy_delete_buffer jsonpath_yy_delete_buffer +#define yy_scan_buffer jsonpath_yy_scan_buffer +#define yy_scan_string jsonpath_yy_scan_string +#define yy_scan_bytes jsonpath_yy_scan_bytes +#define yy_init_buffer jsonpath_yy_init_buffer +#define yy_flush_buffer jsonpath_yy_flush_buffer +#define yy_load_buffer_state jsonpath_yy_load_buffer_state +#define yy_switch_to_buffer jsonpath_yy_switch_to_buffer +#define yypush_buffer_state jsonpath_yypush_buffer_state +#define yypop_buffer_state jsonpath_yypop_buffer_state +#define yyensure_buffer_stack jsonpath_yyensure_buffer_stack +#define yy_flex_debug jsonpath_yy_flex_debug +#define yyin jsonpath_yyin +#define yyleng jsonpath_yyleng +#define yylex jsonpath_yylex +#define yylineno jsonpath_yylineno +#define yyout jsonpath_yyout +#define yyrestart jsonpath_yyrestart +#define yytext jsonpath_yytext +#define yywrap jsonpath_yywrap +#define yyalloc jsonpath_yyalloc +#define yyrealloc jsonpath_yyrealloc +#define yyfree jsonpath_yyfree + +#define FLEX_SCANNER +#define YY_FLEX_MAJOR_VERSION 2 +#define YY_FLEX_MINOR_VERSION 6 +#define YY_FLEX_SUBMINOR_VERSION 4 +#if YY_FLEX_SUBMINOR_VERSION > 0 +#define FLEX_BETA +#endif + +#ifdef yy_create_buffer +#define jsonpath_yy_create_buffer_ALREADY_DEFINED +#else +#define yy_create_buffer jsonpath_yy_create_buffer +#endif + +#ifdef yy_delete_buffer +#define jsonpath_yy_delete_buffer_ALREADY_DEFINED +#else +#define yy_delete_buffer jsonpath_yy_delete_buffer +#endif + +#ifdef yy_scan_buffer +#define jsonpath_yy_scan_buffer_ALREADY_DEFINED +#else +#define yy_scan_buffer jsonpath_yy_scan_buffer +#endif + +#ifdef yy_scan_string +#define jsonpath_yy_scan_string_ALREADY_DEFINED +#else +#define yy_scan_string jsonpath_yy_scan_string +#endif + +#ifdef yy_scan_bytes +#define jsonpath_yy_scan_bytes_ALREADY_DEFINED +#else +#define yy_scan_bytes jsonpath_yy_scan_bytes +#endif + +#ifdef yy_init_buffer +#define jsonpath_yy_init_buffer_ALREADY_DEFINED +#else +#define yy_init_buffer jsonpath_yy_init_buffer +#endif + +#ifdef yy_flush_buffer +#define jsonpath_yy_flush_buffer_ALREADY_DEFINED +#else +#define yy_flush_buffer jsonpath_yy_flush_buffer +#endif + +#ifdef yy_load_buffer_state +#define jsonpath_yy_load_buffer_state_ALREADY_DEFINED +#else +#define yy_load_buffer_state jsonpath_yy_load_buffer_state +#endif + +#ifdef yy_switch_to_buffer +#define jsonpath_yy_switch_to_buffer_ALREADY_DEFINED +#else +#define yy_switch_to_buffer jsonpath_yy_switch_to_buffer +#endif + +#ifdef yypush_buffer_state +#define jsonpath_yypush_buffer_state_ALREADY_DEFINED +#else +#define yypush_buffer_state jsonpath_yypush_buffer_state +#endif + +#ifdef yypop_buffer_state +#define jsonpath_yypop_buffer_state_ALREADY_DEFINED +#else +#define yypop_buffer_state jsonpath_yypop_buffer_state +#endif + +#ifdef yyensure_buffer_stack +#define jsonpath_yyensure_buffer_stack_ALREADY_DEFINED +#else +#define yyensure_buffer_stack jsonpath_yyensure_buffer_stack +#endif + +#ifdef yylex +#define jsonpath_yylex_ALREADY_DEFINED +#else +#define yylex jsonpath_yylex +#endif + +#ifdef yyrestart +#define jsonpath_yyrestart_ALREADY_DEFINED +#else +#define yyrestart jsonpath_yyrestart +#endif + +#ifdef yylex_init +#define jsonpath_yylex_init_ALREADY_DEFINED +#else +#define yylex_init jsonpath_yylex_init +#endif + +#ifdef yylex_init_extra +#define jsonpath_yylex_init_extra_ALREADY_DEFINED +#else +#define yylex_init_extra jsonpath_yylex_init_extra +#endif + +#ifdef yylex_destroy +#define jsonpath_yylex_destroy_ALREADY_DEFINED +#else +#define yylex_destroy jsonpath_yylex_destroy +#endif + +#ifdef yyget_debug +#define jsonpath_yyget_debug_ALREADY_DEFINED +#else +#define yyget_debug jsonpath_yyget_debug +#endif + +#ifdef yyset_debug +#define jsonpath_yyset_debug_ALREADY_DEFINED +#else +#define yyset_debug jsonpath_yyset_debug +#endif + +#ifdef yyget_extra +#define jsonpath_yyget_extra_ALREADY_DEFINED +#else +#define yyget_extra jsonpath_yyget_extra +#endif + +#ifdef yyset_extra +#define jsonpath_yyset_extra_ALREADY_DEFINED +#else +#define yyset_extra jsonpath_yyset_extra +#endif + +#ifdef yyget_in +#define jsonpath_yyget_in_ALREADY_DEFINED +#else +#define yyget_in jsonpath_yyget_in +#endif + +#ifdef yyset_in +#define jsonpath_yyset_in_ALREADY_DEFINED +#else +#define yyset_in jsonpath_yyset_in +#endif + +#ifdef yyget_out +#define jsonpath_yyget_out_ALREADY_DEFINED +#else +#define yyget_out jsonpath_yyget_out +#endif + +#ifdef yyset_out +#define jsonpath_yyset_out_ALREADY_DEFINED +#else +#define yyset_out jsonpath_yyset_out +#endif + +#ifdef yyget_leng +#define jsonpath_yyget_leng_ALREADY_DEFINED +#else +#define yyget_leng jsonpath_yyget_leng +#endif + +#ifdef yyget_text +#define jsonpath_yyget_text_ALREADY_DEFINED +#else +#define yyget_text jsonpath_yyget_text +#endif + +#ifdef yyget_lineno +#define jsonpath_yyget_lineno_ALREADY_DEFINED +#else +#define yyget_lineno jsonpath_yyget_lineno +#endif + +#ifdef yyset_lineno +#define jsonpath_yyset_lineno_ALREADY_DEFINED +#else +#define yyset_lineno jsonpath_yyset_lineno +#endif + +#ifdef yywrap +#define jsonpath_yywrap_ALREADY_DEFINED +#else +#define yywrap jsonpath_yywrap +#endif + +#ifdef yyget_lval +#define jsonpath_yyget_lval_ALREADY_DEFINED +#else +#define yyget_lval jsonpath_yyget_lval +#endif + +#ifdef yyset_lval +#define jsonpath_yyset_lval_ALREADY_DEFINED +#else +#define yyset_lval jsonpath_yyset_lval +#endif + +#ifdef yyalloc +#define jsonpath_yyalloc_ALREADY_DEFINED +#else +#define yyalloc jsonpath_yyalloc +#endif + +#ifdef yyrealloc +#define jsonpath_yyrealloc_ALREADY_DEFINED +#else +#define yyrealloc jsonpath_yyrealloc +#endif + +#ifdef yyfree +#define jsonpath_yyfree_ALREADY_DEFINED +#else +#define yyfree jsonpath_yyfree +#endif + +#ifdef yytext +#define jsonpath_yytext_ALREADY_DEFINED +#else +#define yytext jsonpath_yytext +#endif + +#ifdef yyleng +#define jsonpath_yyleng_ALREADY_DEFINED +#else +#define yyleng jsonpath_yyleng +#endif + +#ifdef yyin +#define jsonpath_yyin_ALREADY_DEFINED +#else +#define yyin jsonpath_yyin +#endif + +#ifdef yyout +#define jsonpath_yyout_ALREADY_DEFINED +#else +#define yyout jsonpath_yyout +#endif + +#ifdef yy_flex_debug +#define jsonpath_yy_flex_debug_ALREADY_DEFINED +#else +#define yy_flex_debug jsonpath_yy_flex_debug +#endif + +#ifdef yylineno +#define jsonpath_yylineno_ALREADY_DEFINED +#else +#define yylineno jsonpath_yylineno +#endif + +/* First, we deal with platform-specific or compiler-specific issues. */ + +/* begin standard C headers. */ +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <stdlib.h> + +/* end standard C headers. */ + +/* flex integer type definitions */ + +#ifndef FLEXINT_H +#define FLEXINT_H + +/* C99 systems have <inttypes.h>. Non-C99 systems may or may not. */ + +#if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L + +/* C99 says to define __STDC_LIMIT_MACROS before including stdint.h, + * if you want the limit (max/min) macros for int types. + */ +#ifndef __STDC_LIMIT_MACROS +#define __STDC_LIMIT_MACROS 1 +#endif + +#include <inttypes.h> +typedef int8_t flex_int8_t; +typedef uint8_t flex_uint8_t; +typedef int16_t flex_int16_t; +typedef uint16_t flex_uint16_t; +typedef int32_t flex_int32_t; +typedef uint32_t flex_uint32_t; +#else +typedef signed char flex_int8_t; +typedef short int flex_int16_t; +typedef int flex_int32_t; +typedef unsigned char flex_uint8_t; +typedef unsigned short int flex_uint16_t; +typedef unsigned int flex_uint32_t; + +/* Limits of integral types. */ +#ifndef INT8_MIN +#define INT8_MIN (-128) +#endif +#ifndef INT16_MIN +#define INT16_MIN (-32767-1) +#endif +#ifndef INT32_MIN +#define INT32_MIN (-2147483647-1) +#endif +#ifndef INT8_MAX +#define INT8_MAX (127) +#endif +#ifndef INT16_MAX +#define INT16_MAX (32767) +#endif +#ifndef INT32_MAX +#define INT32_MAX (2147483647) +#endif +#ifndef UINT8_MAX +#define UINT8_MAX (255U) +#endif +#ifndef UINT16_MAX +#define UINT16_MAX (65535U) +#endif +#ifndef UINT32_MAX +#define UINT32_MAX (4294967295U) +#endif + +#ifndef SIZE_MAX +#define SIZE_MAX (~(size_t)0) +#endif + +#endif /* ! C99 */ + +#endif /* ! FLEXINT_H */ + +/* begin standard C++ headers. */ + +/* TODO: this is always defined, so inline it */ +#define yyconst const + +#if defined(__GNUC__) && __GNUC__ >= 3 +#define yynoreturn __attribute__((__noreturn__)) +#else +#define yynoreturn +#endif + +/* Returned upon end-of-file. */ +#define YY_NULL 0 + +/* Promotes a possibly negative, possibly signed char to an + * integer in range [0..255] for use as an array index. + */ +#define YY_SC_TO_UI(c) ((YY_CHAR) (c)) + +/* Enter a start condition. This macro really ought to take a parameter, + * but we do it the disgusting crufty way forced on us by the ()-less + * definition of BEGIN. + */ +#define BEGIN (yy_start) = 1 + 2 * +/* Translate the current start state into a value that can be later handed + * to BEGIN to return to the state. The YYSTATE alias is for lex + * compatibility. + */ +#define YY_START (((yy_start) - 1) / 2) +#define YYSTATE YY_START +/* Action number for EOF rule of a given start state. */ +#define YY_STATE_EOF(state) (YY_END_OF_BUFFER + state + 1) +/* Special action meaning "start processing a new file". */ +#define YY_NEW_FILE yyrestart( yyin ) +#define YY_END_OF_BUFFER_CHAR 0 + +/* Size of default input buffer. */ +#ifndef YY_BUF_SIZE +#ifdef __ia64__ +/* On IA-64, the buffer size is 16k, not 8k. + * Moreover, YY_BUF_SIZE is 2*YY_READ_BUF_SIZE in the general case. + * Ditto for the __ia64__ case accordingly. + */ +#define YY_BUF_SIZE 32768 +#else +#define YY_BUF_SIZE 16384 +#endif /* __ia64__ */ +#endif + +/* The state buf must be large enough to hold one state per character in the main buffer. + */ +#define YY_STATE_BUF_SIZE ((YY_BUF_SIZE + 2) * sizeof(yy_state_type)) + +#ifndef YY_TYPEDEF_YY_BUFFER_STATE +#define YY_TYPEDEF_YY_BUFFER_STATE +typedef struct yy_buffer_state *YY_BUFFER_STATE; +#endif + +#ifndef YY_TYPEDEF_YY_SIZE_T +#define YY_TYPEDEF_YY_SIZE_T +typedef size_t yy_size_t; +#endif + +extern __thread int yyleng; + +extern __thread FILE *yyin, *yyout; + +#define EOB_ACT_CONTINUE_SCAN 0 +#define EOB_ACT_END_OF_FILE 1 +#define EOB_ACT_LAST_MATCH 2 + + #define YY_LESS_LINENO(n) + #define YY_LINENO_REWIND_TO(ptr) + +/* Return all but the first "n" matched characters back to the input stream. */ +#define yyless(n) \ + do \ + { \ + /* Undo effects of setting up yytext. */ \ + int yyless_macro_arg = (n); \ + YY_LESS_LINENO(yyless_macro_arg);\ + *yy_cp = (yy_hold_char); \ + YY_RESTORE_YY_MORE_OFFSET \ + (yy_c_buf_p) = yy_cp = yy_bp + yyless_macro_arg - YY_MORE_ADJ; \ + YY_DO_BEFORE_ACTION; /* set up yytext again */ \ + } \ + while ( 0 ) +#define unput(c) yyunput( c, (yytext_ptr) ) + +#ifndef YY_STRUCT_YY_BUFFER_STATE +#define YY_STRUCT_YY_BUFFER_STATE +struct yy_buffer_state + { + FILE *yy_input_file; + + char *yy_ch_buf; /* input buffer */ + char *yy_buf_pos; /* current position in input buffer */ + + /* Size of input buffer in bytes, not including room for EOB + * characters. + */ + int yy_buf_size; + + /* Number of characters read into yy_ch_buf, not including EOB + * characters. + */ + int yy_n_chars; + + /* Whether we "own" the buffer - i.e., we know we created it, + * and can realloc() it to grow it, and should free() it to + * delete it. + */ + int yy_is_our_buffer; + + /* Whether this is an "interactive" input source; if so, and + * if we're using stdio for input, then we want to use getc() + * instead of fread(), to make sure we stop fetching input after + * each newline. + */ + int yy_is_interactive; + + /* Whether we're considered to be at the beginning of a line. + * If so, '^' rules will be active on the next match, otherwise + * not. + */ + int yy_at_bol; + + int yy_bs_lineno; /**< The line count. */ + int yy_bs_column; /**< The column count. */ + + /* Whether to try to fill the input buffer when we reach the + * end of it. + */ + int yy_fill_buffer; + + int yy_buffer_status; + +#define YY_BUFFER_NEW 0 +#define YY_BUFFER_NORMAL 1 + /* When an EOF's been seen but there's still some text to process + * then we mark the buffer as YY_EOF_PENDING, to indicate that we + * shouldn't try reading from the input source any more. We might + * still have a bunch of tokens to match, though, because of + * possible backing-up. + * + * When we actually see the EOF, we change the status to "new" + * (via yyrestart()), so that the user can continue scanning by + * just pointing yyin at a new input file. + */ +#define YY_BUFFER_EOF_PENDING 2 + + }; +#endif /* !YY_STRUCT_YY_BUFFER_STATE */ + +/* Stack of input buffers. */ +static __thread size_t yy_buffer_stack_top = 0; /**< index of top of stack. */ +static __thread size_t yy_buffer_stack_max = 0; /**< capacity of stack. */ +static __thread YY_BUFFER_STATE * yy_buffer_stack = NULL; /**< Stack as an array. */ + +/* We provide macros for accessing buffer states in case in the + * future we want to put the buffer states in a more general + * "scanner state". + * + * Returns the top of the stack, or NULL. + */ +#define YY_CURRENT_BUFFER ( (yy_buffer_stack) \ + ? (yy_buffer_stack)[(yy_buffer_stack_top)] \ + : NULL) +/* Same as previous macro, but useful when we know that the buffer stack is not + * NULL or when we need an lvalue. For internal use only. + */ +#define YY_CURRENT_BUFFER_LVALUE (yy_buffer_stack)[(yy_buffer_stack_top)] + +/* yy_hold_char holds the character lost when yytext is formed. */ +static __thread char yy_hold_char; +static __thread int yy_n_chars; /* number of characters read into yy_ch_buf */ +__thread int yyleng; + +/* Points to current character in buffer. */ +static __thread char *yy_c_buf_p = NULL; +static __thread int yy_init = 0; /* whether we need to initialize */ +static __thread int yy_start = 0; /* start state number */ + +/* Flag which is used to allow yywrap()'s to do buffer switches + * instead of setting up a fresh yyin. A bit of a hack ... + */ +static __thread int yy_did_buffer_switch_on_eof; + +void yyrestart ( FILE *input_file ); +void yy_switch_to_buffer ( YY_BUFFER_STATE new_buffer ); +YY_BUFFER_STATE yy_create_buffer ( FILE *file, int size ); +void yy_delete_buffer ( YY_BUFFER_STATE b ); +void yy_flush_buffer ( YY_BUFFER_STATE b ); +void yypush_buffer_state ( YY_BUFFER_STATE new_buffer ); +void yypop_buffer_state ( void ); + +static void yyensure_buffer_stack ( void ); +static void yy_load_buffer_state ( void ); +static void yy_init_buffer ( YY_BUFFER_STATE b, FILE *file ); +#define YY_FLUSH_BUFFER yy_flush_buffer( YY_CURRENT_BUFFER ) + +YY_BUFFER_STATE yy_scan_buffer ( char *base, yy_size_t size ); +YY_BUFFER_STATE yy_scan_string ( const char *yy_str ); +YY_BUFFER_STATE yy_scan_bytes ( const char *bytes, int len ); + +void *yyalloc ( yy_size_t ); +void *yyrealloc ( void *, yy_size_t ); +void yyfree ( void * ); + +#define yy_new_buffer yy_create_buffer +#define yy_set_interactive(is_interactive) \ + { \ + if ( ! YY_CURRENT_BUFFER ){ \ + yyensure_buffer_stack (); \ + YY_CURRENT_BUFFER_LVALUE = \ + yy_create_buffer( yyin, YY_BUF_SIZE ); \ + } \ + YY_CURRENT_BUFFER_LVALUE->yy_is_interactive = is_interactive; \ + } +#define yy_set_bol(at_bol) \ + { \ + if ( ! YY_CURRENT_BUFFER ){\ + yyensure_buffer_stack (); \ + YY_CURRENT_BUFFER_LVALUE = \ + yy_create_buffer( yyin, YY_BUF_SIZE ); \ + } \ + YY_CURRENT_BUFFER_LVALUE->yy_at_bol = at_bol; \ + } +#define YY_AT_BOL() (YY_CURRENT_BUFFER_LVALUE->yy_at_bol) + +/* Begin user sect3 */ + +#define jsonpath_yywrap() (/*CONSTCOND*/1) +#define YY_SKIP_YYWRAP +typedef flex_uint8_t YY_CHAR; + +__thread FILE *yyin = NULL, *yyout = NULL; + +typedef const struct yy_trans_info *yy_state_type; + +extern __thread int yylineno; +__thread int yylineno = 1; + +extern __thread char *yytext; +#ifdef yytext_ptr +#undef yytext_ptr +#endif +#define yytext_ptr yytext + +static yy_state_type yy_get_previous_state ( void ); +static yy_state_type yy_try_NUL_trans ( yy_state_type current_state ); +static int yy_get_next_buffer ( void ); +static void yynoreturn yy_fatal_error ( const char* msg ); + +/* Done after the current pattern has been matched and before the + * corresponding action - sets up yytext. + */ +#define YY_DO_BEFORE_ACTION \ + (yytext_ptr) = yy_bp; \ + yyleng = (int) (yy_cp - yy_bp); \ + (yy_hold_char) = *yy_cp; \ + *yy_cp = '\0'; \ + (yy_c_buf_p) = yy_cp; +#define YY_NUM_RULES 53 +#define YY_END_OF_BUFFER 54 +struct yy_trans_info + { + flex_int16_t yy_verify; + flex_int16_t yy_nxt; + }; +static const struct yy_trans_info yy_transition[15882] = + { + { 0, 0 }, { 0,15626 }, { 0, 0 }, { 0,15624 }, { 1,2580 }, + { 2,2580 }, { 3,2580 }, { 4,2580 }, { 5,2580 }, { 6,2580 }, + { 7,2580 }, { 8,2580 }, { 9,2838 }, { 10,2838 }, { 11,2580 }, + { 12,2838 }, { 13,2838 }, { 14,2580 }, { 15,2580 }, { 16,2580 }, + { 17,2580 }, { 18,2580 }, { 19,2580 }, { 20,2580 }, { 21,2580 }, + { 22,2580 }, { 23,2580 }, { 24,2580 }, { 25,2580 }, { 26,2580 }, + { 27,2580 }, { 28,2580 }, { 29,2580 }, { 30,2580 }, { 31,2580 }, + { 32,2838 }, { 33,2642 }, { 34,2644 }, { 35,2672 }, { 36,2857 }, + { 37,2672 }, { 38,2854 }, { 39,2580 }, { 40,2672 }, { 41,2672 }, + { 42,3115 }, { 43,2672 }, { 44,2672 }, { 45,2672 }, { 46,3117 }, + + { 47,3119 }, { 48,3176 }, { 49,3434 }, { 50,3434 }, { 51,3434 }, + { 52,3434 }, { 53,3434 }, { 54,3434 }, { 55,3434 }, { 56,3434 }, + { 57,3434 }, { 58,2672 }, { 59,2580 }, { 60,3124 }, { 61,3127 }, + { 62,3147 }, { 63,2672 }, { 64,2672 }, { 65,2580 }, { 66,2580 }, + { 67,2580 }, { 68,2580 }, { 69,2580 }, { 70,2580 }, { 71,2580 }, + { 72,2580 }, { 73,2580 }, { 74,2580 }, { 75,2580 }, { 76,2580 }, + { 77,2580 }, { 78,2580 }, { 79,2580 }, { 80,2580 }, { 81,2580 }, + { 82,2580 }, { 83,2580 }, { 84,2580 }, { 85,2580 }, { 86,2580 }, + { 87,2580 }, { 88,2580 }, { 89,2580 }, { 90,2580 }, { 91,2672 }, + { 92,3149 }, { 93,2672 }, { 94,2580 }, { 95,2580 }, { 96,2580 }, + + { 97,2580 }, { 98,2580 }, { 99,2580 }, { 100,2580 }, { 101,2580 }, + { 102,2580 }, { 103,2580 }, { 104,2580 }, { 105,2580 }, { 106,2580 }, + { 107,2580 }, { 108,2580 }, { 109,2580 }, { 110,2580 }, { 111,2580 }, + { 112,2580 }, { 113,2580 }, { 114,2580 }, { 115,2580 }, { 116,2580 }, + { 117,2580 }, { 118,2580 }, { 119,2580 }, { 120,2580 }, { 121,2580 }, + { 122,2580 }, { 123,2672 }, { 124,3692 }, { 125,2672 }, { 126,2580 }, + { 127,2580 }, { 128,2580 }, { 129,2580 }, { 130,2580 }, { 131,2580 }, + { 132,2580 }, { 133,2580 }, { 134,2580 }, { 135,2580 }, { 136,2580 }, + { 137,2580 }, { 138,2580 }, { 139,2580 }, { 140,2580 }, { 141,2580 }, + { 142,2580 }, { 143,2580 }, { 144,2580 }, { 145,2580 }, { 146,2580 }, + + { 147,2580 }, { 148,2580 }, { 149,2580 }, { 150,2580 }, { 151,2580 }, + { 152,2580 }, { 153,2580 }, { 154,2580 }, { 155,2580 }, { 156,2580 }, + { 157,2580 }, { 158,2580 }, { 159,2580 }, { 160,2580 }, { 161,2580 }, + { 162,2580 }, { 163,2580 }, { 164,2580 }, { 165,2580 }, { 166,2580 }, + { 167,2580 }, { 168,2580 }, { 169,2580 }, { 170,2580 }, { 171,2580 }, + { 172,2580 }, { 173,2580 }, { 174,2580 }, { 175,2580 }, { 176,2580 }, + { 177,2580 }, { 178,2580 }, { 179,2580 }, { 180,2580 }, { 181,2580 }, + { 182,2580 }, { 183,2580 }, { 184,2580 }, { 185,2580 }, { 186,2580 }, + { 187,2580 }, { 188,2580 }, { 189,2580 }, { 190,2580 }, { 191,2580 }, + { 192,2580 }, { 193,2580 }, { 194,2580 }, { 195,2580 }, { 196,2580 }, + + { 197,2580 }, { 198,2580 }, { 199,2580 }, { 200,2580 }, { 201,2580 }, + { 202,2580 }, { 203,2580 }, { 204,2580 }, { 205,2580 }, { 206,2580 }, + { 207,2580 }, { 208,2580 }, { 209,2580 }, { 210,2580 }, { 211,2580 }, + { 212,2580 }, { 213,2580 }, { 214,2580 }, { 215,2580 }, { 216,2580 }, + { 217,2580 }, { 218,2580 }, { 219,2580 }, { 220,2580 }, { 221,2580 }, + { 222,2580 }, { 223,2580 }, { 224,2580 }, { 225,2580 }, { 226,2580 }, + { 227,2580 }, { 228,2580 }, { 229,2580 }, { 230,2580 }, { 231,2580 }, + { 232,2580 }, { 233,2580 }, { 234,2580 }, { 235,2580 }, { 236,2580 }, + { 237,2580 }, { 238,2580 }, { 239,2580 }, { 240,2580 }, { 241,2580 }, + { 242,2580 }, { 243,2580 }, { 244,2580 }, { 245,2580 }, { 246,2580 }, + + { 247,2580 }, { 248,2580 }, { 249,2580 }, { 250,2580 }, { 251,2580 }, + { 252,2580 }, { 253,2580 }, { 254,2580 }, { 255,2580 }, { 256,2580 }, + { 0, 0 }, { 0,15366 }, { 1,2322 }, { 2,2322 }, { 3,2322 }, + { 4,2322 }, { 5,2322 }, { 6,2322 }, { 7,2322 }, { 8,2322 }, + { 9,2580 }, { 10,2580 }, { 11,2322 }, { 12,2580 }, { 13,2580 }, + { 14,2322 }, { 15,2322 }, { 16,2322 }, { 17,2322 }, { 18,2322 }, + { 19,2322 }, { 20,2322 }, { 21,2322 }, { 22,2322 }, { 23,2322 }, + { 24,2322 }, { 25,2322 }, { 26,2322 }, { 27,2322 }, { 28,2322 }, + { 29,2322 }, { 30,2322 }, { 31,2322 }, { 32,2580 }, { 33,2384 }, + { 34,2386 }, { 35,2414 }, { 36,2599 }, { 37,2414 }, { 38,2596 }, + + { 39,2322 }, { 40,2414 }, { 41,2414 }, { 42,2857 }, { 43,2414 }, + { 44,2414 }, { 45,2414 }, { 46,2859 }, { 47,2861 }, { 48,2918 }, + { 49,3176 }, { 50,3176 }, { 51,3176 }, { 52,3176 }, { 53,3176 }, + { 54,3176 }, { 55,3176 }, { 56,3176 }, { 57,3176 }, { 58,2414 }, + { 59,2322 }, { 60,2866 }, { 61,2869 }, { 62,2889 }, { 63,2414 }, + { 64,2414 }, { 65,2322 }, { 66,2322 }, { 67,2322 }, { 68,2322 }, + { 69,2322 }, { 70,2322 }, { 71,2322 }, { 72,2322 }, { 73,2322 }, + { 74,2322 }, { 75,2322 }, { 76,2322 }, { 77,2322 }, { 78,2322 }, + { 79,2322 }, { 80,2322 }, { 81,2322 }, { 82,2322 }, { 83,2322 }, + { 84,2322 }, { 85,2322 }, { 86,2322 }, { 87,2322 }, { 88,2322 }, + + { 89,2322 }, { 90,2322 }, { 91,2414 }, { 92,2891 }, { 93,2414 }, + { 94,2322 }, { 95,2322 }, { 96,2322 }, { 97,2322 }, { 98,2322 }, + { 99,2322 }, { 100,2322 }, { 101,2322 }, { 102,2322 }, { 103,2322 }, + { 104,2322 }, { 105,2322 }, { 106,2322 }, { 107,2322 }, { 108,2322 }, + { 109,2322 }, { 110,2322 }, { 111,2322 }, { 112,2322 }, { 113,2322 }, + { 114,2322 }, { 115,2322 }, { 116,2322 }, { 117,2322 }, { 118,2322 }, + { 119,2322 }, { 120,2322 }, { 121,2322 }, { 122,2322 }, { 123,2414 }, + { 124,3434 }, { 125,2414 }, { 126,2322 }, { 127,2322 }, { 128,2322 }, + { 129,2322 }, { 130,2322 }, { 131,2322 }, { 132,2322 }, { 133,2322 }, + { 134,2322 }, { 135,2322 }, { 136,2322 }, { 137,2322 }, { 138,2322 }, + + { 139,2322 }, { 140,2322 }, { 141,2322 }, { 142,2322 }, { 143,2322 }, + { 144,2322 }, { 145,2322 }, { 146,2322 }, { 147,2322 }, { 148,2322 }, + { 149,2322 }, { 150,2322 }, { 151,2322 }, { 152,2322 }, { 153,2322 }, + { 154,2322 }, { 155,2322 }, { 156,2322 }, { 157,2322 }, { 158,2322 }, + { 159,2322 }, { 160,2322 }, { 161,2322 }, { 162,2322 }, { 163,2322 }, + { 164,2322 }, { 165,2322 }, { 166,2322 }, { 167,2322 }, { 168,2322 }, + { 169,2322 }, { 170,2322 }, { 171,2322 }, { 172,2322 }, { 173,2322 }, + { 174,2322 }, { 175,2322 }, { 176,2322 }, { 177,2322 }, { 178,2322 }, + { 179,2322 }, { 180,2322 }, { 181,2322 }, { 182,2322 }, { 183,2322 }, + { 184,2322 }, { 185,2322 }, { 186,2322 }, { 187,2322 }, { 188,2322 }, + + { 189,2322 }, { 190,2322 }, { 191,2322 }, { 192,2322 }, { 193,2322 }, + { 194,2322 }, { 195,2322 }, { 196,2322 }, { 197,2322 }, { 198,2322 }, + { 199,2322 }, { 200,2322 }, { 201,2322 }, { 202,2322 }, { 203,2322 }, + { 204,2322 }, { 205,2322 }, { 206,2322 }, { 207,2322 }, { 208,2322 }, + { 209,2322 }, { 210,2322 }, { 211,2322 }, { 212,2322 }, { 213,2322 }, + { 214,2322 }, { 215,2322 }, { 216,2322 }, { 217,2322 }, { 218,2322 }, + { 219,2322 }, { 220,2322 }, { 221,2322 }, { 222,2322 }, { 223,2322 }, + { 224,2322 }, { 225,2322 }, { 226,2322 }, { 227,2322 }, { 228,2322 }, + { 229,2322 }, { 230,2322 }, { 231,2322 }, { 232,2322 }, { 233,2322 }, + { 234,2322 }, { 235,2322 }, { 236,2322 }, { 237,2322 }, { 238,2322 }, + + { 239,2322 }, { 240,2322 }, { 241,2322 }, { 242,2322 }, { 243,2322 }, + { 244,2322 }, { 245,2322 }, { 246,2322 }, { 247,2322 }, { 248,2322 }, + { 249,2322 }, { 250,2322 }, { 251,2322 }, { 252,2322 }, { 253,2322 }, + { 254,2322 }, { 255,2322 }, { 256,2322 }, { 0, 0 }, { 0,15108 }, + { 1,3208 }, { 2,3208 }, { 3,3208 }, { 4,3208 }, { 5,3208 }, + { 6,3208 }, { 7,3208 }, { 8,3208 }, { 9,3208 }, { 10,3208 }, + { 11,3208 }, { 12,3208 }, { 13,3208 }, { 14,3208 }, { 15,3208 }, + { 16,3208 }, { 17,3208 }, { 18,3208 }, { 19,3208 }, { 20,3208 }, + { 21,3208 }, { 22,3208 }, { 23,3208 }, { 24,3208 }, { 25,3208 }, + { 26,3208 }, { 27,3208 }, { 28,3208 }, { 29,3208 }, { 30,3208 }, + + { 31,3208 }, { 32,3208 }, { 33,3208 }, { 34,3178 }, { 35,3208 }, + { 36,3208 }, { 37,3208 }, { 38,3208 }, { 39,3208 }, { 40,3208 }, + { 41,3208 }, { 42,3208 }, { 43,3208 }, { 44,3208 }, { 45,3208 }, + { 46,3208 }, { 47,3208 }, { 48,3208 }, { 49,3208 }, { 50,3208 }, + { 51,3208 }, { 52,3208 }, { 53,3208 }, { 54,3208 }, { 55,3208 }, + { 56,3208 }, { 57,3208 }, { 58,3208 }, { 59,3208 }, { 60,3208 }, + { 61,3208 }, { 62,3208 }, { 63,3208 }, { 64,3208 }, { 65,3208 }, + { 66,3208 }, { 67,3208 }, { 68,3208 }, { 69,3208 }, { 70,3208 }, + { 71,3208 }, { 72,3208 }, { 73,3208 }, { 74,3208 }, { 75,3208 }, + { 76,3208 }, { 77,3208 }, { 78,3208 }, { 79,3208 }, { 80,3208 }, + + { 81,3208 }, { 82,3208 }, { 83,3208 }, { 84,3208 }, { 85,3208 }, + { 86,3208 }, { 87,3208 }, { 88,3208 }, { 89,3208 }, { 90,3208 }, + { 91,3208 }, { 92,3466 }, { 93,3208 }, { 94,3208 }, { 95,3208 }, + { 96,3208 }, { 97,3208 }, { 98,3208 }, { 99,3208 }, { 100,3208 }, + { 101,3208 }, { 102,3208 }, { 103,3208 }, { 104,3208 }, { 105,3208 }, + { 106,3208 }, { 107,3208 }, { 108,3208 }, { 109,3208 }, { 110,3208 }, + { 111,3208 }, { 112,3208 }, { 113,3208 }, { 114,3208 }, { 115,3208 }, + { 116,3208 }, { 117,3208 }, { 118,3208 }, { 119,3208 }, { 120,3208 }, + { 121,3208 }, { 122,3208 }, { 123,3208 }, { 124,3208 }, { 125,3208 }, + { 126,3208 }, { 127,3208 }, { 128,3208 }, { 129,3208 }, { 130,3208 }, + + { 131,3208 }, { 132,3208 }, { 133,3208 }, { 134,3208 }, { 135,3208 }, + { 136,3208 }, { 137,3208 }, { 138,3208 }, { 139,3208 }, { 140,3208 }, + { 141,3208 }, { 142,3208 }, { 143,3208 }, { 144,3208 }, { 145,3208 }, + { 146,3208 }, { 147,3208 }, { 148,3208 }, { 149,3208 }, { 150,3208 }, + { 151,3208 }, { 152,3208 }, { 153,3208 }, { 154,3208 }, { 155,3208 }, + { 156,3208 }, { 157,3208 }, { 158,3208 }, { 159,3208 }, { 160,3208 }, + { 161,3208 }, { 162,3208 }, { 163,3208 }, { 164,3208 }, { 165,3208 }, + { 166,3208 }, { 167,3208 }, { 168,3208 }, { 169,3208 }, { 170,3208 }, + { 171,3208 }, { 172,3208 }, { 173,3208 }, { 174,3208 }, { 175,3208 }, + { 176,3208 }, { 177,3208 }, { 178,3208 }, { 179,3208 }, { 180,3208 }, + + { 181,3208 }, { 182,3208 }, { 183,3208 }, { 184,3208 }, { 185,3208 }, + { 186,3208 }, { 187,3208 }, { 188,3208 }, { 189,3208 }, { 190,3208 }, + { 191,3208 }, { 192,3208 }, { 193,3208 }, { 194,3208 }, { 195,3208 }, + { 196,3208 }, { 197,3208 }, { 198,3208 }, { 199,3208 }, { 200,3208 }, + { 201,3208 }, { 202,3208 }, { 203,3208 }, { 204,3208 }, { 205,3208 }, + { 206,3208 }, { 207,3208 }, { 208,3208 }, { 209,3208 }, { 210,3208 }, + { 211,3208 }, { 212,3208 }, { 213,3208 }, { 214,3208 }, { 215,3208 }, + { 216,3208 }, { 217,3208 }, { 218,3208 }, { 219,3208 }, { 220,3208 }, + { 221,3208 }, { 222,3208 }, { 223,3208 }, { 224,3208 }, { 225,3208 }, + { 226,3208 }, { 227,3208 }, { 228,3208 }, { 229,3208 }, { 230,3208 }, + + { 231,3208 }, { 232,3208 }, { 233,3208 }, { 234,3208 }, { 235,3208 }, + { 236,3208 }, { 237,3208 }, { 238,3208 }, { 239,3208 }, { 240,3208 }, + { 241,3208 }, { 242,3208 }, { 243,3208 }, { 244,3208 }, { 245,3208 }, + { 246,3208 }, { 247,3208 }, { 248,3208 }, { 249,3208 }, { 250,3208 }, + { 251,3208 }, { 252,3208 }, { 253,3208 }, { 254,3208 }, { 255,3208 }, + { 256,3208 }, { 0, 0 }, { 0,14850 }, { 1,2950 }, { 2,2950 }, + { 3,2950 }, { 4,2950 }, { 5,2950 }, { 6,2950 }, { 7,2950 }, + { 8,2950 }, { 9,2950 }, { 10,2950 }, { 11,2950 }, { 12,2950 }, + { 13,2950 }, { 14,2950 }, { 15,2950 }, { 16,2950 }, { 17,2950 }, + { 18,2950 }, { 19,2950 }, { 20,2950 }, { 21,2950 }, { 22,2950 }, + + { 23,2950 }, { 24,2950 }, { 25,2950 }, { 26,2950 }, { 27,2950 }, + { 28,2950 }, { 29,2950 }, { 30,2950 }, { 31,2950 }, { 32,2950 }, + { 33,2950 }, { 34,2920 }, { 35,2950 }, { 36,2950 }, { 37,2950 }, + { 38,2950 }, { 39,2950 }, { 40,2950 }, { 41,2950 }, { 42,2950 }, + { 43,2950 }, { 44,2950 }, { 45,2950 }, { 46,2950 }, { 47,2950 }, + { 48,2950 }, { 49,2950 }, { 50,2950 }, { 51,2950 }, { 52,2950 }, + { 53,2950 }, { 54,2950 }, { 55,2950 }, { 56,2950 }, { 57,2950 }, + { 58,2950 }, { 59,2950 }, { 60,2950 }, { 61,2950 }, { 62,2950 }, + { 63,2950 }, { 64,2950 }, { 65,2950 }, { 66,2950 }, { 67,2950 }, + { 68,2950 }, { 69,2950 }, { 70,2950 }, { 71,2950 }, { 72,2950 }, + + { 73,2950 }, { 74,2950 }, { 75,2950 }, { 76,2950 }, { 77,2950 }, + { 78,2950 }, { 79,2950 }, { 80,2950 }, { 81,2950 }, { 82,2950 }, + { 83,2950 }, { 84,2950 }, { 85,2950 }, { 86,2950 }, { 87,2950 }, + { 88,2950 }, { 89,2950 }, { 90,2950 }, { 91,2950 }, { 92,3208 }, + { 93,2950 }, { 94,2950 }, { 95,2950 }, { 96,2950 }, { 97,2950 }, + { 98,2950 }, { 99,2950 }, { 100,2950 }, { 101,2950 }, { 102,2950 }, + { 103,2950 }, { 104,2950 }, { 105,2950 }, { 106,2950 }, { 107,2950 }, + { 108,2950 }, { 109,2950 }, { 110,2950 }, { 111,2950 }, { 112,2950 }, + { 113,2950 }, { 114,2950 }, { 115,2950 }, { 116,2950 }, { 117,2950 }, + { 118,2950 }, { 119,2950 }, { 120,2950 }, { 121,2950 }, { 122,2950 }, + + { 123,2950 }, { 124,2950 }, { 125,2950 }, { 126,2950 }, { 127,2950 }, + { 128,2950 }, { 129,2950 }, { 130,2950 }, { 131,2950 }, { 132,2950 }, + { 133,2950 }, { 134,2950 }, { 135,2950 }, { 136,2950 }, { 137,2950 }, + { 138,2950 }, { 139,2950 }, { 140,2950 }, { 141,2950 }, { 142,2950 }, + { 143,2950 }, { 144,2950 }, { 145,2950 }, { 146,2950 }, { 147,2950 }, + { 148,2950 }, { 149,2950 }, { 150,2950 }, { 151,2950 }, { 152,2950 }, + { 153,2950 }, { 154,2950 }, { 155,2950 }, { 156,2950 }, { 157,2950 }, + { 158,2950 }, { 159,2950 }, { 160,2950 }, { 161,2950 }, { 162,2950 }, + { 163,2950 }, { 164,2950 }, { 165,2950 }, { 166,2950 }, { 167,2950 }, + { 168,2950 }, { 169,2950 }, { 170,2950 }, { 171,2950 }, { 172,2950 }, + + { 173,2950 }, { 174,2950 }, { 175,2950 }, { 176,2950 }, { 177,2950 }, + { 178,2950 }, { 179,2950 }, { 180,2950 }, { 181,2950 }, { 182,2950 }, + { 183,2950 }, { 184,2950 }, { 185,2950 }, { 186,2950 }, { 187,2950 }, + { 188,2950 }, { 189,2950 }, { 190,2950 }, { 191,2950 }, { 192,2950 }, + { 193,2950 }, { 194,2950 }, { 195,2950 }, { 196,2950 }, { 197,2950 }, + { 198,2950 }, { 199,2950 }, { 200,2950 }, { 201,2950 }, { 202,2950 }, + { 203,2950 }, { 204,2950 }, { 205,2950 }, { 206,2950 }, { 207,2950 }, + { 208,2950 }, { 209,2950 }, { 210,2950 }, { 211,2950 }, { 212,2950 }, + { 213,2950 }, { 214,2950 }, { 215,2950 }, { 216,2950 }, { 217,2950 }, + { 218,2950 }, { 219,2950 }, { 220,2950 }, { 221,2950 }, { 222,2950 }, + + { 223,2950 }, { 224,2950 }, { 225,2950 }, { 226,2950 }, { 227,2950 }, + { 228,2950 }, { 229,2950 }, { 230,2950 }, { 231,2950 }, { 232,2950 }, + { 233,2950 }, { 234,2950 }, { 235,2950 }, { 236,2950 }, { 237,2950 }, + { 238,2950 }, { 239,2950 }, { 240,2950 }, { 241,2950 }, { 242,2950 }, + { 243,2950 }, { 244,2950 }, { 245,2950 }, { 246,2950 }, { 247,2950 }, + { 248,2950 }, { 249,2950 }, { 250,2950 }, { 251,2950 }, { 252,2950 }, + { 253,2950 }, { 254,2950 }, { 255,2950 }, { 256,2950 }, { 0, 0 }, + { 0,14592 }, { 1,3208 }, { 2,3208 }, { 3,3208 }, { 4,3208 }, + { 5,3208 }, { 6,3208 }, { 7,3208 }, { 8,3208 }, { 9,3466 }, + { 10,3466 }, { 11,3208 }, { 12,3466 }, { 13,3466 }, { 14,3208 }, + + { 15,3208 }, { 16,3208 }, { 17,3208 }, { 18,3208 }, { 19,3208 }, + { 20,3208 }, { 21,3208 }, { 22,3208 }, { 23,3208 }, { 24,3208 }, + { 25,3208 }, { 26,3208 }, { 27,3208 }, { 28,3208 }, { 29,3208 }, + { 30,3208 }, { 31,3208 }, { 32,3466 }, { 33,2664 }, { 34,2664 }, + { 35,2664 }, { 36,2664 }, { 37,2664 }, { 38,2664 }, { 39,3208 }, + { 40,2664 }, { 41,2664 }, { 42,2664 }, { 43,2664 }, { 44,2664 }, + { 45,2664 }, { 46,2664 }, { 47,2684 }, { 48,3208 }, { 49,3208 }, + { 50,3208 }, { 51,3208 }, { 52,3208 }, { 53,3208 }, { 54,3208 }, + { 55,3208 }, { 56,3208 }, { 57,3208 }, { 58,2664 }, { 59,3208 }, + { 60,2664 }, { 61,2664 }, { 62,2664 }, { 63,2664 }, { 64,2664 }, + + { 65,3208 }, { 66,3208 }, { 67,3208 }, { 68,3208 }, { 69,3208 }, + { 70,3208 }, { 71,3208 }, { 72,3208 }, { 73,3208 }, { 74,3208 }, + { 75,3208 }, { 76,3208 }, { 77,3208 }, { 78,3208 }, { 79,3208 }, + { 80,3208 }, { 81,3208 }, { 82,3208 }, { 83,3208 }, { 84,3208 }, + { 85,3208 }, { 86,3208 }, { 87,3208 }, { 88,3208 }, { 89,3208 }, + { 90,3208 }, { 91,2664 }, { 92,2950 }, { 93,2664 }, { 94,3208 }, + { 95,3208 }, { 96,3208 }, { 97,3208 }, { 98,3208 }, { 99,3208 }, + { 100,3208 }, { 101,3208 }, { 102,3208 }, { 103,3208 }, { 104,3208 }, + { 105,3208 }, { 106,3208 }, { 107,3208 }, { 108,3208 }, { 109,3208 }, + { 110,3208 }, { 111,3208 }, { 112,3208 }, { 113,3208 }, { 114,3208 }, + + { 115,3208 }, { 116,3208 }, { 117,3208 }, { 118,3208 }, { 119,3208 }, + { 120,3208 }, { 121,3208 }, { 122,3208 }, { 123,2664 }, { 124,2664 }, + { 125,2664 }, { 126,3208 }, { 127,3208 }, { 128,3208 }, { 129,3208 }, + { 130,3208 }, { 131,3208 }, { 132,3208 }, { 133,3208 }, { 134,3208 }, + { 135,3208 }, { 136,3208 }, { 137,3208 }, { 138,3208 }, { 139,3208 }, + { 140,3208 }, { 141,3208 }, { 142,3208 }, { 143,3208 }, { 144,3208 }, + { 145,3208 }, { 146,3208 }, { 147,3208 }, { 148,3208 }, { 149,3208 }, + { 150,3208 }, { 151,3208 }, { 152,3208 }, { 153,3208 }, { 154,3208 }, + { 155,3208 }, { 156,3208 }, { 157,3208 }, { 158,3208 }, { 159,3208 }, + { 160,3208 }, { 161,3208 }, { 162,3208 }, { 163,3208 }, { 164,3208 }, + + { 165,3208 }, { 166,3208 }, { 167,3208 }, { 168,3208 }, { 169,3208 }, + { 170,3208 }, { 171,3208 }, { 172,3208 }, { 173,3208 }, { 174,3208 }, + { 175,3208 }, { 176,3208 }, { 177,3208 }, { 178,3208 }, { 179,3208 }, + { 180,3208 }, { 181,3208 }, { 182,3208 }, { 183,3208 }, { 184,3208 }, + { 185,3208 }, { 186,3208 }, { 187,3208 }, { 188,3208 }, { 189,3208 }, + { 190,3208 }, { 191,3208 }, { 192,3208 }, { 193,3208 }, { 194,3208 }, + { 195,3208 }, { 196,3208 }, { 197,3208 }, { 198,3208 }, { 199,3208 }, + { 200,3208 }, { 201,3208 }, { 202,3208 }, { 203,3208 }, { 204,3208 }, + { 205,3208 }, { 206,3208 }, { 207,3208 }, { 208,3208 }, { 209,3208 }, + { 210,3208 }, { 211,3208 }, { 212,3208 }, { 213,3208 }, { 214,3208 }, + + { 215,3208 }, { 216,3208 }, { 217,3208 }, { 218,3208 }, { 219,3208 }, + { 220,3208 }, { 221,3208 }, { 222,3208 }, { 223,3208 }, { 224,3208 }, + { 225,3208 }, { 226,3208 }, { 227,3208 }, { 228,3208 }, { 229,3208 }, + { 230,3208 }, { 231,3208 }, { 232,3208 }, { 233,3208 }, { 234,3208 }, + { 235,3208 }, { 236,3208 }, { 237,3208 }, { 238,3208 }, { 239,3208 }, + { 240,3208 }, { 241,3208 }, { 242,3208 }, { 243,3208 }, { 244,3208 }, + { 245,3208 }, { 246,3208 }, { 247,3208 }, { 248,3208 }, { 249,3208 }, + { 250,3208 }, { 251,3208 }, { 252,3208 }, { 253,3208 }, { 254,3208 }, + { 255,3208 }, { 256,3208 }, { 0, 0 }, { 0,14334 }, { 1,2950 }, + { 2,2950 }, { 3,2950 }, { 4,2950 }, { 5,2950 }, { 6,2950 }, + + { 7,2950 }, { 8,2950 }, { 9,3208 }, { 10,3208 }, { 11,2950 }, + { 12,3208 }, { 13,3208 }, { 14,2950 }, { 15,2950 }, { 16,2950 }, + { 17,2950 }, { 18,2950 }, { 19,2950 }, { 20,2950 }, { 21,2950 }, + { 22,2950 }, { 23,2950 }, { 24,2950 }, { 25,2950 }, { 26,2950 }, + { 27,2950 }, { 28,2950 }, { 29,2950 }, { 30,2950 }, { 31,2950 }, + { 32,3208 }, { 33,2406 }, { 34,2406 }, { 35,2406 }, { 36,2406 }, + { 37,2406 }, { 38,2406 }, { 39,2950 }, { 40,2406 }, { 41,2406 }, + { 42,2406 }, { 43,2406 }, { 44,2406 }, { 45,2406 }, { 46,2406 }, + { 47,2426 }, { 48,2950 }, { 49,2950 }, { 50,2950 }, { 51,2950 }, + { 52,2950 }, { 53,2950 }, { 54,2950 }, { 55,2950 }, { 56,2950 }, + + { 57,2950 }, { 58,2406 }, { 59,2950 }, { 60,2406 }, { 61,2406 }, + { 62,2406 }, { 63,2406 }, { 64,2406 }, { 65,2950 }, { 66,2950 }, + { 67,2950 }, { 68,2950 }, { 69,2950 }, { 70,2950 }, { 71,2950 }, + { 72,2950 }, { 73,2950 }, { 74,2950 }, { 75,2950 }, { 76,2950 }, + { 77,2950 }, { 78,2950 }, { 79,2950 }, { 80,2950 }, { 81,2950 }, + { 82,2950 }, { 83,2950 }, { 84,2950 }, { 85,2950 }, { 86,2950 }, + { 87,2950 }, { 88,2950 }, { 89,2950 }, { 90,2950 }, { 91,2406 }, + { 92,2692 }, { 93,2406 }, { 94,2950 }, { 95,2950 }, { 96,2950 }, + { 97,2950 }, { 98,2950 }, { 99,2950 }, { 100,2950 }, { 101,2950 }, + { 102,2950 }, { 103,2950 }, { 104,2950 }, { 105,2950 }, { 106,2950 }, + + { 107,2950 }, { 108,2950 }, { 109,2950 }, { 110,2950 }, { 111,2950 }, + { 112,2950 }, { 113,2950 }, { 114,2950 }, { 115,2950 }, { 116,2950 }, + { 117,2950 }, { 118,2950 }, { 119,2950 }, { 120,2950 }, { 121,2950 }, + { 122,2950 }, { 123,2406 }, { 124,2406 }, { 125,2406 }, { 126,2950 }, + { 127,2950 }, { 128,2950 }, { 129,2950 }, { 130,2950 }, { 131,2950 }, + { 132,2950 }, { 133,2950 }, { 134,2950 }, { 135,2950 }, { 136,2950 }, + { 137,2950 }, { 138,2950 }, { 139,2950 }, { 140,2950 }, { 141,2950 }, + { 142,2950 }, { 143,2950 }, { 144,2950 }, { 145,2950 }, { 146,2950 }, + { 147,2950 }, { 148,2950 }, { 149,2950 }, { 150,2950 }, { 151,2950 }, + { 152,2950 }, { 153,2950 }, { 154,2950 }, { 155,2950 }, { 156,2950 }, + + { 157,2950 }, { 158,2950 }, { 159,2950 }, { 160,2950 }, { 161,2950 }, + { 162,2950 }, { 163,2950 }, { 164,2950 }, { 165,2950 }, { 166,2950 }, + { 167,2950 }, { 168,2950 }, { 169,2950 }, { 170,2950 }, { 171,2950 }, + { 172,2950 }, { 173,2950 }, { 174,2950 }, { 175,2950 }, { 176,2950 }, + { 177,2950 }, { 178,2950 }, { 179,2950 }, { 180,2950 }, { 181,2950 }, + { 182,2950 }, { 183,2950 }, { 184,2950 }, { 185,2950 }, { 186,2950 }, + { 187,2950 }, { 188,2950 }, { 189,2950 }, { 190,2950 }, { 191,2950 }, + { 192,2950 }, { 193,2950 }, { 194,2950 }, { 195,2950 }, { 196,2950 }, + { 197,2950 }, { 198,2950 }, { 199,2950 }, { 200,2950 }, { 201,2950 }, + { 202,2950 }, { 203,2950 }, { 204,2950 }, { 205,2950 }, { 206,2950 }, + + { 207,2950 }, { 208,2950 }, { 209,2950 }, { 210,2950 }, { 211,2950 }, + { 212,2950 }, { 213,2950 }, { 214,2950 }, { 215,2950 }, { 216,2950 }, + { 217,2950 }, { 218,2950 }, { 219,2950 }, { 220,2950 }, { 221,2950 }, + { 222,2950 }, { 223,2950 }, { 224,2950 }, { 225,2950 }, { 226,2950 }, + { 227,2950 }, { 228,2950 }, { 229,2950 }, { 230,2950 }, { 231,2950 }, + { 232,2950 }, { 233,2950 }, { 234,2950 }, { 235,2950 }, { 236,2950 }, + { 237,2950 }, { 238,2950 }, { 239,2950 }, { 240,2950 }, { 241,2950 }, + { 242,2950 }, { 243,2950 }, { 244,2950 }, { 245,2950 }, { 246,2950 }, + { 247,2950 }, { 248,2950 }, { 249,2950 }, { 250,2950 }, { 251,2950 }, + { 252,2950 }, { 253,2950 }, { 254,2950 }, { 255,2950 }, { 256,2950 }, + + { 0, 0 }, { 0,14076 }, { 1,2176 }, { 2,2176 }, { 3,2176 }, + { 4,2176 }, { 5,2176 }, { 6,2176 }, { 7,2176 }, { 8,2176 }, + { 9,2176 }, { 10,2176 }, { 11,2176 }, { 12,2176 }, { 13,2176 }, + { 14,2176 }, { 15,2176 }, { 16,2176 }, { 17,2176 }, { 18,2176 }, + { 19,2176 }, { 20,2176 }, { 21,2176 }, { 22,2176 }, { 23,2176 }, + { 24,2176 }, { 25,2176 }, { 26,2176 }, { 27,2176 }, { 28,2176 }, + { 29,2176 }, { 30,2176 }, { 31,2176 }, { 32,2176 }, { 33,2176 }, + { 34,2170 }, { 35,2176 }, { 36,2176 }, { 37,2176 }, { 38,2176 }, + { 39,2176 }, { 40,2176 }, { 41,2176 }, { 42,2176 }, { 43,2176 }, + { 44,2176 }, { 45,2176 }, { 46,2176 }, { 47,2176 }, { 48,2176 }, + + { 49,2176 }, { 50,2176 }, { 51,2176 }, { 52,2176 }, { 53,2176 }, + { 54,2176 }, { 55,2176 }, { 56,2176 }, { 57,2176 }, { 58,2176 }, + { 59,2176 }, { 60,2176 }, { 61,2176 }, { 62,2176 }, { 63,2176 }, + { 64,2176 }, { 65,2176 }, { 66,2176 }, { 67,2176 }, { 68,2176 }, + { 69,2176 }, { 70,2176 }, { 71,2176 }, { 72,2176 }, { 73,2176 }, + { 74,2176 }, { 75,2176 }, { 76,2176 }, { 77,2176 }, { 78,2176 }, + { 79,2176 }, { 80,2176 }, { 81,2176 }, { 82,2176 }, { 83,2176 }, + { 84,2176 }, { 85,2176 }, { 86,2176 }, { 87,2176 }, { 88,2176 }, + { 89,2176 }, { 90,2176 }, { 91,2176 }, { 92,2434 }, { 93,2176 }, + { 94,2176 }, { 95,2176 }, { 96,2176 }, { 97,2176 }, { 98,2176 }, + + { 99,2176 }, { 100,2176 }, { 101,2176 }, { 102,2176 }, { 103,2176 }, + { 104,2176 }, { 105,2176 }, { 106,2176 }, { 107,2176 }, { 108,2176 }, + { 109,2176 }, { 110,2176 }, { 111,2176 }, { 112,2176 }, { 113,2176 }, + { 114,2176 }, { 115,2176 }, { 116,2176 }, { 117,2176 }, { 118,2176 }, + { 119,2176 }, { 120,2176 }, { 121,2176 }, { 122,2176 }, { 123,2176 }, + { 124,2176 }, { 125,2176 }, { 126,2176 }, { 127,2176 }, { 128,2176 }, + { 129,2176 }, { 130,2176 }, { 131,2176 }, { 132,2176 }, { 133,2176 }, + { 134,2176 }, { 135,2176 }, { 136,2176 }, { 137,2176 }, { 138,2176 }, + { 139,2176 }, { 140,2176 }, { 141,2176 }, { 142,2176 }, { 143,2176 }, + { 144,2176 }, { 145,2176 }, { 146,2176 }, { 147,2176 }, { 148,2176 }, + + { 149,2176 }, { 150,2176 }, { 151,2176 }, { 152,2176 }, { 153,2176 }, + { 154,2176 }, { 155,2176 }, { 156,2176 }, { 157,2176 }, { 158,2176 }, + { 159,2176 }, { 160,2176 }, { 161,2176 }, { 162,2176 }, { 163,2176 }, + { 164,2176 }, { 165,2176 }, { 166,2176 }, { 167,2176 }, { 168,2176 }, + { 169,2176 }, { 170,2176 }, { 171,2176 }, { 172,2176 }, { 173,2176 }, + { 174,2176 }, { 175,2176 }, { 176,2176 }, { 177,2176 }, { 178,2176 }, + { 179,2176 }, { 180,2176 }, { 181,2176 }, { 182,2176 }, { 183,2176 }, + { 184,2176 }, { 185,2176 }, { 186,2176 }, { 187,2176 }, { 188,2176 }, + { 189,2176 }, { 190,2176 }, { 191,2176 }, { 192,2176 }, { 193,2176 }, + { 194,2176 }, { 195,2176 }, { 196,2176 }, { 197,2176 }, { 198,2176 }, + + { 199,2176 }, { 200,2176 }, { 201,2176 }, { 202,2176 }, { 203,2176 }, + { 204,2176 }, { 205,2176 }, { 206,2176 }, { 207,2176 }, { 208,2176 }, + { 209,2176 }, { 210,2176 }, { 211,2176 }, { 212,2176 }, { 213,2176 }, + { 214,2176 }, { 215,2176 }, { 216,2176 }, { 217,2176 }, { 218,2176 }, + { 219,2176 }, { 220,2176 }, { 221,2176 }, { 222,2176 }, { 223,2176 }, + { 224,2176 }, { 225,2176 }, { 226,2176 }, { 227,2176 }, { 228,2176 }, + { 229,2176 }, { 230,2176 }, { 231,2176 }, { 232,2176 }, { 233,2176 }, + { 234,2176 }, { 235,2176 }, { 236,2176 }, { 237,2176 }, { 238,2176 }, + { 239,2176 }, { 240,2176 }, { 241,2176 }, { 242,2176 }, { 243,2176 }, + { 244,2176 }, { 245,2176 }, { 246,2176 }, { 247,2176 }, { 248,2176 }, + + { 249,2176 }, { 250,2176 }, { 251,2176 }, { 252,2176 }, { 253,2176 }, + { 254,2176 }, { 255,2176 }, { 256,2176 }, { 0, 0 }, { 0,13818 }, + { 1,1918 }, { 2,1918 }, { 3,1918 }, { 4,1918 }, { 5,1918 }, + { 6,1918 }, { 7,1918 }, { 8,1918 }, { 9,1918 }, { 10,1918 }, + { 11,1918 }, { 12,1918 }, { 13,1918 }, { 14,1918 }, { 15,1918 }, + { 16,1918 }, { 17,1918 }, { 18,1918 }, { 19,1918 }, { 20,1918 }, + { 21,1918 }, { 22,1918 }, { 23,1918 }, { 24,1918 }, { 25,1918 }, + { 26,1918 }, { 27,1918 }, { 28,1918 }, { 29,1918 }, { 30,1918 }, + { 31,1918 }, { 32,1918 }, { 33,1918 }, { 34,1912 }, { 35,1918 }, + { 36,1918 }, { 37,1918 }, { 38,1918 }, { 39,1918 }, { 40,1918 }, + + { 41,1918 }, { 42,1918 }, { 43,1918 }, { 44,1918 }, { 45,1918 }, + { 46,1918 }, { 47,1918 }, { 48,1918 }, { 49,1918 }, { 50,1918 }, + { 51,1918 }, { 52,1918 }, { 53,1918 }, { 54,1918 }, { 55,1918 }, + { 56,1918 }, { 57,1918 }, { 58,1918 }, { 59,1918 }, { 60,1918 }, + { 61,1918 }, { 62,1918 }, { 63,1918 }, { 64,1918 }, { 65,1918 }, + { 66,1918 }, { 67,1918 }, { 68,1918 }, { 69,1918 }, { 70,1918 }, + { 71,1918 }, { 72,1918 }, { 73,1918 }, { 74,1918 }, { 75,1918 }, + { 76,1918 }, { 77,1918 }, { 78,1918 }, { 79,1918 }, { 80,1918 }, + { 81,1918 }, { 82,1918 }, { 83,1918 }, { 84,1918 }, { 85,1918 }, + { 86,1918 }, { 87,1918 }, { 88,1918 }, { 89,1918 }, { 90,1918 }, + + { 91,1918 }, { 92,2176 }, { 93,1918 }, { 94,1918 }, { 95,1918 }, + { 96,1918 }, { 97,1918 }, { 98,1918 }, { 99,1918 }, { 100,1918 }, + { 101,1918 }, { 102,1918 }, { 103,1918 }, { 104,1918 }, { 105,1918 }, + { 106,1918 }, { 107,1918 }, { 108,1918 }, { 109,1918 }, { 110,1918 }, + { 111,1918 }, { 112,1918 }, { 113,1918 }, { 114,1918 }, { 115,1918 }, + { 116,1918 }, { 117,1918 }, { 118,1918 }, { 119,1918 }, { 120,1918 }, + { 121,1918 }, { 122,1918 }, { 123,1918 }, { 124,1918 }, { 125,1918 }, + { 126,1918 }, { 127,1918 }, { 128,1918 }, { 129,1918 }, { 130,1918 }, + { 131,1918 }, { 132,1918 }, { 133,1918 }, { 134,1918 }, { 135,1918 }, + { 136,1918 }, { 137,1918 }, { 138,1918 }, { 139,1918 }, { 140,1918 }, + + { 141,1918 }, { 142,1918 }, { 143,1918 }, { 144,1918 }, { 145,1918 }, + { 146,1918 }, { 147,1918 }, { 148,1918 }, { 149,1918 }, { 150,1918 }, + { 151,1918 }, { 152,1918 }, { 153,1918 }, { 154,1918 }, { 155,1918 }, + { 156,1918 }, { 157,1918 }, { 158,1918 }, { 159,1918 }, { 160,1918 }, + { 161,1918 }, { 162,1918 }, { 163,1918 }, { 164,1918 }, { 165,1918 }, + { 166,1918 }, { 167,1918 }, { 168,1918 }, { 169,1918 }, { 170,1918 }, + { 171,1918 }, { 172,1918 }, { 173,1918 }, { 174,1918 }, { 175,1918 }, + { 176,1918 }, { 177,1918 }, { 178,1918 }, { 179,1918 }, { 180,1918 }, + { 181,1918 }, { 182,1918 }, { 183,1918 }, { 184,1918 }, { 185,1918 }, + { 186,1918 }, { 187,1918 }, { 188,1918 }, { 189,1918 }, { 190,1918 }, + + { 191,1918 }, { 192,1918 }, { 193,1918 }, { 194,1918 }, { 195,1918 }, + { 196,1918 }, { 197,1918 }, { 198,1918 }, { 199,1918 }, { 200,1918 }, + { 201,1918 }, { 202,1918 }, { 203,1918 }, { 204,1918 }, { 205,1918 }, + { 206,1918 }, { 207,1918 }, { 208,1918 }, { 209,1918 }, { 210,1918 }, + { 211,1918 }, { 212,1918 }, { 213,1918 }, { 214,1918 }, { 215,1918 }, + { 216,1918 }, { 217,1918 }, { 218,1918 }, { 219,1918 }, { 220,1918 }, + { 221,1918 }, { 222,1918 }, { 223,1918 }, { 224,1918 }, { 225,1918 }, + { 226,1918 }, { 227,1918 }, { 228,1918 }, { 229,1918 }, { 230,1918 }, + { 231,1918 }, { 232,1918 }, { 233,1918 }, { 234,1918 }, { 235,1918 }, + { 236,1918 }, { 237,1918 }, { 238,1918 }, { 239,1918 }, { 240,1918 }, + + { 241,1918 }, { 242,1918 }, { 243,1918 }, { 244,1918 }, { 245,1918 }, + { 246,1918 }, { 247,1918 }, { 248,1918 }, { 249,1918 }, { 250,1918 }, + { 251,1918 }, { 252,1918 }, { 253,1918 }, { 254,1918 }, { 255,1918 }, + { 256,1918 }, { 0, 0 }, { 0,13560 }, { 1,2468 }, { 2,2468 }, + { 3,2468 }, { 4,2468 }, { 5,2468 }, { 6,2468 }, { 7,2468 }, + { 8,2468 }, { 9,2468 }, { 10,2468 }, { 11,2468 }, { 12,2468 }, + { 13,2468 }, { 14,2468 }, { 15,2468 }, { 16,2468 }, { 17,2468 }, + { 18,2468 }, { 19,2468 }, { 20,2468 }, { 21,2468 }, { 22,2468 }, + { 23,2468 }, { 24,2468 }, { 25,2468 }, { 26,2468 }, { 27,2468 }, + { 28,2468 }, { 29,2468 }, { 30,2468 }, { 31,2468 }, { 32,2468 }, + + { 33,2468 }, { 34,2468 }, { 35,2468 }, { 36,2468 }, { 37,2468 }, + { 38,2468 }, { 39,2468 }, { 40,2468 }, { 41,2468 }, { 42,2189 }, + { 43,2468 }, { 44,2468 }, { 45,2468 }, { 46,2468 }, { 47,2468 }, + { 48,2468 }, { 49,2468 }, { 50,2468 }, { 51,2468 }, { 52,2468 }, + { 53,2468 }, { 54,2468 }, { 55,2468 }, { 56,2468 }, { 57,2468 }, + { 58,2468 }, { 59,2468 }, { 60,2468 }, { 61,2468 }, { 62,2468 }, + { 63,2468 }, { 64,2468 }, { 65,2468 }, { 66,2468 }, { 67,2468 }, + { 68,2468 }, { 69,2468 }, { 70,2468 }, { 71,2468 }, { 72,2468 }, + { 73,2468 }, { 74,2468 }, { 75,2468 }, { 76,2468 }, { 77,2468 }, + { 78,2468 }, { 79,2468 }, { 80,2468 }, { 81,2468 }, { 82,2468 }, + + { 83,2468 }, { 84,2468 }, { 85,2468 }, { 86,2468 }, { 87,2468 }, + { 88,2468 }, { 89,2468 }, { 90,2468 }, { 91,2468 }, { 92,2468 }, + { 93,2468 }, { 94,2468 }, { 95,2468 }, { 96,2468 }, { 97,2468 }, + { 98,2468 }, { 99,2468 }, { 100,2468 }, { 101,2468 }, { 102,2468 }, + { 103,2468 }, { 104,2468 }, { 105,2468 }, { 106,2468 }, { 107,2468 }, + { 108,2468 }, { 109,2468 }, { 110,2468 }, { 111,2468 }, { 112,2468 }, + { 113,2468 }, { 114,2468 }, { 115,2468 }, { 116,2468 }, { 117,2468 }, + { 118,2468 }, { 119,2468 }, { 120,2468 }, { 121,2468 }, { 122,2468 }, + { 123,2468 }, { 124,2468 }, { 125,2468 }, { 126,2468 }, { 127,2468 }, + { 128,2468 }, { 129,2468 }, { 130,2468 }, { 131,2468 }, { 132,2468 }, + + { 133,2468 }, { 134,2468 }, { 135,2468 }, { 136,2468 }, { 137,2468 }, + { 138,2468 }, { 139,2468 }, { 140,2468 }, { 141,2468 }, { 142,2468 }, + { 143,2468 }, { 144,2468 }, { 145,2468 }, { 146,2468 }, { 147,2468 }, + { 148,2468 }, { 149,2468 }, { 150,2468 }, { 151,2468 }, { 152,2468 }, + { 153,2468 }, { 154,2468 }, { 155,2468 }, { 156,2468 }, { 157,2468 }, + { 158,2468 }, { 159,2468 }, { 160,2468 }, { 161,2468 }, { 162,2468 }, + { 163,2468 }, { 164,2468 }, { 165,2468 }, { 166,2468 }, { 167,2468 }, + { 168,2468 }, { 169,2468 }, { 170,2468 }, { 171,2468 }, { 172,2468 }, + { 173,2468 }, { 174,2468 }, { 175,2468 }, { 176,2468 }, { 177,2468 }, + { 178,2468 }, { 179,2468 }, { 180,2468 }, { 181,2468 }, { 182,2468 }, + + { 183,2468 }, { 184,2468 }, { 185,2468 }, { 186,2468 }, { 187,2468 }, + { 188,2468 }, { 189,2468 }, { 190,2468 }, { 191,2468 }, { 192,2468 }, + { 193,2468 }, { 194,2468 }, { 195,2468 }, { 196,2468 }, { 197,2468 }, + { 198,2468 }, { 199,2468 }, { 200,2468 }, { 201,2468 }, { 202,2468 }, + { 203,2468 }, { 204,2468 }, { 205,2468 }, { 206,2468 }, { 207,2468 }, + { 208,2468 }, { 209,2468 }, { 210,2468 }, { 211,2468 }, { 212,2468 }, + { 213,2468 }, { 214,2468 }, { 215,2468 }, { 216,2468 }, { 217,2468 }, + { 218,2468 }, { 219,2468 }, { 220,2468 }, { 221,2468 }, { 222,2468 }, + { 223,2468 }, { 224,2468 }, { 225,2468 }, { 226,2468 }, { 227,2468 }, + { 228,2468 }, { 229,2468 }, { 230,2468 }, { 231,2468 }, { 232,2468 }, + + { 233,2468 }, { 234,2468 }, { 235,2468 }, { 236,2468 }, { 237,2468 }, + { 238,2468 }, { 239,2468 }, { 240,2468 }, { 241,2468 }, { 242,2468 }, + { 243,2468 }, { 244,2468 }, { 245,2468 }, { 246,2468 }, { 247,2468 }, + { 248,2468 }, { 249,2468 }, { 250,2468 }, { 251,2468 }, { 252,2468 }, + { 253,2468 }, { 254,2468 }, { 255,2468 }, { 256,2468 }, { 0, 0 }, + { 0,13302 }, { 1,2210 }, { 2,2210 }, { 3,2210 }, { 4,2210 }, + { 5,2210 }, { 6,2210 }, { 7,2210 }, { 8,2210 }, { 9,2210 }, + { 10,2210 }, { 11,2210 }, { 12,2210 }, { 13,2210 }, { 14,2210 }, + { 15,2210 }, { 16,2210 }, { 17,2210 }, { 18,2210 }, { 19,2210 }, + { 20,2210 }, { 21,2210 }, { 22,2210 }, { 23,2210 }, { 24,2210 }, + + { 25,2210 }, { 26,2210 }, { 27,2210 }, { 28,2210 }, { 29,2210 }, + { 30,2210 }, { 31,2210 }, { 32,2210 }, { 33,2210 }, { 34,2210 }, + { 35,2210 }, { 36,2210 }, { 37,2210 }, { 38,2210 }, { 39,2210 }, + { 40,2210 }, { 41,2210 }, { 42,1931 }, { 43,2210 }, { 44,2210 }, + { 45,2210 }, { 46,2210 }, { 47,2210 }, { 48,2210 }, { 49,2210 }, + { 50,2210 }, { 51,2210 }, { 52,2210 }, { 53,2210 }, { 54,2210 }, + { 55,2210 }, { 56,2210 }, { 57,2210 }, { 58,2210 }, { 59,2210 }, + { 60,2210 }, { 61,2210 }, { 62,2210 }, { 63,2210 }, { 64,2210 }, + { 65,2210 }, { 66,2210 }, { 67,2210 }, { 68,2210 }, { 69,2210 }, + { 70,2210 }, { 71,2210 }, { 72,2210 }, { 73,2210 }, { 74,2210 }, + + { 75,2210 }, { 76,2210 }, { 77,2210 }, { 78,2210 }, { 79,2210 }, + { 80,2210 }, { 81,2210 }, { 82,2210 }, { 83,2210 }, { 84,2210 }, + { 85,2210 }, { 86,2210 }, { 87,2210 }, { 88,2210 }, { 89,2210 }, + { 90,2210 }, { 91,2210 }, { 92,2210 }, { 93,2210 }, { 94,2210 }, + { 95,2210 }, { 96,2210 }, { 97,2210 }, { 98,2210 }, { 99,2210 }, + { 100,2210 }, { 101,2210 }, { 102,2210 }, { 103,2210 }, { 104,2210 }, + { 105,2210 }, { 106,2210 }, { 107,2210 }, { 108,2210 }, { 109,2210 }, + { 110,2210 }, { 111,2210 }, { 112,2210 }, { 113,2210 }, { 114,2210 }, + { 115,2210 }, { 116,2210 }, { 117,2210 }, { 118,2210 }, { 119,2210 }, + { 120,2210 }, { 121,2210 }, { 122,2210 }, { 123,2210 }, { 124,2210 }, + + { 125,2210 }, { 126,2210 }, { 127,2210 }, { 128,2210 }, { 129,2210 }, + { 130,2210 }, { 131,2210 }, { 132,2210 }, { 133,2210 }, { 134,2210 }, + { 135,2210 }, { 136,2210 }, { 137,2210 }, { 138,2210 }, { 139,2210 }, + { 140,2210 }, { 141,2210 }, { 142,2210 }, { 143,2210 }, { 144,2210 }, + { 145,2210 }, { 146,2210 }, { 147,2210 }, { 148,2210 }, { 149,2210 }, + { 150,2210 }, { 151,2210 }, { 152,2210 }, { 153,2210 }, { 154,2210 }, + { 155,2210 }, { 156,2210 }, { 157,2210 }, { 158,2210 }, { 159,2210 }, + { 160,2210 }, { 161,2210 }, { 162,2210 }, { 163,2210 }, { 164,2210 }, + { 165,2210 }, { 166,2210 }, { 167,2210 }, { 168,2210 }, { 169,2210 }, + { 170,2210 }, { 171,2210 }, { 172,2210 }, { 173,2210 }, { 174,2210 }, + + { 175,2210 }, { 176,2210 }, { 177,2210 }, { 178,2210 }, { 179,2210 }, + { 180,2210 }, { 181,2210 }, { 182,2210 }, { 183,2210 }, { 184,2210 }, + { 185,2210 }, { 186,2210 }, { 187,2210 }, { 188,2210 }, { 189,2210 }, + { 190,2210 }, { 191,2210 }, { 192,2210 }, { 193,2210 }, { 194,2210 }, + { 195,2210 }, { 196,2210 }, { 197,2210 }, { 198,2210 }, { 199,2210 }, + { 200,2210 }, { 201,2210 }, { 202,2210 }, { 203,2210 }, { 204,2210 }, + { 205,2210 }, { 206,2210 }, { 207,2210 }, { 208,2210 }, { 209,2210 }, + { 210,2210 }, { 211,2210 }, { 212,2210 }, { 213,2210 }, { 214,2210 }, + { 215,2210 }, { 216,2210 }, { 217,2210 }, { 218,2210 }, { 219,2210 }, + { 220,2210 }, { 221,2210 }, { 222,2210 }, { 223,2210 }, { 224,2210 }, + + { 225,2210 }, { 226,2210 }, { 227,2210 }, { 228,2210 }, { 229,2210 }, + { 230,2210 }, { 231,2210 }, { 232,2210 }, { 233,2210 }, { 234,2210 }, + { 235,2210 }, { 236,2210 }, { 237,2210 }, { 238,2210 }, { 239,2210 }, + { 240,2210 }, { 241,2210 }, { 242,2210 }, { 243,2210 }, { 244,2210 }, + { 245,2210 }, { 246,2210 }, { 247,2210 }, { 248,2210 }, { 249,2210 }, + { 250,2210 }, { 251,2210 }, { 252,2210 }, { 253,2210 }, { 254,2210 }, + { 255,2210 }, { 256,2210 }, { 0, 52 }, { 0,13044 }, { 1,2210 }, + { 2,2210 }, { 3,2210 }, { 4,2210 }, { 5,2210 }, { 6,2210 }, + { 7,2210 }, { 8,2210 }, { 0, 0 }, { 0, 0 }, { 11,2210 }, + { 0, 0 }, { 0, 0 }, { 14,2210 }, { 15,2210 }, { 16,2210 }, + + { 17,2210 }, { 18,2210 }, { 19,2210 }, { 20,2210 }, { 21,2210 }, + { 22,2210 }, { 23,2210 }, { 24,2210 }, { 25,2210 }, { 26,2210 }, + { 27,2210 }, { 28,2210 }, { 29,2210 }, { 30,2210 }, { 31,2210 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 39,2210 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 48,2210 }, { 49,2210 }, { 50,2210 }, { 51,2210 }, + { 52,2210 }, { 53,2210 }, { 54,2210 }, { 55,2210 }, { 56,2210 }, + { 57,2210 }, { 0, 0 }, { 59,2210 }, { 0, 0 }, { 0, 26 }, + { 0,12982 }, { 0, 50 }, { 0,12980 }, { 65,2210 }, { 66,2210 }, + + { 67,2210 }, { 68,2210 }, { 69,2210 }, { 70,2210 }, { 71,2210 }, + { 72,2210 }, { 73,2210 }, { 74,2210 }, { 75,2210 }, { 76,2210 }, + { 77,2210 }, { 78,2210 }, { 79,2210 }, { 80,2210 }, { 81,2210 }, + { 82,2210 }, { 83,2210 }, { 84,2210 }, { 85,2210 }, { 86,2210 }, + { 87,2210 }, { 88,2210 }, { 89,2210 }, { 90,2210 }, { 0, 37 }, + { 0,12952 }, { 0, 0 }, { 94,2210 }, { 95,2210 }, { 96,2210 }, + { 97,2210 }, { 98,2210 }, { 99,2210 }, { 100,2210 }, { 101,2210 }, + { 102,2210 }, { 103,2210 }, { 104,2210 }, { 105,2210 }, { 106,2210 }, + { 107,2210 }, { 108,2210 }, { 109,2210 }, { 110,2210 }, { 111,2210 }, + { 112,2210 }, { 113,2210 }, { 114,2210 }, { 115,2210 }, { 116,2210 }, + + { 117,2210 }, { 118,2210 }, { 119,2210 }, { 120,2210 }, { 121,2210 }, + { 122,2210 }, { 61,1631 }, { 0, 0 }, { 0, 0 }, { 126,2210 }, + { 127,2210 }, { 128,2210 }, { 129,2210 }, { 130,2210 }, { 131,2210 }, + { 132,2210 }, { 133,2210 }, { 134,2210 }, { 135,2210 }, { 136,2210 }, + { 137,2210 }, { 138,2210 }, { 139,2210 }, { 140,2210 }, { 141,2210 }, + { 142,2210 }, { 143,2210 }, { 144,2210 }, { 145,2210 }, { 146,2210 }, + { 147,2210 }, { 148,2210 }, { 149,2210 }, { 150,2210 }, { 151,2210 }, + { 152,2210 }, { 153,2210 }, { 154,2210 }, { 155,2210 }, { 156,2210 }, + { 157,2210 }, { 158,2210 }, { 159,2210 }, { 160,2210 }, { 161,2210 }, + { 162,2210 }, { 163,2210 }, { 164,2210 }, { 165,2210 }, { 166,2210 }, + + { 167,2210 }, { 168,2210 }, { 169,2210 }, { 170,2210 }, { 171,2210 }, + { 172,2210 }, { 173,2210 }, { 174,2210 }, { 175,2210 }, { 176,2210 }, + { 177,2210 }, { 178,2210 }, { 179,2210 }, { 180,2210 }, { 181,2210 }, + { 182,2210 }, { 183,2210 }, { 184,2210 }, { 185,2210 }, { 186,2210 }, + { 187,2210 }, { 188,2210 }, { 189,2210 }, { 190,2210 }, { 191,2210 }, + { 192,2210 }, { 193,2210 }, { 194,2210 }, { 195,2210 }, { 196,2210 }, + { 197,2210 }, { 198,2210 }, { 199,2210 }, { 200,2210 }, { 201,2210 }, + { 202,2210 }, { 203,2210 }, { 204,2210 }, { 205,2210 }, { 206,2210 }, + { 207,2210 }, { 208,2210 }, { 209,2210 }, { 210,2210 }, { 211,2210 }, + { 212,2210 }, { 213,2210 }, { 214,2210 }, { 215,2210 }, { 216,2210 }, + + { 217,2210 }, { 218,2210 }, { 219,2210 }, { 220,2210 }, { 221,2210 }, + { 222,2210 }, { 223,2210 }, { 224,2210 }, { 225,2210 }, { 226,2210 }, + { 227,2210 }, { 228,2210 }, { 229,2210 }, { 230,2210 }, { 231,2210 }, + { 232,2210 }, { 233,2210 }, { 234,2210 }, { 235,2210 }, { 236,2210 }, + { 237,2210 }, { 238,2210 }, { 239,2210 }, { 240,2210 }, { 241,2210 }, + { 242,2210 }, { 243,2210 }, { 244,2210 }, { 245,2210 }, { 246,2210 }, + { 247,2210 }, { 248,2210 }, { 249,2210 }, { 250,2210 }, { 251,2210 }, + { 252,2210 }, { 253,2210 }, { 254,2210 }, { 255,2210 }, { 256,2210 }, + { 0, 38 }, { 0,12786 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + + { 9,2210 }, { 10,2210 }, { 0, 0 }, { 12,2210 }, { 13,2210 }, + { 0, 0 }, { 0, 37 }, { 0,12770 }, { 0, 0 }, { 0, 37 }, + { 0,12767 }, { 1,2210 }, { 2,2210 }, { 3,2210 }, { 4,2210 }, + { 5,2210 }, { 6,2210 }, { 7,2210 }, { 8,2210 }, { 0, 0 }, + { 0, 0 }, { 11,2210 }, { 0, 0 }, { 32,2210 }, { 14,2210 }, + { 15,2210 }, { 16,2210 }, { 17,2210 }, { 18,2210 }, { 19,2210 }, + { 20,2210 }, { 21,2210 }, { 22,2210 }, { 23,2210 }, { 24,2210 }, + { 25,2210 }, { 26,2210 }, { 27,2210 }, { 28,2210 }, { 29,2210 }, + { 30,2210 }, { 31,2210 }, { 0, 0 }, { 0, 0 }, { 34,1418 }, + { 38,1423 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 39,2210 }, + + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 48,2210 }, { 49,2210 }, + { 50,2210 }, { 51,2210 }, { 52,2210 }, { 53,2210 }, { 54,2210 }, + { 55,2210 }, { 56,2210 }, { 57,2210 }, { 0, 0 }, { 59,2210 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 65,2210 }, { 66,2210 }, { 67,2210 }, { 68,2210 }, { 69,2210 }, + { 70,2210 }, { 71,2210 }, { 72,2210 }, { 73,2210 }, { 74,2210 }, + { 75,2210 }, { 76,2210 }, { 77,2210 }, { 78,2210 }, { 79,2210 }, + { 80,2210 }, { 81,2210 }, { 82,2210 }, { 83,2210 }, { 84,2210 }, + { 85,2210 }, { 86,2210 }, { 87,2210 }, { 88,2210 }, { 89,2210 }, + + { 90,2210 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 94,2210 }, + { 95,2210 }, { 96,2210 }, { 97,2210 }, { 98,2210 }, { 99,2210 }, + { 100,2210 }, { 101,2210 }, { 102,2210 }, { 103,2210 }, { 104,2210 }, + { 105,2210 }, { 106,2210 }, { 107,2210 }, { 108,2210 }, { 109,2210 }, + { 110,2210 }, { 111,2210 }, { 112,2210 }, { 113,2210 }, { 114,2210 }, + { 115,2210 }, { 116,2210 }, { 117,2210 }, { 118,2210 }, { 119,2210 }, + { 120,2210 }, { 121,2210 }, { 122,2210 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 126,2210 }, { 127,2210 }, { 128,2210 }, { 129,2210 }, + { 130,2210 }, { 131,2210 }, { 132,2210 }, { 133,2210 }, { 134,2210 }, + { 135,2210 }, { 136,2210 }, { 137,2210 }, { 138,2210 }, { 139,2210 }, + + { 140,2210 }, { 141,2210 }, { 142,2210 }, { 143,2210 }, { 144,2210 }, + { 145,2210 }, { 146,2210 }, { 147,2210 }, { 148,2210 }, { 149,2210 }, + { 150,2210 }, { 151,2210 }, { 152,2210 }, { 153,2210 }, { 154,2210 }, + { 155,2210 }, { 156,2210 }, { 157,2210 }, { 158,2210 }, { 159,2210 }, + { 160,2210 }, { 161,2210 }, { 162,2210 }, { 163,2210 }, { 164,2210 }, + { 165,2210 }, { 166,2210 }, { 167,2210 }, { 168,2210 }, { 169,2210 }, + { 170,2210 }, { 171,2210 }, { 172,2210 }, { 173,2210 }, { 174,2210 }, + { 175,2210 }, { 176,2210 }, { 177,2210 }, { 178,2210 }, { 179,2210 }, + { 180,2210 }, { 181,2210 }, { 182,2210 }, { 183,2210 }, { 184,2210 }, + { 185,2210 }, { 186,2210 }, { 187,2210 }, { 188,2210 }, { 189,2210 }, + + { 190,2210 }, { 191,2210 }, { 192,2210 }, { 193,2210 }, { 194,2210 }, + { 195,2210 }, { 196,2210 }, { 197,2210 }, { 198,2210 }, { 199,2210 }, + { 200,2210 }, { 201,2210 }, { 202,2210 }, { 203,2210 }, { 204,2210 }, + { 205,2210 }, { 206,2210 }, { 207,2210 }, { 208,2210 }, { 209,2210 }, + { 210,2210 }, { 211,2210 }, { 212,2210 }, { 213,2210 }, { 214,2210 }, + { 215,2210 }, { 216,2210 }, { 217,2210 }, { 218,2210 }, { 219,2210 }, + { 220,2210 }, { 221,2210 }, { 222,2210 }, { 223,2210 }, { 224,2210 }, + { 225,2210 }, { 226,2210 }, { 227,2210 }, { 228,2210 }, { 229,2210 }, + { 230,2210 }, { 231,2210 }, { 232,2210 }, { 233,2210 }, { 234,2210 }, + { 235,2210 }, { 236,2210 }, { 237,2210 }, { 238,2210 }, { 239,2210 }, + + { 240,2210 }, { 241,2210 }, { 242,2210 }, { 243,2210 }, { 244,2210 }, + { 245,2210 }, { 246,2210 }, { 247,2210 }, { 248,2210 }, { 249,2210 }, + { 250,2210 }, { 251,2210 }, { 252,2210 }, { 253,2210 }, { 254,2210 }, + { 255,2210 }, { 256,2210 }, { 0, 37 }, { 0,12509 }, { 0, 37 }, + { 0,12507 }, { 0, 37 }, { 0,12505 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 28 }, { 0,12500 }, { 0, 0 }, { 0, 37 }, + { 0,12497 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 34 }, + + { 0,12477 }, { 0, 51 }, { 0,12475 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 42,1166 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 42,1164 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 48,2208 }, { 49,2208 }, + { 50,2208 }, { 51,2208 }, { 52,2208 }, { 53,2208 }, { 54,2208 }, + { 55,2208 }, { 56,2208 }, { 57,2208 }, { 0, 42 }, { 0,12448 }, + { 1,2407 }, { 2,2407 }, { 3,2407 }, { 4,2407 }, { 5,2407 }, + { 6,2407 }, { 7,2407 }, { 8,2407 }, { 61,1161 }, { 62,1163 }, + { 11,2407 }, { 61,1175 }, { 0, 0 }, { 14,2407 }, { 15,2407 }, + { 16,2407 }, { 17,2407 }, { 18,2407 }, { 19,2407 }, { 20,2407 }, + + { 21,2407 }, { 22,2407 }, { 23,2407 }, { 24,2407 }, { 25,2407 }, + { 26,2407 }, { 27,2407 }, { 28,2407 }, { 29,2407 }, { 30,2407 }, + { 31,2407 }, { 61,1157 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 39,2407 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 46,2665 }, { 0, 0 }, { 48,2407 }, { 49,2407 }, { 50,2407 }, + { 51,2407 }, { 52,2407 }, { 53,2407 }, { 54,2407 }, { 55,2407 }, + { 56,2407 }, { 57,2407 }, { 0, 0 }, { 59,2407 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 65,2407 }, + { 66,2923 }, { 67,2407 }, { 68,2407 }, { 69,3181 }, { 70,2407 }, + + { 71,2407 }, { 72,2407 }, { 73,2407 }, { 74,2407 }, { 75,2407 }, + { 76,2407 }, { 77,2407 }, { 78,2407 }, { 79,3439 }, { 80,2407 }, + { 81,2407 }, { 82,2407 }, { 83,2407 }, { 84,2407 }, { 85,2407 }, + { 86,2407 }, { 87,2407 }, { 88,3697 }, { 89,2407 }, { 90,2407 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 94,2407 }, { 95,2407 }, + { 96,2407 }, { 97,2407 }, { 98,2923 }, { 99,2407 }, { 100,2407 }, + { 101,3181 }, { 102,2407 }, { 103,2407 }, { 104,2407 }, { 105,2407 }, + { 106,2407 }, { 107,2407 }, { 108,2407 }, { 109,2407 }, { 110,2407 }, + { 111,3439 }, { 112,2407 }, { 113,2407 }, { 114,2407 }, { 115,2407 }, + { 116,2407 }, { 117,2407 }, { 118,2407 }, { 119,2407 }, { 120,3697 }, + + { 121,2407 }, { 122,2407 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 126,2407 }, { 127,2407 }, { 128,2407 }, { 129,2407 }, { 130,2407 }, + { 131,2407 }, { 132,2407 }, { 133,2407 }, { 134,2407 }, { 135,2407 }, + { 136,2407 }, { 137,2407 }, { 138,2407 }, { 139,2407 }, { 140,2407 }, + { 141,2407 }, { 142,2407 }, { 143,2407 }, { 144,2407 }, { 145,2407 }, + { 146,2407 }, { 147,2407 }, { 148,2407 }, { 149,2407 }, { 150,2407 }, + { 151,2407 }, { 152,2407 }, { 153,2407 }, { 154,2407 }, { 155,2407 }, + { 156,2407 }, { 157,2407 }, { 158,2407 }, { 159,2407 }, { 160,2407 }, + { 161,2407 }, { 162,2407 }, { 163,2407 }, { 164,2407 }, { 165,2407 }, + { 166,2407 }, { 167,2407 }, { 168,2407 }, { 169,2407 }, { 170,2407 }, + + { 171,2407 }, { 172,2407 }, { 173,2407 }, { 174,2407 }, { 175,2407 }, + { 176,2407 }, { 177,2407 }, { 178,2407 }, { 179,2407 }, { 180,2407 }, + { 181,2407 }, { 182,2407 }, { 183,2407 }, { 184,2407 }, { 185,2407 }, + { 186,2407 }, { 187,2407 }, { 188,2407 }, { 189,2407 }, { 190,2407 }, + { 191,2407 }, { 192,2407 }, { 193,2407 }, { 194,2407 }, { 195,2407 }, + { 196,2407 }, { 197,2407 }, { 198,2407 }, { 199,2407 }, { 200,2407 }, + { 201,2407 }, { 202,2407 }, { 203,2407 }, { 204,2407 }, { 205,2407 }, + { 206,2407 }, { 207,2407 }, { 208,2407 }, { 209,2407 }, { 210,2407 }, + { 211,2407 }, { 212,2407 }, { 213,2407 }, { 214,2407 }, { 215,2407 }, + { 216,2407 }, { 217,2407 }, { 218,2407 }, { 219,2407 }, { 220,2407 }, + + { 221,2407 }, { 222,2407 }, { 223,2407 }, { 224,2407 }, { 225,2407 }, + { 226,2407 }, { 227,2407 }, { 228,2407 }, { 229,2407 }, { 230,2407 }, + { 231,2407 }, { 232,2407 }, { 233,2407 }, { 234,2407 }, { 235,2407 }, + { 236,2407 }, { 237,2407 }, { 238,2407 }, { 239,2407 }, { 240,2407 }, + { 241,2407 }, { 242,2407 }, { 243,2407 }, { 244,2407 }, { 245,2407 }, + { 246,2407 }, { 247,2407 }, { 248,2407 }, { 249,2407 }, { 250,2407 }, + { 251,2407 }, { 252,2407 }, { 253,2407 }, { 254,2407 }, { 255,2407 }, + { 256,2407 }, { 0, 42 }, { 0,12190 }, { 1,2149 }, { 2,2149 }, + { 3,2149 }, { 4,2149 }, { 5,2149 }, { 6,2149 }, { 7,2149 }, + { 8,2149 }, { 0, 0 }, { 0, 0 }, { 11,2149 }, { 0, 0 }, + + { 0, 0 }, { 14,2149 }, { 15,2149 }, { 16,2149 }, { 17,2149 }, + { 18,2149 }, { 19,2149 }, { 20,2149 }, { 21,2149 }, { 22,2149 }, + { 23,2149 }, { 24,2149 }, { 25,2149 }, { 26,2149 }, { 27,2149 }, + { 28,2149 }, { 29,2149 }, { 30,2149 }, { 31,2149 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 39,2149 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 46,2407 }, { 0, 0 }, + { 48,3697 }, { 49,3697 }, { 50,3697 }, { 51,3697 }, { 52,3697 }, + { 53,3697 }, { 54,3697 }, { 55,3697 }, { 56,3697 }, { 57,3697 }, + { 0, 0 }, { 59,2149 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + + { 0, 0 }, { 0, 0 }, { 65,2149 }, { 66,2149 }, { 67,2149 }, + { 68,2149 }, { 69,2923 }, { 70,2149 }, { 71,2149 }, { 72,2149 }, + { 73,2149 }, { 74,2149 }, { 75,2149 }, { 76,2149 }, { 77,2149 }, + { 78,2149 }, { 79,2149 }, { 80,2149 }, { 81,2149 }, { 82,2149 }, + { 83,2149 }, { 84,2149 }, { 85,2149 }, { 86,2149 }, { 87,2149 }, + { 88,2149 }, { 89,2149 }, { 90,2149 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 94,2149 }, { 95,3955 }, { 96,2149 }, { 97,2149 }, + { 98,2149 }, { 99,2149 }, { 100,2149 }, { 101,2923 }, { 102,2149 }, + { 103,2149 }, { 104,2149 }, { 105,2149 }, { 106,2149 }, { 107,2149 }, + { 108,2149 }, { 109,2149 }, { 110,2149 }, { 111,2149 }, { 112,2149 }, + + { 113,2149 }, { 114,2149 }, { 115,2149 }, { 116,2149 }, { 117,2149 }, + { 118,2149 }, { 119,2149 }, { 120,2149 }, { 121,2149 }, { 122,2149 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 126,2149 }, { 127,2149 }, + { 128,2149 }, { 129,2149 }, { 130,2149 }, { 131,2149 }, { 132,2149 }, + { 133,2149 }, { 134,2149 }, { 135,2149 }, { 136,2149 }, { 137,2149 }, + { 138,2149 }, { 139,2149 }, { 140,2149 }, { 141,2149 }, { 142,2149 }, + { 143,2149 }, { 144,2149 }, { 145,2149 }, { 146,2149 }, { 147,2149 }, + { 148,2149 }, { 149,2149 }, { 150,2149 }, { 151,2149 }, { 152,2149 }, + { 153,2149 }, { 154,2149 }, { 155,2149 }, { 156,2149 }, { 157,2149 }, + { 158,2149 }, { 159,2149 }, { 160,2149 }, { 161,2149 }, { 162,2149 }, + + { 163,2149 }, { 164,2149 }, { 165,2149 }, { 166,2149 }, { 167,2149 }, + { 168,2149 }, { 169,2149 }, { 170,2149 }, { 171,2149 }, { 172,2149 }, + { 173,2149 }, { 174,2149 }, { 175,2149 }, { 176,2149 }, { 177,2149 }, + { 178,2149 }, { 179,2149 }, { 180,2149 }, { 181,2149 }, { 182,2149 }, + { 183,2149 }, { 184,2149 }, { 185,2149 }, { 186,2149 }, { 187,2149 }, + { 188,2149 }, { 189,2149 }, { 190,2149 }, { 191,2149 }, { 192,2149 }, + { 193,2149 }, { 194,2149 }, { 195,2149 }, { 196,2149 }, { 197,2149 }, + { 198,2149 }, { 199,2149 }, { 200,2149 }, { 201,2149 }, { 202,2149 }, + { 203,2149 }, { 204,2149 }, { 205,2149 }, { 206,2149 }, { 207,2149 }, + { 208,2149 }, { 209,2149 }, { 210,2149 }, { 211,2149 }, { 212,2149 }, + + { 213,2149 }, { 214,2149 }, { 215,2149 }, { 216,2149 }, { 217,2149 }, + { 218,2149 }, { 219,2149 }, { 220,2149 }, { 221,2149 }, { 222,2149 }, + { 223,2149 }, { 224,2149 }, { 225,2149 }, { 226,2149 }, { 227,2149 }, + { 228,2149 }, { 229,2149 }, { 230,2149 }, { 231,2149 }, { 232,2149 }, + { 233,2149 }, { 234,2149 }, { 235,2149 }, { 236,2149 }, { 237,2149 }, + { 238,2149 }, { 239,2149 }, { 240,2149 }, { 241,2149 }, { 242,2149 }, + { 243,2149 }, { 244,2149 }, { 245,2149 }, { 246,2149 }, { 247,2149 }, + { 248,2149 }, { 249,2149 }, { 250,2149 }, { 251,2149 }, { 252,2149 }, + { 253,2149 }, { 254,2149 }, { 255,2149 }, { 256,2149 }, { 0, 37 }, + { 0,11932 }, { 0, 18 }, { 0,11930 }, { 0, 4 }, { 0,11928 }, + + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 4 }, { 0,11908 }, + { 0, 19 }, { 0,11906 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 20 }, { 0,11900 }, { 1,3923 }, { 2,3923 }, + { 3,3923 }, { 4,3923 }, { 5,3923 }, { 6,3923 }, { 7,3923 }, + { 8,3923 }, { 9,3923 }, { 10,3923 }, { 11,3923 }, { 12,3923 }, + { 13,3923 }, { 14,3923 }, { 15,3923 }, { 16,3923 }, { 17,3923 }, + { 18,3923 }, { 19,3923 }, { 20,3923 }, { 21,3923 }, { 22,3923 }, + + { 23,3923 }, { 24,3923 }, { 25,3923 }, { 26,3923 }, { 27,3923 }, + { 28,3923 }, { 29,3923 }, { 30,3923 }, { 31,3923 }, { 32,3923 }, + { 33,3923 }, { 42, 801 }, { 35,3923 }, { 36,3923 }, { 37,3923 }, + { 38,3923 }, { 39,3923 }, { 40,3923 }, { 41,3923 }, { 42,3923 }, + { 43,3923 }, { 44,3923 }, { 45,3923 }, { 46,3923 }, { 47,3923 }, + { 48,3923 }, { 49,3923 }, { 50,3923 }, { 51,3923 }, { 52,3923 }, + { 53,3923 }, { 54,3923 }, { 55,3923 }, { 56,3923 }, { 57,3923 }, + { 58,3923 }, { 59,3923 }, { 60,3923 }, { 61,3923 }, { 62,3923 }, + { 63,3923 }, { 64,3923 }, { 65,3923 }, { 66,3923 }, { 67,3923 }, + { 68,3923 }, { 69,3923 }, { 70,3923 }, { 71,3923 }, { 72,3923 }, + + { 73,3923 }, { 74,3923 }, { 75,3923 }, { 76,3923 }, { 77,3923 }, + { 78,3923 }, { 79,3923 }, { 80,3923 }, { 81,3923 }, { 82,3923 }, + { 83,3923 }, { 84,3923 }, { 85,3923 }, { 86,3923 }, { 87,3923 }, + { 88,3923 }, { 89,3923 }, { 90,3923 }, { 91,3923 }, { 124, 640 }, + { 93,3923 }, { 94,3923 }, { 95,3923 }, { 96,3923 }, { 97,3923 }, + { 98,3923 }, { 99,3923 }, { 100,3923 }, { 101,3923 }, { 102,3923 }, + { 103,3923 }, { 104,3923 }, { 105,3923 }, { 106,3923 }, { 107,3923 }, + { 108,3923 }, { 109,3923 }, { 110,3923 }, { 111,3923 }, { 112,3923 }, + { 113,3923 }, { 114,3923 }, { 115,3923 }, { 116,3923 }, { 117,3923 }, + { 118,3923 }, { 119,3923 }, { 120,3923 }, { 121,3923 }, { 122,3923 }, + + { 123,3923 }, { 124,3923 }, { 125,3923 }, { 126,3923 }, { 127,3923 }, + { 128,3923 }, { 129,3923 }, { 130,3923 }, { 131,3923 }, { 132,3923 }, + { 133,3923 }, { 134,3923 }, { 135,3923 }, { 136,3923 }, { 137,3923 }, + { 138,3923 }, { 139,3923 }, { 140,3923 }, { 141,3923 }, { 142,3923 }, + { 143,3923 }, { 144,3923 }, { 145,3923 }, { 146,3923 }, { 147,3923 }, + { 148,3923 }, { 149,3923 }, { 150,3923 }, { 151,3923 }, { 152,3923 }, + { 153,3923 }, { 154,3923 }, { 155,3923 }, { 156,3923 }, { 157,3923 }, + { 158,3923 }, { 159,3923 }, { 160,3923 }, { 161,3923 }, { 162,3923 }, + { 163,3923 }, { 164,3923 }, { 165,3923 }, { 166,3923 }, { 167,3923 }, + { 168,3923 }, { 169,3923 }, { 170,3923 }, { 171,3923 }, { 172,3923 }, + + { 173,3923 }, { 174,3923 }, { 175,3923 }, { 176,3923 }, { 177,3923 }, + { 178,3923 }, { 179,3923 }, { 180,3923 }, { 181,3923 }, { 182,3923 }, + { 183,3923 }, { 184,3923 }, { 185,3923 }, { 186,3923 }, { 187,3923 }, + { 188,3923 }, { 189,3923 }, { 190,3923 }, { 191,3923 }, { 192,3923 }, + { 193,3923 }, { 194,3923 }, { 195,3923 }, { 196,3923 }, { 197,3923 }, + { 198,3923 }, { 199,3923 }, { 200,3923 }, { 201,3923 }, { 202,3923 }, + { 203,3923 }, { 204,3923 }, { 205,3923 }, { 206,3923 }, { 207,3923 }, + { 208,3923 }, { 209,3923 }, { 210,3923 }, { 211,3923 }, { 212,3923 }, + { 213,3923 }, { 214,3923 }, { 215,3923 }, { 216,3923 }, { 217,3923 }, + { 218,3923 }, { 219,3923 }, { 220,3923 }, { 221,3923 }, { 222,3923 }, + + { 223,3923 }, { 224,3923 }, { 225,3923 }, { 226,3923 }, { 227,3923 }, + { 228,3923 }, { 229,3923 }, { 230,3923 }, { 231,3923 }, { 232,3923 }, + { 233,3923 }, { 234,3923 }, { 235,3923 }, { 236,3923 }, { 237,3923 }, + { 238,3923 }, { 239,3923 }, { 240,3923 }, { 241,3923 }, { 242,3923 }, + { 243,3923 }, { 244,3923 }, { 245,3923 }, { 246,3923 }, { 247,3923 }, + { 248,3923 }, { 249,3923 }, { 250,3923 }, { 251,3923 }, { 252,3923 }, + { 253,3923 }, { 254,3923 }, { 255,3923 }, { 256,3923 }, { 0, 17 }, + { 0,11642 }, { 1, 382 }, { 2, 382 }, { 3, 382 }, { 4, 382 }, + { 5, 382 }, { 6, 382 }, { 7, 382 }, { 8, 382 }, { 9, 382 }, + { 0, 0 }, { 11, 382 }, { 12, 382 }, { 13, 382 }, { 14, 382 }, + + { 15, 382 }, { 16, 382 }, { 17, 382 }, { 18, 382 }, { 19, 382 }, + { 20, 382 }, { 21, 382 }, { 22, 382 }, { 23, 382 }, { 24, 382 }, + { 25, 382 }, { 26, 382 }, { 27, 382 }, { 28, 382 }, { 29, 382 }, + { 30, 382 }, { 31, 382 }, { 32, 382 }, { 33, 382 }, { 34, 382 }, + { 35, 382 }, { 36, 382 }, { 37, 382 }, { 38, 382 }, { 39, 382 }, + { 40, 382 }, { 41, 382 }, { 42, 382 }, { 43, 382 }, { 44, 382 }, + { 45, 382 }, { 46, 382 }, { 47, 382 }, { 48, 382 }, { 49, 382 }, + { 50, 382 }, { 51, 382 }, { 52, 382 }, { 53, 382 }, { 54, 382 }, + { 55, 382 }, { 56, 382 }, { 57, 382 }, { 58, 382 }, { 59, 382 }, + { 60, 382 }, { 61, 382 }, { 62, 382 }, { 63, 382 }, { 64, 382 }, + + { 65, 382 }, { 66, 382 }, { 67, 382 }, { 68, 382 }, { 69, 382 }, + { 70, 382 }, { 71, 382 }, { 72, 382 }, { 73, 382 }, { 74, 382 }, + { 75, 382 }, { 76, 382 }, { 77, 382 }, { 78, 382 }, { 79, 382 }, + { 80, 382 }, { 81, 382 }, { 82, 382 }, { 83, 382 }, { 84, 382 }, + { 85, 382 }, { 86, 382 }, { 87, 382 }, { 88, 382 }, { 89, 382 }, + { 90, 382 }, { 91, 382 }, { 92, 382 }, { 93, 382 }, { 94, 382 }, + { 95, 382 }, { 96, 382 }, { 97, 382 }, { 98, 518 }, { 99, 382 }, + { 100, 382 }, { 101, 382 }, { 102, 520 }, { 103, 382 }, { 104, 382 }, + { 105, 382 }, { 106, 382 }, { 107, 382 }, { 108, 382 }, { 109, 382 }, + { 110, 522 }, { 111, 382 }, { 112, 382 }, { 113, 382 }, { 114, 524 }, + + { 115, 382 }, { 116, 531 }, { 117,3923 }, { 118, 533 }, { 119, 382 }, + { 120,3961 }, { 121, 382 }, { 122, 382 }, { 123, 382 }, { 124, 382 }, + { 125, 382 }, { 126, 382 }, { 127, 382 }, { 128, 382 }, { 129, 382 }, + { 130, 382 }, { 131, 382 }, { 132, 382 }, { 133, 382 }, { 134, 382 }, + { 135, 382 }, { 136, 382 }, { 137, 382 }, { 138, 382 }, { 139, 382 }, + { 140, 382 }, { 141, 382 }, { 142, 382 }, { 143, 382 }, { 144, 382 }, + { 145, 382 }, { 146, 382 }, { 147, 382 }, { 148, 382 }, { 149, 382 }, + { 150, 382 }, { 151, 382 }, { 152, 382 }, { 153, 382 }, { 154, 382 }, + { 155, 382 }, { 156, 382 }, { 157, 382 }, { 158, 382 }, { 159, 382 }, + { 160, 382 }, { 161, 382 }, { 162, 382 }, { 163, 382 }, { 164, 382 }, + + { 165, 382 }, { 166, 382 }, { 167, 382 }, { 168, 382 }, { 169, 382 }, + { 170, 382 }, { 171, 382 }, { 172, 382 }, { 173, 382 }, { 174, 382 }, + { 175, 382 }, { 176, 382 }, { 177, 382 }, { 178, 382 }, { 179, 382 }, + { 180, 382 }, { 181, 382 }, { 182, 382 }, { 183, 382 }, { 184, 382 }, + { 185, 382 }, { 186, 382 }, { 187, 382 }, { 188, 382 }, { 189, 382 }, + { 190, 382 }, { 191, 382 }, { 192, 382 }, { 193, 382 }, { 194, 382 }, + { 195, 382 }, { 196, 382 }, { 197, 382 }, { 198, 382 }, { 199, 382 }, + { 200, 382 }, { 201, 382 }, { 202, 382 }, { 203, 382 }, { 204, 382 }, + { 205, 382 }, { 206, 382 }, { 207, 382 }, { 208, 382 }, { 209, 382 }, + { 210, 382 }, { 211, 382 }, { 212, 382 }, { 213, 382 }, { 214, 382 }, + + { 215, 382 }, { 216, 382 }, { 217, 382 }, { 218, 382 }, { 219, 382 }, + { 220, 382 }, { 221, 382 }, { 222, 382 }, { 223, 382 }, { 224, 382 }, + { 225, 382 }, { 226, 382 }, { 227, 382 }, { 228, 382 }, { 229, 382 }, + { 230, 382 }, { 231, 382 }, { 232, 382 }, { 233, 382 }, { 234, 382 }, + { 235, 382 }, { 236, 382 }, { 237, 382 }, { 238, 382 }, { 239, 382 }, + { 240, 382 }, { 241, 382 }, { 242, 382 }, { 243, 382 }, { 244, 382 }, + { 245, 382 }, { 246, 382 }, { 247, 382 }, { 248, 382 }, { 249, 382 }, + { 250, 382 }, { 251, 382 }, { 252, 382 }, { 253, 382 }, { 254, 382 }, + { 255, 382 }, { 256, 382 }, { 0, 1 }, { 0,11384 }, { 1,3807 }, + { 2,3807 }, { 3,3807 }, { 4,3807 }, { 5,3807 }, { 6,3807 }, + + { 7,3807 }, { 8,3807 }, { 0, 0 }, { 0, 0 }, { 11,3807 }, + { 0, 23 }, { 0,11371 }, { 14,3807 }, { 15,3807 }, { 16,3807 }, + { 17,3807 }, { 18,3807 }, { 19,3807 }, { 20,3807 }, { 21,3807 }, + { 22,3807 }, { 23,3807 }, { 24,3807 }, { 25,3807 }, { 26,3807 }, + { 27,3807 }, { 28,3807 }, { 29,3807 }, { 30,3807 }, { 31,3807 }, + { 0, 32 }, { 0,11351 }, { 0, 36 }, { 0,11349 }, { 0, 24 }, + { 0,11347 }, { 0, 0 }, { 39,3807 }, { 0, 27 }, { 0,11343 }, + { 0, 39 }, { 0,11341 }, { 0, 29 }, { 0,11339 }, { 0, 31 }, + { 0,11337 }, { 48,3807 }, { 49,3807 }, { 50,3807 }, { 51,3807 }, + { 52,3807 }, { 53,3807 }, { 54,3807 }, { 55,3807 }, { 56,3807 }, + + { 57,3807 }, { 0, 0 }, { 59,3807 }, { 47, 266 }, { 0, 30 }, + { 0,11322 }, { 0, 33 }, { 0,11320 }, { 65,3807 }, { 66,3807 }, + { 67,3807 }, { 68,3807 }, { 69,3807 }, { 70,3807 }, { 71,3807 }, + { 72,3807 }, { 73,3807 }, { 74,3807 }, { 75,3807 }, { 76,3807 }, + { 77,3807 }, { 78,3807 }, { 79,3807 }, { 80,3807 }, { 81,3807 }, + { 82,3807 }, { 83,3807 }, { 84,3807 }, { 85,3807 }, { 86,3807 }, + { 87,3807 }, { 88,3807 }, { 89,3807 }, { 90,3807 }, { 0, 25 }, + { 0,11292 }, { 0, 0 }, { 94,3807 }, { 95,3807 }, { 96,3807 }, + { 97,3807 }, { 98,3807 }, { 99,3807 }, { 100,3807 }, { 101,3807 }, + { 102,3807 }, { 103,3807 }, { 104,3807 }, { 105,3807 }, { 106,3807 }, + + { 107,3807 }, { 108,3807 }, { 109,3807 }, { 110,3807 }, { 111,3807 }, + { 112,3807 }, { 113,3807 }, { 114,3807 }, { 115,3807 }, { 116,3807 }, + { 117,3807 }, { 118,3807 }, { 119,3807 }, { 120,3807 }, { 121,3807 }, + { 122,3807 }, { 0, 16 }, { 0,11260 }, { 0, 0 }, { 126,3807 }, + { 127,3807 }, { 128,3807 }, { 129,3807 }, { 130,3807 }, { 131,3807 }, + { 132,3807 }, { 133,3807 }, { 134,3807 }, { 135,3807 }, { 136,3807 }, + { 137,3807 }, { 138,3807 }, { 139,3807 }, { 140,3807 }, { 141,3807 }, + { 142,3807 }, { 143,3807 }, { 144,3807 }, { 145,3807 }, { 146,3807 }, + { 147,3807 }, { 148,3807 }, { 149,3807 }, { 150,3807 }, { 151,3807 }, + { 152,3807 }, { 153,3807 }, { 154,3807 }, { 155,3807 }, { 156,3807 }, + + { 157,3807 }, { 158,3807 }, { 159,3807 }, { 160,3807 }, { 161,3807 }, + { 162,3807 }, { 163,3807 }, { 164,3807 }, { 165,3807 }, { 166,3807 }, + { 167,3807 }, { 168,3807 }, { 169,3807 }, { 170,3807 }, { 171,3807 }, + { 172,3807 }, { 173,3807 }, { 174,3807 }, { 175,3807 }, { 176,3807 }, + { 177,3807 }, { 178,3807 }, { 179,3807 }, { 180,3807 }, { 181,3807 }, + { 182,3807 }, { 183,3807 }, { 184,3807 }, { 185,3807 }, { 186,3807 }, + { 187,3807 }, { 188,3807 }, { 189,3807 }, { 190,3807 }, { 191,3807 }, + { 192,3807 }, { 193,3807 }, { 194,3807 }, { 195,3807 }, { 196,3807 }, + { 197,3807 }, { 198,3807 }, { 199,3807 }, { 200,3807 }, { 201,3807 }, + { 202,3807 }, { 203,3807 }, { 204,3807 }, { 205,3807 }, { 206,3807 }, + + { 207,3807 }, { 208,3807 }, { 209,3807 }, { 210,3807 }, { 211,3807 }, + { 212,3807 }, { 213,3807 }, { 214,3807 }, { 215,3807 }, { 216,3807 }, + { 217,3807 }, { 218,3807 }, { 219,3807 }, { 220,3807 }, { 221,3807 }, + { 222,3807 }, { 223,3807 }, { 224,3807 }, { 225,3807 }, { 226,3807 }, + { 227,3807 }, { 228,3807 }, { 229,3807 }, { 230,3807 }, { 231,3807 }, + { 232,3807 }, { 233,3807 }, { 234,3807 }, { 235,3807 }, { 236,3807 }, + { 237,3807 }, { 238,3807 }, { 239,3807 }, { 240,3807 }, { 241,3807 }, + { 242,3807 }, { 243,3807 }, { 244,3807 }, { 245,3807 }, { 246,3807 }, + { 247,3807 }, { 248,3807 }, { 249,3807 }, { 250,3807 }, { 251,3807 }, + { 252,3807 }, { 253,3807 }, { 254,3807 }, { 255,3807 }, { 256,3807 }, + + { 0, 2 }, { 0,11126 }, { 0, 5 }, { 0,11124 }, { 0, 6 }, + { 0,11122 }, { 0, 7 }, { 0,11120 }, { 0, 8 }, { 0,11118 }, + { 9,3807 }, { 10,3807 }, { 0, 0 }, { 12,3807 }, { 13,3807 }, + { 0, 9 }, { 0,11111 }, { 0, 10 }, { 0,11109 }, { 0, 3 }, + { 0,11107 }, { 0, 21 }, { 0,11105 }, { 0, 48 }, { 0,11103 }, + { 0, 12 }, { 0,11101 }, { 0, 49 }, { 0,11099 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 32,3807 }, { 0, 22 }, + { 0,11092 }, { 1,3807 }, { 2,3807 }, { 3,3807 }, { 4,3807 }, + { 5,3807 }, { 6,3807 }, { 7,3807 }, { 8,3807 }, { 9,3807 }, + { 10,3807 }, { 11,3807 }, { 12,3807 }, { 13,3807 }, { 14,3807 }, + + { 15,3807 }, { 16,3807 }, { 17,3807 }, { 18,3807 }, { 19,3807 }, + { 20,3807 }, { 21,3807 }, { 22,3807 }, { 23,3807 }, { 24,3807 }, + { 25,3807 }, { 26,3807 }, { 27,3807 }, { 28,3807 }, { 29,3807 }, + { 30,3807 }, { 31,3807 }, { 32,3807 }, { 33,3807 }, { 34,3807 }, + { 35,3807 }, { 36,3807 }, { 37,3807 }, { 38,3807 }, { 39,3807 }, + { 40,3807 }, { 41,3807 }, { 0, 0 }, { 43,3807 }, { 44,3807 }, + { 45,3807 }, { 46,3807 }, { 47,3807 }, { 48,3807 }, { 49,3807 }, + { 50,3807 }, { 51,3807 }, { 52,3807 }, { 53,3807 }, { 54,3807 }, + { 55,3807 }, { 56,3807 }, { 57,3807 }, { 58,3807 }, { 59,3807 }, + { 60,3807 }, { 61,3807 }, { 62,3807 }, { 63,3807 }, { 64,3807 }, + + { 65,3807 }, { 66,3807 }, { 67,3807 }, { 68,3807 }, { 69,3807 }, + { 70,3807 }, { 71,3807 }, { 72,3807 }, { 73,3807 }, { 74,3807 }, + { 75,3807 }, { 76,3807 }, { 77,3807 }, { 78,3807 }, { 79,3807 }, + { 80,3807 }, { 81,3807 }, { 82,3807 }, { 83,3807 }, { 84,3807 }, + { 85,3807 }, { 86,3807 }, { 87,3807 }, { 88,3807 }, { 89,3807 }, + { 90,3807 }, { 91,3807 }, { 92,3807 }, { 93,3807 }, { 94,3807 }, + { 95,3807 }, { 96,3807 }, { 97,3807 }, { 98,3807 }, { 99,3807 }, + { 100,3807 }, { 101,3807 }, { 102,3807 }, { 103,3807 }, { 104,3807 }, + { 105,3807 }, { 106,3807 }, { 107,3807 }, { 108,3807 }, { 109,3807 }, + { 110,3807 }, { 111,3807 }, { 112,3807 }, { 113,3807 }, { 114,3807 }, + + { 115,3807 }, { 116,3807 }, { 117,3807 }, { 118,3807 }, { 119,3807 }, + { 120,3807 }, { 121,3807 }, { 122,3807 }, { 123,3807 }, { 124,3807 }, + { 125,3807 }, { 126,3807 }, { 127,3807 }, { 128,3807 }, { 129,3807 }, + { 130,3807 }, { 131,3807 }, { 132,3807 }, { 133,3807 }, { 134,3807 }, + { 135,3807 }, { 136,3807 }, { 137,3807 }, { 138,3807 }, { 139,3807 }, + { 140,3807 }, { 141,3807 }, { 142,3807 }, { 143,3807 }, { 144,3807 }, + { 145,3807 }, { 146,3807 }, { 147,3807 }, { 148,3807 }, { 149,3807 }, + { 150,3807 }, { 151,3807 }, { 152,3807 }, { 153,3807 }, { 154,3807 }, + { 155,3807 }, { 156,3807 }, { 157,3807 }, { 158,3807 }, { 159,3807 }, + { 160,3807 }, { 161,3807 }, { 162,3807 }, { 163,3807 }, { 164,3807 }, + + { 165,3807 }, { 166,3807 }, { 167,3807 }, { 168,3807 }, { 169,3807 }, + { 170,3807 }, { 171,3807 }, { 172,3807 }, { 173,3807 }, { 174,3807 }, + { 175,3807 }, { 176,3807 }, { 177,3807 }, { 178,3807 }, { 179,3807 }, + { 180,3807 }, { 181,3807 }, { 182,3807 }, { 183,3807 }, { 184,3807 }, + { 185,3807 }, { 186,3807 }, { 187,3807 }, { 188,3807 }, { 189,3807 }, + { 190,3807 }, { 191,3807 }, { 192,3807 }, { 193,3807 }, { 194,3807 }, + { 195,3807 }, { 196,3807 }, { 197,3807 }, { 198,3807 }, { 199,3807 }, + { 200,3807 }, { 201,3807 }, { 202,3807 }, { 203,3807 }, { 204,3807 }, + { 205,3807 }, { 206,3807 }, { 207,3807 }, { 208,3807 }, { 209,3807 }, + { 210,3807 }, { 211,3807 }, { 212,3807 }, { 213,3807 }, { 214,3807 }, + + { 215,3807 }, { 216,3807 }, { 217,3807 }, { 218,3807 }, { 219,3807 }, + { 220,3807 }, { 221,3807 }, { 222,3807 }, { 223,3807 }, { 224,3807 }, + { 225,3807 }, { 226,3807 }, { 227,3807 }, { 228,3807 }, { 229,3807 }, + { 230,3807 }, { 231,3807 }, { 232,3807 }, { 233,3807 }, { 234,3807 }, + { 235,3807 }, { 236,3807 }, { 237,3807 }, { 238,3807 }, { 239,3807 }, + { 240,3807 }, { 241,3807 }, { 242,3807 }, { 243,3807 }, { 244,3807 }, + { 245,3807 }, { 246,3807 }, { 247,3807 }, { 248,3807 }, { 249,3807 }, + { 250,3807 }, { 251,3807 }, { 252,3807 }, { 253,3807 }, { 254,3807 }, + { 255,3807 }, { 256,3807 }, { 0, 52 }, { 0,10834 }, { 1, 0 }, + { 2, 0 }, { 3, 0 }, { 4, 0 }, { 5, 0 }, { 6, 0 }, + + { 7, 0 }, { 8, 0 }, { 0, 0 }, { 0, 0 }, { 11, 0 }, + { 0, 0 }, { 0, 0 }, { 14, 0 }, { 15, 0 }, { 16, 0 }, + { 17, 0 }, { 18, 0 }, { 19, 0 }, { 20, 0 }, { 21, 0 }, + { 22, 0 }, { 23, 0 }, { 24, 0 }, { 25, 0 }, { 26, 0 }, + { 27, 0 }, { 28, 0 }, { 29, 0 }, { 30, 0 }, { 31, 0 }, + { 0, 11 }, { 0,10801 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 39, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 48, 0 }, { 49, 0 }, { 50, 0 }, { 51, 0 }, + { 52, 0 }, { 53, 0 }, { 54, 0 }, { 55, 0 }, { 56, 0 }, + + { 57, 0 }, { 0, 0 }, { 59, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 65, 0 }, { 66, 0 }, + { 67, 0 }, { 68, 0 }, { 69, 0 }, { 70, 0 }, { 71, 0 }, + { 72, 0 }, { 73, 0 }, { 74, 0 }, { 75, 0 }, { 76, 0 }, + { 77, 0 }, { 78, 0 }, { 79, 0 }, { 80, 0 }, { 81, 0 }, + { 82, 0 }, { 83, 0 }, { 84, 0 }, { 85, 0 }, { 86, 0 }, + { 87, 0 }, { 88, 0 }, { 89, 0 }, { 90, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 94, 0 }, { 95, 0 }, { 96, 0 }, + { 97, 0 }, { 98, 0 }, { 99, 0 }, { 100, 0 }, { 101, 0 }, + { 102, 0 }, { 103, 0 }, { 104, 0 }, { 105, 0 }, { 106, 0 }, + + { 107, 0 }, { 108, 0 }, { 109, 0 }, { 110, 0 }, { 111, 0 }, + { 112, 0 }, { 113, 0 }, { 114, 0 }, { 115, 0 }, { 116, 0 }, + { 117, 0 }, { 118, 0 }, { 119, 0 }, { 120, 0 }, { 121, 0 }, + { 122, 0 }, { 0, 0 }, { 0, 0 }, { 92,3084 }, { 126, 0 }, + { 127, 0 }, { 128, 0 }, { 129, 0 }, { 130, 0 }, { 131, 0 }, + { 132, 0 }, { 133, 0 }, { 134, 0 }, { 135, 0 }, { 136, 0 }, + { 137, 0 }, { 138, 0 }, { 139, 0 }, { 140, 0 }, { 141, 0 }, + { 142, 0 }, { 143, 0 }, { 144, 0 }, { 145, 0 }, { 146, 0 }, + { 147, 0 }, { 148, 0 }, { 149, 0 }, { 150, 0 }, { 151, 0 }, + { 152, 0 }, { 153, 0 }, { 154, 0 }, { 155, 0 }, { 156, 0 }, + + { 157, 0 }, { 158, 0 }, { 159, 0 }, { 160, 0 }, { 161, 0 }, + { 162, 0 }, { 163, 0 }, { 164, 0 }, { 165, 0 }, { 166, 0 }, + { 167, 0 }, { 168, 0 }, { 169, 0 }, { 170, 0 }, { 171, 0 }, + { 172, 0 }, { 173, 0 }, { 174, 0 }, { 175, 0 }, { 176, 0 }, + { 177, 0 }, { 178, 0 }, { 179, 0 }, { 180, 0 }, { 181, 0 }, + { 182, 0 }, { 183, 0 }, { 184, 0 }, { 185, 0 }, { 186, 0 }, + { 187, 0 }, { 188, 0 }, { 189, 0 }, { 190, 0 }, { 191, 0 }, + { 192, 0 }, { 193, 0 }, { 194, 0 }, { 195, 0 }, { 196, 0 }, + { 197, 0 }, { 198, 0 }, { 199, 0 }, { 200, 0 }, { 201, 0 }, + { 202, 0 }, { 203, 0 }, { 204, 0 }, { 205, 0 }, { 206, 0 }, + + { 207, 0 }, { 208, 0 }, { 209, 0 }, { 210, 0 }, { 211, 0 }, + { 212, 0 }, { 213, 0 }, { 214, 0 }, { 215, 0 }, { 216, 0 }, + { 217, 0 }, { 218, 0 }, { 219, 0 }, { 220, 0 }, { 221, 0 }, + { 222, 0 }, { 223, 0 }, { 224, 0 }, { 225, 0 }, { 226, 0 }, + { 227, 0 }, { 228, 0 }, { 229, 0 }, { 230, 0 }, { 231, 0 }, + { 232, 0 }, { 233, 0 }, { 234, 0 }, { 235, 0 }, { 236, 0 }, + { 237, 0 }, { 238, 0 }, { 239, 0 }, { 240, 0 }, { 241, 0 }, + { 242, 0 }, { 243, 0 }, { 244, 0 }, { 245, 0 }, { 246, 0 }, + { 247, 0 }, { 248, 0 }, { 249, 0 }, { 250, 0 }, { 251, 0 }, + { 252, 0 }, { 253, 0 }, { 254, 0 }, { 255, 0 }, { 256, 0 }, + + { 0, 38 }, { 0,10576 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 9, 0 }, { 10, 0 }, { 0, 0 }, { 12, 0 }, { 13, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 35 }, + { 0,10557 }, { 1, 0 }, { 2, 0 }, { 3, 0 }, { 4, 0 }, + { 5, 0 }, { 6, 0 }, { 7, 0 }, { 8, 0 }, { 0, 0 }, + { 0, 0 }, { 11, 0 }, { 0, 0 }, { 32, 0 }, { 14, 0 }, + { 15, 0 }, { 16, 0 }, { 17, 0 }, { 18, 0 }, { 19, 0 }, + { 20, 0 }, { 21, 0 }, { 22, 0 }, { 23, 0 }, { 24, 0 }, + { 25, 0 }, { 26, 0 }, { 27, 0 }, { 28, 0 }, { 29, 0 }, + + { 30, 0 }, { 31, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 39, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 48, 0 }, { 49, 0 }, + { 50, 0 }, { 51, 0 }, { 52, 0 }, { 53, 0 }, { 54, 0 }, + { 55, 0 }, { 56, 0 }, { 57, 0 }, { 0, 0 }, { 59, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 65, 0 }, { 66, 0 }, { 67, 0 }, { 68, 0 }, { 69, 0 }, + { 70, 0 }, { 71, 0 }, { 72, 0 }, { 73, 0 }, { 74, 0 }, + { 75, 0 }, { 76, 0 }, { 77, 0 }, { 78, 0 }, { 79, 0 }, + + { 80, 0 }, { 81, 0 }, { 82, 0 }, { 83, 0 }, { 84, 0 }, + { 85, 0 }, { 86, 0 }, { 87, 0 }, { 88, 0 }, { 89, 0 }, + { 90, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 94, 0 }, + { 95, 0 }, { 96, 0 }, { 97, 0 }, { 98, 0 }, { 99, 0 }, + { 100, 0 }, { 101, 0 }, { 102, 0 }, { 103, 0 }, { 104, 0 }, + { 105, 0 }, { 106, 0 }, { 107, 0 }, { 108, 0 }, { 109, 0 }, + { 110, 0 }, { 111, 0 }, { 112, 0 }, { 113, 0 }, { 114, 0 }, + { 115, 0 }, { 116, 0 }, { 117, 0 }, { 118, 0 }, { 119, 0 }, + { 120, 0 }, { 121, 0 }, { 122, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 126, 0 }, { 127, 0 }, { 128, 0 }, { 129, 0 }, + + { 130, 0 }, { 131, 0 }, { 132, 0 }, { 133, 0 }, { 134, 0 }, + { 135, 0 }, { 136, 0 }, { 137, 0 }, { 138, 0 }, { 139, 0 }, + { 140, 0 }, { 141, 0 }, { 142, 0 }, { 143, 0 }, { 144, 0 }, + { 145, 0 }, { 146, 0 }, { 147, 0 }, { 148, 0 }, { 149, 0 }, + { 150, 0 }, { 151, 0 }, { 152, 0 }, { 153, 0 }, { 154, 0 }, + { 155, 0 }, { 156, 0 }, { 157, 0 }, { 158, 0 }, { 159, 0 }, + { 160, 0 }, { 161, 0 }, { 162, 0 }, { 163, 0 }, { 164, 0 }, + { 165, 0 }, { 166, 0 }, { 167, 0 }, { 168, 0 }, { 169, 0 }, + { 170, 0 }, { 171, 0 }, { 172, 0 }, { 173, 0 }, { 174, 0 }, + { 175, 0 }, { 176, 0 }, { 177, 0 }, { 178, 0 }, { 179, 0 }, + + { 180, 0 }, { 181, 0 }, { 182, 0 }, { 183, 0 }, { 184, 0 }, + { 185, 0 }, { 186, 0 }, { 187, 0 }, { 188, 0 }, { 189, 0 }, + { 190, 0 }, { 191, 0 }, { 192, 0 }, { 193, 0 }, { 194, 0 }, + { 195, 0 }, { 196, 0 }, { 197, 0 }, { 198, 0 }, { 199, 0 }, + { 200, 0 }, { 201, 0 }, { 202, 0 }, { 203, 0 }, { 204, 0 }, + { 205, 0 }, { 206, 0 }, { 207, 0 }, { 208, 0 }, { 209, 0 }, + { 210, 0 }, { 211, 0 }, { 212, 0 }, { 213, 0 }, { 214, 0 }, + { 215, 0 }, { 216, 0 }, { 217, 0 }, { 218, 0 }, { 219, 0 }, + { 220, 0 }, { 221, 0 }, { 222, 0 }, { 223, 0 }, { 224, 0 }, + { 225, 0 }, { 226, 0 }, { 227, 0 }, { 228, 0 }, { 229, 0 }, + + { 230, 0 }, { 231, 0 }, { 232, 0 }, { 233, 0 }, { 234, 0 }, + { 235, 0 }, { 236, 0 }, { 237, 0 }, { 238, 0 }, { 239, 0 }, + { 240, 0 }, { 241, 0 }, { 242, 0 }, { 243, 0 }, { 244, 0 }, + { 245, 0 }, { 246, 0 }, { 247, 0 }, { 248, 0 }, { 249, 0 }, + { 250, 0 }, { 251, 0 }, { 252, 0 }, { 253, 0 }, { 254, 0 }, + { 255, 0 }, { 256, 0 }, { 0, 41 }, { 0,10299 }, { 1,-804 }, + { 2,-804 }, { 3,-804 }, { 4,-804 }, { 5,-804 }, { 6,-804 }, + { 7,-804 }, { 8,-804 }, { 0, 0 }, { 0, 0 }, { 11,-804 }, + { 0, 0 }, { 0, 0 }, { 14,-804 }, { 15,-804 }, { 16,-804 }, + { 17,-804 }, { 18,-804 }, { 19,-804 }, { 20,-804 }, { 21,-804 }, + + { 22,-804 }, { 23,-804 }, { 24,-804 }, { 25,-804 }, { 26,-804 }, + { 27,-804 }, { 28,-804 }, { 29,-804 }, { 30,-804 }, { 31,-804 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 39,-804 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 48,3272 }, { 49,3272 }, { 50,3272 }, { 51,3272 }, + { 52,3272 }, { 53,3272 }, { 54,3272 }, { 55,3272 }, { 56,3272 }, + { 57,3272 }, { 0, 0 }, { 59,-804 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 65,-804 }, { 66,-804 }, + { 67,-804 }, { 68,-804 }, { 69,3530 }, { 70,-804 }, { 71,-804 }, + + { 72,-804 }, { 73,-804 }, { 74,-804 }, { 75,-804 }, { 76,-804 }, + { 77,-804 }, { 78,-804 }, { 79,-804 }, { 80,-804 }, { 81,-804 }, + { 82,-804 }, { 83,-804 }, { 84,-804 }, { 85,-804 }, { 86,-804 }, + { 87,-804 }, { 88,-804 }, { 89,-804 }, { 90,-804 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 94,-804 }, { 95,3540 }, { 96,-804 }, + { 97,-804 }, { 98,-804 }, { 99,-804 }, { 100,-804 }, { 101,3530 }, + { 102,-804 }, { 103,-804 }, { 104,-804 }, { 105,-804 }, { 106,-804 }, + { 107,-804 }, { 108,-804 }, { 109,-804 }, { 110,-804 }, { 111,-804 }, + { 112,-804 }, { 113,-804 }, { 114,-804 }, { 115,-804 }, { 116,-804 }, + { 117,-804 }, { 118,-804 }, { 119,-804 }, { 120,-804 }, { 121,-804 }, + + { 122,-804 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 126,-804 }, + { 127,-804 }, { 128,-804 }, { 129,-804 }, { 130,-804 }, { 131,-804 }, + { 132,-804 }, { 133,-804 }, { 134,-804 }, { 135,-804 }, { 136,-804 }, + { 137,-804 }, { 138,-804 }, { 139,-804 }, { 140,-804 }, { 141,-804 }, + { 142,-804 }, { 143,-804 }, { 144,-804 }, { 145,-804 }, { 146,-804 }, + { 147,-804 }, { 148,-804 }, { 149,-804 }, { 150,-804 }, { 151,-804 }, + { 152,-804 }, { 153,-804 }, { 154,-804 }, { 155,-804 }, { 156,-804 }, + { 157,-804 }, { 158,-804 }, { 159,-804 }, { 160,-804 }, { 161,-804 }, + { 162,-804 }, { 163,-804 }, { 164,-804 }, { 165,-804 }, { 166,-804 }, + { 167,-804 }, { 168,-804 }, { 169,-804 }, { 170,-804 }, { 171,-804 }, + + { 172,-804 }, { 173,-804 }, { 174,-804 }, { 175,-804 }, { 176,-804 }, + { 177,-804 }, { 178,-804 }, { 179,-804 }, { 180,-804 }, { 181,-804 }, + { 182,-804 }, { 183,-804 }, { 184,-804 }, { 185,-804 }, { 186,-804 }, + { 187,-804 }, { 188,-804 }, { 189,-804 }, { 190,-804 }, { 191,-804 }, + { 192,-804 }, { 193,-804 }, { 194,-804 }, { 195,-804 }, { 196,-804 }, + { 197,-804 }, { 198,-804 }, { 199,-804 }, { 200,-804 }, { 201,-804 }, + { 202,-804 }, { 203,-804 }, { 204,-804 }, { 205,-804 }, { 206,-804 }, + { 207,-804 }, { 208,-804 }, { 209,-804 }, { 210,-804 }, { 211,-804 }, + { 212,-804 }, { 213,-804 }, { 214,-804 }, { 215,-804 }, { 216,-804 }, + { 217,-804 }, { 218,-804 }, { 219,-804 }, { 220,-804 }, { 221,-804 }, + + { 222,-804 }, { 223,-804 }, { 224,-804 }, { 225,-804 }, { 226,-804 }, + { 227,-804 }, { 228,-804 }, { 229,-804 }, { 230,-804 }, { 231,-804 }, + { 232,-804 }, { 233,-804 }, { 234,-804 }, { 235,-804 }, { 236,-804 }, + { 237,-804 }, { 238,-804 }, { 239,-804 }, { 240,-804 }, { 241,-804 }, + { 242,-804 }, { 243,-804 }, { 244,-804 }, { 245,-804 }, { 246,-804 }, + { 247,-804 }, { 248,-804 }, { 249,-804 }, { 250,-804 }, { 251,-804 }, + { 252,-804 }, { 253,-804 }, { 254,-804 }, { 255,-804 }, { 256,-804 }, + { 0, 47 }, { 0,10041 }, { 1,-793 }, { 2,-793 }, { 3,-793 }, + { 4,-793 }, { 5,-793 }, { 6,-793 }, { 7,-793 }, { 8,-793 }, + { 0, 0 }, { 0, 0 }, { 11,-793 }, { 0, 0 }, { 0, 0 }, + + { 14,-793 }, { 15,-793 }, { 16,-793 }, { 17,-793 }, { 18,-793 }, + { 19,-793 }, { 20,-793 }, { 21,-793 }, { 22,-793 }, { 23,-793 }, + { 24,-793 }, { 25,-793 }, { 26,-793 }, { 27,-793 }, { 28,-793 }, + { 29,-793 }, { 30,-793 }, { 31,-793 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 39,-793 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 48,-793 }, + { 49,-793 }, { 50,-793 }, { 51,-793 }, { 52,-793 }, { 53,-793 }, + { 54,-793 }, { 55,-793 }, { 56,-793 }, { 57,-793 }, { 0, 0 }, + { 59,-793 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + + { 0, 0 }, { 65,-793 }, { 66,-793 }, { 67,-793 }, { 68,-793 }, + { 69,-793 }, { 70,-793 }, { 71,-793 }, { 72,-793 }, { 73,-793 }, + { 74,-793 }, { 75,-793 }, { 76,-793 }, { 77,-793 }, { 78,-793 }, + { 79,-793 }, { 80,-793 }, { 81,-793 }, { 82,-793 }, { 83,-793 }, + { 84,-793 }, { 85,-793 }, { 86,-793 }, { 87,-793 }, { 88,-793 }, + { 89,-793 }, { 90,-793 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 94,-793 }, { 95,-793 }, { 96,-793 }, { 97,-793 }, { 98,-793 }, + { 99,-793 }, { 100,-793 }, { 101,-793 }, { 102,-793 }, { 103,-793 }, + { 104,-793 }, { 105,-793 }, { 106,-793 }, { 107,-793 }, { 108,-793 }, + { 109,-793 }, { 110,-793 }, { 111,-793 }, { 112,-793 }, { 113,-793 }, + + { 114,-793 }, { 115,-793 }, { 116,-793 }, { 117,-793 }, { 118,-793 }, + { 119,-793 }, { 120,-793 }, { 121,-793 }, { 122,-793 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 126,-793 }, { 127,-793 }, { 128,-793 }, + { 129,-793 }, { 130,-793 }, { 131,-793 }, { 132,-793 }, { 133,-793 }, + { 134,-793 }, { 135,-793 }, { 136,-793 }, { 137,-793 }, { 138,-793 }, + { 139,-793 }, { 140,-793 }, { 141,-793 }, { 142,-793 }, { 143,-793 }, + { 144,-793 }, { 145,-793 }, { 146,-793 }, { 147,-793 }, { 148,-793 }, + { 149,-793 }, { 150,-793 }, { 151,-793 }, { 152,-793 }, { 153,-793 }, + { 154,-793 }, { 155,-793 }, { 156,-793 }, { 157,-793 }, { 158,-793 }, + { 159,-793 }, { 160,-793 }, { 161,-793 }, { 162,-793 }, { 163,-793 }, + + { 164,-793 }, { 165,-793 }, { 166,-793 }, { 167,-793 }, { 168,-793 }, + { 169,-793 }, { 170,-793 }, { 171,-793 }, { 172,-793 }, { 173,-793 }, + { 174,-793 }, { 175,-793 }, { 176,-793 }, { 177,-793 }, { 178,-793 }, + { 179,-793 }, { 180,-793 }, { 181,-793 }, { 182,-793 }, { 183,-793 }, + { 184,-793 }, { 185,-793 }, { 186,-793 }, { 187,-793 }, { 188,-793 }, + { 189,-793 }, { 190,-793 }, { 191,-793 }, { 192,-793 }, { 193,-793 }, + { 194,-793 }, { 195,-793 }, { 196,-793 }, { 197,-793 }, { 198,-793 }, + { 199,-793 }, { 200,-793 }, { 201,-793 }, { 202,-793 }, { 203,-793 }, + { 204,-793 }, { 205,-793 }, { 206,-793 }, { 207,-793 }, { 208,-793 }, + { 209,-793 }, { 210,-793 }, { 211,-793 }, { 212,-793 }, { 213,-793 }, + + { 214,-793 }, { 215,-793 }, { 216,-793 }, { 217,-793 }, { 218,-793 }, + { 219,-793 }, { 220,-793 }, { 221,-793 }, { 222,-793 }, { 223,-793 }, + { 224,-793 }, { 225,-793 }, { 226,-793 }, { 227,-793 }, { 228,-793 }, + { 229,-793 }, { 230,-793 }, { 231,-793 }, { 232,-793 }, { 233,-793 }, + { 234,-793 }, { 235,-793 }, { 236,-793 }, { 237,-793 }, { 238,-793 }, + { 239,-793 }, { 240,-793 }, { 241,-793 }, { 242,-793 }, { 243,-793 }, + { 244,-793 }, { 245,-793 }, { 246,-793 }, { 247,-793 }, { 248,-793 }, + { 249,-793 }, { 250,-793 }, { 251,-793 }, { 252,-793 }, { 253,-793 }, + { 254,-793 }, { 255,-793 }, { 256,-793 }, { 0, 41 }, { 0,9783 }, + { 1,-1320 }, { 2,-1320 }, { 3,-1320 }, { 4,-1320 }, { 5,-1320 }, + + { 6,-1320 }, { 7,-1320 }, { 8,-1320 }, { 0, 0 }, { 0, 0 }, + { 11,-1320 }, { 0, 0 }, { 0, 0 }, { 14,-1320 }, { 15,-1320 }, + { 16,-1320 }, { 17,-1320 }, { 18,-1320 }, { 19,-1320 }, { 20,-1320 }, + { 21,-1320 }, { 22,-1320 }, { 23,-1320 }, { 24,-1320 }, { 25,-1320 }, + { 26,-1320 }, { 27,-1320 }, { 28,-1320 }, { 29,-1320 }, { 30,-1320 }, + { 31,-1320 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 39,-1320 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 48,3083 }, { 49,3083 }, { 50,3083 }, + { 51,3083 }, { 52,3083 }, { 53,3083 }, { 54,3083 }, { 55,3083 }, + + { 56,3083 }, { 57,3083 }, { 0, 0 }, { 59,-1320 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 65,-1320 }, + { 66,-1320 }, { 67,-1320 }, { 68,-1320 }, { 69,3014 }, { 70,-1320 }, + { 71,-1320 }, { 72,-1320 }, { 73,-1320 }, { 74,-1320 }, { 75,-1320 }, + { 76,-1320 }, { 77,-1320 }, { 78,-1320 }, { 79,-1320 }, { 80,-1320 }, + { 81,-1320 }, { 82,-1320 }, { 83,-1320 }, { 84,-1320 }, { 85,-1320 }, + { 86,-1320 }, { 87,-1320 }, { 88,-1320 }, { 89,-1320 }, { 90,-1320 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 94,-1320 }, { 95,-1320 }, + { 96,-1320 }, { 97,-1320 }, { 98,-1320 }, { 99,-1320 }, { 100,-1320 }, + { 101,3014 }, { 102,-1320 }, { 103,-1320 }, { 104,-1320 }, { 105,-1320 }, + + { 106,-1320 }, { 107,-1320 }, { 108,-1320 }, { 109,-1320 }, { 110,-1320 }, + { 111,-1320 }, { 112,-1320 }, { 113,-1320 }, { 114,-1320 }, { 115,-1320 }, + { 116,-1320 }, { 117,-1320 }, { 118,-1320 }, { 119,-1320 }, { 120,-1320 }, + { 121,-1320 }, { 122,-1320 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 126,-1320 }, { 127,-1320 }, { 128,-1320 }, { 129,-1320 }, { 130,-1320 }, + { 131,-1320 }, { 132,-1320 }, { 133,-1320 }, { 134,-1320 }, { 135,-1320 }, + { 136,-1320 }, { 137,-1320 }, { 138,-1320 }, { 139,-1320 }, { 140,-1320 }, + { 141,-1320 }, { 142,-1320 }, { 143,-1320 }, { 144,-1320 }, { 145,-1320 }, + { 146,-1320 }, { 147,-1320 }, { 148,-1320 }, { 149,-1320 }, { 150,-1320 }, + { 151,-1320 }, { 152,-1320 }, { 153,-1320 }, { 154,-1320 }, { 155,-1320 }, + + { 156,-1320 }, { 157,-1320 }, { 158,-1320 }, { 159,-1320 }, { 160,-1320 }, + { 161,-1320 }, { 162,-1320 }, { 163,-1320 }, { 164,-1320 }, { 165,-1320 }, + { 166,-1320 }, { 167,-1320 }, { 168,-1320 }, { 169,-1320 }, { 170,-1320 }, + { 171,-1320 }, { 172,-1320 }, { 173,-1320 }, { 174,-1320 }, { 175,-1320 }, + { 176,-1320 }, { 177,-1320 }, { 178,-1320 }, { 179,-1320 }, { 180,-1320 }, + { 181,-1320 }, { 182,-1320 }, { 183,-1320 }, { 184,-1320 }, { 185,-1320 }, + { 186,-1320 }, { 187,-1320 }, { 188,-1320 }, { 189,-1320 }, { 190,-1320 }, + { 191,-1320 }, { 192,-1320 }, { 193,-1320 }, { 194,-1320 }, { 195,-1320 }, + { 196,-1320 }, { 197,-1320 }, { 198,-1320 }, { 199,-1320 }, { 200,-1320 }, + { 201,-1320 }, { 202,-1320 }, { 203,-1320 }, { 204,-1320 }, { 205,-1320 }, + + { 206,-1320 }, { 207,-1320 }, { 208,-1320 }, { 209,-1320 }, { 210,-1320 }, + { 211,-1320 }, { 212,-1320 }, { 213,-1320 }, { 214,-1320 }, { 215,-1320 }, + { 216,-1320 }, { 217,-1320 }, { 218,-1320 }, { 219,-1320 }, { 220,-1320 }, + { 221,-1320 }, { 222,-1320 }, { 223,-1320 }, { 224,-1320 }, { 225,-1320 }, + { 226,-1320 }, { 227,-1320 }, { 228,-1320 }, { 229,-1320 }, { 230,-1320 }, + { 231,-1320 }, { 232,-1320 }, { 233,-1320 }, { 234,-1320 }, { 235,-1320 }, + { 236,-1320 }, { 237,-1320 }, { 238,-1320 }, { 239,-1320 }, { 240,-1320 }, + { 241,-1320 }, { 242,-1320 }, { 243,-1320 }, { 244,-1320 }, { 245,-1320 }, + { 246,-1320 }, { 247,-1320 }, { 248,-1320 }, { 249,-1320 }, { 250,-1320 }, + { 251,-1320 }, { 252,-1320 }, { 253,-1320 }, { 254,-1320 }, { 255,-1320 }, + + { 256,-1320 }, { 0, 47 }, { 0,9525 }, { 1,-1309 }, { 2,-1309 }, + { 3,-1309 }, { 4,-1309 }, { 5,-1309 }, { 6,-1309 }, { 7,-1309 }, + { 8,-1309 }, { 0, 0 }, { 0, 0 }, { 11,-1309 }, { 0, 0 }, + { 0, 0 }, { 14,-1309 }, { 15,-1309 }, { 16,-1309 }, { 17,-1309 }, + { 18,-1309 }, { 19,-1309 }, { 20,-1309 }, { 21,-1309 }, { 22,-1309 }, + { 23,-1309 }, { 24,-1309 }, { 25,-1309 }, { 26,-1309 }, { 27,-1309 }, + { 28,-1309 }, { 29,-1309 }, { 30,-1309 }, { 31,-1309 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 39,-1309 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + + { 48,3083 }, { 49,3083 }, { 50,-1309 }, { 51,-1309 }, { 52,-1309 }, + { 53,-1309 }, { 54,-1309 }, { 55,-1309 }, { 56,-1309 }, { 57,-1309 }, + { 0, 0 }, { 59,-1309 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 65,-1309 }, { 66,-1309 }, { 67,-1309 }, + { 68,-1309 }, { 69,-1309 }, { 70,-1309 }, { 71,-1309 }, { 72,-1309 }, + { 73,-1309 }, { 74,-1309 }, { 75,-1309 }, { 76,-1309 }, { 77,-1309 }, + { 78,-1309 }, { 79,-1309 }, { 80,-1309 }, { 81,-1309 }, { 82,-1309 }, + { 83,-1309 }, { 84,-1309 }, { 85,-1309 }, { 86,-1309 }, { 87,-1309 }, + { 88,-1309 }, { 89,-1309 }, { 90,-1309 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 94,-1309 }, { 95,-1309 }, { 96,-1309 }, { 97,-1309 }, + + { 98,-1309 }, { 99,-1309 }, { 100,-1309 }, { 101,-1309 }, { 102,-1309 }, + { 103,-1309 }, { 104,-1309 }, { 105,-1309 }, { 106,-1309 }, { 107,-1309 }, + { 108,-1309 }, { 109,-1309 }, { 110,-1309 }, { 111,-1309 }, { 112,-1309 }, + { 113,-1309 }, { 114,-1309 }, { 115,-1309 }, { 116,-1309 }, { 117,-1309 }, + { 118,-1309 }, { 119,-1309 }, { 120,-1309 }, { 121,-1309 }, { 122,-1309 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 126,-1309 }, { 127,-1309 }, + { 128,-1309 }, { 129,-1309 }, { 130,-1309 }, { 131,-1309 }, { 132,-1309 }, + { 133,-1309 }, { 134,-1309 }, { 135,-1309 }, { 136,-1309 }, { 137,-1309 }, + { 138,-1309 }, { 139,-1309 }, { 140,-1309 }, { 141,-1309 }, { 142,-1309 }, + { 143,-1309 }, { 144,-1309 }, { 145,-1309 }, { 146,-1309 }, { 147,-1309 }, + + { 148,-1309 }, { 149,-1309 }, { 150,-1309 }, { 151,-1309 }, { 152,-1309 }, + { 153,-1309 }, { 154,-1309 }, { 155,-1309 }, { 156,-1309 }, { 157,-1309 }, + { 158,-1309 }, { 159,-1309 }, { 160,-1309 }, { 161,-1309 }, { 162,-1309 }, + { 163,-1309 }, { 164,-1309 }, { 165,-1309 }, { 166,-1309 }, { 167,-1309 }, + { 168,-1309 }, { 169,-1309 }, { 170,-1309 }, { 171,-1309 }, { 172,-1309 }, + { 173,-1309 }, { 174,-1309 }, { 175,-1309 }, { 176,-1309 }, { 177,-1309 }, + { 178,-1309 }, { 179,-1309 }, { 180,-1309 }, { 181,-1309 }, { 182,-1309 }, + { 183,-1309 }, { 184,-1309 }, { 185,-1309 }, { 186,-1309 }, { 187,-1309 }, + { 188,-1309 }, { 189,-1309 }, { 190,-1309 }, { 191,-1309 }, { 192,-1309 }, + { 193,-1309 }, { 194,-1309 }, { 195,-1309 }, { 196,-1309 }, { 197,-1309 }, + + { 198,-1309 }, { 199,-1309 }, { 200,-1309 }, { 201,-1309 }, { 202,-1309 }, + { 203,-1309 }, { 204,-1309 }, { 205,-1309 }, { 206,-1309 }, { 207,-1309 }, + { 208,-1309 }, { 209,-1309 }, { 210,-1309 }, { 211,-1309 }, { 212,-1309 }, + { 213,-1309 }, { 214,-1309 }, { 215,-1309 }, { 216,-1309 }, { 217,-1309 }, + { 218,-1309 }, { 219,-1309 }, { 220,-1309 }, { 221,-1309 }, { 222,-1309 }, + { 223,-1309 }, { 224,-1309 }, { 225,-1309 }, { 226,-1309 }, { 227,-1309 }, + { 228,-1309 }, { 229,-1309 }, { 230,-1309 }, { 231,-1309 }, { 232,-1309 }, + { 233,-1309 }, { 234,-1309 }, { 235,-1309 }, { 236,-1309 }, { 237,-1309 }, + { 238,-1309 }, { 239,-1309 }, { 240,-1309 }, { 241,-1309 }, { 242,-1309 }, + { 243,-1309 }, { 244,-1309 }, { 245,-1309 }, { 246,-1309 }, { 247,-1309 }, + + { 248,-1309 }, { 249,-1309 }, { 250,-1309 }, { 251,-1309 }, { 252,-1309 }, + { 253,-1309 }, { 254,-1309 }, { 255,-1309 }, { 256,-1309 }, { 0, 47 }, + { 0,9267 }, { 1,-1567 }, { 2,-1567 }, { 3,-1567 }, { 4,-1567 }, + { 5,-1567 }, { 6,-1567 }, { 7,-1567 }, { 8,-1567 }, { 0, 0 }, + { 0, 0 }, { 11,-1567 }, { 0, 0 }, { 0, 0 }, { 14,-1567 }, + { 15,-1567 }, { 16,-1567 }, { 17,-1567 }, { 18,-1567 }, { 19,-1567 }, + { 20,-1567 }, { 21,-1567 }, { 22,-1567 }, { 23,-1567 }, { 24,-1567 }, + { 25,-1567 }, { 26,-1567 }, { 27,-1567 }, { 28,-1567 }, { 29,-1567 }, + { 30,-1567 }, { 31,-1567 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 39,-1567 }, + + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 43,3083 }, { 0, 0 }, + { 45,3083 }, { 0, 0 }, { 0, 0 }, { 48,3142 }, { 49,3142 }, + { 50,3142 }, { 51,3142 }, { 52,3142 }, { 53,3142 }, { 54,3142 }, + { 55,3142 }, { 56,3142 }, { 57,3142 }, { 0, 0 }, { 59,-1567 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 65,-1567 }, { 66,-1567 }, { 67,-1567 }, { 68,-1567 }, { 69,-1567 }, + { 70,-1567 }, { 71,-1567 }, { 72,-1567 }, { 73,-1567 }, { 74,-1567 }, + { 75,-1567 }, { 76,-1567 }, { 77,-1567 }, { 78,-1567 }, { 79,-1567 }, + { 80,-1567 }, { 81,-1567 }, { 82,-1567 }, { 83,-1567 }, { 84,-1567 }, + { 85,-1567 }, { 86,-1567 }, { 87,-1567 }, { 88,-1567 }, { 89,-1567 }, + + { 90,-1567 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 94,-1567 }, + { 95,-1567 }, { 96,-1567 }, { 97,-1567 }, { 98,-1567 }, { 99,-1567 }, + { 100,-1567 }, { 101,-1567 }, { 102,-1567 }, { 103,-1567 }, { 104,-1567 }, + { 105,-1567 }, { 106,-1567 }, { 107,-1567 }, { 108,-1567 }, { 109,-1567 }, + { 110,-1567 }, { 111,-1567 }, { 112,-1567 }, { 113,-1567 }, { 114,-1567 }, + { 115,-1567 }, { 116,-1567 }, { 117,-1567 }, { 118,-1567 }, { 119,-1567 }, + { 120,-1567 }, { 121,-1567 }, { 122,-1567 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 126,-1567 }, { 127,-1567 }, { 128,-1567 }, { 129,-1567 }, + { 130,-1567 }, { 131,-1567 }, { 132,-1567 }, { 133,-1567 }, { 134,-1567 }, + { 135,-1567 }, { 136,-1567 }, { 137,-1567 }, { 138,-1567 }, { 139,-1567 }, + + { 140,-1567 }, { 141,-1567 }, { 142,-1567 }, { 143,-1567 }, { 144,-1567 }, + { 145,-1567 }, { 146,-1567 }, { 147,-1567 }, { 148,-1567 }, { 149,-1567 }, + { 150,-1567 }, { 151,-1567 }, { 152,-1567 }, { 153,-1567 }, { 154,-1567 }, + { 155,-1567 }, { 156,-1567 }, { 157,-1567 }, { 158,-1567 }, { 159,-1567 }, + { 160,-1567 }, { 161,-1567 }, { 162,-1567 }, { 163,-1567 }, { 164,-1567 }, + { 165,-1567 }, { 166,-1567 }, { 167,-1567 }, { 168,-1567 }, { 169,-1567 }, + { 170,-1567 }, { 171,-1567 }, { 172,-1567 }, { 173,-1567 }, { 174,-1567 }, + { 175,-1567 }, { 176,-1567 }, { 177,-1567 }, { 178,-1567 }, { 179,-1567 }, + { 180,-1567 }, { 181,-1567 }, { 182,-1567 }, { 183,-1567 }, { 184,-1567 }, + { 185,-1567 }, { 186,-1567 }, { 187,-1567 }, { 188,-1567 }, { 189,-1567 }, + + { 190,-1567 }, { 191,-1567 }, { 192,-1567 }, { 193,-1567 }, { 194,-1567 }, + { 195,-1567 }, { 196,-1567 }, { 197,-1567 }, { 198,-1567 }, { 199,-1567 }, + { 200,-1567 }, { 201,-1567 }, { 202,-1567 }, { 203,-1567 }, { 204,-1567 }, + { 205,-1567 }, { 206,-1567 }, { 207,-1567 }, { 208,-1567 }, { 209,-1567 }, + { 210,-1567 }, { 211,-1567 }, { 212,-1567 }, { 213,-1567 }, { 214,-1567 }, + { 215,-1567 }, { 216,-1567 }, { 217,-1567 }, { 218,-1567 }, { 219,-1567 }, + { 220,-1567 }, { 221,-1567 }, { 222,-1567 }, { 223,-1567 }, { 224,-1567 }, + { 225,-1567 }, { 226,-1567 }, { 227,-1567 }, { 228,-1567 }, { 229,-1567 }, + { 230,-1567 }, { 231,-1567 }, { 232,-1567 }, { 233,-1567 }, { 234,-1567 }, + { 235,-1567 }, { 236,-1567 }, { 237,-1567 }, { 238,-1567 }, { 239,-1567 }, + + { 240,-1567 }, { 241,-1567 }, { 242,-1567 }, { 243,-1567 }, { 244,-1567 }, + { 245,-1567 }, { 246,-1567 }, { 247,-1567 }, { 248,-1567 }, { 249,-1567 }, + { 250,-1567 }, { 251,-1567 }, { 252,-1567 }, { 253,-1567 }, { 254,-1567 }, + { 255,-1567 }, { 256,-1567 }, { 0, 47 }, { 0,9009 }, { 1,-1825 }, + { 2,-1825 }, { 3,-1825 }, { 4,-1825 }, { 5,-1825 }, { 6,-1825 }, + { 7,-1825 }, { 8,-1825 }, { 0, 0 }, { 0, 0 }, { 11,-1825 }, + { 0, 0 }, { 0, 0 }, { 14,-1825 }, { 15,-1825 }, { 16,-1825 }, + { 17,-1825 }, { 18,-1825 }, { 19,-1825 }, { 20,-1825 }, { 21,-1825 }, + { 22,-1825 }, { 23,-1825 }, { 24,-1825 }, { 25,-1825 }, { 26,-1825 }, + { 27,-1825 }, { 28,-1825 }, { 29,-1825 }, { 30,-1825 }, { 31,-1825 }, + + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 39,-1825 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 48,3142 }, { 49,3142 }, { 50,3142 }, { 51,3142 }, + { 52,3142 }, { 53,3142 }, { 54,3142 }, { 55,3142 }, { 56,-1825 }, + { 57,-1825 }, { 0, 0 }, { 59,-1825 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 65,-1825 }, { 66,-1825 }, + { 67,-1825 }, { 68,-1825 }, { 69,-1825 }, { 70,-1825 }, { 71,-1825 }, + { 72,-1825 }, { 73,-1825 }, { 74,-1825 }, { 75,-1825 }, { 76,-1825 }, + { 77,-1825 }, { 78,-1825 }, { 79,-1825 }, { 80,-1825 }, { 81,-1825 }, + + { 82,-1825 }, { 83,-1825 }, { 84,-1825 }, { 85,-1825 }, { 86,-1825 }, + { 87,-1825 }, { 88,-1825 }, { 89,-1825 }, { 90,-1825 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 94,-1825 }, { 95,-1825 }, { 96,-1825 }, + { 97,-1825 }, { 98,-1825 }, { 99,-1825 }, { 100,-1825 }, { 101,-1825 }, + { 102,-1825 }, { 103,-1825 }, { 104,-1825 }, { 105,-1825 }, { 106,-1825 }, + { 107,-1825 }, { 108,-1825 }, { 109,-1825 }, { 110,-1825 }, { 111,-1825 }, + { 112,-1825 }, { 113,-1825 }, { 114,-1825 }, { 115,-1825 }, { 116,-1825 }, + { 117,-1825 }, { 118,-1825 }, { 119,-1825 }, { 120,-1825 }, { 121,-1825 }, + { 122,-1825 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 126,-1825 }, + { 127,-1825 }, { 128,-1825 }, { 129,-1825 }, { 130,-1825 }, { 131,-1825 }, + + { 132,-1825 }, { 133,-1825 }, { 134,-1825 }, { 135,-1825 }, { 136,-1825 }, + { 137,-1825 }, { 138,-1825 }, { 139,-1825 }, { 140,-1825 }, { 141,-1825 }, + { 142,-1825 }, { 143,-1825 }, { 144,-1825 }, { 145,-1825 }, { 146,-1825 }, + { 147,-1825 }, { 148,-1825 }, { 149,-1825 }, { 150,-1825 }, { 151,-1825 }, + { 152,-1825 }, { 153,-1825 }, { 154,-1825 }, { 155,-1825 }, { 156,-1825 }, + { 157,-1825 }, { 158,-1825 }, { 159,-1825 }, { 160,-1825 }, { 161,-1825 }, + { 162,-1825 }, { 163,-1825 }, { 164,-1825 }, { 165,-1825 }, { 166,-1825 }, + { 167,-1825 }, { 168,-1825 }, { 169,-1825 }, { 170,-1825 }, { 171,-1825 }, + { 172,-1825 }, { 173,-1825 }, { 174,-1825 }, { 175,-1825 }, { 176,-1825 }, + { 177,-1825 }, { 178,-1825 }, { 179,-1825 }, { 180,-1825 }, { 181,-1825 }, + + { 182,-1825 }, { 183,-1825 }, { 184,-1825 }, { 185,-1825 }, { 186,-1825 }, + { 187,-1825 }, { 188,-1825 }, { 189,-1825 }, { 190,-1825 }, { 191,-1825 }, + { 192,-1825 }, { 193,-1825 }, { 194,-1825 }, { 195,-1825 }, { 196,-1825 }, + { 197,-1825 }, { 198,-1825 }, { 199,-1825 }, { 200,-1825 }, { 201,-1825 }, + { 202,-1825 }, { 203,-1825 }, { 204,-1825 }, { 205,-1825 }, { 206,-1825 }, + { 207,-1825 }, { 208,-1825 }, { 209,-1825 }, { 210,-1825 }, { 211,-1825 }, + { 212,-1825 }, { 213,-1825 }, { 214,-1825 }, { 215,-1825 }, { 216,-1825 }, + { 217,-1825 }, { 218,-1825 }, { 219,-1825 }, { 220,-1825 }, { 221,-1825 }, + { 222,-1825 }, { 223,-1825 }, { 224,-1825 }, { 225,-1825 }, { 226,-1825 }, + { 227,-1825 }, { 228,-1825 }, { 229,-1825 }, { 230,-1825 }, { 231,-1825 }, + + { 232,-1825 }, { 233,-1825 }, { 234,-1825 }, { 235,-1825 }, { 236,-1825 }, + { 237,-1825 }, { 238,-1825 }, { 239,-1825 }, { 240,-1825 }, { 241,-1825 }, + { 242,-1825 }, { 243,-1825 }, { 244,-1825 }, { 245,-1825 }, { 246,-1825 }, + { 247,-1825 }, { 248,-1825 }, { 249,-1825 }, { 250,-1825 }, { 251,-1825 }, + { 252,-1825 }, { 253,-1825 }, { 254,-1825 }, { 255,-1825 }, { 256,-1825 }, + { 0, 47 }, { 0,8751 }, { 1,-2083 }, { 2,-2083 }, { 3,-2083 }, + { 4,-2083 }, { 5,-2083 }, { 6,-2083 }, { 7,-2083 }, { 8,-2083 }, + { 0, 0 }, { 0, 0 }, { 11,-2083 }, { 0, 0 }, { 0, 0 }, + { 14,-2083 }, { 15,-2083 }, { 16,-2083 }, { 17,-2083 }, { 18,-2083 }, + { 19,-2083 }, { 20,-2083 }, { 21,-2083 }, { 22,-2083 }, { 23,-2083 }, + + { 24,-2083 }, { 25,-2083 }, { 26,-2083 }, { 27,-2083 }, { 28,-2083 }, + { 29,-2083 }, { 30,-2083 }, { 31,-2083 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 39,-2083 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 48,3142 }, + { 49,3142 }, { 50,3142 }, { 51,3142 }, { 52,3142 }, { 53,3142 }, + { 54,3142 }, { 55,3142 }, { 56,3142 }, { 57,3142 }, { 0, 0 }, + { 59,-2083 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 65,3142 }, { 66,3142 }, { 67,3142 }, { 68,3142 }, + { 69,3142 }, { 70,3142 }, { 71,-2083 }, { 72,-2083 }, { 73,-2083 }, + + { 74,-2083 }, { 75,-2083 }, { 76,-2083 }, { 77,-2083 }, { 78,-2083 }, + { 79,-2083 }, { 80,-2083 }, { 81,-2083 }, { 82,-2083 }, { 83,-2083 }, + { 84,-2083 }, { 85,-2083 }, { 86,-2083 }, { 87,-2083 }, { 88,-2083 }, + { 89,-2083 }, { 90,-2083 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 94,-2083 }, { 95,-2083 }, { 96,-2083 }, { 97,3142 }, { 98,3142 }, + { 99,3142 }, { 100,3142 }, { 101,3142 }, { 102,3142 }, { 103,-2083 }, + { 104,-2083 }, { 105,-2083 }, { 106,-2083 }, { 107,-2083 }, { 108,-2083 }, + { 109,-2083 }, { 110,-2083 }, { 111,-2083 }, { 112,-2083 }, { 113,-2083 }, + { 114,-2083 }, { 115,-2083 }, { 116,-2083 }, { 117,-2083 }, { 118,-2083 }, + { 119,-2083 }, { 120,-2083 }, { 121,-2083 }, { 122,-2083 }, { 0, 0 }, + + { 0, 0 }, { 0, 0 }, { 126,-2083 }, { 127,-2083 }, { 128,-2083 }, + { 129,-2083 }, { 130,-2083 }, { 131,-2083 }, { 132,-2083 }, { 133,-2083 }, + { 134,-2083 }, { 135,-2083 }, { 136,-2083 }, { 137,-2083 }, { 138,-2083 }, + { 139,-2083 }, { 140,-2083 }, { 141,-2083 }, { 142,-2083 }, { 143,-2083 }, + { 144,-2083 }, { 145,-2083 }, { 146,-2083 }, { 147,-2083 }, { 148,-2083 }, + { 149,-2083 }, { 150,-2083 }, { 151,-2083 }, { 152,-2083 }, { 153,-2083 }, + { 154,-2083 }, { 155,-2083 }, { 156,-2083 }, { 157,-2083 }, { 158,-2083 }, + { 159,-2083 }, { 160,-2083 }, { 161,-2083 }, { 162,-2083 }, { 163,-2083 }, + { 164,-2083 }, { 165,-2083 }, { 166,-2083 }, { 167,-2083 }, { 168,-2083 }, + { 169,-2083 }, { 170,-2083 }, { 171,-2083 }, { 172,-2083 }, { 173,-2083 }, + + { 174,-2083 }, { 175,-2083 }, { 176,-2083 }, { 177,-2083 }, { 178,-2083 }, + { 179,-2083 }, { 180,-2083 }, { 181,-2083 }, { 182,-2083 }, { 183,-2083 }, + { 184,-2083 }, { 185,-2083 }, { 186,-2083 }, { 187,-2083 }, { 188,-2083 }, + { 189,-2083 }, { 190,-2083 }, { 191,-2083 }, { 192,-2083 }, { 193,-2083 }, + { 194,-2083 }, { 195,-2083 }, { 196,-2083 }, { 197,-2083 }, { 198,-2083 }, + { 199,-2083 }, { 200,-2083 }, { 201,-2083 }, { 202,-2083 }, { 203,-2083 }, + { 204,-2083 }, { 205,-2083 }, { 206,-2083 }, { 207,-2083 }, { 208,-2083 }, + { 209,-2083 }, { 210,-2083 }, { 211,-2083 }, { 212,-2083 }, { 213,-2083 }, + { 214,-2083 }, { 215,-2083 }, { 216,-2083 }, { 217,-2083 }, { 218,-2083 }, + { 219,-2083 }, { 220,-2083 }, { 221,-2083 }, { 222,-2083 }, { 223,-2083 }, + + { 224,-2083 }, { 225,-2083 }, { 226,-2083 }, { 227,-2083 }, { 228,-2083 }, + { 229,-2083 }, { 230,-2083 }, { 231,-2083 }, { 232,-2083 }, { 233,-2083 }, + { 234,-2083 }, { 235,-2083 }, { 236,-2083 }, { 237,-2083 }, { 238,-2083 }, + { 239,-2083 }, { 240,-2083 }, { 241,-2083 }, { 242,-2083 }, { 243,-2083 }, + { 244,-2083 }, { 245,-2083 }, { 246,-2083 }, { 247,-2083 }, { 248,-2083 }, + { 249,-2083 }, { 250,-2083 }, { 251,-2083 }, { 252,-2083 }, { 253,-2083 }, + { 254,-2083 }, { 255,-2083 }, { 256,-2083 }, { 0, 42 }, { 0,8493 }, + { 1,-1548 }, { 2,-1548 }, { 3,-1548 }, { 4,-1548 }, { 5,-1548 }, + { 6,-1548 }, { 7,-1548 }, { 8,-1548 }, { 0, 0 }, { 0, 0 }, + { 11,-1548 }, { 0, 0 }, { 0, 0 }, { 14,-1548 }, { 15,-1548 }, + + { 16,-1548 }, { 17,-1548 }, { 18,-1548 }, { 19,-1548 }, { 20,-1548 }, + { 21,-1548 }, { 22,-1548 }, { 23,-1548 }, { 24,-1548 }, { 25,-1548 }, + { 26,-1548 }, { 27,-1548 }, { 28,-1548 }, { 29,-1548 }, { 30,-1548 }, + { 31,-1548 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 39,-1548 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 46,-1290 }, { 0, 0 }, { 48, 0 }, { 49, 0 }, { 50, 0 }, + { 51, 0 }, { 52, 0 }, { 53, 0 }, { 54, 0 }, { 55, 0 }, + { 56, 0 }, { 57, 0 }, { 0, 0 }, { 59,-1548 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 65,-1548 }, + + { 66,-1548 }, { 67,-1548 }, { 68,-1548 }, { 69,-774 }, { 70,-1548 }, + { 71,-1548 }, { 72,-1548 }, { 73,-1548 }, { 74,-1548 }, { 75,-1548 }, + { 76,-1548 }, { 77,-1548 }, { 78,-1548 }, { 79,-1548 }, { 80,-1548 }, + { 81,-1548 }, { 82,-1548 }, { 83,-1548 }, { 84,-1548 }, { 85,-1548 }, + { 86,-1548 }, { 87,-1548 }, { 88,-1548 }, { 89,-1548 }, { 90,-1548 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 94,-1548 }, { 95, 258 }, + { 96,-1548 }, { 97,-1548 }, { 98,-1548 }, { 99,-1548 }, { 100,-1548 }, + { 101,-774 }, { 102,-1548 }, { 103,-1548 }, { 104,-1548 }, { 105,-1548 }, + { 106,-1548 }, { 107,-1548 }, { 108,-1548 }, { 109,-1548 }, { 110,-1548 }, + { 111,-1548 }, { 112,-1548 }, { 113,-1548 }, { 114,-1548 }, { 115,-1548 }, + + { 116,-1548 }, { 117,-1548 }, { 118,-1548 }, { 119,-1548 }, { 120,-1548 }, + { 121,-1548 }, { 122,-1548 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 126,-1548 }, { 127,-1548 }, { 128,-1548 }, { 129,-1548 }, { 130,-1548 }, + { 131,-1548 }, { 132,-1548 }, { 133,-1548 }, { 134,-1548 }, { 135,-1548 }, + { 136,-1548 }, { 137,-1548 }, { 138,-1548 }, { 139,-1548 }, { 140,-1548 }, + { 141,-1548 }, { 142,-1548 }, { 143,-1548 }, { 144,-1548 }, { 145,-1548 }, + { 146,-1548 }, { 147,-1548 }, { 148,-1548 }, { 149,-1548 }, { 150,-1548 }, + { 151,-1548 }, { 152,-1548 }, { 153,-1548 }, { 154,-1548 }, { 155,-1548 }, + { 156,-1548 }, { 157,-1548 }, { 158,-1548 }, { 159,-1548 }, { 160,-1548 }, + { 161,-1548 }, { 162,-1548 }, { 163,-1548 }, { 164,-1548 }, { 165,-1548 }, + + { 166,-1548 }, { 167,-1548 }, { 168,-1548 }, { 169,-1548 }, { 170,-1548 }, + { 171,-1548 }, { 172,-1548 }, { 173,-1548 }, { 174,-1548 }, { 175,-1548 }, + { 176,-1548 }, { 177,-1548 }, { 178,-1548 }, { 179,-1548 }, { 180,-1548 }, + { 181,-1548 }, { 182,-1548 }, { 183,-1548 }, { 184,-1548 }, { 185,-1548 }, + { 186,-1548 }, { 187,-1548 }, { 188,-1548 }, { 189,-1548 }, { 190,-1548 }, + { 191,-1548 }, { 192,-1548 }, { 193,-1548 }, { 194,-1548 }, { 195,-1548 }, + { 196,-1548 }, { 197,-1548 }, { 198,-1548 }, { 199,-1548 }, { 200,-1548 }, + { 201,-1548 }, { 202,-1548 }, { 203,-1548 }, { 204,-1548 }, { 205,-1548 }, + { 206,-1548 }, { 207,-1548 }, { 208,-1548 }, { 209,-1548 }, { 210,-1548 }, + { 211,-1548 }, { 212,-1548 }, { 213,-1548 }, { 214,-1548 }, { 215,-1548 }, + + { 216,-1548 }, { 217,-1548 }, { 218,-1548 }, { 219,-1548 }, { 220,-1548 }, + { 221,-1548 }, { 222,-1548 }, { 223,-1548 }, { 224,-1548 }, { 225,-1548 }, + { 226,-1548 }, { 227,-1548 }, { 228,-1548 }, { 229,-1548 }, { 230,-1548 }, + { 231,-1548 }, { 232,-1548 }, { 233,-1548 }, { 234,-1548 }, { 235,-1548 }, + { 236,-1548 }, { 237,-1548 }, { 238,-1548 }, { 239,-1548 }, { 240,-1548 }, + { 241,-1548 }, { 242,-1548 }, { 243,-1548 }, { 244,-1548 }, { 245,-1548 }, + { 246,-1548 }, { 247,-1548 }, { 248,-1548 }, { 249,-1548 }, { 250,-1548 }, + { 251,-1548 }, { 252,-1548 }, { 253,-1548 }, { 254,-1548 }, { 255,-1548 }, + { 256,-1548 }, { 0, 47 }, { 0,8235 }, { 1,-2599 }, { 2,-2599 }, + { 3,-2599 }, { 4,-2599 }, { 5,-2599 }, { 6,-2599 }, { 7,-2599 }, + + { 8,-2599 }, { 0, 0 }, { 0, 0 }, { 11,-2599 }, { 0, 0 }, + { 0, 0 }, { 14,-2599 }, { 15,-2599 }, { 16,-2599 }, { 17,-2599 }, + { 18,-2599 }, { 19,-2599 }, { 20,-2599 }, { 21,-2599 }, { 22,-2599 }, + { 23,-2599 }, { 24,-2599 }, { 25,-2599 }, { 26,-2599 }, { 27,-2599 }, + { 28,-2599 }, { 29,-2599 }, { 30,-2599 }, { 31,-2599 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 39,-2599 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 48,2884 }, { 49,2884 }, { 50,2884 }, { 51,2884 }, { 52,2884 }, + { 53,2884 }, { 54,2884 }, { 55,2884 }, { 56,2884 }, { 57,2884 }, + + { 0, 0 }, { 59,-2599 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 65,-2599 }, { 66,-2599 }, { 67,-2599 }, + { 68,-2599 }, { 69,-2599 }, { 70,-2599 }, { 71,-2599 }, { 72,-2599 }, + { 73,-2599 }, { 74,-2599 }, { 75,-2599 }, { 76,-2599 }, { 77,-2599 }, + { 78,-2599 }, { 79,-2599 }, { 80,-2599 }, { 81,-2599 }, { 82,-2599 }, + { 83,-2599 }, { 84,-2599 }, { 85,-2599 }, { 86,-2599 }, { 87,-2599 }, + { 88,-2599 }, { 89,-2599 }, { 90,-2599 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 94,-2599 }, { 95,-2599 }, { 96,-2599 }, { 97,-2599 }, + { 98,-2599 }, { 99,-2599 }, { 100,-2599 }, { 101,-2599 }, { 102,-2599 }, + { 103,-2599 }, { 104,-2599 }, { 105,-2599 }, { 106,-2599 }, { 107,-2599 }, + + { 108,-2599 }, { 109,-2599 }, { 110,-2599 }, { 111,-2599 }, { 112,-2599 }, + { 113,-2599 }, { 114,-2599 }, { 115,-2599 }, { 116,-2599 }, { 117,-2599 }, + { 118,-2599 }, { 119,-2599 }, { 120,-2599 }, { 121,-2599 }, { 122,-2599 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 126,-2599 }, { 127,-2599 }, + { 128,-2599 }, { 129,-2599 }, { 130,-2599 }, { 131,-2599 }, { 132,-2599 }, + { 133,-2599 }, { 134,-2599 }, { 135,-2599 }, { 136,-2599 }, { 137,-2599 }, + { 138,-2599 }, { 139,-2599 }, { 140,-2599 }, { 141,-2599 }, { 142,-2599 }, + { 143,-2599 }, { 144,-2599 }, { 145,-2599 }, { 146,-2599 }, { 147,-2599 }, + { 148,-2599 }, { 149,-2599 }, { 150,-2599 }, { 151,-2599 }, { 152,-2599 }, + { 153,-2599 }, { 154,-2599 }, { 155,-2599 }, { 156,-2599 }, { 157,-2599 }, + + { 158,-2599 }, { 159,-2599 }, { 160,-2599 }, { 161,-2599 }, { 162,-2599 }, + { 163,-2599 }, { 164,-2599 }, { 165,-2599 }, { 166,-2599 }, { 167,-2599 }, + { 168,-2599 }, { 169,-2599 }, { 170,-2599 }, { 171,-2599 }, { 172,-2599 }, + { 173,-2599 }, { 174,-2599 }, { 175,-2599 }, { 176,-2599 }, { 177,-2599 }, + { 178,-2599 }, { 179,-2599 }, { 180,-2599 }, { 181,-2599 }, { 182,-2599 }, + { 183,-2599 }, { 184,-2599 }, { 185,-2599 }, { 186,-2599 }, { 187,-2599 }, + { 188,-2599 }, { 189,-2599 }, { 190,-2599 }, { 191,-2599 }, { 192,-2599 }, + { 193,-2599 }, { 194,-2599 }, { 195,-2599 }, { 196,-2599 }, { 197,-2599 }, + { 198,-2599 }, { 199,-2599 }, { 200,-2599 }, { 201,-2599 }, { 202,-2599 }, + { 203,-2599 }, { 204,-2599 }, { 205,-2599 }, { 206,-2599 }, { 207,-2599 }, + + { 208,-2599 }, { 209,-2599 }, { 210,-2599 }, { 211,-2599 }, { 212,-2599 }, + { 213,-2599 }, { 214,-2599 }, { 215,-2599 }, { 216,-2599 }, { 217,-2599 }, + { 218,-2599 }, { 219,-2599 }, { 220,-2599 }, { 221,-2599 }, { 222,-2599 }, + { 223,-2599 }, { 224,-2599 }, { 225,-2599 }, { 226,-2599 }, { 227,-2599 }, + { 228,-2599 }, { 229,-2599 }, { 230,-2599 }, { 231,-2599 }, { 232,-2599 }, + { 233,-2599 }, { 234,-2599 }, { 235,-2599 }, { 236,-2599 }, { 237,-2599 }, + { 238,-2599 }, { 239,-2599 }, { 240,-2599 }, { 241,-2599 }, { 242,-2599 }, + { 243,-2599 }, { 244,-2599 }, { 245,-2599 }, { 246,-2599 }, { 247,-2599 }, + { 248,-2599 }, { 249,-2599 }, { 250,-2599 }, { 251,-2599 }, { 252,-2599 }, + { 253,-2599 }, { 254,-2599 }, { 255,-2599 }, { 256,-2599 }, { 0, 20 }, + + { 0,7977 }, { 1, 0 }, { 2, 0 }, { 3, 0 }, { 4, 0 }, + { 5, 0 }, { 6, 0 }, { 7, 0 }, { 8, 0 }, { 9, 0 }, + { 10, 0 }, { 11, 0 }, { 12, 0 }, { 13, 0 }, { 14, 0 }, + { 15, 0 }, { 16, 0 }, { 17, 0 }, { 18, 0 }, { 19, 0 }, + { 20, 0 }, { 21, 0 }, { 22, 0 }, { 23, 0 }, { 24, 0 }, + { 25, 0 }, { 26, 0 }, { 27, 0 }, { 28, 0 }, { 29, 0 }, + { 30, 0 }, { 31, 0 }, { 32, 0 }, { 33, 0 }, { 0, 0 }, + { 35, 0 }, { 36, 0 }, { 37, 0 }, { 38, 0 }, { 39, 0 }, + { 40, 0 }, { 41, 0 }, { 42, 0 }, { 43, 0 }, { 44, 0 }, + { 45, 0 }, { 46, 0 }, { 47, 0 }, { 48, 0 }, { 49, 0 }, + + { 50, 0 }, { 51, 0 }, { 52, 0 }, { 53, 0 }, { 54, 0 }, + { 55, 0 }, { 56, 0 }, { 57, 0 }, { 58, 0 }, { 59, 0 }, + { 60, 0 }, { 61, 0 }, { 62, 0 }, { 63, 0 }, { 64, 0 }, + { 65, 0 }, { 66, 0 }, { 67, 0 }, { 68, 0 }, { 69, 0 }, + { 70, 0 }, { 71, 0 }, { 72, 0 }, { 73, 0 }, { 74, 0 }, + { 75, 0 }, { 76, 0 }, { 77, 0 }, { 78, 0 }, { 79, 0 }, + { 80, 0 }, { 81, 0 }, { 82, 0 }, { 83, 0 }, { 84, 0 }, + { 85, 0 }, { 86, 0 }, { 87, 0 }, { 88, 0 }, { 89, 0 }, + { 90, 0 }, { 91, 0 }, { 0, 0 }, { 93, 0 }, { 94, 0 }, + { 95, 0 }, { 96, 0 }, { 97, 0 }, { 98, 0 }, { 99, 0 }, + + { 100, 0 }, { 101, 0 }, { 102, 0 }, { 103, 0 }, { 104, 0 }, + { 105, 0 }, { 106, 0 }, { 107, 0 }, { 108, 0 }, { 109, 0 }, + { 110, 0 }, { 111, 0 }, { 112, 0 }, { 113, 0 }, { 114, 0 }, + { 115, 0 }, { 116, 0 }, { 117, 0 }, { 118, 0 }, { 119, 0 }, + { 120, 0 }, { 121, 0 }, { 122, 0 }, { 123, 0 }, { 124, 0 }, + { 125, 0 }, { 126, 0 }, { 127, 0 }, { 128, 0 }, { 129, 0 }, + { 130, 0 }, { 131, 0 }, { 132, 0 }, { 133, 0 }, { 134, 0 }, + { 135, 0 }, { 136, 0 }, { 137, 0 }, { 138, 0 }, { 139, 0 }, + { 140, 0 }, { 141, 0 }, { 142, 0 }, { 143, 0 }, { 144, 0 }, + { 145, 0 }, { 146, 0 }, { 147, 0 }, { 148, 0 }, { 149, 0 }, + + { 150, 0 }, { 151, 0 }, { 152, 0 }, { 153, 0 }, { 154, 0 }, + { 155, 0 }, { 156, 0 }, { 157, 0 }, { 158, 0 }, { 159, 0 }, + { 160, 0 }, { 161, 0 }, { 162, 0 }, { 163, 0 }, { 164, 0 }, + { 165, 0 }, { 166, 0 }, { 167, 0 }, { 168, 0 }, { 169, 0 }, + { 170, 0 }, { 171, 0 }, { 172, 0 }, { 173, 0 }, { 174, 0 }, + { 175, 0 }, { 176, 0 }, { 177, 0 }, { 178, 0 }, { 179, 0 }, + { 180, 0 }, { 181, 0 }, { 182, 0 }, { 183, 0 }, { 184, 0 }, + { 185, 0 }, { 186, 0 }, { 187, 0 }, { 188, 0 }, { 189, 0 }, + { 190, 0 }, { 191, 0 }, { 192, 0 }, { 193, 0 }, { 194, 0 }, + { 195, 0 }, { 196, 0 }, { 197, 0 }, { 198, 0 }, { 199, 0 }, + + { 200, 0 }, { 201, 0 }, { 202, 0 }, { 203, 0 }, { 204, 0 }, + { 205, 0 }, { 206, 0 }, { 207, 0 }, { 208, 0 }, { 209, 0 }, + { 210, 0 }, { 211, 0 }, { 212, 0 }, { 213, 0 }, { 214, 0 }, + { 215, 0 }, { 216, 0 }, { 217, 0 }, { 218, 0 }, { 219, 0 }, + { 220, 0 }, { 221, 0 }, { 222, 0 }, { 223, 0 }, { 224, 0 }, + { 225, 0 }, { 226, 0 }, { 227, 0 }, { 228, 0 }, { 229, 0 }, + { 230, 0 }, { 231, 0 }, { 232, 0 }, { 233, 0 }, { 234, 0 }, + { 235, 0 }, { 236, 0 }, { 237, 0 }, { 238, 0 }, { 239, 0 }, + { 240, 0 }, { 241, 0 }, { 242, 0 }, { 243, 0 }, { 244, 0 }, + { 245, 0 }, { 246, 0 }, { 247, 0 }, { 248, 0 }, { 249, 0 }, + + { 250, 0 }, { 251, 0 }, { 252, 0 }, { 253, 0 }, { 254, 0 }, + { 255, 0 }, { 256, 0 }, { 0, 13 }, { 0,7719 }, { 0, 15 }, + { 0,7717 }, { 0, 13 }, { 0,7715 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 14 }, { 0,7681 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 48,2626 }, { 49,2626 }, { 50,2626 }, { 51,2626 }, + { 52,2626 }, { 53,2626 }, { 54,2626 }, { 55,2626 }, { 56,2626 }, + { 57,2626 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 65,2626 }, { 66,2626 }, + { 67,2626 }, { 68,2626 }, { 69,2626 }, { 70,2626 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 48,2649 }, + { 49,2649 }, { 50,2649 }, { 51,2649 }, { 52,2649 }, { 53,2649 }, + + { 54,2649 }, { 55,2649 }, { 56,2649 }, { 57,2649 }, { 0, 0 }, + { 97,2626 }, { 98,2626 }, { 99,2626 }, { 100,2626 }, { 101,2626 }, + { 102,2626 }, { 65,2649 }, { 66,2649 }, { 67,2649 }, { 68,2649 }, + { 69,2649 }, { 70,2649 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 117,7421 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 123,2649 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 125,-3086 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 97,2649 }, { 98,2649 }, + { 99,2649 }, { 100,2649 }, { 101,2649 }, { 102,2649 }, { 0, 1 }, + + { 0,7577 }, { 1, 0 }, { 2, 0 }, { 3, 0 }, { 4, 0 }, + { 5, 0 }, { 6, 0 }, { 7, 0 }, { 8, 0 }, { 0, 0 }, + { 0, 0 }, { 11, 0 }, { 0, 0 }, { 0, 0 }, { 14, 0 }, + { 15, 0 }, { 16, 0 }, { 17, 0 }, { 18, 0 }, { 19, 0 }, + { 20, 0 }, { 21, 0 }, { 22, 0 }, { 23, 0 }, { 24, 0 }, + { 25, 0 }, { 26, 0 }, { 27, 0 }, { 28, 0 }, { 29, 0 }, + { 30, 0 }, { 31, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 39, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 48, 0 }, { 49, 0 }, + + { 50, 0 }, { 51, 0 }, { 52, 0 }, { 53, 0 }, { 54, 0 }, + { 55, 0 }, { 56, 0 }, { 57, 0 }, { 0, 0 }, { 59, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 65, 0 }, { 66, 0 }, { 67, 0 }, { 68, 0 }, { 69, 0 }, + { 70, 0 }, { 71, 0 }, { 72, 0 }, { 73, 0 }, { 74, 0 }, + { 75, 0 }, { 76, 0 }, { 77, 0 }, { 78, 0 }, { 79, 0 }, + { 80, 0 }, { 81, 0 }, { 82, 0 }, { 83, 0 }, { 84, 0 }, + { 85, 0 }, { 86, 0 }, { 87, 0 }, { 88, 0 }, { 89, 0 }, + { 90, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 94, 0 }, + { 95, 0 }, { 96, 0 }, { 97, 0 }, { 98, 0 }, { 99, 0 }, + + { 100, 0 }, { 101, 0 }, { 102, 0 }, { 103, 0 }, { 104, 0 }, + { 105, 0 }, { 106, 0 }, { 107, 0 }, { 108, 0 }, { 109, 0 }, + { 110, 0 }, { 111, 0 }, { 112, 0 }, { 113, 0 }, { 114, 0 }, + { 115, 0 }, { 116, 0 }, { 117, 0 }, { 118, 0 }, { 119, 0 }, + { 120, 0 }, { 121, 0 }, { 122, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 126, 0 }, { 127, 0 }, { 128, 0 }, { 129, 0 }, + { 130, 0 }, { 131, 0 }, { 132, 0 }, { 133, 0 }, { 134, 0 }, + { 135, 0 }, { 136, 0 }, { 137, 0 }, { 138, 0 }, { 139, 0 }, + { 140, 0 }, { 141, 0 }, { 142, 0 }, { 143, 0 }, { 144, 0 }, + { 145, 0 }, { 146, 0 }, { 147, 0 }, { 148, 0 }, { 149, 0 }, + + { 150, 0 }, { 151, 0 }, { 152, 0 }, { 153, 0 }, { 154, 0 }, + { 155, 0 }, { 156, 0 }, { 157, 0 }, { 158, 0 }, { 159, 0 }, + { 160, 0 }, { 161, 0 }, { 162, 0 }, { 163, 0 }, { 164, 0 }, + { 165, 0 }, { 166, 0 }, { 167, 0 }, { 168, 0 }, { 169, 0 }, + { 170, 0 }, { 171, 0 }, { 172, 0 }, { 173, 0 }, { 174, 0 }, + { 175, 0 }, { 176, 0 }, { 177, 0 }, { 178, 0 }, { 179, 0 }, + { 180, 0 }, { 181, 0 }, { 182, 0 }, { 183, 0 }, { 184, 0 }, + { 185, 0 }, { 186, 0 }, { 187, 0 }, { 188, 0 }, { 189, 0 }, + { 190, 0 }, { 191, 0 }, { 192, 0 }, { 193, 0 }, { 194, 0 }, + { 195, 0 }, { 196, 0 }, { 197, 0 }, { 198, 0 }, { 199, 0 }, + + { 200, 0 }, { 201, 0 }, { 202, 0 }, { 203, 0 }, { 204, 0 }, + { 205, 0 }, { 206, 0 }, { 207, 0 }, { 208, 0 }, { 209, 0 }, + { 210, 0 }, { 211, 0 }, { 212, 0 }, { 213, 0 }, { 214, 0 }, + { 215, 0 }, { 216, 0 }, { 217, 0 }, { 218, 0 }, { 219, 0 }, + { 220, 0 }, { 221, 0 }, { 222, 0 }, { 223, 0 }, { 224, 0 }, + { 225, 0 }, { 226, 0 }, { 227, 0 }, { 228, 0 }, { 229, 0 }, + { 230, 0 }, { 231, 0 }, { 232, 0 }, { 233, 0 }, { 234, 0 }, + { 235, 0 }, { 236, 0 }, { 237, 0 }, { 238, 0 }, { 239, 0 }, + { 240, 0 }, { 241, 0 }, { 242, 0 }, { 243, 0 }, { 244, 0 }, + { 245, 0 }, { 246, 0 }, { 247, 0 }, { 248, 0 }, { 249, 0 }, + + { 250, 0 }, { 251, 0 }, { 252, 0 }, { 253, 0 }, { 254, 0 }, + { 255, 0 }, { 256, 0 }, { 0, 2 }, { 0,7319 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 9, 0 }, { 10, 0 }, { 0, 0 }, + { 12, 0 }, { 13, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 32, 0 }, { 0, 22 }, { 0,7285 }, { 1, 0 }, { 2, 0 }, + { 3, 0 }, { 4, 0 }, { 5, 0 }, { 6, 0 }, { 7, 0 }, + + { 8, 0 }, { 9, 0 }, { 10, 0 }, { 11, 0 }, { 12, 0 }, + { 13, 0 }, { 14, 0 }, { 15, 0 }, { 16, 0 }, { 17, 0 }, + { 18, 0 }, { 19, 0 }, { 20, 0 }, { 21, 0 }, { 22, 0 }, + { 23, 0 }, { 24, 0 }, { 25, 0 }, { 26, 0 }, { 27, 0 }, + { 28, 0 }, { 29, 0 }, { 30, 0 }, { 31, 0 }, { 32, 0 }, + { 33, 0 }, { 34, 0 }, { 35, 0 }, { 36, 0 }, { 37, 0 }, + { 38, 0 }, { 39, 0 }, { 40, 0 }, { 41, 0 }, { 0, 0 }, + { 43, 0 }, { 44, 0 }, { 45, 0 }, { 46, 0 }, { 47, 0 }, + { 48, 0 }, { 49, 0 }, { 50, 0 }, { 51, 0 }, { 52, 0 }, + { 53, 0 }, { 54, 0 }, { 55, 0 }, { 56, 0 }, { 57, 0 }, + + { 58, 0 }, { 59, 0 }, { 60, 0 }, { 61, 0 }, { 62, 0 }, + { 63, 0 }, { 64, 0 }, { 65, 0 }, { 66, 0 }, { 67, 0 }, + { 68, 0 }, { 69, 0 }, { 70, 0 }, { 71, 0 }, { 72, 0 }, + { 73, 0 }, { 74, 0 }, { 75, 0 }, { 76, 0 }, { 77, 0 }, + { 78, 0 }, { 79, 0 }, { 80, 0 }, { 81, 0 }, { 82, 0 }, + { 83, 0 }, { 84, 0 }, { 85, 0 }, { 86, 0 }, { 87, 0 }, + { 88, 0 }, { 89, 0 }, { 90, 0 }, { 91, 0 }, { 92, 0 }, + { 93, 0 }, { 94, 0 }, { 95, 0 }, { 96, 0 }, { 97, 0 }, + { 98, 0 }, { 99, 0 }, { 100, 0 }, { 101, 0 }, { 102, 0 }, + { 103, 0 }, { 104, 0 }, { 105, 0 }, { 106, 0 }, { 107, 0 }, + + { 108, 0 }, { 109, 0 }, { 110, 0 }, { 111, 0 }, { 112, 0 }, + { 113, 0 }, { 114, 0 }, { 115, 0 }, { 116, 0 }, { 117, 0 }, + { 118, 0 }, { 119, 0 }, { 120, 0 }, { 121, 0 }, { 122, 0 }, + { 123, 0 }, { 124, 0 }, { 125, 0 }, { 126, 0 }, { 127, 0 }, + { 128, 0 }, { 129, 0 }, { 130, 0 }, { 131, 0 }, { 132, 0 }, + { 133, 0 }, { 134, 0 }, { 135, 0 }, { 136, 0 }, { 137, 0 }, + { 138, 0 }, { 139, 0 }, { 140, 0 }, { 141, 0 }, { 142, 0 }, + { 143, 0 }, { 144, 0 }, { 145, 0 }, { 146, 0 }, { 147, 0 }, + { 148, 0 }, { 149, 0 }, { 150, 0 }, { 151, 0 }, { 152, 0 }, + { 153, 0 }, { 154, 0 }, { 155, 0 }, { 156, 0 }, { 157, 0 }, + + { 158, 0 }, { 159, 0 }, { 160, 0 }, { 161, 0 }, { 162, 0 }, + { 163, 0 }, { 164, 0 }, { 165, 0 }, { 166, 0 }, { 167, 0 }, + { 168, 0 }, { 169, 0 }, { 170, 0 }, { 171, 0 }, { 172, 0 }, + { 173, 0 }, { 174, 0 }, { 175, 0 }, { 176, 0 }, { 177, 0 }, + { 178, 0 }, { 179, 0 }, { 180, 0 }, { 181, 0 }, { 182, 0 }, + { 183, 0 }, { 184, 0 }, { 185, 0 }, { 186, 0 }, { 187, 0 }, + { 188, 0 }, { 189, 0 }, { 190, 0 }, { 191, 0 }, { 192, 0 }, + { 193, 0 }, { 194, 0 }, { 195, 0 }, { 196, 0 }, { 197, 0 }, + { 198, 0 }, { 199, 0 }, { 200, 0 }, { 201, 0 }, { 202, 0 }, + { 203, 0 }, { 204, 0 }, { 205, 0 }, { 206, 0 }, { 207, 0 }, + + { 208, 0 }, { 209, 0 }, { 210, 0 }, { 211, 0 }, { 212, 0 }, + { 213, 0 }, { 214, 0 }, { 215, 0 }, { 216, 0 }, { 217, 0 }, + { 218, 0 }, { 219, 0 }, { 220, 0 }, { 221, 0 }, { 222, 0 }, + { 223, 0 }, { 224, 0 }, { 225, 0 }, { 226, 0 }, { 227, 0 }, + { 228, 0 }, { 229, 0 }, { 230, 0 }, { 231, 0 }, { 232, 0 }, + { 233, 0 }, { 234, 0 }, { 235, 0 }, { 236, 0 }, { 237, 0 }, + { 238, 0 }, { 239, 0 }, { 240, 0 }, { 241, 0 }, { 242, 0 }, + { 243, 0 }, { 244, 0 }, { 245, 0 }, { 246, 0 }, { 247, 0 }, + { 248, 0 }, { 249, 0 }, { 250, 0 }, { 251, 0 }, { 252, 0 }, + { 253, 0 }, { 254, 0 }, { 255, 0 }, { 256, 0 }, { 0, 41 }, + + { 0,7027 }, { 1,-4076 }, { 2,-4076 }, { 3,-4076 }, { 4,-4076 }, + { 5,-4076 }, { 6,-4076 }, { 7,-4076 }, { 8,-4076 }, { 0, 0 }, + { 0, 0 }, { 11,-4076 }, { 0, 0 }, { 0, 0 }, { 14,-4076 }, + { 15,-4076 }, { 16,-4076 }, { 17,-4076 }, { 18,-4076 }, { 19,-4076 }, + { 20,-4076 }, { 21,-4076 }, { 22,-4076 }, { 23,-4076 }, { 24,-4076 }, + { 25,-4076 }, { 26,-4076 }, { 27,-4076 }, { 28,-4076 }, { 29,-4076 }, + { 30,-4076 }, { 31,-4076 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 39,-4076 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 48, 0 }, { 49, 0 }, + + { 50, 0 }, { 51, 0 }, { 52, 0 }, { 53, 0 }, { 54, 0 }, + { 55, 0 }, { 56, 0 }, { 57, 0 }, { 0, 0 }, { 59,-4076 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 65,-4076 }, { 66,-4076 }, { 67,-4076 }, { 68,-4076 }, { 69, 258 }, + { 70,-4076 }, { 71,-4076 }, { 72,-4076 }, { 73,-4076 }, { 74,-4076 }, + { 75,-4076 }, { 76,-4076 }, { 77,-4076 }, { 78,-4076 }, { 79,-4076 }, + { 80,-4076 }, { 81,-4076 }, { 82,-4076 }, { 83,-4076 }, { 84,-4076 }, + { 85,-4076 }, { 86,-4076 }, { 87,-4076 }, { 88,-4076 }, { 89,-4076 }, + { 90,-4076 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 94,-4076 }, + { 95, 268 }, { 96,-4076 }, { 97,-4076 }, { 98,-4076 }, { 99,-4076 }, + + { 100,-4076 }, { 101, 258 }, { 102,-4076 }, { 103,-4076 }, { 104,-4076 }, + { 105,-4076 }, { 106,-4076 }, { 107,-4076 }, { 108,-4076 }, { 109,-4076 }, + { 110,-4076 }, { 111,-4076 }, { 112,-4076 }, { 113,-4076 }, { 114,-4076 }, + { 115,-4076 }, { 116,-4076 }, { 117,-4076 }, { 118,-4076 }, { 119,-4076 }, + { 120,-4076 }, { 121,-4076 }, { 122,-4076 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 126,-4076 }, { 127,-4076 }, { 128,-4076 }, { 129,-4076 }, + { 130,-4076 }, { 131,-4076 }, { 132,-4076 }, { 133,-4076 }, { 134,-4076 }, + { 135,-4076 }, { 136,-4076 }, { 137,-4076 }, { 138,-4076 }, { 139,-4076 }, + { 140,-4076 }, { 141,-4076 }, { 142,-4076 }, { 143,-4076 }, { 144,-4076 }, + { 145,-4076 }, { 146,-4076 }, { 147,-4076 }, { 148,-4076 }, { 149,-4076 }, + + { 150,-4076 }, { 151,-4076 }, { 152,-4076 }, { 153,-4076 }, { 154,-4076 }, + { 155,-4076 }, { 156,-4076 }, { 157,-4076 }, { 158,-4076 }, { 159,-4076 }, + { 160,-4076 }, { 161,-4076 }, { 162,-4076 }, { 163,-4076 }, { 164,-4076 }, + { 165,-4076 }, { 166,-4076 }, { 167,-4076 }, { 168,-4076 }, { 169,-4076 }, + { 170,-4076 }, { 171,-4076 }, { 172,-4076 }, { 173,-4076 }, { 174,-4076 }, + { 175,-4076 }, { 176,-4076 }, { 177,-4076 }, { 178,-4076 }, { 179,-4076 }, + { 180,-4076 }, { 181,-4076 }, { 182,-4076 }, { 183,-4076 }, { 184,-4076 }, + { 185,-4076 }, { 186,-4076 }, { 187,-4076 }, { 188,-4076 }, { 189,-4076 }, + { 190,-4076 }, { 191,-4076 }, { 192,-4076 }, { 193,-4076 }, { 194,-4076 }, + { 195,-4076 }, { 196,-4076 }, { 197,-4076 }, { 198,-4076 }, { 199,-4076 }, + + { 200,-4076 }, { 201,-4076 }, { 202,-4076 }, { 203,-4076 }, { 204,-4076 }, + { 205,-4076 }, { 206,-4076 }, { 207,-4076 }, { 208,-4076 }, { 209,-4076 }, + { 210,-4076 }, { 211,-4076 }, { 212,-4076 }, { 213,-4076 }, { 214,-4076 }, + { 215,-4076 }, { 216,-4076 }, { 217,-4076 }, { 218,-4076 }, { 219,-4076 }, + { 220,-4076 }, { 221,-4076 }, { 222,-4076 }, { 223,-4076 }, { 224,-4076 }, + { 225,-4076 }, { 226,-4076 }, { 227,-4076 }, { 228,-4076 }, { 229,-4076 }, + { 230,-4076 }, { 231,-4076 }, { 232,-4076 }, { 233,-4076 }, { 234,-4076 }, + { 235,-4076 }, { 236,-4076 }, { 237,-4076 }, { 238,-4076 }, { 239,-4076 }, + { 240,-4076 }, { 241,-4076 }, { 242,-4076 }, { 243,-4076 }, { 244,-4076 }, + { 245,-4076 }, { 246,-4076 }, { 247,-4076 }, { 248,-4076 }, { 249,-4076 }, + + { 250,-4076 }, { 251,-4076 }, { 252,-4076 }, { 253,-4076 }, { 254,-4076 }, + { 255,-4076 }, { 256,-4076 }, { 0, 48 }, { 0,6769 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 48 }, { 0,6759 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + + { 0, 0 }, { 43, 585 }, { 0, 0 }, { 45, 585 }, { 0, 0 }, + { 0, 0 }, { 48,1841 }, { 49,1841 }, { 50,1841 }, { 51,1841 }, + { 52,1841 }, { 53,1841 }, { 54,1841 }, { 55,1841 }, { 56,1841 }, + { 57,1841 }, { 48,2089 }, { 49,2089 }, { 50,2089 }, { 51,2089 }, + { 52,2089 }, { 53,2089 }, { 54,2089 }, { 55,2089 }, { 56,2089 }, + { 57,2089 }, { 0, 41 }, { 0,6700 }, { 1,-4403 }, { 2,-4403 }, + { 3,-4403 }, { 4,-4403 }, { 5,-4403 }, { 6,-4403 }, { 7,-4403 }, + { 8,-4403 }, { 0, 0 }, { 0, 0 }, { 11,-4403 }, { 0, 0 }, + { 0, 0 }, { 14,-4403 }, { 15,-4403 }, { 16,-4403 }, { 17,-4403 }, + { 18,-4403 }, { 19,-4403 }, { 20,-4403 }, { 21,-4403 }, { 22,-4403 }, + + { 23,-4403 }, { 24,-4403 }, { 25,-4403 }, { 26,-4403 }, { 27,-4403 }, + { 28,-4403 }, { 29,-4403 }, { 30,-4403 }, { 31,-4403 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 39,-4403 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 48,2288 }, { 49,2288 }, { 50,2288 }, { 51,2288 }, { 52,2288 }, + { 53,2288 }, { 54,2288 }, { 55,2288 }, { 56,2288 }, { 57,2288 }, + { 0, 0 }, { 59,-4403 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 65,-4403 }, { 66,-4403 }, { 67,-4403 }, + { 68,-4403 }, { 69, -69 }, { 70,-4403 }, { 71,-4403 }, { 72,-4403 }, + + { 73,-4403 }, { 74,-4403 }, { 75,-4403 }, { 76,-4403 }, { 77,-4403 }, + { 78,-4403 }, { 79,-4403 }, { 80,-4403 }, { 81,-4403 }, { 82,-4403 }, + { 83,-4403 }, { 84,-4403 }, { 85,-4403 }, { 86,-4403 }, { 87,-4403 }, + { 88,-4403 }, { 89,-4403 }, { 90,-4403 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 94,-4403 }, { 95,2546 }, { 96,-4403 }, { 97,-4403 }, + { 98,-4403 }, { 99,-4403 }, { 100,-4403 }, { 101, -69 }, { 102,-4403 }, + { 103,-4403 }, { 104,-4403 }, { 105,-4403 }, { 106,-4403 }, { 107,-4403 }, + { 108,-4403 }, { 109,-4403 }, { 110,-4403 }, { 111,-4403 }, { 112,-4403 }, + { 113,-4403 }, { 114,-4403 }, { 115,-4403 }, { 116,-4403 }, { 117,-4403 }, + { 118,-4403 }, { 119,-4403 }, { 120,-4403 }, { 121,-4403 }, { 122,-4403 }, + + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 126,-4403 }, { 127,-4403 }, + { 128,-4403 }, { 129,-4403 }, { 130,-4403 }, { 131,-4403 }, { 132,-4403 }, + { 133,-4403 }, { 134,-4403 }, { 135,-4403 }, { 136,-4403 }, { 137,-4403 }, + { 138,-4403 }, { 139,-4403 }, { 140,-4403 }, { 141,-4403 }, { 142,-4403 }, + { 143,-4403 }, { 144,-4403 }, { 145,-4403 }, { 146,-4403 }, { 147,-4403 }, + { 148,-4403 }, { 149,-4403 }, { 150,-4403 }, { 151,-4403 }, { 152,-4403 }, + { 153,-4403 }, { 154,-4403 }, { 155,-4403 }, { 156,-4403 }, { 157,-4403 }, + { 158,-4403 }, { 159,-4403 }, { 160,-4403 }, { 161,-4403 }, { 162,-4403 }, + { 163,-4403 }, { 164,-4403 }, { 165,-4403 }, { 166,-4403 }, { 167,-4403 }, + { 168,-4403 }, { 169,-4403 }, { 170,-4403 }, { 171,-4403 }, { 172,-4403 }, + + { 173,-4403 }, { 174,-4403 }, { 175,-4403 }, { 176,-4403 }, { 177,-4403 }, + { 178,-4403 }, { 179,-4403 }, { 180,-4403 }, { 181,-4403 }, { 182,-4403 }, + { 183,-4403 }, { 184,-4403 }, { 185,-4403 }, { 186,-4403 }, { 187,-4403 }, + { 188,-4403 }, { 189,-4403 }, { 190,-4403 }, { 191,-4403 }, { 192,-4403 }, + { 193,-4403 }, { 194,-4403 }, { 195,-4403 }, { 196,-4403 }, { 197,-4403 }, + { 198,-4403 }, { 199,-4403 }, { 200,-4403 }, { 201,-4403 }, { 202,-4403 }, + { 203,-4403 }, { 204,-4403 }, { 205,-4403 }, { 206,-4403 }, { 207,-4403 }, + { 208,-4403 }, { 209,-4403 }, { 210,-4403 }, { 211,-4403 }, { 212,-4403 }, + { 213,-4403 }, { 214,-4403 }, { 215,-4403 }, { 216,-4403 }, { 217,-4403 }, + { 218,-4403 }, { 219,-4403 }, { 220,-4403 }, { 221,-4403 }, { 222,-4403 }, + + { 223,-4403 }, { 224,-4403 }, { 225,-4403 }, { 226,-4403 }, { 227,-4403 }, + { 228,-4403 }, { 229,-4403 }, { 230,-4403 }, { 231,-4403 }, { 232,-4403 }, + { 233,-4403 }, { 234,-4403 }, { 235,-4403 }, { 236,-4403 }, { 237,-4403 }, + { 238,-4403 }, { 239,-4403 }, { 240,-4403 }, { 241,-4403 }, { 242,-4403 }, + { 243,-4403 }, { 244,-4403 }, { 245,-4403 }, { 246,-4403 }, { 247,-4403 }, + { 248,-4403 }, { 249,-4403 }, { 250,-4403 }, { 251,-4403 }, { 252,-4403 }, + { 253,-4403 }, { 254,-4403 }, { 255,-4403 }, { 256,-4403 }, { 0, 45 }, + { 0,6442 }, { 1,-4392 }, { 2,-4392 }, { 3,-4392 }, { 4,-4392 }, + { 5,-4392 }, { 6,-4392 }, { 7,-4392 }, { 8,-4392 }, { 0, 0 }, + { 0, 0 }, { 11,-4392 }, { 0, 0 }, { 0, 0 }, { 14,-4392 }, + + { 15,-4392 }, { 16,-4392 }, { 17,-4392 }, { 18,-4392 }, { 19,-4392 }, + { 20,-4392 }, { 21,-4392 }, { 22,-4392 }, { 23,-4392 }, { 24,-4392 }, + { 25,-4392 }, { 26,-4392 }, { 27,-4392 }, { 28,-4392 }, { 29,-4392 }, + { 30,-4392 }, { 31,-4392 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 39,-4392 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 48,2347 }, { 49,2347 }, + { 50,-4392 }, { 51,-4392 }, { 52,-4392 }, { 53,-4392 }, { 54,-4392 }, + { 55,-4392 }, { 56,-4392 }, { 57,-4392 }, { 0, 0 }, { 59,-4392 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + + { 65,-4392 }, { 66,-4392 }, { 67,-4392 }, { 68,-4392 }, { 69,-4392 }, + { 70,-4392 }, { 71,-4392 }, { 72,-4392 }, { 73,-4392 }, { 74,-4392 }, + { 75,-4392 }, { 76,-4392 }, { 77,-4392 }, { 78,-4392 }, { 79,-4392 }, + { 80,-4392 }, { 81,-4392 }, { 82,-4392 }, { 83,-4392 }, { 84,-4392 }, + { 85,-4392 }, { 86,-4392 }, { 87,-4392 }, { 88,-4392 }, { 89,-4392 }, + { 90,-4392 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 94,-4392 }, + { 95,2605 }, { 96,-4392 }, { 97,-4392 }, { 98,-4392 }, { 99,-4392 }, + { 100,-4392 }, { 101,-4392 }, { 102,-4392 }, { 103,-4392 }, { 104,-4392 }, + { 105,-4392 }, { 106,-4392 }, { 107,-4392 }, { 108,-4392 }, { 109,-4392 }, + { 110,-4392 }, { 111,-4392 }, { 112,-4392 }, { 113,-4392 }, { 114,-4392 }, + + { 115,-4392 }, { 116,-4392 }, { 117,-4392 }, { 118,-4392 }, { 119,-4392 }, + { 120,-4392 }, { 121,-4392 }, { 122,-4392 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 126,-4392 }, { 127,-4392 }, { 128,-4392 }, { 129,-4392 }, + { 130,-4392 }, { 131,-4392 }, { 132,-4392 }, { 133,-4392 }, { 134,-4392 }, + { 135,-4392 }, { 136,-4392 }, { 137,-4392 }, { 138,-4392 }, { 139,-4392 }, + { 140,-4392 }, { 141,-4392 }, { 142,-4392 }, { 143,-4392 }, { 144,-4392 }, + { 145,-4392 }, { 146,-4392 }, { 147,-4392 }, { 148,-4392 }, { 149,-4392 }, + { 150,-4392 }, { 151,-4392 }, { 152,-4392 }, { 153,-4392 }, { 154,-4392 }, + { 155,-4392 }, { 156,-4392 }, { 157,-4392 }, { 158,-4392 }, { 159,-4392 }, + { 160,-4392 }, { 161,-4392 }, { 162,-4392 }, { 163,-4392 }, { 164,-4392 }, + + { 165,-4392 }, { 166,-4392 }, { 167,-4392 }, { 168,-4392 }, { 169,-4392 }, + { 170,-4392 }, { 171,-4392 }, { 172,-4392 }, { 173,-4392 }, { 174,-4392 }, + { 175,-4392 }, { 176,-4392 }, { 177,-4392 }, { 178,-4392 }, { 179,-4392 }, + { 180,-4392 }, { 181,-4392 }, { 182,-4392 }, { 183,-4392 }, { 184,-4392 }, + { 185,-4392 }, { 186,-4392 }, { 187,-4392 }, { 188,-4392 }, { 189,-4392 }, + { 190,-4392 }, { 191,-4392 }, { 192,-4392 }, { 193,-4392 }, { 194,-4392 }, + { 195,-4392 }, { 196,-4392 }, { 197,-4392 }, { 198,-4392 }, { 199,-4392 }, + { 200,-4392 }, { 201,-4392 }, { 202,-4392 }, { 203,-4392 }, { 204,-4392 }, + { 205,-4392 }, { 206,-4392 }, { 207,-4392 }, { 208,-4392 }, { 209,-4392 }, + { 210,-4392 }, { 211,-4392 }, { 212,-4392 }, { 213,-4392 }, { 214,-4392 }, + + { 215,-4392 }, { 216,-4392 }, { 217,-4392 }, { 218,-4392 }, { 219,-4392 }, + { 220,-4392 }, { 221,-4392 }, { 222,-4392 }, { 223,-4392 }, { 224,-4392 }, + { 225,-4392 }, { 226,-4392 }, { 227,-4392 }, { 228,-4392 }, { 229,-4392 }, + { 230,-4392 }, { 231,-4392 }, { 232,-4392 }, { 233,-4392 }, { 234,-4392 }, + { 235,-4392 }, { 236,-4392 }, { 237,-4392 }, { 238,-4392 }, { 239,-4392 }, + { 240,-4392 }, { 241,-4392 }, { 242,-4392 }, { 243,-4392 }, { 244,-4392 }, + { 245,-4392 }, { 246,-4392 }, { 247,-4392 }, { 248,-4392 }, { 249,-4392 }, + { 250,-4392 }, { 251,-4392 }, { 252,-4392 }, { 253,-4392 }, { 254,-4392 }, + { 255,-4392 }, { 256,-4392 }, { 0, 46 }, { 0,6184 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 48,1256 }, { 49,1256 }, { 50,1256 }, { 51,1256 }, + { 52,1256 }, { 53,1256 }, { 54,1256 }, { 55,1256 }, { 56,1256 }, + + { 57,1256 }, { 0, 40 }, { 0,6125 }, { 1,2546 }, { 2,2546 }, + { 3,2546 }, { 4,2546 }, { 5,2546 }, { 6,2546 }, { 7,2546 }, + { 8,2546 }, { 0, 0 }, { 0, 0 }, { 11,2546 }, { 0, 0 }, + { 0, 0 }, { 14,2546 }, { 15,2546 }, { 16,2546 }, { 17,2546 }, + { 18,2546 }, { 19,2546 }, { 20,2546 }, { 21,2546 }, { 22,2546 }, + { 23,2546 }, { 24,2546 }, { 25,2546 }, { 26,2546 }, { 27,2546 }, + { 28,2546 }, { 29,2546 }, { 30,2546 }, { 31,2546 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 39,2546 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + + { 48,2804 }, { 49,2804 }, { 50,2804 }, { 51,2804 }, { 52,2804 }, + { 53,2804 }, { 54,2804 }, { 55,2804 }, { 56,2804 }, { 57,2804 }, + { 0, 0 }, { 59,2546 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 65,2546 }, { 66,2546 }, { 67,2546 }, + { 68,2546 }, { 69,2546 }, { 70,2546 }, { 71,2546 }, { 72,2546 }, + { 73,2546 }, { 74,2546 }, { 75,2546 }, { 76,2546 }, { 77,2546 }, + { 78,2546 }, { 79,2546 }, { 80,2546 }, { 81,2546 }, { 82,2546 }, + { 83,2546 }, { 84,2546 }, { 85,2546 }, { 86,2546 }, { 87,2546 }, + { 88,2546 }, { 89,2546 }, { 90,2546 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 94,2546 }, { 95,3062 }, { 96,2546 }, { 97,2546 }, + + { 98,2546 }, { 99,2546 }, { 100,2546 }, { 101,2546 }, { 102,2546 }, + { 103,2546 }, { 104,2546 }, { 105,2546 }, { 106,2546 }, { 107,2546 }, + { 108,2546 }, { 109,2546 }, { 110,2546 }, { 111,2546 }, { 112,2546 }, + { 113,2546 }, { 114,2546 }, { 115,2546 }, { 116,2546 }, { 117,2546 }, + { 118,2546 }, { 119,2546 }, { 120,2546 }, { 121,2546 }, { 122,2546 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 126,2546 }, { 127,2546 }, + { 128,2546 }, { 129,2546 }, { 130,2546 }, { 131,2546 }, { 132,2546 }, + { 133,2546 }, { 134,2546 }, { 135,2546 }, { 136,2546 }, { 137,2546 }, + { 138,2546 }, { 139,2546 }, { 140,2546 }, { 141,2546 }, { 142,2546 }, + { 143,2546 }, { 144,2546 }, { 145,2546 }, { 146,2546 }, { 147,2546 }, + + { 148,2546 }, { 149,2546 }, { 150,2546 }, { 151,2546 }, { 152,2546 }, + { 153,2546 }, { 154,2546 }, { 155,2546 }, { 156,2546 }, { 157,2546 }, + { 158,2546 }, { 159,2546 }, { 160,2546 }, { 161,2546 }, { 162,2546 }, + { 163,2546 }, { 164,2546 }, { 165,2546 }, { 166,2546 }, { 167,2546 }, + { 168,2546 }, { 169,2546 }, { 170,2546 }, { 171,2546 }, { 172,2546 }, + { 173,2546 }, { 174,2546 }, { 175,2546 }, { 176,2546 }, { 177,2546 }, + { 178,2546 }, { 179,2546 }, { 180,2546 }, { 181,2546 }, { 182,2546 }, + { 183,2546 }, { 184,2546 }, { 185,2546 }, { 186,2546 }, { 187,2546 }, + { 188,2546 }, { 189,2546 }, { 190,2546 }, { 191,2546 }, { 192,2546 }, + { 193,2546 }, { 194,2546 }, { 195,2546 }, { 196,2546 }, { 197,2546 }, + + { 198,2546 }, { 199,2546 }, { 200,2546 }, { 201,2546 }, { 202,2546 }, + { 203,2546 }, { 204,2546 }, { 205,2546 }, { 206,2546 }, { 207,2546 }, + { 208,2546 }, { 209,2546 }, { 210,2546 }, { 211,2546 }, { 212,2546 }, + { 213,2546 }, { 214,2546 }, { 215,2546 }, { 216,2546 }, { 217,2546 }, + { 218,2546 }, { 219,2546 }, { 220,2546 }, { 221,2546 }, { 222,2546 }, + { 223,2546 }, { 224,2546 }, { 225,2546 }, { 226,2546 }, { 227,2546 }, + { 228,2546 }, { 229,2546 }, { 230,2546 }, { 231,2546 }, { 232,2546 }, + { 233,2546 }, { 234,2546 }, { 235,2546 }, { 236,2546 }, { 237,2546 }, + { 238,2546 }, { 239,2546 }, { 240,2546 }, { 241,2546 }, { 242,2546 }, + { 243,2546 }, { 244,2546 }, { 245,2546 }, { 246,2546 }, { 247,2546 }, + + { 248,2546 }, { 249,2546 }, { 250,2546 }, { 251,2546 }, { 252,2546 }, + { 253,2546 }, { 254,2546 }, { 255,2546 }, { 256,2546 }, { 0, 44 }, + { 0,5867 }, { 1,-4967 }, { 2,-4967 }, { 3,-4967 }, { 4,-4967 }, + { 5,-4967 }, { 6,-4967 }, { 7,-4967 }, { 8,-4967 }, { 0, 0 }, + { 0, 0 }, { 11,-4967 }, { 0, 0 }, { 0, 0 }, { 14,-4967 }, + { 15,-4967 }, { 16,-4967 }, { 17,-4967 }, { 18,-4967 }, { 19,-4967 }, + { 20,-4967 }, { 21,-4967 }, { 22,-4967 }, { 23,-4967 }, { 24,-4967 }, + { 25,-4967 }, { 26,-4967 }, { 27,-4967 }, { 28,-4967 }, { 29,-4967 }, + { 30,-4967 }, { 31,-4967 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 39,-4967 }, + + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 48,3062 }, { 49,3062 }, + { 50,3062 }, { 51,3062 }, { 52,3062 }, { 53,3062 }, { 54,3062 }, + { 55,3062 }, { 56,-4967 }, { 57,-4967 }, { 0, 0 }, { 59,-4967 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 65,-4967 }, { 66,-4967 }, { 67,-4967 }, { 68,-4967 }, { 69,-4967 }, + { 70,-4967 }, { 71,-4967 }, { 72,-4967 }, { 73,-4967 }, { 74,-4967 }, + { 75,-4967 }, { 76,-4967 }, { 77,-4967 }, { 78,-4967 }, { 79,-4967 }, + { 80,-4967 }, { 81,-4967 }, { 82,-4967 }, { 83,-4967 }, { 84,-4967 }, + { 85,-4967 }, { 86,-4967 }, { 87,-4967 }, { 88,-4967 }, { 89,-4967 }, + + { 90,-4967 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 94,-4967 }, + { 95,3320 }, { 96,-4967 }, { 97,-4967 }, { 98,-4967 }, { 99,-4967 }, + { 100,-4967 }, { 101,-4967 }, { 102,-4967 }, { 103,-4967 }, { 104,-4967 }, + { 105,-4967 }, { 106,-4967 }, { 107,-4967 }, { 108,-4967 }, { 109,-4967 }, + { 110,-4967 }, { 111,-4967 }, { 112,-4967 }, { 113,-4967 }, { 114,-4967 }, + { 115,-4967 }, { 116,-4967 }, { 117,-4967 }, { 118,-4967 }, { 119,-4967 }, + { 120,-4967 }, { 121,-4967 }, { 122,-4967 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 126,-4967 }, { 127,-4967 }, { 128,-4967 }, { 129,-4967 }, + { 130,-4967 }, { 131,-4967 }, { 132,-4967 }, { 133,-4967 }, { 134,-4967 }, + { 135,-4967 }, { 136,-4967 }, { 137,-4967 }, { 138,-4967 }, { 139,-4967 }, + + { 140,-4967 }, { 141,-4967 }, { 142,-4967 }, { 143,-4967 }, { 144,-4967 }, + { 145,-4967 }, { 146,-4967 }, { 147,-4967 }, { 148,-4967 }, { 149,-4967 }, + { 150,-4967 }, { 151,-4967 }, { 152,-4967 }, { 153,-4967 }, { 154,-4967 }, + { 155,-4967 }, { 156,-4967 }, { 157,-4967 }, { 158,-4967 }, { 159,-4967 }, + { 160,-4967 }, { 161,-4967 }, { 162,-4967 }, { 163,-4967 }, { 164,-4967 }, + { 165,-4967 }, { 166,-4967 }, { 167,-4967 }, { 168,-4967 }, { 169,-4967 }, + { 170,-4967 }, { 171,-4967 }, { 172,-4967 }, { 173,-4967 }, { 174,-4967 }, + { 175,-4967 }, { 176,-4967 }, { 177,-4967 }, { 178,-4967 }, { 179,-4967 }, + { 180,-4967 }, { 181,-4967 }, { 182,-4967 }, { 183,-4967 }, { 184,-4967 }, + { 185,-4967 }, { 186,-4967 }, { 187,-4967 }, { 188,-4967 }, { 189,-4967 }, + + { 190,-4967 }, { 191,-4967 }, { 192,-4967 }, { 193,-4967 }, { 194,-4967 }, + { 195,-4967 }, { 196,-4967 }, { 197,-4967 }, { 198,-4967 }, { 199,-4967 }, + { 200,-4967 }, { 201,-4967 }, { 202,-4967 }, { 203,-4967 }, { 204,-4967 }, + { 205,-4967 }, { 206,-4967 }, { 207,-4967 }, { 208,-4967 }, { 209,-4967 }, + { 210,-4967 }, { 211,-4967 }, { 212,-4967 }, { 213,-4967 }, { 214,-4967 }, + { 215,-4967 }, { 216,-4967 }, { 217,-4967 }, { 218,-4967 }, { 219,-4967 }, + { 220,-4967 }, { 221,-4967 }, { 222,-4967 }, { 223,-4967 }, { 224,-4967 }, + { 225,-4967 }, { 226,-4967 }, { 227,-4967 }, { 228,-4967 }, { 229,-4967 }, + { 230,-4967 }, { 231,-4967 }, { 232,-4967 }, { 233,-4967 }, { 234,-4967 }, + { 235,-4967 }, { 236,-4967 }, { 237,-4967 }, { 238,-4967 }, { 239,-4967 }, + + { 240,-4967 }, { 241,-4967 }, { 242,-4967 }, { 243,-4967 }, { 244,-4967 }, + { 245,-4967 }, { 246,-4967 }, { 247,-4967 }, { 248,-4967 }, { 249,-4967 }, + { 250,-4967 }, { 251,-4967 }, { 252,-4967 }, { 253,-4967 }, { 254,-4967 }, + { 255,-4967 }, { 256,-4967 }, { 0, 43 }, { 0,5609 }, { 1,-5225 }, + { 2,-5225 }, { 3,-5225 }, { 4,-5225 }, { 5,-5225 }, { 6,-5225 }, + { 7,-5225 }, { 8,-5225 }, { 0, 0 }, { 0, 0 }, { 11,-5225 }, + { 0, 0 }, { 0, 0 }, { 14,-5225 }, { 15,-5225 }, { 16,-5225 }, + { 17,-5225 }, { 18,-5225 }, { 19,-5225 }, { 20,-5225 }, { 21,-5225 }, + { 22,-5225 }, { 23,-5225 }, { 24,-5225 }, { 25,-5225 }, { 26,-5225 }, + { 27,-5225 }, { 28,-5225 }, { 29,-5225 }, { 30,-5225 }, { 31,-5225 }, + + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 39,-5225 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 48,3320 }, { 49,3320 }, { 50,3320 }, { 51,3320 }, + { 52,3320 }, { 53,3320 }, { 54,3320 }, { 55,3320 }, { 56,3320 }, + { 57,3320 }, { 0, 0 }, { 59,-5225 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 65,3320 }, { 66,3320 }, + { 67,3320 }, { 68,3320 }, { 69,3320 }, { 70,3320 }, { 71,-5225 }, + { 72,-5225 }, { 73,-5225 }, { 74,-5225 }, { 75,-5225 }, { 76,-5225 }, + { 77,-5225 }, { 78,-5225 }, { 79,-5225 }, { 80,-5225 }, { 81,-5225 }, + + { 82,-5225 }, { 83,-5225 }, { 84,-5225 }, { 85,-5225 }, { 86,-5225 }, + { 87,-5225 }, { 88,-5225 }, { 89,-5225 }, { 90,-5225 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 94,-5225 }, { 95,3578 }, { 96,-5225 }, + { 97,3320 }, { 98,3320 }, { 99,3320 }, { 100,3320 }, { 101,3320 }, + { 102,3320 }, { 103,-5225 }, { 104,-5225 }, { 105,-5225 }, { 106,-5225 }, + { 107,-5225 }, { 108,-5225 }, { 109,-5225 }, { 110,-5225 }, { 111,-5225 }, + { 112,-5225 }, { 113,-5225 }, { 114,-5225 }, { 115,-5225 }, { 116,-5225 }, + { 117,-5225 }, { 118,-5225 }, { 119,-5225 }, { 120,-5225 }, { 121,-5225 }, + { 122,-5225 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 126,-5225 }, + { 127,-5225 }, { 128,-5225 }, { 129,-5225 }, { 130,-5225 }, { 131,-5225 }, + + { 132,-5225 }, { 133,-5225 }, { 134,-5225 }, { 135,-5225 }, { 136,-5225 }, + { 137,-5225 }, { 138,-5225 }, { 139,-5225 }, { 140,-5225 }, { 141,-5225 }, + { 142,-5225 }, { 143,-5225 }, { 144,-5225 }, { 145,-5225 }, { 146,-5225 }, + { 147,-5225 }, { 148,-5225 }, { 149,-5225 }, { 150,-5225 }, { 151,-5225 }, + { 152,-5225 }, { 153,-5225 }, { 154,-5225 }, { 155,-5225 }, { 156,-5225 }, + { 157,-5225 }, { 158,-5225 }, { 159,-5225 }, { 160,-5225 }, { 161,-5225 }, + { 162,-5225 }, { 163,-5225 }, { 164,-5225 }, { 165,-5225 }, { 166,-5225 }, + { 167,-5225 }, { 168,-5225 }, { 169,-5225 }, { 170,-5225 }, { 171,-5225 }, + { 172,-5225 }, { 173,-5225 }, { 174,-5225 }, { 175,-5225 }, { 176,-5225 }, + { 177,-5225 }, { 178,-5225 }, { 179,-5225 }, { 180,-5225 }, { 181,-5225 }, + + { 182,-5225 }, { 183,-5225 }, { 184,-5225 }, { 185,-5225 }, { 186,-5225 }, + { 187,-5225 }, { 188,-5225 }, { 189,-5225 }, { 190,-5225 }, { 191,-5225 }, + { 192,-5225 }, { 193,-5225 }, { 194,-5225 }, { 195,-5225 }, { 196,-5225 }, + { 197,-5225 }, { 198,-5225 }, { 199,-5225 }, { 200,-5225 }, { 201,-5225 }, + { 202,-5225 }, { 203,-5225 }, { 204,-5225 }, { 205,-5225 }, { 206,-5225 }, + { 207,-5225 }, { 208,-5225 }, { 209,-5225 }, { 210,-5225 }, { 211,-5225 }, + { 212,-5225 }, { 213,-5225 }, { 214,-5225 }, { 215,-5225 }, { 216,-5225 }, + { 217,-5225 }, { 218,-5225 }, { 219,-5225 }, { 220,-5225 }, { 221,-5225 }, + { 222,-5225 }, { 223,-5225 }, { 224,-5225 }, { 225,-5225 }, { 226,-5225 }, + { 227,-5225 }, { 228,-5225 }, { 229,-5225 }, { 230,-5225 }, { 231,-5225 }, + + { 232,-5225 }, { 233,-5225 }, { 234,-5225 }, { 235,-5225 }, { 236,-5225 }, + { 237,-5225 }, { 238,-5225 }, { 239,-5225 }, { 240,-5225 }, { 241,-5225 }, + { 242,-5225 }, { 243,-5225 }, { 244,-5225 }, { 245,-5225 }, { 246,-5225 }, + { 247,-5225 }, { 248,-5225 }, { 249,-5225 }, { 250,-5225 }, { 251,-5225 }, + { 252,-5225 }, { 253,-5225 }, { 254,-5225 }, { 255,-5225 }, { 256,-5225 }, + { 0, 42 }, { 0,5351 }, { 1,-4690 }, { 2,-4690 }, { 3,-4690 }, + { 4,-4690 }, { 5,-4690 }, { 6,-4690 }, { 7,-4690 }, { 8,-4690 }, + { 0, 0 }, { 0, 0 }, { 11,-4690 }, { 0, 0 }, { 0, 0 }, + { 14,-4690 }, { 15,-4690 }, { 16,-4690 }, { 17,-4690 }, { 18,-4690 }, + { 19,-4690 }, { 20,-4690 }, { 21,-4690 }, { 22,-4690 }, { 23,-4690 }, + + { 24,-4690 }, { 25,-4690 }, { 26,-4690 }, { 27,-4690 }, { 28,-4690 }, + { 29,-4690 }, { 30,-4690 }, { 31,-4690 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 39,-4690 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 46,-4432 }, { 0, 0 }, { 48,-3142 }, + { 49,-3142 }, { 50,-3142 }, { 51,-3142 }, { 52,-3142 }, { 53,-3142 }, + { 54,-3142 }, { 55,-3142 }, { 56,-3142 }, { 57,-3142 }, { 0, 0 }, + { 59,-4690 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 65,-4690 }, { 66,-4690 }, { 67,-4690 }, { 68,-4690 }, + { 69,-3916 }, { 70,-4690 }, { 71,-4690 }, { 72,-4690 }, { 73,-4690 }, + + { 74,-4690 }, { 75,-4690 }, { 76,-4690 }, { 77,-4690 }, { 78,-4690 }, + { 79,-4690 }, { 80,-4690 }, { 81,-4690 }, { 82,-4690 }, { 83,-4690 }, + { 84,-4690 }, { 85,-4690 }, { 86,-4690 }, { 87,-4690 }, { 88,-4690 }, + { 89,-4690 }, { 90,-4690 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 94,-4690 }, { 95,-2884 }, { 96,-4690 }, { 97,-4690 }, { 98,-4690 }, + { 99,-4690 }, { 100,-4690 }, { 101,-3916 }, { 102,-4690 }, { 103,-4690 }, + { 104,-4690 }, { 105,-4690 }, { 106,-4690 }, { 107,-4690 }, { 108,-4690 }, + { 109,-4690 }, { 110,-4690 }, { 111,-4690 }, { 112,-4690 }, { 113,-4690 }, + { 114,-4690 }, { 115,-4690 }, { 116,-4690 }, { 117,-4690 }, { 118,-4690 }, + { 119,-4690 }, { 120,-4690 }, { 121,-4690 }, { 122,-4690 }, { 0, 0 }, + + { 0, 0 }, { 0, 0 }, { 126,-4690 }, { 127,-4690 }, { 128,-4690 }, + { 129,-4690 }, { 130,-4690 }, { 131,-4690 }, { 132,-4690 }, { 133,-4690 }, + { 134,-4690 }, { 135,-4690 }, { 136,-4690 }, { 137,-4690 }, { 138,-4690 }, + { 139,-4690 }, { 140,-4690 }, { 141,-4690 }, { 142,-4690 }, { 143,-4690 }, + { 144,-4690 }, { 145,-4690 }, { 146,-4690 }, { 147,-4690 }, { 148,-4690 }, + { 149,-4690 }, { 150,-4690 }, { 151,-4690 }, { 152,-4690 }, { 153,-4690 }, + { 154,-4690 }, { 155,-4690 }, { 156,-4690 }, { 157,-4690 }, { 158,-4690 }, + { 159,-4690 }, { 160,-4690 }, { 161,-4690 }, { 162,-4690 }, { 163,-4690 }, + { 164,-4690 }, { 165,-4690 }, { 166,-4690 }, { 167,-4690 }, { 168,-4690 }, + { 169,-4690 }, { 170,-4690 }, { 171,-4690 }, { 172,-4690 }, { 173,-4690 }, + + { 174,-4690 }, { 175,-4690 }, { 176,-4690 }, { 177,-4690 }, { 178,-4690 }, + { 179,-4690 }, { 180,-4690 }, { 181,-4690 }, { 182,-4690 }, { 183,-4690 }, + { 184,-4690 }, { 185,-4690 }, { 186,-4690 }, { 187,-4690 }, { 188,-4690 }, + { 189,-4690 }, { 190,-4690 }, { 191,-4690 }, { 192,-4690 }, { 193,-4690 }, + { 194,-4690 }, { 195,-4690 }, { 196,-4690 }, { 197,-4690 }, { 198,-4690 }, + { 199,-4690 }, { 200,-4690 }, { 201,-4690 }, { 202,-4690 }, { 203,-4690 }, + { 204,-4690 }, { 205,-4690 }, { 206,-4690 }, { 207,-4690 }, { 208,-4690 }, + { 209,-4690 }, { 210,-4690 }, { 211,-4690 }, { 212,-4690 }, { 213,-4690 }, + { 214,-4690 }, { 215,-4690 }, { 216,-4690 }, { 217,-4690 }, { 218,-4690 }, + { 219,-4690 }, { 220,-4690 }, { 221,-4690 }, { 222,-4690 }, { 223,-4690 }, + + { 224,-4690 }, { 225,-4690 }, { 226,-4690 }, { 227,-4690 }, { 228,-4690 }, + { 229,-4690 }, { 230,-4690 }, { 231,-4690 }, { 232,-4690 }, { 233,-4690 }, + { 234,-4690 }, { 235,-4690 }, { 236,-4690 }, { 237,-4690 }, { 238,-4690 }, + { 239,-4690 }, { 240,-4690 }, { 241,-4690 }, { 242,-4690 }, { 243,-4690 }, + { 244,-4690 }, { 245,-4690 }, { 246,-4690 }, { 247,-4690 }, { 248,-4690 }, + { 249,-4690 }, { 250,-4690 }, { 251,-4690 }, { 252,-4690 }, { 253,-4690 }, + { 254,-4690 }, { 255,-4690 }, { 256,-4690 }, { 0, 13 }, { 0,5093 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 13 }, { 0,5070 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 48,3320 }, { 49,3320 }, { 50,3320 }, + { 51,3320 }, { 52,3320 }, { 53,3320 }, { 54,3320 }, { 55,3320 }, + { 56,3320 }, { 57,3320 }, { 0, 0 }, { 0, 0 }, { 0, 14 }, + { 0,5032 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 65,3320 }, + + { 66,3320 }, { 67,3320 }, { 68,3320 }, { 69,3320 }, { 70,3320 }, + { 48,3320 }, { 49,3320 }, { 50,3320 }, { 51,3320 }, { 52,3320 }, + { 53,3320 }, { 54,3320 }, { 55,3320 }, { 56,3320 }, { 57,3320 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 65,3320 }, { 66,3320 }, { 67,3320 }, + { 68,3320 }, { 69,3320 }, { 70,3320 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 97,3320 }, { 98,3320 }, { 99,3320 }, { 100,3320 }, + { 101,3320 }, { 102,3320 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 48,-6069 }, { 49,-6069 }, + { 50,-6069 }, { 51,-6069 }, { 52,-6069 }, { 53,-6069 }, { 54,-6069 }, + + { 55,-6069 }, { 56,-6069 }, { 57,-6069 }, { 0, 0 }, { 97,3320 }, + { 98,3320 }, { 99,3320 }, { 100,3320 }, { 101,3320 }, { 102,3320 }, + { 65,-6069 }, { 66,-6069 }, { 67,-6069 }, { 68,-6069 }, { 69,-6069 }, + { 70,-6069 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 97,-6069 }, { 98,-6069 }, { 99,-6069 }, + { 100,-6069 }, { 101,-6069 }, { 102,-6069 }, { 0, 40 }, { 0,4928 }, + + { 1,-6171 }, { 2,-6171 }, { 3,-6171 }, { 4,-6171 }, { 5,-6171 }, + { 6,-6171 }, { 7,-6171 }, { 8,-6171 }, { 0, 0 }, { 0, 0 }, + { 11,-6171 }, { 0, 0 }, { 0, 0 }, { 14,-6171 }, { 15,-6171 }, + { 16,-6171 }, { 17,-6171 }, { 18,-6171 }, { 19,-6171 }, { 20,-6171 }, + { 21,-6171 }, { 22,-6171 }, { 23,-6171 }, { 24,-6171 }, { 25,-6171 }, + { 26,-6171 }, { 27,-6171 }, { 28,-6171 }, { 29,-6171 }, { 30,-6171 }, + { 31,-6171 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 39,-6171 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 48,3290 }, { 49,3290 }, { 50,3290 }, + + { 51,3290 }, { 52,3290 }, { 53,3290 }, { 54,3290 }, { 55,3290 }, + { 56,3290 }, { 57,3290 }, { 0, 0 }, { 59,-6171 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 65,-6171 }, + { 66,-6171 }, { 67,-6171 }, { 68,-6171 }, { 69,-6171 }, { 70,-6171 }, + { 71,-6171 }, { 72,-6171 }, { 73,-6171 }, { 74,-6171 }, { 75,-6171 }, + { 76,-6171 }, { 77,-6171 }, { 78,-6171 }, { 79,-6171 }, { 80,-6171 }, + { 81,-6171 }, { 82,-6171 }, { 83,-6171 }, { 84,-6171 }, { 85,-6171 }, + { 86,-6171 }, { 87,-6171 }, { 88,-6171 }, { 89,-6171 }, { 90,-6171 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 94,-6171 }, { 95,3548 }, + { 96,-6171 }, { 97,-6171 }, { 98,-6171 }, { 99,-6171 }, { 100,-6171 }, + + { 101,-6171 }, { 102,-6171 }, { 103,-6171 }, { 104,-6171 }, { 105,-6171 }, + { 106,-6171 }, { 107,-6171 }, { 108,-6171 }, { 109,-6171 }, { 110,-6171 }, + { 111,-6171 }, { 112,-6171 }, { 113,-6171 }, { 114,-6171 }, { 115,-6171 }, + { 116,-6171 }, { 117,-6171 }, { 118,-6171 }, { 119,-6171 }, { 120,-6171 }, + { 121,-6171 }, { 122,-6171 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 126,-6171 }, { 127,-6171 }, { 128,-6171 }, { 129,-6171 }, { 130,-6171 }, + { 131,-6171 }, { 132,-6171 }, { 133,-6171 }, { 134,-6171 }, { 135,-6171 }, + { 136,-6171 }, { 137,-6171 }, { 138,-6171 }, { 139,-6171 }, { 140,-6171 }, + { 141,-6171 }, { 142,-6171 }, { 143,-6171 }, { 144,-6171 }, { 145,-6171 }, + { 146,-6171 }, { 147,-6171 }, { 148,-6171 }, { 149,-6171 }, { 150,-6171 }, + + { 151,-6171 }, { 152,-6171 }, { 153,-6171 }, { 154,-6171 }, { 155,-6171 }, + { 156,-6171 }, { 157,-6171 }, { 158,-6171 }, { 159,-6171 }, { 160,-6171 }, + { 161,-6171 }, { 162,-6171 }, { 163,-6171 }, { 164,-6171 }, { 165,-6171 }, + { 166,-6171 }, { 167,-6171 }, { 168,-6171 }, { 169,-6171 }, { 170,-6171 }, + { 171,-6171 }, { 172,-6171 }, { 173,-6171 }, { 174,-6171 }, { 175,-6171 }, + { 176,-6171 }, { 177,-6171 }, { 178,-6171 }, { 179,-6171 }, { 180,-6171 }, + { 181,-6171 }, { 182,-6171 }, { 183,-6171 }, { 184,-6171 }, { 185,-6171 }, + { 186,-6171 }, { 187,-6171 }, { 188,-6171 }, { 189,-6171 }, { 190,-6171 }, + { 191,-6171 }, { 192,-6171 }, { 193,-6171 }, { 194,-6171 }, { 195,-6171 }, + { 196,-6171 }, { 197,-6171 }, { 198,-6171 }, { 199,-6171 }, { 200,-6171 }, + + { 201,-6171 }, { 202,-6171 }, { 203,-6171 }, { 204,-6171 }, { 205,-6171 }, + { 206,-6171 }, { 207,-6171 }, { 208,-6171 }, { 209,-6171 }, { 210,-6171 }, + { 211,-6171 }, { 212,-6171 }, { 213,-6171 }, { 214,-6171 }, { 215,-6171 }, + { 216,-6171 }, { 217,-6171 }, { 218,-6171 }, { 219,-6171 }, { 220,-6171 }, + { 221,-6171 }, { 222,-6171 }, { 223,-6171 }, { 224,-6171 }, { 225,-6171 }, + { 226,-6171 }, { 227,-6171 }, { 228,-6171 }, { 229,-6171 }, { 230,-6171 }, + { 231,-6171 }, { 232,-6171 }, { 233,-6171 }, { 234,-6171 }, { 235,-6171 }, + { 236,-6171 }, { 237,-6171 }, { 238,-6171 }, { 239,-6171 }, { 240,-6171 }, + { 241,-6171 }, { 242,-6171 }, { 243,-6171 }, { 244,-6171 }, { 245,-6171 }, + { 246,-6171 }, { 247,-6171 }, { 248,-6171 }, { 249,-6171 }, { 250,-6171 }, + + { 251,-6171 }, { 252,-6171 }, { 253,-6171 }, { 254,-6171 }, { 255,-6171 }, + { 256,-6171 }, { 0, 41 }, { 0,4670 }, { 1,-6433 }, { 2,-6433 }, + { 3,-6433 }, { 4,-6433 }, { 5,-6433 }, { 6,-6433 }, { 7,-6433 }, + { 8,-6433 }, { 0, 0 }, { 0, 0 }, { 11,-6433 }, { 0, 0 }, + { 0, 0 }, { 14,-6433 }, { 15,-6433 }, { 16,-6433 }, { 17,-6433 }, + { 18,-6433 }, { 19,-6433 }, { 20,-6433 }, { 21,-6433 }, { 22,-6433 }, + { 23,-6433 }, { 24,-6433 }, { 25,-6433 }, { 26,-6433 }, { 27,-6433 }, + { 28,-6433 }, { 29,-6433 }, { 30,-6433 }, { 31,-6433 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 39,-6433 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 48,-2357 }, { 49,-2357 }, { 50,-2357 }, { 51,-2357 }, { 52,-2357 }, + { 53,-2357 }, { 54,-2357 }, { 55,-2357 }, { 56,-2357 }, { 57,-2357 }, + { 0, 0 }, { 59,-6433 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 65,-6433 }, { 66,-6433 }, { 67,-6433 }, + { 68,-6433 }, { 69,-2099 }, { 70,-6433 }, { 71,-6433 }, { 72,-6433 }, + { 73,-6433 }, { 74,-6433 }, { 75,-6433 }, { 76,-6433 }, { 77,-6433 }, + { 78,-6433 }, { 79,-6433 }, { 80,-6433 }, { 81,-6433 }, { 82,-6433 }, + { 83,-6433 }, { 84,-6433 }, { 85,-6433 }, { 86,-6433 }, { 87,-6433 }, + { 88,-6433 }, { 89,-6433 }, { 90,-6433 }, { 0, 0 }, { 0, 0 }, + + { 0, 0 }, { 94,-6433 }, { 95,-2089 }, { 96,-6433 }, { 97,-6433 }, + { 98,-6433 }, { 99,-6433 }, { 100,-6433 }, { 101,-2099 }, { 102,-6433 }, + { 103,-6433 }, { 104,-6433 }, { 105,-6433 }, { 106,-6433 }, { 107,-6433 }, + { 108,-6433 }, { 109,-6433 }, { 110,-6433 }, { 111,-6433 }, { 112,-6433 }, + { 113,-6433 }, { 114,-6433 }, { 115,-6433 }, { 116,-6433 }, { 117,-6433 }, + { 118,-6433 }, { 119,-6433 }, { 120,-6433 }, { 121,-6433 }, { 122,-6433 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 126,-6433 }, { 127,-6433 }, + { 128,-6433 }, { 129,-6433 }, { 130,-6433 }, { 131,-6433 }, { 132,-6433 }, + { 133,-6433 }, { 134,-6433 }, { 135,-6433 }, { 136,-6433 }, { 137,-6433 }, + { 138,-6433 }, { 139,-6433 }, { 140,-6433 }, { 141,-6433 }, { 142,-6433 }, + + { 143,-6433 }, { 144,-6433 }, { 145,-6433 }, { 146,-6433 }, { 147,-6433 }, + { 148,-6433 }, { 149,-6433 }, { 150,-6433 }, { 151,-6433 }, { 152,-6433 }, + { 153,-6433 }, { 154,-6433 }, { 155,-6433 }, { 156,-6433 }, { 157,-6433 }, + { 158,-6433 }, { 159,-6433 }, { 160,-6433 }, { 161,-6433 }, { 162,-6433 }, + { 163,-6433 }, { 164,-6433 }, { 165,-6433 }, { 166,-6433 }, { 167,-6433 }, + { 168,-6433 }, { 169,-6433 }, { 170,-6433 }, { 171,-6433 }, { 172,-6433 }, + { 173,-6433 }, { 174,-6433 }, { 175,-6433 }, { 176,-6433 }, { 177,-6433 }, + { 178,-6433 }, { 179,-6433 }, { 180,-6433 }, { 181,-6433 }, { 182,-6433 }, + { 183,-6433 }, { 184,-6433 }, { 185,-6433 }, { 186,-6433 }, { 187,-6433 }, + { 188,-6433 }, { 189,-6433 }, { 190,-6433 }, { 191,-6433 }, { 192,-6433 }, + + { 193,-6433 }, { 194,-6433 }, { 195,-6433 }, { 196,-6433 }, { 197,-6433 }, + { 198,-6433 }, { 199,-6433 }, { 200,-6433 }, { 201,-6433 }, { 202,-6433 }, + { 203,-6433 }, { 204,-6433 }, { 205,-6433 }, { 206,-6433 }, { 207,-6433 }, + { 208,-6433 }, { 209,-6433 }, { 210,-6433 }, { 211,-6433 }, { 212,-6433 }, + { 213,-6433 }, { 214,-6433 }, { 215,-6433 }, { 216,-6433 }, { 217,-6433 }, + { 218,-6433 }, { 219,-6433 }, { 220,-6433 }, { 221,-6433 }, { 222,-6433 }, + { 223,-6433 }, { 224,-6433 }, { 225,-6433 }, { 226,-6433 }, { 227,-6433 }, + { 228,-6433 }, { 229,-6433 }, { 230,-6433 }, { 231,-6433 }, { 232,-6433 }, + { 233,-6433 }, { 234,-6433 }, { 235,-6433 }, { 236,-6433 }, { 237,-6433 }, + { 238,-6433 }, { 239,-6433 }, { 240,-6433 }, { 241,-6433 }, { 242,-6433 }, + + { 243,-6433 }, { 244,-6433 }, { 245,-6433 }, { 246,-6433 }, { 247,-6433 }, + { 248,-6433 }, { 249,-6433 }, { 250,-6433 }, { 251,-6433 }, { 252,-6433 }, + { 253,-6433 }, { 254,-6433 }, { 255,-6433 }, { 256,-6433 }, { 0, 41 }, + { 0,4412 }, { 1,-6691 }, { 2,-6691 }, { 3,-6691 }, { 4,-6691 }, + { 5,-6691 }, { 6,-6691 }, { 7,-6691 }, { 8,-6691 }, { 0, 0 }, + { 0, 0 }, { 11,-6691 }, { 0, 0 }, { 0, 0 }, { 14,-6691 }, + { 15,-6691 }, { 16,-6691 }, { 17,-6691 }, { 18,-6691 }, { 19,-6691 }, + { 20,-6691 }, { 21,-6691 }, { 22,-6691 }, { 23,-6691 }, { 24,-6691 }, + { 25,-6691 }, { 26,-6691 }, { 27,-6691 }, { 28,-6691 }, { 29,-6691 }, + { 30,-6691 }, { 31,-6691 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 39,-6691 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 48, 0 }, { 49, 0 }, + { 50, 0 }, { 51, 0 }, { 52, 0 }, { 53, 0 }, { 54, 0 }, + { 55, 0 }, { 56, 0 }, { 57, 0 }, { 0, 0 }, { 59,-6691 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 65,-6691 }, { 66,-6691 }, { 67,-6691 }, { 68,-6691 }, { 69,-2357 }, + { 70,-6691 }, { 71,-6691 }, { 72,-6691 }, { 73,-6691 }, { 74,-6691 }, + { 75,-6691 }, { 76,-6691 }, { 77,-6691 }, { 78,-6691 }, { 79,-6691 }, + { 80,-6691 }, { 81,-6691 }, { 82,-6691 }, { 83,-6691 }, { 84,-6691 }, + + { 85,-6691 }, { 86,-6691 }, { 87,-6691 }, { 88,-6691 }, { 89,-6691 }, + { 90,-6691 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 94,-6691 }, + { 95, 258 }, { 96,-6691 }, { 97,-6691 }, { 98,-6691 }, { 99,-6691 }, + { 100,-6691 }, { 101,-2357 }, { 102,-6691 }, { 103,-6691 }, { 104,-6691 }, + { 105,-6691 }, { 106,-6691 }, { 107,-6691 }, { 108,-6691 }, { 109,-6691 }, + { 110,-6691 }, { 111,-6691 }, { 112,-6691 }, { 113,-6691 }, { 114,-6691 }, + { 115,-6691 }, { 116,-6691 }, { 117,-6691 }, { 118,-6691 }, { 119,-6691 }, + { 120,-6691 }, { 121,-6691 }, { 122,-6691 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 126,-6691 }, { 127,-6691 }, { 128,-6691 }, { 129,-6691 }, + { 130,-6691 }, { 131,-6691 }, { 132,-6691 }, { 133,-6691 }, { 134,-6691 }, + + { 135,-6691 }, { 136,-6691 }, { 137,-6691 }, { 138,-6691 }, { 139,-6691 }, + { 140,-6691 }, { 141,-6691 }, { 142,-6691 }, { 143,-6691 }, { 144,-6691 }, + { 145,-6691 }, { 146,-6691 }, { 147,-6691 }, { 148,-6691 }, { 149,-6691 }, + { 150,-6691 }, { 151,-6691 }, { 152,-6691 }, { 153,-6691 }, { 154,-6691 }, + { 155,-6691 }, { 156,-6691 }, { 157,-6691 }, { 158,-6691 }, { 159,-6691 }, + { 160,-6691 }, { 161,-6691 }, { 162,-6691 }, { 163,-6691 }, { 164,-6691 }, + { 165,-6691 }, { 166,-6691 }, { 167,-6691 }, { 168,-6691 }, { 169,-6691 }, + { 170,-6691 }, { 171,-6691 }, { 172,-6691 }, { 173,-6691 }, { 174,-6691 }, + { 175,-6691 }, { 176,-6691 }, { 177,-6691 }, { 178,-6691 }, { 179,-6691 }, + { 180,-6691 }, { 181,-6691 }, { 182,-6691 }, { 183,-6691 }, { 184,-6691 }, + + { 185,-6691 }, { 186,-6691 }, { 187,-6691 }, { 188,-6691 }, { 189,-6691 }, + { 190,-6691 }, { 191,-6691 }, { 192,-6691 }, { 193,-6691 }, { 194,-6691 }, + { 195,-6691 }, { 196,-6691 }, { 197,-6691 }, { 198,-6691 }, { 199,-6691 }, + { 200,-6691 }, { 201,-6691 }, { 202,-6691 }, { 203,-6691 }, { 204,-6691 }, + { 205,-6691 }, { 206,-6691 }, { 207,-6691 }, { 208,-6691 }, { 209,-6691 }, + { 210,-6691 }, { 211,-6691 }, { 212,-6691 }, { 213,-6691 }, { 214,-6691 }, + { 215,-6691 }, { 216,-6691 }, { 217,-6691 }, { 218,-6691 }, { 219,-6691 }, + { 220,-6691 }, { 221,-6691 }, { 222,-6691 }, { 223,-6691 }, { 224,-6691 }, + { 225,-6691 }, { 226,-6691 }, { 227,-6691 }, { 228,-6691 }, { 229,-6691 }, + { 230,-6691 }, { 231,-6691 }, { 232,-6691 }, { 233,-6691 }, { 234,-6691 }, + + { 235,-6691 }, { 236,-6691 }, { 237,-6691 }, { 238,-6691 }, { 239,-6691 }, + { 240,-6691 }, { 241,-6691 }, { 242,-6691 }, { 243,-6691 }, { 244,-6691 }, + { 245,-6691 }, { 246,-6691 }, { 247,-6691 }, { 248,-6691 }, { 249,-6691 }, + { 250,-6691 }, { 251,-6691 }, { 252,-6691 }, { 253,-6691 }, { 254,-6691 }, + { 255,-6691 }, { 256,-6691 }, { 0, 48 }, { 0,4154 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 48,2833 }, { 49,2833 }, { 50,2833 }, { 51,2833 }, + { 52,2833 }, { 53,2833 }, { 54,2833 }, { 55,2833 }, { 56,2833 }, + { 57,2833 }, { 0, 45 }, { 0,4095 }, { 1,-6739 }, { 2,-6739 }, + { 3,-6739 }, { 4,-6739 }, { 5,-6739 }, { 6,-6739 }, { 7,-6739 }, + { 8,-6739 }, { 0, 0 }, { 0, 0 }, { 11,-6739 }, { 0, 0 }, + { 0, 0 }, { 14,-6739 }, { 15,-6739 }, { 16,-6739 }, { 17,-6739 }, + + { 18,-6739 }, { 19,-6739 }, { 20,-6739 }, { 21,-6739 }, { 22,-6739 }, + { 23,-6739 }, { 24,-6739 }, { 25,-6739 }, { 26,-6739 }, { 27,-6739 }, + { 28,-6739 }, { 29,-6739 }, { 30,-6739 }, { 31,-6739 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 39,-6739 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 48, 0 }, { 49, 0 }, { 50,-6739 }, { 51,-6739 }, { 52,-6739 }, + { 53,-6739 }, { 54,-6739 }, { 55,-6739 }, { 56,-6739 }, { 57,-6739 }, + { 0, 0 }, { 59,-6739 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 65,-6739 }, { 66,-6739 }, { 67,-6739 }, + + { 68,-6739 }, { 69,-6739 }, { 70,-6739 }, { 71,-6739 }, { 72,-6739 }, + { 73,-6739 }, { 74,-6739 }, { 75,-6739 }, { 76,-6739 }, { 77,-6739 }, + { 78,-6739 }, { 79,-6739 }, { 80,-6739 }, { 81,-6739 }, { 82,-6739 }, + { 83,-6739 }, { 84,-6739 }, { 85,-6739 }, { 86,-6739 }, { 87,-6739 }, + { 88,-6739 }, { 89,-6739 }, { 90,-6739 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 94,-6739 }, { 95, 258 }, { 96,-6739 }, { 97,-6739 }, + { 98,-6739 }, { 99,-6739 }, { 100,-6739 }, { 101,-6739 }, { 102,-6739 }, + { 103,-6739 }, { 104,-6739 }, { 105,-6739 }, { 106,-6739 }, { 107,-6739 }, + { 108,-6739 }, { 109,-6739 }, { 110,-6739 }, { 111,-6739 }, { 112,-6739 }, + { 113,-6739 }, { 114,-6739 }, { 115,-6739 }, { 116,-6739 }, { 117,-6739 }, + + { 118,-6739 }, { 119,-6739 }, { 120,-6739 }, { 121,-6739 }, { 122,-6739 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 126,-6739 }, { 127,-6739 }, + { 128,-6739 }, { 129,-6739 }, { 130,-6739 }, { 131,-6739 }, { 132,-6739 }, + { 133,-6739 }, { 134,-6739 }, { 135,-6739 }, { 136,-6739 }, { 137,-6739 }, + { 138,-6739 }, { 139,-6739 }, { 140,-6739 }, { 141,-6739 }, { 142,-6739 }, + { 143,-6739 }, { 144,-6739 }, { 145,-6739 }, { 146,-6739 }, { 147,-6739 }, + { 148,-6739 }, { 149,-6739 }, { 150,-6739 }, { 151,-6739 }, { 152,-6739 }, + { 153,-6739 }, { 154,-6739 }, { 155,-6739 }, { 156,-6739 }, { 157,-6739 }, + { 158,-6739 }, { 159,-6739 }, { 160,-6739 }, { 161,-6739 }, { 162,-6739 }, + { 163,-6739 }, { 164,-6739 }, { 165,-6739 }, { 166,-6739 }, { 167,-6739 }, + + { 168,-6739 }, { 169,-6739 }, { 170,-6739 }, { 171,-6739 }, { 172,-6739 }, + { 173,-6739 }, { 174,-6739 }, { 175,-6739 }, { 176,-6739 }, { 177,-6739 }, + { 178,-6739 }, { 179,-6739 }, { 180,-6739 }, { 181,-6739 }, { 182,-6739 }, + { 183,-6739 }, { 184,-6739 }, { 185,-6739 }, { 186,-6739 }, { 187,-6739 }, + { 188,-6739 }, { 189,-6739 }, { 190,-6739 }, { 191,-6739 }, { 192,-6739 }, + { 193,-6739 }, { 194,-6739 }, { 195,-6739 }, { 196,-6739 }, { 197,-6739 }, + { 198,-6739 }, { 199,-6739 }, { 200,-6739 }, { 201,-6739 }, { 202,-6739 }, + { 203,-6739 }, { 204,-6739 }, { 205,-6739 }, { 206,-6739 }, { 207,-6739 }, + { 208,-6739 }, { 209,-6739 }, { 210,-6739 }, { 211,-6739 }, { 212,-6739 }, + { 213,-6739 }, { 214,-6739 }, { 215,-6739 }, { 216,-6739 }, { 217,-6739 }, + + { 218,-6739 }, { 219,-6739 }, { 220,-6739 }, { 221,-6739 }, { 222,-6739 }, + { 223,-6739 }, { 224,-6739 }, { 225,-6739 }, { 226,-6739 }, { 227,-6739 }, + { 228,-6739 }, { 229,-6739 }, { 230,-6739 }, { 231,-6739 }, { 232,-6739 }, + { 233,-6739 }, { 234,-6739 }, { 235,-6739 }, { 236,-6739 }, { 237,-6739 }, + { 238,-6739 }, { 239,-6739 }, { 240,-6739 }, { 241,-6739 }, { 242,-6739 }, + { 243,-6739 }, { 244,-6739 }, { 245,-6739 }, { 246,-6739 }, { 247,-6739 }, + { 248,-6739 }, { 249,-6739 }, { 250,-6739 }, { 251,-6739 }, { 252,-6739 }, + { 253,-6739 }, { 254,-6739 }, { 255,-6739 }, { 256,-6739 }, { 0, 52 }, + { 0,3837 }, { 1,-6997 }, { 2,-6997 }, { 3,-6997 }, { 4,-6997 }, + { 5,-6997 }, { 6,-6997 }, { 7,-6997 }, { 8,-6997 }, { 0, 0 }, + + { 0, 0 }, { 11,-6997 }, { 0, 0 }, { 0, 0 }, { 14,-6997 }, + { 15,-6997 }, { 16,-6997 }, { 17,-6997 }, { 18,-6997 }, { 19,-6997 }, + { 20,-6997 }, { 21,-6997 }, { 22,-6997 }, { 23,-6997 }, { 24,-6997 }, + { 25,-6997 }, { 26,-6997 }, { 27,-6997 }, { 28,-6997 }, { 29,-6997 }, + { 30,-6997 }, { 31,-6997 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 39,-6997 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 48,-258 }, { 49,-258 }, + { 50,-6997 }, { 51,-6997 }, { 52,-6997 }, { 53,-6997 }, { 54,-6997 }, + { 55,-6997 }, { 56,-6997 }, { 57,-6997 }, { 0, 0 }, { 59,-6997 }, + + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 65,-6997 }, { 66,-6997 }, { 67,-6997 }, { 68,-6997 }, { 69,-6997 }, + { 70,-6997 }, { 71,-6997 }, { 72,-6997 }, { 73,-6997 }, { 74,-6997 }, + { 75,-6997 }, { 76,-6997 }, { 77,-6997 }, { 78,-6997 }, { 79,-6997 }, + { 80,-6997 }, { 81,-6997 }, { 82,-6997 }, { 83,-6997 }, { 84,-6997 }, + { 85,-6997 }, { 86,-6997 }, { 87,-6997 }, { 88,-6997 }, { 89,-6997 }, + { 90,-6997 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 94,-6997 }, + { 95,-6997 }, { 96,-6997 }, { 97,-6997 }, { 98,-6997 }, { 99,-6997 }, + { 100,-6997 }, { 101,-6997 }, { 102,-6997 }, { 103,-6997 }, { 104,-6997 }, + { 105,-6997 }, { 106,-6997 }, { 107,-6997 }, { 108,-6997 }, { 109,-6997 }, + + { 110,-6997 }, { 111,-6997 }, { 112,-6997 }, { 113,-6997 }, { 114,-6997 }, + { 115,-6997 }, { 116,-6997 }, { 117,-6997 }, { 118,-6997 }, { 119,-6997 }, + { 120,-6997 }, { 121,-6997 }, { 122,-6997 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 126,-6997 }, { 127,-6997 }, { 128,-6997 }, { 129,-6997 }, + { 130,-6997 }, { 131,-6997 }, { 132,-6997 }, { 133,-6997 }, { 134,-6997 }, + { 135,-6997 }, { 136,-6997 }, { 137,-6997 }, { 138,-6997 }, { 139,-6997 }, + { 140,-6997 }, { 141,-6997 }, { 142,-6997 }, { 143,-6997 }, { 144,-6997 }, + { 145,-6997 }, { 146,-6997 }, { 147,-6997 }, { 148,-6997 }, { 149,-6997 }, + { 150,-6997 }, { 151,-6997 }, { 152,-6997 }, { 153,-6997 }, { 154,-6997 }, + { 155,-6997 }, { 156,-6997 }, { 157,-6997 }, { 158,-6997 }, { 159,-6997 }, + + { 160,-6997 }, { 161,-6997 }, { 162,-6997 }, { 163,-6997 }, { 164,-6997 }, + { 165,-6997 }, { 166,-6997 }, { 167,-6997 }, { 168,-6997 }, { 169,-6997 }, + { 170,-6997 }, { 171,-6997 }, { 172,-6997 }, { 173,-6997 }, { 174,-6997 }, + { 175,-6997 }, { 176,-6997 }, { 177,-6997 }, { 178,-6997 }, { 179,-6997 }, + { 180,-6997 }, { 181,-6997 }, { 182,-6997 }, { 183,-6997 }, { 184,-6997 }, + { 185,-6997 }, { 186,-6997 }, { 187,-6997 }, { 188,-6997 }, { 189,-6997 }, + { 190,-6997 }, { 191,-6997 }, { 192,-6997 }, { 193,-6997 }, { 194,-6997 }, + { 195,-6997 }, { 196,-6997 }, { 197,-6997 }, { 198,-6997 }, { 199,-6997 }, + { 200,-6997 }, { 201,-6997 }, { 202,-6997 }, { 203,-6997 }, { 204,-6997 }, + { 205,-6997 }, { 206,-6997 }, { 207,-6997 }, { 208,-6997 }, { 209,-6997 }, + + { 210,-6997 }, { 211,-6997 }, { 212,-6997 }, { 213,-6997 }, { 214,-6997 }, + { 215,-6997 }, { 216,-6997 }, { 217,-6997 }, { 218,-6997 }, { 219,-6997 }, + { 220,-6997 }, { 221,-6997 }, { 222,-6997 }, { 223,-6997 }, { 224,-6997 }, + { 225,-6997 }, { 226,-6997 }, { 227,-6997 }, { 228,-6997 }, { 229,-6997 }, + { 230,-6997 }, { 231,-6997 }, { 232,-6997 }, { 233,-6997 }, { 234,-6997 }, + { 235,-6997 }, { 236,-6997 }, { 237,-6997 }, { 238,-6997 }, { 239,-6997 }, + { 240,-6997 }, { 241,-6997 }, { 242,-6997 }, { 243,-6997 }, { 244,-6997 }, + { 245,-6997 }, { 246,-6997 }, { 247,-6997 }, { 248,-6997 }, { 249,-6997 }, + { 250,-6997 }, { 251,-6997 }, { 252,-6997 }, { 253,-6997 }, { 254,-6997 }, + { 255,-6997 }, { 256,-6997 }, { 0, 49 }, { 0,3579 }, { 1,-7255 }, + + { 2,-7255 }, { 3,-7255 }, { 4,-7255 }, { 5,-7255 }, { 6,-7255 }, + { 7,-7255 }, { 8,-7255 }, { 0, 0 }, { 0, 0 }, { 11,-7255 }, + { 0, 0 }, { 0, 0 }, { 14,-7255 }, { 15,-7255 }, { 16,-7255 }, + { 17,-7255 }, { 18,-7255 }, { 19,-7255 }, { 20,-7255 }, { 21,-7255 }, + { 22,-7255 }, { 23,-7255 }, { 24,-7255 }, { 25,-7255 }, { 26,-7255 }, + { 27,-7255 }, { 28,-7255 }, { 29,-7255 }, { 30,-7255 }, { 31,-7255 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 39,-7255 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 48,-7255 }, { 49,-7255 }, { 50,-7255 }, { 51,-7255 }, + + { 52,-7255 }, { 53,-7255 }, { 54,-7255 }, { 55,-7255 }, { 56,-7255 }, + { 57,-7255 }, { 0, 0 }, { 59,-7255 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 65,-7255 }, { 66,-7255 }, + { 67,-7255 }, { 68,-7255 }, { 69,-7255 }, { 70,-7255 }, { 71,-7255 }, + { 72,-7255 }, { 73,-7255 }, { 74,-7255 }, { 75,-7255 }, { 76,-7255 }, + { 77,-7255 }, { 78,-7255 }, { 79,-7255 }, { 80,-7255 }, { 81,-7255 }, + { 82,-7255 }, { 83,-7255 }, { 84,-7255 }, { 85,-7255 }, { 86,-7255 }, + { 87,-7255 }, { 88,-7255 }, { 89,-7255 }, { 90,-7255 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 94,-7255 }, { 95,-7255 }, { 96,-7255 }, + { 97,-7255 }, { 98,-7255 }, { 99,-7255 }, { 100,-7255 }, { 101,-7255 }, + + { 102,-7255 }, { 103,-7255 }, { 104,-7255 }, { 105,-7255 }, { 106,-7255 }, + { 107,-7255 }, { 108,-7255 }, { 109,-7255 }, { 110,-7255 }, { 111,-7255 }, + { 112,-7255 }, { 113,-7255 }, { 114,-7255 }, { 115,-7255 }, { 116,-7255 }, + { 117,-7255 }, { 118,-7255 }, { 119,-7255 }, { 120,-7255 }, { 121,-7255 }, + { 122,-7255 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 126,-7255 }, + { 127,-7255 }, { 128,-7255 }, { 129,-7255 }, { 130,-7255 }, { 131,-7255 }, + { 132,-7255 }, { 133,-7255 }, { 134,-7255 }, { 135,-7255 }, { 136,-7255 }, + { 137,-7255 }, { 138,-7255 }, { 139,-7255 }, { 140,-7255 }, { 141,-7255 }, + { 142,-7255 }, { 143,-7255 }, { 144,-7255 }, { 145,-7255 }, { 146,-7255 }, + { 147,-7255 }, { 148,-7255 }, { 149,-7255 }, { 150,-7255 }, { 151,-7255 }, + + { 152,-7255 }, { 153,-7255 }, { 154,-7255 }, { 155,-7255 }, { 156,-7255 }, + { 157,-7255 }, { 158,-7255 }, { 159,-7255 }, { 160,-7255 }, { 161,-7255 }, + { 162,-7255 }, { 163,-7255 }, { 164,-7255 }, { 165,-7255 }, { 166,-7255 }, + { 167,-7255 }, { 168,-7255 }, { 169,-7255 }, { 170,-7255 }, { 171,-7255 }, + { 172,-7255 }, { 173,-7255 }, { 174,-7255 }, { 175,-7255 }, { 176,-7255 }, + { 177,-7255 }, { 178,-7255 }, { 179,-7255 }, { 180,-7255 }, { 181,-7255 }, + { 182,-7255 }, { 183,-7255 }, { 184,-7255 }, { 185,-7255 }, { 186,-7255 }, + { 187,-7255 }, { 188,-7255 }, { 189,-7255 }, { 190,-7255 }, { 191,-7255 }, + { 192,-7255 }, { 193,-7255 }, { 194,-7255 }, { 195,-7255 }, { 196,-7255 }, + { 197,-7255 }, { 198,-7255 }, { 199,-7255 }, { 200,-7255 }, { 201,-7255 }, + + { 202,-7255 }, { 203,-7255 }, { 204,-7255 }, { 205,-7255 }, { 206,-7255 }, + { 207,-7255 }, { 208,-7255 }, { 209,-7255 }, { 210,-7255 }, { 211,-7255 }, + { 212,-7255 }, { 213,-7255 }, { 214,-7255 }, { 215,-7255 }, { 216,-7255 }, + { 217,-7255 }, { 218,-7255 }, { 219,-7255 }, { 220,-7255 }, { 221,-7255 }, + { 222,-7255 }, { 223,-7255 }, { 224,-7255 }, { 225,-7255 }, { 226,-7255 }, + { 227,-7255 }, { 228,-7255 }, { 229,-7255 }, { 230,-7255 }, { 231,-7255 }, + { 232,-7255 }, { 233,-7255 }, { 234,-7255 }, { 235,-7255 }, { 236,-7255 }, + { 237,-7255 }, { 238,-7255 }, { 239,-7255 }, { 240,-7255 }, { 241,-7255 }, + { 242,-7255 }, { 243,-7255 }, { 244,-7255 }, { 245,-7255 }, { 246,-7255 }, + { 247,-7255 }, { 248,-7255 }, { 249,-7255 }, { 250,-7255 }, { 251,-7255 }, + + { 252,-7255 }, { 253,-7255 }, { 254,-7255 }, { 255,-7255 }, { 256,-7255 }, + { 0, 40 }, { 0,3321 }, { 1,-258 }, { 2,-258 }, { 3,-258 }, + { 4,-258 }, { 5,-258 }, { 6,-258 }, { 7,-258 }, { 8,-258 }, + { 0, 0 }, { 0, 0 }, { 11,-258 }, { 0, 0 }, { 0, 0 }, + { 14,-258 }, { 15,-258 }, { 16,-258 }, { 17,-258 }, { 18,-258 }, + { 19,-258 }, { 20,-258 }, { 21,-258 }, { 22,-258 }, { 23,-258 }, + { 24,-258 }, { 25,-258 }, { 26,-258 }, { 27,-258 }, { 28,-258 }, + { 29,-258 }, { 30,-258 }, { 31,-258 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 39,-258 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 48, 0 }, + { 49, 0 }, { 50, 0 }, { 51, 0 }, { 52, 0 }, { 53, 0 }, + { 54, 0 }, { 55, 0 }, { 56, 0 }, { 57, 0 }, { 0, 0 }, + { 59,-258 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 65,-258 }, { 66,-258 }, { 67,-258 }, { 68,-258 }, + { 69,-258 }, { 70,-258 }, { 71,-258 }, { 72,-258 }, { 73,-258 }, + { 74,-258 }, { 75,-258 }, { 76,-258 }, { 77,-258 }, { 78,-258 }, + { 79,-258 }, { 80,-258 }, { 81,-258 }, { 82,-258 }, { 83,-258 }, + { 84,-258 }, { 85,-258 }, { 86,-258 }, { 87,-258 }, { 88,-258 }, + { 89,-258 }, { 90,-258 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + + { 94,-258 }, { 95, 258 }, { 96,-258 }, { 97,-258 }, { 98,-258 }, + { 99,-258 }, { 100,-258 }, { 101,-258 }, { 102,-258 }, { 103,-258 }, + { 104,-258 }, { 105,-258 }, { 106,-258 }, { 107,-258 }, { 108,-258 }, + { 109,-258 }, { 110,-258 }, { 111,-258 }, { 112,-258 }, { 113,-258 }, + { 114,-258 }, { 115,-258 }, { 116,-258 }, { 117,-258 }, { 118,-258 }, + { 119,-258 }, { 120,-258 }, { 121,-258 }, { 122,-258 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 126,-258 }, { 127,-258 }, { 128,-258 }, + { 129,-258 }, { 130,-258 }, { 131,-258 }, { 132,-258 }, { 133,-258 }, + { 134,-258 }, { 135,-258 }, { 136,-258 }, { 137,-258 }, { 138,-258 }, + { 139,-258 }, { 140,-258 }, { 141,-258 }, { 142,-258 }, { 143,-258 }, + + { 144,-258 }, { 145,-258 }, { 146,-258 }, { 147,-258 }, { 148,-258 }, + { 149,-258 }, { 150,-258 }, { 151,-258 }, { 152,-258 }, { 153,-258 }, + { 154,-258 }, { 155,-258 }, { 156,-258 }, { 157,-258 }, { 158,-258 }, + { 159,-258 }, { 160,-258 }, { 161,-258 }, { 162,-258 }, { 163,-258 }, + { 164,-258 }, { 165,-258 }, { 166,-258 }, { 167,-258 }, { 168,-258 }, + { 169,-258 }, { 170,-258 }, { 171,-258 }, { 172,-258 }, { 173,-258 }, + { 174,-258 }, { 175,-258 }, { 176,-258 }, { 177,-258 }, { 178,-258 }, + { 179,-258 }, { 180,-258 }, { 181,-258 }, { 182,-258 }, { 183,-258 }, + { 184,-258 }, { 185,-258 }, { 186,-258 }, { 187,-258 }, { 188,-258 }, + { 189,-258 }, { 190,-258 }, { 191,-258 }, { 192,-258 }, { 193,-258 }, + + { 194,-258 }, { 195,-258 }, { 196,-258 }, { 197,-258 }, { 198,-258 }, + { 199,-258 }, { 200,-258 }, { 201,-258 }, { 202,-258 }, { 203,-258 }, + { 204,-258 }, { 205,-258 }, { 206,-258 }, { 207,-258 }, { 208,-258 }, + { 209,-258 }, { 210,-258 }, { 211,-258 }, { 212,-258 }, { 213,-258 }, + { 214,-258 }, { 215,-258 }, { 216,-258 }, { 217,-258 }, { 218,-258 }, + { 219,-258 }, { 220,-258 }, { 221,-258 }, { 222,-258 }, { 223,-258 }, + { 224,-258 }, { 225,-258 }, { 226,-258 }, { 227,-258 }, { 228,-258 }, + { 229,-258 }, { 230,-258 }, { 231,-258 }, { 232,-258 }, { 233,-258 }, + { 234,-258 }, { 235,-258 }, { 236,-258 }, { 237,-258 }, { 238,-258 }, + { 239,-258 }, { 240,-258 }, { 241,-258 }, { 242,-258 }, { 243,-258 }, + + { 244,-258 }, { 245,-258 }, { 246,-258 }, { 247,-258 }, { 248,-258 }, + { 249,-258 }, { 250,-258 }, { 251,-258 }, { 252,-258 }, { 253,-258 }, + { 254,-258 }, { 255,-258 }, { 256,-258 }, { 0, 49 }, { 0,3063 }, + { 1,-7771 }, { 2,-7771 }, { 3,-7771 }, { 4,-7771 }, { 5,-7771 }, + { 6,-7771 }, { 7,-7771 }, { 8,-7771 }, { 0, 0 }, { 0, 0 }, + { 11,-7771 }, { 0, 0 }, { 0, 0 }, { 14,-7771 }, { 15,-7771 }, + { 16,-7771 }, { 17,-7771 }, { 18,-7771 }, { 19,-7771 }, { 20,-7771 }, + { 21,-7771 }, { 22,-7771 }, { 23,-7771 }, { 24,-7771 }, { 25,-7771 }, + { 26,-7771 }, { 27,-7771 }, { 28,-7771 }, { 29,-7771 }, { 30,-7771 }, + { 31,-7771 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 39,-7771 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 48,2000 }, { 49,2000 }, { 50,2000 }, + { 51,2000 }, { 52,2000 }, { 53,2000 }, { 54,2000 }, { 55,2000 }, + { 56,2000 }, { 57,2000 }, { 0, 0 }, { 59,-7771 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 65,-7771 }, + { 66,-7771 }, { 67,-7771 }, { 68,-7771 }, { 69,-7771 }, { 70,-7771 }, + { 71,-7771 }, { 72,-7771 }, { 73,-7771 }, { 74,-7771 }, { 75,-7771 }, + { 76,-7771 }, { 77,-7771 }, { 78,-7771 }, { 79,-7771 }, { 80,-7771 }, + { 81,-7771 }, { 82,-7771 }, { 83,-7771 }, { 84,-7771 }, { 85,-7771 }, + + { 86,-7771 }, { 87,-7771 }, { 88,-7771 }, { 89,-7771 }, { 90,-7771 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 94,-7771 }, { 95,-7771 }, + { 96,-7771 }, { 97,-7771 }, { 98,-7771 }, { 99,-7771 }, { 100,-7771 }, + { 101,-7771 }, { 102,-7771 }, { 103,-7771 }, { 104,-7771 }, { 105,-7771 }, + { 106,-7771 }, { 107,-7771 }, { 108,-7771 }, { 109,-7771 }, { 110,-7771 }, + { 111,-7771 }, { 112,-7771 }, { 113,-7771 }, { 114,-7771 }, { 115,-7771 }, + { 116,-7771 }, { 117,-7771 }, { 118,-7771 }, { 119,-7771 }, { 120,-7771 }, + { 121,-7771 }, { 122,-7771 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 126,-7771 }, { 127,-7771 }, { 128,-7771 }, { 129,-7771 }, { 130,-7771 }, + { 131,-7771 }, { 132,-7771 }, { 133,-7771 }, { 134,-7771 }, { 135,-7771 }, + + { 136,-7771 }, { 137,-7771 }, { 138,-7771 }, { 139,-7771 }, { 140,-7771 }, + { 141,-7771 }, { 142,-7771 }, { 143,-7771 }, { 144,-7771 }, { 145,-7771 }, + { 146,-7771 }, { 147,-7771 }, { 148,-7771 }, { 149,-7771 }, { 150,-7771 }, + { 151,-7771 }, { 152,-7771 }, { 153,-7771 }, { 154,-7771 }, { 155,-7771 }, + { 156,-7771 }, { 157,-7771 }, { 158,-7771 }, { 159,-7771 }, { 160,-7771 }, + { 161,-7771 }, { 162,-7771 }, { 163,-7771 }, { 164,-7771 }, { 165,-7771 }, + { 166,-7771 }, { 167,-7771 }, { 168,-7771 }, { 169,-7771 }, { 170,-7771 }, + { 171,-7771 }, { 172,-7771 }, { 173,-7771 }, { 174,-7771 }, { 175,-7771 }, + { 176,-7771 }, { 177,-7771 }, { 178,-7771 }, { 179,-7771 }, { 180,-7771 }, + { 181,-7771 }, { 182,-7771 }, { 183,-7771 }, { 184,-7771 }, { 185,-7771 }, + + { 186,-7771 }, { 187,-7771 }, { 188,-7771 }, { 189,-7771 }, { 190,-7771 }, + { 191,-7771 }, { 192,-7771 }, { 193,-7771 }, { 194,-7771 }, { 195,-7771 }, + { 196,-7771 }, { 197,-7771 }, { 198,-7771 }, { 199,-7771 }, { 200,-7771 }, + { 201,-7771 }, { 202,-7771 }, { 203,-7771 }, { 204,-7771 }, { 205,-7771 }, + { 206,-7771 }, { 207,-7771 }, { 208,-7771 }, { 209,-7771 }, { 210,-7771 }, + { 211,-7771 }, { 212,-7771 }, { 213,-7771 }, { 214,-7771 }, { 215,-7771 }, + { 216,-7771 }, { 217,-7771 }, { 218,-7771 }, { 219,-7771 }, { 220,-7771 }, + { 221,-7771 }, { 222,-7771 }, { 223,-7771 }, { 224,-7771 }, { 225,-7771 }, + { 226,-7771 }, { 227,-7771 }, { 228,-7771 }, { 229,-7771 }, { 230,-7771 }, + { 231,-7771 }, { 232,-7771 }, { 233,-7771 }, { 234,-7771 }, { 235,-7771 }, + + { 236,-7771 }, { 237,-7771 }, { 238,-7771 }, { 239,-7771 }, { 240,-7771 }, + { 241,-7771 }, { 242,-7771 }, { 243,-7771 }, { 244,-7771 }, { 245,-7771 }, + { 246,-7771 }, { 247,-7771 }, { 248,-7771 }, { 249,-7771 }, { 250,-7771 }, + { 251,-7771 }, { 252,-7771 }, { 253,-7771 }, { 254,-7771 }, { 255,-7771 }, + { 256,-7771 }, { 0, 44 }, { 0,2805 }, { 1,-8029 }, { 2,-8029 }, + { 3,-8029 }, { 4,-8029 }, { 5,-8029 }, { 6,-8029 }, { 7,-8029 }, + { 8,-8029 }, { 0, 0 }, { 0, 0 }, { 11,-8029 }, { 0, 0 }, + { 0, 0 }, { 14,-8029 }, { 15,-8029 }, { 16,-8029 }, { 17,-8029 }, + { 18,-8029 }, { 19,-8029 }, { 20,-8029 }, { 21,-8029 }, { 22,-8029 }, + { 23,-8029 }, { 24,-8029 }, { 25,-8029 }, { 26,-8029 }, { 27,-8029 }, + + { 28,-8029 }, { 29,-8029 }, { 30,-8029 }, { 31,-8029 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 39,-8029 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 48, 0 }, { 49, 0 }, { 50, 0 }, { 51, 0 }, { 52, 0 }, + { 53, 0 }, { 54, 0 }, { 55, 0 }, { 56,-8029 }, { 57,-8029 }, + { 0, 0 }, { 59,-8029 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 65,-8029 }, { 66,-8029 }, { 67,-8029 }, + { 68,-8029 }, { 69,-8029 }, { 70,-8029 }, { 71,-8029 }, { 72,-8029 }, + { 73,-8029 }, { 74,-8029 }, { 75,-8029 }, { 76,-8029 }, { 77,-8029 }, + + { 78,-8029 }, { 79,-8029 }, { 80,-8029 }, { 81,-8029 }, { 82,-8029 }, + { 83,-8029 }, { 84,-8029 }, { 85,-8029 }, { 86,-8029 }, { 87,-8029 }, + { 88,-8029 }, { 89,-8029 }, { 90,-8029 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 94,-8029 }, { 95, 258 }, { 96,-8029 }, { 97,-8029 }, + { 98,-8029 }, { 99,-8029 }, { 100,-8029 }, { 101,-8029 }, { 102,-8029 }, + { 103,-8029 }, { 104,-8029 }, { 105,-8029 }, { 106,-8029 }, { 107,-8029 }, + { 108,-8029 }, { 109,-8029 }, { 110,-8029 }, { 111,-8029 }, { 112,-8029 }, + { 113,-8029 }, { 114,-8029 }, { 115,-8029 }, { 116,-8029 }, { 117,-8029 }, + { 118,-8029 }, { 119,-8029 }, { 120,-8029 }, { 121,-8029 }, { 122,-8029 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 126,-8029 }, { 127,-8029 }, + + { 128,-8029 }, { 129,-8029 }, { 130,-8029 }, { 131,-8029 }, { 132,-8029 }, + { 133,-8029 }, { 134,-8029 }, { 135,-8029 }, { 136,-8029 }, { 137,-8029 }, + { 138,-8029 }, { 139,-8029 }, { 140,-8029 }, { 141,-8029 }, { 142,-8029 }, + { 143,-8029 }, { 144,-8029 }, { 145,-8029 }, { 146,-8029 }, { 147,-8029 }, + { 148,-8029 }, { 149,-8029 }, { 150,-8029 }, { 151,-8029 }, { 152,-8029 }, + { 153,-8029 }, { 154,-8029 }, { 155,-8029 }, { 156,-8029 }, { 157,-8029 }, + { 158,-8029 }, { 159,-8029 }, { 160,-8029 }, { 161,-8029 }, { 162,-8029 }, + { 163,-8029 }, { 164,-8029 }, { 165,-8029 }, { 166,-8029 }, { 167,-8029 }, + { 168,-8029 }, { 169,-8029 }, { 170,-8029 }, { 171,-8029 }, { 172,-8029 }, + { 173,-8029 }, { 174,-8029 }, { 175,-8029 }, { 176,-8029 }, { 177,-8029 }, + + { 178,-8029 }, { 179,-8029 }, { 180,-8029 }, { 181,-8029 }, { 182,-8029 }, + { 183,-8029 }, { 184,-8029 }, { 185,-8029 }, { 186,-8029 }, { 187,-8029 }, + { 188,-8029 }, { 189,-8029 }, { 190,-8029 }, { 191,-8029 }, { 192,-8029 }, + { 193,-8029 }, { 194,-8029 }, { 195,-8029 }, { 196,-8029 }, { 197,-8029 }, + { 198,-8029 }, { 199,-8029 }, { 200,-8029 }, { 201,-8029 }, { 202,-8029 }, + { 203,-8029 }, { 204,-8029 }, { 205,-8029 }, { 206,-8029 }, { 207,-8029 }, + { 208,-8029 }, { 209,-8029 }, { 210,-8029 }, { 211,-8029 }, { 212,-8029 }, + { 213,-8029 }, { 214,-8029 }, { 215,-8029 }, { 216,-8029 }, { 217,-8029 }, + { 218,-8029 }, { 219,-8029 }, { 220,-8029 }, { 221,-8029 }, { 222,-8029 }, + { 223,-8029 }, { 224,-8029 }, { 225,-8029 }, { 226,-8029 }, { 227,-8029 }, + + { 228,-8029 }, { 229,-8029 }, { 230,-8029 }, { 231,-8029 }, { 232,-8029 }, + { 233,-8029 }, { 234,-8029 }, { 235,-8029 }, { 236,-8029 }, { 237,-8029 }, + { 238,-8029 }, { 239,-8029 }, { 240,-8029 }, { 241,-8029 }, { 242,-8029 }, + { 243,-8029 }, { 244,-8029 }, { 245,-8029 }, { 246,-8029 }, { 247,-8029 }, + { 248,-8029 }, { 249,-8029 }, { 250,-8029 }, { 251,-8029 }, { 252,-8029 }, + { 253,-8029 }, { 254,-8029 }, { 255,-8029 }, { 256,-8029 }, { 0, 52 }, + { 0,2547 }, { 1,-8287 }, { 2,-8287 }, { 3,-8287 }, { 4,-8287 }, + { 5,-8287 }, { 6,-8287 }, { 7,-8287 }, { 8,-8287 }, { 0, 0 }, + { 0, 0 }, { 11,-8287 }, { 0, 0 }, { 0, 0 }, { 14,-8287 }, + { 15,-8287 }, { 16,-8287 }, { 17,-8287 }, { 18,-8287 }, { 19,-8287 }, + + { 20,-8287 }, { 21,-8287 }, { 22,-8287 }, { 23,-8287 }, { 24,-8287 }, + { 25,-8287 }, { 26,-8287 }, { 27,-8287 }, { 28,-8287 }, { 29,-8287 }, + { 30,-8287 }, { 31,-8287 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 39,-8287 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 48,-258 }, { 49,-258 }, + { 50,-258 }, { 51,-258 }, { 52,-258 }, { 53,-258 }, { 54,-258 }, + { 55,-258 }, { 56,-8287 }, { 57,-8287 }, { 0, 0 }, { 59,-8287 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 65,-8287 }, { 66,-8287 }, { 67,-8287 }, { 68,-8287 }, { 69,-8287 }, + + { 70,-8287 }, { 71,-8287 }, { 72,-8287 }, { 73,-8287 }, { 74,-8287 }, + { 75,-8287 }, { 76,-8287 }, { 77,-8287 }, { 78,-8287 }, { 79,-8287 }, + { 80,-8287 }, { 81,-8287 }, { 82,-8287 }, { 83,-8287 }, { 84,-8287 }, + { 85,-8287 }, { 86,-8287 }, { 87,-8287 }, { 88,-8287 }, { 89,-8287 }, + { 90,-8287 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 94,-8287 }, + { 95,-8287 }, { 96,-8287 }, { 97,-8287 }, { 98,-8287 }, { 99,-8287 }, + { 100,-8287 }, { 101,-8287 }, { 102,-8287 }, { 103,-8287 }, { 104,-8287 }, + { 105,-8287 }, { 106,-8287 }, { 107,-8287 }, { 108,-8287 }, { 109,-8287 }, + { 110,-8287 }, { 111,-8287 }, { 112,-8287 }, { 113,-8287 }, { 114,-8287 }, + { 115,-8287 }, { 116,-8287 }, { 117,-8287 }, { 118,-8287 }, { 119,-8287 }, + + { 120,-8287 }, { 121,-8287 }, { 122,-8287 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 126,-8287 }, { 127,-8287 }, { 128,-8287 }, { 129,-8287 }, + { 130,-8287 }, { 131,-8287 }, { 132,-8287 }, { 133,-8287 }, { 134,-8287 }, + { 135,-8287 }, { 136,-8287 }, { 137,-8287 }, { 138,-8287 }, { 139,-8287 }, + { 140,-8287 }, { 141,-8287 }, { 142,-8287 }, { 143,-8287 }, { 144,-8287 }, + { 145,-8287 }, { 146,-8287 }, { 147,-8287 }, { 148,-8287 }, { 149,-8287 }, + { 150,-8287 }, { 151,-8287 }, { 152,-8287 }, { 153,-8287 }, { 154,-8287 }, + { 155,-8287 }, { 156,-8287 }, { 157,-8287 }, { 158,-8287 }, { 159,-8287 }, + { 160,-8287 }, { 161,-8287 }, { 162,-8287 }, { 163,-8287 }, { 164,-8287 }, + { 165,-8287 }, { 166,-8287 }, { 167,-8287 }, { 168,-8287 }, { 169,-8287 }, + + { 170,-8287 }, { 171,-8287 }, { 172,-8287 }, { 173,-8287 }, { 174,-8287 }, + { 175,-8287 }, { 176,-8287 }, { 177,-8287 }, { 178,-8287 }, { 179,-8287 }, + { 180,-8287 }, { 181,-8287 }, { 182,-8287 }, { 183,-8287 }, { 184,-8287 }, + { 185,-8287 }, { 186,-8287 }, { 187,-8287 }, { 188,-8287 }, { 189,-8287 }, + { 190,-8287 }, { 191,-8287 }, { 192,-8287 }, { 193,-8287 }, { 194,-8287 }, + { 195,-8287 }, { 196,-8287 }, { 197,-8287 }, { 198,-8287 }, { 199,-8287 }, + { 200,-8287 }, { 201,-8287 }, { 202,-8287 }, { 203,-8287 }, { 204,-8287 }, + { 205,-8287 }, { 206,-8287 }, { 207,-8287 }, { 208,-8287 }, { 209,-8287 }, + { 210,-8287 }, { 211,-8287 }, { 212,-8287 }, { 213,-8287 }, { 214,-8287 }, + { 215,-8287 }, { 216,-8287 }, { 217,-8287 }, { 218,-8287 }, { 219,-8287 }, + + { 220,-8287 }, { 221,-8287 }, { 222,-8287 }, { 223,-8287 }, { 224,-8287 }, + { 225,-8287 }, { 226,-8287 }, { 227,-8287 }, { 228,-8287 }, { 229,-8287 }, + { 230,-8287 }, { 231,-8287 }, { 232,-8287 }, { 233,-8287 }, { 234,-8287 }, + { 235,-8287 }, { 236,-8287 }, { 237,-8287 }, { 238,-8287 }, { 239,-8287 }, + { 240,-8287 }, { 241,-8287 }, { 242,-8287 }, { 243,-8287 }, { 244,-8287 }, + { 245,-8287 }, { 246,-8287 }, { 247,-8287 }, { 248,-8287 }, { 249,-8287 }, + { 250,-8287 }, { 251,-8287 }, { 252,-8287 }, { 253,-8287 }, { 254,-8287 }, + { 255,-8287 }, { 256,-8287 }, { 0, 43 }, { 0,2289 }, { 1,-8545 }, + { 2,-8545 }, { 3,-8545 }, { 4,-8545 }, { 5,-8545 }, { 6,-8545 }, + { 7,-8545 }, { 8,-8545 }, { 0, 0 }, { 0, 0 }, { 11,-8545 }, + + { 0, 0 }, { 0, 0 }, { 14,-8545 }, { 15,-8545 }, { 16,-8545 }, + { 17,-8545 }, { 18,-8545 }, { 19,-8545 }, { 20,-8545 }, { 21,-8545 }, + { 22,-8545 }, { 23,-8545 }, { 24,-8545 }, { 25,-8545 }, { 26,-8545 }, + { 27,-8545 }, { 28,-8545 }, { 29,-8545 }, { 30,-8545 }, { 31,-8545 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 39,-8545 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 48, 0 }, { 49, 0 }, { 50, 0 }, { 51, 0 }, + { 52, 0 }, { 53, 0 }, { 54, 0 }, { 55, 0 }, { 56, 0 }, + { 57, 0 }, { 0, 0 }, { 59,-8545 }, { 0, 0 }, { 0, 0 }, + + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 65, 0 }, { 66, 0 }, + { 67, 0 }, { 68, 0 }, { 69, 0 }, { 70, 0 }, { 71,-8545 }, + { 72,-8545 }, { 73,-8545 }, { 74,-8545 }, { 75,-8545 }, { 76,-8545 }, + { 77,-8545 }, { 78,-8545 }, { 79,-8545 }, { 80,-8545 }, { 81,-8545 }, + { 82,-8545 }, { 83,-8545 }, { 84,-8545 }, { 85,-8545 }, { 86,-8545 }, + { 87,-8545 }, { 88,-8545 }, { 89,-8545 }, { 90,-8545 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 94,-8545 }, { 95, 258 }, { 96,-8545 }, + { 97, 0 }, { 98, 0 }, { 99, 0 }, { 100, 0 }, { 101, 0 }, + { 102, 0 }, { 103,-8545 }, { 104,-8545 }, { 105,-8545 }, { 106,-8545 }, + { 107,-8545 }, { 108,-8545 }, { 109,-8545 }, { 110,-8545 }, { 111,-8545 }, + + { 112,-8545 }, { 113,-8545 }, { 114,-8545 }, { 115,-8545 }, { 116,-8545 }, + { 117,-8545 }, { 118,-8545 }, { 119,-8545 }, { 120,-8545 }, { 121,-8545 }, + { 122,-8545 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 126,-8545 }, + { 127,-8545 }, { 128,-8545 }, { 129,-8545 }, { 130,-8545 }, { 131,-8545 }, + { 132,-8545 }, { 133,-8545 }, { 134,-8545 }, { 135,-8545 }, { 136,-8545 }, + { 137,-8545 }, { 138,-8545 }, { 139,-8545 }, { 140,-8545 }, { 141,-8545 }, + { 142,-8545 }, { 143,-8545 }, { 144,-8545 }, { 145,-8545 }, { 146,-8545 }, + { 147,-8545 }, { 148,-8545 }, { 149,-8545 }, { 150,-8545 }, { 151,-8545 }, + { 152,-8545 }, { 153,-8545 }, { 154,-8545 }, { 155,-8545 }, { 156,-8545 }, + { 157,-8545 }, { 158,-8545 }, { 159,-8545 }, { 160,-8545 }, { 161,-8545 }, + + { 162,-8545 }, { 163,-8545 }, { 164,-8545 }, { 165,-8545 }, { 166,-8545 }, + { 167,-8545 }, { 168,-8545 }, { 169,-8545 }, { 170,-8545 }, { 171,-8545 }, + { 172,-8545 }, { 173,-8545 }, { 174,-8545 }, { 175,-8545 }, { 176,-8545 }, + { 177,-8545 }, { 178,-8545 }, { 179,-8545 }, { 180,-8545 }, { 181,-8545 }, + { 182,-8545 }, { 183,-8545 }, { 184,-8545 }, { 185,-8545 }, { 186,-8545 }, + { 187,-8545 }, { 188,-8545 }, { 189,-8545 }, { 190,-8545 }, { 191,-8545 }, + { 192,-8545 }, { 193,-8545 }, { 194,-8545 }, { 195,-8545 }, { 196,-8545 }, + { 197,-8545 }, { 198,-8545 }, { 199,-8545 }, { 200,-8545 }, { 201,-8545 }, + { 202,-8545 }, { 203,-8545 }, { 204,-8545 }, { 205,-8545 }, { 206,-8545 }, + { 207,-8545 }, { 208,-8545 }, { 209,-8545 }, { 210,-8545 }, { 211,-8545 }, + + { 212,-8545 }, { 213,-8545 }, { 214,-8545 }, { 215,-8545 }, { 216,-8545 }, + { 217,-8545 }, { 218,-8545 }, { 219,-8545 }, { 220,-8545 }, { 221,-8545 }, + { 222,-8545 }, { 223,-8545 }, { 224,-8545 }, { 225,-8545 }, { 226,-8545 }, + { 227,-8545 }, { 228,-8545 }, { 229,-8545 }, { 230,-8545 }, { 231,-8545 }, + { 232,-8545 }, { 233,-8545 }, { 234,-8545 }, { 235,-8545 }, { 236,-8545 }, + { 237,-8545 }, { 238,-8545 }, { 239,-8545 }, { 240,-8545 }, { 241,-8545 }, + { 242,-8545 }, { 243,-8545 }, { 244,-8545 }, { 245,-8545 }, { 246,-8545 }, + { 247,-8545 }, { 248,-8545 }, { 249,-8545 }, { 250,-8545 }, { 251,-8545 }, + { 252,-8545 }, { 253,-8545 }, { 254,-8545 }, { 255,-8545 }, { 256,-8545 }, + { 0, 52 }, { 0,2031 }, { 1,-8803 }, { 2,-8803 }, { 3,-8803 }, + + { 4,-8803 }, { 5,-8803 }, { 6,-8803 }, { 7,-8803 }, { 8,-8803 }, + { 0, 0 }, { 0, 0 }, { 11,-8803 }, { 0, 0 }, { 0, 0 }, + { 14,-8803 }, { 15,-8803 }, { 16,-8803 }, { 17,-8803 }, { 18,-8803 }, + { 19,-8803 }, { 20,-8803 }, { 21,-8803 }, { 22,-8803 }, { 23,-8803 }, + { 24,-8803 }, { 25,-8803 }, { 26,-8803 }, { 27,-8803 }, { 28,-8803 }, + { 29,-8803 }, { 30,-8803 }, { 31,-8803 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 39,-8803 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 48,-258 }, + { 49,-258 }, { 50,-258 }, { 51,-258 }, { 52,-258 }, { 53,-258 }, + + { 54,-258 }, { 55,-258 }, { 56,-258 }, { 57,-258 }, { 0, 0 }, + { 59,-8803 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 65,-258 }, { 66,-258 }, { 67,-258 }, { 68,-258 }, + { 69,-258 }, { 70,-258 }, { 71,-8803 }, { 72,-8803 }, { 73,-8803 }, + { 74,-8803 }, { 75,-8803 }, { 76,-8803 }, { 77,-8803 }, { 78,-8803 }, + { 79,-8803 }, { 80,-8803 }, { 81,-8803 }, { 82,-8803 }, { 83,-8803 }, + { 84,-8803 }, { 85,-8803 }, { 86,-8803 }, { 87,-8803 }, { 88,-8803 }, + { 89,-8803 }, { 90,-8803 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 94,-8803 }, { 95,-8803 }, { 96,-8803 }, { 97,-258 }, { 98,-258 }, + { 99,-258 }, { 100,-258 }, { 101,-258 }, { 102,-258 }, { 103,-8803 }, + + { 104,-8803 }, { 105,-8803 }, { 106,-8803 }, { 107,-8803 }, { 108,-8803 }, + { 109,-8803 }, { 110,-8803 }, { 111,-8803 }, { 112,-8803 }, { 113,-8803 }, + { 114,-8803 }, { 115,-8803 }, { 116,-8803 }, { 117,-8803 }, { 118,-8803 }, + { 119,-8803 }, { 120,-8803 }, { 121,-8803 }, { 122,-8803 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 126,-8803 }, { 127,-8803 }, { 128,-8803 }, + { 129,-8803 }, { 130,-8803 }, { 131,-8803 }, { 132,-8803 }, { 133,-8803 }, + { 134,-8803 }, { 135,-8803 }, { 136,-8803 }, { 137,-8803 }, { 138,-8803 }, + { 139,-8803 }, { 140,-8803 }, { 141,-8803 }, { 142,-8803 }, { 143,-8803 }, + { 144,-8803 }, { 145,-8803 }, { 146,-8803 }, { 147,-8803 }, { 148,-8803 }, + { 149,-8803 }, { 150,-8803 }, { 151,-8803 }, { 152,-8803 }, { 153,-8803 }, + + { 154,-8803 }, { 155,-8803 }, { 156,-8803 }, { 157,-8803 }, { 158,-8803 }, + { 159,-8803 }, { 160,-8803 }, { 161,-8803 }, { 162,-8803 }, { 163,-8803 }, + { 164,-8803 }, { 165,-8803 }, { 166,-8803 }, { 167,-8803 }, { 168,-8803 }, + { 169,-8803 }, { 170,-8803 }, { 171,-8803 }, { 172,-8803 }, { 173,-8803 }, + { 174,-8803 }, { 175,-8803 }, { 176,-8803 }, { 177,-8803 }, { 178,-8803 }, + { 179,-8803 }, { 180,-8803 }, { 181,-8803 }, { 182,-8803 }, { 183,-8803 }, + { 184,-8803 }, { 185,-8803 }, { 186,-8803 }, { 187,-8803 }, { 188,-8803 }, + { 189,-8803 }, { 190,-8803 }, { 191,-8803 }, { 192,-8803 }, { 193,-8803 }, + { 194,-8803 }, { 195,-8803 }, { 196,-8803 }, { 197,-8803 }, { 198,-8803 }, + { 199,-8803 }, { 200,-8803 }, { 201,-8803 }, { 202,-8803 }, { 203,-8803 }, + + { 204,-8803 }, { 205,-8803 }, { 206,-8803 }, { 207,-8803 }, { 208,-8803 }, + { 209,-8803 }, { 210,-8803 }, { 211,-8803 }, { 212,-8803 }, { 213,-8803 }, + { 214,-8803 }, { 215,-8803 }, { 216,-8803 }, { 217,-8803 }, { 218,-8803 }, + { 219,-8803 }, { 220,-8803 }, { 221,-8803 }, { 222,-8803 }, { 223,-8803 }, + { 224,-8803 }, { 225,-8803 }, { 226,-8803 }, { 227,-8803 }, { 228,-8803 }, + { 229,-8803 }, { 230,-8803 }, { 231,-8803 }, { 232,-8803 }, { 233,-8803 }, + { 234,-8803 }, { 235,-8803 }, { 236,-8803 }, { 237,-8803 }, { 238,-8803 }, + { 239,-8803 }, { 240,-8803 }, { 241,-8803 }, { 242,-8803 }, { 243,-8803 }, + { 244,-8803 }, { 245,-8803 }, { 246,-8803 }, { 247,-8803 }, { 248,-8803 }, + { 249,-8803 }, { 250,-8803 }, { 251,-8803 }, { 252,-8803 }, { 253,-8803 }, + + { 254,-8803 }, { 255,-8803 }, { 256,-8803 }, { 0, 13 }, { 0,1773 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 13 }, { 0,1750 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + + { 0, 0 }, { 0, 0 }, { 48, 968 }, { 49, 968 }, { 50, 968 }, + { 51, 968 }, { 52, 968 }, { 53, 968 }, { 54, 968 }, { 55, 968 }, + { 56, 968 }, { 57, 968 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 65, 968 }, + { 66, 968 }, { 67, 968 }, { 68, 968 }, { 69, 968 }, { 70, 968 }, + { 48, 968 }, { 49, 968 }, { 50, 968 }, { 51, 968 }, { 52, 968 }, + { 53, 968 }, { 54, 968 }, { 55, 968 }, { 56, 968 }, { 57, 968 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 65, 968 }, { 66, 968 }, { 67, 968 }, + { 68, 968 }, { 69, 968 }, { 70, 968 }, { 0, 0 }, { 0, 0 }, + + { 0, 0 }, { 97, 968 }, { 98, 968 }, { 99, 968 }, { 100, 968 }, + { 101, 968 }, { 102, 968 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 97, 968 }, + { 98, 968 }, { 99, 968 }, { 100, 968 }, { 101, 968 }, { 102, 968 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 40 }, { 0,1638 }, + { 1,-9461 }, { 2,-9461 }, { 3,-9461 }, { 4,-9461 }, { 5,-9461 }, + { 6,-9461 }, { 7,-9461 }, { 8,-9461 }, { 0, 0 }, { 0, 0 }, + + { 11,-9461 }, { 0, 0 }, { 125,-9051 }, { 14,-9461 }, { 15,-9461 }, + { 16,-9461 }, { 17,-9461 }, { 18,-9461 }, { 19,-9461 }, { 20,-9461 }, + { 21,-9461 }, { 22,-9461 }, { 23,-9461 }, { 24,-9461 }, { 25,-9461 }, + { 26,-9461 }, { 27,-9461 }, { 28,-9461 }, { 29,-9461 }, { 30,-9461 }, + { 31,-9461 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 39,-9461 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 48, 0 }, { 49, 0 }, { 50, 0 }, + { 51, 0 }, { 52, 0 }, { 53, 0 }, { 54, 0 }, { 55, 0 }, + { 56, 0 }, { 57, 0 }, { 0, 0 }, { 59,-9461 }, { 0, 0 }, + + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 65,-9461 }, + { 66,-9461 }, { 67,-9461 }, { 68,-9461 }, { 69,-9461 }, { 70,-9461 }, + { 71,-9461 }, { 72,-9461 }, { 73,-9461 }, { 74,-9461 }, { 75,-9461 }, + { 76,-9461 }, { 77,-9461 }, { 78,-9461 }, { 79,-9461 }, { 80,-9461 }, + { 81,-9461 }, { 82,-9461 }, { 83,-9461 }, { 84,-9461 }, { 85,-9461 }, + { 86,-9461 }, { 87,-9461 }, { 88,-9461 }, { 89,-9461 }, { 90,-9461 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 94,-9461 }, { 95, 258 }, + { 96,-9461 }, { 97,-9461 }, { 98,-9461 }, { 99,-9461 }, { 100,-9461 }, + { 101,-9461 }, { 102,-9461 }, { 103,-9461 }, { 104,-9461 }, { 105,-9461 }, + { 106,-9461 }, { 107,-9461 }, { 108,-9461 }, { 109,-9461 }, { 110,-9461 }, + + { 111,-9461 }, { 112,-9461 }, { 113,-9461 }, { 114,-9461 }, { 115,-9461 }, + { 116,-9461 }, { 117,-9461 }, { 118,-9461 }, { 119,-9461 }, { 120,-9461 }, + { 121,-9461 }, { 122,-9461 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 126,-9461 }, { 127,-9461 }, { 128,-9461 }, { 129,-9461 }, { 130,-9461 }, + { 131,-9461 }, { 132,-9461 }, { 133,-9461 }, { 134,-9461 }, { 135,-9461 }, + { 136,-9461 }, { 137,-9461 }, { 138,-9461 }, { 139,-9461 }, { 140,-9461 }, + { 141,-9461 }, { 142,-9461 }, { 143,-9461 }, { 144,-9461 }, { 145,-9461 }, + { 146,-9461 }, { 147,-9461 }, { 148,-9461 }, { 149,-9461 }, { 150,-9461 }, + { 151,-9461 }, { 152,-9461 }, { 153,-9461 }, { 154,-9461 }, { 155,-9461 }, + { 156,-9461 }, { 157,-9461 }, { 158,-9461 }, { 159,-9461 }, { 160,-9461 }, + + { 161,-9461 }, { 162,-9461 }, { 163,-9461 }, { 164,-9461 }, { 165,-9461 }, + { 166,-9461 }, { 167,-9461 }, { 168,-9461 }, { 169,-9461 }, { 170,-9461 }, + { 171,-9461 }, { 172,-9461 }, { 173,-9461 }, { 174,-9461 }, { 175,-9461 }, + { 176,-9461 }, { 177,-9461 }, { 178,-9461 }, { 179,-9461 }, { 180,-9461 }, + { 181,-9461 }, { 182,-9461 }, { 183,-9461 }, { 184,-9461 }, { 185,-9461 }, + { 186,-9461 }, { 187,-9461 }, { 188,-9461 }, { 189,-9461 }, { 190,-9461 }, + { 191,-9461 }, { 192,-9461 }, { 193,-9461 }, { 194,-9461 }, { 195,-9461 }, + { 196,-9461 }, { 197,-9461 }, { 198,-9461 }, { 199,-9461 }, { 200,-9461 }, + { 201,-9461 }, { 202,-9461 }, { 203,-9461 }, { 204,-9461 }, { 205,-9461 }, + { 206,-9461 }, { 207,-9461 }, { 208,-9461 }, { 209,-9461 }, { 210,-9461 }, + + { 211,-9461 }, { 212,-9461 }, { 213,-9461 }, { 214,-9461 }, { 215,-9461 }, + { 216,-9461 }, { 217,-9461 }, { 218,-9461 }, { 219,-9461 }, { 220,-9461 }, + { 221,-9461 }, { 222,-9461 }, { 223,-9461 }, { 224,-9461 }, { 225,-9461 }, + { 226,-9461 }, { 227,-9461 }, { 228,-9461 }, { 229,-9461 }, { 230,-9461 }, + { 231,-9461 }, { 232,-9461 }, { 233,-9461 }, { 234,-9461 }, { 235,-9461 }, + { 236,-9461 }, { 237,-9461 }, { 238,-9461 }, { 239,-9461 }, { 240,-9461 }, + { 241,-9461 }, { 242,-9461 }, { 243,-9461 }, { 244,-9461 }, { 245,-9461 }, + { 246,-9461 }, { 247,-9461 }, { 248,-9461 }, { 249,-9461 }, { 250,-9461 }, + { 251,-9461 }, { 252,-9461 }, { 253,-9461 }, { 254,-9461 }, { 255,-9461 }, + { 256,-9461 }, { 0, 49 }, { 0,1380 }, { 0, 0 }, { 0, 0 }, + + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 48, 710 }, { 49, 710 }, { 50, 710 }, { 51, 710 }, { 52, 710 }, + + { 53, 710 }, { 54, 710 }, { 55, 710 }, { 56, 710 }, { 57, 710 }, + { 0, 41 }, { 0,1321 }, { 1,-9782 }, { 2,-9782 }, { 3,-9782 }, + { 4,-9782 }, { 5,-9782 }, { 6,-9782 }, { 7,-9782 }, { 8,-9782 }, + { 0, 0 }, { 0, 0 }, { 11,-9782 }, { 0, 0 }, { 0, 0 }, + { 14,-9782 }, { 15,-9782 }, { 16,-9782 }, { 17,-9782 }, { 18,-9782 }, + { 19,-9782 }, { 20,-9782 }, { 21,-9782 }, { 22,-9782 }, { 23,-9782 }, + { 24,-9782 }, { 25,-9782 }, { 26,-9782 }, { 27,-9782 }, { 28,-9782 }, + { 29,-9782 }, { 30,-9782 }, { 31,-9782 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 39,-9782 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 48,-3091 }, + { 49,-3091 }, { 50,-3091 }, { 51,-3091 }, { 52,-3091 }, { 53,-3091 }, + { 54,-3091 }, { 55,-3091 }, { 56,-3091 }, { 57,-3091 }, { 0, 0 }, + { 59,-9782 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 65,-9782 }, { 66,-9782 }, { 67,-9782 }, { 68,-9782 }, + { 69,-5448 }, { 70,-9782 }, { 71,-9782 }, { 72,-9782 }, { 73,-9782 }, + { 74,-9782 }, { 75,-9782 }, { 76,-9782 }, { 77,-9782 }, { 78,-9782 }, + { 79,-9782 }, { 80,-9782 }, { 81,-9782 }, { 82,-9782 }, { 83,-9782 }, + { 84,-9782 }, { 85,-9782 }, { 86,-9782 }, { 87,-9782 }, { 88,-9782 }, + { 89,-9782 }, { 90,-9782 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + + { 94,-9782 }, { 95,-2833 }, { 96,-9782 }, { 97,-9782 }, { 98,-9782 }, + { 99,-9782 }, { 100,-9782 }, { 101,-5448 }, { 102,-9782 }, { 103,-9782 }, + { 104,-9782 }, { 105,-9782 }, { 106,-9782 }, { 107,-9782 }, { 108,-9782 }, + { 109,-9782 }, { 110,-9782 }, { 111,-9782 }, { 112,-9782 }, { 113,-9782 }, + { 114,-9782 }, { 115,-9782 }, { 116,-9782 }, { 117,-9782 }, { 118,-9782 }, + { 119,-9782 }, { 120,-9782 }, { 121,-9782 }, { 122,-9782 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 126,-9782 }, { 127,-9782 }, { 128,-9782 }, + { 129,-9782 }, { 130,-9782 }, { 131,-9782 }, { 132,-9782 }, { 133,-9782 }, + { 134,-9782 }, { 135,-9782 }, { 136,-9782 }, { 137,-9782 }, { 138,-9782 }, + { 139,-9782 }, { 140,-9782 }, { 141,-9782 }, { 142,-9782 }, { 143,-9782 }, + + { 144,-9782 }, { 145,-9782 }, { 146,-9782 }, { 147,-9782 }, { 148,-9782 }, + { 149,-9782 }, { 150,-9782 }, { 151,-9782 }, { 152,-9782 }, { 153,-9782 }, + { 154,-9782 }, { 155,-9782 }, { 156,-9782 }, { 157,-9782 }, { 158,-9782 }, + { 159,-9782 }, { 160,-9782 }, { 161,-9782 }, { 162,-9782 }, { 163,-9782 }, + { 164,-9782 }, { 165,-9782 }, { 166,-9782 }, { 167,-9782 }, { 168,-9782 }, + { 169,-9782 }, { 170,-9782 }, { 171,-9782 }, { 172,-9782 }, { 173,-9782 }, + { 174,-9782 }, { 175,-9782 }, { 176,-9782 }, { 177,-9782 }, { 178,-9782 }, + { 179,-9782 }, { 180,-9782 }, { 181,-9782 }, { 182,-9782 }, { 183,-9782 }, + { 184,-9782 }, { 185,-9782 }, { 186,-9782 }, { 187,-9782 }, { 188,-9782 }, + { 189,-9782 }, { 190,-9782 }, { 191,-9782 }, { 192,-9782 }, { 193,-9782 }, + + { 194,-9782 }, { 195,-9782 }, { 196,-9782 }, { 197,-9782 }, { 198,-9782 }, + { 199,-9782 }, { 200,-9782 }, { 201,-9782 }, { 202,-9782 }, { 203,-9782 }, + { 204,-9782 }, { 205,-9782 }, { 206,-9782 }, { 207,-9782 }, { 208,-9782 }, + { 209,-9782 }, { 210,-9782 }, { 211,-9782 }, { 212,-9782 }, { 213,-9782 }, + { 214,-9782 }, { 215,-9782 }, { 216,-9782 }, { 217,-9782 }, { 218,-9782 }, + { 219,-9782 }, { 220,-9782 }, { 221,-9782 }, { 222,-9782 }, { 223,-9782 }, + { 224,-9782 }, { 225,-9782 }, { 226,-9782 }, { 227,-9782 }, { 228,-9782 }, + { 229,-9782 }, { 230,-9782 }, { 231,-9782 }, { 232,-9782 }, { 233,-9782 }, + { 234,-9782 }, { 235,-9782 }, { 236,-9782 }, { 237,-9782 }, { 238,-9782 }, + { 239,-9782 }, { 240,-9782 }, { 241,-9782 }, { 242,-9782 }, { 243,-9782 }, + + { 244,-9782 }, { 245,-9782 }, { 246,-9782 }, { 247,-9782 }, { 248,-9782 }, + { 249,-9782 }, { 250,-9782 }, { 251,-9782 }, { 252,-9782 }, { 253,-9782 }, + { 254,-9782 }, { 255,-9782 }, { 256,-9782 }, { 0, 40 }, { 0,1063 }, + { 1,-2516 }, { 2,-2516 }, { 3,-2516 }, { 4,-2516 }, { 5,-2516 }, + { 6,-2516 }, { 7,-2516 }, { 8,-2516 }, { 0, 0 }, { 0, 0 }, + { 11,-2516 }, { 0, 0 }, { 0, 0 }, { 14,-2516 }, { 15,-2516 }, + { 16,-2516 }, { 17,-2516 }, { 18,-2516 }, { 19,-2516 }, { 20,-2516 }, + { 21,-2516 }, { 22,-2516 }, { 23,-2516 }, { 24,-2516 }, { 25,-2516 }, + { 26,-2516 }, { 27,-2516 }, { 28,-2516 }, { 29,-2516 }, { 30,-2516 }, + { 31,-2516 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 39,-2516 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 48,-2258 }, { 49,-2258 }, { 50,-2258 }, + { 51,-2258 }, { 52,-2258 }, { 53,-2258 }, { 54,-2258 }, { 55,-2258 }, + { 56,-2258 }, { 57,-2258 }, { 0, 0 }, { 59,-2516 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 65,-2516 }, + { 66,-2516 }, { 67,-2516 }, { 68,-2516 }, { 69,-2516 }, { 70,-2516 }, + { 71,-2516 }, { 72,-2516 }, { 73,-2516 }, { 74,-2516 }, { 75,-2516 }, + { 76,-2516 }, { 77,-2516 }, { 78,-2516 }, { 79,-2516 }, { 80,-2516 }, + { 81,-2516 }, { 82,-2516 }, { 83,-2516 }, { 84,-2516 }, { 85,-2516 }, + + { 86,-2516 }, { 87,-2516 }, { 88,-2516 }, { 89,-2516 }, { 90,-2516 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 94,-2516 }, { 95,-2000 }, + { 96,-2516 }, { 97,-2516 }, { 98,-2516 }, { 99,-2516 }, { 100,-2516 }, + { 101,-2516 }, { 102,-2516 }, { 103,-2516 }, { 104,-2516 }, { 105,-2516 }, + { 106,-2516 }, { 107,-2516 }, { 108,-2516 }, { 109,-2516 }, { 110,-2516 }, + { 111,-2516 }, { 112,-2516 }, { 113,-2516 }, { 114,-2516 }, { 115,-2516 }, + { 116,-2516 }, { 117,-2516 }, { 118,-2516 }, { 119,-2516 }, { 120,-2516 }, + { 121,-2516 }, { 122,-2516 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 126,-2516 }, { 127,-2516 }, { 128,-2516 }, { 129,-2516 }, { 130,-2516 }, + { 131,-2516 }, { 132,-2516 }, { 133,-2516 }, { 134,-2516 }, { 135,-2516 }, + + { 136,-2516 }, { 137,-2516 }, { 138,-2516 }, { 139,-2516 }, { 140,-2516 }, + { 141,-2516 }, { 142,-2516 }, { 143,-2516 }, { 144,-2516 }, { 145,-2516 }, + { 146,-2516 }, { 147,-2516 }, { 148,-2516 }, { 149,-2516 }, { 150,-2516 }, + { 151,-2516 }, { 152,-2516 }, { 153,-2516 }, { 154,-2516 }, { 155,-2516 }, + { 156,-2516 }, { 157,-2516 }, { 158,-2516 }, { 159,-2516 }, { 160,-2516 }, + { 161,-2516 }, { 162,-2516 }, { 163,-2516 }, { 164,-2516 }, { 165,-2516 }, + { 166,-2516 }, { 167,-2516 }, { 168,-2516 }, { 169,-2516 }, { 170,-2516 }, + { 171,-2516 }, { 172,-2516 }, { 173,-2516 }, { 174,-2516 }, { 175,-2516 }, + { 176,-2516 }, { 177,-2516 }, { 178,-2516 }, { 179,-2516 }, { 180,-2516 }, + { 181,-2516 }, { 182,-2516 }, { 183,-2516 }, { 184,-2516 }, { 185,-2516 }, + + { 186,-2516 }, { 187,-2516 }, { 188,-2516 }, { 189,-2516 }, { 190,-2516 }, + { 191,-2516 }, { 192,-2516 }, { 193,-2516 }, { 194,-2516 }, { 195,-2516 }, + { 196,-2516 }, { 197,-2516 }, { 198,-2516 }, { 199,-2516 }, { 200,-2516 }, + { 201,-2516 }, { 202,-2516 }, { 203,-2516 }, { 204,-2516 }, { 205,-2516 }, + { 206,-2516 }, { 207,-2516 }, { 208,-2516 }, { 209,-2516 }, { 210,-2516 }, + { 211,-2516 }, { 212,-2516 }, { 213,-2516 }, { 214,-2516 }, { 215,-2516 }, + { 216,-2516 }, { 217,-2516 }, { 218,-2516 }, { 219,-2516 }, { 220,-2516 }, + { 221,-2516 }, { 222,-2516 }, { 223,-2516 }, { 224,-2516 }, { 225,-2516 }, + { 226,-2516 }, { 227,-2516 }, { 228,-2516 }, { 229,-2516 }, { 230,-2516 }, + { 231,-2516 }, { 232,-2516 }, { 233,-2516 }, { 234,-2516 }, { 235,-2516 }, + + { 236,-2516 }, { 237,-2516 }, { 238,-2516 }, { 239,-2516 }, { 240,-2516 }, + { 241,-2516 }, { 242,-2516 }, { 243,-2516 }, { 244,-2516 }, { 245,-2516 }, + { 246,-2516 }, { 247,-2516 }, { 248,-2516 }, { 249,-2516 }, { 250,-2516 }, + { 251,-2516 }, { 252,-2516 }, { 253,-2516 }, { 254,-2516 }, { 255,-2516 }, + { 256,-2516 }, { 0, 13 }, { 0, 805 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 13 }, + { 0, 782 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 48,-9996 }, { 49,-9996 }, { 50,-9996 }, { 51,-9996 }, { 52,-9996 }, + { 53,-9996 }, { 54,-9996 }, { 55,-9996 }, { 56,-9996 }, { 57,-9996 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 65,-9996 }, { 66,-9996 }, { 67,-9996 }, + { 68,-9996 }, { 69,-9996 }, { 70,-9996 }, { 48, 370 }, { 49, 370 }, + { 50, 370 }, { 51, 370 }, { 52, 370 }, { 53, 370 }, { 54, 370 }, + + { 55, 370 }, { 56, 370 }, { 57, 370 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 65, 370 }, { 66, 370 }, { 67, 370 }, { 68, 370 }, { 69, 370 }, + { 70, 370 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 97,-9996 }, + { 98,-9996 }, { 99,-9996 }, { 100,-9996 }, { 101,-9996 }, { 102,-9996 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 97, 370 }, { 98, 370 }, { 99, 370 }, + { 100, 370 }, { 101, 370 }, { 102, 370 }, { 0, 0 }, { 0, 0 }, + + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 40 }, { 0, 670 }, { 1,-10429 }, { 2,-10429 }, + { 3,-10429 }, { 4,-10429 }, { 5,-10429 }, { 6,-10429 }, { 7,-10429 }, + { 8,-10429 }, { 0, 0 }, { 0, 0 }, { 11,-10429 }, { 0, 0 }, + { 125,-10019 }, { 14,-10429 }, { 15,-10429 }, { 16,-10429 }, { 17,-10429 }, + { 18,-10429 }, { 19,-10429 }, { 20,-10429 }, { 21,-10429 }, { 22,-10429 }, + { 23,-10429 }, { 24,-10429 }, { 25,-10429 }, { 26,-10429 }, { 27,-10429 }, + { 28,-10429 }, { 29,-10429 }, { 30,-10429 }, { 31,-10429 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 39,-10429 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 48,-968 }, { 49,-968 }, { 50,-968 }, { 51,-968 }, { 52,-968 }, + { 53,-968 }, { 54,-968 }, { 55,-968 }, { 56,-968 }, { 57,-968 }, + { 0, 0 }, { 59,-10429 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 65,-10429 }, { 66,-10429 }, { 67,-10429 }, + { 68,-10429 }, { 69,-10429 }, { 70,-10429 }, { 71,-10429 }, { 72,-10429 }, + { 73,-10429 }, { 74,-10429 }, { 75,-10429 }, { 76,-10429 }, { 77,-10429 }, + { 78,-10429 }, { 79,-10429 }, { 80,-10429 }, { 81,-10429 }, { 82,-10429 }, + { 83,-10429 }, { 84,-10429 }, { 85,-10429 }, { 86,-10429 }, { 87,-10429 }, + { 88,-10429 }, { 89,-10429 }, { 90,-10429 }, { 0, 0 }, { 0, 0 }, + + { 0, 0 }, { 94,-10429 }, { 95,-710 }, { 96,-10429 }, { 97,-10429 }, + { 98,-10429 }, { 99,-10429 }, { 100,-10429 }, { 101,-10429 }, { 102,-10429 }, + { 103,-10429 }, { 104,-10429 }, { 105,-10429 }, { 106,-10429 }, { 107,-10429 }, + { 108,-10429 }, { 109,-10429 }, { 110,-10429 }, { 111,-10429 }, { 112,-10429 }, + { 113,-10429 }, { 114,-10429 }, { 115,-10429 }, { 116,-10429 }, { 117,-10429 }, + { 118,-10429 }, { 119,-10429 }, { 120,-10429 }, { 121,-10429 }, { 122,-10429 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 126,-10429 }, { 127,-10429 }, + { 128,-10429 }, { 129,-10429 }, { 130,-10429 }, { 131,-10429 }, { 132,-10429 }, + { 133,-10429 }, { 134,-10429 }, { 135,-10429 }, { 136,-10429 }, { 137,-10429 }, + { 138,-10429 }, { 139,-10429 }, { 140,-10429 }, { 141,-10429 }, { 142,-10429 }, + + { 143,-10429 }, { 144,-10429 }, { 145,-10429 }, { 146,-10429 }, { 147,-10429 }, + { 148,-10429 }, { 149,-10429 }, { 150,-10429 }, { 151,-10429 }, { 152,-10429 }, + { 153,-10429 }, { 154,-10429 }, { 155,-10429 }, { 156,-10429 }, { 157,-10429 }, + { 158,-10429 }, { 159,-10429 }, { 160,-10429 }, { 161,-10429 }, { 162,-10429 }, + { 163,-10429 }, { 164,-10429 }, { 165,-10429 }, { 166,-10429 }, { 167,-10429 }, + { 168,-10429 }, { 169,-10429 }, { 170,-10429 }, { 171,-10429 }, { 172,-10429 }, + { 173,-10429 }, { 174,-10429 }, { 175,-10429 }, { 176,-10429 }, { 177,-10429 }, + { 178,-10429 }, { 179,-10429 }, { 180,-10429 }, { 181,-10429 }, { 182,-10429 }, + { 183,-10429 }, { 184,-10429 }, { 185,-10429 }, { 186,-10429 }, { 187,-10429 }, + { 188,-10429 }, { 189,-10429 }, { 190,-10429 }, { 191,-10429 }, { 192,-10429 }, + + { 193,-10429 }, { 194,-10429 }, { 195,-10429 }, { 196,-10429 }, { 197,-10429 }, + { 198,-10429 }, { 199,-10429 }, { 200,-10429 }, { 201,-10429 }, { 202,-10429 }, + { 203,-10429 }, { 204,-10429 }, { 205,-10429 }, { 206,-10429 }, { 207,-10429 }, + { 208,-10429 }, { 209,-10429 }, { 210,-10429 }, { 211,-10429 }, { 212,-10429 }, + { 213,-10429 }, { 214,-10429 }, { 215,-10429 }, { 216,-10429 }, { 217,-10429 }, + { 218,-10429 }, { 219,-10429 }, { 220,-10429 }, { 221,-10429 }, { 222,-10429 }, + { 223,-10429 }, { 224,-10429 }, { 225,-10429 }, { 226,-10429 }, { 227,-10429 }, + { 228,-10429 }, { 229,-10429 }, { 230,-10429 }, { 231,-10429 }, { 232,-10429 }, + { 233,-10429 }, { 234,-10429 }, { 235,-10429 }, { 236,-10429 }, { 237,-10429 }, + { 238,-10429 }, { 239,-10429 }, { 240,-10429 }, { 241,-10429 }, { 242,-10429 }, + + { 243,-10429 }, { 244,-10429 }, { 245,-10429 }, { 246,-10429 }, { 247,-10429 }, + { 248,-10429 }, { 249,-10429 }, { 250,-10429 }, { 251,-10429 }, { 252,-10429 }, + { 253,-10429 }, { 254,-10429 }, { 255,-10429 }, { 256,-10429 }, { 0, 13 }, + { 0, 412 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + + { 0, 0 }, { 0, 0 }, { 0, 13 }, { 0, 374 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 48, 38 }, { 49, 38 }, + { 50, 38 }, { 51, 38 }, { 52, 38 }, { 53, 38 }, { 54, 38 }, + { 55, 38 }, { 56, 38 }, { 57, 38 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 65, 38 }, { 66, 38 }, { 67, 38 }, { 68, 38 }, { 69, 38 }, + { 70, 38 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + + { 0, 0 }, { 48, 116 }, { 49, 116 }, { 50, 116 }, { 51, 116 }, + { 52, 116 }, { 53, 116 }, { 54, 116 }, { 55, 116 }, { 56, 116 }, + { 57, 116 }, { 0, 0 }, { 97, 38 }, { 98, 38 }, { 99, 38 }, + { 100, 38 }, { 101, 38 }, { 102, 38 }, { 65, 116 }, { 66, 116 }, + { 67, 116 }, { 68, 116 }, { 69, 116 }, { 70, 116 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 13 }, { 0, 296 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 125,-10389 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + + { 97, 116 }, { 98, 116 }, { 99, 116 }, { 100, 116 }, { 101, 116 }, + { 102, 116 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 13 }, { 0, 258 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 125,-10427 }, { 48,-4797 }, + { 49,-4797 }, { 50,-4797 }, { 51,-4797 }, { 52,-4797 }, { 53,-4797 }, + { 54,-4797 }, { 55,-4797 }, { 56,-4797 }, { 57,-4797 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 65,-4797 }, { 66,-4797 }, { 67,-4797 }, { 68,-4797 }, + + { 69,-4797 }, { 70,-4797 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 48,-7457 }, { 49,-7457 }, { 50,-7457 }, + { 51,-7457 }, { 52,-7457 }, { 53,-7457 }, { 54,-7457 }, { 55,-7457 }, + { 56,-7457 }, { 57,-7457 }, { 0, 0 }, { 97,-4797 }, { 98,-4797 }, + { 99,-4797 }, { 100,-4797 }, { 101,-4797 }, { 102,-4797 }, { 65,-7457 }, + { 66,-7457 }, { 67,-7457 }, { 68,-7457 }, { 69,-7457 }, { 70,-7457 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 123,-4774 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 97,-7457 }, { 98,-7457 }, { 99,-7457 }, { 100,-7457 }, + { 101,-7457 }, { 102,-7457 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 125,-10543 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, 0 }, { 257, 54 }, { 1, 0 }, }; + +static __thread const struct yy_trans_info *yy_start_state_list[11] = + { + &yy_transition[1], + &yy_transition[3], + &yy_transition[261], + &yy_transition[519], + &yy_transition[777], + &yy_transition[1035], + &yy_transition[1293], + &yy_transition[1551], + &yy_transition[1809], + &yy_transition[2067], + &yy_transition[2325], + + } ; + +extern __thread int yy_flex_debug; +__thread int yy_flex_debug = 0; + +/* The intent behind this definition is that it'll catch + * any uses of REJECT which flex missed. + */ +#define REJECT reject_used_but_not_detected +#define yymore() yymore_used_but_not_detected +#define YY_MORE_ADJ 0 +#define YY_RESTORE_YY_MORE_OFFSET +__thread char *yytext; +#line 1 "jsonpath_scan.l" + +#line 33 "jsonpath_scan.l" +static __thread JsonPathString scanstring; + +/* Handles to the buffer that the lexer uses internally */ +static __thread YY_BUFFER_STATE scanbufhandle; +static __thread char *scanbuf; +static __thread int scanbuflen; + +static void addstring(bool init, char *s, int l); +static void addchar(bool init, char c); +static enum yytokentype checkKeyword(void); +static bool parseUnicode(char *s, int l, struct Node *escontext); +static bool parseHexChar(char *s, struct Node *escontext); + +/* Avoid exit() on fatal scanner errors (a bit ugly -- see yy_fatal_error) */ +#undef fprintf +#define fprintf(file, fmt, msg) fprintf_to_ereport(fmt, msg) + +static void +fprintf_to_ereport(const char *fmt, const char *msg) +{ + ereport(ERROR, (errmsg_internal("%s", msg))); +} + +/* LCOV_EXCL_START */ + +#line 4161 "jsonpath_scan.c" +#define YY_NO_INPUT 1 +/* + * We use exclusive states for quoted and non-quoted strings, + * quoted variable names and C-style comments. + * Exclusive states: + * <xq> - quoted strings + * <xnq> - non-quoted strings + * <xvq> - quoted variable names + * <xc> - C-style comment + */ + +/* "other" means anything that's not special, blank, or '\' or '"' */ +/* DecimalInteger in ECMAScript; must not start with 0 unless it's exactly 0 */ +/* DecimalDigits in ECMAScript; only used as part of other rules */ +/* Non-decimal integers; in ECMAScript, these must not have underscore after prefix */ +#line 4177 "jsonpath_scan.c" + +#define INITIAL 0 +#define xq 1 +#define xnq 2 +#define xvq 3 +#define xc 4 + +#ifndef YY_NO_UNISTD_H +/* Special case for "unistd.h", since it is non-ANSI. We include it way + * down here because we want the user's section 1 to have been scanned first. + * The user has a chance to override it with an option. + */ +#include <unistd.h> +#endif + +#ifndef YY_EXTRA_TYPE +#define YY_EXTRA_TYPE void * +#endif + +static int yy_init_globals ( void ); + +/* Accessor methods to globals. + These are made visible to non-reentrant scanners for convenience. */ + +int yylex_destroy ( void ); + +int yyget_debug ( void ); + +void yyset_debug ( int debug_flag ); + +YY_EXTRA_TYPE yyget_extra ( void ); + +void yyset_extra ( YY_EXTRA_TYPE user_defined ); + +FILE *yyget_in ( void ); + +void yyset_in ( FILE * _in_str ); + +FILE *yyget_out ( void ); + +void yyset_out ( FILE * _out_str ); + + int yyget_leng ( void ); + +char *yyget_text ( void ); + +int yyget_lineno ( void ); + +void yyset_lineno ( int _line_number ); + +YYSTYPE * yyget_lval ( void ); + +void yyset_lval ( YYSTYPE * yylval_param ); + +/* Macros after this point can all be overridden by user definitions in + * section 1. + */ + +#ifndef YY_SKIP_YYWRAP +#ifdef __cplusplus +extern "C" int yywrap ( void ); +#else +extern int yywrap ( void ); +#endif +#endif + +#ifndef YY_NO_UNPUT + +#endif + +#ifndef yytext_ptr +static void yy_flex_strncpy ( char *, const char *, int ); +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen ( const char * ); +#endif + +#ifndef YY_NO_INPUT +#ifdef __cplusplus +static int yyinput ( void ); +#else +static int input ( void ); +#endif + +#endif + +/* Amount of stuff to slurp up with each read. */ +#ifndef YY_READ_BUF_SIZE +#ifdef __ia64__ +/* On IA-64, the buffer size is 16k, not 8k */ +#define YY_READ_BUF_SIZE 16384 +#else +#define YY_READ_BUF_SIZE 8192 +#endif /* __ia64__ */ +#endif + +/* Copy whatever the last rule matched to the standard output. */ +#ifndef ECHO +/* This used to be an fputs(), but since the string might contain NUL's, + * we now use fwrite(). + */ +#define ECHO do { if (fwrite( yytext, (size_t) yyleng, 1, yyout )) {} } while (0) +#endif + +/* Gets input and stuffs it into "buf". number of characters read, or YY_NULL, + * is returned in "result". + */ +#ifndef YY_INPUT +#define YY_INPUT(buf,result,max_size) \ + if ( YY_CURRENT_BUFFER_LVALUE->yy_is_interactive ) \ + { \ + int c = '*'; \ + int n; \ + for ( n = 0; n < max_size && \ + (c = getc( yyin )) != EOF && c != '\n'; ++n ) \ + buf[n] = (char) c; \ + if ( c == '\n' ) \ + buf[n++] = (char) c; \ + if ( c == EOF && ferror( yyin ) ) \ + YY_FATAL_ERROR( "input in flex scanner failed" ); \ + result = n; \ + } \ + else \ + { \ + errno=0; \ + while ( (result = (int) fread(buf, 1, (yy_size_t) max_size, yyin)) == 0 && ferror(yyin)) \ + { \ + if( errno != EINTR) \ + { \ + YY_FATAL_ERROR( "input in flex scanner failed" ); \ + break; \ + } \ + errno=0; \ + clearerr(yyin); \ + } \ + }\ +\ + +#endif + +/* No semi-colon after return; correct usage is to write "yyterminate();" - + * we don't want an extra ';' after the "return" because that will cause + * some compilers to complain about unreachable statements. + */ +#ifndef yyterminate +#define yyterminate() return YY_NULL +#endif + +/* Number of entries by which start-condition stack grows. */ +#ifndef YY_START_STACK_INCR +#define YY_START_STACK_INCR 25 +#endif + +/* Report a fatal error. */ +#ifndef YY_FATAL_ERROR +#define YY_FATAL_ERROR(msg) yy_fatal_error( msg ) +#endif + +/* end tables serialization structures and prototypes */ + +/* Default declaration of generated scanner - a define so the user can + * easily add parameters. + */ +#ifndef YY_DECL +#define YY_DECL_IS_OURS 1 + +extern int yylex \ + (YYSTYPE * yylval_param ); + +#define YY_DECL int yylex \ + (YYSTYPE * yylval_param ) +#endif /* !YY_DECL */ + +/* Code executed at the beginning of each rule, after yytext and yyleng + * have been set up. + */ +#ifndef YY_USER_ACTION +#define YY_USER_ACTION +#endif + +/* Code executed at the end of each rule. */ +#ifndef YY_BREAK +#define YY_BREAK /*LINTED*/break; +#endif + +#define YY_RULE_SETUP \ + YY_USER_ACTION + +/** The main scanner function which does all the work. + */ +YY_DECL +{ + yy_state_type yy_current_state; + char *yy_cp, *yy_bp; + int yy_act; + + YYSTYPE * yylval; + + yylval = yylval_param; + + if ( !(yy_init) ) + { + (yy_init) = 1; + +#ifdef YY_USER_INIT + YY_USER_INIT; +#endif + + if ( ! (yy_start) ) + (yy_start) = 1; /* first start state */ + + if ( ! yyin ) + yyin = stdin; + + if ( ! yyout ) + yyout = stdout; + + if ( ! YY_CURRENT_BUFFER ) { + yyensure_buffer_stack (); + YY_CURRENT_BUFFER_LVALUE = + yy_create_buffer( yyin, YY_BUF_SIZE ); + } + + yy_load_buffer_state( ); + } + + { +#line 120 "jsonpath_scan.l" + + +#line 4409 "jsonpath_scan.c" + + while ( /*CONSTCOND*/1 ) /* loops until end-of-file is reached */ + { + yy_cp = (yy_c_buf_p); + + /* Support of yytext. */ + *yy_cp = (yy_hold_char); + + /* yy_bp points to the position in yy_ch_buf of the start of + * the current run. + */ + yy_bp = yy_cp; + + yy_current_state = yy_start_state_list[(yy_start)]; +yy_match: + { + const struct yy_trans_info *yy_trans_info; + + YY_CHAR yy_c; + + for ( yy_c = YY_SC_TO_UI(*yy_cp); + (yy_trans_info = &yy_current_state[yy_c])-> + yy_verify == yy_c; + yy_c = YY_SC_TO_UI(*++yy_cp) ) + yy_current_state += yy_trans_info->yy_nxt; + } + +yy_find_action: + yy_act = yy_current_state[-1].yy_nxt; + + YY_DO_BEFORE_ACTION; + +do_action: /* This label is used only to access EOF actions. */ + + switch ( yy_act ) + { /* beginning of action switch */ +case 1: +YY_RULE_SETUP +#line 122 "jsonpath_scan.l" +{ + addstring(false, yytext, yyleng); + } + YY_BREAK +case 2: +/* rule 2 can match eol */ +YY_RULE_SETUP +#line 126 "jsonpath_scan.l" +{ + yylval->str = scanstring; + BEGIN INITIAL; + return checkKeyword(); + } + YY_BREAK +case 3: +YY_RULE_SETUP +#line 132 "jsonpath_scan.l" +{ + yylval->str = scanstring; + BEGIN xc; + } + YY_BREAK +case 4: +YY_RULE_SETUP +#line 137 "jsonpath_scan.l" +{ + yylval->str = scanstring; + yyless(0); + BEGIN INITIAL; + return checkKeyword(); + } + YY_BREAK +case YY_STATE_EOF(xnq): +#line 144 "jsonpath_scan.l" +{ + yylval->str = scanstring; + BEGIN INITIAL; + return checkKeyword(); + } + YY_BREAK +case 5: +YY_RULE_SETUP +#line 150 "jsonpath_scan.l" +{ addchar(false, '\b'); } + YY_BREAK +case 6: +YY_RULE_SETUP +#line 152 "jsonpath_scan.l" +{ addchar(false, '\f'); } + YY_BREAK +case 7: +YY_RULE_SETUP +#line 154 "jsonpath_scan.l" +{ addchar(false, '\n'); } + YY_BREAK +case 8: +YY_RULE_SETUP +#line 156 "jsonpath_scan.l" +{ addchar(false, '\r'); } + YY_BREAK +case 9: +YY_RULE_SETUP +#line 158 "jsonpath_scan.l" +{ addchar(false, '\t'); } + YY_BREAK +case 10: +YY_RULE_SETUP +#line 160 "jsonpath_scan.l" +{ addchar(false, '\v'); } + YY_BREAK +case 11: +YY_RULE_SETUP +#line 162 "jsonpath_scan.l" +{ + if (!parseUnicode(yytext, yyleng, escontext)) + yyterminate(); + } + YY_BREAK +case 12: +YY_RULE_SETUP +#line 167 "jsonpath_scan.l" +{ + if (!parseHexChar(yytext, escontext)) + yyterminate(); + } + YY_BREAK +case 13: +YY_RULE_SETUP +#line 172 "jsonpath_scan.l" +{ + jsonpath_yyerror(NULL, escontext, + "invalid Unicode escape sequence"); + yyterminate(); + } + YY_BREAK +case 14: +YY_RULE_SETUP +#line 178 "jsonpath_scan.l" +{ + jsonpath_yyerror(NULL, escontext, + "invalid hexadecimal character sequence"); + yyterminate(); + } + YY_BREAK +case 15: +YY_RULE_SETUP +#line 184 "jsonpath_scan.l" +{ + /* throw back the \\, and treat as unicode */ + yyless(yyleng - 1); + if (!parseUnicode(yytext, yyleng, escontext)) + yyterminate(); + } + YY_BREAK +case 16: +YY_RULE_SETUP +#line 191 "jsonpath_scan.l" +{ addchar(false, yytext[1]); } + YY_BREAK +case 17: +YY_RULE_SETUP +#line 193 "jsonpath_scan.l" +{ + jsonpath_yyerror(NULL, escontext, + "unexpected end after backslash"); + yyterminate(); + } + YY_BREAK +case YY_STATE_EOF(xq): +case YY_STATE_EOF(xvq): +#line 199 "jsonpath_scan.l" +{ + jsonpath_yyerror(NULL, escontext, + "unterminated quoted string"); + yyterminate(); + } + YY_BREAK +case 18: +YY_RULE_SETUP +#line 205 "jsonpath_scan.l" +{ + yylval->str = scanstring; + BEGIN INITIAL; + return STRING_P; + } + YY_BREAK +case 19: +YY_RULE_SETUP +#line 211 "jsonpath_scan.l" +{ + yylval->str = scanstring; + BEGIN INITIAL; + return VARIABLE_P; + } + YY_BREAK +case 20: +/* rule 20 can match eol */ +YY_RULE_SETUP +#line 217 "jsonpath_scan.l" +{ addstring(false, yytext, yyleng); } + YY_BREAK +case 21: +YY_RULE_SETUP +#line 219 "jsonpath_scan.l" +{ BEGIN INITIAL; } + YY_BREAK +case 22: +/* rule 22 can match eol */ +YY_RULE_SETUP +#line 221 "jsonpath_scan.l" +{ } + YY_BREAK +case 23: +YY_RULE_SETUP +#line 223 "jsonpath_scan.l" +{ } + YY_BREAK +case YY_STATE_EOF(xc): +#line 225 "jsonpath_scan.l" +{ + jsonpath_yyerror( + NULL, escontext, + "unexpected end of comment"); + yyterminate(); + } + YY_BREAK +case 24: +YY_RULE_SETUP +#line 231 "jsonpath_scan.l" +{ return AND_P; } + YY_BREAK +case 25: +YY_RULE_SETUP +#line 233 "jsonpath_scan.l" +{ return OR_P; } + YY_BREAK +case 26: +YY_RULE_SETUP +#line 235 "jsonpath_scan.l" +{ return NOT_P; } + YY_BREAK +case 27: +YY_RULE_SETUP +#line 237 "jsonpath_scan.l" +{ return ANY_P; } + YY_BREAK +case 28: +YY_RULE_SETUP +#line 239 "jsonpath_scan.l" +{ return LESS_P; } + YY_BREAK +case 29: +YY_RULE_SETUP +#line 241 "jsonpath_scan.l" +{ return LESSEQUAL_P; } + YY_BREAK +case 30: +YY_RULE_SETUP +#line 243 "jsonpath_scan.l" +{ return EQUAL_P; } + YY_BREAK +case 31: +YY_RULE_SETUP +#line 245 "jsonpath_scan.l" +{ return NOTEQUAL_P; } + YY_BREAK +case 32: +YY_RULE_SETUP +#line 247 "jsonpath_scan.l" +{ return NOTEQUAL_P; } + YY_BREAK +case 33: +YY_RULE_SETUP +#line 249 "jsonpath_scan.l" +{ return GREATEREQUAL_P; } + YY_BREAK +case 34: +YY_RULE_SETUP +#line 251 "jsonpath_scan.l" +{ return GREATER_P; } + YY_BREAK +case 35: +YY_RULE_SETUP +#line 253 "jsonpath_scan.l" +{ + addstring(true, yytext + 1, yyleng - 1); + addchar(false, '\0'); + yylval->str = scanstring; + return VARIABLE_P; + } + YY_BREAK +case 36: +YY_RULE_SETUP +#line 260 "jsonpath_scan.l" +{ + addchar(true, '\0'); + BEGIN xvq; + } + YY_BREAK +case 37: +YY_RULE_SETUP +#line 265 "jsonpath_scan.l" +{ return *yytext; } + YY_BREAK +case 38: +/* rule 38 can match eol */ +YY_RULE_SETUP +#line 267 "jsonpath_scan.l" +{ /* ignore */ } + YY_BREAK +case 39: +YY_RULE_SETUP +#line 269 "jsonpath_scan.l" +{ + addchar(true, '\0'); + BEGIN xc; + } + YY_BREAK +case 40: +YY_RULE_SETUP +#line 274 "jsonpath_scan.l" +{ + addstring(true, yytext, yyleng); + addchar(false, '\0'); + yylval->str = scanstring; + return NUMERIC_P; + } + YY_BREAK +case 41: +YY_RULE_SETUP +#line 281 "jsonpath_scan.l" +{ + addstring(true, yytext, yyleng); + addchar(false, '\0'); + yylval->str = scanstring; + return NUMERIC_P; + } + YY_BREAK +case 42: +YY_RULE_SETUP +#line 288 "jsonpath_scan.l" +{ + addstring(true, yytext, yyleng); + addchar(false, '\0'); + yylval->str = scanstring; + return INT_P; + } + YY_BREAK +case 43: +YY_RULE_SETUP +#line 295 "jsonpath_scan.l" +{ + addstring(true, yytext, yyleng); + addchar(false, '\0'); + yylval->str = scanstring; + return INT_P; + } + YY_BREAK +case 44: +YY_RULE_SETUP +#line 302 "jsonpath_scan.l" +{ + addstring(true, yytext, yyleng); + addchar(false, '\0'); + yylval->str = scanstring; + return INT_P; + } + YY_BREAK +case 45: +YY_RULE_SETUP +#line 309 "jsonpath_scan.l" +{ + addstring(true, yytext, yyleng); + addchar(false, '\0'); + yylval->str = scanstring; + return INT_P; + } + YY_BREAK +case 46: +YY_RULE_SETUP +#line 316 "jsonpath_scan.l" +{ + jsonpath_yyerror( + NULL, escontext, + "invalid numeric literal"); + yyterminate(); + } + YY_BREAK +case 47: +YY_RULE_SETUP +#line 322 "jsonpath_scan.l" +{ + jsonpath_yyerror( + NULL, escontext, + "trailing junk after numeric literal"); + yyterminate(); + } + YY_BREAK +case 48: +YY_RULE_SETUP +#line 328 "jsonpath_scan.l" +{ + jsonpath_yyerror( + NULL, escontext, + "trailing junk after numeric literal"); + yyterminate(); + } + YY_BREAK +case 49: +YY_RULE_SETUP +#line 334 "jsonpath_scan.l" +{ + jsonpath_yyerror( + NULL, escontext, + "trailing junk after numeric literal"); + yyterminate(); + } + YY_BREAK +case 50: +YY_RULE_SETUP +#line 340 "jsonpath_scan.l" +{ + addchar(true, '\0'); + BEGIN xq; + } + YY_BREAK +case 51: +YY_RULE_SETUP +#line 345 "jsonpath_scan.l" +{ + yyless(0); + addchar(true, '\0'); + BEGIN xnq; + } + YY_BREAK +case 52: +YY_RULE_SETUP +#line 351 "jsonpath_scan.l" +{ + addstring(true, yytext, yyleng); + BEGIN xnq; + } + YY_BREAK +case YY_STATE_EOF(INITIAL): +#line 356 "jsonpath_scan.l" +{ yyterminate(); } + YY_BREAK +case 53: +YY_RULE_SETUP +#line 358 "jsonpath_scan.l" +YY_FATAL_ERROR( "flex scanner jammed" ); + YY_BREAK +#line 4861 "jsonpath_scan.c" + + case YY_END_OF_BUFFER: + { + /* Amount of text matched not including the EOB char. */ + int yy_amount_of_matched_text = (int) (yy_cp - (yytext_ptr)) - 1; + + /* Undo the effects of YY_DO_BEFORE_ACTION. */ + *yy_cp = (yy_hold_char); + YY_RESTORE_YY_MORE_OFFSET + + if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_NEW ) + { + /* We're scanning a new file or input source. It's + * possible that this happened because the user + * just pointed yyin at a new source and called + * yylex(). If so, then we have to assure + * consistency between YY_CURRENT_BUFFER and our + * globals. Here is the right place to do so, because + * this is the first action (other than possibly a + * back-up) that will match for the new input source. + */ + (yy_n_chars) = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; + YY_CURRENT_BUFFER_LVALUE->yy_input_file = yyin; + YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = YY_BUFFER_NORMAL; + } + + /* Note that here we test for yy_c_buf_p "<=" to the position + * of the first EOB in the buffer, since yy_c_buf_p will + * already have been incremented past the NUL character + * (since all states make transitions on EOB to the + * end-of-buffer state). Contrast this with the test + * in input(). + */ + if ( (yy_c_buf_p) <= &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] ) + { /* This was really a NUL. */ + yy_state_type yy_next_state; + + (yy_c_buf_p) = (yytext_ptr) + yy_amount_of_matched_text; + + yy_current_state = yy_get_previous_state( ); + + /* Okay, we're now positioned to make the NUL + * transition. We couldn't have + * yy_get_previous_state() go ahead and do it + * for us because it doesn't know how to deal + * with the possibility of jamming (and we don't + * want to build jamming into it because then it + * will run more slowly). + */ + + yy_next_state = yy_try_NUL_trans( yy_current_state ); + + yy_bp = (yytext_ptr) + YY_MORE_ADJ; + + if ( yy_next_state ) + { + /* Consume the NUL. */ + yy_cp = ++(yy_c_buf_p); + yy_current_state = yy_next_state; + goto yy_match; + } + + else + { + yy_cp = (yy_c_buf_p); + goto yy_find_action; + } + } + + else switch ( yy_get_next_buffer( ) ) + { + case EOB_ACT_END_OF_FILE: + { + (yy_did_buffer_switch_on_eof) = 0; + + if ( yywrap( ) ) + { + /* Note: because we've taken care in + * yy_get_next_buffer() to have set up + * yytext, we can now set up + * yy_c_buf_p so that if some total + * hoser (like flex itself) wants to + * call the scanner after we return the + * YY_NULL, it'll still work - another + * YY_NULL will get returned. + */ + (yy_c_buf_p) = (yytext_ptr) + YY_MORE_ADJ; + + yy_act = YY_STATE_EOF(YY_START); + goto do_action; + } + + else + { + if ( ! (yy_did_buffer_switch_on_eof) ) + YY_NEW_FILE; + } + break; + } + + case EOB_ACT_CONTINUE_SCAN: + (yy_c_buf_p) = + (yytext_ptr) + yy_amount_of_matched_text; + + yy_current_state = yy_get_previous_state( ); + + yy_cp = (yy_c_buf_p); + yy_bp = (yytext_ptr) + YY_MORE_ADJ; + goto yy_match; + + case EOB_ACT_LAST_MATCH: + (yy_c_buf_p) = + &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)]; + + yy_current_state = yy_get_previous_state( ); + + yy_cp = (yy_c_buf_p); + yy_bp = (yytext_ptr) + YY_MORE_ADJ; + goto yy_find_action; + } + break; + } + + default: + YY_FATAL_ERROR( + "fatal flex scanner internal error--no action found" ); + } /* end of action switch */ + } /* end of scanning one token */ + } /* end of user's declarations */ +} /* end of yylex */ + +/* yy_get_next_buffer - try to read in a new buffer + * + * Returns a code representing an action: + * EOB_ACT_LAST_MATCH - + * EOB_ACT_CONTINUE_SCAN - continue scanning from current position + * EOB_ACT_END_OF_FILE - end of file + */ +static int yy_get_next_buffer (void) +{ + char *dest = YY_CURRENT_BUFFER_LVALUE->yy_ch_buf; + char *source = (yytext_ptr); + int number_to_move, i; + int ret_val; + + if ( (yy_c_buf_p) > &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars) + 1] ) + YY_FATAL_ERROR( + "fatal flex scanner internal error--end of buffer missed" ); + + if ( YY_CURRENT_BUFFER_LVALUE->yy_fill_buffer == 0 ) + { /* Don't try to fill the buffer, so this is an EOF. */ + if ( (yy_c_buf_p) - (yytext_ptr) - YY_MORE_ADJ == 1 ) + { + /* We matched a single character, the EOB, so + * treat this as a final EOF. + */ + return EOB_ACT_END_OF_FILE; + } + + else + { + /* We matched some text prior to the EOB, first + * process it. + */ + return EOB_ACT_LAST_MATCH; + } + } + + /* Try to read more data. */ + + /* First move last chars to start of buffer. */ + number_to_move = (int) ((yy_c_buf_p) - (yytext_ptr) - 1); + + for ( i = 0; i < number_to_move; ++i ) + *(dest++) = *(source++); + + if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_EOF_PENDING ) + /* don't do the read, it's not guaranteed to return an EOF, + * just force an EOF + */ + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars) = 0; + + else + { + int num_to_read = + YY_CURRENT_BUFFER_LVALUE->yy_buf_size - number_to_move - 1; + + while ( num_to_read <= 0 ) + { /* Not enough room in the buffer - grow it. */ + + /* just a shorter name for the current buffer */ + YY_BUFFER_STATE b = YY_CURRENT_BUFFER_LVALUE; + + int yy_c_buf_p_offset = + (int) ((yy_c_buf_p) - b->yy_ch_buf); + + if ( b->yy_is_our_buffer ) + { + int new_size = b->yy_buf_size * 2; + + if ( new_size <= 0 ) + b->yy_buf_size += b->yy_buf_size / 8; + else + b->yy_buf_size *= 2; + + b->yy_ch_buf = (char *) + /* Include room in for 2 EOB chars. */ + yyrealloc( (void *) b->yy_ch_buf, + (yy_size_t) (b->yy_buf_size + 2) ); + } + else + /* Can't grow it, we don't own it. */ + b->yy_ch_buf = NULL; + + if ( ! b->yy_ch_buf ) + YY_FATAL_ERROR( + "fatal error - scanner input buffer overflow" ); + + (yy_c_buf_p) = &b->yy_ch_buf[yy_c_buf_p_offset]; + + num_to_read = YY_CURRENT_BUFFER_LVALUE->yy_buf_size - + number_to_move - 1; + + } + + if ( num_to_read > YY_READ_BUF_SIZE ) + num_to_read = YY_READ_BUF_SIZE; + + /* Read in more data. */ + YY_INPUT( (&YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move]), + (yy_n_chars), num_to_read ); + + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars); + } + + if ( (yy_n_chars) == 0 ) + { + if ( number_to_move == YY_MORE_ADJ ) + { + ret_val = EOB_ACT_END_OF_FILE; + yyrestart( yyin ); + } + + else + { + ret_val = EOB_ACT_LAST_MATCH; + YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = + YY_BUFFER_EOF_PENDING; + } + } + + else + ret_val = EOB_ACT_CONTINUE_SCAN; + + if (((yy_n_chars) + number_to_move) > YY_CURRENT_BUFFER_LVALUE->yy_buf_size) { + /* Extend the array by 50%, plus the number we really need. */ + int new_size = (yy_n_chars) + number_to_move + ((yy_n_chars) >> 1); + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf = (char *) yyrealloc( + (void *) YY_CURRENT_BUFFER_LVALUE->yy_ch_buf, (yy_size_t) new_size ); + if ( ! YY_CURRENT_BUFFER_LVALUE->yy_ch_buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_get_next_buffer()" ); + /* "- 2" to take care of EOB's */ + YY_CURRENT_BUFFER_LVALUE->yy_buf_size = (int) (new_size - 2); + } + + (yy_n_chars) += number_to_move; + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] = YY_END_OF_BUFFER_CHAR; + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars) + 1] = YY_END_OF_BUFFER_CHAR; + + (yytext_ptr) = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[0]; + + return ret_val; +} + +/* yy_get_previous_state - get the state just before the EOB char was reached */ + + static yy_state_type yy_get_previous_state (void) +{ + yy_state_type yy_current_state; + char *yy_cp; + + yy_current_state = yy_start_state_list[(yy_start)]; + + for ( yy_cp = (yytext_ptr) + YY_MORE_ADJ; yy_cp < (yy_c_buf_p); ++yy_cp ) + { + yy_current_state += yy_current_state[(*yy_cp ? YY_SC_TO_UI(*yy_cp) : 256)].yy_nxt; + } + + return yy_current_state; +} + +/* yy_try_NUL_trans - try to make a transition on the NUL character + * + * synopsis + * next_state = yy_try_NUL_trans( current_state ); + */ + static yy_state_type yy_try_NUL_trans (yy_state_type yy_current_state ) +{ + int yy_is_jam; + + int yy_c = 256; + const struct yy_trans_info *yy_trans_info; + + yy_trans_info = &yy_current_state[(unsigned int) yy_c]; + yy_current_state += yy_trans_info->yy_nxt; + yy_is_jam = (yy_trans_info->yy_verify != yy_c); + + return yy_is_jam ? 0 : yy_current_state; +} + +#ifndef YY_NO_UNPUT + +#endif + +#ifndef YY_NO_INPUT +#ifdef __cplusplus + static int yyinput (void) +#else + static int input (void) +#endif + +{ + int c; + + *(yy_c_buf_p) = (yy_hold_char); + + if ( *(yy_c_buf_p) == YY_END_OF_BUFFER_CHAR ) + { + /* yy_c_buf_p now points to the character we want to return. + * If this occurs *before* the EOB characters, then it's a + * valid NUL; if not, then we've hit the end of the buffer. + */ + if ( (yy_c_buf_p) < &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] ) + /* This was really a NUL. */ + *(yy_c_buf_p) = '\0'; + + else + { /* need more input */ + int offset = (int) ((yy_c_buf_p) - (yytext_ptr)); + ++(yy_c_buf_p); + + switch ( yy_get_next_buffer( ) ) + { + case EOB_ACT_LAST_MATCH: + /* This happens because yy_g_n_b() + * sees that we've accumulated a + * token and flags that we need to + * try matching the token before + * proceeding. But for input(), + * there's no matching to consider. + * So convert the EOB_ACT_LAST_MATCH + * to EOB_ACT_END_OF_FILE. + */ + + /* Reset buffer status. */ + yyrestart( yyin ); + + /*FALLTHROUGH*/ + + case EOB_ACT_END_OF_FILE: + { + if ( yywrap( ) ) + return 0; + + if ( ! (yy_did_buffer_switch_on_eof) ) + YY_NEW_FILE; +#ifdef __cplusplus + return yyinput(); +#else + return input(); +#endif + } + + case EOB_ACT_CONTINUE_SCAN: + (yy_c_buf_p) = (yytext_ptr) + offset; + break; + } + } + } + + c = *(unsigned char *) (yy_c_buf_p); /* cast for 8-bit char's */ + *(yy_c_buf_p) = '\0'; /* preserve yytext */ + (yy_hold_char) = *++(yy_c_buf_p); + + return c; +} +#endif /* ifndef YY_NO_INPUT */ + +/** Immediately switch to a different input stream. + * @param input_file A readable stream. + * + * @note This function does not reset the start condition to @c INITIAL . + */ + void yyrestart (FILE * input_file ) +{ + + if ( ! YY_CURRENT_BUFFER ){ + yyensure_buffer_stack (); + YY_CURRENT_BUFFER_LVALUE = + yy_create_buffer( yyin, YY_BUF_SIZE ); + } + + yy_init_buffer( YY_CURRENT_BUFFER, input_file ); + yy_load_buffer_state( ); +} + +/** Switch to a different input buffer. + * @param new_buffer The new input buffer. + * + */ + void yy_switch_to_buffer (YY_BUFFER_STATE new_buffer ) +{ + + /* TODO. We should be able to replace this entire function body + * with + * yypop_buffer_state(); + * yypush_buffer_state(new_buffer); + */ + yyensure_buffer_stack (); + if ( YY_CURRENT_BUFFER == new_buffer ) + return; + + if ( YY_CURRENT_BUFFER ) + { + /* Flush out information for old buffer. */ + *(yy_c_buf_p) = (yy_hold_char); + YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = (yy_c_buf_p); + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars); + } + + YY_CURRENT_BUFFER_LVALUE = new_buffer; + yy_load_buffer_state( ); + + /* We don't actually know whether we did this switch during + * EOF (yywrap()) processing, but the only time this flag + * is looked at is after yywrap() is called, so it's safe + * to go ahead and always set it. + */ + (yy_did_buffer_switch_on_eof) = 1; +} + +static void yy_load_buffer_state (void) +{ + (yy_n_chars) = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; + (yytext_ptr) = (yy_c_buf_p) = YY_CURRENT_BUFFER_LVALUE->yy_buf_pos; + yyin = YY_CURRENT_BUFFER_LVALUE->yy_input_file; + (yy_hold_char) = *(yy_c_buf_p); +} + +/** Allocate and initialize an input buffer state. + * @param file A readable stream. + * @param size The character buffer size in bytes. When in doubt, use @c YY_BUF_SIZE. + * + * @return the allocated buffer state. + */ + YY_BUFFER_STATE yy_create_buffer (FILE * file, int size ) +{ + YY_BUFFER_STATE b; + + b = (YY_BUFFER_STATE) yyalloc( sizeof( struct yy_buffer_state ) ); + if ( ! b ) + YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" ); + + b->yy_buf_size = size; + + /* yy_ch_buf has to be 2 characters longer than the size given because + * we need to put in 2 end-of-buffer characters. + */ + b->yy_ch_buf = (char *) yyalloc( (yy_size_t) (b->yy_buf_size + 2) ); + if ( ! b->yy_ch_buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" ); + + b->yy_is_our_buffer = 1; + + yy_init_buffer( b, file ); + + return b; +} + +/** Destroy the buffer. + * @param b a buffer created with yy_create_buffer() + * + */ + void yy_delete_buffer (YY_BUFFER_STATE b ) +{ + + if ( ! b ) + return; + + if ( b == YY_CURRENT_BUFFER ) /* Not sure if we should pop here. */ + YY_CURRENT_BUFFER_LVALUE = (YY_BUFFER_STATE) 0; + + if ( b->yy_is_our_buffer ) + yyfree( (void *) b->yy_ch_buf ); + + yyfree( (void *) b ); +} + +/* Initializes or reinitializes a buffer. + * This function is sometimes called more than once on the same buffer, + * such as during a yyrestart() or at EOF. + */ + static void yy_init_buffer (YY_BUFFER_STATE b, FILE * file ) + +{ + int oerrno = errno; + + yy_flush_buffer( b ); + + b->yy_input_file = file; + b->yy_fill_buffer = 1; + + /* If b is the current buffer, then yy_init_buffer was _probably_ + * called from yyrestart() or through yy_get_next_buffer. + * In that case, we don't want to reset the lineno or column. + */ + if (b != YY_CURRENT_BUFFER){ + b->yy_bs_lineno = 1; + b->yy_bs_column = 0; + } + + b->yy_is_interactive = 0; + + errno = oerrno; +} + +/** Discard all buffered characters. On the next scan, YY_INPUT will be called. + * @param b the buffer state to be flushed, usually @c YY_CURRENT_BUFFER. + * + */ + void yy_flush_buffer (YY_BUFFER_STATE b ) +{ + if ( ! b ) + return; + + b->yy_n_chars = 0; + + /* We always need two end-of-buffer characters. The first causes + * a transition to the end-of-buffer state. The second causes + * a jam in that state. + */ + b->yy_ch_buf[0] = YY_END_OF_BUFFER_CHAR; + b->yy_ch_buf[1] = YY_END_OF_BUFFER_CHAR; + + b->yy_buf_pos = &b->yy_ch_buf[0]; + + b->yy_at_bol = 1; + b->yy_buffer_status = YY_BUFFER_NEW; + + if ( b == YY_CURRENT_BUFFER ) + yy_load_buffer_state( ); +} + +/** Pushes the new state onto the stack. The new state becomes + * the current state. This function will allocate the stack + * if necessary. + * @param new_buffer The new state. + * + */ +void yypush_buffer_state (YY_BUFFER_STATE new_buffer ) +{ + if (new_buffer == NULL) + return; + + yyensure_buffer_stack(); + + /* This block is copied from yy_switch_to_buffer. */ + if ( YY_CURRENT_BUFFER ) + { + /* Flush out information for old buffer. */ + *(yy_c_buf_p) = (yy_hold_char); + YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = (yy_c_buf_p); + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars); + } + + /* Only push if top exists. Otherwise, replace top. */ + if (YY_CURRENT_BUFFER) + (yy_buffer_stack_top)++; + YY_CURRENT_BUFFER_LVALUE = new_buffer; + + /* copied from yy_switch_to_buffer. */ + yy_load_buffer_state( ); + (yy_did_buffer_switch_on_eof) = 1; +} + +/** Removes and deletes the top of the stack, if present. + * The next element becomes the new top. + * + */ +void yypop_buffer_state (void) +{ + if (!YY_CURRENT_BUFFER) + return; + + yy_delete_buffer(YY_CURRENT_BUFFER ); + YY_CURRENT_BUFFER_LVALUE = NULL; + if ((yy_buffer_stack_top) > 0) + --(yy_buffer_stack_top); + + if (YY_CURRENT_BUFFER) { + yy_load_buffer_state( ); + (yy_did_buffer_switch_on_eof) = 1; + } +} + +/* Allocates the stack if it does not exist. + * Guarantees space for at least one push. + */ +static void yyensure_buffer_stack (void) +{ + yy_size_t num_to_alloc; + + if (!(yy_buffer_stack)) { + + /* First allocation is just for 2 elements, since we don't know if this + * scanner will even need a stack. We use 2 instead of 1 to avoid an + * immediate realloc on the next call. + */ + num_to_alloc = 1; /* After all that talk, this was set to 1 anyways... */ + (yy_buffer_stack) = (struct yy_buffer_state**)yyalloc + (num_to_alloc * sizeof(struct yy_buffer_state*) + ); + if ( ! (yy_buffer_stack) ) + YY_FATAL_ERROR( "out of dynamic memory in yyensure_buffer_stack()" ); + + memset((yy_buffer_stack), 0, num_to_alloc * sizeof(struct yy_buffer_state*)); + + (yy_buffer_stack_max) = num_to_alloc; + (yy_buffer_stack_top) = 0; + return; + } + + if ((yy_buffer_stack_top) >= ((yy_buffer_stack_max)) - 1){ + + /* Increase the buffer to prepare for a possible push. */ + yy_size_t grow_size = 8 /* arbitrary grow size */; + + num_to_alloc = (yy_buffer_stack_max) + grow_size; + (yy_buffer_stack) = (struct yy_buffer_state**)yyrealloc + ((yy_buffer_stack), + num_to_alloc * sizeof(struct yy_buffer_state*) + ); + if ( ! (yy_buffer_stack) ) + YY_FATAL_ERROR( "out of dynamic memory in yyensure_buffer_stack()" ); + + /* zero only the new slots.*/ + memset((yy_buffer_stack) + (yy_buffer_stack_max), 0, grow_size * sizeof(struct yy_buffer_state*)); + (yy_buffer_stack_max) = num_to_alloc; + } +} + +/** Setup the input buffer state to scan directly from a user-specified character buffer. + * @param base the character buffer + * @param size the size in bytes of the character buffer + * + * @return the newly allocated buffer state object. + */ +YY_BUFFER_STATE yy_scan_buffer (char * base, yy_size_t size ) +{ + YY_BUFFER_STATE b; + + if ( size < 2 || + base[size-2] != YY_END_OF_BUFFER_CHAR || + base[size-1] != YY_END_OF_BUFFER_CHAR ) + /* They forgot to leave room for the EOB's. */ + return NULL; + + b = (YY_BUFFER_STATE) yyalloc( sizeof( struct yy_buffer_state ) ); + if ( ! b ) + YY_FATAL_ERROR( "out of dynamic memory in yy_scan_buffer()" ); + + b->yy_buf_size = (int) (size - 2); /* "- 2" to take care of EOB's */ + b->yy_buf_pos = b->yy_ch_buf = base; + b->yy_is_our_buffer = 0; + b->yy_input_file = NULL; + b->yy_n_chars = b->yy_buf_size; + b->yy_is_interactive = 0; + b->yy_at_bol = 1; + b->yy_fill_buffer = 0; + b->yy_buffer_status = YY_BUFFER_NEW; + + yy_switch_to_buffer( b ); + + return b; +} + +/** Setup the input buffer state to scan a string. The next call to yylex() will + * scan from a @e copy of @a str. + * @param yystr a NUL-terminated string to scan + * + * @return the newly allocated buffer state object. + * @note If you want to scan bytes that may contain NUL values, then use + * yy_scan_bytes() instead. + */ +YY_BUFFER_STATE yy_scan_string (const char * yystr ) +{ + + return yy_scan_bytes( yystr, (int) strlen(yystr) ); +} + +/** Setup the input buffer state to scan the given bytes. The next call to yylex() will + * scan from a @e copy of @a bytes. + * @param yybytes the byte buffer to scan + * @param _yybytes_len the number of bytes in the buffer pointed to by @a bytes. + * + * @return the newly allocated buffer state object. + */ +YY_BUFFER_STATE yy_scan_bytes (const char * yybytes, int _yybytes_len ) +{ + YY_BUFFER_STATE b; + char *buf; + yy_size_t n; + int i; + + /* Get memory for full buffer, including space for trailing EOB's. */ + n = (yy_size_t) (_yybytes_len + 2); + buf = (char *) yyalloc( n ); + if ( ! buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_scan_bytes()" ); + + for ( i = 0; i < _yybytes_len; ++i ) + buf[i] = yybytes[i]; + + buf[_yybytes_len] = buf[_yybytes_len+1] = YY_END_OF_BUFFER_CHAR; + + b = yy_scan_buffer( buf, n ); + if ( ! b ) + YY_FATAL_ERROR( "bad buffer in yy_scan_bytes()" ); + + /* It's okay to grow etc. this buffer, and we should throw it + * away when we're done. + */ + b->yy_is_our_buffer = 1; + + return b; +} + +#ifndef YY_EXIT_FAILURE +#define YY_EXIT_FAILURE 2 +#endif + +static void yynoreturn yy_fatal_error (const char* msg ) +{ + fprintf( stderr, "%s\n", msg ); + exit( YY_EXIT_FAILURE ); +} + +/* Redefine yyless() so it works in section 3 code. */ + +#undef yyless +#define yyless(n) \ + do \ + { \ + /* Undo effects of setting up yytext. */ \ + int yyless_macro_arg = (n); \ + YY_LESS_LINENO(yyless_macro_arg);\ + yytext[yyleng] = (yy_hold_char); \ + (yy_c_buf_p) = yytext + yyless_macro_arg; \ + (yy_hold_char) = *(yy_c_buf_p); \ + *(yy_c_buf_p) = '\0'; \ + yyleng = yyless_macro_arg; \ + } \ + while ( 0 ) + +/* Accessor methods (get/set functions) to struct members. */ + +/** Get the current line number. + * + */ +int yyget_lineno (void) +{ + + return yylineno; +} + +/** Get the input stream. + * + */ +FILE *yyget_in (void) +{ + return yyin; +} + +/** Get the output stream. + * + */ +FILE *yyget_out (void) +{ + return yyout; +} + +/** Get the length of the current token. + * + */ +int yyget_leng (void) +{ + return yyleng; +} + +/** Get the current token. + * + */ + +char *yyget_text (void) +{ + return yytext; +} + +/** Set the current line number. + * @param _line_number line number + * + */ +void yyset_lineno (int _line_number ) +{ + + yylineno = _line_number; +} + +/** Set the input stream. This does not discard the current + * input buffer. + * @param _in_str A readable stream. + * + * @see yy_switch_to_buffer + */ +void yyset_in (FILE * _in_str ) +{ + yyin = _in_str ; +} + +void yyset_out (FILE * _out_str ) +{ + yyout = _out_str ; +} + +int yyget_debug (void) +{ + return yy_flex_debug; +} + +void yyset_debug (int _bdebug ) +{ + yy_flex_debug = _bdebug ; +} + +static int yy_init_globals (void) +{ + /* Initialization is the same as for the non-reentrant scanner. + * This function is called from yylex_destroy(), so don't allocate here. + */ + + (yy_buffer_stack) = NULL; + (yy_buffer_stack_top) = 0; + (yy_buffer_stack_max) = 0; + (yy_c_buf_p) = NULL; + (yy_init) = 0; + (yy_start) = 0; + +/* Defined in main.c */ +#ifdef YY_STDINIT + yyin = stdin; + yyout = stdout; +#else + yyin = NULL; + yyout = NULL; +#endif + + /* For future reference: Set errno on error, since we are called by + * yylex_init() + */ + return 0; +} + +/* yylex_destroy is for both reentrant and non-reentrant scanners. */ +int yylex_destroy (void) +{ + + /* Pop the buffer stack, destroying each element. */ + while(YY_CURRENT_BUFFER){ + yy_delete_buffer( YY_CURRENT_BUFFER ); + YY_CURRENT_BUFFER_LVALUE = NULL; + yypop_buffer_state(); + } + + /* Destroy the stack itself. */ + yyfree((yy_buffer_stack) ); + (yy_buffer_stack) = NULL; + + /* Reset the globals. This is important in a non-reentrant scanner so the next time + * yylex() is called, initialization will occur. */ + yy_init_globals( ); + + return 0; +} + +/* + * Internal utility routines. + */ + +#ifndef yytext_ptr +static void yy_flex_strncpy (char* s1, const char * s2, int n ) +{ + + int i; + for ( i = 0; i < n; ++i ) + s1[i] = s2[i]; +} +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen (const char * s ) +{ + int n; + for ( n = 0; s[n]; ++n ) + ; + + return n; +} +#endif + +#define YYTABLES_NAME "yytables" + +#line 358 "jsonpath_scan.l" + + +/* LCOV_EXCL_STOP */ + +void +jsonpath_yyerror(JsonPathParseResult **result, struct Node *escontext, + const char *message) +{ + /* don't overwrite escontext if it's already been set */ + if (SOFT_ERROR_OCCURRED(escontext)) + return; + + if (*yytext == YY_END_OF_BUFFER_CHAR) + { + errsave(escontext, + (errcode(ERRCODE_SYNTAX_ERROR), + /* translator: %s is typically "syntax error" */ + errmsg("%s at end of jsonpath input", _(message)))); + } + else + { + errsave(escontext, + (errcode(ERRCODE_SYNTAX_ERROR), + /* translator: first %s is typically "syntax error" */ + errmsg("%s at or near \"%s\" of jsonpath input", + _(message), yytext))); + } +} + +typedef struct JsonPathKeyword +{ + int16 len; + bool lowercase; + int val; + const char *keyword; +} JsonPathKeyword; + +/* + * Array of key words should be sorted by length and then + * alphabetical order + */ +static const JsonPathKeyword keywords[] = { + { 2, false, IS_P, "is"}, + { 2, false, TO_P, "to"}, + { 3, false, ABS_P, "abs"}, + { 3, false, LAX_P, "lax"}, + { 4, false, FLAG_P, "flag"}, + { 4, false, LAST_P, "last"}, + { 4, true, NULL_P, "null"}, + { 4, false, SIZE_P, "size"}, + { 4, true, TRUE_P, "true"}, + { 4, false, TYPE_P, "type"}, + { 4, false, WITH_P, "with"}, + { 5, true, FALSE_P, "false"}, + { 5, false, FLOOR_P, "floor"}, + { 6, false, DOUBLE_P, "double"}, + { 6, false, EXISTS_P, "exists"}, + { 6, false, STARTS_P, "starts"}, + { 6, false, STRICT_P, "strict"}, + { 7, false, CEILING_P, "ceiling"}, + { 7, false, UNKNOWN_P, "unknown"}, + { 8, false, DATETIME_P, "datetime"}, + { 8, false, KEYVALUE_P, "keyvalue"}, + { 10,false, LIKE_REGEX_P, "like_regex"}, +}; + +/* Check if current scanstring value is a keyword */ +static enum yytokentype +checkKeyword() +{ + int res = IDENT_P; + int diff; + const JsonPathKeyword *StopLow = keywords, + *StopHigh = keywords + lengthof(keywords), + *StopMiddle; + + if (scanstring.len > keywords[lengthof(keywords) - 1].len) + return res; + + while (StopLow < StopHigh) + { + StopMiddle = StopLow + ((StopHigh - StopLow) >> 1); + + if (StopMiddle->len == scanstring.len) + diff = pg_strncasecmp(StopMiddle->keyword, scanstring.val, + scanstring.len); + else + diff = StopMiddle->len - scanstring.len; + + if (diff < 0) + StopLow = StopMiddle + 1; + else if (diff > 0) + StopHigh = StopMiddle; + else + { + if (StopMiddle->lowercase) + diff = strncmp(StopMiddle->keyword, scanstring.val, + scanstring.len); + + if (diff == 0) + res = StopMiddle->val; + + break; + } + } + + return res; +} + +/* + * Called before any actual parsing is done + */ +static void +jsonpath_scanner_init(const char *str, int slen) +{ + if (slen <= 0) + slen = strlen(str); + + /* + * Might be left over after ereport() + */ + yy_init_globals(); + + /* + * Make a scan buffer with special termination needed by flex. + */ + + scanbuflen = slen; + scanbuf = palloc(slen + 2); + memcpy(scanbuf, str, slen); + scanbuf[slen] = scanbuf[slen + 1] = YY_END_OF_BUFFER_CHAR; + scanbufhandle = yy_scan_buffer(scanbuf, slen + 2); + + BEGIN(INITIAL); +} + + +/* + * Called after parsing is done to clean up after jsonpath_scanner_init() + */ +static void +jsonpath_scanner_finish(void) +{ + yy_delete_buffer(scanbufhandle); + pfree(scanbuf); +} + +/* + * Resize scanstring so that it can append string of given length. + * Reinitialize if required. + */ +static void +resizeString(bool init, int appendLen) +{ + if (init) + { + scanstring.total = Max(32, appendLen); + scanstring.val = (char *) palloc(scanstring.total); + scanstring.len = 0; + } + else + { + if (scanstring.len + appendLen >= scanstring.total) + { + while (scanstring.len + appendLen >= scanstring.total) + scanstring.total *= 2; + scanstring.val = repalloc(scanstring.val, scanstring.total); + } + } +} + +/* Add set of bytes at "s" of length "l" to scanstring */ +static void +addstring(bool init, char *s, int l) +{ + resizeString(init, l + 1); + memcpy(scanstring.val + scanstring.len, s, l); + scanstring.len += l; +} + +/* Add single byte "c" to scanstring */ +static void +addchar(bool init, char c) +{ + resizeString(init, 1); + scanstring.val[scanstring.len] = c; + if (c != '\0') + scanstring.len++; +} + +/* Interface to jsonpath parser */ +JsonPathParseResult * +parsejsonpath(const char *str, int len, struct Node *escontext) +{ + JsonPathParseResult *parseresult; + + jsonpath_scanner_init(str, len); + + if (jsonpath_yyparse((void *) &parseresult, escontext) != 0) + jsonpath_yyerror(NULL, escontext, "invalid input"); /* shouldn't happen */ + + jsonpath_scanner_finish(); + + return parseresult; +} + +/* Turn hex character into integer */ +static bool +hexval(char c, int *result, struct Node *escontext) +{ + if (c >= '0' && c <= '9') + { + *result = c - '0'; + return true; + } + if (c >= 'a' && c <= 'f') + { + *result = c - 'a' + 0xA; + return true; + } + if (c >= 'A' && c <= 'F') + { + *result = c - 'A' + 0xA; + return true; + } + jsonpath_yyerror(NULL, escontext, "invalid hexadecimal digit"); + return false; +} + +/* Add given unicode character to scanstring */ +static bool +addUnicodeChar(int ch, struct Node *escontext) +{ + if (ch == 0) + { + /* We can't allow this, since our TEXT type doesn't */ + ereturn(escontext, false, + (errcode(ERRCODE_UNTRANSLATABLE_CHARACTER), + errmsg("unsupported Unicode escape sequence"), + errdetail("\\u0000 cannot be converted to text."))); + } + else + { + char cbuf[MAX_UNICODE_EQUIVALENT_STRING + 1]; + + /* + * If we're trapping the error status, call the noerror form of the + * conversion function. Otherwise call the normal form which provides + * more detailed errors. + */ + + if (! escontext || ! IsA(escontext, ErrorSaveContext)) + pg_unicode_to_server(ch, (unsigned char *) cbuf); + else if (!pg_unicode_to_server_noerror(ch, (unsigned char *) cbuf)) + ereturn(escontext, false, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("could not convert Unicode to server encoding"))); + addstring(false, cbuf, strlen(cbuf)); + } + return true; +} + +/* Add unicode character, processing any surrogate pairs */ +static bool +addUnicode(int ch, int *hi_surrogate, struct Node *escontext) +{ + if (is_utf16_surrogate_first(ch)) + { + if (*hi_surrogate != -1) + ereturn(escontext, false, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s", "jsonpath"), + errdetail("Unicode high surrogate must not follow " + "a high surrogate."))); + *hi_surrogate = ch; + return true; + } + else if (is_utf16_surrogate_second(ch)) + { + if (*hi_surrogate == -1) + ereturn(escontext, false, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s", "jsonpath"), + errdetail("Unicode low surrogate must follow a high " + "surrogate."))); + ch = surrogate_pair_to_codepoint(*hi_surrogate, ch); + *hi_surrogate = -1; + } + else if (*hi_surrogate != -1) + { + ereturn(escontext, false, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s", "jsonpath"), + errdetail("Unicode low surrogate must follow a high " + "surrogate."))); + } + + return addUnicodeChar(ch, escontext); +} + +/* + * parseUnicode was adopted from json_lex_string() in + * src/backend/utils/adt/json.c + */ +static bool +parseUnicode(char *s, int l, struct Node *escontext) +{ + int i = 2; + int hi_surrogate = -1; + + for (i = 2; i < l; i += 2) /* skip '\u' */ + { + int ch = 0; + int j, si; + + if (s[i] == '{') /* parse '\u{XX...}' */ + { + while (s[++i] != '}' && i < l) + { + if (!hexval(s[i], &si, escontext)) + return false; + ch = (ch << 4) | si; + } + i++; /* skip '}' */ + } + else /* parse '\uXXXX' */ + { + for (j = 0; j < 4 && i < l; j++) + { + if (!hexval(s[i++], &si, escontext)) + return false; + ch = (ch << 4) | si; + } + } + + if (! addUnicode(ch, &hi_surrogate, escontext)) + return false; + } + + if (hi_surrogate != -1) + { + ereturn(escontext, false, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s", "jsonpath"), + errdetail("Unicode low surrogate must follow a high " + "surrogate."))); + } + + return true; +} + +/* Parse sequence of hex-encoded characters */ +static bool +parseHexChar(char *s, struct Node *escontext) +{ + int s2, s3, ch; + if (!hexval(s[2], &s2, escontext)) + return false; + if (!hexval(s[3], &s3, escontext)) + return false; + + ch = (s2 << 4) | s3; + + return addUnicodeChar(ch, escontext); +} + +/* + * Interface functions to make flex use palloc() instead of malloc(). + * It'd be better to make these static, but flex insists otherwise. + */ + +void * +jsonpath_yyalloc(yy_size_t bytes) +{ + return palloc(bytes); +} + +void * +jsonpath_yyrealloc(void *ptr, yy_size_t bytes) +{ + if (ptr) + return repalloc(ptr, bytes); + else + return palloc(bytes); +} + +void +jsonpath_yyfree(void *ptr) +{ + if (ptr) + pfree(ptr); +} + diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/levenshtein.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/levenshtein.c new file mode 100644 index 00000000000..f8979776d0d --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/levenshtein.c @@ -0,0 +1,401 @@ +/*------------------------------------------------------------------------- + * + * levenshtein.c + * Levenshtein distance implementation. + * + * Original author: Joe Conway <mail@joeconway.com> + * + * This file is included by varlena.c twice, to provide matching code for (1) + * Levenshtein distance with custom costings, and (2) Levenshtein distance with + * custom costings and a "max" value above which exact distances are not + * interesting. Before the inclusion, we rely on the presence of the inline + * function rest_of_char_same(). + * + * Written based on a description of the algorithm by Michael Gilleland found + * at http://www.merriampark.com/ld.htm. Also looked at levenshtein.c in the + * PHP 4.0.6 distribution for inspiration. Configurable penalty costs + * extension is introduced by Volkan YAZICI <volkan.yazici@gmail.com. + * + * Copyright (c) 2001-2021, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/utils/adt/levenshtein.c + * + *------------------------------------------------------------------------- + */ +#define MAX_LEVENSHTEIN_STRLEN 255 + +/* + * Calculates Levenshtein distance metric between supplied strings, which are + * not necessarily null-terminated. + * + * source: source string, of length slen bytes. + * target: target string, of length tlen bytes. + * ins_c, del_c, sub_c: costs to charge for character insertion, deletion, + * and substitution respectively; (1, 1, 1) costs suffice for common + * cases, but your mileage may vary. + * max_d: if provided and >= 0, maximum distance we care about; see below. + * trusted: caller is trusted and need not obey MAX_LEVENSHTEIN_STRLEN. + * + * One way to compute Levenshtein distance is to incrementally construct + * an (m+1)x(n+1) matrix where cell (i, j) represents the minimum number + * of operations required to transform the first i characters of s into + * the first j characters of t. The last column of the final row is the + * answer. + * + * We use that algorithm here with some modification. In lieu of holding + * the entire array in memory at once, we'll just use two arrays of size + * m+1 for storing accumulated values. At each step one array represents + * the "previous" row and one is the "current" row of the notional large + * array. + * + * If max_d >= 0, we only need to provide an accurate answer when that answer + * is less than or equal to max_d. From any cell in the matrix, there is + * theoretical "minimum residual distance" from that cell to the last column + * of the final row. This minimum residual distance is zero when the + * untransformed portions of the strings are of equal length (because we might + * get lucky and find all the remaining characters matching) and is otherwise + * based on the minimum number of insertions or deletions needed to make them + * equal length. The residual distance grows as we move toward the upper + * right or lower left corners of the matrix. When the max_d bound is + * usefully tight, we can use this property to avoid computing the entirety + * of each row; instead, we maintain a start_column and stop_column that + * identify the portion of the matrix close to the diagonal which can still + * affect the final answer. + */ +int +#ifdef LEVENSHTEIN_LESS_EQUAL +varstr_levenshtein_less_equal(const char *source, int slen, + const char *target, int tlen, + int ins_c, int del_c, int sub_c, + int max_d, bool trusted) +#else +varstr_levenshtein(const char *source, int slen, + const char *target, int tlen, + int ins_c, int del_c, int sub_c, + bool trusted) +#endif +{ + int m, + n; + int *prev; + int *curr; + int *s_char_len = NULL; + int i, + j; + const char *y; + + /* + * For varstr_levenshtein_less_equal, we have real variables called + * start_column and stop_column; otherwise it's just short-hand for 0 and + * m. + */ +#ifdef LEVENSHTEIN_LESS_EQUAL + int start_column, + stop_column; + +#undef START_COLUMN +#undef STOP_COLUMN +#define START_COLUMN start_column +#define STOP_COLUMN stop_column +#else +#undef START_COLUMN +#undef STOP_COLUMN +#define START_COLUMN 0 +#define STOP_COLUMN m +#endif + + /* Convert string lengths (in bytes) to lengths in characters */ + m = pg_mbstrlen_with_len(source, slen); + n = pg_mbstrlen_with_len(target, tlen); + + /* + * We can transform an empty s into t with n insertions, or a non-empty t + * into an empty s with m deletions. + */ + if (!m) + return n * ins_c; + if (!n) + return m * del_c; + + /* + * For security concerns, restrict excessive CPU+RAM usage. (This + * implementation uses O(m) memory and has O(mn) complexity.) If + * "trusted" is true, caller is responsible for not making excessive + * requests, typically by using a small max_d along with strings that are + * bounded, though not necessarily to MAX_LEVENSHTEIN_STRLEN exactly. + */ + if (!trusted && + (m > MAX_LEVENSHTEIN_STRLEN || + n > MAX_LEVENSHTEIN_STRLEN)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("levenshtein argument exceeds maximum length of %d characters", + MAX_LEVENSHTEIN_STRLEN))); + +#ifdef LEVENSHTEIN_LESS_EQUAL + /* Initialize start and stop columns. */ + start_column = 0; + stop_column = m + 1; + + /* + * If max_d >= 0, determine whether the bound is impossibly tight. If so, + * return max_d + 1 immediately. Otherwise, determine whether it's tight + * enough to limit the computation we must perform. If so, figure out + * initial stop column. + */ + if (max_d >= 0) + { + int min_theo_d; /* Theoretical minimum distance. */ + int max_theo_d; /* Theoretical maximum distance. */ + int net_inserts = n - m; + + min_theo_d = net_inserts < 0 ? + -net_inserts * del_c : net_inserts * ins_c; + if (min_theo_d > max_d) + return max_d + 1; + if (ins_c + del_c < sub_c) + sub_c = ins_c + del_c; + max_theo_d = min_theo_d + sub_c * Min(m, n); + if (max_d >= max_theo_d) + max_d = -1; + else if (ins_c + del_c > 0) + { + /* + * Figure out how much of the first row of the notional matrix we + * need to fill in. If the string is growing, the theoretical + * minimum distance already incorporates the cost of deleting the + * number of characters necessary to make the two strings equal in + * length. Each additional deletion forces another insertion, so + * the best-case total cost increases by ins_c + del_c. If the + * string is shrinking, the minimum theoretical cost assumes no + * excess deletions; that is, we're starting no further right than + * column n - m. If we do start further right, the best-case + * total cost increases by ins_c + del_c for each move right. + */ + int slack_d = max_d - min_theo_d; + int best_column = net_inserts < 0 ? -net_inserts : 0; + + stop_column = best_column + (slack_d / (ins_c + del_c)) + 1; + if (stop_column > m) + stop_column = m + 1; + } + } +#endif + + /* + * In order to avoid calling pg_mblen() repeatedly on each character in s, + * we cache all the lengths before starting the main loop -- but if all + * the characters in both strings are single byte, then we skip this and + * use a fast-path in the main loop. If only one string contains + * multi-byte characters, we still build the array, so that the fast-path + * needn't deal with the case where the array hasn't been initialized. + */ + if (m != slen || n != tlen) + { + int i; + const char *cp = source; + + s_char_len = (int *) palloc((m + 1) * sizeof(int)); + for (i = 0; i < m; ++i) + { + s_char_len[i] = pg_mblen(cp); + cp += s_char_len[i]; + } + s_char_len[i] = 0; + } + + /* One more cell for initialization column and row. */ + ++m; + ++n; + + /* Previous and current rows of notional array. */ + prev = (int *) palloc(2 * m * sizeof(int)); + curr = prev + m; + + /* + * To transform the first i characters of s into the first 0 characters of + * t, we must perform i deletions. + */ + for (i = START_COLUMN; i < STOP_COLUMN; i++) + prev[i] = i * del_c; + + /* Loop through rows of the notional array */ + for (y = target, j = 1; j < n; j++) + { + int *temp; + const char *x = source; + int y_char_len = n != tlen + 1 ? pg_mblen(y) : 1; + +#ifdef LEVENSHTEIN_LESS_EQUAL + + /* + * In the best case, values percolate down the diagonal unchanged, so + * we must increment stop_column unless it's already on the right end + * of the array. The inner loop will read prev[stop_column], so we + * have to initialize it even though it shouldn't affect the result. + */ + if (stop_column < m) + { + prev[stop_column] = max_d + 1; + ++stop_column; + } + + /* + * The main loop fills in curr, but curr[0] needs a special case: to + * transform the first 0 characters of s into the first j characters + * of t, we must perform j insertions. However, if start_column > 0, + * this special case does not apply. + */ + if (start_column == 0) + { + curr[0] = j * ins_c; + i = 1; + } + else + i = start_column; +#else + curr[0] = j * ins_c; + i = 1; +#endif + + /* + * This inner loop is critical to performance, so we include a + * fast-path to handle the (fairly common) case where no multibyte + * characters are in the mix. The fast-path is entitled to assume + * that if s_char_len is not initialized then BOTH strings contain + * only single-byte characters. + */ + if (s_char_len != NULL) + { + for (; i < STOP_COLUMN; i++) + { + int ins; + int del; + int sub; + int x_char_len = s_char_len[i - 1]; + + /* + * Calculate costs for insertion, deletion, and substitution. + * + * When calculating cost for substitution, we compare the last + * character of each possibly-multibyte character first, + * because that's enough to rule out most mis-matches. If we + * get past that test, then we compare the lengths and the + * remaining bytes. + */ + ins = prev[i] + ins_c; + del = curr[i - 1] + del_c; + if (x[x_char_len - 1] == y[y_char_len - 1] + && x_char_len == y_char_len && + (x_char_len == 1 || rest_of_char_same(x, y, x_char_len))) + sub = prev[i - 1]; + else + sub = prev[i - 1] + sub_c; + + /* Take the one with minimum cost. */ + curr[i] = Min(ins, del); + curr[i] = Min(curr[i], sub); + + /* Point to next character. */ + x += x_char_len; + } + } + else + { + for (; i < STOP_COLUMN; i++) + { + int ins; + int del; + int sub; + + /* Calculate costs for insertion, deletion, and substitution. */ + ins = prev[i] + ins_c; + del = curr[i - 1] + del_c; + sub = prev[i - 1] + ((*x == *y) ? 0 : sub_c); + + /* Take the one with minimum cost. */ + curr[i] = Min(ins, del); + curr[i] = Min(curr[i], sub); + + /* Point to next character. */ + x++; + } + } + + /* Swap current row with previous row. */ + temp = curr; + curr = prev; + prev = temp; + + /* Point to next character. */ + y += y_char_len; + +#ifdef LEVENSHTEIN_LESS_EQUAL + + /* + * This chunk of code represents a significant performance hit if used + * in the case where there is no max_d bound. This is probably not + * because the max_d >= 0 test itself is expensive, but rather because + * the possibility of needing to execute this code prevents tight + * optimization of the loop as a whole. + */ + if (max_d >= 0) + { + /* + * The "zero point" is the column of the current row where the + * remaining portions of the strings are of equal length. There + * are (n - 1) characters in the target string, of which j have + * been transformed. There are (m - 1) characters in the source + * string, so we want to find the value for zp where (n - 1) - j = + * (m - 1) - zp. + */ + int zp = j - (n - m); + + /* Check whether the stop column can slide left. */ + while (stop_column > 0) + { + int ii = stop_column - 1; + int net_inserts = ii - zp; + + if (prev[ii] + (net_inserts > 0 ? net_inserts * ins_c : + -net_inserts * del_c) <= max_d) + break; + stop_column--; + } + + /* Check whether the start column can slide right. */ + while (start_column < stop_column) + { + int net_inserts = start_column - zp; + + if (prev[start_column] + + (net_inserts > 0 ? net_inserts * ins_c : + -net_inserts * del_c) <= max_d) + break; + + /* + * We'll never again update these values, so we must make sure + * there's nothing here that could confuse any future + * iteration of the outer loop. + */ + prev[start_column] = max_d + 1; + curr[start_column] = max_d + 1; + if (start_column != 0) + source += (s_char_len != NULL) ? s_char_len[start_column - 1] : 1; + start_column++; + } + + /* If they cross, we're going to exceed the bound. */ + if (start_column >= stop_column) + return max_d + 1; + } +#endif + } + + /* + * Because the final value was swapped from the previous row to the + * current row, that's where we'll find it. + */ + return prev[m - 1]; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/like.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/like.c new file mode 100644 index 00000000000..33a2f46aab0 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/like.c @@ -0,0 +1,456 @@ +/*------------------------------------------------------------------------- + * + * like.c + * like expression handling code. + * + * NOTES + * A big hack of the regexp.c code!! Contributed by + * Keith Parks <emkxp01@mtcc.demon.co.uk> (7/95). + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/utils/adt/like.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <ctype.h> + +#include "catalog/pg_collation.h" +#include "mb/pg_wchar.h" +#include "miscadmin.h" +#include "utils/builtins.h" +#include "utils/pg_locale.h" +#include "varatt.h" + + +#define LIKE_TRUE 1 +#define LIKE_FALSE 0 +#define LIKE_ABORT (-1) + + +static int SB_MatchText(const char *t, int tlen, const char *p, int plen, + pg_locale_t locale, bool locale_is_c); +static text *SB_do_like_escape(text *pat, text *esc); + +static int MB_MatchText(const char *t, int tlen, const char *p, int plen, + pg_locale_t locale, bool locale_is_c); +static text *MB_do_like_escape(text *pat, text *esc); + +static int UTF8_MatchText(const char *t, int tlen, const char *p, int plen, + pg_locale_t locale, bool locale_is_c); + +static int SB_IMatchText(const char *t, int tlen, const char *p, int plen, + pg_locale_t locale, bool locale_is_c); + +static int GenericMatchText(const char *s, int slen, const char *p, int plen, Oid collation); +static int Generic_Text_IC_like(text *str, text *pat, Oid collation); + +/*-------------------- + * Support routine for MatchText. Compares given multibyte streams + * as wide characters. If they match, returns 1 otherwise returns 0. + *-------------------- + */ +static inline int +wchareq(const char *p1, const char *p2) +{ + int p1_len; + + /* Optimization: quickly compare the first byte. */ + if (*p1 != *p2) + return 0; + + p1_len = pg_mblen(p1); + if (pg_mblen(p2) != p1_len) + return 0; + + /* They are the same length */ + while (p1_len--) + { + if (*p1++ != *p2++) + return 0; + } + return 1; +} + +/* + * Formerly we had a routine iwchareq() here that tried to do case-insensitive + * comparison of multibyte characters. It did not work at all, however, + * because it relied on tolower() which has a single-byte API ... and + * towlower() wouldn't be much better since we have no suitably cheap way + * of getting a single character transformed to the system's wchar_t format. + * So now, we just downcase the strings using lower() and apply regular LIKE + * comparison. This should be revisited when we install better locale support. + */ + +/* + * We do handle case-insensitive matching for single-byte encodings using + * fold-on-the-fly processing, however. + */ +static char +SB_lower_char(unsigned char c, pg_locale_t locale, bool locale_is_c) +{ + if (locale_is_c) + return pg_ascii_tolower(c); +#ifdef HAVE_LOCALE_T + else if (locale) + return tolower_l(c, locale->info.lt); +#endif + else + return pg_tolower(c); +} + + +#define NextByte(p, plen) ((p)++, (plen)--) + +/* Set up to compile like_match.c for multibyte characters */ +#define CHAREQ(p1, p2) wchareq((p1), (p2)) +#define NextChar(p, plen) \ + do { int __l = pg_mblen(p); (p) +=__l; (plen) -=__l; } while (0) +#define CopyAdvChar(dst, src, srclen) \ + do { int __l = pg_mblen(src); \ + (srclen) -= __l; \ + while (__l-- > 0) \ + *(dst)++ = *(src)++; \ + } while (0) + +#define MatchText MB_MatchText +#define do_like_escape MB_do_like_escape + +#include "like_match.c" + +/* Set up to compile like_match.c for single-byte characters */ +#define CHAREQ(p1, p2) (*(p1) == *(p2)) +#define NextChar(p, plen) NextByte((p), (plen)) +#define CopyAdvChar(dst, src, srclen) (*(dst)++ = *(src)++, (srclen)--) + +#define MatchText SB_MatchText +#define do_like_escape SB_do_like_escape + +#include "like_match.c" + +/* setup to compile like_match.c for single byte case insensitive matches */ +#define MATCH_LOWER(t) SB_lower_char((unsigned char) (t), locale, locale_is_c) +#define NextChar(p, plen) NextByte((p), (plen)) +#define MatchText SB_IMatchText + +#include "like_match.c" + +/* setup to compile like_match.c for UTF8 encoding, using fast NextChar */ + +#define NextChar(p, plen) \ + do { (p)++; (plen)--; } while ((plen) > 0 && (*(p) & 0xC0) == 0x80 ) +#define MatchText UTF8_MatchText + +#include "like_match.c" + +/* Generic for all cases not requiring inline case-folding */ +static inline int +GenericMatchText(const char *s, int slen, const char *p, int plen, Oid collation) +{ + if (collation && !lc_ctype_is_c(collation)) + { + pg_locale_t locale = pg_newlocale_from_collation(collation); + + if (!pg_locale_deterministic(locale)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("nondeterministic collations are not supported for LIKE"))); + } + + if (pg_database_encoding_max_length() == 1) + return SB_MatchText(s, slen, p, plen, 0, true); + else if (GetDatabaseEncoding() == PG_UTF8) + return UTF8_MatchText(s, slen, p, plen, 0, true); + else + return MB_MatchText(s, slen, p, plen, 0, true); +} + +static inline int +Generic_Text_IC_like(text *str, text *pat, Oid collation) +{ + char *s, + *p; + int slen, + plen; + pg_locale_t locale = 0; + bool locale_is_c = false; + + if (!OidIsValid(collation)) + { + /* + * This typically means that the parser could not resolve a conflict + * of implicit collations, so report it that way. + */ + ereport(ERROR, + (errcode(ERRCODE_INDETERMINATE_COLLATION), + errmsg("could not determine which collation to use for ILIKE"), + errhint("Use the COLLATE clause to set the collation explicitly."))); + } + + if (lc_ctype_is_c(collation)) + locale_is_c = true; + else + locale = pg_newlocale_from_collation(collation); + + if (!pg_locale_deterministic(locale)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("nondeterministic collations are not supported for ILIKE"))); + + /* + * For efficiency reasons, in the single byte case we don't call lower() + * on the pattern and text, but instead call SB_lower_char on each + * character. In the multi-byte case we don't have much choice :-(. Also, + * ICU does not support single-character case folding, so we go the long + * way. + */ + + if (pg_database_encoding_max_length() > 1 || (locale && locale->provider == COLLPROVIDER_ICU)) + { + pat = DatumGetTextPP(DirectFunctionCall1Coll(lower, collation, + PointerGetDatum(pat))); + p = VARDATA_ANY(pat); + plen = VARSIZE_ANY_EXHDR(pat); + str = DatumGetTextPP(DirectFunctionCall1Coll(lower, collation, + PointerGetDatum(str))); + s = VARDATA_ANY(str); + slen = VARSIZE_ANY_EXHDR(str); + if (GetDatabaseEncoding() == PG_UTF8) + return UTF8_MatchText(s, slen, p, plen, 0, true); + else + return MB_MatchText(s, slen, p, plen, 0, true); + } + else + { + p = VARDATA_ANY(pat); + plen = VARSIZE_ANY_EXHDR(pat); + s = VARDATA_ANY(str); + slen = VARSIZE_ANY_EXHDR(str); + return SB_IMatchText(s, slen, p, plen, locale, locale_is_c); + } +} + +/* + * interface routines called by the function manager + */ + +Datum +namelike(PG_FUNCTION_ARGS) +{ + Name str = PG_GETARG_NAME(0); + text *pat = PG_GETARG_TEXT_PP(1); + bool result; + char *s, + *p; + int slen, + plen; + + s = NameStr(*str); + slen = strlen(s); + p = VARDATA_ANY(pat); + plen = VARSIZE_ANY_EXHDR(pat); + + result = (GenericMatchText(s, slen, p, plen, PG_GET_COLLATION()) == LIKE_TRUE); + + PG_RETURN_BOOL(result); +} + +Datum +namenlike(PG_FUNCTION_ARGS) +{ + Name str = PG_GETARG_NAME(0); + text *pat = PG_GETARG_TEXT_PP(1); + bool result; + char *s, + *p; + int slen, + plen; + + s = NameStr(*str); + slen = strlen(s); + p = VARDATA_ANY(pat); + plen = VARSIZE_ANY_EXHDR(pat); + + result = (GenericMatchText(s, slen, p, plen, PG_GET_COLLATION()) != LIKE_TRUE); + + PG_RETURN_BOOL(result); +} + +Datum +textlike(PG_FUNCTION_ARGS) +{ + text *str = PG_GETARG_TEXT_PP(0); + text *pat = PG_GETARG_TEXT_PP(1); + bool result; + char *s, + *p; + int slen, + plen; + + s = VARDATA_ANY(str); + slen = VARSIZE_ANY_EXHDR(str); + p = VARDATA_ANY(pat); + plen = VARSIZE_ANY_EXHDR(pat); + + result = (GenericMatchText(s, slen, p, plen, PG_GET_COLLATION()) == LIKE_TRUE); + + PG_RETURN_BOOL(result); +} + +Datum +textnlike(PG_FUNCTION_ARGS) +{ + text *str = PG_GETARG_TEXT_PP(0); + text *pat = PG_GETARG_TEXT_PP(1); + bool result; + char *s, + *p; + int slen, + plen; + + s = VARDATA_ANY(str); + slen = VARSIZE_ANY_EXHDR(str); + p = VARDATA_ANY(pat); + plen = VARSIZE_ANY_EXHDR(pat); + + result = (GenericMatchText(s, slen, p, plen, PG_GET_COLLATION()) != LIKE_TRUE); + + PG_RETURN_BOOL(result); +} + +Datum +bytealike(PG_FUNCTION_ARGS) +{ + bytea *str = PG_GETARG_BYTEA_PP(0); + bytea *pat = PG_GETARG_BYTEA_PP(1); + bool result; + char *s, + *p; + int slen, + plen; + + s = VARDATA_ANY(str); + slen = VARSIZE_ANY_EXHDR(str); + p = VARDATA_ANY(pat); + plen = VARSIZE_ANY_EXHDR(pat); + + result = (SB_MatchText(s, slen, p, plen, 0, true) == LIKE_TRUE); + + PG_RETURN_BOOL(result); +} + +Datum +byteanlike(PG_FUNCTION_ARGS) +{ + bytea *str = PG_GETARG_BYTEA_PP(0); + bytea *pat = PG_GETARG_BYTEA_PP(1); + bool result; + char *s, + *p; + int slen, + plen; + + s = VARDATA_ANY(str); + slen = VARSIZE_ANY_EXHDR(str); + p = VARDATA_ANY(pat); + plen = VARSIZE_ANY_EXHDR(pat); + + result = (SB_MatchText(s, slen, p, plen, 0, true) != LIKE_TRUE); + + PG_RETURN_BOOL(result); +} + +/* + * Case-insensitive versions + */ + +Datum +nameiclike(PG_FUNCTION_ARGS) +{ + Name str = PG_GETARG_NAME(0); + text *pat = PG_GETARG_TEXT_PP(1); + bool result; + text *strtext; + + strtext = DatumGetTextPP(DirectFunctionCall1(name_text, + NameGetDatum(str))); + result = (Generic_Text_IC_like(strtext, pat, PG_GET_COLLATION()) == LIKE_TRUE); + + PG_RETURN_BOOL(result); +} + +Datum +nameicnlike(PG_FUNCTION_ARGS) +{ + Name str = PG_GETARG_NAME(0); + text *pat = PG_GETARG_TEXT_PP(1); + bool result; + text *strtext; + + strtext = DatumGetTextPP(DirectFunctionCall1(name_text, + NameGetDatum(str))); + result = (Generic_Text_IC_like(strtext, pat, PG_GET_COLLATION()) != LIKE_TRUE); + + PG_RETURN_BOOL(result); +} + +Datum +texticlike(PG_FUNCTION_ARGS) +{ + text *str = PG_GETARG_TEXT_PP(0); + text *pat = PG_GETARG_TEXT_PP(1); + bool result; + + result = (Generic_Text_IC_like(str, pat, PG_GET_COLLATION()) == LIKE_TRUE); + + PG_RETURN_BOOL(result); +} + +Datum +texticnlike(PG_FUNCTION_ARGS) +{ + text *str = PG_GETARG_TEXT_PP(0); + text *pat = PG_GETARG_TEXT_PP(1); + bool result; + + result = (Generic_Text_IC_like(str, pat, PG_GET_COLLATION()) != LIKE_TRUE); + + PG_RETURN_BOOL(result); +} + +/* + * like_escape() --- given a pattern and an ESCAPE string, + * convert the pattern to use Postgres' standard backslash escape convention. + */ +Datum +like_escape(PG_FUNCTION_ARGS) +{ + text *pat = PG_GETARG_TEXT_PP(0); + text *esc = PG_GETARG_TEXT_PP(1); + text *result; + + if (pg_database_encoding_max_length() == 1) + result = SB_do_like_escape(pat, esc); + else + result = MB_do_like_escape(pat, esc); + + PG_RETURN_TEXT_P(result); +} + +/* + * like_escape_bytea() --- given a pattern and an ESCAPE string, + * convert the pattern to use Postgres' standard backslash escape convention. + */ +Datum +like_escape_bytea(PG_FUNCTION_ARGS) +{ + bytea *pat = PG_GETARG_BYTEA_PP(0); + bytea *esc = PG_GETARG_BYTEA_PP(1); + bytea *result = SB_do_like_escape((text *) pat, (text *) esc); + + PG_RETURN_BYTEA_P((bytea *) result); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/like_match.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/like_match.c new file mode 100644 index 00000000000..2f32cdaf020 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/like_match.c @@ -0,0 +1,360 @@ +/*------------------------------------------------------------------------- + * + * like_match.c + * LIKE pattern matching internal code. + * + * This file is included by like.c four times, to provide matching code for + * (1) single-byte encodings, (2) UTF8, (3) other multi-byte encodings, + * and (4) case insensitive matches in single-byte encodings. + * (UTF8 is a special case because we can use a much more efficient version + * of NextChar than can be used for general multi-byte encodings.) + * + * Before the inclusion, we need to define the following macros: + * + * NextChar + * MatchText - to name of function wanted + * do_like_escape - name of function if wanted - needs CHAREQ and CopyAdvChar + * MATCH_LOWER - define for case (4) to specify case folding for 1-byte chars + * + * Copyright (c) 1996-2021, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/utils/adt/like_match.c + * + *------------------------------------------------------------------------- + */ + +/* + * Originally written by Rich $alz, mirror!rs, Wed Nov 26 19:03:17 EST 1986. + * Rich $alz is now <rsalz@bbn.com>. + * Special thanks to Lars Mathiesen <thorinn@diku.dk> for the + * LIKE_ABORT code. + * + * This code was shamelessly stolen from the "pql" code by myself and + * slightly modified :) + * + * All references to the word "star" were replaced by "percent" + * All references to the word "wild" were replaced by "like" + * + * All the nice shell RE matching stuff was replaced by just "_" and "%" + * + * As I don't have a copy of the SQL standard handy I wasn't sure whether + * to leave in the '\' escape character handling. + * + * Keith Parks. <keith@mtcc.demon.co.uk> + * + * SQL lets you specify the escape character by saying + * LIKE <pattern> ESCAPE <escape character>. We are a small operation + * so we force you to use '\'. - ay 7/95 + * + * Now we have the like_escape() function that converts patterns with + * any specified escape character (or none at all) to the internal + * default escape character, which is still '\'. - tgl 9/2000 + * + * The code is rewritten to avoid requiring null-terminated strings, + * which in turn allows us to leave out some memcpy() operations. + * This code should be faster and take less memory, but no promises... + * - thomas 2000-08-06 + */ + + +/*-------------------- + * Match text and pattern, return LIKE_TRUE, LIKE_FALSE, or LIKE_ABORT. + * + * LIKE_TRUE: they match + * LIKE_FALSE: they don't match + * LIKE_ABORT: not only don't they match, but the text is too short. + * + * If LIKE_ABORT is returned, then no suffix of the text can match the + * pattern either, so an upper-level % scan can stop scanning now. + *-------------------- + */ + +#ifdef MATCH_LOWER +#define GETCHAR(t) MATCH_LOWER(t) +#else +#define GETCHAR(t) (t) +#endif + +static int +MatchText(const char *t, int tlen, const char *p, int plen, + pg_locale_t locale, bool locale_is_c) +{ + /* Fast path for match-everything pattern */ + if (plen == 1 && *p == '%') + return LIKE_TRUE; + + /* Since this function recurses, it could be driven to stack overflow */ + check_stack_depth(); + + /* + * In this loop, we advance by char when matching wildcards (and thus on + * recursive entry to this function we are properly char-synced). On other + * occasions it is safe to advance by byte, as the text and pattern will + * be in lockstep. This allows us to perform all comparisons between the + * text and pattern on a byte by byte basis, even for multi-byte + * encodings. + */ + while (tlen > 0 && plen > 0) + { + if (*p == '\\') + { + /* Next pattern byte must match literally, whatever it is */ + NextByte(p, plen); + /* ... and there had better be one, per SQL standard */ + if (plen <= 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_ESCAPE_SEQUENCE), + errmsg("LIKE pattern must not end with escape character"))); + if (GETCHAR(*p) != GETCHAR(*t)) + return LIKE_FALSE; + } + else if (*p == '%') + { + char firstpat; + + /* + * % processing is essentially a search for a text position at + * which the remainder of the text matches the remainder of the + * pattern, using a recursive call to check each potential match. + * + * If there are wildcards immediately following the %, we can skip + * over them first, using the idea that any sequence of N _'s and + * one or more %'s is equivalent to N _'s and one % (ie, it will + * match any sequence of at least N text characters). In this way + * we will always run the recursive search loop using a pattern + * fragment that begins with a literal character-to-match, thereby + * not recursing more than we have to. + */ + NextByte(p, plen); + + while (plen > 0) + { + if (*p == '%') + NextByte(p, plen); + else if (*p == '_') + { + /* If not enough text left to match the pattern, ABORT */ + if (tlen <= 0) + return LIKE_ABORT; + NextChar(t, tlen); + NextByte(p, plen); + } + else + break; /* Reached a non-wildcard pattern char */ + } + + /* + * If we're at end of pattern, match: we have a trailing % which + * matches any remaining text string. + */ + if (plen <= 0) + return LIKE_TRUE; + + /* + * Otherwise, scan for a text position at which we can match the + * rest of the pattern. The first remaining pattern char is known + * to be a regular or escaped literal character, so we can compare + * the first pattern byte to each text byte to avoid recursing + * more than we have to. This fact also guarantees that we don't + * have to consider a match to the zero-length substring at the + * end of the text. + */ + if (*p == '\\') + { + if (plen < 2) + ereport(ERROR, + (errcode(ERRCODE_INVALID_ESCAPE_SEQUENCE), + errmsg("LIKE pattern must not end with escape character"))); + firstpat = GETCHAR(p[1]); + } + else + firstpat = GETCHAR(*p); + + while (tlen > 0) + { + if (GETCHAR(*t) == firstpat) + { + int matched = MatchText(t, tlen, p, plen, + locale, locale_is_c); + + if (matched != LIKE_FALSE) + return matched; /* TRUE or ABORT */ + } + + NextChar(t, tlen); + } + + /* + * End of text with no match, so no point in trying later places + * to start matching this pattern. + */ + return LIKE_ABORT; + } + else if (*p == '_') + { + /* _ matches any single character, and we know there is one */ + NextChar(t, tlen); + NextByte(p, plen); + continue; + } + else if (GETCHAR(*p) != GETCHAR(*t)) + { + /* non-wildcard pattern char fails to match text char */ + return LIKE_FALSE; + } + + /* + * Pattern and text match, so advance. + * + * It is safe to use NextByte instead of NextChar here, even for + * multi-byte character sets, because we are not following immediately + * after a wildcard character. If we are in the middle of a multibyte + * character, we must already have matched at least one byte of the + * character from both text and pattern; so we cannot get out-of-sync + * on character boundaries. And we know that no backend-legal + * encoding allows ASCII characters such as '%' to appear as non-first + * bytes of characters, so we won't mistakenly detect a new wildcard. + */ + NextByte(t, tlen); + NextByte(p, plen); + } + + if (tlen > 0) + return LIKE_FALSE; /* end of pattern, but not of text */ + + /* + * End of text, but perhaps not of pattern. Match iff the remaining + * pattern can match a zero-length string, ie, it's zero or more %'s. + */ + while (plen > 0 && *p == '%') + NextByte(p, plen); + if (plen <= 0) + return LIKE_TRUE; + + /* + * End of text with no match, so no point in trying later places to start + * matching this pattern. + */ + return LIKE_ABORT; +} /* MatchText() */ + +/* + * like_escape() --- given a pattern and an ESCAPE string, + * convert the pattern to use Postgres' standard backslash escape convention. + */ +#ifdef do_like_escape + +static text * +do_like_escape(text *pat, text *esc) +{ + text *result; + char *p, + *e, + *r; + int plen, + elen; + bool afterescape; + + p = VARDATA_ANY(pat); + plen = VARSIZE_ANY_EXHDR(pat); + e = VARDATA_ANY(esc); + elen = VARSIZE_ANY_EXHDR(esc); + + /* + * Worst-case pattern growth is 2x --- unlikely, but it's hardly worth + * trying to calculate the size more accurately than that. + */ + result = (text *) palloc(plen * 2 + VARHDRSZ); + r = VARDATA(result); + + if (elen == 0) + { + /* + * No escape character is wanted. Double any backslashes in the + * pattern to make them act like ordinary characters. + */ + while (plen > 0) + { + if (*p == '\\') + *r++ = '\\'; + CopyAdvChar(r, p, plen); + } + } + else + { + /* + * The specified escape must be only a single character. + */ + NextChar(e, elen); + if (elen != 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_ESCAPE_SEQUENCE), + errmsg("invalid escape string"), + errhint("Escape string must be empty or one character."))); + + e = VARDATA_ANY(esc); + + /* + * If specified escape is '\', just copy the pattern as-is. + */ + if (*e == '\\') + { + memcpy(result, pat, VARSIZE_ANY(pat)); + return result; + } + + /* + * Otherwise, convert occurrences of the specified escape character to + * '\', and double occurrences of '\' --- unless they immediately + * follow an escape character! + */ + afterescape = false; + while (plen > 0) + { + if (CHAREQ(p, e) && !afterescape) + { + *r++ = '\\'; + NextChar(p, plen); + afterescape = true; + } + else if (*p == '\\') + { + *r++ = '\\'; + if (!afterescape) + *r++ = '\\'; + NextChar(p, plen); + afterescape = false; + } + else + { + CopyAdvChar(r, p, plen); + afterescape = false; + } + } + } + + SET_VARSIZE(result, r - ((char *) result)); + + return result; +} +#endif /* do_like_escape */ + +#ifdef CHAREQ +#undef CHAREQ +#endif + +#undef NextChar +#undef CopyAdvChar +#undef MatchText + +#ifdef do_like_escape +#undef do_like_escape +#endif + +#undef GETCHAR + +#ifdef MATCH_LOWER +#undef MATCH_LOWER + +#endif diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/like_support.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/like_support.c new file mode 100644 index 00000000000..555304ceb6d --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/like_support.c @@ -0,0 +1,1800 @@ +/*------------------------------------------------------------------------- + * + * like_support.c + * Planner support functions for LIKE, regex, and related operators. + * + * These routines handle special optimization of operators that can be + * used with index scans even though they are not known to the executor's + * indexscan machinery. The key idea is that these operators allow us + * to derive approximate indexscan qual clauses, such that any tuples + * that pass the operator clause itself must also satisfy the simpler + * indexscan condition(s). Then we can use the indexscan machinery + * to avoid scanning as much of the table as we'd otherwise have to, + * while applying the original operator as a qpqual condition to ensure + * we deliver only the tuples we want. (In essence, we're using a regular + * index as if it were a lossy index.) + * + * An example of what we're doing is + * textfield LIKE 'abc%def' + * from which we can generate the indexscanable conditions + * textfield >= 'abc' AND textfield < 'abd' + * which allow efficient scanning of an index on textfield. + * (In reality, character set and collation issues make the transformation + * from LIKE to indexscan limits rather harder than one might think ... + * but that's the basic idea.) + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/adt/like_support.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <math.h> + +#include "access/htup_details.h" +#include "access/stratnum.h" +#include "catalog/pg_collation.h" +#include "catalog/pg_operator.h" +#include "catalog/pg_opfamily.h" +#include "catalog/pg_statistic.h" +#include "catalog/pg_type.h" +#include "mb/pg_wchar.h" +#include "miscadmin.h" +#include "nodes/makefuncs.h" +#include "nodes/nodeFuncs.h" +#include "nodes/supportnodes.h" +#include "utils/builtins.h" +#include "utils/datum.h" +#include "utils/lsyscache.h" +#include "utils/pg_locale.h" +#include "utils/selfuncs.h" +#include "utils/varlena.h" + + +typedef enum +{ + Pattern_Type_Like, + Pattern_Type_Like_IC, + Pattern_Type_Regex, + Pattern_Type_Regex_IC, + Pattern_Type_Prefix +} Pattern_Type; + +typedef enum +{ + Pattern_Prefix_None, Pattern_Prefix_Partial, Pattern_Prefix_Exact +} Pattern_Prefix_Status; + +static Node *like_regex_support(Node *rawreq, Pattern_Type ptype); +static List *match_pattern_prefix(Node *leftop, + Node *rightop, + Pattern_Type ptype, + Oid expr_coll, + Oid opfamily, + Oid indexcollation); +static double patternsel_common(PlannerInfo *root, + Oid oprid, + Oid opfuncid, + List *args, + int varRelid, + Oid collation, + Pattern_Type ptype, + bool negate); +static Pattern_Prefix_Status pattern_fixed_prefix(Const *patt, + Pattern_Type ptype, + Oid collation, + Const **prefix, + Selectivity *rest_selec); +static Selectivity prefix_selectivity(PlannerInfo *root, + VariableStatData *vardata, + Oid eqopr, Oid ltopr, Oid geopr, + Oid collation, + Const *prefixcon); +static Selectivity like_selectivity(const char *patt, int pattlen, + bool case_insensitive); +static Selectivity regex_selectivity(const char *patt, int pattlen, + bool case_insensitive, + int fixed_prefix_len); +static int pattern_char_isalpha(char c, bool is_multibyte, + pg_locale_t locale, bool locale_is_c); +static Const *make_greater_string(const Const *str_const, FmgrInfo *ltproc, + Oid collation); +static Datum string_to_datum(const char *str, Oid datatype); +static Const *string_to_const(const char *str, Oid datatype); +static Const *string_to_bytea_const(const char *str, size_t str_len); + + +/* + * Planner support functions for LIKE, regex, and related operators + */ +Datum +textlike_support(PG_FUNCTION_ARGS) +{ + Node *rawreq = (Node *) PG_GETARG_POINTER(0); + + PG_RETURN_POINTER(like_regex_support(rawreq, Pattern_Type_Like)); +} + +Datum +texticlike_support(PG_FUNCTION_ARGS) +{ + Node *rawreq = (Node *) PG_GETARG_POINTER(0); + + PG_RETURN_POINTER(like_regex_support(rawreq, Pattern_Type_Like_IC)); +} + +Datum +textregexeq_support(PG_FUNCTION_ARGS) +{ + Node *rawreq = (Node *) PG_GETARG_POINTER(0); + + PG_RETURN_POINTER(like_regex_support(rawreq, Pattern_Type_Regex)); +} + +Datum +texticregexeq_support(PG_FUNCTION_ARGS) +{ + Node *rawreq = (Node *) PG_GETARG_POINTER(0); + + PG_RETURN_POINTER(like_regex_support(rawreq, Pattern_Type_Regex_IC)); +} + +Datum +text_starts_with_support(PG_FUNCTION_ARGS) +{ + Node *rawreq = (Node *) PG_GETARG_POINTER(0); + + PG_RETURN_POINTER(like_regex_support(rawreq, Pattern_Type_Prefix)); +} + +/* Common code for the above */ +static Node * +like_regex_support(Node *rawreq, Pattern_Type ptype) +{ + Node *ret = NULL; + + if (IsA(rawreq, SupportRequestSelectivity)) + { + /* + * Make a selectivity estimate for a function call, just as we'd do if + * the call was via the corresponding operator. + */ + SupportRequestSelectivity *req = (SupportRequestSelectivity *) rawreq; + Selectivity s1; + + if (req->is_join) + { + /* + * For the moment we just punt. If patternjoinsel is ever + * improved to do better, this should be made to call it. + */ + s1 = DEFAULT_MATCH_SEL; + } + else + { + /* Share code with operator restriction selectivity functions */ + s1 = patternsel_common(req->root, + InvalidOid, + req->funcid, + req->args, + req->varRelid, + req->inputcollid, + ptype, + false); + } + req->selectivity = s1; + ret = (Node *) req; + } + else if (IsA(rawreq, SupportRequestIndexCondition)) + { + /* Try to convert operator/function call to index conditions */ + SupportRequestIndexCondition *req = (SupportRequestIndexCondition *) rawreq; + + /* + * Currently we have no "reverse" match operators with the pattern on + * the left, so we only need consider cases with the indexkey on the + * left. + */ + if (req->indexarg != 0) + return NULL; + + if (is_opclause(req->node)) + { + OpExpr *clause = (OpExpr *) req->node; + + Assert(list_length(clause->args) == 2); + ret = (Node *) + match_pattern_prefix((Node *) linitial(clause->args), + (Node *) lsecond(clause->args), + ptype, + clause->inputcollid, + req->opfamily, + req->indexcollation); + } + else if (is_funcclause(req->node)) /* be paranoid */ + { + FuncExpr *clause = (FuncExpr *) req->node; + + Assert(list_length(clause->args) == 2); + ret = (Node *) + match_pattern_prefix((Node *) linitial(clause->args), + (Node *) lsecond(clause->args), + ptype, + clause->inputcollid, + req->opfamily, + req->indexcollation); + } + } + + return ret; +} + +/* + * match_pattern_prefix + * Try to generate an indexqual for a LIKE or regex operator. + */ +static List * +match_pattern_prefix(Node *leftop, + Node *rightop, + Pattern_Type ptype, + Oid expr_coll, + Oid opfamily, + Oid indexcollation) +{ + List *result; + Const *patt; + Const *prefix; + Pattern_Prefix_Status pstatus; + Oid ldatatype; + Oid rdatatype; + Oid eqopr; + Oid ltopr; + Oid geopr; + Oid preopr = InvalidOid; + bool collation_aware; + Expr *expr; + FmgrInfo ltproc; + Const *greaterstr; + + /* + * Can't do anything with a non-constant or NULL pattern argument. + * + * Note that since we restrict ourselves to cases with a hard constant on + * the RHS, it's a-fortiori a pseudoconstant, and we don't need to worry + * about verifying that. + */ + if (!IsA(rightop, Const) || + ((Const *) rightop)->constisnull) + return NIL; + patt = (Const *) rightop; + + /* + * Not supported if the expression collation is nondeterministic. The + * optimized equality or prefix tests use bytewise comparisons, which is + * not consistent with nondeterministic collations. The actual + * pattern-matching implementation functions will later error out that + * pattern-matching is not supported with nondeterministic collations. (We + * could also error out here, but by doing it later we get more precise + * error messages.) (It should be possible to support at least + * Pattern_Prefix_Exact, but no point as long as the actual + * pattern-matching implementations don't support it.) + * + * expr_coll is not set for a non-collation-aware data type such as bytea. + */ + if (expr_coll && !get_collation_isdeterministic(expr_coll)) + return NIL; + + /* + * Try to extract a fixed prefix from the pattern. + */ + pstatus = pattern_fixed_prefix(patt, ptype, expr_coll, + &prefix, NULL); + + /* fail if no fixed prefix */ + if (pstatus == Pattern_Prefix_None) + return NIL; + + /* + * Identify the operators we want to use, based on the type of the + * left-hand argument. Usually these are just the type's regular + * comparison operators, but if we are considering one of the semi-legacy + * "pattern" opclasses, use the "pattern" operators instead. Those are + * not collation-sensitive but always use C collation, as we want. The + * selected operators also determine the needed type of the prefix + * constant. + */ + ldatatype = exprType(leftop); + switch (ldatatype) + { + case TEXTOID: + if (opfamily == TEXT_PATTERN_BTREE_FAM_OID) + { + eqopr = TextEqualOperator; + ltopr = TextPatternLessOperator; + geopr = TextPatternGreaterEqualOperator; + collation_aware = false; + } + else if (opfamily == TEXT_SPGIST_FAM_OID) + { + eqopr = TextEqualOperator; + ltopr = TextPatternLessOperator; + geopr = TextPatternGreaterEqualOperator; + /* This opfamily has direct support for prefixing */ + preopr = TextPrefixOperator; + collation_aware = false; + } + else + { + eqopr = TextEqualOperator; + ltopr = TextLessOperator; + geopr = TextGreaterEqualOperator; + collation_aware = true; + } + rdatatype = TEXTOID; + break; + case NAMEOID: + + /* + * Note that here, we need the RHS type to be text, so that the + * comparison value isn't improperly truncated to NAMEDATALEN. + */ + eqopr = NameEqualTextOperator; + ltopr = NameLessTextOperator; + geopr = NameGreaterEqualTextOperator; + collation_aware = true; + rdatatype = TEXTOID; + break; + case BPCHAROID: + if (opfamily == BPCHAR_PATTERN_BTREE_FAM_OID) + { + eqopr = BpcharEqualOperator; + ltopr = BpcharPatternLessOperator; + geopr = BpcharPatternGreaterEqualOperator; + collation_aware = false; + } + else + { + eqopr = BpcharEqualOperator; + ltopr = BpcharLessOperator; + geopr = BpcharGreaterEqualOperator; + collation_aware = true; + } + rdatatype = BPCHAROID; + break; + case BYTEAOID: + eqopr = ByteaEqualOperator; + ltopr = ByteaLessOperator; + geopr = ByteaGreaterEqualOperator; + collation_aware = false; + rdatatype = BYTEAOID; + break; + default: + /* Can't get here unless we're attached to the wrong operator */ + return NIL; + } + + /* + * If necessary, coerce the prefix constant to the right type. The given + * prefix constant is either text or bytea type, therefore the only case + * where we need to do anything is when converting text to bpchar. Those + * two types are binary-compatible, so relabeling the Const node is + * sufficient. + */ + if (prefix->consttype != rdatatype) + { + Assert(prefix->consttype == TEXTOID && + rdatatype == BPCHAROID); + prefix->consttype = rdatatype; + } + + /* + * If we found an exact-match pattern, generate an "=" indexqual. + * + * Here and below, check to see whether the desired operator is actually + * supported by the index opclass, and fail quietly if not. This allows + * us to not be concerned with specific opclasses (except for the legacy + * "pattern" cases); any index that correctly implements the operators + * will work. + */ + if (pstatus == Pattern_Prefix_Exact) + { + if (!op_in_opfamily(eqopr, opfamily)) + return NIL; + expr = make_opclause(eqopr, BOOLOID, false, + (Expr *) leftop, (Expr *) prefix, + InvalidOid, indexcollation); + result = list_make1(expr); + return result; + } + + /* + * Otherwise, we have a nonempty required prefix of the values. Some + * opclasses support prefix checks directly, otherwise we'll try to + * generate a range constraint. + */ + if (OidIsValid(preopr) && op_in_opfamily(preopr, opfamily)) + { + expr = make_opclause(preopr, BOOLOID, false, + (Expr *) leftop, (Expr *) prefix, + InvalidOid, indexcollation); + result = list_make1(expr); + return result; + } + + /* + * Since we need a range constraint, it's only going to work reliably if + * the index is collation-insensitive or has "C" collation. Note that + * here we are looking at the index's collation, not the expression's + * collation -- this test is *not* dependent on the LIKE/regex operator's + * collation. + */ + if (collation_aware && + !lc_collate_is_c(indexcollation)) + return NIL; + + /* + * We can always say "x >= prefix". + */ + if (!op_in_opfamily(geopr, opfamily)) + return NIL; + expr = make_opclause(geopr, BOOLOID, false, + (Expr *) leftop, (Expr *) prefix, + InvalidOid, indexcollation); + result = list_make1(expr); + + /*------- + * If we can create a string larger than the prefix, we can say + * "x < greaterstr". NB: we rely on make_greater_string() to generate + * a guaranteed-greater string, not just a probably-greater string. + * In general this is only guaranteed in C locale, so we'd better be + * using a C-locale index collation. + *------- + */ + if (!op_in_opfamily(ltopr, opfamily)) + return result; + fmgr_info(get_opcode(ltopr), <proc); + greaterstr = make_greater_string(prefix, <proc, indexcollation); + if (greaterstr) + { + expr = make_opclause(ltopr, BOOLOID, false, + (Expr *) leftop, (Expr *) greaterstr, + InvalidOid, indexcollation); + result = lappend(result, expr); + } + + return result; +} + + +/* + * patternsel_common - generic code for pattern-match restriction selectivity. + * + * To support using this from either the operator or function paths, caller + * may pass either operator OID or underlying function OID; we look up the + * latter from the former if needed. (We could just have patternsel() call + * get_opcode(), but the work would be wasted if we don't have a need to + * compare a fixed prefix to the pg_statistic data.) + * + * Note that oprid and/or opfuncid should be for the positive-match operator + * even when negate is true. + */ +static double +patternsel_common(PlannerInfo *root, + Oid oprid, + Oid opfuncid, + List *args, + int varRelid, + Oid collation, + Pattern_Type ptype, + bool negate) +{ + VariableStatData vardata; + Node *other; + bool varonleft; + Datum constval; + Oid consttype; + Oid vartype; + Oid rdatatype; + Oid eqopr; + Oid ltopr; + Oid geopr; + Pattern_Prefix_Status pstatus; + Const *patt; + Const *prefix = NULL; + Selectivity rest_selec = 0; + double nullfrac = 0.0; + double result; + + /* + * Initialize result to the appropriate default estimate depending on + * whether it's a match or not-match operator. + */ + if (negate) + result = 1.0 - DEFAULT_MATCH_SEL; + else + result = DEFAULT_MATCH_SEL; + + /* + * If expression is not variable op constant, then punt and return the + * default estimate. + */ + if (!get_restriction_variable(root, args, varRelid, + &vardata, &other, &varonleft)) + return result; + if (!varonleft || !IsA(other, Const)) + { + ReleaseVariableStats(vardata); + return result; + } + + /* + * If the constant is NULL, assume operator is strict and return zero, ie, + * operator will never return TRUE. (It's zero even for a negator op.) + */ + if (((Const *) other)->constisnull) + { + ReleaseVariableStats(vardata); + return 0.0; + } + constval = ((Const *) other)->constvalue; + consttype = ((Const *) other)->consttype; + + /* + * The right-hand const is type text or bytea for all supported operators. + * We do not expect to see binary-compatible types here, since + * const-folding should have relabeled the const to exactly match the + * operator's declared type. + */ + if (consttype != TEXTOID && consttype != BYTEAOID) + { + ReleaseVariableStats(vardata); + return result; + } + + /* + * Similarly, the exposed type of the left-hand side should be one of + * those we know. (Do not look at vardata.atttype, which might be + * something binary-compatible but different.) We can use it to identify + * the comparison operators and the required type of the comparison + * constant, much as in match_pattern_prefix(). + */ + vartype = vardata.vartype; + + switch (vartype) + { + case TEXTOID: + eqopr = TextEqualOperator; + ltopr = TextLessOperator; + geopr = TextGreaterEqualOperator; + rdatatype = TEXTOID; + break; + case NAMEOID: + + /* + * Note that here, we need the RHS type to be text, so that the + * comparison value isn't improperly truncated to NAMEDATALEN. + */ + eqopr = NameEqualTextOperator; + ltopr = NameLessTextOperator; + geopr = NameGreaterEqualTextOperator; + rdatatype = TEXTOID; + break; + case BPCHAROID: + eqopr = BpcharEqualOperator; + ltopr = BpcharLessOperator; + geopr = BpcharGreaterEqualOperator; + rdatatype = BPCHAROID; + break; + case BYTEAOID: + eqopr = ByteaEqualOperator; + ltopr = ByteaLessOperator; + geopr = ByteaGreaterEqualOperator; + rdatatype = BYTEAOID; + break; + default: + /* Can't get here unless we're attached to the wrong operator */ + ReleaseVariableStats(vardata); + return result; + } + + /* + * Grab the nullfrac for use below. + */ + if (HeapTupleIsValid(vardata.statsTuple)) + { + Form_pg_statistic stats; + + stats = (Form_pg_statistic) GETSTRUCT(vardata.statsTuple); + nullfrac = stats->stanullfrac; + } + + /* + * Pull out any fixed prefix implied by the pattern, and estimate the + * fractional selectivity of the remainder of the pattern. Unlike many + * other selectivity estimators, we use the pattern operator's actual + * collation for this step. This is not because we expect the collation + * to make a big difference in the selectivity estimate (it seldom would), + * but because we want to be sure we cache compiled regexps under the + * right cache key, so that they can be re-used at runtime. + */ + patt = (Const *) other; + pstatus = pattern_fixed_prefix(patt, ptype, collation, + &prefix, &rest_selec); + + /* + * If necessary, coerce the prefix constant to the right type. The only + * case where we need to do anything is when converting text to bpchar. + * Those two types are binary-compatible, so relabeling the Const node is + * sufficient. + */ + if (prefix && prefix->consttype != rdatatype) + { + Assert(prefix->consttype == TEXTOID && + rdatatype == BPCHAROID); + prefix->consttype = rdatatype; + } + + if (pstatus == Pattern_Prefix_Exact) + { + /* + * Pattern specifies an exact match, so estimate as for '=' + */ + result = var_eq_const(&vardata, eqopr, collation, prefix->constvalue, + false, true, false); + } + else + { + /* + * Not exact-match pattern. If we have a sufficiently large + * histogram, estimate selectivity for the histogram part of the + * population by counting matches in the histogram. If not, estimate + * selectivity of the fixed prefix and remainder of pattern + * separately, then combine the two to get an estimate of the + * selectivity for the part of the column population represented by + * the histogram. (For small histograms, we combine these + * approaches.) + * + * We then add up data for any most-common-values values; these are + * not in the histogram population, and we can get exact answers for + * them by applying the pattern operator, so there's no reason to + * approximate. (If the MCVs cover a significant part of the total + * population, this gives us a big leg up in accuracy.) + */ + Selectivity selec; + int hist_size; + FmgrInfo opproc; + double mcv_selec, + sumcommon; + + /* Try to use the histogram entries to get selectivity */ + if (!OidIsValid(opfuncid)) + opfuncid = get_opcode(oprid); + fmgr_info(opfuncid, &opproc); + + selec = histogram_selectivity(&vardata, &opproc, collation, + constval, true, + 10, 1, &hist_size); + + /* If not at least 100 entries, use the heuristic method */ + if (hist_size < 100) + { + Selectivity heursel; + Selectivity prefixsel; + + if (pstatus == Pattern_Prefix_Partial) + prefixsel = prefix_selectivity(root, &vardata, + eqopr, ltopr, geopr, + collation, + prefix); + else + prefixsel = 1.0; + heursel = prefixsel * rest_selec; + + if (selec < 0) /* fewer than 10 histogram entries? */ + selec = heursel; + else + { + /* + * For histogram sizes from 10 to 100, we combine the + * histogram and heuristic selectivities, putting increasingly + * more trust in the histogram for larger sizes. + */ + double hist_weight = hist_size / 100.0; + + selec = selec * hist_weight + heursel * (1.0 - hist_weight); + } + } + + /* In any case, don't believe extremely small or large estimates. */ + if (selec < 0.0001) + selec = 0.0001; + else if (selec > 0.9999) + selec = 0.9999; + + /* + * If we have most-common-values info, add up the fractions of the MCV + * entries that satisfy MCV OP PATTERN. These fractions contribute + * directly to the result selectivity. Also add up the total fraction + * represented by MCV entries. + */ + mcv_selec = mcv_selectivity(&vardata, &opproc, collation, + constval, true, + &sumcommon); + + /* + * Now merge the results from the MCV and histogram calculations, + * realizing that the histogram covers only the non-null values that + * are not listed in MCV. + */ + selec *= 1.0 - nullfrac - sumcommon; + selec += mcv_selec; + result = selec; + } + + /* now adjust if we wanted not-match rather than match */ + if (negate) + result = 1.0 - result - nullfrac; + + /* result should be in range, but make sure... */ + CLAMP_PROBABILITY(result); + + if (prefix) + { + pfree(DatumGetPointer(prefix->constvalue)); + pfree(prefix); + } + + ReleaseVariableStats(vardata); + + return result; +} + +/* + * Fix impedance mismatch between SQL-callable functions and patternsel_common + */ +static double +patternsel(PG_FUNCTION_ARGS, Pattern_Type ptype, bool negate) +{ + PlannerInfo *root = (PlannerInfo *) PG_GETARG_POINTER(0); + Oid operator = PG_GETARG_OID(1); + List *args = (List *) PG_GETARG_POINTER(2); + int varRelid = PG_GETARG_INT32(3); + Oid collation = PG_GET_COLLATION(); + + /* + * If this is for a NOT LIKE or similar operator, get the corresponding + * positive-match operator and work with that. + */ + if (negate) + { + operator = get_negator(operator); + if (!OidIsValid(operator)) + elog(ERROR, "patternsel called for operator without a negator"); + } + + return patternsel_common(root, + operator, + InvalidOid, + args, + varRelid, + collation, + ptype, + negate); +} + +/* + * regexeqsel - Selectivity of regular-expression pattern match. + */ +Datum +regexeqsel(PG_FUNCTION_ARGS) +{ + PG_RETURN_FLOAT8(patternsel(fcinfo, Pattern_Type_Regex, false)); +} + +/* + * icregexeqsel - Selectivity of case-insensitive regex match. + */ +Datum +icregexeqsel(PG_FUNCTION_ARGS) +{ + PG_RETURN_FLOAT8(patternsel(fcinfo, Pattern_Type_Regex_IC, false)); +} + +/* + * likesel - Selectivity of LIKE pattern match. + */ +Datum +likesel(PG_FUNCTION_ARGS) +{ + PG_RETURN_FLOAT8(patternsel(fcinfo, Pattern_Type_Like, false)); +} + +/* + * prefixsel - selectivity of prefix operator + */ +Datum +prefixsel(PG_FUNCTION_ARGS) +{ + PG_RETURN_FLOAT8(patternsel(fcinfo, Pattern_Type_Prefix, false)); +} + +/* + * + * iclikesel - Selectivity of ILIKE pattern match. + */ +Datum +iclikesel(PG_FUNCTION_ARGS) +{ + PG_RETURN_FLOAT8(patternsel(fcinfo, Pattern_Type_Like_IC, false)); +} + +/* + * regexnesel - Selectivity of regular-expression pattern non-match. + */ +Datum +regexnesel(PG_FUNCTION_ARGS) +{ + PG_RETURN_FLOAT8(patternsel(fcinfo, Pattern_Type_Regex, true)); +} + +/* + * icregexnesel - Selectivity of case-insensitive regex non-match. + */ +Datum +icregexnesel(PG_FUNCTION_ARGS) +{ + PG_RETURN_FLOAT8(patternsel(fcinfo, Pattern_Type_Regex_IC, true)); +} + +/* + * nlikesel - Selectivity of LIKE pattern non-match. + */ +Datum +nlikesel(PG_FUNCTION_ARGS) +{ + PG_RETURN_FLOAT8(patternsel(fcinfo, Pattern_Type_Like, true)); +} + +/* + * icnlikesel - Selectivity of ILIKE pattern non-match. + */ +Datum +icnlikesel(PG_FUNCTION_ARGS) +{ + PG_RETURN_FLOAT8(patternsel(fcinfo, Pattern_Type_Like_IC, true)); +} + +/* + * patternjoinsel - Generic code for pattern-match join selectivity. + */ +static double +patternjoinsel(PG_FUNCTION_ARGS, Pattern_Type ptype, bool negate) +{ + /* For the moment we just punt. */ + return negate ? (1.0 - DEFAULT_MATCH_SEL) : DEFAULT_MATCH_SEL; +} + +/* + * regexeqjoinsel - Join selectivity of regular-expression pattern match. + */ +Datum +regexeqjoinsel(PG_FUNCTION_ARGS) +{ + PG_RETURN_FLOAT8(patternjoinsel(fcinfo, Pattern_Type_Regex, false)); +} + +/* + * icregexeqjoinsel - Join selectivity of case-insensitive regex match. + */ +Datum +icregexeqjoinsel(PG_FUNCTION_ARGS) +{ + PG_RETURN_FLOAT8(patternjoinsel(fcinfo, Pattern_Type_Regex_IC, false)); +} + +/* + * likejoinsel - Join selectivity of LIKE pattern match. + */ +Datum +likejoinsel(PG_FUNCTION_ARGS) +{ + PG_RETURN_FLOAT8(patternjoinsel(fcinfo, Pattern_Type_Like, false)); +} + +/* + * prefixjoinsel - Join selectivity of prefix operator + */ +Datum +prefixjoinsel(PG_FUNCTION_ARGS) +{ + PG_RETURN_FLOAT8(patternjoinsel(fcinfo, Pattern_Type_Prefix, false)); +} + +/* + * iclikejoinsel - Join selectivity of ILIKE pattern match. + */ +Datum +iclikejoinsel(PG_FUNCTION_ARGS) +{ + PG_RETURN_FLOAT8(patternjoinsel(fcinfo, Pattern_Type_Like_IC, false)); +} + +/* + * regexnejoinsel - Join selectivity of regex non-match. + */ +Datum +regexnejoinsel(PG_FUNCTION_ARGS) +{ + PG_RETURN_FLOAT8(patternjoinsel(fcinfo, Pattern_Type_Regex, true)); +} + +/* + * icregexnejoinsel - Join selectivity of case-insensitive regex non-match. + */ +Datum +icregexnejoinsel(PG_FUNCTION_ARGS) +{ + PG_RETURN_FLOAT8(patternjoinsel(fcinfo, Pattern_Type_Regex_IC, true)); +} + +/* + * nlikejoinsel - Join selectivity of LIKE pattern non-match. + */ +Datum +nlikejoinsel(PG_FUNCTION_ARGS) +{ + PG_RETURN_FLOAT8(patternjoinsel(fcinfo, Pattern_Type_Like, true)); +} + +/* + * icnlikejoinsel - Join selectivity of ILIKE pattern non-match. + */ +Datum +icnlikejoinsel(PG_FUNCTION_ARGS) +{ + PG_RETURN_FLOAT8(patternjoinsel(fcinfo, Pattern_Type_Like_IC, true)); +} + + +/*------------------------------------------------------------------------- + * + * Pattern analysis functions + * + * These routines support analysis of LIKE and regular-expression patterns + * by the planner/optimizer. It's important that they agree with the + * regular-expression code in backend/regex/ and the LIKE code in + * backend/utils/adt/like.c. Also, the computation of the fixed prefix + * must be conservative: if we report a string longer than the true fixed + * prefix, the query may produce actually wrong answers, rather than just + * getting a bad selectivity estimate! + * + *------------------------------------------------------------------------- + */ + +/* + * Extract the fixed prefix, if any, for a pattern. + * + * *prefix is set to a palloc'd prefix string (in the form of a Const node), + * or to NULL if no fixed prefix exists for the pattern. + * If rest_selec is not NULL, *rest_selec is set to an estimate of the + * selectivity of the remainder of the pattern (without any fixed prefix). + * The prefix Const has the same type (TEXT or BYTEA) as the input pattern. + * + * The return value distinguishes no fixed prefix, a partial prefix, + * or an exact-match-only pattern. + */ + +static Pattern_Prefix_Status +like_fixed_prefix(Const *patt_const, bool case_insensitive, Oid collation, + Const **prefix_const, Selectivity *rest_selec) +{ + char *match; + char *patt; + int pattlen; + Oid typeid = patt_const->consttype; + int pos, + match_pos; + bool is_multibyte = (pg_database_encoding_max_length() > 1); + pg_locale_t locale = 0; + bool locale_is_c = false; + + /* the right-hand const is type text or bytea */ + Assert(typeid == BYTEAOID || typeid == TEXTOID); + + if (case_insensitive) + { + if (typeid == BYTEAOID) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("case insensitive matching not supported on type bytea"))); + + if (!OidIsValid(collation)) + { + /* + * This typically means that the parser could not resolve a + * conflict of implicit collations, so report it that way. + */ + ereport(ERROR, + (errcode(ERRCODE_INDETERMINATE_COLLATION), + errmsg("could not determine which collation to use for ILIKE"), + errhint("Use the COLLATE clause to set the collation explicitly."))); + } + + /* If case-insensitive, we need locale info */ + if (lc_ctype_is_c(collation)) + locale_is_c = true; + else + locale = pg_newlocale_from_collation(collation); + } + + if (typeid != BYTEAOID) + { + patt = TextDatumGetCString(patt_const->constvalue); + pattlen = strlen(patt); + } + else + { + bytea *bstr = DatumGetByteaPP(patt_const->constvalue); + + pattlen = VARSIZE_ANY_EXHDR(bstr); + patt = (char *) palloc(pattlen); + memcpy(patt, VARDATA_ANY(bstr), pattlen); + Assert((Pointer) bstr == DatumGetPointer(patt_const->constvalue)); + } + + match = palloc(pattlen + 1); + match_pos = 0; + for (pos = 0; pos < pattlen; pos++) + { + /* % and _ are wildcard characters in LIKE */ + if (patt[pos] == '%' || + patt[pos] == '_') + break; + + /* Backslash escapes the next character */ + if (patt[pos] == '\\') + { + pos++; + if (pos >= pattlen) + break; + } + + /* Stop if case-varying character (it's sort of a wildcard) */ + if (case_insensitive && + pattern_char_isalpha(patt[pos], is_multibyte, locale, locale_is_c)) + break; + + match[match_pos++] = patt[pos]; + } + + match[match_pos] = '\0'; + + if (typeid != BYTEAOID) + *prefix_const = string_to_const(match, typeid); + else + *prefix_const = string_to_bytea_const(match, match_pos); + + if (rest_selec != NULL) + *rest_selec = like_selectivity(&patt[pos], pattlen - pos, + case_insensitive); + + pfree(patt); + pfree(match); + + /* in LIKE, an empty pattern is an exact match! */ + if (pos == pattlen) + return Pattern_Prefix_Exact; /* reached end of pattern, so exact */ + + if (match_pos > 0) + return Pattern_Prefix_Partial; + + return Pattern_Prefix_None; +} + +static Pattern_Prefix_Status +regex_fixed_prefix(Const *patt_const, bool case_insensitive, Oid collation, + Const **prefix_const, Selectivity *rest_selec) +{ + Oid typeid = patt_const->consttype; + char *prefix; + bool exact; + + /* + * Should be unnecessary, there are no bytea regex operators defined. As + * such, it should be noted that the rest of this function has *not* been + * made safe for binary (possibly NULL containing) strings. + */ + if (typeid == BYTEAOID) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("regular-expression matching not supported on type bytea"))); + + /* Use the regexp machinery to extract the prefix, if any */ + prefix = regexp_fixed_prefix(DatumGetTextPP(patt_const->constvalue), + case_insensitive, collation, + &exact); + + if (prefix == NULL) + { + *prefix_const = NULL; + + if (rest_selec != NULL) + { + char *patt = TextDatumGetCString(patt_const->constvalue); + + *rest_selec = regex_selectivity(patt, strlen(patt), + case_insensitive, + 0); + pfree(patt); + } + + return Pattern_Prefix_None; + } + + *prefix_const = string_to_const(prefix, typeid); + + if (rest_selec != NULL) + { + if (exact) + { + /* Exact match, so there's no additional selectivity */ + *rest_selec = 1.0; + } + else + { + char *patt = TextDatumGetCString(patt_const->constvalue); + + *rest_selec = regex_selectivity(patt, strlen(patt), + case_insensitive, + strlen(prefix)); + pfree(patt); + } + } + + pfree(prefix); + + if (exact) + return Pattern_Prefix_Exact; /* pattern specifies exact match */ + else + return Pattern_Prefix_Partial; +} + +static Pattern_Prefix_Status +pattern_fixed_prefix(Const *patt, Pattern_Type ptype, Oid collation, + Const **prefix, Selectivity *rest_selec) +{ + Pattern_Prefix_Status result; + + switch (ptype) + { + case Pattern_Type_Like: + result = like_fixed_prefix(patt, false, collation, + prefix, rest_selec); + break; + case Pattern_Type_Like_IC: + result = like_fixed_prefix(patt, true, collation, + prefix, rest_selec); + break; + case Pattern_Type_Regex: + result = regex_fixed_prefix(patt, false, collation, + prefix, rest_selec); + break; + case Pattern_Type_Regex_IC: + result = regex_fixed_prefix(patt, true, collation, + prefix, rest_selec); + break; + case Pattern_Type_Prefix: + /* Prefix type work is trivial. */ + result = Pattern_Prefix_Partial; + *prefix = makeConst(patt->consttype, + patt->consttypmod, + patt->constcollid, + patt->constlen, + datumCopy(patt->constvalue, + patt->constbyval, + patt->constlen), + patt->constisnull, + patt->constbyval); + if (rest_selec != NULL) + *rest_selec = 1.0; /* all */ + break; + default: + elog(ERROR, "unrecognized ptype: %d", (int) ptype); + result = Pattern_Prefix_None; /* keep compiler quiet */ + break; + } + return result; +} + +/* + * Estimate the selectivity of a fixed prefix for a pattern match. + * + * A fixed prefix "foo" is estimated as the selectivity of the expression + * "variable >= 'foo' AND variable < 'fop'". + * + * The selectivity estimate is with respect to the portion of the column + * population represented by the histogram --- the caller must fold this + * together with info about MCVs and NULLs. + * + * We use the given comparison operators and collation to do the estimation. + * The given variable and Const must be of the associated datatype(s). + * + * XXX Note: we make use of the upper bound to estimate operator selectivity + * even if the locale is such that we cannot rely on the upper-bound string. + * The selectivity only needs to be approximately right anyway, so it seems + * more useful to use the upper-bound code than not. + */ +static Selectivity +prefix_selectivity(PlannerInfo *root, VariableStatData *vardata, + Oid eqopr, Oid ltopr, Oid geopr, + Oid collation, + Const *prefixcon) +{ + Selectivity prefixsel; + FmgrInfo opproc; + Const *greaterstrcon; + Selectivity eq_sel; + + /* Estimate the selectivity of "x >= prefix" */ + fmgr_info(get_opcode(geopr), &opproc); + + prefixsel = ineq_histogram_selectivity(root, vardata, + geopr, &opproc, true, true, + collation, + prefixcon->constvalue, + prefixcon->consttype); + + if (prefixsel < 0.0) + { + /* No histogram is present ... return a suitable default estimate */ + return DEFAULT_MATCH_SEL; + } + + /* + * If we can create a string larger than the prefix, say "x < greaterstr". + */ + fmgr_info(get_opcode(ltopr), &opproc); + greaterstrcon = make_greater_string(prefixcon, &opproc, collation); + if (greaterstrcon) + { + Selectivity topsel; + + topsel = ineq_histogram_selectivity(root, vardata, + ltopr, &opproc, false, false, + collation, + greaterstrcon->constvalue, + greaterstrcon->consttype); + + /* ineq_histogram_selectivity worked before, it shouldn't fail now */ + Assert(topsel >= 0.0); + + /* + * Merge the two selectivities in the same way as for a range query + * (see clauselist_selectivity()). Note that we don't need to worry + * about double-exclusion of nulls, since ineq_histogram_selectivity + * doesn't count those anyway. + */ + prefixsel = topsel + prefixsel - 1.0; + } + + /* + * If the prefix is long then the two bounding values might be too close + * together for the histogram to distinguish them usefully, resulting in a + * zero estimate (plus or minus roundoff error). To avoid returning a + * ridiculously small estimate, compute the estimated selectivity for + * "variable = 'foo'", and clamp to that. (Obviously, the resultant + * estimate should be at least that.) + * + * We apply this even if we couldn't make a greater string. That case + * suggests that the prefix is near the maximum possible, and thus + * probably off the end of the histogram, and thus we probably got a very + * small estimate from the >= condition; so we still need to clamp. + */ + eq_sel = var_eq_const(vardata, eqopr, collation, prefixcon->constvalue, + false, true, false); + + prefixsel = Max(prefixsel, eq_sel); + + return prefixsel; +} + + +/* + * Estimate the selectivity of a pattern of the specified type. + * Note that any fixed prefix of the pattern will have been removed already, + * so actually we may be looking at just a fragment of the pattern. + * + * For now, we use a very simplistic approach: fixed characters reduce the + * selectivity a good deal, character ranges reduce it a little, + * wildcards (such as % for LIKE or .* for regex) increase it. + */ + +#define FIXED_CHAR_SEL 0.20 /* about 1/5 */ +#define CHAR_RANGE_SEL 0.25 +#define ANY_CHAR_SEL 0.9 /* not 1, since it won't match end-of-string */ +#define FULL_WILDCARD_SEL 5.0 +#define PARTIAL_WILDCARD_SEL 2.0 + +static Selectivity +like_selectivity(const char *patt, int pattlen, bool case_insensitive) +{ + Selectivity sel = 1.0; + int pos; + + /* Skip any leading wildcard; it's already factored into initial sel */ + for (pos = 0; pos < pattlen; pos++) + { + if (patt[pos] != '%' && patt[pos] != '_') + break; + } + + for (; pos < pattlen; pos++) + { + /* % and _ are wildcard characters in LIKE */ + if (patt[pos] == '%') + sel *= FULL_WILDCARD_SEL; + else if (patt[pos] == '_') + sel *= ANY_CHAR_SEL; + else if (patt[pos] == '\\') + { + /* Backslash quotes the next character */ + pos++; + if (pos >= pattlen) + break; + sel *= FIXED_CHAR_SEL; + } + else + sel *= FIXED_CHAR_SEL; + } + /* Could get sel > 1 if multiple wildcards */ + if (sel > 1.0) + sel = 1.0; + return sel; +} + +static Selectivity +regex_selectivity_sub(const char *patt, int pattlen, bool case_insensitive) +{ + Selectivity sel = 1.0; + int paren_depth = 0; + int paren_pos = 0; /* dummy init to keep compiler quiet */ + int pos; + + /* since this function recurses, it could be driven to stack overflow */ + check_stack_depth(); + + for (pos = 0; pos < pattlen; pos++) + { + if (patt[pos] == '(') + { + if (paren_depth == 0) + paren_pos = pos; /* remember start of parenthesized item */ + paren_depth++; + } + else if (patt[pos] == ')' && paren_depth > 0) + { + paren_depth--; + if (paren_depth == 0) + sel *= regex_selectivity_sub(patt + (paren_pos + 1), + pos - (paren_pos + 1), + case_insensitive); + } + else if (patt[pos] == '|' && paren_depth == 0) + { + /* + * If unquoted | is present at paren level 0 in pattern, we have + * multiple alternatives; sum their probabilities. + */ + sel += regex_selectivity_sub(patt + (pos + 1), + pattlen - (pos + 1), + case_insensitive); + break; /* rest of pattern is now processed */ + } + else if (patt[pos] == '[') + { + bool negclass = false; + + if (patt[++pos] == '^') + { + negclass = true; + pos++; + } + if (patt[pos] == ']') /* ']' at start of class is not special */ + pos++; + while (pos < pattlen && patt[pos] != ']') + pos++; + if (paren_depth == 0) + sel *= (negclass ? (1.0 - CHAR_RANGE_SEL) : CHAR_RANGE_SEL); + } + else if (patt[pos] == '.') + { + if (paren_depth == 0) + sel *= ANY_CHAR_SEL; + } + else if (patt[pos] == '*' || + patt[pos] == '?' || + patt[pos] == '+') + { + /* Ought to be smarter about quantifiers... */ + if (paren_depth == 0) + sel *= PARTIAL_WILDCARD_SEL; + } + else if (patt[pos] == '{') + { + while (pos < pattlen && patt[pos] != '}') + pos++; + if (paren_depth == 0) + sel *= PARTIAL_WILDCARD_SEL; + } + else if (patt[pos] == '\\') + { + /* backslash quotes the next character */ + pos++; + if (pos >= pattlen) + break; + if (paren_depth == 0) + sel *= FIXED_CHAR_SEL; + } + else + { + if (paren_depth == 0) + sel *= FIXED_CHAR_SEL; + } + } + /* Could get sel > 1 if multiple wildcards */ + if (sel > 1.0) + sel = 1.0; + return sel; +} + +static Selectivity +regex_selectivity(const char *patt, int pattlen, bool case_insensitive, + int fixed_prefix_len) +{ + Selectivity sel; + + /* If patt doesn't end with $, consider it to have a trailing wildcard */ + if (pattlen > 0 && patt[pattlen - 1] == '$' && + (pattlen == 1 || patt[pattlen - 2] != '\\')) + { + /* has trailing $ */ + sel = regex_selectivity_sub(patt, pattlen - 1, case_insensitive); + } + else + { + /* no trailing $ */ + sel = regex_selectivity_sub(patt, pattlen, case_insensitive); + sel *= FULL_WILDCARD_SEL; + } + + /* + * If there's a fixed prefix, discount its selectivity. We have to be + * careful here since a very long prefix could result in pow's result + * underflowing to zero (in which case "sel" probably has as well). + */ + if (fixed_prefix_len > 0) + { + double prefixsel = pow(FIXED_CHAR_SEL, fixed_prefix_len); + + if (prefixsel > 0.0) + sel /= prefixsel; + } + + /* Make sure result stays in range */ + CLAMP_PROBABILITY(sel); + return sel; +} + +/* + * Check whether char is a letter (and, hence, subject to case-folding) + * + * In multibyte character sets or with ICU, we can't use isalpha, and it does + * not seem worth trying to convert to wchar_t to use iswalpha or u_isalpha. + * Instead, just assume any non-ASCII char is potentially case-varying, and + * hard-wire knowledge of which ASCII chars are letters. + */ +static int +pattern_char_isalpha(char c, bool is_multibyte, + pg_locale_t locale, bool locale_is_c) +{ + if (locale_is_c) + return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); + else if (is_multibyte && IS_HIGHBIT_SET(c)) + return true; + else if (locale && locale->provider == COLLPROVIDER_ICU) + return IS_HIGHBIT_SET(c) || + (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); +#ifdef HAVE_LOCALE_T + else if (locale && locale->provider == COLLPROVIDER_LIBC) + return isalpha_l((unsigned char) c, locale->info.lt); +#endif + else + return isalpha((unsigned char) c); +} + + +/* + * For bytea, the increment function need only increment the current byte + * (there are no multibyte characters to worry about). + */ +static bool +byte_increment(unsigned char *ptr, int len) +{ + if (*ptr >= 255) + return false; + (*ptr)++; + return true; +} + +/* + * Try to generate a string greater than the given string or any + * string it is a prefix of. If successful, return a palloc'd string + * in the form of a Const node; else return NULL. + * + * The caller must provide the appropriate "less than" comparison function + * for testing the strings, along with the collation to use. + * + * The key requirement here is that given a prefix string, say "foo", + * we must be able to generate another string "fop" that is greater than + * all strings "foobar" starting with "foo". We can test that we have + * generated a string greater than the prefix string, but in non-C collations + * that is not a bulletproof guarantee that an extension of the string might + * not sort after it; an example is that "foo " is less than "foo!", but it + * is not clear that a "dictionary" sort ordering will consider "foo!" less + * than "foo bar". CAUTION: Therefore, this function should be used only for + * estimation purposes when working in a non-C collation. + * + * To try to catch most cases where an extended string might otherwise sort + * before the result value, we determine which of the strings "Z", "z", "y", + * and "9" is seen as largest by the collation, and append that to the given + * prefix before trying to find a string that compares as larger. + * + * To search for a greater string, we repeatedly "increment" the rightmost + * character, using an encoding-specific character incrementer function. + * When it's no longer possible to increment the last character, we truncate + * off that character and start incrementing the next-to-rightmost. + * For example, if "z" were the last character in the sort order, then we + * could produce "foo" as a string greater than "fonz". + * + * This could be rather slow in the worst case, but in most cases we + * won't have to try more than one or two strings before succeeding. + * + * Note that it's important for the character incrementer not to be too anal + * about producing every possible character code, since in some cases the only + * way to get a larger string is to increment a previous character position. + * So we don't want to spend too much time trying every possible character + * code at the last position. A good rule of thumb is to be sure that we + * don't try more than 256*K values for a K-byte character (and definitely + * not 256^K, which is what an exhaustive search would approach). + */ +static Const * +make_greater_string(const Const *str_const, FmgrInfo *ltproc, Oid collation) +{ + Oid datatype = str_const->consttype; + char *workstr; + int len; + Datum cmpstr; + char *cmptxt = NULL; + mbcharacter_incrementer charinc; + + /* + * Get a modifiable copy of the prefix string in C-string format, and set + * up the string we will compare to as a Datum. In C locale this can just + * be the given prefix string, otherwise we need to add a suffix. Type + * BYTEA sorts bytewise so it never needs a suffix either. + */ + if (datatype == BYTEAOID) + { + bytea *bstr = DatumGetByteaPP(str_const->constvalue); + + len = VARSIZE_ANY_EXHDR(bstr); + workstr = (char *) palloc(len); + memcpy(workstr, VARDATA_ANY(bstr), len); + Assert((Pointer) bstr == DatumGetPointer(str_const->constvalue)); + cmpstr = str_const->constvalue; + } + else + { + if (datatype == NAMEOID) + workstr = DatumGetCString(DirectFunctionCall1(nameout, + str_const->constvalue)); + else + workstr = TextDatumGetCString(str_const->constvalue); + len = strlen(workstr); + if (lc_collate_is_c(collation) || len == 0) + cmpstr = str_const->constvalue; + else + { + /* If first time through, determine the suffix to use */ + static __thread char suffixchar = 0; + static __thread Oid suffixcollation = 0; + + if (!suffixchar || suffixcollation != collation) + { + char *best; + + best = "Z"; + if (varstr_cmp(best, 1, "z", 1, collation) < 0) + best = "z"; + if (varstr_cmp(best, 1, "y", 1, collation) < 0) + best = "y"; + if (varstr_cmp(best, 1, "9", 1, collation) < 0) + best = "9"; + suffixchar = *best; + suffixcollation = collation; + } + + /* And build the string to compare to */ + if (datatype == NAMEOID) + { + cmptxt = palloc(len + 2); + memcpy(cmptxt, workstr, len); + cmptxt[len] = suffixchar; + cmptxt[len + 1] = '\0'; + cmpstr = PointerGetDatum(cmptxt); + } + else + { + cmptxt = palloc(VARHDRSZ + len + 1); + SET_VARSIZE(cmptxt, VARHDRSZ + len + 1); + memcpy(VARDATA(cmptxt), workstr, len); + *(VARDATA(cmptxt) + len) = suffixchar; + cmpstr = PointerGetDatum(cmptxt); + } + } + } + + /* Select appropriate character-incrementer function */ + if (datatype == BYTEAOID) + charinc = byte_increment; + else + charinc = pg_database_encoding_character_incrementer(); + + /* And search ... */ + while (len > 0) + { + int charlen; + unsigned char *lastchar; + + /* Identify the last character --- for bytea, just the last byte */ + if (datatype == BYTEAOID) + charlen = 1; + else + charlen = len - pg_mbcliplen(workstr, len, len - 1); + lastchar = (unsigned char *) (workstr + len - charlen); + + /* + * Try to generate a larger string by incrementing the last character + * (for BYTEA, we treat each byte as a character). + * + * Note: the incrementer function is expected to return true if it's + * generated a valid-per-the-encoding new character, otherwise false. + * The contents of the character on false return are unspecified. + */ + while (charinc(lastchar, charlen)) + { + Const *workstr_const; + + if (datatype == BYTEAOID) + workstr_const = string_to_bytea_const(workstr, len); + else + workstr_const = string_to_const(workstr, datatype); + + if (DatumGetBool(FunctionCall2Coll(ltproc, + collation, + cmpstr, + workstr_const->constvalue))) + { + /* Successfully made a string larger than cmpstr */ + if (cmptxt) + pfree(cmptxt); + pfree(workstr); + return workstr_const; + } + + /* No good, release unusable value and try again */ + pfree(DatumGetPointer(workstr_const->constvalue)); + pfree(workstr_const); + } + + /* + * No luck here, so truncate off the last character and try to + * increment the next one. + */ + len -= charlen; + workstr[len] = '\0'; + } + + /* Failed... */ + if (cmptxt) + pfree(cmptxt); + pfree(workstr); + + return NULL; +} + +/* + * Generate a Datum of the appropriate type from a C string. + * Note that all of the supported types are pass-by-ref, so the + * returned value should be pfree'd if no longer needed. + */ +static Datum +string_to_datum(const char *str, Oid datatype) +{ + Assert(str != NULL); + + /* + * We cheat a little by assuming that CStringGetTextDatum() will do for + * bpchar and varchar constants too... + */ + if (datatype == NAMEOID) + return DirectFunctionCall1(namein, CStringGetDatum(str)); + else if (datatype == BYTEAOID) + return DirectFunctionCall1(byteain, CStringGetDatum(str)); + else + return CStringGetTextDatum(str); +} + +/* + * Generate a Const node of the appropriate type from a C string. + */ +static Const * +string_to_const(const char *str, Oid datatype) +{ + Datum conval = string_to_datum(str, datatype); + Oid collation; + int constlen; + + /* + * We only need to support a few datatypes here, so hard-wire properties + * instead of incurring the expense of catalog lookups. + */ + switch (datatype) + { + case TEXTOID: + case VARCHAROID: + case BPCHAROID: + collation = DEFAULT_COLLATION_OID; + constlen = -1; + break; + + case NAMEOID: + collation = C_COLLATION_OID; + constlen = NAMEDATALEN; + break; + + case BYTEAOID: + collation = InvalidOid; + constlen = -1; + break; + + default: + elog(ERROR, "unexpected datatype in string_to_const: %u", + datatype); + return NULL; + } + + return makeConst(datatype, -1, collation, constlen, + conval, false, false); +} + +/* + * Generate a Const node of bytea type from a binary C string and a length. + */ +static Const * +string_to_bytea_const(const char *str, size_t str_len) +{ + bytea *bstr = palloc(VARHDRSZ + str_len); + Datum conval; + + memcpy(VARDATA(bstr), str, str_len); + SET_VARSIZE(bstr, VARHDRSZ + str_len); + conval = PointerGetDatum(bstr); + + return makeConst(BYTEAOID, -1, InvalidOid, -1, conval, false, false); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/lockfuncs.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/lockfuncs.c new file mode 100644 index 00000000000..f9b9590997b --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/lockfuncs.c @@ -0,0 +1,1083 @@ +/*------------------------------------------------------------------------- + * + * lockfuncs.c + * Functions for SQL access to various lock-manager capabilities. + * + * Copyright (c) 2002-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/utils/adt/lockfuncs.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/htup_details.h" +#include "access/xact.h" +#include "catalog/pg_type.h" +#include "funcapi.h" +#include "miscadmin.h" +#include "storage/predicate_internals.h" +#include "utils/array.h" +#include "utils/builtins.h" + + +/* + * This must match enum LockTagType! Also, be sure to document any changes + * in the docs for the pg_locks view and for wait event types. + */ +const char *const LockTagTypeNames[] = { + "relation", + "extend", + "frozenid", + "page", + "tuple", + "transactionid", + "virtualxid", + "spectoken", + "object", + "userlock", + "advisory", + "applytransaction" +}; + +StaticAssertDecl(lengthof(LockTagTypeNames) == (LOCKTAG_LAST_TYPE + 1), + "array length mismatch"); + +/* This must match enum PredicateLockTargetType (predicate_internals.h) */ +static const char *const PredicateLockTagTypeNames[] = { + "relation", + "page", + "tuple" +}; + +StaticAssertDecl(lengthof(PredicateLockTagTypeNames) == (PREDLOCKTAG_TUPLE + 1), + "array length mismatch"); + +/* Working status for pg_lock_status */ +typedef struct +{ + LockData *lockData; /* state data from lmgr */ + int currIdx; /* current PROCLOCK index */ + PredicateLockData *predLockData; /* state data for pred locks */ + int predLockIdx; /* current index for pred lock */ +} PG_Lock_Status; + +/* Number of columns in pg_locks output */ +#define NUM_LOCK_STATUS_COLUMNS 16 + +/* + * VXIDGetDatum - Construct a text representation of a VXID + * + * This is currently only used in pg_lock_status, so we put it here. + */ +static Datum +VXIDGetDatum(BackendId bid, LocalTransactionId lxid) +{ + /* + * The representation is "<bid>/<lxid>", decimal and unsigned decimal + * respectively. Note that elog.c also knows how to format a vxid. + */ + char vxidstr[32]; + + snprintf(vxidstr, sizeof(vxidstr), "%d/%u", bid, lxid); + + return CStringGetTextDatum(vxidstr); +} + + +/* + * pg_lock_status - produce a view with one row per held or awaited lock mode + */ +Datum +pg_lock_status(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + PG_Lock_Status *mystatus; + LockData *lockData; + PredicateLockData *predLockData; + + if (SRF_IS_FIRSTCALL()) + { + TupleDesc tupdesc; + MemoryContext oldcontext; + + /* 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); + + /* build tupdesc for result tuples */ + /* this had better match function's declaration in pg_proc.h */ + tupdesc = CreateTemplateTupleDesc(NUM_LOCK_STATUS_COLUMNS); + TupleDescInitEntry(tupdesc, (AttrNumber) 1, "locktype", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 2, "database", + OIDOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 3, "relation", + OIDOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 4, "page", + INT4OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 5, "tuple", + INT2OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 6, "virtualxid", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 7, "transactionid", + XIDOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 8, "classid", + OIDOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 9, "objid", + OIDOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 10, "objsubid", + INT2OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 11, "virtualtransaction", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 12, "pid", + INT4OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 13, "mode", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 14, "granted", + BOOLOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 15, "fastpath", + BOOLOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 16, "waitstart", + TIMESTAMPTZOID, -1, 0); + + funcctx->tuple_desc = BlessTupleDesc(tupdesc); + + /* + * Collect all the locking information that we will format and send + * out as a result set. + */ + mystatus = (PG_Lock_Status *) palloc(sizeof(PG_Lock_Status)); + funcctx->user_fctx = (void *) mystatus; + + mystatus->lockData = GetLockStatusData(); + mystatus->currIdx = 0; + mystatus->predLockData = GetPredicateLockStatusData(); + mystatus->predLockIdx = 0; + + MemoryContextSwitchTo(oldcontext); + } + + funcctx = SRF_PERCALL_SETUP(); + mystatus = (PG_Lock_Status *) funcctx->user_fctx; + lockData = mystatus->lockData; + + while (mystatus->currIdx < lockData->nelements) + { + bool granted; + LOCKMODE mode = 0; + const char *locktypename; + char tnbuf[32]; + Datum values[NUM_LOCK_STATUS_COLUMNS] = {0}; + bool nulls[NUM_LOCK_STATUS_COLUMNS] = {0}; + HeapTuple tuple; + Datum result; + LockInstanceData *instance; + + instance = &(lockData->locks[mystatus->currIdx]); + + /* + * Look to see if there are any held lock modes in this PROCLOCK. If + * so, report, and destructively modify lockData so we don't report + * again. + */ + granted = false; + if (instance->holdMask) + { + for (mode = 0; mode < MAX_LOCKMODES; mode++) + { + if (instance->holdMask & LOCKBIT_ON(mode)) + { + granted = true; + instance->holdMask &= LOCKBIT_OFF(mode); + break; + } + } + } + + /* + * If no (more) held modes to report, see if PROC is waiting for a + * lock on this lock. + */ + if (!granted) + { + if (instance->waitLockMode != NoLock) + { + /* Yes, so report it with proper mode */ + mode = instance->waitLockMode; + + /* + * We are now done with this PROCLOCK, so advance pointer to + * continue with next one on next call. + */ + mystatus->currIdx++; + } + else + { + /* + * Okay, we've displayed all the locks associated with this + * PROCLOCK, proceed to the next one. + */ + mystatus->currIdx++; + continue; + } + } + + /* + * Form tuple with appropriate data. + */ + + if (instance->locktag.locktag_type <= LOCKTAG_LAST_TYPE) + locktypename = LockTagTypeNames[instance->locktag.locktag_type]; + else + { + snprintf(tnbuf, sizeof(tnbuf), "unknown %d", + (int) instance->locktag.locktag_type); + locktypename = tnbuf; + } + values[0] = CStringGetTextDatum(locktypename); + + switch ((LockTagType) instance->locktag.locktag_type) + { + case LOCKTAG_RELATION: + case LOCKTAG_RELATION_EXTEND: + values[1] = ObjectIdGetDatum(instance->locktag.locktag_field1); + values[2] = ObjectIdGetDatum(instance->locktag.locktag_field2); + nulls[3] = true; + nulls[4] = true; + nulls[5] = true; + nulls[6] = true; + nulls[7] = true; + nulls[8] = true; + nulls[9] = true; + break; + case LOCKTAG_DATABASE_FROZEN_IDS: + values[1] = ObjectIdGetDatum(instance->locktag.locktag_field1); + nulls[2] = true; + nulls[3] = true; + nulls[4] = true; + nulls[5] = true; + nulls[6] = true; + nulls[7] = true; + nulls[8] = true; + nulls[9] = true; + break; + case LOCKTAG_PAGE: + values[1] = ObjectIdGetDatum(instance->locktag.locktag_field1); + values[2] = ObjectIdGetDatum(instance->locktag.locktag_field2); + values[3] = UInt32GetDatum(instance->locktag.locktag_field3); + nulls[4] = true; + nulls[5] = true; + nulls[6] = true; + nulls[7] = true; + nulls[8] = true; + nulls[9] = true; + break; + case LOCKTAG_TUPLE: + values[1] = ObjectIdGetDatum(instance->locktag.locktag_field1); + values[2] = ObjectIdGetDatum(instance->locktag.locktag_field2); + values[3] = UInt32GetDatum(instance->locktag.locktag_field3); + values[4] = UInt16GetDatum(instance->locktag.locktag_field4); + nulls[5] = true; + nulls[6] = true; + nulls[7] = true; + nulls[8] = true; + nulls[9] = true; + break; + case LOCKTAG_TRANSACTION: + values[6] = + TransactionIdGetDatum(instance->locktag.locktag_field1); + nulls[1] = true; + nulls[2] = true; + nulls[3] = true; + nulls[4] = true; + nulls[5] = true; + nulls[7] = true; + nulls[8] = true; + nulls[9] = true; + break; + case LOCKTAG_VIRTUALTRANSACTION: + values[5] = VXIDGetDatum(instance->locktag.locktag_field1, + instance->locktag.locktag_field2); + nulls[1] = true; + nulls[2] = true; + nulls[3] = true; + nulls[4] = true; + nulls[6] = true; + nulls[7] = true; + nulls[8] = true; + nulls[9] = true; + break; + case LOCKTAG_SPECULATIVE_TOKEN: + values[6] = + TransactionIdGetDatum(instance->locktag.locktag_field1); + values[8] = ObjectIdGetDatum(instance->locktag.locktag_field2); + nulls[1] = true; + nulls[2] = true; + nulls[3] = true; + nulls[4] = true; + nulls[5] = true; + nulls[7] = true; + nulls[9] = true; + break; + case LOCKTAG_APPLY_TRANSACTION: + values[1] = ObjectIdGetDatum(instance->locktag.locktag_field1); + values[8] = ObjectIdGetDatum(instance->locktag.locktag_field2); + values[6] = ObjectIdGetDatum(instance->locktag.locktag_field3); + values[9] = Int16GetDatum(instance->locktag.locktag_field4); + nulls[2] = true; + nulls[3] = true; + nulls[4] = true; + nulls[5] = true; + nulls[7] = true; + break; + case LOCKTAG_OBJECT: + case LOCKTAG_USERLOCK: + case LOCKTAG_ADVISORY: + default: /* treat unknown locktags like OBJECT */ + values[1] = ObjectIdGetDatum(instance->locktag.locktag_field1); + values[7] = ObjectIdGetDatum(instance->locktag.locktag_field2); + values[8] = ObjectIdGetDatum(instance->locktag.locktag_field3); + values[9] = Int16GetDatum(instance->locktag.locktag_field4); + nulls[2] = true; + nulls[3] = true; + nulls[4] = true; + nulls[5] = true; + nulls[6] = true; + break; + } + + values[10] = VXIDGetDatum(instance->backend, instance->lxid); + if (instance->pid != 0) + values[11] = Int32GetDatum(instance->pid); + else + nulls[11] = true; + values[12] = CStringGetTextDatum(GetLockmodeName(instance->locktag.locktag_lockmethodid, mode)); + values[13] = BoolGetDatum(granted); + values[14] = BoolGetDatum(instance->fastpath); + if (!granted && instance->waitStart != 0) + values[15] = TimestampTzGetDatum(instance->waitStart); + else + nulls[15] = true; + + tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); + result = HeapTupleGetDatum(tuple); + SRF_RETURN_NEXT(funcctx, result); + } + + /* + * Have returned all regular locks. Now start on the SIREAD predicate + * locks. + */ + predLockData = mystatus->predLockData; + if (mystatus->predLockIdx < predLockData->nelements) + { + PredicateLockTargetType lockType; + + PREDICATELOCKTARGETTAG *predTag = &(predLockData->locktags[mystatus->predLockIdx]); + SERIALIZABLEXACT *xact = &(predLockData->xacts[mystatus->predLockIdx]); + Datum values[NUM_LOCK_STATUS_COLUMNS] = {0}; + bool nulls[NUM_LOCK_STATUS_COLUMNS] = {0}; + HeapTuple tuple; + Datum result; + + mystatus->predLockIdx++; + + /* + * Form tuple with appropriate data. + */ + + /* lock type */ + lockType = GET_PREDICATELOCKTARGETTAG_TYPE(*predTag); + + values[0] = CStringGetTextDatum(PredicateLockTagTypeNames[lockType]); + + /* lock target */ + values[1] = GET_PREDICATELOCKTARGETTAG_DB(*predTag); + values[2] = GET_PREDICATELOCKTARGETTAG_RELATION(*predTag); + if (lockType == PREDLOCKTAG_TUPLE) + values[4] = GET_PREDICATELOCKTARGETTAG_OFFSET(*predTag); + else + nulls[4] = true; + if ((lockType == PREDLOCKTAG_TUPLE) || + (lockType == PREDLOCKTAG_PAGE)) + values[3] = GET_PREDICATELOCKTARGETTAG_PAGE(*predTag); + else + nulls[3] = true; + + /* these fields are targets for other types of locks */ + nulls[5] = true; /* virtualxid */ + nulls[6] = true; /* transactionid */ + nulls[7] = true; /* classid */ + nulls[8] = true; /* objid */ + nulls[9] = true; /* objsubid */ + + /* lock holder */ + values[10] = VXIDGetDatum(xact->vxid.backendId, + xact->vxid.localTransactionId); + if (xact->pid != 0) + values[11] = Int32GetDatum(xact->pid); + else + nulls[11] = true; + + /* + * Lock mode. Currently all predicate locks are SIReadLocks, which are + * always held (never waiting) and have no fast path + */ + values[12] = CStringGetTextDatum("SIReadLock"); + values[13] = BoolGetDatum(true); + values[14] = BoolGetDatum(false); + nulls[15] = true; + + tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); + result = HeapTupleGetDatum(tuple); + SRF_RETURN_NEXT(funcctx, result); + } + + SRF_RETURN_DONE(funcctx); +} + + +/* + * pg_blocking_pids - produce an array of the PIDs blocking given PID + * + * The reported PIDs are those that hold a lock conflicting with blocked_pid's + * current request (hard block), or are requesting such a lock and are ahead + * of blocked_pid in the lock's wait queue (soft block). + * + * In parallel-query cases, we report all PIDs blocking any member of the + * given PID's lock group, and the reported PIDs are those of the blocking + * PIDs' lock group leaders. This allows callers to compare the result to + * lists of clients' pg_backend_pid() results even during a parallel query. + * + * Parallel query makes it possible for there to be duplicate PIDs in the + * result (either because multiple waiters are blocked by same PID, or + * because multiple blockers have same group leader PID). We do not bother + * to eliminate such duplicates from the result. + * + * We need not consider predicate locks here, since those don't block anything. + */ +Datum +pg_blocking_pids(PG_FUNCTION_ARGS) +{ + int blocked_pid = PG_GETARG_INT32(0); + Datum *arrayelems; + int narrayelems; + BlockedProcsData *lockData; /* state data from lmgr */ + int i, + j; + + /* Collect a snapshot of lock manager state */ + lockData = GetBlockerStatusData(blocked_pid); + + /* We can't need more output entries than there are reported PROCLOCKs */ + arrayelems = (Datum *) palloc(lockData->nlocks * sizeof(Datum)); + narrayelems = 0; + + /* For each blocked proc in the lock group ... */ + for (i = 0; i < lockData->nprocs; i++) + { + BlockedProcData *bproc = &lockData->procs[i]; + LockInstanceData *instances = &lockData->locks[bproc->first_lock]; + int *preceding_waiters = &lockData->waiter_pids[bproc->first_waiter]; + LockInstanceData *blocked_instance; + LockMethod lockMethodTable; + int conflictMask; + + /* + * Locate the blocked proc's own entry in the LockInstanceData array. + * There should be exactly one matching entry. + */ + blocked_instance = NULL; + for (j = 0; j < bproc->num_locks; j++) + { + LockInstanceData *instance = &(instances[j]); + + if (instance->pid == bproc->pid) + { + Assert(blocked_instance == NULL); + blocked_instance = instance; + } + } + Assert(blocked_instance != NULL); + + lockMethodTable = GetLockTagsMethodTable(&(blocked_instance->locktag)); + conflictMask = lockMethodTable->conflictTab[blocked_instance->waitLockMode]; + + /* Now scan the PROCLOCK data for conflicting procs */ + for (j = 0; j < bproc->num_locks; j++) + { + LockInstanceData *instance = &(instances[j]); + + /* A proc never blocks itself, so ignore that entry */ + if (instance == blocked_instance) + continue; + /* Members of same lock group never block each other, either */ + if (instance->leaderPid == blocked_instance->leaderPid) + continue; + + if (conflictMask & instance->holdMask) + { + /* hard block: blocked by lock already held by this entry */ + } + else if (instance->waitLockMode != NoLock && + (conflictMask & LOCKBIT_ON(instance->waitLockMode))) + { + /* conflict in lock requests; who's in front in wait queue? */ + bool ahead = false; + int k; + + for (k = 0; k < bproc->num_waiters; k++) + { + if (preceding_waiters[k] == instance->pid) + { + /* soft block: this entry is ahead of blocked proc */ + ahead = true; + break; + } + } + if (!ahead) + continue; /* not blocked by this entry */ + } + else + { + /* not blocked by this entry */ + continue; + } + + /* blocked by this entry, so emit a record */ + arrayelems[narrayelems++] = Int32GetDatum(instance->leaderPid); + } + } + + /* Assert we didn't overrun arrayelems[] */ + Assert(narrayelems <= lockData->nlocks); + + PG_RETURN_ARRAYTYPE_P(construct_array_builtin(arrayelems, narrayelems, INT4OID)); +} + + +/* + * pg_safe_snapshot_blocking_pids - produce an array of the PIDs blocking + * given PID from getting a safe snapshot + * + * XXX this does not consider parallel-query cases; not clear how big a + * problem that is in practice + */ +Datum +pg_safe_snapshot_blocking_pids(PG_FUNCTION_ARGS) +{ + int blocked_pid = PG_GETARG_INT32(0); + int *blockers; + int num_blockers; + Datum *blocker_datums; + + /* A buffer big enough for any possible blocker list without truncation */ + blockers = (int *) palloc(MaxBackends * sizeof(int)); + + /* Collect a snapshot of processes waited for by GetSafeSnapshot */ + num_blockers = + GetSafeSnapshotBlockingPids(blocked_pid, blockers, MaxBackends); + + /* Convert int array to Datum array */ + if (num_blockers > 0) + { + int i; + + blocker_datums = (Datum *) palloc(num_blockers * sizeof(Datum)); + for (i = 0; i < num_blockers; ++i) + blocker_datums[i] = Int32GetDatum(blockers[i]); + } + else + blocker_datums = NULL; + + PG_RETURN_ARRAYTYPE_P(construct_array_builtin(blocker_datums, num_blockers, INT4OID)); +} + + +/* + * pg_isolation_test_session_is_blocked - support function for isolationtester + * + * Check if specified PID is blocked by any of the PIDs listed in the second + * argument. Currently, this looks for blocking caused by waiting for + * heavyweight locks or safe snapshots. We ignore blockage caused by PIDs + * not directly under the isolationtester's control, eg autovacuum. + * + * This is an undocumented function intended for use by the isolation tester, + * and may change in future releases as required for testing purposes. + */ +Datum +pg_isolation_test_session_is_blocked(PG_FUNCTION_ARGS) +{ + int blocked_pid = PG_GETARG_INT32(0); + ArrayType *interesting_pids_a = PG_GETARG_ARRAYTYPE_P(1); + ArrayType *blocking_pids_a; + int32 *interesting_pids; + int32 *blocking_pids; + int num_interesting_pids; + int num_blocking_pids; + int dummy; + int i, + j; + + /* Validate the passed-in array */ + Assert(ARR_ELEMTYPE(interesting_pids_a) == INT4OID); + if (array_contains_nulls(interesting_pids_a)) + elog(ERROR, "array must not contain nulls"); + interesting_pids = (int32 *) ARR_DATA_PTR(interesting_pids_a); + num_interesting_pids = ArrayGetNItems(ARR_NDIM(interesting_pids_a), + ARR_DIMS(interesting_pids_a)); + + /* + * Get the PIDs of all sessions blocking the given session's attempt to + * acquire heavyweight locks. + */ + blocking_pids_a = + DatumGetArrayTypeP(DirectFunctionCall1(pg_blocking_pids, blocked_pid)); + + Assert(ARR_ELEMTYPE(blocking_pids_a) == INT4OID); + Assert(!array_contains_nulls(blocking_pids_a)); + blocking_pids = (int32 *) ARR_DATA_PTR(blocking_pids_a); + num_blocking_pids = ArrayGetNItems(ARR_NDIM(blocking_pids_a), + ARR_DIMS(blocking_pids_a)); + + /* + * Check if any of these are in the list of interesting PIDs, that being + * the sessions that the isolation tester is running. We don't use + * "arrayoverlaps" here, because it would lead to cache lookups and one of + * our goals is to run quickly with debug_discard_caches > 0. We expect + * blocking_pids to be usually empty and otherwise a very small number in + * isolation tester cases, so make that the outer loop of a naive search + * for a match. + */ + for (i = 0; i < num_blocking_pids; i++) + for (j = 0; j < num_interesting_pids; j++) + { + if (blocking_pids[i] == interesting_pids[j]) + PG_RETURN_BOOL(true); + } + + /* + * Check if blocked_pid is waiting for a safe snapshot. We could in + * theory check the resulting array of blocker PIDs against the + * interesting PIDs list, but since there is no danger of autovacuum + * blocking GetSafeSnapshot there seems to be no point in expending cycles + * on allocating a buffer and searching for overlap; so it's presently + * sufficient for the isolation tester's purposes to use a single element + * buffer and check if the number of safe snapshot blockers is non-zero. + */ + if (GetSafeSnapshotBlockingPids(blocked_pid, &dummy, 1) > 0) + PG_RETURN_BOOL(true); + + PG_RETURN_BOOL(false); +} + + +/* + * Functions for manipulating advisory locks + * + * We make use of the locktag fields as follows: + * + * field1: MyDatabaseId ... ensures locks are local to each database + * field2: first of 2 int4 keys, or high-order half of an int8 key + * field3: second of 2 int4 keys, or low-order half of an int8 key + * field4: 1 if using an int8 key, 2 if using 2 int4 keys + */ +#define SET_LOCKTAG_INT64(tag, key64) \ + SET_LOCKTAG_ADVISORY(tag, \ + MyDatabaseId, \ + (uint32) ((key64) >> 32), \ + (uint32) (key64), \ + 1) +#define SET_LOCKTAG_INT32(tag, key1, key2) \ + SET_LOCKTAG_ADVISORY(tag, MyDatabaseId, key1, key2, 2) + +/* + * pg_advisory_lock(int8) - acquire exclusive lock on an int8 key + */ +Datum +pg_advisory_lock_int8(PG_FUNCTION_ARGS) +{ + int64 key = PG_GETARG_INT64(0); + LOCKTAG tag; + + SET_LOCKTAG_INT64(tag, key); + + (void) LockAcquire(&tag, ExclusiveLock, true, false); + + PG_RETURN_VOID(); +} + +/* + * pg_advisory_xact_lock(int8) - acquire xact scoped + * exclusive lock on an int8 key + */ +Datum +pg_advisory_xact_lock_int8(PG_FUNCTION_ARGS) +{ + int64 key = PG_GETARG_INT64(0); + LOCKTAG tag; + + SET_LOCKTAG_INT64(tag, key); + + (void) LockAcquire(&tag, ExclusiveLock, false, false); + + PG_RETURN_VOID(); +} + +/* + * pg_advisory_lock_shared(int8) - acquire share lock on an int8 key + */ +Datum +pg_advisory_lock_shared_int8(PG_FUNCTION_ARGS) +{ + int64 key = PG_GETARG_INT64(0); + LOCKTAG tag; + + SET_LOCKTAG_INT64(tag, key); + + (void) LockAcquire(&tag, ShareLock, true, false); + + PG_RETURN_VOID(); +} + +/* + * pg_advisory_xact_lock_shared(int8) - acquire xact scoped + * share lock on an int8 key + */ +Datum +pg_advisory_xact_lock_shared_int8(PG_FUNCTION_ARGS) +{ + int64 key = PG_GETARG_INT64(0); + LOCKTAG tag; + + SET_LOCKTAG_INT64(tag, key); + + (void) LockAcquire(&tag, ShareLock, false, false); + + PG_RETURN_VOID(); +} + +/* + * pg_try_advisory_lock(int8) - acquire exclusive lock on an int8 key, no wait + * + * Returns true if successful, false if lock not available + */ +Datum +pg_try_advisory_lock_int8(PG_FUNCTION_ARGS) +{ + int64 key = PG_GETARG_INT64(0); + LOCKTAG tag; + LockAcquireResult res; + + SET_LOCKTAG_INT64(tag, key); + + res = LockAcquire(&tag, ExclusiveLock, true, true); + + PG_RETURN_BOOL(res != LOCKACQUIRE_NOT_AVAIL); +} + +/* + * pg_try_advisory_xact_lock(int8) - acquire xact scoped + * exclusive lock on an int8 key, no wait + * + * Returns true if successful, false if lock not available + */ +Datum +pg_try_advisory_xact_lock_int8(PG_FUNCTION_ARGS) +{ + int64 key = PG_GETARG_INT64(0); + LOCKTAG tag; + LockAcquireResult res; + + SET_LOCKTAG_INT64(tag, key); + + res = LockAcquire(&tag, ExclusiveLock, false, true); + + PG_RETURN_BOOL(res != LOCKACQUIRE_NOT_AVAIL); +} + +/* + * pg_try_advisory_lock_shared(int8) - acquire share lock on an int8 key, no wait + * + * Returns true if successful, false if lock not available + */ +Datum +pg_try_advisory_lock_shared_int8(PG_FUNCTION_ARGS) +{ + int64 key = PG_GETARG_INT64(0); + LOCKTAG tag; + LockAcquireResult res; + + SET_LOCKTAG_INT64(tag, key); + + res = LockAcquire(&tag, ShareLock, true, true); + + PG_RETURN_BOOL(res != LOCKACQUIRE_NOT_AVAIL); +} + +/* + * pg_try_advisory_xact_lock_shared(int8) - acquire xact scoped + * share lock on an int8 key, no wait + * + * Returns true if successful, false if lock not available + */ +Datum +pg_try_advisory_xact_lock_shared_int8(PG_FUNCTION_ARGS) +{ + int64 key = PG_GETARG_INT64(0); + LOCKTAG tag; + LockAcquireResult res; + + SET_LOCKTAG_INT64(tag, key); + + res = LockAcquire(&tag, ShareLock, false, true); + + PG_RETURN_BOOL(res != LOCKACQUIRE_NOT_AVAIL); +} + +/* + * pg_advisory_unlock(int8) - release exclusive lock on an int8 key + * + * Returns true if successful, false if lock was not held +*/ +Datum +pg_advisory_unlock_int8(PG_FUNCTION_ARGS) +{ + int64 key = PG_GETARG_INT64(0); + LOCKTAG tag; + bool res; + + SET_LOCKTAG_INT64(tag, key); + + res = LockRelease(&tag, ExclusiveLock, true); + + PG_RETURN_BOOL(res); +} + +/* + * pg_advisory_unlock_shared(int8) - release share lock on an int8 key + * + * Returns true if successful, false if lock was not held + */ +Datum +pg_advisory_unlock_shared_int8(PG_FUNCTION_ARGS) +{ + int64 key = PG_GETARG_INT64(0); + LOCKTAG tag; + bool res; + + SET_LOCKTAG_INT64(tag, key); + + res = LockRelease(&tag, ShareLock, true); + + PG_RETURN_BOOL(res); +} + +/* + * pg_advisory_lock(int4, int4) - acquire exclusive lock on 2 int4 keys + */ +Datum +pg_advisory_lock_int4(PG_FUNCTION_ARGS) +{ + int32 key1 = PG_GETARG_INT32(0); + int32 key2 = PG_GETARG_INT32(1); + LOCKTAG tag; + + SET_LOCKTAG_INT32(tag, key1, key2); + + (void) LockAcquire(&tag, ExclusiveLock, true, false); + + PG_RETURN_VOID(); +} + +/* + * pg_advisory_xact_lock(int4, int4) - acquire xact scoped + * exclusive lock on 2 int4 keys + */ +Datum +pg_advisory_xact_lock_int4(PG_FUNCTION_ARGS) +{ + int32 key1 = PG_GETARG_INT32(0); + int32 key2 = PG_GETARG_INT32(1); + LOCKTAG tag; + + SET_LOCKTAG_INT32(tag, key1, key2); + + (void) LockAcquire(&tag, ExclusiveLock, false, false); + + PG_RETURN_VOID(); +} + +/* + * pg_advisory_lock_shared(int4, int4) - acquire share lock on 2 int4 keys + */ +Datum +pg_advisory_lock_shared_int4(PG_FUNCTION_ARGS) +{ + int32 key1 = PG_GETARG_INT32(0); + int32 key2 = PG_GETARG_INT32(1); + LOCKTAG tag; + + SET_LOCKTAG_INT32(tag, key1, key2); + + (void) LockAcquire(&tag, ShareLock, true, false); + + PG_RETURN_VOID(); +} + +/* + * pg_advisory_xact_lock_shared(int4, int4) - acquire xact scoped + * share lock on 2 int4 keys + */ +Datum +pg_advisory_xact_lock_shared_int4(PG_FUNCTION_ARGS) +{ + int32 key1 = PG_GETARG_INT32(0); + int32 key2 = PG_GETARG_INT32(1); + LOCKTAG tag; + + SET_LOCKTAG_INT32(tag, key1, key2); + + (void) LockAcquire(&tag, ShareLock, false, false); + + PG_RETURN_VOID(); +} + +/* + * pg_try_advisory_lock(int4, int4) - acquire exclusive lock on 2 int4 keys, no wait + * + * Returns true if successful, false if lock not available + */ +Datum +pg_try_advisory_lock_int4(PG_FUNCTION_ARGS) +{ + int32 key1 = PG_GETARG_INT32(0); + int32 key2 = PG_GETARG_INT32(1); + LOCKTAG tag; + LockAcquireResult res; + + SET_LOCKTAG_INT32(tag, key1, key2); + + res = LockAcquire(&tag, ExclusiveLock, true, true); + + PG_RETURN_BOOL(res != LOCKACQUIRE_NOT_AVAIL); +} + +/* + * pg_try_advisory_xact_lock(int4, int4) - acquire xact scoped + * exclusive lock on 2 int4 keys, no wait + * + * Returns true if successful, false if lock not available + */ +Datum +pg_try_advisory_xact_lock_int4(PG_FUNCTION_ARGS) +{ + int32 key1 = PG_GETARG_INT32(0); + int32 key2 = PG_GETARG_INT32(1); + LOCKTAG tag; + LockAcquireResult res; + + SET_LOCKTAG_INT32(tag, key1, key2); + + res = LockAcquire(&tag, ExclusiveLock, false, true); + + PG_RETURN_BOOL(res != LOCKACQUIRE_NOT_AVAIL); +} + +/* + * pg_try_advisory_lock_shared(int4, int4) - acquire share lock on 2 int4 keys, no wait + * + * Returns true if successful, false if lock not available + */ +Datum +pg_try_advisory_lock_shared_int4(PG_FUNCTION_ARGS) +{ + int32 key1 = PG_GETARG_INT32(0); + int32 key2 = PG_GETARG_INT32(1); + LOCKTAG tag; + LockAcquireResult res; + + SET_LOCKTAG_INT32(tag, key1, key2); + + res = LockAcquire(&tag, ShareLock, true, true); + + PG_RETURN_BOOL(res != LOCKACQUIRE_NOT_AVAIL); +} + +/* + * pg_try_advisory_xact_lock_shared(int4, int4) - acquire xact scoped + * share lock on 2 int4 keys, no wait + * + * Returns true if successful, false if lock not available + */ +Datum +pg_try_advisory_xact_lock_shared_int4(PG_FUNCTION_ARGS) +{ + int32 key1 = PG_GETARG_INT32(0); + int32 key2 = PG_GETARG_INT32(1); + LOCKTAG tag; + LockAcquireResult res; + + SET_LOCKTAG_INT32(tag, key1, key2); + + res = LockAcquire(&tag, ShareLock, false, true); + + PG_RETURN_BOOL(res != LOCKACQUIRE_NOT_AVAIL); +} + +/* + * pg_advisory_unlock(int4, int4) - release exclusive lock on 2 int4 keys + * + * Returns true if successful, false if lock was not held +*/ +Datum +pg_advisory_unlock_int4(PG_FUNCTION_ARGS) +{ + int32 key1 = PG_GETARG_INT32(0); + int32 key2 = PG_GETARG_INT32(1); + LOCKTAG tag; + bool res; + + SET_LOCKTAG_INT32(tag, key1, key2); + + res = LockRelease(&tag, ExclusiveLock, true); + + PG_RETURN_BOOL(res); +} + +/* + * pg_advisory_unlock_shared(int4, int4) - release share lock on 2 int4 keys + * + * Returns true if successful, false if lock was not held + */ +Datum +pg_advisory_unlock_shared_int4(PG_FUNCTION_ARGS) +{ + int32 key1 = PG_GETARG_INT32(0); + int32 key2 = PG_GETARG_INT32(1); + LOCKTAG tag; + bool res; + + SET_LOCKTAG_INT32(tag, key1, key2); + + res = LockRelease(&tag, ShareLock, true); + + PG_RETURN_BOOL(res); +} + +/* + * pg_advisory_unlock_all() - release all advisory locks + */ +Datum +pg_advisory_unlock_all(PG_FUNCTION_ARGS) +{ + LockReleaseSession(USER_LOCKMETHOD); + + PG_RETURN_VOID(); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/mac.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/mac.c new file mode 100644 index 00000000000..6abf9485af2 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/mac.c @@ -0,0 +1,532 @@ +/*------------------------------------------------------------------------- + * + * mac.c + * PostgreSQL type definitions for 6 byte, EUI-48, MAC addresses. + * + * Portions Copyright (c) 1998-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/utils/adt/mac.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "common/hashfn.h" +#include "lib/hyperloglog.h" +#include "libpq/pqformat.h" +#include "port/pg_bswap.h" +#include "utils/builtins.h" +#include "utils/guc.h" +#include "utils/inet.h" +#include "utils/sortsupport.h" + + +/* + * Utility macros used for sorting and comparing: + */ + +#define hibits(addr) \ + ((unsigned long)(((addr)->a<<16)|((addr)->b<<8)|((addr)->c))) + +#define lobits(addr) \ + ((unsigned long)(((addr)->d<<16)|((addr)->e<<8)|((addr)->f))) + +/* sortsupport for macaddr */ +typedef struct +{ + int64 input_count; /* number of non-null values seen */ + bool estimating; /* true if estimating cardinality */ + + hyperLogLogState abbr_card; /* cardinality estimator */ +} macaddr_sortsupport_state; + +static int macaddr_cmp_internal(macaddr *a1, macaddr *a2); +static int macaddr_fast_cmp(Datum x, Datum y, SortSupport ssup); +static bool macaddr_abbrev_abort(int memtupcount, SortSupport ssup); +static Datum macaddr_abbrev_convert(Datum original, SortSupport ssup); + +/* + * MAC address reader. Accepts several common notations. + */ + +Datum +macaddr_in(PG_FUNCTION_ARGS) +{ + char *str = PG_GETARG_CSTRING(0); + Node *escontext = fcinfo->context; + macaddr *result; + int a, + b, + c, + d, + e, + f; + char junk[2]; + int count; + + /* %1s matches iff there is trailing non-whitespace garbage */ + + count = sscanf(str, "%x:%x:%x:%x:%x:%x%1s", + &a, &b, &c, &d, &e, &f, junk); + if (count != 6) + count = sscanf(str, "%x-%x-%x-%x-%x-%x%1s", + &a, &b, &c, &d, &e, &f, junk); + if (count != 6) + count = sscanf(str, "%2x%2x%2x:%2x%2x%2x%1s", + &a, &b, &c, &d, &e, &f, junk); + if (count != 6) + count = sscanf(str, "%2x%2x%2x-%2x%2x%2x%1s", + &a, &b, &c, &d, &e, &f, junk); + if (count != 6) + count = sscanf(str, "%2x%2x.%2x%2x.%2x%2x%1s", + &a, &b, &c, &d, &e, &f, junk); + if (count != 6) + count = sscanf(str, "%2x%2x-%2x%2x-%2x%2x%1s", + &a, &b, &c, &d, &e, &f, junk); + if (count != 6) + count = sscanf(str, "%2x%2x%2x%2x%2x%2x%1s", + &a, &b, &c, &d, &e, &f, junk); + if (count != 6) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s: \"%s\"", "macaddr", + str))); + + if ((a < 0) || (a > 255) || (b < 0) || (b > 255) || + (c < 0) || (c > 255) || (d < 0) || (d > 255) || + (e < 0) || (e > 255) || (f < 0) || (f > 255)) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("invalid octet value in \"macaddr\" value: \"%s\"", str))); + + result = (macaddr *) palloc(sizeof(macaddr)); + + result->a = a; + result->b = b; + result->c = c; + result->d = d; + result->e = e; + result->f = f; + + PG_RETURN_MACADDR_P(result); +} + +/* + * MAC address output function. Fixed format. + */ + +Datum +macaddr_out(PG_FUNCTION_ARGS) +{ + macaddr *addr = PG_GETARG_MACADDR_P(0); + char *result; + + result = (char *) palloc(32); + + snprintf(result, 32, "%02x:%02x:%02x:%02x:%02x:%02x", + addr->a, addr->b, addr->c, addr->d, addr->e, addr->f); + + PG_RETURN_CSTRING(result); +} + +/* + * macaddr_recv - converts external binary format to macaddr + * + * The external representation is just the six bytes, MSB first. + */ +Datum +macaddr_recv(PG_FUNCTION_ARGS) +{ + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + macaddr *addr; + + addr = (macaddr *) palloc(sizeof(macaddr)); + + addr->a = pq_getmsgbyte(buf); + addr->b = pq_getmsgbyte(buf); + addr->c = pq_getmsgbyte(buf); + addr->d = pq_getmsgbyte(buf); + addr->e = pq_getmsgbyte(buf); + addr->f = pq_getmsgbyte(buf); + + PG_RETURN_MACADDR_P(addr); +} + +/* + * macaddr_send - converts macaddr to binary format + */ +Datum +macaddr_send(PG_FUNCTION_ARGS) +{ + macaddr *addr = PG_GETARG_MACADDR_P(0); + StringInfoData buf; + + pq_begintypsend(&buf); + pq_sendbyte(&buf, addr->a); + pq_sendbyte(&buf, addr->b); + pq_sendbyte(&buf, addr->c); + pq_sendbyte(&buf, addr->d); + pq_sendbyte(&buf, addr->e); + pq_sendbyte(&buf, addr->f); + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} + + +/* + * Comparison function for sorting: + */ + +static int +macaddr_cmp_internal(macaddr *a1, macaddr *a2) +{ + if (hibits(a1) < hibits(a2)) + return -1; + else if (hibits(a1) > hibits(a2)) + return 1; + else if (lobits(a1) < lobits(a2)) + return -1; + else if (lobits(a1) > lobits(a2)) + return 1; + else + return 0; +} + +Datum +macaddr_cmp(PG_FUNCTION_ARGS) +{ + macaddr *a1 = PG_GETARG_MACADDR_P(0); + macaddr *a2 = PG_GETARG_MACADDR_P(1); + + PG_RETURN_INT32(macaddr_cmp_internal(a1, a2)); +} + +/* + * Boolean comparisons. + */ + +Datum +macaddr_lt(PG_FUNCTION_ARGS) +{ + macaddr *a1 = PG_GETARG_MACADDR_P(0); + macaddr *a2 = PG_GETARG_MACADDR_P(1); + + PG_RETURN_BOOL(macaddr_cmp_internal(a1, a2) < 0); +} + +Datum +macaddr_le(PG_FUNCTION_ARGS) +{ + macaddr *a1 = PG_GETARG_MACADDR_P(0); + macaddr *a2 = PG_GETARG_MACADDR_P(1); + + PG_RETURN_BOOL(macaddr_cmp_internal(a1, a2) <= 0); +} + +Datum +macaddr_eq(PG_FUNCTION_ARGS) +{ + macaddr *a1 = PG_GETARG_MACADDR_P(0); + macaddr *a2 = PG_GETARG_MACADDR_P(1); + + PG_RETURN_BOOL(macaddr_cmp_internal(a1, a2) == 0); +} + +Datum +macaddr_ge(PG_FUNCTION_ARGS) +{ + macaddr *a1 = PG_GETARG_MACADDR_P(0); + macaddr *a2 = PG_GETARG_MACADDR_P(1); + + PG_RETURN_BOOL(macaddr_cmp_internal(a1, a2) >= 0); +} + +Datum +macaddr_gt(PG_FUNCTION_ARGS) +{ + macaddr *a1 = PG_GETARG_MACADDR_P(0); + macaddr *a2 = PG_GETARG_MACADDR_P(1); + + PG_RETURN_BOOL(macaddr_cmp_internal(a1, a2) > 0); +} + +Datum +macaddr_ne(PG_FUNCTION_ARGS) +{ + macaddr *a1 = PG_GETARG_MACADDR_P(0); + macaddr *a2 = PG_GETARG_MACADDR_P(1); + + PG_RETURN_BOOL(macaddr_cmp_internal(a1, a2) != 0); +} + +/* + * Support function for hash indexes on macaddr. + */ +Datum +hashmacaddr(PG_FUNCTION_ARGS) +{ + macaddr *key = PG_GETARG_MACADDR_P(0); + + return hash_any((unsigned char *) key, sizeof(macaddr)); +} + +Datum +hashmacaddrextended(PG_FUNCTION_ARGS) +{ + macaddr *key = PG_GETARG_MACADDR_P(0); + + return hash_any_extended((unsigned char *) key, sizeof(macaddr), + PG_GETARG_INT64(1)); +} + +/* + * Arithmetic functions: bitwise NOT, AND, OR. + */ +Datum +macaddr_not(PG_FUNCTION_ARGS) +{ + macaddr *addr = PG_GETARG_MACADDR_P(0); + macaddr *result; + + result = (macaddr *) palloc(sizeof(macaddr)); + result->a = ~addr->a; + result->b = ~addr->b; + result->c = ~addr->c; + result->d = ~addr->d; + result->e = ~addr->e; + result->f = ~addr->f; + PG_RETURN_MACADDR_P(result); +} + +Datum +macaddr_and(PG_FUNCTION_ARGS) +{ + macaddr *addr1 = PG_GETARG_MACADDR_P(0); + macaddr *addr2 = PG_GETARG_MACADDR_P(1); + macaddr *result; + + result = (macaddr *) palloc(sizeof(macaddr)); + result->a = addr1->a & addr2->a; + result->b = addr1->b & addr2->b; + result->c = addr1->c & addr2->c; + result->d = addr1->d & addr2->d; + result->e = addr1->e & addr2->e; + result->f = addr1->f & addr2->f; + PG_RETURN_MACADDR_P(result); +} + +Datum +macaddr_or(PG_FUNCTION_ARGS) +{ + macaddr *addr1 = PG_GETARG_MACADDR_P(0); + macaddr *addr2 = PG_GETARG_MACADDR_P(1); + macaddr *result; + + result = (macaddr *) palloc(sizeof(macaddr)); + result->a = addr1->a | addr2->a; + result->b = addr1->b | addr2->b; + result->c = addr1->c | addr2->c; + result->d = addr1->d | addr2->d; + result->e = addr1->e | addr2->e; + result->f = addr1->f | addr2->f; + PG_RETURN_MACADDR_P(result); +} + +/* + * Truncation function to allow comparing mac manufacturers. + * From suggestion by Alex Pilosov <alex@pilosoft.com> + */ +Datum +macaddr_trunc(PG_FUNCTION_ARGS) +{ + macaddr *addr = PG_GETARG_MACADDR_P(0); + macaddr *result; + + result = (macaddr *) palloc(sizeof(macaddr)); + + result->a = addr->a; + result->b = addr->b; + result->c = addr->c; + result->d = 0; + result->e = 0; + result->f = 0; + + PG_RETURN_MACADDR_P(result); +} + +/* + * SortSupport strategy function. Populates a SortSupport struct with the + * information necessary to use comparison by abbreviated keys. + */ +Datum +macaddr_sortsupport(PG_FUNCTION_ARGS) +{ + SortSupport ssup = (SortSupport) PG_GETARG_POINTER(0); + + ssup->comparator = macaddr_fast_cmp; + ssup->ssup_extra = NULL; + + if (ssup->abbreviate) + { + macaddr_sortsupport_state *uss; + MemoryContext oldcontext; + + oldcontext = MemoryContextSwitchTo(ssup->ssup_cxt); + + uss = palloc(sizeof(macaddr_sortsupport_state)); + uss->input_count = 0; + uss->estimating = true; + initHyperLogLog(&uss->abbr_card, 10); + + ssup->ssup_extra = uss; + + ssup->comparator = ssup_datum_unsigned_cmp; + ssup->abbrev_converter = macaddr_abbrev_convert; + ssup->abbrev_abort = macaddr_abbrev_abort; + ssup->abbrev_full_comparator = macaddr_fast_cmp; + + MemoryContextSwitchTo(oldcontext); + } + + PG_RETURN_VOID(); +} + +/* + * SortSupport "traditional" comparison function. Pulls two MAC addresses from + * the heap and runs a standard comparison on them. + */ +static int +macaddr_fast_cmp(Datum x, Datum y, SortSupport ssup) +{ + macaddr *arg1 = DatumGetMacaddrP(x); + macaddr *arg2 = DatumGetMacaddrP(y); + + return macaddr_cmp_internal(arg1, arg2); +} + +/* + * Callback for estimating effectiveness of abbreviated key optimization. + * + * We pay no attention to the cardinality of the non-abbreviated data, because + * there is no equality fast-path within authoritative macaddr comparator. + */ +static bool +macaddr_abbrev_abort(int memtupcount, SortSupport ssup) +{ + macaddr_sortsupport_state *uss = ssup->ssup_extra; + double abbr_card; + + if (memtupcount < 10000 || uss->input_count < 10000 || !uss->estimating) + return false; + + abbr_card = estimateHyperLogLog(&uss->abbr_card); + + /* + * If we have >100k distinct values, then even if we were sorting many + * billion rows we'd likely still break even, and the penalty of undoing + * that many rows of abbrevs would probably not be worth it. At this point + * we stop counting because we know that we're now fully committed. + */ + if (abbr_card > 100000.0) + { +#ifdef TRACE_SORT + if (trace_sort) + elog(LOG, + "macaddr_abbrev: estimation ends at cardinality %f" + " after " INT64_FORMAT " values (%d rows)", + abbr_card, uss->input_count, memtupcount); +#endif + uss->estimating = false; + return false; + } + + /* + * Target minimum cardinality is 1 per ~2k of non-null inputs. 0.5 row + * fudge factor allows us to abort earlier on genuinely pathological data + * where we've had exactly one abbreviated value in the first 2k + * (non-null) rows. + */ + if (abbr_card < uss->input_count / 2000.0 + 0.5) + { +#ifdef TRACE_SORT + if (trace_sort) + elog(LOG, + "macaddr_abbrev: aborting abbreviation at cardinality %f" + " below threshold %f after " INT64_FORMAT " values (%d rows)", + abbr_card, uss->input_count / 2000.0 + 0.5, uss->input_count, + memtupcount); +#endif + return true; + } + +#ifdef TRACE_SORT + if (trace_sort) + elog(LOG, + "macaddr_abbrev: cardinality %f after " INT64_FORMAT + " values (%d rows)", abbr_card, uss->input_count, memtupcount); +#endif + + return false; +} + +/* + * SortSupport conversion routine. Converts original macaddr representation + * to abbreviated key representation. + * + * Packs the bytes of a 6-byte MAC address into a Datum and treats it as an + * unsigned integer for purposes of comparison. On a 64-bit machine, there + * will be two zeroed bytes of padding. The integer is converted to native + * endianness to facilitate easy comparison. + */ +static Datum +macaddr_abbrev_convert(Datum original, SortSupport ssup) +{ + macaddr_sortsupport_state *uss = ssup->ssup_extra; + macaddr *authoritative = DatumGetMacaddrP(original); + Datum res; + + /* + * On a 64-bit machine, zero out the 8-byte datum and copy the 6 bytes of + * the MAC address in. There will be two bytes of zero padding on the end + * of the least significant bits. + */ +#if SIZEOF_DATUM == 8 + memset(&res, 0, SIZEOF_DATUM); + memcpy(&res, authoritative, sizeof(macaddr)); +#else /* SIZEOF_DATUM != 8 */ + memcpy(&res, authoritative, SIZEOF_DATUM); +#endif + uss->input_count += 1; + + /* + * Cardinality estimation. The estimate uses uint32, so on a 64-bit + * architecture, XOR the two 32-bit halves together to produce slightly + * more entropy. The two zeroed bytes won't have any practical impact on + * this operation. + */ + if (uss->estimating) + { + uint32 tmp; + +#if SIZEOF_DATUM == 8 + tmp = (uint32) res ^ (uint32) ((uint64) res >> 32); +#else /* SIZEOF_DATUM != 8 */ + tmp = (uint32) res; +#endif + + addHyperLogLog(&uss->abbr_card, DatumGetUInt32(hash_uint32(tmp))); + } + + /* + * Byteswap on little-endian machines. + * + * This is needed so that ssup_datum_unsigned_cmp() (an unsigned integer + * 3-way comparator) works correctly on all platforms. Without this, the + * comparator would have to call memcmp() with a pair of pointers to the + * first byte of each abbreviated key, which is slower. + */ + res = DatumBigEndianToNative(res); + + return res; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/mac8.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/mac8.c new file mode 100644 index 00000000000..25bb6c16666 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/mac8.c @@ -0,0 +1,568 @@ +/*------------------------------------------------------------------------- + * + * mac8.c + * PostgreSQL type definitions for 8 byte (EUI-64) MAC addresses. + * + * EUI-48 (6 byte) MAC addresses are accepted as input and are stored in + * EUI-64 format, with the 4th and 5th bytes set to FF and FE, respectively. + * + * Output is always in 8 byte (EUI-64) format. + * + * The following code is written with the assumption that the OUI field + * size is 24 bits. + * + * Portions Copyright (c) 1998-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/utils/adt/mac8.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "common/hashfn.h" +#include "libpq/pqformat.h" +#include "utils/builtins.h" +#include "utils/inet.h" + +/* + * Utility macros used for sorting and comparing: + */ +#define hibits(addr) \ + ((unsigned long)(((addr)->a<<24) | ((addr)->b<<16) | ((addr)->c<<8) | ((addr)->d))) + +#define lobits(addr) \ + ((unsigned long)(((addr)->e<<24) | ((addr)->f<<16) | ((addr)->g<<8) | ((addr)->h))) + +static unsigned char hex2_to_uchar(const unsigned char *ptr, bool *badhex); + +static const signed char hexlookup[128] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, +}; + +/* + * hex2_to_uchar - convert 2 hex digits to a byte (unsigned char) + * + * Sets *badhex to true if the end of the string is reached ('\0' found), or if + * either character is not a valid hex digit. + */ +static inline unsigned char +hex2_to_uchar(const unsigned char *ptr, bool *badhex) +{ + unsigned char ret; + signed char lookup; + + /* Handle the first character */ + if (*ptr > 127) + goto invalid_input; + + lookup = hexlookup[*ptr]; + if (lookup < 0) + goto invalid_input; + + ret = lookup << 4; + + /* Move to the second character */ + ptr++; + + if (*ptr > 127) + goto invalid_input; + + lookup = hexlookup[*ptr]; + if (lookup < 0) + goto invalid_input; + + ret += lookup; + + return ret; + +invalid_input: + *badhex = true; + return 0; +} + +/* + * MAC address (EUI-48 and EUI-64) reader. Accepts several common notations. + */ +Datum +macaddr8_in(PG_FUNCTION_ARGS) +{ + const unsigned char *str = (unsigned char *) PG_GETARG_CSTRING(0); + Node *escontext = fcinfo->context; + const unsigned char *ptr = str; + bool badhex = false; + macaddr8 *result; + unsigned char a = 0, + b = 0, + c = 0, + d = 0, + e = 0, + f = 0, + g = 0, + h = 0; + int count = 0; + unsigned char spacer = '\0'; + + /* skip leading spaces */ + while (*ptr && isspace(*ptr)) + ptr++; + + /* digits must always come in pairs */ + while (*ptr && *(ptr + 1)) + { + /* + * Attempt to decode each byte, which must be 2 hex digits in a row. + * If either digit is not hex, hex2_to_uchar will throw ereport() for + * us. Either 6 or 8 byte MAC addresses are supported. + */ + + /* Attempt to collect a byte */ + count++; + + switch (count) + { + case 1: + a = hex2_to_uchar(ptr, &badhex); + break; + case 2: + b = hex2_to_uchar(ptr, &badhex); + break; + case 3: + c = hex2_to_uchar(ptr, &badhex); + break; + case 4: + d = hex2_to_uchar(ptr, &badhex); + break; + case 5: + e = hex2_to_uchar(ptr, &badhex); + break; + case 6: + f = hex2_to_uchar(ptr, &badhex); + break; + case 7: + g = hex2_to_uchar(ptr, &badhex); + break; + case 8: + h = hex2_to_uchar(ptr, &badhex); + break; + default: + /* must be trailing garbage... */ + goto fail; + } + + if (badhex) + goto fail; + + /* Move forward to where the next byte should be */ + ptr += 2; + + /* Check for a spacer, these are valid, anything else is not */ + if (*ptr == ':' || *ptr == '-' || *ptr == '.') + { + /* remember the spacer used, if it changes then it isn't valid */ + if (spacer == '\0') + spacer = *ptr; + + /* Have to use the same spacer throughout */ + else if (spacer != *ptr) + goto fail; + + /* move past the spacer */ + ptr++; + } + + /* allow trailing whitespace after if we have 6 or 8 bytes */ + if (count == 6 || count == 8) + { + if (isspace(*ptr)) + { + while (*++ptr && isspace(*ptr)); + + /* If we found a space and then non-space, it's invalid */ + if (*ptr) + goto fail; + } + } + } + + /* Convert a 6 byte MAC address to macaddr8 */ + if (count == 6) + { + h = f; + g = e; + f = d; + + d = 0xFF; + e = 0xFE; + } + else if (count != 8) + goto fail; + + result = (macaddr8 *) palloc0(sizeof(macaddr8)); + + result->a = a; + result->b = b; + result->c = c; + result->d = d; + result->e = e; + result->f = f; + result->g = g; + result->h = h; + + PG_RETURN_MACADDR8_P(result); + +fail: + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s: \"%s\"", "macaddr8", + str))); +} + +/* + * MAC8 address (EUI-64) output function. Fixed format. + */ +Datum +macaddr8_out(PG_FUNCTION_ARGS) +{ + macaddr8 *addr = PG_GETARG_MACADDR8_P(0); + char *result; + + result = (char *) palloc(32); + + snprintf(result, 32, "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x", + addr->a, addr->b, addr->c, addr->d, + addr->e, addr->f, addr->g, addr->h); + + PG_RETURN_CSTRING(result); +} + +/* + * macaddr8_recv - converts external binary format(EUI-48 and EUI-64) to macaddr8 + * + * The external representation is just the eight bytes, MSB first. + */ +Datum +macaddr8_recv(PG_FUNCTION_ARGS) +{ + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + macaddr8 *addr; + + addr = (macaddr8 *) palloc0(sizeof(macaddr8)); + + addr->a = pq_getmsgbyte(buf); + addr->b = pq_getmsgbyte(buf); + addr->c = pq_getmsgbyte(buf); + + if (buf->len == 6) + { + addr->d = 0xFF; + addr->e = 0xFE; + } + else + { + addr->d = pq_getmsgbyte(buf); + addr->e = pq_getmsgbyte(buf); + } + + addr->f = pq_getmsgbyte(buf); + addr->g = pq_getmsgbyte(buf); + addr->h = pq_getmsgbyte(buf); + + PG_RETURN_MACADDR8_P(addr); +} + +/* + * macaddr8_send - converts macaddr8(EUI-64) to binary format + */ +Datum +macaddr8_send(PG_FUNCTION_ARGS) +{ + macaddr8 *addr = PG_GETARG_MACADDR8_P(0); + StringInfoData buf; + + pq_begintypsend(&buf); + pq_sendbyte(&buf, addr->a); + pq_sendbyte(&buf, addr->b); + pq_sendbyte(&buf, addr->c); + pq_sendbyte(&buf, addr->d); + pq_sendbyte(&buf, addr->e); + pq_sendbyte(&buf, addr->f); + pq_sendbyte(&buf, addr->g); + pq_sendbyte(&buf, addr->h); + + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} + + +/* + * macaddr8_cmp_internal - comparison function for sorting: + */ +static int32 +macaddr8_cmp_internal(macaddr8 *a1, macaddr8 *a2) +{ + if (hibits(a1) < hibits(a2)) + return -1; + else if (hibits(a1) > hibits(a2)) + return 1; + else if (lobits(a1) < lobits(a2)) + return -1; + else if (lobits(a1) > lobits(a2)) + return 1; + else + return 0; +} + +Datum +macaddr8_cmp(PG_FUNCTION_ARGS) +{ + macaddr8 *a1 = PG_GETARG_MACADDR8_P(0); + macaddr8 *a2 = PG_GETARG_MACADDR8_P(1); + + PG_RETURN_INT32(macaddr8_cmp_internal(a1, a2)); +} + +/* + * Boolean comparison functions. + */ + +Datum +macaddr8_lt(PG_FUNCTION_ARGS) +{ + macaddr8 *a1 = PG_GETARG_MACADDR8_P(0); + macaddr8 *a2 = PG_GETARG_MACADDR8_P(1); + + PG_RETURN_BOOL(macaddr8_cmp_internal(a1, a2) < 0); +} + +Datum +macaddr8_le(PG_FUNCTION_ARGS) +{ + macaddr8 *a1 = PG_GETARG_MACADDR8_P(0); + macaddr8 *a2 = PG_GETARG_MACADDR8_P(1); + + PG_RETURN_BOOL(macaddr8_cmp_internal(a1, a2) <= 0); +} + +Datum +macaddr8_eq(PG_FUNCTION_ARGS) +{ + macaddr8 *a1 = PG_GETARG_MACADDR8_P(0); + macaddr8 *a2 = PG_GETARG_MACADDR8_P(1); + + PG_RETURN_BOOL(macaddr8_cmp_internal(a1, a2) == 0); +} + +Datum +macaddr8_ge(PG_FUNCTION_ARGS) +{ + macaddr8 *a1 = PG_GETARG_MACADDR8_P(0); + macaddr8 *a2 = PG_GETARG_MACADDR8_P(1); + + PG_RETURN_BOOL(macaddr8_cmp_internal(a1, a2) >= 0); +} + +Datum +macaddr8_gt(PG_FUNCTION_ARGS) +{ + macaddr8 *a1 = PG_GETARG_MACADDR8_P(0); + macaddr8 *a2 = PG_GETARG_MACADDR8_P(1); + + PG_RETURN_BOOL(macaddr8_cmp_internal(a1, a2) > 0); +} + +Datum +macaddr8_ne(PG_FUNCTION_ARGS) +{ + macaddr8 *a1 = PG_GETARG_MACADDR8_P(0); + macaddr8 *a2 = PG_GETARG_MACADDR8_P(1); + + PG_RETURN_BOOL(macaddr8_cmp_internal(a1, a2) != 0); +} + +/* + * Support function for hash indexes on macaddr8. + */ +Datum +hashmacaddr8(PG_FUNCTION_ARGS) +{ + macaddr8 *key = PG_GETARG_MACADDR8_P(0); + + return hash_any((unsigned char *) key, sizeof(macaddr8)); +} + +Datum +hashmacaddr8extended(PG_FUNCTION_ARGS) +{ + macaddr8 *key = PG_GETARG_MACADDR8_P(0); + + return hash_any_extended((unsigned char *) key, sizeof(macaddr8), + PG_GETARG_INT64(1)); +} + +/* + * Arithmetic functions: bitwise NOT, AND, OR. + */ +Datum +macaddr8_not(PG_FUNCTION_ARGS) +{ + macaddr8 *addr = PG_GETARG_MACADDR8_P(0); + macaddr8 *result; + + result = (macaddr8 *) palloc0(sizeof(macaddr8)); + result->a = ~addr->a; + result->b = ~addr->b; + result->c = ~addr->c; + result->d = ~addr->d; + result->e = ~addr->e; + result->f = ~addr->f; + result->g = ~addr->g; + result->h = ~addr->h; + + PG_RETURN_MACADDR8_P(result); +} + +Datum +macaddr8_and(PG_FUNCTION_ARGS) +{ + macaddr8 *addr1 = PG_GETARG_MACADDR8_P(0); + macaddr8 *addr2 = PG_GETARG_MACADDR8_P(1); + macaddr8 *result; + + result = (macaddr8 *) palloc0(sizeof(macaddr8)); + result->a = addr1->a & addr2->a; + result->b = addr1->b & addr2->b; + result->c = addr1->c & addr2->c; + result->d = addr1->d & addr2->d; + result->e = addr1->e & addr2->e; + result->f = addr1->f & addr2->f; + result->g = addr1->g & addr2->g; + result->h = addr1->h & addr2->h; + + PG_RETURN_MACADDR8_P(result); +} + +Datum +macaddr8_or(PG_FUNCTION_ARGS) +{ + macaddr8 *addr1 = PG_GETARG_MACADDR8_P(0); + macaddr8 *addr2 = PG_GETARG_MACADDR8_P(1); + macaddr8 *result; + + result = (macaddr8 *) palloc0(sizeof(macaddr8)); + result->a = addr1->a | addr2->a; + result->b = addr1->b | addr2->b; + result->c = addr1->c | addr2->c; + result->d = addr1->d | addr2->d; + result->e = addr1->e | addr2->e; + result->f = addr1->f | addr2->f; + result->g = addr1->g | addr2->g; + result->h = addr1->h | addr2->h; + + PG_RETURN_MACADDR8_P(result); +} + +/* + * Truncation function to allow comparing macaddr8 manufacturers. + */ +Datum +macaddr8_trunc(PG_FUNCTION_ARGS) +{ + macaddr8 *addr = PG_GETARG_MACADDR8_P(0); + macaddr8 *result; + + result = (macaddr8 *) palloc0(sizeof(macaddr8)); + + result->a = addr->a; + result->b = addr->b; + result->c = addr->c; + result->d = 0; + result->e = 0; + result->f = 0; + result->g = 0; + result->h = 0; + + PG_RETURN_MACADDR8_P(result); +} + +/* + * Set 7th bit for modified EUI-64 as used in IPv6. + */ +Datum +macaddr8_set7bit(PG_FUNCTION_ARGS) +{ + macaddr8 *addr = PG_GETARG_MACADDR8_P(0); + macaddr8 *result; + + result = (macaddr8 *) palloc0(sizeof(macaddr8)); + + result->a = addr->a | 0x02; + result->b = addr->b; + result->c = addr->c; + result->d = addr->d; + result->e = addr->e; + result->f = addr->f; + result->g = addr->g; + result->h = addr->h; + + PG_RETURN_MACADDR8_P(result); +} + +/*---------------------------------------------------------- + * Conversion operators. + *---------------------------------------------------------*/ + +Datum +macaddrtomacaddr8(PG_FUNCTION_ARGS) +{ + macaddr *addr6 = PG_GETARG_MACADDR_P(0); + macaddr8 *result; + + result = (macaddr8 *) palloc0(sizeof(macaddr8)); + + result->a = addr6->a; + result->b = addr6->b; + result->c = addr6->c; + result->d = 0xFF; + result->e = 0xFE; + result->f = addr6->d; + result->g = addr6->e; + result->h = addr6->f; + + + PG_RETURN_MACADDR8_P(result); +} + +Datum +macaddr8tomacaddr(PG_FUNCTION_ARGS) +{ + macaddr8 *addr = PG_GETARG_MACADDR8_P(0); + macaddr *result; + + result = (macaddr *) palloc0(sizeof(macaddr)); + + if ((addr->d != 0xFF) || (addr->e != 0xFE)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("macaddr8 data out of range to convert to macaddr"), + errhint("Only addresses that have FF and FE as values in the " + "4th and 5th bytes from the left, for example " + "xx:xx:xx:ff:fe:xx:xx:xx, are eligible to be converted " + "from macaddr8 to macaddr."))); + + result->a = addr->a; + result->b = addr->b; + result->c = addr->c; + result->d = addr->f; + result->e = addr->g; + result->f = addr->h; + + PG_RETURN_MACADDR_P(result); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/mcxtfuncs.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/mcxtfuncs.c new file mode 100644 index 00000000000..92ca5b2f728 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/mcxtfuncs.c @@ -0,0 +1,195 @@ +/*------------------------------------------------------------------------- + * + * mcxtfuncs.c + * Functions to show backend memory context. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/adt/mcxtfuncs.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "funcapi.h" +#include "miscadmin.h" +#include "mb/pg_wchar.h" +#include "storage/proc.h" +#include "storage/procarray.h" +#include "utils/builtins.h" + +/* ---------- + * The max bytes for showing identifiers of MemoryContext. + * ---------- + */ +#define MEMORY_CONTEXT_IDENT_DISPLAY_SIZE 1024 + +/* + * PutMemoryContextsStatsTupleStore + * One recursion level for pg_get_backend_memory_contexts. + */ +static void +PutMemoryContextsStatsTupleStore(Tuplestorestate *tupstore, + TupleDesc tupdesc, MemoryContext context, + const char *parent, int level) +{ +#define PG_GET_BACKEND_MEMORY_CONTEXTS_COLS 9 + + Datum values[PG_GET_BACKEND_MEMORY_CONTEXTS_COLS]; + bool nulls[PG_GET_BACKEND_MEMORY_CONTEXTS_COLS]; + MemoryContextCounters stat; + MemoryContext child; + const char *name; + const char *ident; + + Assert(MemoryContextIsValid(context)); + + name = context->name; + ident = context->ident; + + /* + * To be consistent with logging output, we label dynahash contexts with + * just the hash table name as with MemoryContextStatsPrint(). + */ + if (ident && strcmp(name, "dynahash") == 0) + { + name = ident; + ident = NULL; + } + + /* Examine the context itself */ + memset(&stat, 0, sizeof(stat)); + (*context->methods->stats) (context, NULL, (void *) &level, &stat, true); + + memset(values, 0, sizeof(values)); + memset(nulls, 0, sizeof(nulls)); + + if (name) + values[0] = CStringGetTextDatum(name); + else + nulls[0] = true; + + if (ident) + { + int idlen = strlen(ident); + char clipped_ident[MEMORY_CONTEXT_IDENT_DISPLAY_SIZE]; + + /* + * Some identifiers such as SQL query string can be very long, + * truncate oversize identifiers. + */ + if (idlen >= MEMORY_CONTEXT_IDENT_DISPLAY_SIZE) + idlen = pg_mbcliplen(ident, idlen, MEMORY_CONTEXT_IDENT_DISPLAY_SIZE - 1); + + memcpy(clipped_ident, ident, idlen); + clipped_ident[idlen] = '\0'; + values[1] = CStringGetTextDatum(clipped_ident); + } + else + nulls[1] = true; + + if (parent) + values[2] = CStringGetTextDatum(parent); + else + nulls[2] = true; + + values[3] = Int32GetDatum(level); + values[4] = Int64GetDatum(stat.totalspace); + values[5] = Int64GetDatum(stat.nblocks); + values[6] = Int64GetDatum(stat.freespace); + values[7] = Int64GetDatum(stat.freechunks); + values[8] = Int64GetDatum(stat.totalspace - stat.freespace); + tuplestore_putvalues(tupstore, tupdesc, values, nulls); + + for (child = context->firstchild; child != NULL; child = child->nextchild) + { + PutMemoryContextsStatsTupleStore(tupstore, tupdesc, + child, name, level + 1); + } +} + +/* + * pg_get_backend_memory_contexts + * SQL SRF showing backend memory context. + */ +Datum +pg_get_backend_memory_contexts(PG_FUNCTION_ARGS) +{ + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + PutMemoryContextsStatsTupleStore(rsinfo->setResult, rsinfo->setDesc, + TopMemoryContext, NULL, 0); + + return (Datum) 0; +} + +/* + * pg_log_backend_memory_contexts + * Signal a backend or an auxiliary process to log its memory contexts. + * + * By default, only superusers are allowed to signal to log the memory + * contexts because allowing any users to issue this request at an unbounded + * rate would cause lots of log messages and which can lead to denial of + * service. Additional roles can be permitted with GRANT. + * + * On receipt of this signal, a backend or an auxiliary process sets the flag + * in the signal handler, which causes the next CHECK_FOR_INTERRUPTS() + * or process-specific interrupt handler to log the memory contexts. + */ +Datum +pg_log_backend_memory_contexts(PG_FUNCTION_ARGS) +{ + int pid = PG_GETARG_INT32(0); + PGPROC *proc; + BackendId backendId = InvalidBackendId; + + proc = BackendPidGetProc(pid); + + /* + * See if the process with given pid is a backend or an auxiliary process. + * + * If the given process is a backend, use its backend id in + * SendProcSignal() later to speed up the operation. Otherwise, don't do + * that because auxiliary processes (except the startup process) don't + * have a valid backend id. + */ + if (proc != NULL) + backendId = proc->backendId; + else + proc = AuxiliaryPidGetProc(pid); + + /* + * BackendPidGetProc() and AuxiliaryPidGetProc() return NULL if the pid + * isn't valid; but by the time we reach kill(), a process for which we + * get a valid proc here might have terminated on its own. There's no way + * to acquire a lock on an arbitrary process to prevent that. But since + * this mechanism is usually used to debug a backend or an auxiliary + * process running and consuming lots of memory, that it might end on its + * own first and its memory contexts are not logged is not a problem. + */ + if (proc == NULL) + { + /* + * This is just a warning so a loop-through-resultset will not abort + * if one backend terminated on its own during the run. + */ + ereport(WARNING, + (errmsg("PID %d is not a PostgreSQL server process", pid))); + PG_RETURN_BOOL(false); + } + + if (SendProcSignal(pid, PROCSIG_LOG_MEMORY_CONTEXT, backendId) < 0) + { + /* Again, just a warning to allow loops */ + ereport(WARNING, + (errmsg("could not send signal to process %d: %m", pid))); + PG_RETURN_BOOL(false); + } + + PG_RETURN_BOOL(true); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/misc.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/misc.c new file mode 100644 index 00000000000..f94abc14b02 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/misc.c @@ -0,0 +1,1080 @@ +/*------------------------------------------------------------------------- + * + * misc.c + * + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/adt/misc.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <sys/file.h> +#include <sys/stat.h> +#include <dirent.h> +#include <fcntl.h> +#include <math.h> +#include <unistd.h> + +#include "access/sysattr.h" +#include "access/table.h" +#include "catalog/catalog.h" +#include "catalog/pg_tablespace.h" +#include "catalog/pg_type.h" +#include "catalog/system_fk_info.h" +#include "commands/dbcommands.h" +#include "commands/tablespace.h" +#include "common/keywords.h" +#include "funcapi.h" +#include "miscadmin.h" +#include "nodes/miscnodes.h" +#include "parser/parse_type.h" +#include "parser/scansup.h" +#include "pgstat.h" +#include "postmaster/syslogger.h" +#include "rewrite/rewriteHandler.h" +#include "storage/fd.h" +#include "storage/latch.h" +#include "tcop/tcopprot.h" +#include "utils/builtins.h" +#include "utils/fmgroids.h" +#include "utils/lsyscache.h" +#include "utils/ruleutils.h" +#include "utils/timestamp.h" + + +/* + * structure to cache metadata needed in pg_input_is_valid_common + */ +typedef struct ValidIOData +{ + Oid typoid; + int32 typmod; + bool typname_constant; + Oid typiofunc; + Oid typioparam; + FmgrInfo inputproc; +} ValidIOData; + +static bool pg_input_is_valid_common(FunctionCallInfo fcinfo, + text *txt, text *typname, + ErrorSaveContext *escontext); + + +/* + * Common subroutine for num_nulls() and num_nonnulls(). + * Returns true if successful, false if function should return NULL. + * If successful, total argument count and number of nulls are + * returned into *nargs and *nulls. + */ +static bool +count_nulls(FunctionCallInfo fcinfo, + int32 *nargs, int32 *nulls) +{ + int32 count = 0; + int i; + + /* Did we get a VARIADIC array argument, or separate arguments? */ + if (get_fn_expr_variadic(fcinfo->flinfo)) + { + ArrayType *arr; + int ndims, + nitems, + *dims; + bits8 *bitmap; + + Assert(PG_NARGS() == 1); + + /* + * If we get a null as VARIADIC array argument, we can't say anything + * useful about the number of elements, so return NULL. This behavior + * is consistent with other variadic functions - see concat_internal. + */ + if (PG_ARGISNULL(0)) + return false; + + /* + * Non-null argument had better be an array. We assume that any call + * context that could let get_fn_expr_variadic return true will have + * checked that a VARIADIC-labeled parameter actually is an array. So + * it should be okay to just Assert that it's an array rather than + * doing a full-fledged error check. + */ + Assert(OidIsValid(get_base_element_type(get_fn_expr_argtype(fcinfo->flinfo, 0)))); + + /* OK, safe to fetch the array value */ + arr = PG_GETARG_ARRAYTYPE_P(0); + + /* Count the array elements */ + ndims = ARR_NDIM(arr); + dims = ARR_DIMS(arr); + nitems = ArrayGetNItems(ndims, dims); + + /* Count those that are NULL */ + bitmap = ARR_NULLBITMAP(arr); + if (bitmap) + { + int bitmask = 1; + + for (i = 0; i < nitems; i++) + { + if ((*bitmap & bitmask) == 0) + count++; + + bitmask <<= 1; + if (bitmask == 0x100) + { + bitmap++; + bitmask = 1; + } + } + } + + *nargs = nitems; + *nulls = count; + } + else + { + /* Separate arguments, so just count 'em */ + for (i = 0; i < PG_NARGS(); i++) + { + if (PG_ARGISNULL(i)) + count++; + } + + *nargs = PG_NARGS(); + *nulls = count; + } + + return true; +} + +/* + * num_nulls() + * Count the number of NULL arguments + */ +Datum +pg_num_nulls(PG_FUNCTION_ARGS) +{ + int32 nargs, + nulls; + + if (!count_nulls(fcinfo, &nargs, &nulls)) + PG_RETURN_NULL(); + + PG_RETURN_INT32(nulls); +} + +/* + * num_nonnulls() + * Count the number of non-NULL arguments + */ +Datum +pg_num_nonnulls(PG_FUNCTION_ARGS) +{ + int32 nargs, + nulls; + + if (!count_nulls(fcinfo, &nargs, &nulls)) + PG_RETURN_NULL(); + + PG_RETURN_INT32(nargs - nulls); +} + + +/* + * current_database() + * Expose the current database to the user + */ +Datum +current_database(PG_FUNCTION_ARGS) +{ + Name db; + + db = (Name) palloc(NAMEDATALEN); + + namestrcpy(db, get_database_name(MyDatabaseId)); + PG_RETURN_NAME(db); +} + + +/* + * current_query() + * Expose the current query to the user (useful in stored procedures) + * We might want to use ActivePortal->sourceText someday. + */ +Datum +current_query(PG_FUNCTION_ARGS) +{ + /* there is no easy way to access the more concise 'query_string' */ + if (debug_query_string) + PG_RETURN_TEXT_P(cstring_to_text(debug_query_string)); + else + PG_RETURN_NULL(); +} + +/* Function to find out which databases make use of a tablespace */ + +Datum +pg_tablespace_databases(PG_FUNCTION_ARGS) +{ + Oid tablespaceOid = PG_GETARG_OID(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + char *location; + DIR *dirdesc; + struct dirent *de; + + InitMaterializedSRF(fcinfo, MAT_SRF_USE_EXPECTED_DESC); + + if (tablespaceOid == GLOBALTABLESPACE_OID) + { + ereport(WARNING, + (errmsg("global tablespace never has databases"))); + /* return empty tuplestore */ + return (Datum) 0; + } + + if (tablespaceOid == DEFAULTTABLESPACE_OID) + location = "base"; + else + location = psprintf("pg_tblspc/%u/%s", tablespaceOid, + TABLESPACE_VERSION_DIRECTORY); + + dirdesc = AllocateDir(location); + + if (!dirdesc) + { + /* the only expected error is ENOENT */ + if (errno != ENOENT) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not open directory \"%s\": %m", + location))); + ereport(WARNING, + (errmsg("%u is not a tablespace OID", tablespaceOid))); + /* return empty tuplestore */ + return (Datum) 0; + } + + while ((de = ReadDir(dirdesc, location)) != NULL) + { + Oid datOid = atooid(de->d_name); + char *subdir; + bool isempty; + Datum values[1]; + bool nulls[1]; + + /* this test skips . and .., but is awfully weak */ + if (!datOid) + continue; + + /* if database subdir is empty, don't report tablespace as used */ + + subdir = psprintf("%s/%s", location, de->d_name); + isempty = directory_is_empty(subdir); + pfree(subdir); + + if (isempty) + continue; /* indeed, nothing in it */ + + values[0] = ObjectIdGetDatum(datOid); + nulls[0] = false; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, + values, nulls); + } + + FreeDir(dirdesc); + return (Datum) 0; +} + + +/* + * pg_tablespace_location - get location for a tablespace + */ +Datum +pg_tablespace_location(PG_FUNCTION_ARGS) +{ + Oid tablespaceOid = PG_GETARG_OID(0); + char sourcepath[MAXPGPATH]; + char targetpath[MAXPGPATH]; + int rllen; + struct stat st; + + /* + * It's useful to apply this function to pg_class.reltablespace, wherein + * zero means "the database's default tablespace". So, rather than + * throwing an error for zero, we choose to assume that's what is meant. + */ + if (tablespaceOid == InvalidOid) + tablespaceOid = MyDatabaseTableSpace; + + /* + * Return empty string for the cluster's default tablespaces + */ + if (tablespaceOid == DEFAULTTABLESPACE_OID || + tablespaceOid == GLOBALTABLESPACE_OID) + PG_RETURN_TEXT_P(cstring_to_text("")); + + /* + * Find the location of the tablespace by reading the symbolic link that + * is in pg_tblspc/<oid>. + */ + snprintf(sourcepath, sizeof(sourcepath), "pg_tblspc/%u", tablespaceOid); + + /* + * Before reading the link, check if the source path is a link or a + * junction point. Note that a directory is possible for a tablespace + * created with allow_in_place_tablespaces enabled. If a directory is + * found, a relative path to the data directory is returned. + */ + if (lstat(sourcepath, &st) < 0) + { + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not stat file \"%s\": %m", + sourcepath))); + } + + if (!S_ISLNK(st.st_mode)) + PG_RETURN_TEXT_P(cstring_to_text(sourcepath)); + + /* + * In presence of a link or a junction point, return the path pointing to. + */ + rllen = readlink(sourcepath, targetpath, sizeof(targetpath)); + if (rllen < 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not read symbolic link \"%s\": %m", + sourcepath))); + if (rllen >= sizeof(targetpath)) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("symbolic link \"%s\" target is too long", + sourcepath))); + targetpath[rllen] = '\0'; + + PG_RETURN_TEXT_P(cstring_to_text(targetpath)); +} + +/* + * pg_sleep - delay for N seconds + */ +Datum +pg_sleep(PG_FUNCTION_ARGS) +{ + float8 secs = PG_GETARG_FLOAT8(0); + float8 endtime; + + /* + * We sleep using WaitLatch, to ensure that we'll wake up promptly if an + * important signal (such as SIGALRM or SIGINT) arrives. Because + * WaitLatch's upper limit of delay is INT_MAX milliseconds, and the user + * might ask for more than that, we sleep for at most 10 minutes and then + * loop. + * + * By computing the intended stop time initially, we avoid accumulation of + * extra delay across multiple sleeps. This also ensures we won't delay + * less than the specified time when WaitLatch is terminated early by a + * non-query-canceling signal such as SIGHUP. + */ +#define GetNowFloat() ((float8) GetCurrentTimestamp() / 1000000.0) + + endtime = GetNowFloat() + secs; + + for (;;) + { + float8 delay; + long delay_ms; + + CHECK_FOR_INTERRUPTS(); + + delay = endtime - GetNowFloat(); + if (delay >= 600.0) + delay_ms = 600000; + else if (delay > 0.0) + delay_ms = (long) ceil(delay * 1000.0); + else + break; + + (void) WaitLatch(MyLatch, + WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH, + delay_ms, + WAIT_EVENT_PG_SLEEP); + ResetLatch(MyLatch); + } + + PG_RETURN_VOID(); +} + +/* Function to return the list of grammar keywords */ +Datum +pg_get_keywords(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + + if (SRF_IS_FIRSTCALL()) + { + MemoryContext oldcontext; + TupleDesc tupdesc; + + funcctx = SRF_FIRSTCALL_INIT(); + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + funcctx->tuple_desc = tupdesc; + funcctx->attinmeta = TupleDescGetAttInMetadata(tupdesc); + + MemoryContextSwitchTo(oldcontext); + } + + funcctx = SRF_PERCALL_SETUP(); + + if (funcctx->call_cntr < ScanKeywords.num_keywords) + { + char *values[5]; + HeapTuple tuple; + + /* cast-away-const is ugly but alternatives aren't much better */ + values[0] = unconstify(char *, + GetScanKeyword(funcctx->call_cntr, + &ScanKeywords)); + + switch (ScanKeywordCategories[funcctx->call_cntr]) + { + case UNRESERVED_KEYWORD: + values[1] = "U"; + values[3] = _("unreserved"); + break; + case COL_NAME_KEYWORD: + values[1] = "C"; + values[3] = _("unreserved (cannot be function or type name)"); + break; + case TYPE_FUNC_NAME_KEYWORD: + values[1] = "T"; + values[3] = _("reserved (can be function or type name)"); + break; + case RESERVED_KEYWORD: + values[1] = "R"; + values[3] = _("reserved"); + break; + default: /* shouldn't be possible */ + values[1] = NULL; + values[3] = NULL; + break; + } + + if (ScanKeywordBareLabel[funcctx->call_cntr]) + { + values[2] = "true"; + values[4] = _("can be bare label"); + } + else + { + values[2] = "false"; + values[4] = _("requires AS"); + } + + tuple = BuildTupleFromCStrings(funcctx->attinmeta, values); + + SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple)); + } + + SRF_RETURN_DONE(funcctx); +} + + +/* Function to return the list of catalog foreign key relationships */ +Datum +pg_get_catalog_foreign_keys(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + FmgrInfo *arrayinp; + + if (SRF_IS_FIRSTCALL()) + { + MemoryContext oldcontext; + TupleDesc tupdesc; + + funcctx = SRF_FIRSTCALL_INIT(); + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + funcctx->tuple_desc = BlessTupleDesc(tupdesc); + + /* + * We use array_in to convert the C strings in sys_fk_relationships[] + * to text arrays. But we cannot use DirectFunctionCallN to call + * array_in, and it wouldn't be very efficient if we could. Fill an + * FmgrInfo to use for the call. + */ + arrayinp = (FmgrInfo *) palloc(sizeof(FmgrInfo)); + fmgr_info(F_ARRAY_IN, arrayinp); + funcctx->user_fctx = arrayinp; + + MemoryContextSwitchTo(oldcontext); + } + + funcctx = SRF_PERCALL_SETUP(); + arrayinp = (FmgrInfo *) funcctx->user_fctx; + + if (funcctx->call_cntr < lengthof(sys_fk_relationships)) + { + const SysFKRelationship *fkrel = &sys_fk_relationships[funcctx->call_cntr]; + Datum values[6]; + bool nulls[6]; + HeapTuple tuple; + + memset(nulls, false, sizeof(nulls)); + + values[0] = ObjectIdGetDatum(fkrel->fk_table); + values[1] = FunctionCall3(arrayinp, + CStringGetDatum(fkrel->fk_columns), + ObjectIdGetDatum(TEXTOID), + Int32GetDatum(-1)); + values[2] = ObjectIdGetDatum(fkrel->pk_table); + values[3] = FunctionCall3(arrayinp, + CStringGetDatum(fkrel->pk_columns), + ObjectIdGetDatum(TEXTOID), + Int32GetDatum(-1)); + values[4] = BoolGetDatum(fkrel->is_array); + values[5] = BoolGetDatum(fkrel->is_opt); + + tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); + + SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple)); + } + + SRF_RETURN_DONE(funcctx); +} + + +/* + * Return the type of the argument. + */ +Datum +pg_typeof(PG_FUNCTION_ARGS) +{ + PG_RETURN_OID(get_fn_expr_argtype(fcinfo->flinfo, 0)); +} + + +/* + * Implementation of the COLLATE FOR expression; returns the collation + * of the argument. + */ +Datum +pg_collation_for(PG_FUNCTION_ARGS) +{ + Oid typeid; + Oid collid; + + typeid = get_fn_expr_argtype(fcinfo->flinfo, 0); + if (!typeid) + PG_RETURN_NULL(); + if (!type_is_collatable(typeid) && typeid != UNKNOWNOID) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("collations are not supported by type %s", + format_type_be(typeid)))); + + collid = PG_GET_COLLATION(); + if (!collid) + PG_RETURN_NULL(); + PG_RETURN_TEXT_P(cstring_to_text(generate_collation_name(collid))); +} + + +/* + * pg_relation_is_updatable - determine which update events the specified + * relation supports. + * + * This relies on relation_is_updatable() in rewriteHandler.c, which see + * for additional information. + */ +Datum +pg_relation_is_updatable(PG_FUNCTION_ARGS) +{ + Oid reloid = PG_GETARG_OID(0); + bool include_triggers = PG_GETARG_BOOL(1); + + PG_RETURN_INT32(relation_is_updatable(reloid, NIL, include_triggers, NULL)); +} + +/* + * pg_column_is_updatable - determine whether a column is updatable + * + * This function encapsulates the decision about just what + * information_schema.columns.is_updatable actually means. It's not clear + * whether deletability of the column's relation should be required, so + * we want that decision in C code where we could change it without initdb. + */ +Datum +pg_column_is_updatable(PG_FUNCTION_ARGS) +{ + Oid reloid = PG_GETARG_OID(0); + AttrNumber attnum = PG_GETARG_INT16(1); + AttrNumber col = attnum - FirstLowInvalidHeapAttributeNumber; + bool include_triggers = PG_GETARG_BOOL(2); + int events; + + /* System columns are never updatable */ + if (attnum <= 0) + PG_RETURN_BOOL(false); + + events = relation_is_updatable(reloid, NIL, include_triggers, + bms_make_singleton(col)); + + /* We require both updatability and deletability of the relation */ +#define REQ_EVENTS ((1 << CMD_UPDATE) | (1 << CMD_DELETE)) + + PG_RETURN_BOOL((events & REQ_EVENTS) == REQ_EVENTS); +} + + +/* + * pg_input_is_valid - test whether string is valid input for datatype. + * + * Returns true if OK, false if not. + * + * This will only work usefully if the datatype's input function has been + * updated to return "soft" errors via errsave/ereturn. + */ +Datum +pg_input_is_valid(PG_FUNCTION_ARGS) +{ + text *txt = PG_GETARG_TEXT_PP(0); + text *typname = PG_GETARG_TEXT_PP(1); + ErrorSaveContext escontext = {T_ErrorSaveContext}; + + PG_RETURN_BOOL(pg_input_is_valid_common(fcinfo, txt, typname, + &escontext)); +} + +/* + * pg_input_error_info - test whether string is valid input for datatype. + * + * Returns NULL if OK, else the primary message, detail message, hint message + * and sql error code from the error. + * + * This will only work usefully if the datatype's input function has been + * updated to return "soft" errors via errsave/ereturn. + */ +Datum +pg_input_error_info(PG_FUNCTION_ARGS) +{ + text *txt = PG_GETARG_TEXT_PP(0); + text *typname = PG_GETARG_TEXT_PP(1); + ErrorSaveContext escontext = {T_ErrorSaveContext}; + TupleDesc tupdesc; + Datum values[4]; + bool isnull[4]; + + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + + /* Enable details_wanted */ + escontext.details_wanted = true; + + if (pg_input_is_valid_common(fcinfo, txt, typname, + &escontext)) + memset(isnull, true, sizeof(isnull)); + else + { + char *sqlstate; + + Assert(escontext.error_occurred); + Assert(escontext.error_data != NULL); + Assert(escontext.error_data->message != NULL); + + memset(isnull, false, sizeof(isnull)); + + values[0] = CStringGetTextDatum(escontext.error_data->message); + + if (escontext.error_data->detail != NULL) + values[1] = CStringGetTextDatum(escontext.error_data->detail); + else + isnull[1] = true; + + if (escontext.error_data->hint != NULL) + values[2] = CStringGetTextDatum(escontext.error_data->hint); + else + isnull[2] = true; + + sqlstate = unpack_sql_state(escontext.error_data->sqlerrcode); + values[3] = CStringGetTextDatum(sqlstate); + } + + return HeapTupleGetDatum(heap_form_tuple(tupdesc, values, isnull)); +} + +/* Common subroutine for the above */ +static bool +pg_input_is_valid_common(FunctionCallInfo fcinfo, + text *txt, text *typname, + ErrorSaveContext *escontext) +{ + char *str = text_to_cstring(txt); + ValidIOData *my_extra; + Datum converted; + + /* + * We arrange to look up the needed I/O info just once per series of + * calls, assuming the data type doesn't change underneath us. + */ + my_extra = (ValidIOData *) fcinfo->flinfo->fn_extra; + if (my_extra == NULL) + { + fcinfo->flinfo->fn_extra = + MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, + sizeof(ValidIOData)); + my_extra = (ValidIOData *) fcinfo->flinfo->fn_extra; + my_extra->typoid = InvalidOid; + /* Detect whether typname argument is constant. */ + my_extra->typname_constant = get_fn_expr_arg_stable(fcinfo->flinfo, 1); + } + + /* + * If the typname argument is constant, we only need to parse it the first + * time through. + */ + if (my_extra->typoid == InvalidOid || !my_extra->typname_constant) + { + char *typnamestr = text_to_cstring(typname); + Oid typoid; + + /* Parse type-name argument to obtain type OID and encoded typmod. */ + (void) parseTypeString(typnamestr, &typoid, &my_extra->typmod, NULL); + + /* Update type-specific info if typoid changed. */ + if (my_extra->typoid != typoid) + { + getTypeInputInfo(typoid, + &my_extra->typiofunc, + &my_extra->typioparam); + fmgr_info_cxt(my_extra->typiofunc, &my_extra->inputproc, + fcinfo->flinfo->fn_mcxt); + my_extra->typoid = typoid; + } + } + + /* Now we can try to perform the conversion. */ + return InputFunctionCallSafe(&my_extra->inputproc, + str, + my_extra->typioparam, + my_extra->typmod, + (Node *) escontext, + &converted); +} + + +/* + * Is character a valid identifier start? + * Must match scan.l's {ident_start} character class. + */ +static bool +is_ident_start(unsigned char c) +{ + /* Underscores and ASCII letters are OK */ + if (c == '_') + return true; + if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) + return true; + /* Any high-bit-set character is OK (might be part of a multibyte char) */ + if (IS_HIGHBIT_SET(c)) + return true; + return false; +} + +/* + * Is character a valid identifier continuation? + * Must match scan.l's {ident_cont} character class. + */ +static bool +is_ident_cont(unsigned char c) +{ + /* Can be digit or dollar sign ... */ + if ((c >= '0' && c <= '9') || c == '$') + return true; + /* ... or an identifier start character */ + return is_ident_start(c); +} + +/* + * parse_ident - parse a SQL qualified identifier into separate identifiers. + * When strict mode is active (second parameter), then any chars after + * the last identifier are disallowed. + */ +Datum +parse_ident(PG_FUNCTION_ARGS) +{ + text *qualname = PG_GETARG_TEXT_PP(0); + bool strict = PG_GETARG_BOOL(1); + char *qualname_str = text_to_cstring(qualname); + ArrayBuildState *astate = NULL; + char *nextp; + bool after_dot = false; + + /* + * The code below scribbles on qualname_str in some cases, so we should + * reconvert qualname if we need to show the original string in error + * messages. + */ + nextp = qualname_str; + + /* skip leading whitespace */ + while (scanner_isspace(*nextp)) + nextp++; + + for (;;) + { + char *curname; + bool missing_ident = true; + + if (*nextp == '"') + { + char *endp; + + curname = nextp + 1; + for (;;) + { + endp = strchr(nextp + 1, '"'); + if (endp == NULL) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("string is not a valid identifier: \"%s\"", + text_to_cstring(qualname)), + errdetail("String has unclosed double quotes."))); + if (endp[1] != '"') + break; + memmove(endp, endp + 1, strlen(endp)); + nextp = endp; + } + nextp = endp + 1; + *endp = '\0'; + + if (endp - curname == 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("string is not a valid identifier: \"%s\"", + text_to_cstring(qualname)), + errdetail("Quoted identifier must not be empty."))); + + astate = accumArrayResult(astate, CStringGetTextDatum(curname), + false, TEXTOID, CurrentMemoryContext); + missing_ident = false; + } + else if (is_ident_start((unsigned char) *nextp)) + { + char *downname; + int len; + text *part; + + curname = nextp++; + while (is_ident_cont((unsigned char) *nextp)) + nextp++; + + len = nextp - curname; + + /* + * We don't implicitly truncate identifiers. This is useful for + * allowing the user to check for specific parts of the identifier + * being too long. It's easy enough for the user to get the + * truncated names by casting our output to name[]. + */ + downname = copy_identifier(curname, len); + part = cstring_to_text_with_len(downname, len); + astate = accumArrayResult(astate, PointerGetDatum(part), false, + TEXTOID, CurrentMemoryContext); + missing_ident = false; + } + + if (missing_ident) + { + /* Different error messages based on where we failed. */ + if (*nextp == '.') + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("string is not a valid identifier: \"%s\"", + text_to_cstring(qualname)), + errdetail("No valid identifier before \".\"."))); + else if (after_dot) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("string is not a valid identifier: \"%s\"", + text_to_cstring(qualname)), + errdetail("No valid identifier after \".\"."))); + else + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("string is not a valid identifier: \"%s\"", + text_to_cstring(qualname)))); + } + + while (scanner_isspace(*nextp)) + nextp++; + + if (*nextp == '.') + { + after_dot = true; + nextp++; + while (scanner_isspace(*nextp)) + nextp++; + } + else if (*nextp == '\0') + { + break; + } + else + { + if (strict) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("string is not a valid identifier: \"%s\"", + text_to_cstring(qualname)))); + break; + } + } + + PG_RETURN_DATUM(makeArrayResult(astate, CurrentMemoryContext)); +} + +/* + * pg_current_logfile + * + * Report current log file used by log collector by scanning current_logfiles. + */ +Datum +pg_current_logfile(PG_FUNCTION_ARGS) +{ + FILE *fd; + char lbuffer[MAXPGPATH]; + char *logfmt; + + /* The log format parameter is optional */ + if (PG_NARGS() == 0 || PG_ARGISNULL(0)) + logfmt = NULL; + else + { + logfmt = text_to_cstring(PG_GETARG_TEXT_PP(0)); + + if (strcmp(logfmt, "stderr") != 0 && + strcmp(logfmt, "csvlog") != 0 && + strcmp(logfmt, "jsonlog") != 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("log format \"%s\" is not supported", logfmt), + errhint("The supported log formats are \"stderr\", \"csvlog\", and \"jsonlog\"."))); + } + + fd = AllocateFile(LOG_METAINFO_DATAFILE, "r"); + if (fd == NULL) + { + if (errno != ENOENT) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not read file \"%s\": %m", + LOG_METAINFO_DATAFILE))); + PG_RETURN_NULL(); + } + +#ifdef WIN32 + /* syslogger.c writes CRLF line endings on Windows */ + _setmode(_fileno(fd), _O_TEXT); +#endif + + /* + * Read the file to gather current log filename(s) registered by the + * syslogger. + */ + while (fgets(lbuffer, sizeof(lbuffer), fd) != NULL) + { + char *log_format; + char *log_filepath; + char *nlpos; + + /* Extract log format and log file path from the line. */ + log_format = lbuffer; + log_filepath = strchr(lbuffer, ' '); + if (log_filepath == NULL) + { + /* Uh oh. No space found, so file content is corrupted. */ + elog(ERROR, + "missing space character in \"%s\"", LOG_METAINFO_DATAFILE); + break; + } + + *log_filepath = '\0'; + log_filepath++; + nlpos = strchr(log_filepath, '\n'); + if (nlpos == NULL) + { + /* Uh oh. No newline found, so file content is corrupted. */ + elog(ERROR, + "missing newline character in \"%s\"", LOG_METAINFO_DATAFILE); + break; + } + *nlpos = '\0'; + + if (logfmt == NULL || strcmp(logfmt, log_format) == 0) + { + FreeFile(fd); + PG_RETURN_TEXT_P(cstring_to_text(log_filepath)); + } + } + + /* Close the current log filename file. */ + FreeFile(fd); + + PG_RETURN_NULL(); +} + +/* + * Report current log file used by log collector (1 argument version) + * + * note: this wrapper is necessary to pass the sanity check in opr_sanity, + * which checks that all built-in functions that share the implementing C + * function take the same number of arguments + */ +Datum +pg_current_logfile_1arg(PG_FUNCTION_ARGS) +{ + return pg_current_logfile(fcinfo); +} + +/* + * SQL wrapper around RelationGetReplicaIndex(). + */ +Datum +pg_get_replica_identity_index(PG_FUNCTION_ARGS) +{ + Oid reloid = PG_GETARG_OID(0); + Oid idxoid; + Relation rel; + + rel = table_open(reloid, AccessShareLock); + idxoid = RelationGetReplicaIndex(rel); + table_close(rel, AccessShareLock); + + if (OidIsValid(idxoid)) + PG_RETURN_OID(idxoid); + else + PG_RETURN_NULL(); +} + +/* + * Transition function for the ANY_VALUE aggregate + */ +Datum +any_value_transfn(PG_FUNCTION_ARGS) +{ + PG_RETURN_DATUM(PG_GETARG_DATUM(0)); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/multirangetypes.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/multirangetypes.c new file mode 100644 index 00000000000..9443c2b884a --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/multirangetypes.c @@ -0,0 +1,2927 @@ +/*------------------------------------------------------------------------- + * + * multirangetypes.c + * I/O functions, operators, and support functions for multirange types. + * + * The stored (serialized) format of a multirange value is: + * + * 12 bytes: MultirangeType struct including varlena header, multirange + * type's OID and the number of ranges in the multirange. + * 4 * (rangesCount - 1) bytes: 32-bit items pointing to the each range + * in the multirange starting from + * the second one. + * 1 * rangesCount bytes : 8-bit flags for each range in the multirange + * The rest of the multirange are range bound values pointed by multirange + * items. + * + * Majority of items contain lengths of corresponding range bound values. + * Thanks to that items are typically low numbers. This makes multiranges + * compression-friendly. Every MULTIRANGE_ITEM_OFFSET_STRIDE item contains + * an offset of the corresponding range bound values. That allows fast lookups + * for a particular range index. Offsets are counted starting from the end of + * flags aligned to the bound type. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/adt/multirangetypes.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/tupmacs.h" +#include "common/hashfn.h" +#include "funcapi.h" +#include "lib/stringinfo.h" +#include "libpq/pqformat.h" +#include "miscadmin.h" +#include "port/pg_bitutils.h" +#include "utils/builtins.h" +#include "utils/lsyscache.h" +#include "utils/rangetypes.h" +#include "utils/multirangetypes.h" +#include "utils/array.h" +#include "utils/memutils.h" + +/* fn_extra cache entry for one of the range I/O functions */ +typedef struct MultirangeIOData +{ + TypeCacheEntry *typcache; /* multirange type's typcache entry */ + FmgrInfo typioproc; /* range type's I/O proc */ + Oid typioparam; /* range type's I/O parameter */ +} MultirangeIOData; + +typedef enum +{ + MULTIRANGE_BEFORE_RANGE, + MULTIRANGE_IN_RANGE, + MULTIRANGE_IN_RANGE_ESCAPED, + MULTIRANGE_IN_RANGE_QUOTED, + MULTIRANGE_IN_RANGE_QUOTED_ESCAPED, + MULTIRANGE_AFTER_RANGE, + MULTIRANGE_FINISHED, +} MultirangeParseState; + +/* + * Macros for accessing past MultirangeType parts of multirange: items, flags + * and boundaries. + */ +#define MultirangeGetItemsPtr(mr) ((uint32 *) ((Pointer) (mr) + \ + sizeof(MultirangeType))) +#define MultirangeGetFlagsPtr(mr) ((uint8 *) ((Pointer) (mr) + \ + sizeof(MultirangeType) + ((mr)->rangeCount - 1) * sizeof(uint32))) +#define MultirangeGetBoundariesPtr(mr, align) ((Pointer) (mr) + \ + att_align_nominal(sizeof(MultirangeType) + \ + ((mr)->rangeCount - 1) * sizeof(uint32) + \ + (mr)->rangeCount * sizeof(uint8), (align))) + +#define MULTIRANGE_ITEM_OFF_BIT 0x80000000 +#define MULTIRANGE_ITEM_GET_OFFLEN(item) ((item) & 0x7FFFFFFF) +#define MULTIRANGE_ITEM_HAS_OFF(item) ((item) & MULTIRANGE_ITEM_OFF_BIT) +#define MULTIRANGE_ITEM_OFFSET_STRIDE 4 + +typedef int (*multirange_bsearch_comparison) (TypeCacheEntry *typcache, + RangeBound *lower, + RangeBound *upper, + void *key, + bool *match); + +static MultirangeIOData *get_multirange_io_data(FunctionCallInfo fcinfo, + Oid mltrngtypid, + IOFuncSelector func); +static int32 multirange_canonicalize(TypeCacheEntry *rangetyp, + int32 input_range_count, + RangeType **ranges); + +/* + *---------------------------------------------------------- + * I/O FUNCTIONS + *---------------------------------------------------------- + */ + +/* + * Converts string to multirange. + * + * We expect curly brackets to bound the list, with zero or more ranges + * separated by commas. We accept whitespace anywhere: before/after our + * brackets and around the commas. Ranges can be the empty literal or some + * stuff inside parens/brackets. Mostly we delegate parsing the individual + * range contents to range_in, but we have to detect quoting and + * backslash-escaping which can happen for range bounds. Backslashes can + * escape something inside or outside a quoted string, and a quoted string + * can escape quote marks with either backslashes or double double-quotes. + */ +Datum +multirange_in(PG_FUNCTION_ARGS) +{ + char *input_str = PG_GETARG_CSTRING(0); + Oid mltrngtypoid = PG_GETARG_OID(1); + Oid typmod = PG_GETARG_INT32(2); + Node *escontext = fcinfo->context; + TypeCacheEntry *rangetyp; + int32 ranges_seen = 0; + int32 range_count = 0; + int32 range_capacity = 8; + RangeType *range; + RangeType **ranges = palloc(range_capacity * sizeof(RangeType *)); + MultirangeIOData *cache; + MultirangeType *ret; + MultirangeParseState parse_state; + const char *ptr = input_str; + const char *range_str_begin = NULL; + int32 range_str_len; + char *range_str; + Datum range_datum; + + cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_input); + rangetyp = cache->typcache->rngtype; + + /* consume whitespace */ + while (*ptr != '\0' && isspace((unsigned char) *ptr)) + ptr++; + + if (*ptr == '{') + ptr++; + else + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed multirange literal: \"%s\"", + input_str), + errdetail("Missing left brace."))); + + /* consume ranges */ + parse_state = MULTIRANGE_BEFORE_RANGE; + for (; parse_state != MULTIRANGE_FINISHED; ptr++) + { + char ch = *ptr; + + if (ch == '\0') + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed multirange literal: \"%s\"", + input_str), + errdetail("Unexpected end of input."))); + + /* skip whitespace */ + if (isspace((unsigned char) ch)) + continue; + + switch (parse_state) + { + case MULTIRANGE_BEFORE_RANGE: + if (ch == '[' || ch == '(') + { + range_str_begin = ptr; + parse_state = MULTIRANGE_IN_RANGE; + } + else if (ch == '}' && ranges_seen == 0) + parse_state = MULTIRANGE_FINISHED; + else if (pg_strncasecmp(ptr, RANGE_EMPTY_LITERAL, + strlen(RANGE_EMPTY_LITERAL)) == 0) + { + ranges_seen++; + /* nothing to do with an empty range */ + ptr += strlen(RANGE_EMPTY_LITERAL) - 1; + parse_state = MULTIRANGE_AFTER_RANGE; + } + else + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed multirange literal: \"%s\"", + input_str), + errdetail("Expected range start."))); + break; + case MULTIRANGE_IN_RANGE: + if (ch == ']' || ch == ')') + { + range_str_len = ptr - range_str_begin + 1; + range_str = pnstrdup(range_str_begin, range_str_len); + if (range_capacity == range_count) + { + range_capacity *= 2; + ranges = (RangeType **) + repalloc(ranges, range_capacity * sizeof(RangeType *)); + } + ranges_seen++; + if (!InputFunctionCallSafe(&cache->typioproc, + range_str, + cache->typioparam, + typmod, + escontext, + &range_datum)) + PG_RETURN_NULL(); + range = DatumGetRangeTypeP(range_datum); + if (!RangeIsEmpty(range)) + ranges[range_count++] = range; + parse_state = MULTIRANGE_AFTER_RANGE; + } + else + { + if (ch == '"') + parse_state = MULTIRANGE_IN_RANGE_QUOTED; + else if (ch == '\\') + parse_state = MULTIRANGE_IN_RANGE_ESCAPED; + + /* + * We will include this character into range_str once we + * find the end of the range value. + */ + } + break; + case MULTIRANGE_IN_RANGE_ESCAPED: + + /* + * We will include this character into range_str once we find + * the end of the range value. + */ + parse_state = MULTIRANGE_IN_RANGE; + break; + case MULTIRANGE_IN_RANGE_QUOTED: + if (ch == '"') + if (*(ptr + 1) == '"') + { + /* two quote marks means an escaped quote mark */ + ptr++; + } + else + parse_state = MULTIRANGE_IN_RANGE; + else if (ch == '\\') + parse_state = MULTIRANGE_IN_RANGE_QUOTED_ESCAPED; + + /* + * We will include this character into range_str once we find + * the end of the range value. + */ + break; + case MULTIRANGE_AFTER_RANGE: + if (ch == ',') + parse_state = MULTIRANGE_BEFORE_RANGE; + else if (ch == '}') + parse_state = MULTIRANGE_FINISHED; + else + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed multirange literal: \"%s\"", + input_str), + errdetail("Expected comma or end of multirange."))); + break; + case MULTIRANGE_IN_RANGE_QUOTED_ESCAPED: + + /* + * We will include this character into range_str once we find + * the end of the range value. + */ + parse_state = MULTIRANGE_IN_RANGE_QUOTED; + break; + default: + elog(ERROR, "unknown parse state: %d", parse_state); + } + } + + /* consume whitespace */ + while (*ptr != '\0' && isspace((unsigned char) *ptr)) + ptr++; + + if (*ptr != '\0') + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed multirange literal: \"%s\"", + input_str), + errdetail("Junk after closing right brace."))); + + ret = make_multirange(mltrngtypoid, rangetyp, range_count, ranges); + PG_RETURN_MULTIRANGE_P(ret); +} + +Datum +multirange_out(PG_FUNCTION_ARGS) +{ + MultirangeType *multirange = PG_GETARG_MULTIRANGE_P(0); + Oid mltrngtypoid = MultirangeTypeGetOid(multirange); + MultirangeIOData *cache; + StringInfoData buf; + RangeType *range; + char *rangeStr; + int32 range_count; + int32 i; + RangeType **ranges; + + cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_output); + + initStringInfo(&buf); + + appendStringInfoChar(&buf, '{'); + + multirange_deserialize(cache->typcache->rngtype, multirange, &range_count, &ranges); + for (i = 0; i < range_count; i++) + { + if (i > 0) + appendStringInfoChar(&buf, ','); + range = ranges[i]; + rangeStr = OutputFunctionCall(&cache->typioproc, RangeTypePGetDatum(range)); + appendStringInfoString(&buf, rangeStr); + } + + appendStringInfoChar(&buf, '}'); + + PG_RETURN_CSTRING(buf.data); +} + +/* + * Binary representation: First a int32-sized count of ranges, followed by + * ranges in their native binary representation. + */ +Datum +multirange_recv(PG_FUNCTION_ARGS) +{ + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + Oid mltrngtypoid = PG_GETARG_OID(1); + int32 typmod = PG_GETARG_INT32(2); + MultirangeIOData *cache; + uint32 range_count; + RangeType **ranges; + MultirangeType *ret; + StringInfoData tmpbuf; + + cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_receive); + + range_count = pq_getmsgint(buf, 4); + ranges = palloc(range_count * sizeof(RangeType *)); + + initStringInfo(&tmpbuf); + for (int i = 0; i < range_count; i++) + { + uint32 range_len = pq_getmsgint(buf, 4); + const char *range_data = pq_getmsgbytes(buf, range_len); + + resetStringInfo(&tmpbuf); + appendBinaryStringInfo(&tmpbuf, range_data, range_len); + + ranges[i] = DatumGetRangeTypeP(ReceiveFunctionCall(&cache->typioproc, + &tmpbuf, + cache->typioparam, + typmod)); + } + pfree(tmpbuf.data); + + pq_getmsgend(buf); + + ret = make_multirange(mltrngtypoid, cache->typcache->rngtype, + range_count, ranges); + PG_RETURN_MULTIRANGE_P(ret); +} + +Datum +multirange_send(PG_FUNCTION_ARGS) +{ + MultirangeType *multirange = PG_GETARG_MULTIRANGE_P(0); + Oid mltrngtypoid = MultirangeTypeGetOid(multirange); + StringInfo buf = makeStringInfo(); + RangeType **ranges; + int32 range_count; + MultirangeIOData *cache; + + cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_send); + + /* construct output */ + pq_begintypsend(buf); + + pq_sendint32(buf, multirange->rangeCount); + + multirange_deserialize(cache->typcache->rngtype, multirange, &range_count, &ranges); + for (int i = 0; i < range_count; i++) + { + Datum range; + + range = RangeTypePGetDatum(ranges[i]); + range = PointerGetDatum(SendFunctionCall(&cache->typioproc, range)); + + pq_sendint32(buf, VARSIZE(range) - VARHDRSZ); + pq_sendbytes(buf, VARDATA(range), VARSIZE(range) - VARHDRSZ); + } + + PG_RETURN_BYTEA_P(pq_endtypsend(buf)); +} + +/* + * get_multirange_io_data: get cached information needed for multirange type I/O + * + * The multirange I/O functions need a bit more cached info than other multirange + * functions, so they store a MultirangeIOData struct in fn_extra, not just a + * pointer to a type cache entry. + */ +static MultirangeIOData * +get_multirange_io_data(FunctionCallInfo fcinfo, Oid mltrngtypid, IOFuncSelector func) +{ + MultirangeIOData *cache = (MultirangeIOData *) fcinfo->flinfo->fn_extra; + + if (cache == NULL || cache->typcache->type_id != mltrngtypid) + { + Oid typiofunc; + int16 typlen; + bool typbyval; + char typalign; + char typdelim; + + cache = (MultirangeIOData *) MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, + sizeof(MultirangeIOData)); + cache->typcache = lookup_type_cache(mltrngtypid, TYPECACHE_MULTIRANGE_INFO); + if (cache->typcache->rngtype == NULL) + elog(ERROR, "type %u is not a multirange type", mltrngtypid); + + /* get_type_io_data does more than we need, but is convenient */ + get_type_io_data(cache->typcache->rngtype->type_id, + func, + &typlen, + &typbyval, + &typalign, + &typdelim, + &cache->typioparam, + &typiofunc); + + if (!OidIsValid(typiofunc)) + { + /* this could only happen for receive or send */ + if (func == IOFunc_receive) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("no binary input function available for type %s", + format_type_be(cache->typcache->rngtype->type_id)))); + else + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("no binary output function available for type %s", + format_type_be(cache->typcache->rngtype->type_id)))); + } + fmgr_info_cxt(typiofunc, &cache->typioproc, + fcinfo->flinfo->fn_mcxt); + + fcinfo->flinfo->fn_extra = (void *) cache; + } + + return cache; +} + +/* + * Converts a list of arbitrary ranges into a list that is sorted and merged. + * Changes the contents of `ranges`. + * + * Returns the number of slots actually used, which may be less than + * input_range_count but never more. + * + * We assume that no input ranges are null, but empties are okay. + */ +static int32 +multirange_canonicalize(TypeCacheEntry *rangetyp, int32 input_range_count, + RangeType **ranges) +{ + RangeType *lastRange = NULL; + RangeType *currentRange; + int32 i; + int32 output_range_count = 0; + + /* Sort the ranges so we can find the ones that overlap/meet. */ + qsort_arg(ranges, input_range_count, sizeof(RangeType *), range_compare, + rangetyp); + + /* Now merge where possible: */ + for (i = 0; i < input_range_count; i++) + { + currentRange = ranges[i]; + if (RangeIsEmpty(currentRange)) + continue; + + if (lastRange == NULL) + { + ranges[output_range_count++] = lastRange = currentRange; + continue; + } + + /* + * range_adjacent_internal gives true if *either* A meets B or B meets + * A, which is not quite want we want, but we rely on the sorting + * above to rule out B meets A ever happening. + */ + if (range_adjacent_internal(rangetyp, lastRange, currentRange)) + { + /* The two ranges touch (without overlap), so merge them: */ + ranges[output_range_count - 1] = lastRange = + range_union_internal(rangetyp, lastRange, currentRange, false); + } + else if (range_before_internal(rangetyp, lastRange, currentRange)) + { + /* There's a gap, so make a new entry: */ + lastRange = ranges[output_range_count] = currentRange; + output_range_count++; + } + else + { + /* They must overlap, so merge them: */ + ranges[output_range_count - 1] = lastRange = + range_union_internal(rangetyp, lastRange, currentRange, true); + } + } + + return output_range_count; +} + +/* + *---------------------------------------------------------- + * SUPPORT FUNCTIONS + * + * These functions aren't in pg_proc, but are useful for + * defining new generic multirange functions in C. + *---------------------------------------------------------- + */ + +/* + * multirange_get_typcache: get cached information about a multirange type + * + * This is for use by multirange-related functions that follow the convention + * of using the fn_extra field as a pointer to the type cache entry for + * the multirange type. Functions that need to cache more information than + * that must fend for themselves. + */ +TypeCacheEntry * +multirange_get_typcache(FunctionCallInfo fcinfo, Oid mltrngtypid) +{ + TypeCacheEntry *typcache = (TypeCacheEntry *) fcinfo->flinfo->fn_extra; + + if (typcache == NULL || + typcache->type_id != mltrngtypid) + { + typcache = lookup_type_cache(mltrngtypid, TYPECACHE_MULTIRANGE_INFO); + if (typcache->rngtype == NULL) + elog(ERROR, "type %u is not a multirange type", mltrngtypid); + fcinfo->flinfo->fn_extra = (void *) typcache; + } + + return typcache; +} + + +/* + * Estimate size occupied by serialized multirange. + */ +static Size +multirange_size_estimate(TypeCacheEntry *rangetyp, int32 range_count, + RangeType **ranges) +{ + char elemalign = rangetyp->rngelemtype->typalign; + Size size; + int32 i; + + /* + * Count space for MultirangeType struct, items and flags. + */ + size = att_align_nominal(sizeof(MultirangeType) + + Max(range_count - 1, 0) * sizeof(uint32) + + range_count * sizeof(uint8), elemalign); + + /* Count space for range bounds */ + for (i = 0; i < range_count; i++) + size += att_align_nominal(VARSIZE(ranges[i]) - + sizeof(RangeType) - + sizeof(char), elemalign); + + return size; +} + +/* + * Write multirange data into pre-allocated space. + */ +static void +write_multirange_data(MultirangeType *multirange, TypeCacheEntry *rangetyp, + int32 range_count, RangeType **ranges) +{ + uint32 *items; + uint32 prev_offset = 0; + uint8 *flags; + int32 i; + Pointer begin, + ptr; + char elemalign = rangetyp->rngelemtype->typalign; + + items = MultirangeGetItemsPtr(multirange); + flags = MultirangeGetFlagsPtr(multirange); + ptr = begin = MultirangeGetBoundariesPtr(multirange, elemalign); + for (i = 0; i < range_count; i++) + { + uint32 len; + + if (i > 0) + { + /* + * Every range, except the first one, has an item. Every + * MULTIRANGE_ITEM_OFFSET_STRIDE item contains an offset, others + * contain lengths. + */ + items[i - 1] = ptr - begin; + if ((i % MULTIRANGE_ITEM_OFFSET_STRIDE) != 0) + items[i - 1] -= prev_offset; + else + items[i - 1] |= MULTIRANGE_ITEM_OFF_BIT; + prev_offset = ptr - begin; + } + flags[i] = *((Pointer) ranges[i] + VARSIZE(ranges[i]) - sizeof(char)); + len = VARSIZE(ranges[i]) - sizeof(RangeType) - sizeof(char); + memcpy(ptr, (Pointer) (ranges[i] + 1), len); + ptr += att_align_nominal(len, elemalign); + } +} + + +/* + * This serializes the multirange from a list of non-null ranges. It also + * sorts the ranges and merges any that touch. The ranges should already be + * detoasted, and there should be no NULLs. This should be used by most + * callers. + * + * Note that we may change the `ranges` parameter (the pointers, but not + * any already-existing RangeType contents). + */ +MultirangeType * +make_multirange(Oid mltrngtypoid, TypeCacheEntry *rangetyp, int32 range_count, + RangeType **ranges) +{ + MultirangeType *multirange; + Size size; + + /* Sort and merge input ranges. */ + range_count = multirange_canonicalize(rangetyp, range_count, ranges); + + /* Note: zero-fill is required here, just as in heap tuples */ + size = multirange_size_estimate(rangetyp, range_count, ranges); + multirange = palloc0(size); + SET_VARSIZE(multirange, size); + + /* Now fill in the datum */ + multirange->multirangetypid = mltrngtypoid; + multirange->rangeCount = range_count; + + write_multirange_data(multirange, rangetyp, range_count, ranges); + + return multirange; +} + +/* + * Get offset of bounds values of the i'th range in the multirange. + */ +static uint32 +multirange_get_bounds_offset(const MultirangeType *multirange, int32 i) +{ + uint32 *items = MultirangeGetItemsPtr(multirange); + uint32 offset = 0; + + /* + * Summarize lengths till we meet an offset. + */ + while (i > 0) + { + offset += MULTIRANGE_ITEM_GET_OFFLEN(items[i - 1]); + if (MULTIRANGE_ITEM_HAS_OFF(items[i - 1])) + break; + i--; + } + return offset; +} + +/* + * Fetch the i'th range from the multirange. + */ +RangeType * +multirange_get_range(TypeCacheEntry *rangetyp, + const MultirangeType *multirange, int i) +{ + uint32 offset; + uint8 flags; + Pointer begin, + ptr; + int16 typlen = rangetyp->rngelemtype->typlen; + char typalign = rangetyp->rngelemtype->typalign; + uint32 len; + RangeType *range; + + Assert(i < multirange->rangeCount); + + offset = multirange_get_bounds_offset(multirange, i); + flags = MultirangeGetFlagsPtr(multirange)[i]; + ptr = begin = MultirangeGetBoundariesPtr(multirange, typalign) + offset; + + /* + * Calculate the size of bound values. In principle, we could get offset + * of the next range bound values and calculate accordingly. But range + * bound values are aligned, so we have to walk the values to get the + * exact size. + */ + if (RANGE_HAS_LBOUND(flags)) + ptr = (Pointer) att_addlength_pointer(ptr, typlen, ptr); + if (RANGE_HAS_UBOUND(flags)) + { + ptr = (Pointer) att_align_pointer(ptr, typalign, typlen, ptr); + ptr = (Pointer) att_addlength_pointer(ptr, typlen, ptr); + } + len = (ptr - begin) + sizeof(RangeType) + sizeof(uint8); + + range = palloc0(len); + SET_VARSIZE(range, len); + range->rangetypid = rangetyp->type_id; + + memcpy(range + 1, begin, ptr - begin); + *((uint8 *) (range + 1) + (ptr - begin)) = flags; + + return range; +} + +/* + * Fetch bounds from the i'th range of the multirange. This is the shortcut for + * doing the same thing as multirange_get_range() + range_deserialize(), but + * performing fewer operations. + */ +void +multirange_get_bounds(TypeCacheEntry *rangetyp, + const MultirangeType *multirange, + uint32 i, RangeBound *lower, RangeBound *upper) +{ + uint32 offset; + uint8 flags; + Pointer ptr; + int16 typlen = rangetyp->rngelemtype->typlen; + char typalign = rangetyp->rngelemtype->typalign; + bool typbyval = rangetyp->rngelemtype->typbyval; + Datum lbound; + Datum ubound; + + Assert(i < multirange->rangeCount); + + offset = multirange_get_bounds_offset(multirange, i); + flags = MultirangeGetFlagsPtr(multirange)[i]; + ptr = MultirangeGetBoundariesPtr(multirange, typalign) + offset; + + /* multirange can't contain empty ranges */ + Assert((flags & RANGE_EMPTY) == 0); + + /* fetch lower bound, if any */ + if (RANGE_HAS_LBOUND(flags)) + { + /* att_align_pointer cannot be necessary here */ + lbound = fetch_att(ptr, typbyval, typlen); + ptr = (Pointer) att_addlength_pointer(ptr, typlen, ptr); + } + else + lbound = (Datum) 0; + + /* fetch upper bound, if any */ + if (RANGE_HAS_UBOUND(flags)) + { + ptr = (Pointer) att_align_pointer(ptr, typalign, typlen, ptr); + ubound = fetch_att(ptr, typbyval, typlen); + /* no need for att_addlength_pointer */ + } + else + ubound = (Datum) 0; + + /* emit results */ + lower->val = lbound; + lower->infinite = (flags & RANGE_LB_INF) != 0; + lower->inclusive = (flags & RANGE_LB_INC) != 0; + lower->lower = true; + + upper->val = ubound; + upper->infinite = (flags & RANGE_UB_INF) != 0; + upper->inclusive = (flags & RANGE_UB_INC) != 0; + upper->lower = false; +} + +/* + * Construct union range from the multirange. + */ +RangeType * +multirange_get_union_range(TypeCacheEntry *rangetyp, + const MultirangeType *mr) +{ + RangeBound lower, + upper, + tmp; + + if (MultirangeIsEmpty(mr)) + return make_empty_range(rangetyp); + + multirange_get_bounds(rangetyp, mr, 0, &lower, &tmp); + multirange_get_bounds(rangetyp, mr, mr->rangeCount - 1, &tmp, &upper); + + return make_range(rangetyp, &lower, &upper, false, NULL); +} + + +/* + * multirange_deserialize: deconstruct a multirange value + * + * NB: the given multirange object must be fully detoasted; it cannot have a + * short varlena header. + */ +void +multirange_deserialize(TypeCacheEntry *rangetyp, + const MultirangeType *multirange, int32 *range_count, + RangeType ***ranges) +{ + *range_count = multirange->rangeCount; + + /* Convert each ShortRangeType into a RangeType */ + if (*range_count > 0) + { + int i; + + *ranges = palloc(*range_count * sizeof(RangeType *)); + for (i = 0; i < *range_count; i++) + (*ranges)[i] = multirange_get_range(rangetyp, multirange, i); + } + else + { + *ranges = NULL; + } +} + +MultirangeType * +make_empty_multirange(Oid mltrngtypoid, TypeCacheEntry *rangetyp) +{ + return make_multirange(mltrngtypoid, rangetyp, 0, NULL); +} + +/* + * Similar to range_overlaps_internal(), but takes range bounds instead of + * ranges as arguments. + */ +static bool +range_bounds_overlaps(TypeCacheEntry *typcache, + RangeBound *lower1, RangeBound *upper1, + RangeBound *lower2, RangeBound *upper2) +{ + if (range_cmp_bounds(typcache, lower1, lower2) >= 0 && + range_cmp_bounds(typcache, lower1, upper2) <= 0) + return true; + + if (range_cmp_bounds(typcache, lower2, lower1) >= 0 && + range_cmp_bounds(typcache, lower2, upper1) <= 0) + return true; + + return false; +} + +/* + * Similar to range_contains_internal(), but takes range bounds instead of + * ranges as arguments. + */ +static bool +range_bounds_contains(TypeCacheEntry *typcache, + RangeBound *lower1, RangeBound *upper1, + RangeBound *lower2, RangeBound *upper2) +{ + if (range_cmp_bounds(typcache, lower1, lower2) <= 0 && + range_cmp_bounds(typcache, upper1, upper2) >= 0) + return true; + + return false; +} + +/* + * Check if the given key matches any range in multirange using binary search. + * If the required range isn't found, that counts as a mismatch. When the + * required range is found, the comparison function can still report this as + * either match or mismatch. For instance, if we search for containment, we can + * found a range, which is overlapping but not containing the key range, and + * that would count as a mismatch. + */ +static bool +multirange_bsearch_match(TypeCacheEntry *typcache, const MultirangeType *mr, + void *key, multirange_bsearch_comparison cmp_func) +{ + uint32 l, + u, + idx; + int comparison; + bool match = false; + + l = 0; + u = mr->rangeCount; + while (l < u) + { + RangeBound lower, + upper; + + idx = (l + u) / 2; + multirange_get_bounds(typcache, mr, idx, &lower, &upper); + comparison = (*cmp_func) (typcache, &lower, &upper, key, &match); + + if (comparison < 0) + u = idx; + else if (comparison > 0) + l = idx + 1; + else + return match; + } + + return false; +} + +/* + *---------------------------------------------------------- + * GENERIC FUNCTIONS + *---------------------------------------------------------- + */ + +/* + * Construct multirange value from zero or more ranges. Since this is a + * variadic function we get passed an array. The array must contain ranges + * that match our return value, and there must be no NULLs. + */ +Datum +multirange_constructor2(PG_FUNCTION_ARGS) +{ + Oid mltrngtypid = get_fn_expr_rettype(fcinfo->flinfo); + Oid rngtypid; + TypeCacheEntry *typcache; + TypeCacheEntry *rangetyp; + ArrayType *rangeArray; + int range_count; + Datum *elements; + bool *nulls; + RangeType **ranges; + int dims; + int i; + + typcache = multirange_get_typcache(fcinfo, mltrngtypid); + rangetyp = typcache->rngtype; + + /* + * A no-arg invocation should call multirange_constructor0 instead, but + * returning an empty range is what that does. + */ + + if (PG_NARGS() == 0) + PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypid, rangetyp, 0, NULL)); + + /* + * This check should be guaranteed by our signature, but let's do it just + * in case. + */ + + if (PG_ARGISNULL(0)) + elog(ERROR, + "multirange values cannot contain null members"); + + rangeArray = PG_GETARG_ARRAYTYPE_P(0); + + dims = ARR_NDIM(rangeArray); + if (dims > 1) + ereport(ERROR, + (errcode(ERRCODE_CARDINALITY_VIOLATION), + errmsg("multiranges cannot be constructed from multidimensional arrays"))); + + rngtypid = ARR_ELEMTYPE(rangeArray); + if (rngtypid != rangetyp->type_id) + elog(ERROR, "type %u does not match constructor type", rngtypid); + + /* + * Be careful: we can still be called with zero ranges, like this: + * `int4multirange(variadic '{}'::int4range[]) + */ + if (dims == 0) + { + range_count = 0; + ranges = NULL; + } + else + { + deconstruct_array(rangeArray, rngtypid, rangetyp->typlen, rangetyp->typbyval, + rangetyp->typalign, &elements, &nulls, &range_count); + + ranges = palloc0(range_count * sizeof(RangeType *)); + for (i = 0; i < range_count; i++) + { + if (nulls[i]) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("multirange values cannot contain null members"))); + + /* make_multirange will do its own copy */ + ranges[i] = DatumGetRangeTypeP(elements[i]); + } + } + + PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypid, rangetyp, range_count, ranges)); +} + +/* + * Construct multirange value from a single range. It'd be nice if we could + * just use multirange_constructor2 for this case, but we need a non-variadic + * single-arg function to let us define a CAST from a range to its multirange. + */ +Datum +multirange_constructor1(PG_FUNCTION_ARGS) +{ + Oid mltrngtypid = get_fn_expr_rettype(fcinfo->flinfo); + Oid rngtypid; + TypeCacheEntry *typcache; + TypeCacheEntry *rangetyp; + RangeType *range; + + typcache = multirange_get_typcache(fcinfo, mltrngtypid); + rangetyp = typcache->rngtype; + + /* + * This check should be guaranteed by our signature, but let's do it just + * in case. + */ + + if (PG_ARGISNULL(0)) + elog(ERROR, + "multirange values cannot contain null members"); + + range = PG_GETARG_RANGE_P(0); + + /* Make sure the range type matches. */ + rngtypid = RangeTypeGetOid(range); + if (rngtypid != rangetyp->type_id) + elog(ERROR, "type %u does not match constructor type", rngtypid); + + PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypid, rangetyp, 1, &range)); +} + +/* + * Constructor just like multirange_constructor1, but opr_sanity gets angry + * if the same internal function handles multiple functions with different arg + * counts. + */ +Datum +multirange_constructor0(PG_FUNCTION_ARGS) +{ + Oid mltrngtypid; + TypeCacheEntry *typcache; + TypeCacheEntry *rangetyp; + + /* This should always be called without arguments */ + if (PG_NARGS() != 0) + elog(ERROR, + "niladic multirange constructor must not receive arguments"); + + mltrngtypid = get_fn_expr_rettype(fcinfo->flinfo); + typcache = multirange_get_typcache(fcinfo, mltrngtypid); + rangetyp = typcache->rngtype; + + PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypid, rangetyp, 0, NULL)); +} + + +/* multirange, multirange -> multirange type functions */ + +/* multirange union */ +Datum +multirange_union(PG_FUNCTION_ARGS) +{ + MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0); + MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1); + TypeCacheEntry *typcache; + int32 range_count1; + int32 range_count2; + int32 range_count3; + RangeType **ranges1; + RangeType **ranges2; + RangeType **ranges3; + + if (MultirangeIsEmpty(mr1)) + PG_RETURN_MULTIRANGE_P(mr2); + if (MultirangeIsEmpty(mr2)) + PG_RETURN_MULTIRANGE_P(mr1); + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1)); + + multirange_deserialize(typcache->rngtype, mr1, &range_count1, &ranges1); + multirange_deserialize(typcache->rngtype, mr2, &range_count2, &ranges2); + + range_count3 = range_count1 + range_count2; + ranges3 = palloc0(range_count3 * sizeof(RangeType *)); + memcpy(ranges3, ranges1, range_count1 * sizeof(RangeType *)); + memcpy(ranges3 + range_count1, ranges2, range_count2 * sizeof(RangeType *)); + PG_RETURN_MULTIRANGE_P(make_multirange(typcache->type_id, typcache->rngtype, + range_count3, ranges3)); +} + +/* multirange minus */ +Datum +multirange_minus(PG_FUNCTION_ARGS) +{ + MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0); + MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1); + Oid mltrngtypoid = MultirangeTypeGetOid(mr1); + TypeCacheEntry *typcache; + TypeCacheEntry *rangetyp; + int32 range_count1; + int32 range_count2; + RangeType **ranges1; + RangeType **ranges2; + + typcache = multirange_get_typcache(fcinfo, mltrngtypoid); + rangetyp = typcache->rngtype; + + if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2)) + PG_RETURN_MULTIRANGE_P(mr1); + + multirange_deserialize(typcache->rngtype, mr1, &range_count1, &ranges1); + multirange_deserialize(typcache->rngtype, mr2, &range_count2, &ranges2); + + PG_RETURN_MULTIRANGE_P(multirange_minus_internal(mltrngtypoid, + rangetyp, + range_count1, + ranges1, + range_count2, + ranges2)); +} + +MultirangeType * +multirange_minus_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp, + int32 range_count1, RangeType **ranges1, + int32 range_count2, RangeType **ranges2) +{ + RangeType *r1; + RangeType *r2; + RangeType **ranges3; + int32 range_count3; + int32 i1; + int32 i2; + + /* + * Worst case: every range in ranges1 makes a different cut to some range + * in ranges2. + */ + ranges3 = palloc0((range_count1 + range_count2) * sizeof(RangeType *)); + range_count3 = 0; + + /* + * For each range in mr1, keep subtracting until it's gone or the ranges + * in mr2 have passed it. After a subtraction we assign what's left back + * to r1. The parallel progress through mr1 and mr2 is similar to + * multirange_overlaps_multirange_internal. + */ + r2 = ranges2[0]; + for (i1 = 0, i2 = 0; i1 < range_count1; i1++) + { + r1 = ranges1[i1]; + + /* Discard r2s while r2 << r1 */ + while (r2 != NULL && range_before_internal(rangetyp, r2, r1)) + { + r2 = ++i2 >= range_count2 ? NULL : ranges2[i2]; + } + + while (r2 != NULL) + { + if (range_split_internal(rangetyp, r1, r2, &ranges3[range_count3], &r1)) + { + /* + * If r2 takes a bite out of the middle of r1, we need two + * outputs + */ + range_count3++; + r2 = ++i2 >= range_count2 ? NULL : ranges2[i2]; + } + else if (range_overlaps_internal(rangetyp, r1, r2)) + { + /* + * If r2 overlaps r1, replace r1 with r1 - r2. + */ + r1 = range_minus_internal(rangetyp, r1, r2); + + /* + * If r2 goes past r1, then we need to stay with it, in case + * it hits future r1s. Otherwise we need to keep r1, in case + * future r2s hit it. Since we already subtracted, there's no + * point in using the overright/overleft calls. + */ + if (RangeIsEmpty(r1) || range_before_internal(rangetyp, r1, r2)) + break; + else + r2 = ++i2 >= range_count2 ? NULL : ranges2[i2]; + } + else + { + /* + * This and all future r2s are past r1, so keep them. Also + * assign whatever is left of r1 to the result. + */ + break; + } + } + + /* + * Nothing else can remove anything from r1, so keep it. Even if r1 is + * empty here, make_multirange will remove it. + */ + ranges3[range_count3++] = r1; + } + + return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3); +} + +/* multirange intersection */ +Datum +multirange_intersect(PG_FUNCTION_ARGS) +{ + MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0); + MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1); + Oid mltrngtypoid = MultirangeTypeGetOid(mr1); + TypeCacheEntry *typcache; + TypeCacheEntry *rangetyp; + int32 range_count1; + int32 range_count2; + RangeType **ranges1; + RangeType **ranges2; + + typcache = multirange_get_typcache(fcinfo, mltrngtypoid); + rangetyp = typcache->rngtype; + + if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2)) + PG_RETURN_MULTIRANGE_P(make_empty_multirange(mltrngtypoid, rangetyp)); + + multirange_deserialize(rangetyp, mr1, &range_count1, &ranges1); + multirange_deserialize(rangetyp, mr2, &range_count2, &ranges2); + + PG_RETURN_MULTIRANGE_P(multirange_intersect_internal(mltrngtypoid, + rangetyp, + range_count1, + ranges1, + range_count2, + ranges2)); +} + +MultirangeType * +multirange_intersect_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp, + int32 range_count1, RangeType **ranges1, + int32 range_count2, RangeType **ranges2) +{ + RangeType *r1; + RangeType *r2; + RangeType **ranges3; + int32 range_count3; + int32 i1; + int32 i2; + + if (range_count1 == 0 || range_count2 == 0) + return make_multirange(mltrngtypoid, rangetyp, 0, NULL); + + /*----------------------------------------------- + * Worst case is a stitching pattern like this: + * + * mr1: --- --- --- --- + * mr2: --- --- --- + * mr3: - - - - - - + * + * That seems to be range_count1 + range_count2 - 1, + * but one extra won't hurt. + *----------------------------------------------- + */ + ranges3 = palloc0((range_count1 + range_count2) * sizeof(RangeType *)); + range_count3 = 0; + + /* + * For each range in mr1, keep intersecting until the ranges in mr2 have + * passed it. The parallel progress through mr1 and mr2 is similar to + * multirange_minus_multirange_internal, but we don't have to assign back + * to r1. + */ + r2 = ranges2[0]; + for (i1 = 0, i2 = 0; i1 < range_count1; i1++) + { + r1 = ranges1[i1]; + + /* Discard r2s while r2 << r1 */ + while (r2 != NULL && range_before_internal(rangetyp, r2, r1)) + { + r2 = ++i2 >= range_count2 ? NULL : ranges2[i2]; + } + + while (r2 != NULL) + { + if (range_overlaps_internal(rangetyp, r1, r2)) + { + /* Keep the overlapping part */ + ranges3[range_count3++] = range_intersect_internal(rangetyp, r1, r2); + + /* If we "used up" all of r2, go to the next one... */ + if (range_overleft_internal(rangetyp, r2, r1)) + r2 = ++i2 >= range_count2 ? NULL : ranges2[i2]; + + /* ...otherwise go to the next r1 */ + else + break; + } + else + /* We're past r1, so move to the next one */ + break; + } + + /* If we're out of r2s, there can be no more intersections */ + if (r2 == NULL) + break; + } + + return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3); +} + +/* + * range_agg_transfn: combine adjacent/overlapping ranges. + * + * All we do here is gather the input ranges into an array + * so that the finalfn can sort and combine them. + */ +Datum +range_agg_transfn(PG_FUNCTION_ARGS) +{ + MemoryContext aggContext; + Oid rngtypoid; + ArrayBuildState *state; + + if (!AggCheckCallContext(fcinfo, &aggContext)) + elog(ERROR, "range_agg_transfn called in non-aggregate context"); + + rngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1); + if (!type_is_range(rngtypoid)) + elog(ERROR, "range_agg must be called with a range"); + + if (PG_ARGISNULL(0)) + state = initArrayResult(rngtypoid, aggContext, false); + else + state = (ArrayBuildState *) PG_GETARG_POINTER(0); + + /* skip NULLs */ + if (!PG_ARGISNULL(1)) + accumArrayResult(state, PG_GETARG_DATUM(1), false, rngtypoid, aggContext); + + PG_RETURN_POINTER(state); +} + +/* + * range_agg_finalfn: use our internal array to merge touching ranges. + * + * Shared by range_agg_finalfn(anyrange) and + * multirange_agg_finalfn(anymultirange). + */ +Datum +range_agg_finalfn(PG_FUNCTION_ARGS) +{ + MemoryContext aggContext; + Oid mltrngtypoid; + TypeCacheEntry *typcache; + ArrayBuildState *state; + int32 range_count; + RangeType **ranges; + int i; + + if (!AggCheckCallContext(fcinfo, &aggContext)) + elog(ERROR, "range_agg_finalfn called in non-aggregate context"); + + state = PG_ARGISNULL(0) ? NULL : (ArrayBuildState *) PG_GETARG_POINTER(0); + if (state == NULL) + /* This shouldn't be possible, but just in case.... */ + PG_RETURN_NULL(); + + /* Also return NULL if we had zero inputs, like other aggregates */ + range_count = state->nelems; + if (range_count == 0) + PG_RETURN_NULL(); + + mltrngtypoid = get_fn_expr_rettype(fcinfo->flinfo); + typcache = multirange_get_typcache(fcinfo, mltrngtypoid); + + ranges = palloc0(range_count * sizeof(RangeType *)); + for (i = 0; i < range_count; i++) + ranges[i] = DatumGetRangeTypeP(state->dvalues[i]); + + PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypoid, typcache->rngtype, range_count, ranges)); +} + +/* + * multirange_agg_transfn: combine adjacent/overlapping multiranges. + * + * All we do here is gather the input multiranges' ranges into an array so + * that the finalfn can sort and combine them. + */ +Datum +multirange_agg_transfn(PG_FUNCTION_ARGS) +{ + MemoryContext aggContext; + Oid mltrngtypoid; + TypeCacheEntry *typcache; + TypeCacheEntry *rngtypcache; + ArrayBuildState *state; + + if (!AggCheckCallContext(fcinfo, &aggContext)) + elog(ERROR, "multirange_agg_transfn called in non-aggregate context"); + + mltrngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1); + if (!type_is_multirange(mltrngtypoid)) + elog(ERROR, "range_agg must be called with a multirange"); + + typcache = multirange_get_typcache(fcinfo, mltrngtypoid); + rngtypcache = typcache->rngtype; + + if (PG_ARGISNULL(0)) + state = initArrayResult(rngtypcache->type_id, aggContext, false); + else + state = (ArrayBuildState *) PG_GETARG_POINTER(0); + + /* skip NULLs */ + if (!PG_ARGISNULL(1)) + { + MultirangeType *current; + int32 range_count; + RangeType **ranges; + + current = PG_GETARG_MULTIRANGE_P(1); + multirange_deserialize(rngtypcache, current, &range_count, &ranges); + if (range_count == 0) + { + /* + * Add an empty range so we get an empty result (not a null + * result). + */ + accumArrayResult(state, + RangeTypePGetDatum(make_empty_range(rngtypcache)), + false, rngtypcache->type_id, aggContext); + } + else + { + for (int32 i = 0; i < range_count; i++) + accumArrayResult(state, RangeTypePGetDatum(ranges[i]), false, rngtypcache->type_id, aggContext); + } + } + + PG_RETURN_POINTER(state); +} + +Datum +multirange_intersect_agg_transfn(PG_FUNCTION_ARGS) +{ + MemoryContext aggContext; + Oid mltrngtypoid; + TypeCacheEntry *typcache; + MultirangeType *result; + MultirangeType *current; + int32 range_count1; + int32 range_count2; + RangeType **ranges1; + RangeType **ranges2; + + if (!AggCheckCallContext(fcinfo, &aggContext)) + elog(ERROR, "multirange_intersect_agg_transfn called in non-aggregate context"); + + mltrngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1); + if (!type_is_multirange(mltrngtypoid)) + elog(ERROR, "range_intersect_agg must be called with a multirange"); + + typcache = multirange_get_typcache(fcinfo, mltrngtypoid); + + /* strictness ensures these are non-null */ + result = PG_GETARG_MULTIRANGE_P(0); + current = PG_GETARG_MULTIRANGE_P(1); + + multirange_deserialize(typcache->rngtype, result, &range_count1, &ranges1); + multirange_deserialize(typcache->rngtype, current, &range_count2, &ranges2); + + result = multirange_intersect_internal(mltrngtypoid, + typcache->rngtype, + range_count1, + ranges1, + range_count2, + ranges2); + PG_RETURN_MULTIRANGE_P(result); +} + + +/* multirange -> element type functions */ + +/* extract lower bound value */ +Datum +multirange_lower(PG_FUNCTION_ARGS) +{ + MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0); + TypeCacheEntry *typcache; + RangeBound lower; + RangeBound upper; + + if (MultirangeIsEmpty(mr)) + PG_RETURN_NULL(); + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr)); + + multirange_get_bounds(typcache->rngtype, mr, 0, + &lower, &upper); + + if (!lower.infinite) + PG_RETURN_DATUM(lower.val); + else + PG_RETURN_NULL(); +} + +/* extract upper bound value */ +Datum +multirange_upper(PG_FUNCTION_ARGS) +{ + MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0); + TypeCacheEntry *typcache; + RangeBound lower; + RangeBound upper; + + if (MultirangeIsEmpty(mr)) + PG_RETURN_NULL(); + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr)); + + multirange_get_bounds(typcache->rngtype, mr, mr->rangeCount - 1, + &lower, &upper); + + if (!upper.infinite) + PG_RETURN_DATUM(upper.val); + else + PG_RETURN_NULL(); +} + + +/* multirange -> bool functions */ + +/* is multirange empty? */ +Datum +multirange_empty(PG_FUNCTION_ARGS) +{ + MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0); + + PG_RETURN_BOOL(MultirangeIsEmpty(mr)); +} + +/* is lower bound inclusive? */ +Datum +multirange_lower_inc(PG_FUNCTION_ARGS) +{ + MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0); + TypeCacheEntry *typcache; + RangeBound lower; + RangeBound upper; + + if (MultirangeIsEmpty(mr)) + PG_RETURN_BOOL(false); + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr)); + multirange_get_bounds(typcache->rngtype, mr, 0, + &lower, &upper); + + PG_RETURN_BOOL(lower.inclusive); +} + +/* is upper bound inclusive? */ +Datum +multirange_upper_inc(PG_FUNCTION_ARGS) +{ + MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0); + TypeCacheEntry *typcache; + RangeBound lower; + RangeBound upper; + + if (MultirangeIsEmpty(mr)) + PG_RETURN_BOOL(false); + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr)); + multirange_get_bounds(typcache->rngtype, mr, mr->rangeCount - 1, + &lower, &upper); + + PG_RETURN_BOOL(upper.inclusive); +} + +/* is lower bound infinite? */ +Datum +multirange_lower_inf(PG_FUNCTION_ARGS) +{ + MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0); + TypeCacheEntry *typcache; + RangeBound lower; + RangeBound upper; + + if (MultirangeIsEmpty(mr)) + PG_RETURN_BOOL(false); + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr)); + multirange_get_bounds(typcache->rngtype, mr, 0, + &lower, &upper); + + PG_RETURN_BOOL(lower.infinite); +} + +/* is upper bound infinite? */ +Datum +multirange_upper_inf(PG_FUNCTION_ARGS) +{ + MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0); + TypeCacheEntry *typcache; + RangeBound lower; + RangeBound upper; + + if (MultirangeIsEmpty(mr)) + PG_RETURN_BOOL(false); + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr)); + multirange_get_bounds(typcache->rngtype, mr, mr->rangeCount - 1, + &lower, &upper); + + PG_RETURN_BOOL(upper.infinite); +} + + + +/* multirange, element -> bool functions */ + +/* contains? */ +Datum +multirange_contains_elem(PG_FUNCTION_ARGS) +{ + MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0); + Datum val = PG_GETARG_DATUM(1); + TypeCacheEntry *typcache; + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr)); + + PG_RETURN_BOOL(multirange_contains_elem_internal(typcache->rngtype, mr, val)); +} + +/* contained by? */ +Datum +elem_contained_by_multirange(PG_FUNCTION_ARGS) +{ + Datum val = PG_GETARG_DATUM(0); + MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1); + TypeCacheEntry *typcache; + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr)); + + PG_RETURN_BOOL(multirange_contains_elem_internal(typcache->rngtype, mr, val)); +} + +/* + * Comparison function for checking if any range of multirange contains given + * key element using binary search. + */ +static int +multirange_elem_bsearch_comparison(TypeCacheEntry *typcache, + RangeBound *lower, RangeBound *upper, + void *key, bool *match) +{ + Datum val = *((Datum *) key); + int cmp; + + if (!lower->infinite) + { + cmp = DatumGetInt32(FunctionCall2Coll(&typcache->rng_cmp_proc_finfo, + typcache->rng_collation, + lower->val, val)); + if (cmp > 0 || (cmp == 0 && !lower->inclusive)) + return -1; + } + + if (!upper->infinite) + { + cmp = DatumGetInt32(FunctionCall2Coll(&typcache->rng_cmp_proc_finfo, + typcache->rng_collation, + upper->val, val)); + if (cmp < 0 || (cmp == 0 && !upper->inclusive)) + return 1; + } + + *match = true; + return 0; +} + +/* + * Test whether multirange mr contains a specific element value. + */ +bool +multirange_contains_elem_internal(TypeCacheEntry *rangetyp, + const MultirangeType *mr, Datum val) +{ + if (MultirangeIsEmpty(mr)) + return false; + + return multirange_bsearch_match(rangetyp, mr, &val, + multirange_elem_bsearch_comparison); +} + +/* multirange, range -> bool functions */ + +/* contains? */ +Datum +multirange_contains_range(PG_FUNCTION_ARGS) +{ + MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0); + RangeType *r = PG_GETARG_RANGE_P(1); + TypeCacheEntry *typcache; + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr)); + + PG_RETURN_BOOL(multirange_contains_range_internal(typcache->rngtype, mr, r)); +} + +Datum +range_contains_multirange(PG_FUNCTION_ARGS) +{ + RangeType *r = PG_GETARG_RANGE_P(0); + MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1); + TypeCacheEntry *typcache; + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr)); + + PG_RETURN_BOOL(range_contains_multirange_internal(typcache->rngtype, r, mr)); +} + +/* contained by? */ +Datum +range_contained_by_multirange(PG_FUNCTION_ARGS) +{ + RangeType *r = PG_GETARG_RANGE_P(0); + MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1); + TypeCacheEntry *typcache; + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr)); + + PG_RETURN_BOOL(multirange_contains_range_internal(typcache->rngtype, mr, r)); +} + +Datum +multirange_contained_by_range(PG_FUNCTION_ARGS) +{ + MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0); + RangeType *r = PG_GETARG_RANGE_P(1); + TypeCacheEntry *typcache; + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr)); + + PG_RETURN_BOOL(range_contains_multirange_internal(typcache->rngtype, r, mr)); +} + +/* + * Comparison function for checking if any range of multirange contains given + * key range using binary search. + */ +static int +multirange_range_contains_bsearch_comparison(TypeCacheEntry *typcache, + RangeBound *lower, RangeBound *upper, + void *key, bool *match) +{ + RangeBound *keyLower = (RangeBound *) key; + RangeBound *keyUpper = (RangeBound *) key + 1; + + /* Check if key range is strictly in the left or in the right */ + if (range_cmp_bounds(typcache, keyUpper, lower) < 0) + return -1; + if (range_cmp_bounds(typcache, keyLower, upper) > 0) + return 1; + + /* + * At this point we found overlapping range. But we have to check if it + * really contains the key range. Anyway, we have to stop our search + * here, because multirange contains only non-overlapping ranges. + */ + *match = range_bounds_contains(typcache, lower, upper, keyLower, keyUpper); + + return 0; +} + +/* + * Test whether multirange mr contains a specific range r. + */ +bool +multirange_contains_range_internal(TypeCacheEntry *rangetyp, + const MultirangeType *mr, + const RangeType *r) +{ + RangeBound bounds[2]; + bool empty; + + /* + * Every multirange contains an infinite number of empty ranges, even an + * empty one. + */ + if (RangeIsEmpty(r)) + return true; + + if (MultirangeIsEmpty(mr)) + return false; + + range_deserialize(rangetyp, r, &bounds[0], &bounds[1], &empty); + Assert(!empty); + + return multirange_bsearch_match(rangetyp, mr, bounds, + multirange_range_contains_bsearch_comparison); +} + +/* + * Test whether range r contains a multirange mr. + */ +bool +range_contains_multirange_internal(TypeCacheEntry *rangetyp, + const RangeType *r, + const MultirangeType *mr) +{ + RangeBound lower1, + upper1, + lower2, + upper2, + tmp; + bool empty; + + /* + * Every range contains an infinite number of empty multiranges, even an + * empty one. + */ + if (MultirangeIsEmpty(mr)) + return true; + + if (RangeIsEmpty(r)) + return false; + + /* Range contains multirange iff it contains its union range. */ + range_deserialize(rangetyp, r, &lower1, &upper1, &empty); + Assert(!empty); + multirange_get_bounds(rangetyp, mr, 0, &lower2, &tmp); + multirange_get_bounds(rangetyp, mr, mr->rangeCount - 1, &tmp, &upper2); + + return range_bounds_contains(rangetyp, &lower1, &upper1, &lower2, &upper2); +} + + +/* multirange, multirange -> bool functions */ + +/* equality (internal version) */ +bool +multirange_eq_internal(TypeCacheEntry *rangetyp, + const MultirangeType *mr1, + const MultirangeType *mr2) +{ + int32 range_count_1; + int32 range_count_2; + int32 i; + RangeBound lower1, + upper1, + lower2, + upper2; + + /* Different types should be prevented by ANYMULTIRANGE matching rules */ + if (MultirangeTypeGetOid(mr1) != MultirangeTypeGetOid(mr2)) + elog(ERROR, "multirange types do not match"); + + range_count_1 = mr1->rangeCount; + range_count_2 = mr2->rangeCount; + + if (range_count_1 != range_count_2) + return false; + + for (i = 0; i < range_count_1; i++) + { + multirange_get_bounds(rangetyp, mr1, i, &lower1, &upper1); + multirange_get_bounds(rangetyp, mr2, i, &lower2, &upper2); + + if (range_cmp_bounds(rangetyp, &lower1, &lower2) != 0 || + range_cmp_bounds(rangetyp, &upper1, &upper2) != 0) + return false; + } + + return true; +} + +/* equality */ +Datum +multirange_eq(PG_FUNCTION_ARGS) +{ + MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0); + MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1); + TypeCacheEntry *typcache; + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1)); + + PG_RETURN_BOOL(multirange_eq_internal(typcache->rngtype, mr1, mr2)); +} + +/* inequality (internal version) */ +bool +multirange_ne_internal(TypeCacheEntry *rangetyp, + const MultirangeType *mr1, + const MultirangeType *mr2) +{ + return (!multirange_eq_internal(rangetyp, mr1, mr2)); +} + +/* inequality */ +Datum +multirange_ne(PG_FUNCTION_ARGS) +{ + MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0); + MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1); + TypeCacheEntry *typcache; + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1)); + + PG_RETURN_BOOL(multirange_ne_internal(typcache->rngtype, mr1, mr2)); +} + +/* overlaps? */ +Datum +range_overlaps_multirange(PG_FUNCTION_ARGS) +{ + RangeType *r = PG_GETARG_RANGE_P(0); + MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1); + TypeCacheEntry *typcache; + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr)); + + PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache->rngtype, r, mr)); +} + +Datum +multirange_overlaps_range(PG_FUNCTION_ARGS) +{ + MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0); + RangeType *r = PG_GETARG_RANGE_P(1); + TypeCacheEntry *typcache; + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr)); + + PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache->rngtype, r, mr)); +} + +Datum +multirange_overlaps_multirange(PG_FUNCTION_ARGS) +{ + MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0); + MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1); + TypeCacheEntry *typcache; + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1)); + + PG_RETURN_BOOL(multirange_overlaps_multirange_internal(typcache->rngtype, mr1, mr2)); +} + +/* + * Comparison function for checking if any range of multirange overlaps given + * key range using binary search. + */ +static int +multirange_range_overlaps_bsearch_comparison(TypeCacheEntry *typcache, + RangeBound *lower, RangeBound *upper, + void *key, bool *match) +{ + RangeBound *keyLower = (RangeBound *) key; + RangeBound *keyUpper = (RangeBound *) key + 1; + + if (range_cmp_bounds(typcache, keyUpper, lower) < 0) + return -1; + if (range_cmp_bounds(typcache, keyLower, upper) > 0) + return 1; + + *match = true; + return 0; +} + +bool +range_overlaps_multirange_internal(TypeCacheEntry *rangetyp, + const RangeType *r, + const MultirangeType *mr) +{ + RangeBound bounds[2]; + bool empty; + + /* + * Empties never overlap, even with empties. (This seems strange since + * they *do* contain each other, but we want to follow how ranges work.) + */ + if (RangeIsEmpty(r) || MultirangeIsEmpty(mr)) + return false; + + range_deserialize(rangetyp, r, &bounds[0], &bounds[1], &empty); + Assert(!empty); + + return multirange_bsearch_match(rangetyp, mr, bounds, + multirange_range_overlaps_bsearch_comparison); +} + +bool +multirange_overlaps_multirange_internal(TypeCacheEntry *rangetyp, + const MultirangeType *mr1, + const MultirangeType *mr2) +{ + int32 range_count1; + int32 range_count2; + int32 i1; + int32 i2; + RangeBound lower1, + upper1, + lower2, + upper2; + + /* + * Empties never overlap, even with empties. (This seems strange since + * they *do* contain each other, but we want to follow how ranges work.) + */ + if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2)) + return false; + + range_count1 = mr1->rangeCount; + range_count2 = mr2->rangeCount; + + /* + * Every range in mr1 gets a chance to overlap with the ranges in mr2, but + * we can use their ordering to avoid O(n^2). This is similar to + * range_overlaps_multirange where r1 : r2 :: mrr : r, but there if we + * don't find an overlap with r we're done, and here if we don't find an + * overlap with r2 we try the next r2. + */ + i1 = 0; + multirange_get_bounds(rangetyp, mr1, i1, &lower1, &upper1); + for (i1 = 0, i2 = 0; i2 < range_count2; i2++) + { + multirange_get_bounds(rangetyp, mr2, i2, &lower2, &upper2); + + /* Discard r1s while r1 << r2 */ + while (range_cmp_bounds(rangetyp, &upper1, &lower2) < 0) + { + if (++i1 >= range_count1) + return false; + multirange_get_bounds(rangetyp, mr1, i1, &lower1, &upper1); + } + + /* + * If r1 && r2, we're done, otherwise we failed to find an overlap for + * r2, so go to the next one. + */ + if (range_bounds_overlaps(rangetyp, &lower1, &upper1, &lower2, &upper2)) + return true; + } + + /* We looked through all of mr2 without finding an overlap */ + return false; +} + +/* does not extend to right of? */ +bool +range_overleft_multirange_internal(TypeCacheEntry *rangetyp, + const RangeType *r, + const MultirangeType *mr) +{ + RangeBound lower1, + upper1, + lower2, + upper2; + bool empty; + + if (RangeIsEmpty(r) || MultirangeIsEmpty(mr)) + PG_RETURN_BOOL(false); + + + range_deserialize(rangetyp, r, &lower1, &upper1, &empty); + Assert(!empty); + multirange_get_bounds(rangetyp, mr, mr->rangeCount - 1, + &lower2, &upper2); + + PG_RETURN_BOOL(range_cmp_bounds(rangetyp, &upper1, &upper2) <= 0); +} + +Datum +range_overleft_multirange(PG_FUNCTION_ARGS) +{ + RangeType *r = PG_GETARG_RANGE_P(0); + MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1); + TypeCacheEntry *typcache; + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr)); + + PG_RETURN_BOOL(range_overleft_multirange_internal(typcache->rngtype, r, mr)); +} + +Datum +multirange_overleft_range(PG_FUNCTION_ARGS) +{ + MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0); + RangeType *r = PG_GETARG_RANGE_P(1); + TypeCacheEntry *typcache; + RangeBound lower1, + upper1, + lower2, + upper2; + bool empty; + + if (MultirangeIsEmpty(mr) || RangeIsEmpty(r)) + PG_RETURN_BOOL(false); + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr)); + + multirange_get_bounds(typcache->rngtype, mr, mr->rangeCount - 1, + &lower1, &upper1); + range_deserialize(typcache->rngtype, r, &lower2, &upper2, &empty); + Assert(!empty); + + PG_RETURN_BOOL(range_cmp_bounds(typcache->rngtype, &upper1, &upper2) <= 0); +} + +Datum +multirange_overleft_multirange(PG_FUNCTION_ARGS) +{ + MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0); + MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1); + TypeCacheEntry *typcache; + RangeBound lower1, + upper1, + lower2, + upper2; + + if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2)) + PG_RETURN_BOOL(false); + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1)); + + multirange_get_bounds(typcache->rngtype, mr1, mr1->rangeCount - 1, + &lower1, &upper1); + multirange_get_bounds(typcache->rngtype, mr2, mr2->rangeCount - 1, + &lower2, &upper2); + + PG_RETURN_BOOL(range_cmp_bounds(typcache->rngtype, &upper1, &upper2) <= 0); +} + +/* does not extend to left of? */ +bool +range_overright_multirange_internal(TypeCacheEntry *rangetyp, + const RangeType *r, + const MultirangeType *mr) +{ + RangeBound lower1, + upper1, + lower2, + upper2; + bool empty; + + if (RangeIsEmpty(r) || MultirangeIsEmpty(mr)) + PG_RETURN_BOOL(false); + + range_deserialize(rangetyp, r, &lower1, &upper1, &empty); + Assert(!empty); + multirange_get_bounds(rangetyp, mr, 0, &lower2, &upper2); + + return (range_cmp_bounds(rangetyp, &lower1, &lower2) >= 0); +} + +Datum +range_overright_multirange(PG_FUNCTION_ARGS) +{ + RangeType *r = PG_GETARG_RANGE_P(0); + MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1); + TypeCacheEntry *typcache; + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr)); + + PG_RETURN_BOOL(range_overright_multirange_internal(typcache->rngtype, r, mr)); +} + +Datum +multirange_overright_range(PG_FUNCTION_ARGS) +{ + MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0); + RangeType *r = PG_GETARG_RANGE_P(1); + TypeCacheEntry *typcache; + RangeBound lower1, + upper1, + lower2, + upper2; + bool empty; + + if (MultirangeIsEmpty(mr) || RangeIsEmpty(r)) + PG_RETURN_BOOL(false); + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr)); + + multirange_get_bounds(typcache->rngtype, mr, 0, &lower1, &upper1); + range_deserialize(typcache->rngtype, r, &lower2, &upper2, &empty); + Assert(!empty); + + PG_RETURN_BOOL(range_cmp_bounds(typcache->rngtype, &lower1, &lower2) >= 0); +} + +Datum +multirange_overright_multirange(PG_FUNCTION_ARGS) +{ + MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0); + MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1); + TypeCacheEntry *typcache; + RangeBound lower1, + upper1, + lower2, + upper2; + + if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2)) + PG_RETURN_BOOL(false); + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1)); + + multirange_get_bounds(typcache->rngtype, mr1, 0, &lower1, &upper1); + multirange_get_bounds(typcache->rngtype, mr2, 0, &lower2, &upper2); + + PG_RETURN_BOOL(range_cmp_bounds(typcache->rngtype, &lower1, &lower2) >= 0); +} + +/* contains? */ +Datum +multirange_contains_multirange(PG_FUNCTION_ARGS) +{ + MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0); + MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1); + TypeCacheEntry *typcache; + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1)); + + PG_RETURN_BOOL(multirange_contains_multirange_internal(typcache->rngtype, mr1, mr2)); +} + +/* contained by? */ +Datum +multirange_contained_by_multirange(PG_FUNCTION_ARGS) +{ + MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0); + MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1); + TypeCacheEntry *typcache; + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1)); + + PG_RETURN_BOOL(multirange_contains_multirange_internal(typcache->rngtype, mr2, mr1)); +} + +/* + * Test whether multirange mr1 contains every range from another multirange mr2. + */ +bool +multirange_contains_multirange_internal(TypeCacheEntry *rangetyp, + const MultirangeType *mr1, + const MultirangeType *mr2) +{ + int32 range_count1 = mr1->rangeCount; + int32 range_count2 = mr2->rangeCount; + int i1, + i2; + RangeBound lower1, + upper1, + lower2, + upper2; + + /* + * We follow the same logic for empties as ranges: - an empty multirange + * contains an empty range/multirange. - an empty multirange can't contain + * any other range/multirange. - an empty multirange is contained by any + * other range/multirange. + */ + + if (range_count2 == 0) + return true; + if (range_count1 == 0) + return false; + + /* + * Every range in mr2 must be contained by some range in mr1. To avoid + * O(n^2) we walk through both ranges in tandem. + */ + i1 = 0; + multirange_get_bounds(rangetyp, mr1, i1, &lower1, &upper1); + for (i2 = 0; i2 < range_count2; i2++) + { + multirange_get_bounds(rangetyp, mr2, i2, &lower2, &upper2); + + /* Discard r1s while r1 << r2 */ + while (range_cmp_bounds(rangetyp, &upper1, &lower2) < 0) + { + if (++i1 >= range_count1) + return false; + multirange_get_bounds(rangetyp, mr1, i1, &lower1, &upper1); + } + + /* + * If r1 @> r2, go to the next r2, otherwise return false (since every + * r1[n] and r1[n+1] must have a gap). Note this will give weird + * answers if you don't canonicalize, e.g. with a custom + * int2multirange {[1,1], [2,2]} there is a "gap". But that is + * consistent with other range operators, e.g. '[1,1]'::int2range -|- + * '[2,2]'::int2range is false. + */ + if (!range_bounds_contains(rangetyp, &lower1, &upper1, + &lower2, &upper2)) + return false; + } + + /* All ranges in mr2 are satisfied */ + return true; +} + +/* strictly left of? */ +Datum +range_before_multirange(PG_FUNCTION_ARGS) +{ + RangeType *r = PG_GETARG_RANGE_P(0); + MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1); + TypeCacheEntry *typcache; + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr)); + + PG_RETURN_BOOL(range_before_multirange_internal(typcache->rngtype, r, mr)); +} + +Datum +multirange_before_range(PG_FUNCTION_ARGS) +{ + MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0); + RangeType *r = PG_GETARG_RANGE_P(1); + TypeCacheEntry *typcache; + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr)); + + PG_RETURN_BOOL(range_after_multirange_internal(typcache->rngtype, r, mr)); +} + +Datum +multirange_before_multirange(PG_FUNCTION_ARGS) +{ + MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0); + MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1); + TypeCacheEntry *typcache; + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1)); + + PG_RETURN_BOOL(multirange_before_multirange_internal(typcache->rngtype, mr1, mr2)); +} + +/* strictly right of? */ +Datum +range_after_multirange(PG_FUNCTION_ARGS) +{ + RangeType *r = PG_GETARG_RANGE_P(0); + MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1); + TypeCacheEntry *typcache; + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr)); + + PG_RETURN_BOOL(range_after_multirange_internal(typcache->rngtype, r, mr)); +} + +Datum +multirange_after_range(PG_FUNCTION_ARGS) +{ + MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0); + RangeType *r = PG_GETARG_RANGE_P(1); + TypeCacheEntry *typcache; + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr)); + + PG_RETURN_BOOL(range_before_multirange_internal(typcache->rngtype, r, mr)); +} + +Datum +multirange_after_multirange(PG_FUNCTION_ARGS) +{ + MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0); + MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1); + TypeCacheEntry *typcache; + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1)); + + PG_RETURN_BOOL(multirange_before_multirange_internal(typcache->rngtype, mr2, mr1)); +} + +/* strictly left of? (internal version) */ +bool +range_before_multirange_internal(TypeCacheEntry *rangetyp, + const RangeType *r, + const MultirangeType *mr) +{ + RangeBound lower1, + upper1, + lower2, + upper2; + bool empty; + + if (RangeIsEmpty(r) || MultirangeIsEmpty(mr)) + return false; + + range_deserialize(rangetyp, r, &lower1, &upper1, &empty); + Assert(!empty); + + multirange_get_bounds(rangetyp, mr, 0, &lower2, &upper2); + + return (range_cmp_bounds(rangetyp, &upper1, &lower2) < 0); +} + +bool +multirange_before_multirange_internal(TypeCacheEntry *rangetyp, + const MultirangeType *mr1, + const MultirangeType *mr2) +{ + RangeBound lower1, + upper1, + lower2, + upper2; + + if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2)) + return false; + + multirange_get_bounds(rangetyp, mr1, mr1->rangeCount - 1, + &lower1, &upper1); + multirange_get_bounds(rangetyp, mr2, 0, + &lower2, &upper2); + + return (range_cmp_bounds(rangetyp, &upper1, &lower2) < 0); +} + +/* strictly right of? (internal version) */ +bool +range_after_multirange_internal(TypeCacheEntry *rangetyp, + const RangeType *r, + const MultirangeType *mr) +{ + RangeBound lower1, + upper1, + lower2, + upper2; + bool empty; + int32 range_count; + + if (RangeIsEmpty(r) || MultirangeIsEmpty(mr)) + return false; + + range_deserialize(rangetyp, r, &lower1, &upper1, &empty); + Assert(!empty); + + range_count = mr->rangeCount; + multirange_get_bounds(rangetyp, mr, range_count - 1, + &lower2, &upper2); + + return (range_cmp_bounds(rangetyp, &lower1, &upper2) > 0); +} + +bool +range_adjacent_multirange_internal(TypeCacheEntry *rangetyp, + const RangeType *r, + const MultirangeType *mr) +{ + RangeBound lower1, + upper1, + lower2, + upper2; + bool empty; + int32 range_count; + + if (RangeIsEmpty(r) || MultirangeIsEmpty(mr)) + return false; + + range_deserialize(rangetyp, r, &lower1, &upper1, &empty); + Assert(!empty); + + range_count = mr->rangeCount; + multirange_get_bounds(rangetyp, mr, 0, + &lower2, &upper2); + + if (bounds_adjacent(rangetyp, upper1, lower2)) + return true; + + if (range_count > 1) + multirange_get_bounds(rangetyp, mr, range_count - 1, + &lower2, &upper2); + + if (bounds_adjacent(rangetyp, upper2, lower1)) + return true; + + return false; +} + +/* adjacent to? */ +Datum +range_adjacent_multirange(PG_FUNCTION_ARGS) +{ + RangeType *r = PG_GETARG_RANGE_P(0); + MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1); + TypeCacheEntry *typcache; + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr)); + + PG_RETURN_BOOL(range_adjacent_multirange_internal(typcache->rngtype, r, mr)); +} + +Datum +multirange_adjacent_range(PG_FUNCTION_ARGS) +{ + MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0); + RangeType *r = PG_GETARG_RANGE_P(1); + TypeCacheEntry *typcache; + + if (RangeIsEmpty(r) || MultirangeIsEmpty(mr)) + return false; + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr)); + + PG_RETURN_BOOL(range_adjacent_multirange_internal(typcache->rngtype, r, mr)); +} + +Datum +multirange_adjacent_multirange(PG_FUNCTION_ARGS) +{ + MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0); + MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1); + TypeCacheEntry *typcache; + int32 range_count1; + int32 range_count2; + RangeBound lower1, + upper1, + lower2, + upper2; + + if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2)) + return false; + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1)); + + range_count1 = mr1->rangeCount; + range_count2 = mr2->rangeCount; + multirange_get_bounds(typcache->rngtype, mr1, range_count1 - 1, + &lower1, &upper1); + multirange_get_bounds(typcache->rngtype, mr2, 0, + &lower2, &upper2); + if (bounds_adjacent(typcache->rngtype, upper1, lower2)) + PG_RETURN_BOOL(true); + + if (range_count1 > 1) + multirange_get_bounds(typcache->rngtype, mr1, 0, + &lower1, &upper1); + if (range_count2 > 1) + multirange_get_bounds(typcache->rngtype, mr2, range_count2 - 1, + &lower2, &upper2); + if (bounds_adjacent(typcache->rngtype, upper2, lower1)) + PG_RETURN_BOOL(true); + PG_RETURN_BOOL(false); +} + +/* Btree support */ + +/* btree comparator */ +Datum +multirange_cmp(PG_FUNCTION_ARGS) +{ + MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0); + MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1); + int32 range_count_1; + int32 range_count_2; + int32 range_count_max; + int32 i; + TypeCacheEntry *typcache; + int cmp = 0; /* If both are empty we'll use this. */ + + /* Different types should be prevented by ANYMULTIRANGE matching rules */ + if (MultirangeTypeGetOid(mr1) != MultirangeTypeGetOid(mr2)) + elog(ERROR, "multirange types do not match"); + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1)); + + range_count_1 = mr1->rangeCount; + range_count_2 = mr2->rangeCount; + + /* Loop over source data */ + range_count_max = Max(range_count_1, range_count_2); + for (i = 0; i < range_count_max; i++) + { + RangeBound lower1, + upper1, + lower2, + upper2; + + /* + * If one multirange is shorter, it's as if it had empty ranges at the + * end to extend its length. An empty range compares earlier than any + * other range, so the shorter multirange comes before the longer. + * This is the same behavior as in other types, e.g. in strings 'aaa' + * < 'aaaaaa'. + */ + if (i >= range_count_1) + { + cmp = -1; + break; + } + if (i >= range_count_2) + { + cmp = 1; + break; + } + + multirange_get_bounds(typcache->rngtype, mr1, i, &lower1, &upper1); + multirange_get_bounds(typcache->rngtype, mr2, i, &lower2, &upper2); + + cmp = range_cmp_bounds(typcache->rngtype, &lower1, &lower2); + if (cmp == 0) + cmp = range_cmp_bounds(typcache->rngtype, &upper1, &upper2); + if (cmp != 0) + break; + } + + PG_FREE_IF_COPY(mr1, 0); + PG_FREE_IF_COPY(mr2, 1); + + PG_RETURN_INT32(cmp); +} + +/* inequality operators using the multirange_cmp function */ +Datum +multirange_lt(PG_FUNCTION_ARGS) +{ + int cmp = multirange_cmp(fcinfo); + + PG_RETURN_BOOL(cmp < 0); +} + +Datum +multirange_le(PG_FUNCTION_ARGS) +{ + int cmp = multirange_cmp(fcinfo); + + PG_RETURN_BOOL(cmp <= 0); +} + +Datum +multirange_ge(PG_FUNCTION_ARGS) +{ + int cmp = multirange_cmp(fcinfo); + + PG_RETURN_BOOL(cmp >= 0); +} + +Datum +multirange_gt(PG_FUNCTION_ARGS) +{ + int cmp = multirange_cmp(fcinfo); + + PG_RETURN_BOOL(cmp > 0); +} + +/* multirange -> range functions */ + +/* Find the smallest range that includes everything in the multirange */ +Datum +range_merge_from_multirange(PG_FUNCTION_ARGS) +{ + MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0); + Oid mltrngtypoid = MultirangeTypeGetOid(mr); + TypeCacheEntry *typcache; + RangeType *result; + + typcache = multirange_get_typcache(fcinfo, mltrngtypoid); + + if (MultirangeIsEmpty(mr)) + { + result = make_empty_range(typcache->rngtype); + } + else if (mr->rangeCount == 1) + { + result = multirange_get_range(typcache->rngtype, mr, 0); + } + else + { + RangeBound firstLower, + firstUpper, + lastLower, + lastUpper; + + multirange_get_bounds(typcache->rngtype, mr, 0, + &firstLower, &firstUpper); + multirange_get_bounds(typcache->rngtype, mr, mr->rangeCount - 1, + &lastLower, &lastUpper); + + result = make_range(typcache->rngtype, &firstLower, &lastUpper, + false, NULL); + } + + PG_RETURN_RANGE_P(result); +} + +/* Turn multirange into a set of ranges */ +Datum +multirange_unnest(PG_FUNCTION_ARGS) +{ + typedef struct + { + MultirangeType *mr; + TypeCacheEntry *typcache; + int index; + } multirange_unnest_fctx; + + FuncCallContext *funcctx; + multirange_unnest_fctx *fctx; + MemoryContext oldcontext; + + /* stuff done only on the first call of the function */ + if (SRF_IS_FIRSTCALL()) + { + MultirangeType *mr; + + /* 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); + + /* + * Get the multirange value and detoast if needed. We can't do this + * earlier because if we have to detoast, we want the detoasted copy + * to be in multi_call_memory_ctx, so it will go away when we're done + * and not before. (If no detoast happens, we assume the originally + * passed multirange will stick around till then.) + */ + mr = PG_GETARG_MULTIRANGE_P(0); + + /* allocate memory for user context */ + fctx = (multirange_unnest_fctx *) palloc(sizeof(multirange_unnest_fctx)); + + /* initialize state */ + fctx->mr = mr; + fctx->index = 0; + fctx->typcache = lookup_type_cache(MultirangeTypeGetOid(mr), + TYPECACHE_MULTIRANGE_INFO); + + funcctx->user_fctx = fctx; + MemoryContextSwitchTo(oldcontext); + } + + /* stuff done on every call of the function */ + funcctx = SRF_PERCALL_SETUP(); + fctx = funcctx->user_fctx; + + if (fctx->index < fctx->mr->rangeCount) + { + RangeType *range; + + range = multirange_get_range(fctx->typcache->rngtype, + fctx->mr, + fctx->index); + fctx->index++; + + SRF_RETURN_NEXT(funcctx, RangeTypePGetDatum(range)); + } + else + { + /* do when there is no more left */ + SRF_RETURN_DONE(funcctx); + } +} + +/* Hash support */ + +/* hash a multirange value */ +Datum +hash_multirange(PG_FUNCTION_ARGS) +{ + MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0); + uint32 result = 1; + TypeCacheEntry *typcache, + *scache; + int32 range_count, + i; + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr)); + scache = typcache->rngtype->rngelemtype; + if (!OidIsValid(scache->hash_proc_finfo.fn_oid)) + { + scache = lookup_type_cache(scache->type_id, + TYPECACHE_HASH_PROC_FINFO); + if (!OidIsValid(scache->hash_proc_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify a hash function for type %s", + format_type_be(scache->type_id)))); + } + + range_count = mr->rangeCount; + for (i = 0; i < range_count; i++) + { + RangeBound lower, + upper; + uint8 flags = MultirangeGetFlagsPtr(mr)[i]; + uint32 lower_hash; + uint32 upper_hash; + uint32 range_hash; + + multirange_get_bounds(typcache->rngtype, mr, i, &lower, &upper); + + if (RANGE_HAS_LBOUND(flags)) + lower_hash = DatumGetUInt32(FunctionCall1Coll(&scache->hash_proc_finfo, + typcache->rngtype->rng_collation, + lower.val)); + else + lower_hash = 0; + + if (RANGE_HAS_UBOUND(flags)) + upper_hash = DatumGetUInt32(FunctionCall1Coll(&scache->hash_proc_finfo, + typcache->rngtype->rng_collation, + upper.val)); + else + upper_hash = 0; + + /* Merge hashes of flags and bounds */ + range_hash = hash_uint32((uint32) flags); + range_hash ^= lower_hash; + range_hash = pg_rotate_left32(range_hash, 1); + range_hash ^= upper_hash; + + /* + * Use the same approach as hash_array to combine the individual + * elements' hash values: + */ + result = (result << 5) - result + range_hash; + } + + PG_FREE_IF_COPY(mr, 0); + + PG_RETURN_UINT32(result); +} + +/* + * Returns 64-bit value by hashing a value to a 64-bit value, with a seed. + * Otherwise, similar to hash_multirange. + */ +Datum +hash_multirange_extended(PG_FUNCTION_ARGS) +{ + MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0); + Datum seed = PG_GETARG_DATUM(1); + uint64 result = 1; + TypeCacheEntry *typcache, + *scache; + int32 range_count, + i; + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr)); + scache = typcache->rngtype->rngelemtype; + if (!OidIsValid(scache->hash_extended_proc_finfo.fn_oid)) + { + scache = lookup_type_cache(scache->type_id, + TYPECACHE_HASH_EXTENDED_PROC_FINFO); + if (!OidIsValid(scache->hash_extended_proc_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify a hash function for type %s", + format_type_be(scache->type_id)))); + } + + range_count = mr->rangeCount; + for (i = 0; i < range_count; i++) + { + RangeBound lower, + upper; + uint8 flags = MultirangeGetFlagsPtr(mr)[i]; + uint64 lower_hash; + uint64 upper_hash; + uint64 range_hash; + + multirange_get_bounds(typcache->rngtype, mr, i, &lower, &upper); + + if (RANGE_HAS_LBOUND(flags)) + lower_hash = DatumGetUInt64(FunctionCall2Coll(&scache->hash_extended_proc_finfo, + typcache->rngtype->rng_collation, + lower.val, + seed)); + else + lower_hash = 0; + + if (RANGE_HAS_UBOUND(flags)) + upper_hash = DatumGetUInt64(FunctionCall2Coll(&scache->hash_extended_proc_finfo, + typcache->rngtype->rng_collation, + upper.val, + seed)); + else + upper_hash = 0; + + /* Merge hashes of flags and bounds */ + range_hash = DatumGetUInt64(hash_uint32_extended((uint32) flags, + DatumGetInt64(seed))); + range_hash ^= lower_hash; + range_hash = ROTATE_HIGH_AND_LOW_32BITS(range_hash); + range_hash ^= upper_hash; + + /* + * Use the same approach as hash_array to combine the individual + * elements' hash values: + */ + result = (result << 5) - result + range_hash; + } + + PG_FREE_IF_COPY(mr, 0); + + PG_RETURN_UINT64(result); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/multirangetypes_selfuncs.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/multirangetypes_selfuncs.c new file mode 100644 index 00000000000..cefc4710fd4 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/multirangetypes_selfuncs.c @@ -0,0 +1,1337 @@ +/*------------------------------------------------------------------------- + * + * multirangetypes_selfuncs.c + * Functions for selectivity estimation of multirange operators + * + * Estimates are based on histograms of lower and upper bounds, and the + * fraction of empty multiranges. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/adt/multirangetypes_selfuncs.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <math.h> + +#include "access/htup_details.h" +#include "catalog/pg_operator.h" +#include "catalog/pg_statistic.h" +#include "catalog/pg_type.h" +#include "utils/float.h" +#include "utils/fmgrprotos.h" +#include "utils/lsyscache.h" +#include "utils/rangetypes.h" +#include "utils/multirangetypes.h" +#include "utils/selfuncs.h" +#include "utils/typcache.h" + +static double calc_multirangesel(TypeCacheEntry *typcache, + VariableStatData *vardata, + const MultirangeType *constval, Oid operator); +static double default_multirange_selectivity(Oid operator); +static double calc_hist_selectivity(TypeCacheEntry *typcache, + VariableStatData *vardata, + const MultirangeType *constval, + Oid operator); +static double calc_hist_selectivity_scalar(TypeCacheEntry *typcache, + const RangeBound *constbound, + const RangeBound *hist, + int hist_nvalues, bool equal); +static int rbound_bsearch(TypeCacheEntry *typcache, const RangeBound *value, + const RangeBound *hist, int hist_length, bool equal); +static float8 get_position(TypeCacheEntry *typcache, const RangeBound *value, + const RangeBound *hist1, const RangeBound *hist2); +static float8 get_len_position(double value, double hist1, double hist2); +static float8 get_distance(TypeCacheEntry *typcache, const RangeBound *bound1, + const RangeBound *bound2); +static int length_hist_bsearch(Datum *length_hist_values, + int length_hist_nvalues, double value, + bool equal); +static double calc_length_hist_frac(Datum *length_hist_values, + int length_hist_nvalues, double length1, + double length2, bool equal); +static double calc_hist_selectivity_contained(TypeCacheEntry *typcache, + const RangeBound *lower, + RangeBound *upper, + const RangeBound *hist_lower, + int hist_nvalues, + Datum *length_hist_values, + int length_hist_nvalues); +static double calc_hist_selectivity_contains(TypeCacheEntry *typcache, + const RangeBound *lower, + const RangeBound *upper, + const RangeBound *hist_lower, + int hist_nvalues, + Datum *length_hist_values, + int length_hist_nvalues); + +/* + * Returns a default selectivity estimate for given operator, when we don't + * have statistics or cannot use them for some reason. + */ +static double +default_multirange_selectivity(Oid operator) +{ + switch (operator) + { + case OID_MULTIRANGE_OVERLAPS_MULTIRANGE_OP: + case OID_MULTIRANGE_OVERLAPS_RANGE_OP: + case OID_RANGE_OVERLAPS_MULTIRANGE_OP: + return 0.01; + + case OID_RANGE_CONTAINS_MULTIRANGE_OP: + case OID_RANGE_MULTIRANGE_CONTAINED_OP: + case OID_MULTIRANGE_CONTAINS_RANGE_OP: + case OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP: + case OID_MULTIRANGE_RANGE_CONTAINED_OP: + case OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP: + return 0.005; + + case OID_MULTIRANGE_CONTAINS_ELEM_OP: + case OID_MULTIRANGE_ELEM_CONTAINED_OP: + + /* + * "multirange @> elem" is more or less identical to a scalar + * inequality "A >= b AND A <= c". + */ + return DEFAULT_MULTIRANGE_INEQ_SEL; + + case OID_MULTIRANGE_LESS_OP: + case OID_MULTIRANGE_LESS_EQUAL_OP: + case OID_MULTIRANGE_GREATER_OP: + case OID_MULTIRANGE_GREATER_EQUAL_OP: + case OID_MULTIRANGE_LEFT_RANGE_OP: + case OID_MULTIRANGE_LEFT_MULTIRANGE_OP: + case OID_RANGE_LEFT_MULTIRANGE_OP: + case OID_MULTIRANGE_RIGHT_RANGE_OP: + case OID_MULTIRANGE_RIGHT_MULTIRANGE_OP: + case OID_RANGE_RIGHT_MULTIRANGE_OP: + case OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP: + case OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP: + case OID_MULTIRANGE_OVERLAPS_LEFT_MULTIRANGE_OP: + case OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP: + case OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP: + case OID_MULTIRANGE_OVERLAPS_RIGHT_MULTIRANGE_OP: + /* these are similar to regular scalar inequalities */ + return DEFAULT_INEQ_SEL; + + default: + + /* + * all multirange operators should be handled above, but just in + * case + */ + return 0.01; + } +} + +/* + * multirangesel -- restriction selectivity for multirange operators + */ +Datum +multirangesel(PG_FUNCTION_ARGS) +{ + PlannerInfo *root = (PlannerInfo *) PG_GETARG_POINTER(0); + Oid operator = PG_GETARG_OID(1); + List *args = (List *) PG_GETARG_POINTER(2); + int varRelid = PG_GETARG_INT32(3); + VariableStatData vardata; + Node *other; + bool varonleft; + Selectivity selec; + TypeCacheEntry *typcache = NULL; + MultirangeType *constmultirange = NULL; + RangeType *constrange = NULL; + + /* + * If expression is not (variable op something) or (something op + * variable), then punt and return a default estimate. + */ + if (!get_restriction_variable(root, args, varRelid, + &vardata, &other, &varonleft)) + PG_RETURN_FLOAT8(default_multirange_selectivity(operator)); + + /* + * Can't do anything useful if the something is not a constant, either. + */ + if (!IsA(other, Const)) + { + ReleaseVariableStats(vardata); + PG_RETURN_FLOAT8(default_multirange_selectivity(operator)); + } + + /* + * All the multirange operators are strict, so we can cope with a NULL + * constant right away. + */ + if (((Const *) other)->constisnull) + { + ReleaseVariableStats(vardata); + PG_RETURN_FLOAT8(0.0); + } + + /* + * If var is on the right, commute the operator, so that we can assume the + * var is on the left in what follows. + */ + if (!varonleft) + { + /* we have other Op var, commute to make var Op other */ + operator = get_commutator(operator); + if (!operator) + { + /* Use default selectivity (should we raise an error instead?) */ + ReleaseVariableStats(vardata); + PG_RETURN_FLOAT8(default_multirange_selectivity(operator)); + } + } + + /* + * OK, there's a Var and a Const we're dealing with here. We need the + * Const to be of same multirange type as the column, else we can't do + * anything useful. (Such cases will likely fail at runtime, but here we'd + * rather just return a default estimate.) + * + * If the operator is "multirange @> element", the constant should be of + * the element type of the multirange column. Convert it to a multirange + * that includes only that single point, so that we don't need special + * handling for that in what follows. + */ + if (operator == OID_MULTIRANGE_CONTAINS_ELEM_OP) + { + typcache = multirange_get_typcache(fcinfo, vardata.vartype); + + if (((Const *) other)->consttype == typcache->rngtype->rngelemtype->type_id) + { + RangeBound lower, + upper; + + lower.inclusive = true; + lower.val = ((Const *) other)->constvalue; + lower.infinite = false; + lower.lower = true; + upper.inclusive = true; + upper.val = ((Const *) other)->constvalue; + upper.infinite = false; + upper.lower = false; + constrange = range_serialize(typcache->rngtype, &lower, &upper, + false, NULL); + constmultirange = make_multirange(typcache->type_id, typcache->rngtype, + 1, &constrange); + } + } + else if (operator == OID_RANGE_MULTIRANGE_CONTAINED_OP || + operator == OID_MULTIRANGE_CONTAINS_RANGE_OP || + operator == OID_MULTIRANGE_OVERLAPS_RANGE_OP || + operator == OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP || + operator == OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP || + operator == OID_MULTIRANGE_LEFT_RANGE_OP || + operator == OID_MULTIRANGE_RIGHT_RANGE_OP) + { + /* + * Promote a range in "multirange OP range" just like we do an element + * in "multirange OP element". + */ + typcache = multirange_get_typcache(fcinfo, vardata.vartype); + if (((Const *) other)->consttype == typcache->rngtype->type_id) + { + constrange = DatumGetRangeTypeP(((Const *) other)->constvalue); + constmultirange = make_multirange(typcache->type_id, typcache->rngtype, + 1, &constrange); + } + } + else if (operator == OID_RANGE_OVERLAPS_MULTIRANGE_OP || + operator == OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP || + operator == OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP || + operator == OID_RANGE_LEFT_MULTIRANGE_OP || + operator == OID_RANGE_RIGHT_MULTIRANGE_OP || + operator == OID_RANGE_CONTAINS_MULTIRANGE_OP || + operator == OID_MULTIRANGE_ELEM_CONTAINED_OP || + operator == OID_MULTIRANGE_RANGE_CONTAINED_OP) + { + /* + * Here, the Var is the elem/range, not the multirange. For now we + * just punt and return the default estimate. In future we could + * disassemble the multirange constant to do something more + * intelligent. + */ + } + else if (((Const *) other)->consttype == vardata.vartype) + { + /* Both sides are the same multirange type */ + typcache = multirange_get_typcache(fcinfo, vardata.vartype); + + constmultirange = DatumGetMultirangeTypeP(((Const *) other)->constvalue); + } + + /* + * If we got a valid constant on one side of the operator, proceed to + * estimate using statistics. Otherwise punt and return a default constant + * estimate. Note that calc_multirangesel need not handle + * OID_MULTIRANGE_*_CONTAINED_OP. + */ + if (constmultirange) + selec = calc_multirangesel(typcache, &vardata, constmultirange, operator); + else + selec = default_multirange_selectivity(operator); + + ReleaseVariableStats(vardata); + + CLAMP_PROBABILITY(selec); + + PG_RETURN_FLOAT8((float8) selec); +} + +static double +calc_multirangesel(TypeCacheEntry *typcache, VariableStatData *vardata, + const MultirangeType *constval, Oid operator) +{ + double hist_selec; + double selec; + float4 empty_frac, + null_frac; + + /* + * First look up the fraction of NULLs and empty multiranges from + * pg_statistic. + */ + if (HeapTupleIsValid(vardata->statsTuple)) + { + Form_pg_statistic stats; + AttStatsSlot sslot; + + stats = (Form_pg_statistic) GETSTRUCT(vardata->statsTuple); + null_frac = stats->stanullfrac; + + /* Try to get fraction of empty multiranges */ + if (get_attstatsslot(&sslot, vardata->statsTuple, + STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM, + InvalidOid, + ATTSTATSSLOT_NUMBERS)) + { + if (sslot.nnumbers != 1) + elog(ERROR, "invalid empty fraction statistic"); /* shouldn't happen */ + empty_frac = sslot.numbers[0]; + free_attstatsslot(&sslot); + } + else + { + /* No empty fraction statistic. Assume no empty ranges. */ + empty_frac = 0.0; + } + } + else + { + /* + * No stats are available. Follow through the calculations below + * anyway, assuming no NULLs and no empty multiranges. This still + * allows us to give a better-than-nothing estimate based on whether + * the constant is an empty multirange or not. + */ + null_frac = 0.0; + empty_frac = 0.0; + } + + if (MultirangeIsEmpty(constval)) + { + /* + * An empty multirange matches all multiranges, all empty multiranges, + * or nothing, depending on the operator + */ + switch (operator) + { + /* these return false if either argument is empty */ + case OID_MULTIRANGE_OVERLAPS_RANGE_OP: + case OID_MULTIRANGE_OVERLAPS_MULTIRANGE_OP: + case OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP: + case OID_MULTIRANGE_OVERLAPS_LEFT_MULTIRANGE_OP: + case OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP: + case OID_MULTIRANGE_OVERLAPS_RIGHT_MULTIRANGE_OP: + case OID_MULTIRANGE_LEFT_RANGE_OP: + case OID_MULTIRANGE_LEFT_MULTIRANGE_OP: + case OID_MULTIRANGE_RIGHT_RANGE_OP: + case OID_MULTIRANGE_RIGHT_MULTIRANGE_OP: + /* nothing is less than an empty multirange */ + case OID_MULTIRANGE_LESS_OP: + selec = 0.0; + break; + + /* + * only empty multiranges can be contained by an empty + * multirange + */ + case OID_RANGE_MULTIRANGE_CONTAINED_OP: + case OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP: + /* only empty ranges are <= an empty multirange */ + case OID_MULTIRANGE_LESS_EQUAL_OP: + selec = empty_frac; + break; + + /* everything contains an empty multirange */ + case OID_MULTIRANGE_CONTAINS_RANGE_OP: + case OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP: + /* everything is >= an empty multirange */ + case OID_MULTIRANGE_GREATER_EQUAL_OP: + selec = 1.0; + break; + + /* all non-empty multiranges are > an empty multirange */ + case OID_MULTIRANGE_GREATER_OP: + selec = 1.0 - empty_frac; + break; + + /* an element cannot be empty */ + case OID_MULTIRANGE_CONTAINS_ELEM_OP: + + /* filtered out by multirangesel() */ + case OID_RANGE_OVERLAPS_MULTIRANGE_OP: + case OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP: + case OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP: + case OID_RANGE_LEFT_MULTIRANGE_OP: + case OID_RANGE_RIGHT_MULTIRANGE_OP: + case OID_RANGE_CONTAINS_MULTIRANGE_OP: + case OID_MULTIRANGE_ELEM_CONTAINED_OP: + case OID_MULTIRANGE_RANGE_CONTAINED_OP: + + default: + elog(ERROR, "unexpected operator %u", operator); + selec = 0.0; /* keep compiler quiet */ + break; + } + } + else + { + /* + * Calculate selectivity using bound histograms. If that fails for + * some reason, e.g no histogram in pg_statistic, use the default + * constant estimate for the fraction of non-empty values. This is + * still somewhat better than just returning the default estimate, + * because this still takes into account the fraction of empty and + * NULL tuples, if we had statistics for them. + */ + hist_selec = calc_hist_selectivity(typcache, vardata, constval, + operator); + if (hist_selec < 0.0) + hist_selec = default_multirange_selectivity(operator); + + /* + * Now merge the results for the empty multiranges and histogram + * calculations, realizing that the histogram covers only the + * non-null, non-empty values. + */ + if (operator == OID_RANGE_MULTIRANGE_CONTAINED_OP || + operator == OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP) + { + /* empty is contained by anything non-empty */ + selec = (1.0 - empty_frac) * hist_selec + empty_frac; + } + else + { + /* with any other operator, empty Op non-empty matches nothing */ + selec = (1.0 - empty_frac) * hist_selec; + } + } + + /* all multirange operators are strict */ + selec *= (1.0 - null_frac); + + /* result should be in range, but make sure... */ + CLAMP_PROBABILITY(selec); + + return selec; +} + +/* + * Calculate multirange operator selectivity using histograms of multirange bounds. + * + * This estimate is for the portion of values that are not empty and not + * NULL. + */ +static double +calc_hist_selectivity(TypeCacheEntry *typcache, VariableStatData *vardata, + const MultirangeType *constval, Oid operator) +{ + TypeCacheEntry *rng_typcache = typcache->rngtype; + AttStatsSlot hslot; + AttStatsSlot lslot; + int nhist; + RangeBound *hist_lower; + RangeBound *hist_upper; + int i; + RangeBound const_lower; + RangeBound const_upper; + RangeBound tmp; + double hist_selec; + + /* Can't use the histogram with insecure multirange support functions */ + if (!statistic_proc_security_check(vardata, + rng_typcache->rng_cmp_proc_finfo.fn_oid)) + return -1; + if (OidIsValid(rng_typcache->rng_subdiff_finfo.fn_oid) && + !statistic_proc_security_check(vardata, + rng_typcache->rng_subdiff_finfo.fn_oid)) + return -1; + + /* Try to get histogram of ranges */ + if (!(HeapTupleIsValid(vardata->statsTuple) && + get_attstatsslot(&hslot, vardata->statsTuple, + STATISTIC_KIND_BOUNDS_HISTOGRAM, InvalidOid, + ATTSTATSSLOT_VALUES))) + return -1.0; + + /* check that it's a histogram, not just a dummy entry */ + if (hslot.nvalues < 2) + { + free_attstatsslot(&hslot); + return -1.0; + } + + /* + * Convert histogram of ranges into histograms of its lower and upper + * bounds. + */ + nhist = hslot.nvalues; + hist_lower = (RangeBound *) palloc(sizeof(RangeBound) * nhist); + hist_upper = (RangeBound *) palloc(sizeof(RangeBound) * nhist); + for (i = 0; i < nhist; i++) + { + bool empty; + + range_deserialize(rng_typcache, DatumGetRangeTypeP(hslot.values[i]), + &hist_lower[i], &hist_upper[i], &empty); + /* The histogram should not contain any empty ranges */ + if (empty) + elog(ERROR, "bounds histogram contains an empty range"); + } + + /* @> and @< also need a histogram of range lengths */ + if (operator == OID_MULTIRANGE_CONTAINS_RANGE_OP || + operator == OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP || + operator == OID_MULTIRANGE_RANGE_CONTAINED_OP || + operator == OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP) + { + if (!(HeapTupleIsValid(vardata->statsTuple) && + get_attstatsslot(&lslot, vardata->statsTuple, + STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM, + InvalidOid, + ATTSTATSSLOT_VALUES))) + { + free_attstatsslot(&hslot); + return -1.0; + } + + /* check that it's a histogram, not just a dummy entry */ + if (lslot.nvalues < 2) + { + free_attstatsslot(&lslot); + free_attstatsslot(&hslot); + return -1.0; + } + } + else + memset(&lslot, 0, sizeof(lslot)); + + /* Extract the bounds of the constant value. */ + Assert(constval->rangeCount > 0); + multirange_get_bounds(rng_typcache, constval, 0, + &const_lower, &tmp); + multirange_get_bounds(rng_typcache, constval, constval->rangeCount - 1, + &tmp, &const_upper); + + /* + * Calculate selectivity comparing the lower or upper bound of the + * constant with the histogram of lower or upper bounds. + */ + switch (operator) + { + case OID_MULTIRANGE_LESS_OP: + + /* + * The regular b-tree comparison operators (<, <=, >, >=) compare + * the lower bounds first, and the upper bounds for values with + * equal lower bounds. Estimate that by comparing the lower bounds + * only. This gives a fairly accurate estimate assuming there + * aren't many rows with a lower bound equal to the constant's + * lower bound. + */ + hist_selec = + calc_hist_selectivity_scalar(rng_typcache, &const_lower, + hist_lower, nhist, false); + break; + + case OID_MULTIRANGE_LESS_EQUAL_OP: + hist_selec = + calc_hist_selectivity_scalar(rng_typcache, &const_lower, + hist_lower, nhist, true); + break; + + case OID_MULTIRANGE_GREATER_OP: + hist_selec = + 1 - calc_hist_selectivity_scalar(rng_typcache, &const_lower, + hist_lower, nhist, false); + break; + + case OID_MULTIRANGE_GREATER_EQUAL_OP: + hist_selec = + 1 - calc_hist_selectivity_scalar(rng_typcache, &const_lower, + hist_lower, nhist, true); + break; + + case OID_MULTIRANGE_LEFT_RANGE_OP: + case OID_MULTIRANGE_LEFT_MULTIRANGE_OP: + /* var << const when upper(var) < lower(const) */ + hist_selec = + calc_hist_selectivity_scalar(rng_typcache, &const_lower, + hist_upper, nhist, false); + break; + + case OID_MULTIRANGE_RIGHT_RANGE_OP: + case OID_MULTIRANGE_RIGHT_MULTIRANGE_OP: + /* var >> const when lower(var) > upper(const) */ + hist_selec = + 1 - calc_hist_selectivity_scalar(rng_typcache, &const_upper, + hist_lower, nhist, true); + break; + + case OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP: + case OID_MULTIRANGE_OVERLAPS_RIGHT_MULTIRANGE_OP: + /* compare lower bounds */ + hist_selec = + 1 - calc_hist_selectivity_scalar(rng_typcache, &const_lower, + hist_lower, nhist, false); + break; + + case OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP: + case OID_MULTIRANGE_OVERLAPS_LEFT_MULTIRANGE_OP: + /* compare upper bounds */ + hist_selec = + calc_hist_selectivity_scalar(rng_typcache, &const_upper, + hist_upper, nhist, true); + break; + + case OID_MULTIRANGE_OVERLAPS_RANGE_OP: + case OID_MULTIRANGE_OVERLAPS_MULTIRANGE_OP: + case OID_MULTIRANGE_CONTAINS_ELEM_OP: + + /* + * A && B <=> NOT (A << B OR A >> B). + * + * Since A << B and A >> B are mutually exclusive events we can + * sum their probabilities to find probability of (A << B OR A >> + * B). + * + * "multirange @> elem" is equivalent to "multirange && + * {[elem,elem]}". The caller already constructed the singular + * range from the element constant, so just treat it the same as + * &&. + */ + hist_selec = + calc_hist_selectivity_scalar(rng_typcache, + &const_lower, hist_upper, + nhist, false); + hist_selec += + (1.0 - calc_hist_selectivity_scalar(rng_typcache, + &const_upper, hist_lower, + nhist, true)); + hist_selec = 1.0 - hist_selec; + break; + + case OID_MULTIRANGE_CONTAINS_RANGE_OP: + case OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP: + hist_selec = + calc_hist_selectivity_contains(rng_typcache, &const_lower, + &const_upper, hist_lower, nhist, + lslot.values, lslot.nvalues); + break; + + case OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP: + case OID_RANGE_MULTIRANGE_CONTAINED_OP: + if (const_lower.infinite) + { + /* + * Lower bound no longer matters. Just estimate the fraction + * with an upper bound <= const upper bound + */ + hist_selec = + calc_hist_selectivity_scalar(rng_typcache, &const_upper, + hist_upper, nhist, true); + } + else if (const_upper.infinite) + { + hist_selec = + 1.0 - calc_hist_selectivity_scalar(rng_typcache, &const_lower, + hist_lower, nhist, false); + } + else + { + hist_selec = + calc_hist_selectivity_contained(rng_typcache, &const_lower, + &const_upper, hist_lower, nhist, + lslot.values, lslot.nvalues); + } + break; + + /* filtered out by multirangesel() */ + case OID_RANGE_OVERLAPS_MULTIRANGE_OP: + case OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP: + case OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP: + case OID_RANGE_LEFT_MULTIRANGE_OP: + case OID_RANGE_RIGHT_MULTIRANGE_OP: + case OID_RANGE_CONTAINS_MULTIRANGE_OP: + case OID_MULTIRANGE_ELEM_CONTAINED_OP: + case OID_MULTIRANGE_RANGE_CONTAINED_OP: + + default: + elog(ERROR, "unknown multirange operator %u", operator); + hist_selec = -1.0; /* keep compiler quiet */ + break; + } + + free_attstatsslot(&lslot); + free_attstatsslot(&hslot); + + return hist_selec; +} + + +/* + * Look up the fraction of values less than (or equal, if 'equal' argument + * is true) a given const in a histogram of range bounds. + */ +static double +calc_hist_selectivity_scalar(TypeCacheEntry *typcache, const RangeBound *constbound, + const RangeBound *hist, int hist_nvalues, bool equal) +{ + Selectivity selec; + int index; + + /* + * Find the histogram bin the given constant falls into. Estimate + * selectivity as the number of preceding whole bins. + */ + index = rbound_bsearch(typcache, constbound, hist, hist_nvalues, equal); + selec = (Selectivity) (Max(index, 0)) / (Selectivity) (hist_nvalues - 1); + + /* Adjust using linear interpolation within the bin */ + if (index >= 0 && index < hist_nvalues - 1) + selec += get_position(typcache, constbound, &hist[index], + &hist[index + 1]) / (Selectivity) (hist_nvalues - 1); + + return selec; +} + +/* + * Binary search on an array of range bounds. Returns greatest index of range + * bound in array which is less(less or equal) than given range bound. If all + * range bounds in array are greater or equal(greater) than given range bound, + * return -1. When "equal" flag is set conditions in brackets are used. + * + * This function is used in scalar operator selectivity estimation. Another + * goal of this function is to find a histogram bin where to stop + * interpolation of portion of bounds which are less than or equal to given bound. + */ +static int +rbound_bsearch(TypeCacheEntry *typcache, const RangeBound *value, const RangeBound *hist, + int hist_length, bool equal) +{ + int lower = -1, + upper = hist_length - 1, + cmp, + middle; + + while (lower < upper) + { + middle = (lower + upper + 1) / 2; + cmp = range_cmp_bounds(typcache, &hist[middle], value); + + if (cmp < 0 || (equal && cmp == 0)) + lower = middle; + else + upper = middle - 1; + } + return lower; +} + + +/* + * Binary search on length histogram. Returns greatest index of range length in + * histogram which is less than (less than or equal) the given length value. If + * all lengths in the histogram are greater than (greater than or equal) the + * given length, returns -1. + */ +static int +length_hist_bsearch(Datum *length_hist_values, int length_hist_nvalues, + double value, bool equal) +{ + int lower = -1, + upper = length_hist_nvalues - 1, + middle; + + while (lower < upper) + { + double middleval; + + middle = (lower + upper + 1) / 2; + + middleval = DatumGetFloat8(length_hist_values[middle]); + if (middleval < value || (equal && middleval <= value)) + lower = middle; + else + upper = middle - 1; + } + return lower; +} + +/* + * Get relative position of value in histogram bin in [0,1] range. + */ +static float8 +get_position(TypeCacheEntry *typcache, const RangeBound *value, const RangeBound *hist1, + const RangeBound *hist2) +{ + bool has_subdiff = OidIsValid(typcache->rng_subdiff_finfo.fn_oid); + float8 position; + + if (!hist1->infinite && !hist2->infinite) + { + float8 bin_width; + + /* + * Both bounds are finite. Assuming the subtype's comparison function + * works sanely, the value must be finite, too, because it lies + * somewhere between the bounds. If it doesn't, arbitrarily return + * 0.5. + */ + if (value->infinite) + return 0.5; + + /* Can't interpolate without subdiff function */ + if (!has_subdiff) + return 0.5; + + /* Calculate relative position using subdiff function. */ + bin_width = DatumGetFloat8(FunctionCall2Coll(&typcache->rng_subdiff_finfo, + typcache->rng_collation, + hist2->val, + hist1->val)); + if (isnan(bin_width) || bin_width <= 0.0) + return 0.5; /* punt for NaN or zero-width bin */ + + position = DatumGetFloat8(FunctionCall2Coll(&typcache->rng_subdiff_finfo, + typcache->rng_collation, + value->val, + hist1->val)) + / bin_width; + + if (isnan(position)) + return 0.5; /* punt for NaN from subdiff, Inf/Inf, etc */ + + /* Relative position must be in [0,1] range */ + position = Max(position, 0.0); + position = Min(position, 1.0); + return position; + } + else if (hist1->infinite && !hist2->infinite) + { + /* + * Lower bin boundary is -infinite, upper is finite. If the value is + * -infinite, return 0.0 to indicate it's equal to the lower bound. + * Otherwise return 1.0 to indicate it's infinitely far from the lower + * bound. + */ + return ((value->infinite && value->lower) ? 0.0 : 1.0); + } + else if (!hist1->infinite && hist2->infinite) + { + /* same as above, but in reverse */ + return ((value->infinite && !value->lower) ? 1.0 : 0.0); + } + else + { + /* + * If both bin boundaries are infinite, they should be equal to each + * other, and the value should also be infinite and equal to both + * bounds. (But don't Assert that, to avoid crashing if a user creates + * a datatype with a broken comparison function). + * + * Assume the value to lie in the middle of the infinite bounds. + */ + return 0.5; + } +} + + +/* + * Get relative position of value in a length histogram bin in [0,1] range. + */ +static double +get_len_position(double value, double hist1, double hist2) +{ + if (!isinf(hist1) && !isinf(hist2)) + { + /* + * Both bounds are finite. The value should be finite too, because it + * lies somewhere between the bounds. If it doesn't, just return + * something. + */ + if (isinf(value)) + return 0.5; + + return 1.0 - (hist2 - value) / (hist2 - hist1); + } + else if (isinf(hist1) && !isinf(hist2)) + { + /* + * Lower bin boundary is -infinite, upper is finite. Return 1.0 to + * indicate the value is infinitely far from the lower bound. + */ + return 1.0; + } + else if (isinf(hist1) && isinf(hist2)) + { + /* same as above, but in reverse */ + return 0.0; + } + else + { + /* + * If both bin boundaries are infinite, they should be equal to each + * other, and the value should also be infinite and equal to both + * bounds. (But don't Assert that, to avoid crashing unnecessarily if + * the caller messes up) + * + * Assume the value to lie in the middle of the infinite bounds. + */ + return 0.5; + } +} + +/* + * Measure distance between two range bounds. + */ +static float8 +get_distance(TypeCacheEntry *typcache, const RangeBound *bound1, const RangeBound *bound2) +{ + bool has_subdiff = OidIsValid(typcache->rng_subdiff_finfo.fn_oid); + + if (!bound1->infinite && !bound2->infinite) + { + /* + * Neither bound is infinite, use subdiff function or return default + * value of 1.0 if no subdiff is available. + */ + if (has_subdiff) + { + float8 res; + + res = DatumGetFloat8(FunctionCall2Coll(&typcache->rng_subdiff_finfo, + typcache->rng_collation, + bound2->val, + bound1->val)); + /* Reject possible NaN result, also negative result */ + if (isnan(res) || res < 0.0) + return 1.0; + else + return res; + } + else + return 1.0; + } + else if (bound1->infinite && bound2->infinite) + { + /* Both bounds are infinite */ + if (bound1->lower == bound2->lower) + return 0.0; + else + return get_float8_infinity(); + } + else + { + /* One bound is infinite, the other is not */ + return get_float8_infinity(); + } +} + +/* + * Calculate the average of function P(x), in the interval [length1, length2], + * where P(x) is the fraction of tuples with length < x (or length <= x if + * 'equal' is true). + */ +static double +calc_length_hist_frac(Datum *length_hist_values, int length_hist_nvalues, + double length1, double length2, bool equal) +{ + double frac; + double A, + B, + PA, + PB; + double pos; + int i; + double area; + + Assert(length2 >= length1); + + if (length2 < 0.0) + return 0.0; /* shouldn't happen, but doesn't hurt to check */ + + /* All lengths in the table are <= infinite. */ + if (isinf(length2) && equal) + return 1.0; + + /*---------- + * The average of a function between A and B can be calculated by the + * formula: + * + * B + * 1 / + * ------- | P(x)dx + * B - A / + * A + * + * The geometrical interpretation of the integral is the area under the + * graph of P(x). P(x) is defined by the length histogram. We calculate + * the area in a piecewise fashion, iterating through the length histogram + * bins. Each bin is a trapezoid: + * + * P(x2) + * /| + * / | + * P(x1)/ | + * | | + * | | + * ---+---+-- + * x1 x2 + * + * where x1 and x2 are the boundaries of the current histogram, and P(x1) + * and P(x1) are the cumulative fraction of tuples at the boundaries. + * + * The area of each trapezoid is 1/2 * (P(x2) + P(x1)) * (x2 - x1) + * + * The first bin contains the lower bound passed by the caller, so we + * use linear interpolation between the previous and next histogram bin + * boundary to calculate P(x1). Likewise for the last bin: we use linear + * interpolation to calculate P(x2). For the bins in between, x1 and x2 + * lie on histogram bin boundaries, so P(x1) and P(x2) are simply: + * P(x1) = (bin index) / (number of bins) + * P(x2) = (bin index + 1 / (number of bins) + */ + + /* First bin, the one that contains lower bound */ + i = length_hist_bsearch(length_hist_values, length_hist_nvalues, length1, equal); + if (i >= length_hist_nvalues - 1) + return 1.0; + + if (i < 0) + { + i = 0; + pos = 0.0; + } + else + { + /* interpolate length1's position in the bin */ + pos = get_len_position(length1, + DatumGetFloat8(length_hist_values[i]), + DatumGetFloat8(length_hist_values[i + 1])); + } + PB = (((double) i) + pos) / (double) (length_hist_nvalues - 1); + B = length1; + + /* + * In the degenerate case that length1 == length2, simply return + * P(length1). This is not merely an optimization: if length1 == length2, + * we'd divide by zero later on. + */ + if (length2 == length1) + return PB; + + /* + * Loop through all the bins, until we hit the last bin, the one that + * contains the upper bound. (if lower and upper bounds are in the same + * bin, this falls out immediately) + */ + area = 0.0; + for (; i < length_hist_nvalues - 1; i++) + { + double bin_upper = DatumGetFloat8(length_hist_values[i + 1]); + + /* check if we've reached the last bin */ + if (!(bin_upper < length2 || (equal && bin_upper <= length2))) + break; + + /* the upper bound of previous bin is the lower bound of this bin */ + A = B; + PA = PB; + + B = bin_upper; + PB = (double) i / (double) (length_hist_nvalues - 1); + + /* + * Add the area of this trapezoid to the total. The point of the + * if-check is to avoid NaN, in the corner case that PA == PB == 0, + * and B - A == Inf. The area of a zero-height trapezoid (PA == PB == + * 0) is zero, regardless of the width (B - A). + */ + if (PA > 0 || PB > 0) + area += 0.5 * (PB + PA) * (B - A); + } + + /* Last bin */ + A = B; + PA = PB; + + B = length2; /* last bin ends at the query upper bound */ + if (i >= length_hist_nvalues - 1) + pos = 0.0; + else + { + if (DatumGetFloat8(length_hist_values[i]) == DatumGetFloat8(length_hist_values[i + 1])) + pos = 0.0; + else + pos = get_len_position(length2, + DatumGetFloat8(length_hist_values[i]), + DatumGetFloat8(length_hist_values[i + 1])); + } + PB = (((double) i) + pos) / (double) (length_hist_nvalues - 1); + + if (PA > 0 || PB > 0) + area += 0.5 * (PB + PA) * (B - A); + + /* + * Ok, we have calculated the area, ie. the integral. Divide by width to + * get the requested average. + * + * Avoid NaN arising from infinite / infinite. This happens at least if + * length2 is infinite. It's not clear what the correct value would be in + * that case, so 0.5 seems as good as any value. + */ + if (isinf(area) && isinf(length2)) + frac = 0.5; + else + frac = area / (length2 - length1); + + return frac; +} + +/* + * Calculate selectivity of "var <@ const" operator, ie. estimate the fraction + * of multiranges that fall within the constant lower and upper bounds. This uses + * the histograms of range lower bounds and range lengths, on the assumption + * that the range lengths are independent of the lower bounds. + * + * The caller has already checked that constant lower and upper bounds are + * finite. + */ +static double +calc_hist_selectivity_contained(TypeCacheEntry *typcache, + const RangeBound *lower, RangeBound *upper, + const RangeBound *hist_lower, int hist_nvalues, + Datum *length_hist_values, int length_hist_nvalues) +{ + int i, + upper_index; + float8 prev_dist; + double bin_width; + double upper_bin_width; + double sum_frac; + + /* + * Begin by finding the bin containing the upper bound, in the lower bound + * histogram. Any range with a lower bound > constant upper bound can't + * match, ie. there are no matches in bins greater than upper_index. + */ + upper->inclusive = !upper->inclusive; + upper->lower = true; + upper_index = rbound_bsearch(typcache, upper, hist_lower, hist_nvalues, + false); + + /* + * If the upper bound value is below the histogram's lower limit, there + * are no matches. + */ + if (upper_index < 0) + return 0.0; + + /* + * If the upper bound value is at or beyond the histogram's upper limit, + * start our loop at the last actual bin, as though the upper bound were + * within that bin; get_position will clamp its result to 1.0 anyway. + * (This corresponds to assuming that the data population above the + * histogram's upper limit is empty, exactly like what we just assumed for + * the lower limit.) + */ + upper_index = Min(upper_index, hist_nvalues - 2); + + /* + * Calculate upper_bin_width, ie. the fraction of the (upper_index, + * upper_index + 1) bin which is greater than upper bound of query range + * using linear interpolation of subdiff function. + */ + upper_bin_width = get_position(typcache, upper, + &hist_lower[upper_index], + &hist_lower[upper_index + 1]); + + /* + * In the loop, dist and prev_dist are the distance of the "current" bin's + * lower and upper bounds from the constant upper bound. + * + * bin_width represents the width of the current bin. Normally it is 1.0, + * meaning a full width bin, but can be less in the corner cases: start + * and end of the loop. We start with bin_width = upper_bin_width, because + * we begin at the bin containing the upper bound. + */ + prev_dist = 0.0; + bin_width = upper_bin_width; + + sum_frac = 0.0; + for (i = upper_index; i >= 0; i--) + { + double dist; + double length_hist_frac; + bool final_bin = false; + + /* + * dist -- distance from upper bound of query range to lower bound of + * the current bin in the lower bound histogram. Or to the lower bound + * of the constant range, if this is the final bin, containing the + * constant lower bound. + */ + if (range_cmp_bounds(typcache, &hist_lower[i], lower) < 0) + { + dist = get_distance(typcache, lower, upper); + + /* + * Subtract from bin_width the portion of this bin that we want to + * ignore. + */ + bin_width -= get_position(typcache, lower, &hist_lower[i], + &hist_lower[i + 1]); + if (bin_width < 0.0) + bin_width = 0.0; + final_bin = true; + } + else + dist = get_distance(typcache, &hist_lower[i], upper); + + /* + * Estimate the fraction of tuples in this bin that are narrow enough + * to not exceed the distance to the upper bound of the query range. + */ + length_hist_frac = calc_length_hist_frac(length_hist_values, + length_hist_nvalues, + prev_dist, dist, true); + + /* + * Add the fraction of tuples in this bin, with a suitable length, to + * the total. + */ + sum_frac += length_hist_frac * bin_width / (double) (hist_nvalues - 1); + + if (final_bin) + break; + + bin_width = 1.0; + prev_dist = dist; + } + + return sum_frac; +} + +/* + * Calculate selectivity of "var @> const" operator, ie. estimate the fraction + * of multiranges that contain the constant lower and upper bounds. This uses + * the histograms of range lower bounds and range lengths, on the assumption + * that the range lengths are independent of the lower bounds. + */ +static double +calc_hist_selectivity_contains(TypeCacheEntry *typcache, + const RangeBound *lower, const RangeBound *upper, + const RangeBound *hist_lower, int hist_nvalues, + Datum *length_hist_values, int length_hist_nvalues) +{ + int i, + lower_index; + double bin_width, + lower_bin_width; + double sum_frac; + float8 prev_dist; + + /* Find the bin containing the lower bound of query range. */ + lower_index = rbound_bsearch(typcache, lower, hist_lower, hist_nvalues, + true); + + /* + * If the lower bound value is below the histogram's lower limit, there + * are no matches. + */ + if (lower_index < 0) + return 0.0; + + /* + * If the lower bound value is at or beyond the histogram's upper limit, + * start our loop at the last actual bin, as though the upper bound were + * within that bin; get_position will clamp its result to 1.0 anyway. + * (This corresponds to assuming that the data population above the + * histogram's upper limit is empty, exactly like what we just assumed for + * the lower limit.) + */ + lower_index = Min(lower_index, hist_nvalues - 2); + + /* + * Calculate lower_bin_width, ie. the fraction of the of (lower_index, + * lower_index + 1) bin which is greater than lower bound of query range + * using linear interpolation of subdiff function. + */ + lower_bin_width = get_position(typcache, lower, &hist_lower[lower_index], + &hist_lower[lower_index + 1]); + + /* + * Loop through all the lower bound bins, smaller than the query lower + * bound. In the loop, dist and prev_dist are the distance of the + * "current" bin's lower and upper bounds from the constant upper bound. + * We begin from query lower bound, and walk backwards, so the first bin's + * upper bound is the query lower bound, and its distance to the query + * upper bound is the length of the query range. + * + * bin_width represents the width of the current bin. Normally it is 1.0, + * meaning a full width bin, except for the first bin, which is only + * counted up to the constant lower bound. + */ + prev_dist = get_distance(typcache, lower, upper); + sum_frac = 0.0; + bin_width = lower_bin_width; + for (i = lower_index; i >= 0; i--) + { + float8 dist; + double length_hist_frac; + + /* + * dist -- distance from upper bound of query range to current value + * of lower bound histogram or lower bound of query range (if we've + * reach it). + */ + dist = get_distance(typcache, &hist_lower[i], upper); + + /* + * Get average fraction of length histogram which covers intervals + * longer than (or equal to) distance to upper bound of query range. + */ + length_hist_frac = + 1.0 - calc_length_hist_frac(length_hist_values, + length_hist_nvalues, + prev_dist, dist, false); + + sum_frac += length_hist_frac * bin_width / (double) (hist_nvalues - 1); + + bin_width = 1.0; + prev_dist = dist; + } + + return sum_frac; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/name.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/name.c new file mode 100644 index 00000000000..c136eabdbc9 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/name.c @@ -0,0 +1,355 @@ +/*------------------------------------------------------------------------- + * + * name.c + * Functions for the built-in type "name". + * + * name replaces char16 and is carefully implemented so that it + * is a string of physical length NAMEDATALEN. + * DO NOT use hard-coded constants anywhere + * always use NAMEDATALEN as the symbolic constant! - jolly 8/21/95 + * + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/adt/name.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "catalog/namespace.h" +#include "catalog/pg_collation.h" +#include "catalog/pg_type.h" +#include "libpq/pqformat.h" +#include "mb/pg_wchar.h" +#include "miscadmin.h" +#include "utils/array.h" +#include "utils/builtins.h" +#include "utils/lsyscache.h" +#include "utils/varlena.h" + + +/***************************************************************************** + * USER I/O ROUTINES (none) * + *****************************************************************************/ + + +/* + * namein - converts cstring to internal representation + * + * Note: + * [Old] Currently if strlen(s) < NAMEDATALEN, the extra chars are nulls + * Now, always NULL terminated + */ +Datum +namein(PG_FUNCTION_ARGS) +{ + char *s = PG_GETARG_CSTRING(0); + Name result; + int len; + + len = strlen(s); + + /* Truncate oversize input */ + if (len >= NAMEDATALEN) + len = pg_mbcliplen(s, len, NAMEDATALEN - 1); + + /* We use palloc0 here to ensure result is zero-padded */ + result = (Name) palloc0(NAMEDATALEN); + memcpy(NameStr(*result), s, len); + + PG_RETURN_NAME(result); +} + +/* + * nameout - converts internal representation to cstring + */ +Datum +nameout(PG_FUNCTION_ARGS) +{ + Name s = PG_GETARG_NAME(0); + + PG_RETURN_CSTRING(pstrdup(NameStr(*s))); +} + +/* + * namerecv - converts external binary format to name + */ +Datum +namerecv(PG_FUNCTION_ARGS) +{ + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + Name result; + char *str; + int nbytes; + + str = pq_getmsgtext(buf, buf->len - buf->cursor, &nbytes); + if (nbytes >= NAMEDATALEN) + ereport(ERROR, + (errcode(ERRCODE_NAME_TOO_LONG), + errmsg("identifier too long"), + errdetail("Identifier must be less than %d characters.", + NAMEDATALEN))); + result = (NameData *) palloc0(NAMEDATALEN); + memcpy(result, str, nbytes); + pfree(str); + PG_RETURN_NAME(result); +} + +/* + * namesend - converts name to binary format + */ +Datum +namesend(PG_FUNCTION_ARGS) +{ + Name s = PG_GETARG_NAME(0); + StringInfoData buf; + + pq_begintypsend(&buf); + pq_sendtext(&buf, NameStr(*s), strlen(NameStr(*s))); + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} + + +/***************************************************************************** + * COMPARISON/SORTING ROUTINES * + *****************************************************************************/ + +/* + * nameeq - returns 1 iff arguments are equal + * namene - returns 1 iff arguments are not equal + * namelt - returns 1 iff a < b + * namele - returns 1 iff a <= b + * namegt - returns 1 iff a > b + * namege - returns 1 iff a >= b + * + * Note that the use of strncmp with NAMEDATALEN limit is mostly historical; + * strcmp would do as well, because we do not allow NAME values that don't + * have a '\0' terminator. Whatever might be past the terminator is not + * considered relevant to comparisons. + */ +static int +namecmp(Name arg1, Name arg2, Oid collid) +{ + /* Fast path for common case used in system catalogs */ + if (collid == C_COLLATION_OID) + return strncmp(NameStr(*arg1), NameStr(*arg2), NAMEDATALEN); + + /* Else rely on the varstr infrastructure */ + return varstr_cmp(NameStr(*arg1), strlen(NameStr(*arg1)), + NameStr(*arg2), strlen(NameStr(*arg2)), + collid); +} + +Datum +nameeq(PG_FUNCTION_ARGS) +{ + Name arg1 = PG_GETARG_NAME(0); + Name arg2 = PG_GETARG_NAME(1); + + PG_RETURN_BOOL(namecmp(arg1, arg2, PG_GET_COLLATION()) == 0); +} + +Datum +namene(PG_FUNCTION_ARGS) +{ + Name arg1 = PG_GETARG_NAME(0); + Name arg2 = PG_GETARG_NAME(1); + + PG_RETURN_BOOL(namecmp(arg1, arg2, PG_GET_COLLATION()) != 0); +} + +Datum +namelt(PG_FUNCTION_ARGS) +{ + Name arg1 = PG_GETARG_NAME(0); + Name arg2 = PG_GETARG_NAME(1); + + PG_RETURN_BOOL(namecmp(arg1, arg2, PG_GET_COLLATION()) < 0); +} + +Datum +namele(PG_FUNCTION_ARGS) +{ + Name arg1 = PG_GETARG_NAME(0); + Name arg2 = PG_GETARG_NAME(1); + + PG_RETURN_BOOL(namecmp(arg1, arg2, PG_GET_COLLATION()) <= 0); +} + +Datum +namegt(PG_FUNCTION_ARGS) +{ + Name arg1 = PG_GETARG_NAME(0); + Name arg2 = PG_GETARG_NAME(1); + + PG_RETURN_BOOL(namecmp(arg1, arg2, PG_GET_COLLATION()) > 0); +} + +Datum +namege(PG_FUNCTION_ARGS) +{ + Name arg1 = PG_GETARG_NAME(0); + Name arg2 = PG_GETARG_NAME(1); + + PG_RETURN_BOOL(namecmp(arg1, arg2, PG_GET_COLLATION()) >= 0); +} + +Datum +btnamecmp(PG_FUNCTION_ARGS) +{ + Name arg1 = PG_GETARG_NAME(0); + Name arg2 = PG_GETARG_NAME(1); + + PG_RETURN_INT32(namecmp(arg1, arg2, PG_GET_COLLATION())); +} + +Datum +btnamesortsupport(PG_FUNCTION_ARGS) +{ + SortSupport ssup = (SortSupport) PG_GETARG_POINTER(0); + Oid collid = ssup->ssup_collation; + MemoryContext oldcontext; + + oldcontext = MemoryContextSwitchTo(ssup->ssup_cxt); + + /* Use generic string SortSupport */ + varstr_sortsupport(ssup, NAMEOID, collid); + + MemoryContextSwitchTo(oldcontext); + + PG_RETURN_VOID(); +} + + +/***************************************************************************** + * MISCELLANEOUS PUBLIC ROUTINES * + *****************************************************************************/ + +void +namestrcpy(Name name, const char *str) +{ + /* NB: We need to zero-pad the destination. */ + strncpy(NameStr(*name), str, NAMEDATALEN); + NameStr(*name)[NAMEDATALEN - 1] = '\0'; +} + +/* + * Compare a NAME to a C string + * + * Assumes C collation always; be careful when using this for + * anything but equality checks! + */ +int +namestrcmp(Name name, const char *str) +{ + if (!name && !str) + return 0; + if (!name) + return -1; /* NULL < anything */ + if (!str) + return 1; /* NULL < anything */ + return strncmp(NameStr(*name), str, NAMEDATALEN); +} + + +/* + * SQL-functions CURRENT_USER, SESSION_USER + */ +Datum +current_user(PG_FUNCTION_ARGS) +{ + PG_RETURN_DATUM(DirectFunctionCall1(namein, CStringGetDatum(GetUserNameFromId(GetUserId(), false)))); +} + +Datum +session_user(PG_FUNCTION_ARGS) +{ + PG_RETURN_DATUM(DirectFunctionCall1(namein, CStringGetDatum(GetUserNameFromId(GetSessionUserId(), false)))); +} + + +/* + * SQL-functions CURRENT_SCHEMA, CURRENT_SCHEMAS + */ +Datum +current_schema(PG_FUNCTION_ARGS) +{ + List *search_path = fetch_search_path(false); + char *nspname; + + if (search_path == NIL) + PG_RETURN_NULL(); + nspname = get_namespace_name(linitial_oid(search_path)); + list_free(search_path); + if (!nspname) + PG_RETURN_NULL(); /* recently-deleted namespace? */ + PG_RETURN_DATUM(DirectFunctionCall1(namein, CStringGetDatum(nspname))); +} + +Datum +current_schemas(PG_FUNCTION_ARGS) +{ + List *search_path = fetch_search_path(PG_GETARG_BOOL(0)); + ListCell *l; + Datum *names; + int i; + ArrayType *array; + + names = (Datum *) palloc(list_length(search_path) * sizeof(Datum)); + i = 0; + foreach(l, search_path) + { + char *nspname; + + nspname = get_namespace_name(lfirst_oid(l)); + if (nspname) /* watch out for deleted namespace */ + { + names[i] = DirectFunctionCall1(namein, CStringGetDatum(nspname)); + i++; + } + } + list_free(search_path); + + array = construct_array_builtin(names, i, NAMEOID); + + PG_RETURN_POINTER(array); +} + +/* + * SQL-function nameconcatoid(name, oid) returns name + * + * This is used in the information_schema to produce specific_name columns, + * which are supposed to be unique per schema. We achieve that (in an ugly + * way) by appending the object's OID. The result is the same as + * ($1::text || '_' || $2::text)::name + * except that, if it would not fit in NAMEDATALEN, we make it do so by + * truncating the name input (not the oid). + */ +Datum +nameconcatoid(PG_FUNCTION_ARGS) +{ + Name nam = PG_GETARG_NAME(0); + Oid oid = PG_GETARG_OID(1); + Name result; + char suffix[20]; + int suflen; + int namlen; + + suflen = snprintf(suffix, sizeof(suffix), "_%u", oid); + namlen = strlen(NameStr(*nam)); + + /* Truncate oversize input by truncating name part, not suffix */ + if (namlen + suflen >= NAMEDATALEN) + namlen = pg_mbcliplen(NameStr(*nam), namlen, NAMEDATALEN - 1 - suflen); + + /* We use palloc0 here to ensure result is zero-padded */ + result = (Name) palloc0(NAMEDATALEN); + memcpy(NameStr(*result), NameStr(*nam), namlen); + memcpy(NameStr(*result) + namlen, suffix, suflen); + + PG_RETURN_NAME(result); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/network.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/network.c new file mode 100644 index 00000000000..640fc37dc83 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/network.c @@ -0,0 +1,2104 @@ +/* + * PostgreSQL type definitions for the INET and CIDR types. + * + * src/backend/utils/adt/network.c + * + * Jon Postel RIP 16 Oct 1998 + */ + +#include "postgres.h" + +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#include "access/stratnum.h" +#include "catalog/pg_opfamily.h" +#include "catalog/pg_type.h" +#include "common/hashfn.h" +#include "common/ip.h" +#include "lib/hyperloglog.h" +#include "libpq/libpq-be.h" +#include "libpq/pqformat.h" +#include "miscadmin.h" +#include "nodes/makefuncs.h" +#include "nodes/nodeFuncs.h" +#include "nodes/supportnodes.h" +#include "utils/builtins.h" +#include "utils/fmgroids.h" +#include "utils/guc.h" +#include "utils/inet.h" +#include "utils/lsyscache.h" +#include "utils/sortsupport.h" + + +/* + * An IPv4 netmask size is a value in the range of 0 - 32, which is + * represented with 6 bits in inet/cidr abbreviated keys where possible. + * + * An IPv4 inet/cidr abbreviated key can use up to 25 bits for subnet + * component. + */ +#define ABBREV_BITS_INET4_NETMASK_SIZE 6 +#define ABBREV_BITS_INET4_SUBNET 25 + +/* sortsupport for inet/cidr */ +typedef struct +{ + int64 input_count; /* number of non-null values seen */ + bool estimating; /* true if estimating cardinality */ + + hyperLogLogState abbr_card; /* cardinality estimator */ +} network_sortsupport_state; + +static int32 network_cmp_internal(inet *a1, inet *a2); +static int network_fast_cmp(Datum x, Datum y, SortSupport ssup); +static bool network_abbrev_abort(int memtupcount, SortSupport ssup); +static Datum network_abbrev_convert(Datum original, SortSupport ssup); +static List *match_network_function(Node *leftop, + Node *rightop, + int indexarg, + Oid funcid, + Oid opfamily); +static List *match_network_subset(Node *leftop, + Node *rightop, + bool is_eq, + Oid opfamily); +static bool addressOK(unsigned char *a, int bits, int family); +static inet *internal_inetpl(inet *ip, int64 addend); + + +/* + * Common INET/CIDR input routine + */ +static inet * +network_in(char *src, bool is_cidr, Node *escontext) +{ + int bits; + inet *dst; + + dst = (inet *) palloc0(sizeof(inet)); + + /* + * First, check to see if this is an IPv6 or IPv4 address. IPv6 addresses + * will have a : somewhere in them (several, in fact) so if there is one + * present, assume it's V6, otherwise assume it's V4. + */ + + if (strchr(src, ':') != NULL) + ip_family(dst) = PGSQL_AF_INET6; + else + ip_family(dst) = PGSQL_AF_INET; + + bits = pg_inet_net_pton(ip_family(dst), src, ip_addr(dst), + is_cidr ? ip_addrsize(dst) : -1); + if ((bits < 0) || (bits > ip_maxbits(dst))) + ereturn(escontext, NULL, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + /* translator: first %s is inet or cidr */ + errmsg("invalid input syntax for type %s: \"%s\"", + is_cidr ? "cidr" : "inet", src))); + + /* + * Error check: CIDR values must not have any bits set beyond the masklen. + */ + if (is_cidr) + { + if (!addressOK(ip_addr(dst), bits, ip_family(dst))) + ereturn(escontext, NULL, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid cidr value: \"%s\"", src), + errdetail("Value has bits set to right of mask."))); + } + + ip_bits(dst) = bits; + SET_INET_VARSIZE(dst); + + return dst; +} + +Datum +inet_in(PG_FUNCTION_ARGS) +{ + char *src = PG_GETARG_CSTRING(0); + + PG_RETURN_INET_P(network_in(src, false, fcinfo->context)); +} + +Datum +cidr_in(PG_FUNCTION_ARGS) +{ + char *src = PG_GETARG_CSTRING(0); + + PG_RETURN_INET_P(network_in(src, true, fcinfo->context)); +} + + +/* + * Common INET/CIDR output routine + */ +static char * +network_out(inet *src, bool is_cidr) +{ + char tmp[sizeof("xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:255.255.255.255/128")]; + char *dst; + int len; + + dst = pg_inet_net_ntop(ip_family(src), ip_addr(src), ip_bits(src), + tmp, sizeof(tmp)); + if (dst == NULL) + ereport(ERROR, + (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION), + errmsg("could not format inet value: %m"))); + + /* For CIDR, add /n if not present */ + if (is_cidr && strchr(tmp, '/') == NULL) + { + len = strlen(tmp); + snprintf(tmp + len, sizeof(tmp) - len, "/%u", ip_bits(src)); + } + + return pstrdup(tmp); +} + +Datum +inet_out(PG_FUNCTION_ARGS) +{ + inet *src = PG_GETARG_INET_PP(0); + + PG_RETURN_CSTRING(network_out(src, false)); +} + +Datum +cidr_out(PG_FUNCTION_ARGS) +{ + inet *src = PG_GETARG_INET_PP(0); + + PG_RETURN_CSTRING(network_out(src, true)); +} + + +/* + * network_recv - converts external binary format to inet + * + * The external representation is (one byte apiece for) + * family, bits, is_cidr, address length, address in network byte order. + * + * Presence of is_cidr is largely for historical reasons, though it might + * allow some code-sharing on the client side. We send it correctly on + * output, but ignore the value on input. + */ +static inet * +network_recv(StringInfo buf, bool is_cidr) +{ + inet *addr; + char *addrptr; + int bits; + int nb, + i; + + /* make sure any unused bits in a CIDR value are zeroed */ + addr = (inet *) palloc0(sizeof(inet)); + + ip_family(addr) = pq_getmsgbyte(buf); + if (ip_family(addr) != PGSQL_AF_INET && + ip_family(addr) != PGSQL_AF_INET6) + ereport(ERROR, + (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION), + /* translator: %s is inet or cidr */ + errmsg("invalid address family in external \"%s\" value", + is_cidr ? "cidr" : "inet"))); + bits = pq_getmsgbyte(buf); + if (bits < 0 || bits > ip_maxbits(addr)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION), + /* translator: %s is inet or cidr */ + errmsg("invalid bits in external \"%s\" value", + is_cidr ? "cidr" : "inet"))); + ip_bits(addr) = bits; + i = pq_getmsgbyte(buf); /* ignore is_cidr */ + nb = pq_getmsgbyte(buf); + if (nb != ip_addrsize(addr)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION), + /* translator: %s is inet or cidr */ + errmsg("invalid length in external \"%s\" value", + is_cidr ? "cidr" : "inet"))); + + addrptr = (char *) ip_addr(addr); + for (i = 0; i < nb; i++) + addrptr[i] = pq_getmsgbyte(buf); + + /* + * Error check: CIDR values must not have any bits set beyond the masklen. + */ + if (is_cidr) + { + if (!addressOK(ip_addr(addr), bits, ip_family(addr))) + ereport(ERROR, + (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION), + errmsg("invalid external \"cidr\" value"), + errdetail("Value has bits set to right of mask."))); + } + + SET_INET_VARSIZE(addr); + + return addr; +} + +Datum +inet_recv(PG_FUNCTION_ARGS) +{ + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + + PG_RETURN_INET_P(network_recv(buf, false)); +} + +Datum +cidr_recv(PG_FUNCTION_ARGS) +{ + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + + PG_RETURN_INET_P(network_recv(buf, true)); +} + + +/* + * network_send - converts inet to binary format + */ +static bytea * +network_send(inet *addr, bool is_cidr) +{ + StringInfoData buf; + char *addrptr; + int nb, + i; + + pq_begintypsend(&buf); + pq_sendbyte(&buf, ip_family(addr)); + pq_sendbyte(&buf, ip_bits(addr)); + pq_sendbyte(&buf, is_cidr); + nb = ip_addrsize(addr); + if (nb < 0) + nb = 0; + pq_sendbyte(&buf, nb); + addrptr = (char *) ip_addr(addr); + for (i = 0; i < nb; i++) + pq_sendbyte(&buf, addrptr[i]); + return pq_endtypsend(&buf); +} + +Datum +inet_send(PG_FUNCTION_ARGS) +{ + inet *addr = PG_GETARG_INET_PP(0); + + PG_RETURN_BYTEA_P(network_send(addr, false)); +} + +Datum +cidr_send(PG_FUNCTION_ARGS) +{ + inet *addr = PG_GETARG_INET_PP(0); + + PG_RETURN_BYTEA_P(network_send(addr, true)); +} + + +Datum +inet_to_cidr(PG_FUNCTION_ARGS) +{ + inet *src = PG_GETARG_INET_PP(0); + int bits; + + bits = ip_bits(src); + + /* safety check */ + if ((bits < 0) || (bits > ip_maxbits(src))) + elog(ERROR, "invalid inet bit length: %d", bits); + + PG_RETURN_INET_P(cidr_set_masklen_internal(src, bits)); +} + +Datum +inet_set_masklen(PG_FUNCTION_ARGS) +{ + inet *src = PG_GETARG_INET_PP(0); + int bits = PG_GETARG_INT32(1); + inet *dst; + + if (bits == -1) + bits = ip_maxbits(src); + + if ((bits < 0) || (bits > ip_maxbits(src))) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid mask length: %d", bits))); + + /* clone the original data */ + dst = (inet *) palloc(VARSIZE_ANY(src)); + memcpy(dst, src, VARSIZE_ANY(src)); + + ip_bits(dst) = bits; + + PG_RETURN_INET_P(dst); +} + +Datum +cidr_set_masklen(PG_FUNCTION_ARGS) +{ + inet *src = PG_GETARG_INET_PP(0); + int bits = PG_GETARG_INT32(1); + + if (bits == -1) + bits = ip_maxbits(src); + + if ((bits < 0) || (bits > ip_maxbits(src))) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid mask length: %d", bits))); + + PG_RETURN_INET_P(cidr_set_masklen_internal(src, bits)); +} + +/* + * Copy src and set mask length to 'bits' (which must be valid for the family) + */ +inet * +cidr_set_masklen_internal(const inet *src, int bits) +{ + inet *dst = (inet *) palloc0(sizeof(inet)); + + ip_family(dst) = ip_family(src); + ip_bits(dst) = bits; + + if (bits > 0) + { + Assert(bits <= ip_maxbits(dst)); + + /* Clone appropriate bytes of the address, leaving the rest 0 */ + memcpy(ip_addr(dst), ip_addr(src), (bits + 7) / 8); + + /* Clear any unwanted bits in the last partial byte */ + if (bits % 8) + ip_addr(dst)[bits / 8] &= ~(0xFF >> (bits % 8)); + } + + /* Set varlena header correctly */ + SET_INET_VARSIZE(dst); + + return dst; +} + +/* + * Basic comparison function for sorting and inet/cidr comparisons. + * + * Comparison is first on the common bits of the network part, then on + * the length of the network part, and then on the whole unmasked address. + * The effect is that the network part is the major sort key, and for + * equal network parts we sort on the host part. Note this is only sane + * for CIDR if address bits to the right of the mask are guaranteed zero; + * otherwise logically-equal CIDRs might compare different. + */ + +static int32 +network_cmp_internal(inet *a1, inet *a2) +{ + if (ip_family(a1) == ip_family(a2)) + { + int order; + + order = bitncmp(ip_addr(a1), ip_addr(a2), + Min(ip_bits(a1), ip_bits(a2))); + if (order != 0) + return order; + order = ((int) ip_bits(a1)) - ((int) ip_bits(a2)); + if (order != 0) + return order; + return bitncmp(ip_addr(a1), ip_addr(a2), ip_maxbits(a1)); + } + + return ip_family(a1) - ip_family(a2); +} + +Datum +network_cmp(PG_FUNCTION_ARGS) +{ + inet *a1 = PG_GETARG_INET_PP(0); + inet *a2 = PG_GETARG_INET_PP(1); + + PG_RETURN_INT32(network_cmp_internal(a1, a2)); +} + +/* + * SortSupport strategy routine + */ +Datum +network_sortsupport(PG_FUNCTION_ARGS) +{ + SortSupport ssup = (SortSupport) PG_GETARG_POINTER(0); + + ssup->comparator = network_fast_cmp; + ssup->ssup_extra = NULL; + + if (ssup->abbreviate) + { + network_sortsupport_state *uss; + MemoryContext oldcontext; + + oldcontext = MemoryContextSwitchTo(ssup->ssup_cxt); + + uss = palloc(sizeof(network_sortsupport_state)); + uss->input_count = 0; + uss->estimating = true; + initHyperLogLog(&uss->abbr_card, 10); + + ssup->ssup_extra = uss; + + ssup->comparator = ssup_datum_unsigned_cmp; + ssup->abbrev_converter = network_abbrev_convert; + ssup->abbrev_abort = network_abbrev_abort; + ssup->abbrev_full_comparator = network_fast_cmp; + + MemoryContextSwitchTo(oldcontext); + } + + PG_RETURN_VOID(); +} + +/* + * SortSupport comparison func + */ +static int +network_fast_cmp(Datum x, Datum y, SortSupport ssup) +{ + inet *arg1 = DatumGetInetPP(x); + inet *arg2 = DatumGetInetPP(y); + + return network_cmp_internal(arg1, arg2); +} + +/* + * Callback for estimating effectiveness of abbreviated key optimization. + * + * We pay no attention to the cardinality of the non-abbreviated data, because + * there is no equality fast-path within authoritative inet comparator. + */ +static bool +network_abbrev_abort(int memtupcount, SortSupport ssup) +{ + network_sortsupport_state *uss = ssup->ssup_extra; + double abbr_card; + + if (memtupcount < 10000 || uss->input_count < 10000 || !uss->estimating) + return false; + + abbr_card = estimateHyperLogLog(&uss->abbr_card); + + /* + * If we have >100k distinct values, then even if we were sorting many + * billion rows we'd likely still break even, and the penalty of undoing + * that many rows of abbrevs would probably not be worth it. At this point + * we stop counting because we know that we're now fully committed. + */ + if (abbr_card > 100000.0) + { +#ifdef TRACE_SORT + if (trace_sort) + elog(LOG, + "network_abbrev: estimation ends at cardinality %f" + " after " INT64_FORMAT " values (%d rows)", + abbr_card, uss->input_count, memtupcount); +#endif + uss->estimating = false; + return false; + } + + /* + * Target minimum cardinality is 1 per ~2k of non-null inputs. 0.5 row + * fudge factor allows us to abort earlier on genuinely pathological data + * where we've had exactly one abbreviated value in the first 2k + * (non-null) rows. + */ + if (abbr_card < uss->input_count / 2000.0 + 0.5) + { +#ifdef TRACE_SORT + if (trace_sort) + elog(LOG, + "network_abbrev: aborting abbreviation at cardinality %f" + " below threshold %f after " INT64_FORMAT " values (%d rows)", + abbr_card, uss->input_count / 2000.0 + 0.5, uss->input_count, + memtupcount); +#endif + return true; + } + +#ifdef TRACE_SORT + if (trace_sort) + elog(LOG, + "network_abbrev: cardinality %f after " INT64_FORMAT + " values (%d rows)", abbr_card, uss->input_count, memtupcount); +#endif + + return false; +} + +/* + * SortSupport conversion routine. Converts original inet/cidr representation + * to abbreviated key representation that works with simple 3-way unsigned int + * comparisons. The network_cmp_internal() rules for sorting inet/cidr datums + * are followed by abbreviated comparisons by an encoding scheme that + * conditions keys through careful use of padding. + * + * Some background: inet values have three major components (take for example + * the address 1.2.3.4/24): + * + * * A network, or netmasked bits (1.2.3.0). + * * A netmask size (/24). + * * A subnet, or bits outside of the netmask (0.0.0.4). + * + * cidr values are the same except that with only the first two components -- + * all their subnet bits *must* be zero (1.2.3.0/24). + * + * IPv4 and IPv6 are identical in this makeup, with the difference being that + * IPv4 addresses have a maximum of 32 bits compared to IPv6's 64 bits, so in + * IPv6 each part may be larger. + * + * inet/cidr types compare using these sorting rules. If inequality is detected + * at any step, comparison is finished. If any rule is a tie, the algorithm + * drops through to the next to break it: + * + * 1. IPv4 always appears before IPv6. + * 2. Network bits are compared. + * 3. Netmask size is compared. + * 4. All bits are compared (having made it here, we know that both + * netmasked bits and netmask size are equal, so we're in effect only + * comparing subnet bits). + * + * When generating abbreviated keys for SortSupport, we pack as much as we can + * into a datum while ensuring that when comparing those keys as integers, + * these rules will be respected. Exact contents depend on IP family and datum + * size. + * + * IPv4 + * ---- + * + * 4 byte datums: + * + * Start with 1 bit for the IP family (IPv4 or IPv6; this bit is present in + * every case below) followed by all but 1 of the netmasked bits. + * + * +----------+---------------------+ + * | 1 bit IP | 31 bits network | (1 bit network + * | family | (truncated) | omitted) + * +----------+---------------------+ + * + * 8 byte datums: + * + * We have space to store all netmasked bits, followed by the netmask size, + * followed by 25 bits of the subnet (25 bits is usually more than enough in + * practice). cidr datums always have all-zero subnet bits. + * + * +----------+-----------------------+--------------+--------------------+ + * | 1 bit IP | 32 bits network | 6 bits | 25 bits subnet | + * | family | (full) | network size | (truncated) | + * +----------+-----------------------+--------------+--------------------+ + * + * IPv6 + * ---- + * + * 4 byte datums: + * + * +----------+---------------------+ + * | 1 bit IP | 31 bits network | (up to 97 bits + * | family | (truncated) | network omitted) + * +----------+---------------------+ + * + * 8 byte datums: + * + * +----------+---------------------------------+ + * | 1 bit IP | 63 bits network | (up to 65 bits + * | family | (truncated) | network omitted) + * +----------+---------------------------------+ + */ +static Datum +network_abbrev_convert(Datum original, SortSupport ssup) +{ + network_sortsupport_state *uss = ssup->ssup_extra; + inet *authoritative = DatumGetInetPP(original); + Datum res, + ipaddr_datum, + subnet_bitmask, + network; + int subnet_size; + + Assert(ip_family(authoritative) == PGSQL_AF_INET || + ip_family(authoritative) == PGSQL_AF_INET6); + + /* + * Get an unsigned integer representation of the IP address by taking its + * first 4 or 8 bytes. Always take all 4 bytes of an IPv4 address. Take + * the first 8 bytes of an IPv6 address with an 8 byte datum and 4 bytes + * otherwise. + * + * We're consuming an array of unsigned char, so byteswap on little endian + * systems (an inet's ipaddr field stores the most significant byte + * first). + */ + if (ip_family(authoritative) == PGSQL_AF_INET) + { + uint32 ipaddr_datum32; + + memcpy(&ipaddr_datum32, ip_addr(authoritative), sizeof(uint32)); + + /* Must byteswap on little-endian machines */ +#ifndef WORDS_BIGENDIAN + ipaddr_datum = pg_bswap32(ipaddr_datum32); +#else + ipaddr_datum = ipaddr_datum32; +#endif + + /* Initialize result without setting ipfamily bit */ + res = (Datum) 0; + } + else + { + memcpy(&ipaddr_datum, ip_addr(authoritative), sizeof(Datum)); + + /* Must byteswap on little-endian machines */ + ipaddr_datum = DatumBigEndianToNative(ipaddr_datum); + + /* Initialize result with ipfamily (most significant) bit set */ + res = ((Datum) 1) << (SIZEOF_DATUM * BITS_PER_BYTE - 1); + } + + /* + * ipaddr_datum must be "split": high order bits go in "network" component + * of abbreviated key (often with zeroed bits at the end due to masking), + * while low order bits go in "subnet" component when there is space for + * one. This is often accomplished by generating a temp datum subnet + * bitmask, which we may reuse later when generating the subnet bits + * themselves. (Note that subnet bits are only used with IPv4 datums on + * platforms where datum is 8 bytes.) + * + * The number of bits in subnet is used to generate a datum subnet + * bitmask. For example, with a /24 IPv4 datum there are 8 subnet bits + * (since 32 - 24 is 8), so the final subnet bitmask is B'1111 1111'. We + * need explicit handling for cases where the ipaddr bits cannot all fit + * in a datum, though (otherwise we'd incorrectly mask the network + * component with IPv6 values). + */ + subnet_size = ip_maxbits(authoritative) - ip_bits(authoritative); + Assert(subnet_size >= 0); + /* subnet size must work with prefix ipaddr cases */ + subnet_size %= SIZEOF_DATUM * BITS_PER_BYTE; + if (ip_bits(authoritative) == 0) + { + /* Fit as many ipaddr bits as possible into subnet */ + subnet_bitmask = ((Datum) 0) - 1; + network = 0; + } + else if (ip_bits(authoritative) < SIZEOF_DATUM * BITS_PER_BYTE) + { + /* Split ipaddr bits between network and subnet */ + subnet_bitmask = (((Datum) 1) << subnet_size) - 1; + network = ipaddr_datum & ~subnet_bitmask; + } + else + { + /* Fit as many ipaddr bits as possible into network */ + subnet_bitmask = 0; + network = ipaddr_datum; + } + +#if SIZEOF_DATUM == 8 + if (ip_family(authoritative) == PGSQL_AF_INET) + { + /* + * IPv4 with 8 byte datums: keep all 32 netmasked bits, netmask size, + * and most significant 25 subnet bits + */ + Datum netmask_size = (Datum) ip_bits(authoritative); + Datum subnet; + + /* + * Shift left 31 bits: 6 bits netmask size + 25 subnet bits. + * + * We don't make any distinction between network bits that are zero + * due to masking and "true"/non-masked zero bits. An abbreviated + * comparison that is resolved by comparing a non-masked and non-zero + * bit to a masked/zeroed bit is effectively resolved based on + * ip_bits(), even though the comparison won't reach the netmask_size + * bits. + */ + network <<= (ABBREV_BITS_INET4_NETMASK_SIZE + + ABBREV_BITS_INET4_SUBNET); + + /* Shift size to make room for subnet bits at the end */ + netmask_size <<= ABBREV_BITS_INET4_SUBNET; + + /* Extract subnet bits without shifting them */ + subnet = ipaddr_datum & subnet_bitmask; + + /* + * If we have more than 25 subnet bits, we can't fit everything. Shift + * subnet down to avoid clobbering bits that are only supposed to be + * used for netmask_size. + * + * Discarding the least significant subnet bits like this is correct + * because abbreviated comparisons that are resolved at the subnet + * level must have had equal netmask_size/ip_bits() values in order to + * get that far. + */ + if (subnet_size > ABBREV_BITS_INET4_SUBNET) + subnet >>= subnet_size - ABBREV_BITS_INET4_SUBNET; + + /* + * Assemble the final abbreviated key without clobbering the ipfamily + * bit that must remain a zero. + */ + res |= network | netmask_size | subnet; + } + else +#endif + { + /* + * 4 byte datums, or IPv6 with 8 byte datums: Use as many of the + * netmasked bits as will fit in final abbreviated key. Avoid + * clobbering the ipfamily bit that was set earlier. + */ + res |= network >> 1; + } + + uss->input_count += 1; + + /* Hash abbreviated key */ + if (uss->estimating) + { + uint32 tmp; + +#if SIZEOF_DATUM == 8 + tmp = (uint32) res ^ (uint32) ((uint64) res >> 32); +#else /* SIZEOF_DATUM != 8 */ + tmp = (uint32) res; +#endif + + addHyperLogLog(&uss->abbr_card, DatumGetUInt32(hash_uint32(tmp))); + } + + return res; +} + +/* + * Boolean ordering tests. + */ +Datum +network_lt(PG_FUNCTION_ARGS) +{ + inet *a1 = PG_GETARG_INET_PP(0); + inet *a2 = PG_GETARG_INET_PP(1); + + PG_RETURN_BOOL(network_cmp_internal(a1, a2) < 0); +} + +Datum +network_le(PG_FUNCTION_ARGS) +{ + inet *a1 = PG_GETARG_INET_PP(0); + inet *a2 = PG_GETARG_INET_PP(1); + + PG_RETURN_BOOL(network_cmp_internal(a1, a2) <= 0); +} + +Datum +network_eq(PG_FUNCTION_ARGS) +{ + inet *a1 = PG_GETARG_INET_PP(0); + inet *a2 = PG_GETARG_INET_PP(1); + + PG_RETURN_BOOL(network_cmp_internal(a1, a2) == 0); +} + +Datum +network_ge(PG_FUNCTION_ARGS) +{ + inet *a1 = PG_GETARG_INET_PP(0); + inet *a2 = PG_GETARG_INET_PP(1); + + PG_RETURN_BOOL(network_cmp_internal(a1, a2) >= 0); +} + +Datum +network_gt(PG_FUNCTION_ARGS) +{ + inet *a1 = PG_GETARG_INET_PP(0); + inet *a2 = PG_GETARG_INET_PP(1); + + PG_RETURN_BOOL(network_cmp_internal(a1, a2) > 0); +} + +Datum +network_ne(PG_FUNCTION_ARGS) +{ + inet *a1 = PG_GETARG_INET_PP(0); + inet *a2 = PG_GETARG_INET_PP(1); + + PG_RETURN_BOOL(network_cmp_internal(a1, a2) != 0); +} + +/* + * MIN/MAX support functions. + */ +Datum +network_smaller(PG_FUNCTION_ARGS) +{ + inet *a1 = PG_GETARG_INET_PP(0); + inet *a2 = PG_GETARG_INET_PP(1); + + if (network_cmp_internal(a1, a2) < 0) + PG_RETURN_INET_P(a1); + else + PG_RETURN_INET_P(a2); +} + +Datum +network_larger(PG_FUNCTION_ARGS) +{ + inet *a1 = PG_GETARG_INET_PP(0); + inet *a2 = PG_GETARG_INET_PP(1); + + if (network_cmp_internal(a1, a2) > 0) + PG_RETURN_INET_P(a1); + else + PG_RETURN_INET_P(a2); +} + +/* + * Support function for hash indexes on inet/cidr. + */ +Datum +hashinet(PG_FUNCTION_ARGS) +{ + inet *addr = PG_GETARG_INET_PP(0); + int addrsize = ip_addrsize(addr); + + /* XXX this assumes there are no pad bytes in the data structure */ + return hash_any((unsigned char *) VARDATA_ANY(addr), addrsize + 2); +} + +Datum +hashinetextended(PG_FUNCTION_ARGS) +{ + inet *addr = PG_GETARG_INET_PP(0); + int addrsize = ip_addrsize(addr); + + return hash_any_extended((unsigned char *) VARDATA_ANY(addr), addrsize + 2, + PG_GETARG_INT64(1)); +} + +/* + * Boolean network-inclusion tests. + */ +Datum +network_sub(PG_FUNCTION_ARGS) +{ + inet *a1 = PG_GETARG_INET_PP(0); + inet *a2 = PG_GETARG_INET_PP(1); + + if (ip_family(a1) == ip_family(a2)) + { + PG_RETURN_BOOL(ip_bits(a1) > ip_bits(a2) && + bitncmp(ip_addr(a1), ip_addr(a2), ip_bits(a2)) == 0); + } + + PG_RETURN_BOOL(false); +} + +Datum +network_subeq(PG_FUNCTION_ARGS) +{ + inet *a1 = PG_GETARG_INET_PP(0); + inet *a2 = PG_GETARG_INET_PP(1); + + if (ip_family(a1) == ip_family(a2)) + { + PG_RETURN_BOOL(ip_bits(a1) >= ip_bits(a2) && + bitncmp(ip_addr(a1), ip_addr(a2), ip_bits(a2)) == 0); + } + + PG_RETURN_BOOL(false); +} + +Datum +network_sup(PG_FUNCTION_ARGS) +{ + inet *a1 = PG_GETARG_INET_PP(0); + inet *a2 = PG_GETARG_INET_PP(1); + + if (ip_family(a1) == ip_family(a2)) + { + PG_RETURN_BOOL(ip_bits(a1) < ip_bits(a2) && + bitncmp(ip_addr(a1), ip_addr(a2), ip_bits(a1)) == 0); + } + + PG_RETURN_BOOL(false); +} + +Datum +network_supeq(PG_FUNCTION_ARGS) +{ + inet *a1 = PG_GETARG_INET_PP(0); + inet *a2 = PG_GETARG_INET_PP(1); + + if (ip_family(a1) == ip_family(a2)) + { + PG_RETURN_BOOL(ip_bits(a1) <= ip_bits(a2) && + bitncmp(ip_addr(a1), ip_addr(a2), ip_bits(a1)) == 0); + } + + PG_RETURN_BOOL(false); +} + +Datum +network_overlap(PG_FUNCTION_ARGS) +{ + inet *a1 = PG_GETARG_INET_PP(0); + inet *a2 = PG_GETARG_INET_PP(1); + + if (ip_family(a1) == ip_family(a2)) + { + PG_RETURN_BOOL(bitncmp(ip_addr(a1), ip_addr(a2), + Min(ip_bits(a1), ip_bits(a2))) == 0); + } + + PG_RETURN_BOOL(false); +} + +/* + * Planner support function for network subset/superset operators + */ +Datum +network_subset_support(PG_FUNCTION_ARGS) +{ + Node *rawreq = (Node *) PG_GETARG_POINTER(0); + Node *ret = NULL; + + if (IsA(rawreq, SupportRequestIndexCondition)) + { + /* Try to convert operator/function call to index conditions */ + SupportRequestIndexCondition *req = (SupportRequestIndexCondition *) rawreq; + + if (is_opclause(req->node)) + { + OpExpr *clause = (OpExpr *) req->node; + + Assert(list_length(clause->args) == 2); + ret = (Node *) + match_network_function((Node *) linitial(clause->args), + (Node *) lsecond(clause->args), + req->indexarg, + req->funcid, + req->opfamily); + } + else if (is_funcclause(req->node)) /* be paranoid */ + { + FuncExpr *clause = (FuncExpr *) req->node; + + Assert(list_length(clause->args) == 2); + ret = (Node *) + match_network_function((Node *) linitial(clause->args), + (Node *) lsecond(clause->args), + req->indexarg, + req->funcid, + req->opfamily); + } + } + + PG_RETURN_POINTER(ret); +} + +/* + * match_network_function + * Try to generate an indexqual for a network subset/superset function. + * + * This layer is just concerned with identifying the function and swapping + * the arguments if necessary. + */ +static List * +match_network_function(Node *leftop, + Node *rightop, + int indexarg, + Oid funcid, + Oid opfamily) +{ + switch (funcid) + { + case F_NETWORK_SUB: + /* indexkey must be on the left */ + if (indexarg != 0) + return NIL; + return match_network_subset(leftop, rightop, false, opfamily); + + case F_NETWORK_SUBEQ: + /* indexkey must be on the left */ + if (indexarg != 0) + return NIL; + return match_network_subset(leftop, rightop, true, opfamily); + + case F_NETWORK_SUP: + /* indexkey must be on the right */ + if (indexarg != 1) + return NIL; + return match_network_subset(rightop, leftop, false, opfamily); + + case F_NETWORK_SUPEQ: + /* indexkey must be on the right */ + if (indexarg != 1) + return NIL; + return match_network_subset(rightop, leftop, true, opfamily); + + default: + + /* + * We'd only get here if somebody attached this support function + * to an unexpected function. Maybe we should complain, but for + * now, do nothing. + */ + return NIL; + } +} + +/* + * match_network_subset + * Try to generate an indexqual for a network subset function. + */ +static List * +match_network_subset(Node *leftop, + Node *rightop, + bool is_eq, + Oid opfamily) +{ + List *result; + Datum rightopval; + Oid datatype = INETOID; + Oid opr1oid; + Oid opr2oid; + Datum opr1right; + Datum opr2right; + Expr *expr; + + /* + * Can't do anything with a non-constant or NULL comparison value. + * + * Note that since we restrict ourselves to cases with a hard constant on + * the RHS, it's a-fortiori a pseudoconstant, and we don't need to worry + * about verifying that. + */ + if (!IsA(rightop, Const) || + ((Const *) rightop)->constisnull) + return NIL; + rightopval = ((Const *) rightop)->constvalue; + + /* + * Must check that index's opfamily supports the operators we will want to + * apply. + * + * We insist on the opfamily being the specific one we expect, else we'd + * do the wrong thing if someone were to make a reverse-sort opfamily with + * the same operators. + */ + if (opfamily != NETWORK_BTREE_FAM_OID) + return NIL; + + /* + * create clause "key >= network_scan_first( rightopval )", or ">" if the + * operator disallows equality. + * + * Note: seeing that this function supports only fixed values for opfamily + * and datatype, we could just hard-wire the operator OIDs instead of + * looking them up. But for now it seems better to be general. + */ + if (is_eq) + { + opr1oid = get_opfamily_member(opfamily, datatype, datatype, + BTGreaterEqualStrategyNumber); + if (opr1oid == InvalidOid) + elog(ERROR, "no >= operator for opfamily %u", opfamily); + } + else + { + opr1oid = get_opfamily_member(opfamily, datatype, datatype, + BTGreaterStrategyNumber); + if (opr1oid == InvalidOid) + elog(ERROR, "no > operator for opfamily %u", opfamily); + } + + opr1right = network_scan_first(rightopval); + + expr = make_opclause(opr1oid, BOOLOID, false, + (Expr *) leftop, + (Expr *) makeConst(datatype, -1, + InvalidOid, /* not collatable */ + -1, opr1right, + false, false), + InvalidOid, InvalidOid); + result = list_make1(expr); + + /* create clause "key <= network_scan_last( rightopval )" */ + + opr2oid = get_opfamily_member(opfamily, datatype, datatype, + BTLessEqualStrategyNumber); + if (opr2oid == InvalidOid) + elog(ERROR, "no <= operator for opfamily %u", opfamily); + + opr2right = network_scan_last(rightopval); + + expr = make_opclause(opr2oid, BOOLOID, false, + (Expr *) leftop, + (Expr *) makeConst(datatype, -1, + InvalidOid, /* not collatable */ + -1, opr2right, + false, false), + InvalidOid, InvalidOid); + result = lappend(result, expr); + + return result; +} + + +/* + * Extract data from a network datatype. + */ +Datum +network_host(PG_FUNCTION_ARGS) +{ + inet *ip = PG_GETARG_INET_PP(0); + char *ptr; + char tmp[sizeof("xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:255.255.255.255/128")]; + + /* force display of max bits, regardless of masklen... */ + if (pg_inet_net_ntop(ip_family(ip), ip_addr(ip), ip_maxbits(ip), + tmp, sizeof(tmp)) == NULL) + ereport(ERROR, + (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION), + errmsg("could not format inet value: %m"))); + + /* Suppress /n if present (shouldn't happen now) */ + if ((ptr = strchr(tmp, '/')) != NULL) + *ptr = '\0'; + + PG_RETURN_TEXT_P(cstring_to_text(tmp)); +} + +/* + * network_show implements the inet and cidr casts to text. This is not + * quite the same behavior as network_out, hence we can't drop it in favor + * of CoerceViaIO. + */ +Datum +network_show(PG_FUNCTION_ARGS) +{ + inet *ip = PG_GETARG_INET_PP(0); + int len; + char tmp[sizeof("xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:255.255.255.255/128")]; + + if (pg_inet_net_ntop(ip_family(ip), ip_addr(ip), ip_maxbits(ip), + tmp, sizeof(tmp)) == NULL) + ereport(ERROR, + (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION), + errmsg("could not format inet value: %m"))); + + /* Add /n if not present (which it won't be) */ + if (strchr(tmp, '/') == NULL) + { + len = strlen(tmp); + snprintf(tmp + len, sizeof(tmp) - len, "/%u", ip_bits(ip)); + } + + PG_RETURN_TEXT_P(cstring_to_text(tmp)); +} + +Datum +inet_abbrev(PG_FUNCTION_ARGS) +{ + inet *ip = PG_GETARG_INET_PP(0); + char *dst; + char tmp[sizeof("xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:255.255.255.255/128")]; + + dst = pg_inet_net_ntop(ip_family(ip), ip_addr(ip), + ip_bits(ip), tmp, sizeof(tmp)); + + if (dst == NULL) + ereport(ERROR, + (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION), + errmsg("could not format inet value: %m"))); + + PG_RETURN_TEXT_P(cstring_to_text(tmp)); +} + +Datum +cidr_abbrev(PG_FUNCTION_ARGS) +{ + inet *ip = PG_GETARG_INET_PP(0); + char *dst; + char tmp[sizeof("xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:255.255.255.255/128")]; + + dst = pg_inet_cidr_ntop(ip_family(ip), ip_addr(ip), + ip_bits(ip), tmp, sizeof(tmp)); + + if (dst == NULL) + ereport(ERROR, + (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION), + errmsg("could not format cidr value: %m"))); + + PG_RETURN_TEXT_P(cstring_to_text(tmp)); +} + +Datum +network_masklen(PG_FUNCTION_ARGS) +{ + inet *ip = PG_GETARG_INET_PP(0); + + PG_RETURN_INT32(ip_bits(ip)); +} + +Datum +network_family(PG_FUNCTION_ARGS) +{ + inet *ip = PG_GETARG_INET_PP(0); + + switch (ip_family(ip)) + { + case PGSQL_AF_INET: + PG_RETURN_INT32(4); + break; + case PGSQL_AF_INET6: + PG_RETURN_INT32(6); + break; + default: + PG_RETURN_INT32(0); + break; + } +} + +Datum +network_broadcast(PG_FUNCTION_ARGS) +{ + inet *ip = PG_GETARG_INET_PP(0); + inet *dst; + int byte; + int bits; + int maxbytes; + unsigned char mask; + unsigned char *a, + *b; + + /* make sure any unused bits are zeroed */ + dst = (inet *) palloc0(sizeof(inet)); + + maxbytes = ip_addrsize(ip); + bits = ip_bits(ip); + a = ip_addr(ip); + b = ip_addr(dst); + + for (byte = 0; byte < maxbytes; byte++) + { + if (bits >= 8) + { + mask = 0x00; + bits -= 8; + } + else if (bits == 0) + mask = 0xff; + else + { + mask = 0xff >> bits; + bits = 0; + } + + b[byte] = a[byte] | mask; + } + + ip_family(dst) = ip_family(ip); + ip_bits(dst) = ip_bits(ip); + SET_INET_VARSIZE(dst); + + PG_RETURN_INET_P(dst); +} + +Datum +network_network(PG_FUNCTION_ARGS) +{ + inet *ip = PG_GETARG_INET_PP(0); + inet *dst; + int byte; + int bits; + unsigned char mask; + unsigned char *a, + *b; + + /* make sure any unused bits are zeroed */ + dst = (inet *) palloc0(sizeof(inet)); + + bits = ip_bits(ip); + a = ip_addr(ip); + b = ip_addr(dst); + + byte = 0; + + while (bits) + { + if (bits >= 8) + { + mask = 0xff; + bits -= 8; + } + else + { + mask = 0xff << (8 - bits); + bits = 0; + } + + b[byte] = a[byte] & mask; + byte++; + } + + ip_family(dst) = ip_family(ip); + ip_bits(dst) = ip_bits(ip); + SET_INET_VARSIZE(dst); + + PG_RETURN_INET_P(dst); +} + +Datum +network_netmask(PG_FUNCTION_ARGS) +{ + inet *ip = PG_GETARG_INET_PP(0); + inet *dst; + int byte; + int bits; + unsigned char mask; + unsigned char *b; + + /* make sure any unused bits are zeroed */ + dst = (inet *) palloc0(sizeof(inet)); + + bits = ip_bits(ip); + b = ip_addr(dst); + + byte = 0; + + while (bits) + { + if (bits >= 8) + { + mask = 0xff; + bits -= 8; + } + else + { + mask = 0xff << (8 - bits); + bits = 0; + } + + b[byte] = mask; + byte++; + } + + ip_family(dst) = ip_family(ip); + ip_bits(dst) = ip_maxbits(ip); + SET_INET_VARSIZE(dst); + + PG_RETURN_INET_P(dst); +} + +Datum +network_hostmask(PG_FUNCTION_ARGS) +{ + inet *ip = PG_GETARG_INET_PP(0); + inet *dst; + int byte; + int bits; + int maxbytes; + unsigned char mask; + unsigned char *b; + + /* make sure any unused bits are zeroed */ + dst = (inet *) palloc0(sizeof(inet)); + + maxbytes = ip_addrsize(ip); + bits = ip_maxbits(ip) - ip_bits(ip); + b = ip_addr(dst); + + byte = maxbytes - 1; + + while (bits) + { + if (bits >= 8) + { + mask = 0xff; + bits -= 8; + } + else + { + mask = 0xff >> (8 - bits); + bits = 0; + } + + b[byte] = mask; + byte--; + } + + ip_family(dst) = ip_family(ip); + ip_bits(dst) = ip_maxbits(ip); + SET_INET_VARSIZE(dst); + + PG_RETURN_INET_P(dst); +} + +/* + * Returns true if the addresses are from the same family, or false. Used to + * check that we can create a network which contains both of the networks. + */ +Datum +inet_same_family(PG_FUNCTION_ARGS) +{ + inet *a1 = PG_GETARG_INET_PP(0); + inet *a2 = PG_GETARG_INET_PP(1); + + PG_RETURN_BOOL(ip_family(a1) == ip_family(a2)); +} + +/* + * Returns the smallest CIDR which contains both of the inputs. + */ +Datum +inet_merge(PG_FUNCTION_ARGS) +{ + inet *a1 = PG_GETARG_INET_PP(0), + *a2 = PG_GETARG_INET_PP(1); + int commonbits; + + if (ip_family(a1) != ip_family(a2)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot merge addresses from different families"))); + + commonbits = bitncommon(ip_addr(a1), ip_addr(a2), + Min(ip_bits(a1), ip_bits(a2))); + + PG_RETURN_INET_P(cidr_set_masklen_internal(a1, commonbits)); +} + +/* + * Convert a value of a network datatype to an approximate scalar value. + * This is used for estimating selectivities of inequality operators + * involving network types. + * + * On failure (e.g., unsupported typid), set *failure to true; + * otherwise, that variable is not changed. + */ +double +convert_network_to_scalar(Datum value, Oid typid, bool *failure) +{ + switch (typid) + { + case INETOID: + case CIDROID: + { + inet *ip = DatumGetInetPP(value); + int len; + double res; + int i; + + /* + * Note that we don't use the full address for IPv6. + */ + if (ip_family(ip) == PGSQL_AF_INET) + len = 4; + else + len = 5; + + res = ip_family(ip); + for (i = 0; i < len; i++) + { + res *= 256; + res += ip_addr(ip)[i]; + } + return res; + } + case MACADDROID: + { + macaddr *mac = DatumGetMacaddrP(value); + double res; + + res = (mac->a << 16) | (mac->b << 8) | (mac->c); + res *= 256 * 256 * 256; + res += (mac->d << 16) | (mac->e << 8) | (mac->f); + return res; + } + case MACADDR8OID: + { + macaddr8 *mac = DatumGetMacaddr8P(value); + double res; + + res = (mac->a << 24) | (mac->b << 16) | (mac->c << 8) | (mac->d); + res *= ((double) 256) * 256 * 256 * 256; + res += (mac->e << 24) | (mac->f << 16) | (mac->g << 8) | (mac->h); + return res; + } + } + + *failure = true; + return 0; +} + +/* + * int + * bitncmp(l, r, n) + * compare bit masks l and r, for n bits. + * return: + * <0, >0, or 0 in the libc tradition. + * note: + * network byte order assumed. this means 192.5.5.240/28 has + * 0x11110000 in its fourth octet. + * author: + * Paul Vixie (ISC), June 1996 + */ +int +bitncmp(const unsigned char *l, const unsigned char *r, int n) +{ + unsigned int lb, + rb; + int x, + b; + + b = n / 8; + x = memcmp(l, r, b); + if (x || (n % 8) == 0) + return x; + + lb = l[b]; + rb = r[b]; + for (b = n % 8; b > 0; b--) + { + if (IS_HIGHBIT_SET(lb) != IS_HIGHBIT_SET(rb)) + { + if (IS_HIGHBIT_SET(lb)) + return 1; + return -1; + } + lb <<= 1; + rb <<= 1; + } + return 0; +} + +/* + * bitncommon: compare bit masks l and r, for up to n bits. + * + * Returns the number of leading bits that match (0 to n). + */ +int +bitncommon(const unsigned char *l, const unsigned char *r, int n) +{ + int byte, + nbits; + + /* number of bits to examine in last byte */ + nbits = n % 8; + + /* check whole bytes */ + for (byte = 0; byte < n / 8; byte++) + { + if (l[byte] != r[byte]) + { + /* at least one bit in the last byte is not common */ + nbits = 7; + break; + } + } + + /* check bits in last partial byte */ + if (nbits != 0) + { + /* calculate diff of first non-matching bytes */ + unsigned int diff = l[byte] ^ r[byte]; + + /* compare the bits from the most to the least */ + while ((diff >> (8 - nbits)) != 0) + nbits--; + } + + return (8 * byte) + nbits; +} + + +/* + * Verify a CIDR address is OK (doesn't have bits set past the masklen) + */ +static bool +addressOK(unsigned char *a, int bits, int family) +{ + int byte; + int nbits; + int maxbits; + int maxbytes; + unsigned char mask; + + if (family == PGSQL_AF_INET) + { + maxbits = 32; + maxbytes = 4; + } + else + { + maxbits = 128; + maxbytes = 16; + } + Assert(bits <= maxbits); + + if (bits == maxbits) + return true; + + byte = bits / 8; + + nbits = bits % 8; + mask = 0xff; + if (bits != 0) + mask >>= nbits; + + while (byte < maxbytes) + { + if ((a[byte] & mask) != 0) + return false; + mask = 0xff; + byte++; + } + + return true; +} + + +/* + * These functions are used by planner to generate indexscan limits + * for clauses a << b and a <<= b + */ + +/* return the minimal value for an IP on a given network */ +Datum +network_scan_first(Datum in) +{ + return DirectFunctionCall1(network_network, in); +} + +/* + * return "last" IP on a given network. It's the broadcast address, + * however, masklen has to be set to its max bits, since + * 192.168.0.255/24 is considered less than 192.168.0.255/32 + * + * inet_set_masklen() hacked to max out the masklength to 128 for IPv6 + * and 32 for IPv4 when given '-1' as argument. + */ +Datum +network_scan_last(Datum in) +{ + return DirectFunctionCall2(inet_set_masklen, + DirectFunctionCall1(network_broadcast, in), + Int32GetDatum(-1)); +} + + +/* + * IP address that the client is connecting from (NULL if Unix socket) + */ +Datum +inet_client_addr(PG_FUNCTION_ARGS) +{ + Port *port = MyProcPort; + char remote_host[NI_MAXHOST]; + int ret; + + if (port == NULL) + PG_RETURN_NULL(); + + switch (port->raddr.addr.ss_family) + { + case AF_INET: + case AF_INET6: + break; + default: + PG_RETURN_NULL(); + } + + remote_host[0] = '\0'; + + ret = pg_getnameinfo_all(&port->raddr.addr, port->raddr.salen, + remote_host, sizeof(remote_host), + NULL, 0, + NI_NUMERICHOST | NI_NUMERICSERV); + if (ret != 0) + PG_RETURN_NULL(); + + clean_ipv6_addr(port->raddr.addr.ss_family, remote_host); + + PG_RETURN_INET_P(network_in(remote_host, false, NULL)); +} + + +/* + * port that the client is connecting from (NULL if Unix socket) + */ +Datum +inet_client_port(PG_FUNCTION_ARGS) +{ + Port *port = MyProcPort; + char remote_port[NI_MAXSERV]; + int ret; + + if (port == NULL) + PG_RETURN_NULL(); + + switch (port->raddr.addr.ss_family) + { + case AF_INET: + case AF_INET6: + break; + default: + PG_RETURN_NULL(); + } + + remote_port[0] = '\0'; + + ret = pg_getnameinfo_all(&port->raddr.addr, port->raddr.salen, + NULL, 0, + remote_port, sizeof(remote_port), + NI_NUMERICHOST | NI_NUMERICSERV); + if (ret != 0) + PG_RETURN_NULL(); + + PG_RETURN_DATUM(DirectFunctionCall1(int4in, CStringGetDatum(remote_port))); +} + + +/* + * IP address that the server accepted the connection on (NULL if Unix socket) + */ +Datum +inet_server_addr(PG_FUNCTION_ARGS) +{ + Port *port = MyProcPort; + char local_host[NI_MAXHOST]; + int ret; + + if (port == NULL) + PG_RETURN_NULL(); + + switch (port->laddr.addr.ss_family) + { + case AF_INET: + case AF_INET6: + break; + default: + PG_RETURN_NULL(); + } + + local_host[0] = '\0'; + + ret = pg_getnameinfo_all(&port->laddr.addr, port->laddr.salen, + local_host, sizeof(local_host), + NULL, 0, + NI_NUMERICHOST | NI_NUMERICSERV); + if (ret != 0) + PG_RETURN_NULL(); + + clean_ipv6_addr(port->laddr.addr.ss_family, local_host); + + PG_RETURN_INET_P(network_in(local_host, false, NULL)); +} + + +/* + * port that the server accepted the connection on (NULL if Unix socket) + */ +Datum +inet_server_port(PG_FUNCTION_ARGS) +{ + Port *port = MyProcPort; + char local_port[NI_MAXSERV]; + int ret; + + if (port == NULL) + PG_RETURN_NULL(); + + switch (port->laddr.addr.ss_family) + { + case AF_INET: + case AF_INET6: + break; + default: + PG_RETURN_NULL(); + } + + local_port[0] = '\0'; + + ret = pg_getnameinfo_all(&port->laddr.addr, port->laddr.salen, + NULL, 0, + local_port, sizeof(local_port), + NI_NUMERICHOST | NI_NUMERICSERV); + if (ret != 0) + PG_RETURN_NULL(); + + PG_RETURN_DATUM(DirectFunctionCall1(int4in, CStringGetDatum(local_port))); +} + + +Datum +inetnot(PG_FUNCTION_ARGS) +{ + inet *ip = PG_GETARG_INET_PP(0); + inet *dst; + + dst = (inet *) palloc0(sizeof(inet)); + + { + int nb = ip_addrsize(ip); + unsigned char *pip = ip_addr(ip); + unsigned char *pdst = ip_addr(dst); + + while (--nb >= 0) + pdst[nb] = ~pip[nb]; + } + ip_bits(dst) = ip_bits(ip); + + ip_family(dst) = ip_family(ip); + SET_INET_VARSIZE(dst); + + PG_RETURN_INET_P(dst); +} + + +Datum +inetand(PG_FUNCTION_ARGS) +{ + inet *ip = PG_GETARG_INET_PP(0); + inet *ip2 = PG_GETARG_INET_PP(1); + inet *dst; + + dst = (inet *) palloc0(sizeof(inet)); + + if (ip_family(ip) != ip_family(ip2)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot AND inet values of different sizes"))); + else + { + int nb = ip_addrsize(ip); + unsigned char *pip = ip_addr(ip); + unsigned char *pip2 = ip_addr(ip2); + unsigned char *pdst = ip_addr(dst); + + while (--nb >= 0) + pdst[nb] = pip[nb] & pip2[nb]; + } + ip_bits(dst) = Max(ip_bits(ip), ip_bits(ip2)); + + ip_family(dst) = ip_family(ip); + SET_INET_VARSIZE(dst); + + PG_RETURN_INET_P(dst); +} + + +Datum +inetor(PG_FUNCTION_ARGS) +{ + inet *ip = PG_GETARG_INET_PP(0); + inet *ip2 = PG_GETARG_INET_PP(1); + inet *dst; + + dst = (inet *) palloc0(sizeof(inet)); + + if (ip_family(ip) != ip_family(ip2)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot OR inet values of different sizes"))); + else + { + int nb = ip_addrsize(ip); + unsigned char *pip = ip_addr(ip); + unsigned char *pip2 = ip_addr(ip2); + unsigned char *pdst = ip_addr(dst); + + while (--nb >= 0) + pdst[nb] = pip[nb] | pip2[nb]; + } + ip_bits(dst) = Max(ip_bits(ip), ip_bits(ip2)); + + ip_family(dst) = ip_family(ip); + SET_INET_VARSIZE(dst); + + PG_RETURN_INET_P(dst); +} + + +static inet * +internal_inetpl(inet *ip, int64 addend) +{ + inet *dst; + + dst = (inet *) palloc0(sizeof(inet)); + + { + int nb = ip_addrsize(ip); + unsigned char *pip = ip_addr(ip); + unsigned char *pdst = ip_addr(dst); + int carry = 0; + + while (--nb >= 0) + { + carry = pip[nb] + (int) (addend & 0xFF) + carry; + pdst[nb] = (unsigned char) (carry & 0xFF); + carry >>= 8; + + /* + * We have to be careful about right-shifting addend because + * right-shift isn't portable for negative values, while simply + * dividing by 256 doesn't work (the standard rounding is in the + * wrong direction, besides which there may be machines out there + * that round the wrong way). So, explicitly clear the low-order + * byte to remove any doubt about the correct result of the + * division, and then divide rather than shift. + */ + addend &= ~((int64) 0xFF); + addend /= 0x100; + } + + /* + * At this point we should have addend and carry both zero if original + * addend was >= 0, or addend -1 and carry 1 if original addend was < + * 0. Anything else means overflow. + */ + if (!((addend == 0 && carry == 0) || + (addend == -1 && carry == 1))) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("result is out of range"))); + } + + ip_bits(dst) = ip_bits(ip); + ip_family(dst) = ip_family(ip); + SET_INET_VARSIZE(dst); + + return dst; +} + + +Datum +inetpl(PG_FUNCTION_ARGS) +{ + inet *ip = PG_GETARG_INET_PP(0); + int64 addend = PG_GETARG_INT64(1); + + PG_RETURN_INET_P(internal_inetpl(ip, addend)); +} + + +Datum +inetmi_int8(PG_FUNCTION_ARGS) +{ + inet *ip = PG_GETARG_INET_PP(0); + int64 addend = PG_GETARG_INT64(1); + + PG_RETURN_INET_P(internal_inetpl(ip, -addend)); +} + + +Datum +inetmi(PG_FUNCTION_ARGS) +{ + inet *ip = PG_GETARG_INET_PP(0); + inet *ip2 = PG_GETARG_INET_PP(1); + int64 res = 0; + + if (ip_family(ip) != ip_family(ip2)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot subtract inet values of different sizes"))); + else + { + /* + * We form the difference using the traditional complement, increment, + * and add rule, with the increment part being handled by starting the + * carry off at 1. If you don't think integer arithmetic is done in + * two's complement, too bad. + */ + int nb = ip_addrsize(ip); + int byte = 0; + unsigned char *pip = ip_addr(ip); + unsigned char *pip2 = ip_addr(ip2); + int carry = 1; + + while (--nb >= 0) + { + int lobyte; + + carry = pip[nb] + (~pip2[nb] & 0xFF) + carry; + lobyte = carry & 0xFF; + if (byte < sizeof(int64)) + { + res |= ((int64) lobyte) << (byte * 8); + } + else + { + /* + * Input wider than int64: check for overflow. All bytes to + * the left of what will fit should be 0 or 0xFF, depending on + * sign of the now-complete result. + */ + if ((res < 0) ? (lobyte != 0xFF) : (lobyte != 0)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("result is out of range"))); + } + carry >>= 8; + byte++; + } + + /* + * If input is narrower than int64, overflow is not possible, but we + * have to do proper sign extension. + */ + if (carry == 0 && byte < sizeof(int64)) + res |= ((uint64) (int64) -1) << (byte * 8); + } + + PG_RETURN_INT64(res); +} + + +/* + * clean_ipv6_addr --- remove any '%zone' part from an IPv6 address string + * + * XXX This should go away someday! + * + * This is a kluge needed because we don't yet support zones in stored inet + * values. Since the result of getnameinfo() might include a zone spec, + * call this to remove it anywhere we want to feed getnameinfo's output to + * network_in. Beats failing entirely. + * + * An alternative approach would be to let network_in ignore %-parts for + * itself, but that would mean we'd silently drop zone specs in user input, + * which seems not such a good idea. + */ +void +clean_ipv6_addr(int addr_family, char *addr) +{ + if (addr_family == AF_INET6) + { + char *pct = strchr(addr, '%'); + + if (pct) + *pct = '\0'; + } +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/network_gist.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/network_gist.c new file mode 100644 index 00000000000..32cde28f00e --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/network_gist.c @@ -0,0 +1,810 @@ +/*------------------------------------------------------------------------- + * + * network_gist.c + * GiST support for network types. + * + * The key thing to understand about this code is the definition of the + * "union" of a set of INET/CIDR values. It works like this: + * 1. If the values are not all of the same IP address family, the "union" + * is a dummy value with family number zero, minbits zero, commonbits zero, + * address all zeroes. Otherwise: + * 2. The union has the common IP address family number. + * 3. The union's minbits value is the smallest netmask length ("ip_bits") + * of all the input values. + * 4. Let C be the number of leading address bits that are in common among + * all the input values (C ranges from 0 to ip_maxbits for the family). + * 5. The union's commonbits value is C. + * 6. The union's address value is the same as the common prefix for its + * first C bits, and is zeroes to the right of that. The physical width + * of the address value is ip_maxbits for the address family. + * + * In a leaf index entry (representing a single key), commonbits is equal to + * ip_maxbits for the address family, minbits is the same as the represented + * value's ip_bits, and the address is equal to the represented address. + * Although it may appear that we're wasting a byte by storing the union + * format and not just the represented INET/CIDR value in leaf keys, the + * extra byte is actually "free" because of alignment considerations. + * + * Note that this design tracks minbits and commonbits independently; in any + * given union value, either might be smaller than the other. This does not + * help us much when descending the tree, because of the way inet comparison + * is defined: at non-leaf nodes we can't compare more than minbits bits + * even if we know them. However, it greatly improves the quality of split + * decisions. Preliminary testing suggests that searches are as much as + * twice as fast as for a simpler design in which a single field doubles as + * the common prefix length and the minimum ip_bits value. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/adt/network_gist.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <sys/socket.h> + +#include "access/gist.h" +#include "access/stratnum.h" +#include "utils/builtins.h" +#include "utils/inet.h" +#include "varatt.h" + +/* + * Operator strategy numbers used in the GiST inet_ops opclass + */ +#define INETSTRAT_OVERLAPS RTOverlapStrategyNumber +#define INETSTRAT_EQ RTEqualStrategyNumber +#define INETSTRAT_NE RTNotEqualStrategyNumber +#define INETSTRAT_LT RTLessStrategyNumber +#define INETSTRAT_LE RTLessEqualStrategyNumber +#define INETSTRAT_GT RTGreaterStrategyNumber +#define INETSTRAT_GE RTGreaterEqualStrategyNumber +#define INETSTRAT_SUB RTSubStrategyNumber +#define INETSTRAT_SUBEQ RTSubEqualStrategyNumber +#define INETSTRAT_SUP RTSuperStrategyNumber +#define INETSTRAT_SUPEQ RTSuperEqualStrategyNumber + + +/* + * Representation of a GiST INET/CIDR index key. This is not identical to + * INET/CIDR because we need to keep track of the length of the common address + * prefix as well as the minimum netmask length. However, as long as it + * follows varlena header rules, the core GiST code won't know the difference. + * For simplicity we always use 1-byte-header varlena format. + */ +typedef struct GistInetKey +{ + uint8 va_header; /* varlena header --- don't touch directly */ + unsigned char family; /* PGSQL_AF_INET, PGSQL_AF_INET6, or zero */ + unsigned char minbits; /* minimum number of bits in netmask */ + unsigned char commonbits; /* number of common prefix bits in addresses */ + unsigned char ipaddr[16]; /* up to 128 bits of common address */ +} GistInetKey; + +#define DatumGetInetKeyP(X) ((GistInetKey *) DatumGetPointer(X)) +#define InetKeyPGetDatum(X) PointerGetDatum(X) + +/* + * Access macros; not really exciting, but we use these for notational + * consistency with access to INET/CIDR values. Note that family-zero values + * are stored with 4 bytes of address, not 16. + */ +#define gk_ip_family(gkptr) ((gkptr)->family) +#define gk_ip_minbits(gkptr) ((gkptr)->minbits) +#define gk_ip_commonbits(gkptr) ((gkptr)->commonbits) +#define gk_ip_addr(gkptr) ((gkptr)->ipaddr) +#define ip_family_maxbits(fam) ((fam) == PGSQL_AF_INET6 ? 128 : 32) + +/* These require that the family field has been set: */ +#define gk_ip_addrsize(gkptr) \ + (gk_ip_family(gkptr) == PGSQL_AF_INET6 ? 16 : 4) +#define gk_ip_maxbits(gkptr) \ + ip_family_maxbits(gk_ip_family(gkptr)) +#define SET_GK_VARSIZE(dst) \ + SET_VARSIZE_SHORT(dst, offsetof(GistInetKey, ipaddr) + gk_ip_addrsize(dst)) + + +/* + * The GiST query consistency check + */ +Datum +inet_gist_consistent(PG_FUNCTION_ARGS) +{ + GISTENTRY *ent = (GISTENTRY *) PG_GETARG_POINTER(0); + inet *query = PG_GETARG_INET_PP(1); + StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2); + + /* Oid subtype = PG_GETARG_OID(3); */ + bool *recheck = (bool *) PG_GETARG_POINTER(4); + GistInetKey *key = DatumGetInetKeyP(ent->key); + int minbits, + order; + + /* All operators served by this function are exact. */ + *recheck = false; + + /* + * Check 0: different families + * + * If key represents multiple address families, its children could match + * anything. This can only happen on an inner index page. + */ + if (gk_ip_family(key) == 0) + { + Assert(!GIST_LEAF(ent)); + PG_RETURN_BOOL(true); + } + + /* + * Check 1: different families + * + * Matching families do not help any of the strategies. + */ + if (gk_ip_family(key) != ip_family(query)) + { + switch (strategy) + { + case INETSTRAT_LT: + case INETSTRAT_LE: + if (gk_ip_family(key) < ip_family(query)) + PG_RETURN_BOOL(true); + break; + + case INETSTRAT_GE: + case INETSTRAT_GT: + if (gk_ip_family(key) > ip_family(query)) + PG_RETURN_BOOL(true); + break; + + case INETSTRAT_NE: + PG_RETURN_BOOL(true); + } + /* For all other cases, we can be sure there is no match */ + PG_RETURN_BOOL(false); + } + + /* + * Check 2: network bit count + * + * Network bit count (ip_bits) helps to check leaves for sub network and + * sup network operators. At non-leaf nodes, we know every child value + * has ip_bits >= gk_ip_minbits(key), so we can avoid descending in some + * cases too. + */ + switch (strategy) + { + case INETSTRAT_SUB: + if (GIST_LEAF(ent) && gk_ip_minbits(key) <= ip_bits(query)) + PG_RETURN_BOOL(false); + break; + + case INETSTRAT_SUBEQ: + if (GIST_LEAF(ent) && gk_ip_minbits(key) < ip_bits(query)) + PG_RETURN_BOOL(false); + break; + + case INETSTRAT_SUPEQ: + case INETSTRAT_EQ: + if (gk_ip_minbits(key) > ip_bits(query)) + PG_RETURN_BOOL(false); + break; + + case INETSTRAT_SUP: + if (gk_ip_minbits(key) >= ip_bits(query)) + PG_RETURN_BOOL(false); + break; + } + + /* + * Check 3: common network bits + * + * Compare available common prefix bits to the query, but not beyond + * either the query's netmask or the minimum netmask among the represented + * values. If these bits don't match the query, we have our answer (and + * may or may not need to descend, depending on the operator). If they do + * match, and we are not at a leaf, we descend in all cases. + * + * Note this is the final check for operators that only consider the + * network part of the address. + */ + minbits = Min(gk_ip_commonbits(key), gk_ip_minbits(key)); + minbits = Min(minbits, ip_bits(query)); + + order = bitncmp(gk_ip_addr(key), ip_addr(query), minbits); + + switch (strategy) + { + case INETSTRAT_SUB: + case INETSTRAT_SUBEQ: + case INETSTRAT_OVERLAPS: + case INETSTRAT_SUPEQ: + case INETSTRAT_SUP: + PG_RETURN_BOOL(order == 0); + + case INETSTRAT_LT: + case INETSTRAT_LE: + if (order > 0) + PG_RETURN_BOOL(false); + if (order < 0 || !GIST_LEAF(ent)) + PG_RETURN_BOOL(true); + break; + + case INETSTRAT_EQ: + if (order != 0) + PG_RETURN_BOOL(false); + if (!GIST_LEAF(ent)) + PG_RETURN_BOOL(true); + break; + + case INETSTRAT_GE: + case INETSTRAT_GT: + if (order < 0) + PG_RETURN_BOOL(false); + if (order > 0 || !GIST_LEAF(ent)) + PG_RETURN_BOOL(true); + break; + + case INETSTRAT_NE: + if (order != 0 || !GIST_LEAF(ent)) + PG_RETURN_BOOL(true); + break; + } + + /* + * Remaining checks are only for leaves and basic comparison strategies. + * See network_cmp_internal() in network.c for the implementation we need + * to match. Note that in a leaf key, commonbits should equal the address + * length, so we compared the whole network parts above. + */ + Assert(GIST_LEAF(ent)); + + /* + * Check 4: network bit count + * + * Next step is to compare netmask widths. + */ + switch (strategy) + { + case INETSTRAT_LT: + case INETSTRAT_LE: + if (gk_ip_minbits(key) < ip_bits(query)) + PG_RETURN_BOOL(true); + if (gk_ip_minbits(key) > ip_bits(query)) + PG_RETURN_BOOL(false); + break; + + case INETSTRAT_EQ: + if (gk_ip_minbits(key) != ip_bits(query)) + PG_RETURN_BOOL(false); + break; + + case INETSTRAT_GE: + case INETSTRAT_GT: + if (gk_ip_minbits(key) > ip_bits(query)) + PG_RETURN_BOOL(true); + if (gk_ip_minbits(key) < ip_bits(query)) + PG_RETURN_BOOL(false); + break; + + case INETSTRAT_NE: + if (gk_ip_minbits(key) != ip_bits(query)) + PG_RETURN_BOOL(true); + break; + } + + /* + * Check 5: whole address + * + * Netmask bit counts are the same, so check all the address bits. + */ + order = bitncmp(gk_ip_addr(key), ip_addr(query), gk_ip_maxbits(key)); + + switch (strategy) + { + case INETSTRAT_LT: + PG_RETURN_BOOL(order < 0); + + case INETSTRAT_LE: + PG_RETURN_BOOL(order <= 0); + + case INETSTRAT_EQ: + PG_RETURN_BOOL(order == 0); + + case INETSTRAT_GE: + PG_RETURN_BOOL(order >= 0); + + case INETSTRAT_GT: + PG_RETURN_BOOL(order > 0); + + case INETSTRAT_NE: + PG_RETURN_BOOL(order != 0); + } + + elog(ERROR, "unknown strategy for inet GiST"); + PG_RETURN_BOOL(false); /* keep compiler quiet */ +} + +/* + * Calculate parameters of the union of some GistInetKeys. + * + * Examine the keys in elements m..n inclusive of the GISTENTRY array, + * and compute these output parameters: + * *minfamily_p = minimum IP address family number + * *maxfamily_p = maximum IP address family number + * *minbits_p = minimum netmask width + * *commonbits_p = number of leading bits in common among the addresses + * + * minbits and commonbits are forced to zero if there's more than one + * address family. + */ +static void +calc_inet_union_params(GISTENTRY *ent, + int m, int n, + int *minfamily_p, + int *maxfamily_p, + int *minbits_p, + int *commonbits_p) +{ + int minfamily, + maxfamily, + minbits, + commonbits; + unsigned char *addr; + GistInetKey *tmp; + int i; + + /* Must be at least one key. */ + Assert(m <= n); + + /* Initialize variables using the first key. */ + tmp = DatumGetInetKeyP(ent[m].key); + minfamily = maxfamily = gk_ip_family(tmp); + minbits = gk_ip_minbits(tmp); + commonbits = gk_ip_commonbits(tmp); + addr = gk_ip_addr(tmp); + + /* Scan remaining keys. */ + for (i = m + 1; i <= n; i++) + { + tmp = DatumGetInetKeyP(ent[i].key); + + /* Determine range of family numbers */ + if (minfamily > gk_ip_family(tmp)) + minfamily = gk_ip_family(tmp); + if (maxfamily < gk_ip_family(tmp)) + maxfamily = gk_ip_family(tmp); + + /* Find minimum minbits */ + if (minbits > gk_ip_minbits(tmp)) + minbits = gk_ip_minbits(tmp); + + /* Find minimum number of bits in common */ + if (commonbits > gk_ip_commonbits(tmp)) + commonbits = gk_ip_commonbits(tmp); + if (commonbits > 0) + commonbits = bitncommon(addr, gk_ip_addr(tmp), commonbits); + } + + /* Force minbits/commonbits to zero if more than one family. */ + if (minfamily != maxfamily) + minbits = commonbits = 0; + + *minfamily_p = minfamily; + *maxfamily_p = maxfamily; + *minbits_p = minbits; + *commonbits_p = commonbits; +} + +/* + * Same as above, but the GISTENTRY elements to examine are those with + * indices listed in the offsets[] array. + */ +static void +calc_inet_union_params_indexed(GISTENTRY *ent, + OffsetNumber *offsets, int noffsets, + int *minfamily_p, + int *maxfamily_p, + int *minbits_p, + int *commonbits_p) +{ + int minfamily, + maxfamily, + minbits, + commonbits; + unsigned char *addr; + GistInetKey *tmp; + int i; + + /* Must be at least one key. */ + Assert(noffsets > 0); + + /* Initialize variables using the first key. */ + tmp = DatumGetInetKeyP(ent[offsets[0]].key); + minfamily = maxfamily = gk_ip_family(tmp); + minbits = gk_ip_minbits(tmp); + commonbits = gk_ip_commonbits(tmp); + addr = gk_ip_addr(tmp); + + /* Scan remaining keys. */ + for (i = 1; i < noffsets; i++) + { + tmp = DatumGetInetKeyP(ent[offsets[i]].key); + + /* Determine range of family numbers */ + if (minfamily > gk_ip_family(tmp)) + minfamily = gk_ip_family(tmp); + if (maxfamily < gk_ip_family(tmp)) + maxfamily = gk_ip_family(tmp); + + /* Find minimum minbits */ + if (minbits > gk_ip_minbits(tmp)) + minbits = gk_ip_minbits(tmp); + + /* Find minimum number of bits in common */ + if (commonbits > gk_ip_commonbits(tmp)) + commonbits = gk_ip_commonbits(tmp); + if (commonbits > 0) + commonbits = bitncommon(addr, gk_ip_addr(tmp), commonbits); + } + + /* Force minbits/commonbits to zero if more than one family. */ + if (minfamily != maxfamily) + minbits = commonbits = 0; + + *minfamily_p = minfamily; + *maxfamily_p = maxfamily; + *minbits_p = minbits; + *commonbits_p = commonbits; +} + +/* + * Construct a GistInetKey representing a union value. + * + * Inputs are the family/minbits/commonbits values to use, plus a pointer to + * the address field of one of the union inputs. (Since we're going to copy + * just the bits-in-common, it doesn't matter which one.) + */ +static GistInetKey * +build_inet_union_key(int family, int minbits, int commonbits, + unsigned char *addr) +{ + GistInetKey *result; + + /* Make sure any unused bits are zeroed. */ + result = (GistInetKey *) palloc0(sizeof(GistInetKey)); + + gk_ip_family(result) = family; + gk_ip_minbits(result) = minbits; + gk_ip_commonbits(result) = commonbits; + + /* Clone appropriate bytes of the address. */ + if (commonbits > 0) + memcpy(gk_ip_addr(result), addr, (commonbits + 7) / 8); + + /* Clean any unwanted bits in the last partial byte. */ + if (commonbits % 8 != 0) + gk_ip_addr(result)[commonbits / 8] &= ~(0xFF >> (commonbits % 8)); + + /* Set varlena header correctly. */ + SET_GK_VARSIZE(result); + + return result; +} + + +/* + * The GiST union function + * + * See comments at head of file for the definition of the union. + */ +Datum +inet_gist_union(PG_FUNCTION_ARGS) +{ + GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); + GISTENTRY *ent = entryvec->vector; + int minfamily, + maxfamily, + minbits, + commonbits; + unsigned char *addr; + GistInetKey *tmp, + *result; + + /* Determine parameters of the union. */ + calc_inet_union_params(ent, 0, entryvec->n - 1, + &minfamily, &maxfamily, + &minbits, &commonbits); + + /* If more than one family, emit family number zero. */ + if (minfamily != maxfamily) + minfamily = 0; + + /* Initialize address using the first key. */ + tmp = DatumGetInetKeyP(ent[0].key); + addr = gk_ip_addr(tmp); + + /* Construct the union value. */ + result = build_inet_union_key(minfamily, minbits, commonbits, addr); + + PG_RETURN_POINTER(result); +} + +/* + * The GiST compress function + * + * Convert an inet value to GistInetKey. + */ +Datum +inet_gist_compress(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + GISTENTRY *retval; + + if (entry->leafkey) + { + retval = palloc(sizeof(GISTENTRY)); + if (DatumGetPointer(entry->key) != NULL) + { + inet *in = DatumGetInetPP(entry->key); + GistInetKey *r; + + r = (GistInetKey *) palloc0(sizeof(GistInetKey)); + + gk_ip_family(r) = ip_family(in); + gk_ip_minbits(r) = ip_bits(in); + gk_ip_commonbits(r) = gk_ip_maxbits(r); + memcpy(gk_ip_addr(r), ip_addr(in), gk_ip_addrsize(r)); + SET_GK_VARSIZE(r); + + gistentryinit(*retval, PointerGetDatum(r), + entry->rel, entry->page, + entry->offset, false); + } + else + { + gistentryinit(*retval, (Datum) 0, + entry->rel, entry->page, + entry->offset, false); + } + } + else + retval = entry; + PG_RETURN_POINTER(retval); +} + +/* + * We do not need a decompress function, because the other GiST inet + * support functions work with the GistInetKey representation. + */ + +/* + * The GiST fetch function + * + * Reconstruct the original inet datum from a GistInetKey. + */ +Datum +inet_gist_fetch(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + GistInetKey *key = DatumGetInetKeyP(entry->key); + GISTENTRY *retval; + inet *dst; + + dst = (inet *) palloc0(sizeof(inet)); + + ip_family(dst) = gk_ip_family(key); + ip_bits(dst) = gk_ip_minbits(key); + memcpy(ip_addr(dst), gk_ip_addr(key), ip_addrsize(dst)); + SET_INET_VARSIZE(dst); + + retval = palloc(sizeof(GISTENTRY)); + gistentryinit(*retval, InetPGetDatum(dst), entry->rel, entry->page, + entry->offset, false); + + PG_RETURN_POINTER(retval); +} + +/* + * The GiST page split penalty function + * + * Charge a large penalty if address family doesn't match, or a somewhat + * smaller one if the new value would degrade the union's minbits + * (minimum netmask width). Otherwise, penalty is inverse of the + * new number of common address bits. + */ +Datum +inet_gist_penalty(PG_FUNCTION_ARGS) +{ + GISTENTRY *origent = (GISTENTRY *) PG_GETARG_POINTER(0); + GISTENTRY *newent = (GISTENTRY *) PG_GETARG_POINTER(1); + float *penalty = (float *) PG_GETARG_POINTER(2); + GistInetKey *orig = DatumGetInetKeyP(origent->key), + *new = DatumGetInetKeyP(newent->key); + int commonbits; + + if (gk_ip_family(orig) == gk_ip_family(new)) + { + if (gk_ip_minbits(orig) <= gk_ip_minbits(new)) + { + commonbits = bitncommon(gk_ip_addr(orig), gk_ip_addr(new), + Min(gk_ip_commonbits(orig), + gk_ip_commonbits(new))); + if (commonbits > 0) + *penalty = 1.0f / commonbits; + else + *penalty = 2; + } + else + *penalty = 3; + } + else + *penalty = 4; + + PG_RETURN_POINTER(penalty); +} + +/* + * The GiST PickSplit method + * + * There are two ways to split. First one is to split by address families, + * if there are multiple families appearing in the input. + * + * The second and more common way is to split by addresses. To achieve this, + * determine the number of leading bits shared by all the keys, then split on + * the next bit. (We don't currently consider the netmask widths while doing + * this; should we?) If we fail to get a nontrivial split that way, split + * 50-50. + */ +Datum +inet_gist_picksplit(PG_FUNCTION_ARGS) +{ + GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); + GIST_SPLITVEC *splitvec = (GIST_SPLITVEC *) PG_GETARG_POINTER(1); + GISTENTRY *ent = entryvec->vector; + int minfamily, + maxfamily, + minbits, + commonbits; + unsigned char *addr; + GistInetKey *tmp, + *left_union, + *right_union; + int maxoff, + nbytes; + OffsetNumber i, + *left, + *right; + + maxoff = entryvec->n - 1; + nbytes = (maxoff + 1) * sizeof(OffsetNumber); + + left = (OffsetNumber *) palloc(nbytes); + right = (OffsetNumber *) palloc(nbytes); + + splitvec->spl_left = left; + splitvec->spl_right = right; + + splitvec->spl_nleft = 0; + splitvec->spl_nright = 0; + + /* Determine parameters of the union of all the inputs. */ + calc_inet_union_params(ent, FirstOffsetNumber, maxoff, + &minfamily, &maxfamily, + &minbits, &commonbits); + + if (minfamily != maxfamily) + { + /* Multiple families, so split by family. */ + for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i)) + { + /* + * If there's more than 2 families, all but maxfamily go into the + * left union. This could only happen if the inputs include some + * IPv4, some IPv6, and some already-multiple-family unions. + */ + tmp = DatumGetInetKeyP(ent[i].key); + if (gk_ip_family(tmp) != maxfamily) + left[splitvec->spl_nleft++] = i; + else + right[splitvec->spl_nright++] = i; + } + } + else + { + /* + * Split on the next bit after the common bits. If that yields a + * trivial split, try the next bit position to the right. Repeat till + * success; or if we run out of bits, do an arbitrary 50-50 split. + */ + int maxbits = ip_family_maxbits(minfamily); + + while (commonbits < maxbits) + { + /* Split using the commonbits'th bit position. */ + int bitbyte = commonbits / 8; + int bitmask = 0x80 >> (commonbits % 8); + + splitvec->spl_nleft = splitvec->spl_nright = 0; + + for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i)) + { + tmp = DatumGetInetKeyP(ent[i].key); + addr = gk_ip_addr(tmp); + if ((addr[bitbyte] & bitmask) == 0) + left[splitvec->spl_nleft++] = i; + else + right[splitvec->spl_nright++] = i; + } + + if (splitvec->spl_nleft > 0 && splitvec->spl_nright > 0) + break; /* success */ + commonbits++; + } + + if (commonbits >= maxbits) + { + /* Failed ... do a 50-50 split. */ + splitvec->spl_nleft = splitvec->spl_nright = 0; + + for (i = FirstOffsetNumber; i <= maxoff / 2; i = OffsetNumberNext(i)) + { + left[splitvec->spl_nleft++] = i; + } + for (; i <= maxoff; i = OffsetNumberNext(i)) + { + right[splitvec->spl_nright++] = i; + } + } + } + + /* + * Compute the union value for each side from scratch. In most cases we + * could approximate the union values with what we already know, but this + * ensures that each side has minbits and commonbits set as high as + * possible. + */ + calc_inet_union_params_indexed(ent, left, splitvec->spl_nleft, + &minfamily, &maxfamily, + &minbits, &commonbits); + if (minfamily != maxfamily) + minfamily = 0; + tmp = DatumGetInetKeyP(ent[left[0]].key); + addr = gk_ip_addr(tmp); + left_union = build_inet_union_key(minfamily, minbits, commonbits, addr); + splitvec->spl_ldatum = PointerGetDatum(left_union); + + calc_inet_union_params_indexed(ent, right, splitvec->spl_nright, + &minfamily, &maxfamily, + &minbits, &commonbits); + if (minfamily != maxfamily) + minfamily = 0; + tmp = DatumGetInetKeyP(ent[right[0]].key); + addr = gk_ip_addr(tmp); + right_union = build_inet_union_key(minfamily, minbits, commonbits, addr); + splitvec->spl_rdatum = PointerGetDatum(right_union); + + PG_RETURN_POINTER(splitvec); +} + +/* + * The GiST equality function + */ +Datum +inet_gist_same(PG_FUNCTION_ARGS) +{ + GistInetKey *left = DatumGetInetKeyP(PG_GETARG_DATUM(0)); + GistInetKey *right = DatumGetInetKeyP(PG_GETARG_DATUM(1)); + bool *result = (bool *) PG_GETARG_POINTER(2); + + *result = (gk_ip_family(left) == gk_ip_family(right) && + gk_ip_minbits(left) == gk_ip_minbits(right) && + gk_ip_commonbits(left) == gk_ip_commonbits(right) && + memcmp(gk_ip_addr(left), gk_ip_addr(right), + gk_ip_addrsize(left)) == 0); + + PG_RETURN_POINTER(result); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/network_selfuncs.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/network_selfuncs.c new file mode 100644 index 00000000000..315985215c3 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/network_selfuncs.c @@ -0,0 +1,972 @@ +/*------------------------------------------------------------------------- + * + * network_selfuncs.c + * Functions for selectivity estimation of inet/cidr operators + * + * This module provides estimators for the subnet inclusion and overlap + * operators. Estimates are based on null fraction, most common values, + * and histogram of inet/cidr columns. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/adt/network_selfuncs.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <math.h> + +#include "access/htup_details.h" +#include "catalog/pg_operator.h" +#include "catalog/pg_statistic.h" +#include "utils/builtins.h" +#include "utils/inet.h" +#include "utils/lsyscache.h" +#include "utils/selfuncs.h" + + +/* Default selectivity for the inet overlap operator */ +#define DEFAULT_OVERLAP_SEL 0.01 + +/* Default selectivity for the various inclusion operators */ +#define DEFAULT_INCLUSION_SEL 0.005 + +/* Default selectivity for specified operator */ +#define DEFAULT_SEL(operator) \ + ((operator) == OID_INET_OVERLAP_OP ? \ + DEFAULT_OVERLAP_SEL : DEFAULT_INCLUSION_SEL) + +/* Maximum number of items to consider in join selectivity calculations */ +#define MAX_CONSIDERED_ELEMS 1024 + +static Selectivity networkjoinsel_inner(Oid operator, + VariableStatData *vardata1, VariableStatData *vardata2); +static Selectivity networkjoinsel_semi(Oid operator, + VariableStatData *vardata1, VariableStatData *vardata2); +static Selectivity mcv_population(float4 *mcv_numbers, int mcv_nvalues); +static Selectivity inet_hist_value_sel(Datum *values, int nvalues, + Datum constvalue, int opr_codenum); +static Selectivity inet_mcv_join_sel(Datum *mcv1_values, + float4 *mcv1_numbers, int mcv1_nvalues, Datum *mcv2_values, + float4 *mcv2_numbers, int mcv2_nvalues, Oid operator); +static Selectivity inet_mcv_hist_sel(Datum *mcv_values, float4 *mcv_numbers, + int mcv_nvalues, Datum *hist_values, int hist_nvalues, + int opr_codenum); +static Selectivity inet_hist_inclusion_join_sel(Datum *hist1_values, + int hist1_nvalues, + Datum *hist2_values, int hist2_nvalues, + int opr_codenum); +static Selectivity inet_semi_join_sel(Datum lhs_value, + bool mcv_exists, Datum *mcv_values, int mcv_nvalues, + bool hist_exists, Datum *hist_values, int hist_nvalues, + double hist_weight, + FmgrInfo *proc, int opr_codenum); +static int inet_opr_codenum(Oid operator); +static int inet_inclusion_cmp(inet *left, inet *right, int opr_codenum); +static int inet_masklen_inclusion_cmp(inet *left, inet *right, + int opr_codenum); +static int inet_hist_match_divider(inet *boundary, inet *query, + int opr_codenum); + +/* + * Selectivity estimation for the subnet inclusion/overlap operators + */ +Datum +networksel(PG_FUNCTION_ARGS) +{ + PlannerInfo *root = (PlannerInfo *) PG_GETARG_POINTER(0); + Oid operator = PG_GETARG_OID(1); + List *args = (List *) PG_GETARG_POINTER(2); + int varRelid = PG_GETARG_INT32(3); + VariableStatData vardata; + Node *other; + bool varonleft; + Selectivity selec, + mcv_selec, + non_mcv_selec; + Datum constvalue; + Form_pg_statistic stats; + AttStatsSlot hslot; + double sumcommon, + nullfrac; + FmgrInfo proc; + + /* + * If expression is not (variable op something) or (something op + * variable), then punt and return a default estimate. + */ + if (!get_restriction_variable(root, args, varRelid, + &vardata, &other, &varonleft)) + PG_RETURN_FLOAT8(DEFAULT_SEL(operator)); + + /* + * Can't do anything useful if the something is not a constant, either. + */ + if (!IsA(other, Const)) + { + ReleaseVariableStats(vardata); + PG_RETURN_FLOAT8(DEFAULT_SEL(operator)); + } + + /* All of the operators handled here are strict. */ + if (((Const *) other)->constisnull) + { + ReleaseVariableStats(vardata); + PG_RETURN_FLOAT8(0.0); + } + constvalue = ((Const *) other)->constvalue; + + /* Otherwise, we need stats in order to produce a non-default estimate. */ + if (!HeapTupleIsValid(vardata.statsTuple)) + { + ReleaseVariableStats(vardata); + PG_RETURN_FLOAT8(DEFAULT_SEL(operator)); + } + + stats = (Form_pg_statistic) GETSTRUCT(vardata.statsTuple); + nullfrac = stats->stanullfrac; + + /* + * If we have most-common-values info, add up the fractions of the MCV + * entries that satisfy MCV OP CONST. These fractions contribute directly + * to the result selectivity. Also add up the total fraction represented + * by MCV entries. + */ + fmgr_info(get_opcode(operator), &proc); + mcv_selec = mcv_selectivity(&vardata, &proc, InvalidOid, + constvalue, varonleft, + &sumcommon); + + /* + * If we have a histogram, use it to estimate the proportion of the + * non-MCV population that satisfies the clause. If we don't, apply the + * default selectivity to that population. + */ + if (get_attstatsslot(&hslot, vardata.statsTuple, + STATISTIC_KIND_HISTOGRAM, InvalidOid, + ATTSTATSSLOT_VALUES)) + { + int opr_codenum = inet_opr_codenum(operator); + + /* Commute if needed, so we can consider histogram to be on the left */ + if (!varonleft) + opr_codenum = -opr_codenum; + non_mcv_selec = inet_hist_value_sel(hslot.values, hslot.nvalues, + constvalue, opr_codenum); + + free_attstatsslot(&hslot); + } + else + non_mcv_selec = DEFAULT_SEL(operator); + + /* Combine selectivities for MCV and non-MCV populations */ + selec = mcv_selec + (1.0 - nullfrac - sumcommon) * non_mcv_selec; + + /* Result should be in range, but make sure... */ + CLAMP_PROBABILITY(selec); + + ReleaseVariableStats(vardata); + + PG_RETURN_FLOAT8(selec); +} + +/* + * Join selectivity estimation for the subnet inclusion/overlap operators + * + * This function has the same structure as eqjoinsel() in selfuncs.c. + * + * Throughout networkjoinsel and its subroutines, we have a performance issue + * in that the amount of work to be done is O(N^2) in the length of the MCV + * and histogram arrays. To keep the runtime from getting out of hand when + * large statistics targets have been set, we arbitrarily limit the number of + * values considered to 1024 (MAX_CONSIDERED_ELEMS). For the MCV arrays, this + * is easy: just consider at most the first N elements. (Since the MCVs are + * sorted by decreasing frequency, this correctly gets us the first N MCVs.) + * For the histogram arrays, we decimate; that is consider only every k'th + * element, where k is chosen so that no more than MAX_CONSIDERED_ELEMS + * elements are considered. This should still give us a good random sample of + * the non-MCV population. Decimation is done on-the-fly in the loops that + * iterate over the histogram arrays. + */ +Datum +networkjoinsel(PG_FUNCTION_ARGS) +{ + PlannerInfo *root = (PlannerInfo *) PG_GETARG_POINTER(0); + Oid operator = PG_GETARG_OID(1); + List *args = (List *) PG_GETARG_POINTER(2); +#ifdef NOT_USED + JoinType jointype = (JoinType) PG_GETARG_INT16(3); +#endif + SpecialJoinInfo *sjinfo = (SpecialJoinInfo *) PG_GETARG_POINTER(4); + double selec; + VariableStatData vardata1; + VariableStatData vardata2; + bool join_is_reversed; + + get_join_variables(root, args, sjinfo, + &vardata1, &vardata2, &join_is_reversed); + + switch (sjinfo->jointype) + { + case JOIN_INNER: + case JOIN_LEFT: + case JOIN_FULL: + + /* + * Selectivity for left/full join is not exactly the same as inner + * join, but we neglect the difference, as eqjoinsel does. + */ + selec = networkjoinsel_inner(operator, &vardata1, &vardata2); + break; + case JOIN_SEMI: + case JOIN_ANTI: + /* Here, it's important that we pass the outer var on the left. */ + if (!join_is_reversed) + selec = networkjoinsel_semi(operator, &vardata1, &vardata2); + else + selec = networkjoinsel_semi(get_commutator(operator), + &vardata2, &vardata1); + break; + default: + /* other values not expected here */ + elog(ERROR, "unrecognized join type: %d", + (int) sjinfo->jointype); + selec = 0; /* keep compiler quiet */ + break; + } + + ReleaseVariableStats(vardata1); + ReleaseVariableStats(vardata2); + + CLAMP_PROBABILITY(selec); + + PG_RETURN_FLOAT8((float8) selec); +} + +/* + * Inner join selectivity estimation for subnet inclusion/overlap operators + * + * Calculates MCV vs MCV, MCV vs histogram and histogram vs histogram + * selectivity for join using the subnet inclusion operators. Unlike the + * join selectivity function for the equality operator, eqjoinsel_inner(), + * one to one matching of the values is not enough. Network inclusion + * operators are likely to match many to many, so we must check all pairs. + * (Note: it might be possible to exploit understanding of the histogram's + * btree ordering to reduce the work needed, but we don't currently try.) + * Also, MCV vs histogram selectivity is not neglected as in eqjoinsel_inner(). + */ +static Selectivity +networkjoinsel_inner(Oid operator, + VariableStatData *vardata1, VariableStatData *vardata2) +{ + Form_pg_statistic stats; + double nullfrac1 = 0.0, + nullfrac2 = 0.0; + Selectivity selec = 0.0, + sumcommon1 = 0.0, + sumcommon2 = 0.0; + bool mcv1_exists = false, + mcv2_exists = false, + hist1_exists = false, + hist2_exists = false; + int opr_codenum; + int mcv1_length = 0, + mcv2_length = 0; + AttStatsSlot mcv1_slot; + AttStatsSlot mcv2_slot; + AttStatsSlot hist1_slot; + AttStatsSlot hist2_slot; + + if (HeapTupleIsValid(vardata1->statsTuple)) + { + stats = (Form_pg_statistic) GETSTRUCT(vardata1->statsTuple); + nullfrac1 = stats->stanullfrac; + + mcv1_exists = get_attstatsslot(&mcv1_slot, vardata1->statsTuple, + STATISTIC_KIND_MCV, InvalidOid, + ATTSTATSSLOT_VALUES | ATTSTATSSLOT_NUMBERS); + hist1_exists = get_attstatsslot(&hist1_slot, vardata1->statsTuple, + STATISTIC_KIND_HISTOGRAM, InvalidOid, + ATTSTATSSLOT_VALUES); + /* Arbitrarily limit number of MCVs considered */ + mcv1_length = Min(mcv1_slot.nvalues, MAX_CONSIDERED_ELEMS); + if (mcv1_exists) + sumcommon1 = mcv_population(mcv1_slot.numbers, mcv1_length); + } + else + { + memset(&mcv1_slot, 0, sizeof(mcv1_slot)); + memset(&hist1_slot, 0, sizeof(hist1_slot)); + } + + if (HeapTupleIsValid(vardata2->statsTuple)) + { + stats = (Form_pg_statistic) GETSTRUCT(vardata2->statsTuple); + nullfrac2 = stats->stanullfrac; + + mcv2_exists = get_attstatsslot(&mcv2_slot, vardata2->statsTuple, + STATISTIC_KIND_MCV, InvalidOid, + ATTSTATSSLOT_VALUES | ATTSTATSSLOT_NUMBERS); + hist2_exists = get_attstatsslot(&hist2_slot, vardata2->statsTuple, + STATISTIC_KIND_HISTOGRAM, InvalidOid, + ATTSTATSSLOT_VALUES); + /* Arbitrarily limit number of MCVs considered */ + mcv2_length = Min(mcv2_slot.nvalues, MAX_CONSIDERED_ELEMS); + if (mcv2_exists) + sumcommon2 = mcv_population(mcv2_slot.numbers, mcv2_length); + } + else + { + memset(&mcv2_slot, 0, sizeof(mcv2_slot)); + memset(&hist2_slot, 0, sizeof(hist2_slot)); + } + + opr_codenum = inet_opr_codenum(operator); + + /* + * Calculate selectivity for MCV vs MCV matches. + */ + if (mcv1_exists && mcv2_exists) + selec += inet_mcv_join_sel(mcv1_slot.values, mcv1_slot.numbers, + mcv1_length, + mcv2_slot.values, mcv2_slot.numbers, + mcv2_length, + operator); + + /* + * Add in selectivities for MCV vs histogram matches, scaling according to + * the fractions of the populations represented by the histograms. Note + * that the second case needs to commute the operator. + */ + if (mcv1_exists && hist2_exists) + selec += (1.0 - nullfrac2 - sumcommon2) * + inet_mcv_hist_sel(mcv1_slot.values, mcv1_slot.numbers, mcv1_length, + hist2_slot.values, hist2_slot.nvalues, + opr_codenum); + if (mcv2_exists && hist1_exists) + selec += (1.0 - nullfrac1 - sumcommon1) * + inet_mcv_hist_sel(mcv2_slot.values, mcv2_slot.numbers, mcv2_length, + hist1_slot.values, hist1_slot.nvalues, + -opr_codenum); + + /* + * Add in selectivity for histogram vs histogram matches, again scaling + * appropriately. + */ + if (hist1_exists && hist2_exists) + selec += (1.0 - nullfrac1 - sumcommon1) * + (1.0 - nullfrac2 - sumcommon2) * + inet_hist_inclusion_join_sel(hist1_slot.values, hist1_slot.nvalues, + hist2_slot.values, hist2_slot.nvalues, + opr_codenum); + + /* + * If useful statistics are not available then use the default estimate. + * We can apply null fractions if known, though. + */ + if ((!mcv1_exists && !hist1_exists) || (!mcv2_exists && !hist2_exists)) + selec = (1.0 - nullfrac1) * (1.0 - nullfrac2) * DEFAULT_SEL(operator); + + /* Release stats. */ + free_attstatsslot(&mcv1_slot); + free_attstatsslot(&mcv2_slot); + free_attstatsslot(&hist1_slot); + free_attstatsslot(&hist2_slot); + + return selec; +} + +/* + * Semi join selectivity estimation for subnet inclusion/overlap operators + * + * Calculates MCV vs MCV, MCV vs histogram, histogram vs MCV, and histogram vs + * histogram selectivity for semi/anti join cases. + */ +static Selectivity +networkjoinsel_semi(Oid operator, + VariableStatData *vardata1, VariableStatData *vardata2) +{ + Form_pg_statistic stats; + Selectivity selec = 0.0, + sumcommon1 = 0.0, + sumcommon2 = 0.0; + double nullfrac1 = 0.0, + nullfrac2 = 0.0, + hist2_weight = 0.0; + bool mcv1_exists = false, + mcv2_exists = false, + hist1_exists = false, + hist2_exists = false; + int opr_codenum; + FmgrInfo proc; + int i, + mcv1_length = 0, + mcv2_length = 0; + AttStatsSlot mcv1_slot; + AttStatsSlot mcv2_slot; + AttStatsSlot hist1_slot; + AttStatsSlot hist2_slot; + + if (HeapTupleIsValid(vardata1->statsTuple)) + { + stats = (Form_pg_statistic) GETSTRUCT(vardata1->statsTuple); + nullfrac1 = stats->stanullfrac; + + mcv1_exists = get_attstatsslot(&mcv1_slot, vardata1->statsTuple, + STATISTIC_KIND_MCV, InvalidOid, + ATTSTATSSLOT_VALUES | ATTSTATSSLOT_NUMBERS); + hist1_exists = get_attstatsslot(&hist1_slot, vardata1->statsTuple, + STATISTIC_KIND_HISTOGRAM, InvalidOid, + ATTSTATSSLOT_VALUES); + /* Arbitrarily limit number of MCVs considered */ + mcv1_length = Min(mcv1_slot.nvalues, MAX_CONSIDERED_ELEMS); + if (mcv1_exists) + sumcommon1 = mcv_population(mcv1_slot.numbers, mcv1_length); + } + else + { + memset(&mcv1_slot, 0, sizeof(mcv1_slot)); + memset(&hist1_slot, 0, sizeof(hist1_slot)); + } + + if (HeapTupleIsValid(vardata2->statsTuple)) + { + stats = (Form_pg_statistic) GETSTRUCT(vardata2->statsTuple); + nullfrac2 = stats->stanullfrac; + + mcv2_exists = get_attstatsslot(&mcv2_slot, vardata2->statsTuple, + STATISTIC_KIND_MCV, InvalidOid, + ATTSTATSSLOT_VALUES | ATTSTATSSLOT_NUMBERS); + hist2_exists = get_attstatsslot(&hist2_slot, vardata2->statsTuple, + STATISTIC_KIND_HISTOGRAM, InvalidOid, + ATTSTATSSLOT_VALUES); + /* Arbitrarily limit number of MCVs considered */ + mcv2_length = Min(mcv2_slot.nvalues, MAX_CONSIDERED_ELEMS); + if (mcv2_exists) + sumcommon2 = mcv_population(mcv2_slot.numbers, mcv2_length); + } + else + { + memset(&mcv2_slot, 0, sizeof(mcv2_slot)); + memset(&hist2_slot, 0, sizeof(hist2_slot)); + } + + opr_codenum = inet_opr_codenum(operator); + fmgr_info(get_opcode(operator), &proc); + + /* Estimate number of input rows represented by RHS histogram. */ + if (hist2_exists && vardata2->rel) + hist2_weight = (1.0 - nullfrac2 - sumcommon2) * vardata2->rel->rows; + + /* + * Consider each element of the LHS MCV list, matching it to whatever RHS + * stats we have. Scale according to the known frequency of the MCV. + */ + if (mcv1_exists && (mcv2_exists || hist2_exists)) + { + for (i = 0; i < mcv1_length; i++) + { + selec += mcv1_slot.numbers[i] * + inet_semi_join_sel(mcv1_slot.values[i], + mcv2_exists, mcv2_slot.values, mcv2_length, + hist2_exists, + hist2_slot.values, hist2_slot.nvalues, + hist2_weight, + &proc, opr_codenum); + } + } + + /* + * Consider each element of the LHS histogram, except for the first and + * last elements, which we exclude on the grounds that they're outliers + * and thus not very representative. Scale on the assumption that each + * such histogram element represents an equal share of the LHS histogram + * population (which is a bit bogus, because the members of its bucket may + * not all act the same with respect to the join clause, but it's hard to + * do better). + * + * If there are too many histogram elements, decimate to limit runtime. + */ + if (hist1_exists && hist1_slot.nvalues > 2 && (mcv2_exists || hist2_exists)) + { + double hist_selec_sum = 0.0; + int k, + n; + + k = (hist1_slot.nvalues - 3) / MAX_CONSIDERED_ELEMS + 1; + + n = 0; + for (i = 1; i < hist1_slot.nvalues - 1; i += k) + { + hist_selec_sum += + inet_semi_join_sel(hist1_slot.values[i], + mcv2_exists, mcv2_slot.values, mcv2_length, + hist2_exists, + hist2_slot.values, hist2_slot.nvalues, + hist2_weight, + &proc, opr_codenum); + n++; + } + + selec += (1.0 - nullfrac1 - sumcommon1) * hist_selec_sum / n; + } + + /* + * If useful statistics are not available then use the default estimate. + * We can apply null fractions if known, though. + */ + if ((!mcv1_exists && !hist1_exists) || (!mcv2_exists && !hist2_exists)) + selec = (1.0 - nullfrac1) * (1.0 - nullfrac2) * DEFAULT_SEL(operator); + + /* Release stats. */ + free_attstatsslot(&mcv1_slot); + free_attstatsslot(&mcv2_slot); + free_attstatsslot(&hist1_slot); + free_attstatsslot(&hist2_slot); + + return selec; +} + +/* + * Compute the fraction of a relation's population that is represented + * by the MCV list. + */ +static Selectivity +mcv_population(float4 *mcv_numbers, int mcv_nvalues) +{ + Selectivity sumcommon = 0.0; + int i; + + for (i = 0; i < mcv_nvalues; i++) + { + sumcommon += mcv_numbers[i]; + } + + return sumcommon; +} + +/* + * Inet histogram vs single value selectivity estimation + * + * Estimate the fraction of the histogram population that satisfies + * "value OPR CONST". (The result needs to be scaled to reflect the + * proportion of the total population represented by the histogram.) + * + * The histogram is originally for the inet btree comparison operators. + * Only the common bits of the network part and the length of the network part + * (masklen) are interesting for the subnet inclusion operators. Fortunately, + * btree comparison treats the network part as the major sort key. Even so, + * the length of the network part would not really be significant in the + * histogram. This would lead to big mistakes for data sets with uneven + * masklen distribution. To reduce this problem, comparisons with the left + * and the right sides of the buckets are used together. + * + * Histogram bucket matches are calculated in two forms. If the constant + * matches both bucket endpoints the bucket is considered as fully matched. + * The second form is to match the bucket partially; we recognize this when + * the constant matches just one endpoint, or the two endpoints fall on + * opposite sides of the constant. (Note that when the constant matches an + * interior histogram element, it gets credit for partial matches to the + * buckets on both sides, while a match to a histogram endpoint gets credit + * for only one partial match. This is desirable.) + * + * The divider in the partial bucket match is imagined as the distance + * between the decisive bits and the common bits of the addresses. It will + * be used as a power of two as it is the natural scale for the IP network + * inclusion. This partial bucket match divider calculation is an empirical + * formula and subject to change with more experiment. + * + * For a partial match, we try to calculate dividers for both of the + * boundaries. If the address family of a boundary value does not match the + * constant or comparison of the length of the network parts is not correct + * for the operator, the divider for that boundary will not be taken into + * account. If both of the dividers are valid, the greater one will be used + * to minimize the mistake in buckets that have disparate masklens. This + * calculation is unfair when dividers can be calculated for both of the + * boundaries but they are far from each other; but it is not a common + * situation as the boundaries are expected to share most of their significant + * bits of their masklens. The mistake would be greater, if we would use the + * minimum instead of the maximum, and we don't know a sensible way to combine + * them. + * + * For partial match in buckets that have different address families on the + * left and right sides, only the boundary with the same address family is + * taken into consideration. This can cause more mistakes for these buckets + * if the masklens of their boundaries are also disparate. But this can only + * happen in one bucket, since only two address families exist. It seems a + * better option than not considering these buckets at all. + */ +static Selectivity +inet_hist_value_sel(Datum *values, int nvalues, Datum constvalue, + int opr_codenum) +{ + Selectivity match = 0.0; + inet *query, + *left, + *right; + int i, + k, + n; + int left_order, + right_order, + left_divider, + right_divider; + + /* guard against zero-divide below */ + if (nvalues <= 1) + return 0.0; + + /* if there are too many histogram elements, decimate to limit runtime */ + k = (nvalues - 2) / MAX_CONSIDERED_ELEMS + 1; + + query = DatumGetInetPP(constvalue); + + /* "left" is the left boundary value of the current bucket ... */ + left = DatumGetInetPP(values[0]); + left_order = inet_inclusion_cmp(left, query, opr_codenum); + + n = 0; + for (i = k; i < nvalues; i += k) + { + /* ... and "right" is the right boundary value */ + right = DatumGetInetPP(values[i]); + right_order = inet_inclusion_cmp(right, query, opr_codenum); + + if (left_order == 0 && right_order == 0) + { + /* The whole bucket matches, since both endpoints do. */ + match += 1.0; + } + else if ((left_order <= 0 && right_order >= 0) || + (left_order >= 0 && right_order <= 0)) + { + /* Partial bucket match. */ + left_divider = inet_hist_match_divider(left, query, opr_codenum); + right_divider = inet_hist_match_divider(right, query, opr_codenum); + + if (left_divider >= 0 || right_divider >= 0) + match += 1.0 / pow(2.0, Max(left_divider, right_divider)); + } + + /* Shift the variables. */ + left = right; + left_order = right_order; + + /* Count the number of buckets considered. */ + n++; + } + + return match / n; +} + +/* + * Inet MCV vs MCV join selectivity estimation + * + * We simply add up the fractions of the populations that satisfy the clause. + * The result is exact and does not need to be scaled further. + */ +static Selectivity +inet_mcv_join_sel(Datum *mcv1_values, float4 *mcv1_numbers, int mcv1_nvalues, + Datum *mcv2_values, float4 *mcv2_numbers, int mcv2_nvalues, + Oid operator) +{ + Selectivity selec = 0.0; + FmgrInfo proc; + int i, + j; + + fmgr_info(get_opcode(operator), &proc); + + for (i = 0; i < mcv1_nvalues; i++) + { + for (j = 0; j < mcv2_nvalues; j++) + if (DatumGetBool(FunctionCall2(&proc, + mcv1_values[i], + mcv2_values[j]))) + selec += mcv1_numbers[i] * mcv2_numbers[j]; + } + return selec; +} + +/* + * Inet MCV vs histogram join selectivity estimation + * + * For each MCV on the lefthand side, estimate the fraction of the righthand's + * histogram population that satisfies the join clause, and add those up, + * scaling by the MCV's frequency. The result still needs to be scaled + * according to the fraction of the righthand's population represented by + * the histogram. + */ +static Selectivity +inet_mcv_hist_sel(Datum *mcv_values, float4 *mcv_numbers, int mcv_nvalues, + Datum *hist_values, int hist_nvalues, + int opr_codenum) +{ + Selectivity selec = 0.0; + int i; + + /* + * We'll call inet_hist_value_selec with the histogram on the left, so we + * must commute the operator. + */ + opr_codenum = -opr_codenum; + + for (i = 0; i < mcv_nvalues; i++) + { + selec += mcv_numbers[i] * + inet_hist_value_sel(hist_values, hist_nvalues, mcv_values[i], + opr_codenum); + } + return selec; +} + +/* + * Inet histogram vs histogram join selectivity estimation + * + * Here, we take all values listed in the second histogram (except for the + * first and last elements, which are excluded on the grounds of possibly + * not being very representative) and treat them as a uniform sample of + * the non-MCV population for that relation. For each one, we apply + * inet_hist_value_selec to see what fraction of the first histogram + * it matches. + * + * We could alternatively do this the other way around using the operator's + * commutator. XXX would it be worthwhile to do it both ways and take the + * average? That would at least avoid non-commutative estimation results. + */ +static Selectivity +inet_hist_inclusion_join_sel(Datum *hist1_values, int hist1_nvalues, + Datum *hist2_values, int hist2_nvalues, + int opr_codenum) +{ + double match = 0.0; + int i, + k, + n; + + if (hist2_nvalues <= 2) + return 0.0; /* no interior histogram elements */ + + /* if there are too many histogram elements, decimate to limit runtime */ + k = (hist2_nvalues - 3) / MAX_CONSIDERED_ELEMS + 1; + + n = 0; + for (i = 1; i < hist2_nvalues - 1; i += k) + { + match += inet_hist_value_sel(hist1_values, hist1_nvalues, + hist2_values[i], opr_codenum); + n++; + } + + return match / n; +} + +/* + * Inet semi join selectivity estimation for one value + * + * The function calculates the probability that there is at least one row + * in the RHS table that satisfies the "lhs_value op column" condition. + * It is used in semi join estimation to check a sample from the left hand + * side table. + * + * The MCV and histogram from the right hand side table should be provided as + * arguments with the lhs_value from the left hand side table for the join. + * hist_weight is the total number of rows represented by the histogram. + * For example, if the table has 1000 rows, and 10% of the rows are in the MCV + * list, and another 10% are NULLs, hist_weight would be 800. + * + * First, the lhs_value will be matched to the most common values. If it + * matches any of them, 1.0 will be returned, because then there is surely + * a match. + * + * Otherwise, the histogram will be used to estimate the number of rows in + * the second table that match the condition. If the estimate is greater + * than 1.0, 1.0 will be returned, because it means there is a greater chance + * that the lhs_value will match more than one row in the table. If it is + * between 0.0 and 1.0, it will be returned as the probability. + */ +static Selectivity +inet_semi_join_sel(Datum lhs_value, + bool mcv_exists, Datum *mcv_values, int mcv_nvalues, + bool hist_exists, Datum *hist_values, int hist_nvalues, + double hist_weight, + FmgrInfo *proc, int opr_codenum) +{ + if (mcv_exists) + { + int i; + + for (i = 0; i < mcv_nvalues; i++) + { + if (DatumGetBool(FunctionCall2(proc, + lhs_value, + mcv_values[i]))) + return 1.0; + } + } + + if (hist_exists && hist_weight > 0) + { + Selectivity hist_selec; + + /* Commute operator, since we're passing lhs_value on the right */ + hist_selec = inet_hist_value_sel(hist_values, hist_nvalues, + lhs_value, -opr_codenum); + + if (hist_selec > 0) + return Min(1.0, hist_weight * hist_selec); + } + + return 0.0; +} + +/* + * Assign useful code numbers for the subnet inclusion/overlap operators + * + * Only inet_masklen_inclusion_cmp() and inet_hist_match_divider() depend + * on the exact codes assigned here; but many other places in this file + * know that they can negate a code to obtain the code for the commutator + * operator. + */ +static int +inet_opr_codenum(Oid operator) +{ + switch (operator) + { + case OID_INET_SUP_OP: + return -2; + case OID_INET_SUPEQ_OP: + return -1; + case OID_INET_OVERLAP_OP: + return 0; + case OID_INET_SUBEQ_OP: + return 1; + case OID_INET_SUB_OP: + return 2; + default: + elog(ERROR, "unrecognized operator %u for inet selectivity", + operator); + } + return 0; /* unreached, but keep compiler quiet */ +} + +/* + * Comparison function for the subnet inclusion/overlap operators + * + * If the comparison is okay for the specified inclusion operator, the return + * value will be 0. Otherwise the return value will be less than or greater + * than 0 as appropriate for the operator. + * + * Comparison is compatible with the basic comparison function for the inet + * type. See network_cmp_internal() in network.c for the original. Basic + * comparison operators are implemented with the network_cmp_internal() + * function. It is possible to implement the subnet inclusion operators with + * this function. + * + * Comparison is first on the common bits of the network part, then on the + * length of the network part (masklen) as in the network_cmp_internal() + * function. Only the first part is in this function. The second part is + * separated to another function for reusability. The difference between the + * second part and the original network_cmp_internal() is that the inclusion + * operator is considered while comparing the lengths of the network parts. + * See the inet_masklen_inclusion_cmp() function below. + */ +static int +inet_inclusion_cmp(inet *left, inet *right, int opr_codenum) +{ + if (ip_family(left) == ip_family(right)) + { + int order; + + order = bitncmp(ip_addr(left), ip_addr(right), + Min(ip_bits(left), ip_bits(right))); + if (order != 0) + return order; + + return inet_masklen_inclusion_cmp(left, right, opr_codenum); + } + + return ip_family(left) - ip_family(right); +} + +/* + * Masklen comparison function for the subnet inclusion/overlap operators + * + * Compares the lengths of the network parts of the inputs. If the comparison + * is okay for the specified inclusion operator, the return value will be 0. + * Otherwise the return value will be less than or greater than 0 as + * appropriate for the operator. + */ +static int +inet_masklen_inclusion_cmp(inet *left, inet *right, int opr_codenum) +{ + int order; + + order = (int) ip_bits(left) - (int) ip_bits(right); + + /* + * Return 0 if the operator would accept this combination of masklens. + * Note that opr_codenum zero (overlaps) will accept all cases. + */ + if ((order > 0 && opr_codenum >= 0) || + (order == 0 && opr_codenum >= -1 && opr_codenum <= 1) || + (order < 0 && opr_codenum <= 0)) + return 0; + + /* + * Otherwise, return a negative value for sup/supeq (notionally, the RHS + * needs to have a larger masklen than it has, which would make it sort + * later), or a positive value for sub/subeq (vice versa). + */ + return opr_codenum; +} + +/* + * Inet histogram partial match divider calculation + * + * First the families and the lengths of the network parts are compared using + * the subnet inclusion operator. If those are acceptable for the operator, + * the divider will be calculated using the masklens and the common bits of + * the addresses. -1 will be returned if it cannot be calculated. + * + * See commentary for inet_hist_value_sel() for some rationale for this. + */ +static int +inet_hist_match_divider(inet *boundary, inet *query, int opr_codenum) +{ + if (ip_family(boundary) == ip_family(query) && + inet_masklen_inclusion_cmp(boundary, query, opr_codenum) == 0) + { + int min_bits, + decisive_bits; + + min_bits = Min(ip_bits(boundary), ip_bits(query)); + + /* + * Set decisive_bits to the masklen of the one that should contain the + * other according to the operator. + */ + if (opr_codenum < 0) + decisive_bits = ip_bits(boundary); + else if (opr_codenum > 0) + decisive_bits = ip_bits(query); + else + decisive_bits = min_bits; + + /* + * Now return the number of non-common decisive bits. (This will be + * zero if the boundary and query in fact match, else positive.) + */ + if (min_bits > 0) + return decisive_bits - bitncommon(ip_addr(boundary), + ip_addr(query), + min_bits); + return decisive_bits; + } + + return -1; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/network_spgist.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/network_spgist.c new file mode 100644 index 00000000000..5d3697306c0 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/network_spgist.c @@ -0,0 +1,712 @@ +/*------------------------------------------------------------------------- + * + * network_spgist.c + * SP-GiST support for network types. + * + * We split inet index entries first by address family (IPv4 or IPv6). + * If the entries below a given inner tuple are all of the same family, + * we identify their common prefix and split by the next bit of the address, + * and by whether their masklens exceed the length of the common prefix. + * + * An inner tuple that has both IPv4 and IPv6 children has a null prefix + * and exactly two nodes, the first being for IPv4 and the second for IPv6. + * + * Otherwise, the prefix is a CIDR value representing the common prefix, + * and there are exactly four nodes. Node numbers 0 and 1 are for addresses + * with the same masklen as the prefix, while node numbers 2 and 3 are for + * addresses with larger masklen. (We do not allow a tuple to contain + * entries with masklen smaller than its prefix's.) Node numbers 0 and 1 + * are distinguished by the next bit of the address after the common prefix, + * and likewise for node numbers 2 and 3. If there are no more bits in + * the address family, everything goes into node 0 (which will probably + * lead to creating an allTheSame tuple). + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/utils/adt/network_spgist.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <sys/socket.h> + +#include "access/spgist.h" +#include "catalog/pg_type.h" +#include "utils/builtins.h" +#include "utils/inet.h" +#include "varatt.h" + + +static int inet_spg_node_number(const inet *val, int commonbits); +static int inet_spg_consistent_bitmap(const inet *prefix, int nkeys, + ScanKey scankeys, bool leaf); + +/* + * The SP-GiST configuration function + */ +Datum +inet_spg_config(PG_FUNCTION_ARGS) +{ + /* spgConfigIn *cfgin = (spgConfigIn *) PG_GETARG_POINTER(0); */ + spgConfigOut *cfg = (spgConfigOut *) PG_GETARG_POINTER(1); + + cfg->prefixType = CIDROID; + cfg->labelType = VOIDOID; + cfg->canReturnData = true; + cfg->longValuesOK = false; + + PG_RETURN_VOID(); +} + +/* + * The SP-GiST choose function + */ +Datum +inet_spg_choose(PG_FUNCTION_ARGS) +{ + spgChooseIn *in = (spgChooseIn *) PG_GETARG_POINTER(0); + spgChooseOut *out = (spgChooseOut *) PG_GETARG_POINTER(1); + inet *val = DatumGetInetPP(in->datum), + *prefix; + int commonbits; + + /* + * If we're looking at a tuple that splits by address family, choose the + * appropriate subnode. + */ + if (!in->hasPrefix) + { + /* allTheSame isn't possible for such a tuple */ + Assert(!in->allTheSame); + Assert(in->nNodes == 2); + + out->resultType = spgMatchNode; + out->result.matchNode.nodeN = (ip_family(val) == PGSQL_AF_INET) ? 0 : 1; + out->result.matchNode.restDatum = InetPGetDatum(val); + + PG_RETURN_VOID(); + } + + /* Else it must split by prefix */ + Assert(in->nNodes == 4 || in->allTheSame); + + prefix = DatumGetInetPP(in->prefixDatum); + commonbits = ip_bits(prefix); + + /* + * We cannot put addresses from different families under the same inner + * node, so we have to split if the new value's family is different. + */ + if (ip_family(val) != ip_family(prefix)) + { + /* Set up 2-node tuple */ + out->resultType = spgSplitTuple; + out->result.splitTuple.prefixHasPrefix = false; + out->result.splitTuple.prefixNNodes = 2; + out->result.splitTuple.prefixNodeLabels = NULL; + + /* Identify which node the existing data goes into */ + out->result.splitTuple.childNodeN = + (ip_family(prefix) == PGSQL_AF_INET) ? 0 : 1; + + out->result.splitTuple.postfixHasPrefix = true; + out->result.splitTuple.postfixPrefixDatum = InetPGetDatum(prefix); + + PG_RETURN_VOID(); + } + + /* + * If the new value does not match the existing prefix, we have to split. + */ + if (ip_bits(val) < commonbits || + bitncmp(ip_addr(prefix), ip_addr(val), commonbits) != 0) + { + /* Determine new prefix length for the split tuple */ + commonbits = bitncommon(ip_addr(prefix), ip_addr(val), + Min(ip_bits(val), commonbits)); + + /* Set up 4-node tuple */ + out->resultType = spgSplitTuple; + out->result.splitTuple.prefixHasPrefix = true; + out->result.splitTuple.prefixPrefixDatum = + InetPGetDatum(cidr_set_masklen_internal(val, commonbits)); + out->result.splitTuple.prefixNNodes = 4; + out->result.splitTuple.prefixNodeLabels = NULL; + + /* Identify which node the existing data goes into */ + out->result.splitTuple.childNodeN = + inet_spg_node_number(prefix, commonbits); + + out->result.splitTuple.postfixHasPrefix = true; + out->result.splitTuple.postfixPrefixDatum = InetPGetDatum(prefix); + + PG_RETURN_VOID(); + } + + /* + * All OK, choose the node to descend into. (If this tuple is marked + * allTheSame, the core code will ignore our choice of nodeN; but we need + * not account for that case explicitly here.) + */ + out->resultType = spgMatchNode; + out->result.matchNode.nodeN = inet_spg_node_number(val, commonbits); + out->result.matchNode.restDatum = InetPGetDatum(val); + + PG_RETURN_VOID(); +} + +/* + * The GiST PickSplit method + */ +Datum +inet_spg_picksplit(PG_FUNCTION_ARGS) +{ + spgPickSplitIn *in = (spgPickSplitIn *) PG_GETARG_POINTER(0); + spgPickSplitOut *out = (spgPickSplitOut *) PG_GETARG_POINTER(1); + inet *prefix, + *tmp; + int i, + commonbits; + bool differentFamilies = false; + + /* Initialize the prefix with the first item */ + prefix = DatumGetInetPP(in->datums[0]); + commonbits = ip_bits(prefix); + + /* Examine remaining items to discover minimum common prefix length */ + for (i = 1; i < in->nTuples; i++) + { + tmp = DatumGetInetPP(in->datums[i]); + + if (ip_family(tmp) != ip_family(prefix)) + { + differentFamilies = true; + break; + } + + if (ip_bits(tmp) < commonbits) + commonbits = ip_bits(tmp); + commonbits = bitncommon(ip_addr(prefix), ip_addr(tmp), commonbits); + if (commonbits == 0) + break; + } + + /* Don't need labels; allocate output arrays */ + out->nodeLabels = NULL; + out->mapTuplesToNodes = (int *) palloc(sizeof(int) * in->nTuples); + out->leafTupleDatums = (Datum *) palloc(sizeof(Datum) * in->nTuples); + + if (differentFamilies) + { + /* Set up 2-node tuple */ + out->hasPrefix = false; + out->nNodes = 2; + + for (i = 0; i < in->nTuples; i++) + { + tmp = DatumGetInetPP(in->datums[i]); + out->mapTuplesToNodes[i] = + (ip_family(tmp) == PGSQL_AF_INET) ? 0 : 1; + out->leafTupleDatums[i] = InetPGetDatum(tmp); + } + } + else + { + /* Set up 4-node tuple */ + out->hasPrefix = true; + out->prefixDatum = + InetPGetDatum(cidr_set_masklen_internal(prefix, commonbits)); + out->nNodes = 4; + + for (i = 0; i < in->nTuples; i++) + { + tmp = DatumGetInetPP(in->datums[i]); + out->mapTuplesToNodes[i] = inet_spg_node_number(tmp, commonbits); + out->leafTupleDatums[i] = InetPGetDatum(tmp); + } + } + + PG_RETURN_VOID(); +} + +/* + * The SP-GiST query consistency check for inner tuples + */ +Datum +inet_spg_inner_consistent(PG_FUNCTION_ARGS) +{ + spgInnerConsistentIn *in = (spgInnerConsistentIn *) PG_GETARG_POINTER(0); + spgInnerConsistentOut *out = (spgInnerConsistentOut *) PG_GETARG_POINTER(1); + int i; + int which; + + if (!in->hasPrefix) + { + Assert(!in->allTheSame); + Assert(in->nNodes == 2); + + /* Identify which child nodes need to be visited */ + which = 1 | (1 << 1); + + for (i = 0; i < in->nkeys; i++) + { + StrategyNumber strategy = in->scankeys[i].sk_strategy; + inet *argument = DatumGetInetPP(in->scankeys[i].sk_argument); + + switch (strategy) + { + case RTLessStrategyNumber: + case RTLessEqualStrategyNumber: + if (ip_family(argument) == PGSQL_AF_INET) + which &= 1; + break; + + case RTGreaterEqualStrategyNumber: + case RTGreaterStrategyNumber: + if (ip_family(argument) == PGSQL_AF_INET6) + which &= (1 << 1); + break; + + case RTNotEqualStrategyNumber: + break; + + default: + /* all other ops can only match addrs of same family */ + if (ip_family(argument) == PGSQL_AF_INET) + which &= 1; + else + which &= (1 << 1); + break; + } + } + } + else if (!in->allTheSame) + { + Assert(in->nNodes == 4); + + /* Identify which child nodes need to be visited */ + which = inet_spg_consistent_bitmap(DatumGetInetPP(in->prefixDatum), + in->nkeys, in->scankeys, false); + } + else + { + /* Must visit all nodes; we assume there are less than 32 of 'em */ + which = ~0; + } + + out->nNodes = 0; + + if (which) + { + out->nodeNumbers = (int *) palloc(sizeof(int) * in->nNodes); + + for (i = 0; i < in->nNodes; i++) + { + if (which & (1 << i)) + { + out->nodeNumbers[out->nNodes] = i; + out->nNodes++; + } + } + } + + PG_RETURN_VOID(); +} + +/* + * The SP-GiST query consistency check for leaf tuples + */ +Datum +inet_spg_leaf_consistent(PG_FUNCTION_ARGS) +{ + spgLeafConsistentIn *in = (spgLeafConsistentIn *) PG_GETARG_POINTER(0); + spgLeafConsistentOut *out = (spgLeafConsistentOut *) PG_GETARG_POINTER(1); + inet *leaf = DatumGetInetPP(in->leafDatum); + + /* All tests are exact. */ + out->recheck = false; + + /* Leaf is what it is... */ + out->leafValue = InetPGetDatum(leaf); + + /* Use common code to apply the tests. */ + PG_RETURN_BOOL(inet_spg_consistent_bitmap(leaf, in->nkeys, in->scankeys, + true)); +} + +/* + * Calculate node number (within a 4-node, single-family inner index tuple) + * + * The value must have the same family as the node's prefix, and + * commonbits is the mask length of the prefix. We use even or odd + * nodes according to the next address bit after the commonbits, + * and low or high nodes according to whether the value's mask length + * is larger than commonbits. + */ +static int +inet_spg_node_number(const inet *val, int commonbits) +{ + int nodeN = 0; + + if (commonbits < ip_maxbits(val) && + ip_addr(val)[commonbits / 8] & (1 << (7 - commonbits % 8))) + nodeN |= 1; + if (commonbits < ip_bits(val)) + nodeN |= 2; + + return nodeN; +} + +/* + * Calculate bitmap of node numbers that are consistent with the query + * + * This can be used either at a 4-way inner tuple, or at a leaf tuple. + * In the latter case, we should return a boolean result (0 or 1) + * not a bitmap. + * + * This definition is pretty odd, but the inner and leaf consistency checks + * are mostly common and it seems best to keep them in one function. + */ +static int +inet_spg_consistent_bitmap(const inet *prefix, int nkeys, ScanKey scankeys, + bool leaf) +{ + int bitmap; + int commonbits, + i; + + /* Initialize result to allow visiting all children */ + if (leaf) + bitmap = 1; + else + bitmap = 1 | (1 << 1) | (1 << 2) | (1 << 3); + + commonbits = ip_bits(prefix); + + for (i = 0; i < nkeys; i++) + { + inet *argument = DatumGetInetPP(scankeys[i].sk_argument); + StrategyNumber strategy = scankeys[i].sk_strategy; + int order; + + /* + * Check 0: different families + * + * Matching families do not help any of the strategies. + */ + if (ip_family(argument) != ip_family(prefix)) + { + switch (strategy) + { + case RTLessStrategyNumber: + case RTLessEqualStrategyNumber: + if (ip_family(argument) < ip_family(prefix)) + bitmap = 0; + break; + + case RTGreaterEqualStrategyNumber: + case RTGreaterStrategyNumber: + if (ip_family(argument) > ip_family(prefix)) + bitmap = 0; + break; + + case RTNotEqualStrategyNumber: + break; + + default: + /* For all other cases, we can be sure there is no match */ + bitmap = 0; + break; + } + + if (!bitmap) + break; + + /* Other checks make no sense with different families. */ + continue; + } + + /* + * Check 1: network bit count + * + * Network bit count (ip_bits) helps to check leaves for sub network + * and sup network operators. At non-leaf nodes, we know every child + * value has greater ip_bits, so we can avoid descending in some cases + * too. + * + * This check is less expensive than checking the address bits, so we + * are doing this before, but it has to be done after for the basic + * comparison strategies, because ip_bits only affect their results + * when the common network bits are the same. + */ + switch (strategy) + { + case RTSubStrategyNumber: + if (commonbits <= ip_bits(argument)) + bitmap &= (1 << 2) | (1 << 3); + break; + + case RTSubEqualStrategyNumber: + if (commonbits < ip_bits(argument)) + bitmap &= (1 << 2) | (1 << 3); + break; + + case RTSuperStrategyNumber: + if (commonbits == ip_bits(argument) - 1) + bitmap &= 1 | (1 << 1); + else if (commonbits >= ip_bits(argument)) + bitmap = 0; + break; + + case RTSuperEqualStrategyNumber: + if (commonbits == ip_bits(argument)) + bitmap &= 1 | (1 << 1); + else if (commonbits > ip_bits(argument)) + bitmap = 0; + break; + + case RTEqualStrategyNumber: + if (commonbits < ip_bits(argument)) + bitmap &= (1 << 2) | (1 << 3); + else if (commonbits == ip_bits(argument)) + bitmap &= 1 | (1 << 1); + else + bitmap = 0; + break; + } + + if (!bitmap) + break; + + /* + * Check 2: common network bits + * + * Compare available common prefix bits to the query, but not beyond + * either the query's netmask or the minimum netmask among the + * represented values. If these bits don't match the query, we can + * eliminate some cases. + */ + order = bitncmp(ip_addr(prefix), ip_addr(argument), + Min(commonbits, ip_bits(argument))); + + if (order != 0) + { + switch (strategy) + { + case RTLessStrategyNumber: + case RTLessEqualStrategyNumber: + if (order > 0) + bitmap = 0; + break; + + case RTGreaterEqualStrategyNumber: + case RTGreaterStrategyNumber: + if (order < 0) + bitmap = 0; + break; + + case RTNotEqualStrategyNumber: + break; + + default: + /* For all other cases, we can be sure there is no match */ + bitmap = 0; + break; + } + + if (!bitmap) + break; + + /* + * Remaining checks make no sense when common bits don't match. + */ + continue; + } + + /* + * Check 3: next network bit + * + * We can filter out branch 2 or 3 using the next network bit of the + * argument, if it is available. + * + * This check matters for the performance of the search. The results + * would be correct without it. + */ + if (bitmap & ((1 << 2) | (1 << 3)) && + commonbits < ip_bits(argument)) + { + int nextbit; + + nextbit = ip_addr(argument)[commonbits / 8] & + (1 << (7 - commonbits % 8)); + + switch (strategy) + { + case RTLessStrategyNumber: + case RTLessEqualStrategyNumber: + if (!nextbit) + bitmap &= 1 | (1 << 1) | (1 << 2); + break; + + case RTGreaterEqualStrategyNumber: + case RTGreaterStrategyNumber: + if (nextbit) + bitmap &= 1 | (1 << 1) | (1 << 3); + break; + + case RTNotEqualStrategyNumber: + break; + + default: + if (!nextbit) + bitmap &= 1 | (1 << 1) | (1 << 2); + else + bitmap &= 1 | (1 << 1) | (1 << 3); + break; + } + + if (!bitmap) + break; + } + + /* + * Remaining checks are only for the basic comparison strategies. This + * test relies on the strategy number ordering defined in stratnum.h. + */ + if (strategy < RTEqualStrategyNumber || + strategy > RTGreaterEqualStrategyNumber) + continue; + + /* + * Check 4: network bit count + * + * At this point, we know that the common network bits of the prefix + * and the argument are the same, so we can go forward and check the + * ip_bits. + */ + switch (strategy) + { + case RTLessStrategyNumber: + case RTLessEqualStrategyNumber: + if (commonbits == ip_bits(argument)) + bitmap &= 1 | (1 << 1); + else if (commonbits > ip_bits(argument)) + bitmap = 0; + break; + + case RTGreaterEqualStrategyNumber: + case RTGreaterStrategyNumber: + if (commonbits < ip_bits(argument)) + bitmap &= (1 << 2) | (1 << 3); + break; + } + + if (!bitmap) + break; + + /* Remaining checks don't make sense with different ip_bits. */ + if (commonbits != ip_bits(argument)) + continue; + + /* + * Check 5: next host bit + * + * We can filter out branch 0 or 1 using the next host bit of the + * argument, if it is available. + * + * This check matters for the performance of the search. The results + * would be correct without it. There is no point in running it for + * leafs as we have to check the whole address on the next step. + */ + if (!leaf && bitmap & (1 | (1 << 1)) && + commonbits < ip_maxbits(argument)) + { + int nextbit; + + nextbit = ip_addr(argument)[commonbits / 8] & + (1 << (7 - commonbits % 8)); + + switch (strategy) + { + case RTLessStrategyNumber: + case RTLessEqualStrategyNumber: + if (!nextbit) + bitmap &= 1 | (1 << 2) | (1 << 3); + break; + + case RTGreaterEqualStrategyNumber: + case RTGreaterStrategyNumber: + if (nextbit) + bitmap &= (1 << 1) | (1 << 2) | (1 << 3); + break; + + case RTNotEqualStrategyNumber: + break; + + default: + if (!nextbit) + bitmap &= 1 | (1 << 2) | (1 << 3); + else + bitmap &= (1 << 1) | (1 << 2) | (1 << 3); + break; + } + + if (!bitmap) + break; + } + + /* + * Check 6: whole address + * + * This is the last check for correctness of the basic comparison + * strategies. It's only appropriate at leaf entries. + */ + if (leaf) + { + /* Redo ordering comparison using all address bits */ + order = bitncmp(ip_addr(prefix), ip_addr(argument), + ip_maxbits(prefix)); + + switch (strategy) + { + case RTLessStrategyNumber: + if (order >= 0) + bitmap = 0; + break; + + case RTLessEqualStrategyNumber: + if (order > 0) + bitmap = 0; + break; + + case RTEqualStrategyNumber: + if (order != 0) + bitmap = 0; + break; + + case RTGreaterEqualStrategyNumber: + if (order < 0) + bitmap = 0; + break; + + case RTGreaterStrategyNumber: + if (order <= 0) + bitmap = 0; + break; + + case RTNotEqualStrategyNumber: + if (order == 0) + bitmap = 0; + break; + } + + if (!bitmap) + break; + } + } + + return bitmap; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/numeric.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/numeric.c new file mode 100644 index 00000000000..3c3184f15b5 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/numeric.c @@ -0,0 +1,12025 @@ +/*------------------------------------------------------------------------- + * + * numeric.c + * An exact numeric data type for the Postgres database system + * + * Original coding 1998, Jan Wieck. Heavily revised 2003, Tom Lane. + * + * Many of the algorithmic ideas are borrowed from David M. Smith's "FM" + * multiple-precision math library, most recently published as Algorithm + * 786: Multiple-Precision Complex Arithmetic and Functions, ACM + * Transactions on Mathematical Software, Vol. 24, No. 4, December 1998, + * pages 359-367. + * + * Copyright (c) 1998-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/utils/adt/numeric.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include <ctype.h> +#include <float.h> +#include <limits.h> +#include <math.h> + +#include "catalog/pg_type.h" +#include "common/hashfn.h" +#include "common/int.h" +#include "funcapi.h" +#include "lib/hyperloglog.h" +#include "libpq/pqformat.h" +#include "miscadmin.h" +#include "nodes/nodeFuncs.h" +#include "nodes/supportnodes.h" +#include "utils/array.h" +#include "utils/builtins.h" +#include "utils/float.h" +#include "utils/guc.h" +#include "utils/numeric.h" +#include "utils/pg_lsn.h" +#include "utils/sortsupport.h" + +/* ---------- + * Uncomment the following to enable compilation of dump_numeric() + * and dump_var() and to get a dump of any result produced by make_result(). + * ---------- +#define NUMERIC_DEBUG + */ + + +/* ---------- + * Local data types + * + * Numeric values are represented in a base-NBASE floating point format. + * Each "digit" ranges from 0 to NBASE-1. The type NumericDigit is signed + * and wide enough to store a digit. We assume that NBASE*NBASE can fit in + * an int. Although the purely calculational routines could handle any even + * NBASE that's less than sqrt(INT_MAX), in practice we are only interested + * in NBASE a power of ten, so that I/O conversions and decimal rounding + * are easy. Also, it's actually more efficient if NBASE is rather less than + * sqrt(INT_MAX), so that there is "headroom" for mul_var and div_var_fast to + * postpone processing carries. + * + * Values of NBASE other than 10000 are considered of historical interest only + * and are no longer supported in any sense; no mechanism exists for the client + * to discover the base, so every client supporting binary mode expects the + * base-10000 format. If you plan to change this, also note the numeric + * abbreviation code, which assumes NBASE=10000. + * ---------- + */ + +#if 0 +#define NBASE 10 +#define HALF_NBASE 5 +#define DEC_DIGITS 1 /* decimal digits per NBASE digit */ +#define MUL_GUARD_DIGITS 4 /* these are measured in NBASE digits */ +#define DIV_GUARD_DIGITS 8 + +typedef signed char NumericDigit; +#endif + +#if 0 +#define NBASE 100 +#define HALF_NBASE 50 +#define DEC_DIGITS 2 /* decimal digits per NBASE digit */ +#define MUL_GUARD_DIGITS 3 /* these are measured in NBASE digits */ +#define DIV_GUARD_DIGITS 6 + +typedef signed char NumericDigit; +#endif + +#if 1 +#define NBASE 10000 +#define HALF_NBASE 5000 +#define DEC_DIGITS 4 /* decimal digits per NBASE digit */ +#define MUL_GUARD_DIGITS 2 /* these are measured in NBASE digits */ +#define DIV_GUARD_DIGITS 4 + +typedef int16 NumericDigit; +#endif + +/* + * The Numeric type as stored on disk. + * + * If the high bits of the first word of a NumericChoice (n_header, or + * n_short.n_header, or n_long.n_sign_dscale) are NUMERIC_SHORT, then the + * numeric follows the NumericShort format; if they are NUMERIC_POS or + * NUMERIC_NEG, it follows the NumericLong format. If they are NUMERIC_SPECIAL, + * the value is a NaN or Infinity. We currently always store SPECIAL values + * using just two bytes (i.e. only n_header), but previous releases used only + * the NumericLong format, so we might find 4-byte NaNs (though not infinities) + * on disk if a database has been migrated using pg_upgrade. In either case, + * the low-order bits of a special value's header are reserved and currently + * should always be set to zero. + * + * In the NumericShort format, the remaining 14 bits of the header word + * (n_short.n_header) are allocated as follows: 1 for sign (positive or + * negative), 6 for dynamic scale, and 7 for weight. In practice, most + * commonly-encountered values can be represented this way. + * + * In the NumericLong format, the remaining 14 bits of the header word + * (n_long.n_sign_dscale) represent the display scale; and the weight is + * stored separately in n_weight. + * + * NOTE: by convention, values in the packed form have been stripped of + * all leading and trailing zero digits (where a "digit" is of base NBASE). + * In particular, if the value is zero, there will be no digits at all! + * The weight is arbitrary in that case, but we normally set it to zero. + */ + +struct NumericShort +{ + uint16 n_header; /* Sign + display scale + weight */ + NumericDigit n_data[FLEXIBLE_ARRAY_MEMBER]; /* Digits */ +}; + +struct NumericLong +{ + uint16 n_sign_dscale; /* Sign + display scale */ + int16 n_weight; /* Weight of 1st digit */ + NumericDigit n_data[FLEXIBLE_ARRAY_MEMBER]; /* Digits */ +}; + +union NumericChoice +{ + uint16 n_header; /* Header word */ + struct NumericLong n_long; /* Long form (4-byte header) */ + struct NumericShort n_short; /* Short form (2-byte header) */ +}; + +struct NumericData +{ + int32 vl_len_; /* varlena header (do not touch directly!) */ + union NumericChoice choice; /* choice of format */ +}; + + +/* + * Interpretation of high bits. + */ + +#define NUMERIC_SIGN_MASK 0xC000 +#define NUMERIC_POS 0x0000 +#define NUMERIC_NEG 0x4000 +#define NUMERIC_SHORT 0x8000 +#define NUMERIC_SPECIAL 0xC000 + +#define NUMERIC_FLAGBITS(n) ((n)->choice.n_header & NUMERIC_SIGN_MASK) +#define NUMERIC_IS_SHORT(n) (NUMERIC_FLAGBITS(n) == NUMERIC_SHORT) +#define NUMERIC_IS_SPECIAL(n) (NUMERIC_FLAGBITS(n) == NUMERIC_SPECIAL) + +#define NUMERIC_HDRSZ (VARHDRSZ + sizeof(uint16) + sizeof(int16)) +#define NUMERIC_HDRSZ_SHORT (VARHDRSZ + sizeof(uint16)) + +/* + * If the flag bits are NUMERIC_SHORT or NUMERIC_SPECIAL, we want the short + * header; otherwise, we want the long one. Instead of testing against each + * value, we can just look at the high bit, for a slight efficiency gain. + */ +#define NUMERIC_HEADER_IS_SHORT(n) (((n)->choice.n_header & 0x8000) != 0) +#define NUMERIC_HEADER_SIZE(n) \ + (VARHDRSZ + sizeof(uint16) + \ + (NUMERIC_HEADER_IS_SHORT(n) ? 0 : sizeof(int16))) + +/* + * Definitions for special values (NaN, positive infinity, negative infinity). + * + * The two bits after the NUMERIC_SPECIAL bits are 00 for NaN, 01 for positive + * infinity, 11 for negative infinity. (This makes the sign bit match where + * it is in a short-format value, though we make no use of that at present.) + * We could mask off the remaining bits before testing the active bits, but + * currently those bits must be zeroes, so masking would just add cycles. + */ +#define NUMERIC_EXT_SIGN_MASK 0xF000 /* high bits plus NaN/Inf flag bits */ +#define NUMERIC_NAN 0xC000 +#define NUMERIC_PINF 0xD000 +#define NUMERIC_NINF 0xF000 +#define NUMERIC_INF_SIGN_MASK 0x2000 + +#define NUMERIC_EXT_FLAGBITS(n) ((n)->choice.n_header & NUMERIC_EXT_SIGN_MASK) +#define NUMERIC_IS_NAN(n) ((n)->choice.n_header == NUMERIC_NAN) +#define NUMERIC_IS_PINF(n) ((n)->choice.n_header == NUMERIC_PINF) +#define NUMERIC_IS_NINF(n) ((n)->choice.n_header == NUMERIC_NINF) +#define NUMERIC_IS_INF(n) \ + (((n)->choice.n_header & ~NUMERIC_INF_SIGN_MASK) == NUMERIC_PINF) + +/* + * Short format definitions. + */ + +#define NUMERIC_SHORT_SIGN_MASK 0x2000 +#define NUMERIC_SHORT_DSCALE_MASK 0x1F80 +#define NUMERIC_SHORT_DSCALE_SHIFT 7 +#define NUMERIC_SHORT_DSCALE_MAX \ + (NUMERIC_SHORT_DSCALE_MASK >> NUMERIC_SHORT_DSCALE_SHIFT) +#define NUMERIC_SHORT_WEIGHT_SIGN_MASK 0x0040 +#define NUMERIC_SHORT_WEIGHT_MASK 0x003F +#define NUMERIC_SHORT_WEIGHT_MAX NUMERIC_SHORT_WEIGHT_MASK +#define NUMERIC_SHORT_WEIGHT_MIN (-(NUMERIC_SHORT_WEIGHT_MASK+1)) + +/* + * Extract sign, display scale, weight. These macros extract field values + * suitable for the NumericVar format from the Numeric (on-disk) format. + * + * Note that we don't trouble to ensure that dscale and weight read as zero + * for an infinity; however, that doesn't matter since we never convert + * "special" numerics to NumericVar form. Only the constants defined below + * (const_nan, etc) ever represent a non-finite value as a NumericVar. + */ + +#define NUMERIC_DSCALE_MASK 0x3FFF +#define NUMERIC_DSCALE_MAX NUMERIC_DSCALE_MASK + +#define NUMERIC_SIGN(n) \ + (NUMERIC_IS_SHORT(n) ? \ + (((n)->choice.n_short.n_header & NUMERIC_SHORT_SIGN_MASK) ? \ + NUMERIC_NEG : NUMERIC_POS) : \ + (NUMERIC_IS_SPECIAL(n) ? \ + NUMERIC_EXT_FLAGBITS(n) : NUMERIC_FLAGBITS(n))) +#define NUMERIC_DSCALE(n) (NUMERIC_HEADER_IS_SHORT((n)) ? \ + ((n)->choice.n_short.n_header & NUMERIC_SHORT_DSCALE_MASK) \ + >> NUMERIC_SHORT_DSCALE_SHIFT \ + : ((n)->choice.n_long.n_sign_dscale & NUMERIC_DSCALE_MASK)) +#define NUMERIC_WEIGHT(n) (NUMERIC_HEADER_IS_SHORT((n)) ? \ + (((n)->choice.n_short.n_header & NUMERIC_SHORT_WEIGHT_SIGN_MASK ? \ + ~NUMERIC_SHORT_WEIGHT_MASK : 0) \ + | ((n)->choice.n_short.n_header & NUMERIC_SHORT_WEIGHT_MASK)) \ + : ((n)->choice.n_long.n_weight)) + +/* ---------- + * NumericVar is the format we use for arithmetic. The digit-array part + * is the same as the NumericData storage format, but the header is more + * complex. + * + * The value represented by a NumericVar is determined by the sign, weight, + * ndigits, and digits[] array. If it is a "special" value (NaN or Inf) + * then only the sign field matters; ndigits should be zero, and the weight + * and dscale fields are ignored. + * + * Note: the first digit of a NumericVar's value is assumed to be multiplied + * by NBASE ** weight. Another way to say it is that there are weight+1 + * digits before the decimal point. It is possible to have weight < 0. + * + * buf points at the physical start of the palloc'd digit buffer for the + * NumericVar. digits points at the first digit in actual use (the one + * with the specified weight). We normally leave an unused digit or two + * (preset to zeroes) between buf and digits, so that there is room to store + * a carry out of the top digit without reallocating space. We just need to + * decrement digits (and increment weight) to make room for the carry digit. + * (There is no such extra space in a numeric value stored in the database, + * only in a NumericVar in memory.) + * + * If buf is NULL then the digit buffer isn't actually palloc'd and should + * not be freed --- see the constants below for an example. + * + * dscale, or display scale, is the nominal precision expressed as number + * of digits after the decimal point (it must always be >= 0 at present). + * dscale may be more than the number of physically stored fractional digits, + * implying that we have suppressed storage of significant trailing zeroes. + * It should never be less than the number of stored digits, since that would + * imply hiding digits that are present. NOTE that dscale is always expressed + * in *decimal* digits, and so it may correspond to a fractional number of + * base-NBASE digits --- divide by DEC_DIGITS to convert to NBASE digits. + * + * rscale, or result scale, is the target precision for a computation. + * Like dscale it is expressed as number of *decimal* digits after the decimal + * point, and is always >= 0 at present. + * Note that rscale is not stored in variables --- it's figured on-the-fly + * from the dscales of the inputs. + * + * While we consistently use "weight" to refer to the base-NBASE weight of + * a numeric value, it is convenient in some scale-related calculations to + * make use of the base-10 weight (ie, the approximate log10 of the value). + * To avoid confusion, such a decimal-units weight is called a "dweight". + * + * NB: All the variable-level functions are written in a style that makes it + * possible to give one and the same variable as argument and destination. + * This is feasible because the digit buffer is separate from the variable. + * ---------- + */ +typedef struct NumericVar +{ + int ndigits; /* # of digits in digits[] - can be 0! */ + int weight; /* weight of first digit */ + int sign; /* NUMERIC_POS, _NEG, _NAN, _PINF, or _NINF */ + int dscale; /* display scale */ + NumericDigit *buf; /* start of palloc'd space for digits[] */ + NumericDigit *digits; /* base-NBASE digits */ +} NumericVar; + + +/* ---------- + * Data for generate_series + * ---------- + */ +typedef struct +{ + NumericVar current; + NumericVar stop; + NumericVar step; +} generate_series_numeric_fctx; + + +/* ---------- + * Sort support. + * ---------- + */ +typedef struct +{ + void *buf; /* buffer for short varlenas */ + int64 input_count; /* number of non-null values seen */ + bool estimating; /* true if estimating cardinality */ + + hyperLogLogState abbr_card; /* cardinality estimator */ +} NumericSortSupport; + + +/* ---------- + * Fast sum accumulator. + * + * NumericSumAccum is used to implement SUM(), and other standard aggregates + * that track the sum of input values. It uses 32-bit integers to store the + * digits, instead of the normal 16-bit integers (with NBASE=10000). This + * way, we can safely accumulate up to NBASE - 1 values without propagating + * carry, before risking overflow of any of the digits. 'num_uncarried' + * tracks how many values have been accumulated without propagating carry. + * + * Positive and negative values are accumulated separately, in 'pos_digits' + * and 'neg_digits'. This is simpler and faster than deciding whether to add + * or subtract from the current value, for each new value (see sub_var() for + * the logic we avoid by doing this). Both buffers are of same size, and + * have the same weight and scale. In accum_sum_final(), the positive and + * negative sums are added together to produce the final result. + * + * When a new value has a larger ndigits or weight than the accumulator + * currently does, the accumulator is enlarged to accommodate the new value. + * We normally have one zero digit reserved for carry propagation, and that + * is indicated by the 'have_carry_space' flag. When accum_sum_carry() uses + * up the reserved digit, it clears the 'have_carry_space' flag. The next + * call to accum_sum_add() will enlarge the buffer, to make room for the + * extra digit, and set the flag again. + * + * To initialize a new accumulator, simply reset all fields to zeros. + * + * The accumulator does not handle NaNs. + * ---------- + */ +typedef struct NumericSumAccum +{ + int ndigits; + int weight; + int dscale; + int num_uncarried; + bool have_carry_space; + int32 *pos_digits; + int32 *neg_digits; +} NumericSumAccum; + + +/* + * We define our own macros for packing and unpacking abbreviated-key + * representations for numeric values in order to avoid depending on + * USE_FLOAT8_BYVAL. The type of abbreviation we use is based only on + * the size of a datum, not the argument-passing convention for float8. + * + * The range of abbreviations for finite values is from +PG_INT64/32_MAX + * to -PG_INT64/32_MAX. NaN has the abbreviation PG_INT64/32_MIN, and we + * define the sort ordering to make that work out properly (see further + * comments below). PINF and NINF share the abbreviations of the largest + * and smallest finite abbreviation classes. + */ +#define NUMERIC_ABBREV_BITS (SIZEOF_DATUM * BITS_PER_BYTE) +#if SIZEOF_DATUM == 8 +#define NumericAbbrevGetDatum(X) ((Datum) (X)) +#define DatumGetNumericAbbrev(X) ((int64) (X)) +#define NUMERIC_ABBREV_NAN NumericAbbrevGetDatum(PG_INT64_MIN) +#define NUMERIC_ABBREV_PINF NumericAbbrevGetDatum(-PG_INT64_MAX) +#define NUMERIC_ABBREV_NINF NumericAbbrevGetDatum(PG_INT64_MAX) +#else +#define NumericAbbrevGetDatum(X) ((Datum) (X)) +#define DatumGetNumericAbbrev(X) ((int32) (X)) +#define NUMERIC_ABBREV_NAN NumericAbbrevGetDatum(PG_INT32_MIN) +#define NUMERIC_ABBREV_PINF NumericAbbrevGetDatum(-PG_INT32_MAX) +#define NUMERIC_ABBREV_NINF NumericAbbrevGetDatum(PG_INT32_MAX) +#endif + + +/* ---------- + * Some preinitialized constants + * ---------- + */ +static const NumericDigit const_zero_data[1] = {0}; +static const NumericVar const_zero = +{0, 0, NUMERIC_POS, 0, NULL, (NumericDigit *) const_zero_data}; + +static const NumericDigit const_one_data[1] = {1}; +static const NumericVar const_one = +{1, 0, NUMERIC_POS, 0, NULL, (NumericDigit *) const_one_data}; + +static const NumericVar const_minus_one = +{1, 0, NUMERIC_NEG, 0, NULL, (NumericDigit *) const_one_data}; + +static const NumericDigit const_two_data[1] = {2}; +static const NumericVar const_two = +{1, 0, NUMERIC_POS, 0, NULL, (NumericDigit *) const_two_data}; + +#if DEC_DIGITS == 4 +static const NumericDigit const_zero_point_nine_data[1] = {9000}; +#elif DEC_DIGITS == 2 +static const NumericDigit const_zero_point_nine_data[1] = {90}; +#elif DEC_DIGITS == 1 +static const NumericDigit const_zero_point_nine_data[1] = {9}; +#endif +static const NumericVar const_zero_point_nine = +{1, -1, NUMERIC_POS, 1, NULL, (NumericDigit *) const_zero_point_nine_data}; + +#if DEC_DIGITS == 4 +static const NumericDigit const_one_point_one_data[2] = {1, 1000}; +#elif DEC_DIGITS == 2 +static const NumericDigit const_one_point_one_data[2] = {1, 10}; +#elif DEC_DIGITS == 1 +static const NumericDigit const_one_point_one_data[2] = {1, 1}; +#endif +static const NumericVar const_one_point_one = +{2, 0, NUMERIC_POS, 1, NULL, (NumericDigit *) const_one_point_one_data}; + +static const NumericVar const_nan = +{0, 0, NUMERIC_NAN, 0, NULL, NULL}; + +static const NumericVar const_pinf = +{0, 0, NUMERIC_PINF, 0, NULL, NULL}; + +static const NumericVar const_ninf = +{0, 0, NUMERIC_NINF, 0, NULL, NULL}; + +#if DEC_DIGITS == 4 +static const int round_powers[4] = {0, 1000, 100, 10}; +#endif + + +/* ---------- + * Local functions + * ---------- + */ + +#ifdef NUMERIC_DEBUG +static void dump_numeric(const char *str, Numeric num); +static void dump_var(const char *str, NumericVar *var); +#else +#define dump_numeric(s,n) +#define dump_var(s,v) +#endif + +#define digitbuf_alloc(ndigits) \ + ((NumericDigit *) palloc((ndigits) * sizeof(NumericDigit))) +#define digitbuf_free(buf) \ + do { \ + if ((buf) != NULL) \ + pfree(buf); \ + } while (0) + +#define init_var(v) memset(v, 0, sizeof(NumericVar)) + +#define NUMERIC_DIGITS(num) (NUMERIC_HEADER_IS_SHORT(num) ? \ + (num)->choice.n_short.n_data : (num)->choice.n_long.n_data) +#define NUMERIC_NDIGITS(num) \ + ((VARSIZE(num) - NUMERIC_HEADER_SIZE(num)) / sizeof(NumericDigit)) +#define NUMERIC_CAN_BE_SHORT(scale,weight) \ + ((scale) <= NUMERIC_SHORT_DSCALE_MAX && \ + (weight) <= NUMERIC_SHORT_WEIGHT_MAX && \ + (weight) >= NUMERIC_SHORT_WEIGHT_MIN) + +static void alloc_var(NumericVar *var, int ndigits); +static void free_var(NumericVar *var); +static void zero_var(NumericVar *var); + +static bool set_var_from_str(const char *str, const char *cp, + NumericVar *dest, const char **endptr, + Node *escontext); +static bool set_var_from_non_decimal_integer_str(const char *str, + const char *cp, int sign, + int base, NumericVar *dest, + const char **endptr, + Node *escontext); +static void set_var_from_num(Numeric num, NumericVar *dest); +static void init_var_from_num(Numeric num, NumericVar *dest); +static void set_var_from_var(const NumericVar *value, NumericVar *dest); +static char *get_str_from_var(const NumericVar *var); +static char *get_str_from_var_sci(const NumericVar *var, int rscale); + +static void numericvar_serialize(StringInfo buf, const NumericVar *var); +static void numericvar_deserialize(StringInfo buf, NumericVar *var); + +static Numeric duplicate_numeric(Numeric num); +static Numeric make_result(const NumericVar *var); +static Numeric make_result_opt_error(const NumericVar *var, bool *have_error); + +static bool apply_typmod(NumericVar *var, int32 typmod, Node *escontext); +static bool apply_typmod_special(Numeric num, int32 typmod, Node *escontext); + +static bool numericvar_to_int32(const NumericVar *var, int32 *result); +static bool numericvar_to_int64(const NumericVar *var, int64 *result); +static void int64_to_numericvar(int64 val, NumericVar *var); +static bool numericvar_to_uint64(const NumericVar *var, uint64 *result); +#ifdef HAVE_INT128 +static bool numericvar_to_int128(const NumericVar *var, int128 *result); +static void int128_to_numericvar(int128 val, NumericVar *var); +#endif +static double numericvar_to_double_no_overflow(const NumericVar *var); + +static Datum numeric_abbrev_convert(Datum original_datum, SortSupport ssup); +static bool numeric_abbrev_abort(int memtupcount, SortSupport ssup); +static int numeric_fast_cmp(Datum x, Datum y, SortSupport ssup); +static int numeric_cmp_abbrev(Datum x, Datum y, SortSupport ssup); + +static Datum numeric_abbrev_convert_var(const NumericVar *var, + NumericSortSupport *nss); + +static int cmp_numerics(Numeric num1, Numeric num2); +static int cmp_var(const NumericVar *var1, const NumericVar *var2); +static int cmp_var_common(const NumericDigit *var1digits, int var1ndigits, + int var1weight, int var1sign, + const NumericDigit *var2digits, int var2ndigits, + int var2weight, int var2sign); +static void add_var(const NumericVar *var1, const NumericVar *var2, + NumericVar *result); +static void sub_var(const NumericVar *var1, const NumericVar *var2, + NumericVar *result); +static void mul_var(const NumericVar *var1, const NumericVar *var2, + NumericVar *result, + int rscale); +static void div_var(const NumericVar *var1, const NumericVar *var2, + NumericVar *result, + int rscale, bool round); +static void div_var_fast(const NumericVar *var1, const NumericVar *var2, + NumericVar *result, int rscale, bool round); +static void div_var_int(const NumericVar *var, int ival, int ival_weight, + NumericVar *result, int rscale, bool round); +#ifdef HAVE_INT128 +static void div_var_int64(const NumericVar *var, int64 ival, int ival_weight, + NumericVar *result, int rscale, bool round); +#endif +static int select_div_scale(const NumericVar *var1, const NumericVar *var2); +static void mod_var(const NumericVar *var1, const NumericVar *var2, + NumericVar *result); +static void div_mod_var(const NumericVar *var1, const NumericVar *var2, + NumericVar *quot, NumericVar *rem); +static void ceil_var(const NumericVar *var, NumericVar *result); +static void floor_var(const NumericVar *var, NumericVar *result); + +static void gcd_var(const NumericVar *var1, const NumericVar *var2, + NumericVar *result); +static void sqrt_var(const NumericVar *arg, NumericVar *result, int rscale); +static void exp_var(const NumericVar *arg, NumericVar *result, int rscale); +static int estimate_ln_dweight(const NumericVar *var); +static void ln_var(const NumericVar *arg, NumericVar *result, int rscale); +static void log_var(const NumericVar *base, const NumericVar *num, + NumericVar *result); +static void power_var(const NumericVar *base, const NumericVar *exp, + NumericVar *result); +static void power_var_int(const NumericVar *base, int exp, int exp_dscale, + NumericVar *result); +static void power_ten_int(int exp, NumericVar *result); + +static int cmp_abs(const NumericVar *var1, const NumericVar *var2); +static int cmp_abs_common(const NumericDigit *var1digits, int var1ndigits, + int var1weight, + const NumericDigit *var2digits, int var2ndigits, + int var2weight); +static void add_abs(const NumericVar *var1, const NumericVar *var2, + NumericVar *result); +static void sub_abs(const NumericVar *var1, const NumericVar *var2, + NumericVar *result); +static void round_var(NumericVar *var, int rscale); +static void trunc_var(NumericVar *var, int rscale); +static void strip_var(NumericVar *var); +static void compute_bucket(Numeric operand, Numeric bound1, Numeric bound2, + const NumericVar *count_var, bool reversed_bounds, + NumericVar *result_var); + +static void accum_sum_add(NumericSumAccum *accum, const NumericVar *val); +static void accum_sum_rescale(NumericSumAccum *accum, const NumericVar *val); +static void accum_sum_carry(NumericSumAccum *accum); +static void accum_sum_reset(NumericSumAccum *accum); +static void accum_sum_final(NumericSumAccum *accum, NumericVar *result); +static void accum_sum_copy(NumericSumAccum *dst, NumericSumAccum *src); +static void accum_sum_combine(NumericSumAccum *accum, NumericSumAccum *accum2); + + +/* ---------------------------------------------------------------------- + * + * Input-, output- and rounding-functions + * + * ---------------------------------------------------------------------- + */ + + +/* + * numeric_in() - + * + * Input function for numeric data type + */ +Datum +numeric_in(PG_FUNCTION_ARGS) +{ + char *str = PG_GETARG_CSTRING(0); +#ifdef NOT_USED + Oid typelem = PG_GETARG_OID(1); +#endif + int32 typmod = PG_GETARG_INT32(2); + Node *escontext = fcinfo->context; + Numeric res; + const char *cp; + const char *numstart; + int sign; + + /* Skip leading spaces */ + cp = str; + while (*cp) + { + if (!isspace((unsigned char) *cp)) + break; + cp++; + } + + /* + * Process the number's sign. This duplicates logic in set_var_from_str(), + * but it's worth doing here, since it simplifies the handling of + * infinities and non-decimal integers. + */ + numstart = cp; + sign = NUMERIC_POS; + + if (*cp == '+') + cp++; + else if (*cp == '-') + { + sign = NUMERIC_NEG; + cp++; + } + + /* + * Check for NaN and infinities. We recognize the same strings allowed by + * float8in(). + * + * Since all other legal inputs have a digit or a decimal point after the + * sign, we need only check for NaN/infinity if that's not the case. + */ + if (!isdigit((unsigned char) *cp) && *cp != '.') + { + /* + * The number must be NaN or infinity; anything else can only be a + * syntax error. Note that NaN mustn't have a sign. + */ + if (pg_strncasecmp(numstart, "NaN", 3) == 0) + { + res = make_result(&const_nan); + cp = numstart + 3; + } + else if (pg_strncasecmp(cp, "Infinity", 8) == 0) + { + res = make_result(sign == NUMERIC_POS ? &const_pinf : &const_ninf); + cp += 8; + } + else if (pg_strncasecmp(cp, "inf", 3) == 0) + { + res = make_result(sign == NUMERIC_POS ? &const_pinf : &const_ninf); + cp += 3; + } + else + goto invalid_syntax; + + /* + * Check for trailing junk; there should be nothing left but spaces. + * + * We intentionally do this check before applying the typmod because + * we would like to throw any trailing-junk syntax error before any + * semantic error resulting from apply_typmod_special(). + */ + while (*cp) + { + if (!isspace((unsigned char) *cp)) + goto invalid_syntax; + cp++; + } + + if (!apply_typmod_special(res, typmod, escontext)) + PG_RETURN_NULL(); + } + else + { + /* + * We have a normal numeric value, which may be a non-decimal integer + * or a regular decimal number. + */ + NumericVar value; + int base; + bool have_error; + + init_var(&value); + + /* + * Determine the number's base by looking for a non-decimal prefix + * indicator ("0x", "0o", or "0b"). + */ + if (cp[0] == '0') + { + switch (cp[1]) + { + case 'x': + case 'X': + base = 16; + break; + case 'o': + case 'O': + base = 8; + break; + case 'b': + case 'B': + base = 2; + break; + default: + base = 10; + } + } + else + base = 10; + + /* Parse the rest of the number and apply the sign */ + if (base == 10) + { + if (!set_var_from_str(str, cp, &value, &cp, escontext)) + PG_RETURN_NULL(); + value.sign = sign; + } + else + { + if (!set_var_from_non_decimal_integer_str(str, cp + 2, sign, base, + &value, &cp, escontext)) + PG_RETURN_NULL(); + } + + /* + * Should be nothing left but spaces. As above, throw any typmod error + * after finishing syntax check. + */ + while (*cp) + { + if (!isspace((unsigned char) *cp)) + goto invalid_syntax; + cp++; + } + + if (!apply_typmod(&value, typmod, escontext)) + PG_RETURN_NULL(); + + res = make_result_opt_error(&value, &have_error); + + if (have_error) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("value overflows numeric format"))); + + free_var(&value); + } + + PG_RETURN_NUMERIC(res); + +invalid_syntax: + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s: \"%s\"", + "numeric", str))); +} + + +/* + * numeric_out() - + * + * Output function for numeric data type + */ +Datum +numeric_out(PG_FUNCTION_ARGS) +{ + Numeric num = PG_GETARG_NUMERIC(0); + NumericVar x; + char *str; + + /* + * Handle NaN and infinities + */ + if (NUMERIC_IS_SPECIAL(num)) + { + if (NUMERIC_IS_PINF(num)) + PG_RETURN_CSTRING(pstrdup("Infinity")); + else if (NUMERIC_IS_NINF(num)) + PG_RETURN_CSTRING(pstrdup("-Infinity")); + else + PG_RETURN_CSTRING(pstrdup("NaN")); + } + + /* + * Get the number in the variable format. + */ + init_var_from_num(num, &x); + + str = get_str_from_var(&x); + + PG_RETURN_CSTRING(str); +} + +/* + * numeric_is_nan() - + * + * Is Numeric value a NaN? + */ +bool +numeric_is_nan(Numeric num) +{ + return NUMERIC_IS_NAN(num); +} + +/* + * numeric_is_inf() - + * + * Is Numeric value an infinity? + */ +bool +numeric_is_inf(Numeric num) +{ + return NUMERIC_IS_INF(num); +} + +/* + * numeric_is_integral() - + * + * Is Numeric value integral? + */ +static bool +numeric_is_integral(Numeric num) +{ + NumericVar arg; + + /* Reject NaN, but infinities are considered integral */ + if (NUMERIC_IS_SPECIAL(num)) + { + if (NUMERIC_IS_NAN(num)) + return false; + return true; + } + + /* Integral if there are no digits to the right of the decimal point */ + init_var_from_num(num, &arg); + + return (arg.ndigits == 0 || arg.ndigits <= arg.weight + 1); +} + +/* + * make_numeric_typmod() - + * + * Pack numeric precision and scale values into a typmod. The upper 16 bits + * are used for the precision (though actually not all these bits are needed, + * since the maximum allowed precision is 1000). The lower 16 bits are for + * the scale, but since the scale is constrained to the range [-1000, 1000], + * we use just the lower 11 of those 16 bits, and leave the remaining 5 bits + * unset, for possible future use. + * + * For purely historical reasons VARHDRSZ is then added to the result, thus + * the unused space in the upper 16 bits is not all as freely available as it + * might seem. (We can't let the result overflow to a negative int32, as + * other parts of the system would interpret that as not-a-valid-typmod.) + */ +static inline int32 +make_numeric_typmod(int precision, int scale) +{ + return ((precision << 16) | (scale & 0x7ff)) + VARHDRSZ; +} + +/* + * Because of the offset, valid numeric typmods are at least VARHDRSZ + */ +static inline bool +is_valid_numeric_typmod(int32 typmod) +{ + return typmod >= (int32) VARHDRSZ; +} + +/* + * numeric_typmod_precision() - + * + * Extract the precision from a numeric typmod --- see make_numeric_typmod(). + */ +static inline int +numeric_typmod_precision(int32 typmod) +{ + return ((typmod - VARHDRSZ) >> 16) & 0xffff; +} + +/* + * numeric_typmod_scale() - + * + * Extract the scale from a numeric typmod --- see make_numeric_typmod(). + * + * Note that the scale may be negative, so we must do sign extension when + * unpacking it. We do this using the bit hack (x^1024)-1024, which sign + * extends an 11-bit two's complement number x. + */ +static inline int +numeric_typmod_scale(int32 typmod) +{ + return (((typmod - VARHDRSZ) & 0x7ff) ^ 1024) - 1024; +} + +/* + * numeric_maximum_size() - + * + * Maximum size of a numeric with given typmod, or -1 if unlimited/unknown. + */ +int32 +numeric_maximum_size(int32 typmod) +{ + int precision; + int numeric_digits; + + if (!is_valid_numeric_typmod(typmod)) + return -1; + + /* precision (ie, max # of digits) is in upper bits of typmod */ + precision = numeric_typmod_precision(typmod); + + /* + * This formula computes the maximum number of NumericDigits we could need + * in order to store the specified number of decimal digits. Because the + * weight is stored as a number of NumericDigits rather than a number of + * decimal digits, it's possible that the first NumericDigit will contain + * only a single decimal digit. Thus, the first two decimal digits can + * require two NumericDigits to store, but it isn't until we reach + * DEC_DIGITS + 2 decimal digits that we potentially need a third + * NumericDigit. + */ + numeric_digits = (precision + 2 * (DEC_DIGITS - 1)) / DEC_DIGITS; + + /* + * In most cases, the size of a numeric will be smaller than the value + * computed below, because the varlena header will typically get toasted + * down to a single byte before being stored on disk, and it may also be + * possible to use a short numeric header. But our job here is to compute + * the worst case. + */ + return NUMERIC_HDRSZ + (numeric_digits * sizeof(NumericDigit)); +} + +/* + * numeric_out_sci() - + * + * Output function for numeric data type in scientific notation. + */ +char * +numeric_out_sci(Numeric num, int scale) +{ + NumericVar x; + char *str; + + /* + * Handle NaN and infinities + */ + if (NUMERIC_IS_SPECIAL(num)) + { + if (NUMERIC_IS_PINF(num)) + return pstrdup("Infinity"); + else if (NUMERIC_IS_NINF(num)) + return pstrdup("-Infinity"); + else + return pstrdup("NaN"); + } + + init_var_from_num(num, &x); + + str = get_str_from_var_sci(&x, scale); + + return str; +} + +/* + * numeric_normalize() - + * + * Output function for numeric data type, suppressing insignificant trailing + * zeroes and then any trailing decimal point. The intent of this is to + * produce strings that are equal if and only if the input numeric values + * compare equal. + */ +char * +numeric_normalize(Numeric num) +{ + NumericVar x; + char *str; + int last; + + /* + * Handle NaN and infinities + */ + if (NUMERIC_IS_SPECIAL(num)) + { + if (NUMERIC_IS_PINF(num)) + return pstrdup("Infinity"); + else if (NUMERIC_IS_NINF(num)) + return pstrdup("-Infinity"); + else + return pstrdup("NaN"); + } + + init_var_from_num(num, &x); + + str = get_str_from_var(&x); + + /* If there's no decimal point, there's certainly nothing to remove. */ + if (strchr(str, '.') != NULL) + { + /* + * Back up over trailing fractional zeroes. Since there is a decimal + * point, this loop will terminate safely. + */ + last = strlen(str) - 1; + while (str[last] == '0') + last--; + + /* We want to get rid of the decimal point too, if it's now last. */ + if (str[last] == '.') + last--; + + /* Delete whatever we backed up over. */ + str[last + 1] = '\0'; + } + + return str; +} + +/* + * numeric_recv - converts external binary format to numeric + * + * External format is a sequence of int16's: + * ndigits, weight, sign, dscale, NumericDigits. + */ +Datum +numeric_recv(PG_FUNCTION_ARGS) +{ + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + +#ifdef NOT_USED + Oid typelem = PG_GETARG_OID(1); +#endif + int32 typmod = PG_GETARG_INT32(2); + NumericVar value; + Numeric res; + int len, + i; + + init_var(&value); + + len = (uint16) pq_getmsgint(buf, sizeof(uint16)); + + alloc_var(&value, len); + + value.weight = (int16) pq_getmsgint(buf, sizeof(int16)); + /* we allow any int16 for weight --- OK? */ + + value.sign = (uint16) pq_getmsgint(buf, sizeof(uint16)); + if (!(value.sign == NUMERIC_POS || + value.sign == NUMERIC_NEG || + value.sign == NUMERIC_NAN || + value.sign == NUMERIC_PINF || + value.sign == NUMERIC_NINF)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION), + errmsg("invalid sign in external \"numeric\" value"))); + + value.dscale = (uint16) pq_getmsgint(buf, sizeof(uint16)); + if ((value.dscale & NUMERIC_DSCALE_MASK) != value.dscale) + ereport(ERROR, + (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION), + errmsg("invalid scale in external \"numeric\" value"))); + + for (i = 0; i < len; i++) + { + NumericDigit d = pq_getmsgint(buf, sizeof(NumericDigit)); + + if (d < 0 || d >= NBASE) + ereport(ERROR, + (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION), + errmsg("invalid digit in external \"numeric\" value"))); + value.digits[i] = d; + } + + /* + * If the given dscale would hide any digits, truncate those digits away. + * We could alternatively throw an error, but that would take a bunch of + * extra code (about as much as trunc_var involves), and it might cause + * client compatibility issues. Be careful not to apply trunc_var to + * special values, as it could do the wrong thing; we don't need it + * anyway, since make_result will ignore all but the sign field. + * + * After doing that, be sure to check the typmod restriction. + */ + if (value.sign == NUMERIC_POS || + value.sign == NUMERIC_NEG) + { + trunc_var(&value, value.dscale); + + (void) apply_typmod(&value, typmod, NULL); + + res = make_result(&value); + } + else + { + /* apply_typmod_special wants us to make the Numeric first */ + res = make_result(&value); + + (void) apply_typmod_special(res, typmod, NULL); + } + + free_var(&value); + + PG_RETURN_NUMERIC(res); +} + +/* + * numeric_send - converts numeric to binary format + */ +Datum +numeric_send(PG_FUNCTION_ARGS) +{ + Numeric num = PG_GETARG_NUMERIC(0); + NumericVar x; + StringInfoData buf; + int i; + + init_var_from_num(num, &x); + + pq_begintypsend(&buf); + + pq_sendint16(&buf, x.ndigits); + pq_sendint16(&buf, x.weight); + pq_sendint16(&buf, x.sign); + pq_sendint16(&buf, x.dscale); + for (i = 0; i < x.ndigits; i++) + pq_sendint16(&buf, x.digits[i]); + + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} + + +/* + * numeric_support() + * + * Planner support function for the numeric() length coercion function. + * + * Flatten calls that solely represent increases in allowable precision. + * Scale changes mutate every datum, so they are unoptimizable. Some values, + * e.g. 1E-1001, can only fit into an unconstrained numeric, so a change from + * an unconstrained numeric to any constrained numeric is also unoptimizable. + */ +Datum +numeric_support(PG_FUNCTION_ARGS) +{ + Node *rawreq = (Node *) PG_GETARG_POINTER(0); + Node *ret = NULL; + + if (IsA(rawreq, SupportRequestSimplify)) + { + SupportRequestSimplify *req = (SupportRequestSimplify *) rawreq; + FuncExpr *expr = req->fcall; + Node *typmod; + + Assert(list_length(expr->args) >= 2); + + typmod = (Node *) lsecond(expr->args); + + if (IsA(typmod, Const) && !((Const *) typmod)->constisnull) + { + Node *source = (Node *) linitial(expr->args); + int32 old_typmod = exprTypmod(source); + int32 new_typmod = DatumGetInt32(((Const *) typmod)->constvalue); + int32 old_scale = numeric_typmod_scale(old_typmod); + int32 new_scale = numeric_typmod_scale(new_typmod); + int32 old_precision = numeric_typmod_precision(old_typmod); + int32 new_precision = numeric_typmod_precision(new_typmod); + + /* + * If new_typmod is invalid, the destination is unconstrained; + * that's always OK. If old_typmod is valid, the source is + * constrained, and we're OK if the scale is unchanged and the + * precision is not decreasing. See further notes in function + * header comment. + */ + if (!is_valid_numeric_typmod(new_typmod) || + (is_valid_numeric_typmod(old_typmod) && + new_scale == old_scale && new_precision >= old_precision)) + ret = relabel_to_typmod(source, new_typmod); + } + } + + PG_RETURN_POINTER(ret); +} + +/* + * numeric() - + * + * This is a special function called by the Postgres database system + * before a value is stored in a tuple's attribute. The precision and + * scale of the attribute have to be applied on the value. + */ +Datum +numeric (PG_FUNCTION_ARGS) +{ + Numeric num = PG_GETARG_NUMERIC(0); + int32 typmod = PG_GETARG_INT32(1); + Numeric new; + int precision; + int scale; + int ddigits; + int maxdigits; + int dscale; + NumericVar var; + + /* + * Handle NaN and infinities: if apply_typmod_special doesn't complain, + * just return a copy of the input. + */ + if (NUMERIC_IS_SPECIAL(num)) + { + (void) apply_typmod_special(num, typmod, NULL); + PG_RETURN_NUMERIC(duplicate_numeric(num)); + } + + /* + * If the value isn't a valid type modifier, simply return a copy of the + * input value + */ + if (!is_valid_numeric_typmod(typmod)) + PG_RETURN_NUMERIC(duplicate_numeric(num)); + + /* + * Get the precision and scale out of the typmod value + */ + precision = numeric_typmod_precision(typmod); + scale = numeric_typmod_scale(typmod); + maxdigits = precision - scale; + + /* The target display scale is non-negative */ + dscale = Max(scale, 0); + + /* + * If the number is certainly in bounds and due to the target scale no + * rounding could be necessary, just make a copy of the input and modify + * its scale fields, unless the larger scale forces us to abandon the + * short representation. (Note we assume the existing dscale is + * honest...) + */ + ddigits = (NUMERIC_WEIGHT(num) + 1) * DEC_DIGITS; + if (ddigits <= maxdigits && scale >= NUMERIC_DSCALE(num) + && (NUMERIC_CAN_BE_SHORT(dscale, NUMERIC_WEIGHT(num)) + || !NUMERIC_IS_SHORT(num))) + { + new = duplicate_numeric(num); + if (NUMERIC_IS_SHORT(num)) + new->choice.n_short.n_header = + (num->choice.n_short.n_header & ~NUMERIC_SHORT_DSCALE_MASK) + | (dscale << NUMERIC_SHORT_DSCALE_SHIFT); + else + new->choice.n_long.n_sign_dscale = NUMERIC_SIGN(new) | + ((uint16) dscale & NUMERIC_DSCALE_MASK); + PG_RETURN_NUMERIC(new); + } + + /* + * We really need to fiddle with things - unpack the number into a + * variable and let apply_typmod() do it. + */ + init_var(&var); + + set_var_from_num(num, &var); + (void) apply_typmod(&var, typmod, NULL); + new = make_result(&var); + + free_var(&var); + + PG_RETURN_NUMERIC(new); +} + +Datum +numerictypmodin(PG_FUNCTION_ARGS) +{ + ArrayType *ta = PG_GETARG_ARRAYTYPE_P(0); + int32 *tl; + int n; + int32 typmod; + + tl = ArrayGetIntegerTypmods(ta, &n); + + if (n == 2) + { + if (tl[0] < 1 || tl[0] > NUMERIC_MAX_PRECISION) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("NUMERIC precision %d must be between 1 and %d", + tl[0], NUMERIC_MAX_PRECISION))); + if (tl[1] < NUMERIC_MIN_SCALE || tl[1] > NUMERIC_MAX_SCALE) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("NUMERIC scale %d must be between %d and %d", + tl[1], NUMERIC_MIN_SCALE, NUMERIC_MAX_SCALE))); + typmod = make_numeric_typmod(tl[0], tl[1]); + } + else if (n == 1) + { + if (tl[0] < 1 || tl[0] > NUMERIC_MAX_PRECISION) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("NUMERIC precision %d must be between 1 and %d", + tl[0], NUMERIC_MAX_PRECISION))); + /* scale defaults to zero */ + typmod = make_numeric_typmod(tl[0], 0); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid NUMERIC type modifier"))); + typmod = 0; /* keep compiler quiet */ + } + + PG_RETURN_INT32(typmod); +} + +Datum +numerictypmodout(PG_FUNCTION_ARGS) +{ + int32 typmod = PG_GETARG_INT32(0); + char *res = (char *) palloc(64); + + if (is_valid_numeric_typmod(typmod)) + snprintf(res, 64, "(%d,%d)", + numeric_typmod_precision(typmod), + numeric_typmod_scale(typmod)); + else + *res = '\0'; + + PG_RETURN_CSTRING(res); +} + + +/* ---------------------------------------------------------------------- + * + * Sign manipulation, rounding and the like + * + * ---------------------------------------------------------------------- + */ + +Datum +numeric_abs(PG_FUNCTION_ARGS) +{ + Numeric num = PG_GETARG_NUMERIC(0); + Numeric res; + + /* + * Do it the easy way directly on the packed format + */ + res = duplicate_numeric(num); + + if (NUMERIC_IS_SHORT(num)) + res->choice.n_short.n_header = + num->choice.n_short.n_header & ~NUMERIC_SHORT_SIGN_MASK; + else if (NUMERIC_IS_SPECIAL(num)) + { + /* This changes -Inf to Inf, and doesn't affect NaN */ + res->choice.n_short.n_header = + num->choice.n_short.n_header & ~NUMERIC_INF_SIGN_MASK; + } + else + res->choice.n_long.n_sign_dscale = NUMERIC_POS | NUMERIC_DSCALE(num); + + PG_RETURN_NUMERIC(res); +} + + +Datum +numeric_uminus(PG_FUNCTION_ARGS) +{ + Numeric num = PG_GETARG_NUMERIC(0); + Numeric res; + + /* + * Do it the easy way directly on the packed format + */ + res = duplicate_numeric(num); + + if (NUMERIC_IS_SPECIAL(num)) + { + /* Flip the sign, if it's Inf or -Inf */ + if (!NUMERIC_IS_NAN(num)) + res->choice.n_short.n_header = + num->choice.n_short.n_header ^ NUMERIC_INF_SIGN_MASK; + } + + /* + * The packed format is known to be totally zero digit trimmed always. So + * once we've eliminated specials, we can identify a zero by the fact that + * there are no digits at all. Do nothing to a zero. + */ + else if (NUMERIC_NDIGITS(num) != 0) + { + /* Else, flip the sign */ + if (NUMERIC_IS_SHORT(num)) + res->choice.n_short.n_header = + num->choice.n_short.n_header ^ NUMERIC_SHORT_SIGN_MASK; + else if (NUMERIC_SIGN(num) == NUMERIC_POS) + res->choice.n_long.n_sign_dscale = + NUMERIC_NEG | NUMERIC_DSCALE(num); + else + res->choice.n_long.n_sign_dscale = + NUMERIC_POS | NUMERIC_DSCALE(num); + } + + PG_RETURN_NUMERIC(res); +} + + +Datum +numeric_uplus(PG_FUNCTION_ARGS) +{ + Numeric num = PG_GETARG_NUMERIC(0); + + PG_RETURN_NUMERIC(duplicate_numeric(num)); +} + + +/* + * numeric_sign_internal() - + * + * Returns -1 if the argument is less than 0, 0 if the argument is equal + * to 0, and 1 if the argument is greater than zero. Caller must have + * taken care of the NaN case, but we can handle infinities here. + */ +static int +numeric_sign_internal(Numeric num) +{ + if (NUMERIC_IS_SPECIAL(num)) + { + Assert(!NUMERIC_IS_NAN(num)); + /* Must be Inf or -Inf */ + if (NUMERIC_IS_PINF(num)) + return 1; + else + return -1; + } + + /* + * The packed format is known to be totally zero digit trimmed always. So + * once we've eliminated specials, we can identify a zero by the fact that + * there are no digits at all. + */ + else if (NUMERIC_NDIGITS(num) == 0) + return 0; + else if (NUMERIC_SIGN(num) == NUMERIC_NEG) + return -1; + else + return 1; +} + +/* + * numeric_sign() - + * + * returns -1 if the argument is less than 0, 0 if the argument is equal + * to 0, and 1 if the argument is greater than zero. + */ +Datum +numeric_sign(PG_FUNCTION_ARGS) +{ + Numeric num = PG_GETARG_NUMERIC(0); + + /* + * Handle NaN (infinities can be handled normally) + */ + if (NUMERIC_IS_NAN(num)) + PG_RETURN_NUMERIC(make_result(&const_nan)); + + switch (numeric_sign_internal(num)) + { + case 0: + PG_RETURN_NUMERIC(make_result(&const_zero)); + case 1: + PG_RETURN_NUMERIC(make_result(&const_one)); + case -1: + PG_RETURN_NUMERIC(make_result(&const_minus_one)); + } + + Assert(false); + return (Datum) 0; +} + + +/* + * numeric_round() - + * + * Round a value to have 'scale' digits after the decimal point. + * We allow negative 'scale', implying rounding before the decimal + * point --- Oracle interprets rounding that way. + */ +Datum +numeric_round(PG_FUNCTION_ARGS) +{ + Numeric num = PG_GETARG_NUMERIC(0); + int32 scale = PG_GETARG_INT32(1); + Numeric res; + NumericVar arg; + + /* + * Handle NaN and infinities + */ + if (NUMERIC_IS_SPECIAL(num)) + PG_RETURN_NUMERIC(duplicate_numeric(num)); + + /* + * Limit the scale value to avoid possible overflow in calculations + */ + scale = Max(scale, -NUMERIC_MAX_RESULT_SCALE); + scale = Min(scale, NUMERIC_MAX_RESULT_SCALE); + + /* + * Unpack the argument and round it at the proper digit position + */ + init_var(&arg); + set_var_from_num(num, &arg); + + round_var(&arg, scale); + + /* We don't allow negative output dscale */ + if (scale < 0) + arg.dscale = 0; + + /* + * Return the rounded result + */ + res = make_result(&arg); + + free_var(&arg); + PG_RETURN_NUMERIC(res); +} + + +/* + * numeric_trunc() - + * + * Truncate a value to have 'scale' digits after the decimal point. + * We allow negative 'scale', implying a truncation before the decimal + * point --- Oracle interprets truncation that way. + */ +Datum +numeric_trunc(PG_FUNCTION_ARGS) +{ + Numeric num = PG_GETARG_NUMERIC(0); + int32 scale = PG_GETARG_INT32(1); + Numeric res; + NumericVar arg; + + /* + * Handle NaN and infinities + */ + if (NUMERIC_IS_SPECIAL(num)) + PG_RETURN_NUMERIC(duplicate_numeric(num)); + + /* + * Limit the scale value to avoid possible overflow in calculations + */ + scale = Max(scale, -NUMERIC_MAX_RESULT_SCALE); + scale = Min(scale, NUMERIC_MAX_RESULT_SCALE); + + /* + * Unpack the argument and truncate it at the proper digit position + */ + init_var(&arg); + set_var_from_num(num, &arg); + + trunc_var(&arg, scale); + + /* We don't allow negative output dscale */ + if (scale < 0) + arg.dscale = 0; + + /* + * Return the truncated result + */ + res = make_result(&arg); + + free_var(&arg); + PG_RETURN_NUMERIC(res); +} + + +/* + * numeric_ceil() - + * + * Return the smallest integer greater than or equal to the argument + */ +Datum +numeric_ceil(PG_FUNCTION_ARGS) +{ + Numeric num = PG_GETARG_NUMERIC(0); + Numeric res; + NumericVar result; + + /* + * Handle NaN and infinities + */ + if (NUMERIC_IS_SPECIAL(num)) + PG_RETURN_NUMERIC(duplicate_numeric(num)); + + init_var_from_num(num, &result); + ceil_var(&result, &result); + + res = make_result(&result); + free_var(&result); + + PG_RETURN_NUMERIC(res); +} + + +/* + * numeric_floor() - + * + * Return the largest integer equal to or less than the argument + */ +Datum +numeric_floor(PG_FUNCTION_ARGS) +{ + Numeric num = PG_GETARG_NUMERIC(0); + Numeric res; + NumericVar result; + + /* + * Handle NaN and infinities + */ + if (NUMERIC_IS_SPECIAL(num)) + PG_RETURN_NUMERIC(duplicate_numeric(num)); + + init_var_from_num(num, &result); + floor_var(&result, &result); + + res = make_result(&result); + free_var(&result); + + PG_RETURN_NUMERIC(res); +} + + +/* + * generate_series_numeric() - + * + * Generate series of numeric. + */ +Datum +generate_series_numeric(PG_FUNCTION_ARGS) +{ + return generate_series_step_numeric(fcinfo); +} + +Datum +generate_series_step_numeric(PG_FUNCTION_ARGS) +{ + generate_series_numeric_fctx *fctx; + FuncCallContext *funcctx; + MemoryContext oldcontext; + + if (SRF_IS_FIRSTCALL()) + { + Numeric start_num = PG_GETARG_NUMERIC(0); + Numeric stop_num = PG_GETARG_NUMERIC(1); + NumericVar steploc = const_one; + + /* Reject NaN and infinities in start and stop values */ + if (NUMERIC_IS_SPECIAL(start_num)) + { + if (NUMERIC_IS_NAN(start_num)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("start value cannot be NaN"))); + else + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("start value cannot be infinity"))); + } + if (NUMERIC_IS_SPECIAL(stop_num)) + { + if (NUMERIC_IS_NAN(stop_num)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("stop value cannot be NaN"))); + else + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("stop value cannot be infinity"))); + } + + /* see if we were given an explicit step size */ + if (PG_NARGS() == 3) + { + Numeric step_num = PG_GETARG_NUMERIC(2); + + if (NUMERIC_IS_SPECIAL(step_num)) + { + if (NUMERIC_IS_NAN(step_num)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("step size cannot be NaN"))); + else + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("step size cannot be infinity"))); + } + + init_var_from_num(step_num, &steploc); + + if (cmp_var(&steploc, &const_zero) == 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("step size cannot equal zero"))); + } + + /* 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 */ + fctx = (generate_series_numeric_fctx *) + palloc(sizeof(generate_series_numeric_fctx)); + + /* + * Use fctx to keep state from call to call. Seed current with the + * original start value. We must copy the start_num and stop_num + * values rather than pointing to them, since we may have detoasted + * them in the per-call context. + */ + init_var(&fctx->current); + init_var(&fctx->stop); + init_var(&fctx->step); + + set_var_from_num(start_num, &fctx->current); + set_var_from_num(stop_num, &fctx->stop); + set_var_from_var(&steploc, &fctx->step); + + funcctx->user_fctx = fctx; + MemoryContextSwitchTo(oldcontext); + } + + /* stuff done on every call of the function */ + funcctx = SRF_PERCALL_SETUP(); + + /* + * Get the saved state and use current state as the result of this + * iteration. + */ + fctx = funcctx->user_fctx; + + if ((fctx->step.sign == NUMERIC_POS && + cmp_var(&fctx->current, &fctx->stop) <= 0) || + (fctx->step.sign == NUMERIC_NEG && + cmp_var(&fctx->current, &fctx->stop) >= 0)) + { + Numeric result = make_result(&fctx->current); + + /* switch to memory context appropriate for iteration calculation */ + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + /* increment current in preparation for next iteration */ + add_var(&fctx->current, &fctx->step, &fctx->current); + MemoryContextSwitchTo(oldcontext); + + /* do when there is more left to send */ + SRF_RETURN_NEXT(funcctx, NumericGetDatum(result)); + } + else + /* do when there is no more left */ + SRF_RETURN_DONE(funcctx); +} + + +/* + * Implements the numeric version of the width_bucket() function + * defined by SQL2003. See also width_bucket_float8(). + * + * 'bound1' and 'bound2' are the lower and upper bounds of the + * histogram's range, respectively. 'count' is the number of buckets + * in the histogram. width_bucket() returns an integer indicating the + * bucket number that 'operand' belongs to in an equiwidth histogram + * with the specified characteristics. An operand smaller than the + * lower bound is assigned to bucket 0. An operand greater than the + * upper bound is assigned to an additional bucket (with number + * count+1). We don't allow "NaN" for any of the numeric arguments. + */ +Datum +width_bucket_numeric(PG_FUNCTION_ARGS) +{ + Numeric operand = PG_GETARG_NUMERIC(0); + Numeric bound1 = PG_GETARG_NUMERIC(1); + Numeric bound2 = PG_GETARG_NUMERIC(2); + int32 count = PG_GETARG_INT32(3); + NumericVar count_var; + NumericVar result_var; + int32 result; + + if (count <= 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_ARGUMENT_FOR_WIDTH_BUCKET_FUNCTION), + errmsg("count must be greater than zero"))); + + if (NUMERIC_IS_SPECIAL(operand) || + NUMERIC_IS_SPECIAL(bound1) || + NUMERIC_IS_SPECIAL(bound2)) + { + if (NUMERIC_IS_NAN(operand) || + NUMERIC_IS_NAN(bound1) || + NUMERIC_IS_NAN(bound2)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_ARGUMENT_FOR_WIDTH_BUCKET_FUNCTION), + errmsg("operand, lower bound, and upper bound cannot be NaN"))); + /* We allow "operand" to be infinite; cmp_numerics will cope */ + if (NUMERIC_IS_INF(bound1) || NUMERIC_IS_INF(bound2)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_ARGUMENT_FOR_WIDTH_BUCKET_FUNCTION), + errmsg("lower and upper bounds must be finite"))); + } + + init_var(&result_var); + init_var(&count_var); + + /* Convert 'count' to a numeric, for ease of use later */ + int64_to_numericvar((int64) count, &count_var); + + switch (cmp_numerics(bound1, bound2)) + { + case 0: + ereport(ERROR, + (errcode(ERRCODE_INVALID_ARGUMENT_FOR_WIDTH_BUCKET_FUNCTION), + errmsg("lower bound cannot equal upper bound"))); + break; + + /* bound1 < bound2 */ + case -1: + if (cmp_numerics(operand, bound1) < 0) + set_var_from_var(&const_zero, &result_var); + else if (cmp_numerics(operand, bound2) >= 0) + add_var(&count_var, &const_one, &result_var); + else + compute_bucket(operand, bound1, bound2, &count_var, false, + &result_var); + break; + + /* bound1 > bound2 */ + case 1: + if (cmp_numerics(operand, bound1) > 0) + set_var_from_var(&const_zero, &result_var); + else if (cmp_numerics(operand, bound2) <= 0) + add_var(&count_var, &const_one, &result_var); + else + compute_bucket(operand, bound1, bound2, &count_var, true, + &result_var); + break; + } + + /* if result exceeds the range of a legal int4, we ereport here */ + if (!numericvar_to_int32(&result_var, &result)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("integer out of range"))); + + free_var(&count_var); + free_var(&result_var); + + PG_RETURN_INT32(result); +} + +/* + * 'operand' is inside the bucket range, so determine the correct + * bucket for it to go. The calculations performed by this function + * are derived directly from the SQL2003 spec. Note however that we + * multiply by count before dividing, to avoid unnecessary roundoff error. + */ +static void +compute_bucket(Numeric operand, Numeric bound1, Numeric bound2, + const NumericVar *count_var, bool reversed_bounds, + NumericVar *result_var) +{ + NumericVar bound1_var; + NumericVar bound2_var; + NumericVar operand_var; + + init_var_from_num(bound1, &bound1_var); + init_var_from_num(bound2, &bound2_var); + init_var_from_num(operand, &operand_var); + + if (!reversed_bounds) + { + sub_var(&operand_var, &bound1_var, &operand_var); + sub_var(&bound2_var, &bound1_var, &bound2_var); + } + else + { + sub_var(&bound1_var, &operand_var, &operand_var); + sub_var(&bound1_var, &bound2_var, &bound2_var); + } + + mul_var(&operand_var, count_var, &operand_var, + operand_var.dscale + count_var->dscale); + div_var(&operand_var, &bound2_var, result_var, + select_div_scale(&operand_var, &bound2_var), true); + + /* + * Roundoff in the division could give us a quotient exactly equal to + * "count", which is too large. Clamp so that we do not emit a result + * larger than "count". + */ + if (cmp_var(result_var, count_var) >= 0) + set_var_from_var(count_var, result_var); + else + { + add_var(result_var, &const_one, result_var); + floor_var(result_var, result_var); + } + + free_var(&bound1_var); + free_var(&bound2_var); + free_var(&operand_var); +} + +/* ---------------------------------------------------------------------- + * + * Comparison functions + * + * Note: btree indexes need these routines not to leak memory; therefore, + * be careful to free working copies of toasted datums. Most places don't + * need to be so careful. + * + * Sort support: + * + * We implement the sortsupport strategy routine in order to get the benefit of + * abbreviation. The ordinary numeric comparison can be quite slow as a result + * of palloc/pfree cycles (due to detoasting packed values for alignment); + * while this could be worked on itself, the abbreviation strategy gives more + * speedup in many common cases. + * + * Two different representations are used for the abbreviated form, one in + * int32 and one in int64, whichever fits into a by-value Datum. In both cases + * the representation is negated relative to the original value, because we use + * the largest negative value for NaN, which sorts higher than other values. We + * convert the absolute value of the numeric to a 31-bit or 63-bit positive + * value, and then negate it if the original number was positive. + * + * We abort the abbreviation process if the abbreviation cardinality is below + * 0.01% of the row count (1 per 10k non-null rows). The actual break-even + * point is somewhat below that, perhaps 1 per 30k (at 1 per 100k there's a + * very small penalty), but we don't want to build up too many abbreviated + * values before first testing for abort, so we take the slightly pessimistic + * number. We make no attempt to estimate the cardinality of the real values, + * since it plays no part in the cost model here (if the abbreviation is equal, + * the cost of comparing equal and unequal underlying values is comparable). + * We discontinue even checking for abort (saving us the hashing overhead) if + * the estimated cardinality gets to 100k; that would be enough to support many + * billions of rows while doing no worse than breaking even. + * + * ---------------------------------------------------------------------- + */ + +/* + * Sort support strategy routine. + */ +Datum +numeric_sortsupport(PG_FUNCTION_ARGS) +{ + SortSupport ssup = (SortSupport) PG_GETARG_POINTER(0); + + ssup->comparator = numeric_fast_cmp; + + if (ssup->abbreviate) + { + NumericSortSupport *nss; + MemoryContext oldcontext = MemoryContextSwitchTo(ssup->ssup_cxt); + + nss = palloc(sizeof(NumericSortSupport)); + + /* + * palloc a buffer for handling unaligned packed values in addition to + * the support struct + */ + nss->buf = palloc(VARATT_SHORT_MAX + VARHDRSZ + 1); + + nss->input_count = 0; + nss->estimating = true; + initHyperLogLog(&nss->abbr_card, 10); + + ssup->ssup_extra = nss; + + ssup->abbrev_full_comparator = ssup->comparator; + ssup->comparator = numeric_cmp_abbrev; + ssup->abbrev_converter = numeric_abbrev_convert; + ssup->abbrev_abort = numeric_abbrev_abort; + + MemoryContextSwitchTo(oldcontext); + } + + PG_RETURN_VOID(); +} + +/* + * Abbreviate a numeric datum, handling NaNs and detoasting + * (must not leak memory!) + */ +static Datum +numeric_abbrev_convert(Datum original_datum, SortSupport ssup) +{ + NumericSortSupport *nss = ssup->ssup_extra; + void *original_varatt = PG_DETOAST_DATUM_PACKED(original_datum); + Numeric value; + Datum result; + + nss->input_count += 1; + + /* + * This is to handle packed datums without needing a palloc/pfree cycle; + * we keep and reuse a buffer large enough to handle any short datum. + */ + if (VARATT_IS_SHORT(original_varatt)) + { + void *buf = nss->buf; + Size sz = VARSIZE_SHORT(original_varatt) - VARHDRSZ_SHORT; + + Assert(sz <= VARATT_SHORT_MAX - VARHDRSZ_SHORT); + + SET_VARSIZE(buf, VARHDRSZ + sz); + memcpy(VARDATA(buf), VARDATA_SHORT(original_varatt), sz); + + value = (Numeric) buf; + } + else + value = (Numeric) original_varatt; + + if (NUMERIC_IS_SPECIAL(value)) + { + if (NUMERIC_IS_PINF(value)) + result = NUMERIC_ABBREV_PINF; + else if (NUMERIC_IS_NINF(value)) + result = NUMERIC_ABBREV_NINF; + else + result = NUMERIC_ABBREV_NAN; + } + else + { + NumericVar var; + + init_var_from_num(value, &var); + + result = numeric_abbrev_convert_var(&var, nss); + } + + /* should happen only for external/compressed toasts */ + if ((Pointer) original_varatt != DatumGetPointer(original_datum)) + pfree(original_varatt); + + return result; +} + +/* + * Consider whether to abort abbreviation. + * + * We pay no attention to the cardinality of the non-abbreviated data. There is + * no reason to do so: unlike text, we have no fast check for equal values, so + * we pay the full overhead whenever the abbreviations are equal regardless of + * whether the underlying values are also equal. + */ +static bool +numeric_abbrev_abort(int memtupcount, SortSupport ssup) +{ + NumericSortSupport *nss = ssup->ssup_extra; + double abbr_card; + + if (memtupcount < 10000 || nss->input_count < 10000 || !nss->estimating) + return false; + + abbr_card = estimateHyperLogLog(&nss->abbr_card); + + /* + * If we have >100k distinct values, then even if we were sorting many + * billion rows we'd likely still break even, and the penalty of undoing + * that many rows of abbrevs would probably not be worth it. Stop even + * counting at that point. + */ + if (abbr_card > 100000.0) + { +#ifdef TRACE_SORT + if (trace_sort) + elog(LOG, + "numeric_abbrev: estimation ends at cardinality %f" + " after " INT64_FORMAT " values (%d rows)", + abbr_card, nss->input_count, memtupcount); +#endif + nss->estimating = false; + return false; + } + + /* + * Target minimum cardinality is 1 per ~10k of non-null inputs. (The + * break even point is somewhere between one per 100k rows, where + * abbreviation has a very slight penalty, and 1 per 10k where it wins by + * a measurable percentage.) We use the relatively pessimistic 10k + * threshold, and add a 0.5 row fudge factor, because it allows us to + * abort earlier on genuinely pathological data where we've had exactly + * one abbreviated value in the first 10k (non-null) rows. + */ + if (abbr_card < nss->input_count / 10000.0 + 0.5) + { +#ifdef TRACE_SORT + if (trace_sort) + elog(LOG, + "numeric_abbrev: aborting abbreviation at cardinality %f" + " below threshold %f after " INT64_FORMAT " values (%d rows)", + abbr_card, nss->input_count / 10000.0 + 0.5, + nss->input_count, memtupcount); +#endif + return true; + } + +#ifdef TRACE_SORT + if (trace_sort) + elog(LOG, + "numeric_abbrev: cardinality %f" + " after " INT64_FORMAT " values (%d rows)", + abbr_card, nss->input_count, memtupcount); +#endif + + return false; +} + +/* + * Non-fmgr interface to the comparison routine to allow sortsupport to elide + * the fmgr call. The saving here is small given how slow numeric comparisons + * are, but it is a required part of the sort support API when abbreviations + * are performed. + * + * Two palloc/pfree cycles could be saved here by using persistent buffers for + * aligning short-varlena inputs, but this has not so far been considered to + * be worth the effort. + */ +static int +numeric_fast_cmp(Datum x, Datum y, SortSupport ssup) +{ + Numeric nx = DatumGetNumeric(x); + Numeric ny = DatumGetNumeric(y); + int result; + + result = cmp_numerics(nx, ny); + + if ((Pointer) nx != DatumGetPointer(x)) + pfree(nx); + if ((Pointer) ny != DatumGetPointer(y)) + pfree(ny); + + return result; +} + +/* + * Compare abbreviations of values. (Abbreviations may be equal where the true + * values differ, but if the abbreviations differ, they must reflect the + * ordering of the true values.) + */ +static int +numeric_cmp_abbrev(Datum x, Datum y, SortSupport ssup) +{ + /* + * NOTE WELL: this is intentionally backwards, because the abbreviation is + * negated relative to the original value, to handle NaN/infinity cases. + */ + if (DatumGetNumericAbbrev(x) < DatumGetNumericAbbrev(y)) + return 1; + if (DatumGetNumericAbbrev(x) > DatumGetNumericAbbrev(y)) + return -1; + return 0; +} + +/* + * Abbreviate a NumericVar according to the available bit size. + * + * The 31-bit value is constructed as: + * + * 0 + 7bits digit weight + 24 bits digit value + * + * where the digit weight is in single decimal digits, not digit words, and + * stored in excess-44 representation[1]. The 24-bit digit value is the 7 most + * significant decimal digits of the value converted to binary. Values whose + * weights would fall outside the representable range are rounded off to zero + * (which is also used to represent actual zeros) or to 0x7FFFFFFF (which + * otherwise cannot occur). Abbreviation therefore fails to gain any advantage + * where values are outside the range 10^-44 to 10^83, which is not considered + * to be a serious limitation, or when values are of the same magnitude and + * equal in the first 7 decimal digits, which is considered to be an + * unavoidable limitation given the available bits. (Stealing three more bits + * to compare another digit would narrow the range of representable weights by + * a factor of 8, which starts to look like a real limiting factor.) + * + * (The value 44 for the excess is essentially arbitrary) + * + * The 63-bit value is constructed as: + * + * 0 + 7bits weight + 4 x 14-bit packed digit words + * + * The weight in this case is again stored in excess-44, but this time it is + * the original weight in digit words (i.e. powers of 10000). The first four + * digit words of the value (if present; trailing zeros are assumed as needed) + * are packed into 14 bits each to form the rest of the value. Again, + * out-of-range values are rounded off to 0 or 0x7FFFFFFFFFFFFFFF. The + * representable range in this case is 10^-176 to 10^332, which is considered + * to be good enough for all practical purposes, and comparison of 4 words + * means that at least 13 decimal digits are compared, which is considered to + * be a reasonable compromise between effectiveness and efficiency in computing + * the abbreviation. + * + * (The value 44 for the excess is even more arbitrary here, it was chosen just + * to match the value used in the 31-bit case) + * + * [1] - Excess-k representation means that the value is offset by adding 'k' + * and then treated as unsigned, so the smallest representable value is stored + * with all bits zero. This allows simple comparisons to work on the composite + * value. + */ + +#if NUMERIC_ABBREV_BITS == 64 + +static Datum +numeric_abbrev_convert_var(const NumericVar *var, NumericSortSupport *nss) +{ + int ndigits = var->ndigits; + int weight = var->weight; + int64 result; + + if (ndigits == 0 || weight < -44) + { + result = 0; + } + else if (weight > 83) + { + result = PG_INT64_MAX; + } + else + { + result = ((int64) (weight + 44) << 56); + + switch (ndigits) + { + default: + result |= ((int64) var->digits[3]); + /* FALLTHROUGH */ + case 3: + result |= ((int64) var->digits[2]) << 14; + /* FALLTHROUGH */ + case 2: + result |= ((int64) var->digits[1]) << 28; + /* FALLTHROUGH */ + case 1: + result |= ((int64) var->digits[0]) << 42; + break; + } + } + + /* the abbrev is negated relative to the original */ + if (var->sign == NUMERIC_POS) + result = -result; + + if (nss->estimating) + { + uint32 tmp = ((uint32) result + ^ (uint32) ((uint64) result >> 32)); + + addHyperLogLog(&nss->abbr_card, DatumGetUInt32(hash_uint32(tmp))); + } + + return NumericAbbrevGetDatum(result); +} + +#endif /* NUMERIC_ABBREV_BITS == 64 */ + +#if NUMERIC_ABBREV_BITS == 32 + +static Datum +numeric_abbrev_convert_var(const NumericVar *var, NumericSortSupport *nss) +{ + int ndigits = var->ndigits; + int weight = var->weight; + int32 result; + + if (ndigits == 0 || weight < -11) + { + result = 0; + } + else if (weight > 20) + { + result = PG_INT32_MAX; + } + else + { + NumericDigit nxt1 = (ndigits > 1) ? var->digits[1] : 0; + + weight = (weight + 11) * 4; + + result = var->digits[0]; + + /* + * "result" now has 1 to 4 nonzero decimal digits. We pack in more + * digits to make 7 in total (largest we can fit in 24 bits) + */ + + if (result > 999) + { + /* already have 4 digits, add 3 more */ + result = (result * 1000) + (nxt1 / 10); + weight += 3; + } + else if (result > 99) + { + /* already have 3 digits, add 4 more */ + result = (result * 10000) + nxt1; + weight += 2; + } + else if (result > 9) + { + NumericDigit nxt2 = (ndigits > 2) ? var->digits[2] : 0; + + /* already have 2 digits, add 5 more */ + result = (result * 100000) + (nxt1 * 10) + (nxt2 / 1000); + weight += 1; + } + else + { + NumericDigit nxt2 = (ndigits > 2) ? var->digits[2] : 0; + + /* already have 1 digit, add 6 more */ + result = (result * 1000000) + (nxt1 * 100) + (nxt2 / 100); + } + + result = result | (weight << 24); + } + + /* the abbrev is negated relative to the original */ + if (var->sign == NUMERIC_POS) + result = -result; + + if (nss->estimating) + { + uint32 tmp = (uint32) result; + + addHyperLogLog(&nss->abbr_card, DatumGetUInt32(hash_uint32(tmp))); + } + + return NumericAbbrevGetDatum(result); +} + +#endif /* NUMERIC_ABBREV_BITS == 32 */ + +/* + * Ordinary (non-sortsupport) comparisons follow. + */ + +Datum +numeric_cmp(PG_FUNCTION_ARGS) +{ + Numeric num1 = PG_GETARG_NUMERIC(0); + Numeric num2 = PG_GETARG_NUMERIC(1); + int result; + + result = cmp_numerics(num1, num2); + + PG_FREE_IF_COPY(num1, 0); + PG_FREE_IF_COPY(num2, 1); + + PG_RETURN_INT32(result); +} + + +Datum +numeric_eq(PG_FUNCTION_ARGS) +{ + Numeric num1 = PG_GETARG_NUMERIC(0); + Numeric num2 = PG_GETARG_NUMERIC(1); + bool result; + + result = cmp_numerics(num1, num2) == 0; + + PG_FREE_IF_COPY(num1, 0); + PG_FREE_IF_COPY(num2, 1); + + PG_RETURN_BOOL(result); +} + +Datum +numeric_ne(PG_FUNCTION_ARGS) +{ + Numeric num1 = PG_GETARG_NUMERIC(0); + Numeric num2 = PG_GETARG_NUMERIC(1); + bool result; + + result = cmp_numerics(num1, num2) != 0; + + PG_FREE_IF_COPY(num1, 0); + PG_FREE_IF_COPY(num2, 1); + + PG_RETURN_BOOL(result); +} + +Datum +numeric_gt(PG_FUNCTION_ARGS) +{ + Numeric num1 = PG_GETARG_NUMERIC(0); + Numeric num2 = PG_GETARG_NUMERIC(1); + bool result; + + result = cmp_numerics(num1, num2) > 0; + + PG_FREE_IF_COPY(num1, 0); + PG_FREE_IF_COPY(num2, 1); + + PG_RETURN_BOOL(result); +} + +Datum +numeric_ge(PG_FUNCTION_ARGS) +{ + Numeric num1 = PG_GETARG_NUMERIC(0); + Numeric num2 = PG_GETARG_NUMERIC(1); + bool result; + + result = cmp_numerics(num1, num2) >= 0; + + PG_FREE_IF_COPY(num1, 0); + PG_FREE_IF_COPY(num2, 1); + + PG_RETURN_BOOL(result); +} + +Datum +numeric_lt(PG_FUNCTION_ARGS) +{ + Numeric num1 = PG_GETARG_NUMERIC(0); + Numeric num2 = PG_GETARG_NUMERIC(1); + bool result; + + result = cmp_numerics(num1, num2) < 0; + + PG_FREE_IF_COPY(num1, 0); + PG_FREE_IF_COPY(num2, 1); + + PG_RETURN_BOOL(result); +} + +Datum +numeric_le(PG_FUNCTION_ARGS) +{ + Numeric num1 = PG_GETARG_NUMERIC(0); + Numeric num2 = PG_GETARG_NUMERIC(1); + bool result; + + result = cmp_numerics(num1, num2) <= 0; + + PG_FREE_IF_COPY(num1, 0); + PG_FREE_IF_COPY(num2, 1); + + PG_RETURN_BOOL(result); +} + +static int +cmp_numerics(Numeric num1, Numeric num2) +{ + int result; + + /* + * We consider all NANs to be equal and larger than any non-NAN (including + * Infinity). This is somewhat arbitrary; the important thing is to have + * a consistent sort order. + */ + if (NUMERIC_IS_SPECIAL(num1)) + { + if (NUMERIC_IS_NAN(num1)) + { + if (NUMERIC_IS_NAN(num2)) + result = 0; /* NAN = NAN */ + else + result = 1; /* NAN > non-NAN */ + } + else if (NUMERIC_IS_PINF(num1)) + { + if (NUMERIC_IS_NAN(num2)) + result = -1; /* PINF < NAN */ + else if (NUMERIC_IS_PINF(num2)) + result = 0; /* PINF = PINF */ + else + result = 1; /* PINF > anything else */ + } + else /* num1 must be NINF */ + { + if (NUMERIC_IS_NINF(num2)) + result = 0; /* NINF = NINF */ + else + result = -1; /* NINF < anything else */ + } + } + else if (NUMERIC_IS_SPECIAL(num2)) + { + if (NUMERIC_IS_NINF(num2)) + result = 1; /* normal > NINF */ + else + result = -1; /* normal < NAN or PINF */ + } + else + { + result = cmp_var_common(NUMERIC_DIGITS(num1), NUMERIC_NDIGITS(num1), + NUMERIC_WEIGHT(num1), NUMERIC_SIGN(num1), + NUMERIC_DIGITS(num2), NUMERIC_NDIGITS(num2), + NUMERIC_WEIGHT(num2), NUMERIC_SIGN(num2)); + } + + return result; +} + +/* + * in_range support function for numeric. + */ +Datum +in_range_numeric_numeric(PG_FUNCTION_ARGS) +{ + Numeric val = PG_GETARG_NUMERIC(0); + Numeric base = PG_GETARG_NUMERIC(1); + Numeric offset = PG_GETARG_NUMERIC(2); + bool sub = PG_GETARG_BOOL(3); + bool less = PG_GETARG_BOOL(4); + bool result; + + /* + * Reject negative (including -Inf) or NaN offset. Negative is per spec, + * and NaN is because appropriate semantics for that seem non-obvious. + */ + if (NUMERIC_IS_NAN(offset) || + NUMERIC_IS_NINF(offset) || + NUMERIC_SIGN(offset) == NUMERIC_NEG) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE), + errmsg("invalid preceding or following size in window function"))); + + /* + * Deal with cases where val and/or base is NaN, following the rule that + * NaN sorts after non-NaN (cf cmp_numerics). The offset cannot affect + * the conclusion. + */ + if (NUMERIC_IS_NAN(val)) + { + if (NUMERIC_IS_NAN(base)) + result = true; /* NAN = NAN */ + else + result = !less; /* NAN > non-NAN */ + } + else if (NUMERIC_IS_NAN(base)) + { + result = less; /* non-NAN < NAN */ + } + + /* + * Deal with infinite offset (necessarily +Inf, at this point). + */ + else if (NUMERIC_IS_SPECIAL(offset)) + { + Assert(NUMERIC_IS_PINF(offset)); + if (sub ? NUMERIC_IS_PINF(base) : NUMERIC_IS_NINF(base)) + { + /* + * base +/- offset would produce NaN, so return true for any val + * (see in_range_float8_float8() for reasoning). + */ + result = true; + } + else if (sub) + { + /* base - offset must be -inf */ + if (less) + result = NUMERIC_IS_NINF(val); /* only -inf is <= sum */ + else + result = true; /* any val is >= sum */ + } + else + { + /* base + offset must be +inf */ + if (less) + result = true; /* any val is <= sum */ + else + result = NUMERIC_IS_PINF(val); /* only +inf is >= sum */ + } + } + + /* + * Deal with cases where val and/or base is infinite. The offset, being + * now known finite, cannot affect the conclusion. + */ + else if (NUMERIC_IS_SPECIAL(val)) + { + if (NUMERIC_IS_PINF(val)) + { + if (NUMERIC_IS_PINF(base)) + result = true; /* PINF = PINF */ + else + result = !less; /* PINF > any other non-NAN */ + } + else /* val must be NINF */ + { + if (NUMERIC_IS_NINF(base)) + result = true; /* NINF = NINF */ + else + result = less; /* NINF < anything else */ + } + } + else if (NUMERIC_IS_SPECIAL(base)) + { + if (NUMERIC_IS_NINF(base)) + result = !less; /* normal > NINF */ + else + result = less; /* normal < PINF */ + } + else + { + /* + * Otherwise go ahead and compute base +/- offset. While it's + * possible for this to overflow the numeric format, it's unlikely + * enough that we don't take measures to prevent it. + */ + NumericVar valv; + NumericVar basev; + NumericVar offsetv; + NumericVar sum; + + init_var_from_num(val, &valv); + init_var_from_num(base, &basev); + init_var_from_num(offset, &offsetv); + init_var(&sum); + + if (sub) + sub_var(&basev, &offsetv, &sum); + else + add_var(&basev, &offsetv, &sum); + + if (less) + result = (cmp_var(&valv, &sum) <= 0); + else + result = (cmp_var(&valv, &sum) >= 0); + + free_var(&sum); + } + + PG_FREE_IF_COPY(val, 0); + PG_FREE_IF_COPY(base, 1); + PG_FREE_IF_COPY(offset, 2); + + PG_RETURN_BOOL(result); +} + +Datum +hash_numeric(PG_FUNCTION_ARGS) +{ + Numeric key = PG_GETARG_NUMERIC(0); + Datum digit_hash; + Datum result; + int weight; + int start_offset; + int end_offset; + int i; + int hash_len; + NumericDigit *digits; + + /* If it's NaN or infinity, don't try to hash the rest of the fields */ + if (NUMERIC_IS_SPECIAL(key)) + PG_RETURN_UINT32(0); + + weight = NUMERIC_WEIGHT(key); + start_offset = 0; + end_offset = 0; + + /* + * Omit any leading or trailing zeros from the input to the hash. The + * numeric implementation *should* guarantee that leading and trailing + * zeros are suppressed, but we're paranoid. Note that we measure the + * starting and ending offsets in units of NumericDigits, not bytes. + */ + digits = NUMERIC_DIGITS(key); + for (i = 0; i < NUMERIC_NDIGITS(key); i++) + { + if (digits[i] != (NumericDigit) 0) + break; + + start_offset++; + + /* + * The weight is effectively the # of digits before the decimal point, + * so decrement it for each leading zero we skip. + */ + weight--; + } + + /* + * If there are no non-zero digits, then the value of the number is zero, + * regardless of any other fields. + */ + if (NUMERIC_NDIGITS(key) == start_offset) + PG_RETURN_UINT32(-1); + + for (i = NUMERIC_NDIGITS(key) - 1; i >= 0; i--) + { + if (digits[i] != (NumericDigit) 0) + break; + + end_offset++; + } + + /* If we get here, there should be at least one non-zero digit */ + Assert(start_offset + end_offset < NUMERIC_NDIGITS(key)); + + /* + * Note that we don't hash on the Numeric's scale, since two numerics can + * compare equal but have different scales. We also don't hash on the + * sign, although we could: since a sign difference implies inequality, + * this shouldn't affect correctness. + */ + hash_len = NUMERIC_NDIGITS(key) - start_offset - end_offset; + digit_hash = hash_any((unsigned char *) (NUMERIC_DIGITS(key) + start_offset), + hash_len * sizeof(NumericDigit)); + + /* Mix in the weight, via XOR */ + result = digit_hash ^ weight; + + PG_RETURN_DATUM(result); +} + +/* + * Returns 64-bit value by hashing a value to a 64-bit value, with a seed. + * Otherwise, similar to hash_numeric. + */ +Datum +hash_numeric_extended(PG_FUNCTION_ARGS) +{ + Numeric key = PG_GETARG_NUMERIC(0); + uint64 seed = PG_GETARG_INT64(1); + Datum digit_hash; + Datum result; + int weight; + int start_offset; + int end_offset; + int i; + int hash_len; + NumericDigit *digits; + + /* If it's NaN or infinity, don't try to hash the rest of the fields */ + if (NUMERIC_IS_SPECIAL(key)) + PG_RETURN_UINT64(seed); + + weight = NUMERIC_WEIGHT(key); + start_offset = 0; + end_offset = 0; + + digits = NUMERIC_DIGITS(key); + for (i = 0; i < NUMERIC_NDIGITS(key); i++) + { + if (digits[i] != (NumericDigit) 0) + break; + + start_offset++; + + weight--; + } + + if (NUMERIC_NDIGITS(key) == start_offset) + PG_RETURN_UINT64(seed - 1); + + for (i = NUMERIC_NDIGITS(key) - 1; i >= 0; i--) + { + if (digits[i] != (NumericDigit) 0) + break; + + end_offset++; + } + + Assert(start_offset + end_offset < NUMERIC_NDIGITS(key)); + + hash_len = NUMERIC_NDIGITS(key) - start_offset - end_offset; + digit_hash = hash_any_extended((unsigned char *) (NUMERIC_DIGITS(key) + + start_offset), + hash_len * sizeof(NumericDigit), + seed); + + result = UInt64GetDatum(DatumGetUInt64(digit_hash) ^ weight); + + PG_RETURN_DATUM(result); +} + + +/* ---------------------------------------------------------------------- + * + * Basic arithmetic functions + * + * ---------------------------------------------------------------------- + */ + + +/* + * numeric_add() - + * + * Add two numerics + */ +Datum +numeric_add(PG_FUNCTION_ARGS) +{ + Numeric num1 = PG_GETARG_NUMERIC(0); + Numeric num2 = PG_GETARG_NUMERIC(1); + Numeric res; + + res = numeric_add_opt_error(num1, num2, NULL); + + PG_RETURN_NUMERIC(res); +} + +/* + * numeric_add_opt_error() - + * + * Internal version of numeric_add(). If "*have_error" flag is provided, + * on error it's set to true, NULL returned. This is helpful when caller + * need to handle errors by itself. + */ +Numeric +numeric_add_opt_error(Numeric num1, Numeric num2, bool *have_error) +{ + NumericVar arg1; + NumericVar arg2; + NumericVar result; + Numeric res; + + /* + * Handle NaN and infinities + */ + if (NUMERIC_IS_SPECIAL(num1) || NUMERIC_IS_SPECIAL(num2)) + { + if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2)) + return make_result(&const_nan); + if (NUMERIC_IS_PINF(num1)) + { + if (NUMERIC_IS_NINF(num2)) + return make_result(&const_nan); /* Inf + -Inf */ + else + return make_result(&const_pinf); + } + if (NUMERIC_IS_NINF(num1)) + { + if (NUMERIC_IS_PINF(num2)) + return make_result(&const_nan); /* -Inf + Inf */ + else + return make_result(&const_ninf); + } + /* by here, num1 must be finite, so num2 is not */ + if (NUMERIC_IS_PINF(num2)) + return make_result(&const_pinf); + Assert(NUMERIC_IS_NINF(num2)); + return make_result(&const_ninf); + } + + /* + * Unpack the values, let add_var() compute the result and return it. + */ + init_var_from_num(num1, &arg1); + init_var_from_num(num2, &arg2); + + init_var(&result); + add_var(&arg1, &arg2, &result); + + res = make_result_opt_error(&result, have_error); + + free_var(&result); + + return res; +} + + +/* + * numeric_sub() - + * + * Subtract one numeric from another + */ +Datum +numeric_sub(PG_FUNCTION_ARGS) +{ + Numeric num1 = PG_GETARG_NUMERIC(0); + Numeric num2 = PG_GETARG_NUMERIC(1); + Numeric res; + + res = numeric_sub_opt_error(num1, num2, NULL); + + PG_RETURN_NUMERIC(res); +} + + +/* + * numeric_sub_opt_error() - + * + * Internal version of numeric_sub(). If "*have_error" flag is provided, + * on error it's set to true, NULL returned. This is helpful when caller + * need to handle errors by itself. + */ +Numeric +numeric_sub_opt_error(Numeric num1, Numeric num2, bool *have_error) +{ + NumericVar arg1; + NumericVar arg2; + NumericVar result; + Numeric res; + + /* + * Handle NaN and infinities + */ + if (NUMERIC_IS_SPECIAL(num1) || NUMERIC_IS_SPECIAL(num2)) + { + if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2)) + return make_result(&const_nan); + if (NUMERIC_IS_PINF(num1)) + { + if (NUMERIC_IS_PINF(num2)) + return make_result(&const_nan); /* Inf - Inf */ + else + return make_result(&const_pinf); + } + if (NUMERIC_IS_NINF(num1)) + { + if (NUMERIC_IS_NINF(num2)) + return make_result(&const_nan); /* -Inf - -Inf */ + else + return make_result(&const_ninf); + } + /* by here, num1 must be finite, so num2 is not */ + if (NUMERIC_IS_PINF(num2)) + return make_result(&const_ninf); + Assert(NUMERIC_IS_NINF(num2)); + return make_result(&const_pinf); + } + + /* + * Unpack the values, let sub_var() compute the result and return it. + */ + init_var_from_num(num1, &arg1); + init_var_from_num(num2, &arg2); + + init_var(&result); + sub_var(&arg1, &arg2, &result); + + res = make_result_opt_error(&result, have_error); + + free_var(&result); + + return res; +} + + +/* + * numeric_mul() - + * + * Calculate the product of two numerics + */ +Datum +numeric_mul(PG_FUNCTION_ARGS) +{ + Numeric num1 = PG_GETARG_NUMERIC(0); + Numeric num2 = PG_GETARG_NUMERIC(1); + Numeric res; + + res = numeric_mul_opt_error(num1, num2, NULL); + + PG_RETURN_NUMERIC(res); +} + + +/* + * numeric_mul_opt_error() - + * + * Internal version of numeric_mul(). If "*have_error" flag is provided, + * on error it's set to true, NULL returned. This is helpful when caller + * need to handle errors by itself. + */ +Numeric +numeric_mul_opt_error(Numeric num1, Numeric num2, bool *have_error) +{ + NumericVar arg1; + NumericVar arg2; + NumericVar result; + Numeric res; + + /* + * Handle NaN and infinities + */ + if (NUMERIC_IS_SPECIAL(num1) || NUMERIC_IS_SPECIAL(num2)) + { + if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2)) + return make_result(&const_nan); + if (NUMERIC_IS_PINF(num1)) + { + switch (numeric_sign_internal(num2)) + { + case 0: + return make_result(&const_nan); /* Inf * 0 */ + case 1: + return make_result(&const_pinf); + case -1: + return make_result(&const_ninf); + } + Assert(false); + } + if (NUMERIC_IS_NINF(num1)) + { + switch (numeric_sign_internal(num2)) + { + case 0: + return make_result(&const_nan); /* -Inf * 0 */ + case 1: + return make_result(&const_ninf); + case -1: + return make_result(&const_pinf); + } + Assert(false); + } + /* by here, num1 must be finite, so num2 is not */ + if (NUMERIC_IS_PINF(num2)) + { + switch (numeric_sign_internal(num1)) + { + case 0: + return make_result(&const_nan); /* 0 * Inf */ + case 1: + return make_result(&const_pinf); + case -1: + return make_result(&const_ninf); + } + Assert(false); + } + Assert(NUMERIC_IS_NINF(num2)); + switch (numeric_sign_internal(num1)) + { + case 0: + return make_result(&const_nan); /* 0 * -Inf */ + case 1: + return make_result(&const_ninf); + case -1: + return make_result(&const_pinf); + } + Assert(false); + } + + /* + * Unpack the values, let mul_var() compute the result and return it. + * Unlike add_var() and sub_var(), mul_var() will round its result. In the + * case of numeric_mul(), which is invoked for the * operator on numerics, + * we request exact representation for the product (rscale = sum(dscale of + * arg1, dscale of arg2)). If the exact result has more digits after the + * decimal point than can be stored in a numeric, we round it. Rounding + * after computing the exact result ensures that the final result is + * correctly rounded (rounding in mul_var() using a truncated product + * would not guarantee this). + */ + init_var_from_num(num1, &arg1); + init_var_from_num(num2, &arg2); + + init_var(&result); + mul_var(&arg1, &arg2, &result, arg1.dscale + arg2.dscale); + + if (result.dscale > NUMERIC_DSCALE_MAX) + round_var(&result, NUMERIC_DSCALE_MAX); + + res = make_result_opt_error(&result, have_error); + + free_var(&result); + + return res; +} + + +/* + * numeric_div() - + * + * Divide one numeric into another + */ +Datum +numeric_div(PG_FUNCTION_ARGS) +{ + Numeric num1 = PG_GETARG_NUMERIC(0); + Numeric num2 = PG_GETARG_NUMERIC(1); + Numeric res; + + res = numeric_div_opt_error(num1, num2, NULL); + + PG_RETURN_NUMERIC(res); +} + + +/* + * numeric_div_opt_error() - + * + * Internal version of numeric_div(). If "*have_error" flag is provided, + * on error it's set to true, NULL returned. This is helpful when caller + * need to handle errors by itself. + */ +Numeric +numeric_div_opt_error(Numeric num1, Numeric num2, bool *have_error) +{ + NumericVar arg1; + NumericVar arg2; + NumericVar result; + Numeric res; + int rscale; + + if (have_error) + *have_error = false; + + /* + * Handle NaN and infinities + */ + if (NUMERIC_IS_SPECIAL(num1) || NUMERIC_IS_SPECIAL(num2)) + { + if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2)) + return make_result(&const_nan); + if (NUMERIC_IS_PINF(num1)) + { + if (NUMERIC_IS_SPECIAL(num2)) + return make_result(&const_nan); /* Inf / [-]Inf */ + switch (numeric_sign_internal(num2)) + { + case 0: + if (have_error) + { + *have_error = true; + return NULL; + } + ereport(ERROR, + (errcode(ERRCODE_DIVISION_BY_ZERO), + errmsg("division by zero"))); + break; + case 1: + return make_result(&const_pinf); + case -1: + return make_result(&const_ninf); + } + Assert(false); + } + if (NUMERIC_IS_NINF(num1)) + { + if (NUMERIC_IS_SPECIAL(num2)) + return make_result(&const_nan); /* -Inf / [-]Inf */ + switch (numeric_sign_internal(num2)) + { + case 0: + if (have_error) + { + *have_error = true; + return NULL; + } + ereport(ERROR, + (errcode(ERRCODE_DIVISION_BY_ZERO), + errmsg("division by zero"))); + break; + case 1: + return make_result(&const_ninf); + case -1: + return make_result(&const_pinf); + } + Assert(false); + } + /* by here, num1 must be finite, so num2 is not */ + + /* + * POSIX would have us return zero or minus zero if num1 is zero, and + * otherwise throw an underflow error. But the numeric type doesn't + * really do underflow, so let's just return zero. + */ + return make_result(&const_zero); + } + + /* + * Unpack the arguments + */ + init_var_from_num(num1, &arg1); + init_var_from_num(num2, &arg2); + + init_var(&result); + + /* + * Select scale for division result + */ + rscale = select_div_scale(&arg1, &arg2); + + /* + * If "have_error" is provided, check for division by zero here + */ + if (have_error && (arg2.ndigits == 0 || arg2.digits[0] == 0)) + { + *have_error = true; + return NULL; + } + + /* + * Do the divide and return the result + */ + div_var(&arg1, &arg2, &result, rscale, true); + + res = make_result_opt_error(&result, have_error); + + free_var(&result); + + return res; +} + + +/* + * numeric_div_trunc() - + * + * Divide one numeric into another, truncating the result to an integer + */ +Datum +numeric_div_trunc(PG_FUNCTION_ARGS) +{ + Numeric num1 = PG_GETARG_NUMERIC(0); + Numeric num2 = PG_GETARG_NUMERIC(1); + NumericVar arg1; + NumericVar arg2; + NumericVar result; + Numeric res; + + /* + * Handle NaN and infinities + */ + if (NUMERIC_IS_SPECIAL(num1) || NUMERIC_IS_SPECIAL(num2)) + { + if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2)) + PG_RETURN_NUMERIC(make_result(&const_nan)); + if (NUMERIC_IS_PINF(num1)) + { + if (NUMERIC_IS_SPECIAL(num2)) + PG_RETURN_NUMERIC(make_result(&const_nan)); /* Inf / [-]Inf */ + switch (numeric_sign_internal(num2)) + { + case 0: + ereport(ERROR, + (errcode(ERRCODE_DIVISION_BY_ZERO), + errmsg("division by zero"))); + break; + case 1: + PG_RETURN_NUMERIC(make_result(&const_pinf)); + case -1: + PG_RETURN_NUMERIC(make_result(&const_ninf)); + } + Assert(false); + } + if (NUMERIC_IS_NINF(num1)) + { + if (NUMERIC_IS_SPECIAL(num2)) + PG_RETURN_NUMERIC(make_result(&const_nan)); /* -Inf / [-]Inf */ + switch (numeric_sign_internal(num2)) + { + case 0: + ereport(ERROR, + (errcode(ERRCODE_DIVISION_BY_ZERO), + errmsg("division by zero"))); + break; + case 1: + PG_RETURN_NUMERIC(make_result(&const_ninf)); + case -1: + PG_RETURN_NUMERIC(make_result(&const_pinf)); + } + Assert(false); + } + /* by here, num1 must be finite, so num2 is not */ + + /* + * POSIX would have us return zero or minus zero if num1 is zero, and + * otherwise throw an underflow error. But the numeric type doesn't + * really do underflow, so let's just return zero. + */ + PG_RETURN_NUMERIC(make_result(&const_zero)); + } + + /* + * Unpack the arguments + */ + init_var_from_num(num1, &arg1); + init_var_from_num(num2, &arg2); + + init_var(&result); + + /* + * Do the divide and return the result + */ + div_var(&arg1, &arg2, &result, 0, false); + + res = make_result(&result); + + free_var(&result); + + PG_RETURN_NUMERIC(res); +} + + +/* + * numeric_mod() - + * + * Calculate the modulo of two numerics + */ +Datum +numeric_mod(PG_FUNCTION_ARGS) +{ + Numeric num1 = PG_GETARG_NUMERIC(0); + Numeric num2 = PG_GETARG_NUMERIC(1); + Numeric res; + + res = numeric_mod_opt_error(num1, num2, NULL); + + PG_RETURN_NUMERIC(res); +} + + +/* + * numeric_mod_opt_error() - + * + * Internal version of numeric_mod(). If "*have_error" flag is provided, + * on error it's set to true, NULL returned. This is helpful when caller + * need to handle errors by itself. + */ +Numeric +numeric_mod_opt_error(Numeric num1, Numeric num2, bool *have_error) +{ + Numeric res; + NumericVar arg1; + NumericVar arg2; + NumericVar result; + + if (have_error) + *have_error = false; + + /* + * Handle NaN and infinities. We follow POSIX fmod() on this, except that + * POSIX treats x-is-infinite and y-is-zero identically, raising EDOM and + * returning NaN. We choose to throw error only for y-is-zero. + */ + if (NUMERIC_IS_SPECIAL(num1) || NUMERIC_IS_SPECIAL(num2)) + { + if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2)) + return make_result(&const_nan); + if (NUMERIC_IS_INF(num1)) + { + if (numeric_sign_internal(num2) == 0) + { + if (have_error) + { + *have_error = true; + return NULL; + } + ereport(ERROR, + (errcode(ERRCODE_DIVISION_BY_ZERO), + errmsg("division by zero"))); + } + /* Inf % any nonzero = NaN */ + return make_result(&const_nan); + } + /* num2 must be [-]Inf; result is num1 regardless of sign of num2 */ + return duplicate_numeric(num1); + } + + init_var_from_num(num1, &arg1); + init_var_from_num(num2, &arg2); + + init_var(&result); + + /* + * If "have_error" is provided, check for division by zero here + */ + if (have_error && (arg2.ndigits == 0 || arg2.digits[0] == 0)) + { + *have_error = true; + return NULL; + } + + mod_var(&arg1, &arg2, &result); + + res = make_result_opt_error(&result, NULL); + + free_var(&result); + + return res; +} + + +/* + * numeric_inc() - + * + * Increment a number by one + */ +Datum +numeric_inc(PG_FUNCTION_ARGS) +{ + Numeric num = PG_GETARG_NUMERIC(0); + NumericVar arg; + Numeric res; + + /* + * Handle NaN and infinities + */ + if (NUMERIC_IS_SPECIAL(num)) + PG_RETURN_NUMERIC(duplicate_numeric(num)); + + /* + * Compute the result and return it + */ + init_var_from_num(num, &arg); + + add_var(&arg, &const_one, &arg); + + res = make_result(&arg); + + free_var(&arg); + + PG_RETURN_NUMERIC(res); +} + + +/* + * numeric_smaller() - + * + * Return the smaller of two numbers + */ +Datum +numeric_smaller(PG_FUNCTION_ARGS) +{ + Numeric num1 = PG_GETARG_NUMERIC(0); + Numeric num2 = PG_GETARG_NUMERIC(1); + + /* + * Use cmp_numerics so that this will agree with the comparison operators, + * particularly as regards comparisons involving NaN. + */ + if (cmp_numerics(num1, num2) < 0) + PG_RETURN_NUMERIC(num1); + else + PG_RETURN_NUMERIC(num2); +} + + +/* + * numeric_larger() - + * + * Return the larger of two numbers + */ +Datum +numeric_larger(PG_FUNCTION_ARGS) +{ + Numeric num1 = PG_GETARG_NUMERIC(0); + Numeric num2 = PG_GETARG_NUMERIC(1); + + /* + * Use cmp_numerics so that this will agree with the comparison operators, + * particularly as regards comparisons involving NaN. + */ + if (cmp_numerics(num1, num2) > 0) + PG_RETURN_NUMERIC(num1); + else + PG_RETURN_NUMERIC(num2); +} + + +/* ---------------------------------------------------------------------- + * + * Advanced math functions + * + * ---------------------------------------------------------------------- + */ + +/* + * numeric_gcd() - + * + * Calculate the greatest common divisor of two numerics + */ +Datum +numeric_gcd(PG_FUNCTION_ARGS) +{ + Numeric num1 = PG_GETARG_NUMERIC(0); + Numeric num2 = PG_GETARG_NUMERIC(1); + NumericVar arg1; + NumericVar arg2; + NumericVar result; + Numeric res; + + /* + * Handle NaN and infinities: we consider the result to be NaN in all such + * cases. + */ + if (NUMERIC_IS_SPECIAL(num1) || NUMERIC_IS_SPECIAL(num2)) + PG_RETURN_NUMERIC(make_result(&const_nan)); + + /* + * Unpack the arguments + */ + init_var_from_num(num1, &arg1); + init_var_from_num(num2, &arg2); + + init_var(&result); + + /* + * Find the GCD and return the result + */ + gcd_var(&arg1, &arg2, &result); + + res = make_result(&result); + + free_var(&result); + + PG_RETURN_NUMERIC(res); +} + + +/* + * numeric_lcm() - + * + * Calculate the least common multiple of two numerics + */ +Datum +numeric_lcm(PG_FUNCTION_ARGS) +{ + Numeric num1 = PG_GETARG_NUMERIC(0); + Numeric num2 = PG_GETARG_NUMERIC(1); + NumericVar arg1; + NumericVar arg2; + NumericVar result; + Numeric res; + + /* + * Handle NaN and infinities: we consider the result to be NaN in all such + * cases. + */ + if (NUMERIC_IS_SPECIAL(num1) || NUMERIC_IS_SPECIAL(num2)) + PG_RETURN_NUMERIC(make_result(&const_nan)); + + /* + * Unpack the arguments + */ + init_var_from_num(num1, &arg1); + init_var_from_num(num2, &arg2); + + init_var(&result); + + /* + * Compute the result using lcm(x, y) = abs(x / gcd(x, y) * y), returning + * zero if either input is zero. + * + * Note that the division is guaranteed to be exact, returning an integer + * result, so the LCM is an integral multiple of both x and y. A display + * scale of Min(x.dscale, y.dscale) would be sufficient to represent it, + * but as with other numeric functions, we choose to return a result whose + * display scale is no smaller than either input. + */ + if (arg1.ndigits == 0 || arg2.ndigits == 0) + set_var_from_var(&const_zero, &result); + else + { + gcd_var(&arg1, &arg2, &result); + div_var(&arg1, &result, &result, 0, false); + mul_var(&arg2, &result, &result, arg2.dscale); + result.sign = NUMERIC_POS; + } + + result.dscale = Max(arg1.dscale, arg2.dscale); + + res = make_result(&result); + + free_var(&result); + + PG_RETURN_NUMERIC(res); +} + + +/* + * numeric_fac() + * + * Compute factorial + */ +Datum +numeric_fac(PG_FUNCTION_ARGS) +{ + int64 num = PG_GETARG_INT64(0); + Numeric res; + NumericVar fact; + NumericVar result; + + if (num < 0) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("factorial of a negative number is undefined"))); + if (num <= 1) + { + res = make_result(&const_one); + PG_RETURN_NUMERIC(res); + } + /* Fail immediately if the result would overflow */ + if (num > 32177) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("value overflows numeric format"))); + + init_var(&fact); + init_var(&result); + + int64_to_numericvar(num, &result); + + for (num = num - 1; num > 1; num--) + { + /* this loop can take awhile, so allow it to be interrupted */ + CHECK_FOR_INTERRUPTS(); + + int64_to_numericvar(num, &fact); + + mul_var(&result, &fact, &result, 0); + } + + res = make_result(&result); + + free_var(&fact); + free_var(&result); + + PG_RETURN_NUMERIC(res); +} + + +/* + * numeric_sqrt() - + * + * Compute the square root of a numeric. + */ +Datum +numeric_sqrt(PG_FUNCTION_ARGS) +{ + Numeric num = PG_GETARG_NUMERIC(0); + Numeric res; + NumericVar arg; + NumericVar result; + int sweight; + int rscale; + + /* + * Handle NaN and infinities + */ + if (NUMERIC_IS_SPECIAL(num)) + { + /* error should match that in sqrt_var() */ + if (NUMERIC_IS_NINF(num)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_ARGUMENT_FOR_POWER_FUNCTION), + errmsg("cannot take square root of a negative number"))); + /* For NAN or PINF, just duplicate the input */ + PG_RETURN_NUMERIC(duplicate_numeric(num)); + } + + /* + * Unpack the argument and determine the result scale. We choose a scale + * to give at least NUMERIC_MIN_SIG_DIGITS significant digits; but in any + * case not less than the input's dscale. + */ + init_var_from_num(num, &arg); + + init_var(&result); + + /* + * Assume the input was normalized, so arg.weight is accurate. The result + * then has at least sweight = floor(arg.weight * DEC_DIGITS / 2 + 1) + * digits before the decimal point. When DEC_DIGITS is even, we can save + * a few cycles, since the division is exact and there is no need to round + * towards negative infinity. + */ +#if DEC_DIGITS == ((DEC_DIGITS / 2) * 2) + sweight = arg.weight * DEC_DIGITS / 2 + 1; +#else + if (arg.weight >= 0) + sweight = arg.weight * DEC_DIGITS / 2 + 1; + else + sweight = 1 - (1 - arg.weight * DEC_DIGITS) / 2; +#endif + + rscale = NUMERIC_MIN_SIG_DIGITS - sweight; + rscale = Max(rscale, arg.dscale); + rscale = Max(rscale, NUMERIC_MIN_DISPLAY_SCALE); + rscale = Min(rscale, NUMERIC_MAX_DISPLAY_SCALE); + + /* + * Let sqrt_var() do the calculation and return the result. + */ + sqrt_var(&arg, &result, rscale); + + res = make_result(&result); + + free_var(&result); + + PG_RETURN_NUMERIC(res); +} + + +/* + * numeric_exp() - + * + * Raise e to the power of x + */ +Datum +numeric_exp(PG_FUNCTION_ARGS) +{ + Numeric num = PG_GETARG_NUMERIC(0); + Numeric res; + NumericVar arg; + NumericVar result; + int rscale; + double val; + + /* + * Handle NaN and infinities + */ + if (NUMERIC_IS_SPECIAL(num)) + { + /* Per POSIX, exp(-Inf) is zero */ + if (NUMERIC_IS_NINF(num)) + PG_RETURN_NUMERIC(make_result(&const_zero)); + /* For NAN or PINF, just duplicate the input */ + PG_RETURN_NUMERIC(duplicate_numeric(num)); + } + + /* + * Unpack the argument and determine the result scale. We choose a scale + * to give at least NUMERIC_MIN_SIG_DIGITS significant digits; but in any + * case not less than the input's dscale. + */ + init_var_from_num(num, &arg); + + init_var(&result); + + /* convert input to float8, ignoring overflow */ + val = numericvar_to_double_no_overflow(&arg); + + /* + * log10(result) = num * log10(e), so this is approximately the decimal + * weight of the result: + */ + val *= 0.434294481903252; + + /* limit to something that won't cause integer overflow */ + val = Max(val, -NUMERIC_MAX_RESULT_SCALE); + val = Min(val, NUMERIC_MAX_RESULT_SCALE); + + rscale = NUMERIC_MIN_SIG_DIGITS - (int) val; + rscale = Max(rscale, arg.dscale); + rscale = Max(rscale, NUMERIC_MIN_DISPLAY_SCALE); + rscale = Min(rscale, NUMERIC_MAX_DISPLAY_SCALE); + + /* + * Let exp_var() do the calculation and return the result. + */ + exp_var(&arg, &result, rscale); + + res = make_result(&result); + + free_var(&result); + + PG_RETURN_NUMERIC(res); +} + + +/* + * numeric_ln() - + * + * Compute the natural logarithm of x + */ +Datum +numeric_ln(PG_FUNCTION_ARGS) +{ + Numeric num = PG_GETARG_NUMERIC(0); + Numeric res; + NumericVar arg; + NumericVar result; + int ln_dweight; + int rscale; + + /* + * Handle NaN and infinities + */ + if (NUMERIC_IS_SPECIAL(num)) + { + if (NUMERIC_IS_NINF(num)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_ARGUMENT_FOR_LOG), + errmsg("cannot take logarithm of a negative number"))); + /* For NAN or PINF, just duplicate the input */ + PG_RETURN_NUMERIC(duplicate_numeric(num)); + } + + init_var_from_num(num, &arg); + init_var(&result); + + /* Estimated dweight of logarithm */ + ln_dweight = estimate_ln_dweight(&arg); + + rscale = NUMERIC_MIN_SIG_DIGITS - ln_dweight; + rscale = Max(rscale, arg.dscale); + rscale = Max(rscale, NUMERIC_MIN_DISPLAY_SCALE); + rscale = Min(rscale, NUMERIC_MAX_DISPLAY_SCALE); + + ln_var(&arg, &result, rscale); + + res = make_result(&result); + + free_var(&result); + + PG_RETURN_NUMERIC(res); +} + + +/* + * numeric_log() - + * + * Compute the logarithm of x in a given base + */ +Datum +numeric_log(PG_FUNCTION_ARGS) +{ + Numeric num1 = PG_GETARG_NUMERIC(0); + Numeric num2 = PG_GETARG_NUMERIC(1); + Numeric res; + NumericVar arg1; + NumericVar arg2; + NumericVar result; + + /* + * Handle NaN and infinities + */ + if (NUMERIC_IS_SPECIAL(num1) || NUMERIC_IS_SPECIAL(num2)) + { + int sign1, + sign2; + + if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2)) + PG_RETURN_NUMERIC(make_result(&const_nan)); + /* fail on negative inputs including -Inf, as log_var would */ + sign1 = numeric_sign_internal(num1); + sign2 = numeric_sign_internal(num2); + if (sign1 < 0 || sign2 < 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_ARGUMENT_FOR_LOG), + errmsg("cannot take logarithm of a negative number"))); + /* fail on zero inputs, as log_var would */ + if (sign1 == 0 || sign2 == 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_ARGUMENT_FOR_LOG), + errmsg("cannot take logarithm of zero"))); + if (NUMERIC_IS_PINF(num1)) + { + /* log(Inf, Inf) reduces to Inf/Inf, so it's NaN */ + if (NUMERIC_IS_PINF(num2)) + PG_RETURN_NUMERIC(make_result(&const_nan)); + /* log(Inf, finite-positive) is zero (we don't throw underflow) */ + PG_RETURN_NUMERIC(make_result(&const_zero)); + } + Assert(NUMERIC_IS_PINF(num2)); + /* log(finite-positive, Inf) is Inf */ + PG_RETURN_NUMERIC(make_result(&const_pinf)); + } + + /* + * Initialize things + */ + init_var_from_num(num1, &arg1); + init_var_from_num(num2, &arg2); + init_var(&result); + + /* + * Call log_var() to compute and return the result; note it handles scale + * selection itself. + */ + log_var(&arg1, &arg2, &result); + + res = make_result(&result); + + free_var(&result); + + PG_RETURN_NUMERIC(res); +} + + +/* + * numeric_power() - + * + * Raise x to the power of y + */ +Datum +numeric_power(PG_FUNCTION_ARGS) +{ + Numeric num1 = PG_GETARG_NUMERIC(0); + Numeric num2 = PG_GETARG_NUMERIC(1); + Numeric res; + NumericVar arg1; + NumericVar arg2; + NumericVar result; + int sign1, + sign2; + + /* + * Handle NaN and infinities + */ + if (NUMERIC_IS_SPECIAL(num1) || NUMERIC_IS_SPECIAL(num2)) + { + /* + * We follow the POSIX spec for pow(3), which says that NaN ^ 0 = 1, + * and 1 ^ NaN = 1, while all other cases with NaN inputs yield NaN + * (with no error). + */ + if (NUMERIC_IS_NAN(num1)) + { + if (!NUMERIC_IS_SPECIAL(num2)) + { + init_var_from_num(num2, &arg2); + if (cmp_var(&arg2, &const_zero) == 0) + PG_RETURN_NUMERIC(make_result(&const_one)); + } + PG_RETURN_NUMERIC(make_result(&const_nan)); + } + if (NUMERIC_IS_NAN(num2)) + { + if (!NUMERIC_IS_SPECIAL(num1)) + { + init_var_from_num(num1, &arg1); + if (cmp_var(&arg1, &const_one) == 0) + PG_RETURN_NUMERIC(make_result(&const_one)); + } + PG_RETURN_NUMERIC(make_result(&const_nan)); + } + /* At least one input is infinite, but error rules still apply */ + sign1 = numeric_sign_internal(num1); + sign2 = numeric_sign_internal(num2); + if (sign1 == 0 && sign2 < 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_ARGUMENT_FOR_POWER_FUNCTION), + errmsg("zero raised to a negative power is undefined"))); + if (sign1 < 0 && !numeric_is_integral(num2)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_ARGUMENT_FOR_POWER_FUNCTION), + errmsg("a negative number raised to a non-integer power yields a complex result"))); + + /* + * POSIX gives this series of rules for pow(3) with infinite inputs: + * + * For any value of y, if x is +1, 1.0 shall be returned. + */ + if (!NUMERIC_IS_SPECIAL(num1)) + { + init_var_from_num(num1, &arg1); + if (cmp_var(&arg1, &const_one) == 0) + PG_RETURN_NUMERIC(make_result(&const_one)); + } + + /* + * For any value of x, if y is [-]0, 1.0 shall be returned. + */ + if (sign2 == 0) + PG_RETURN_NUMERIC(make_result(&const_one)); + + /* + * For any odd integer value of y > 0, if x is [-]0, [-]0 shall be + * returned. For y > 0 and not an odd integer, if x is [-]0, +0 shall + * be returned. (Since we don't deal in minus zero, we need not + * distinguish these two cases.) + */ + if (sign1 == 0 && sign2 > 0) + PG_RETURN_NUMERIC(make_result(&const_zero)); + + /* + * If x is -1, and y is [-]Inf, 1.0 shall be returned. + * + * For |x| < 1, if y is -Inf, +Inf shall be returned. + * + * For |x| > 1, if y is -Inf, +0 shall be returned. + * + * For |x| < 1, if y is +Inf, +0 shall be returned. + * + * For |x| > 1, if y is +Inf, +Inf shall be returned. + */ + if (NUMERIC_IS_INF(num2)) + { + bool abs_x_gt_one; + + if (NUMERIC_IS_SPECIAL(num1)) + abs_x_gt_one = true; /* x is either Inf or -Inf */ + else + { + init_var_from_num(num1, &arg1); + if (cmp_var(&arg1, &const_minus_one) == 0) + PG_RETURN_NUMERIC(make_result(&const_one)); + arg1.sign = NUMERIC_POS; /* now arg1 = abs(x) */ + abs_x_gt_one = (cmp_var(&arg1, &const_one) > 0); + } + if (abs_x_gt_one == (sign2 > 0)) + PG_RETURN_NUMERIC(make_result(&const_pinf)); + else + PG_RETURN_NUMERIC(make_result(&const_zero)); + } + + /* + * For y < 0, if x is +Inf, +0 shall be returned. + * + * For y > 0, if x is +Inf, +Inf shall be returned. + */ + if (NUMERIC_IS_PINF(num1)) + { + if (sign2 > 0) + PG_RETURN_NUMERIC(make_result(&const_pinf)); + else + PG_RETURN_NUMERIC(make_result(&const_zero)); + } + + Assert(NUMERIC_IS_NINF(num1)); + + /* + * For y an odd integer < 0, if x is -Inf, -0 shall be returned. For + * y < 0 and not an odd integer, if x is -Inf, +0 shall be returned. + * (Again, we need not distinguish these two cases.) + */ + if (sign2 < 0) + PG_RETURN_NUMERIC(make_result(&const_zero)); + + /* + * For y an odd integer > 0, if x is -Inf, -Inf shall be returned. For + * y > 0 and not an odd integer, if x is -Inf, +Inf shall be returned. + */ + init_var_from_num(num2, &arg2); + if (arg2.ndigits > 0 && arg2.ndigits == arg2.weight + 1 && + (arg2.digits[arg2.ndigits - 1] & 1)) + PG_RETURN_NUMERIC(make_result(&const_ninf)); + else + PG_RETURN_NUMERIC(make_result(&const_pinf)); + } + + /* + * The SQL spec requires that we emit a particular SQLSTATE error code for + * certain error conditions. Specifically, we don't return a + * divide-by-zero error code for 0 ^ -1. Raising a negative number to a + * non-integer power must produce the same error code, but that case is + * handled in power_var(). + */ + sign1 = numeric_sign_internal(num1); + sign2 = numeric_sign_internal(num2); + + if (sign1 == 0 && sign2 < 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_ARGUMENT_FOR_POWER_FUNCTION), + errmsg("zero raised to a negative power is undefined"))); + + /* + * Initialize things + */ + init_var(&result); + init_var_from_num(num1, &arg1); + init_var_from_num(num2, &arg2); + + /* + * Call power_var() to compute and return the result; note it handles + * scale selection itself. + */ + power_var(&arg1, &arg2, &result); + + res = make_result(&result); + + free_var(&result); + + PG_RETURN_NUMERIC(res); +} + +/* + * numeric_scale() - + * + * Returns the scale, i.e. the count of decimal digits in the fractional part + */ +Datum +numeric_scale(PG_FUNCTION_ARGS) +{ + Numeric num = PG_GETARG_NUMERIC(0); + + if (NUMERIC_IS_SPECIAL(num)) + PG_RETURN_NULL(); + + PG_RETURN_INT32(NUMERIC_DSCALE(num)); +} + +/* + * Calculate minimum scale for value. + */ +static int +get_min_scale(NumericVar *var) +{ + int min_scale; + int last_digit_pos; + + /* + * Ordinarily, the input value will be "stripped" so that the last + * NumericDigit is nonzero. But we don't want to get into an infinite + * loop if it isn't, so explicitly find the last nonzero digit. + */ + last_digit_pos = var->ndigits - 1; + while (last_digit_pos >= 0 && + var->digits[last_digit_pos] == 0) + last_digit_pos--; + + if (last_digit_pos >= 0) + { + /* compute min_scale assuming that last ndigit has no zeroes */ + min_scale = (last_digit_pos - var->weight) * DEC_DIGITS; + + /* + * We could get a negative result if there are no digits after the + * decimal point. In this case the min_scale must be zero. + */ + if (min_scale > 0) + { + /* + * Reduce min_scale if trailing digit(s) in last NumericDigit are + * zero. + */ + NumericDigit last_digit = var->digits[last_digit_pos]; + + while (last_digit % 10 == 0) + { + min_scale--; + last_digit /= 10; + } + } + else + min_scale = 0; + } + else + min_scale = 0; /* result if input is zero */ + + return min_scale; +} + +/* + * Returns minimum scale required to represent supplied value without loss. + */ +Datum +numeric_min_scale(PG_FUNCTION_ARGS) +{ + Numeric num = PG_GETARG_NUMERIC(0); + NumericVar arg; + int min_scale; + + if (NUMERIC_IS_SPECIAL(num)) + PG_RETURN_NULL(); + + init_var_from_num(num, &arg); + min_scale = get_min_scale(&arg); + free_var(&arg); + + PG_RETURN_INT32(min_scale); +} + +/* + * Reduce scale of numeric value to represent supplied value without loss. + */ +Datum +numeric_trim_scale(PG_FUNCTION_ARGS) +{ + Numeric num = PG_GETARG_NUMERIC(0); + Numeric res; + NumericVar result; + + if (NUMERIC_IS_SPECIAL(num)) + PG_RETURN_NUMERIC(duplicate_numeric(num)); + + init_var_from_num(num, &result); + result.dscale = get_min_scale(&result); + res = make_result(&result); + free_var(&result); + + PG_RETURN_NUMERIC(res); +} + + +/* ---------------------------------------------------------------------- + * + * Type conversion functions + * + * ---------------------------------------------------------------------- + */ + +Numeric +int64_to_numeric(int64 val) +{ + Numeric res; + NumericVar result; + + init_var(&result); + + int64_to_numericvar(val, &result); + + res = make_result(&result); + + free_var(&result); + + return res; +} + +/* + * Convert val1/(10**log10val2) to numeric. This is much faster than normal + * numeric division. + */ +Numeric +int64_div_fast_to_numeric(int64 val1, int log10val2) +{ + Numeric res; + NumericVar result; + int rscale; + int w; + int m; + + init_var(&result); + + /* result scale */ + rscale = log10val2 < 0 ? 0 : log10val2; + + /* how much to decrease the weight by */ + w = log10val2 / DEC_DIGITS; + /* how much is left to divide by */ + m = log10val2 % DEC_DIGITS; + if (m < 0) + { + m += DEC_DIGITS; + w--; + } + + /* + * If there is anything left to divide by (10^m with 0 < m < DEC_DIGITS), + * multiply the dividend by 10^(DEC_DIGITS - m), and shift the weight by + * one more. + */ + if (m > 0) + { +#if DEC_DIGITS == 4 + static const int pow10[] = {1, 10, 100, 1000}; +#elif DEC_DIGITS == 2 + static const int pow10[] = {1, 10}; +#elif DEC_DIGITS == 1 + static const int pow10[] = {1}; +#else +#error unsupported NBASE +#endif + int64 factor = pow10[DEC_DIGITS - m]; + int64 new_val1; + + StaticAssertDecl(lengthof(pow10) == DEC_DIGITS, "mismatch with DEC_DIGITS"); + + if (unlikely(pg_mul_s64_overflow(val1, factor, &new_val1))) + { +#ifdef HAVE_INT128 + /* do the multiplication using 128-bit integers */ + int128 tmp; + + tmp = (int128) val1 * (int128) factor; + + int128_to_numericvar(tmp, &result); +#else + /* do the multiplication using numerics */ + NumericVar tmp; + + init_var(&tmp); + + int64_to_numericvar(val1, &result); + int64_to_numericvar(factor, &tmp); + mul_var(&result, &tmp, &result, 0); + + free_var(&tmp); +#endif + } + else + int64_to_numericvar(new_val1, &result); + + w++; + } + else + int64_to_numericvar(val1, &result); + + result.weight -= w; + result.dscale = rscale; + + res = make_result(&result); + + free_var(&result); + + return res; +} + +Datum +int4_numeric(PG_FUNCTION_ARGS) +{ + int32 val = PG_GETARG_INT32(0); + + PG_RETURN_NUMERIC(int64_to_numeric(val)); +} + +int32 +numeric_int4_opt_error(Numeric num, bool *have_error) +{ + NumericVar x; + int32 result; + + if (have_error) + *have_error = false; + + if (NUMERIC_IS_SPECIAL(num)) + { + if (have_error) + { + *have_error = true; + return 0; + } + else + { + if (NUMERIC_IS_NAN(num)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot convert NaN to %s", "integer"))); + else + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot convert infinity to %s", "integer"))); + } + } + + /* Convert to variable format, then convert to int4 */ + init_var_from_num(num, &x); + + if (!numericvar_to_int32(&x, &result)) + { + if (have_error) + { + *have_error = true; + return 0; + } + else + { + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("integer out of range"))); + } + } + + return result; +} + +Datum +numeric_int4(PG_FUNCTION_ARGS) +{ + Numeric num = PG_GETARG_NUMERIC(0); + + PG_RETURN_INT32(numeric_int4_opt_error(num, NULL)); +} + +/* + * Given a NumericVar, convert it to an int32. If the NumericVar + * exceeds the range of an int32, false is returned, otherwise true is returned. + * The input NumericVar is *not* free'd. + */ +static bool +numericvar_to_int32(const NumericVar *var, int32 *result) +{ + int64 val; + + if (!numericvar_to_int64(var, &val)) + return false; + + if (unlikely(val < PG_INT32_MIN) || unlikely(val > PG_INT32_MAX)) + return false; + + /* Down-convert to int4 */ + *result = (int32) val; + + return true; +} + +Datum +int8_numeric(PG_FUNCTION_ARGS) +{ + int64 val = PG_GETARG_INT64(0); + + PG_RETURN_NUMERIC(int64_to_numeric(val)); +} + + +Datum +numeric_int8(PG_FUNCTION_ARGS) +{ + Numeric num = PG_GETARG_NUMERIC(0); + NumericVar x; + int64 result; + + if (NUMERIC_IS_SPECIAL(num)) + { + if (NUMERIC_IS_NAN(num)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot convert NaN to %s", "bigint"))); + else + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot convert infinity to %s", "bigint"))); + } + + /* Convert to variable format and thence to int8 */ + init_var_from_num(num, &x); + + if (!numericvar_to_int64(&x, &result)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("bigint out of range"))); + + PG_RETURN_INT64(result); +} + + +Datum +int2_numeric(PG_FUNCTION_ARGS) +{ + int16 val = PG_GETARG_INT16(0); + + PG_RETURN_NUMERIC(int64_to_numeric(val)); +} + + +Datum +numeric_int2(PG_FUNCTION_ARGS) +{ + Numeric num = PG_GETARG_NUMERIC(0); + NumericVar x; + int64 val; + int16 result; + + if (NUMERIC_IS_SPECIAL(num)) + { + if (NUMERIC_IS_NAN(num)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot convert NaN to %s", "smallint"))); + else + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot convert infinity to %s", "smallint"))); + } + + /* Convert to variable format and thence to int8 */ + init_var_from_num(num, &x); + + if (!numericvar_to_int64(&x, &val)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("smallint out of range"))); + + if (unlikely(val < PG_INT16_MIN) || unlikely(val > PG_INT16_MAX)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("smallint out of range"))); + + /* Down-convert to int2 */ + result = (int16) val; + + PG_RETURN_INT16(result); +} + + +Datum +float8_numeric(PG_FUNCTION_ARGS) +{ + float8 val = PG_GETARG_FLOAT8(0); + Numeric res; + NumericVar result; + char buf[DBL_DIG + 100]; + const char *endptr; + + if (isnan(val)) + PG_RETURN_NUMERIC(make_result(&const_nan)); + + if (isinf(val)) + { + if (val < 0) + PG_RETURN_NUMERIC(make_result(&const_ninf)); + else + PG_RETURN_NUMERIC(make_result(&const_pinf)); + } + + snprintf(buf, sizeof(buf), "%.*g", DBL_DIG, val); + + init_var(&result); + + /* Assume we need not worry about leading/trailing spaces */ + (void) set_var_from_str(buf, buf, &result, &endptr, NULL); + + res = make_result(&result); + + free_var(&result); + + PG_RETURN_NUMERIC(res); +} + + +Datum +numeric_float8(PG_FUNCTION_ARGS) +{ + Numeric num = PG_GETARG_NUMERIC(0); + char *tmp; + Datum result; + + if (NUMERIC_IS_SPECIAL(num)) + { + if (NUMERIC_IS_PINF(num)) + PG_RETURN_FLOAT8(get_float8_infinity()); + else if (NUMERIC_IS_NINF(num)) + PG_RETURN_FLOAT8(-get_float8_infinity()); + else + PG_RETURN_FLOAT8(get_float8_nan()); + } + + tmp = DatumGetCString(DirectFunctionCall1(numeric_out, + NumericGetDatum(num))); + + result = DirectFunctionCall1(float8in, CStringGetDatum(tmp)); + + pfree(tmp); + + PG_RETURN_DATUM(result); +} + + +/* + * Convert numeric to float8; if out of range, return +/- HUGE_VAL + * + * (internal helper function, not directly callable from SQL) + */ +Datum +numeric_float8_no_overflow(PG_FUNCTION_ARGS) +{ + Numeric num = PG_GETARG_NUMERIC(0); + double val; + + if (NUMERIC_IS_SPECIAL(num)) + { + if (NUMERIC_IS_PINF(num)) + val = HUGE_VAL; + else if (NUMERIC_IS_NINF(num)) + val = -HUGE_VAL; + else + val = get_float8_nan(); + } + else + { + NumericVar x; + + init_var_from_num(num, &x); + val = numericvar_to_double_no_overflow(&x); + } + + PG_RETURN_FLOAT8(val); +} + +Datum +float4_numeric(PG_FUNCTION_ARGS) +{ + float4 val = PG_GETARG_FLOAT4(0); + Numeric res; + NumericVar result; + char buf[FLT_DIG + 100]; + const char *endptr; + + if (isnan(val)) + PG_RETURN_NUMERIC(make_result(&const_nan)); + + if (isinf(val)) + { + if (val < 0) + PG_RETURN_NUMERIC(make_result(&const_ninf)); + else + PG_RETURN_NUMERIC(make_result(&const_pinf)); + } + + snprintf(buf, sizeof(buf), "%.*g", FLT_DIG, val); + + init_var(&result); + + /* Assume we need not worry about leading/trailing spaces */ + (void) set_var_from_str(buf, buf, &result, &endptr, NULL); + + res = make_result(&result); + + free_var(&result); + + PG_RETURN_NUMERIC(res); +} + + +Datum +numeric_float4(PG_FUNCTION_ARGS) +{ + Numeric num = PG_GETARG_NUMERIC(0); + char *tmp; + Datum result; + + if (NUMERIC_IS_SPECIAL(num)) + { + if (NUMERIC_IS_PINF(num)) + PG_RETURN_FLOAT4(get_float4_infinity()); + else if (NUMERIC_IS_NINF(num)) + PG_RETURN_FLOAT4(-get_float4_infinity()); + else + PG_RETURN_FLOAT4(get_float4_nan()); + } + + tmp = DatumGetCString(DirectFunctionCall1(numeric_out, + NumericGetDatum(num))); + + result = DirectFunctionCall1(float4in, CStringGetDatum(tmp)); + + pfree(tmp); + + PG_RETURN_DATUM(result); +} + + +Datum +numeric_pg_lsn(PG_FUNCTION_ARGS) +{ + Numeric num = PG_GETARG_NUMERIC(0); + NumericVar x; + XLogRecPtr result; + + if (NUMERIC_IS_SPECIAL(num)) + { + if (NUMERIC_IS_NAN(num)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot convert NaN to %s", "pg_lsn"))); + else + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot convert infinity to %s", "pg_lsn"))); + } + + /* Convert to variable format and thence to pg_lsn */ + init_var_from_num(num, &x); + + if (!numericvar_to_uint64(&x, (uint64 *) &result)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("pg_lsn out of range"))); + + PG_RETURN_LSN(result); +} + + +/* ---------------------------------------------------------------------- + * + * Aggregate functions + * + * The transition datatype for all these aggregates is declared as INTERNAL. + * Actually, it's a pointer to a NumericAggState allocated in the aggregate + * context. The digit buffers for the NumericVars will be there too. + * + * On platforms which support 128-bit integers some aggregates instead use a + * 128-bit integer based transition datatype to speed up calculations. + * + * ---------------------------------------------------------------------- + */ + +typedef struct NumericAggState +{ + bool calcSumX2; /* if true, calculate sumX2 */ + MemoryContext agg_context; /* context we're calculating in */ + int64 N; /* count of processed numbers */ + NumericSumAccum sumX; /* sum of processed numbers */ + NumericSumAccum sumX2; /* sum of squares of processed numbers */ + int maxScale; /* maximum scale seen so far */ + int64 maxScaleCount; /* number of values seen with maximum scale */ + /* These counts are *not* included in N! Use NA_TOTAL_COUNT() as needed */ + int64 NaNcount; /* count of NaN values */ + int64 pInfcount; /* count of +Inf values */ + int64 nInfcount; /* count of -Inf values */ +} NumericAggState; + +#define NA_TOTAL_COUNT(na) \ + ((na)->N + (na)->NaNcount + (na)->pInfcount + (na)->nInfcount) + +/* + * Prepare state data for a numeric aggregate function that needs to compute + * sum, count and optionally sum of squares of the input. + */ +static NumericAggState * +makeNumericAggState(FunctionCallInfo fcinfo, bool calcSumX2) +{ + NumericAggState *state; + MemoryContext agg_context; + MemoryContext old_context; + + if (!AggCheckCallContext(fcinfo, &agg_context)) + elog(ERROR, "aggregate function called in non-aggregate context"); + + old_context = MemoryContextSwitchTo(agg_context); + + state = (NumericAggState *) palloc0(sizeof(NumericAggState)); + state->calcSumX2 = calcSumX2; + state->agg_context = agg_context; + + MemoryContextSwitchTo(old_context); + + return state; +} + +/* + * Like makeNumericAggState(), but allocate the state in the current memory + * context. + */ +static NumericAggState * +makeNumericAggStateCurrentContext(bool calcSumX2) +{ + NumericAggState *state; + + state = (NumericAggState *) palloc0(sizeof(NumericAggState)); + state->calcSumX2 = calcSumX2; + state->agg_context = CurrentMemoryContext; + + return state; +} + +/* + * Accumulate a new input value for numeric aggregate functions. + */ +static void +do_numeric_accum(NumericAggState *state, Numeric newval) +{ + NumericVar X; + NumericVar X2; + MemoryContext old_context; + + /* Count NaN/infinity inputs separately from all else */ + if (NUMERIC_IS_SPECIAL(newval)) + { + if (NUMERIC_IS_PINF(newval)) + state->pInfcount++; + else if (NUMERIC_IS_NINF(newval)) + state->nInfcount++; + else + state->NaNcount++; + return; + } + + /* load processed number in short-lived context */ + init_var_from_num(newval, &X); + + /* + * Track the highest input dscale that we've seen, to support inverse + * transitions (see do_numeric_discard). + */ + if (X.dscale > state->maxScale) + { + state->maxScale = X.dscale; + state->maxScaleCount = 1; + } + else if (X.dscale == state->maxScale) + state->maxScaleCount++; + + /* if we need X^2, calculate that in short-lived context */ + if (state->calcSumX2) + { + init_var(&X2); + mul_var(&X, &X, &X2, X.dscale * 2); + } + + /* The rest of this needs to work in the aggregate context */ + old_context = MemoryContextSwitchTo(state->agg_context); + + state->N++; + + /* Accumulate sums */ + accum_sum_add(&(state->sumX), &X); + + if (state->calcSumX2) + accum_sum_add(&(state->sumX2), &X2); + + MemoryContextSwitchTo(old_context); +} + +/* + * Attempt to remove an input value from the aggregated state. + * + * If the value cannot be removed then the function will return false; the + * possible reasons for failing are described below. + * + * If we aggregate the values 1.01 and 2 then the result will be 3.01. + * If we are then asked to un-aggregate the 1.01 then we must fail as we + * won't be able to tell what the new aggregated value's dscale should be. + * We don't want to return 2.00 (dscale = 2), since the sum's dscale would + * have been zero if we'd really aggregated only 2. + * + * Note: alternatively, we could count the number of inputs with each possible + * dscale (up to some sane limit). Not yet clear if it's worth the trouble. + */ +static bool +do_numeric_discard(NumericAggState *state, Numeric newval) +{ + NumericVar X; + NumericVar X2; + MemoryContext old_context; + + /* Count NaN/infinity inputs separately from all else */ + if (NUMERIC_IS_SPECIAL(newval)) + { + if (NUMERIC_IS_PINF(newval)) + state->pInfcount--; + else if (NUMERIC_IS_NINF(newval)) + state->nInfcount--; + else + state->NaNcount--; + return true; + } + + /* load processed number in short-lived context */ + init_var_from_num(newval, &X); + + /* + * state->sumX's dscale is the maximum dscale of any of the inputs. + * Removing the last input with that dscale would require us to recompute + * the maximum dscale of the *remaining* inputs, which we cannot do unless + * no more non-NaN inputs remain at all. So we report a failure instead, + * and force the aggregation to be redone from scratch. + */ + if (X.dscale == state->maxScale) + { + if (state->maxScaleCount > 1 || state->maxScale == 0) + { + /* + * Some remaining inputs have same dscale, or dscale hasn't gotten + * above zero anyway + */ + state->maxScaleCount--; + } + else if (state->N == 1) + { + /* No remaining non-NaN inputs at all, so reset maxScale */ + state->maxScale = 0; + state->maxScaleCount = 0; + } + else + { + /* Correct new maxScale is uncertain, must fail */ + return false; + } + } + + /* if we need X^2, calculate that in short-lived context */ + if (state->calcSumX2) + { + init_var(&X2); + mul_var(&X, &X, &X2, X.dscale * 2); + } + + /* The rest of this needs to work in the aggregate context */ + old_context = MemoryContextSwitchTo(state->agg_context); + + if (state->N-- > 1) + { + /* Negate X, to subtract it from the sum */ + X.sign = (X.sign == NUMERIC_POS ? NUMERIC_NEG : NUMERIC_POS); + accum_sum_add(&(state->sumX), &X); + + if (state->calcSumX2) + { + /* Negate X^2. X^2 is always positive */ + X2.sign = NUMERIC_NEG; + accum_sum_add(&(state->sumX2), &X2); + } + } + else + { + /* Zero the sums */ + Assert(state->N == 0); + + accum_sum_reset(&state->sumX); + if (state->calcSumX2) + accum_sum_reset(&state->sumX2); + } + + MemoryContextSwitchTo(old_context); + + return true; +} + +/* + * Generic transition function for numeric aggregates that require sumX2. + */ +Datum +numeric_accum(PG_FUNCTION_ARGS) +{ + NumericAggState *state; + + state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0); + + /* Create the state data on the first call */ + if (state == NULL) + state = makeNumericAggState(fcinfo, true); + + if (!PG_ARGISNULL(1)) + do_numeric_accum(state, PG_GETARG_NUMERIC(1)); + + PG_RETURN_POINTER(state); +} + +/* + * Generic combine function for numeric aggregates which require sumX2 + */ +Datum +numeric_combine(PG_FUNCTION_ARGS) +{ + NumericAggState *state1; + NumericAggState *state2; + MemoryContext agg_context; + MemoryContext old_context; + + if (!AggCheckCallContext(fcinfo, &agg_context)) + elog(ERROR, "aggregate function called in non-aggregate context"); + + state1 = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0); + state2 = PG_ARGISNULL(1) ? NULL : (NumericAggState *) PG_GETARG_POINTER(1); + + if (state2 == NULL) + PG_RETURN_POINTER(state1); + + /* manually copy all fields from state2 to state1 */ + if (state1 == NULL) + { + old_context = MemoryContextSwitchTo(agg_context); + + state1 = makeNumericAggStateCurrentContext(true); + state1->N = state2->N; + state1->NaNcount = state2->NaNcount; + state1->pInfcount = state2->pInfcount; + state1->nInfcount = state2->nInfcount; + state1->maxScale = state2->maxScale; + state1->maxScaleCount = state2->maxScaleCount; + + accum_sum_copy(&state1->sumX, &state2->sumX); + accum_sum_copy(&state1->sumX2, &state2->sumX2); + + MemoryContextSwitchTo(old_context); + + PG_RETURN_POINTER(state1); + } + + state1->N += state2->N; + state1->NaNcount += state2->NaNcount; + state1->pInfcount += state2->pInfcount; + state1->nInfcount += state2->nInfcount; + + if (state2->N > 0) + { + /* + * These are currently only needed for moving aggregates, but let's do + * the right thing anyway... + */ + if (state2->maxScale > state1->maxScale) + { + state1->maxScale = state2->maxScale; + state1->maxScaleCount = state2->maxScaleCount; + } + else if (state2->maxScale == state1->maxScale) + state1->maxScaleCount += state2->maxScaleCount; + + /* The rest of this needs to work in the aggregate context */ + old_context = MemoryContextSwitchTo(agg_context); + + /* Accumulate sums */ + accum_sum_combine(&state1->sumX, &state2->sumX); + accum_sum_combine(&state1->sumX2, &state2->sumX2); + + MemoryContextSwitchTo(old_context); + } + PG_RETURN_POINTER(state1); +} + +/* + * Generic transition function for numeric aggregates that don't require sumX2. + */ +Datum +numeric_avg_accum(PG_FUNCTION_ARGS) +{ + NumericAggState *state; + + state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0); + + /* Create the state data on the first call */ + if (state == NULL) + state = makeNumericAggState(fcinfo, false); + + if (!PG_ARGISNULL(1)) + do_numeric_accum(state, PG_GETARG_NUMERIC(1)); + + PG_RETURN_POINTER(state); +} + +/* + * Combine function for numeric aggregates which don't require sumX2 + */ +Datum +numeric_avg_combine(PG_FUNCTION_ARGS) +{ + NumericAggState *state1; + NumericAggState *state2; + MemoryContext agg_context; + MemoryContext old_context; + + if (!AggCheckCallContext(fcinfo, &agg_context)) + elog(ERROR, "aggregate function called in non-aggregate context"); + + state1 = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0); + state2 = PG_ARGISNULL(1) ? NULL : (NumericAggState *) PG_GETARG_POINTER(1); + + if (state2 == NULL) + PG_RETURN_POINTER(state1); + + /* manually copy all fields from state2 to state1 */ + if (state1 == NULL) + { + old_context = MemoryContextSwitchTo(agg_context); + + state1 = makeNumericAggStateCurrentContext(false); + state1->N = state2->N; + state1->NaNcount = state2->NaNcount; + state1->pInfcount = state2->pInfcount; + state1->nInfcount = state2->nInfcount; + state1->maxScale = state2->maxScale; + state1->maxScaleCount = state2->maxScaleCount; + + accum_sum_copy(&state1->sumX, &state2->sumX); + + MemoryContextSwitchTo(old_context); + + PG_RETURN_POINTER(state1); + } + + state1->N += state2->N; + state1->NaNcount += state2->NaNcount; + state1->pInfcount += state2->pInfcount; + state1->nInfcount += state2->nInfcount; + + if (state2->N > 0) + { + /* + * These are currently only needed for moving aggregates, but let's do + * the right thing anyway... + */ + if (state2->maxScale > state1->maxScale) + { + state1->maxScale = state2->maxScale; + state1->maxScaleCount = state2->maxScaleCount; + } + else if (state2->maxScale == state1->maxScale) + state1->maxScaleCount += state2->maxScaleCount; + + /* The rest of this needs to work in the aggregate context */ + old_context = MemoryContextSwitchTo(agg_context); + + /* Accumulate sums */ + accum_sum_combine(&state1->sumX, &state2->sumX); + + MemoryContextSwitchTo(old_context); + } + PG_RETURN_POINTER(state1); +} + +/* + * numeric_avg_serialize + * Serialize NumericAggState for numeric aggregates that don't require + * sumX2. + */ +Datum +numeric_avg_serialize(PG_FUNCTION_ARGS) +{ + NumericAggState *state; + StringInfoData buf; + bytea *result; + NumericVar tmp_var; + + /* Ensure we disallow calling when not in aggregate context */ + if (!AggCheckCallContext(fcinfo, NULL)) + elog(ERROR, "aggregate function called in non-aggregate context"); + + state = (NumericAggState *) PG_GETARG_POINTER(0); + + init_var(&tmp_var); + + pq_begintypsend(&buf); + + /* N */ + pq_sendint64(&buf, state->N); + + /* sumX */ + accum_sum_final(&state->sumX, &tmp_var); + numericvar_serialize(&buf, &tmp_var); + + /* maxScale */ + pq_sendint32(&buf, state->maxScale); + + /* maxScaleCount */ + pq_sendint64(&buf, state->maxScaleCount); + + /* NaNcount */ + pq_sendint64(&buf, state->NaNcount); + + /* pInfcount */ + pq_sendint64(&buf, state->pInfcount); + + /* nInfcount */ + pq_sendint64(&buf, state->nInfcount); + + result = pq_endtypsend(&buf); + + free_var(&tmp_var); + + PG_RETURN_BYTEA_P(result); +} + +/* + * numeric_avg_deserialize + * Deserialize bytea into NumericAggState for numeric aggregates that + * don't require sumX2. + */ +Datum +numeric_avg_deserialize(PG_FUNCTION_ARGS) +{ + bytea *sstate; + NumericAggState *result; + StringInfoData buf; + NumericVar tmp_var; + + if (!AggCheckCallContext(fcinfo, NULL)) + elog(ERROR, "aggregate function called in non-aggregate context"); + + sstate = PG_GETARG_BYTEA_PP(0); + + init_var(&tmp_var); + + /* + * Copy the bytea into a StringInfo so that we can "receive" it using the + * standard recv-function infrastructure. + */ + initStringInfo(&buf); + appendBinaryStringInfo(&buf, + VARDATA_ANY(sstate), VARSIZE_ANY_EXHDR(sstate)); + + result = makeNumericAggStateCurrentContext(false); + + /* N */ + result->N = pq_getmsgint64(&buf); + + /* sumX */ + numericvar_deserialize(&buf, &tmp_var); + accum_sum_add(&(result->sumX), &tmp_var); + + /* maxScale */ + result->maxScale = pq_getmsgint(&buf, 4); + + /* maxScaleCount */ + result->maxScaleCount = pq_getmsgint64(&buf); + + /* NaNcount */ + result->NaNcount = pq_getmsgint64(&buf); + + /* pInfcount */ + result->pInfcount = pq_getmsgint64(&buf); + + /* nInfcount */ + result->nInfcount = pq_getmsgint64(&buf); + + pq_getmsgend(&buf); + pfree(buf.data); + + free_var(&tmp_var); + + PG_RETURN_POINTER(result); +} + +/* + * numeric_serialize + * Serialization function for NumericAggState for numeric aggregates that + * require sumX2. + */ +Datum +numeric_serialize(PG_FUNCTION_ARGS) +{ + NumericAggState *state; + StringInfoData buf; + bytea *result; + NumericVar tmp_var; + + /* Ensure we disallow calling when not in aggregate context */ + if (!AggCheckCallContext(fcinfo, NULL)) + elog(ERROR, "aggregate function called in non-aggregate context"); + + state = (NumericAggState *) PG_GETARG_POINTER(0); + + init_var(&tmp_var); + + pq_begintypsend(&buf); + + /* N */ + pq_sendint64(&buf, state->N); + + /* sumX */ + accum_sum_final(&state->sumX, &tmp_var); + numericvar_serialize(&buf, &tmp_var); + + /* sumX2 */ + accum_sum_final(&state->sumX2, &tmp_var); + numericvar_serialize(&buf, &tmp_var); + + /* maxScale */ + pq_sendint32(&buf, state->maxScale); + + /* maxScaleCount */ + pq_sendint64(&buf, state->maxScaleCount); + + /* NaNcount */ + pq_sendint64(&buf, state->NaNcount); + + /* pInfcount */ + pq_sendint64(&buf, state->pInfcount); + + /* nInfcount */ + pq_sendint64(&buf, state->nInfcount); + + result = pq_endtypsend(&buf); + + free_var(&tmp_var); + + PG_RETURN_BYTEA_P(result); +} + +/* + * numeric_deserialize + * Deserialization function for NumericAggState for numeric aggregates that + * require sumX2. + */ +Datum +numeric_deserialize(PG_FUNCTION_ARGS) +{ + bytea *sstate; + NumericAggState *result; + StringInfoData buf; + NumericVar tmp_var; + + if (!AggCheckCallContext(fcinfo, NULL)) + elog(ERROR, "aggregate function called in non-aggregate context"); + + sstate = PG_GETARG_BYTEA_PP(0); + + init_var(&tmp_var); + + /* + * Copy the bytea into a StringInfo so that we can "receive" it using the + * standard recv-function infrastructure. + */ + initStringInfo(&buf); + appendBinaryStringInfo(&buf, + VARDATA_ANY(sstate), VARSIZE_ANY_EXHDR(sstate)); + + result = makeNumericAggStateCurrentContext(false); + + /* N */ + result->N = pq_getmsgint64(&buf); + + /* sumX */ + numericvar_deserialize(&buf, &tmp_var); + accum_sum_add(&(result->sumX), &tmp_var); + + /* sumX2 */ + numericvar_deserialize(&buf, &tmp_var); + accum_sum_add(&(result->sumX2), &tmp_var); + + /* maxScale */ + result->maxScale = pq_getmsgint(&buf, 4); + + /* maxScaleCount */ + result->maxScaleCount = pq_getmsgint64(&buf); + + /* NaNcount */ + result->NaNcount = pq_getmsgint64(&buf); + + /* pInfcount */ + result->pInfcount = pq_getmsgint64(&buf); + + /* nInfcount */ + result->nInfcount = pq_getmsgint64(&buf); + + pq_getmsgend(&buf); + pfree(buf.data); + + free_var(&tmp_var); + + PG_RETURN_POINTER(result); +} + +/* + * Generic inverse transition function for numeric aggregates + * (with or without requirement for X^2). + */ +Datum +numeric_accum_inv(PG_FUNCTION_ARGS) +{ + NumericAggState *state; + + state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0); + + /* Should not get here with no state */ + if (state == NULL) + elog(ERROR, "numeric_accum_inv called with NULL state"); + + if (!PG_ARGISNULL(1)) + { + /* If we fail to perform the inverse transition, return NULL */ + if (!do_numeric_discard(state, PG_GETARG_NUMERIC(1))) + PG_RETURN_NULL(); + } + + PG_RETURN_POINTER(state); +} + + +/* + * Integer data types in general use Numeric accumulators to share code + * and avoid risk of overflow. + * + * However for performance reasons optimized special-purpose accumulator + * routines are used when possible. + * + * On platforms with 128-bit integer support, the 128-bit routines will be + * used when sum(X) or sum(X*X) fit into 128-bit. + * + * For 16 and 32 bit inputs, the N and sum(X) fit into 64-bit so the 64-bit + * accumulators will be used for SUM and AVG of these data types. + */ + +#ifdef HAVE_INT128 +typedef struct Int128AggState +{ + bool calcSumX2; /* if true, calculate sumX2 */ + int64 N; /* count of processed numbers */ + int128 sumX; /* sum of processed numbers */ + int128 sumX2; /* sum of squares of processed numbers */ +} Int128AggState; + +/* + * Prepare state data for a 128-bit aggregate function that needs to compute + * sum, count and optionally sum of squares of the input. + */ +static Int128AggState * +makeInt128AggState(FunctionCallInfo fcinfo, bool calcSumX2) +{ + Int128AggState *state; + MemoryContext agg_context; + MemoryContext old_context; + + if (!AggCheckCallContext(fcinfo, &agg_context)) + elog(ERROR, "aggregate function called in non-aggregate context"); + + old_context = MemoryContextSwitchTo(agg_context); + + state = (Int128AggState *) palloc0(sizeof(Int128AggState)); + state->calcSumX2 = calcSumX2; + + MemoryContextSwitchTo(old_context); + + return state; +} + +/* + * Like makeInt128AggState(), but allocate the state in the current memory + * context. + */ +static Int128AggState * +makeInt128AggStateCurrentContext(bool calcSumX2) +{ + Int128AggState *state; + + state = (Int128AggState *) palloc0(sizeof(Int128AggState)); + state->calcSumX2 = calcSumX2; + + return state; +} + +/* + * Accumulate a new input value for 128-bit aggregate functions. + */ +static void +do_int128_accum(Int128AggState *state, int128 newval) +{ + if (state->calcSumX2) + state->sumX2 += newval * newval; + + state->sumX += newval; + state->N++; +} + +/* + * Remove an input value from the aggregated state. + */ +static void +do_int128_discard(Int128AggState *state, int128 newval) +{ + if (state->calcSumX2) + state->sumX2 -= newval * newval; + + state->sumX -= newval; + state->N--; +} + +typedef Int128AggState PolyNumAggState; +#define makePolyNumAggState makeInt128AggState +#define makePolyNumAggStateCurrentContext makeInt128AggStateCurrentContext +#else +typedef NumericAggState PolyNumAggState; +#define makePolyNumAggState makeNumericAggState +#define makePolyNumAggStateCurrentContext makeNumericAggStateCurrentContext +#endif + +Datum +int2_accum(PG_FUNCTION_ARGS) +{ + PolyNumAggState *state; + + state = PG_ARGISNULL(0) ? NULL : (PolyNumAggState *) PG_GETARG_POINTER(0); + + /* Create the state data on the first call */ + if (state == NULL) + state = makePolyNumAggState(fcinfo, true); + + if (!PG_ARGISNULL(1)) + { +#ifdef HAVE_INT128 + do_int128_accum(state, (int128) PG_GETARG_INT16(1)); +#else + do_numeric_accum(state, int64_to_numeric(PG_GETARG_INT16(1))); +#endif + } + + PG_RETURN_POINTER(state); +} + +Datum +int4_accum(PG_FUNCTION_ARGS) +{ + PolyNumAggState *state; + + state = PG_ARGISNULL(0) ? NULL : (PolyNumAggState *) PG_GETARG_POINTER(0); + + /* Create the state data on the first call */ + if (state == NULL) + state = makePolyNumAggState(fcinfo, true); + + if (!PG_ARGISNULL(1)) + { +#ifdef HAVE_INT128 + do_int128_accum(state, (int128) PG_GETARG_INT32(1)); +#else + do_numeric_accum(state, int64_to_numeric(PG_GETARG_INT32(1))); +#endif + } + + PG_RETURN_POINTER(state); +} + +Datum +int8_accum(PG_FUNCTION_ARGS) +{ + NumericAggState *state; + + state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0); + + /* Create the state data on the first call */ + if (state == NULL) + state = makeNumericAggState(fcinfo, true); + + if (!PG_ARGISNULL(1)) + do_numeric_accum(state, int64_to_numeric(PG_GETARG_INT64(1))); + + PG_RETURN_POINTER(state); +} + +/* + * Combine function for numeric aggregates which require sumX2 + */ +Datum +numeric_poly_combine(PG_FUNCTION_ARGS) +{ + PolyNumAggState *state1; + PolyNumAggState *state2; + MemoryContext agg_context; + MemoryContext old_context; + + if (!AggCheckCallContext(fcinfo, &agg_context)) + elog(ERROR, "aggregate function called in non-aggregate context"); + + state1 = PG_ARGISNULL(0) ? NULL : (PolyNumAggState *) PG_GETARG_POINTER(0); + state2 = PG_ARGISNULL(1) ? NULL : (PolyNumAggState *) PG_GETARG_POINTER(1); + + if (state2 == NULL) + PG_RETURN_POINTER(state1); + + /* manually copy all fields from state2 to state1 */ + if (state1 == NULL) + { + old_context = MemoryContextSwitchTo(agg_context); + + state1 = makePolyNumAggState(fcinfo, true); + state1->N = state2->N; + +#ifdef HAVE_INT128 + state1->sumX = state2->sumX; + state1->sumX2 = state2->sumX2; +#else + accum_sum_copy(&state1->sumX, &state2->sumX); + accum_sum_copy(&state1->sumX2, &state2->sumX2); +#endif + + MemoryContextSwitchTo(old_context); + + PG_RETURN_POINTER(state1); + } + + if (state2->N > 0) + { + state1->N += state2->N; + +#ifdef HAVE_INT128 + state1->sumX += state2->sumX; + state1->sumX2 += state2->sumX2; +#else + /* The rest of this needs to work in the aggregate context */ + old_context = MemoryContextSwitchTo(agg_context); + + /* Accumulate sums */ + accum_sum_combine(&state1->sumX, &state2->sumX); + accum_sum_combine(&state1->sumX2, &state2->sumX2); + + MemoryContextSwitchTo(old_context); +#endif + + } + PG_RETURN_POINTER(state1); +} + +/* + * numeric_poly_serialize + * Serialize PolyNumAggState into bytea for aggregate functions which + * require sumX2. + */ +Datum +numeric_poly_serialize(PG_FUNCTION_ARGS) +{ + PolyNumAggState *state; + StringInfoData buf; + bytea *result; + NumericVar tmp_var; + + /* Ensure we disallow calling when not in aggregate context */ + if (!AggCheckCallContext(fcinfo, NULL)) + elog(ERROR, "aggregate function called in non-aggregate context"); + + state = (PolyNumAggState *) PG_GETARG_POINTER(0); + + /* + * If the platform supports int128 then sumX and sumX2 will be a 128 bit + * integer type. Here we'll convert that into a numeric type so that the + * combine state is in the same format for both int128 enabled machines + * and machines which don't support that type. The logic here is that one + * day we might like to send these over to another server for further + * processing and we want a standard format to work with. + */ + + init_var(&tmp_var); + + pq_begintypsend(&buf); + + /* N */ + pq_sendint64(&buf, state->N); + + /* sumX */ +#ifdef HAVE_INT128 + int128_to_numericvar(state->sumX, &tmp_var); +#else + accum_sum_final(&state->sumX, &tmp_var); +#endif + numericvar_serialize(&buf, &tmp_var); + + /* sumX2 */ +#ifdef HAVE_INT128 + int128_to_numericvar(state->sumX2, &tmp_var); +#else + accum_sum_final(&state->sumX2, &tmp_var); +#endif + numericvar_serialize(&buf, &tmp_var); + + result = pq_endtypsend(&buf); + + free_var(&tmp_var); + + PG_RETURN_BYTEA_P(result); +} + +/* + * numeric_poly_deserialize + * Deserialize PolyNumAggState from bytea for aggregate functions which + * require sumX2. + */ +Datum +numeric_poly_deserialize(PG_FUNCTION_ARGS) +{ + bytea *sstate; + PolyNumAggState *result; + StringInfoData buf; + NumericVar tmp_var; + + if (!AggCheckCallContext(fcinfo, NULL)) + elog(ERROR, "aggregate function called in non-aggregate context"); + + sstate = PG_GETARG_BYTEA_PP(0); + + init_var(&tmp_var); + + /* + * Copy the bytea into a StringInfo so that we can "receive" it using the + * standard recv-function infrastructure. + */ + initStringInfo(&buf); + appendBinaryStringInfo(&buf, + VARDATA_ANY(sstate), VARSIZE_ANY_EXHDR(sstate)); + + result = makePolyNumAggStateCurrentContext(false); + + /* N */ + result->N = pq_getmsgint64(&buf); + + /* sumX */ + numericvar_deserialize(&buf, &tmp_var); +#ifdef HAVE_INT128 + numericvar_to_int128(&tmp_var, &result->sumX); +#else + accum_sum_add(&result->sumX, &tmp_var); +#endif + + /* sumX2 */ + numericvar_deserialize(&buf, &tmp_var); +#ifdef HAVE_INT128 + numericvar_to_int128(&tmp_var, &result->sumX2); +#else + accum_sum_add(&result->sumX2, &tmp_var); +#endif + + pq_getmsgend(&buf); + pfree(buf.data); + + free_var(&tmp_var); + + PG_RETURN_POINTER(result); +} + +/* + * Transition function for int8 input when we don't need sumX2. + */ +Datum +int8_avg_accum(PG_FUNCTION_ARGS) +{ + PolyNumAggState *state; + + state = PG_ARGISNULL(0) ? NULL : (PolyNumAggState *) PG_GETARG_POINTER(0); + + /* Create the state data on the first call */ + if (state == NULL) + state = makePolyNumAggState(fcinfo, false); + + if (!PG_ARGISNULL(1)) + { +#ifdef HAVE_INT128 + do_int128_accum(state, (int128) PG_GETARG_INT64(1)); +#else + do_numeric_accum(state, int64_to_numeric(PG_GETARG_INT64(1))); +#endif + } + + PG_RETURN_POINTER(state); +} + +/* + * Combine function for PolyNumAggState for aggregates which don't require + * sumX2 + */ +Datum +int8_avg_combine(PG_FUNCTION_ARGS) +{ + PolyNumAggState *state1; + PolyNumAggState *state2; + MemoryContext agg_context; + MemoryContext old_context; + + if (!AggCheckCallContext(fcinfo, &agg_context)) + elog(ERROR, "aggregate function called in non-aggregate context"); + + state1 = PG_ARGISNULL(0) ? NULL : (PolyNumAggState *) PG_GETARG_POINTER(0); + state2 = PG_ARGISNULL(1) ? NULL : (PolyNumAggState *) PG_GETARG_POINTER(1); + + if (state2 == NULL) + PG_RETURN_POINTER(state1); + + /* manually copy all fields from state2 to state1 */ + if (state1 == NULL) + { + old_context = MemoryContextSwitchTo(agg_context); + + state1 = makePolyNumAggState(fcinfo, false); + state1->N = state2->N; + +#ifdef HAVE_INT128 + state1->sumX = state2->sumX; +#else + accum_sum_copy(&state1->sumX, &state2->sumX); +#endif + MemoryContextSwitchTo(old_context); + + PG_RETURN_POINTER(state1); + } + + if (state2->N > 0) + { + state1->N += state2->N; + +#ifdef HAVE_INT128 + state1->sumX += state2->sumX; +#else + /* The rest of this needs to work in the aggregate context */ + old_context = MemoryContextSwitchTo(agg_context); + + /* Accumulate sums */ + accum_sum_combine(&state1->sumX, &state2->sumX); + + MemoryContextSwitchTo(old_context); +#endif + + } + PG_RETURN_POINTER(state1); +} + +/* + * int8_avg_serialize + * Serialize PolyNumAggState into bytea using the standard + * recv-function infrastructure. + */ +Datum +int8_avg_serialize(PG_FUNCTION_ARGS) +{ + PolyNumAggState *state; + StringInfoData buf; + bytea *result; + NumericVar tmp_var; + + /* Ensure we disallow calling when not in aggregate context */ + if (!AggCheckCallContext(fcinfo, NULL)) + elog(ERROR, "aggregate function called in non-aggregate context"); + + state = (PolyNumAggState *) PG_GETARG_POINTER(0); + + /* + * If the platform supports int128 then sumX will be a 128 integer type. + * Here we'll convert that into a numeric type so that the combine state + * is in the same format for both int128 enabled machines and machines + * which don't support that type. The logic here is that one day we might + * like to send these over to another server for further processing and we + * want a standard format to work with. + */ + + init_var(&tmp_var); + + pq_begintypsend(&buf); + + /* N */ + pq_sendint64(&buf, state->N); + + /* sumX */ +#ifdef HAVE_INT128 + int128_to_numericvar(state->sumX, &tmp_var); +#else + accum_sum_final(&state->sumX, &tmp_var); +#endif + numericvar_serialize(&buf, &tmp_var); + + result = pq_endtypsend(&buf); + + free_var(&tmp_var); + + PG_RETURN_BYTEA_P(result); +} + +/* + * int8_avg_deserialize + * Deserialize bytea back into PolyNumAggState. + */ +Datum +int8_avg_deserialize(PG_FUNCTION_ARGS) +{ + bytea *sstate; + PolyNumAggState *result; + StringInfoData buf; + NumericVar tmp_var; + + if (!AggCheckCallContext(fcinfo, NULL)) + elog(ERROR, "aggregate function called in non-aggregate context"); + + sstate = PG_GETARG_BYTEA_PP(0); + + init_var(&tmp_var); + + /* + * Copy the bytea into a StringInfo so that we can "receive" it using the + * standard recv-function infrastructure. + */ + initStringInfo(&buf); + appendBinaryStringInfo(&buf, + VARDATA_ANY(sstate), VARSIZE_ANY_EXHDR(sstate)); + + result = makePolyNumAggStateCurrentContext(false); + + /* N */ + result->N = pq_getmsgint64(&buf); + + /* sumX */ + numericvar_deserialize(&buf, &tmp_var); +#ifdef HAVE_INT128 + numericvar_to_int128(&tmp_var, &result->sumX); +#else + accum_sum_add(&result->sumX, &tmp_var); +#endif + + pq_getmsgend(&buf); + pfree(buf.data); + + free_var(&tmp_var); + + PG_RETURN_POINTER(result); +} + +/* + * Inverse transition functions to go with the above. + */ + +Datum +int2_accum_inv(PG_FUNCTION_ARGS) +{ + PolyNumAggState *state; + + state = PG_ARGISNULL(0) ? NULL : (PolyNumAggState *) PG_GETARG_POINTER(0); + + /* Should not get here with no state */ + if (state == NULL) + elog(ERROR, "int2_accum_inv called with NULL state"); + + if (!PG_ARGISNULL(1)) + { +#ifdef HAVE_INT128 + do_int128_discard(state, (int128) PG_GETARG_INT16(1)); +#else + /* Should never fail, all inputs have dscale 0 */ + if (!do_numeric_discard(state, int64_to_numeric(PG_GETARG_INT16(1)))) + elog(ERROR, "do_numeric_discard failed unexpectedly"); +#endif + } + + PG_RETURN_POINTER(state); +} + +Datum +int4_accum_inv(PG_FUNCTION_ARGS) +{ + PolyNumAggState *state; + + state = PG_ARGISNULL(0) ? NULL : (PolyNumAggState *) PG_GETARG_POINTER(0); + + /* Should not get here with no state */ + if (state == NULL) + elog(ERROR, "int4_accum_inv called with NULL state"); + + if (!PG_ARGISNULL(1)) + { +#ifdef HAVE_INT128 + do_int128_discard(state, (int128) PG_GETARG_INT32(1)); +#else + /* Should never fail, all inputs have dscale 0 */ + if (!do_numeric_discard(state, int64_to_numeric(PG_GETARG_INT32(1)))) + elog(ERROR, "do_numeric_discard failed unexpectedly"); +#endif + } + + PG_RETURN_POINTER(state); +} + +Datum +int8_accum_inv(PG_FUNCTION_ARGS) +{ + NumericAggState *state; + + state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0); + + /* Should not get here with no state */ + if (state == NULL) + elog(ERROR, "int8_accum_inv called with NULL state"); + + if (!PG_ARGISNULL(1)) + { + /* Should never fail, all inputs have dscale 0 */ + if (!do_numeric_discard(state, int64_to_numeric(PG_GETARG_INT64(1)))) + elog(ERROR, "do_numeric_discard failed unexpectedly"); + } + + PG_RETURN_POINTER(state); +} + +Datum +int8_avg_accum_inv(PG_FUNCTION_ARGS) +{ + PolyNumAggState *state; + + state = PG_ARGISNULL(0) ? NULL : (PolyNumAggState *) PG_GETARG_POINTER(0); + + /* Should not get here with no state */ + if (state == NULL) + elog(ERROR, "int8_avg_accum_inv called with NULL state"); + + if (!PG_ARGISNULL(1)) + { +#ifdef HAVE_INT128 + do_int128_discard(state, (int128) PG_GETARG_INT64(1)); +#else + /* Should never fail, all inputs have dscale 0 */ + if (!do_numeric_discard(state, int64_to_numeric(PG_GETARG_INT64(1)))) + elog(ERROR, "do_numeric_discard failed unexpectedly"); +#endif + } + + PG_RETURN_POINTER(state); +} + +Datum +numeric_poly_sum(PG_FUNCTION_ARGS) +{ +#ifdef HAVE_INT128 + PolyNumAggState *state; + Numeric res; + NumericVar result; + + state = PG_ARGISNULL(0) ? NULL : (PolyNumAggState *) PG_GETARG_POINTER(0); + + /* If there were no non-null inputs, return NULL */ + if (state == NULL || state->N == 0) + PG_RETURN_NULL(); + + init_var(&result); + + int128_to_numericvar(state->sumX, &result); + + res = make_result(&result); + + free_var(&result); + + PG_RETURN_NUMERIC(res); +#else + return numeric_sum(fcinfo); +#endif +} + +Datum +numeric_poly_avg(PG_FUNCTION_ARGS) +{ +#ifdef HAVE_INT128 + PolyNumAggState *state; + NumericVar result; + Datum countd, + sumd; + + state = PG_ARGISNULL(0) ? NULL : (PolyNumAggState *) PG_GETARG_POINTER(0); + + /* If there were no non-null inputs, return NULL */ + if (state == NULL || state->N == 0) + PG_RETURN_NULL(); + + init_var(&result); + + int128_to_numericvar(state->sumX, &result); + + countd = NumericGetDatum(int64_to_numeric(state->N)); + sumd = NumericGetDatum(make_result(&result)); + + free_var(&result); + + PG_RETURN_DATUM(DirectFunctionCall2(numeric_div, sumd, countd)); +#else + return numeric_avg(fcinfo); +#endif +} + +Datum +numeric_avg(PG_FUNCTION_ARGS) +{ + NumericAggState *state; + Datum N_datum; + Datum sumX_datum; + NumericVar sumX_var; + + state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0); + + /* If there were no non-null inputs, return NULL */ + if (state == NULL || NA_TOTAL_COUNT(state) == 0) + PG_RETURN_NULL(); + + if (state->NaNcount > 0) /* there was at least one NaN input */ + PG_RETURN_NUMERIC(make_result(&const_nan)); + + /* adding plus and minus infinities gives NaN */ + if (state->pInfcount > 0 && state->nInfcount > 0) + PG_RETURN_NUMERIC(make_result(&const_nan)); + if (state->pInfcount > 0) + PG_RETURN_NUMERIC(make_result(&const_pinf)); + if (state->nInfcount > 0) + PG_RETURN_NUMERIC(make_result(&const_ninf)); + + N_datum = NumericGetDatum(int64_to_numeric(state->N)); + + init_var(&sumX_var); + accum_sum_final(&state->sumX, &sumX_var); + sumX_datum = NumericGetDatum(make_result(&sumX_var)); + free_var(&sumX_var); + + PG_RETURN_DATUM(DirectFunctionCall2(numeric_div, sumX_datum, N_datum)); +} + +Datum +numeric_sum(PG_FUNCTION_ARGS) +{ + NumericAggState *state; + NumericVar sumX_var; + Numeric result; + + state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0); + + /* If there were no non-null inputs, return NULL */ + if (state == NULL || NA_TOTAL_COUNT(state) == 0) + PG_RETURN_NULL(); + + if (state->NaNcount > 0) /* there was at least one NaN input */ + PG_RETURN_NUMERIC(make_result(&const_nan)); + + /* adding plus and minus infinities gives NaN */ + if (state->pInfcount > 0 && state->nInfcount > 0) + PG_RETURN_NUMERIC(make_result(&const_nan)); + if (state->pInfcount > 0) + PG_RETURN_NUMERIC(make_result(&const_pinf)); + if (state->nInfcount > 0) + PG_RETURN_NUMERIC(make_result(&const_ninf)); + + init_var(&sumX_var); + accum_sum_final(&state->sumX, &sumX_var); + result = make_result(&sumX_var); + free_var(&sumX_var); + + PG_RETURN_NUMERIC(result); +} + +/* + * Workhorse routine for the standard deviance and variance + * aggregates. 'state' is aggregate's transition state. + * 'variance' specifies whether we should calculate the + * variance or the standard deviation. 'sample' indicates whether the + * caller is interested in the sample or the population + * variance/stddev. + * + * If appropriate variance statistic is undefined for the input, + * *is_null is set to true and NULL is returned. + */ +static Numeric +numeric_stddev_internal(NumericAggState *state, + bool variance, bool sample, + bool *is_null) +{ + Numeric res; + NumericVar vN, + vsumX, + vsumX2, + vNminus1; + int64 totCount; + int rscale; + + /* + * Sample stddev and variance are undefined when N <= 1; population stddev + * is undefined when N == 0. Return NULL in either case (note that NaNs + * and infinities count as normal inputs for this purpose). + */ + if (state == NULL || (totCount = NA_TOTAL_COUNT(state)) == 0) + { + *is_null = true; + return NULL; + } + + if (sample && totCount <= 1) + { + *is_null = true; + return NULL; + } + + *is_null = false; + + /* + * Deal with NaN and infinity cases. By analogy to the behavior of the + * float8 functions, any infinity input produces NaN output. + */ + if (state->NaNcount > 0 || state->pInfcount > 0 || state->nInfcount > 0) + return make_result(&const_nan); + + /* OK, normal calculation applies */ + init_var(&vN); + init_var(&vsumX); + init_var(&vsumX2); + + int64_to_numericvar(state->N, &vN); + accum_sum_final(&(state->sumX), &vsumX); + accum_sum_final(&(state->sumX2), &vsumX2); + + init_var(&vNminus1); + sub_var(&vN, &const_one, &vNminus1); + + /* compute rscale for mul_var calls */ + rscale = vsumX.dscale * 2; + + mul_var(&vsumX, &vsumX, &vsumX, rscale); /* vsumX = sumX * sumX */ + mul_var(&vN, &vsumX2, &vsumX2, rscale); /* vsumX2 = N * sumX2 */ + sub_var(&vsumX2, &vsumX, &vsumX2); /* N * sumX2 - sumX * sumX */ + + if (cmp_var(&vsumX2, &const_zero) <= 0) + { + /* Watch out for roundoff error producing a negative numerator */ + res = make_result(&const_zero); + } + else + { + if (sample) + mul_var(&vN, &vNminus1, &vNminus1, 0); /* N * (N - 1) */ + else + mul_var(&vN, &vN, &vNminus1, 0); /* N * N */ + rscale = select_div_scale(&vsumX2, &vNminus1); + div_var(&vsumX2, &vNminus1, &vsumX, rscale, true); /* variance */ + if (!variance) + sqrt_var(&vsumX, &vsumX, rscale); /* stddev */ + + res = make_result(&vsumX); + } + + free_var(&vNminus1); + free_var(&vsumX); + free_var(&vsumX2); + + return res; +} + +Datum +numeric_var_samp(PG_FUNCTION_ARGS) +{ + NumericAggState *state; + Numeric res; + bool is_null; + + state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0); + + res = numeric_stddev_internal(state, true, true, &is_null); + + if (is_null) + PG_RETURN_NULL(); + else + PG_RETURN_NUMERIC(res); +} + +Datum +numeric_stddev_samp(PG_FUNCTION_ARGS) +{ + NumericAggState *state; + Numeric res; + bool is_null; + + state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0); + + res = numeric_stddev_internal(state, false, true, &is_null); + + if (is_null) + PG_RETURN_NULL(); + else + PG_RETURN_NUMERIC(res); +} + +Datum +numeric_var_pop(PG_FUNCTION_ARGS) +{ + NumericAggState *state; + Numeric res; + bool is_null; + + state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0); + + res = numeric_stddev_internal(state, true, false, &is_null); + + if (is_null) + PG_RETURN_NULL(); + else + PG_RETURN_NUMERIC(res); +} + +Datum +numeric_stddev_pop(PG_FUNCTION_ARGS) +{ + NumericAggState *state; + Numeric res; + bool is_null; + + state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0); + + res = numeric_stddev_internal(state, false, false, &is_null); + + if (is_null) + PG_RETURN_NULL(); + else + PG_RETURN_NUMERIC(res); +} + +#ifdef HAVE_INT128 +static Numeric +numeric_poly_stddev_internal(Int128AggState *state, + bool variance, bool sample, + bool *is_null) +{ + NumericAggState numstate; + Numeric res; + + /* Initialize an empty agg state */ + memset(&numstate, 0, sizeof(NumericAggState)); + + if (state) + { + NumericVar tmp_var; + + numstate.N = state->N; + + init_var(&tmp_var); + + int128_to_numericvar(state->sumX, &tmp_var); + accum_sum_add(&numstate.sumX, &tmp_var); + + int128_to_numericvar(state->sumX2, &tmp_var); + accum_sum_add(&numstate.sumX2, &tmp_var); + + free_var(&tmp_var); + } + + res = numeric_stddev_internal(&numstate, variance, sample, is_null); + + if (numstate.sumX.ndigits > 0) + { + pfree(numstate.sumX.pos_digits); + pfree(numstate.sumX.neg_digits); + } + if (numstate.sumX2.ndigits > 0) + { + pfree(numstate.sumX2.pos_digits); + pfree(numstate.sumX2.neg_digits); + } + + return res; +} +#endif + +Datum +numeric_poly_var_samp(PG_FUNCTION_ARGS) +{ +#ifdef HAVE_INT128 + PolyNumAggState *state; + Numeric res; + bool is_null; + + state = PG_ARGISNULL(0) ? NULL : (PolyNumAggState *) PG_GETARG_POINTER(0); + + res = numeric_poly_stddev_internal(state, true, true, &is_null); + + if (is_null) + PG_RETURN_NULL(); + else + PG_RETURN_NUMERIC(res); +#else + return numeric_var_samp(fcinfo); +#endif +} + +Datum +numeric_poly_stddev_samp(PG_FUNCTION_ARGS) +{ +#ifdef HAVE_INT128 + PolyNumAggState *state; + Numeric res; + bool is_null; + + state = PG_ARGISNULL(0) ? NULL : (PolyNumAggState *) PG_GETARG_POINTER(0); + + res = numeric_poly_stddev_internal(state, false, true, &is_null); + + if (is_null) + PG_RETURN_NULL(); + else + PG_RETURN_NUMERIC(res); +#else + return numeric_stddev_samp(fcinfo); +#endif +} + +Datum +numeric_poly_var_pop(PG_FUNCTION_ARGS) +{ +#ifdef HAVE_INT128 + PolyNumAggState *state; + Numeric res; + bool is_null; + + state = PG_ARGISNULL(0) ? NULL : (PolyNumAggState *) PG_GETARG_POINTER(0); + + res = numeric_poly_stddev_internal(state, true, false, &is_null); + + if (is_null) + PG_RETURN_NULL(); + else + PG_RETURN_NUMERIC(res); +#else + return numeric_var_pop(fcinfo); +#endif +} + +Datum +numeric_poly_stddev_pop(PG_FUNCTION_ARGS) +{ +#ifdef HAVE_INT128 + PolyNumAggState *state; + Numeric res; + bool is_null; + + state = PG_ARGISNULL(0) ? NULL : (PolyNumAggState *) PG_GETARG_POINTER(0); + + res = numeric_poly_stddev_internal(state, false, false, &is_null); + + if (is_null) + PG_RETURN_NULL(); + else + PG_RETURN_NUMERIC(res); +#else + return numeric_stddev_pop(fcinfo); +#endif +} + +/* + * SUM transition functions for integer datatypes. + * + * To avoid overflow, we use accumulators wider than the input datatype. + * A Numeric accumulator is needed for int8 input; for int4 and int2 + * inputs, we use int8 accumulators which should be sufficient for practical + * purposes. (The latter two therefore don't really belong in this file, + * but we keep them here anyway.) + * + * Because SQL defines the SUM() of no values to be NULL, not zero, + * the initial condition of the transition data value needs to be NULL. This + * means we can't rely on ExecAgg to automatically insert the first non-null + * data value into the transition data: it doesn't know how to do the type + * conversion. The upshot is that these routines have to be marked non-strict + * and handle substitution of the first non-null input themselves. + * + * Note: these functions are used only in plain aggregation mode. + * In moving-aggregate mode, we use intX_avg_accum and intX_avg_accum_inv. + */ + +Datum +int2_sum(PG_FUNCTION_ARGS) +{ + int64 newval; + + if (PG_ARGISNULL(0)) + { + /* No non-null input seen so far... */ + if (PG_ARGISNULL(1)) + PG_RETURN_NULL(); /* still no non-null */ + /* This is the first non-null input. */ + newval = (int64) PG_GETARG_INT16(1); + PG_RETURN_INT64(newval); + } + + /* + * If we're invoked as an aggregate, we can cheat and modify our first + * parameter in-place to avoid palloc overhead. If not, we need to return + * the new value of the transition variable. (If int8 is pass-by-value, + * then of course this is useless as well as incorrect, so just ifdef it + * out.) + */ +#ifndef USE_FLOAT8_BYVAL /* controls int8 too */ + if (AggCheckCallContext(fcinfo, NULL)) + { + int64 *oldsum = (int64 *) PG_GETARG_POINTER(0); + + /* Leave the running sum unchanged in the new input is null */ + if (!PG_ARGISNULL(1)) + *oldsum = *oldsum + (int64) PG_GETARG_INT16(1); + + PG_RETURN_POINTER(oldsum); + } + else +#endif + { + int64 oldsum = PG_GETARG_INT64(0); + + /* Leave sum unchanged if new input is null. */ + if (PG_ARGISNULL(1)) + PG_RETURN_INT64(oldsum); + + /* OK to do the addition. */ + newval = oldsum + (int64) PG_GETARG_INT16(1); + + PG_RETURN_INT64(newval); + } +} + +Datum +int4_sum(PG_FUNCTION_ARGS) +{ + int64 newval; + + if (PG_ARGISNULL(0)) + { + /* No non-null input seen so far... */ + if (PG_ARGISNULL(1)) + PG_RETURN_NULL(); /* still no non-null */ + /* This is the first non-null input. */ + newval = (int64) PG_GETARG_INT32(1); + PG_RETURN_INT64(newval); + } + + /* + * If we're invoked as an aggregate, we can cheat and modify our first + * parameter in-place to avoid palloc overhead. If not, we need to return + * the new value of the transition variable. (If int8 is pass-by-value, + * then of course this is useless as well as incorrect, so just ifdef it + * out.) + */ +#ifndef USE_FLOAT8_BYVAL /* controls int8 too */ + if (AggCheckCallContext(fcinfo, NULL)) + { + int64 *oldsum = (int64 *) PG_GETARG_POINTER(0); + + /* Leave the running sum unchanged in the new input is null */ + if (!PG_ARGISNULL(1)) + *oldsum = *oldsum + (int64) PG_GETARG_INT32(1); + + PG_RETURN_POINTER(oldsum); + } + else +#endif + { + int64 oldsum = PG_GETARG_INT64(0); + + /* Leave sum unchanged if new input is null. */ + if (PG_ARGISNULL(1)) + PG_RETURN_INT64(oldsum); + + /* OK to do the addition. */ + newval = oldsum + (int64) PG_GETARG_INT32(1); + + PG_RETURN_INT64(newval); + } +} + +/* + * Note: this function is obsolete, it's no longer used for SUM(int8). + */ +Datum +int8_sum(PG_FUNCTION_ARGS) +{ + Numeric oldsum; + + if (PG_ARGISNULL(0)) + { + /* No non-null input seen so far... */ + if (PG_ARGISNULL(1)) + PG_RETURN_NULL(); /* still no non-null */ + /* This is the first non-null input. */ + PG_RETURN_NUMERIC(int64_to_numeric(PG_GETARG_INT64(1))); + } + + /* + * Note that we cannot special-case the aggregate case here, as we do for + * int2_sum and int4_sum: numeric is of variable size, so we cannot modify + * our first parameter in-place. + */ + + oldsum = PG_GETARG_NUMERIC(0); + + /* Leave sum unchanged if new input is null. */ + if (PG_ARGISNULL(1)) + PG_RETURN_NUMERIC(oldsum); + + /* OK to do the addition. */ + PG_RETURN_DATUM(DirectFunctionCall2(numeric_add, + NumericGetDatum(oldsum), + NumericGetDatum(int64_to_numeric(PG_GETARG_INT64(1))))); +} + + +/* + * Routines for avg(int2) and avg(int4). The transition datatype + * is a two-element int8 array, holding count and sum. + * + * These functions are also used for sum(int2) and sum(int4) when + * operating in moving-aggregate mode, since for correct inverse transitions + * we need to count the inputs. + */ + +typedef struct Int8TransTypeData +{ + int64 count; + int64 sum; +} Int8TransTypeData; + +Datum +int2_avg_accum(PG_FUNCTION_ARGS) +{ + ArrayType *transarray; + int16 newval = PG_GETARG_INT16(1); + Int8TransTypeData *transdata; + + /* + * If we're invoked as an aggregate, we can cheat and modify our first + * parameter in-place to reduce palloc overhead. Otherwise we need to make + * a copy of it before scribbling on it. + */ + if (AggCheckCallContext(fcinfo, NULL)) + transarray = PG_GETARG_ARRAYTYPE_P(0); + else + transarray = PG_GETARG_ARRAYTYPE_P_COPY(0); + + if (ARR_HASNULL(transarray) || + ARR_SIZE(transarray) != ARR_OVERHEAD_NONULLS(1) + sizeof(Int8TransTypeData)) + elog(ERROR, "expected 2-element int8 array"); + + transdata = (Int8TransTypeData *) ARR_DATA_PTR(transarray); + transdata->count++; + transdata->sum += newval; + + PG_RETURN_ARRAYTYPE_P(transarray); +} + +Datum +int4_avg_accum(PG_FUNCTION_ARGS) +{ + ArrayType *transarray; + int32 newval = PG_GETARG_INT32(1); + Int8TransTypeData *transdata; + + /* + * If we're invoked as an aggregate, we can cheat and modify our first + * parameter in-place to reduce palloc overhead. Otherwise we need to make + * a copy of it before scribbling on it. + */ + if (AggCheckCallContext(fcinfo, NULL)) + transarray = PG_GETARG_ARRAYTYPE_P(0); + else + transarray = PG_GETARG_ARRAYTYPE_P_COPY(0); + + if (ARR_HASNULL(transarray) || + ARR_SIZE(transarray) != ARR_OVERHEAD_NONULLS(1) + sizeof(Int8TransTypeData)) + elog(ERROR, "expected 2-element int8 array"); + + transdata = (Int8TransTypeData *) ARR_DATA_PTR(transarray); + transdata->count++; + transdata->sum += newval; + + PG_RETURN_ARRAYTYPE_P(transarray); +} + +Datum +int4_avg_combine(PG_FUNCTION_ARGS) +{ + ArrayType *transarray1; + ArrayType *transarray2; + Int8TransTypeData *state1; + Int8TransTypeData *state2; + + if (!AggCheckCallContext(fcinfo, NULL)) + elog(ERROR, "aggregate function called in non-aggregate context"); + + transarray1 = PG_GETARG_ARRAYTYPE_P(0); + transarray2 = PG_GETARG_ARRAYTYPE_P(1); + + if (ARR_HASNULL(transarray1) || + ARR_SIZE(transarray1) != ARR_OVERHEAD_NONULLS(1) + sizeof(Int8TransTypeData)) + elog(ERROR, "expected 2-element int8 array"); + + if (ARR_HASNULL(transarray2) || + ARR_SIZE(transarray2) != ARR_OVERHEAD_NONULLS(1) + sizeof(Int8TransTypeData)) + elog(ERROR, "expected 2-element int8 array"); + + state1 = (Int8TransTypeData *) ARR_DATA_PTR(transarray1); + state2 = (Int8TransTypeData *) ARR_DATA_PTR(transarray2); + + state1->count += state2->count; + state1->sum += state2->sum; + + PG_RETURN_ARRAYTYPE_P(transarray1); +} + +Datum +int2_avg_accum_inv(PG_FUNCTION_ARGS) +{ + ArrayType *transarray; + int16 newval = PG_GETARG_INT16(1); + Int8TransTypeData *transdata; + + /* + * If we're invoked as an aggregate, we can cheat and modify our first + * parameter in-place to reduce palloc overhead. Otherwise we need to make + * a copy of it before scribbling on it. + */ + if (AggCheckCallContext(fcinfo, NULL)) + transarray = PG_GETARG_ARRAYTYPE_P(0); + else + transarray = PG_GETARG_ARRAYTYPE_P_COPY(0); + + if (ARR_HASNULL(transarray) || + ARR_SIZE(transarray) != ARR_OVERHEAD_NONULLS(1) + sizeof(Int8TransTypeData)) + elog(ERROR, "expected 2-element int8 array"); + + transdata = (Int8TransTypeData *) ARR_DATA_PTR(transarray); + transdata->count--; + transdata->sum -= newval; + + PG_RETURN_ARRAYTYPE_P(transarray); +} + +Datum +int4_avg_accum_inv(PG_FUNCTION_ARGS) +{ + ArrayType *transarray; + int32 newval = PG_GETARG_INT32(1); + Int8TransTypeData *transdata; + + /* + * If we're invoked as an aggregate, we can cheat and modify our first + * parameter in-place to reduce palloc overhead. Otherwise we need to make + * a copy of it before scribbling on it. + */ + if (AggCheckCallContext(fcinfo, NULL)) + transarray = PG_GETARG_ARRAYTYPE_P(0); + else + transarray = PG_GETARG_ARRAYTYPE_P_COPY(0); + + if (ARR_HASNULL(transarray) || + ARR_SIZE(transarray) != ARR_OVERHEAD_NONULLS(1) + sizeof(Int8TransTypeData)) + elog(ERROR, "expected 2-element int8 array"); + + transdata = (Int8TransTypeData *) ARR_DATA_PTR(transarray); + transdata->count--; + transdata->sum -= newval; + + PG_RETURN_ARRAYTYPE_P(transarray); +} + +Datum +int8_avg(PG_FUNCTION_ARGS) +{ + ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0); + Int8TransTypeData *transdata; + Datum countd, + sumd; + + if (ARR_HASNULL(transarray) || + ARR_SIZE(transarray) != ARR_OVERHEAD_NONULLS(1) + sizeof(Int8TransTypeData)) + elog(ERROR, "expected 2-element int8 array"); + transdata = (Int8TransTypeData *) ARR_DATA_PTR(transarray); + + /* SQL defines AVG of no values to be NULL */ + if (transdata->count == 0) + PG_RETURN_NULL(); + + countd = NumericGetDatum(int64_to_numeric(transdata->count)); + sumd = NumericGetDatum(int64_to_numeric(transdata->sum)); + + PG_RETURN_DATUM(DirectFunctionCall2(numeric_div, sumd, countd)); +} + +/* + * SUM(int2) and SUM(int4) both return int8, so we can use this + * final function for both. + */ +Datum +int2int4_sum(PG_FUNCTION_ARGS) +{ + ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0); + Int8TransTypeData *transdata; + + if (ARR_HASNULL(transarray) || + ARR_SIZE(transarray) != ARR_OVERHEAD_NONULLS(1) + sizeof(Int8TransTypeData)) + elog(ERROR, "expected 2-element int8 array"); + transdata = (Int8TransTypeData *) ARR_DATA_PTR(transarray); + + /* SQL defines SUM of no values to be NULL */ + if (transdata->count == 0) + PG_RETURN_NULL(); + + PG_RETURN_DATUM(Int64GetDatumFast(transdata->sum)); +} + + +/* ---------------------------------------------------------------------- + * + * Debug support + * + * ---------------------------------------------------------------------- + */ + +#ifdef NUMERIC_DEBUG + +/* + * dump_numeric() - Dump a value in the db storage format for debugging + */ +static void +dump_numeric(const char *str, Numeric num) +{ + NumericDigit *digits = NUMERIC_DIGITS(num); + int ndigits; + int i; + + ndigits = NUMERIC_NDIGITS(num); + + printf("%s: NUMERIC w=%d d=%d ", str, + NUMERIC_WEIGHT(num), NUMERIC_DSCALE(num)); + switch (NUMERIC_SIGN(num)) + { + case NUMERIC_POS: + printf("POS"); + break; + case NUMERIC_NEG: + printf("NEG"); + break; + case NUMERIC_NAN: + printf("NaN"); + break; + case NUMERIC_PINF: + printf("Infinity"); + break; + case NUMERIC_NINF: + printf("-Infinity"); + break; + default: + printf("SIGN=0x%x", NUMERIC_SIGN(num)); + break; + } + + for (i = 0; i < ndigits; i++) + printf(" %0*d", DEC_DIGITS, digits[i]); + printf("\n"); +} + + +/* + * dump_var() - Dump a value in the variable format for debugging + */ +static void +dump_var(const char *str, NumericVar *var) +{ + int i; + + printf("%s: VAR w=%d d=%d ", str, var->weight, var->dscale); + switch (var->sign) + { + case NUMERIC_POS: + printf("POS"); + break; + case NUMERIC_NEG: + printf("NEG"); + break; + case NUMERIC_NAN: + printf("NaN"); + break; + case NUMERIC_PINF: + printf("Infinity"); + break; + case NUMERIC_NINF: + printf("-Infinity"); + break; + default: + printf("SIGN=0x%x", var->sign); + break; + } + + for (i = 0; i < var->ndigits; i++) + printf(" %0*d", DEC_DIGITS, var->digits[i]); + + printf("\n"); +} +#endif /* NUMERIC_DEBUG */ + + +/* ---------------------------------------------------------------------- + * + * Local functions follow + * + * In general, these do not support "special" (NaN or infinity) inputs; + * callers should handle those possibilities first. + * (There are one or two exceptions, noted in their header comments.) + * + * ---------------------------------------------------------------------- + */ + + +/* + * alloc_var() - + * + * Allocate a digit buffer of ndigits digits (plus a spare digit for rounding) + */ +static void +alloc_var(NumericVar *var, int ndigits) +{ + digitbuf_free(var->buf); + var->buf = digitbuf_alloc(ndigits + 1); + var->buf[0] = 0; /* spare digit for rounding */ + var->digits = var->buf + 1; + var->ndigits = ndigits; +} + + +/* + * free_var() - + * + * Return the digit buffer of a variable to the free pool + */ +static void +free_var(NumericVar *var) +{ + digitbuf_free(var->buf); + var->buf = NULL; + var->digits = NULL; + var->sign = NUMERIC_NAN; +} + + +/* + * zero_var() - + * + * Set a variable to ZERO. + * Note: its dscale is not touched. + */ +static void +zero_var(NumericVar *var) +{ + digitbuf_free(var->buf); + var->buf = NULL; + var->digits = NULL; + var->ndigits = 0; + var->weight = 0; /* by convention; doesn't really matter */ + var->sign = NUMERIC_POS; /* anything but NAN... */ +} + + +/* + * set_var_from_str() + * + * Parse a string and put the number into a variable + * + * This function does not handle leading or trailing spaces. It returns + * the end+1 position parsed into *endptr, so that caller can check for + * trailing spaces/garbage if deemed necessary. + * + * cp is the place to actually start parsing; str is what to use in error + * reports. (Typically cp would be the same except advanced over spaces.) + * + * Returns true on success, false on failure (if escontext points to an + * ErrorSaveContext; otherwise errors are thrown). + */ +static bool +set_var_from_str(const char *str, const char *cp, + NumericVar *dest, const char **endptr, + Node *escontext) +{ + bool have_dp = false; + int i; + unsigned char *decdigits; + int sign = NUMERIC_POS; + int dweight = -1; + int ddigits; + int dscale = 0; + int weight; + int ndigits; + int offset; + NumericDigit *digits; + + /* + * We first parse the string to extract decimal digits and determine the + * correct decimal weight. Then convert to NBASE representation. + */ + switch (*cp) + { + case '+': + sign = NUMERIC_POS; + cp++; + break; + + case '-': + sign = NUMERIC_NEG; + cp++; + break; + } + + if (*cp == '.') + { + have_dp = true; + cp++; + } + + if (!isdigit((unsigned char) *cp)) + goto invalid_syntax; + + decdigits = (unsigned char *) palloc(strlen(cp) + DEC_DIGITS * 2); + + /* leading padding for digit alignment later */ + memset(decdigits, 0, DEC_DIGITS); + i = DEC_DIGITS; + + while (*cp) + { + if (isdigit((unsigned char) *cp)) + { + decdigits[i++] = *cp++ - '0'; + if (!have_dp) + dweight++; + else + dscale++; + } + else if (*cp == '.') + { + if (have_dp) + goto invalid_syntax; + have_dp = true; + cp++; + /* decimal point must not be followed by underscore */ + if (*cp == '_') + goto invalid_syntax; + } + else if (*cp == '_') + { + /* underscore must be followed by more digits */ + cp++; + if (!isdigit((unsigned char) *cp)) + goto invalid_syntax; + } + else + break; + } + + ddigits = i - DEC_DIGITS; + /* trailing padding for digit alignment later */ + memset(decdigits + i, 0, DEC_DIGITS - 1); + + /* Handle exponent, if any */ + if (*cp == 'e' || *cp == 'E') + { + int64 exponent = 0; + bool neg = false; + + /* + * At this point, dweight and dscale can't be more than about + * INT_MAX/2 due to the MaxAllocSize limit on string length, so + * constraining the exponent similarly should be enough to prevent + * integer overflow in this function. If the value is too large to + * fit in storage format, make_result() will complain about it later; + * for consistency use the same ereport errcode/text as make_result(). + */ + + /* exponent sign */ + cp++; + if (*cp == '+') + cp++; + else if (*cp == '-') + { + neg = true; + cp++; + } + + /* exponent digits */ + if (!isdigit((unsigned char) *cp)) + goto invalid_syntax; + + while (*cp) + { + if (isdigit((unsigned char) *cp)) + { + exponent = exponent * 10 + (*cp++ - '0'); + if (exponent > PG_INT32_MAX / 2) + goto out_of_range; + } + else if (*cp == '_') + { + /* underscore must be followed by more digits */ + cp++; + if (!isdigit((unsigned char) *cp)) + goto invalid_syntax; + } + else + break; + } + + if (neg) + exponent = -exponent; + + dweight += (int) exponent; + dscale -= (int) exponent; + if (dscale < 0) + dscale = 0; + } + + /* + * Okay, convert pure-decimal representation to base NBASE. First we need + * to determine the converted weight and ndigits. offset is the number of + * decimal zeroes to insert before the first given digit to have a + * correctly aligned first NBASE digit. + */ + if (dweight >= 0) + weight = (dweight + 1 + DEC_DIGITS - 1) / DEC_DIGITS - 1; + else + weight = -((-dweight - 1) / DEC_DIGITS + 1); + offset = (weight + 1) * DEC_DIGITS - (dweight + 1); + ndigits = (ddigits + offset + DEC_DIGITS - 1) / DEC_DIGITS; + + alloc_var(dest, ndigits); + dest->sign = sign; + dest->weight = weight; + dest->dscale = dscale; + + i = DEC_DIGITS - offset; + digits = dest->digits; + + while (ndigits-- > 0) + { +#if DEC_DIGITS == 4 + *digits++ = ((decdigits[i] * 10 + decdigits[i + 1]) * 10 + + decdigits[i + 2]) * 10 + decdigits[i + 3]; +#elif DEC_DIGITS == 2 + *digits++ = decdigits[i] * 10 + decdigits[i + 1]; +#elif DEC_DIGITS == 1 + *digits++ = decdigits[i]; +#else +#error unsupported NBASE +#endif + i += DEC_DIGITS; + } + + pfree(decdigits); + + /* Strip any leading/trailing zeroes, and normalize weight if zero */ + strip_var(dest); + + /* Return end+1 position for caller */ + *endptr = cp; + + return true; + +out_of_range: + ereturn(escontext, false, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("value overflows numeric format"))); + +invalid_syntax: + ereturn(escontext, false, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s: \"%s\"", + "numeric", str))); +} + + +/* + * Return the numeric value of a single hex digit. + */ +static inline int +xdigit_value(char dig) +{ + return dig >= '0' && dig <= '9' ? dig - '0' : + dig >= 'a' && dig <= 'f' ? dig - 'a' + 10 : + dig >= 'A' && dig <= 'F' ? dig - 'A' + 10 : -1; +} + +/* + * set_var_from_non_decimal_integer_str() + * + * Parse a string containing a non-decimal integer + * + * This function does not handle leading or trailing spaces. It returns + * the end+1 position parsed into *endptr, so that caller can check for + * trailing spaces/garbage if deemed necessary. + * + * cp is the place to actually start parsing; str is what to use in error + * reports. The number's sign and base prefix indicator (e.g., "0x") are + * assumed to have already been parsed, so cp should point to the number's + * first digit in the base specified. + * + * base is expected to be 2, 8 or 16. + * + * Returns true on success, false on failure (if escontext points to an + * ErrorSaveContext; otherwise errors are thrown). + */ +static bool +set_var_from_non_decimal_integer_str(const char *str, const char *cp, int sign, + int base, NumericVar *dest, + const char **endptr, Node *escontext) +{ + const char *firstdigit = cp; + int64 tmp; + int64 mul; + NumericVar tmp_var; + + init_var(&tmp_var); + + zero_var(dest); + + /* + * Process input digits in groups that fit in int64. Here "tmp" is the + * value of the digits in the group, and "mul" is base^n, where n is the + * number of digits in the group. Thus tmp < mul, and we must start a new + * group when mul * base threatens to overflow PG_INT64_MAX. + */ + tmp = 0; + mul = 1; + + if (base == 16) + { + while (*cp) + { + if (isxdigit((unsigned char) *cp)) + { + if (mul > PG_INT64_MAX / 16) + { + /* Add the contribution from this group of digits */ + int64_to_numericvar(mul, &tmp_var); + mul_var(dest, &tmp_var, dest, 0); + int64_to_numericvar(tmp, &tmp_var); + add_var(dest, &tmp_var, dest); + + /* Result will overflow if weight overflows int16 */ + if (dest->weight > SHRT_MAX) + goto out_of_range; + + /* Begin a new group */ + tmp = 0; + mul = 1; + } + + tmp = tmp * 16 + xdigit_value(*cp++); + mul = mul * 16; + } + else if (*cp == '_') + { + /* Underscore must be followed by more digits */ + cp++; + if (!isxdigit((unsigned char) *cp)) + goto invalid_syntax; + } + else + break; + } + } + else if (base == 8) + { + while (*cp) + { + if (*cp >= '0' && *cp <= '7') + { + if (mul > PG_INT64_MAX / 8) + { + /* Add the contribution from this group of digits */ + int64_to_numericvar(mul, &tmp_var); + mul_var(dest, &tmp_var, dest, 0); + int64_to_numericvar(tmp, &tmp_var); + add_var(dest, &tmp_var, dest); + + /* Result will overflow if weight overflows int16 */ + if (dest->weight > SHRT_MAX) + goto out_of_range; + + /* Begin a new group */ + tmp = 0; + mul = 1; + } + + tmp = tmp * 8 + (*cp++ - '0'); + mul = mul * 8; + } + else if (*cp == '_') + { + /* Underscore must be followed by more digits */ + cp++; + if (*cp < '0' || *cp > '7') + goto invalid_syntax; + } + else + break; + } + } + else if (base == 2) + { + while (*cp) + { + if (*cp >= '0' && *cp <= '1') + { + if (mul > PG_INT64_MAX / 2) + { + /* Add the contribution from this group of digits */ + int64_to_numericvar(mul, &tmp_var); + mul_var(dest, &tmp_var, dest, 0); + int64_to_numericvar(tmp, &tmp_var); + add_var(dest, &tmp_var, dest); + + /* Result will overflow if weight overflows int16 */ + if (dest->weight > SHRT_MAX) + goto out_of_range; + + /* Begin a new group */ + tmp = 0; + mul = 1; + } + + tmp = tmp * 2 + (*cp++ - '0'); + mul = mul * 2; + } + else if (*cp == '_') + { + /* Underscore must be followed by more digits */ + cp++; + if (*cp < '0' || *cp > '1') + goto invalid_syntax; + } + else + break; + } + } + else + /* Should never happen; treat as invalid input */ + goto invalid_syntax; + + /* Check that we got at least one digit */ + if (unlikely(cp == firstdigit)) + goto invalid_syntax; + + /* Add the contribution from the final group of digits */ + int64_to_numericvar(mul, &tmp_var); + mul_var(dest, &tmp_var, dest, 0); + int64_to_numericvar(tmp, &tmp_var); + add_var(dest, &tmp_var, dest); + + if (dest->weight > SHRT_MAX) + goto out_of_range; + + dest->sign = sign; + + free_var(&tmp_var); + + /* Return end+1 position for caller */ + *endptr = cp; + + return true; + +out_of_range: + ereturn(escontext, false, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("value overflows numeric format"))); + +invalid_syntax: + ereturn(escontext, false, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s: \"%s\"", + "numeric", str))); +} + + +/* + * set_var_from_num() - + * + * Convert the packed db format into a variable + */ +static void +set_var_from_num(Numeric num, NumericVar *dest) +{ + int ndigits; + + ndigits = NUMERIC_NDIGITS(num); + + alloc_var(dest, ndigits); + + dest->weight = NUMERIC_WEIGHT(num); + dest->sign = NUMERIC_SIGN(num); + dest->dscale = NUMERIC_DSCALE(num); + + memcpy(dest->digits, NUMERIC_DIGITS(num), ndigits * sizeof(NumericDigit)); +} + + +/* + * init_var_from_num() - + * + * Initialize a variable from packed db format. The digits array is not + * copied, which saves some cycles when the resulting var is not modified. + * Also, there's no need to call free_var(), as long as you don't assign any + * other value to it (with set_var_* functions, or by using the var as the + * destination of a function like add_var()) + * + * CAUTION: Do not modify the digits buffer of a var initialized with this + * function, e.g by calling round_var() or trunc_var(), as the changes will + * propagate to the original Numeric! It's OK to use it as the destination + * argument of one of the calculational functions, though. + */ +static void +init_var_from_num(Numeric num, NumericVar *dest) +{ + dest->ndigits = NUMERIC_NDIGITS(num); + dest->weight = NUMERIC_WEIGHT(num); + dest->sign = NUMERIC_SIGN(num); + dest->dscale = NUMERIC_DSCALE(num); + dest->digits = NUMERIC_DIGITS(num); + dest->buf = NULL; /* digits array is not palloc'd */ +} + + +/* + * set_var_from_var() - + * + * Copy one variable into another + */ +static void +set_var_from_var(const NumericVar *value, NumericVar *dest) +{ + NumericDigit *newbuf; + + newbuf = digitbuf_alloc(value->ndigits + 1); + newbuf[0] = 0; /* spare digit for rounding */ + if (value->ndigits > 0) /* else value->digits might be null */ + memcpy(newbuf + 1, value->digits, + value->ndigits * sizeof(NumericDigit)); + + digitbuf_free(dest->buf); + + memmove(dest, value, sizeof(NumericVar)); + dest->buf = newbuf; + dest->digits = newbuf + 1; +} + + +/* + * get_str_from_var() - + * + * Convert a var to text representation (guts of numeric_out). + * The var is displayed to the number of digits indicated by its dscale. + * Returns a palloc'd string. + */ +static char * +get_str_from_var(const NumericVar *var) +{ + int dscale; + char *str; + char *cp; + char *endcp; + int i; + int d; + NumericDigit dig; + +#if DEC_DIGITS > 1 + NumericDigit d1; +#endif + + dscale = var->dscale; + + /* + * Allocate space for the result. + * + * i is set to the # of decimal digits before decimal point. dscale is the + * # of decimal digits we will print after decimal point. We may generate + * as many as DEC_DIGITS-1 excess digits at the end, and in addition we + * need room for sign, decimal point, null terminator. + */ + i = (var->weight + 1) * DEC_DIGITS; + if (i <= 0) + i = 1; + + str = palloc(i + dscale + DEC_DIGITS + 2); + cp = str; + + /* + * Output a dash for negative values + */ + if (var->sign == NUMERIC_NEG) + *cp++ = '-'; + + /* + * Output all digits before the decimal point + */ + if (var->weight < 0) + { + d = var->weight + 1; + *cp++ = '0'; + } + else + { + for (d = 0; d <= var->weight; d++) + { + dig = (d < var->ndigits) ? var->digits[d] : 0; + /* In the first digit, suppress extra leading decimal zeroes */ +#if DEC_DIGITS == 4 + { + bool putit = (d > 0); + + d1 = dig / 1000; + dig -= d1 * 1000; + putit |= (d1 > 0); + if (putit) + *cp++ = d1 + '0'; + d1 = dig / 100; + dig -= d1 * 100; + putit |= (d1 > 0); + if (putit) + *cp++ = d1 + '0'; + d1 = dig / 10; + dig -= d1 * 10; + putit |= (d1 > 0); + if (putit) + *cp++ = d1 + '0'; + *cp++ = dig + '0'; + } +#elif DEC_DIGITS == 2 + d1 = dig / 10; + dig -= d1 * 10; + if (d1 > 0 || d > 0) + *cp++ = d1 + '0'; + *cp++ = dig + '0'; +#elif DEC_DIGITS == 1 + *cp++ = dig + '0'; +#else +#error unsupported NBASE +#endif + } + } + + /* + * If requested, output a decimal point and all the digits that follow it. + * We initially put out a multiple of DEC_DIGITS digits, then truncate if + * needed. + */ + if (dscale > 0) + { + *cp++ = '.'; + endcp = cp + dscale; + for (i = 0; i < dscale; d++, i += DEC_DIGITS) + { + dig = (d >= 0 && d < var->ndigits) ? var->digits[d] : 0; +#if DEC_DIGITS == 4 + d1 = dig / 1000; + dig -= d1 * 1000; + *cp++ = d1 + '0'; + d1 = dig / 100; + dig -= d1 * 100; + *cp++ = d1 + '0'; + d1 = dig / 10; + dig -= d1 * 10; + *cp++ = d1 + '0'; + *cp++ = dig + '0'; +#elif DEC_DIGITS == 2 + d1 = dig / 10; + dig -= d1 * 10; + *cp++ = d1 + '0'; + *cp++ = dig + '0'; +#elif DEC_DIGITS == 1 + *cp++ = dig + '0'; +#else +#error unsupported NBASE +#endif + } + cp = endcp; + } + + /* + * terminate the string and return it + */ + *cp = '\0'; + return str; +} + +/* + * get_str_from_var_sci() - + * + * Convert a var to a normalised scientific notation text representation. + * This function does the heavy lifting for numeric_out_sci(). + * + * This notation has the general form a * 10^b, where a is known as the + * "significand" and b is known as the "exponent". + * + * Because we can't do superscript in ASCII (and because we want to copy + * printf's behaviour) we display the exponent using E notation, with a + * minimum of two exponent digits. + * + * For example, the value 1234 could be output as 1.2e+03. + * + * We assume that the exponent can fit into an int32. + * + * rscale is the number of decimal digits desired after the decimal point in + * the output, negative values will be treated as meaning zero. + * + * Returns a palloc'd string. + */ +static char * +get_str_from_var_sci(const NumericVar *var, int rscale) +{ + int32 exponent; + NumericVar tmp_var; + size_t len; + char *str; + char *sig_out; + + if (rscale < 0) + rscale = 0; + + /* + * Determine the exponent of this number in normalised form. + * + * This is the exponent required to represent the number with only one + * significant digit before the decimal place. + */ + if (var->ndigits > 0) + { + exponent = (var->weight + 1) * DEC_DIGITS; + + /* + * Compensate for leading decimal zeroes in the first numeric digit by + * decrementing the exponent. + */ + exponent -= DEC_DIGITS - (int) log10(var->digits[0]); + } + else + { + /* + * If var has no digits, then it must be zero. + * + * Zero doesn't technically have a meaningful exponent in normalised + * notation, but we just display the exponent as zero for consistency + * of output. + */ + exponent = 0; + } + + /* + * Divide var by 10^exponent to get the significand, rounding to rscale + * decimal digits in the process. + */ + init_var(&tmp_var); + + power_ten_int(exponent, &tmp_var); + div_var(var, &tmp_var, &tmp_var, rscale, true); + sig_out = get_str_from_var(&tmp_var); + + free_var(&tmp_var); + + /* + * Allocate space for the result. + * + * In addition to the significand, we need room for the exponent + * decoration ("e"), the sign of the exponent, up to 10 digits for the + * exponent itself, and of course the null terminator. + */ + len = strlen(sig_out) + 13; + str = palloc(len); + snprintf(str, len, "%se%+03d", sig_out, exponent); + + pfree(sig_out); + + return str; +} + + +/* + * numericvar_serialize - serialize NumericVar to binary format + * + * At variable level, no checks are performed on the weight or dscale, allowing + * us to pass around intermediate values with higher precision than supported + * by the numeric type. Note: this is incompatible with numeric_send/recv(), + * which use 16-bit integers for these fields. + */ +static void +numericvar_serialize(StringInfo buf, const NumericVar *var) +{ + int i; + + pq_sendint32(buf, var->ndigits); + pq_sendint32(buf, var->weight); + pq_sendint32(buf, var->sign); + pq_sendint32(buf, var->dscale); + for (i = 0; i < var->ndigits; i++) + pq_sendint16(buf, var->digits[i]); +} + +/* + * numericvar_deserialize - deserialize binary format to NumericVar + */ +static void +numericvar_deserialize(StringInfo buf, NumericVar *var) +{ + int len, + i; + + len = pq_getmsgint(buf, sizeof(int32)); + + alloc_var(var, len); /* sets var->ndigits */ + + var->weight = pq_getmsgint(buf, sizeof(int32)); + var->sign = pq_getmsgint(buf, sizeof(int32)); + var->dscale = pq_getmsgint(buf, sizeof(int32)); + for (i = 0; i < len; i++) + var->digits[i] = pq_getmsgint(buf, sizeof(int16)); +} + + +/* + * duplicate_numeric() - copy a packed-format Numeric + * + * This will handle NaN and Infinity cases. + */ +static Numeric +duplicate_numeric(Numeric num) +{ + Numeric res; + + res = (Numeric) palloc(VARSIZE(num)); + memcpy(res, num, VARSIZE(num)); + return res; +} + +/* + * make_result_opt_error() - + * + * Create the packed db numeric format in palloc()'d memory from + * a variable. This will handle NaN and Infinity cases. + * + * If "have_error" isn't NULL, on overflow *have_error is set to true and + * NULL is returned. This is helpful when caller needs to handle errors. + */ +static Numeric +make_result_opt_error(const NumericVar *var, bool *have_error) +{ + Numeric result; + NumericDigit *digits = var->digits; + int weight = var->weight; + int sign = var->sign; + int n; + Size len; + + if (have_error) + *have_error = false; + + if ((sign & NUMERIC_SIGN_MASK) == NUMERIC_SPECIAL) + { + /* + * Verify valid special value. This could be just an Assert, perhaps, + * but it seems worthwhile to expend a few cycles to ensure that we + * never write any nonzero reserved bits to disk. + */ + if (!(sign == NUMERIC_NAN || + sign == NUMERIC_PINF || + sign == NUMERIC_NINF)) + elog(ERROR, "invalid numeric sign value 0x%x", sign); + + result = (Numeric) palloc(NUMERIC_HDRSZ_SHORT); + + SET_VARSIZE(result, NUMERIC_HDRSZ_SHORT); + result->choice.n_header = sign; + /* the header word is all we need */ + + dump_numeric("make_result()", result); + return result; + } + + n = var->ndigits; + + /* truncate leading zeroes */ + while (n > 0 && *digits == 0) + { + digits++; + weight--; + n--; + } + /* truncate trailing zeroes */ + while (n > 0 && digits[n - 1] == 0) + n--; + + /* If zero result, force to weight=0 and positive sign */ + if (n == 0) + { + weight = 0; + sign = NUMERIC_POS; + } + + /* Build the result */ + if (NUMERIC_CAN_BE_SHORT(var->dscale, weight)) + { + len = NUMERIC_HDRSZ_SHORT + n * sizeof(NumericDigit); + result = (Numeric) palloc(len); + SET_VARSIZE(result, len); + result->choice.n_short.n_header = + (sign == NUMERIC_NEG ? (NUMERIC_SHORT | NUMERIC_SHORT_SIGN_MASK) + : NUMERIC_SHORT) + | (var->dscale << NUMERIC_SHORT_DSCALE_SHIFT) + | (weight < 0 ? NUMERIC_SHORT_WEIGHT_SIGN_MASK : 0) + | (weight & NUMERIC_SHORT_WEIGHT_MASK); + } + else + { + len = NUMERIC_HDRSZ + n * sizeof(NumericDigit); + result = (Numeric) palloc(len); + SET_VARSIZE(result, len); + result->choice.n_long.n_sign_dscale = + sign | (var->dscale & NUMERIC_DSCALE_MASK); + result->choice.n_long.n_weight = weight; + } + + Assert(NUMERIC_NDIGITS(result) == n); + if (n > 0) + memcpy(NUMERIC_DIGITS(result), digits, n * sizeof(NumericDigit)); + + /* Check for overflow of int16 fields */ + if (NUMERIC_WEIGHT(result) != weight || + NUMERIC_DSCALE(result) != var->dscale) + { + if (have_error) + { + *have_error = true; + return NULL; + } + else + { + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("value overflows numeric format"))); + } + } + + dump_numeric("make_result()", result); + return result; +} + + +/* + * make_result() - + * + * An interface to make_result_opt_error() without "have_error" argument. + */ +static Numeric +make_result(const NumericVar *var) +{ + return make_result_opt_error(var, NULL); +} + + +/* + * apply_typmod() - + * + * Do bounds checking and rounding according to the specified typmod. + * Note that this is only applied to normal finite values. + * + * Returns true on success, false on failure (if escontext points to an + * ErrorSaveContext; otherwise errors are thrown). + */ +static bool +apply_typmod(NumericVar *var, int32 typmod, Node *escontext) +{ + int precision; + int scale; + int maxdigits; + int ddigits; + int i; + + /* Do nothing if we have an invalid typmod */ + if (!is_valid_numeric_typmod(typmod)) + return true; + + precision = numeric_typmod_precision(typmod); + scale = numeric_typmod_scale(typmod); + maxdigits = precision - scale; + + /* Round to target scale (and set var->dscale) */ + round_var(var, scale); + + /* but don't allow var->dscale to be negative */ + if (var->dscale < 0) + var->dscale = 0; + + /* + * Check for overflow - note we can't do this before rounding, because + * rounding could raise the weight. Also note that the var's weight could + * be inflated by leading zeroes, which will be stripped before storage + * but perhaps might not have been yet. In any case, we must recognize a + * true zero, whose weight doesn't mean anything. + */ + ddigits = (var->weight + 1) * DEC_DIGITS; + if (ddigits > maxdigits) + { + /* Determine true weight; and check for all-zero result */ + for (i = 0; i < var->ndigits; i++) + { + NumericDigit dig = var->digits[i]; + + if (dig) + { + /* Adjust for any high-order decimal zero digits */ +#if DEC_DIGITS == 4 + if (dig < 10) + ddigits -= 3; + else if (dig < 100) + ddigits -= 2; + else if (dig < 1000) + ddigits -= 1; +#elif DEC_DIGITS == 2 + if (dig < 10) + ddigits -= 1; +#elif DEC_DIGITS == 1 + /* no adjustment */ +#else +#error unsupported NBASE +#endif + if (ddigits > maxdigits) + ereturn(escontext, false, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("numeric field overflow"), + errdetail("A field with precision %d, scale %d must round to an absolute value less than %s%d.", + precision, scale, + /* Display 10^0 as 1 */ + maxdigits ? "10^" : "", + maxdigits ? maxdigits : 1 + ))); + break; + } + ddigits -= DEC_DIGITS; + } + } + + return true; +} + +/* + * apply_typmod_special() - + * + * Do bounds checking according to the specified typmod, for an Inf or NaN. + * For convenience of most callers, the value is presented in packed form. + * + * Returns true on success, false on failure (if escontext points to an + * ErrorSaveContext; otherwise errors are thrown). + */ +static bool +apply_typmod_special(Numeric num, int32 typmod, Node *escontext) +{ + int precision; + int scale; + + Assert(NUMERIC_IS_SPECIAL(num)); /* caller error if not */ + + /* + * NaN is allowed regardless of the typmod; that's rather dubious perhaps, + * but it's a longstanding behavior. Inf is rejected if we have any + * typmod restriction, since an infinity shouldn't be claimed to fit in + * any finite number of digits. + */ + if (NUMERIC_IS_NAN(num)) + return true; + + /* Do nothing if we have a default typmod (-1) */ + if (!is_valid_numeric_typmod(typmod)) + return true; + + precision = numeric_typmod_precision(typmod); + scale = numeric_typmod_scale(typmod); + + ereturn(escontext, false, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("numeric field overflow"), + errdetail("A field with precision %d, scale %d cannot hold an infinite value.", + precision, scale))); +} + + +/* + * Convert numeric to int8, rounding if needed. + * + * If overflow, return false (no error is raised). Return true if okay. + */ +static bool +numericvar_to_int64(const NumericVar *var, int64 *result) +{ + NumericDigit *digits; + int ndigits; + int weight; + int i; + int64 val; + bool neg; + NumericVar rounded; + + /* Round to nearest integer */ + init_var(&rounded); + set_var_from_var(var, &rounded); + round_var(&rounded, 0); + + /* Check for zero input */ + strip_var(&rounded); + ndigits = rounded.ndigits; + if (ndigits == 0) + { + *result = 0; + free_var(&rounded); + return true; + } + + /* + * For input like 10000000000, we must treat stripped digits as real. So + * the loop assumes there are weight+1 digits before the decimal point. + */ + weight = rounded.weight; + Assert(weight >= 0 && ndigits <= weight + 1); + + /* + * Construct the result. To avoid issues with converting a value + * corresponding to INT64_MIN (which can't be represented as a positive 64 + * bit two's complement integer), accumulate value as a negative number. + */ + digits = rounded.digits; + neg = (rounded.sign == NUMERIC_NEG); + val = -digits[0]; + for (i = 1; i <= weight; i++) + { + if (unlikely(pg_mul_s64_overflow(val, NBASE, &val))) + { + free_var(&rounded); + return false; + } + + if (i < ndigits) + { + if (unlikely(pg_sub_s64_overflow(val, digits[i], &val))) + { + free_var(&rounded); + return false; + } + } + } + + free_var(&rounded); + + if (!neg) + { + if (unlikely(val == PG_INT64_MIN)) + return false; + val = -val; + } + *result = val; + + return true; +} + +/* + * Convert int8 value to numeric. + */ +static void +int64_to_numericvar(int64 val, NumericVar *var) +{ + uint64 uval, + newuval; + NumericDigit *ptr; + int ndigits; + + /* int64 can require at most 19 decimal digits; add one for safety */ + alloc_var(var, 20 / DEC_DIGITS); + if (val < 0) + { + var->sign = NUMERIC_NEG; + uval = -val; + } + else + { + var->sign = NUMERIC_POS; + uval = val; + } + var->dscale = 0; + if (val == 0) + { + var->ndigits = 0; + var->weight = 0; + return; + } + ptr = var->digits + var->ndigits; + ndigits = 0; + do + { + ptr--; + ndigits++; + newuval = uval / NBASE; + *ptr = uval - newuval * NBASE; + uval = newuval; + } while (uval); + var->digits = ptr; + var->ndigits = ndigits; + var->weight = ndigits - 1; +} + +/* + * Convert numeric to uint64, rounding if needed. + * + * If overflow, return false (no error is raised). Return true if okay. + */ +static bool +numericvar_to_uint64(const NumericVar *var, uint64 *result) +{ + NumericDigit *digits; + int ndigits; + int weight; + int i; + uint64 val; + NumericVar rounded; + + /* Round to nearest integer */ + init_var(&rounded); + set_var_from_var(var, &rounded); + round_var(&rounded, 0); + + /* Check for zero input */ + strip_var(&rounded); + ndigits = rounded.ndigits; + if (ndigits == 0) + { + *result = 0; + free_var(&rounded); + return true; + } + + /* Check for negative input */ + if (rounded.sign == NUMERIC_NEG) + { + free_var(&rounded); + return false; + } + + /* + * For input like 10000000000, we must treat stripped digits as real. So + * the loop assumes there are weight+1 digits before the decimal point. + */ + weight = rounded.weight; + Assert(weight >= 0 && ndigits <= weight + 1); + + /* Construct the result */ + digits = rounded.digits; + val = digits[0]; + for (i = 1; i <= weight; i++) + { + if (unlikely(pg_mul_u64_overflow(val, NBASE, &val))) + { + free_var(&rounded); + return false; + } + + if (i < ndigits) + { + if (unlikely(pg_add_u64_overflow(val, digits[i], &val))) + { + free_var(&rounded); + return false; + } + } + } + + free_var(&rounded); + + *result = val; + + return true; +} + +#ifdef HAVE_INT128 +/* + * Convert numeric to int128, rounding if needed. + * + * If overflow, return false (no error is raised). Return true if okay. + */ +static bool +numericvar_to_int128(const NumericVar *var, int128 *result) +{ + NumericDigit *digits; + int ndigits; + int weight; + int i; + int128 val, + oldval; + bool neg; + NumericVar rounded; + + /* Round to nearest integer */ + init_var(&rounded); + set_var_from_var(var, &rounded); + round_var(&rounded, 0); + + /* Check for zero input */ + strip_var(&rounded); + ndigits = rounded.ndigits; + if (ndigits == 0) + { + *result = 0; + free_var(&rounded); + return true; + } + + /* + * For input like 10000000000, we must treat stripped digits as real. So + * the loop assumes there are weight+1 digits before the decimal point. + */ + weight = rounded.weight; + Assert(weight >= 0 && ndigits <= weight + 1); + + /* Construct the result */ + digits = rounded.digits; + neg = (rounded.sign == NUMERIC_NEG); + val = digits[0]; + for (i = 1; i <= weight; i++) + { + oldval = val; + val *= NBASE; + if (i < ndigits) + val += digits[i]; + + /* + * The overflow check is a bit tricky because we want to accept + * INT128_MIN, which will overflow the positive accumulator. We can + * detect this case easily though because INT128_MIN is the only + * nonzero value for which -val == val (on a two's complement machine, + * anyway). + */ + if ((val / NBASE) != oldval) /* possible overflow? */ + { + if (!neg || (-val) != val || val == 0 || oldval < 0) + { + free_var(&rounded); + return false; + } + } + } + + free_var(&rounded); + + *result = neg ? -val : val; + return true; +} + +/* + * Convert 128 bit integer to numeric. + */ +static void +int128_to_numericvar(int128 val, NumericVar *var) +{ + uint128 uval, + newuval; + NumericDigit *ptr; + int ndigits; + + /* int128 can require at most 39 decimal digits; add one for safety */ + alloc_var(var, 40 / DEC_DIGITS); + if (val < 0) + { + var->sign = NUMERIC_NEG; + uval = -val; + } + else + { + var->sign = NUMERIC_POS; + uval = val; + } + var->dscale = 0; + if (val == 0) + { + var->ndigits = 0; + var->weight = 0; + return; + } + ptr = var->digits + var->ndigits; + ndigits = 0; + do + { + ptr--; + ndigits++; + newuval = uval / NBASE; + *ptr = uval - newuval * NBASE; + uval = newuval; + } while (uval); + var->digits = ptr; + var->ndigits = ndigits; + var->weight = ndigits - 1; +} +#endif + +/* + * Convert a NumericVar to float8; if out of range, return +/- HUGE_VAL + */ +static double +numericvar_to_double_no_overflow(const NumericVar *var) +{ + char *tmp; + double val; + char *endptr; + + tmp = get_str_from_var(var); + + /* unlike float8in, we ignore ERANGE from strtod */ + val = strtod(tmp, &endptr); + if (*endptr != '\0') + { + /* shouldn't happen ... */ + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s: \"%s\"", + "double precision", tmp))); + } + + pfree(tmp); + + return val; +} + + +/* + * cmp_var() - + * + * Compare two values on variable level. We assume zeroes have been + * truncated to no digits. + */ +static int +cmp_var(const NumericVar *var1, const NumericVar *var2) +{ + return cmp_var_common(var1->digits, var1->ndigits, + var1->weight, var1->sign, + var2->digits, var2->ndigits, + var2->weight, var2->sign); +} + +/* + * cmp_var_common() - + * + * Main routine of cmp_var(). This function can be used by both + * NumericVar and Numeric. + */ +static int +cmp_var_common(const NumericDigit *var1digits, int var1ndigits, + int var1weight, int var1sign, + const NumericDigit *var2digits, int var2ndigits, + int var2weight, int var2sign) +{ + if (var1ndigits == 0) + { + if (var2ndigits == 0) + return 0; + if (var2sign == NUMERIC_NEG) + return 1; + return -1; + } + if (var2ndigits == 0) + { + if (var1sign == NUMERIC_POS) + return 1; + return -1; + } + + if (var1sign == NUMERIC_POS) + { + if (var2sign == NUMERIC_NEG) + return 1; + return cmp_abs_common(var1digits, var1ndigits, var1weight, + var2digits, var2ndigits, var2weight); + } + + if (var2sign == NUMERIC_POS) + return -1; + + return cmp_abs_common(var2digits, var2ndigits, var2weight, + var1digits, var1ndigits, var1weight); +} + + +/* + * add_var() - + * + * Full version of add functionality on variable level (handling signs). + * result might point to one of the operands too without danger. + */ +static void +add_var(const NumericVar *var1, const NumericVar *var2, NumericVar *result) +{ + /* + * Decide on the signs of the two variables what to do + */ + if (var1->sign == NUMERIC_POS) + { + if (var2->sign == NUMERIC_POS) + { + /* + * Both are positive result = +(ABS(var1) + ABS(var2)) + */ + add_abs(var1, var2, result); + result->sign = NUMERIC_POS; + } + else + { + /* + * var1 is positive, var2 is negative Must compare absolute values + */ + switch (cmp_abs(var1, var2)) + { + case 0: + /* ---------- + * ABS(var1) == ABS(var2) + * result = ZERO + * ---------- + */ + zero_var(result); + result->dscale = Max(var1->dscale, var2->dscale); + break; + + case 1: + /* ---------- + * ABS(var1) > ABS(var2) + * result = +(ABS(var1) - ABS(var2)) + * ---------- + */ + sub_abs(var1, var2, result); + result->sign = NUMERIC_POS; + break; + + case -1: + /* ---------- + * ABS(var1) < ABS(var2) + * result = -(ABS(var2) - ABS(var1)) + * ---------- + */ + sub_abs(var2, var1, result); + result->sign = NUMERIC_NEG; + break; + } + } + } + else + { + if (var2->sign == NUMERIC_POS) + { + /* ---------- + * var1 is negative, var2 is positive + * Must compare absolute values + * ---------- + */ + switch (cmp_abs(var1, var2)) + { + case 0: + /* ---------- + * ABS(var1) == ABS(var2) + * result = ZERO + * ---------- + */ + zero_var(result); + result->dscale = Max(var1->dscale, var2->dscale); + break; + + case 1: + /* ---------- + * ABS(var1) > ABS(var2) + * result = -(ABS(var1) - ABS(var2)) + * ---------- + */ + sub_abs(var1, var2, result); + result->sign = NUMERIC_NEG; + break; + + case -1: + /* ---------- + * ABS(var1) < ABS(var2) + * result = +(ABS(var2) - ABS(var1)) + * ---------- + */ + sub_abs(var2, var1, result); + result->sign = NUMERIC_POS; + break; + } + } + else + { + /* ---------- + * Both are negative + * result = -(ABS(var1) + ABS(var2)) + * ---------- + */ + add_abs(var1, var2, result); + result->sign = NUMERIC_NEG; + } + } +} + + +/* + * sub_var() - + * + * Full version of sub functionality on variable level (handling signs). + * result might point to one of the operands too without danger. + */ +static void +sub_var(const NumericVar *var1, const NumericVar *var2, NumericVar *result) +{ + /* + * Decide on the signs of the two variables what to do + */ + if (var1->sign == NUMERIC_POS) + { + if (var2->sign == NUMERIC_NEG) + { + /* ---------- + * var1 is positive, var2 is negative + * result = +(ABS(var1) + ABS(var2)) + * ---------- + */ + add_abs(var1, var2, result); + result->sign = NUMERIC_POS; + } + else + { + /* ---------- + * Both are positive + * Must compare absolute values + * ---------- + */ + switch (cmp_abs(var1, var2)) + { + case 0: + /* ---------- + * ABS(var1) == ABS(var2) + * result = ZERO + * ---------- + */ + zero_var(result); + result->dscale = Max(var1->dscale, var2->dscale); + break; + + case 1: + /* ---------- + * ABS(var1) > ABS(var2) + * result = +(ABS(var1) - ABS(var2)) + * ---------- + */ + sub_abs(var1, var2, result); + result->sign = NUMERIC_POS; + break; + + case -1: + /* ---------- + * ABS(var1) < ABS(var2) + * result = -(ABS(var2) - ABS(var1)) + * ---------- + */ + sub_abs(var2, var1, result); + result->sign = NUMERIC_NEG; + break; + } + } + } + else + { + if (var2->sign == NUMERIC_NEG) + { + /* ---------- + * Both are negative + * Must compare absolute values + * ---------- + */ + switch (cmp_abs(var1, var2)) + { + case 0: + /* ---------- + * ABS(var1) == ABS(var2) + * result = ZERO + * ---------- + */ + zero_var(result); + result->dscale = Max(var1->dscale, var2->dscale); + break; + + case 1: + /* ---------- + * ABS(var1) > ABS(var2) + * result = -(ABS(var1) - ABS(var2)) + * ---------- + */ + sub_abs(var1, var2, result); + result->sign = NUMERIC_NEG; + break; + + case -1: + /* ---------- + * ABS(var1) < ABS(var2) + * result = +(ABS(var2) - ABS(var1)) + * ---------- + */ + sub_abs(var2, var1, result); + result->sign = NUMERIC_POS; + break; + } + } + else + { + /* ---------- + * var1 is negative, var2 is positive + * result = -(ABS(var1) + ABS(var2)) + * ---------- + */ + add_abs(var1, var2, result); + result->sign = NUMERIC_NEG; + } + } +} + + +/* + * mul_var() - + * + * Multiplication on variable level. Product of var1 * var2 is stored + * in result. Result is rounded to no more than rscale fractional digits. + */ +static void +mul_var(const NumericVar *var1, const NumericVar *var2, NumericVar *result, + int rscale) +{ + int res_ndigits; + int res_sign; + int res_weight; + int maxdigits; + int *dig; + int carry; + int maxdig; + int newdig; + int var1ndigits; + int var2ndigits; + NumericDigit *var1digits; + NumericDigit *var2digits; + NumericDigit *res_digits; + int i, + i1, + i2; + + /* + * Arrange for var1 to be the shorter of the two numbers. This improves + * performance because the inner multiplication loop is much simpler than + * the outer loop, so it's better to have a smaller number of iterations + * of the outer loop. This also reduces the number of times that the + * accumulator array needs to be normalized. + */ + if (var1->ndigits > var2->ndigits) + { + const NumericVar *tmp = var1; + + var1 = var2; + var2 = tmp; + } + + /* copy these values into local vars for speed in inner loop */ + var1ndigits = var1->ndigits; + var2ndigits = var2->ndigits; + var1digits = var1->digits; + var2digits = var2->digits; + + if (var1ndigits == 0 || var2ndigits == 0) + { + /* one or both inputs is zero; so is result */ + zero_var(result); + result->dscale = rscale; + return; + } + + /* Determine result sign and (maximum possible) weight */ + if (var1->sign == var2->sign) + res_sign = NUMERIC_POS; + else + res_sign = NUMERIC_NEG; + res_weight = var1->weight + var2->weight + 2; + + /* + * Determine the number of result digits to compute. If the exact result + * would have more than rscale fractional digits, truncate the computation + * with MUL_GUARD_DIGITS guard digits, i.e., ignore input digits that + * would only contribute to the right of that. (This will give the exact + * rounded-to-rscale answer unless carries out of the ignored positions + * would have propagated through more than MUL_GUARD_DIGITS digits.) + * + * Note: an exact computation could not produce more than var1ndigits + + * var2ndigits digits, but we allocate one extra output digit in case + * rscale-driven rounding produces a carry out of the highest exact digit. + */ + res_ndigits = var1ndigits + var2ndigits + 1; + maxdigits = res_weight + 1 + (rscale + DEC_DIGITS - 1) / DEC_DIGITS + + MUL_GUARD_DIGITS; + res_ndigits = Min(res_ndigits, maxdigits); + + if (res_ndigits < 3) + { + /* All input digits will be ignored; so result is zero */ + zero_var(result); + result->dscale = rscale; + return; + } + + /* + * We do the arithmetic in an array "dig[]" of signed int's. Since + * INT_MAX is noticeably larger than NBASE*NBASE, this gives us headroom + * to avoid normalizing carries immediately. + * + * maxdig tracks the maximum possible value of any dig[] entry; when this + * threatens to exceed INT_MAX, we take the time to propagate carries. + * Furthermore, we need to ensure that overflow doesn't occur during the + * carry propagation passes either. The carry values could be as much as + * INT_MAX/NBASE, so really we must normalize when digits threaten to + * exceed INT_MAX - INT_MAX/NBASE. + * + * To avoid overflow in maxdig itself, it actually represents the max + * possible value divided by NBASE-1, ie, at the top of the loop it is + * known that no dig[] entry exceeds maxdig * (NBASE-1). + */ + dig = (int *) palloc0(res_ndigits * sizeof(int)); + maxdig = 0; + + /* + * The least significant digits of var1 should be ignored if they don't + * contribute directly to the first res_ndigits digits of the result that + * we are computing. + * + * Digit i1 of var1 and digit i2 of var2 are multiplied and added to digit + * i1+i2+2 of the accumulator array, so we need only consider digits of + * var1 for which i1 <= res_ndigits - 3. + */ + for (i1 = Min(var1ndigits - 1, res_ndigits - 3); i1 >= 0; i1--) + { + NumericDigit var1digit = var1digits[i1]; + + if (var1digit == 0) + continue; + + /* Time to normalize? */ + maxdig += var1digit; + if (maxdig > (INT_MAX - INT_MAX / NBASE) / (NBASE - 1)) + { + /* Yes, do it */ + carry = 0; + for (i = res_ndigits - 1; i >= 0; i--) + { + newdig = dig[i] + carry; + if (newdig >= NBASE) + { + carry = newdig / NBASE; + newdig -= carry * NBASE; + } + else + carry = 0; + dig[i] = newdig; + } + Assert(carry == 0); + /* Reset maxdig to indicate new worst-case */ + maxdig = 1 + var1digit; + } + + /* + * Add the appropriate multiple of var2 into the accumulator. + * + * As above, digits of var2 can be ignored if they don't contribute, + * so we only include digits for which i1+i2+2 < res_ndigits. + * + * This inner loop is the performance bottleneck for multiplication, + * so we want to keep it simple enough so that it can be + * auto-vectorized. Accordingly, process the digits left-to-right + * even though schoolbook multiplication would suggest right-to-left. + * Since we aren't propagating carries in this loop, the order does + * not matter. + */ + { + int i2limit = Min(var2ndigits, res_ndigits - i1 - 2); + int *dig_i1_2 = &dig[i1 + 2]; + + for (i2 = 0; i2 < i2limit; i2++) + dig_i1_2[i2] += var1digit * var2digits[i2]; + } + } + + /* + * Now we do a final carry propagation pass to normalize the result, which + * we combine with storing the result digits into the output. Note that + * this is still done at full precision w/guard digits. + */ + alloc_var(result, res_ndigits); + res_digits = result->digits; + carry = 0; + for (i = res_ndigits - 1; i >= 0; i--) + { + newdig = dig[i] + carry; + if (newdig >= NBASE) + { + carry = newdig / NBASE; + newdig -= carry * NBASE; + } + else + carry = 0; + res_digits[i] = newdig; + } + Assert(carry == 0); + + pfree(dig); + + /* + * Finally, round the result to the requested precision. + */ + result->weight = res_weight; + result->sign = res_sign; + + /* Round to target rscale (and set result->dscale) */ + round_var(result, rscale); + + /* Strip leading and trailing zeroes */ + strip_var(result); +} + + +/* + * div_var() - + * + * Division on variable level. Quotient of var1 / var2 is stored in result. + * The quotient is figured to exactly rscale fractional digits. + * If round is true, it is rounded at the rscale'th digit; if false, it + * is truncated (towards zero) at that digit. + */ +static void +div_var(const NumericVar *var1, const NumericVar *var2, NumericVar *result, + int rscale, bool round) +{ + int div_ndigits; + int res_ndigits; + int res_sign; + int res_weight; + int carry; + int borrow; + int divisor1; + int divisor2; + NumericDigit *dividend; + NumericDigit *divisor; + NumericDigit *res_digits; + int i; + int j; + + /* copy these values into local vars for speed in inner loop */ + int var1ndigits = var1->ndigits; + int var2ndigits = var2->ndigits; + + /* + * First of all division by zero check; we must not be handed an + * unnormalized divisor. + */ + if (var2ndigits == 0 || var2->digits[0] == 0) + ereport(ERROR, + (errcode(ERRCODE_DIVISION_BY_ZERO), + errmsg("division by zero"))); + + /* + * If the divisor has just one or two digits, delegate to div_var_int(), + * which uses fast short division. + * + * Similarly, on platforms with 128-bit integer support, delegate to + * div_var_int64() for divisors with three or four digits. + */ + if (var2ndigits <= 2) + { + int idivisor; + int idivisor_weight; + + idivisor = var2->digits[0]; + idivisor_weight = var2->weight; + if (var2ndigits == 2) + { + idivisor = idivisor * NBASE + var2->digits[1]; + idivisor_weight--; + } + if (var2->sign == NUMERIC_NEG) + idivisor = -idivisor; + + div_var_int(var1, idivisor, idivisor_weight, result, rscale, round); + return; + } +#ifdef HAVE_INT128 + if (var2ndigits <= 4) + { + int64 idivisor; + int idivisor_weight; + + idivisor = var2->digits[0]; + idivisor_weight = var2->weight; + for (i = 1; i < var2ndigits; i++) + { + idivisor = idivisor * NBASE + var2->digits[i]; + idivisor_weight--; + } + if (var2->sign == NUMERIC_NEG) + idivisor = -idivisor; + + div_var_int64(var1, idivisor, idivisor_weight, result, rscale, round); + return; + } +#endif + + /* + * Otherwise, perform full long division. + */ + + /* Result zero check */ + if (var1ndigits == 0) + { + zero_var(result); + result->dscale = rscale; + return; + } + + /* + * Determine the result sign, weight and number of digits to calculate. + * The weight figured here is correct if the emitted quotient has no + * leading zero digits; otherwise strip_var() will fix things up. + */ + if (var1->sign == var2->sign) + res_sign = NUMERIC_POS; + else + res_sign = NUMERIC_NEG; + res_weight = var1->weight - var2->weight; + /* The number of accurate result digits we need to produce: */ + res_ndigits = res_weight + 1 + (rscale + DEC_DIGITS - 1) / DEC_DIGITS; + /* ... but always at least 1 */ + res_ndigits = Max(res_ndigits, 1); + /* If rounding needed, figure one more digit to ensure correct result */ + if (round) + res_ndigits++; + + /* + * The working dividend normally requires res_ndigits + var2ndigits + * digits, but make it at least var1ndigits so we can load all of var1 + * into it. (There will be an additional digit dividend[0] in the + * dividend space, but for consistency with Knuth's notation we don't + * count that in div_ndigits.) + */ + div_ndigits = res_ndigits + var2ndigits; + div_ndigits = Max(div_ndigits, var1ndigits); + + /* + * We need a workspace with room for the working dividend (div_ndigits+1 + * digits) plus room for the possibly-normalized divisor (var2ndigits + * digits). It is convenient also to have a zero at divisor[0] with the + * actual divisor data in divisor[1 .. var2ndigits]. Transferring the + * digits into the workspace also allows us to realloc the result (which + * might be the same as either input var) before we begin the main loop. + * Note that we use palloc0 to ensure that divisor[0], dividend[0], and + * any additional dividend positions beyond var1ndigits, start out 0. + */ + dividend = (NumericDigit *) + palloc0((div_ndigits + var2ndigits + 2) * sizeof(NumericDigit)); + divisor = dividend + (div_ndigits + 1); + memcpy(dividend + 1, var1->digits, var1ndigits * sizeof(NumericDigit)); + memcpy(divisor + 1, var2->digits, var2ndigits * sizeof(NumericDigit)); + + /* + * Now we can realloc the result to hold the generated quotient digits. + */ + alloc_var(result, res_ndigits); + res_digits = result->digits; + + /* + * The full multiple-place algorithm is taken from Knuth volume 2, + * Algorithm 4.3.1D. + * + * We need the first divisor digit to be >= NBASE/2. If it isn't, make it + * so by scaling up both the divisor and dividend by the factor "d". (The + * reason for allocating dividend[0] above is to leave room for possible + * carry here.) + */ + if (divisor[1] < HALF_NBASE) + { + int d = NBASE / (divisor[1] + 1); + + carry = 0; + for (i = var2ndigits; i > 0; i--) + { + carry += divisor[i] * d; + divisor[i] = carry % NBASE; + carry = carry / NBASE; + } + Assert(carry == 0); + carry = 0; + /* at this point only var1ndigits of dividend can be nonzero */ + for (i = var1ndigits; i >= 0; i--) + { + carry += dividend[i] * d; + dividend[i] = carry % NBASE; + carry = carry / NBASE; + } + Assert(carry == 0); + Assert(divisor[1] >= HALF_NBASE); + } + /* First 2 divisor digits are used repeatedly in main loop */ + divisor1 = divisor[1]; + divisor2 = divisor[2]; + + /* + * Begin the main loop. Each iteration of this loop produces the j'th + * quotient digit by dividing dividend[j .. j + var2ndigits] by the + * divisor; this is essentially the same as the common manual procedure + * for long division. + */ + for (j = 0; j < res_ndigits; j++) + { + /* Estimate quotient digit from the first two dividend digits */ + int next2digits = dividend[j] * NBASE + dividend[j + 1]; + int qhat; + + /* + * If next2digits are 0, then quotient digit must be 0 and there's no + * need to adjust the working dividend. It's worth testing here to + * fall out ASAP when processing trailing zeroes in a dividend. + */ + if (next2digits == 0) + { + res_digits[j] = 0; + continue; + } + + if (dividend[j] == divisor1) + qhat = NBASE - 1; + else + qhat = next2digits / divisor1; + + /* + * Adjust quotient digit if it's too large. Knuth proves that after + * this step, the quotient digit will be either correct or just one + * too large. (Note: it's OK to use dividend[j+2] here because we + * know the divisor length is at least 2.) + */ + while (divisor2 * qhat > + (next2digits - qhat * divisor1) * NBASE + dividend[j + 2]) + qhat--; + + /* As above, need do nothing more when quotient digit is 0 */ + if (qhat > 0) + { + NumericDigit *dividend_j = ÷nd[j]; + + /* + * Multiply the divisor by qhat, and subtract that from the + * working dividend. The multiplication and subtraction are + * folded together here, noting that qhat <= NBASE (since it might + * be one too large), and so the intermediate result "tmp_result" + * is in the range [-NBASE^2, NBASE - 1], and "borrow" is in the + * range [0, NBASE]. + */ + borrow = 0; + for (i = var2ndigits; i >= 0; i--) + { + int tmp_result; + + tmp_result = dividend_j[i] - borrow - divisor[i] * qhat; + borrow = (NBASE - 1 - tmp_result) / NBASE; + dividend_j[i] = tmp_result + borrow * NBASE; + } + + /* + * If we got a borrow out of the top dividend digit, then indeed + * qhat was one too large. Fix it, and add back the divisor to + * correct the working dividend. (Knuth proves that this will + * occur only about 3/NBASE of the time; hence, it's a good idea + * to test this code with small NBASE to be sure this section gets + * exercised.) + */ + if (borrow) + { + qhat--; + carry = 0; + for (i = var2ndigits; i >= 0; i--) + { + carry += dividend_j[i] + divisor[i]; + if (carry >= NBASE) + { + dividend_j[i] = carry - NBASE; + carry = 1; + } + else + { + dividend_j[i] = carry; + carry = 0; + } + } + /* A carry should occur here to cancel the borrow above */ + Assert(carry == 1); + } + } + + /* And we're done with this quotient digit */ + res_digits[j] = qhat; + } + + pfree(dividend); + + /* + * Finally, round or truncate the result to the requested precision. + */ + result->weight = res_weight; + result->sign = res_sign; + + /* Round or truncate to target rscale (and set result->dscale) */ + if (round) + round_var(result, rscale); + else + trunc_var(result, rscale); + + /* Strip leading and trailing zeroes */ + strip_var(result); +} + + +/* + * div_var_fast() - + * + * This has the same API as div_var, but is implemented using the division + * algorithm from the "FM" library, rather than Knuth's schoolbook-division + * approach. This is significantly faster but can produce inaccurate + * results, because it sometimes has to propagate rounding to the left, + * and so we can never be entirely sure that we know the requested digits + * exactly. We compute DIV_GUARD_DIGITS extra digits, but there is + * no certainty that that's enough. We use this only in the transcendental + * function calculation routines, where everything is approximate anyway. + * + * Although we provide a "round" argument for consistency with div_var, + * it is unwise to use this function with round=false. In truncation mode + * it is possible to get a result with no significant digits, for example + * with rscale=0 we might compute 0.99999... and truncate that to 0 when + * the correct answer is 1. + */ +static void +div_var_fast(const NumericVar *var1, const NumericVar *var2, + NumericVar *result, int rscale, bool round) +{ + int div_ndigits; + int load_ndigits; + int res_sign; + int res_weight; + int *div; + int qdigit; + int carry; + int maxdiv; + int newdig; + NumericDigit *res_digits; + double fdividend, + fdivisor, + fdivisorinverse, + fquotient; + int qi; + int i; + + /* copy these values into local vars for speed in inner loop */ + int var1ndigits = var1->ndigits; + int var2ndigits = var2->ndigits; + NumericDigit *var1digits = var1->digits; + NumericDigit *var2digits = var2->digits; + + /* + * First of all division by zero check; we must not be handed an + * unnormalized divisor. + */ + if (var2ndigits == 0 || var2digits[0] == 0) + ereport(ERROR, + (errcode(ERRCODE_DIVISION_BY_ZERO), + errmsg("division by zero"))); + + /* + * If the divisor has just one or two digits, delegate to div_var_int(), + * which uses fast short division. + * + * Similarly, on platforms with 128-bit integer support, delegate to + * div_var_int64() for divisors with three or four digits. + */ + if (var2ndigits <= 2) + { + int idivisor; + int idivisor_weight; + + idivisor = var2->digits[0]; + idivisor_weight = var2->weight; + if (var2ndigits == 2) + { + idivisor = idivisor * NBASE + var2->digits[1]; + idivisor_weight--; + } + if (var2->sign == NUMERIC_NEG) + idivisor = -idivisor; + + div_var_int(var1, idivisor, idivisor_weight, result, rscale, round); + return; + } +#ifdef HAVE_INT128 + if (var2ndigits <= 4) + { + int64 idivisor; + int idivisor_weight; + + idivisor = var2->digits[0]; + idivisor_weight = var2->weight; + for (i = 1; i < var2ndigits; i++) + { + idivisor = idivisor * NBASE + var2->digits[i]; + idivisor_weight--; + } + if (var2->sign == NUMERIC_NEG) + idivisor = -idivisor; + + div_var_int64(var1, idivisor, idivisor_weight, result, rscale, round); + return; + } +#endif + + /* + * Otherwise, perform full long division. + */ + + /* Result zero check */ + if (var1ndigits == 0) + { + zero_var(result); + result->dscale = rscale; + return; + } + + /* + * Determine the result sign, weight and number of digits to calculate + */ + if (var1->sign == var2->sign) + res_sign = NUMERIC_POS; + else + res_sign = NUMERIC_NEG; + res_weight = var1->weight - var2->weight + 1; + /* The number of accurate result digits we need to produce: */ + div_ndigits = res_weight + 1 + (rscale + DEC_DIGITS - 1) / DEC_DIGITS; + /* Add guard digits for roundoff error */ + div_ndigits += DIV_GUARD_DIGITS; + if (div_ndigits < DIV_GUARD_DIGITS) + div_ndigits = DIV_GUARD_DIGITS; + + /* + * We do the arithmetic in an array "div[]" of signed int's. Since + * INT_MAX is noticeably larger than NBASE*NBASE, this gives us headroom + * to avoid normalizing carries immediately. + * + * We start with div[] containing one zero digit followed by the + * dividend's digits (plus appended zeroes to reach the desired precision + * including guard digits). Each step of the main loop computes an + * (approximate) quotient digit and stores it into div[], removing one + * position of dividend space. A final pass of carry propagation takes + * care of any mistaken quotient digits. + * + * Note that div[] doesn't necessarily contain all of the digits from the + * dividend --- the desired precision plus guard digits might be less than + * the dividend's precision. This happens, for example, in the square + * root algorithm, where we typically divide a 2N-digit number by an + * N-digit number, and only require a result with N digits of precision. + */ + div = (int *) palloc0((div_ndigits + 1) * sizeof(int)); + load_ndigits = Min(div_ndigits, var1ndigits); + for (i = 0; i < load_ndigits; i++) + div[i + 1] = var1digits[i]; + + /* + * We estimate each quotient digit using floating-point arithmetic, taking + * the first four digits of the (current) dividend and divisor. This must + * be float to avoid overflow. The quotient digits will generally be off + * by no more than one from the exact answer. + */ + fdivisor = (double) var2digits[0]; + for (i = 1; i < 4; i++) + { + fdivisor *= NBASE; + if (i < var2ndigits) + fdivisor += (double) var2digits[i]; + } + fdivisorinverse = 1.0 / fdivisor; + + /* + * maxdiv tracks the maximum possible absolute value of any div[] entry; + * when this threatens to exceed INT_MAX, we take the time to propagate + * carries. Furthermore, we need to ensure that overflow doesn't occur + * during the carry propagation passes either. The carry values may have + * an absolute value as high as INT_MAX/NBASE + 1, so really we must + * normalize when digits threaten to exceed INT_MAX - INT_MAX/NBASE - 1. + * + * To avoid overflow in maxdiv itself, it represents the max absolute + * value divided by NBASE-1, ie, at the top of the loop it is known that + * no div[] entry has an absolute value exceeding maxdiv * (NBASE-1). + * + * Actually, though, that holds good only for div[] entries after div[qi]; + * the adjustment done at the bottom of the loop may cause div[qi + 1] to + * exceed the maxdiv limit, so that div[qi] in the next iteration is + * beyond the limit. This does not cause problems, as explained below. + */ + maxdiv = 1; + + /* + * Outer loop computes next quotient digit, which will go into div[qi] + */ + for (qi = 0; qi < div_ndigits; qi++) + { + /* Approximate the current dividend value */ + fdividend = (double) div[qi]; + for (i = 1; i < 4; i++) + { + fdividend *= NBASE; + if (qi + i <= div_ndigits) + fdividend += (double) div[qi + i]; + } + /* Compute the (approximate) quotient digit */ + fquotient = fdividend * fdivisorinverse; + qdigit = (fquotient >= 0.0) ? ((int) fquotient) : + (((int) fquotient) - 1); /* truncate towards -infinity */ + + if (qdigit != 0) + { + /* Do we need to normalize now? */ + maxdiv += abs(qdigit); + if (maxdiv > (INT_MAX - INT_MAX / NBASE - 1) / (NBASE - 1)) + { + /* + * Yes, do it. Note that if var2ndigits is much smaller than + * div_ndigits, we can save a significant amount of effort + * here by noting that we only need to normalise those div[] + * entries touched where prior iterations subtracted multiples + * of the divisor. + */ + carry = 0; + for (i = Min(qi + var2ndigits - 2, div_ndigits); i > qi; i--) + { + newdig = div[i] + carry; + if (newdig < 0) + { + carry = -((-newdig - 1) / NBASE) - 1; + newdig -= carry * NBASE; + } + else if (newdig >= NBASE) + { + carry = newdig / NBASE; + newdig -= carry * NBASE; + } + else + carry = 0; + div[i] = newdig; + } + newdig = div[qi] + carry; + div[qi] = newdig; + + /* + * All the div[] digits except possibly div[qi] are now in the + * range 0..NBASE-1. We do not need to consider div[qi] in + * the maxdiv value anymore, so we can reset maxdiv to 1. + */ + maxdiv = 1; + + /* + * Recompute the quotient digit since new info may have + * propagated into the top four dividend digits + */ + fdividend = (double) div[qi]; + for (i = 1; i < 4; i++) + { + fdividend *= NBASE; + if (qi + i <= div_ndigits) + fdividend += (double) div[qi + i]; + } + /* Compute the (approximate) quotient digit */ + fquotient = fdividend * fdivisorinverse; + qdigit = (fquotient >= 0.0) ? ((int) fquotient) : + (((int) fquotient) - 1); /* truncate towards -infinity */ + maxdiv += abs(qdigit); + } + + /* + * Subtract off the appropriate multiple of the divisor. + * + * The digits beyond div[qi] cannot overflow, because we know they + * will fall within the maxdiv limit. As for div[qi] itself, note + * that qdigit is approximately trunc(div[qi] / vardigits[0]), + * which would make the new value simply div[qi] mod vardigits[0]. + * The lower-order terms in qdigit can change this result by not + * more than about twice INT_MAX/NBASE, so overflow is impossible. + * + * This inner loop is the performance bottleneck for division, so + * code it in the same way as the inner loop of mul_var() so that + * it can be auto-vectorized. We cast qdigit to NumericDigit + * before multiplying to allow the compiler to generate more + * efficient code (using 16-bit multiplication), which is safe + * since we know that the quotient digit is off by at most one, so + * there is no overflow risk. + */ + if (qdigit != 0) + { + int istop = Min(var2ndigits, div_ndigits - qi + 1); + int *div_qi = &div[qi]; + + for (i = 0; i < istop; i++) + div_qi[i] -= ((NumericDigit) qdigit) * var2digits[i]; + } + } + + /* + * The dividend digit we are about to replace might still be nonzero. + * Fold it into the next digit position. + * + * There is no risk of overflow here, although proving that requires + * some care. Much as with the argument for div[qi] not overflowing, + * if we consider the first two terms in the numerator and denominator + * of qdigit, we can see that the final value of div[qi + 1] will be + * approximately a remainder mod (vardigits[0]*NBASE + vardigits[1]). + * Accounting for the lower-order terms is a bit complicated but ends + * up adding not much more than INT_MAX/NBASE to the possible range. + * Thus, div[qi + 1] cannot overflow here, and in its role as div[qi] + * in the next loop iteration, it can't be large enough to cause + * overflow in the carry propagation step (if any), either. + * + * But having said that: div[qi] can be more than INT_MAX/NBASE, as + * noted above, which means that the product div[qi] * NBASE *can* + * overflow. When that happens, adding it to div[qi + 1] will always + * cause a canceling overflow so that the end result is correct. We + * could avoid the intermediate overflow by doing the multiplication + * and addition in int64 arithmetic, but so far there appears no need. + */ + div[qi + 1] += div[qi] * NBASE; + + div[qi] = qdigit; + } + + /* + * Approximate and store the last quotient digit (div[div_ndigits]) + */ + fdividend = (double) div[qi]; + for (i = 1; i < 4; i++) + fdividend *= NBASE; + fquotient = fdividend * fdivisorinverse; + qdigit = (fquotient >= 0.0) ? ((int) fquotient) : + (((int) fquotient) - 1); /* truncate towards -infinity */ + div[qi] = qdigit; + + /* + * Because the quotient digits might be off by one, some of them might be + * -1 or NBASE at this point. The represented value is correct in a + * mathematical sense, but it doesn't look right. We do a final carry + * propagation pass to normalize the digits, which we combine with storing + * the result digits into the output. Note that this is still done at + * full precision w/guard digits. + */ + alloc_var(result, div_ndigits + 1); + res_digits = result->digits; + carry = 0; + for (i = div_ndigits; i >= 0; i--) + { + newdig = div[i] + carry; + if (newdig < 0) + { + carry = -((-newdig - 1) / NBASE) - 1; + newdig -= carry * NBASE; + } + else if (newdig >= NBASE) + { + carry = newdig / NBASE; + newdig -= carry * NBASE; + } + else + carry = 0; + res_digits[i] = newdig; + } + Assert(carry == 0); + + pfree(div); + + /* + * Finally, round the result to the requested precision. + */ + result->weight = res_weight; + result->sign = res_sign; + + /* Round to target rscale (and set result->dscale) */ + if (round) + round_var(result, rscale); + else + trunc_var(result, rscale); + + /* Strip leading and trailing zeroes */ + strip_var(result); +} + + +/* + * div_var_int() - + * + * Divide a numeric variable by a 32-bit integer with the specified weight. + * The quotient var / (ival * NBASE^ival_weight) is stored in result. + */ +static void +div_var_int(const NumericVar *var, int ival, int ival_weight, + NumericVar *result, int rscale, bool round) +{ + NumericDigit *var_digits = var->digits; + int var_ndigits = var->ndigits; + int res_sign; + int res_weight; + int res_ndigits; + NumericDigit *res_buf; + NumericDigit *res_digits; + uint32 divisor; + int i; + + /* Guard against division by zero */ + if (ival == 0) + ereport(ERROR, + errcode(ERRCODE_DIVISION_BY_ZERO), + errmsg("division by zero")); + + /* Result zero check */ + if (var_ndigits == 0) + { + zero_var(result); + result->dscale = rscale; + return; + } + + /* + * Determine the result sign, weight and number of digits to calculate. + * The weight figured here is correct if the emitted quotient has no + * leading zero digits; otherwise strip_var() will fix things up. + */ + if (var->sign == NUMERIC_POS) + res_sign = ival > 0 ? NUMERIC_POS : NUMERIC_NEG; + else + res_sign = ival > 0 ? NUMERIC_NEG : NUMERIC_POS; + res_weight = var->weight - ival_weight; + /* The number of accurate result digits we need to produce: */ + res_ndigits = res_weight + 1 + (rscale + DEC_DIGITS - 1) / DEC_DIGITS; + /* ... but always at least 1 */ + res_ndigits = Max(res_ndigits, 1); + /* If rounding needed, figure one more digit to ensure correct result */ + if (round) + res_ndigits++; + + res_buf = digitbuf_alloc(res_ndigits + 1); + res_buf[0] = 0; /* spare digit for later rounding */ + res_digits = res_buf + 1; + + /* + * Now compute the quotient digits. This is the short division algorithm + * described in Knuth volume 2, section 4.3.1 exercise 16, except that we + * allow the divisor to exceed the internal base. + * + * In this algorithm, the carry from one digit to the next is at most + * divisor - 1. Therefore, while processing the next digit, carry may + * become as large as divisor * NBASE - 1, and so it requires a 64-bit + * integer if this exceeds UINT_MAX. + */ + divisor = abs(ival); + + if (divisor <= UINT_MAX / NBASE) + { + /* carry cannot overflow 32 bits */ + uint32 carry = 0; + + for (i = 0; i < res_ndigits; i++) + { + carry = carry * NBASE + (i < var_ndigits ? var_digits[i] : 0); + res_digits[i] = (NumericDigit) (carry / divisor); + carry = carry % divisor; + } + } + else + { + /* carry may exceed 32 bits */ + uint64 carry = 0; + + for (i = 0; i < res_ndigits; i++) + { + carry = carry * NBASE + (i < var_ndigits ? var_digits[i] : 0); + res_digits[i] = (NumericDigit) (carry / divisor); + carry = carry % divisor; + } + } + + /* Store the quotient in result */ + digitbuf_free(result->buf); + result->ndigits = res_ndigits; + result->buf = res_buf; + result->digits = res_digits; + result->weight = res_weight; + result->sign = res_sign; + + /* Round or truncate to target rscale (and set result->dscale) */ + if (round) + round_var(result, rscale); + else + trunc_var(result, rscale); + + /* Strip leading/trailing zeroes */ + strip_var(result); +} + + +#ifdef HAVE_INT128 +/* + * div_var_int64() - + * + * Divide a numeric variable by a 64-bit integer with the specified weight. + * The quotient var / (ival * NBASE^ival_weight) is stored in result. + * + * This duplicates the logic in div_var_int(), so any changes made there + * should be made here too. + */ +static void +div_var_int64(const NumericVar *var, int64 ival, int ival_weight, + NumericVar *result, int rscale, bool round) +{ + NumericDigit *var_digits = var->digits; + int var_ndigits = var->ndigits; + int res_sign; + int res_weight; + int res_ndigits; + NumericDigit *res_buf; + NumericDigit *res_digits; + uint64 divisor; + int i; + + /* Guard against division by zero */ + if (ival == 0) + ereport(ERROR, + errcode(ERRCODE_DIVISION_BY_ZERO), + errmsg("division by zero")); + + /* Result zero check */ + if (var_ndigits == 0) + { + zero_var(result); + result->dscale = rscale; + return; + } + + /* + * Determine the result sign, weight and number of digits to calculate. + * The weight figured here is correct if the emitted quotient has no + * leading zero digits; otherwise strip_var() will fix things up. + */ + if (var->sign == NUMERIC_POS) + res_sign = ival > 0 ? NUMERIC_POS : NUMERIC_NEG; + else + res_sign = ival > 0 ? NUMERIC_NEG : NUMERIC_POS; + res_weight = var->weight - ival_weight; + /* The number of accurate result digits we need to produce: */ + res_ndigits = res_weight + 1 + (rscale + DEC_DIGITS - 1) / DEC_DIGITS; + /* ... but always at least 1 */ + res_ndigits = Max(res_ndigits, 1); + /* If rounding needed, figure one more digit to ensure correct result */ + if (round) + res_ndigits++; + + res_buf = digitbuf_alloc(res_ndigits + 1); + res_buf[0] = 0; /* spare digit for later rounding */ + res_digits = res_buf + 1; + + /* + * Now compute the quotient digits. This is the short division algorithm + * described in Knuth volume 2, section 4.3.1 exercise 16, except that we + * allow the divisor to exceed the internal base. + * + * In this algorithm, the carry from one digit to the next is at most + * divisor - 1. Therefore, while processing the next digit, carry may + * become as large as divisor * NBASE - 1, and so it requires a 128-bit + * integer if this exceeds PG_UINT64_MAX. + */ + divisor = i64abs(ival); + + if (divisor <= PG_UINT64_MAX / NBASE) + { + /* carry cannot overflow 64 bits */ + uint64 carry = 0; + + for (i = 0; i < res_ndigits; i++) + { + carry = carry * NBASE + (i < var_ndigits ? var_digits[i] : 0); + res_digits[i] = (NumericDigit) (carry / divisor); + carry = carry % divisor; + } + } + else + { + /* carry may exceed 64 bits */ + uint128 carry = 0; + + for (i = 0; i < res_ndigits; i++) + { + carry = carry * NBASE + (i < var_ndigits ? var_digits[i] : 0); + res_digits[i] = (NumericDigit) (carry / divisor); + carry = carry % divisor; + } + } + + /* Store the quotient in result */ + digitbuf_free(result->buf); + result->ndigits = res_ndigits; + result->buf = res_buf; + result->digits = res_digits; + result->weight = res_weight; + result->sign = res_sign; + + /* Round or truncate to target rscale (and set result->dscale) */ + if (round) + round_var(result, rscale); + else + trunc_var(result, rscale); + + /* Strip leading/trailing zeroes */ + strip_var(result); +} +#endif + + +/* + * Default scale selection for division + * + * Returns the appropriate result scale for the division result. + */ +static int +select_div_scale(const NumericVar *var1, const NumericVar *var2) +{ + int weight1, + weight2, + qweight, + i; + NumericDigit firstdigit1, + firstdigit2; + int rscale; + + /* + * The result scale of a division isn't specified in any SQL standard. For + * PostgreSQL we select a result scale that will give at least + * NUMERIC_MIN_SIG_DIGITS significant digits, so that numeric gives a + * result no less accurate than float8; but use a scale not less than + * either input's display scale. + */ + + /* Get the actual (normalized) weight and first digit of each input */ + + weight1 = 0; /* values to use if var1 is zero */ + firstdigit1 = 0; + for (i = 0; i < var1->ndigits; i++) + { + firstdigit1 = var1->digits[i]; + if (firstdigit1 != 0) + { + weight1 = var1->weight - i; + break; + } + } + + weight2 = 0; /* values to use if var2 is zero */ + firstdigit2 = 0; + for (i = 0; i < var2->ndigits; i++) + { + firstdigit2 = var2->digits[i]; + if (firstdigit2 != 0) + { + weight2 = var2->weight - i; + break; + } + } + + /* + * Estimate weight of quotient. If the two first digits are equal, we + * can't be sure, but assume that var1 is less than var2. + */ + qweight = weight1 - weight2; + if (firstdigit1 <= firstdigit2) + qweight--; + + /* Select result scale */ + rscale = NUMERIC_MIN_SIG_DIGITS - qweight * DEC_DIGITS; + rscale = Max(rscale, var1->dscale); + rscale = Max(rscale, var2->dscale); + rscale = Max(rscale, NUMERIC_MIN_DISPLAY_SCALE); + rscale = Min(rscale, NUMERIC_MAX_DISPLAY_SCALE); + + return rscale; +} + + +/* + * mod_var() - + * + * Calculate the modulo of two numerics at variable level + */ +static void +mod_var(const NumericVar *var1, const NumericVar *var2, NumericVar *result) +{ + NumericVar tmp; + + init_var(&tmp); + + /* --------- + * We do this using the equation + * mod(x,y) = x - trunc(x/y)*y + * div_var can be persuaded to give us trunc(x/y) directly. + * ---------- + */ + div_var(var1, var2, &tmp, 0, false); + + mul_var(var2, &tmp, &tmp, var2->dscale); + + sub_var(var1, &tmp, result); + + free_var(&tmp); +} + + +/* + * div_mod_var() - + * + * Calculate the truncated integer quotient and numeric remainder of two + * numeric variables. The remainder is precise to var2's dscale. + */ +static void +div_mod_var(const NumericVar *var1, const NumericVar *var2, + NumericVar *quot, NumericVar *rem) +{ + NumericVar q; + NumericVar r; + + init_var(&q); + init_var(&r); + + /* + * Use div_var_fast() to get an initial estimate for the integer quotient. + * This might be inaccurate (per the warning in div_var_fast's comments), + * but we can correct it below. + */ + div_var_fast(var1, var2, &q, 0, false); + + /* Compute initial estimate of remainder using the quotient estimate. */ + mul_var(var2, &q, &r, var2->dscale); + sub_var(var1, &r, &r); + + /* + * Adjust the results if necessary --- the remainder should have the same + * sign as var1, and its absolute value should be less than the absolute + * value of var2. + */ + while (r.ndigits != 0 && r.sign != var1->sign) + { + /* The absolute value of the quotient is too large */ + if (var1->sign == var2->sign) + { + sub_var(&q, &const_one, &q); + add_var(&r, var2, &r); + } + else + { + add_var(&q, &const_one, &q); + sub_var(&r, var2, &r); + } + } + + while (cmp_abs(&r, var2) >= 0) + { + /* The absolute value of the quotient is too small */ + if (var1->sign == var2->sign) + { + add_var(&q, &const_one, &q); + sub_var(&r, var2, &r); + } + else + { + sub_var(&q, &const_one, &q); + add_var(&r, var2, &r); + } + } + + set_var_from_var(&q, quot); + set_var_from_var(&r, rem); + + free_var(&q); + free_var(&r); +} + + +/* + * ceil_var() - + * + * Return the smallest integer greater than or equal to the argument + * on variable level + */ +static void +ceil_var(const NumericVar *var, NumericVar *result) +{ + NumericVar tmp; + + init_var(&tmp); + set_var_from_var(var, &tmp); + + trunc_var(&tmp, 0); + + if (var->sign == NUMERIC_POS && cmp_var(var, &tmp) != 0) + add_var(&tmp, &const_one, &tmp); + + set_var_from_var(&tmp, result); + free_var(&tmp); +} + + +/* + * floor_var() - + * + * Return the largest integer equal to or less than the argument + * on variable level + */ +static void +floor_var(const NumericVar *var, NumericVar *result) +{ + NumericVar tmp; + + init_var(&tmp); + set_var_from_var(var, &tmp); + + trunc_var(&tmp, 0); + + if (var->sign == NUMERIC_NEG && cmp_var(var, &tmp) != 0) + sub_var(&tmp, &const_one, &tmp); + + set_var_from_var(&tmp, result); + free_var(&tmp); +} + + +/* + * gcd_var() - + * + * Calculate the greatest common divisor of two numerics at variable level + */ +static void +gcd_var(const NumericVar *var1, const NumericVar *var2, NumericVar *result) +{ + int res_dscale; + int cmp; + NumericVar tmp_arg; + NumericVar mod; + + res_dscale = Max(var1->dscale, var2->dscale); + + /* + * Arrange for var1 to be the number with the greater absolute value. + * + * This would happen automatically in the loop below, but avoids an + * expensive modulo operation. + */ + cmp = cmp_abs(var1, var2); + if (cmp < 0) + { + const NumericVar *tmp = var1; + + var1 = var2; + var2 = tmp; + } + + /* + * Also avoid the taking the modulo if the inputs have the same absolute + * value, or if the smaller input is zero. + */ + if (cmp == 0 || var2->ndigits == 0) + { + set_var_from_var(var1, result); + result->sign = NUMERIC_POS; + result->dscale = res_dscale; + return; + } + + init_var(&tmp_arg); + init_var(&mod); + + /* Use the Euclidean algorithm to find the GCD */ + set_var_from_var(var1, &tmp_arg); + set_var_from_var(var2, result); + + for (;;) + { + /* this loop can take a while, so allow it to be interrupted */ + CHECK_FOR_INTERRUPTS(); + + mod_var(&tmp_arg, result, &mod); + if (mod.ndigits == 0) + break; + set_var_from_var(result, &tmp_arg); + set_var_from_var(&mod, result); + } + result->sign = NUMERIC_POS; + result->dscale = res_dscale; + + free_var(&tmp_arg); + free_var(&mod); +} + + +/* + * sqrt_var() - + * + * Compute the square root of x using the Karatsuba Square Root algorithm. + * NOTE: we allow rscale < 0 here, implying rounding before the decimal + * point. + */ +static void +sqrt_var(const NumericVar *arg, NumericVar *result, int rscale) +{ + int stat; + int res_weight; + int res_ndigits; + int src_ndigits; + int step; + int ndigits[32]; + int blen; + int64 arg_int64; + int src_idx; + int64 s_int64; + int64 r_int64; + NumericVar s_var; + NumericVar r_var; + NumericVar a0_var; + NumericVar a1_var; + NumericVar q_var; + NumericVar u_var; + + stat = cmp_var(arg, &const_zero); + if (stat == 0) + { + zero_var(result); + result->dscale = rscale; + return; + } + + /* + * SQL2003 defines sqrt() in terms of power, so we need to emit the right + * SQLSTATE error code if the operand is negative. + */ + if (stat < 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_ARGUMENT_FOR_POWER_FUNCTION), + errmsg("cannot take square root of a negative number"))); + + init_var(&s_var); + init_var(&r_var); + init_var(&a0_var); + init_var(&a1_var); + init_var(&q_var); + init_var(&u_var); + + /* + * The result weight is half the input weight, rounded towards minus + * infinity --- res_weight = floor(arg->weight / 2). + */ + if (arg->weight >= 0) + res_weight = arg->weight / 2; + else + res_weight = -((-arg->weight - 1) / 2 + 1); + + /* + * Number of NBASE digits to compute. To ensure correct rounding, compute + * at least 1 extra decimal digit. We explicitly allow rscale to be + * negative here, but must always compute at least 1 NBASE digit. Thus + * res_ndigits = res_weight + 1 + ceil((rscale + 1) / DEC_DIGITS) or 1. + */ + if (rscale + 1 >= 0) + res_ndigits = res_weight + 1 + (rscale + DEC_DIGITS) / DEC_DIGITS; + else + res_ndigits = res_weight + 1 - (-rscale - 1) / DEC_DIGITS; + res_ndigits = Max(res_ndigits, 1); + + /* + * Number of source NBASE digits logically required to produce a result + * with this precision --- every digit before the decimal point, plus 2 + * for each result digit after the decimal point (or minus 2 for each + * result digit we round before the decimal point). + */ + src_ndigits = arg->weight + 1 + (res_ndigits - res_weight - 1) * 2; + src_ndigits = Max(src_ndigits, 1); + + /* ---------- + * From this point on, we treat the input and the result as integers and + * compute the integer square root and remainder using the Karatsuba + * Square Root algorithm, which may be written recursively as follows: + * + * SqrtRem(n = a3*b^3 + a2*b^2 + a1*b + a0): + * [ for some base b, and coefficients a0,a1,a2,a3 chosen so that + * 0 <= a0,a1,a2 < b and a3 >= b/4 ] + * Let (s,r) = SqrtRem(a3*b + a2) + * Let (q,u) = DivRem(r*b + a1, 2*s) + * Let s = s*b + q + * Let r = u*b + a0 - q^2 + * If r < 0 Then + * Let r = r + s + * Let s = s - 1 + * Let r = r + s + * Return (s,r) + * + * See "Karatsuba Square Root", Paul Zimmermann, INRIA Research Report + * RR-3805, November 1999. At the time of writing this was available + * on the net at <https://hal.inria.fr/inria-00072854>. + * + * The way to read the assumption "n = a3*b^3 + a2*b^2 + a1*b + a0" is + * "choose a base b such that n requires at least four base-b digits to + * express; then those digits are a3,a2,a1,a0, with a3 possibly larger + * than b". For optimal performance, b should have approximately a + * quarter the number of digits in the input, so that the outer square + * root computes roughly twice as many digits as the inner one. For + * simplicity, we choose b = NBASE^blen, an integer power of NBASE. + * + * We implement the algorithm iteratively rather than recursively, to + * allow the working variables to be reused. With this approach, each + * digit of the input is read precisely once --- src_idx tracks the number + * of input digits used so far. + * + * The array ndigits[] holds the number of NBASE digits of the input that + * will have been used at the end of each iteration, which roughly doubles + * each time. Note that the array elements are stored in reverse order, + * so if the final iteration requires src_ndigits = 37 input digits, the + * array will contain [37,19,11,7,5,3], and we would start by computing + * the square root of the 3 most significant NBASE digits. + * + * In each iteration, we choose blen to be the largest integer for which + * the input number has a3 >= b/4, when written in the form above. In + * general, this means blen = src_ndigits / 4 (truncated), but if + * src_ndigits is a multiple of 4, that might lead to the coefficient a3 + * being less than b/4 (if the first input digit is less than NBASE/4), in + * which case we choose blen = src_ndigits / 4 - 1. The number of digits + * in the inner square root is then src_ndigits - 2*blen. So, for + * example, if we have src_ndigits = 26 initially, the array ndigits[] + * will be either [26,14,8,4] or [26,14,8,6,4], depending on the size of + * the first input digit. + * + * Additionally, we can put an upper bound on the number of steps required + * as follows --- suppose that the number of source digits is an n-bit + * number in the range [2^(n-1), 2^n-1], then blen will be in the range + * [2^(n-3)-1, 2^(n-2)-1] and the number of digits in the inner square + * root will be in the range [2^(n-2), 2^(n-1)+1]. In the next step, blen + * will be in the range [2^(n-4)-1, 2^(n-3)] and the number of digits in + * the next inner square root will be in the range [2^(n-3), 2^(n-2)+1]. + * This pattern repeats, and in the worst case the array ndigits[] will + * contain [2^n-1, 2^(n-1)+1, 2^(n-2)+1, ... 9, 5, 3], and the computation + * will require n steps. Therefore, since all digit array sizes are + * signed 32-bit integers, the number of steps required is guaranteed to + * be less than 32. + * ---------- + */ + step = 0; + while ((ndigits[step] = src_ndigits) > 4) + { + /* Choose b so that a3 >= b/4, as described above */ + blen = src_ndigits / 4; + if (blen * 4 == src_ndigits && arg->digits[0] < NBASE / 4) + blen--; + + /* Number of digits in the next step (inner square root) */ + src_ndigits -= 2 * blen; + step++; + } + + /* + * First iteration (innermost square root and remainder): + * + * Here src_ndigits <= 4, and the input fits in an int64. Its square root + * has at most 9 decimal digits, so estimate it using double precision + * arithmetic, which will in fact almost certainly return the correct + * result with no further correction required. + */ + arg_int64 = arg->digits[0]; + for (src_idx = 1; src_idx < src_ndigits; src_idx++) + { + arg_int64 *= NBASE; + if (src_idx < arg->ndigits) + arg_int64 += arg->digits[src_idx]; + } + + s_int64 = (int64) sqrt((double) arg_int64); + r_int64 = arg_int64 - s_int64 * s_int64; + + /* + * Use Newton's method to correct the result, if necessary. + * + * This uses integer division with truncation to compute the truncated + * integer square root by iterating using the formula x -> (x + n/x) / 2. + * This is known to converge to isqrt(n), unless n+1 is a perfect square. + * If n+1 is a perfect square, the sequence will oscillate between the two + * values isqrt(n) and isqrt(n)+1, so we can be assured of convergence by + * checking the remainder. + */ + while (r_int64 < 0 || r_int64 > 2 * s_int64) + { + s_int64 = (s_int64 + arg_int64 / s_int64) / 2; + r_int64 = arg_int64 - s_int64 * s_int64; + } + + /* + * Iterations with src_ndigits <= 8: + * + * The next 1 or 2 iterations compute larger (outer) square roots with + * src_ndigits <= 8, so the result still fits in an int64 (even though the + * input no longer does) and we can continue to compute using int64 + * variables to avoid more expensive numeric computations. + * + * It is fairly easy to see that there is no risk of the intermediate + * values below overflowing 64-bit integers. In the worst case, the + * previous iteration will have computed a 3-digit square root (of a + * 6-digit input less than NBASE^6 / 4), so at the start of this + * iteration, s will be less than NBASE^3 / 2 = 10^12 / 2, and r will be + * less than 10^12. In this case, blen will be 1, so numer will be less + * than 10^17, and denom will be less than 10^12 (and hence u will also be + * less than 10^12). Finally, since q^2 = u*b + a0 - r, we can also be + * sure that q^2 < 10^17. Therefore all these quantities fit comfortably + * in 64-bit integers. + */ + step--; + while (step >= 0 && (src_ndigits = ndigits[step]) <= 8) + { + int b; + int a0; + int a1; + int i; + int64 numer; + int64 denom; + int64 q; + int64 u; + + blen = (src_ndigits - src_idx) / 2; + + /* Extract a1 and a0, and compute b */ + a0 = 0; + a1 = 0; + b = 1; + + for (i = 0; i < blen; i++, src_idx++) + { + b *= NBASE; + a1 *= NBASE; + if (src_idx < arg->ndigits) + a1 += arg->digits[src_idx]; + } + + for (i = 0; i < blen; i++, src_idx++) + { + a0 *= NBASE; + if (src_idx < arg->ndigits) + a0 += arg->digits[src_idx]; + } + + /* Compute (q,u) = DivRem(r*b + a1, 2*s) */ + numer = r_int64 * b + a1; + denom = 2 * s_int64; + q = numer / denom; + u = numer - q * denom; + + /* Compute s = s*b + q and r = u*b + a0 - q^2 */ + s_int64 = s_int64 * b + q; + r_int64 = u * b + a0 - q * q; + + if (r_int64 < 0) + { + /* s is too large by 1; set r += s, s--, r += s */ + r_int64 += s_int64; + s_int64--; + r_int64 += s_int64; + } + + Assert(src_idx == src_ndigits); /* All input digits consumed */ + step--; + } + + /* + * On platforms with 128-bit integer support, we can further delay the + * need to use numeric variables. + */ +#ifdef HAVE_INT128 + if (step >= 0) + { + int128 s_int128; + int128 r_int128; + + s_int128 = s_int64; + r_int128 = r_int64; + + /* + * Iterations with src_ndigits <= 16: + * + * The result fits in an int128 (even though the input doesn't) so we + * use int128 variables to avoid more expensive numeric computations. + */ + while (step >= 0 && (src_ndigits = ndigits[step]) <= 16) + { + int64 b; + int64 a0; + int64 a1; + int64 i; + int128 numer; + int128 denom; + int128 q; + int128 u; + + blen = (src_ndigits - src_idx) / 2; + + /* Extract a1 and a0, and compute b */ + a0 = 0; + a1 = 0; + b = 1; + + for (i = 0; i < blen; i++, src_idx++) + { + b *= NBASE; + a1 *= NBASE; + if (src_idx < arg->ndigits) + a1 += arg->digits[src_idx]; + } + + for (i = 0; i < blen; i++, src_idx++) + { + a0 *= NBASE; + if (src_idx < arg->ndigits) + a0 += arg->digits[src_idx]; + } + + /* Compute (q,u) = DivRem(r*b + a1, 2*s) */ + numer = r_int128 * b + a1; + denom = 2 * s_int128; + q = numer / denom; + u = numer - q * denom; + + /* Compute s = s*b + q and r = u*b + a0 - q^2 */ + s_int128 = s_int128 * b + q; + r_int128 = u * b + a0 - q * q; + + if (r_int128 < 0) + { + /* s is too large by 1; set r += s, s--, r += s */ + r_int128 += s_int128; + s_int128--; + r_int128 += s_int128; + } + + Assert(src_idx == src_ndigits); /* All input digits consumed */ + step--; + } + + /* + * All remaining iterations require numeric variables. Convert the + * integer values to NumericVar and continue. Note that in the final + * iteration we don't need the remainder, so we can save a few cycles + * there by not fully computing it. + */ + int128_to_numericvar(s_int128, &s_var); + if (step >= 0) + int128_to_numericvar(r_int128, &r_var); + } + else + { + int64_to_numericvar(s_int64, &s_var); + /* step < 0, so we certainly don't need r */ + } +#else /* !HAVE_INT128 */ + int64_to_numericvar(s_int64, &s_var); + if (step >= 0) + int64_to_numericvar(r_int64, &r_var); +#endif /* HAVE_INT128 */ + + /* + * The remaining iterations with src_ndigits > 8 (or 16, if have int128) + * use numeric variables. + */ + while (step >= 0) + { + int tmp_len; + + src_ndigits = ndigits[step]; + blen = (src_ndigits - src_idx) / 2; + + /* Extract a1 and a0 */ + if (src_idx < arg->ndigits) + { + tmp_len = Min(blen, arg->ndigits - src_idx); + alloc_var(&a1_var, tmp_len); + memcpy(a1_var.digits, arg->digits + src_idx, + tmp_len * sizeof(NumericDigit)); + a1_var.weight = blen - 1; + a1_var.sign = NUMERIC_POS; + a1_var.dscale = 0; + strip_var(&a1_var); + } + else + { + zero_var(&a1_var); + a1_var.dscale = 0; + } + src_idx += blen; + + if (src_idx < arg->ndigits) + { + tmp_len = Min(blen, arg->ndigits - src_idx); + alloc_var(&a0_var, tmp_len); + memcpy(a0_var.digits, arg->digits + src_idx, + tmp_len * sizeof(NumericDigit)); + a0_var.weight = blen - 1; + a0_var.sign = NUMERIC_POS; + a0_var.dscale = 0; + strip_var(&a0_var); + } + else + { + zero_var(&a0_var); + a0_var.dscale = 0; + } + src_idx += blen; + + /* Compute (q,u) = DivRem(r*b + a1, 2*s) */ + set_var_from_var(&r_var, &q_var); + q_var.weight += blen; + add_var(&q_var, &a1_var, &q_var); + add_var(&s_var, &s_var, &u_var); + div_mod_var(&q_var, &u_var, &q_var, &u_var); + + /* Compute s = s*b + q */ + s_var.weight += blen; + add_var(&s_var, &q_var, &s_var); + + /* + * Compute r = u*b + a0 - q^2. + * + * In the final iteration, we don't actually need r; we just need to + * know whether it is negative, so that we know whether to adjust s. + * So instead of the final subtraction we can just compare. + */ + u_var.weight += blen; + add_var(&u_var, &a0_var, &u_var); + mul_var(&q_var, &q_var, &q_var, 0); + + if (step > 0) + { + /* Need r for later iterations */ + sub_var(&u_var, &q_var, &r_var); + if (r_var.sign == NUMERIC_NEG) + { + /* s is too large by 1; set r += s, s--, r += s */ + add_var(&r_var, &s_var, &r_var); + sub_var(&s_var, &const_one, &s_var); + add_var(&r_var, &s_var, &r_var); + } + } + else + { + /* Don't need r anymore, except to test if s is too large by 1 */ + if (cmp_var(&u_var, &q_var) < 0) + sub_var(&s_var, &const_one, &s_var); + } + + Assert(src_idx == src_ndigits); /* All input digits consumed */ + step--; + } + + /* + * Construct the final result, rounding it to the requested precision. + */ + set_var_from_var(&s_var, result); + result->weight = res_weight; + result->sign = NUMERIC_POS; + + /* Round to target rscale (and set result->dscale) */ + round_var(result, rscale); + + /* Strip leading and trailing zeroes */ + strip_var(result); + + free_var(&s_var); + free_var(&r_var); + free_var(&a0_var); + free_var(&a1_var); + free_var(&q_var); + free_var(&u_var); +} + + +/* + * exp_var() - + * + * Raise e to the power of x, computed to rscale fractional digits + */ +static void +exp_var(const NumericVar *arg, NumericVar *result, int rscale) +{ + NumericVar x; + NumericVar elem; + int ni; + double val; + int dweight; + int ndiv2; + int sig_digits; + int local_rscale; + + init_var(&x); + init_var(&elem); + + set_var_from_var(arg, &x); + + /* + * Estimate the dweight of the result using floating point arithmetic, so + * that we can choose an appropriate local rscale for the calculation. + */ + val = numericvar_to_double_no_overflow(&x); + + /* Guard against overflow/underflow */ + /* If you change this limit, see also power_var()'s limit */ + if (fabs(val) >= NUMERIC_MAX_RESULT_SCALE * 3) + { + if (val > 0) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("value overflows numeric format"))); + zero_var(result); + result->dscale = rscale; + return; + } + + /* decimal weight = log10(e^x) = x * log10(e) */ + dweight = (int) (val * 0.434294481903252); + + /* + * Reduce x to the range -0.01 <= x <= 0.01 (approximately) by dividing by + * 2^ndiv2, to improve the convergence rate of the Taylor series. + * + * Note that the overflow check above ensures that fabs(x) < 6000, which + * means that ndiv2 <= 20 here. + */ + if (fabs(val) > 0.01) + { + ndiv2 = 1; + val /= 2; + + while (fabs(val) > 0.01) + { + ndiv2++; + val /= 2; + } + + local_rscale = x.dscale + ndiv2; + div_var_int(&x, 1 << ndiv2, 0, &x, local_rscale, true); + } + else + ndiv2 = 0; + + /* + * Set the scale for the Taylor series expansion. The final result has + * (dweight + rscale + 1) significant digits. In addition, we have to + * raise the Taylor series result to the power 2^ndiv2, which introduces + * an error of up to around log10(2^ndiv2) digits, so work with this many + * extra digits of precision (plus a few more for good measure). + */ + sig_digits = 1 + dweight + rscale + (int) (ndiv2 * 0.301029995663981); + sig_digits = Max(sig_digits, 0) + 8; + + local_rscale = sig_digits - 1; + + /* + * Use the Taylor series + * + * exp(x) = 1 + x + x^2/2! + x^3/3! + ... + * + * Given the limited range of x, this should converge reasonably quickly. + * We run the series until the terms fall below the local_rscale limit. + */ + add_var(&const_one, &x, result); + + mul_var(&x, &x, &elem, local_rscale); + ni = 2; + div_var_int(&elem, ni, 0, &elem, local_rscale, true); + + while (elem.ndigits != 0) + { + add_var(result, &elem, result); + + mul_var(&elem, &x, &elem, local_rscale); + ni++; + div_var_int(&elem, ni, 0, &elem, local_rscale, true); + } + + /* + * Compensate for the argument range reduction. Since the weight of the + * result doubles with each multiplication, we can reduce the local rscale + * as we proceed. + */ + while (ndiv2-- > 0) + { + local_rscale = sig_digits - result->weight * 2 * DEC_DIGITS; + local_rscale = Max(local_rscale, NUMERIC_MIN_DISPLAY_SCALE); + mul_var(result, result, result, local_rscale); + } + + /* Round to requested rscale */ + round_var(result, rscale); + + free_var(&x); + free_var(&elem); +} + + +/* + * Estimate the dweight of the most significant decimal digit of the natural + * logarithm of a number. + * + * Essentially, we're approximating log10(abs(ln(var))). This is used to + * determine the appropriate rscale when computing natural logarithms. + * + * Note: many callers call this before range-checking the input. Therefore, + * we must be robust against values that are invalid to apply ln() to. + * We don't wish to throw an error here, so just return zero in such cases. + */ +static int +estimate_ln_dweight(const NumericVar *var) +{ + int ln_dweight; + + /* Caller should fail on ln(negative), but for the moment return zero */ + if (var->sign != NUMERIC_POS) + return 0; + + if (cmp_var(var, &const_zero_point_nine) >= 0 && + cmp_var(var, &const_one_point_one) <= 0) + { + /* + * 0.9 <= var <= 1.1 + * + * ln(var) has a negative weight (possibly very large). To get a + * reasonably accurate result, estimate it using ln(1+x) ~= x. + */ + NumericVar x; + + init_var(&x); + sub_var(var, &const_one, &x); + + if (x.ndigits > 0) + { + /* Use weight of most significant decimal digit of x */ + ln_dweight = x.weight * DEC_DIGITS + (int) log10(x.digits[0]); + } + else + { + /* x = 0. Since ln(1) = 0 exactly, we don't need extra digits */ + ln_dweight = 0; + } + + free_var(&x); + } + else + { + /* + * Estimate the logarithm using the first couple of digits from the + * input number. This will give an accurate result whenever the input + * is not too close to 1. + */ + if (var->ndigits > 0) + { + int digits; + int dweight; + double ln_var; + + digits = var->digits[0]; + dweight = var->weight * DEC_DIGITS; + + if (var->ndigits > 1) + { + digits = digits * NBASE + var->digits[1]; + dweight -= DEC_DIGITS; + } + + /*---------- + * We have var ~= digits * 10^dweight + * so ln(var) ~= ln(digits) + dweight * ln(10) + *---------- + */ + ln_var = log((double) digits) + dweight * 2.302585092994046; + ln_dweight = (int) log10(fabs(ln_var)); + } + else + { + /* Caller should fail on ln(0), but for the moment return zero */ + ln_dweight = 0; + } + } + + return ln_dweight; +} + + +/* + * ln_var() - + * + * Compute the natural log of x + */ +static void +ln_var(const NumericVar *arg, NumericVar *result, int rscale) +{ + NumericVar x; + NumericVar xx; + int ni; + NumericVar elem; + NumericVar fact; + int nsqrt; + int local_rscale; + int cmp; + + cmp = cmp_var(arg, &const_zero); + if (cmp == 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_ARGUMENT_FOR_LOG), + errmsg("cannot take logarithm of zero"))); + else if (cmp < 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_ARGUMENT_FOR_LOG), + errmsg("cannot take logarithm of a negative number"))); + + init_var(&x); + init_var(&xx); + init_var(&elem); + init_var(&fact); + + set_var_from_var(arg, &x); + set_var_from_var(&const_two, &fact); + + /* + * Reduce input into range 0.9 < x < 1.1 with repeated sqrt() operations. + * + * The final logarithm will have up to around rscale+6 significant digits. + * Each sqrt() will roughly halve the weight of x, so adjust the local + * rscale as we work so that we keep this many significant digits at each + * step (plus a few more for good measure). + * + * Note that we allow local_rscale < 0 during this input reduction + * process, which implies rounding before the decimal point. sqrt_var() + * explicitly supports this, and it significantly reduces the work + * required to reduce very large inputs to the required range. Once the + * input reduction is complete, x.weight will be 0 and its display scale + * will be non-negative again. + */ + nsqrt = 0; + while (cmp_var(&x, &const_zero_point_nine) <= 0) + { + local_rscale = rscale - x.weight * DEC_DIGITS / 2 + 8; + sqrt_var(&x, &x, local_rscale); + mul_var(&fact, &const_two, &fact, 0); + nsqrt++; + } + while (cmp_var(&x, &const_one_point_one) >= 0) + { + local_rscale = rscale - x.weight * DEC_DIGITS / 2 + 8; + sqrt_var(&x, &x, local_rscale); + mul_var(&fact, &const_two, &fact, 0); + nsqrt++; + } + + /* + * We use the Taylor series for 0.5 * ln((1+z)/(1-z)), + * + * z + z^3/3 + z^5/5 + ... + * + * where z = (x-1)/(x+1) is in the range (approximately) -0.053 .. 0.048 + * due to the above range-reduction of x. + * + * The convergence of this is not as fast as one would like, but is + * tolerable given that z is small. + * + * The Taylor series result will be multiplied by 2^(nsqrt+1), which has a + * decimal weight of (nsqrt+1) * log10(2), so work with this many extra + * digits of precision (plus a few more for good measure). + */ + local_rscale = rscale + (int) ((nsqrt + 1) * 0.301029995663981) + 8; + + sub_var(&x, &const_one, result); + add_var(&x, &const_one, &elem); + div_var_fast(result, &elem, result, local_rscale, true); + set_var_from_var(result, &xx); + mul_var(result, result, &x, local_rscale); + + ni = 1; + + for (;;) + { + ni += 2; + mul_var(&xx, &x, &xx, local_rscale); + div_var_int(&xx, ni, 0, &elem, local_rscale, true); + + if (elem.ndigits == 0) + break; + + add_var(result, &elem, result); + + if (elem.weight < (result->weight - local_rscale * 2 / DEC_DIGITS)) + break; + } + + /* Compensate for argument range reduction, round to requested rscale */ + mul_var(result, &fact, result, rscale); + + free_var(&x); + free_var(&xx); + free_var(&elem); + free_var(&fact); +} + + +/* + * log_var() - + * + * Compute the logarithm of num in a given base. + * + * Note: this routine chooses dscale of the result. + */ +static void +log_var(const NumericVar *base, const NumericVar *num, NumericVar *result) +{ + NumericVar ln_base; + NumericVar ln_num; + int ln_base_dweight; + int ln_num_dweight; + int result_dweight; + int rscale; + int ln_base_rscale; + int ln_num_rscale; + + init_var(&ln_base); + init_var(&ln_num); + + /* Estimated dweights of ln(base), ln(num) and the final result */ + ln_base_dweight = estimate_ln_dweight(base); + ln_num_dweight = estimate_ln_dweight(num); + result_dweight = ln_num_dweight - ln_base_dweight; + + /* + * Select the scale of the result so that it will have at least + * NUMERIC_MIN_SIG_DIGITS significant digits and is not less than either + * input's display scale. + */ + rscale = NUMERIC_MIN_SIG_DIGITS - result_dweight; + rscale = Max(rscale, base->dscale); + rscale = Max(rscale, num->dscale); + rscale = Max(rscale, NUMERIC_MIN_DISPLAY_SCALE); + rscale = Min(rscale, NUMERIC_MAX_DISPLAY_SCALE); + + /* + * Set the scales for ln(base) and ln(num) so that they each have more + * significant digits than the final result. + */ + ln_base_rscale = rscale + result_dweight - ln_base_dweight + 8; + ln_base_rscale = Max(ln_base_rscale, NUMERIC_MIN_DISPLAY_SCALE); + + ln_num_rscale = rscale + result_dweight - ln_num_dweight + 8; + ln_num_rscale = Max(ln_num_rscale, NUMERIC_MIN_DISPLAY_SCALE); + + /* Form natural logarithms */ + ln_var(base, &ln_base, ln_base_rscale); + ln_var(num, &ln_num, ln_num_rscale); + + /* Divide and round to the required scale */ + div_var_fast(&ln_num, &ln_base, result, rscale, true); + + free_var(&ln_num); + free_var(&ln_base); +} + + +/* + * power_var() - + * + * Raise base to the power of exp + * + * Note: this routine chooses dscale of the result. + */ +static void +power_var(const NumericVar *base, const NumericVar *exp, NumericVar *result) +{ + int res_sign; + NumericVar abs_base; + NumericVar ln_base; + NumericVar ln_num; + int ln_dweight; + int rscale; + int sig_digits; + int local_rscale; + double val; + + /* If exp can be represented as an integer, use power_var_int */ + if (exp->ndigits == 0 || exp->ndigits <= exp->weight + 1) + { + /* exact integer, but does it fit in int? */ + int64 expval64; + + if (numericvar_to_int64(exp, &expval64)) + { + if (expval64 >= PG_INT32_MIN && expval64 <= PG_INT32_MAX) + { + /* Okay, use power_var_int */ + power_var_int(base, (int) expval64, exp->dscale, result); + return; + } + } + } + + /* + * This avoids log(0) for cases of 0 raised to a non-integer. 0 ^ 0 is + * handled by power_var_int(). + */ + if (cmp_var(base, &const_zero) == 0) + { + set_var_from_var(&const_zero, result); + result->dscale = NUMERIC_MIN_SIG_DIGITS; /* no need to round */ + return; + } + + init_var(&abs_base); + init_var(&ln_base); + init_var(&ln_num); + + /* + * If base is negative, insist that exp be an integer. The result is then + * positive if exp is even and negative if exp is odd. + */ + if (base->sign == NUMERIC_NEG) + { + /* + * Check that exp is an integer. This error code is defined by the + * SQL standard, and matches other errors in numeric_power(). + */ + if (exp->ndigits > 0 && exp->ndigits > exp->weight + 1) + ereport(ERROR, + (errcode(ERRCODE_INVALID_ARGUMENT_FOR_POWER_FUNCTION), + errmsg("a negative number raised to a non-integer power yields a complex result"))); + + /* Test if exp is odd or even */ + if (exp->ndigits > 0 && exp->ndigits == exp->weight + 1 && + (exp->digits[exp->ndigits - 1] & 1)) + res_sign = NUMERIC_NEG; + else + res_sign = NUMERIC_POS; + + /* Then work with abs(base) below */ + set_var_from_var(base, &abs_base); + abs_base.sign = NUMERIC_POS; + base = &abs_base; + } + else + res_sign = NUMERIC_POS; + + /*---------- + * Decide on the scale for the ln() calculation. For this we need an + * estimate of the weight of the result, which we obtain by doing an + * initial low-precision calculation of exp * ln(base). + * + * We want result = e ^ (exp * ln(base)) + * so result dweight = log10(result) = exp * ln(base) * log10(e) + * + * We also perform a crude overflow test here so that we can exit early if + * the full-precision result is sure to overflow, and to guard against + * integer overflow when determining the scale for the real calculation. + * exp_var() supports inputs up to NUMERIC_MAX_RESULT_SCALE * 3, so the + * result will overflow if exp * ln(base) >= NUMERIC_MAX_RESULT_SCALE * 3. + * Since the values here are only approximations, we apply a small fuzz + * factor to this overflow test and let exp_var() determine the exact + * overflow threshold so that it is consistent for all inputs. + *---------- + */ + ln_dweight = estimate_ln_dweight(base); + + /* + * Set the scale for the low-precision calculation, computing ln(base) to + * around 8 significant digits. Note that ln_dweight may be as small as + * -SHRT_MAX, so the scale may exceed NUMERIC_MAX_DISPLAY_SCALE here. + */ + local_rscale = 8 - ln_dweight; + local_rscale = Max(local_rscale, NUMERIC_MIN_DISPLAY_SCALE); + + ln_var(base, &ln_base, local_rscale); + + mul_var(&ln_base, exp, &ln_num, local_rscale); + + val = numericvar_to_double_no_overflow(&ln_num); + + /* initial overflow/underflow test with fuzz factor */ + if (fabs(val) > NUMERIC_MAX_RESULT_SCALE * 3.01) + { + if (val > 0) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("value overflows numeric format"))); + zero_var(result); + result->dscale = NUMERIC_MAX_DISPLAY_SCALE; + return; + } + + val *= 0.434294481903252; /* approximate decimal result weight */ + + /* choose the result scale */ + rscale = NUMERIC_MIN_SIG_DIGITS - (int) val; + rscale = Max(rscale, base->dscale); + rscale = Max(rscale, exp->dscale); + rscale = Max(rscale, NUMERIC_MIN_DISPLAY_SCALE); + rscale = Min(rscale, NUMERIC_MAX_DISPLAY_SCALE); + + /* significant digits required in the result */ + sig_digits = rscale + (int) val; + sig_digits = Max(sig_digits, 0); + + /* set the scale for the real exp * ln(base) calculation */ + local_rscale = sig_digits - ln_dweight + 8; + local_rscale = Max(local_rscale, NUMERIC_MIN_DISPLAY_SCALE); + + /* and do the real calculation */ + + ln_var(base, &ln_base, local_rscale); + + mul_var(&ln_base, exp, &ln_num, local_rscale); + + exp_var(&ln_num, result, rscale); + + if (res_sign == NUMERIC_NEG && result->ndigits > 0) + result->sign = NUMERIC_NEG; + + free_var(&ln_num); + free_var(&ln_base); + free_var(&abs_base); +} + +/* + * power_var_int() - + * + * Raise base to the power of exp, where exp is an integer. + * + * Note: this routine chooses dscale of the result. + */ +static void +power_var_int(const NumericVar *base, int exp, int exp_dscale, + NumericVar *result) +{ + double f; + int p; + int i; + int rscale; + int sig_digits; + unsigned int mask; + bool neg; + NumericVar base_prod; + int local_rscale; + + /* + * Choose the result scale. For this we need an estimate of the decimal + * weight of the result, which we obtain by approximating using double + * precision arithmetic. + * + * We also perform crude overflow/underflow tests here so that we can exit + * early if the result is sure to overflow/underflow, and to guard against + * integer overflow when choosing the result scale. + */ + if (base->ndigits != 0) + { + /*---------- + * Choose f (double) and p (int) such that base ~= f * 10^p. + * Then log10(result) = log10(base^exp) ~= exp * (log10(f) + p). + *---------- + */ + f = base->digits[0]; + p = base->weight * DEC_DIGITS; + + for (i = 1; i < base->ndigits && i * DEC_DIGITS < 16; i++) + { + f = f * NBASE + base->digits[i]; + p -= DEC_DIGITS; + } + + f = exp * (log10(f) + p); /* approximate decimal result weight */ + } + else + f = 0; /* result is 0 or 1 (weight 0), or error */ + + /* overflow/underflow tests with fuzz factors */ + if (f > (SHRT_MAX + 1) * DEC_DIGITS) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("value overflows numeric format"))); + if (f + 1 < -NUMERIC_MAX_DISPLAY_SCALE) + { + zero_var(result); + result->dscale = NUMERIC_MAX_DISPLAY_SCALE; + return; + } + + /* + * Choose the result scale in the same way as power_var(), so it has at + * least NUMERIC_MIN_SIG_DIGITS significant digits and is not less than + * either input's display scale. + */ + rscale = NUMERIC_MIN_SIG_DIGITS - (int) f; + rscale = Max(rscale, base->dscale); + rscale = Max(rscale, exp_dscale); + rscale = Max(rscale, NUMERIC_MIN_DISPLAY_SCALE); + rscale = Min(rscale, NUMERIC_MAX_DISPLAY_SCALE); + + /* Handle some common special cases, as well as corner cases */ + switch (exp) + { + case 0: + + /* + * While 0 ^ 0 can be either 1 or indeterminate (error), we treat + * it as 1 because most programming languages do this. SQL:2003 + * also requires a return value of 1. + * https://en.wikipedia.org/wiki/Exponentiation#Zero_to_the_zero_power + */ + set_var_from_var(&const_one, result); + result->dscale = rscale; /* no need to round */ + return; + case 1: + set_var_from_var(base, result); + round_var(result, rscale); + return; + case -1: + div_var(&const_one, base, result, rscale, true); + return; + case 2: + mul_var(base, base, result, rscale); + return; + default: + break; + } + + /* Handle the special case where the base is zero */ + if (base->ndigits == 0) + { + if (exp < 0) + ereport(ERROR, + (errcode(ERRCODE_DIVISION_BY_ZERO), + errmsg("division by zero"))); + zero_var(result); + result->dscale = rscale; + return; + } + + /* + * The general case repeatedly multiplies base according to the bit + * pattern of exp. + * + * The local rscale used for each multiplication is varied to keep a fixed + * number of significant digits, sufficient to give the required result + * scale. + */ + + /* + * Approximate number of significant digits in the result. Note that the + * underflow test above, together with the choice of rscale, ensures that + * this approximation is necessarily > 0. + */ + sig_digits = 1 + rscale + (int) f; + + /* + * The multiplications to produce the result may introduce an error of up + * to around log10(abs(exp)) digits, so work with this many extra digits + * of precision (plus a few more for good measure). + */ + sig_digits += (int) log(fabs((double) exp)) + 8; + + /* + * Now we can proceed with the multiplications. + */ + neg = (exp < 0); + mask = abs(exp); + + init_var(&base_prod); + set_var_from_var(base, &base_prod); + + if (mask & 1) + set_var_from_var(base, result); + else + set_var_from_var(&const_one, result); + + while ((mask >>= 1) > 0) + { + /* + * Do the multiplications using rscales large enough to hold the + * results to the required number of significant digits, but don't + * waste time by exceeding the scales of the numbers themselves. + */ + local_rscale = sig_digits - 2 * base_prod.weight * DEC_DIGITS; + local_rscale = Min(local_rscale, 2 * base_prod.dscale); + local_rscale = Max(local_rscale, NUMERIC_MIN_DISPLAY_SCALE); + + mul_var(&base_prod, &base_prod, &base_prod, local_rscale); + + if (mask & 1) + { + local_rscale = sig_digits - + (base_prod.weight + result->weight) * DEC_DIGITS; + local_rscale = Min(local_rscale, + base_prod.dscale + result->dscale); + local_rscale = Max(local_rscale, NUMERIC_MIN_DISPLAY_SCALE); + + mul_var(&base_prod, result, result, local_rscale); + } + + /* + * When abs(base) > 1, the number of digits to the left of the decimal + * point in base_prod doubles at each iteration, so if exp is large we + * could easily spend large amounts of time and memory space doing the + * multiplications. But once the weight exceeds what will fit in + * int16, the final result is guaranteed to overflow (or underflow, if + * exp < 0), so we can give up before wasting too many cycles. + */ + if (base_prod.weight > SHRT_MAX || result->weight > SHRT_MAX) + { + /* overflow, unless neg, in which case result should be 0 */ + if (!neg) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("value overflows numeric format"))); + zero_var(result); + neg = false; + break; + } + } + + free_var(&base_prod); + + /* Compensate for input sign, and round to requested rscale */ + if (neg) + div_var_fast(&const_one, result, result, rscale, true); + else + round_var(result, rscale); +} + +/* + * power_ten_int() - + * + * Raise ten to the power of exp, where exp is an integer. Note that unlike + * power_var_int(), this does no overflow/underflow checking or rounding. + */ +static void +power_ten_int(int exp, NumericVar *result) +{ + /* Construct the result directly, starting from 10^0 = 1 */ + set_var_from_var(&const_one, result); + + /* Scale needed to represent the result exactly */ + result->dscale = exp < 0 ? -exp : 0; + + /* Base-NBASE weight of result and remaining exponent */ + if (exp >= 0) + result->weight = exp / DEC_DIGITS; + else + result->weight = (exp + 1) / DEC_DIGITS - 1; + + exp -= result->weight * DEC_DIGITS; + + /* Final adjustment of the result's single NBASE digit */ + while (exp-- > 0) + result->digits[0] *= 10; +} + + +/* ---------------------------------------------------------------------- + * + * Following are the lowest level functions that operate unsigned + * on the variable level + * + * ---------------------------------------------------------------------- + */ + + +/* ---------- + * cmp_abs() - + * + * Compare the absolute values of var1 and var2 + * Returns: -1 for ABS(var1) < ABS(var2) + * 0 for ABS(var1) == ABS(var2) + * 1 for ABS(var1) > ABS(var2) + * ---------- + */ +static int +cmp_abs(const NumericVar *var1, const NumericVar *var2) +{ + return cmp_abs_common(var1->digits, var1->ndigits, var1->weight, + var2->digits, var2->ndigits, var2->weight); +} + +/* ---------- + * cmp_abs_common() - + * + * Main routine of cmp_abs(). This function can be used by both + * NumericVar and Numeric. + * ---------- + */ +static int +cmp_abs_common(const NumericDigit *var1digits, int var1ndigits, int var1weight, + const NumericDigit *var2digits, int var2ndigits, int var2weight) +{ + int i1 = 0; + int i2 = 0; + + /* Check any digits before the first common digit */ + + while (var1weight > var2weight && i1 < var1ndigits) + { + if (var1digits[i1++] != 0) + return 1; + var1weight--; + } + while (var2weight > var1weight && i2 < var2ndigits) + { + if (var2digits[i2++] != 0) + return -1; + var2weight--; + } + + /* At this point, either w1 == w2 or we've run out of digits */ + + if (var1weight == var2weight) + { + while (i1 < var1ndigits && i2 < var2ndigits) + { + int stat = var1digits[i1++] - var2digits[i2++]; + + if (stat) + { + if (stat > 0) + return 1; + return -1; + } + } + } + + /* + * At this point, we've run out of digits on one side or the other; so any + * remaining nonzero digits imply that side is larger + */ + while (i1 < var1ndigits) + { + if (var1digits[i1++] != 0) + return 1; + } + while (i2 < var2ndigits) + { + if (var2digits[i2++] != 0) + return -1; + } + + return 0; +} + + +/* + * add_abs() - + * + * Add the absolute values of two variables into result. + * result might point to one of the operands without danger. + */ +static void +add_abs(const NumericVar *var1, const NumericVar *var2, NumericVar *result) +{ + NumericDigit *res_buf; + NumericDigit *res_digits; + int res_ndigits; + int res_weight; + int res_rscale, + rscale1, + rscale2; + int res_dscale; + int i, + i1, + i2; + int carry = 0; + + /* copy these values into local vars for speed in inner loop */ + int var1ndigits = var1->ndigits; + int var2ndigits = var2->ndigits; + NumericDigit *var1digits = var1->digits; + NumericDigit *var2digits = var2->digits; + + res_weight = Max(var1->weight, var2->weight) + 1; + + res_dscale = Max(var1->dscale, var2->dscale); + + /* Note: here we are figuring rscale in base-NBASE digits */ + rscale1 = var1->ndigits - var1->weight - 1; + rscale2 = var2->ndigits - var2->weight - 1; + res_rscale = Max(rscale1, rscale2); + + res_ndigits = res_rscale + res_weight + 1; + if (res_ndigits <= 0) + res_ndigits = 1; + + res_buf = digitbuf_alloc(res_ndigits + 1); + res_buf[0] = 0; /* spare digit for later rounding */ + res_digits = res_buf + 1; + + i1 = res_rscale + var1->weight + 1; + i2 = res_rscale + var2->weight + 1; + for (i = res_ndigits - 1; i >= 0; i--) + { + i1--; + i2--; + if (i1 >= 0 && i1 < var1ndigits) + carry += var1digits[i1]; + if (i2 >= 0 && i2 < var2ndigits) + carry += var2digits[i2]; + + if (carry >= NBASE) + { + res_digits[i] = carry - NBASE; + carry = 1; + } + else + { + res_digits[i] = carry; + carry = 0; + } + } + + Assert(carry == 0); /* else we failed to allow for carry out */ + + digitbuf_free(result->buf); + result->ndigits = res_ndigits; + result->buf = res_buf; + result->digits = res_digits; + result->weight = res_weight; + result->dscale = res_dscale; + + /* Remove leading/trailing zeroes */ + strip_var(result); +} + + +/* + * sub_abs() + * + * Subtract the absolute value of var2 from the absolute value of var1 + * and store in result. result might point to one of the operands + * without danger. + * + * ABS(var1) MUST BE GREATER OR EQUAL ABS(var2) !!! + */ +static void +sub_abs(const NumericVar *var1, const NumericVar *var2, NumericVar *result) +{ + NumericDigit *res_buf; + NumericDigit *res_digits; + int res_ndigits; + int res_weight; + int res_rscale, + rscale1, + rscale2; + int res_dscale; + int i, + i1, + i2; + int borrow = 0; + + /* copy these values into local vars for speed in inner loop */ + int var1ndigits = var1->ndigits; + int var2ndigits = var2->ndigits; + NumericDigit *var1digits = var1->digits; + NumericDigit *var2digits = var2->digits; + + res_weight = var1->weight; + + res_dscale = Max(var1->dscale, var2->dscale); + + /* Note: here we are figuring rscale in base-NBASE digits */ + rscale1 = var1->ndigits - var1->weight - 1; + rscale2 = var2->ndigits - var2->weight - 1; + res_rscale = Max(rscale1, rscale2); + + res_ndigits = res_rscale + res_weight + 1; + if (res_ndigits <= 0) + res_ndigits = 1; + + res_buf = digitbuf_alloc(res_ndigits + 1); + res_buf[0] = 0; /* spare digit for later rounding */ + res_digits = res_buf + 1; + + i1 = res_rscale + var1->weight + 1; + i2 = res_rscale + var2->weight + 1; + for (i = res_ndigits - 1; i >= 0; i--) + { + i1--; + i2--; + if (i1 >= 0 && i1 < var1ndigits) + borrow += var1digits[i1]; + if (i2 >= 0 && i2 < var2ndigits) + borrow -= var2digits[i2]; + + if (borrow < 0) + { + res_digits[i] = borrow + NBASE; + borrow = -1; + } + else + { + res_digits[i] = borrow; + borrow = 0; + } + } + + Assert(borrow == 0); /* else caller gave us var1 < var2 */ + + digitbuf_free(result->buf); + result->ndigits = res_ndigits; + result->buf = res_buf; + result->digits = res_digits; + result->weight = res_weight; + result->dscale = res_dscale; + + /* Remove leading/trailing zeroes */ + strip_var(result); +} + +/* + * round_var + * + * Round the value of a variable to no more than rscale decimal digits + * after the decimal point. NOTE: we allow rscale < 0 here, implying + * rounding before the decimal point. + */ +static void +round_var(NumericVar *var, int rscale) +{ + NumericDigit *digits = var->digits; + int di; + int ndigits; + int carry; + + var->dscale = rscale; + + /* decimal digits wanted */ + di = (var->weight + 1) * DEC_DIGITS + rscale; + + /* + * If di = 0, the value loses all digits, but could round up to 1 if its + * first extra digit is >= 5. If di < 0 the result must be 0. + */ + if (di < 0) + { + var->ndigits = 0; + var->weight = 0; + var->sign = NUMERIC_POS; + } + else + { + /* NBASE digits wanted */ + ndigits = (di + DEC_DIGITS - 1) / DEC_DIGITS; + + /* 0, or number of decimal digits to keep in last NBASE digit */ + di %= DEC_DIGITS; + + if (ndigits < var->ndigits || + (ndigits == var->ndigits && di > 0)) + { + var->ndigits = ndigits; + +#if DEC_DIGITS == 1 + /* di must be zero */ + carry = (digits[ndigits] >= HALF_NBASE) ? 1 : 0; +#else + if (di == 0) + carry = (digits[ndigits] >= HALF_NBASE) ? 1 : 0; + else + { + /* Must round within last NBASE digit */ + int extra, + pow10; + +#if DEC_DIGITS == 4 + pow10 = round_powers[di]; +#elif DEC_DIGITS == 2 + pow10 = 10; +#else +#error unsupported NBASE +#endif + extra = digits[--ndigits] % pow10; + digits[ndigits] -= extra; + carry = 0; + if (extra >= pow10 / 2) + { + pow10 += digits[ndigits]; + if (pow10 >= NBASE) + { + pow10 -= NBASE; + carry = 1; + } + digits[ndigits] = pow10; + } + } +#endif + + /* Propagate carry if needed */ + while (carry) + { + carry += digits[--ndigits]; + if (carry >= NBASE) + { + digits[ndigits] = carry - NBASE; + carry = 1; + } + else + { + digits[ndigits] = carry; + carry = 0; + } + } + + if (ndigits < 0) + { + Assert(ndigits == -1); /* better not have added > 1 digit */ + Assert(var->digits > var->buf); + var->digits--; + var->ndigits++; + var->weight++; + } + } + } +} + +/* + * trunc_var + * + * Truncate (towards zero) the value of a variable at rscale decimal digits + * after the decimal point. NOTE: we allow rscale < 0 here, implying + * truncation before the decimal point. + */ +static void +trunc_var(NumericVar *var, int rscale) +{ + int di; + int ndigits; + + var->dscale = rscale; + + /* decimal digits wanted */ + di = (var->weight + 1) * DEC_DIGITS + rscale; + + /* + * If di <= 0, the value loses all digits. + */ + if (di <= 0) + { + var->ndigits = 0; + var->weight = 0; + var->sign = NUMERIC_POS; + } + else + { + /* NBASE digits wanted */ + ndigits = (di + DEC_DIGITS - 1) / DEC_DIGITS; + + if (ndigits <= var->ndigits) + { + var->ndigits = ndigits; + +#if DEC_DIGITS == 1 + /* no within-digit stuff to worry about */ +#else + /* 0, or number of decimal digits to keep in last NBASE digit */ + di %= DEC_DIGITS; + + if (di > 0) + { + /* Must truncate within last NBASE digit */ + NumericDigit *digits = var->digits; + int extra, + pow10; + +#if DEC_DIGITS == 4 + pow10 = round_powers[di]; +#elif DEC_DIGITS == 2 + pow10 = 10; +#else +#error unsupported NBASE +#endif + extra = digits[--ndigits] % pow10; + digits[ndigits] -= extra; + } +#endif + } + } +} + +/* + * strip_var + * + * Strip any leading and trailing zeroes from a numeric variable + */ +static void +strip_var(NumericVar *var) +{ + NumericDigit *digits = var->digits; + int ndigits = var->ndigits; + + /* Strip leading zeroes */ + while (ndigits > 0 && *digits == 0) + { + digits++; + var->weight--; + ndigits--; + } + + /* Strip trailing zeroes */ + while (ndigits > 0 && digits[ndigits - 1] == 0) + ndigits--; + + /* If it's zero, normalize the sign and weight */ + if (ndigits == 0) + { + var->sign = NUMERIC_POS; + var->weight = 0; + } + + var->digits = digits; + var->ndigits = ndigits; +} + + +/* ---------------------------------------------------------------------- + * + * Fast sum accumulator functions + * + * ---------------------------------------------------------------------- + */ + +/* + * Reset the accumulator's value to zero. The buffers to hold the digits + * are not free'd. + */ +static void +accum_sum_reset(NumericSumAccum *accum) +{ + int i; + + accum->dscale = 0; + for (i = 0; i < accum->ndigits; i++) + { + accum->pos_digits[i] = 0; + accum->neg_digits[i] = 0; + } +} + +/* + * Accumulate a new value. + */ +static void +accum_sum_add(NumericSumAccum *accum, const NumericVar *val) +{ + int32 *accum_digits; + int i, + val_i; + int val_ndigits; + NumericDigit *val_digits; + + /* + * If we have accumulated too many values since the last carry + * propagation, do it now, to avoid overflowing. (We could allow more + * than NBASE - 1, if we reserved two extra digits, rather than one, for + * carry propagation. But even with NBASE - 1, this needs to be done so + * seldom, that the performance difference is negligible.) + */ + if (accum->num_uncarried == NBASE - 1) + accum_sum_carry(accum); + + /* + * Adjust the weight or scale of the old value, so that it can accommodate + * the new value. + */ + accum_sum_rescale(accum, val); + + /* */ + if (val->sign == NUMERIC_POS) + accum_digits = accum->pos_digits; + else + accum_digits = accum->neg_digits; + + /* copy these values into local vars for speed in loop */ + val_ndigits = val->ndigits; + val_digits = val->digits; + + i = accum->weight - val->weight; + for (val_i = 0; val_i < val_ndigits; val_i++) + { + accum_digits[i] += (int32) val_digits[val_i]; + i++; + } + + accum->num_uncarried++; +} + +/* + * Propagate carries. + */ +static void +accum_sum_carry(NumericSumAccum *accum) +{ + int i; + int ndigits; + int32 *dig; + int32 carry; + int32 newdig = 0; + + /* + * If no new values have been added since last carry propagation, nothing + * to do. + */ + if (accum->num_uncarried == 0) + return; + + /* + * We maintain that the weight of the accumulator is always one larger + * than needed to hold the current value, before carrying, to make sure + * there is enough space for the possible extra digit when carry is + * propagated. We cannot expand the buffer here, unless we require + * callers of accum_sum_final() to switch to the right memory context. + */ + Assert(accum->pos_digits[0] == 0 && accum->neg_digits[0] == 0); + + ndigits = accum->ndigits; + + /* Propagate carry in the positive sum */ + dig = accum->pos_digits; + carry = 0; + for (i = ndigits - 1; i >= 0; i--) + { + newdig = dig[i] + carry; + if (newdig >= NBASE) + { + carry = newdig / NBASE; + newdig -= carry * NBASE; + } + else + carry = 0; + dig[i] = newdig; + } + /* Did we use up the digit reserved for carry propagation? */ + if (newdig > 0) + accum->have_carry_space = false; + + /* And the same for the negative sum */ + dig = accum->neg_digits; + carry = 0; + for (i = ndigits - 1; i >= 0; i--) + { + newdig = dig[i] + carry; + if (newdig >= NBASE) + { + carry = newdig / NBASE; + newdig -= carry * NBASE; + } + else + carry = 0; + dig[i] = newdig; + } + if (newdig > 0) + accum->have_carry_space = false; + + accum->num_uncarried = 0; +} + +/* + * Re-scale accumulator to accommodate new value. + * + * If the new value has more digits than the current digit buffers in the + * accumulator, enlarge the buffers. + */ +static void +accum_sum_rescale(NumericSumAccum *accum, const NumericVar *val) +{ + int old_weight = accum->weight; + int old_ndigits = accum->ndigits; + int accum_ndigits; + int accum_weight; + int accum_rscale; + int val_rscale; + + accum_weight = old_weight; + accum_ndigits = old_ndigits; + + /* + * Does the new value have a larger weight? If so, enlarge the buffers, + * and shift the existing value to the new weight, by adding leading + * zeros. + * + * We enforce that the accumulator always has a weight one larger than + * needed for the inputs, so that we have space for an extra digit at the + * final carry-propagation phase, if necessary. + */ + if (val->weight >= accum_weight) + { + accum_weight = val->weight + 1; + accum_ndigits = accum_ndigits + (accum_weight - old_weight); + } + + /* + * Even though the new value is small, we might've used up the space + * reserved for the carry digit in the last call to accum_sum_carry(). If + * so, enlarge to make room for another one. + */ + else if (!accum->have_carry_space) + { + accum_weight++; + accum_ndigits++; + } + + /* Is the new value wider on the right side? */ + accum_rscale = accum_ndigits - accum_weight - 1; + val_rscale = val->ndigits - val->weight - 1; + if (val_rscale > accum_rscale) + accum_ndigits = accum_ndigits + (val_rscale - accum_rscale); + + if (accum_ndigits != old_ndigits || + accum_weight != old_weight) + { + int32 *new_pos_digits; + int32 *new_neg_digits; + int weightdiff; + + weightdiff = accum_weight - old_weight; + + new_pos_digits = palloc0(accum_ndigits * sizeof(int32)); + new_neg_digits = palloc0(accum_ndigits * sizeof(int32)); + + if (accum->pos_digits) + { + memcpy(&new_pos_digits[weightdiff], accum->pos_digits, + old_ndigits * sizeof(int32)); + pfree(accum->pos_digits); + + memcpy(&new_neg_digits[weightdiff], accum->neg_digits, + old_ndigits * sizeof(int32)); + pfree(accum->neg_digits); + } + + accum->pos_digits = new_pos_digits; + accum->neg_digits = new_neg_digits; + + accum->weight = accum_weight; + accum->ndigits = accum_ndigits; + + Assert(accum->pos_digits[0] == 0 && accum->neg_digits[0] == 0); + accum->have_carry_space = true; + } + + if (val->dscale > accum->dscale) + accum->dscale = val->dscale; +} + +/* + * Return the current value of the accumulator. This perform final carry + * propagation, and adds together the positive and negative sums. + * + * Unlike all the other routines, the caller is not required to switch to + * the memory context that holds the accumulator. + */ +static void +accum_sum_final(NumericSumAccum *accum, NumericVar *result) +{ + int i; + NumericVar pos_var; + NumericVar neg_var; + + if (accum->ndigits == 0) + { + set_var_from_var(&const_zero, result); + return; + } + + /* Perform final carry */ + accum_sum_carry(accum); + + /* Create NumericVars representing the positive and negative sums */ + init_var(&pos_var); + init_var(&neg_var); + + pos_var.ndigits = neg_var.ndigits = accum->ndigits; + pos_var.weight = neg_var.weight = accum->weight; + pos_var.dscale = neg_var.dscale = accum->dscale; + pos_var.sign = NUMERIC_POS; + neg_var.sign = NUMERIC_NEG; + + pos_var.buf = pos_var.digits = digitbuf_alloc(accum->ndigits); + neg_var.buf = neg_var.digits = digitbuf_alloc(accum->ndigits); + + for (i = 0; i < accum->ndigits; i++) + { + Assert(accum->pos_digits[i] < NBASE); + pos_var.digits[i] = (int16) accum->pos_digits[i]; + + Assert(accum->neg_digits[i] < NBASE); + neg_var.digits[i] = (int16) accum->neg_digits[i]; + } + + /* And add them together */ + add_var(&pos_var, &neg_var, result); + + /* Remove leading/trailing zeroes */ + strip_var(result); +} + +/* + * Copy an accumulator's state. + * + * 'dst' is assumed to be uninitialized beforehand. No attempt is made at + * freeing old values. + */ +static void +accum_sum_copy(NumericSumAccum *dst, NumericSumAccum *src) +{ + dst->pos_digits = palloc(src->ndigits * sizeof(int32)); + dst->neg_digits = palloc(src->ndigits * sizeof(int32)); + + memcpy(dst->pos_digits, src->pos_digits, src->ndigits * sizeof(int32)); + memcpy(dst->neg_digits, src->neg_digits, src->ndigits * sizeof(int32)); + dst->num_uncarried = src->num_uncarried; + dst->ndigits = src->ndigits; + dst->weight = src->weight; + dst->dscale = src->dscale; +} + +/* + * Add the current value of 'accum2' into 'accum'. + */ +static void +accum_sum_combine(NumericSumAccum *accum, NumericSumAccum *accum2) +{ + NumericVar tmp_var; + + init_var(&tmp_var); + + accum_sum_final(accum2, &tmp_var); + accum_sum_add(accum, &tmp_var); + + free_var(&tmp_var); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/numutils.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/numutils.c new file mode 100644 index 00000000000..d07a5602076 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/numutils.c @@ -0,0 +1,1315 @@ +/*------------------------------------------------------------------------- + * + * numutils.c + * utility functions for I/O of built-in numeric types. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/adt/numutils.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <math.h> +#include <limits.h> +#include <ctype.h> + +#include "common/int.h" +#include "utils/builtins.h" +#include "port/pg_bitutils.h" + +/* + * A table of all two-digit numbers. This is used to speed up decimal digit + * generation by copying pairs of digits into the final output. + */ +static const char DIGIT_TABLE[200] = +"00" "01" "02" "03" "04" "05" "06" "07" "08" "09" +"10" "11" "12" "13" "14" "15" "16" "17" "18" "19" +"20" "21" "22" "23" "24" "25" "26" "27" "28" "29" +"30" "31" "32" "33" "34" "35" "36" "37" "38" "39" +"40" "41" "42" "43" "44" "45" "46" "47" "48" "49" +"50" "51" "52" "53" "54" "55" "56" "57" "58" "59" +"60" "61" "62" "63" "64" "65" "66" "67" "68" "69" +"70" "71" "72" "73" "74" "75" "76" "77" "78" "79" +"80" "81" "82" "83" "84" "85" "86" "87" "88" "89" +"90" "91" "92" "93" "94" "95" "96" "97" "98" "99"; + +/* + * Adapted from http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog10 + */ +static inline int +decimalLength32(const uint32 v) +{ + int t; + static const uint32 PowersOfTen[] = { + 1, 10, 100, + 1000, 10000, 100000, + 1000000, 10000000, 100000000, + 1000000000 + }; + + /* + * Compute base-10 logarithm by dividing the base-2 logarithm by a + * good-enough approximation of the base-2 logarithm of 10 + */ + t = (pg_leftmost_one_pos32(v) + 1) * 1233 / 4096; + return t + (v >= PowersOfTen[t]); +} + +static inline int +decimalLength64(const uint64 v) +{ + int t; + static const uint64 PowersOfTen[] = { + UINT64CONST(1), UINT64CONST(10), + UINT64CONST(100), UINT64CONST(1000), + UINT64CONST(10000), UINT64CONST(100000), + UINT64CONST(1000000), UINT64CONST(10000000), + UINT64CONST(100000000), UINT64CONST(1000000000), + UINT64CONST(10000000000), UINT64CONST(100000000000), + UINT64CONST(1000000000000), UINT64CONST(10000000000000), + UINT64CONST(100000000000000), UINT64CONST(1000000000000000), + UINT64CONST(10000000000000000), UINT64CONST(100000000000000000), + UINT64CONST(1000000000000000000), UINT64CONST(10000000000000000000) + }; + + /* + * Compute base-10 logarithm by dividing the base-2 logarithm by a + * good-enough approximation of the base-2 logarithm of 10 + */ + t = (pg_leftmost_one_pos64(v) + 1) * 1233 / 4096; + return t + (v >= PowersOfTen[t]); +} + +static const int8 hexlookup[128] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, +}; + +/* + * Convert input string to a signed 16 bit integer. Input strings may be + * expressed in base-10, hexadecimal, octal, or binary format, all of which + * can be prefixed by an optional sign character, either '+' (the default) or + * '-' for negative numbers. Hex strings are recognized by the digits being + * prefixed by 0x or 0X while octal strings are recognized by the 0o or 0O + * prefix. The binary representation is recognized by the 0b or 0B prefix. + * + * Allows any number of leading or trailing whitespace characters. Digits may + * optionally be separated by a single underscore character. These can only + * come between digits and not before or after the digits. Underscores have + * no effect on the return value and are supported only to assist in improving + * the human readability of the input strings. + * + * pg_strtoint16() will throw ereport() upon bad input format or overflow; + * while pg_strtoint16_safe() instead returns such complaints in *escontext, + * if it's an ErrorSaveContext. +* + * NB: Accumulate input as an unsigned number, to deal with two's complement + * representation of the most negative number, which can't be represented as a + * signed positive number. + */ +int16 +pg_strtoint16(const char *s) +{ + return pg_strtoint16_safe(s, NULL); +} + +int16 +pg_strtoint16_safe(const char *s, Node *escontext) +{ + const char *ptr = s; + const char *firstdigit; + uint16 tmp = 0; + bool neg = false; + unsigned char digit; + + /* + * The majority of cases are likely to be base-10 digits without any + * underscore separator characters. We'll first try to parse the string + * with the assumption that's the case and only fallback on a slower + * implementation which handles hex, octal and binary strings and + * underscores if the fastpath version cannot parse the string. + */ + + /* leave it up to the slow path to look for leading spaces */ + + if (*ptr == '-') + { + ptr++; + neg = true; + } + + /* a leading '+' is uncommon so leave that for the slow path */ + + /* process the first digit */ + digit = (*ptr - '0'); + + /* + * Exploit unsigned arithmetic to save having to check both the upper and + * lower bounds of the digit. + */ + if (likely(digit < 10)) + { + ptr++; + tmp = digit; + } + else + { + /* we need at least one digit */ + goto slow; + } + + /* process remaining digits */ + for (;;) + { + digit = (*ptr - '0'); + + if (digit >= 10) + break; + + ptr++; + + if (unlikely(tmp > -(PG_INT16_MIN / 10))) + goto out_of_range; + + tmp = tmp * 10 + digit; + } + + /* when the string does not end in a digit, let the slow path handle it */ + if (unlikely(*ptr != '\0')) + goto slow; + + if (neg) + { + /* check the negative equivalent will fit without overflowing */ + if (unlikely(tmp > (uint16) (-(PG_INT16_MIN + 1)) + 1)) + goto out_of_range; + return -((int16) tmp); + } + + if (unlikely(tmp > PG_INT16_MAX)) + goto out_of_range; + + return (int16) tmp; + +slow: + tmp = 0; + ptr = s; + /* no need to reset neg */ + + /* skip leading spaces */ + while (isspace((unsigned char) *ptr)) + ptr++; + + /* handle sign */ + if (*ptr == '-') + { + ptr++; + neg = true; + } + else if (*ptr == '+') + ptr++; + + /* process digits */ + if (ptr[0] == '0' && (ptr[1] == 'x' || ptr[1] == 'X')) + { + firstdigit = ptr += 2; + + for (;;) + { + if (isxdigit((unsigned char) *ptr)) + { + if (unlikely(tmp > -(PG_INT16_MIN / 16))) + goto out_of_range; + + tmp = tmp * 16 + hexlookup[(unsigned char) *ptr++]; + } + else if (*ptr == '_') + { + /* underscore must be followed by more digits */ + ptr++; + if (*ptr == '\0' || !isxdigit((unsigned char) *ptr)) + goto invalid_syntax; + } + else + break; + } + } + else if (ptr[0] == '0' && (ptr[1] == 'o' || ptr[1] == 'O')) + { + firstdigit = ptr += 2; + + for (;;) + { + if (*ptr >= '0' && *ptr <= '7') + { + if (unlikely(tmp > -(PG_INT16_MIN / 8))) + goto out_of_range; + + tmp = tmp * 8 + (*ptr++ - '0'); + } + else if (*ptr == '_') + { + /* underscore must be followed by more digits */ + ptr++; + if (*ptr == '\0' || *ptr < '0' || *ptr > '7') + goto invalid_syntax; + } + else + break; + } + } + else if (ptr[0] == '0' && (ptr[1] == 'b' || ptr[1] == 'B')) + { + firstdigit = ptr += 2; + + for (;;) + { + if (*ptr >= '0' && *ptr <= '1') + { + if (unlikely(tmp > -(PG_INT16_MIN / 2))) + goto out_of_range; + + tmp = tmp * 2 + (*ptr++ - '0'); + } + else if (*ptr == '_') + { + /* underscore must be followed by more digits */ + ptr++; + if (*ptr == '\0' || *ptr < '0' || *ptr > '1') + goto invalid_syntax; + } + else + break; + } + } + else + { + firstdigit = ptr; + + for (;;) + { + if (*ptr >= '0' && *ptr <= '9') + { + if (unlikely(tmp > -(PG_INT16_MIN / 10))) + goto out_of_range; + + tmp = tmp * 10 + (*ptr++ - '0'); + } + else if (*ptr == '_') + { + /* underscore may not be first */ + if (unlikely(ptr == firstdigit)) + goto invalid_syntax; + /* and it must be followed by more digits */ + ptr++; + if (*ptr == '\0' || !isdigit((unsigned char) *ptr)) + goto invalid_syntax; + } + else + break; + } + } + + /* require at least one digit */ + if (unlikely(ptr == firstdigit)) + goto invalid_syntax; + + /* allow trailing whitespace, but not other trailing chars */ + while (isspace((unsigned char) *ptr)) + ptr++; + + if (unlikely(*ptr != '\0')) + goto invalid_syntax; + + if (neg) + { + /* check the negative equivalent will fit without overflowing */ + if (tmp > (uint16) (-(PG_INT16_MIN + 1)) + 1) + goto out_of_range; + return -((int16) tmp); + } + + if (tmp > PG_INT16_MAX) + goto out_of_range; + + return (int16) tmp; + +out_of_range: + ereturn(escontext, 0, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("value \"%s\" is out of range for type %s", + s, "smallint"))); + +invalid_syntax: + ereturn(escontext, 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s: \"%s\"", + "smallint", s))); +} + +/* + * Convert input string to a signed 32 bit integer. Input strings may be + * expressed in base-10, hexadecimal, octal, or binary format, all of which + * can be prefixed by an optional sign character, either '+' (the default) or + * '-' for negative numbers. Hex strings are recognized by the digits being + * prefixed by 0x or 0X while octal strings are recognized by the 0o or 0O + * prefix. The binary representation is recognized by the 0b or 0B prefix. + * + * Allows any number of leading or trailing whitespace characters. Digits may + * optionally be separated by a single underscore character. These can only + * come between digits and not before or after the digits. Underscores have + * no effect on the return value and are supported only to assist in improving + * the human readability of the input strings. + * + * pg_strtoint32() will throw ereport() upon bad input format or overflow; + * while pg_strtoint32_safe() instead returns such complaints in *escontext, + * if it's an ErrorSaveContext. + * + * NB: Accumulate input as an unsigned number, to deal with two's complement + * representation of the most negative number, which can't be represented as a + * signed positive number. + */ +int32 +pg_strtoint32(const char *s) +{ + return pg_strtoint32_safe(s, NULL); +} + +int32 +pg_strtoint32_safe(const char *s, Node *escontext) +{ + const char *ptr = s; + const char *firstdigit; + uint32 tmp = 0; + bool neg = false; + unsigned char digit; + + /* + * The majority of cases are likely to be base-10 digits without any + * underscore separator characters. We'll first try to parse the string + * with the assumption that's the case and only fallback on a slower + * implementation which handles hex, octal and binary strings and + * underscores if the fastpath version cannot parse the string. + */ + + /* leave it up to the slow path to look for leading spaces */ + + if (*ptr == '-') + { + ptr++; + neg = true; + } + + /* a leading '+' is uncommon so leave that for the slow path */ + + /* process the first digit */ + digit = (*ptr - '0'); + + /* + * Exploit unsigned arithmetic to save having to check both the upper and + * lower bounds of the digit. + */ + if (likely(digit < 10)) + { + ptr++; + tmp = digit; + } + else + { + /* we need at least one digit */ + goto slow; + } + + /* process remaining digits */ + for (;;) + { + digit = (*ptr - '0'); + + if (digit >= 10) + break; + + ptr++; + + if (unlikely(tmp > -(PG_INT32_MIN / 10))) + goto out_of_range; + + tmp = tmp * 10 + digit; + } + + /* when the string does not end in a digit, let the slow path handle it */ + if (unlikely(*ptr != '\0')) + goto slow; + + if (neg) + { + /* check the negative equivalent will fit without overflowing */ + if (unlikely(tmp > (uint32) (-(PG_INT32_MIN + 1)) + 1)) + goto out_of_range; + return -((int32) tmp); + } + + if (unlikely(tmp > PG_INT32_MAX)) + goto out_of_range; + + return (int32) tmp; + +slow: + tmp = 0; + ptr = s; + /* no need to reset neg */ + + /* skip leading spaces */ + while (isspace((unsigned char) *ptr)) + ptr++; + + /* handle sign */ + if (*ptr == '-') + { + ptr++; + neg = true; + } + else if (*ptr == '+') + ptr++; + + /* process digits */ + if (ptr[0] == '0' && (ptr[1] == 'x' || ptr[1] == 'X')) + { + firstdigit = ptr += 2; + + for (;;) + { + if (isxdigit((unsigned char) *ptr)) + { + if (unlikely(tmp > -(PG_INT32_MIN / 16))) + goto out_of_range; + + tmp = tmp * 16 + hexlookup[(unsigned char) *ptr++]; + } + else if (*ptr == '_') + { + /* underscore must be followed by more digits */ + ptr++; + if (*ptr == '\0' || !isxdigit((unsigned char) *ptr)) + goto invalid_syntax; + } + else + break; + } + } + else if (ptr[0] == '0' && (ptr[1] == 'o' || ptr[1] == 'O')) + { + firstdigit = ptr += 2; + + for (;;) + { + if (*ptr >= '0' && *ptr <= '7') + { + if (unlikely(tmp > -(PG_INT32_MIN / 8))) + goto out_of_range; + + tmp = tmp * 8 + (*ptr++ - '0'); + } + else if (*ptr == '_') + { + /* underscore must be followed by more digits */ + ptr++; + if (*ptr == '\0' || *ptr < '0' || *ptr > '7') + goto invalid_syntax; + } + else + break; + } + } + else if (ptr[0] == '0' && (ptr[1] == 'b' || ptr[1] == 'B')) + { + firstdigit = ptr += 2; + + for (;;) + { + if (*ptr >= '0' && *ptr <= '1') + { + if (unlikely(tmp > -(PG_INT32_MIN / 2))) + goto out_of_range; + + tmp = tmp * 2 + (*ptr++ - '0'); + } + else if (*ptr == '_') + { + /* underscore must be followed by more digits */ + ptr++; + if (*ptr == '\0' || *ptr < '0' || *ptr > '1') + goto invalid_syntax; + } + else + break; + } + } + else + { + firstdigit = ptr; + + for (;;) + { + if (*ptr >= '0' && *ptr <= '9') + { + if (unlikely(tmp > -(PG_INT32_MIN / 10))) + goto out_of_range; + + tmp = tmp * 10 + (*ptr++ - '0'); + } + else if (*ptr == '_') + { + /* underscore may not be first */ + if (unlikely(ptr == firstdigit)) + goto invalid_syntax; + /* and it must be followed by more digits */ + ptr++; + if (*ptr == '\0' || !isdigit((unsigned char) *ptr)) + goto invalid_syntax; + } + else + break; + } + } + + /* require at least one digit */ + if (unlikely(ptr == firstdigit)) + goto invalid_syntax; + + /* allow trailing whitespace, but not other trailing chars */ + while (isspace((unsigned char) *ptr)) + ptr++; + + if (unlikely(*ptr != '\0')) + goto invalid_syntax; + + if (neg) + { + /* check the negative equivalent will fit without overflowing */ + if (tmp > (uint32) (-(PG_INT32_MIN + 1)) + 1) + goto out_of_range; + return -((int32) tmp); + } + + if (tmp > PG_INT32_MAX) + goto out_of_range; + + return (int32) tmp; + +out_of_range: + ereturn(escontext, 0, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("value \"%s\" is out of range for type %s", + s, "integer"))); + +invalid_syntax: + ereturn(escontext, 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s: \"%s\"", + "integer", s))); +} + +/* + * Convert input string to a signed 64 bit integer. Input strings may be + * expressed in base-10, hexadecimal, octal, or binary format, all of which + * can be prefixed by an optional sign character, either '+' (the default) or + * '-' for negative numbers. Hex strings are recognized by the digits being + * prefixed by 0x or 0X while octal strings are recognized by the 0o or 0O + * prefix. The binary representation is recognized by the 0b or 0B prefix. + * + * Allows any number of leading or trailing whitespace characters. Digits may + * optionally be separated by a single underscore character. These can only + * come between digits and not before or after the digits. Underscores have + * no effect on the return value and are supported only to assist in improving + * the human readability of the input strings. + * + * pg_strtoint64() will throw ereport() upon bad input format or overflow; + * while pg_strtoint64_safe() instead returns such complaints in *escontext, + * if it's an ErrorSaveContext. + * + * NB: Accumulate input as an unsigned number, to deal with two's complement + * representation of the most negative number, which can't be represented as a + * signed positive number. + */ +int64 +pg_strtoint64(const char *s) +{ + return pg_strtoint64_safe(s, NULL); +} + +int64 +pg_strtoint64_safe(const char *s, Node *escontext) +{ + const char *ptr = s; + const char *firstdigit; + uint64 tmp = 0; + bool neg = false; + unsigned char digit; + + /* + * The majority of cases are likely to be base-10 digits without any + * underscore separator characters. We'll first try to parse the string + * with the assumption that's the case and only fallback on a slower + * implementation which handles hex, octal and binary strings and + * underscores if the fastpath version cannot parse the string. + */ + + /* leave it up to the slow path to look for leading spaces */ + + if (*ptr == '-') + { + ptr++; + neg = true; + } + + /* a leading '+' is uncommon so leave that for the slow path */ + + /* process the first digit */ + digit = (*ptr - '0'); + + /* + * Exploit unsigned arithmetic to save having to check both the upper and + * lower bounds of the digit. + */ + if (likely(digit < 10)) + { + ptr++; + tmp = digit; + } + else + { + /* we need at least one digit */ + goto slow; + } + + /* process remaining digits */ + for (;;) + { + digit = (*ptr - '0'); + + if (digit >= 10) + break; + + ptr++; + + if (unlikely(tmp > -(PG_INT64_MIN / 10))) + goto out_of_range; + + tmp = tmp * 10 + digit; + } + + /* when the string does not end in a digit, let the slow path handle it */ + if (unlikely(*ptr != '\0')) + goto slow; + + if (neg) + { + /* check the negative equivalent will fit without overflowing */ + if (unlikely(tmp > (uint64) (-(PG_INT64_MIN + 1)) + 1)) + goto out_of_range; + return -((int64) tmp); + } + + if (unlikely(tmp > PG_INT64_MAX)) + goto out_of_range; + + return (int64) tmp; + +slow: + tmp = 0; + ptr = s; + /* no need to reset neg */ + + /* skip leading spaces */ + while (isspace((unsigned char) *ptr)) + ptr++; + + /* handle sign */ + if (*ptr == '-') + { + ptr++; + neg = true; + } + else if (*ptr == '+') + ptr++; + + /* process digits */ + if (ptr[0] == '0' && (ptr[1] == 'x' || ptr[1] == 'X')) + { + firstdigit = ptr += 2; + + for (;;) + { + if (isxdigit((unsigned char) *ptr)) + { + if (unlikely(tmp > -(PG_INT64_MIN / 16))) + goto out_of_range; + + tmp = tmp * 16 + hexlookup[(unsigned char) *ptr++]; + } + else if (*ptr == '_') + { + /* underscore must be followed by more digits */ + ptr++; + if (*ptr == '\0' || !isxdigit((unsigned char) *ptr)) + goto invalid_syntax; + } + else + break; + } + } + else if (ptr[0] == '0' && (ptr[1] == 'o' || ptr[1] == 'O')) + { + firstdigit = ptr += 2; + + for (;;) + { + if (*ptr >= '0' && *ptr <= '7') + { + if (unlikely(tmp > -(PG_INT64_MIN / 8))) + goto out_of_range; + + tmp = tmp * 8 + (*ptr++ - '0'); + } + else if (*ptr == '_') + { + /* underscore must be followed by more digits */ + ptr++; + if (*ptr == '\0' || *ptr < '0' || *ptr > '7') + goto invalid_syntax; + } + else + break; + } + } + else if (ptr[0] == '0' && (ptr[1] == 'b' || ptr[1] == 'B')) + { + firstdigit = ptr += 2; + + for (;;) + { + if (*ptr >= '0' && *ptr <= '1') + { + if (unlikely(tmp > -(PG_INT64_MIN / 2))) + goto out_of_range; + + tmp = tmp * 2 + (*ptr++ - '0'); + } + else if (*ptr == '_') + { + /* underscore must be followed by more digits */ + ptr++; + if (*ptr == '\0' || *ptr < '0' || *ptr > '1') + goto invalid_syntax; + } + else + break; + } + } + else + { + firstdigit = ptr; + + for (;;) + { + if (*ptr >= '0' && *ptr <= '9') + { + if (unlikely(tmp > -(PG_INT64_MIN / 10))) + goto out_of_range; + + tmp = tmp * 10 + (*ptr++ - '0'); + } + else if (*ptr == '_') + { + /* underscore may not be first */ + if (unlikely(ptr == firstdigit)) + goto invalid_syntax; + /* and it must be followed by more digits */ + ptr++; + if (*ptr == '\0' || !isdigit((unsigned char) *ptr)) + goto invalid_syntax; + } + else + break; + } + } + + /* require at least one digit */ + if (unlikely(ptr == firstdigit)) + goto invalid_syntax; + + /* allow trailing whitespace, but not other trailing chars */ + while (isspace((unsigned char) *ptr)) + ptr++; + + if (unlikely(*ptr != '\0')) + goto invalid_syntax; + + if (neg) + { + /* check the negative equivalent will fit without overflowing */ + if (tmp > (uint64) (-(PG_INT64_MIN + 1)) + 1) + goto out_of_range; + return -((int64) tmp); + } + + if (tmp > PG_INT64_MAX) + goto out_of_range; + + return (int64) tmp; + +out_of_range: + ereturn(escontext, 0, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("value \"%s\" is out of range for type %s", + s, "bigint"))); + +invalid_syntax: + ereturn(escontext, 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s: \"%s\"", + "bigint", s))); +} + +/* + * Convert input string to an unsigned 32 bit integer. + * + * Allows any number of leading or trailing whitespace characters. + * + * If endloc isn't NULL, store a pointer to the rest of the string there, + * so that caller can parse the rest. Otherwise, it's an error if anything + * but whitespace follows. + * + * typname is what is reported in error messges. + * + * If escontext points to an ErrorSaveContext node, that is filled instead + * of throwing an error; the caller must check SOFT_ERROR_OCCURRED() + * to detect errors. + */ +uint32 +uint32in_subr(const char *s, char **endloc, + const char *typname, Node *escontext) +{ + uint32 result; + unsigned long cvt; + char *endptr; + + errno = 0; + cvt = strtoul(s, &endptr, 0); + + /* + * strtoul() normally only sets ERANGE. On some systems it may also set + * EINVAL, which simply means it couldn't parse the input string. Be sure + * to report that the same way as the standard error indication (that + * endptr == s). + */ + if ((errno && errno != ERANGE) || endptr == s) + ereturn(escontext, 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s: \"%s\"", + typname, s))); + + if (errno == ERANGE) + ereturn(escontext, 0, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("value \"%s\" is out of range for type %s", + s, typname))); + + if (endloc) + { + /* caller wants to deal with rest of string */ + *endloc = endptr; + } + else + { + /* allow only whitespace after number */ + while (*endptr && isspace((unsigned char) *endptr)) + endptr++; + if (*endptr) + ereturn(escontext, 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s: \"%s\"", + typname, s))); + } + + result = (uint32) cvt; + + /* + * Cope with possibility that unsigned long is wider than uint32, in which + * case strtoul will not raise an error for some values that are out of + * the range of uint32. + * + * For backwards compatibility, we want to accept inputs that are given + * with a minus sign, so allow the input value if it matches after either + * signed or unsigned extension to long. + * + * To ensure consistent results on 32-bit and 64-bit platforms, make sure + * the error message is the same as if strtoul() had returned ERANGE. + */ +#if PG_UINT32_MAX != ULONG_MAX + if (cvt != (unsigned long) result && + cvt != (unsigned long) ((int) result)) + ereturn(escontext, 0, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("value \"%s\" is out of range for type %s", + s, typname))); +#endif + + return result; +} + +/* + * Convert input string to an unsigned 64 bit integer. + * + * Allows any number of leading or trailing whitespace characters. + * + * If endloc isn't NULL, store a pointer to the rest of the string there, + * so that caller can parse the rest. Otherwise, it's an error if anything + * but whitespace follows. + * + * typname is what is reported in error messges. + * + * If escontext points to an ErrorSaveContext node, that is filled instead + * of throwing an error; the caller must check SOFT_ERROR_OCCURRED() + * to detect errors. + */ +uint64 +uint64in_subr(const char *s, char **endloc, + const char *typname, Node *escontext) +{ + uint64 result; + char *endptr; + + errno = 0; + result = strtou64(s, &endptr, 0); + + /* + * strtoul[l] normally only sets ERANGE. On some systems it may also set + * EINVAL, which simply means it couldn't parse the input string. Be sure + * to report that the same way as the standard error indication (that + * endptr == s). + */ + if ((errno && errno != ERANGE) || endptr == s) + ereturn(escontext, 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s: \"%s\"", + typname, s))); + + if (errno == ERANGE) + ereturn(escontext, 0, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("value \"%s\" is out of range for type %s", + s, typname))); + + if (endloc) + { + /* caller wants to deal with rest of string */ + *endloc = endptr; + } + else + { + /* allow only whitespace after number */ + while (*endptr && isspace((unsigned char) *endptr)) + endptr++; + if (*endptr) + ereturn(escontext, 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s: \"%s\"", + typname, s))); + } + + return result; +} + +/* + * pg_itoa: converts a signed 16-bit integer to its string representation + * and returns strlen(a). + * + * Caller must ensure that 'a' points to enough memory to hold the result + * (at least 7 bytes, counting a leading sign and trailing NUL). + * + * It doesn't seem worth implementing this separately. + */ +int +pg_itoa(int16 i, char *a) +{ + return pg_ltoa((int32) i, a); +} + +/* + * pg_ultoa_n: converts an unsigned 32-bit integer to its string representation, + * not NUL-terminated, and returns the length of that string representation + * + * Caller must ensure that 'a' points to enough memory to hold the result (at + * least 10 bytes) + */ +int +pg_ultoa_n(uint32 value, char *a) +{ + int olength, + i = 0; + + /* Degenerate case */ + if (value == 0) + { + *a = '0'; + return 1; + } + + olength = decimalLength32(value); + + /* Compute the result string. */ + while (value >= 10000) + { + const uint32 c = value - 10000 * (value / 10000); + const uint32 c0 = (c % 100) << 1; + const uint32 c1 = (c / 100) << 1; + + char *pos = a + olength - i; + + value /= 10000; + + memcpy(pos - 2, DIGIT_TABLE + c0, 2); + memcpy(pos - 4, DIGIT_TABLE + c1, 2); + i += 4; + } + if (value >= 100) + { + const uint32 c = (value % 100) << 1; + + char *pos = a + olength - i; + + value /= 100; + + memcpy(pos - 2, DIGIT_TABLE + c, 2); + i += 2; + } + if (value >= 10) + { + const uint32 c = value << 1; + + char *pos = a + olength - i; + + memcpy(pos - 2, DIGIT_TABLE + c, 2); + } + else + { + *a = (char) ('0' + value); + } + + return olength; +} + +/* + * pg_ltoa: converts a signed 32-bit integer to its string representation and + * returns strlen(a). + * + * It is the caller's responsibility to ensure that a is at least 12 bytes long, + * which is enough room to hold a minus sign, a maximally long int32, and the + * above terminating NUL. + */ +int +pg_ltoa(int32 value, char *a) +{ + uint32 uvalue = (uint32) value; + int len = 0; + + if (value < 0) + { + uvalue = (uint32) 0 - uvalue; + a[len++] = '-'; + } + len += pg_ultoa_n(uvalue, a + len); + a[len] = '\0'; + return len; +} + +/* + * Get the decimal representation, not NUL-terminated, and return the length of + * same. Caller must ensure that a points to at least MAXINT8LEN bytes. + */ +int +pg_ulltoa_n(uint64 value, char *a) +{ + int olength, + i = 0; + uint32 value2; + + /* Degenerate case */ + if (value == 0) + { + *a = '0'; + return 1; + } + + olength = decimalLength64(value); + + /* Compute the result string. */ + while (value >= 100000000) + { + const uint64 q = value / 100000000; + uint32 value3 = (uint32) (value - 100000000 * q); + + const uint32 c = value3 % 10000; + const uint32 d = value3 / 10000; + const uint32 c0 = (c % 100) << 1; + const uint32 c1 = (c / 100) << 1; + const uint32 d0 = (d % 100) << 1; + const uint32 d1 = (d / 100) << 1; + + char *pos = a + olength - i; + + value = q; + + memcpy(pos - 2, DIGIT_TABLE + c0, 2); + memcpy(pos - 4, DIGIT_TABLE + c1, 2); + memcpy(pos - 6, DIGIT_TABLE + d0, 2); + memcpy(pos - 8, DIGIT_TABLE + d1, 2); + i += 8; + } + + /* Switch to 32-bit for speed */ + value2 = (uint32) value; + + if (value2 >= 10000) + { + const uint32 c = value2 - 10000 * (value2 / 10000); + const uint32 c0 = (c % 100) << 1; + const uint32 c1 = (c / 100) << 1; + + char *pos = a + olength - i; + + value2 /= 10000; + + memcpy(pos - 2, DIGIT_TABLE + c0, 2); + memcpy(pos - 4, DIGIT_TABLE + c1, 2); + i += 4; + } + if (value2 >= 100) + { + const uint32 c = (value2 % 100) << 1; + char *pos = a + olength - i; + + value2 /= 100; + + memcpy(pos - 2, DIGIT_TABLE + c, 2); + i += 2; + } + if (value2 >= 10) + { + const uint32 c = value2 << 1; + char *pos = a + olength - i; + + memcpy(pos - 2, DIGIT_TABLE + c, 2); + } + else + *a = (char) ('0' + value2); + + return olength; +} + +/* + * pg_lltoa: converts a signed 64-bit integer to its string representation and + * returns strlen(a). + * + * Caller must ensure that 'a' points to enough memory to hold the result + * (at least MAXINT8LEN + 1 bytes, counting a leading sign and trailing NUL). + */ +int +pg_lltoa(int64 value, char *a) +{ + uint64 uvalue = value; + int len = 0; + + if (value < 0) + { + uvalue = (uint64) 0 - uvalue; + a[len++] = '-'; + } + + len += pg_ulltoa_n(uvalue, a + len); + a[len] = '\0'; + return len; +} + + +/* + * pg_ultostr_zeropad + * Converts 'value' into a decimal string representation stored at 'str'. + * 'minwidth' specifies the minimum width of the result; any extra space + * is filled up by prefixing the number with zeros. + * + * Returns the ending address of the string result (the last character written + * plus 1). Note that no NUL terminator is written. + * + * The intended use-case for this function is to build strings that contain + * multiple individual numbers, for example: + * + * str = pg_ultostr_zeropad(str, hours, 2); + * *str++ = ':'; + * str = pg_ultostr_zeropad(str, mins, 2); + * *str++ = ':'; + * str = pg_ultostr_zeropad(str, secs, 2); + * *str = '\0'; + * + * Note: Caller must ensure that 'str' points to enough memory to hold the + * result. + */ +char * +pg_ultostr_zeropad(char *str, uint32 value, int32 minwidth) +{ + int len; + + Assert(minwidth > 0); + + if (value < 100 && minwidth == 2) /* Short cut for common case */ + { + memcpy(str, DIGIT_TABLE + value * 2, 2); + return str + 2; + } + + len = pg_ultoa_n(value, str); + if (len >= minwidth) + return str + len; + + memmove(str + minwidth - len, str, len); + memset(str, '0', minwidth - len); + return str + minwidth; +} + +/* + * pg_ultostr + * Converts 'value' into a decimal string representation stored at 'str'. + * + * Returns the ending address of the string result (the last character written + * plus 1). Note that no NUL terminator is written. + * + * The intended use-case for this function is to build strings that contain + * multiple individual numbers, for example: + * + * str = pg_ultostr(str, a); + * *str++ = ' '; + * str = pg_ultostr(str, b); + * *str = '\0'; + * + * Note: Caller must ensure that 'str' points to enough memory to hold the + * result. + */ +char * +pg_ultostr(char *str, uint32 value) +{ + int len = pg_ultoa_n(value, str); + + return str + len; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/oid.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/oid.c new file mode 100644 index 00000000000..3f7af5b3a06 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/oid.c @@ -0,0 +1,392 @@ +/*------------------------------------------------------------------------- + * + * oid.c + * Functions for the built-in type Oid ... also oidvector. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/adt/oid.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <ctype.h> +#include <limits.h> + +#include "catalog/pg_type.h" +#include "libpq/pqformat.h" +#include "nodes/miscnodes.h" +#include "nodes/value.h" +#include "utils/array.h" +#include "utils/builtins.h" + + +#define OidVectorSize(n) (offsetof(oidvector, values) + (n) * sizeof(Oid)) + + +/***************************************************************************** + * USER I/O ROUTINES * + *****************************************************************************/ + +Datum +oidin(PG_FUNCTION_ARGS) +{ + char *s = PG_GETARG_CSTRING(0); + Oid result; + + result = uint32in_subr(s, NULL, "oid", fcinfo->context); + PG_RETURN_OID(result); +} + +Datum +oidout(PG_FUNCTION_ARGS) +{ + Oid o = PG_GETARG_OID(0); + char *result = (char *) palloc(12); + + snprintf(result, 12, "%u", o); + PG_RETURN_CSTRING(result); +} + +/* + * oidrecv - converts external binary format to oid + */ +Datum +oidrecv(PG_FUNCTION_ARGS) +{ + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + + PG_RETURN_OID((Oid) pq_getmsgint(buf, sizeof(Oid))); +} + +/* + * oidsend - converts oid to binary format + */ +Datum +oidsend(PG_FUNCTION_ARGS) +{ + Oid arg1 = PG_GETARG_OID(0); + StringInfoData buf; + + pq_begintypsend(&buf); + pq_sendint32(&buf, arg1); + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} + +/* + * construct oidvector given a raw array of Oids + * + * If oids is NULL then caller must fill values[] afterward + */ +oidvector * +buildoidvector(const Oid *oids, int n) +{ + oidvector *result; + + result = (oidvector *) palloc0(OidVectorSize(n)); + + if (n > 0 && oids) + memcpy(result->values, oids, n * sizeof(Oid)); + + /* + * Attach standard array header. For historical reasons, we set the index + * lower bound to 0 not 1. + */ + SET_VARSIZE(result, OidVectorSize(n)); + result->ndim = 1; + result->dataoffset = 0; /* never any nulls */ + result->elemtype = OIDOID; + result->dim1 = n; + result->lbound1 = 0; + + return result; +} + +/* + * oidvectorin - converts "num num ..." to internal form + */ +Datum +oidvectorin(PG_FUNCTION_ARGS) +{ + char *oidString = PG_GETARG_CSTRING(0); + Node *escontext = fcinfo->context; + oidvector *result; + int nalloc; + int n; + + nalloc = 32; /* arbitrary initial size guess */ + result = (oidvector *) palloc0(OidVectorSize(nalloc)); + + for (n = 0;; n++) + { + while (*oidString && isspace((unsigned char) *oidString)) + oidString++; + if (*oidString == '\0') + break; + + if (n >= nalloc) + { + nalloc *= 2; + result = (oidvector *) repalloc(result, OidVectorSize(nalloc)); + } + + result->values[n] = uint32in_subr(oidString, &oidString, + "oid", escontext); + if (SOFT_ERROR_OCCURRED(escontext)) + PG_RETURN_NULL(); + } + + SET_VARSIZE(result, OidVectorSize(n)); + result->ndim = 1; + result->dataoffset = 0; /* never any nulls */ + result->elemtype = OIDOID; + result->dim1 = n; + result->lbound1 = 0; + + PG_RETURN_POINTER(result); +} + +/* + * oidvectorout - converts internal form to "num num ..." + */ +Datum +oidvectorout(PG_FUNCTION_ARGS) +{ + oidvector *oidArray = (oidvector *) PG_GETARG_POINTER(0); + int num, + nnums = oidArray->dim1; + char *rp; + char *result; + + /* assumes sign, 10 digits, ' ' */ + rp = result = (char *) palloc(nnums * 12 + 1); + for (num = 0; num < nnums; num++) + { + if (num != 0) + *rp++ = ' '; + sprintf(rp, "%u", oidArray->values[num]); + while (*++rp != '\0') + ; + } + *rp = '\0'; + PG_RETURN_CSTRING(result); +} + +/* + * oidvectorrecv - converts external binary format to oidvector + */ +Datum +oidvectorrecv(PG_FUNCTION_ARGS) +{ + LOCAL_FCINFO(locfcinfo, 3); + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + oidvector *result; + + /* + * Normally one would call array_recv() using DirectFunctionCall3, but + * that does not work since array_recv wants to cache some data using + * fcinfo->flinfo->fn_extra. So we need to pass it our own flinfo + * parameter. + */ + InitFunctionCallInfoData(*locfcinfo, fcinfo->flinfo, 3, + InvalidOid, NULL, NULL); + + locfcinfo->args[0].value = PointerGetDatum(buf); + locfcinfo->args[0].isnull = false; + locfcinfo->args[1].value = ObjectIdGetDatum(OIDOID); + locfcinfo->args[1].isnull = false; + locfcinfo->args[2].value = Int32GetDatum(-1); + locfcinfo->args[2].isnull = false; + + result = (oidvector *) DatumGetPointer(array_recv(locfcinfo)); + + Assert(!locfcinfo->isnull); + + /* sanity checks: oidvector must be 1-D, 0-based, no nulls */ + if (ARR_NDIM(result) != 1 || + ARR_HASNULL(result) || + ARR_ELEMTYPE(result) != OIDOID || + ARR_LBOUND(result)[0] != 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION), + errmsg("invalid oidvector data"))); + + PG_RETURN_POINTER(result); +} + +/* + * oidvectorsend - converts oidvector to binary format + */ +Datum +oidvectorsend(PG_FUNCTION_ARGS) +{ + return array_send(fcinfo); +} + +/* + * oidparse - get OID from ICONST/FCONST node + */ +Oid +oidparse(Node *node) +{ + switch (nodeTag(node)) + { + case T_Integer: + return intVal(node); + case T_Float: + + /* + * Values too large for int4 will be represented as Float + * constants by the lexer. Accept these if they are valid OID + * strings. + */ + return uint32in_subr(castNode(Float, node)->fval, NULL, + "oid", NULL); + default: + elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node)); + } + return InvalidOid; /* keep compiler quiet */ +} + +/* qsort comparison function for Oids */ +int +oid_cmp(const void *p1, const void *p2) +{ + Oid v1 = *((const Oid *) p1); + Oid v2 = *((const Oid *) p2); + + if (v1 < v2) + return -1; + if (v1 > v2) + return 1; + return 0; +} + + +/***************************************************************************** + * PUBLIC ROUTINES * + *****************************************************************************/ + +Datum +oideq(PG_FUNCTION_ARGS) +{ + Oid arg1 = PG_GETARG_OID(0); + Oid arg2 = PG_GETARG_OID(1); + + PG_RETURN_BOOL(arg1 == arg2); +} + +Datum +oidne(PG_FUNCTION_ARGS) +{ + Oid arg1 = PG_GETARG_OID(0); + Oid arg2 = PG_GETARG_OID(1); + + PG_RETURN_BOOL(arg1 != arg2); +} + +Datum +oidlt(PG_FUNCTION_ARGS) +{ + Oid arg1 = PG_GETARG_OID(0); + Oid arg2 = PG_GETARG_OID(1); + + PG_RETURN_BOOL(arg1 < arg2); +} + +Datum +oidle(PG_FUNCTION_ARGS) +{ + Oid arg1 = PG_GETARG_OID(0); + Oid arg2 = PG_GETARG_OID(1); + + PG_RETURN_BOOL(arg1 <= arg2); +} + +Datum +oidge(PG_FUNCTION_ARGS) +{ + Oid arg1 = PG_GETARG_OID(0); + Oid arg2 = PG_GETARG_OID(1); + + PG_RETURN_BOOL(arg1 >= arg2); +} + +Datum +oidgt(PG_FUNCTION_ARGS) +{ + Oid arg1 = PG_GETARG_OID(0); + Oid arg2 = PG_GETARG_OID(1); + + PG_RETURN_BOOL(arg1 > arg2); +} + +Datum +oidlarger(PG_FUNCTION_ARGS) +{ + Oid arg1 = PG_GETARG_OID(0); + Oid arg2 = PG_GETARG_OID(1); + + PG_RETURN_OID((arg1 > arg2) ? arg1 : arg2); +} + +Datum +oidsmaller(PG_FUNCTION_ARGS) +{ + Oid arg1 = PG_GETARG_OID(0); + Oid arg2 = PG_GETARG_OID(1); + + PG_RETURN_OID((arg1 < arg2) ? arg1 : arg2); +} + +Datum +oidvectoreq(PG_FUNCTION_ARGS) +{ + int32 cmp = DatumGetInt32(btoidvectorcmp(fcinfo)); + + PG_RETURN_BOOL(cmp == 0); +} + +Datum +oidvectorne(PG_FUNCTION_ARGS) +{ + int32 cmp = DatumGetInt32(btoidvectorcmp(fcinfo)); + + PG_RETURN_BOOL(cmp != 0); +} + +Datum +oidvectorlt(PG_FUNCTION_ARGS) +{ + int32 cmp = DatumGetInt32(btoidvectorcmp(fcinfo)); + + PG_RETURN_BOOL(cmp < 0); +} + +Datum +oidvectorle(PG_FUNCTION_ARGS) +{ + int32 cmp = DatumGetInt32(btoidvectorcmp(fcinfo)); + + PG_RETURN_BOOL(cmp <= 0); +} + +Datum +oidvectorge(PG_FUNCTION_ARGS) +{ + int32 cmp = DatumGetInt32(btoidvectorcmp(fcinfo)); + + PG_RETURN_BOOL(cmp >= 0); +} + +Datum +oidvectorgt(PG_FUNCTION_ARGS) +{ + int32 cmp = DatumGetInt32(btoidvectorcmp(fcinfo)); + + PG_RETURN_BOOL(cmp > 0); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/oracle_compat.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/oracle_compat.c new file mode 100644 index 00000000000..3b5b794afb3 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/oracle_compat.c @@ -0,0 +1,1157 @@ +/*------------------------------------------------------------------------- + * oracle_compat.c + * Oracle compatible functions. + * + * Copyright (c) 1996-2023, PostgreSQL Global Development Group + * + * Author: Edmund Mergl <E.Mergl@bawue.de> + * Multibyte enhancement: Tatsuo Ishii <ishii@postgresql.org> + * + * + * IDENTIFICATION + * src/backend/utils/adt/oracle_compat.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "common/int.h" +#include "mb/pg_wchar.h" +#include "miscadmin.h" +#include "utils/builtins.h" +#include "utils/formatting.h" +#include "utils/memutils.h" +#include "varatt.h" + + +static text *dotrim(const char *string, int stringlen, + const char *set, int setlen, + bool doltrim, bool dortrim); +static bytea *dobyteatrim(bytea *string, bytea *set, + bool doltrim, bool dortrim); + + +/******************************************************************** + * + * lower + * + * Syntax: + * + * text lower(text string) + * + * Purpose: + * + * Returns string, with all letters forced to lowercase. + * + ********************************************************************/ + +Datum +lower(PG_FUNCTION_ARGS) +{ + text *in_string = PG_GETARG_TEXT_PP(0); + char *out_string; + text *result; + + out_string = str_tolower(VARDATA_ANY(in_string), + VARSIZE_ANY_EXHDR(in_string), + PG_GET_COLLATION()); + result = cstring_to_text(out_string); + pfree(out_string); + + PG_RETURN_TEXT_P(result); +} + + +/******************************************************************** + * + * upper + * + * Syntax: + * + * text upper(text string) + * + * Purpose: + * + * Returns string, with all letters forced to uppercase. + * + ********************************************************************/ + +Datum +upper(PG_FUNCTION_ARGS) +{ + text *in_string = PG_GETARG_TEXT_PP(0); + char *out_string; + text *result; + + out_string = str_toupper(VARDATA_ANY(in_string), + VARSIZE_ANY_EXHDR(in_string), + PG_GET_COLLATION()); + result = cstring_to_text(out_string); + pfree(out_string); + + PG_RETURN_TEXT_P(result); +} + + +/******************************************************************** + * + * initcap + * + * Syntax: + * + * text initcap(text string) + * + * Purpose: + * + * Returns string, with first letter of each word in uppercase, all + * other letters in lowercase. A word is defined as a sequence of + * alphanumeric characters, delimited by non-alphanumeric + * characters. + * + ********************************************************************/ + +Datum +initcap(PG_FUNCTION_ARGS) +{ + text *in_string = PG_GETARG_TEXT_PP(0); + char *out_string; + text *result; + + out_string = str_initcap(VARDATA_ANY(in_string), + VARSIZE_ANY_EXHDR(in_string), + PG_GET_COLLATION()); + result = cstring_to_text(out_string); + pfree(out_string); + + PG_RETURN_TEXT_P(result); +} + + +/******************************************************************** + * + * lpad + * + * Syntax: + * + * text lpad(text string1, int4 len, text string2) + * + * Purpose: + * + * Returns string1, left-padded to length len with the sequence of + * characters in string2. If len is less than the length of string1, + * instead truncate (on the right) to len. + * + ********************************************************************/ + +Datum +lpad(PG_FUNCTION_ARGS) +{ + text *string1 = PG_GETARG_TEXT_PP(0); + int32 len = PG_GETARG_INT32(1); + text *string2 = PG_GETARG_TEXT_PP(2); + text *ret; + char *ptr1, + *ptr2, + *ptr2start, + *ptr2end, + *ptr_ret; + int m, + s1len, + s2len; + int bytelen; + + /* Negative len is silently taken as zero */ + if (len < 0) + len = 0; + + s1len = VARSIZE_ANY_EXHDR(string1); + if (s1len < 0) + s1len = 0; /* shouldn't happen */ + + s2len = VARSIZE_ANY_EXHDR(string2); + if (s2len < 0) + s2len = 0; /* shouldn't happen */ + + s1len = pg_mbstrlen_with_len(VARDATA_ANY(string1), s1len); + + if (s1len > len) + s1len = len; /* truncate string1 to len chars */ + + if (s2len <= 0) + len = s1len; /* nothing to pad with, so don't pad */ + + /* compute worst-case output length */ + if (unlikely(pg_mul_s32_overflow(pg_database_encoding_max_length(), len, + &bytelen)) || + unlikely(pg_add_s32_overflow(bytelen, VARHDRSZ, &bytelen)) || + unlikely(!AllocSizeIsValid(bytelen))) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("requested length too large"))); + + ret = (text *) palloc(bytelen); + + m = len - s1len; + + ptr2 = ptr2start = VARDATA_ANY(string2); + ptr2end = ptr2 + s2len; + ptr_ret = VARDATA(ret); + + while (m--) + { + int mlen = pg_mblen(ptr2); + + memcpy(ptr_ret, ptr2, mlen); + ptr_ret += mlen; + ptr2 += mlen; + if (ptr2 == ptr2end) /* wrap around at end of s2 */ + ptr2 = ptr2start; + } + + ptr1 = VARDATA_ANY(string1); + + while (s1len--) + { + int mlen = pg_mblen(ptr1); + + memcpy(ptr_ret, ptr1, mlen); + ptr_ret += mlen; + ptr1 += mlen; + } + + SET_VARSIZE(ret, ptr_ret - (char *) ret); + + PG_RETURN_TEXT_P(ret); +} + + +/******************************************************************** + * + * rpad + * + * Syntax: + * + * text rpad(text string1, int4 len, text string2) + * + * Purpose: + * + * Returns string1, right-padded to length len with the sequence of + * characters in string2. If len is less than the length of string1, + * instead truncate (on the right) to len. + * + ********************************************************************/ + +Datum +rpad(PG_FUNCTION_ARGS) +{ + text *string1 = PG_GETARG_TEXT_PP(0); + int32 len = PG_GETARG_INT32(1); + text *string2 = PG_GETARG_TEXT_PP(2); + text *ret; + char *ptr1, + *ptr2, + *ptr2start, + *ptr2end, + *ptr_ret; + int m, + s1len, + s2len; + int bytelen; + + /* Negative len is silently taken as zero */ + if (len < 0) + len = 0; + + s1len = VARSIZE_ANY_EXHDR(string1); + if (s1len < 0) + s1len = 0; /* shouldn't happen */ + + s2len = VARSIZE_ANY_EXHDR(string2); + if (s2len < 0) + s2len = 0; /* shouldn't happen */ + + s1len = pg_mbstrlen_with_len(VARDATA_ANY(string1), s1len); + + if (s1len > len) + s1len = len; /* truncate string1 to len chars */ + + if (s2len <= 0) + len = s1len; /* nothing to pad with, so don't pad */ + + /* compute worst-case output length */ + if (unlikely(pg_mul_s32_overflow(pg_database_encoding_max_length(), len, + &bytelen)) || + unlikely(pg_add_s32_overflow(bytelen, VARHDRSZ, &bytelen)) || + unlikely(!AllocSizeIsValid(bytelen))) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("requested length too large"))); + + ret = (text *) palloc(bytelen); + + m = len - s1len; + + ptr1 = VARDATA_ANY(string1); + ptr_ret = VARDATA(ret); + + while (s1len--) + { + int mlen = pg_mblen(ptr1); + + memcpy(ptr_ret, ptr1, mlen); + ptr_ret += mlen; + ptr1 += mlen; + } + + ptr2 = ptr2start = VARDATA_ANY(string2); + ptr2end = ptr2 + s2len; + + while (m--) + { + int mlen = pg_mblen(ptr2); + + memcpy(ptr_ret, ptr2, mlen); + ptr_ret += mlen; + ptr2 += mlen; + if (ptr2 == ptr2end) /* wrap around at end of s2 */ + ptr2 = ptr2start; + } + + SET_VARSIZE(ret, ptr_ret - (char *) ret); + + PG_RETURN_TEXT_P(ret); +} + + +/******************************************************************** + * + * btrim + * + * Syntax: + * + * text btrim(text string, text set) + * + * Purpose: + * + * Returns string with characters removed from the front and back + * up to the first character not in set. + * + ********************************************************************/ + +Datum +btrim(PG_FUNCTION_ARGS) +{ + text *string = PG_GETARG_TEXT_PP(0); + text *set = PG_GETARG_TEXT_PP(1); + text *ret; + + ret = dotrim(VARDATA_ANY(string), VARSIZE_ANY_EXHDR(string), + VARDATA_ANY(set), VARSIZE_ANY_EXHDR(set), + true, true); + + PG_RETURN_TEXT_P(ret); +} + +/******************************************************************** + * + * btrim1 --- btrim with set fixed as ' ' + * + ********************************************************************/ + +Datum +btrim1(PG_FUNCTION_ARGS) +{ + text *string = PG_GETARG_TEXT_PP(0); + text *ret; + + ret = dotrim(VARDATA_ANY(string), VARSIZE_ANY_EXHDR(string), + " ", 1, + true, true); + + PG_RETURN_TEXT_P(ret); +} + +/* + * Common implementation for btrim, ltrim, rtrim + */ +static text * +dotrim(const char *string, int stringlen, + const char *set, int setlen, + bool doltrim, bool dortrim) +{ + int i; + + /* Nothing to do if either string or set is empty */ + if (stringlen > 0 && setlen > 0) + { + if (pg_database_encoding_max_length() > 1) + { + /* + * In the multibyte-encoding case, build arrays of pointers to + * character starts, so that we can avoid inefficient checks in + * the inner loops. + */ + const char **stringchars; + const char **setchars; + int *stringmblen; + int *setmblen; + int stringnchars; + int setnchars; + int resultndx; + int resultnchars; + const char *p; + int len; + int mblen; + const char *str_pos; + int str_len; + + stringchars = (const char **) palloc(stringlen * sizeof(char *)); + stringmblen = (int *) palloc(stringlen * sizeof(int)); + stringnchars = 0; + p = string; + len = stringlen; + while (len > 0) + { + stringchars[stringnchars] = p; + stringmblen[stringnchars] = mblen = pg_mblen(p); + stringnchars++; + p += mblen; + len -= mblen; + } + + setchars = (const char **) palloc(setlen * sizeof(char *)); + setmblen = (int *) palloc(setlen * sizeof(int)); + setnchars = 0; + p = set; + len = setlen; + while (len > 0) + { + setchars[setnchars] = p; + setmblen[setnchars] = mblen = pg_mblen(p); + setnchars++; + p += mblen; + len -= mblen; + } + + resultndx = 0; /* index in stringchars[] */ + resultnchars = stringnchars; + + if (doltrim) + { + while (resultnchars > 0) + { + str_pos = stringchars[resultndx]; + str_len = stringmblen[resultndx]; + for (i = 0; i < setnchars; i++) + { + if (str_len == setmblen[i] && + memcmp(str_pos, setchars[i], str_len) == 0) + break; + } + if (i >= setnchars) + break; /* no match here */ + string += str_len; + stringlen -= str_len; + resultndx++; + resultnchars--; + } + } + + if (dortrim) + { + while (resultnchars > 0) + { + str_pos = stringchars[resultndx + resultnchars - 1]; + str_len = stringmblen[resultndx + resultnchars - 1]; + for (i = 0; i < setnchars; i++) + { + if (str_len == setmblen[i] && + memcmp(str_pos, setchars[i], str_len) == 0) + break; + } + if (i >= setnchars) + break; /* no match here */ + stringlen -= str_len; + resultnchars--; + } + } + + pfree(stringchars); + pfree(stringmblen); + pfree(setchars); + pfree(setmblen); + } + else + { + /* + * In the single-byte-encoding case, we don't need such overhead. + */ + if (doltrim) + { + while (stringlen > 0) + { + char str_ch = *string; + + for (i = 0; i < setlen; i++) + { + if (str_ch == set[i]) + break; + } + if (i >= setlen) + break; /* no match here */ + string++; + stringlen--; + } + } + + if (dortrim) + { + while (stringlen > 0) + { + char str_ch = string[stringlen - 1]; + + for (i = 0; i < setlen; i++) + { + if (str_ch == set[i]) + break; + } + if (i >= setlen) + break; /* no match here */ + stringlen--; + } + } + } + } + + /* Return selected portion of string */ + return cstring_to_text_with_len(string, stringlen); +} + +/* + * Common implementation for bytea versions of btrim, ltrim, rtrim + */ +bytea * +dobyteatrim(bytea *string, bytea *set, bool doltrim, bool dortrim) +{ + bytea *ret; + char *ptr, + *end, + *ptr2, + *ptr2start, + *end2; + int m, + stringlen, + setlen; + + stringlen = VARSIZE_ANY_EXHDR(string); + setlen = VARSIZE_ANY_EXHDR(set); + + if (stringlen <= 0 || setlen <= 0) + return string; + + m = stringlen; + ptr = VARDATA_ANY(string); + end = ptr + stringlen - 1; + ptr2start = VARDATA_ANY(set); + end2 = ptr2start + setlen - 1; + + if (doltrim) + { + while (m > 0) + { + ptr2 = ptr2start; + while (ptr2 <= end2) + { + if (*ptr == *ptr2) + break; + ++ptr2; + } + if (ptr2 > end2) + break; + ptr++; + m--; + } + } + + if (dortrim) + { + while (m > 0) + { + ptr2 = ptr2start; + while (ptr2 <= end2) + { + if (*end == *ptr2) + break; + ++ptr2; + } + if (ptr2 > end2) + break; + end--; + m--; + } + } + + ret = (bytea *) palloc(VARHDRSZ + m); + SET_VARSIZE(ret, VARHDRSZ + m); + memcpy(VARDATA(ret), ptr, m); + return ret; +} + +/******************************************************************** + * + * byteatrim + * + * Syntax: + * + * bytea byteatrim(bytea string, bytea set) + * + * Purpose: + * + * Returns string with characters removed from the front and back + * up to the first character not in set. + * + * Cloned from btrim and modified as required. + ********************************************************************/ + +Datum +byteatrim(PG_FUNCTION_ARGS) +{ + bytea *string = PG_GETARG_BYTEA_PP(0); + bytea *set = PG_GETARG_BYTEA_PP(1); + bytea *ret; + + ret = dobyteatrim(string, set, true, true); + + PG_RETURN_BYTEA_P(ret); +} + +/******************************************************************** + * + * bytealtrim + * + * Syntax: + * + * bytea bytealtrim(bytea string, bytea set) + * + * Purpose: + * + * Returns string with initial characters removed up to the first + * character not in set. + * + ********************************************************************/ + +Datum +bytealtrim(PG_FUNCTION_ARGS) +{ + bytea *string = PG_GETARG_BYTEA_PP(0); + bytea *set = PG_GETARG_BYTEA_PP(1); + bytea *ret; + + ret = dobyteatrim(string, set, true, false); + + PG_RETURN_BYTEA_P(ret); +} + +/******************************************************************** + * + * byteartrim + * + * Syntax: + * + * bytea byteartrim(bytea string, bytea set) + * + * Purpose: + * + * Returns string with final characters removed after the last + * character not in set. + * + ********************************************************************/ + +Datum +byteartrim(PG_FUNCTION_ARGS) +{ + bytea *string = PG_GETARG_BYTEA_PP(0); + bytea *set = PG_GETARG_BYTEA_PP(1); + bytea *ret; + + ret = dobyteatrim(string, set, false, true); + + PG_RETURN_BYTEA_P(ret); +} + +/******************************************************************** + * + * ltrim + * + * Syntax: + * + * text ltrim(text string, text set) + * + * Purpose: + * + * Returns string with initial characters removed up to the first + * character not in set. + * + ********************************************************************/ + +Datum +ltrim(PG_FUNCTION_ARGS) +{ + text *string = PG_GETARG_TEXT_PP(0); + text *set = PG_GETARG_TEXT_PP(1); + text *ret; + + ret = dotrim(VARDATA_ANY(string), VARSIZE_ANY_EXHDR(string), + VARDATA_ANY(set), VARSIZE_ANY_EXHDR(set), + true, false); + + PG_RETURN_TEXT_P(ret); +} + +/******************************************************************** + * + * ltrim1 --- ltrim with set fixed as ' ' + * + ********************************************************************/ + +Datum +ltrim1(PG_FUNCTION_ARGS) +{ + text *string = PG_GETARG_TEXT_PP(0); + text *ret; + + ret = dotrim(VARDATA_ANY(string), VARSIZE_ANY_EXHDR(string), + " ", 1, + true, false); + + PG_RETURN_TEXT_P(ret); +} + +/******************************************************************** + * + * rtrim + * + * Syntax: + * + * text rtrim(text string, text set) + * + * Purpose: + * + * Returns string with final characters removed after the last + * character not in set. + * + ********************************************************************/ + +Datum +rtrim(PG_FUNCTION_ARGS) +{ + text *string = PG_GETARG_TEXT_PP(0); + text *set = PG_GETARG_TEXT_PP(1); + text *ret; + + ret = dotrim(VARDATA_ANY(string), VARSIZE_ANY_EXHDR(string), + VARDATA_ANY(set), VARSIZE_ANY_EXHDR(set), + false, true); + + PG_RETURN_TEXT_P(ret); +} + +/******************************************************************** + * + * rtrim1 --- rtrim with set fixed as ' ' + * + ********************************************************************/ + +Datum +rtrim1(PG_FUNCTION_ARGS) +{ + text *string = PG_GETARG_TEXT_PP(0); + text *ret; + + ret = dotrim(VARDATA_ANY(string), VARSIZE_ANY_EXHDR(string), + " ", 1, + false, true); + + PG_RETURN_TEXT_P(ret); +} + + +/******************************************************************** + * + * translate + * + * Syntax: + * + * text translate(text string, text from, text to) + * + * Purpose: + * + * Returns string after replacing all occurrences of characters in from + * with the corresponding character in to. If from is longer than to, + * occurrences of the extra characters in from are deleted. + * Improved by Edwin Ramirez <ramirez@doc.mssm.edu>. + * + ********************************************************************/ + +Datum +translate(PG_FUNCTION_ARGS) +{ + text *string = PG_GETARG_TEXT_PP(0); + text *from = PG_GETARG_TEXT_PP(1); + text *to = PG_GETARG_TEXT_PP(2); + text *result; + char *from_ptr, + *to_ptr, + *to_end; + char *source, + *target; + int m, + fromlen, + tolen, + retlen, + i; + int bytelen; + int len; + int source_len; + int from_index; + + m = VARSIZE_ANY_EXHDR(string); + if (m <= 0) + PG_RETURN_TEXT_P(string); + source = VARDATA_ANY(string); + + fromlen = VARSIZE_ANY_EXHDR(from); + from_ptr = VARDATA_ANY(from); + tolen = VARSIZE_ANY_EXHDR(to); + to_ptr = VARDATA_ANY(to); + to_end = to_ptr + tolen; + + /* + * The worst-case expansion is to substitute a max-length character for a + * single-byte character at each position of the string. + */ + if (unlikely(pg_mul_s32_overflow(pg_database_encoding_max_length(), m, + &bytelen)) || + unlikely(pg_add_s32_overflow(bytelen, VARHDRSZ, &bytelen)) || + unlikely(!AllocSizeIsValid(bytelen))) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("requested length too large"))); + + result = (text *) palloc(bytelen); + + target = VARDATA(result); + retlen = 0; + + while (m > 0) + { + source_len = pg_mblen(source); + from_index = 0; + + for (i = 0; i < fromlen; i += len) + { + len = pg_mblen(&from_ptr[i]); + if (len == source_len && + memcmp(source, &from_ptr[i], len) == 0) + break; + + from_index++; + } + if (i < fromlen) + { + /* substitute, or delete if no corresponding "to" character */ + char *p = to_ptr; + + for (i = 0; i < from_index; i++) + { + if (p >= to_end) + break; + p += pg_mblen(p); + } + if (p < to_end) + { + len = pg_mblen(p); + memcpy(target, p, len); + target += len; + retlen += len; + } + } + else + { + /* no match, so copy */ + memcpy(target, source, source_len); + target += source_len; + retlen += source_len; + } + + source += source_len; + m -= source_len; + } + + SET_VARSIZE(result, retlen + VARHDRSZ); + + /* + * The function result is probably much bigger than needed, if we're using + * a multibyte encoding, but it's not worth reallocating it; the result + * probably won't live long anyway. + */ + + PG_RETURN_TEXT_P(result); +} + +/******************************************************************** + * + * ascii + * + * Syntax: + * + * int ascii(text string) + * + * Purpose: + * + * Returns the decimal representation of the first character from + * string. + * If the string is empty we return 0. + * If the database encoding is UTF8, we return the Unicode codepoint. + * If the database encoding is any other multi-byte encoding, we + * return the value of the first byte if it is an ASCII character + * (range 1 .. 127), or raise an error. + * For all other encodings we return the value of the first byte, + * (range 1..255). + * + ********************************************************************/ + +Datum +ascii(PG_FUNCTION_ARGS) +{ + text *string = PG_GETARG_TEXT_PP(0); + int encoding = GetDatabaseEncoding(); + unsigned char *data; + + if (VARSIZE_ANY_EXHDR(string) <= 0) + PG_RETURN_INT32(0); + + data = (unsigned char *) VARDATA_ANY(string); + + if (encoding == PG_UTF8 && *data > 127) + { + /* return the code point for Unicode */ + + int result = 0, + tbytes = 0, + i; + + if (*data >= 0xF0) + { + result = *data & 0x07; + tbytes = 3; + } + else if (*data >= 0xE0) + { + result = *data & 0x0F; + tbytes = 2; + } + else + { + Assert(*data > 0xC0); + result = *data & 0x1f; + tbytes = 1; + } + + Assert(tbytes > 0); + + for (i = 1; i <= tbytes; i++) + { + Assert((data[i] & 0xC0) == 0x80); + result = (result << 6) + (data[i] & 0x3f); + } + + PG_RETURN_INT32(result); + } + else + { + if (pg_encoding_max_length(encoding) > 1 && *data > 127) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("requested character too large"))); + + + PG_RETURN_INT32((int32) *data); + } +} + +/******************************************************************** + * + * chr + * + * Syntax: + * + * text chr(int val) + * + * Purpose: + * + * Returns the character having the binary equivalent to val. + * + * For UTF8 we treat the argument as a Unicode code point. + * For other multi-byte encodings we raise an error for arguments + * outside the strict ASCII range (1..127). + * + * It's important that we don't ever return a value that is not valid + * in the database encoding, so that this doesn't become a way for + * invalid data to enter the database. + * + ********************************************************************/ + +Datum +chr (PG_FUNCTION_ARGS) +{ + int32 arg = PG_GETARG_INT32(0); + uint32 cvalue; + text *result; + int encoding = GetDatabaseEncoding(); + + /* + * Error out on arguments that make no sense or that we can't validly + * represent in the encoding. + */ + if (arg < 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("character number must be positive"))); + else if (arg == 0) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("null character not permitted"))); + + cvalue = arg; + + if (encoding == PG_UTF8 && cvalue > 127) + { + /* for Unicode we treat the argument as a code point */ + int bytes; + unsigned char *wch; + + /* + * We only allow valid Unicode code points; per RFC3629 that stops at + * U+10FFFF, even though 4-byte UTF8 sequences can hold values up to + * U+1FFFFF. + */ + if (cvalue > 0x0010ffff) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("requested character too large for encoding: %u", + cvalue))); + + if (cvalue > 0xffff) + bytes = 4; + else if (cvalue > 0x07ff) + bytes = 3; + else + bytes = 2; + + result = (text *) palloc(VARHDRSZ + bytes); + SET_VARSIZE(result, VARHDRSZ + bytes); + wch = (unsigned char *) VARDATA(result); + + if (bytes == 2) + { + wch[0] = 0xC0 | ((cvalue >> 6) & 0x1F); + wch[1] = 0x80 | (cvalue & 0x3F); + } + else if (bytes == 3) + { + wch[0] = 0xE0 | ((cvalue >> 12) & 0x0F); + wch[1] = 0x80 | ((cvalue >> 6) & 0x3F); + wch[2] = 0x80 | (cvalue & 0x3F); + } + else + { + wch[0] = 0xF0 | ((cvalue >> 18) & 0x07); + wch[1] = 0x80 | ((cvalue >> 12) & 0x3F); + wch[2] = 0x80 | ((cvalue >> 6) & 0x3F); + wch[3] = 0x80 | (cvalue & 0x3F); + } + + /* + * The preceding range check isn't sufficient, because UTF8 excludes + * Unicode "surrogate pair" codes. Make sure what we created is valid + * UTF8. + */ + if (!pg_utf8_islegal(wch, bytes)) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("requested character not valid for encoding: %u", + cvalue))); + } + else + { + bool is_mb; + + is_mb = pg_encoding_max_length(encoding) > 1; + + if ((is_mb && (cvalue > 127)) || (!is_mb && (cvalue > 255))) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("requested character too large for encoding: %u", + cvalue))); + + result = (text *) palloc(VARHDRSZ + 1); + SET_VARSIZE(result, VARHDRSZ + 1); + *VARDATA(result) = (char) cvalue; + } + + PG_RETURN_TEXT_P(result); +} + +/******************************************************************** + * + * repeat + * + * Syntax: + * + * text repeat(text string, int val) + * + * Purpose: + * + * Repeat string by val. + * + ********************************************************************/ + +Datum +repeat(PG_FUNCTION_ARGS) +{ + text *string = PG_GETARG_TEXT_PP(0); + int32 count = PG_GETARG_INT32(1); + text *result; + int slen, + tlen; + int i; + char *cp, + *sp; + + if (count < 0) + count = 0; + + slen = VARSIZE_ANY_EXHDR(string); + + if (unlikely(pg_mul_s32_overflow(count, slen, &tlen)) || + unlikely(pg_add_s32_overflow(tlen, VARHDRSZ, &tlen)) || + unlikely(!AllocSizeIsValid(tlen))) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("requested length too large"))); + + result = (text *) palloc(tlen); + + SET_VARSIZE(result, tlen); + cp = VARDATA(result); + sp = VARDATA_ANY(string); + for (i = 0; i < count; i++) + { + memcpy(cp, sp, slen); + cp += slen; + CHECK_FOR_INTERRUPTS(); + } + + PG_RETURN_TEXT_P(result); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/orderedsetaggs.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/orderedsetaggs.c new file mode 100644 index 00000000000..2582a5cf459 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/orderedsetaggs.c @@ -0,0 +1,1432 @@ +/*------------------------------------------------------------------------- + * + * orderedsetaggs.c + * Ordered-set aggregate functions. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/adt/orderedsetaggs.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <math.h> + +#include "catalog/pg_aggregate.h" +#include "catalog/pg_operator.h" +#include "catalog/pg_type.h" +#include "executor/executor.h" +#include "miscadmin.h" +#include "nodes/nodeFuncs.h" +#include "optimizer/optimizer.h" +#include "utils/array.h" +#include "utils/builtins.h" +#include "utils/lsyscache.h" +#include "utils/memutils.h" +#include "utils/timestamp.h" +#include "utils/tuplesort.h" + + +/* + * Generic support for ordered-set aggregates + * + * The state for an ordered-set aggregate is divided into a per-group struct + * (which is the internal-type transition state datum returned to nodeAgg.c) + * and a per-query struct, which contains data and sub-objects that we can + * create just once per query because they will not change across groups. + * The per-query struct and subsidiary data live in the executor's per-query + * memory context, and go away implicitly at ExecutorEnd(). + * + * These structs are set up during the first call of the transition function. + * Because we allow nodeAgg.c to merge ordered-set aggregates (but not + * hypothetical aggregates) with identical inputs and transition functions, + * this info must not depend on the particular aggregate (ie, particular + * final-function), nor on the direct argument(s) of the aggregate. + */ + +typedef struct OSAPerQueryState +{ + /* Representative Aggref for this aggregate: */ + Aggref *aggref; + /* Memory context containing this struct and other per-query data: */ + MemoryContext qcontext; + /* Context for expression evaluation */ + ExprContext *econtext; + /* Do we expect multiple final-function calls within one group? */ + bool rescan_needed; + + /* These fields are used only when accumulating tuples: */ + + /* Tuple descriptor for tuples inserted into sortstate: */ + TupleDesc tupdesc; + /* Tuple slot we can use for inserting/extracting tuples: */ + TupleTableSlot *tupslot; + /* Per-sort-column sorting information */ + int numSortCols; + AttrNumber *sortColIdx; + Oid *sortOperators; + Oid *eqOperators; + Oid *sortCollations; + bool *sortNullsFirsts; + /* Equality operator call info, created only if needed: */ + ExprState *compareTuple; + + /* These fields are used only when accumulating datums: */ + + /* Info about datatype of datums being sorted: */ + Oid sortColType; + int16 typLen; + bool typByVal; + char typAlign; + /* Info about sort ordering: */ + Oid sortOperator; + Oid eqOperator; + Oid sortCollation; + bool sortNullsFirst; + /* Equality operator call info, created only if needed: */ + FmgrInfo equalfn; +} OSAPerQueryState; + +typedef struct OSAPerGroupState +{ + /* Link to the per-query state for this aggregate: */ + OSAPerQueryState *qstate; + /* Memory context containing per-group data: */ + MemoryContext gcontext; + /* Sort object we're accumulating data in: */ + Tuplesortstate *sortstate; + /* Number of normal rows inserted into sortstate: */ + int64 number_of_rows; + /* Have we already done tuplesort_performsort? */ + bool sort_done; +} OSAPerGroupState; + +static void ordered_set_shutdown(Datum arg); + + +/* + * Set up working state for an ordered-set aggregate + */ +static OSAPerGroupState * +ordered_set_startup(FunctionCallInfo fcinfo, bool use_tuples) +{ + OSAPerGroupState *osastate; + OSAPerQueryState *qstate; + MemoryContext gcontext; + MemoryContext oldcontext; + int tuplesortopt; + + /* + * Check we're called as aggregate (and not a window function), and get + * the Agg node's group-lifespan context (which might change from group to + * group, so we shouldn't cache it in the per-query state). + */ + if (AggCheckCallContext(fcinfo, &gcontext) != AGG_CONTEXT_AGGREGATE) + elog(ERROR, "ordered-set aggregate called in non-aggregate context"); + + /* + * We keep a link to the per-query state in fn_extra; if it's not there, + * create it, and do the per-query setup we need. + */ + qstate = (OSAPerQueryState *) fcinfo->flinfo->fn_extra; + if (qstate == NULL) + { + Aggref *aggref; + MemoryContext qcontext; + List *sortlist; + int numSortCols; + + /* Get the Aggref so we can examine aggregate's arguments */ + aggref = AggGetAggref(fcinfo); + if (!aggref) + elog(ERROR, "ordered-set aggregate called in non-aggregate context"); + if (!AGGKIND_IS_ORDERED_SET(aggref->aggkind)) + elog(ERROR, "ordered-set aggregate support function called for non-ordered-set aggregate"); + + /* + * Prepare per-query structures in the fn_mcxt, which we assume is the + * executor's per-query context; in any case it's the right place to + * keep anything found via fn_extra. + */ + qcontext = fcinfo->flinfo->fn_mcxt; + oldcontext = MemoryContextSwitchTo(qcontext); + + qstate = (OSAPerQueryState *) palloc0(sizeof(OSAPerQueryState)); + qstate->aggref = aggref; + qstate->qcontext = qcontext; + + /* We need to support rescans if the trans state is shared */ + qstate->rescan_needed = AggStateIsShared(fcinfo); + + /* Extract the sort information */ + sortlist = aggref->aggorder; + numSortCols = list_length(sortlist); + + if (use_tuples) + { + bool ishypothetical = (aggref->aggkind == AGGKIND_HYPOTHETICAL); + ListCell *lc; + int i; + + if (ishypothetical) + numSortCols++; /* make space for flag column */ + qstate->numSortCols = numSortCols; + qstate->sortColIdx = (AttrNumber *) palloc(numSortCols * sizeof(AttrNumber)); + qstate->sortOperators = (Oid *) palloc(numSortCols * sizeof(Oid)); + qstate->eqOperators = (Oid *) palloc(numSortCols * sizeof(Oid)); + qstate->sortCollations = (Oid *) palloc(numSortCols * sizeof(Oid)); + qstate->sortNullsFirsts = (bool *) palloc(numSortCols * sizeof(bool)); + + i = 0; + foreach(lc, sortlist) + { + SortGroupClause *sortcl = (SortGroupClause *) lfirst(lc); + TargetEntry *tle = get_sortgroupclause_tle(sortcl, + aggref->args); + + /* the parser should have made sure of this */ + Assert(OidIsValid(sortcl->sortop)); + + qstate->sortColIdx[i] = tle->resno; + qstate->sortOperators[i] = sortcl->sortop; + qstate->eqOperators[i] = sortcl->eqop; + qstate->sortCollations[i] = exprCollation((Node *) tle->expr); + qstate->sortNullsFirsts[i] = sortcl->nulls_first; + i++; + } + + if (ishypothetical) + { + /* Add an integer flag column as the last sort column */ + qstate->sortColIdx[i] = list_length(aggref->args) + 1; + qstate->sortOperators[i] = Int4LessOperator; + qstate->eqOperators[i] = Int4EqualOperator; + qstate->sortCollations[i] = InvalidOid; + qstate->sortNullsFirsts[i] = false; + i++; + } + + Assert(i == numSortCols); + + /* + * Get a tupledesc corresponding to the aggregated inputs + * (including sort expressions) of the agg. + */ + qstate->tupdesc = ExecTypeFromTL(aggref->args); + + /* If we need a flag column, hack the tupledesc to include that */ + if (ishypothetical) + { + TupleDesc newdesc; + int natts = qstate->tupdesc->natts; + + newdesc = CreateTemplateTupleDesc(natts + 1); + for (i = 1; i <= natts; i++) + TupleDescCopyEntry(newdesc, i, qstate->tupdesc, i); + + TupleDescInitEntry(newdesc, + (AttrNumber) ++natts, + "flag", + INT4OID, + -1, + 0); + + FreeTupleDesc(qstate->tupdesc); + qstate->tupdesc = newdesc; + } + + /* Create slot we'll use to store/retrieve rows */ + qstate->tupslot = MakeSingleTupleTableSlot(qstate->tupdesc, + &TTSOpsMinimalTuple); + } + else + { + /* Sort single datums */ + SortGroupClause *sortcl; + TargetEntry *tle; + + if (numSortCols != 1 || aggref->aggkind == AGGKIND_HYPOTHETICAL) + elog(ERROR, "ordered-set aggregate support function does not support multiple aggregated columns"); + + sortcl = (SortGroupClause *) linitial(sortlist); + tle = get_sortgroupclause_tle(sortcl, aggref->args); + + /* the parser should have made sure of this */ + Assert(OidIsValid(sortcl->sortop)); + + /* Save sort ordering info */ + qstate->sortColType = exprType((Node *) tle->expr); + qstate->sortOperator = sortcl->sortop; + qstate->eqOperator = sortcl->eqop; + qstate->sortCollation = exprCollation((Node *) tle->expr); + qstate->sortNullsFirst = sortcl->nulls_first; + + /* Save datatype info */ + get_typlenbyvalalign(qstate->sortColType, + &qstate->typLen, + &qstate->typByVal, + &qstate->typAlign); + } + + fcinfo->flinfo->fn_extra = (void *) qstate; + + MemoryContextSwitchTo(oldcontext); + } + + /* Now build the stuff we need in group-lifespan context */ + oldcontext = MemoryContextSwitchTo(gcontext); + + osastate = (OSAPerGroupState *) palloc(sizeof(OSAPerGroupState)); + osastate->qstate = qstate; + osastate->gcontext = gcontext; + + tuplesortopt = TUPLESORT_NONE; + + if (qstate->rescan_needed) + tuplesortopt |= TUPLESORT_RANDOMACCESS; + + /* + * Initialize tuplesort object. + */ + if (use_tuples) + osastate->sortstate = tuplesort_begin_heap(qstate->tupdesc, + qstate->numSortCols, + qstate->sortColIdx, + qstate->sortOperators, + qstate->sortCollations, + qstate->sortNullsFirsts, + work_mem, + NULL, + tuplesortopt); + else + osastate->sortstate = tuplesort_begin_datum(qstate->sortColType, + qstate->sortOperator, + qstate->sortCollation, + qstate->sortNullsFirst, + work_mem, + NULL, + tuplesortopt); + + osastate->number_of_rows = 0; + osastate->sort_done = false; + + /* Now register a shutdown callback to clean things up at end of group */ + AggRegisterCallback(fcinfo, + ordered_set_shutdown, + PointerGetDatum(osastate)); + + MemoryContextSwitchTo(oldcontext); + + return osastate; +} + +/* + * Clean up when evaluation of an ordered-set aggregate is complete. + * + * We don't need to bother freeing objects in the per-group memory context, + * since that will get reset anyway by nodeAgg.c; nor should we free anything + * in the per-query context, which will get cleared (if this was the last + * group) by ExecutorEnd. But we must take care to release any potential + * non-memory resources. + * + * In the case where we're not expecting multiple finalfn calls, we could + * arguably rely on the finalfn to clean up; but it's easier and more testable + * if we just do it the same way in either case. + */ +static void +ordered_set_shutdown(Datum arg) +{ + OSAPerGroupState *osastate = (OSAPerGroupState *) DatumGetPointer(arg); + + /* Tuplesort object might have temp files. */ + if (osastate->sortstate) + tuplesort_end(osastate->sortstate); + osastate->sortstate = NULL; + /* The tupleslot probably can't be holding a pin, but let's be safe. */ + if (osastate->qstate->tupslot) + ExecClearTuple(osastate->qstate->tupslot); +} + + +/* + * Generic transition function for ordered-set aggregates + * with a single input column in which we want to suppress nulls + */ +Datum +ordered_set_transition(PG_FUNCTION_ARGS) +{ + OSAPerGroupState *osastate; + + /* If first call, create the transition state workspace */ + if (PG_ARGISNULL(0)) + osastate = ordered_set_startup(fcinfo, false); + else + osastate = (OSAPerGroupState *) PG_GETARG_POINTER(0); + + /* Load the datum into the tuplesort object, but only if it's not null */ + if (!PG_ARGISNULL(1)) + { + tuplesort_putdatum(osastate->sortstate, PG_GETARG_DATUM(1), false); + osastate->number_of_rows++; + } + + PG_RETURN_POINTER(osastate); +} + +/* + * Generic transition function for ordered-set aggregates + * with (potentially) multiple aggregated input columns + */ +Datum +ordered_set_transition_multi(PG_FUNCTION_ARGS) +{ + OSAPerGroupState *osastate; + TupleTableSlot *slot; + int nargs; + int i; + + /* If first call, create the transition state workspace */ + if (PG_ARGISNULL(0)) + osastate = ordered_set_startup(fcinfo, true); + else + osastate = (OSAPerGroupState *) PG_GETARG_POINTER(0); + + /* Form a tuple from all the other inputs besides the transition value */ + slot = osastate->qstate->tupslot; + ExecClearTuple(slot); + nargs = PG_NARGS() - 1; + for (i = 0; i < nargs; i++) + { + slot->tts_values[i] = PG_GETARG_DATUM(i + 1); + slot->tts_isnull[i] = PG_ARGISNULL(i + 1); + } + if (osastate->qstate->aggref->aggkind == AGGKIND_HYPOTHETICAL) + { + /* Add a zero flag value to mark this row as a normal input row */ + slot->tts_values[i] = Int32GetDatum(0); + slot->tts_isnull[i] = false; + i++; + } + Assert(i == slot->tts_tupleDescriptor->natts); + ExecStoreVirtualTuple(slot); + + /* Load the row into the tuplesort object */ + tuplesort_puttupleslot(osastate->sortstate, slot); + osastate->number_of_rows++; + + PG_RETURN_POINTER(osastate); +} + + +/* + * percentile_disc(float8) within group(anyelement) - discrete percentile + */ +Datum +percentile_disc_final(PG_FUNCTION_ARGS) +{ + OSAPerGroupState *osastate; + double percentile; + Datum val; + bool isnull; + int64 rownum; + + Assert(AggCheckCallContext(fcinfo, NULL) == AGG_CONTEXT_AGGREGATE); + + /* Get and check the percentile argument */ + if (PG_ARGISNULL(1)) + PG_RETURN_NULL(); + + percentile = PG_GETARG_FLOAT8(1); + + if (percentile < 0 || percentile > 1 || isnan(percentile)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("percentile value %g is not between 0 and 1", + percentile))); + + /* If there were no regular rows, the result is NULL */ + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + + osastate = (OSAPerGroupState *) PG_GETARG_POINTER(0); + + /* number_of_rows could be zero if we only saw NULL input values */ + if (osastate->number_of_rows == 0) + PG_RETURN_NULL(); + + /* Finish the sort, or rescan if we already did */ + if (!osastate->sort_done) + { + tuplesort_performsort(osastate->sortstate); + osastate->sort_done = true; + } + else + tuplesort_rescan(osastate->sortstate); + + /*---------- + * We need the smallest K such that (K/N) >= percentile. + * N>0, therefore K >= N*percentile, therefore K = ceil(N*percentile). + * So we skip K-1 rows (if K>0) and return the next row fetched. + *---------- + */ + rownum = (int64) ceil(percentile * osastate->number_of_rows); + Assert(rownum <= osastate->number_of_rows); + + if (rownum > 1) + { + if (!tuplesort_skiptuples(osastate->sortstate, rownum - 1, true)) + elog(ERROR, "missing row in percentile_disc"); + } + + if (!tuplesort_getdatum(osastate->sortstate, true, true, &val, &isnull, + NULL)) + elog(ERROR, "missing row in percentile_disc"); + + /* We shouldn't have stored any nulls, but do the right thing anyway */ + if (isnull) + PG_RETURN_NULL(); + else + PG_RETURN_DATUM(val); +} + + +/* + * For percentile_cont, we need a way to interpolate between consecutive + * values. Use a helper function for that, so that we can share the rest + * of the code between types. + */ +typedef Datum (*LerpFunc) (Datum lo, Datum hi, double pct); + +static Datum +float8_lerp(Datum lo, Datum hi, double pct) +{ + double loval = DatumGetFloat8(lo); + double hival = DatumGetFloat8(hi); + + return Float8GetDatum(loval + (pct * (hival - loval))); +} + +static Datum +interval_lerp(Datum lo, Datum hi, double pct) +{ + Datum diff_result = DirectFunctionCall2(interval_mi, hi, lo); + Datum mul_result = DirectFunctionCall2(interval_mul, + diff_result, + Float8GetDatumFast(pct)); + + return DirectFunctionCall2(interval_pl, mul_result, lo); +} + +/* + * Continuous percentile + */ +static Datum +percentile_cont_final_common(FunctionCallInfo fcinfo, + Oid expect_type, + LerpFunc lerpfunc) +{ + OSAPerGroupState *osastate; + double percentile; + int64 first_row = 0; + int64 second_row = 0; + Datum val; + Datum first_val; + Datum second_val; + double proportion; + bool isnull; + + Assert(AggCheckCallContext(fcinfo, NULL) == AGG_CONTEXT_AGGREGATE); + + /* Get and check the percentile argument */ + if (PG_ARGISNULL(1)) + PG_RETURN_NULL(); + + percentile = PG_GETARG_FLOAT8(1); + + if (percentile < 0 || percentile > 1 || isnan(percentile)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("percentile value %g is not between 0 and 1", + percentile))); + + /* If there were no regular rows, the result is NULL */ + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + + osastate = (OSAPerGroupState *) PG_GETARG_POINTER(0); + + /* number_of_rows could be zero if we only saw NULL input values */ + if (osastate->number_of_rows == 0) + PG_RETURN_NULL(); + + Assert(expect_type == osastate->qstate->sortColType); + + /* Finish the sort, or rescan if we already did */ + if (!osastate->sort_done) + { + tuplesort_performsort(osastate->sortstate); + osastate->sort_done = true; + } + else + tuplesort_rescan(osastate->sortstate); + + first_row = floor(percentile * (osastate->number_of_rows - 1)); + second_row = ceil(percentile * (osastate->number_of_rows - 1)); + + Assert(first_row < osastate->number_of_rows); + + if (!tuplesort_skiptuples(osastate->sortstate, first_row, true)) + elog(ERROR, "missing row in percentile_cont"); + + if (!tuplesort_getdatum(osastate->sortstate, true, true, &first_val, + &isnull, NULL)) + elog(ERROR, "missing row in percentile_cont"); + if (isnull) + PG_RETURN_NULL(); + + if (first_row == second_row) + { + val = first_val; + } + else + { + if (!tuplesort_getdatum(osastate->sortstate, true, true, &second_val, + &isnull, NULL)) + elog(ERROR, "missing row in percentile_cont"); + + if (isnull) + PG_RETURN_NULL(); + + proportion = (percentile * (osastate->number_of_rows - 1)) - first_row; + val = lerpfunc(first_val, second_val, proportion); + } + + PG_RETURN_DATUM(val); +} + +/* + * percentile_cont(float8) within group (float8) - continuous percentile + */ +Datum +percentile_cont_float8_final(PG_FUNCTION_ARGS) +{ + return percentile_cont_final_common(fcinfo, FLOAT8OID, float8_lerp); +} + +/* + * percentile_cont(float8) within group (interval) - continuous percentile + */ +Datum +percentile_cont_interval_final(PG_FUNCTION_ARGS) +{ + return percentile_cont_final_common(fcinfo, INTERVALOID, interval_lerp); +} + + +/* + * Support code for handling arrays of percentiles + * + * Note: in each pct_info entry, second_row should be equal to or + * exactly one more than first_row. + */ +struct pct_info +{ + int64 first_row; /* first row to sample */ + int64 second_row; /* possible second row to sample */ + double proportion; /* interpolation fraction */ + int idx; /* index of this item in original array */ +}; + +/* + * Sort comparator to sort pct_infos by first_row then second_row + */ +static int +pct_info_cmp(const void *pa, const void *pb) +{ + const struct pct_info *a = (const struct pct_info *) pa; + const struct pct_info *b = (const struct pct_info *) pb; + + if (a->first_row != b->first_row) + return (a->first_row < b->first_row) ? -1 : 1; + if (a->second_row != b->second_row) + return (a->second_row < b->second_row) ? -1 : 1; + return 0; +} + +/* + * Construct array showing which rows to sample for percentiles. + */ +static struct pct_info * +setup_pct_info(int num_percentiles, + Datum *percentiles_datum, + bool *percentiles_null, + int64 rowcount, + bool continuous) +{ + struct pct_info *pct_info; + int i; + + pct_info = (struct pct_info *) palloc(num_percentiles * sizeof(struct pct_info)); + + for (i = 0; i < num_percentiles; i++) + { + pct_info[i].idx = i; + + if (percentiles_null[i]) + { + /* dummy entry for any NULL in array */ + pct_info[i].first_row = 0; + pct_info[i].second_row = 0; + pct_info[i].proportion = 0; + } + else + { + double p = DatumGetFloat8(percentiles_datum[i]); + + if (p < 0 || p > 1 || isnan(p)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("percentile value %g is not between 0 and 1", + p))); + + if (continuous) + { + pct_info[i].first_row = 1 + floor(p * (rowcount - 1)); + pct_info[i].second_row = 1 + ceil(p * (rowcount - 1)); + pct_info[i].proportion = (p * (rowcount - 1)) - floor(p * (rowcount - 1)); + } + else + { + /*---------- + * We need the smallest K such that (K/N) >= percentile. + * N>0, therefore K >= N*percentile, therefore + * K = ceil(N*percentile); but not less than 1. + *---------- + */ + int64 row = (int64) ceil(p * rowcount); + + row = Max(1, row); + pct_info[i].first_row = row; + pct_info[i].second_row = row; + pct_info[i].proportion = 0; + } + } + } + + /* + * The parameter array wasn't necessarily in sorted order, but we need to + * visit the rows in order, so sort by first_row/second_row. + */ + qsort(pct_info, num_percentiles, sizeof(struct pct_info), pct_info_cmp); + + return pct_info; +} + +/* + * percentile_disc(float8[]) within group (anyelement) - discrete percentiles + */ +Datum +percentile_disc_multi_final(PG_FUNCTION_ARGS) +{ + OSAPerGroupState *osastate; + ArrayType *param; + Datum *percentiles_datum; + bool *percentiles_null; + int num_percentiles; + struct pct_info *pct_info; + Datum *result_datum; + bool *result_isnull; + int64 rownum = 0; + Datum val = (Datum) 0; + bool isnull = true; + int i; + + Assert(AggCheckCallContext(fcinfo, NULL) == AGG_CONTEXT_AGGREGATE); + + /* If there were no regular rows, the result is NULL */ + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + + osastate = (OSAPerGroupState *) PG_GETARG_POINTER(0); + + /* number_of_rows could be zero if we only saw NULL input values */ + if (osastate->number_of_rows == 0) + PG_RETURN_NULL(); + + /* Deconstruct the percentile-array input */ + if (PG_ARGISNULL(1)) + PG_RETURN_NULL(); + param = PG_GETARG_ARRAYTYPE_P(1); + + deconstruct_array_builtin(param, FLOAT8OID, + &percentiles_datum, + &percentiles_null, + &num_percentiles); + + if (num_percentiles == 0) + PG_RETURN_POINTER(construct_empty_array(osastate->qstate->sortColType)); + + pct_info = setup_pct_info(num_percentiles, + percentiles_datum, + percentiles_null, + osastate->number_of_rows, + false); + + result_datum = (Datum *) palloc(num_percentiles * sizeof(Datum)); + result_isnull = (bool *) palloc(num_percentiles * sizeof(bool)); + + /* + * Start by dealing with any nulls in the param array - those are sorted + * to the front on row=0, so set the corresponding result indexes to null + */ + for (i = 0; i < num_percentiles; i++) + { + int idx = pct_info[i].idx; + + if (pct_info[i].first_row > 0) + break; + + result_datum[idx] = (Datum) 0; + result_isnull[idx] = true; + } + + /* + * If there's anything left after doing the nulls, then grind the input + * and extract the needed values + */ + if (i < num_percentiles) + { + /* Finish the sort, or rescan if we already did */ + if (!osastate->sort_done) + { + tuplesort_performsort(osastate->sortstate); + osastate->sort_done = true; + } + else + tuplesort_rescan(osastate->sortstate); + + for (; i < num_percentiles; i++) + { + int64 target_row = pct_info[i].first_row; + int idx = pct_info[i].idx; + + /* Advance to target row, if not already there */ + if (target_row > rownum) + { + if (!tuplesort_skiptuples(osastate->sortstate, target_row - rownum - 1, true)) + elog(ERROR, "missing row in percentile_disc"); + + if (!tuplesort_getdatum(osastate->sortstate, true, true, &val, + &isnull, NULL)) + elog(ERROR, "missing row in percentile_disc"); + + rownum = target_row; + } + + result_datum[idx] = val; + result_isnull[idx] = isnull; + } + } + + /* We make the output array the same shape as the input */ + PG_RETURN_POINTER(construct_md_array(result_datum, result_isnull, + ARR_NDIM(param), + ARR_DIMS(param), + ARR_LBOUND(param), + osastate->qstate->sortColType, + osastate->qstate->typLen, + osastate->qstate->typByVal, + osastate->qstate->typAlign)); +} + +/* + * percentile_cont(float8[]) within group () - continuous percentiles + */ +static Datum +percentile_cont_multi_final_common(FunctionCallInfo fcinfo, + Oid expect_type, + int16 typLen, bool typByVal, char typAlign, + LerpFunc lerpfunc) +{ + OSAPerGroupState *osastate; + ArrayType *param; + Datum *percentiles_datum; + bool *percentiles_null; + int num_percentiles; + struct pct_info *pct_info; + Datum *result_datum; + bool *result_isnull; + int64 rownum = 0; + Datum first_val = (Datum) 0; + Datum second_val = (Datum) 0; + bool isnull; + int i; + + Assert(AggCheckCallContext(fcinfo, NULL) == AGG_CONTEXT_AGGREGATE); + + /* If there were no regular rows, the result is NULL */ + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + + osastate = (OSAPerGroupState *) PG_GETARG_POINTER(0); + + /* number_of_rows could be zero if we only saw NULL input values */ + if (osastate->number_of_rows == 0) + PG_RETURN_NULL(); + + Assert(expect_type == osastate->qstate->sortColType); + + /* Deconstruct the percentile-array input */ + if (PG_ARGISNULL(1)) + PG_RETURN_NULL(); + param = PG_GETARG_ARRAYTYPE_P(1); + + deconstruct_array_builtin(param, FLOAT8OID, + &percentiles_datum, + &percentiles_null, + &num_percentiles); + + if (num_percentiles == 0) + PG_RETURN_POINTER(construct_empty_array(osastate->qstate->sortColType)); + + pct_info = setup_pct_info(num_percentiles, + percentiles_datum, + percentiles_null, + osastate->number_of_rows, + true); + + result_datum = (Datum *) palloc(num_percentiles * sizeof(Datum)); + result_isnull = (bool *) palloc(num_percentiles * sizeof(bool)); + + /* + * Start by dealing with any nulls in the param array - those are sorted + * to the front on row=0, so set the corresponding result indexes to null + */ + for (i = 0; i < num_percentiles; i++) + { + int idx = pct_info[i].idx; + + if (pct_info[i].first_row > 0) + break; + + result_datum[idx] = (Datum) 0; + result_isnull[idx] = true; + } + + /* + * If there's anything left after doing the nulls, then grind the input + * and extract the needed values + */ + if (i < num_percentiles) + { + /* Finish the sort, or rescan if we already did */ + if (!osastate->sort_done) + { + tuplesort_performsort(osastate->sortstate); + osastate->sort_done = true; + } + else + tuplesort_rescan(osastate->sortstate); + + for (; i < num_percentiles; i++) + { + int64 first_row = pct_info[i].first_row; + int64 second_row = pct_info[i].second_row; + int idx = pct_info[i].idx; + + /* + * Advance to first_row, if not already there. Note that we might + * already have rownum beyond first_row, in which case first_val + * is already correct. (This occurs when interpolating between + * the same two input rows as for the previous percentile.) + */ + if (first_row > rownum) + { + if (!tuplesort_skiptuples(osastate->sortstate, first_row - rownum - 1, true)) + elog(ERROR, "missing row in percentile_cont"); + + if (!tuplesort_getdatum(osastate->sortstate, true, true, + &first_val, &isnull, NULL) || isnull) + elog(ERROR, "missing row in percentile_cont"); + + rownum = first_row; + /* Always advance second_val to be latest input value */ + second_val = first_val; + } + else if (first_row == rownum) + { + /* + * We are already at the desired row, so we must previously + * have read its value into second_val (and perhaps first_val + * as well, but this assignment is harmless in that case). + */ + first_val = second_val; + } + + /* Fetch second_row if needed */ + if (second_row > rownum) + { + if (!tuplesort_getdatum(osastate->sortstate, true, true, + &second_val, &isnull, NULL) || isnull) + elog(ERROR, "missing row in percentile_cont"); + rownum++; + } + /* We should now certainly be on second_row exactly */ + Assert(second_row == rownum); + + /* Compute appropriate result */ + if (second_row > first_row) + result_datum[idx] = lerpfunc(first_val, second_val, + pct_info[i].proportion); + else + result_datum[idx] = first_val; + + result_isnull[idx] = false; + } + } + + /* We make the output array the same shape as the input */ + PG_RETURN_POINTER(construct_md_array(result_datum, result_isnull, + ARR_NDIM(param), + ARR_DIMS(param), ARR_LBOUND(param), + expect_type, + typLen, + typByVal, + typAlign)); +} + +/* + * percentile_cont(float8[]) within group (float8) - continuous percentiles + */ +Datum +percentile_cont_float8_multi_final(PG_FUNCTION_ARGS) +{ + return percentile_cont_multi_final_common(fcinfo, + FLOAT8OID, + /* hard-wired info on type float8 */ + sizeof(float8), + FLOAT8PASSBYVAL, + TYPALIGN_DOUBLE, + float8_lerp); +} + +/* + * percentile_cont(float8[]) within group (interval) - continuous percentiles + */ +Datum +percentile_cont_interval_multi_final(PG_FUNCTION_ARGS) +{ + return percentile_cont_multi_final_common(fcinfo, + INTERVALOID, + /* hard-wired info on type interval */ + 16, false, TYPALIGN_DOUBLE, + interval_lerp); +} + + +/* + * mode() within group (anyelement) - most common value + */ +Datum +mode_final(PG_FUNCTION_ARGS) +{ + OSAPerGroupState *osastate; + Datum val; + bool isnull; + Datum mode_val = (Datum) 0; + int64 mode_freq = 0; + Datum last_val = (Datum) 0; + int64 last_val_freq = 0; + bool last_val_is_mode = false; + FmgrInfo *equalfn; + Datum abbrev_val = (Datum) 0; + Datum last_abbrev_val = (Datum) 0; + bool shouldfree; + + Assert(AggCheckCallContext(fcinfo, NULL) == AGG_CONTEXT_AGGREGATE); + + /* If there were no regular rows, the result is NULL */ + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + + osastate = (OSAPerGroupState *) PG_GETARG_POINTER(0); + + /* number_of_rows could be zero if we only saw NULL input values */ + if (osastate->number_of_rows == 0) + PG_RETURN_NULL(); + + /* Look up the equality function for the datatype, if we didn't already */ + equalfn = &(osastate->qstate->equalfn); + if (!OidIsValid(equalfn->fn_oid)) + fmgr_info_cxt(get_opcode(osastate->qstate->eqOperator), equalfn, + osastate->qstate->qcontext); + + shouldfree = !(osastate->qstate->typByVal); + + /* Finish the sort, or rescan if we already did */ + if (!osastate->sort_done) + { + tuplesort_performsort(osastate->sortstate); + osastate->sort_done = true; + } + else + tuplesort_rescan(osastate->sortstate); + + /* Scan tuples and count frequencies */ + while (tuplesort_getdatum(osastate->sortstate, true, true, &val, &isnull, + &abbrev_val)) + { + /* we don't expect any nulls, but ignore them if found */ + if (isnull) + continue; + + if (last_val_freq == 0) + { + /* first nonnull value - it's the mode for now */ + mode_val = last_val = val; + mode_freq = last_val_freq = 1; + last_val_is_mode = true; + last_abbrev_val = abbrev_val; + } + else if (abbrev_val == last_abbrev_val && + DatumGetBool(FunctionCall2Coll(equalfn, PG_GET_COLLATION(), val, last_val))) + { + /* value equal to previous value, count it */ + if (last_val_is_mode) + mode_freq++; /* needn't maintain last_val_freq */ + else if (++last_val_freq > mode_freq) + { + /* last_val becomes new mode */ + if (shouldfree) + pfree(DatumGetPointer(mode_val)); + mode_val = last_val; + mode_freq = last_val_freq; + last_val_is_mode = true; + } + if (shouldfree) + pfree(DatumGetPointer(val)); + } + else + { + /* val should replace last_val */ + if (shouldfree && !last_val_is_mode) + pfree(DatumGetPointer(last_val)); + last_val = val; + /* avoid equality function calls by reusing abbreviated keys */ + last_abbrev_val = abbrev_val; + last_val_freq = 1; + last_val_is_mode = false; + } + + CHECK_FOR_INTERRUPTS(); + } + + if (shouldfree && !last_val_is_mode) + pfree(DatumGetPointer(last_val)); + + if (mode_freq) + PG_RETURN_DATUM(mode_val); + else + PG_RETURN_NULL(); +} + + +/* + * Common code to sanity-check args for hypothetical-set functions. No need + * for friendly errors, these can only happen if someone's messing up the + * aggregate definitions. The checks are needed for security, however. + */ +static void +hypothetical_check_argtypes(FunctionCallInfo fcinfo, int nargs, + TupleDesc tupdesc) +{ + int i; + + /* check that we have an int4 flag column */ + if (!tupdesc || + (nargs + 1) != tupdesc->natts || + TupleDescAttr(tupdesc, nargs)->atttypid != INT4OID) + elog(ERROR, "type mismatch in hypothetical-set function"); + + /* check that direct args match in type with aggregated args */ + for (i = 0; i < nargs; i++) + { + Form_pg_attribute attr = TupleDescAttr(tupdesc, i); + + if (get_fn_expr_argtype(fcinfo->flinfo, i + 1) != attr->atttypid) + elog(ERROR, "type mismatch in hypothetical-set function"); + } +} + +/* + * compute rank of hypothetical row + * + * flag should be -1 to sort hypothetical row ahead of its peers, or +1 + * to sort behind. + * total number of regular rows is returned into *number_of_rows. + */ +static int64 +hypothetical_rank_common(FunctionCallInfo fcinfo, int flag, + int64 *number_of_rows) +{ + int nargs = PG_NARGS() - 1; + int64 rank = 1; + OSAPerGroupState *osastate; + TupleTableSlot *slot; + int i; + + Assert(AggCheckCallContext(fcinfo, NULL) == AGG_CONTEXT_AGGREGATE); + + /* If there were no regular rows, the rank is always 1 */ + if (PG_ARGISNULL(0)) + { + *number_of_rows = 0; + return 1; + } + + osastate = (OSAPerGroupState *) PG_GETARG_POINTER(0); + *number_of_rows = osastate->number_of_rows; + + /* Adjust nargs to be the number of direct (or aggregated) args */ + if (nargs % 2 != 0) + elog(ERROR, "wrong number of arguments in hypothetical-set function"); + nargs /= 2; + + hypothetical_check_argtypes(fcinfo, nargs, osastate->qstate->tupdesc); + + /* because we need a hypothetical row, we can't share transition state */ + Assert(!osastate->sort_done); + + /* insert the hypothetical row into the sort */ + slot = osastate->qstate->tupslot; + ExecClearTuple(slot); + for (i = 0; i < nargs; i++) + { + slot->tts_values[i] = PG_GETARG_DATUM(i + 1); + slot->tts_isnull[i] = PG_ARGISNULL(i + 1); + } + slot->tts_values[i] = Int32GetDatum(flag); + slot->tts_isnull[i] = false; + ExecStoreVirtualTuple(slot); + + tuplesort_puttupleslot(osastate->sortstate, slot); + + /* finish the sort */ + tuplesort_performsort(osastate->sortstate); + osastate->sort_done = true; + + /* iterate till we find the hypothetical row */ + while (tuplesort_gettupleslot(osastate->sortstate, true, true, slot, NULL)) + { + bool isnull; + Datum d = slot_getattr(slot, nargs + 1, &isnull); + + if (!isnull && DatumGetInt32(d) != 0) + break; + + rank++; + + CHECK_FOR_INTERRUPTS(); + } + + ExecClearTuple(slot); + + return rank; +} + + +/* + * rank() - rank of hypothetical row + */ +Datum +hypothetical_rank_final(PG_FUNCTION_ARGS) +{ + int64 rank; + int64 rowcount; + + rank = hypothetical_rank_common(fcinfo, -1, &rowcount); + + PG_RETURN_INT64(rank); +} + +/* + * percent_rank() - percentile rank of hypothetical row + */ +Datum +hypothetical_percent_rank_final(PG_FUNCTION_ARGS) +{ + int64 rank; + int64 rowcount; + double result_val; + + rank = hypothetical_rank_common(fcinfo, -1, &rowcount); + + if (rowcount == 0) + PG_RETURN_FLOAT8(0); + + result_val = (double) (rank - 1) / (double) (rowcount); + + PG_RETURN_FLOAT8(result_val); +} + +/* + * cume_dist() - cumulative distribution of hypothetical row + */ +Datum +hypothetical_cume_dist_final(PG_FUNCTION_ARGS) +{ + int64 rank; + int64 rowcount; + double result_val; + + rank = hypothetical_rank_common(fcinfo, 1, &rowcount); + + result_val = (double) (rank) / (double) (rowcount + 1); + + PG_RETURN_FLOAT8(result_val); +} + +/* + * dense_rank() - rank of hypothetical row without gaps in ranking + */ +Datum +hypothetical_dense_rank_final(PG_FUNCTION_ARGS) +{ + ExprContext *econtext; + ExprState *compareTuple; + int nargs = PG_NARGS() - 1; + int64 rank = 1; + int64 duplicate_count = 0; + OSAPerGroupState *osastate; + int numDistinctCols; + Datum abbrevVal = (Datum) 0; + Datum abbrevOld = (Datum) 0; + TupleTableSlot *slot; + TupleTableSlot *extraslot; + TupleTableSlot *slot2; + int i; + + Assert(AggCheckCallContext(fcinfo, NULL) == AGG_CONTEXT_AGGREGATE); + + /* If there were no regular rows, the rank is always 1 */ + if (PG_ARGISNULL(0)) + PG_RETURN_INT64(rank); + + osastate = (OSAPerGroupState *) PG_GETARG_POINTER(0); + econtext = osastate->qstate->econtext; + if (!econtext) + { + MemoryContext oldcontext; + + /* Make sure to we create econtext under correct parent context. */ + oldcontext = MemoryContextSwitchTo(osastate->qstate->qcontext); + osastate->qstate->econtext = CreateStandaloneExprContext(); + econtext = osastate->qstate->econtext; + MemoryContextSwitchTo(oldcontext); + } + + /* Adjust nargs to be the number of direct (or aggregated) args */ + if (nargs % 2 != 0) + elog(ERROR, "wrong number of arguments in hypothetical-set function"); + nargs /= 2; + + hypothetical_check_argtypes(fcinfo, nargs, osastate->qstate->tupdesc); + + /* + * When comparing tuples, we can omit the flag column since we will only + * compare rows with flag == 0. + */ + numDistinctCols = osastate->qstate->numSortCols - 1; + + /* Build tuple comparator, if we didn't already */ + compareTuple = osastate->qstate->compareTuple; + if (compareTuple == NULL) + { + AttrNumber *sortColIdx = osastate->qstate->sortColIdx; + MemoryContext oldContext; + + oldContext = MemoryContextSwitchTo(osastate->qstate->qcontext); + compareTuple = execTuplesMatchPrepare(osastate->qstate->tupdesc, + numDistinctCols, + sortColIdx, + osastate->qstate->eqOperators, + osastate->qstate->sortCollations, + NULL); + MemoryContextSwitchTo(oldContext); + osastate->qstate->compareTuple = compareTuple; + } + + /* because we need a hypothetical row, we can't share transition state */ + Assert(!osastate->sort_done); + + /* insert the hypothetical row into the sort */ + slot = osastate->qstate->tupslot; + ExecClearTuple(slot); + for (i = 0; i < nargs; i++) + { + slot->tts_values[i] = PG_GETARG_DATUM(i + 1); + slot->tts_isnull[i] = PG_ARGISNULL(i + 1); + } + slot->tts_values[i] = Int32GetDatum(-1); + slot->tts_isnull[i] = false; + ExecStoreVirtualTuple(slot); + + tuplesort_puttupleslot(osastate->sortstate, slot); + + /* finish the sort */ + tuplesort_performsort(osastate->sortstate); + osastate->sort_done = true; + + /* + * We alternate fetching into tupslot and extraslot so that we have the + * previous row available for comparisons. This is accomplished by + * swapping the slot pointer variables after each row. + */ + extraslot = MakeSingleTupleTableSlot(osastate->qstate->tupdesc, + &TTSOpsMinimalTuple); + slot2 = extraslot; + + /* iterate till we find the hypothetical row */ + while (tuplesort_gettupleslot(osastate->sortstate, true, true, slot, + &abbrevVal)) + { + bool isnull; + Datum d = slot_getattr(slot, nargs + 1, &isnull); + TupleTableSlot *tmpslot; + + if (!isnull && DatumGetInt32(d) != 0) + break; + + /* count non-distinct tuples */ + econtext->ecxt_outertuple = slot; + econtext->ecxt_innertuple = slot2; + + if (!TupIsNull(slot2) && + abbrevVal == abbrevOld && + ExecQualAndReset(compareTuple, econtext)) + duplicate_count++; + + tmpslot = slot2; + slot2 = slot; + slot = tmpslot; + /* avoid ExecQual() calls by reusing abbreviated keys */ + abbrevOld = abbrevVal; + + rank++; + + CHECK_FOR_INTERRUPTS(); + } + + ExecClearTuple(slot); + ExecClearTuple(slot2); + + ExecDropSingleTupleTableSlot(extraslot); + + rank = rank - duplicate_count; + + PG_RETURN_INT64(rank); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/partitionfuncs.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/partitionfuncs.c new file mode 100644 index 00000000000..70e4c1308c9 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/partitionfuncs.c @@ -0,0 +1,239 @@ +/*------------------------------------------------------------------------- + * + * partitionfuncs.c + * Functions for accessing partition-related metadata + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/adt/partitionfuncs.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/htup_details.h" +#include "catalog/partition.h" +#include "catalog/pg_class.h" +#include "catalog/pg_inherits.h" +#include "catalog/pg_type.h" +#include "funcapi.h" +#include "utils/fmgrprotos.h" +#include "utils/lsyscache.h" +#include "utils/syscache.h" + +/* + * Checks if a given relation can be part of a partition tree. Returns + * false if the relation cannot be processed, in which case it is up to + * the caller to decide what to do, by either raising an error or doing + * something else. + */ +static bool +check_rel_can_be_partition(Oid relid) +{ + char relkind; + bool relispartition; + + /* Check if relation exists */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + return false; + + relkind = get_rel_relkind(relid); + relispartition = get_rel_relispartition(relid); + + /* Only allow relation types that can appear in partition trees. */ + if (!relispartition && !RELKIND_HAS_PARTITIONS(relkind)) + return false; + + return true; +} + +/* + * pg_partition_tree + * + * Produce a view with one row per member of a partition tree, beginning + * from the top-most parent given by the caller. This gives information + * about each partition, its immediate partitioned parent, if it is + * a leaf partition and its level in the hierarchy. + */ +Datum +pg_partition_tree(PG_FUNCTION_ARGS) +{ +#define PG_PARTITION_TREE_COLS 4 + Oid rootrelid = PG_GETARG_OID(0); + FuncCallContext *funcctx; + List *partitions; + + /* stuff done only on the first call of the function */ + if (SRF_IS_FIRSTCALL()) + { + MemoryContext oldcxt; + TupleDesc tupdesc; + + /* create a function context for cross-call persistence */ + funcctx = SRF_FIRSTCALL_INIT(); + + if (!check_rel_can_be_partition(rootrelid)) + SRF_RETURN_DONE(funcctx); + + /* switch to memory context appropriate for multiple function calls */ + oldcxt = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + /* + * Find all members of inheritance set. We only need AccessShareLock + * on the children for the partition information lookup. + */ + partitions = find_all_inheritors(rootrelid, AccessShareLock, NULL); + + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + funcctx->tuple_desc = tupdesc; + + /* The only state we need is the partition list */ + funcctx->user_fctx = (void *) partitions; + + MemoryContextSwitchTo(oldcxt); + } + + /* stuff done on every call of the function */ + funcctx = SRF_PERCALL_SETUP(); + partitions = (List *) funcctx->user_fctx; + + if (funcctx->call_cntr < list_length(partitions)) + { + Datum result; + Datum values[PG_PARTITION_TREE_COLS] = {0}; + bool nulls[PG_PARTITION_TREE_COLS] = {0}; + HeapTuple tuple; + Oid parentid = InvalidOid; + Oid relid = list_nth_oid(partitions, funcctx->call_cntr); + char relkind = get_rel_relkind(relid); + int level = 0; + List *ancestors = get_partition_ancestors(relid); + ListCell *lc; + + /* + * Form tuple with appropriate data. + */ + + /* relid */ + values[0] = ObjectIdGetDatum(relid); + + /* parentid */ + if (ancestors != NIL) + parentid = linitial_oid(ancestors); + if (OidIsValid(parentid)) + values[1] = ObjectIdGetDatum(parentid); + else + nulls[1] = true; + + /* isleaf */ + values[2] = BoolGetDatum(!RELKIND_HAS_PARTITIONS(relkind)); + + /* level */ + if (relid != rootrelid) + { + foreach(lc, ancestors) + { + level++; + if (lfirst_oid(lc) == rootrelid) + break; + } + } + values[3] = Int32GetDatum(level); + + tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); + result = HeapTupleGetDatum(tuple); + SRF_RETURN_NEXT(funcctx, result); + } + + /* done when there are no more elements left */ + SRF_RETURN_DONE(funcctx); +} + +/* + * pg_partition_root + * + * Returns the top-most parent of the partition tree to which a given + * relation belongs, or NULL if it's not (or cannot be) part of any + * partition tree. + */ +Datum +pg_partition_root(PG_FUNCTION_ARGS) +{ + Oid relid = PG_GETARG_OID(0); + Oid rootrelid; + List *ancestors; + + if (!check_rel_can_be_partition(relid)) + PG_RETURN_NULL(); + + /* fetch the list of ancestors */ + ancestors = get_partition_ancestors(relid); + + /* + * If the input relation is already the top-most parent, just return + * itself. + */ + if (ancestors == NIL) + PG_RETURN_OID(relid); + + rootrelid = llast_oid(ancestors); + list_free(ancestors); + + /* + * "rootrelid" must contain a valid OID, given that the input relation is + * a valid partition tree member as checked above. + */ + Assert(OidIsValid(rootrelid)); + PG_RETURN_OID(rootrelid); +} + +/* + * pg_partition_ancestors + * + * Produces a view with one row per ancestor of the given partition, + * including the input relation itself. + */ +Datum +pg_partition_ancestors(PG_FUNCTION_ARGS) +{ + Oid relid = PG_GETARG_OID(0); + FuncCallContext *funcctx; + List *ancestors; + + if (SRF_IS_FIRSTCALL()) + { + MemoryContext oldcxt; + + funcctx = SRF_FIRSTCALL_INIT(); + + if (!check_rel_can_be_partition(relid)) + SRF_RETURN_DONE(funcctx); + + oldcxt = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + ancestors = get_partition_ancestors(relid); + ancestors = lcons_oid(relid, ancestors); + + /* The only state we need is the ancestors list */ + funcctx->user_fctx = (void *) ancestors; + + MemoryContextSwitchTo(oldcxt); + } + + funcctx = SRF_PERCALL_SETUP(); + ancestors = (List *) funcctx->user_fctx; + + if (funcctx->call_cntr < list_length(ancestors)) + { + Oid resultrel = list_nth_oid(ancestors, funcctx->call_cntr); + + SRF_RETURN_NEXT(funcctx, ObjectIdGetDatum(resultrel)); + } + + SRF_RETURN_DONE(funcctx); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/pg_locale.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/pg_locale.c new file mode 100644 index 00000000000..5b0ff75414d --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/pg_locale.c @@ -0,0 +1,3086 @@ +/*----------------------------------------------------------------------- + * + * PostgreSQL locale utilities + * + * Portions Copyright (c) 2002-2023, PostgreSQL Global Development Group + * + * src/backend/utils/adt/pg_locale.c + * + *----------------------------------------------------------------------- + */ + +/*---------- + * Here is how the locale stuff is handled: LC_COLLATE and LC_CTYPE + * are fixed at CREATE DATABASE time, stored in pg_database, and cannot + * be changed. Thus, the effects of strcoll(), strxfrm(), isupper(), + * toupper(), etc. are always in the same fixed locale. + * + * LC_MESSAGES is settable at run time and will take effect + * immediately. + * + * The other categories, LC_MONETARY, LC_NUMERIC, and LC_TIME are also + * settable at run-time. However, we don't actually set those locale + * categories permanently. This would have bizarre effects like no + * longer accepting standard floating-point literals in some locales. + * Instead, we only set these locale categories briefly when needed, + * cache the required information obtained from localeconv() or + * strftime(), and then set the locale categories back to "C". + * The cached information is only used by the formatting functions + * (to_char, etc.) and the money type. For the user, this should all be + * transparent. + * + * !!! NOW HEAR THIS !!! + * + * We've been bitten repeatedly by this bug, so let's try to keep it in + * mind in future: on some platforms, the locale functions return pointers + * to static data that will be overwritten by any later locale function. + * Thus, for example, the obvious-looking sequence + * save = setlocale(category, NULL); + * if (!setlocale(category, value)) + * fail = true; + * setlocale(category, save); + * DOES NOT WORK RELIABLY: on some platforms the second setlocale() call + * will change the memory save is pointing at. To do this sort of thing + * safely, you *must* pstrdup what setlocale returns the first time. + * + * The POSIX locale standard is available here: + * + * http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap07.html + *---------- + */ + + +#include "postgres.h" + +#include <time.h> + +#include "access/htup_details.h" +#include "catalog/pg_collation.h" +#include "catalog/pg_control.h" +#include "mb/pg_wchar.h" +#include "miscadmin.h" +#include "utils/builtins.h" +#include "utils/formatting.h" +#include "utils/guc_hooks.h" +#include "utils/hsearch.h" +#include "utils/lsyscache.h" +#include "utils/memutils.h" +#include "utils/pg_locale.h" +#include "utils/syscache.h" + +#ifdef USE_ICU +#include <unicode/ucnv.h> +#include <unicode/ustring.h> +#endif + +#ifdef __GLIBC__ +#include <gnu/libc-version.h> +#endif + +#ifdef WIN32 +#include <shlwapi.h> +#endif + +/* Error triggered for locale-sensitive subroutines */ +#define PGLOCALE_SUPPORT_ERROR(provider) \ + elog(ERROR, "unsupported collprovider for %s: %c", __func__, provider) + +/* + * This should be large enough that most strings will fit, but small enough + * that we feel comfortable putting it on the stack + */ +#define TEXTBUFLEN 1024 + +#define MAX_L10N_DATA 80 + + +/* GUC settings */ +__thread char *locale_messages; +__thread char *locale_monetary; +__thread char *locale_numeric; +__thread char *locale_time; + +__thread int icu_validation_level = WARNING; + +/* + * lc_time localization cache. + * + * We use only the first 7 or 12 entries of these arrays. The last array + * element is left as NULL for the convenience of outside code that wants + * to sequentially scan these arrays. + */ +__thread char *localized_abbrev_days[7 + 1]; +__thread char *localized_full_days[7 + 1]; +__thread char *localized_abbrev_months[12 + 1]; +__thread char *localized_full_months[12 + 1]; + +/* is the databases's LC_CTYPE the C locale? */ +__thread bool database_ctype_is_c = false; + +/* indicates whether locale information cache is valid */ +static __thread bool CurrentLocaleConvValid = false; +static __thread bool CurrentLCTimeValid = false; +static __thread struct lconv CurrentLocaleConv; +static __thread bool CurrentLocaleConvAllocated = false; + +/* Cache for collation-related knowledge */ + +typedef struct +{ + Oid collid; /* hash key: pg_collation OID */ + bool collate_is_c; /* is collation's LC_COLLATE C? */ + bool ctype_is_c; /* is collation's LC_CTYPE C? */ + bool flags_valid; /* true if above flags are valid */ + pg_locale_t locale; /* locale_t struct, or 0 if not valid */ +} collation_cache_entry; + +static __thread HTAB *collation_cache = NULL; + + +#if defined(WIN32) && defined(LC_MESSAGES) +static char *IsoLocaleName(const char *); +#endif + +#ifdef USE_ICU +/* + * Converter object for converting between ICU's UChar strings and C strings + * in database encoding. Since the database encoding doesn't change, we only + * need one of these per session. + */ +static __thread UConverter *icu_converter = NULL; + +static UCollator *pg_ucol_open(const char *loc_str); +static void init_icu_converter(void); +static size_t uchar_length(UConverter *converter, + const char *str, int32_t len); +static int32_t uchar_convert(UConverter *converter, + UChar *dest, int32_t destlen, + const char *src, int32_t srclen); +static void icu_set_collation_attributes(UCollator *collator, const char *loc, + UErrorCode *status); +#endif + +/* + * pg_perm_setlocale + * + * This wraps the libc function setlocale(), with two additions. First, when + * changing LC_CTYPE, update gettext's encoding for the current message + * domain. GNU gettext automatically tracks LC_CTYPE on most platforms, but + * not on Windows. Second, if the operation is successful, the corresponding + * LC_XXX environment variable is set to match. By setting the environment + * variable, we ensure that any subsequent use of setlocale(..., "") will + * preserve the settings made through this routine. Of course, LC_ALL must + * also be unset to fully ensure that, but that has to be done elsewhere after + * all the individual LC_XXX variables have been set correctly. (Thank you + * Perl for making this kluge necessary.) + */ +char * +pg_perm_setlocale(int category, const char *locale) +{ + char *result; + const char *envvar; + +#ifndef WIN32 + result = setlocale(category, locale); +#else + + /* + * On Windows, setlocale(LC_MESSAGES) does not work, so just assume that + * the given value is good and set it in the environment variables. We + * must ignore attempts to set to "", which means "keep using the old + * environment value". + */ +#ifdef LC_MESSAGES + if (category == LC_MESSAGES) + { + result = (char *) locale; + if (locale == NULL || locale[0] == '\0') + return result; + } + else +#endif + result = setlocale(category, locale); +#endif /* WIN32 */ + + if (result == NULL) + return result; /* fall out immediately on failure */ + + /* + * Use the right encoding in translated messages. Under ENABLE_NLS, let + * pg_bind_textdomain_codeset() figure it out. Under !ENABLE_NLS, message + * format strings are ASCII, but database-encoding strings may enter the + * message via %s. This makes the overall message encoding equal to the + * database encoding. + */ + if (category == LC_CTYPE) + { + static __thread char save_lc_ctype[LOCALE_NAME_BUFLEN]; + + /* copy setlocale() return value before callee invokes it again */ + strlcpy(save_lc_ctype, result, sizeof(save_lc_ctype)); + result = save_lc_ctype; + +#ifdef ENABLE_NLS + SetMessageEncoding(pg_bind_textdomain_codeset(textdomain(NULL))); +#else + SetMessageEncoding(GetDatabaseEncoding()); +#endif + } + + switch (category) + { + case LC_COLLATE: + envvar = "LC_COLLATE"; + break; + case LC_CTYPE: + envvar = "LC_CTYPE"; + break; +#ifdef LC_MESSAGES + case LC_MESSAGES: + envvar = "LC_MESSAGES"; +#ifdef WIN32 + result = IsoLocaleName(locale); + if (result == NULL) + result = (char *) locale; + elog(DEBUG3, "IsoLocaleName() executed; locale: \"%s\"", result); +#endif /* WIN32 */ + break; +#endif /* LC_MESSAGES */ + case LC_MONETARY: + envvar = "LC_MONETARY"; + break; + case LC_NUMERIC: + envvar = "LC_NUMERIC"; + break; + case LC_TIME: + envvar = "LC_TIME"; + break; + default: + elog(FATAL, "unrecognized LC category: %d", category); + return NULL; /* keep compiler quiet */ + } + + if (setenv(envvar, result, 1) != 0) + return NULL; + + return result; +} + + +/* + * Is the locale name valid for the locale category? + * + * If successful, and canonname isn't NULL, a palloc'd copy of the locale's + * canonical name is stored there. This is especially useful for figuring out + * what locale name "" means (ie, the server environment value). (Actually, + * it seems that on most implementations that's the only thing it's good for; + * we could wish that setlocale gave back a canonically spelled version of + * the locale name, but typically it doesn't.) + */ +bool +check_locale(int category, const char *locale, char **canonname) +{ + char *save; + char *res; + + if (canonname) + *canonname = NULL; /* in case of failure */ + + save = setlocale(category, NULL); + if (!save) + return false; /* won't happen, we hope */ + + /* save may be pointing at a modifiable scratch variable, see above. */ + save = pstrdup(save); + + /* set the locale with setlocale, to see if it accepts it. */ + res = setlocale(category, locale); + + /* save canonical name if requested. */ + if (res && canonname) + *canonname = pstrdup(res); + + /* restore old value. */ + if (!setlocale(category, save)) + elog(WARNING, "failed to restore old locale \"%s\"", save); + pfree(save); + + return (res != NULL); +} + + +/* + * GUC check/assign hooks + * + * For most locale categories, the assign hook doesn't actually set the locale + * permanently, just reset flags so that the next use will cache the + * appropriate values. (See explanation at the top of this file.) + * + * Note: we accept value = "" as selecting the postmaster's environment + * value, whatever it was (so long as the environment setting is legal). + * This will have been locked down by an earlier call to pg_perm_setlocale. + */ +bool +check_locale_monetary(char **newval, void **extra, GucSource source) +{ + return check_locale(LC_MONETARY, *newval, NULL); +} + +void +assign_locale_monetary(const char *newval, void *extra) +{ + CurrentLocaleConvValid = false; +} + +bool +check_locale_numeric(char **newval, void **extra, GucSource source) +{ + return check_locale(LC_NUMERIC, *newval, NULL); +} + +void +assign_locale_numeric(const char *newval, void *extra) +{ + CurrentLocaleConvValid = false; +} + +bool +check_locale_time(char **newval, void **extra, GucSource source) +{ + return check_locale(LC_TIME, *newval, NULL); +} + +void +assign_locale_time(const char *newval, void *extra) +{ + CurrentLCTimeValid = false; +} + +/* + * We allow LC_MESSAGES to actually be set globally. + * + * Note: we normally disallow value = "" because it wouldn't have consistent + * semantics (it'd effectively just use the previous value). However, this + * is the value passed for PGC_S_DEFAULT, so don't complain in that case, + * not even if the attempted setting fails due to invalid environment value. + * The idea there is just to accept the environment setting *if possible* + * during startup, until we can read the proper value from postgresql.conf. + */ +bool +check_locale_messages(char **newval, void **extra, GucSource source) +{ + if (**newval == '\0') + { + if (source == PGC_S_DEFAULT) + return true; + else + return false; + } + + /* + * LC_MESSAGES category does not exist everywhere, but accept it anyway + * + * On Windows, we can't even check the value, so accept blindly + */ +#if defined(LC_MESSAGES) && !defined(WIN32) + return check_locale(LC_MESSAGES, *newval, NULL); +#else + return true; +#endif +} + +void +assign_locale_messages(const char *newval, void *extra) +{ + /* + * LC_MESSAGES category does not exist everywhere, but accept it anyway. + * We ignore failure, as per comment above. + */ +#ifdef LC_MESSAGES + (void) pg_perm_setlocale(LC_MESSAGES, newval); +#endif +} + + +/* + * Frees the malloced content of a struct lconv. (But not the struct + * itself.) It's important that this not throw elog(ERROR). + */ +static void +free_struct_lconv(struct lconv *s) +{ + free(s->decimal_point); + free(s->thousands_sep); + free(s->grouping); + free(s->int_curr_symbol); + free(s->currency_symbol); + free(s->mon_decimal_point); + free(s->mon_thousands_sep); + free(s->mon_grouping); + free(s->positive_sign); + free(s->negative_sign); +} + +void free_current_locale_conv() +{ + if (CurrentLocaleConvAllocated) + { + free_struct_lconv(&CurrentLocaleConv); + CurrentLocaleConvAllocated = false; + } +} +/* + * Check that all fields of a struct lconv (or at least, the ones we care + * about) are non-NULL. The field list must match free_struct_lconv(). + */ +static bool +struct_lconv_is_valid(struct lconv *s) +{ + if (s->decimal_point == NULL) + return false; + if (s->thousands_sep == NULL) + return false; + if (s->grouping == NULL) + return false; + if (s->int_curr_symbol == NULL) + return false; + if (s->currency_symbol == NULL) + return false; + if (s->mon_decimal_point == NULL) + return false; + if (s->mon_thousands_sep == NULL) + return false; + if (s->mon_grouping == NULL) + return false; + if (s->positive_sign == NULL) + return false; + if (s->negative_sign == NULL) + return false; + return true; +} + + +/* + * Convert the strdup'd string at *str from the specified encoding to the + * database encoding. + */ +static void +db_encoding_convert(int encoding, char **str) +{ + char *pstr; + char *mstr; + + /* convert the string to the database encoding */ + pstr = pg_any_to_server(*str, strlen(*str), encoding); + if (pstr == *str) + return; /* no conversion happened */ + + /* need it malloc'd not palloc'd */ + mstr = strdup(pstr); + if (mstr == NULL) + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"))); + + /* replace old string */ + free(*str); + *str = mstr; + + pfree(pstr); +} + + +/* + * Return the POSIX lconv struct (contains number/money formatting + * information) with locale information for all categories. + */ +struct lconv * +PGLC_localeconv(void) +{ + struct lconv *extlconv; + struct lconv worklconv; + char *save_lc_monetary; + char *save_lc_numeric; +#ifdef WIN32 + char *save_lc_ctype; +#endif + + /* Did we do it already? */ + if (CurrentLocaleConvValid) + return &CurrentLocaleConv; + + /* Free any already-allocated storage */ + if (CurrentLocaleConvAllocated) + { + free_struct_lconv(&CurrentLocaleConv); + CurrentLocaleConvAllocated = false; + } + + /* + * This is tricky because we really don't want to risk throwing error + * while the locale is set to other than our usual settings. Therefore, + * the process is: collect the usual settings, set locale to special + * setting, copy relevant data into worklconv using strdup(), restore + * normal settings, convert data to desired encoding, and finally stash + * the collected data in CurrentLocaleConv. This makes it safe if we + * throw an error during encoding conversion or run out of memory anywhere + * in the process. All data pointed to by struct lconv members is + * allocated with strdup, to avoid premature elog(ERROR) and to allow + * using a single cleanup routine. + */ + memset(&worklconv, 0, sizeof(worklconv)); + + /* Save prevailing values of monetary and numeric locales */ + save_lc_monetary = setlocale(LC_MONETARY, NULL); + if (!save_lc_monetary) + elog(ERROR, "setlocale(NULL) failed"); + save_lc_monetary = pstrdup(save_lc_monetary); + + save_lc_numeric = setlocale(LC_NUMERIC, NULL); + if (!save_lc_numeric) + elog(ERROR, "setlocale(NULL) failed"); + save_lc_numeric = pstrdup(save_lc_numeric); + +#ifdef WIN32 + + /* + * The POSIX standard explicitly says that it is undefined what happens if + * LC_MONETARY or LC_NUMERIC imply an encoding (codeset) different from + * that implied by LC_CTYPE. In practice, all Unix-ish platforms seem to + * believe that localeconv() should return strings that are encoded in the + * codeset implied by the LC_MONETARY or LC_NUMERIC locale name. Hence, + * once we have successfully collected the localeconv() results, we will + * convert them from that codeset to the desired server encoding. + * + * Windows, of course, resolutely does things its own way; on that + * platform LC_CTYPE has to match LC_MONETARY/LC_NUMERIC to get sane + * results. Hence, we must temporarily set that category as well. + */ + + /* Save prevailing value of ctype locale */ + save_lc_ctype = setlocale(LC_CTYPE, NULL); + if (!save_lc_ctype) + elog(ERROR, "setlocale(NULL) failed"); + save_lc_ctype = pstrdup(save_lc_ctype); + + /* Here begins the critical section where we must not throw error */ + + /* use numeric to set the ctype */ + setlocale(LC_CTYPE, locale_numeric); +#endif + + /* Get formatting information for numeric */ + setlocale(LC_NUMERIC, locale_numeric); + extlconv = localeconv(); + + /* Must copy data now in case setlocale() overwrites it */ + worklconv.decimal_point = strdup(extlconv->decimal_point); + worklconv.thousands_sep = strdup(extlconv->thousands_sep); + worklconv.grouping = strdup(extlconv->grouping); + +#ifdef WIN32 + /* use monetary to set the ctype */ + setlocale(LC_CTYPE, locale_monetary); +#endif + + /* Get formatting information for monetary */ + setlocale(LC_MONETARY, locale_monetary); + extlconv = localeconv(); + + /* Must copy data now in case setlocale() overwrites it */ + worklconv.int_curr_symbol = strdup(extlconv->int_curr_symbol); + worklconv.currency_symbol = strdup(extlconv->currency_symbol); + worklconv.mon_decimal_point = strdup(extlconv->mon_decimal_point); + worklconv.mon_thousands_sep = strdup(extlconv->mon_thousands_sep); + worklconv.mon_grouping = strdup(extlconv->mon_grouping); + worklconv.positive_sign = strdup(extlconv->positive_sign); + worklconv.negative_sign = strdup(extlconv->negative_sign); + /* Copy scalar fields as well */ + worklconv.int_frac_digits = extlconv->int_frac_digits; + worklconv.frac_digits = extlconv->frac_digits; + worklconv.p_cs_precedes = extlconv->p_cs_precedes; + worklconv.p_sep_by_space = extlconv->p_sep_by_space; + worklconv.n_cs_precedes = extlconv->n_cs_precedes; + worklconv.n_sep_by_space = extlconv->n_sep_by_space; + worklconv.p_sign_posn = extlconv->p_sign_posn; + worklconv.n_sign_posn = extlconv->n_sign_posn; + + /* + * Restore the prevailing locale settings; failure to do so is fatal. + * Possibly we could limp along with nondefault LC_MONETARY or LC_NUMERIC, + * but proceeding with the wrong value of LC_CTYPE would certainly be bad + * news; and considering that the prevailing LC_MONETARY and LC_NUMERIC + * are almost certainly "C", there's really no reason that restoring those + * should fail. + */ +#ifdef WIN32 + if (!setlocale(LC_CTYPE, save_lc_ctype)) + elog(FATAL, "failed to restore LC_CTYPE to \"%s\"", save_lc_ctype); +#endif + if (!setlocale(LC_MONETARY, save_lc_monetary)) + elog(FATAL, "failed to restore LC_MONETARY to \"%s\"", save_lc_monetary); + if (!setlocale(LC_NUMERIC, save_lc_numeric)) + elog(FATAL, "failed to restore LC_NUMERIC to \"%s\"", save_lc_numeric); + + /* + * At this point we've done our best to clean up, and can call functions + * that might possibly throw errors with a clean conscience. But let's + * make sure we don't leak any already-strdup'd fields in worklconv. + */ + PG_TRY(); + { + int encoding; + + /* Release the pstrdup'd locale names */ + pfree(save_lc_monetary); + pfree(save_lc_numeric); +#ifdef WIN32 + pfree(save_lc_ctype); +#endif + + /* If any of the preceding strdup calls failed, complain now. */ + if (!struct_lconv_is_valid(&worklconv)) + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"))); + + /* + * Now we must perform encoding conversion from whatever's associated + * with the locales into the database encoding. If we can't identify + * the encoding implied by LC_NUMERIC or LC_MONETARY (ie we get -1), + * use PG_SQL_ASCII, which will result in just validating that the + * strings are OK in the database encoding. + */ + encoding = pg_get_encoding_from_locale(locale_numeric, true); + if (encoding < 0) + encoding = PG_SQL_ASCII; + + db_encoding_convert(encoding, &worklconv.decimal_point); + db_encoding_convert(encoding, &worklconv.thousands_sep); + /* grouping is not text and does not require conversion */ + + encoding = pg_get_encoding_from_locale(locale_monetary, true); + if (encoding < 0) + encoding = PG_SQL_ASCII; + + db_encoding_convert(encoding, &worklconv.int_curr_symbol); + db_encoding_convert(encoding, &worklconv.currency_symbol); + db_encoding_convert(encoding, &worklconv.mon_decimal_point); + db_encoding_convert(encoding, &worklconv.mon_thousands_sep); + /* mon_grouping is not text and does not require conversion */ + db_encoding_convert(encoding, &worklconv.positive_sign); + db_encoding_convert(encoding, &worklconv.negative_sign); + } + PG_CATCH(); + { + free_struct_lconv(&worklconv); + PG_RE_THROW(); + } + PG_END_TRY(); + + /* + * Everything is good, so save the results. + */ + CurrentLocaleConv = worklconv; + CurrentLocaleConvAllocated = true; + CurrentLocaleConvValid = true; + return &CurrentLocaleConv; +} + +#ifdef WIN32 +/* + * On Windows, strftime() returns its output in encoding CP_ACP (the default + * operating system codepage for the computer), which is likely different + * from SERVER_ENCODING. This is especially important in Japanese versions + * of Windows which will use SJIS encoding, which we don't support as a + * server encoding. + * + * So, instead of using strftime(), use wcsftime() to return the value in + * wide characters (internally UTF16) and then convert to UTF8, which we + * know how to handle directly. + * + * Note that this only affects the calls to strftime() in this file, which are + * used to get the locale-aware strings. Other parts of the backend use + * pg_strftime(), which isn't locale-aware and does not need to be replaced. + */ +static size_t +strftime_win32(char *dst, size_t dstlen, + const char *format, const struct tm *tm) +{ + size_t len; + wchar_t wformat[8]; /* formats used below need 3 chars */ + wchar_t wbuf[MAX_L10N_DATA]; + + /* + * Get a wchar_t version of the format string. We only actually use + * plain-ASCII formats in this file, so we can say that they're UTF8. + */ + len = MultiByteToWideChar(CP_UTF8, 0, format, -1, + wformat, lengthof(wformat)); + if (len == 0) + elog(ERROR, "could not convert format string from UTF-8: error code %lu", + GetLastError()); + + len = wcsftime(wbuf, MAX_L10N_DATA, wformat, tm); + if (len == 0) + { + /* + * wcsftime failed, possibly because the result would not fit in + * MAX_L10N_DATA. Return 0 with the contents of dst unspecified. + */ + return 0; + } + + len = WideCharToMultiByte(CP_UTF8, 0, wbuf, len, dst, dstlen - 1, + NULL, NULL); + if (len == 0) + elog(ERROR, "could not convert string to UTF-8: error code %lu", + GetLastError()); + + dst[len] = '\0'; + + return len; +} + +/* redefine strftime() */ +#define strftime(a,b,c,d) strftime_win32(a,b,c,d) +#endif /* WIN32 */ + +/* + * Subroutine for cache_locale_time(). + * Convert the given string from encoding "encoding" to the database + * encoding, and store the result at *dst, replacing any previous value. + */ +static void +cache_single_string(char **dst, const char *src, int encoding) +{ + char *ptr; + char *olddst; + + /* Convert the string to the database encoding, or validate it's OK */ + ptr = pg_any_to_server(src, strlen(src), encoding); + + /* Store the string in long-lived storage, replacing any previous value */ + olddst = *dst; + *dst = MemoryContextStrdup(TopMemoryContext, ptr); + if (olddst) + pfree(olddst); + + /* Might as well clean up any palloc'd conversion result, too */ + if (ptr != src) + pfree(ptr); +} + +/* + * Update the lc_time localization cache variables if needed. + */ +void +cache_locale_time(void) +{ + char buf[(2 * 7 + 2 * 12) * MAX_L10N_DATA]; + char *bufptr; + time_t timenow; + struct tm *timeinfo; + bool strftimefail = false; + int encoding; + int i; + char *save_lc_time; +#ifdef WIN32 + char *save_lc_ctype; +#endif + + /* did we do this already? */ + if (CurrentLCTimeValid) + return; + + elog(DEBUG3, "cache_locale_time() executed; locale: \"%s\"", locale_time); + + /* + * As in PGLC_localeconv(), it's critical that we not throw error while + * libc's locale settings have nondefault values. Hence, we just call + * strftime() within the critical section, and then convert and save its + * results afterwards. + */ + + /* Save prevailing value of time locale */ + save_lc_time = setlocale(LC_TIME, NULL); + if (!save_lc_time) + elog(ERROR, "setlocale(NULL) failed"); + save_lc_time = pstrdup(save_lc_time); + +#ifdef WIN32 + + /* + * On Windows, it appears that wcsftime() internally uses LC_CTYPE, so we + * must set it here. This code looks the same as what PGLC_localeconv() + * does, but the underlying reason is different: this does NOT determine + * the encoding we'll get back from strftime_win32(). + */ + + /* Save prevailing value of ctype locale */ + save_lc_ctype = setlocale(LC_CTYPE, NULL); + if (!save_lc_ctype) + elog(ERROR, "setlocale(NULL) failed"); + save_lc_ctype = pstrdup(save_lc_ctype); + + /* use lc_time to set the ctype */ + setlocale(LC_CTYPE, locale_time); +#endif + + setlocale(LC_TIME, locale_time); + + /* We use times close to current time as data for strftime(). */ + timenow = time(NULL); + timeinfo = localtime(&timenow); + + /* Store the strftime results in MAX_L10N_DATA-sized portions of buf[] */ + bufptr = buf; + + /* + * MAX_L10N_DATA is sufficient buffer space for every known locale, and + * POSIX defines no strftime() errors. (Buffer space exhaustion is not an + * error.) An implementation might report errors (e.g. ENOMEM) by + * returning 0 (or, less plausibly, a negative value) and setting errno. + * Report errno just in case the implementation did that, but clear it in + * advance of the calls so we don't emit a stale, unrelated errno. + */ + errno = 0; + + /* localized days */ + for (i = 0; i < 7; i++) + { + timeinfo->tm_wday = i; + if (strftime(bufptr, MAX_L10N_DATA, "%a", timeinfo) <= 0) + strftimefail = true; + bufptr += MAX_L10N_DATA; + if (strftime(bufptr, MAX_L10N_DATA, "%A", timeinfo) <= 0) + strftimefail = true; + bufptr += MAX_L10N_DATA; + } + + /* localized months */ + for (i = 0; i < 12; i++) + { + timeinfo->tm_mon = i; + timeinfo->tm_mday = 1; /* make sure we don't have invalid date */ + if (strftime(bufptr, MAX_L10N_DATA, "%b", timeinfo) <= 0) + strftimefail = true; + bufptr += MAX_L10N_DATA; + if (strftime(bufptr, MAX_L10N_DATA, "%B", timeinfo) <= 0) + strftimefail = true; + bufptr += MAX_L10N_DATA; + } + + /* + * Restore the prevailing locale settings; as in PGLC_localeconv(), + * failure to do so is fatal. + */ +#ifdef WIN32 + if (!setlocale(LC_CTYPE, save_lc_ctype)) + elog(FATAL, "failed to restore LC_CTYPE to \"%s\"", save_lc_ctype); +#endif + if (!setlocale(LC_TIME, save_lc_time)) + elog(FATAL, "failed to restore LC_TIME to \"%s\"", save_lc_time); + + /* + * At this point we've done our best to clean up, and can throw errors, or + * call functions that might throw errors, with a clean conscience. + */ + if (strftimefail) + elog(ERROR, "strftime() failed: %m"); + + /* Release the pstrdup'd locale names */ + pfree(save_lc_time); +#ifdef WIN32 + pfree(save_lc_ctype); +#endif + +#ifndef WIN32 + + /* + * As in PGLC_localeconv(), we must convert strftime()'s output from the + * encoding implied by LC_TIME to the database encoding. If we can't + * identify the LC_TIME encoding, just perform encoding validation. + */ + encoding = pg_get_encoding_from_locale(locale_time, true); + if (encoding < 0) + encoding = PG_SQL_ASCII; + +#else + + /* + * On Windows, strftime_win32() always returns UTF8 data, so convert from + * that if necessary. + */ + encoding = PG_UTF8; + +#endif /* WIN32 */ + + bufptr = buf; + + /* localized days */ + for (i = 0; i < 7; i++) + { + cache_single_string(&localized_abbrev_days[i], bufptr, encoding); + bufptr += MAX_L10N_DATA; + cache_single_string(&localized_full_days[i], bufptr, encoding); + bufptr += MAX_L10N_DATA; + } + localized_abbrev_days[7] = NULL; + localized_full_days[7] = NULL; + + /* localized months */ + for (i = 0; i < 12; i++) + { + cache_single_string(&localized_abbrev_months[i], bufptr, encoding); + bufptr += MAX_L10N_DATA; + cache_single_string(&localized_full_months[i], bufptr, encoding); + bufptr += MAX_L10N_DATA; + } + localized_abbrev_months[12] = NULL; + localized_full_months[12] = NULL; + + CurrentLCTimeValid = true; +} + + +#if defined(WIN32) && defined(LC_MESSAGES) +/* + * Convert a Windows setlocale() argument to a Unix-style one. + * + * Regardless of platform, we install message catalogs under a Unix-style + * LL[_CC][.ENCODING][@VARIANT] naming convention. Only LC_MESSAGES settings + * following that style will elicit localized interface strings. + * + * Before Visual Studio 2012 (msvcr110.dll), Windows setlocale() accepted "C" + * (but not "c") and strings of the form <Language>[_<Country>][.<CodePage>], + * case-insensitive. setlocale() returns the fully-qualified form; for + * example, setlocale("thaI") returns "Thai_Thailand.874". Internally, + * setlocale() and _create_locale() select a "locale identifier"[1] and store + * it in an undocumented _locale_t field. From that LCID, we can retrieve the + * ISO 639 language and the ISO 3166 country. Character encoding does not + * matter, because the server and client encodings govern that. + * + * Windows Vista introduced the "locale name" concept[2], closely following + * RFC 4646. Locale identifiers are now deprecated. Starting with Visual + * Studio 2012, setlocale() accepts locale names in addition to the strings it + * accepted historically. It does not standardize them; setlocale("Th-tH") + * returns "Th-tH". setlocale(category, "") still returns a traditional + * string. Furthermore, msvcr110.dll changed the undocumented _locale_t + * content to carry locale names instead of locale identifiers. + * + * Visual Studio 2015 should still be able to do the same as Visual Studio + * 2012, but the declaration of locale_name is missing in _locale_t, causing + * this code compilation to fail, hence this falls back instead on to + * enumerating all system locales by using EnumSystemLocalesEx to find the + * required locale name. If the input argument is in Unix-style then we can + * get ISO Locale name directly by using GetLocaleInfoEx() with LCType as + * LOCALE_SNAME. + * + * MinGW headers declare _create_locale(), but msvcrt.dll lacks that symbol in + * releases before Windows 8. IsoLocaleName() always fails in a MinGW-built + * postgres.exe, so only Unix-style values of the lc_messages GUC can elicit + * localized messages. In particular, every lc_messages setting that initdb + * can select automatically will yield only C-locale messages. XXX This could + * be fixed by running the fully-qualified locale name through a lookup table. + * + * This function returns a pointer to a static buffer bearing the converted + * name or NULL if conversion fails. + * + * [1] https://docs.microsoft.com/en-us/windows/win32/intl/locale-identifiers + * [2] https://docs.microsoft.com/en-us/windows/win32/intl/locale-names + */ + +#if defined(_MSC_VER) + +/* + * Callback function for EnumSystemLocalesEx() in get_iso_localename(). + * + * This function enumerates all system locales, searching for one that matches + * an input with the format: <Language>[_<Country>], e.g. + * English[_United States] + * + * The input is a three wchar_t array as an LPARAM. The first element is the + * locale_name we want to match, the second element is an allocated buffer + * where the Unix-style locale is copied if a match is found, and the third + * element is the search status, 1 if a match was found, 0 otherwise. + */ +static BOOL CALLBACK +search_locale_enum(LPWSTR pStr, DWORD dwFlags, LPARAM lparam) +{ + wchar_t test_locale[LOCALE_NAME_MAX_LENGTH]; + wchar_t **argv; + + (void) (dwFlags); + + argv = (wchar_t **) lparam; + *argv[2] = (wchar_t) 0; + + memset(test_locale, 0, sizeof(test_locale)); + + /* Get the name of the <Language> in English */ + if (GetLocaleInfoEx(pStr, LOCALE_SENGLISHLANGUAGENAME, + test_locale, LOCALE_NAME_MAX_LENGTH)) + { + /* + * If the enumerated locale does not have a hyphen ("en") OR the + * locale_name input does not have an underscore ("English"), we only + * need to compare the <Language> tags. + */ + if (wcsrchr(pStr, '-') == NULL || wcsrchr(argv[0], '_') == NULL) + { + if (_wcsicmp(argv[0], test_locale) == 0) + { + wcscpy(argv[1], pStr); + *argv[2] = (wchar_t) 1; + return FALSE; + } + } + + /* + * We have to compare a full <Language>_<Country> tag, so we append + * the underscore and name of the country/region in English, e.g. + * "English_United States". + */ + else + { + size_t len; + + wcscat(test_locale, L"_"); + len = wcslen(test_locale); + if (GetLocaleInfoEx(pStr, LOCALE_SENGLISHCOUNTRYNAME, + test_locale + len, + LOCALE_NAME_MAX_LENGTH - len)) + { + if (_wcsicmp(argv[0], test_locale) == 0) + { + wcscpy(argv[1], pStr); + *argv[2] = (wchar_t) 1; + return FALSE; + } + } + } + } + + return TRUE; +} + +/* + * This function converts a Windows locale name to an ISO formatted version + * for Visual Studio 2015 or greater. + * + * Returns NULL, if no valid conversion was found. + */ +static char * +get_iso_localename(const char *winlocname) +{ + wchar_t wc_locale_name[LOCALE_NAME_MAX_LENGTH]; + wchar_t buffer[LOCALE_NAME_MAX_LENGTH]; + static char iso_lc_messages[LOCALE_NAME_MAX_LENGTH]; + char *period; + int len; + int ret_val; + + /* + * Valid locales have the following syntax: + * <Language>[_<Country>[.<CodePage>]] + * + * GetLocaleInfoEx can only take locale name without code-page and for the + * purpose of this API the code-page doesn't matter. + */ + period = strchr(winlocname, '.'); + if (period != NULL) + len = period - winlocname; + else + len = pg_mbstrlen(winlocname); + + memset(wc_locale_name, 0, sizeof(wc_locale_name)); + memset(buffer, 0, sizeof(buffer)); + MultiByteToWideChar(CP_ACP, 0, winlocname, len, wc_locale_name, + LOCALE_NAME_MAX_LENGTH); + + /* + * If the lc_messages is already a Unix-style string, we have a direct + * match with LOCALE_SNAME, e.g. en-US, en_US. + */ + ret_val = GetLocaleInfoEx(wc_locale_name, LOCALE_SNAME, (LPWSTR) &buffer, + LOCALE_NAME_MAX_LENGTH); + if (!ret_val) + { + /* + * Search for a locale in the system that matches language and country + * name. + */ + wchar_t *argv[3]; + + argv[0] = wc_locale_name; + argv[1] = buffer; + argv[2] = (wchar_t *) &ret_val; + EnumSystemLocalesEx(search_locale_enum, LOCALE_WINDOWS, (LPARAM) argv, + NULL); + } + + if (ret_val) + { + size_t rc; + char *hyphen; + + /* Locale names use only ASCII, any conversion locale suffices. */ + rc = wchar2char(iso_lc_messages, buffer, sizeof(iso_lc_messages), NULL); + if (rc == -1 || rc == sizeof(iso_lc_messages)) + return NULL; + + /* + * Since the message catalogs sit on a case-insensitive filesystem, we + * need not standardize letter case here. So long as we do not ship + * message catalogs for which it would matter, we also need not + * translate the script/variant portion, e.g. uz-Cyrl-UZ to + * uz_UZ@cyrillic. Simply replace the hyphen with an underscore. + */ + hyphen = strchr(iso_lc_messages, '-'); + if (hyphen) + *hyphen = '_'; + return iso_lc_messages; + } + + return NULL; +} + +static char * +IsoLocaleName(const char *winlocname) +{ + static char iso_lc_messages[LOCALE_NAME_MAX_LENGTH]; + + if (pg_strcasecmp("c", winlocname) == 0 || + pg_strcasecmp("posix", winlocname) == 0) + { + strcpy(iso_lc_messages, "C"); + return iso_lc_messages; + } + else + return get_iso_localename(winlocname); +} + +#else /* !defined(_MSC_VER) */ + +static char * +IsoLocaleName(const char *winlocname) +{ + return NULL; /* Not supported on MinGW */ +} + +#endif /* defined(_MSC_VER) */ + +#endif /* WIN32 && LC_MESSAGES */ + + +/* + * Cache mechanism for collation information. + * + * We cache two flags: whether the collation's LC_COLLATE or LC_CTYPE is C + * (or POSIX), so we can optimize a few code paths in various places. + * For the built-in C and POSIX collations, we can know that without even + * doing a cache lookup, but we want to support aliases for C/POSIX too. + * For the "default" collation, there are separate static cache variables, + * since consulting the pg_collation catalog doesn't tell us what we need. + * + * Also, if a pg_locale_t has been requested for a collation, we cache that + * for the life of a backend. + * + * Note that some code relies on the flags not reporting false negatives + * (that is, saying it's not C when it is). For example, char2wchar() + * could fail if the locale is C, so str_tolower() shouldn't call it + * in that case. + * + * Note that we currently lack any way to flush the cache. Since we don't + * support ALTER COLLATION, this is OK. The worst case is that someone + * drops a collation, and a useless cache entry hangs around in existing + * backends. + */ + +static collation_cache_entry * +lookup_collation_cache(Oid collation, bool set_flags) +{ + collation_cache_entry *cache_entry; + bool found; + + Assert(OidIsValid(collation)); + Assert(collation != DEFAULT_COLLATION_OID); + + if (collation_cache == NULL) + { + /* First time through, initialize the hash table */ + HASHCTL ctl; + + ctl.keysize = sizeof(Oid); + ctl.entrysize = sizeof(collation_cache_entry); + collation_cache = hash_create("Collation cache", 100, &ctl, + HASH_ELEM | HASH_BLOBS); + } + + cache_entry = hash_search(collation_cache, &collation, HASH_ENTER, &found); + if (!found) + { + /* + * Make sure cache entry is marked invalid, in case we fail before + * setting things. + */ + cache_entry->flags_valid = false; + cache_entry->locale = 0; + } + + if (set_flags && !cache_entry->flags_valid) + { + /* Attempt to set the flags */ + HeapTuple tp; + Form_pg_collation collform; + + tp = SearchSysCache1(COLLOID, ObjectIdGetDatum(collation)); + if (!HeapTupleIsValid(tp)) + elog(ERROR, "cache lookup failed for collation %u", collation); + collform = (Form_pg_collation) GETSTRUCT(tp); + + if (collform->collprovider == COLLPROVIDER_LIBC) + { + Datum datum; + const char *collcollate; + const char *collctype; + + datum = SysCacheGetAttrNotNull(COLLOID, tp, Anum_pg_collation_collcollate); + collcollate = TextDatumGetCString(datum); + datum = SysCacheGetAttrNotNull(COLLOID, tp, Anum_pg_collation_collctype); + collctype = TextDatumGetCString(datum); + + cache_entry->collate_is_c = ((strcmp(collcollate, "C") == 0) || + (strcmp(collcollate, "POSIX") == 0)); + cache_entry->ctype_is_c = ((strcmp(collctype, "C") == 0) || + (strcmp(collctype, "POSIX") == 0)); + } + else + { + cache_entry->collate_is_c = false; + cache_entry->ctype_is_c = false; + } + + cache_entry->flags_valid = true; + + ReleaseSysCache(tp); + } + + return cache_entry; +} + + +/* + * Detect whether collation's LC_COLLATE property is C + */ +bool +lc_collate_is_c(Oid collation) +{ + /* + * If we're asked about "collation 0", return false, so that the code will + * go into the non-C path and report that the collation is bogus. + */ + if (!OidIsValid(collation)) + return false; + + /* + * If we're asked about the default collation, we have to inquire of the C + * library. Cache the result so we only have to compute it once. + */ + if (collation == DEFAULT_COLLATION_OID) + { + static __thread int result = -1; + char *localeptr; + + if (default_locale.provider == COLLPROVIDER_ICU) + return false; + + if (result >= 0) + return (bool) result; + localeptr = setlocale(LC_COLLATE, NULL); + if (!localeptr) + elog(ERROR, "invalid LC_COLLATE setting"); + + if (strcmp(localeptr, "C") == 0) + result = true; + else if (strcmp(localeptr, "POSIX") == 0) + result = true; + else + result = false; + return (bool) result; + } + + /* + * If we're asked about the built-in C/POSIX collations, we know that. + */ + if (collation == C_COLLATION_OID || + collation == POSIX_COLLATION_OID) + return true; + + /* + * Otherwise, we have to consult pg_collation, but we cache that. + */ + return (lookup_collation_cache(collation, true))->collate_is_c; +} + +/* + * Detect whether collation's LC_CTYPE property is C + */ +bool +lc_ctype_is_c(Oid collation) +{ + /* + * If we're asked about "collation 0", return false, so that the code will + * go into the non-C path and report that the collation is bogus. + */ + if (!OidIsValid(collation)) + return false; + + /* + * If we're asked about the default collation, we have to inquire of the C + * library. Cache the result so we only have to compute it once. + */ + if (collation == DEFAULT_COLLATION_OID) + { + static __thread int result = -1; + char *localeptr; + + if (default_locale.provider == COLLPROVIDER_ICU) + return false; + + if (result >= 0) + return (bool) result; + localeptr = setlocale(LC_CTYPE, NULL); + if (!localeptr) + elog(ERROR, "invalid LC_CTYPE setting"); + + if (strcmp(localeptr, "C") == 0) + result = true; + else if (strcmp(localeptr, "POSIX") == 0) + result = true; + else + result = false; + return (bool) result; + } + + /* + * If we're asked about the built-in C/POSIX collations, we know that. + */ + if (collation == C_COLLATION_OID || + collation == POSIX_COLLATION_OID) + return true; + + /* + * Otherwise, we have to consult pg_collation, but we cache that. + */ + return (lookup_collation_cache(collation, true))->ctype_is_c; +} + +__thread struct pg_locale_struct default_locale; + +void +make_icu_collator(const char *iculocstr, + const char *icurules, + struct pg_locale_struct *resultp) +{ +#ifdef USE_ICU + UCollator *collator; + + collator = pg_ucol_open(iculocstr); + + /* + * If rules are specified, we extract the rules of the standard collation, + * add our own rules, and make a new collator with the combined rules. + */ + if (icurules) + { + const UChar *default_rules; + UChar *agg_rules; + UChar *my_rules; + UErrorCode status; + int32_t length; + + default_rules = ucol_getRules(collator, &length); + icu_to_uchar(&my_rules, icurules, strlen(icurules)); + + agg_rules = palloc_array(UChar, u_strlen(default_rules) + u_strlen(my_rules) + 1); + u_strcpy(agg_rules, default_rules); + u_strcat(agg_rules, my_rules); + + ucol_close(collator); + + status = U_ZERO_ERROR; + collator = ucol_openRules(agg_rules, u_strlen(agg_rules), + UCOL_DEFAULT, UCOL_DEFAULT_STRENGTH, NULL, &status); + if (U_FAILURE(status)) + ereport(ERROR, + (errmsg("could not open collator for locale \"%s\" with rules \"%s\": %s", + iculocstr, icurules, u_errorName(status)))); + } + + /* We will leak this string if the caller errors later :-( */ + resultp->info.icu.locale = MemoryContextStrdup(TopMemoryContext, iculocstr); + resultp->info.icu.ucol = collator; +#else /* not USE_ICU */ + /* could get here if a collation was created by a build with ICU */ + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("ICU is not supported in this build"))); +#endif /* not USE_ICU */ +} + + +/* simple subroutine for reporting errors from newlocale() */ +#ifdef HAVE_LOCALE_T +static void +report_newlocale_failure(const char *localename) +{ + int save_errno; + + /* + * Windows doesn't provide any useful error indication from + * _create_locale(), and BSD-derived platforms don't seem to feel they + * need to set errno either (even though POSIX is pretty clear that + * newlocale should do so). So, if errno hasn't been set, assume ENOENT + * is what to report. + */ + if (errno == 0) + errno = ENOENT; + + /* + * ENOENT means "no such locale", not "no such file", so clarify that + * errno with an errdetail message. + */ + save_errno = errno; /* auxiliary funcs might change errno */ + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("could not create locale \"%s\": %m", + localename), + (save_errno == ENOENT ? + errdetail("The operating system could not find any locale data for the locale name \"%s\".", + localename) : 0))); +} +#endif /* HAVE_LOCALE_T */ + +bool +pg_locale_deterministic(pg_locale_t locale) +{ + /* default locale must always be deterministic */ + if (locale == NULL) + return true; + else + return locale->deterministic; +} + +/* + * Create a locale_t from a collation OID. Results are cached for the + * lifetime of the backend. Thus, do not free the result with freelocale(). + * + * As a special optimization, the default/database collation returns 0. + * Callers should then revert to the non-locale_t-enabled code path. + * Also, callers should avoid calling this before going down a C/POSIX + * fastpath, because such a fastpath should work even on platforms without + * locale_t support in the C library. + * + * For simplicity, we always generate COLLATE + CTYPE even though we + * might only need one of them. Since this is called only once per session, + * it shouldn't cost much. + */ +pg_locale_t +pg_newlocale_from_collation(Oid collid) +{ + collation_cache_entry *cache_entry; + + /* Callers must pass a valid OID */ + Assert(OidIsValid(collid)); + + if (collid == DEFAULT_COLLATION_OID) + { + if (default_locale.provider == COLLPROVIDER_ICU) + return &default_locale; + else + return (pg_locale_t) 0; + } + + cache_entry = lookup_collation_cache(collid, false); + + if (cache_entry->locale == 0) + { + /* We haven't computed this yet in this session, so do it */ + HeapTuple tp; + Form_pg_collation collform; + struct pg_locale_struct result; + pg_locale_t resultp; + Datum datum; + bool isnull; + + tp = SearchSysCache1(COLLOID, ObjectIdGetDatum(collid)); + if (!HeapTupleIsValid(tp)) + elog(ERROR, "cache lookup failed for collation %u", collid); + collform = (Form_pg_collation) GETSTRUCT(tp); + + /* We'll fill in the result struct locally before allocating memory */ + memset(&result, 0, sizeof(result)); + result.provider = collform->collprovider; + result.deterministic = collform->collisdeterministic; + + if (collform->collprovider == COLLPROVIDER_LIBC) + { +#ifdef HAVE_LOCALE_T + const char *collcollate; + const char *collctype pg_attribute_unused(); + locale_t loc; + + datum = SysCacheGetAttrNotNull(COLLOID, tp, Anum_pg_collation_collcollate); + collcollate = TextDatumGetCString(datum); + datum = SysCacheGetAttrNotNull(COLLOID, tp, Anum_pg_collation_collctype); + collctype = TextDatumGetCString(datum); + + if (strcmp(collcollate, collctype) == 0) + { + /* Normal case where they're the same */ + errno = 0; +#ifndef WIN32 + loc = newlocale(LC_COLLATE_MASK | LC_CTYPE_MASK, collcollate, + NULL); +#else + loc = _create_locale(LC_ALL, collcollate); +#endif + if (!loc) + report_newlocale_failure(collcollate); + } + else + { +#ifndef WIN32 + /* We need two newlocale() steps */ + locale_t loc1; + + errno = 0; + loc1 = newlocale(LC_COLLATE_MASK, collcollate, NULL); + if (!loc1) + report_newlocale_failure(collcollate); + errno = 0; + loc = newlocale(LC_CTYPE_MASK, collctype, loc1); + if (!loc) + report_newlocale_failure(collctype); +#else + + /* + * XXX The _create_locale() API doesn't appear to support + * this. Could perhaps be worked around by changing + * pg_locale_t to contain two separate fields. + */ + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("collations with different collate and ctype values are not supported on this platform"))); +#endif + } + + result.info.lt = loc; +#else /* not HAVE_LOCALE_T */ + /* platform that doesn't support locale_t */ + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("collation provider LIBC is not supported on this platform"))); +#endif /* not HAVE_LOCALE_T */ + } + else if (collform->collprovider == COLLPROVIDER_ICU) + { + const char *iculocstr; + const char *icurules; + + datum = SysCacheGetAttrNotNull(COLLOID, tp, Anum_pg_collation_colliculocale); + iculocstr = TextDatumGetCString(datum); + + datum = SysCacheGetAttr(COLLOID, tp, Anum_pg_collation_collicurules, &isnull); + if (!isnull) + icurules = TextDatumGetCString(datum); + else + icurules = NULL; + + make_icu_collator(iculocstr, icurules, &result); + } + + datum = SysCacheGetAttr(COLLOID, tp, Anum_pg_collation_collversion, + &isnull); + if (!isnull) + { + char *actual_versionstr; + char *collversionstr; + + collversionstr = TextDatumGetCString(datum); + + datum = SysCacheGetAttrNotNull(COLLOID, tp, collform->collprovider == COLLPROVIDER_ICU ? Anum_pg_collation_colliculocale : Anum_pg_collation_collcollate); + + actual_versionstr = get_collation_actual_version(collform->collprovider, + TextDatumGetCString(datum)); + if (!actual_versionstr) + { + /* + * This could happen when specifying a version in CREATE + * COLLATION but the provider does not support versioning, or + * manually creating a mess in the catalogs. + */ + ereport(ERROR, + (errmsg("collation \"%s\" has no actual version, but a version was recorded", + NameStr(collform->collname)))); + } + + if (strcmp(actual_versionstr, collversionstr) != 0) + ereport(WARNING, + (errmsg("collation \"%s\" has version mismatch", + NameStr(collform->collname)), + errdetail("The collation in the database was created using version %s, " + "but the operating system provides version %s.", + collversionstr, actual_versionstr), + errhint("Rebuild all objects affected by this collation and run " + "ALTER COLLATION %s REFRESH VERSION, " + "or build PostgreSQL with the right library version.", + quote_qualified_identifier(get_namespace_name(collform->collnamespace), + NameStr(collform->collname))))); + } + + ReleaseSysCache(tp); + + /* We'll keep the pg_locale_t structures in TopMemoryContext */ + resultp = MemoryContextAlloc(TopMemoryContext, sizeof(*resultp)); + *resultp = result; + + cache_entry->locale = resultp; + } + + return cache_entry->locale; +} + +/* + * Get provider-specific collation version string for the given collation from + * the operating system/library. + */ +char * +get_collation_actual_version(char collprovider, const char *collcollate) +{ + char *collversion = NULL; + +#ifdef USE_ICU + if (collprovider == COLLPROVIDER_ICU) + { + UCollator *collator; + UVersionInfo versioninfo; + char buf[U_MAX_VERSION_STRING_LENGTH]; + + collator = pg_ucol_open(collcollate); + + ucol_getVersion(collator, versioninfo); + ucol_close(collator); + + u_versionToString(versioninfo, buf); + collversion = pstrdup(buf); + } + else +#endif + if (collprovider == COLLPROVIDER_LIBC && + pg_strcasecmp("C", collcollate) != 0 && + pg_strncasecmp("C.", collcollate, 2) != 0 && + pg_strcasecmp("POSIX", collcollate) != 0) + { +#if defined(__GLIBC__) + /* Use the glibc version because we don't have anything better. */ + collversion = pstrdup(gnu_get_libc_version()); +#elif defined(LC_VERSION_MASK) + locale_t loc; + + /* Look up FreeBSD collation version. */ + loc = newlocale(LC_COLLATE, collcollate, NULL); + if (loc) + { + collversion = + pstrdup(querylocale(LC_COLLATE_MASK | LC_VERSION_MASK, loc)); + freelocale(loc); + } + else + ereport(ERROR, + (errmsg("could not load locale \"%s\"", collcollate))); +#elif defined(WIN32) + /* + * If we are targeting Windows Vista and above, we can ask for a name + * given a collation name (earlier versions required a location code + * that we don't have). + */ + NLSVERSIONINFOEX version = {sizeof(NLSVERSIONINFOEX)}; + WCHAR wide_collcollate[LOCALE_NAME_MAX_LENGTH]; + + MultiByteToWideChar(CP_ACP, 0, collcollate, -1, wide_collcollate, + LOCALE_NAME_MAX_LENGTH); + if (!GetNLSVersionEx(COMPARE_STRING, wide_collcollate, &version)) + { + /* + * GetNLSVersionEx() wants a language tag such as "en-US", not a + * locale name like "English_United States.1252". Until those + * values can be prevented from entering the system, or 100% + * reliably converted to the more useful tag format, tolerate the + * resulting error and report that we have no version data. + */ + if (GetLastError() == ERROR_INVALID_PARAMETER) + return NULL; + + ereport(ERROR, + (errmsg("could not get collation version for locale \"%s\": error code %lu", + collcollate, + GetLastError()))); + } + collversion = psprintf("%lu.%lu,%lu.%lu", + (version.dwNLSVersion >> 8) & 0xFFFF, + version.dwNLSVersion & 0xFF, + (version.dwDefinedVersion >> 8) & 0xFFFF, + version.dwDefinedVersion & 0xFF); +#endif + } + + return collversion; +} + +/* + * pg_strncoll_libc_win32_utf8 + * + * Win32 does not have UTF-8. Convert UTF8 arguments to wide characters and + * invoke wcscoll() or wcscoll_l(). + */ +#ifdef WIN32 +static int +pg_strncoll_libc_win32_utf8(const char *arg1, size_t len1, const char *arg2, + size_t len2, pg_locale_t locale) +{ + char sbuf[TEXTBUFLEN]; + char *buf = sbuf; + char *a1p, + *a2p; + int a1len = len1 * 2 + 2; + int a2len = len2 * 2 + 2; + int r; + int result; + + Assert(!locale || locale->provider == COLLPROVIDER_LIBC); + Assert(GetDatabaseEncoding() == PG_UTF8); +#ifndef WIN32 + Assert(false); +#endif + + if (a1len + a2len > TEXTBUFLEN) + buf = palloc(a1len + a2len); + + a1p = buf; + a2p = buf + a1len; + + /* API does not work for zero-length input */ + if (len1 == 0) + r = 0; + else + { + r = MultiByteToWideChar(CP_UTF8, 0, arg1, len1, + (LPWSTR) a1p, a1len / 2); + if (!r) + ereport(ERROR, + (errmsg("could not convert string to UTF-16: error code %lu", + GetLastError()))); + } + ((LPWSTR) a1p)[r] = 0; + + if (len2 == 0) + r = 0; + else + { + r = MultiByteToWideChar(CP_UTF8, 0, arg2, len2, + (LPWSTR) a2p, a2len / 2); + if (!r) + ereport(ERROR, + (errmsg("could not convert string to UTF-16: error code %lu", + GetLastError()))); + } + ((LPWSTR) a2p)[r] = 0; + + errno = 0; +#ifdef HAVE_LOCALE_T + if (locale) + result = wcscoll_l((LPWSTR) a1p, (LPWSTR) a2p, locale->info.lt); + else +#endif + result = wcscoll((LPWSTR) a1p, (LPWSTR) a2p); + if (result == 2147483647) /* _NLSCMPERROR; missing from mingw headers */ + ereport(ERROR, + (errmsg("could not compare Unicode strings: %m"))); + + if (buf != sbuf) + pfree(buf); + + return result; +} +#endif /* WIN32 */ + +/* + * pg_strcoll_libc + * + * Call strcoll(), strcoll_l(), wcscoll(), or wcscoll_l() as appropriate for + * the given locale, platform, and database encoding. If the locale is NULL, + * use the database collation. + * + * Arguments must be encoded in the database encoding and nul-terminated. + */ +static int +pg_strcoll_libc(const char *arg1, const char *arg2, pg_locale_t locale) +{ + int result; + + Assert(!locale || locale->provider == COLLPROVIDER_LIBC); +#ifdef WIN32 + if (GetDatabaseEncoding() == PG_UTF8) + { + size_t len1 = strlen(arg1); + size_t len2 = strlen(arg2); + + result = pg_strncoll_libc_win32_utf8(arg1, len1, arg2, len2, locale); + } + else +#endif /* WIN32 */ + if (locale) + { +#ifdef HAVE_LOCALE_T + result = strcoll_l(arg1, arg2, locale->info.lt); +#else + /* shouldn't happen */ + elog(ERROR, "unsupported collprovider: %c", locale->provider); +#endif + } + else + result = strcoll(arg1, arg2); + + return result; +} + +/* + * pg_strncoll_libc + * + * Nul-terminate the arguments and call pg_strcoll_libc(). + */ +static int +pg_strncoll_libc(const char *arg1, size_t len1, const char *arg2, size_t len2, + pg_locale_t locale) +{ + char sbuf[TEXTBUFLEN]; + char *buf = sbuf; + size_t bufsize1 = len1 + 1; + size_t bufsize2 = len2 + 1; + char *arg1n; + char *arg2n; + int result; + + Assert(!locale || locale->provider == COLLPROVIDER_LIBC); + +#ifdef WIN32 + /* check for this case before doing the work for nul-termination */ + if (GetDatabaseEncoding() == PG_UTF8) + return pg_strncoll_libc_win32_utf8(arg1, len1, arg2, len2, locale); +#endif /* WIN32 */ + + if (bufsize1 + bufsize2 > TEXTBUFLEN) + buf = palloc(bufsize1 + bufsize2); + + arg1n = buf; + arg2n = buf + bufsize1; + + /* nul-terminate arguments */ + memcpy(arg1n, arg1, len1); + arg1n[len1] = '\0'; + memcpy(arg2n, arg2, len2); + arg2n[len2] = '\0'; + + result = pg_strcoll_libc(arg1n, arg2n, locale); + + if (buf != sbuf) + pfree(buf); + + return result; +} + +#ifdef USE_ICU + +/* + * pg_strncoll_icu_no_utf8 + * + * Convert the arguments from the database encoding to UChar strings, then + * call ucol_strcoll(). An argument length of -1 means that the string is + * NUL-terminated. + * + * When the database encoding is UTF-8, and ICU supports ucol_strcollUTF8(), + * caller should call that instead. + */ +static int +pg_strncoll_icu_no_utf8(const char *arg1, int32_t len1, + const char *arg2, int32_t len2, pg_locale_t locale) +{ + char sbuf[TEXTBUFLEN]; + char *buf = sbuf; + int32_t ulen1; + int32_t ulen2; + size_t bufsize1; + size_t bufsize2; + UChar *uchar1, + *uchar2; + int result; + + Assert(locale->provider == COLLPROVIDER_ICU); +#ifdef HAVE_UCOL_STRCOLLUTF8 + Assert(GetDatabaseEncoding() != PG_UTF8); +#endif + + init_icu_converter(); + + ulen1 = uchar_length(icu_converter, arg1, len1); + ulen2 = uchar_length(icu_converter, arg2, len2); + + bufsize1 = (ulen1 + 1) * sizeof(UChar); + bufsize2 = (ulen2 + 1) * sizeof(UChar); + + if (bufsize1 + bufsize2 > TEXTBUFLEN) + buf = palloc(bufsize1 + bufsize2); + + uchar1 = (UChar *) buf; + uchar2 = (UChar *) (buf + bufsize1); + + ulen1 = uchar_convert(icu_converter, uchar1, ulen1 + 1, arg1, len1); + ulen2 = uchar_convert(icu_converter, uchar2, ulen2 + 1, arg2, len2); + + result = ucol_strcoll(locale->info.icu.ucol, + uchar1, ulen1, + uchar2, ulen2); + + if (buf != sbuf) + pfree(buf); + + return result; +} + +/* + * pg_strncoll_icu + * + * Call ucol_strcollUTF8() or ucol_strcoll() as appropriate for the given + * database encoding. An argument length of -1 means the string is + * NUL-terminated. + * + * Arguments must be encoded in the database encoding. + */ +static int +pg_strncoll_icu(const char *arg1, int32_t len1, const char *arg2, int32_t len2, + pg_locale_t locale) +{ + int result; + + Assert(locale->provider == COLLPROVIDER_ICU); + +#ifdef HAVE_UCOL_STRCOLLUTF8 + if (GetDatabaseEncoding() == PG_UTF8) + { + UErrorCode status; + + status = U_ZERO_ERROR; + result = ucol_strcollUTF8(locale->info.icu.ucol, + arg1, len1, + arg2, len2, + &status); + if (U_FAILURE(status)) + ereport(ERROR, + (errmsg("collation failed: %s", u_errorName(status)))); + } + else +#endif + { + result = pg_strncoll_icu_no_utf8(arg1, len1, arg2, len2, locale); + } + + return result; +} + +#endif /* USE_ICU */ + +/* + * pg_strcoll + * + * Call ucol_strcollUTF8(), ucol_strcoll(), strcoll(), strcoll_l(), wcscoll(), + * or wcscoll_l() as appropriate for the given locale, platform, and database + * encoding. If the locale is not specified, use the database collation. + * + * Arguments must be encoded in the database encoding and nul-terminated. + * + * The caller is responsible for breaking ties if the collation is + * deterministic; this maintains consistency with pg_strxfrm(), which cannot + * easily account for deterministic collations. + */ +int +pg_strcoll(const char *arg1, const char *arg2, pg_locale_t locale) +{ + int result; + + if (!locale || locale->provider == COLLPROVIDER_LIBC) + result = pg_strcoll_libc(arg1, arg2, locale); +#ifdef USE_ICU + else if (locale->provider == COLLPROVIDER_ICU) + result = pg_strncoll_icu(arg1, -1, arg2, -1, locale); +#endif + else + /* shouldn't happen */ + PGLOCALE_SUPPORT_ERROR(locale->provider); + + return result; +} + +/* + * pg_strncoll + * + * Call ucol_strcollUTF8(), ucol_strcoll(), strcoll(), strcoll_l(), wcscoll(), + * or wcscoll_l() as appropriate for the given locale, platform, and database + * encoding. If the locale is not specified, use the database collation. + * + * Arguments must be encoded in the database encoding. + * + * This function may need to nul-terminate the arguments for libc functions; + * so if the caller already has nul-terminated strings, it should call + * pg_strcoll() instead. + * + * The caller is responsible for breaking ties if the collation is + * deterministic; this maintains consistency with pg_strnxfrm(), which cannot + * easily account for deterministic collations. + */ +int +pg_strncoll(const char *arg1, size_t len1, const char *arg2, size_t len2, + pg_locale_t locale) +{ + int result; + + if (!locale || locale->provider == COLLPROVIDER_LIBC) + result = pg_strncoll_libc(arg1, len1, arg2, len2, locale); +#ifdef USE_ICU + else if (locale->provider == COLLPROVIDER_ICU) + result = pg_strncoll_icu(arg1, len1, arg2, len2, locale); +#endif + else + /* shouldn't happen */ + PGLOCALE_SUPPORT_ERROR(locale->provider); + + return result; +} + + +static size_t +pg_strxfrm_libc(char *dest, const char *src, size_t destsize, + pg_locale_t locale) +{ + Assert(!locale || locale->provider == COLLPROVIDER_LIBC); + +#ifdef TRUST_STRXFRM +#ifdef HAVE_LOCALE_T + if (locale) + return strxfrm_l(dest, src, destsize, locale->info.lt); + else +#endif + return strxfrm(dest, src, destsize); +#else + /* shouldn't happen */ + PGLOCALE_SUPPORT_ERROR(locale->provider); + return 0; /* keep compiler quiet */ +#endif +} + +static size_t +pg_strnxfrm_libc(char *dest, const char *src, size_t srclen, size_t destsize, + pg_locale_t locale) +{ + char sbuf[TEXTBUFLEN]; + char *buf = sbuf; + size_t bufsize = srclen + 1; + size_t result; + + Assert(!locale || locale->provider == COLLPROVIDER_LIBC); + + if (bufsize > TEXTBUFLEN) + buf = palloc(bufsize); + + /* nul-terminate arguments */ + memcpy(buf, src, srclen); + buf[srclen] = '\0'; + + result = pg_strxfrm_libc(dest, buf, destsize, locale); + + if (buf != sbuf) + pfree(buf); + + /* if dest is defined, it should be nul-terminated */ + Assert(result >= destsize || dest[result] == '\0'); + + return result; +} + +#ifdef USE_ICU + +/* 'srclen' of -1 means the strings are NUL-terminated */ +static size_t +pg_strnxfrm_icu(char *dest, const char *src, int32_t srclen, int32_t destsize, + pg_locale_t locale) +{ + char sbuf[TEXTBUFLEN]; + char *buf = sbuf; + UChar *uchar; + int32_t ulen; + size_t uchar_bsize; + Size result_bsize; + + Assert(locale->provider == COLLPROVIDER_ICU); + + init_icu_converter(); + + ulen = uchar_length(icu_converter, src, srclen); + + uchar_bsize = (ulen + 1) * sizeof(UChar); + + if (uchar_bsize > TEXTBUFLEN) + buf = palloc(uchar_bsize); + + uchar = (UChar *) buf; + + ulen = uchar_convert(icu_converter, uchar, ulen + 1, src, srclen); + + result_bsize = ucol_getSortKey(locale->info.icu.ucol, + uchar, ulen, + (uint8_t *) dest, destsize); + + /* + * ucol_getSortKey() counts the nul-terminator in the result length, but + * this function should not. + */ + Assert(result_bsize > 0); + result_bsize--; + + if (buf != sbuf) + pfree(buf); + + /* if dest is defined, it should be nul-terminated */ + Assert(result_bsize >= destsize || dest[result_bsize] == '\0'); + + return result_bsize; +} + +/* 'srclen' of -1 means the strings are NUL-terminated */ +static size_t +pg_strnxfrm_prefix_icu_no_utf8(char *dest, const char *src, int32_t srclen, + int32_t destsize, pg_locale_t locale) +{ + char sbuf[TEXTBUFLEN]; + char *buf = sbuf; + UCharIterator iter; + uint32_t state[2]; + UErrorCode status; + int32_t ulen = -1; + UChar *uchar = NULL; + size_t uchar_bsize; + Size result_bsize; + + Assert(locale->provider == COLLPROVIDER_ICU); + Assert(GetDatabaseEncoding() != PG_UTF8); + + init_icu_converter(); + + ulen = uchar_length(icu_converter, src, srclen); + + uchar_bsize = (ulen + 1) * sizeof(UChar); + + if (uchar_bsize > TEXTBUFLEN) + buf = palloc(uchar_bsize); + + uchar = (UChar *) buf; + + ulen = uchar_convert(icu_converter, uchar, ulen + 1, src, srclen); + + uiter_setString(&iter, uchar, ulen); + state[0] = state[1] = 0; /* won't need that again */ + status = U_ZERO_ERROR; + result_bsize = ucol_nextSortKeyPart(locale->info.icu.ucol, + &iter, + state, + (uint8_t *) dest, + destsize, + &status); + if (U_FAILURE(status)) + ereport(ERROR, + (errmsg("sort key generation failed: %s", + u_errorName(status)))); + + return result_bsize; +} + +/* 'srclen' of -1 means the strings are NUL-terminated */ +static size_t +pg_strnxfrm_prefix_icu(char *dest, const char *src, int32_t srclen, + int32_t destsize, pg_locale_t locale) +{ + size_t result; + + Assert(locale->provider == COLLPROVIDER_ICU); + + if (GetDatabaseEncoding() == PG_UTF8) + { + UCharIterator iter; + uint32_t state[2]; + UErrorCode status; + + uiter_setUTF8(&iter, src, srclen); + state[0] = state[1] = 0; /* won't need that again */ + status = U_ZERO_ERROR; + result = ucol_nextSortKeyPart(locale->info.icu.ucol, + &iter, + state, + (uint8_t *) dest, + destsize, + &status); + if (U_FAILURE(status)) + ereport(ERROR, + (errmsg("sort key generation failed: %s", + u_errorName(status)))); + } + else + result = pg_strnxfrm_prefix_icu_no_utf8(dest, src, srclen, destsize, + locale); + + return result; +} + +#endif + +/* + * Return true if the collation provider supports pg_strxfrm() and + * pg_strnxfrm(); otherwise false. + * + * Unfortunately, it seems that strxfrm() for non-C collations is broken on + * many common platforms; testing of multiple versions of glibc reveals that, + * for many locales, strcoll() and strxfrm() do not return consistent + * results. While no other libc other than Cygwin has so far been shown to + * have a problem, we take the conservative course of action for right now and + * disable this categorically. (Users who are certain this isn't a problem on + * their system can define TRUST_STRXFRM.) + * + * No similar problem is known for the ICU provider. + */ +bool +pg_strxfrm_enabled(pg_locale_t locale) +{ + if (!locale || locale->provider == COLLPROVIDER_LIBC) +#ifdef TRUST_STRXFRM + return true; +#else + return false; +#endif + else if (locale->provider == COLLPROVIDER_ICU) + return true; + else + /* shouldn't happen */ + PGLOCALE_SUPPORT_ERROR(locale->provider); + + return false; /* keep compiler quiet */ +} + +/* + * pg_strxfrm + * + * Transforms 'src' to a nul-terminated string stored in 'dest' such that + * ordinary strcmp() on transformed strings is equivalent to pg_strcoll() on + * untransformed strings. + * + * The provided 'src' must be nul-terminated. If 'destsize' is zero, 'dest' + * may be NULL. + * + * Returns the number of bytes needed to store the transformed string, + * excluding the terminating nul byte. If the value returned is 'destsize' or + * greater, the resulting contents of 'dest' are undefined. + */ +size_t +pg_strxfrm(char *dest, const char *src, size_t destsize, pg_locale_t locale) +{ + size_t result = 0; /* keep compiler quiet */ + + if (!locale || locale->provider == COLLPROVIDER_LIBC) + result = pg_strxfrm_libc(dest, src, destsize, locale); +#ifdef USE_ICU + else if (locale->provider == COLLPROVIDER_ICU) + result = pg_strnxfrm_icu(dest, src, -1, destsize, locale); +#endif + else + /* shouldn't happen */ + PGLOCALE_SUPPORT_ERROR(locale->provider); + + return result; +} + +/* + * pg_strnxfrm + * + * Transforms 'src' to a nul-terminated string stored in 'dest' such that + * ordinary strcmp() on transformed strings is equivalent to pg_strcoll() on + * untransformed strings. + * + * 'src' does not need to be nul-terminated. If 'destsize' is zero, 'dest' may + * be NULL. + * + * Returns the number of bytes needed to store the transformed string, + * excluding the terminating nul byte. If the value returned is 'destsize' or + * greater, the resulting contents of 'dest' are undefined. + * + * This function may need to nul-terminate the argument for libc functions; + * so if the caller already has a nul-terminated string, it should call + * pg_strxfrm() instead. + */ +size_t +pg_strnxfrm(char *dest, size_t destsize, const char *src, size_t srclen, + pg_locale_t locale) +{ + size_t result = 0; /* keep compiler quiet */ + + if (!locale || locale->provider == COLLPROVIDER_LIBC) + result = pg_strnxfrm_libc(dest, src, srclen, destsize, locale); +#ifdef USE_ICU + else if (locale->provider == COLLPROVIDER_ICU) + result = pg_strnxfrm_icu(dest, src, srclen, destsize, locale); +#endif + else + /* shouldn't happen */ + PGLOCALE_SUPPORT_ERROR(locale->provider); + + return result; +} + +/* + * Return true if the collation provider supports pg_strxfrm_prefix() and + * pg_strnxfrm_prefix(); otherwise false. + */ +bool +pg_strxfrm_prefix_enabled(pg_locale_t locale) +{ + if (!locale || locale->provider == COLLPROVIDER_LIBC) + return false; + else if (locale->provider == COLLPROVIDER_ICU) + return true; + else + /* shouldn't happen */ + PGLOCALE_SUPPORT_ERROR(locale->provider); + + return false; /* keep compiler quiet */ +} + +/* + * pg_strxfrm_prefix + * + * Transforms 'src' to a byte sequence stored in 'dest' such that ordinary + * memcmp() on the byte sequence is equivalent to pg_strcoll() on + * untransformed strings. The result is not nul-terminated. + * + * The provided 'src' must be nul-terminated. + * + * If destsize is not large enough to hold the resulting byte sequence, stores + * only the first destsize bytes in 'dest'. Returns the number of bytes + * actually copied to 'dest'. + */ +size_t +pg_strxfrm_prefix(char *dest, const char *src, size_t destsize, + pg_locale_t locale) +{ + size_t result = 0; /* keep compiler quiet */ + + if (!locale) + PGLOCALE_SUPPORT_ERROR(COLLPROVIDER_LIBC); +#ifdef USE_ICU + else if (locale->provider == COLLPROVIDER_ICU) + result = pg_strnxfrm_prefix_icu(dest, src, -1, destsize, locale); +#endif + else + PGLOCALE_SUPPORT_ERROR(locale->provider); + + return result; +} + +/* + * pg_strnxfrm_prefix + * + * Transforms 'src' to a byte sequence stored in 'dest' such that ordinary + * memcmp() on the byte sequence is equivalent to pg_strcoll() on + * untransformed strings. The result is not nul-terminated. + * + * The provided 'src' must be nul-terminated. + * + * If destsize is not large enough to hold the resulting byte sequence, stores + * only the first destsize bytes in 'dest'. Returns the number of bytes + * actually copied to 'dest'. + * + * This function may need to nul-terminate the argument for libc functions; + * so if the caller already has a nul-terminated string, it should call + * pg_strxfrm_prefix() instead. + */ +size_t +pg_strnxfrm_prefix(char *dest, size_t destsize, const char *src, + size_t srclen, pg_locale_t locale) +{ + size_t result = 0; /* keep compiler quiet */ + + if (!locale) + PGLOCALE_SUPPORT_ERROR(COLLPROVIDER_LIBC); +#ifdef USE_ICU + else if (locale->provider == COLLPROVIDER_ICU) + result = pg_strnxfrm_prefix_icu(dest, src, -1, destsize, locale); +#endif + else + PGLOCALE_SUPPORT_ERROR(locale->provider); + + return result; +} + +#ifdef USE_ICU + +/* + * Wrapper around ucol_open() to handle API differences for older ICU + * versions. + */ +static UCollator * +pg_ucol_open(const char *loc_str) +{ + UCollator *collator; + UErrorCode status; + const char *orig_str = loc_str; + char *fixed_str = NULL; + + /* + * Must never open default collator, because it depends on the environment + * and may change at any time. Should not happen, but check here to catch + * bugs that might be hard to catch otherwise. + * + * NB: the default collator is not the same as the collator for the root + * locale. The root locale may be specified as the empty string, "und", or + * "root". The default collator is opened by passing NULL to ucol_open(). + */ + if (loc_str == NULL) + elog(ERROR, "opening default collator is not supported"); + + /* + * In ICU versions 54 and earlier, "und" is not a recognized spelling of + * the root locale. If the first component of the locale is "und", replace + * with "root" before opening. + */ + if (U_ICU_VERSION_MAJOR_NUM < 55) + { + char lang[ULOC_LANG_CAPACITY]; + + status = U_ZERO_ERROR; + uloc_getLanguage(loc_str, lang, ULOC_LANG_CAPACITY, &status); + if (U_FAILURE(status) || status == U_STRING_NOT_TERMINATED_WARNING) + { + ereport(ERROR, + (errmsg("could not get language from locale \"%s\": %s", + loc_str, u_errorName(status)))); + } + + if (strcmp(lang, "und") == 0) + { + const char *remainder = loc_str + strlen("und"); + + fixed_str = palloc(strlen("root") + strlen(remainder) + 1); + strcpy(fixed_str, "root"); + strcat(fixed_str, remainder); + + loc_str = fixed_str; + } + } + + status = U_ZERO_ERROR; + collator = ucol_open(loc_str, &status); + if (U_FAILURE(status)) + ereport(ERROR, + /* use original string for error report */ + (errmsg("could not open collator for locale \"%s\": %s", + orig_str, u_errorName(status)))); + + if (U_ICU_VERSION_MAJOR_NUM < 54) + { + status = U_ZERO_ERROR; + icu_set_collation_attributes(collator, loc_str, &status); + + /* + * Pretend the error came from ucol_open(), for consistent error + * message across ICU versions. + */ + if (U_FAILURE(status) || status == U_STRING_NOT_TERMINATED_WARNING) + { + ucol_close(collator); + ereport(ERROR, + (errmsg("could not open collator for locale \"%s\": %s", + orig_str, u_errorName(status)))); + } + } + + if (fixed_str != NULL) + pfree(fixed_str); + + return collator; +} + +static void +init_icu_converter(void) +{ + const char *icu_encoding_name; + UErrorCode status; + UConverter *conv; + + if (icu_converter) + return; /* already done */ + + icu_encoding_name = get_encoding_name_for_icu(GetDatabaseEncoding()); + if (!icu_encoding_name) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("encoding \"%s\" not supported by ICU", + pg_encoding_to_char(GetDatabaseEncoding())))); + + status = U_ZERO_ERROR; + conv = ucnv_open(icu_encoding_name, &status); + if (U_FAILURE(status)) + ereport(ERROR, + (errmsg("could not open ICU converter for encoding \"%s\": %s", + icu_encoding_name, u_errorName(status)))); + + icu_converter = conv; +} + +/* + * Find length, in UChars, of given string if converted to UChar string. + */ +static size_t +uchar_length(UConverter *converter, const char *str, int32_t len) +{ + UErrorCode status = U_ZERO_ERROR; + int32_t ulen; + + ulen = ucnv_toUChars(converter, NULL, 0, str, len, &status); + if (U_FAILURE(status) && status != U_BUFFER_OVERFLOW_ERROR) + ereport(ERROR, + (errmsg("%s failed: %s", "ucnv_toUChars", u_errorName(status)))); + return ulen; +} + +/* + * Convert the given source string into a UChar string, stored in dest, and + * return the length (in UChars). + */ +static int32_t +uchar_convert(UConverter *converter, UChar *dest, int32_t destlen, + const char *src, int32_t srclen) +{ + UErrorCode status = U_ZERO_ERROR; + int32_t ulen; + + status = U_ZERO_ERROR; + ulen = ucnv_toUChars(converter, dest, destlen, src, srclen, &status); + if (U_FAILURE(status)) + ereport(ERROR, + (errmsg("%s failed: %s", "ucnv_toUChars", u_errorName(status)))); + return ulen; +} + +/* + * Convert a string in the database encoding into a string of UChars. + * + * The source string at buff is of length nbytes + * (it needn't be nul-terminated) + * + * *buff_uchar receives a pointer to the palloc'd result string, and + * the function's result is the number of UChars generated. + * + * The result string is nul-terminated, though most callers rely on the + * result length instead. + */ +int32_t +icu_to_uchar(UChar **buff_uchar, const char *buff, size_t nbytes) +{ + int32_t len_uchar; + + init_icu_converter(); + + len_uchar = uchar_length(icu_converter, buff, nbytes); + + *buff_uchar = palloc((len_uchar + 1) * sizeof(**buff_uchar)); + len_uchar = uchar_convert(icu_converter, + *buff_uchar, len_uchar + 1, buff, nbytes); + + return len_uchar; +} + +/* + * Convert a string of UChars into the database encoding. + * + * The source string at buff_uchar is of length len_uchar + * (it needn't be nul-terminated) + * + * *result receives a pointer to the palloc'd result string, and the + * function's result is the number of bytes generated (not counting nul). + * + * The result string is nul-terminated. + */ +int32_t +icu_from_uchar(char **result, const UChar *buff_uchar, int32_t len_uchar) +{ + UErrorCode status; + int32_t len_result; + + init_icu_converter(); + + status = U_ZERO_ERROR; + len_result = ucnv_fromUChars(icu_converter, NULL, 0, + buff_uchar, len_uchar, &status); + if (U_FAILURE(status) && status != U_BUFFER_OVERFLOW_ERROR) + ereport(ERROR, + (errmsg("%s failed: %s", "ucnv_fromUChars", + u_errorName(status)))); + + *result = palloc(len_result + 1); + + status = U_ZERO_ERROR; + len_result = ucnv_fromUChars(icu_converter, *result, len_result + 1, + buff_uchar, len_uchar, &status); + if (U_FAILURE(status) || + status == U_STRING_NOT_TERMINATED_WARNING) + ereport(ERROR, + (errmsg("%s failed: %s", "ucnv_fromUChars", + u_errorName(status)))); + + return len_result; +} + +/* + * Parse collation attributes from the given locale string and apply them to + * the open collator. + * + * First, the locale string is canonicalized to an ICU format locale ID such + * as "und@colStrength=primary;colCaseLevel=yes". Then, it parses and applies + * the key-value arguments. + * + * Starting with ICU version 54, the attributes are processed automatically by + * ucol_open(), so this is only necessary for emulating this behavior on older + * versions. + */ +pg_attribute_unused() +static void +icu_set_collation_attributes(UCollator *collator, const char *loc, + UErrorCode *status) +{ + int32_t len; + char *icu_locale_id; + char *lower_str; + char *str; + + /* + * The input locale may be a BCP 47 language tag, e.g. + * "und-u-kc-ks-level1", which expresses the same attributes in a + * different form. It will be converted to the equivalent ICU format + * locale ID, e.g. "und@colcaselevel=yes;colstrength=primary", by + * uloc_canonicalize(). + */ + *status = U_ZERO_ERROR; + len = uloc_canonicalize(loc, NULL, 0, status); + icu_locale_id = palloc(len + 1); + *status = U_ZERO_ERROR; + len = uloc_canonicalize(loc, icu_locale_id, len + 1, status); + if (U_FAILURE(*status) || *status == U_STRING_NOT_TERMINATED_WARNING) + return; + + lower_str = asc_tolower(icu_locale_id, strlen(icu_locale_id)); + + pfree(icu_locale_id); + + str = strchr(lower_str, '@'); + if (!str) + return; + str++; + + for (char *token = strtok(str, ";"); token; token = strtok(NULL, ";")) + { + char *e = strchr(token, '='); + + if (e) + { + char *name; + char *value; + UColAttribute uattr; + UColAttributeValue uvalue; + + *status = U_ZERO_ERROR; + + *e = '\0'; + name = token; + value = e + 1; + + /* + * See attribute name and value lists in ICU i18n/coll.cpp + */ + if (strcmp(name, "colstrength") == 0) + uattr = UCOL_STRENGTH; + else if (strcmp(name, "colbackwards") == 0) + uattr = UCOL_FRENCH_COLLATION; + else if (strcmp(name, "colcaselevel") == 0) + uattr = UCOL_CASE_LEVEL; + else if (strcmp(name, "colcasefirst") == 0) + uattr = UCOL_CASE_FIRST; + else if (strcmp(name, "colalternate") == 0) + uattr = UCOL_ALTERNATE_HANDLING; + else if (strcmp(name, "colnormalization") == 0) + uattr = UCOL_NORMALIZATION_MODE; + else if (strcmp(name, "colnumeric") == 0) + uattr = UCOL_NUMERIC_COLLATION; + else + /* ignore if unknown */ + continue; + + if (strcmp(value, "primary") == 0) + uvalue = UCOL_PRIMARY; + else if (strcmp(value, "secondary") == 0) + uvalue = UCOL_SECONDARY; + else if (strcmp(value, "tertiary") == 0) + uvalue = UCOL_TERTIARY; + else if (strcmp(value, "quaternary") == 0) + uvalue = UCOL_QUATERNARY; + else if (strcmp(value, "identical") == 0) + uvalue = UCOL_IDENTICAL; + else if (strcmp(value, "no") == 0) + uvalue = UCOL_OFF; + else if (strcmp(value, "yes") == 0) + uvalue = UCOL_ON; + else if (strcmp(value, "shifted") == 0) + uvalue = UCOL_SHIFTED; + else if (strcmp(value, "non-ignorable") == 0) + uvalue = UCOL_NON_IGNORABLE; + else if (strcmp(value, "lower") == 0) + uvalue = UCOL_LOWER_FIRST; + else if (strcmp(value, "upper") == 0) + uvalue = UCOL_UPPER_FIRST; + else + { + *status = U_ILLEGAL_ARGUMENT_ERROR; + break; + } + + ucol_setAttribute(collator, uattr, uvalue, status); + } + } + + pfree(lower_str); +} +#endif + +/* + * Return the BCP47 language tag representation of the requested locale. + * + * This function should be called before passing the string to ucol_open(), + * because conversion to a language tag also performs "level 2 + * canonicalization". In addition to producing a consistent format, level 2 + * canonicalization is able to more accurately interpret different input + * locale string formats, such as POSIX and .NET IDs. + */ +char * +icu_language_tag(const char *loc_str, int elevel) +{ +#ifdef USE_ICU + UErrorCode status; + char *langtag; + size_t buflen = 32; /* arbitrary starting buffer size */ + const bool strict = true; + + /* + * A BCP47 language tag doesn't have a clearly-defined upper limit (cf. + * RFC5646 section 4.4). Additionally, in older ICU versions, + * uloc_toLanguageTag() doesn't always return the ultimate length on the + * first call, necessitating a loop. + */ + langtag = palloc(buflen); + while (true) + { + status = U_ZERO_ERROR; + uloc_toLanguageTag(loc_str, langtag, buflen, strict, &status); + + /* try again if the buffer is not large enough */ + if ((status == U_BUFFER_OVERFLOW_ERROR || + status == U_STRING_NOT_TERMINATED_WARNING) && + buflen < MaxAllocSize) + { + buflen = Min(buflen * 2, MaxAllocSize); + langtag = repalloc(langtag, buflen); + continue; + } + + break; + } + + if (U_FAILURE(status)) + { + pfree(langtag); + + if (elevel > 0) + ereport(elevel, + (errmsg("could not convert locale name \"%s\" to language tag: %s", + loc_str, u_errorName(status)))); + return NULL; + } + + return langtag; +#else /* not USE_ICU */ + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("ICU is not supported in this build"))); + return NULL; /* keep compiler quiet */ +#endif /* not USE_ICU */ +} + +/* + * Perform best-effort check that the locale is a valid one. + */ +void +icu_validate_locale(const char *loc_str) +{ +#ifdef USE_ICU + UCollator *collator; + UErrorCode status; + char lang[ULOC_LANG_CAPACITY]; + bool found = false; + int elevel = icu_validation_level; + + /* no validation */ + if (elevel < 0) + return; + + /* downgrade to WARNING during pg_upgrade */ + if (IsBinaryUpgrade && elevel > WARNING) + elevel = WARNING; + + /* validate that we can extract the language */ + status = U_ZERO_ERROR; + uloc_getLanguage(loc_str, lang, ULOC_LANG_CAPACITY, &status); + if (U_FAILURE(status) || status == U_STRING_NOT_TERMINATED_WARNING) + { + ereport(elevel, + (errmsg("could not get language from ICU locale \"%s\": %s", + loc_str, u_errorName(status)), + errhint("To disable ICU locale validation, set the parameter \"%s\" to \"%s\".", + "icu_validation_level", "disabled"))); + return; + } + + /* check for special language name */ + if (strcmp(lang, "") == 0 || + strcmp(lang, "root") == 0 || strcmp(lang, "und") == 0) + found = true; + + /* search for matching language within ICU */ + for (int32_t i = 0; !found && i < uloc_countAvailable(); i++) + { + const char *otherloc = uloc_getAvailable(i); + char otherlang[ULOC_LANG_CAPACITY]; + + status = U_ZERO_ERROR; + uloc_getLanguage(otherloc, otherlang, ULOC_LANG_CAPACITY, &status); + if (U_FAILURE(status) || status == U_STRING_NOT_TERMINATED_WARNING) + continue; + + if (strcmp(lang, otherlang) == 0) + found = true; + } + + if (!found) + ereport(elevel, + (errmsg("ICU locale \"%s\" has unknown language \"%s\"", + loc_str, lang), + errhint("To disable ICU locale validation, set the parameter \"%s\" to \"%s\".", + "icu_validation_level", "disabled"))); + + /* check that it can be opened */ + collator = pg_ucol_open(loc_str); + ucol_close(collator); +#else /* not USE_ICU */ + /* could get here if a collation was created by a build with ICU */ + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("ICU is not supported in this build"))); +#endif /* not USE_ICU */ +} + +/* + * These functions convert from/to libc's wchar_t, *not* pg_wchar_t. + * Therefore we keep them here rather than with the mbutils code. + */ + +/* + * wchar2char --- convert wide characters to multibyte format + * + * This has the same API as the standard wcstombs_l() function; in particular, + * tolen is the maximum number of bytes to store at *to, and *from must be + * zero-terminated. The output will be zero-terminated iff there is room. + */ +size_t +wchar2char(char *to, const wchar_t *from, size_t tolen, pg_locale_t locale) +{ + size_t result; + + Assert(!locale || locale->provider == COLLPROVIDER_LIBC); + + if (tolen == 0) + return 0; + +#ifdef WIN32 + + /* + * On Windows, the "Unicode" locales assume UTF16 not UTF8 encoding, and + * for some reason mbstowcs and wcstombs won't do this for us, so we use + * MultiByteToWideChar(). + */ + if (GetDatabaseEncoding() == PG_UTF8) + { + result = WideCharToMultiByte(CP_UTF8, 0, from, -1, to, tolen, + NULL, NULL); + /* A zero return is failure */ + if (result <= 0) + result = -1; + else + { + Assert(result <= tolen); + /* Microsoft counts the zero terminator in the result */ + result--; + } + } + else +#endif /* WIN32 */ + if (locale == (pg_locale_t) 0) + { + /* Use wcstombs directly for the default locale */ + result = wcstombs(to, from, tolen); + } + else + { +#ifdef HAVE_LOCALE_T +#ifdef HAVE_WCSTOMBS_L + /* Use wcstombs_l for nondefault locales */ + result = wcstombs_l(to, from, tolen, locale->info.lt); +#else /* !HAVE_WCSTOMBS_L */ + /* We have to temporarily set the locale as current ... ugh */ + locale_t save_locale = uselocale(locale->info.lt); + + result = wcstombs(to, from, tolen); + + uselocale(save_locale); +#endif /* HAVE_WCSTOMBS_L */ +#else /* !HAVE_LOCALE_T */ + /* Can't have locale != 0 without HAVE_LOCALE_T */ + elog(ERROR, "wcstombs_l is not available"); + result = 0; /* keep compiler quiet */ +#endif /* HAVE_LOCALE_T */ + } + + return result; +} + +/* + * char2wchar --- convert multibyte characters to wide characters + * + * This has almost the API of mbstowcs_l(), except that *from need not be + * null-terminated; instead, the number of input bytes is specified as + * fromlen. Also, we ereport() rather than returning -1 for invalid + * input encoding. tolen is the maximum number of wchar_t's to store at *to. + * The output will be zero-terminated iff there is room. + */ +size_t +char2wchar(wchar_t *to, size_t tolen, const char *from, size_t fromlen, + pg_locale_t locale) +{ + size_t result; + + Assert(!locale || locale->provider == COLLPROVIDER_LIBC); + + if (tolen == 0) + return 0; + +#ifdef WIN32 + /* See WIN32 "Unicode" comment above */ + if (GetDatabaseEncoding() == PG_UTF8) + { + /* Win32 API does not work for zero-length input */ + if (fromlen == 0) + result = 0; + else + { + result = MultiByteToWideChar(CP_UTF8, 0, from, fromlen, to, tolen - 1); + /* A zero return is failure */ + if (result == 0) + result = -1; + } + + if (result != -1) + { + Assert(result < tolen); + /* Append trailing null wchar (MultiByteToWideChar() does not) */ + to[result] = 0; + } + } + else +#endif /* WIN32 */ + { + /* mbstowcs requires ending '\0' */ + char *str = pnstrdup(from, fromlen); + + if (locale == (pg_locale_t) 0) + { + /* Use mbstowcs directly for the default locale */ + result = mbstowcs(to, str, tolen); + } + else + { +#ifdef HAVE_LOCALE_T +#ifdef HAVE_MBSTOWCS_L + /* Use mbstowcs_l for nondefault locales */ + result = mbstowcs_l(to, str, tolen, locale->info.lt); +#else /* !HAVE_MBSTOWCS_L */ + /* We have to temporarily set the locale as current ... ugh */ + locale_t save_locale = uselocale(locale->info.lt); + + result = mbstowcs(to, str, tolen); + + uselocale(save_locale); +#endif /* HAVE_MBSTOWCS_L */ +#else /* !HAVE_LOCALE_T */ + /* Can't have locale != 0 without HAVE_LOCALE_T */ + elog(ERROR, "mbstowcs_l is not available"); + result = 0; /* keep compiler quiet */ +#endif /* HAVE_LOCALE_T */ + } + + pfree(str); + } + + if (result == -1) + { + /* + * Invalid multibyte character encountered. We try to give a useful + * error message by letting pg_verifymbstr check the string. But it's + * possible that the string is OK to us, and not OK to mbstowcs --- + * this suggests that the LC_CTYPE locale is different from the + * database encoding. Give a generic error message if pg_verifymbstr + * can't find anything wrong. + */ + pg_verifymbstr(from, fromlen, false); /* might not return */ + /* but if it does ... */ + ereport(ERROR, + (errcode(ERRCODE_CHARACTER_NOT_IN_REPERTOIRE), + errmsg("invalid multibyte character for locale"), + errhint("The server's LC_CTYPE locale is probably incompatible with the database encoding."))); + } + + return result; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/pg_lsn.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/pg_lsn.c new file mode 100644 index 00000000000..613c3722b94 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/pg_lsn.c @@ -0,0 +1,313 @@ +/*------------------------------------------------------------------------- + * + * pg_lsn.c + * Operations for the pg_lsn datatype. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/utils/adt/pg_lsn.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "funcapi.h" +#include "libpq/pqformat.h" +#include "utils/builtins.h" +#include "utils/numeric.h" +#include "utils/pg_lsn.h" + +#define MAXPG_LSNLEN 17 +#define MAXPG_LSNCOMPONENT 8 + +/*---------------------------------------------------------- + * Formatting and conversion routines. + *---------------------------------------------------------*/ + +XLogRecPtr +pg_lsn_in_internal(const char *str, bool *have_error) +{ + int len1, + len2; + uint32 id, + off; + XLogRecPtr result; + + Assert(have_error != NULL); + *have_error = false; + + /* Sanity check input format. */ + len1 = strspn(str, "0123456789abcdefABCDEF"); + if (len1 < 1 || len1 > MAXPG_LSNCOMPONENT || str[len1] != '/') + { + *have_error = true; + return InvalidXLogRecPtr; + } + len2 = strspn(str + len1 + 1, "0123456789abcdefABCDEF"); + if (len2 < 1 || len2 > MAXPG_LSNCOMPONENT || str[len1 + 1 + len2] != '\0') + { + *have_error = true; + return InvalidXLogRecPtr; + } + + /* Decode result. */ + id = (uint32) strtoul(str, NULL, 16); + off = (uint32) strtoul(str + len1 + 1, NULL, 16); + result = ((uint64) id << 32) | off; + + return result; +} + +Datum +pg_lsn_in(PG_FUNCTION_ARGS) +{ + char *str = PG_GETARG_CSTRING(0); + XLogRecPtr result; + bool have_error = false; + + result = pg_lsn_in_internal(str, &have_error); + if (have_error) + ereturn(fcinfo->context, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s: \"%s\"", + "pg_lsn", str))); + + PG_RETURN_LSN(result); +} + +Datum +pg_lsn_out(PG_FUNCTION_ARGS) +{ + XLogRecPtr lsn = PG_GETARG_LSN(0); + char buf[MAXPG_LSNLEN + 1]; + char *result; + + snprintf(buf, sizeof buf, "%X/%X", LSN_FORMAT_ARGS(lsn)); + result = pstrdup(buf); + PG_RETURN_CSTRING(result); +} + +Datum +pg_lsn_recv(PG_FUNCTION_ARGS) +{ + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + XLogRecPtr result; + + result = pq_getmsgint64(buf); + PG_RETURN_LSN(result); +} + +Datum +pg_lsn_send(PG_FUNCTION_ARGS) +{ + XLogRecPtr lsn = PG_GETARG_LSN(0); + StringInfoData buf; + + pq_begintypsend(&buf); + pq_sendint64(&buf, lsn); + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} + + +/*---------------------------------------------------------- + * Operators for PostgreSQL LSNs + *---------------------------------------------------------*/ + +Datum +pg_lsn_eq(PG_FUNCTION_ARGS) +{ + XLogRecPtr lsn1 = PG_GETARG_LSN(0); + XLogRecPtr lsn2 = PG_GETARG_LSN(1); + + PG_RETURN_BOOL(lsn1 == lsn2); +} + +Datum +pg_lsn_ne(PG_FUNCTION_ARGS) +{ + XLogRecPtr lsn1 = PG_GETARG_LSN(0); + XLogRecPtr lsn2 = PG_GETARG_LSN(1); + + PG_RETURN_BOOL(lsn1 != lsn2); +} + +Datum +pg_lsn_lt(PG_FUNCTION_ARGS) +{ + XLogRecPtr lsn1 = PG_GETARG_LSN(0); + XLogRecPtr lsn2 = PG_GETARG_LSN(1); + + PG_RETURN_BOOL(lsn1 < lsn2); +} + +Datum +pg_lsn_gt(PG_FUNCTION_ARGS) +{ + XLogRecPtr lsn1 = PG_GETARG_LSN(0); + XLogRecPtr lsn2 = PG_GETARG_LSN(1); + + PG_RETURN_BOOL(lsn1 > lsn2); +} + +Datum +pg_lsn_le(PG_FUNCTION_ARGS) +{ + XLogRecPtr lsn1 = PG_GETARG_LSN(0); + XLogRecPtr lsn2 = PG_GETARG_LSN(1); + + PG_RETURN_BOOL(lsn1 <= lsn2); +} + +Datum +pg_lsn_ge(PG_FUNCTION_ARGS) +{ + XLogRecPtr lsn1 = PG_GETARG_LSN(0); + XLogRecPtr lsn2 = PG_GETARG_LSN(1); + + PG_RETURN_BOOL(lsn1 >= lsn2); +} + +Datum +pg_lsn_larger(PG_FUNCTION_ARGS) +{ + XLogRecPtr lsn1 = PG_GETARG_LSN(0); + XLogRecPtr lsn2 = PG_GETARG_LSN(1); + + PG_RETURN_LSN((lsn1 > lsn2) ? lsn1 : lsn2); +} + +Datum +pg_lsn_smaller(PG_FUNCTION_ARGS) +{ + XLogRecPtr lsn1 = PG_GETARG_LSN(0); + XLogRecPtr lsn2 = PG_GETARG_LSN(1); + + PG_RETURN_LSN((lsn1 < lsn2) ? lsn1 : lsn2); +} + +/* btree index opclass support */ +Datum +pg_lsn_cmp(PG_FUNCTION_ARGS) +{ + XLogRecPtr a = PG_GETARG_LSN(0); + XLogRecPtr b = PG_GETARG_LSN(1); + + if (a > b) + PG_RETURN_INT32(1); + else if (a == b) + PG_RETURN_INT32(0); + else + PG_RETURN_INT32(-1); +} + +/* hash index opclass support */ +Datum +pg_lsn_hash(PG_FUNCTION_ARGS) +{ + /* We can use hashint8 directly */ + return hashint8(fcinfo); +} + +Datum +pg_lsn_hash_extended(PG_FUNCTION_ARGS) +{ + return hashint8extended(fcinfo); +} + + +/*---------------------------------------------------------- + * Arithmetic operators on PostgreSQL LSNs. + *---------------------------------------------------------*/ + +Datum +pg_lsn_mi(PG_FUNCTION_ARGS) +{ + XLogRecPtr lsn1 = PG_GETARG_LSN(0); + XLogRecPtr lsn2 = PG_GETARG_LSN(1); + char buf[256]; + Datum result; + + /* Output could be as large as plus or minus 2^63 - 1. */ + if (lsn1 < lsn2) + snprintf(buf, sizeof buf, "-" UINT64_FORMAT, lsn2 - lsn1); + else + snprintf(buf, sizeof buf, UINT64_FORMAT, lsn1 - lsn2); + + /* Convert to numeric. */ + result = DirectFunctionCall3(numeric_in, + CStringGetDatum(buf), + ObjectIdGetDatum(0), + Int32GetDatum(-1)); + + return result; +} + +/* + * Add the number of bytes to pg_lsn, giving a new pg_lsn. + * Must handle both positive and negative numbers of bytes. + */ +Datum +pg_lsn_pli(PG_FUNCTION_ARGS) +{ + XLogRecPtr lsn = PG_GETARG_LSN(0); + Numeric nbytes = PG_GETARG_NUMERIC(1); + Datum num; + Datum res; + char buf[32]; + + if (numeric_is_nan(nbytes)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot add NaN to pg_lsn"))); + + /* Convert to numeric */ + snprintf(buf, sizeof(buf), UINT64_FORMAT, lsn); + num = DirectFunctionCall3(numeric_in, + CStringGetDatum(buf), + ObjectIdGetDatum(0), + Int32GetDatum(-1)); + + /* Add two numerics */ + res = DirectFunctionCall2(numeric_add, + num, + NumericGetDatum(nbytes)); + + /* Convert to pg_lsn */ + return DirectFunctionCall1(numeric_pg_lsn, res); +} + +/* + * Subtract the number of bytes from pg_lsn, giving a new pg_lsn. + * Must handle both positive and negative numbers of bytes. + */ +Datum +pg_lsn_mii(PG_FUNCTION_ARGS) +{ + XLogRecPtr lsn = PG_GETARG_LSN(0); + Numeric nbytes = PG_GETARG_NUMERIC(1); + Datum num; + Datum res; + char buf[32]; + + if (numeric_is_nan(nbytes)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot subtract NaN from pg_lsn"))); + + /* Convert to numeric */ + snprintf(buf, sizeof(buf), UINT64_FORMAT, lsn); + num = DirectFunctionCall3(numeric_in, + CStringGetDatum(buf), + ObjectIdGetDatum(0), + Int32GetDatum(-1)); + + /* Subtract two numerics */ + res = DirectFunctionCall2(numeric_sub, + num, + NumericGetDatum(nbytes)); + + /* Convert to pg_lsn */ + return DirectFunctionCall1(numeric_pg_lsn, res); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/pg_upgrade_support.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/pg_upgrade_support.c new file mode 100644 index 00000000000..0186636d9f8 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/pg_upgrade_support.c @@ -0,0 +1,263 @@ +/* + * pg_upgrade_support.c + * + * server-side functions to set backend global variables + * to control oid and relfilenumber assignment, and do other special + * hacks needed for pg_upgrade. + * + * Copyright (c) 2010-2023, PostgreSQL Global Development Group + * src/backend/utils/adt/pg_upgrade_support.c + */ + +#include "postgres.h" + +#include "catalog/binary_upgrade.h" +#include "catalog/heap.h" +#include "catalog/namespace.h" +#include "catalog/pg_type.h" +#include "commands/extension.h" +#include "miscadmin.h" +#include "utils/array.h" +#include "utils/builtins.h" + + +#define CHECK_IS_BINARY_UPGRADE \ +do { \ + if (!IsBinaryUpgrade) \ + ereport(ERROR, \ + (errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM), \ + errmsg("function can only be called when server is in binary upgrade mode"))); \ +} while (0) + +Datum +binary_upgrade_set_next_pg_tablespace_oid(PG_FUNCTION_ARGS) +{ + Oid tbspoid = PG_GETARG_OID(0); + + CHECK_IS_BINARY_UPGRADE; + binary_upgrade_next_pg_tablespace_oid = tbspoid; + + PG_RETURN_VOID(); +} + +Datum +binary_upgrade_set_next_pg_type_oid(PG_FUNCTION_ARGS) +{ + Oid typoid = PG_GETARG_OID(0); + + CHECK_IS_BINARY_UPGRADE; + binary_upgrade_next_pg_type_oid = typoid; + + PG_RETURN_VOID(); +} + +Datum +binary_upgrade_set_next_array_pg_type_oid(PG_FUNCTION_ARGS) +{ + Oid typoid = PG_GETARG_OID(0); + + CHECK_IS_BINARY_UPGRADE; + binary_upgrade_next_array_pg_type_oid = typoid; + + PG_RETURN_VOID(); +} + +Datum +binary_upgrade_set_next_multirange_pg_type_oid(PG_FUNCTION_ARGS) +{ + Oid typoid = PG_GETARG_OID(0); + + CHECK_IS_BINARY_UPGRADE; + binary_upgrade_next_mrng_pg_type_oid = typoid; + + PG_RETURN_VOID(); +} + +Datum +binary_upgrade_set_next_multirange_array_pg_type_oid(PG_FUNCTION_ARGS) +{ + Oid typoid = PG_GETARG_OID(0); + + CHECK_IS_BINARY_UPGRADE; + binary_upgrade_next_mrng_array_pg_type_oid = typoid; + + PG_RETURN_VOID(); +} + +Datum +binary_upgrade_set_next_heap_pg_class_oid(PG_FUNCTION_ARGS) +{ + Oid reloid = PG_GETARG_OID(0); + + CHECK_IS_BINARY_UPGRADE; + binary_upgrade_next_heap_pg_class_oid = reloid; + + PG_RETURN_VOID(); +} + +Datum +binary_upgrade_set_next_heap_relfilenode(PG_FUNCTION_ARGS) +{ + RelFileNumber relfilenumber = PG_GETARG_OID(0); + + CHECK_IS_BINARY_UPGRADE; + binary_upgrade_next_heap_pg_class_relfilenumber = relfilenumber; + + PG_RETURN_VOID(); +} + +Datum +binary_upgrade_set_next_index_pg_class_oid(PG_FUNCTION_ARGS) +{ + Oid reloid = PG_GETARG_OID(0); + + CHECK_IS_BINARY_UPGRADE; + binary_upgrade_next_index_pg_class_oid = reloid; + + PG_RETURN_VOID(); +} + +Datum +binary_upgrade_set_next_index_relfilenode(PG_FUNCTION_ARGS) +{ + RelFileNumber relfilenumber = PG_GETARG_OID(0); + + CHECK_IS_BINARY_UPGRADE; + binary_upgrade_next_index_pg_class_relfilenumber = relfilenumber; + + PG_RETURN_VOID(); +} + +Datum +binary_upgrade_set_next_toast_pg_class_oid(PG_FUNCTION_ARGS) +{ + Oid reloid = PG_GETARG_OID(0); + + CHECK_IS_BINARY_UPGRADE; + binary_upgrade_next_toast_pg_class_oid = reloid; + + PG_RETURN_VOID(); +} + +Datum +binary_upgrade_set_next_toast_relfilenode(PG_FUNCTION_ARGS) +{ + RelFileNumber relfilenumber = PG_GETARG_OID(0); + + CHECK_IS_BINARY_UPGRADE; + binary_upgrade_next_toast_pg_class_relfilenumber = relfilenumber; + + PG_RETURN_VOID(); +} + +Datum +binary_upgrade_set_next_pg_enum_oid(PG_FUNCTION_ARGS) +{ + Oid enumoid = PG_GETARG_OID(0); + + CHECK_IS_BINARY_UPGRADE; + binary_upgrade_next_pg_enum_oid = enumoid; + + PG_RETURN_VOID(); +} + +Datum +binary_upgrade_set_next_pg_authid_oid(PG_FUNCTION_ARGS) +{ + Oid authoid = PG_GETARG_OID(0); + + CHECK_IS_BINARY_UPGRADE; + binary_upgrade_next_pg_authid_oid = authoid; + PG_RETURN_VOID(); +} + +Datum +binary_upgrade_create_empty_extension(PG_FUNCTION_ARGS) +{ + text *extName; + text *schemaName; + bool relocatable; + text *extVersion; + Datum extConfig; + Datum extCondition; + List *requiredExtensions; + + CHECK_IS_BINARY_UPGRADE; + + /* We must check these things before dereferencing the arguments */ + if (PG_ARGISNULL(0) || + PG_ARGISNULL(1) || + PG_ARGISNULL(2) || + PG_ARGISNULL(3)) + elog(ERROR, "null argument to binary_upgrade_create_empty_extension is not allowed"); + + extName = PG_GETARG_TEXT_PP(0); + schemaName = PG_GETARG_TEXT_PP(1); + relocatable = PG_GETARG_BOOL(2); + extVersion = PG_GETARG_TEXT_PP(3); + + if (PG_ARGISNULL(4)) + extConfig = PointerGetDatum(NULL); + else + extConfig = PG_GETARG_DATUM(4); + + if (PG_ARGISNULL(5)) + extCondition = PointerGetDatum(NULL); + else + extCondition = PG_GETARG_DATUM(5); + + requiredExtensions = NIL; + if (!PG_ARGISNULL(6)) + { + ArrayType *textArray = PG_GETARG_ARRAYTYPE_P(6); + Datum *textDatums; + int ndatums; + int i; + + deconstruct_array_builtin(textArray, TEXTOID, &textDatums, NULL, &ndatums); + for (i = 0; i < ndatums; i++) + { + char *extName = TextDatumGetCString(textDatums[i]); + Oid extOid = get_extension_oid(extName, false); + + requiredExtensions = lappend_oid(requiredExtensions, extOid); + } + } + + InsertExtensionTuple(text_to_cstring(extName), + GetUserId(), + get_namespace_oid(text_to_cstring(schemaName), false), + relocatable, + text_to_cstring(extVersion), + extConfig, + extCondition, + requiredExtensions); + + PG_RETURN_VOID(); +} + +Datum +binary_upgrade_set_record_init_privs(PG_FUNCTION_ARGS) +{ + bool record_init_privs = PG_GETARG_BOOL(0); + + CHECK_IS_BINARY_UPGRADE; + binary_upgrade_record_init_privs = record_init_privs; + + PG_RETURN_VOID(); +} + +Datum +binary_upgrade_set_missing_value(PG_FUNCTION_ARGS) +{ + Oid table_id = PG_GETARG_OID(0); + text *attname = PG_GETARG_TEXT_P(1); + text *value = PG_GETARG_TEXT_P(2); + char *cattname = text_to_cstring(attname); + char *cvalue = text_to_cstring(value); + + CHECK_IS_BINARY_UPGRADE; + SetAttrMissing(table_id, cattname, cvalue); + + PG_RETURN_VOID(); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/pgstatfuncs.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/pgstatfuncs.c new file mode 100644 index 00000000000..68ecd3bc66b --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/pgstatfuncs.c @@ -0,0 +1,2069 @@ +/*------------------------------------------------------------------------- + * + * pgstatfuncs.c + * Functions for accessing various forms of statistics data + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/adt/pgstatfuncs.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/htup_details.h" +#include "access/xlog.h" +#include "access/xlogprefetcher.h" +#include "catalog/catalog.h" +#include "catalog/pg_authid.h" +#include "catalog/pg_type.h" +#include "common/ip.h" +#include "funcapi.h" +#include "miscadmin.h" +#include "pgstat.h" +#include "postmaster/bgworker_internals.h" +#include "postmaster/postmaster.h" +#include "replication/logicallauncher.h" +#include "storage/proc.h" +#include "storage/procarray.h" +#include "utils/acl.h" +#include "utils/builtins.h" +#include "utils/inet.h" +#include "utils/timestamp.h" + +#define UINT32_ACCESS_ONCE(var) ((uint32)(*((volatile uint32 *)&(var)))) + +#define HAS_PGSTAT_PERMISSIONS(role) (has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS) || has_privs_of_role(GetUserId(), role)) + +#define PG_STAT_GET_RELENTRY_INT64(stat) \ +Datum \ +CppConcat(pg_stat_get_,stat)(PG_FUNCTION_ARGS) \ +{ \ + Oid relid = PG_GETARG_OID(0); \ + int64 result; \ + PgStat_StatTabEntry *tabentry; \ + \ + if ((tabentry = pgstat_fetch_stat_tabentry(relid)) == NULL) \ + result = 0; \ + else \ + result = (int64) (tabentry->stat); \ + \ + PG_RETURN_INT64(result); \ +} + +/* pg_stat_get_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(analyze_count) + +/* pg_stat_get_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(autoanalyze_count) + +/* pg_stat_get_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(autovacuum_count) + +/* pg_stat_get_blocks_fetched */ +PG_STAT_GET_RELENTRY_INT64(blocks_fetched) + +/* pg_stat_get_blocks_hit */ +PG_STAT_GET_RELENTRY_INT64(blocks_hit) + +/* pg_stat_get_dead_tuples */ +PG_STAT_GET_RELENTRY_INT64(dead_tuples) + +/* pg_stat_get_ins_since_vacuum */ +PG_STAT_GET_RELENTRY_INT64(ins_since_vacuum) + +/* pg_stat_get_live_tuples */ +PG_STAT_GET_RELENTRY_INT64(live_tuples) + +/* pg_stat_get_mod_since_analyze */ +PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) + +/* pg_stat_get_numscans */ +PG_STAT_GET_RELENTRY_INT64(numscans) + +/* pg_stat_get_tuples_deleted */ +PG_STAT_GET_RELENTRY_INT64(tuples_deleted) + +/* pg_stat_get_tuples_fetched */ +PG_STAT_GET_RELENTRY_INT64(tuples_fetched) + +/* pg_stat_get_tuples_hot_updated */ +PG_STAT_GET_RELENTRY_INT64(tuples_hot_updated) + +/* pg_stat_get_tuples_newpage_updated */ +PG_STAT_GET_RELENTRY_INT64(tuples_newpage_updated) + +/* pg_stat_get_tuples_inserted */ +PG_STAT_GET_RELENTRY_INT64(tuples_inserted) + +/* pg_stat_get_tuples_returned */ +PG_STAT_GET_RELENTRY_INT64(tuples_returned) + +/* pg_stat_get_tuples_updated */ +PG_STAT_GET_RELENTRY_INT64(tuples_updated) + +/* pg_stat_get_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(vacuum_count) + +#define PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat) \ +Datum \ +CppConcat(pg_stat_get_,stat)(PG_FUNCTION_ARGS) \ +{ \ + Oid relid = PG_GETARG_OID(0); \ + TimestampTz result; \ + PgStat_StatTabEntry *tabentry; \ + \ + if ((tabentry = pgstat_fetch_stat_tabentry(relid)) == NULL) \ + result = 0; \ + else \ + result = tabentry->stat; \ + \ + if (result == 0) \ + PG_RETURN_NULL(); \ + else \ + PG_RETURN_TIMESTAMPTZ(result); \ +} + +/* pg_stat_get_last_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_analyze_time) + +/* pg_stat_get_last_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_autoanalyze_time) + +/* pg_stat_get_last_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_autovacuum_time) + +/* pg_stat_get_last_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) + +/* pg_stat_get_lastscan */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) + +Datum +pg_stat_get_function_calls(PG_FUNCTION_ARGS) +{ + Oid funcid = PG_GETARG_OID(0); + PgStat_StatFuncEntry *funcentry; + + if ((funcentry = pgstat_fetch_stat_funcentry(funcid)) == NULL) + PG_RETURN_NULL(); + PG_RETURN_INT64(funcentry->numcalls); +} + +/* convert counter from microsec to millisec for display */ +#define PG_STAT_GET_FUNCENTRY_FLOAT8_MS(stat) \ +Datum \ +CppConcat(pg_stat_get_function_,stat)(PG_FUNCTION_ARGS) \ +{ \ + Oid funcid = PG_GETARG_OID(0); \ + double result; \ + PgStat_StatFuncEntry *funcentry; \ + \ + if ((funcentry = pgstat_fetch_stat_funcentry(funcid)) == NULL) \ + PG_RETURN_NULL(); \ + result = ((double) funcentry->stat) / 1000.0; \ + PG_RETURN_FLOAT8(result); \ +} + +/* pg_stat_get_function_total_time */ +PG_STAT_GET_FUNCENTRY_FLOAT8_MS(total_time) + +/* pg_stat_get_function_self_time */ +PG_STAT_GET_FUNCENTRY_FLOAT8_MS(self_time) + +Datum +pg_stat_get_backend_idset(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + int *fctx; + + /* stuff done only on the first call of the function */ + if (SRF_IS_FIRSTCALL()) + { + /* create a function context for cross-call persistence */ + funcctx = SRF_FIRSTCALL_INIT(); + + fctx = MemoryContextAlloc(funcctx->multi_call_memory_ctx, + sizeof(int)); + funcctx->user_fctx = fctx; + + fctx[0] = 0; + } + + /* stuff done on every call of the function */ + funcctx = SRF_PERCALL_SETUP(); + fctx = funcctx->user_fctx; + + fctx[0] += 1; + + /* + * We recheck pgstat_fetch_stat_numbackends() each time through, just in + * case the local status data has been refreshed since we started. It's + * plenty cheap enough if not. If a refresh does happen, we'll likely + * miss or duplicate some backend IDs, but we're content not to crash. + * (Refreshing midway through such a query would be problematic usage + * anyway, since the backend IDs we've already returned might no longer + * refer to extant sessions.) + */ + if (fctx[0] <= pgstat_fetch_stat_numbackends()) + { + /* do when there is more left to send */ + LocalPgBackendStatus *local_beentry = pgstat_get_local_beentry_by_index(fctx[0]); + + SRF_RETURN_NEXT(funcctx, Int32GetDatum(local_beentry->backend_id)); + } + else + { + /* do when there is no more left */ + SRF_RETURN_DONE(funcctx); + } +} + +/* + * Returns command progress information for the named command. + */ +Datum +pg_stat_get_progress_info(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_PROGRESS_COLS PGSTAT_NUM_PROGRESS_PARAM + 3 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + char *cmd = text_to_cstring(PG_GETARG_TEXT_PP(0)); + ProgressCommandType cmdtype; + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + /* Translate command name into command type code. */ + if (pg_strcasecmp(cmd, "VACUUM") == 0) + cmdtype = PROGRESS_COMMAND_VACUUM; + else if (pg_strcasecmp(cmd, "ANALYZE") == 0) + cmdtype = PROGRESS_COMMAND_ANALYZE; + else if (pg_strcasecmp(cmd, "CLUSTER") == 0) + cmdtype = PROGRESS_COMMAND_CLUSTER; + else if (pg_strcasecmp(cmd, "CREATE INDEX") == 0) + cmdtype = PROGRESS_COMMAND_CREATE_INDEX; + else if (pg_strcasecmp(cmd, "BASEBACKUP") == 0) + cmdtype = PROGRESS_COMMAND_BASEBACKUP; + else if (pg_strcasecmp(cmd, "COPY") == 0) + cmdtype = PROGRESS_COMMAND_COPY; + else + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid command name: \"%s\"", cmd))); + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + Datum values[PG_STAT_GET_PROGRESS_COLS] = {0}; + bool nulls[PG_STAT_GET_PROGRESS_COLS] = {0}; + int i; + + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* + * Report values for only those backends which are running the given + * command. + */ + if (beentry->st_progress_command != cmdtype) + continue; + + /* Value available to all callers */ + values[0] = Int32GetDatum(beentry->st_procpid); + values[1] = ObjectIdGetDatum(beentry->st_databaseid); + + /* show rest of the values including relid only to role members */ + if (HAS_PGSTAT_PERMISSIONS(beentry->st_userid)) + { + values[2] = ObjectIdGetDatum(beentry->st_progress_command_target); + for (i = 0; i < PGSTAT_NUM_PROGRESS_PARAM; i++) + values[i + 3] = Int64GetDatum(beentry->st_progress_param[i]); + } + else + { + nulls[2] = true; + for (i = 0; i < PGSTAT_NUM_PROGRESS_PARAM; i++) + nulls[i + 3] = true; + } + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + } + + return (Datum) 0; +} + +/* + * Returns activity of PG backends. + */ +Datum +pg_stat_get_activity(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_ACTIVITY_COLS 31 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_ACTIVITY_COLS] = {0}; + bool nulls[PG_STAT_GET_ACTIVITY_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PGPROC *proc; + const char *wait_event_type = NULL; + const char *wait_event = NULL; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + /* Values available to all callers */ + if (beentry->st_databaseid != InvalidOid) + values[0] = ObjectIdGetDatum(beentry->st_databaseid); + else + nulls[0] = true; + + values[1] = Int32GetDatum(beentry->st_procpid); + + if (beentry->st_userid != InvalidOid) + values[2] = ObjectIdGetDatum(beentry->st_userid); + else + nulls[2] = true; + + if (beentry->st_appname) + values[3] = CStringGetTextDatum(beentry->st_appname); + else + nulls[3] = true; + + if (TransactionIdIsValid(local_beentry->backend_xid)) + values[15] = TransactionIdGetDatum(local_beentry->backend_xid); + else + nulls[15] = true; + + if (TransactionIdIsValid(local_beentry->backend_xmin)) + values[16] = TransactionIdGetDatum(local_beentry->backend_xmin); + else + nulls[16] = true; + + /* Values only available to role member or pg_read_all_stats */ + if (HAS_PGSTAT_PERMISSIONS(beentry->st_userid)) + { + SockAddr zero_clientaddr; + char *clipped_activity; + + switch (beentry->st_state) + { + case STATE_IDLE: + values[4] = CStringGetTextDatum("idle"); + break; + case STATE_RUNNING: + values[4] = CStringGetTextDatum("active"); + break; + case STATE_IDLEINTRANSACTION: + values[4] = CStringGetTextDatum("idle in transaction"); + break; + case STATE_FASTPATH: + values[4] = CStringGetTextDatum("fastpath function call"); + break; + case STATE_IDLEINTRANSACTION_ABORTED: + values[4] = CStringGetTextDatum("idle in transaction (aborted)"); + break; + case STATE_DISABLED: + values[4] = CStringGetTextDatum("disabled"); + break; + case STATE_UNDEFINED: + nulls[4] = true; + break; + } + + clipped_activity = pgstat_clip_activity(beentry->st_activity_raw); + values[5] = CStringGetTextDatum(clipped_activity); + pfree(clipped_activity); + + /* leader_pid */ + nulls[29] = true; + + proc = BackendPidGetProc(beentry->st_procpid); + + if (proc == NULL && (beentry->st_backendType != B_BACKEND)) + { + /* + * For an auxiliary process, retrieve process info from + * AuxiliaryProcs stored in shared-memory. + */ + proc = AuxiliaryPidGetProc(beentry->st_procpid); + } + + /* + * If a PGPROC entry was retrieved, display wait events and lock + * group leader or apply leader information if any. To avoid + * extra overhead, no extra lock is being held, so there is no + * guarantee of consistency across multiple rows. + */ + if (proc != NULL) + { + uint32 raw_wait_event; + PGPROC *leader; + + raw_wait_event = UINT32_ACCESS_ONCE(proc->wait_event_info); + wait_event_type = pgstat_get_wait_event_type(raw_wait_event); + wait_event = pgstat_get_wait_event(raw_wait_event); + + leader = proc->lockGroupLeader; + + /* + * Show the leader only for active parallel workers. This + * leaves the field as NULL for the leader of a parallel group + * or the leader of parallel apply workers. + */ + if (leader && leader->pid != beentry->st_procpid) + { + values[29] = Int32GetDatum(leader->pid); + nulls[29] = false; + } + else if (beentry->st_backendType == B_BG_WORKER) + { + int leader_pid = GetLeaderApplyWorkerPid(beentry->st_procpid); + + if (leader_pid != InvalidPid) + { + values[29] = Int32GetDatum(leader_pid); + nulls[29] = false; + } + } + } + + if (wait_event_type) + values[6] = CStringGetTextDatum(wait_event_type); + else + nulls[6] = true; + + if (wait_event) + values[7] = CStringGetTextDatum(wait_event); + else + nulls[7] = true; + + /* + * Don't expose transaction time for walsenders; it confuses + * monitoring, particularly because we don't keep the time up-to- + * date. + */ + if (beentry->st_xact_start_timestamp != 0 && + beentry->st_backendType != B_WAL_SENDER) + values[8] = TimestampTzGetDatum(beentry->st_xact_start_timestamp); + else + nulls[8] = true; + + if (beentry->st_activity_start_timestamp != 0) + values[9] = TimestampTzGetDatum(beentry->st_activity_start_timestamp); + else + nulls[9] = true; + + if (beentry->st_proc_start_timestamp != 0) + values[10] = TimestampTzGetDatum(beentry->st_proc_start_timestamp); + else + nulls[10] = true; + + if (beentry->st_state_start_timestamp != 0) + values[11] = TimestampTzGetDatum(beentry->st_state_start_timestamp); + else + nulls[11] = true; + + /* A zeroed client addr means we don't know */ + memset(&zero_clientaddr, 0, sizeof(zero_clientaddr)); + if (memcmp(&(beentry->st_clientaddr), &zero_clientaddr, + sizeof(zero_clientaddr)) == 0) + { + nulls[12] = true; + nulls[13] = true; + nulls[14] = true; + } + else + { + if (beentry->st_clientaddr.addr.ss_family == AF_INET || + beentry->st_clientaddr.addr.ss_family == AF_INET6) + { + char remote_host[NI_MAXHOST]; + char remote_port[NI_MAXSERV]; + int ret; + + remote_host[0] = '\0'; + remote_port[0] = '\0'; + ret = pg_getnameinfo_all(&beentry->st_clientaddr.addr, + beentry->st_clientaddr.salen, + remote_host, sizeof(remote_host), + remote_port, sizeof(remote_port), + NI_NUMERICHOST | NI_NUMERICSERV); + if (ret == 0) + { + clean_ipv6_addr(beentry->st_clientaddr.addr.ss_family, remote_host); + values[12] = DirectFunctionCall1(inet_in, + CStringGetDatum(remote_host)); + if (beentry->st_clienthostname && + beentry->st_clienthostname[0]) + values[13] = CStringGetTextDatum(beentry->st_clienthostname); + else + nulls[13] = true; + values[14] = Int32GetDatum(atoi(remote_port)); + } + else + { + nulls[12] = true; + nulls[13] = true; + nulls[14] = true; + } + } + else if (beentry->st_clientaddr.addr.ss_family == AF_UNIX) + { + /* + * Unix sockets always reports NULL for host and -1 for + * port, so it's possible to tell the difference to + * connections we have no permissions to view, or with + * errors. + */ + nulls[12] = true; + nulls[13] = true; + values[14] = Int32GetDatum(-1); + } + else + { + /* Unknown address type, should never happen */ + nulls[12] = true; + nulls[13] = true; + nulls[14] = true; + } + } + /* Add backend type */ + if (beentry->st_backendType == B_BG_WORKER) + { + const char *bgw_type; + + bgw_type = GetBackgroundWorkerTypeByPid(beentry->st_procpid); + if (bgw_type) + values[17] = CStringGetTextDatum(bgw_type); + else + nulls[17] = true; + } + else + values[17] = + CStringGetTextDatum(GetBackendTypeDesc(beentry->st_backendType)); + + /* SSL information */ + if (beentry->st_ssl) + { + values[18] = BoolGetDatum(true); /* ssl */ + values[19] = CStringGetTextDatum(beentry->st_sslstatus->ssl_version); + values[20] = CStringGetTextDatum(beentry->st_sslstatus->ssl_cipher); + values[21] = Int32GetDatum(beentry->st_sslstatus->ssl_bits); + + if (beentry->st_sslstatus->ssl_client_dn[0]) + values[22] = CStringGetTextDatum(beentry->st_sslstatus->ssl_client_dn); + else + nulls[22] = true; + + if (beentry->st_sslstatus->ssl_client_serial[0]) + values[23] = DirectFunctionCall3(numeric_in, + CStringGetDatum(beentry->st_sslstatus->ssl_client_serial), + ObjectIdGetDatum(InvalidOid), + Int32GetDatum(-1)); + else + nulls[23] = true; + + if (beentry->st_sslstatus->ssl_issuer_dn[0]) + values[24] = CStringGetTextDatum(beentry->st_sslstatus->ssl_issuer_dn); + else + nulls[24] = true; + } + else + { + values[18] = BoolGetDatum(false); /* ssl */ + nulls[19] = nulls[20] = nulls[21] = nulls[22] = nulls[23] = nulls[24] = true; + } + + /* GSSAPI information */ + if (beentry->st_gss) + { + values[25] = BoolGetDatum(beentry->st_gssstatus->gss_auth); /* gss_auth */ + values[26] = CStringGetTextDatum(beentry->st_gssstatus->gss_princ); + values[27] = BoolGetDatum(beentry->st_gssstatus->gss_enc); /* GSS Encryption in use */ + values[28] = BoolGetDatum(beentry->st_gssstatus->gss_delegation); /* GSS credentials + * delegated */ + } + else + { + values[25] = BoolGetDatum(false); /* gss_auth */ + nulls[26] = true; /* No GSS principal */ + values[27] = BoolGetDatum(false); /* GSS Encryption not in + * use */ + values[28] = BoolGetDatum(false); /* GSS credentials not + * delegated */ + } + if (beentry->st_query_id == 0) + nulls[30] = true; + else + values[30] = UInt64GetDatum(beentry->st_query_id); + } + else + { + /* No permissions to view data about this session */ + values[5] = CStringGetTextDatum("<insufficient privilege>"); + nulls[4] = true; + nulls[6] = true; + nulls[7] = true; + nulls[8] = true; + nulls[9] = true; + nulls[10] = true; + nulls[11] = true; + nulls[12] = true; + nulls[13] = true; + nulls[14] = true; + nulls[17] = true; + nulls[18] = true; + nulls[19] = true; + nulls[20] = true; + nulls[21] = true; + nulls[22] = true; + nulls[23] = true; + nulls[24] = true; + nulls[25] = true; + nulls[26] = true; + nulls[27] = true; + nulls[28] = true; + nulls[29] = true; + nulls[30] = true; + } + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} + + +Datum +pg_backend_pid(PG_FUNCTION_ARGS) +{ + PG_RETURN_INT32(MyProcPid); +} + + +Datum +pg_stat_get_backend_pid(PG_FUNCTION_ARGS) +{ + int32 beid = PG_GETARG_INT32(0); + PgBackendStatus *beentry; + + if ((beentry = pgstat_get_beentry_by_backend_id(beid)) == NULL) + PG_RETURN_NULL(); + + PG_RETURN_INT32(beentry->st_procpid); +} + + +Datum +pg_stat_get_backend_dbid(PG_FUNCTION_ARGS) +{ + int32 beid = PG_GETARG_INT32(0); + PgBackendStatus *beentry; + + if ((beentry = pgstat_get_beentry_by_backend_id(beid)) == NULL) + PG_RETURN_NULL(); + + PG_RETURN_OID(beentry->st_databaseid); +} + + +Datum +pg_stat_get_backend_userid(PG_FUNCTION_ARGS) +{ + int32 beid = PG_GETARG_INT32(0); + PgBackendStatus *beentry; + + if ((beentry = pgstat_get_beentry_by_backend_id(beid)) == NULL) + PG_RETURN_NULL(); + + PG_RETURN_OID(beentry->st_userid); +} + +Datum +pg_stat_get_backend_subxact(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_SUBXACT_COLS 2 + TupleDesc tupdesc; + Datum values[PG_STAT_GET_SUBXACT_COLS]; + bool nulls[PG_STAT_GET_SUBXACT_COLS]; + int32 beid = PG_GETARG_INT32(0); + LocalPgBackendStatus *local_beentry; + + /* Initialise values and NULL flags arrays */ + MemSet(values, 0, sizeof(values)); + MemSet(nulls, 0, sizeof(nulls)); + + /* Initialise attributes information in the tuple descriptor */ + tupdesc = CreateTemplateTupleDesc(PG_STAT_GET_SUBXACT_COLS); + TupleDescInitEntry(tupdesc, (AttrNumber) 1, "subxact_count", + INT4OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 2, "subxact_overflow", + BOOLOID, -1, 0); + + BlessTupleDesc(tupdesc); + + if ((local_beentry = pgstat_get_local_beentry_by_backend_id(beid)) != NULL) + { + /* Fill values and NULLs */ + values[0] = Int32GetDatum(local_beentry->backend_subxact_count); + values[1] = BoolGetDatum(local_beentry->backend_subxact_overflowed); + } + else + { + nulls[0] = true; + nulls[1] = true; + } + + /* Returns the record as Datum */ + PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls))); +} + +Datum +pg_stat_get_backend_activity(PG_FUNCTION_ARGS) +{ + int32 beid = PG_GETARG_INT32(0); + PgBackendStatus *beentry; + const char *activity; + char *clipped_activity; + text *ret; + + if ((beentry = pgstat_get_beentry_by_backend_id(beid)) == NULL) + activity = "<backend information not available>"; + else if (!HAS_PGSTAT_PERMISSIONS(beentry->st_userid)) + activity = "<insufficient privilege>"; + else if (*(beentry->st_activity_raw) == '\0') + activity = "<command string not enabled>"; + else + activity = beentry->st_activity_raw; + + clipped_activity = pgstat_clip_activity(activity); + ret = cstring_to_text(activity); + pfree(clipped_activity); + + PG_RETURN_TEXT_P(ret); +} + +Datum +pg_stat_get_backend_wait_event_type(PG_FUNCTION_ARGS) +{ + int32 beid = PG_GETARG_INT32(0); + PgBackendStatus *beentry; + PGPROC *proc; + const char *wait_event_type = NULL; + + if ((beentry = pgstat_get_beentry_by_backend_id(beid)) == NULL) + wait_event_type = "<backend information not available>"; + else if (!HAS_PGSTAT_PERMISSIONS(beentry->st_userid)) + wait_event_type = "<insufficient privilege>"; + else if ((proc = BackendPidGetProc(beentry->st_procpid)) != NULL) + wait_event_type = pgstat_get_wait_event_type(proc->wait_event_info); + + if (!wait_event_type) + PG_RETURN_NULL(); + + PG_RETURN_TEXT_P(cstring_to_text(wait_event_type)); +} + +Datum +pg_stat_get_backend_wait_event(PG_FUNCTION_ARGS) +{ + int32 beid = PG_GETARG_INT32(0); + PgBackendStatus *beentry; + PGPROC *proc; + const char *wait_event = NULL; + + if ((beentry = pgstat_get_beentry_by_backend_id(beid)) == NULL) + wait_event = "<backend information not available>"; + else if (!HAS_PGSTAT_PERMISSIONS(beentry->st_userid)) + wait_event = "<insufficient privilege>"; + else if ((proc = BackendPidGetProc(beentry->st_procpid)) != NULL) + wait_event = pgstat_get_wait_event(proc->wait_event_info); + + if (!wait_event) + PG_RETURN_NULL(); + + PG_RETURN_TEXT_P(cstring_to_text(wait_event)); +} + + +Datum +pg_stat_get_backend_activity_start(PG_FUNCTION_ARGS) +{ + int32 beid = PG_GETARG_INT32(0); + TimestampTz result; + PgBackendStatus *beentry; + + if ((beentry = pgstat_get_beentry_by_backend_id(beid)) == NULL) + PG_RETURN_NULL(); + + else if (!HAS_PGSTAT_PERMISSIONS(beentry->st_userid)) + PG_RETURN_NULL(); + + result = beentry->st_activity_start_timestamp; + + /* + * No time recorded for start of current query -- this is the case if the + * user hasn't enabled query-level stats collection. + */ + if (result == 0) + PG_RETURN_NULL(); + + PG_RETURN_TIMESTAMPTZ(result); +} + + +Datum +pg_stat_get_backend_xact_start(PG_FUNCTION_ARGS) +{ + int32 beid = PG_GETARG_INT32(0); + TimestampTz result; + PgBackendStatus *beentry; + + if ((beentry = pgstat_get_beentry_by_backend_id(beid)) == NULL) + PG_RETURN_NULL(); + + else if (!HAS_PGSTAT_PERMISSIONS(beentry->st_userid)) + PG_RETURN_NULL(); + + result = beentry->st_xact_start_timestamp; + + if (result == 0) /* not in a transaction */ + PG_RETURN_NULL(); + + PG_RETURN_TIMESTAMPTZ(result); +} + + +Datum +pg_stat_get_backend_start(PG_FUNCTION_ARGS) +{ + int32 beid = PG_GETARG_INT32(0); + TimestampTz result; + PgBackendStatus *beentry; + + if ((beentry = pgstat_get_beentry_by_backend_id(beid)) == NULL) + PG_RETURN_NULL(); + + else if (!HAS_PGSTAT_PERMISSIONS(beentry->st_userid)) + PG_RETURN_NULL(); + + result = beentry->st_proc_start_timestamp; + + if (result == 0) /* probably can't happen? */ + PG_RETURN_NULL(); + + PG_RETURN_TIMESTAMPTZ(result); +} + + +Datum +pg_stat_get_backend_client_addr(PG_FUNCTION_ARGS) +{ + int32 beid = PG_GETARG_INT32(0); + PgBackendStatus *beentry; + SockAddr zero_clientaddr; + char remote_host[NI_MAXHOST]; + int ret; + + if ((beentry = pgstat_get_beentry_by_backend_id(beid)) == NULL) + PG_RETURN_NULL(); + + else if (!HAS_PGSTAT_PERMISSIONS(beentry->st_userid)) + PG_RETURN_NULL(); + + /* A zeroed client addr means we don't know */ + memset(&zero_clientaddr, 0, sizeof(zero_clientaddr)); + if (memcmp(&(beentry->st_clientaddr), &zero_clientaddr, + sizeof(zero_clientaddr)) == 0) + PG_RETURN_NULL(); + + switch (beentry->st_clientaddr.addr.ss_family) + { + case AF_INET: + case AF_INET6: + break; + default: + PG_RETURN_NULL(); + } + + remote_host[0] = '\0'; + ret = pg_getnameinfo_all(&beentry->st_clientaddr.addr, + beentry->st_clientaddr.salen, + remote_host, sizeof(remote_host), + NULL, 0, + NI_NUMERICHOST | NI_NUMERICSERV); + if (ret != 0) + PG_RETURN_NULL(); + + clean_ipv6_addr(beentry->st_clientaddr.addr.ss_family, remote_host); + + PG_RETURN_DATUM(DirectFunctionCall1(inet_in, + CStringGetDatum(remote_host))); +} + +Datum +pg_stat_get_backend_client_port(PG_FUNCTION_ARGS) +{ + int32 beid = PG_GETARG_INT32(0); + PgBackendStatus *beentry; + SockAddr zero_clientaddr; + char remote_port[NI_MAXSERV]; + int ret; + + if ((beentry = pgstat_get_beentry_by_backend_id(beid)) == NULL) + PG_RETURN_NULL(); + + else if (!HAS_PGSTAT_PERMISSIONS(beentry->st_userid)) + PG_RETURN_NULL(); + + /* A zeroed client addr means we don't know */ + memset(&zero_clientaddr, 0, sizeof(zero_clientaddr)); + if (memcmp(&(beentry->st_clientaddr), &zero_clientaddr, + sizeof(zero_clientaddr)) == 0) + PG_RETURN_NULL(); + + switch (beentry->st_clientaddr.addr.ss_family) + { + case AF_INET: + case AF_INET6: + break; + case AF_UNIX: + PG_RETURN_INT32(-1); + default: + PG_RETURN_NULL(); + } + + remote_port[0] = '\0'; + ret = pg_getnameinfo_all(&beentry->st_clientaddr.addr, + beentry->st_clientaddr.salen, + NULL, 0, + remote_port, sizeof(remote_port), + NI_NUMERICHOST | NI_NUMERICSERV); + if (ret != 0) + PG_RETURN_NULL(); + + PG_RETURN_DATUM(DirectFunctionCall1(int4in, + CStringGetDatum(remote_port))); +} + + +Datum +pg_stat_get_db_numbackends(PG_FUNCTION_ARGS) +{ + Oid dbid = PG_GETARG_OID(0); + int32 result; + int tot_backends = pgstat_fetch_stat_numbackends(); + int idx; + + result = 0; + for (idx = 1; idx <= tot_backends; idx++) + { + LocalPgBackendStatus *local_beentry = pgstat_get_local_beentry_by_index(idx); + + if (local_beentry->backendStatus.st_databaseid == dbid) + result++; + } + + PG_RETURN_INT32(result); +} + + +#define PG_STAT_GET_DBENTRY_INT64(stat) \ +Datum \ +CppConcat(pg_stat_get_db_,stat)(PG_FUNCTION_ARGS) \ +{ \ + Oid dbid = PG_GETARG_OID(0); \ + int64 result; \ + PgStat_StatDBEntry *dbentry; \ + \ + if ((dbentry = pgstat_fetch_stat_dbentry(dbid)) == NULL) \ + result = 0; \ + else \ + result = (int64) (dbentry->stat); \ + \ + PG_RETURN_INT64(result); \ +} + +/* pg_stat_get_db_blocks_fetched */ +PG_STAT_GET_DBENTRY_INT64(blocks_fetched) + +/* pg_stat_get_db_blocks_hit */ +PG_STAT_GET_DBENTRY_INT64(blocks_hit) + +/* pg_stat_get_db_conflict_bufferpin */ +PG_STAT_GET_DBENTRY_INT64(conflict_bufferpin) + +/* pg_stat_get_db_conflict_lock */ +PG_STAT_GET_DBENTRY_INT64(conflict_lock) + +/* pg_stat_get_db_conflict_snapshot */ +PG_STAT_GET_DBENTRY_INT64(conflict_snapshot) + +/* pg_stat_get_db_conflict_startup_deadlock */ +PG_STAT_GET_DBENTRY_INT64(conflict_startup_deadlock) + +/* pg_stat_get_db_conflict_tablespace */ +PG_STAT_GET_DBENTRY_INT64(conflict_tablespace) + +/* pg_stat_get_db_deadlocks */ +PG_STAT_GET_DBENTRY_INT64(deadlocks) + +/* pg_stat_get_db_sessions */ +PG_STAT_GET_DBENTRY_INT64(sessions) + +/* pg_stat_get_db_sessions_abandoned */ +PG_STAT_GET_DBENTRY_INT64(sessions_abandoned) + +/* pg_stat_get_db_sessions_fatal */ +PG_STAT_GET_DBENTRY_INT64(sessions_fatal) + +/* pg_stat_get_db_sessions_killed */ +PG_STAT_GET_DBENTRY_INT64(sessions_killed) + +/* pg_stat_get_db_temp_bytes */ +PG_STAT_GET_DBENTRY_INT64(temp_bytes) + +/* pg_stat_get_db_temp_files */ +PG_STAT_GET_DBENTRY_INT64(temp_files) + +/* pg_stat_get_db_tuples_deleted */ +PG_STAT_GET_DBENTRY_INT64(tuples_deleted) + +/* pg_stat_get_db_tuples_fetched */ +PG_STAT_GET_DBENTRY_INT64(tuples_fetched) + +/* pg_stat_get_db_tuples_inserted */ +PG_STAT_GET_DBENTRY_INT64(tuples_inserted) + +/* pg_stat_get_db_tuples_returned */ +PG_STAT_GET_DBENTRY_INT64(tuples_returned) + +/* pg_stat_get_db_tuples_updated */ +PG_STAT_GET_DBENTRY_INT64(tuples_updated) + +/* pg_stat_get_db_xact_commit */ +PG_STAT_GET_DBENTRY_INT64(xact_commit) + +/* pg_stat_get_db_xact_rollback */ +PG_STAT_GET_DBENTRY_INT64(xact_rollback) + +/* pg_stat_get_db_conflict_logicalslot */ +PG_STAT_GET_DBENTRY_INT64(conflict_logicalslot) + +Datum +pg_stat_get_db_stat_reset_time(PG_FUNCTION_ARGS) +{ + Oid dbid = PG_GETARG_OID(0); + TimestampTz result; + PgStat_StatDBEntry *dbentry; + + if ((dbentry = pgstat_fetch_stat_dbentry(dbid)) == NULL) + result = 0; + else + result = dbentry->stat_reset_timestamp; + + if (result == 0) + PG_RETURN_NULL(); + else + PG_RETURN_TIMESTAMPTZ(result); +} + + +Datum +pg_stat_get_db_conflict_all(PG_FUNCTION_ARGS) +{ + Oid dbid = PG_GETARG_OID(0); + int64 result; + PgStat_StatDBEntry *dbentry; + + if ((dbentry = pgstat_fetch_stat_dbentry(dbid)) == NULL) + result = 0; + else + result = (int64) (dbentry->conflict_tablespace + + dbentry->conflict_lock + + dbentry->conflict_snapshot + + dbentry->conflict_logicalslot + + dbentry->conflict_bufferpin + + dbentry->conflict_startup_deadlock); + + PG_RETURN_INT64(result); +} + +Datum +pg_stat_get_db_checksum_failures(PG_FUNCTION_ARGS) +{ + Oid dbid = PG_GETARG_OID(0); + int64 result; + PgStat_StatDBEntry *dbentry; + + if (!DataChecksumsEnabled()) + PG_RETURN_NULL(); + + if ((dbentry = pgstat_fetch_stat_dbentry(dbid)) == NULL) + result = 0; + else + result = (int64) (dbentry->checksum_failures); + + PG_RETURN_INT64(result); +} + +Datum +pg_stat_get_db_checksum_last_failure(PG_FUNCTION_ARGS) +{ + Oid dbid = PG_GETARG_OID(0); + TimestampTz result; + PgStat_StatDBEntry *dbentry; + + if (!DataChecksumsEnabled()) + PG_RETURN_NULL(); + + if ((dbentry = pgstat_fetch_stat_dbentry(dbid)) == NULL) + result = 0; + else + result = dbentry->last_checksum_failure; + + if (result == 0) + PG_RETURN_NULL(); + else + PG_RETURN_TIMESTAMPTZ(result); +} + +/* convert counter from microsec to millisec for display */ +#define PG_STAT_GET_DBENTRY_FLOAT8_MS(stat) \ +Datum \ +CppConcat(pg_stat_get_db_,stat)(PG_FUNCTION_ARGS) \ +{ \ + Oid dbid = PG_GETARG_OID(0); \ + double result; \ + PgStat_StatDBEntry *dbentry; \ + \ + if ((dbentry = pgstat_fetch_stat_dbentry(dbid)) == NULL) \ + result = 0; \ + else \ + result = ((double) dbentry->stat) / 1000.0; \ + \ + PG_RETURN_FLOAT8(result); \ +} + +/* pg_stat_get_db_active_time */ +PG_STAT_GET_DBENTRY_FLOAT8_MS(active_time) + +/* pg_stat_get_db_blk_read_time */ +PG_STAT_GET_DBENTRY_FLOAT8_MS(blk_read_time) + +/* pg_stat_get_db_blk_write_time */ +PG_STAT_GET_DBENTRY_FLOAT8_MS(blk_write_time) + +/* pg_stat_get_db_idle_in_transaction_time */ +PG_STAT_GET_DBENTRY_FLOAT8_MS(idle_in_transaction_time) + +/* pg_stat_get_db_session_time */ +PG_STAT_GET_DBENTRY_FLOAT8_MS(session_time) + +Datum +pg_stat_get_bgwriter_timed_checkpoints(PG_FUNCTION_ARGS) +{ + PG_RETURN_INT64(pgstat_fetch_stat_checkpointer()->timed_checkpoints); +} + +Datum +pg_stat_get_bgwriter_requested_checkpoints(PG_FUNCTION_ARGS) +{ + PG_RETURN_INT64(pgstat_fetch_stat_checkpointer()->requested_checkpoints); +} + +Datum +pg_stat_get_bgwriter_buf_written_checkpoints(PG_FUNCTION_ARGS) +{ + PG_RETURN_INT64(pgstat_fetch_stat_checkpointer()->buf_written_checkpoints); +} + +Datum +pg_stat_get_bgwriter_buf_written_clean(PG_FUNCTION_ARGS) +{ + PG_RETURN_INT64(pgstat_fetch_stat_bgwriter()->buf_written_clean); +} + +Datum +pg_stat_get_bgwriter_maxwritten_clean(PG_FUNCTION_ARGS) +{ + PG_RETURN_INT64(pgstat_fetch_stat_bgwriter()->maxwritten_clean); +} + +Datum +pg_stat_get_checkpoint_write_time(PG_FUNCTION_ARGS) +{ + /* time is already in msec, just convert to double for presentation */ + PG_RETURN_FLOAT8((double) + pgstat_fetch_stat_checkpointer()->checkpoint_write_time); +} + +Datum +pg_stat_get_checkpoint_sync_time(PG_FUNCTION_ARGS) +{ + /* time is already in msec, just convert to double for presentation */ + PG_RETURN_FLOAT8((double) + pgstat_fetch_stat_checkpointer()->checkpoint_sync_time); +} + +Datum +pg_stat_get_bgwriter_stat_reset_time(PG_FUNCTION_ARGS) +{ + PG_RETURN_TIMESTAMPTZ(pgstat_fetch_stat_bgwriter()->stat_reset_timestamp); +} + +Datum +pg_stat_get_buf_written_backend(PG_FUNCTION_ARGS) +{ + PG_RETURN_INT64(pgstat_fetch_stat_checkpointer()->buf_written_backend); +} + +Datum +pg_stat_get_buf_fsync_backend(PG_FUNCTION_ARGS) +{ + PG_RETURN_INT64(pgstat_fetch_stat_checkpointer()->buf_fsync_backend); +} + +Datum +pg_stat_get_buf_alloc(PG_FUNCTION_ARGS) +{ + PG_RETURN_INT64(pgstat_fetch_stat_bgwriter()->buf_alloc); +} + +/* +* When adding a new column to the pg_stat_io view, add a new enum value +* here above IO_NUM_COLUMNS. +*/ +typedef enum io_stat_col +{ + IO_COL_INVALID = -1, + IO_COL_BACKEND_TYPE, + IO_COL_OBJECT, + IO_COL_CONTEXT, + IO_COL_READS, + IO_COL_READ_TIME, + IO_COL_WRITES, + IO_COL_WRITE_TIME, + IO_COL_WRITEBACKS, + IO_COL_WRITEBACK_TIME, + IO_COL_EXTENDS, + IO_COL_EXTEND_TIME, + IO_COL_CONVERSION, + IO_COL_HITS, + IO_COL_EVICTIONS, + IO_COL_REUSES, + IO_COL_FSYNCS, + IO_COL_FSYNC_TIME, + IO_COL_RESET_TIME, + IO_NUM_COLUMNS, +} io_stat_col; + +/* + * When adding a new IOOp, add a new io_stat_col and add a case to this + * function returning the corresponding io_stat_col. + */ +static io_stat_col +pgstat_get_io_op_index(IOOp io_op) +{ + switch (io_op) + { + case IOOP_EVICT: + return IO_COL_EVICTIONS; + case IOOP_EXTEND: + return IO_COL_EXTENDS; + case IOOP_FSYNC: + return IO_COL_FSYNCS; + case IOOP_HIT: + return IO_COL_HITS; + case IOOP_READ: + return IO_COL_READS; + case IOOP_REUSE: + return IO_COL_REUSES; + case IOOP_WRITE: + return IO_COL_WRITES; + case IOOP_WRITEBACK: + return IO_COL_WRITEBACKS; + } + + elog(ERROR, "unrecognized IOOp value: %d", io_op); + pg_unreachable(); +} + +/* + * Get the number of the column containing IO times for the specified IOOp. + * This function encodes our assumption that IO time for an IOOp is displayed + * in the view in the column directly after the IOOp counts. If an op has no + * associated time, IO_COL_INVALID is returned. + */ +static io_stat_col +pgstat_get_io_time_index(IOOp io_op) +{ + switch (io_op) + { + case IOOP_READ: + case IOOP_WRITE: + case IOOP_WRITEBACK: + case IOOP_EXTEND: + case IOOP_FSYNC: + return pgstat_get_io_op_index(io_op) + 1; + case IOOP_EVICT: + case IOOP_HIT: + case IOOP_REUSE: + return IO_COL_INVALID; + } + + elog(ERROR, "unrecognized IOOp value: %d", io_op); + pg_unreachable(); +} + +static inline double +pg_stat_us_to_ms(PgStat_Counter val_ms) +{ + return val_ms * (double) 0.001; +} + +Datum +pg_stat_get_io(PG_FUNCTION_ARGS) +{ + ReturnSetInfo *rsinfo; + PgStat_IO *backends_io_stats; + Datum reset_time; + + InitMaterializedSRF(fcinfo, 0); + rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + backends_io_stats = pgstat_fetch_stat_io(); + + reset_time = TimestampTzGetDatum(backends_io_stats->stat_reset_timestamp); + + for (int bktype = 0; bktype < BACKEND_NUM_TYPES; bktype++) + { + Datum bktype_desc = CStringGetTextDatum(GetBackendTypeDesc(bktype)); + PgStat_BktypeIO *bktype_stats = &backends_io_stats->stats[bktype]; + + /* + * In Assert builds, we can afford an extra loop through all of the + * counters checking that only expected stats are non-zero, since it + * keeps the non-Assert code cleaner. + */ + Assert(pgstat_bktype_io_stats_valid(bktype_stats, bktype)); + + /* + * For those BackendTypes without IO Operation stats, skip + * representing them in the view altogether. + */ + if (!pgstat_tracks_io_bktype(bktype)) + continue; + + for (int io_obj = 0; io_obj < IOOBJECT_NUM_TYPES; io_obj++) + { + const char *obj_name = pgstat_get_io_object_name(io_obj); + + for (int io_context = 0; io_context < IOCONTEXT_NUM_TYPES; io_context++) + { + const char *context_name = pgstat_get_io_context_name(io_context); + + Datum values[IO_NUM_COLUMNS] = {0}; + bool nulls[IO_NUM_COLUMNS] = {0}; + + /* + * Some combinations of BackendType, IOObject, and IOContext + * are not valid for any type of IOOp. In such cases, omit the + * entire row from the view. + */ + if (!pgstat_tracks_io_object(bktype, io_obj, io_context)) + continue; + + values[IO_COL_BACKEND_TYPE] = bktype_desc; + values[IO_COL_CONTEXT] = CStringGetTextDatum(context_name); + values[IO_COL_OBJECT] = CStringGetTextDatum(obj_name); + values[IO_COL_RESET_TIME] = TimestampTzGetDatum(reset_time); + + /* + * Hard-code this to the value of BLCKSZ for now. Future + * values could include XLOG_BLCKSZ, once WAL IO is tracked, + * and constant multipliers, once non-block-oriented IO (e.g. + * temporary file IO) is tracked. + */ + values[IO_COL_CONVERSION] = Int64GetDatum(BLCKSZ); + + for (int io_op = 0; io_op < IOOP_NUM_TYPES; io_op++) + { + int op_idx = pgstat_get_io_op_index(io_op); + int time_idx = pgstat_get_io_time_index(io_op); + + /* + * Some combinations of BackendType and IOOp, of IOContext + * and IOOp, and of IOObject and IOOp are not tracked. Set + * these cells in the view NULL. + */ + if (pgstat_tracks_io_op(bktype, io_obj, io_context, io_op)) + { + PgStat_Counter count = + bktype_stats->counts[io_obj][io_context][io_op]; + + values[op_idx] = Int64GetDatum(count); + } + else + nulls[op_idx] = true; + + /* not every operation is timed */ + if (time_idx == IO_COL_INVALID) + continue; + + if (!nulls[op_idx]) + { + PgStat_Counter time = + bktype_stats->times[io_obj][io_context][io_op]; + + values[time_idx] = Float8GetDatum(pg_stat_us_to_ms(time)); + } + else + nulls[time_idx] = true; + } + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, + values, nulls); + } + } + } + + return (Datum) 0; +} + +/* + * Returns statistics of WAL activity + */ +Datum +pg_stat_get_wal(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_WAL_COLS 9 + TupleDesc tupdesc; + Datum values[PG_STAT_GET_WAL_COLS] = {0}; + bool nulls[PG_STAT_GET_WAL_COLS] = {0}; + char buf[256]; + PgStat_WalStats *wal_stats; + + /* Initialise attributes information in the tuple descriptor */ + tupdesc = CreateTemplateTupleDesc(PG_STAT_GET_WAL_COLS); + TupleDescInitEntry(tupdesc, (AttrNumber) 1, "wal_records", + INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 2, "wal_fpi", + INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 3, "wal_bytes", + NUMERICOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 4, "wal_buffers_full", + INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 5, "wal_write", + INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 6, "wal_sync", + INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 7, "wal_write_time", + FLOAT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 8, "wal_sync_time", + FLOAT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 9, "stats_reset", + TIMESTAMPTZOID, -1, 0); + + BlessTupleDesc(tupdesc); + + /* Get statistics about WAL activity */ + wal_stats = pgstat_fetch_stat_wal(); + + /* Fill values and NULLs */ + values[0] = Int64GetDatum(wal_stats->wal_records); + values[1] = Int64GetDatum(wal_stats->wal_fpi); + + /* Convert to numeric. */ + snprintf(buf, sizeof buf, UINT64_FORMAT, wal_stats->wal_bytes); + values[2] = DirectFunctionCall3(numeric_in, + CStringGetDatum(buf), + ObjectIdGetDatum(0), + Int32GetDatum(-1)); + + values[3] = Int64GetDatum(wal_stats->wal_buffers_full); + values[4] = Int64GetDatum(wal_stats->wal_write); + values[5] = Int64GetDatum(wal_stats->wal_sync); + + /* Convert counters from microsec to millisec for display */ + values[6] = Float8GetDatum(((double) wal_stats->wal_write_time) / 1000.0); + values[7] = Float8GetDatum(((double) wal_stats->wal_sync_time) / 1000.0); + + values[8] = TimestampTzGetDatum(wal_stats->stat_reset_timestamp); + + /* Returns the record as Datum */ + PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls))); +} + +/* + * Returns statistics of SLRU caches. + */ +Datum +pg_stat_get_slru(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_SLRU_COLS 9 + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + int i; + PgStat_SLRUStats *stats; + + InitMaterializedSRF(fcinfo, 0); + + /* request SLRU stats from the cumulative stats system */ + stats = pgstat_fetch_slru(); + + for (i = 0;; i++) + { + /* for each row */ + Datum values[PG_STAT_GET_SLRU_COLS] = {0}; + bool nulls[PG_STAT_GET_SLRU_COLS] = {0}; + PgStat_SLRUStats stat; + const char *name; + + name = pgstat_get_slru_name(i); + + if (!name) + break; + + stat = stats[i]; + + values[0] = PointerGetDatum(cstring_to_text(name)); + values[1] = Int64GetDatum(stat.blocks_zeroed); + values[2] = Int64GetDatum(stat.blocks_hit); + values[3] = Int64GetDatum(stat.blocks_read); + values[4] = Int64GetDatum(stat.blocks_written); + values[5] = Int64GetDatum(stat.blocks_exists); + values[6] = Int64GetDatum(stat.flush); + values[7] = Int64GetDatum(stat.truncate); + values[8] = TimestampTzGetDatum(stat.stat_reset_timestamp); + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + } + + return (Datum) 0; +} + +#define PG_STAT_GET_XACT_RELENTRY_INT64(stat) \ +Datum \ +CppConcat(pg_stat_get_xact_,stat)(PG_FUNCTION_ARGS) \ +{ \ + Oid relid = PG_GETARG_OID(0); \ + int64 result; \ + PgStat_TableStatus *tabentry; \ + \ + if ((tabentry = find_tabstat_entry(relid)) == NULL) \ + result = 0; \ + else \ + result = (int64) (tabentry->counts.stat); \ + \ + PG_RETURN_INT64(result); \ +} + +/* pg_stat_get_xact_numscans */ +PG_STAT_GET_XACT_RELENTRY_INT64(numscans) + +/* pg_stat_get_xact_tuples_returned */ +PG_STAT_GET_XACT_RELENTRY_INT64(tuples_returned) + +/* pg_stat_get_xact_tuples_fetched */ +PG_STAT_GET_XACT_RELENTRY_INT64(tuples_fetched) + +/* pg_stat_get_xact_tuples_hot_updated */ +PG_STAT_GET_XACT_RELENTRY_INT64(tuples_hot_updated) + +/* pg_stat_get_xact_tuples_newpage_updated */ +PG_STAT_GET_XACT_RELENTRY_INT64(tuples_newpage_updated) + +/* pg_stat_get_xact_blocks_fetched */ +PG_STAT_GET_XACT_RELENTRY_INT64(blocks_fetched) + +/* pg_stat_get_xact_blocks_hit */ +PG_STAT_GET_XACT_RELENTRY_INT64(blocks_hit) + +Datum +pg_stat_get_xact_tuples_inserted(PG_FUNCTION_ARGS) +{ + Oid relid = PG_GETARG_OID(0); + int64 result; + PgStat_TableStatus *tabentry; + PgStat_TableXactStatus *trans; + + if ((tabentry = find_tabstat_entry(relid)) == NULL) + result = 0; + else + { + result = tabentry->counts.tuples_inserted; + /* live subtransactions' counts aren't in tuples_inserted yet */ + for (trans = tabentry->trans; trans != NULL; trans = trans->upper) + result += trans->tuples_inserted; + } + + PG_RETURN_INT64(result); +} + +Datum +pg_stat_get_xact_tuples_updated(PG_FUNCTION_ARGS) +{ + Oid relid = PG_GETARG_OID(0); + int64 result; + PgStat_TableStatus *tabentry; + PgStat_TableXactStatus *trans; + + if ((tabentry = find_tabstat_entry(relid)) == NULL) + result = 0; + else + { + result = tabentry->counts.tuples_updated; + /* live subtransactions' counts aren't in tuples_updated yet */ + for (trans = tabentry->trans; trans != NULL; trans = trans->upper) + result += trans->tuples_updated; + } + + PG_RETURN_INT64(result); +} + +Datum +pg_stat_get_xact_tuples_deleted(PG_FUNCTION_ARGS) +{ + Oid relid = PG_GETARG_OID(0); + int64 result; + PgStat_TableStatus *tabentry; + PgStat_TableXactStatus *trans; + + if ((tabentry = find_tabstat_entry(relid)) == NULL) + result = 0; + else + { + result = tabentry->counts.tuples_deleted; + /* live subtransactions' counts aren't in tuples_deleted yet */ + for (trans = tabentry->trans; trans != NULL; trans = trans->upper) + result += trans->tuples_deleted; + } + + PG_RETURN_INT64(result); +} + +Datum +pg_stat_get_xact_function_calls(PG_FUNCTION_ARGS) +{ + Oid funcid = PG_GETARG_OID(0); + PgStat_FunctionCounts *funcentry; + + if ((funcentry = find_funcstat_entry(funcid)) == NULL) + PG_RETURN_NULL(); + PG_RETURN_INT64(funcentry->numcalls); +} + +#define PG_STAT_GET_XACT_FUNCENTRY_FLOAT8_MS(stat) \ +Datum \ +CppConcat(pg_stat_get_xact_function_,stat)(PG_FUNCTION_ARGS) \ +{ \ + Oid funcid = PG_GETARG_OID(0); \ + PgStat_FunctionCounts *funcentry; \ + \ + if ((funcentry = find_funcstat_entry(funcid)) == NULL) \ + PG_RETURN_NULL(); \ + PG_RETURN_FLOAT8(INSTR_TIME_GET_MILLISEC(funcentry->stat)); \ +} + +/* pg_stat_get_xact_function_total_time */ +PG_STAT_GET_XACT_FUNCENTRY_FLOAT8_MS(total_time) + +/* pg_stat_get_xact_function_self_time */ +PG_STAT_GET_XACT_FUNCENTRY_FLOAT8_MS(self_time) + +/* Get the timestamp of the current statistics snapshot */ +Datum +pg_stat_get_snapshot_timestamp(PG_FUNCTION_ARGS) +{ + bool have_snapshot; + TimestampTz ts; + + ts = pgstat_get_stat_snapshot_timestamp(&have_snapshot); + + if (!have_snapshot) + PG_RETURN_NULL(); + + PG_RETURN_TIMESTAMPTZ(ts); +} + +/* Discard the active statistics snapshot */ +Datum +pg_stat_clear_snapshot(PG_FUNCTION_ARGS) +{ + pgstat_clear_snapshot(); + + PG_RETURN_VOID(); +} + + +/* Force statistics to be reported at the next occasion */ +Datum +pg_stat_force_next_flush(PG_FUNCTION_ARGS) +{ + pgstat_force_next_flush(); + + PG_RETURN_VOID(); +} + + +/* Reset all counters for the current database */ +Datum +pg_stat_reset(PG_FUNCTION_ARGS) +{ + pgstat_reset_counters(); + + PG_RETURN_VOID(); +} + +/* + * Reset some shared cluster-wide counters + * + * When adding a new reset target, ideally the name should match that in + * pgstat_kind_infos, if relevant. + */ +Datum +pg_stat_reset_shared(PG_FUNCTION_ARGS) +{ + char *target = text_to_cstring(PG_GETARG_TEXT_PP(0)); + + if (strcmp(target, "archiver") == 0) + pgstat_reset_of_kind(PGSTAT_KIND_ARCHIVER); + else if (strcmp(target, "bgwriter") == 0) + { + /* + * Historically checkpointer was part of bgwriter, continue to reset + * both for now. + */ + pgstat_reset_of_kind(PGSTAT_KIND_BGWRITER); + pgstat_reset_of_kind(PGSTAT_KIND_CHECKPOINTER); + } + else if (strcmp(target, "io") == 0) + pgstat_reset_of_kind(PGSTAT_KIND_IO); + else if (strcmp(target, "recovery_prefetch") == 0) + XLogPrefetchResetStats(); + else if (strcmp(target, "wal") == 0) + pgstat_reset_of_kind(PGSTAT_KIND_WAL); + else + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unrecognized reset target: \"%s\"", target), + errhint("Target must be \"archiver\", \"bgwriter\", \"io\", \"recovery_prefetch\", or \"wal\"."))); + + PG_RETURN_VOID(); +} + +/* + * Reset a statistics for a single object, which may be of current + * database or shared across all databases in the cluster. + */ +Datum +pg_stat_reset_single_table_counters(PG_FUNCTION_ARGS) +{ + Oid taboid = PG_GETARG_OID(0); + Oid dboid = (IsSharedRelation(taboid) ? InvalidOid : MyDatabaseId); + + pgstat_reset(PGSTAT_KIND_RELATION, dboid, taboid); + + PG_RETURN_VOID(); +} + +Datum +pg_stat_reset_single_function_counters(PG_FUNCTION_ARGS) +{ + Oid funcoid = PG_GETARG_OID(0); + + pgstat_reset(PGSTAT_KIND_FUNCTION, MyDatabaseId, funcoid); + + PG_RETURN_VOID(); +} + +/* Reset SLRU counters (a specific one or all of them). */ +Datum +pg_stat_reset_slru(PG_FUNCTION_ARGS) +{ + char *target = NULL; + + if (PG_ARGISNULL(0)) + pgstat_reset_of_kind(PGSTAT_KIND_SLRU); + else + { + target = text_to_cstring(PG_GETARG_TEXT_PP(0)); + pgstat_reset_slru(target); + } + + PG_RETURN_VOID(); +} + +/* Reset replication slots stats (a specific one or all of them). */ +Datum +pg_stat_reset_replication_slot(PG_FUNCTION_ARGS) +{ + char *target = NULL; + + if (PG_ARGISNULL(0)) + pgstat_reset_of_kind(PGSTAT_KIND_REPLSLOT); + else + { + target = text_to_cstring(PG_GETARG_TEXT_PP(0)); + pgstat_reset_replslot(target); + } + + PG_RETURN_VOID(); +} + +/* Reset subscription stats (a specific one or all of them) */ +Datum +pg_stat_reset_subscription_stats(PG_FUNCTION_ARGS) +{ + Oid subid; + + if (PG_ARGISNULL(0)) + { + /* Clear all subscription stats */ + pgstat_reset_of_kind(PGSTAT_KIND_SUBSCRIPTION); + } + else + { + subid = PG_GETARG_OID(0); + + if (!OidIsValid(subid)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid subscription OID %u", subid))); + pgstat_reset(PGSTAT_KIND_SUBSCRIPTION, InvalidOid, subid); + } + + PG_RETURN_VOID(); +} + +Datum +pg_stat_get_archiver(PG_FUNCTION_ARGS) +{ + TupleDesc tupdesc; + Datum values[7] = {0}; + bool nulls[7] = {0}; + PgStat_ArchiverStats *archiver_stats; + + /* Initialise attributes information in the tuple descriptor */ + tupdesc = CreateTemplateTupleDesc(7); + TupleDescInitEntry(tupdesc, (AttrNumber) 1, "archived_count", + INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 2, "last_archived_wal", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 3, "last_archived_time", + TIMESTAMPTZOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 4, "failed_count", + INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 5, "last_failed_wal", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 6, "last_failed_time", + TIMESTAMPTZOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 7, "stats_reset", + TIMESTAMPTZOID, -1, 0); + + BlessTupleDesc(tupdesc); + + /* Get statistics about the archiver process */ + archiver_stats = pgstat_fetch_stat_archiver(); + + /* Fill values and NULLs */ + values[0] = Int64GetDatum(archiver_stats->archived_count); + if (*(archiver_stats->last_archived_wal) == '\0') + nulls[1] = true; + else + values[1] = CStringGetTextDatum(archiver_stats->last_archived_wal); + + if (archiver_stats->last_archived_timestamp == 0) + nulls[2] = true; + else + values[2] = TimestampTzGetDatum(archiver_stats->last_archived_timestamp); + + values[3] = Int64GetDatum(archiver_stats->failed_count); + if (*(archiver_stats->last_failed_wal) == '\0') + nulls[4] = true; + else + values[4] = CStringGetTextDatum(archiver_stats->last_failed_wal); + + if (archiver_stats->last_failed_timestamp == 0) + nulls[5] = true; + else + values[5] = TimestampTzGetDatum(archiver_stats->last_failed_timestamp); + + if (archiver_stats->stat_reset_timestamp == 0) + nulls[6] = true; + else + values[6] = TimestampTzGetDatum(archiver_stats->stat_reset_timestamp); + + /* Returns the record as Datum */ + PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls))); +} + +/* + * Get the statistics for the replication slot. If the slot statistics is not + * available, return all-zeroes stats. + */ +Datum +pg_stat_get_replication_slot(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_REPLICATION_SLOT_COLS 10 + text *slotname_text = PG_GETARG_TEXT_P(0); + NameData slotname; + TupleDesc tupdesc; + Datum values[PG_STAT_GET_REPLICATION_SLOT_COLS] = {0}; + bool nulls[PG_STAT_GET_REPLICATION_SLOT_COLS] = {0}; + PgStat_StatReplSlotEntry *slotent; + PgStat_StatReplSlotEntry allzero; + + /* Initialise attributes information in the tuple descriptor */ + tupdesc = CreateTemplateTupleDesc(PG_STAT_GET_REPLICATION_SLOT_COLS); + TupleDescInitEntry(tupdesc, (AttrNumber) 1, "slot_name", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 2, "spill_txns", + INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 3, "spill_count", + INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 4, "spill_bytes", + INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 5, "stream_txns", + INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 6, "stream_count", + INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 7, "stream_bytes", + INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 8, "total_txns", + INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 9, "total_bytes", + INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 10, "stats_reset", + TIMESTAMPTZOID, -1, 0); + BlessTupleDesc(tupdesc); + + namestrcpy(&slotname, text_to_cstring(slotname_text)); + slotent = pgstat_fetch_replslot(slotname); + if (!slotent) + { + /* + * If the slot is not found, initialise its stats. This is possible if + * the create slot message is lost. + */ + memset(&allzero, 0, sizeof(PgStat_StatReplSlotEntry)); + slotent = &allzero; + } + + values[0] = CStringGetTextDatum(NameStr(slotname)); + values[1] = Int64GetDatum(slotent->spill_txns); + values[2] = Int64GetDatum(slotent->spill_count); + values[3] = Int64GetDatum(slotent->spill_bytes); + values[4] = Int64GetDatum(slotent->stream_txns); + values[5] = Int64GetDatum(slotent->stream_count); + values[6] = Int64GetDatum(slotent->stream_bytes); + values[7] = Int64GetDatum(slotent->total_txns); + values[8] = Int64GetDatum(slotent->total_bytes); + + if (slotent->stat_reset_timestamp == 0) + nulls[9] = true; + else + values[9] = TimestampTzGetDatum(slotent->stat_reset_timestamp); + + /* Returns the record as Datum */ + PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls))); +} + +/* + * Get the subscription statistics for the given subscription. If the + * subscription statistics is not available, return all-zeros stats. + */ +Datum +pg_stat_get_subscription_stats(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_SUBSCRIPTION_STATS_COLS 4 + Oid subid = PG_GETARG_OID(0); + TupleDesc tupdesc; + Datum values[PG_STAT_GET_SUBSCRIPTION_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_SUBSCRIPTION_STATS_COLS] = {0}; + PgStat_StatSubEntry *subentry; + PgStat_StatSubEntry allzero; + + /* Get subscription stats */ + subentry = pgstat_fetch_stat_subscription(subid); + + /* Initialise attributes information in the tuple descriptor */ + tupdesc = CreateTemplateTupleDesc(PG_STAT_GET_SUBSCRIPTION_STATS_COLS); + TupleDescInitEntry(tupdesc, (AttrNumber) 1, "subid", + OIDOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 2, "apply_error_count", + INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 3, "sync_error_count", + INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 4, "stats_reset", + TIMESTAMPTZOID, -1, 0); + BlessTupleDesc(tupdesc); + + if (!subentry) + { + /* If the subscription is not found, initialise its stats */ + memset(&allzero, 0, sizeof(PgStat_StatSubEntry)); + subentry = &allzero; + } + + /* subid */ + values[0] = ObjectIdGetDatum(subid); + + /* apply_error_count */ + values[1] = Int64GetDatum(subentry->apply_error_count); + + /* sync_error_count */ + values[2] = Int64GetDatum(subentry->sync_error_count); + + /* stats_reset */ + if (subentry->stat_reset_timestamp == 0) + nulls[3] = true; + else + values[3] = TimestampTzGetDatum(subentry->stat_reset_timestamp); + + /* Returns the record as Datum */ + PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls))); +} + +/* + * Checks for presence of stats for object with provided kind, database oid, + * object oid. + * + * This is useful for tests, but not really anything else. Therefore not + * documented. + */ +Datum +pg_stat_have_stats(PG_FUNCTION_ARGS) +{ + char *stats_type = text_to_cstring(PG_GETARG_TEXT_P(0)); + Oid dboid = PG_GETARG_OID(1); + Oid objoid = PG_GETARG_OID(2); + PgStat_Kind kind = pgstat_get_kind_from_str(stats_type); + + PG_RETURN_BOOL(pgstat_have_entry(kind, dboid, objoid)); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/pseudotypes.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/pseudotypes.c new file mode 100644 index 00000000000..3ba8cb192ca --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/pseudotypes.c @@ -0,0 +1,380 @@ +/*------------------------------------------------------------------------- + * + * pseudotypes.c + * Functions for the system pseudo-types. + * + * A pseudo-type isn't really a type and never has any operations, but + * we do need to supply input and output functions to satisfy the links + * in the pseudo-type's entry in pg_type. In most cases the functions + * just throw an error if invoked. (XXX the error messages here cover + * the most common case, but might be confusing in some contexts. Can + * we do better?) + * + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/adt/pseudotypes.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "libpq/pqformat.h" +#include "utils/array.h" +#include "utils/builtins.h" +#include "utils/rangetypes.h" +#include "utils/multirangetypes.h" + + +/* + * These macros generate input and output functions for a pseudo-type that + * will reject all input and output attempts. (But for some types, only + * the input function need be dummy.) + */ +#define PSEUDOTYPE_DUMMY_INPUT_FUNC(typname) \ +Datum \ +typname##_in(PG_FUNCTION_ARGS) \ +{ \ + ereport(ERROR, \ + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), \ + errmsg("cannot accept a value of type %s", #typname))); \ +\ + PG_RETURN_VOID(); /* keep compiler quiet */ \ +} \ +\ +extern int no_such_variable + +#define PSEUDOTYPE_DUMMY_IO_FUNCS(typname) \ +PSEUDOTYPE_DUMMY_INPUT_FUNC(typname); \ +\ +Datum \ +typname##_out(PG_FUNCTION_ARGS) \ +{ \ + ereport(ERROR, \ + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), \ + errmsg("cannot display a value of type %s", #typname))); \ +\ + PG_RETURN_VOID(); /* keep compiler quiet */ \ +} \ +\ +extern int no_such_variable + +/* + * Likewise for binary send/receive functions. We don't bother with these + * at all for many pseudotypes, but some have them. (By convention, if + * a type has a send function it should have a receive function, even if + * that's only dummy.) + */ +#define PSEUDOTYPE_DUMMY_RECEIVE_FUNC(typname) \ +Datum \ +typname##_recv(PG_FUNCTION_ARGS) \ +{ \ + ereport(ERROR, \ + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), \ + errmsg("cannot accept a value of type %s", #typname))); \ +\ + PG_RETURN_VOID(); /* keep compiler quiet */ \ +} \ +\ +extern int no_such_variable + +#define PSEUDOTYPE_DUMMY_BINARY_IO_FUNCS(typname) \ +PSEUDOTYPE_DUMMY_RECEIVE_FUNC(typname); \ +\ +Datum \ +typname##_send(PG_FUNCTION_ARGS) \ +{ \ + ereport(ERROR, \ + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), \ + errmsg("cannot display a value of type %s", #typname))); \ +\ + PG_RETURN_VOID(); /* keep compiler quiet */ \ +} \ +\ +extern int no_such_variable + + +/* + * cstring + * + * cstring is marked as a pseudo-type because we don't want people using it + * in tables. But it's really a perfectly functional type, so provide + * a full set of working I/O functions for it. Among other things, this + * allows manual invocation of datatype I/O functions, along the lines of + * "SELECT foo_in('blah')" or "SELECT foo_out(some-foo-value)". + */ +Datum +cstring_in(PG_FUNCTION_ARGS) +{ + char *str = PG_GETARG_CSTRING(0); + + PG_RETURN_CSTRING(pstrdup(str)); +} + +Datum +cstring_out(PG_FUNCTION_ARGS) +{ + char *str = PG_GETARG_CSTRING(0); + + PG_RETURN_CSTRING(pstrdup(str)); +} + +Datum +cstring_recv(PG_FUNCTION_ARGS) +{ + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + char *str; + int nbytes; + + str = pq_getmsgtext(buf, buf->len - buf->cursor, &nbytes); + PG_RETURN_CSTRING(str); +} + +Datum +cstring_send(PG_FUNCTION_ARGS) +{ + char *str = PG_GETARG_CSTRING(0); + StringInfoData buf; + + pq_begintypsend(&buf); + pq_sendtext(&buf, str, strlen(str)); + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} + +/* + * anyarray + * + * We need to allow output of anyarray so that, e.g., pg_statistic columns + * can be printed. Input has to be disallowed, however. + * + * XXX anyarray_recv could actually be made to work, since the incoming + * array data would contain the element type OID. It seems unlikely that + * it'd be sufficiently type-safe, though. + */ +PSEUDOTYPE_DUMMY_INPUT_FUNC(anyarray); +PSEUDOTYPE_DUMMY_RECEIVE_FUNC(anyarray); + +Datum +anyarray_out(PG_FUNCTION_ARGS) +{ + return array_out(fcinfo); +} + +Datum +anyarray_send(PG_FUNCTION_ARGS) +{ + return array_send(fcinfo); +} + +/* + * anycompatiblearray + * + * We may as well allow output, since we do for anyarray. + */ +PSEUDOTYPE_DUMMY_INPUT_FUNC(anycompatiblearray); +PSEUDOTYPE_DUMMY_RECEIVE_FUNC(anycompatiblearray); + +Datum +anycompatiblearray_out(PG_FUNCTION_ARGS) +{ + return array_out(fcinfo); +} + +Datum +anycompatiblearray_send(PG_FUNCTION_ARGS) +{ + return array_send(fcinfo); +} + +/* + * anyenum + * + * We may as well allow output, since enum_out will in fact work. + */ +PSEUDOTYPE_DUMMY_INPUT_FUNC(anyenum); + +Datum +anyenum_out(PG_FUNCTION_ARGS) +{ + return enum_out(fcinfo); +} + +/* + * anyrange + * + * We may as well allow output, since range_out will in fact work. + */ +PSEUDOTYPE_DUMMY_INPUT_FUNC(anyrange); + +Datum +anyrange_out(PG_FUNCTION_ARGS) +{ + return range_out(fcinfo); +} + +/* + * anycompatiblerange + * + * We may as well allow output, since range_out will in fact work. + */ +PSEUDOTYPE_DUMMY_INPUT_FUNC(anycompatiblerange); + +Datum +anycompatiblerange_out(PG_FUNCTION_ARGS) +{ + return range_out(fcinfo); +} + +/* + * anymultirange + * + * We may as well allow output, since multirange_out will in fact work. + */ +PSEUDOTYPE_DUMMY_INPUT_FUNC(anymultirange); + +Datum +anymultirange_out(PG_FUNCTION_ARGS) +{ + return multirange_out(fcinfo); +} + +/* + * anycompatiblemultirange + * + * We may as well allow output, since multirange_out will in fact work. + */ +PSEUDOTYPE_DUMMY_INPUT_FUNC(anycompatiblemultirange); + +Datum +anycompatiblemultirange_out(PG_FUNCTION_ARGS) +{ + return multirange_out(fcinfo); +} + +/* + * void + * + * We support void_in so that PL functions can return VOID without any + * special hack in the PL handler. Whatever value the PL thinks it's + * returning will just be ignored. Conversely, void_out and void_send + * are needed so that "SELECT function_returning_void(...)" works. + */ +Datum +void_in(PG_FUNCTION_ARGS) +{ + PG_RETURN_VOID(); /* you were expecting something different? */ +} + +Datum +void_out(PG_FUNCTION_ARGS) +{ + PG_RETURN_CSTRING(pstrdup("")); +} + +Datum +void_recv(PG_FUNCTION_ARGS) +{ + /* + * Note that since we consume no bytes, an attempt to send anything but an + * empty string will result in an "invalid message format" error. + */ + PG_RETURN_VOID(); +} + +Datum +void_send(PG_FUNCTION_ARGS) +{ + StringInfoData buf; + + /* send an empty string */ + pq_begintypsend(&buf); + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} + +/* + * shell + * + * shell_in and shell_out are entered in pg_type for "shell" types + * (those not yet filled in). They should be unreachable, but we + * set them up just in case some code path tries to do I/O without + * having checked pg_type.typisdefined anywhere along the way. + */ +Datum +shell_in(PG_FUNCTION_ARGS) +{ + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot accept a value of a shell type"))); + + PG_RETURN_VOID(); /* keep compiler quiet */ +} + +Datum +shell_out(PG_FUNCTION_ARGS) +{ + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot display a value of a shell type"))); + + PG_RETURN_VOID(); /* keep compiler quiet */ +} + + +/* + * pg_node_tree + * + * pg_node_tree isn't really a pseudotype --- it's real enough to be a table + * column --- but it presently has no operations of its own, and disallows + * input too, so its I/O functions seem to fit here as much as anywhere. + * + * We must disallow input of pg_node_tree values because the SQL functions + * that operate on the type are not secure against malformed input. + * We do want to allow output, though. + */ +PSEUDOTYPE_DUMMY_INPUT_FUNC(pg_node_tree); +PSEUDOTYPE_DUMMY_RECEIVE_FUNC(pg_node_tree); + +Datum +pg_node_tree_out(PG_FUNCTION_ARGS) +{ + return textout(fcinfo); +} + +Datum +pg_node_tree_send(PG_FUNCTION_ARGS) +{ + return textsend(fcinfo); +} + +/* + * pg_ddl_command + * + * Like pg_node_tree, pg_ddl_command isn't really a pseudotype; it's here + * for the same reasons as that one. + * + * We don't have any good way to output this type directly, so punt + * for output as well as input. + */ +PSEUDOTYPE_DUMMY_IO_FUNCS(pg_ddl_command); +PSEUDOTYPE_DUMMY_BINARY_IO_FUNCS(pg_ddl_command); + + +/* + * Dummy I/O functions for various other pseudotypes. + */ +PSEUDOTYPE_DUMMY_IO_FUNCS(any); +PSEUDOTYPE_DUMMY_IO_FUNCS(trigger); +PSEUDOTYPE_DUMMY_IO_FUNCS(event_trigger); +PSEUDOTYPE_DUMMY_IO_FUNCS(language_handler); +PSEUDOTYPE_DUMMY_IO_FUNCS(fdw_handler); +PSEUDOTYPE_DUMMY_IO_FUNCS(table_am_handler); +PSEUDOTYPE_DUMMY_IO_FUNCS(index_am_handler); +PSEUDOTYPE_DUMMY_IO_FUNCS(tsm_handler); +PSEUDOTYPE_DUMMY_IO_FUNCS(internal); +PSEUDOTYPE_DUMMY_IO_FUNCS(anyelement); +PSEUDOTYPE_DUMMY_IO_FUNCS(anynonarray); +PSEUDOTYPE_DUMMY_IO_FUNCS(anycompatible); +PSEUDOTYPE_DUMMY_IO_FUNCS(anycompatiblenonarray); diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/quote.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/quote.c new file mode 100644 index 00000000000..f2f633befac --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/quote.c @@ -0,0 +1,132 @@ +/*------------------------------------------------------------------------- + * + * quote.c + * Functions for quoting identifiers and literals + * + * Portions Copyright (c) 2000-2023, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/utils/adt/quote.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "utils/builtins.h" +#include "varatt.h" + + +/* + * quote_ident - + * returns a properly quoted identifier + */ +Datum +quote_ident(PG_FUNCTION_ARGS) +{ + text *t = PG_GETARG_TEXT_PP(0); + const char *qstr; + char *str; + + str = text_to_cstring(t); + qstr = quote_identifier(str); + PG_RETURN_TEXT_P(cstring_to_text(qstr)); +} + +/* + * quote_literal_internal - + * helper function for quote_literal and quote_literal_cstr + * + * NOTE: think not to make this function's behavior change with + * standard_conforming_strings. We don't know where the result + * literal will be used, and so we must generate a result that + * will work with either setting. Take a look at what dblink + * uses this for before thinking you know better. + */ +static size_t +quote_literal_internal(char *dst, const char *src, size_t len) +{ + const char *s; + char *savedst = dst; + + for (s = src; s < src + len; s++) + { + if (*s == '\\') + { + *dst++ = ESCAPE_STRING_SYNTAX; + break; + } + } + + *dst++ = '\''; + while (len-- > 0) + { + if (SQL_STR_DOUBLE(*src, true)) + *dst++ = *src; + *dst++ = *src++; + } + *dst++ = '\''; + + return dst - savedst; +} + +/* + * quote_literal - + * returns a properly quoted literal + */ +Datum +quote_literal(PG_FUNCTION_ARGS) +{ + text *t = PG_GETARG_TEXT_PP(0); + text *result; + char *cp1; + char *cp2; + int len; + + len = VARSIZE_ANY_EXHDR(t); + /* We make a worst-case result area; wasting a little space is OK */ + result = (text *) palloc(len * 2 + 3 + VARHDRSZ); + + cp1 = VARDATA_ANY(t); + cp2 = VARDATA(result); + + SET_VARSIZE(result, VARHDRSZ + quote_literal_internal(cp2, cp1, len)); + + PG_RETURN_TEXT_P(result); +} + +/* + * quote_literal_cstr - + * returns a properly quoted literal + */ +char * +quote_literal_cstr(const char *rawstr) +{ + char *result; + int len; + int newlen; + + len = strlen(rawstr); + /* We make a worst-case result area; wasting a little space is OK */ + result = palloc(len * 2 + 3 + 1); + + newlen = quote_literal_internal(result, rawstr, len); + result[newlen] = '\0'; + + return result; +} + +/* + * quote_nullable - + * Returns a properly quoted literal, with null values returned + * as the text string 'NULL'. + */ +Datum +quote_nullable(PG_FUNCTION_ARGS) +{ + if (PG_ARGISNULL(0)) + PG_RETURN_TEXT_P(cstring_to_text("NULL")); + else + PG_RETURN_DATUM(DirectFunctionCall1(quote_literal, + PG_GETARG_DATUM(0))); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/rangetypes.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/rangetypes.c new file mode 100644 index 00000000000..24bad529239 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/rangetypes.c @@ -0,0 +1,2717 @@ +/*------------------------------------------------------------------------- + * + * rangetypes.c + * I/O functions, operators, and support functions for range types. + * + * The stored (serialized) format of a range value is: + * + * 4 bytes: varlena header + * 4 bytes: range type's OID + * Lower boundary value, if any, aligned according to subtype's typalign + * Upper boundary value, if any, aligned according to subtype's typalign + * 1 byte for flags + * + * This representation is chosen to avoid needing any padding before the + * lower boundary value, even when it requires double alignment. We can + * expect that the varlena header is presented to us on a suitably aligned + * boundary (possibly after detoasting), and then the lower boundary is too. + * Note that this means we can't work with a packed (short varlena header) + * value; we must detoast it first. + * + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/adt/rangetypes.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/tupmacs.h" +#include "common/hashfn.h" +#include "lib/stringinfo.h" +#include "libpq/pqformat.h" +#include "miscadmin.h" +#include "nodes/miscnodes.h" +#include "port/pg_bitutils.h" +#include "utils/builtins.h" +#include "utils/date.h" +#include "utils/lsyscache.h" +#include "utils/rangetypes.h" +#include "utils/timestamp.h" +#include "varatt.h" + + +/* fn_extra cache entry for one of the range I/O functions */ +typedef struct RangeIOData +{ + TypeCacheEntry *typcache; /* range type's typcache entry */ + FmgrInfo typioproc; /* element type's I/O function */ + Oid typioparam; /* element type's I/O parameter */ +} RangeIOData; + + +static RangeIOData *get_range_io_data(FunctionCallInfo fcinfo, Oid rngtypid, + IOFuncSelector func); +static char range_parse_flags(const char *flags_str); +static bool range_parse(const char *string, char *flags, char **lbound_str, + char **ubound_str, Node *escontext); +static const char *range_parse_bound(const char *string, const char *ptr, + char **bound_str, bool *infinite, + Node *escontext); +static char *range_deparse(char flags, const char *lbound_str, + const char *ubound_str); +static char *range_bound_escape(const char *value); +static Size datum_compute_size(Size data_length, Datum val, bool typbyval, + char typalign, int16 typlen, char typstorage); +static Pointer datum_write(Pointer ptr, Datum datum, bool typbyval, + char typalign, int16 typlen, char typstorage); + + +/* + *---------------------------------------------------------- + * I/O FUNCTIONS + *---------------------------------------------------------- + */ + +Datum +range_in(PG_FUNCTION_ARGS) +{ + char *input_str = PG_GETARG_CSTRING(0); + Oid rngtypoid = PG_GETARG_OID(1); + Oid typmod = PG_GETARG_INT32(2); + Node *escontext = fcinfo->context; + RangeType *range; + RangeIOData *cache; + char flags; + char *lbound_str; + char *ubound_str; + RangeBound lower; + RangeBound upper; + + check_stack_depth(); /* recurses when subtype is a range type */ + + cache = get_range_io_data(fcinfo, rngtypoid, IOFunc_input); + + /* parse */ + if (!range_parse(input_str, &flags, &lbound_str, &ubound_str, escontext)) + PG_RETURN_NULL(); + + /* call element type's input function */ + if (RANGE_HAS_LBOUND(flags)) + if (!InputFunctionCallSafe(&cache->typioproc, lbound_str, + cache->typioparam, typmod, + escontext, &lower.val)) + PG_RETURN_NULL(); + if (RANGE_HAS_UBOUND(flags)) + if (!InputFunctionCallSafe(&cache->typioproc, ubound_str, + cache->typioparam, typmod, + escontext, &upper.val)) + PG_RETURN_NULL(); + + lower.infinite = (flags & RANGE_LB_INF) != 0; + lower.inclusive = (flags & RANGE_LB_INC) != 0; + lower.lower = true; + upper.infinite = (flags & RANGE_UB_INF) != 0; + upper.inclusive = (flags & RANGE_UB_INC) != 0; + upper.lower = false; + + /* serialize and canonicalize */ + range = make_range(cache->typcache, &lower, &upper, + flags & RANGE_EMPTY, escontext); + + PG_RETURN_RANGE_P(range); +} + +Datum +range_out(PG_FUNCTION_ARGS) +{ + RangeType *range = PG_GETARG_RANGE_P(0); + char *output_str; + RangeIOData *cache; + char flags; + char *lbound_str = NULL; + char *ubound_str = NULL; + RangeBound lower; + RangeBound upper; + bool empty; + + check_stack_depth(); /* recurses when subtype is a range type */ + + cache = get_range_io_data(fcinfo, RangeTypeGetOid(range), IOFunc_output); + + /* deserialize */ + range_deserialize(cache->typcache, range, &lower, &upper, &empty); + flags = range_get_flags(range); + + /* call element type's output function */ + if (RANGE_HAS_LBOUND(flags)) + lbound_str = OutputFunctionCall(&cache->typioproc, lower.val); + if (RANGE_HAS_UBOUND(flags)) + ubound_str = OutputFunctionCall(&cache->typioproc, upper.val); + + /* construct result string */ + output_str = range_deparse(flags, lbound_str, ubound_str); + + PG_RETURN_CSTRING(output_str); +} + +/* + * Binary representation: The first byte is the flags, then the lower bound + * (if present), then the upper bound (if present). Each bound is represented + * by a 4-byte length header and the binary representation of that bound (as + * returned by a call to the send function for the subtype). + */ + +Datum +range_recv(PG_FUNCTION_ARGS) +{ + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + Oid rngtypoid = PG_GETARG_OID(1); + int32 typmod = PG_GETARG_INT32(2); + RangeType *range; + RangeIOData *cache; + char flags; + RangeBound lower; + RangeBound upper; + + check_stack_depth(); /* recurses when subtype is a range type */ + + cache = get_range_io_data(fcinfo, rngtypoid, IOFunc_receive); + + /* receive the flags... */ + flags = (unsigned char) pq_getmsgbyte(buf); + + /* + * Mask out any unsupported flags, particularly RANGE_xB_NULL which would + * confuse following tests. Note that range_serialize will take care of + * cleaning up any inconsistencies in the remaining flags. + */ + flags &= (RANGE_EMPTY | + RANGE_LB_INC | + RANGE_LB_INF | + RANGE_UB_INC | + RANGE_UB_INF); + + /* receive the bounds ... */ + if (RANGE_HAS_LBOUND(flags)) + { + uint32 bound_len = pq_getmsgint(buf, 4); + const char *bound_data = pq_getmsgbytes(buf, bound_len); + StringInfoData bound_buf; + + initStringInfo(&bound_buf); + appendBinaryStringInfo(&bound_buf, bound_data, bound_len); + + lower.val = ReceiveFunctionCall(&cache->typioproc, + &bound_buf, + cache->typioparam, + typmod); + pfree(bound_buf.data); + } + else + lower.val = (Datum) 0; + + if (RANGE_HAS_UBOUND(flags)) + { + uint32 bound_len = pq_getmsgint(buf, 4); + const char *bound_data = pq_getmsgbytes(buf, bound_len); + StringInfoData bound_buf; + + initStringInfo(&bound_buf); + appendBinaryStringInfo(&bound_buf, bound_data, bound_len); + + upper.val = ReceiveFunctionCall(&cache->typioproc, + &bound_buf, + cache->typioparam, + typmod); + pfree(bound_buf.data); + } + else + upper.val = (Datum) 0; + + pq_getmsgend(buf); + + /* finish constructing RangeBound representation */ + lower.infinite = (flags & RANGE_LB_INF) != 0; + lower.inclusive = (flags & RANGE_LB_INC) != 0; + lower.lower = true; + upper.infinite = (flags & RANGE_UB_INF) != 0; + upper.inclusive = (flags & RANGE_UB_INC) != 0; + upper.lower = false; + + /* serialize and canonicalize */ + range = make_range(cache->typcache, &lower, &upper, + flags & RANGE_EMPTY, NULL); + + PG_RETURN_RANGE_P(range); +} + +Datum +range_send(PG_FUNCTION_ARGS) +{ + RangeType *range = PG_GETARG_RANGE_P(0); + StringInfo buf = makeStringInfo(); + RangeIOData *cache; + char flags; + RangeBound lower; + RangeBound upper; + bool empty; + + check_stack_depth(); /* recurses when subtype is a range type */ + + cache = get_range_io_data(fcinfo, RangeTypeGetOid(range), IOFunc_send); + + /* deserialize */ + range_deserialize(cache->typcache, range, &lower, &upper, &empty); + flags = range_get_flags(range); + + /* construct output */ + pq_begintypsend(buf); + + pq_sendbyte(buf, flags); + + if (RANGE_HAS_LBOUND(flags)) + { + Datum bound = PointerGetDatum(SendFunctionCall(&cache->typioproc, + lower.val)); + uint32 bound_len = VARSIZE(bound) - VARHDRSZ; + char *bound_data = VARDATA(bound); + + pq_sendint32(buf, bound_len); + pq_sendbytes(buf, bound_data, bound_len); + } + + if (RANGE_HAS_UBOUND(flags)) + { + Datum bound = PointerGetDatum(SendFunctionCall(&cache->typioproc, + upper.val)); + uint32 bound_len = VARSIZE(bound) - VARHDRSZ; + char *bound_data = VARDATA(bound); + + pq_sendint32(buf, bound_len); + pq_sendbytes(buf, bound_data, bound_len); + } + + PG_RETURN_BYTEA_P(pq_endtypsend(buf)); +} + +/* + * get_range_io_data: get cached information needed for range type I/O + * + * The range I/O functions need a bit more cached info than other range + * functions, so they store a RangeIOData struct in fn_extra, not just a + * pointer to a type cache entry. + */ +static RangeIOData * +get_range_io_data(FunctionCallInfo fcinfo, Oid rngtypid, IOFuncSelector func) +{ + RangeIOData *cache = (RangeIOData *) fcinfo->flinfo->fn_extra; + + if (cache == NULL || cache->typcache->type_id != rngtypid) + { + int16 typlen; + bool typbyval; + char typalign; + char typdelim; + Oid typiofunc; + + cache = (RangeIOData *) MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, + sizeof(RangeIOData)); + cache->typcache = lookup_type_cache(rngtypid, TYPECACHE_RANGE_INFO); + if (cache->typcache->rngelemtype == NULL) + elog(ERROR, "type %u is not a range type", rngtypid); + + /* get_type_io_data does more than we need, but is convenient */ + get_type_io_data(cache->typcache->rngelemtype->type_id, + func, + &typlen, + &typbyval, + &typalign, + &typdelim, + &cache->typioparam, + &typiofunc); + + if (!OidIsValid(typiofunc)) + { + /* this could only happen for receive or send */ + if (func == IOFunc_receive) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("no binary input function available for type %s", + format_type_be(cache->typcache->rngelemtype->type_id)))); + else + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("no binary output function available for type %s", + format_type_be(cache->typcache->rngelemtype->type_id)))); + } + fmgr_info_cxt(typiofunc, &cache->typioproc, + fcinfo->flinfo->fn_mcxt); + + fcinfo->flinfo->fn_extra = (void *) cache; + } + + return cache; +} + + +/* + *---------------------------------------------------------- + * GENERIC FUNCTIONS + *---------------------------------------------------------- + */ + +/* Construct standard-form range value from two arguments */ +Datum +range_constructor2(PG_FUNCTION_ARGS) +{ + Datum arg1 = PG_GETARG_DATUM(0); + Datum arg2 = PG_GETARG_DATUM(1); + Oid rngtypid = get_fn_expr_rettype(fcinfo->flinfo); + RangeType *range; + TypeCacheEntry *typcache; + RangeBound lower; + RangeBound upper; + + typcache = range_get_typcache(fcinfo, rngtypid); + + lower.val = PG_ARGISNULL(0) ? (Datum) 0 : arg1; + lower.infinite = PG_ARGISNULL(0); + lower.inclusive = true; + lower.lower = true; + + upper.val = PG_ARGISNULL(1) ? (Datum) 0 : arg2; + upper.infinite = PG_ARGISNULL(1); + upper.inclusive = false; + upper.lower = false; + + range = make_range(typcache, &lower, &upper, false, NULL); + + PG_RETURN_RANGE_P(range); +} + +/* Construct general range value from three arguments */ +Datum +range_constructor3(PG_FUNCTION_ARGS) +{ + Datum arg1 = PG_GETARG_DATUM(0); + Datum arg2 = PG_GETARG_DATUM(1); + Oid rngtypid = get_fn_expr_rettype(fcinfo->flinfo); + RangeType *range; + TypeCacheEntry *typcache; + RangeBound lower; + RangeBound upper; + char flags; + + typcache = range_get_typcache(fcinfo, rngtypid); + + if (PG_ARGISNULL(2)) + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("range constructor flags argument must not be null"))); + + flags = range_parse_flags(text_to_cstring(PG_GETARG_TEXT_PP(2))); + + lower.val = PG_ARGISNULL(0) ? (Datum) 0 : arg1; + lower.infinite = PG_ARGISNULL(0); + lower.inclusive = (flags & RANGE_LB_INC) != 0; + lower.lower = true; + + upper.val = PG_ARGISNULL(1) ? (Datum) 0 : arg2; + upper.infinite = PG_ARGISNULL(1); + upper.inclusive = (flags & RANGE_UB_INC) != 0; + upper.lower = false; + + range = make_range(typcache, &lower, &upper, false, NULL); + + PG_RETURN_RANGE_P(range); +} + + +/* range -> subtype functions */ + +/* extract lower bound value */ +Datum +range_lower(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE_P(0); + TypeCacheEntry *typcache; + RangeBound lower; + RangeBound upper; + bool empty; + + typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1)); + + range_deserialize(typcache, r1, &lower, &upper, &empty); + + /* Return NULL if there's no finite lower bound */ + if (empty || lower.infinite) + PG_RETURN_NULL(); + + PG_RETURN_DATUM(lower.val); +} + +/* extract upper bound value */ +Datum +range_upper(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE_P(0); + TypeCacheEntry *typcache; + RangeBound lower; + RangeBound upper; + bool empty; + + typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1)); + + range_deserialize(typcache, r1, &lower, &upper, &empty); + + /* Return NULL if there's no finite upper bound */ + if (empty || upper.infinite) + PG_RETURN_NULL(); + + PG_RETURN_DATUM(upper.val); +} + + +/* range -> bool functions */ + +/* is range empty? */ +Datum +range_empty(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE_P(0); + char flags = range_get_flags(r1); + + PG_RETURN_BOOL(flags & RANGE_EMPTY); +} + +/* is lower bound inclusive? */ +Datum +range_lower_inc(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE_P(0); + char flags = range_get_flags(r1); + + PG_RETURN_BOOL(flags & RANGE_LB_INC); +} + +/* is upper bound inclusive? */ +Datum +range_upper_inc(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE_P(0); + char flags = range_get_flags(r1); + + PG_RETURN_BOOL(flags & RANGE_UB_INC); +} + +/* is lower bound infinite? */ +Datum +range_lower_inf(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE_P(0); + char flags = range_get_flags(r1); + + PG_RETURN_BOOL(flags & RANGE_LB_INF); +} + +/* is upper bound infinite? */ +Datum +range_upper_inf(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE_P(0); + char flags = range_get_flags(r1); + + PG_RETURN_BOOL(flags & RANGE_UB_INF); +} + + +/* range, element -> bool functions */ + +/* contains? */ +Datum +range_contains_elem(PG_FUNCTION_ARGS) +{ + RangeType *r = PG_GETARG_RANGE_P(0); + Datum val = PG_GETARG_DATUM(1); + TypeCacheEntry *typcache; + + typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r)); + + PG_RETURN_BOOL(range_contains_elem_internal(typcache, r, val)); +} + +/* contained by? */ +Datum +elem_contained_by_range(PG_FUNCTION_ARGS) +{ + Datum val = PG_GETARG_DATUM(0); + RangeType *r = PG_GETARG_RANGE_P(1); + TypeCacheEntry *typcache; + + typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r)); + + PG_RETURN_BOOL(range_contains_elem_internal(typcache, r, val)); +} + + +/* range, range -> bool functions */ + +/* equality (internal version) */ +bool +range_eq_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2) +{ + RangeBound lower1, + lower2; + RangeBound upper1, + upper2; + bool empty1, + empty2; + + /* Different types should be prevented by ANYRANGE matching rules */ + if (RangeTypeGetOid(r1) != RangeTypeGetOid(r2)) + elog(ERROR, "range types do not match"); + + range_deserialize(typcache, r1, &lower1, &upper1, &empty1); + range_deserialize(typcache, r2, &lower2, &upper2, &empty2); + + if (empty1 && empty2) + return true; + if (empty1 != empty2) + return false; + + if (range_cmp_bounds(typcache, &lower1, &lower2) != 0) + return false; + + if (range_cmp_bounds(typcache, &upper1, &upper2) != 0) + return false; + + return true; +} + +/* equality */ +Datum +range_eq(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE_P(0); + RangeType *r2 = PG_GETARG_RANGE_P(1); + TypeCacheEntry *typcache; + + typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1)); + + PG_RETURN_BOOL(range_eq_internal(typcache, r1, r2)); +} + +/* inequality (internal version) */ +bool +range_ne_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2) +{ + return (!range_eq_internal(typcache, r1, r2)); +} + +/* inequality */ +Datum +range_ne(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE_P(0); + RangeType *r2 = PG_GETARG_RANGE_P(1); + TypeCacheEntry *typcache; + + typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1)); + + PG_RETURN_BOOL(range_ne_internal(typcache, r1, r2)); +} + +/* contains? */ +Datum +range_contains(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE_P(0); + RangeType *r2 = PG_GETARG_RANGE_P(1); + TypeCacheEntry *typcache; + + typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1)); + + PG_RETURN_BOOL(range_contains_internal(typcache, r1, r2)); +} + +/* contained by? */ +Datum +range_contained_by(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE_P(0); + RangeType *r2 = PG_GETARG_RANGE_P(1); + TypeCacheEntry *typcache; + + typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1)); + + PG_RETURN_BOOL(range_contained_by_internal(typcache, r1, r2)); +} + +/* strictly left of? (internal version) */ +bool +range_before_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2) +{ + RangeBound lower1, + lower2; + RangeBound upper1, + upper2; + bool empty1, + empty2; + + /* Different types should be prevented by ANYRANGE matching rules */ + if (RangeTypeGetOid(r1) != RangeTypeGetOid(r2)) + elog(ERROR, "range types do not match"); + + range_deserialize(typcache, r1, &lower1, &upper1, &empty1); + range_deserialize(typcache, r2, &lower2, &upper2, &empty2); + + /* An empty range is neither before nor after any other range */ + if (empty1 || empty2) + return false; + + return (range_cmp_bounds(typcache, &upper1, &lower2) < 0); +} + +/* strictly left of? */ +Datum +range_before(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE_P(0); + RangeType *r2 = PG_GETARG_RANGE_P(1); + TypeCacheEntry *typcache; + + typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1)); + + PG_RETURN_BOOL(range_before_internal(typcache, r1, r2)); +} + +/* strictly right of? (internal version) */ +bool +range_after_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2) +{ + RangeBound lower1, + lower2; + RangeBound upper1, + upper2; + bool empty1, + empty2; + + /* Different types should be prevented by ANYRANGE matching rules */ + if (RangeTypeGetOid(r1) != RangeTypeGetOid(r2)) + elog(ERROR, "range types do not match"); + + range_deserialize(typcache, r1, &lower1, &upper1, &empty1); + range_deserialize(typcache, r2, &lower2, &upper2, &empty2); + + /* An empty range is neither before nor after any other range */ + if (empty1 || empty2) + return false; + + return (range_cmp_bounds(typcache, &lower1, &upper2) > 0); +} + +/* strictly right of? */ +Datum +range_after(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE_P(0); + RangeType *r2 = PG_GETARG_RANGE_P(1); + TypeCacheEntry *typcache; + + typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1)); + + PG_RETURN_BOOL(range_after_internal(typcache, r1, r2)); +} + +/* + * Check if two bounds A and B are "adjacent", where A is an upper bound and B + * is a lower bound. For the bounds to be adjacent, each subtype value must + * satisfy strictly one of the bounds: there are no values which satisfy both + * bounds (i.e. less than A and greater than B); and there are no values which + * satisfy neither bound (i.e. greater than A and less than B). + * + * For discrete ranges, we rely on the canonicalization function to see if A..B + * normalizes to empty. (If there is no canonicalization function, it's + * impossible for such a range to normalize to empty, so we needn't bother to + * try.) + * + * If A == B, the ranges are adjacent only if the bounds have different + * inclusive flags (i.e., exactly one of the ranges includes the common + * boundary point). + * + * And if A > B then the ranges are not adjacent in this order. + */ +bool +bounds_adjacent(TypeCacheEntry *typcache, RangeBound boundA, RangeBound boundB) +{ + int cmp; + + Assert(!boundA.lower && boundB.lower); + + cmp = range_cmp_bound_values(typcache, &boundA, &boundB); + if (cmp < 0) + { + RangeType *r; + + /* + * Bounds do not overlap; see if there are points in between. + */ + + /* in a continuous subtype, there are assumed to be points between */ + if (!OidIsValid(typcache->rng_canonical_finfo.fn_oid)) + return false; + + /* + * The bounds are of a discrete range type; so make a range A..B and + * see if it's empty. + */ + + /* flip the inclusion flags */ + boundA.inclusive = !boundA.inclusive; + boundB.inclusive = !boundB.inclusive; + /* change upper/lower labels to avoid Assert failures */ + boundA.lower = true; + boundB.lower = false; + r = make_range(typcache, &boundA, &boundB, false, NULL); + return RangeIsEmpty(r); + } + else if (cmp == 0) + return boundA.inclusive != boundB.inclusive; + else + return false; /* bounds overlap */ +} + +/* adjacent to (but not overlapping)? (internal version) */ +bool +range_adjacent_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2) +{ + RangeBound lower1, + lower2; + RangeBound upper1, + upper2; + bool empty1, + empty2; + + /* Different types should be prevented by ANYRANGE matching rules */ + if (RangeTypeGetOid(r1) != RangeTypeGetOid(r2)) + elog(ERROR, "range types do not match"); + + range_deserialize(typcache, r1, &lower1, &upper1, &empty1); + range_deserialize(typcache, r2, &lower2, &upper2, &empty2); + + /* An empty range is not adjacent to any other range */ + if (empty1 || empty2) + return false; + + /* + * Given two ranges A..B and C..D, the ranges are adjacent if and only if + * B is adjacent to C, or D is adjacent to A. + */ + return (bounds_adjacent(typcache, upper1, lower2) || + bounds_adjacent(typcache, upper2, lower1)); +} + +/* adjacent to (but not overlapping)? */ +Datum +range_adjacent(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE_P(0); + RangeType *r2 = PG_GETARG_RANGE_P(1); + TypeCacheEntry *typcache; + + typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1)); + + PG_RETURN_BOOL(range_adjacent_internal(typcache, r1, r2)); +} + +/* overlaps? (internal version) */ +bool +range_overlaps_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2) +{ + RangeBound lower1, + lower2; + RangeBound upper1, + upper2; + bool empty1, + empty2; + + /* Different types should be prevented by ANYRANGE matching rules */ + if (RangeTypeGetOid(r1) != RangeTypeGetOid(r2)) + elog(ERROR, "range types do not match"); + + range_deserialize(typcache, r1, &lower1, &upper1, &empty1); + range_deserialize(typcache, r2, &lower2, &upper2, &empty2); + + /* An empty range does not overlap any other range */ + if (empty1 || empty2) + return false; + + if (range_cmp_bounds(typcache, &lower1, &lower2) >= 0 && + range_cmp_bounds(typcache, &lower1, &upper2) <= 0) + return true; + + if (range_cmp_bounds(typcache, &lower2, &lower1) >= 0 && + range_cmp_bounds(typcache, &lower2, &upper1) <= 0) + return true; + + return false; +} + +/* overlaps? */ +Datum +range_overlaps(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE_P(0); + RangeType *r2 = PG_GETARG_RANGE_P(1); + TypeCacheEntry *typcache; + + typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1)); + + PG_RETURN_BOOL(range_overlaps_internal(typcache, r1, r2)); +} + +/* does not extend to right of? (internal version) */ +bool +range_overleft_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2) +{ + RangeBound lower1, + lower2; + RangeBound upper1, + upper2; + bool empty1, + empty2; + + /* Different types should be prevented by ANYRANGE matching rules */ + if (RangeTypeGetOid(r1) != RangeTypeGetOid(r2)) + elog(ERROR, "range types do not match"); + + range_deserialize(typcache, r1, &lower1, &upper1, &empty1); + range_deserialize(typcache, r2, &lower2, &upper2, &empty2); + + /* An empty range is neither before nor after any other range */ + if (empty1 || empty2) + return false; + + if (range_cmp_bounds(typcache, &upper1, &upper2) <= 0) + return true; + + return false; +} + +/* does not extend to right of? */ +Datum +range_overleft(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE_P(0); + RangeType *r2 = PG_GETARG_RANGE_P(1); + TypeCacheEntry *typcache; + + typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1)); + + PG_RETURN_BOOL(range_overleft_internal(typcache, r1, r2)); +} + +/* does not extend to left of? (internal version) */ +bool +range_overright_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2) +{ + RangeBound lower1, + lower2; + RangeBound upper1, + upper2; + bool empty1, + empty2; + + /* Different types should be prevented by ANYRANGE matching rules */ + if (RangeTypeGetOid(r1) != RangeTypeGetOid(r2)) + elog(ERROR, "range types do not match"); + + range_deserialize(typcache, r1, &lower1, &upper1, &empty1); + range_deserialize(typcache, r2, &lower2, &upper2, &empty2); + + /* An empty range is neither before nor after any other range */ + if (empty1 || empty2) + return false; + + if (range_cmp_bounds(typcache, &lower1, &lower2) >= 0) + return true; + + return false; +} + +/* does not extend to left of? */ +Datum +range_overright(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE_P(0); + RangeType *r2 = PG_GETARG_RANGE_P(1); + TypeCacheEntry *typcache; + + typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1)); + + PG_RETURN_BOOL(range_overright_internal(typcache, r1, r2)); +} + + +/* range, range -> range functions */ + +/* set difference */ +Datum +range_minus(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE_P(0); + RangeType *r2 = PG_GETARG_RANGE_P(1); + RangeType *ret; + TypeCacheEntry *typcache; + + /* Different types should be prevented by ANYRANGE matching rules */ + if (RangeTypeGetOid(r1) != RangeTypeGetOid(r2)) + elog(ERROR, "range types do not match"); + + typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1)); + + ret = range_minus_internal(typcache, r1, r2); + if (ret) + PG_RETURN_RANGE_P(ret); + else + PG_RETURN_NULL(); +} + +RangeType * +range_minus_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2) +{ + RangeBound lower1, + lower2; + RangeBound upper1, + upper2; + bool empty1, + empty2; + int cmp_l1l2, + cmp_l1u2, + cmp_u1l2, + cmp_u1u2; + + range_deserialize(typcache, r1, &lower1, &upper1, &empty1); + range_deserialize(typcache, r2, &lower2, &upper2, &empty2); + + /* if either is empty, r1 is the correct answer */ + if (empty1 || empty2) + return r1; + + cmp_l1l2 = range_cmp_bounds(typcache, &lower1, &lower2); + cmp_l1u2 = range_cmp_bounds(typcache, &lower1, &upper2); + cmp_u1l2 = range_cmp_bounds(typcache, &upper1, &lower2); + cmp_u1u2 = range_cmp_bounds(typcache, &upper1, &upper2); + + if (cmp_l1l2 < 0 && cmp_u1u2 > 0) + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("result of range difference would not be contiguous"))); + + if (cmp_l1u2 > 0 || cmp_u1l2 < 0) + return r1; + + if (cmp_l1l2 >= 0 && cmp_u1u2 <= 0) + return make_empty_range(typcache); + + if (cmp_l1l2 <= 0 && cmp_u1l2 >= 0 && cmp_u1u2 <= 0) + { + lower2.inclusive = !lower2.inclusive; + lower2.lower = false; /* it will become the upper bound */ + return make_range(typcache, &lower1, &lower2, false, NULL); + } + + if (cmp_l1l2 >= 0 && cmp_u1u2 >= 0 && cmp_l1u2 <= 0) + { + upper2.inclusive = !upper2.inclusive; + upper2.lower = true; /* it will become the lower bound */ + return make_range(typcache, &upper2, &upper1, false, NULL); + } + + elog(ERROR, "unexpected case in range_minus"); + return NULL; +} + +/* + * Set union. If strict is true, it is an error that the two input ranges + * are not adjacent or overlapping. + */ +RangeType * +range_union_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2, + bool strict) +{ + RangeBound lower1, + lower2; + RangeBound upper1, + upper2; + bool empty1, + empty2; + RangeBound *result_lower; + RangeBound *result_upper; + + /* Different types should be prevented by ANYRANGE matching rules */ + if (RangeTypeGetOid(r1) != RangeTypeGetOid(r2)) + elog(ERROR, "range types do not match"); + + range_deserialize(typcache, r1, &lower1, &upper1, &empty1); + range_deserialize(typcache, r2, &lower2, &upper2, &empty2); + + /* if either is empty, the other is the correct answer */ + if (empty1) + return r2; + if (empty2) + return r1; + + if (strict && + !DatumGetBool(range_overlaps_internal(typcache, r1, r2)) && + !DatumGetBool(range_adjacent_internal(typcache, r1, r2))) + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("result of range union would not be contiguous"))); + + if (range_cmp_bounds(typcache, &lower1, &lower2) < 0) + result_lower = &lower1; + else + result_lower = &lower2; + + if (range_cmp_bounds(typcache, &upper1, &upper2) > 0) + result_upper = &upper1; + else + result_upper = &upper2; + + return make_range(typcache, result_lower, result_upper, false, NULL); +} + +Datum +range_union(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE_P(0); + RangeType *r2 = PG_GETARG_RANGE_P(1); + TypeCacheEntry *typcache; + + typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1)); + + PG_RETURN_RANGE_P(range_union_internal(typcache, r1, r2, true)); +} + +/* + * range merge: like set union, except also allow and account for non-adjacent + * input ranges. + */ +Datum +range_merge(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE_P(0); + RangeType *r2 = PG_GETARG_RANGE_P(1); + TypeCacheEntry *typcache; + + typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1)); + + PG_RETURN_RANGE_P(range_union_internal(typcache, r1, r2, false)); +} + +/* set intersection */ +Datum +range_intersect(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE_P(0); + RangeType *r2 = PG_GETARG_RANGE_P(1); + TypeCacheEntry *typcache; + + /* Different types should be prevented by ANYRANGE matching rules */ + if (RangeTypeGetOid(r1) != RangeTypeGetOid(r2)) + elog(ERROR, "range types do not match"); + + typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1)); + + PG_RETURN_RANGE_P(range_intersect_internal(typcache, r1, r2)); +} + +RangeType * +range_intersect_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2) +{ + RangeBound lower1, + lower2; + RangeBound upper1, + upper2; + bool empty1, + empty2; + RangeBound *result_lower; + RangeBound *result_upper; + + range_deserialize(typcache, r1, &lower1, &upper1, &empty1); + range_deserialize(typcache, r2, &lower2, &upper2, &empty2); + + if (empty1 || empty2 || !range_overlaps_internal(typcache, r1, r2)) + return make_empty_range(typcache); + + if (range_cmp_bounds(typcache, &lower1, &lower2) >= 0) + result_lower = &lower1; + else + result_lower = &lower2; + + if (range_cmp_bounds(typcache, &upper1, &upper2) <= 0) + result_upper = &upper1; + else + result_upper = &upper2; + + return make_range(typcache, result_lower, result_upper, false, NULL); +} + +/* range, range -> range, range functions */ + +/* + * range_split_internal - if r2 intersects the middle of r1, leaving non-empty + * ranges on both sides, then return true and set output1 and output2 to the + * results of r1 - r2 (in order). Otherwise return false and don't set output1 + * or output2. Neither input range should be empty. + */ +bool +range_split_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2, + RangeType **output1, RangeType **output2) +{ + RangeBound lower1, + lower2; + RangeBound upper1, + upper2; + bool empty1, + empty2; + + range_deserialize(typcache, r1, &lower1, &upper1, &empty1); + range_deserialize(typcache, r2, &lower2, &upper2, &empty2); + + if (range_cmp_bounds(typcache, &lower1, &lower2) < 0 && + range_cmp_bounds(typcache, &upper1, &upper2) > 0) + { + /* + * Need to invert inclusive/exclusive for the lower2 and upper2 + * points. They can't be infinite though. We're allowed to overwrite + * these RangeBounds since they only exist locally. + */ + lower2.inclusive = !lower2.inclusive; + lower2.lower = false; + upper2.inclusive = !upper2.inclusive; + upper2.lower = true; + + *output1 = make_range(typcache, &lower1, &lower2, false, NULL); + *output2 = make_range(typcache, &upper2, &upper1, false, NULL); + return true; + } + + return false; +} + +/* range -> range aggregate functions */ + +Datum +range_intersect_agg_transfn(PG_FUNCTION_ARGS) +{ + MemoryContext aggContext; + Oid rngtypoid; + TypeCacheEntry *typcache; + RangeType *result; + RangeType *current; + + if (!AggCheckCallContext(fcinfo, &aggContext)) + elog(ERROR, "range_intersect_agg_transfn called in non-aggregate context"); + + rngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1); + if (!type_is_range(rngtypoid)) + elog(ERROR, "range_intersect_agg must be called with a range"); + + typcache = range_get_typcache(fcinfo, rngtypoid); + + /* strictness ensures these are non-null */ + result = PG_GETARG_RANGE_P(0); + current = PG_GETARG_RANGE_P(1); + + result = range_intersect_internal(typcache, result, current); + PG_RETURN_RANGE_P(result); +} + + +/* Btree support */ + +/* btree comparator */ +Datum +range_cmp(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE_P(0); + RangeType *r2 = PG_GETARG_RANGE_P(1); + TypeCacheEntry *typcache; + RangeBound lower1, + lower2; + RangeBound upper1, + upper2; + bool empty1, + empty2; + int cmp; + + check_stack_depth(); /* recurses when subtype is a range type */ + + /* Different types should be prevented by ANYRANGE matching rules */ + if (RangeTypeGetOid(r1) != RangeTypeGetOid(r2)) + elog(ERROR, "range types do not match"); + + typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1)); + + range_deserialize(typcache, r1, &lower1, &upper1, &empty1); + range_deserialize(typcache, r2, &lower2, &upper2, &empty2); + + /* For b-tree use, empty ranges sort before all else */ + if (empty1 && empty2) + cmp = 0; + else if (empty1) + cmp = -1; + else if (empty2) + cmp = 1; + else + { + cmp = range_cmp_bounds(typcache, &lower1, &lower2); + if (cmp == 0) + cmp = range_cmp_bounds(typcache, &upper1, &upper2); + } + + PG_FREE_IF_COPY(r1, 0); + PG_FREE_IF_COPY(r2, 1); + + PG_RETURN_INT32(cmp); +} + +/* inequality operators using the range_cmp function */ +Datum +range_lt(PG_FUNCTION_ARGS) +{ + int cmp = range_cmp(fcinfo); + + PG_RETURN_BOOL(cmp < 0); +} + +Datum +range_le(PG_FUNCTION_ARGS) +{ + int cmp = range_cmp(fcinfo); + + PG_RETURN_BOOL(cmp <= 0); +} + +Datum +range_ge(PG_FUNCTION_ARGS) +{ + int cmp = range_cmp(fcinfo); + + PG_RETURN_BOOL(cmp >= 0); +} + +Datum +range_gt(PG_FUNCTION_ARGS) +{ + int cmp = range_cmp(fcinfo); + + PG_RETURN_BOOL(cmp > 0); +} + +/* Hash support */ + +/* hash a range value */ +Datum +hash_range(PG_FUNCTION_ARGS) +{ + RangeType *r = PG_GETARG_RANGE_P(0); + uint32 result; + TypeCacheEntry *typcache; + TypeCacheEntry *scache; + RangeBound lower; + RangeBound upper; + bool empty; + char flags; + uint32 lower_hash; + uint32 upper_hash; + + check_stack_depth(); /* recurses when subtype is a range type */ + + typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r)); + + /* deserialize */ + range_deserialize(typcache, r, &lower, &upper, &empty); + flags = range_get_flags(r); + + /* + * Look up the element type's hash function, if not done already. + */ + scache = typcache->rngelemtype; + if (!OidIsValid(scache->hash_proc_finfo.fn_oid)) + { + scache = lookup_type_cache(scache->type_id, TYPECACHE_HASH_PROC_FINFO); + if (!OidIsValid(scache->hash_proc_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify a hash function for type %s", + format_type_be(scache->type_id)))); + } + + /* + * Apply the hash function to each bound. + */ + if (RANGE_HAS_LBOUND(flags)) + lower_hash = DatumGetUInt32(FunctionCall1Coll(&scache->hash_proc_finfo, + typcache->rng_collation, + lower.val)); + else + lower_hash = 0; + + if (RANGE_HAS_UBOUND(flags)) + upper_hash = DatumGetUInt32(FunctionCall1Coll(&scache->hash_proc_finfo, + typcache->rng_collation, + upper.val)); + else + upper_hash = 0; + + /* Merge hashes of flags and bounds */ + result = hash_uint32((uint32) flags); + result ^= lower_hash; + result = pg_rotate_left32(result, 1); + result ^= upper_hash; + + PG_RETURN_INT32(result); +} + +/* + * Returns 64-bit value by hashing a value to a 64-bit value, with a seed. + * Otherwise, similar to hash_range. + */ +Datum +hash_range_extended(PG_FUNCTION_ARGS) +{ + RangeType *r = PG_GETARG_RANGE_P(0); + Datum seed = PG_GETARG_DATUM(1); + uint64 result; + TypeCacheEntry *typcache; + TypeCacheEntry *scache; + RangeBound lower; + RangeBound upper; + bool empty; + char flags; + uint64 lower_hash; + uint64 upper_hash; + + check_stack_depth(); + + typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r)); + + range_deserialize(typcache, r, &lower, &upper, &empty); + flags = range_get_flags(r); + + scache = typcache->rngelemtype; + if (!OidIsValid(scache->hash_extended_proc_finfo.fn_oid)) + { + scache = lookup_type_cache(scache->type_id, + TYPECACHE_HASH_EXTENDED_PROC_FINFO); + if (!OidIsValid(scache->hash_extended_proc_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify a hash function for type %s", + format_type_be(scache->type_id)))); + } + + if (RANGE_HAS_LBOUND(flags)) + lower_hash = DatumGetUInt64(FunctionCall2Coll(&scache->hash_extended_proc_finfo, + typcache->rng_collation, + lower.val, + seed)); + else + lower_hash = 0; + + if (RANGE_HAS_UBOUND(flags)) + upper_hash = DatumGetUInt64(FunctionCall2Coll(&scache->hash_extended_proc_finfo, + typcache->rng_collation, + upper.val, + seed)); + else + upper_hash = 0; + + /* Merge hashes of flags and bounds */ + result = DatumGetUInt64(hash_uint32_extended((uint32) flags, + DatumGetInt64(seed))); + result ^= lower_hash; + result = ROTATE_HIGH_AND_LOW_32BITS(result); + result ^= upper_hash; + + PG_RETURN_UINT64(result); +} + +/* + *---------------------------------------------------------- + * CANONICAL FUNCTIONS + * + * Functions for specific built-in range types. + *---------------------------------------------------------- + */ + +Datum +int4range_canonical(PG_FUNCTION_ARGS) +{ + RangeType *r = PG_GETARG_RANGE_P(0); + Node *escontext = fcinfo->context; + TypeCacheEntry *typcache; + RangeBound lower; + RangeBound upper; + bool empty; + + typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r)); + + range_deserialize(typcache, r, &lower, &upper, &empty); + + if (empty) + PG_RETURN_RANGE_P(r); + + if (!lower.infinite && !lower.inclusive) + { + int32 bnd = DatumGetInt32(lower.val); + + /* Handle possible overflow manually */ + if (unlikely(bnd == PG_INT32_MAX)) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("integer out of range"))); + lower.val = Int32GetDatum(bnd + 1); + lower.inclusive = true; + } + + if (!upper.infinite && upper.inclusive) + { + int32 bnd = DatumGetInt32(upper.val); + + /* Handle possible overflow manually */ + if (unlikely(bnd == PG_INT32_MAX)) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("integer out of range"))); + upper.val = Int32GetDatum(bnd + 1); + upper.inclusive = false; + } + + PG_RETURN_RANGE_P(range_serialize(typcache, &lower, &upper, + false, escontext)); +} + +Datum +int8range_canonical(PG_FUNCTION_ARGS) +{ + RangeType *r = PG_GETARG_RANGE_P(0); + Node *escontext = fcinfo->context; + TypeCacheEntry *typcache; + RangeBound lower; + RangeBound upper; + bool empty; + + typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r)); + + range_deserialize(typcache, r, &lower, &upper, &empty); + + if (empty) + PG_RETURN_RANGE_P(r); + + if (!lower.infinite && !lower.inclusive) + { + int64 bnd = DatumGetInt64(lower.val); + + /* Handle possible overflow manually */ + if (unlikely(bnd == PG_INT64_MAX)) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("bigint out of range"))); + lower.val = Int64GetDatum(bnd + 1); + lower.inclusive = true; + } + + if (!upper.infinite && upper.inclusive) + { + int64 bnd = DatumGetInt64(upper.val); + + /* Handle possible overflow manually */ + if (unlikely(bnd == PG_INT64_MAX)) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("bigint out of range"))); + upper.val = Int64GetDatum(bnd + 1); + upper.inclusive = false; + } + + PG_RETURN_RANGE_P(range_serialize(typcache, &lower, &upper, + false, escontext)); +} + +Datum +daterange_canonical(PG_FUNCTION_ARGS) +{ + RangeType *r = PG_GETARG_RANGE_P(0); + Node *escontext = fcinfo->context; + TypeCacheEntry *typcache; + RangeBound lower; + RangeBound upper; + bool empty; + + typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r)); + + range_deserialize(typcache, r, &lower, &upper, &empty); + + if (empty) + PG_RETURN_RANGE_P(r); + + if (!lower.infinite && !DATE_NOT_FINITE(DatumGetDateADT(lower.val)) && + !lower.inclusive) + { + DateADT bnd = DatumGetDateADT(lower.val); + + /* Check for overflow -- note we already eliminated PG_INT32_MAX */ + bnd++; + if (unlikely(!IS_VALID_DATE(bnd))) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("date out of range"))); + lower.val = DateADTGetDatum(bnd); + lower.inclusive = true; + } + + if (!upper.infinite && !DATE_NOT_FINITE(DatumGetDateADT(upper.val)) && + upper.inclusive) + { + DateADT bnd = DatumGetDateADT(upper.val); + + /* Check for overflow -- note we already eliminated PG_INT32_MAX */ + bnd++; + if (unlikely(!IS_VALID_DATE(bnd))) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("date out of range"))); + upper.val = DateADTGetDatum(bnd); + upper.inclusive = false; + } + + PG_RETURN_RANGE_P(range_serialize(typcache, &lower, &upper, + false, escontext)); +} + +/* + *---------------------------------------------------------- + * SUBTYPE_DIFF FUNCTIONS + * + * Functions for specific built-in range types. + * + * Note that subtype_diff does return the difference, not the absolute value + * of the difference, and it must take care to avoid overflow. + * (numrange_subdiff is at some risk there ...) + *---------------------------------------------------------- + */ + +Datum +int4range_subdiff(PG_FUNCTION_ARGS) +{ + int32 v1 = PG_GETARG_INT32(0); + int32 v2 = PG_GETARG_INT32(1); + + PG_RETURN_FLOAT8((float8) v1 - (float8) v2); +} + +Datum +int8range_subdiff(PG_FUNCTION_ARGS) +{ + int64 v1 = PG_GETARG_INT64(0); + int64 v2 = PG_GETARG_INT64(1); + + PG_RETURN_FLOAT8((float8) v1 - (float8) v2); +} + +Datum +numrange_subdiff(PG_FUNCTION_ARGS) +{ + Datum v1 = PG_GETARG_DATUM(0); + Datum v2 = PG_GETARG_DATUM(1); + Datum numresult; + float8 floatresult; + + numresult = DirectFunctionCall2(numeric_sub, v1, v2); + + floatresult = DatumGetFloat8(DirectFunctionCall1(numeric_float8, + numresult)); + + PG_RETURN_FLOAT8(floatresult); +} + +Datum +daterange_subdiff(PG_FUNCTION_ARGS) +{ + int32 v1 = PG_GETARG_INT32(0); + int32 v2 = PG_GETARG_INT32(1); + + PG_RETURN_FLOAT8((float8) v1 - (float8) v2); +} + +Datum +tsrange_subdiff(PG_FUNCTION_ARGS) +{ + Timestamp v1 = PG_GETARG_TIMESTAMP(0); + Timestamp v2 = PG_GETARG_TIMESTAMP(1); + float8 result; + + result = ((float8) v1 - (float8) v2) / USECS_PER_SEC; + PG_RETURN_FLOAT8(result); +} + +Datum +tstzrange_subdiff(PG_FUNCTION_ARGS) +{ + Timestamp v1 = PG_GETARG_TIMESTAMP(0); + Timestamp v2 = PG_GETARG_TIMESTAMP(1); + float8 result; + + result = ((float8) v1 - (float8) v2) / USECS_PER_SEC; + PG_RETURN_FLOAT8(result); +} + +/* + *---------------------------------------------------------- + * SUPPORT FUNCTIONS + * + * These functions aren't in pg_proc, but are useful for + * defining new generic range functions in C. + *---------------------------------------------------------- + */ + +/* + * range_get_typcache: get cached information about a range type + * + * This is for use by range-related functions that follow the convention + * of using the fn_extra field as a pointer to the type cache entry for + * the range type. Functions that need to cache more information than + * that must fend for themselves. + */ +TypeCacheEntry * +range_get_typcache(FunctionCallInfo fcinfo, Oid rngtypid) +{ + TypeCacheEntry *typcache = (TypeCacheEntry *) fcinfo->flinfo->fn_extra; + + if (typcache == NULL || + typcache->type_id != rngtypid) + { + typcache = lookup_type_cache(rngtypid, TYPECACHE_RANGE_INFO); + if (typcache->rngelemtype == NULL) + elog(ERROR, "type %u is not a range type", rngtypid); + fcinfo->flinfo->fn_extra = (void *) typcache; + } + + return typcache; +} + +/* + * range_serialize: construct a range value from bounds and empty-flag + * + * This does not force canonicalization of the range value. In most cases, + * external callers should only be canonicalization functions. Note that + * we perform some datatype-independent canonicalization checks anyway. + */ +RangeType * +range_serialize(TypeCacheEntry *typcache, RangeBound *lower, RangeBound *upper, + bool empty, struct Node *escontext) +{ + RangeType *range; + int cmp; + Size msize; + Pointer ptr; + int16 typlen; + bool typbyval; + char typalign; + char typstorage; + char flags = 0; + + /* + * Verify range is not invalid on its face, and construct flags value, + * preventing any non-canonical combinations such as infinite+inclusive. + */ + Assert(lower->lower); + Assert(!upper->lower); + + if (empty) + flags |= RANGE_EMPTY; + else + { + cmp = range_cmp_bound_values(typcache, lower, upper); + + /* error check: if lower bound value is above upper, it's wrong */ + if (cmp > 0) + ereturn(escontext, NULL, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("range lower bound must be less than or equal to range upper bound"))); + + /* if bounds are equal, and not both inclusive, range is empty */ + if (cmp == 0 && !(lower->inclusive && upper->inclusive)) + flags |= RANGE_EMPTY; + else + { + /* infinite boundaries are never inclusive */ + if (lower->infinite) + flags |= RANGE_LB_INF; + else if (lower->inclusive) + flags |= RANGE_LB_INC; + if (upper->infinite) + flags |= RANGE_UB_INF; + else if (upper->inclusive) + flags |= RANGE_UB_INC; + } + } + + /* Fetch information about range's element type */ + typlen = typcache->rngelemtype->typlen; + typbyval = typcache->rngelemtype->typbyval; + typalign = typcache->rngelemtype->typalign; + typstorage = typcache->rngelemtype->typstorage; + + /* Count space for varlena header and range type's OID */ + msize = sizeof(RangeType); + Assert(msize == MAXALIGN(msize)); + + /* Count space for bounds */ + if (RANGE_HAS_LBOUND(flags)) + { + /* + * Make sure item to be inserted is not toasted. It is essential that + * we not insert an out-of-line toast value pointer into a range + * object, for the same reasons that arrays and records can't contain + * them. It would work to store a compressed-in-line value, but we + * prefer to decompress and then let compression be applied to the + * whole range object if necessary. But, unlike arrays, we do allow + * short-header varlena objects to stay as-is. + */ + if (typlen == -1) + lower->val = PointerGetDatum(PG_DETOAST_DATUM_PACKED(lower->val)); + + msize = datum_compute_size(msize, lower->val, typbyval, typalign, + typlen, typstorage); + } + + if (RANGE_HAS_UBOUND(flags)) + { + /* Make sure item to be inserted is not toasted */ + if (typlen == -1) + upper->val = PointerGetDatum(PG_DETOAST_DATUM_PACKED(upper->val)); + + msize = datum_compute_size(msize, upper->val, typbyval, typalign, + typlen, typstorage); + } + + /* Add space for flag byte */ + msize += sizeof(char); + + /* Note: zero-fill is required here, just as in heap tuples */ + range = (RangeType *) palloc0(msize); + SET_VARSIZE(range, msize); + + /* Now fill in the datum */ + range->rangetypid = typcache->type_id; + + ptr = (char *) (range + 1); + + if (RANGE_HAS_LBOUND(flags)) + { + Assert(lower->lower); + ptr = datum_write(ptr, lower->val, typbyval, typalign, typlen, + typstorage); + } + + if (RANGE_HAS_UBOUND(flags)) + { + Assert(!upper->lower); + ptr = datum_write(ptr, upper->val, typbyval, typalign, typlen, + typstorage); + } + + *((char *) ptr) = flags; + + return range; +} + +/* + * range_deserialize: deconstruct a range value + * + * NB: the given range object must be fully detoasted; it cannot have a + * short varlena header. + * + * Note that if the element type is pass-by-reference, the datums in the + * RangeBound structs will be pointers into the given range object. + */ +void +range_deserialize(TypeCacheEntry *typcache, const RangeType *range, + RangeBound *lower, RangeBound *upper, bool *empty) +{ + char flags; + int16 typlen; + bool typbyval; + char typalign; + Pointer ptr; + Datum lbound; + Datum ubound; + + /* assert caller passed the right typcache entry */ + Assert(RangeTypeGetOid(range) == typcache->type_id); + + /* fetch the flag byte from datum's last byte */ + flags = *((const char *) range + VARSIZE(range) - 1); + + /* fetch information about range's element type */ + typlen = typcache->rngelemtype->typlen; + typbyval = typcache->rngelemtype->typbyval; + typalign = typcache->rngelemtype->typalign; + + /* initialize data pointer just after the range OID */ + ptr = (Pointer) (range + 1); + + /* fetch lower bound, if any */ + if (RANGE_HAS_LBOUND(flags)) + { + /* att_align_pointer cannot be necessary here */ + lbound = fetch_att(ptr, typbyval, typlen); + ptr = (Pointer) att_addlength_pointer(ptr, typlen, ptr); + } + else + lbound = (Datum) 0; + + /* fetch upper bound, if any */ + if (RANGE_HAS_UBOUND(flags)) + { + ptr = (Pointer) att_align_pointer(ptr, typalign, typlen, ptr); + ubound = fetch_att(ptr, typbyval, typlen); + /* no need for att_addlength_pointer */ + } + else + ubound = (Datum) 0; + + /* emit results */ + + *empty = (flags & RANGE_EMPTY) != 0; + + lower->val = lbound; + lower->infinite = (flags & RANGE_LB_INF) != 0; + lower->inclusive = (flags & RANGE_LB_INC) != 0; + lower->lower = true; + + upper->val = ubound; + upper->infinite = (flags & RANGE_UB_INF) != 0; + upper->inclusive = (flags & RANGE_UB_INC) != 0; + upper->lower = false; +} + +/* + * range_get_flags: just get the flags from a RangeType value. + * + * This is frequently useful in places that only need the flags and not + * the full results of range_deserialize. + */ +char +range_get_flags(const RangeType *range) +{ + /* fetch the flag byte from datum's last byte */ + return *((char *) range + VARSIZE(range) - 1); +} + +/* + * range_set_contain_empty: set the RANGE_CONTAIN_EMPTY bit in the value. + * + * This is only needed in GiST operations, so we don't include a provision + * for setting it in range_serialize; rather, this function must be applied + * afterwards. + */ +void +range_set_contain_empty(RangeType *range) +{ + char *flagsp; + + /* flag byte is datum's last byte */ + flagsp = (char *) range + VARSIZE(range) - 1; + + *flagsp |= RANGE_CONTAIN_EMPTY; +} + +/* + * This both serializes and canonicalizes (if applicable) the range. + * This should be used by most callers. + */ +RangeType * +make_range(TypeCacheEntry *typcache, RangeBound *lower, RangeBound *upper, + bool empty, struct Node *escontext) +{ + RangeType *range; + + range = range_serialize(typcache, lower, upper, empty, escontext); + + if (SOFT_ERROR_OCCURRED(escontext)) + return NULL; + + /* no need to call canonical on empty ranges ... */ + if (OidIsValid(typcache->rng_canonical_finfo.fn_oid) && + !RangeIsEmpty(range)) + { + /* Do this the hard way so that we can pass escontext */ + LOCAL_FCINFO(fcinfo, 1); + Datum result; + + InitFunctionCallInfoData(*fcinfo, &typcache->rng_canonical_finfo, 1, + InvalidOid, escontext, NULL); + + fcinfo->args[0].value = RangeTypePGetDatum(range); + fcinfo->args[0].isnull = false; + + result = FunctionCallInvoke(fcinfo); + + if (SOFT_ERROR_OCCURRED(escontext)) + return NULL; + + /* Should not get a null result if there was no error */ + if (fcinfo->isnull) + elog(ERROR, "function %u returned NULL", + typcache->rng_canonical_finfo.fn_oid); + + range = DatumGetRangeTypeP(result); + } + + return range; +} + +/* + * Compare two range boundary points, returning <0, 0, or >0 according to + * whether b1 is less than, equal to, or greater than b2. + * + * The boundaries can be any combination of upper and lower; so it's useful + * for a variety of operators. + * + * The simple case is when b1 and b2 are both finite and inclusive, in which + * case the result is just a comparison of the values held in b1 and b2. + * + * If a bound is exclusive, then we need to know whether it's a lower bound, + * in which case we treat the boundary point as "just greater than" the held + * value; or an upper bound, in which case we treat the boundary point as + * "just less than" the held value. + * + * If a bound is infinite, it represents minus infinity (less than every other + * point) if it's a lower bound; or plus infinity (greater than every other + * point) if it's an upper bound. + * + * There is only one case where two boundaries compare equal but are not + * identical: when both bounds are inclusive and hold the same finite value, + * but one is an upper bound and the other a lower bound. + */ +int +range_cmp_bounds(TypeCacheEntry *typcache, const RangeBound *b1, const RangeBound *b2) +{ + int32 result; + + /* + * First, handle cases involving infinity, which don't require invoking + * the comparison proc. + */ + if (b1->infinite && b2->infinite) + { + /* + * Both are infinity, so they are equal unless one is lower and the + * other not. + */ + if (b1->lower == b2->lower) + return 0; + else + return b1->lower ? -1 : 1; + } + else if (b1->infinite) + return b1->lower ? -1 : 1; + else if (b2->infinite) + return b2->lower ? 1 : -1; + + /* + * Both boundaries are finite, so compare the held values. + */ + result = DatumGetInt32(FunctionCall2Coll(&typcache->rng_cmp_proc_finfo, + typcache->rng_collation, + b1->val, b2->val)); + + /* + * If the comparison is anything other than equal, we're done. If they + * compare equal though, we still have to consider whether the boundaries + * are inclusive or exclusive. + */ + if (result == 0) + { + if (!b1->inclusive && !b2->inclusive) + { + /* both are exclusive */ + if (b1->lower == b2->lower) + return 0; + else + return b1->lower ? 1 : -1; + } + else if (!b1->inclusive) + return b1->lower ? 1 : -1; + else if (!b2->inclusive) + return b2->lower ? -1 : 1; + else + { + /* + * Both are inclusive and the values held are equal, so they are + * equal regardless of whether they are upper or lower boundaries, + * or a mix. + */ + return 0; + } + } + + return result; +} + +/* + * Compare two range boundary point values, returning <0, 0, or >0 according + * to whether b1 is less than, equal to, or greater than b2. + * + * This is similar to but simpler than range_cmp_bounds(). We just compare + * the values held in b1 and b2, ignoring inclusive/exclusive flags. The + * lower/upper flags only matter for infinities, where they tell us if the + * infinity is plus or minus. + */ +int +range_cmp_bound_values(TypeCacheEntry *typcache, const RangeBound *b1, + const RangeBound *b2) +{ + /* + * First, handle cases involving infinity, which don't require invoking + * the comparison proc. + */ + if (b1->infinite && b2->infinite) + { + /* + * Both are infinity, so they are equal unless one is lower and the + * other not. + */ + if (b1->lower == b2->lower) + return 0; + else + return b1->lower ? -1 : 1; + } + else if (b1->infinite) + return b1->lower ? -1 : 1; + else if (b2->infinite) + return b2->lower ? 1 : -1; + + /* + * Both boundaries are finite, so compare the held values. + */ + return DatumGetInt32(FunctionCall2Coll(&typcache->rng_cmp_proc_finfo, + typcache->rng_collation, + b1->val, b2->val)); +} + +/* + * qsort callback for sorting ranges. + * + * Two empty ranges compare equal; an empty range sorts to the left of any + * non-empty range. Two non-empty ranges are sorted by lower bound first + * and by upper bound next. + */ +int +range_compare(const void *key1, const void *key2, void *arg) +{ + RangeType *r1 = *(RangeType **) key1; + RangeType *r2 = *(RangeType **) key2; + TypeCacheEntry *typcache = (TypeCacheEntry *) arg; + RangeBound lower1; + RangeBound upper1; + RangeBound lower2; + RangeBound upper2; + bool empty1; + bool empty2; + int cmp; + + range_deserialize(typcache, r1, &lower1, &upper1, &empty1); + range_deserialize(typcache, r2, &lower2, &upper2, &empty2); + + if (empty1 && empty2) + cmp = 0; + else if (empty1) + cmp = -1; + else if (empty2) + cmp = 1; + else + { + cmp = range_cmp_bounds(typcache, &lower1, &lower2); + if (cmp == 0) + cmp = range_cmp_bounds(typcache, &upper1, &upper2); + } + + return cmp; +} + +/* + * Build an empty range value of the type indicated by the typcache entry. + */ +RangeType * +make_empty_range(TypeCacheEntry *typcache) +{ + RangeBound lower; + RangeBound upper; + + lower.val = (Datum) 0; + lower.infinite = false; + lower.inclusive = false; + lower.lower = true; + + upper.val = (Datum) 0; + upper.infinite = false; + upper.inclusive = false; + upper.lower = false; + + return make_range(typcache, &lower, &upper, true, NULL); +} + + +/* + *---------------------------------------------------------- + * STATIC FUNCTIONS + *---------------------------------------------------------- + */ + +/* + * Given a string representing the flags for the range type, return the flags + * represented as a char. + */ +static char +range_parse_flags(const char *flags_str) +{ + char flags = 0; + + if (flags_str[0] == '\0' || + flags_str[1] == '\0' || + flags_str[2] != '\0') + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid range bound flags"), + errhint("Valid values are \"[]\", \"[)\", \"(]\", and \"()\"."))); + + switch (flags_str[0]) + { + case '[': + flags |= RANGE_LB_INC; + break; + case '(': + break; + default: + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid range bound flags"), + errhint("Valid values are \"[]\", \"[)\", \"(]\", and \"()\"."))); + } + + switch (flags_str[1]) + { + case ']': + flags |= RANGE_UB_INC; + break; + case ')': + break; + default: + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid range bound flags"), + errhint("Valid values are \"[]\", \"[)\", \"(]\", and \"()\"."))); + } + + return flags; +} + +/* + * Parse range input. + * + * Input parameters: + * string: input string to be parsed + * Output parameters: + * *flags: receives flags bitmask + * *lbound_str: receives palloc'd lower bound string, or NULL if none + * *ubound_str: receives palloc'd upper bound string, or NULL if none + * + * This is modeled somewhat after record_in in rowtypes.c. + * The input syntax is: + * <range> := EMPTY + * | <lb-inc> <string>, <string> <ub-inc> + * <lb-inc> := '[' | '(' + * <ub-inc> := ']' | ')' + * + * Whitespace before or after <range> is ignored. Whitespace within a <string> + * is taken literally and becomes part of the input string for that bound. + * + * A <string> of length zero is taken as "infinite" (i.e. no bound), unless it + * is surrounded by double-quotes, in which case it is the literal empty + * string. + * + * Within a <string>, special characters (such as comma, parenthesis, or + * brackets) can be enclosed in double-quotes or escaped with backslash. Within + * double-quotes, a double-quote can be escaped with double-quote or backslash. + * + * Returns true on success, false on failure (but failures will return only if + * escontext is an ErrorSaveContext). + */ +static bool +range_parse(const char *string, char *flags, char **lbound_str, + char **ubound_str, Node *escontext) +{ + const char *ptr = string; + bool infinite; + + *flags = 0; + + /* consume whitespace */ + while (*ptr != '\0' && isspace((unsigned char) *ptr)) + ptr++; + + /* check for empty range */ + if (pg_strncasecmp(ptr, RANGE_EMPTY_LITERAL, + strlen(RANGE_EMPTY_LITERAL)) == 0) + { + *flags = RANGE_EMPTY; + *lbound_str = NULL; + *ubound_str = NULL; + + ptr += strlen(RANGE_EMPTY_LITERAL); + + /* the rest should be whitespace */ + while (*ptr != '\0' && isspace((unsigned char) *ptr)) + ptr++; + + /* should have consumed everything */ + if (*ptr != '\0') + ereturn(escontext, false, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed range literal: \"%s\"", + string), + errdetail("Junk after \"empty\" key word."))); + + return true; + } + + if (*ptr == '[') + { + *flags |= RANGE_LB_INC; + ptr++; + } + else if (*ptr == '(') + ptr++; + else + ereturn(escontext, false, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed range literal: \"%s\"", + string), + errdetail("Missing left parenthesis or bracket."))); + + ptr = range_parse_bound(string, ptr, lbound_str, &infinite, escontext); + if (ptr == NULL) + return false; + if (infinite) + *flags |= RANGE_LB_INF; + + if (*ptr == ',') + ptr++; + else + ereturn(escontext, false, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed range literal: \"%s\"", + string), + errdetail("Missing comma after lower bound."))); + + ptr = range_parse_bound(string, ptr, ubound_str, &infinite, escontext); + if (ptr == NULL) + return false; + if (infinite) + *flags |= RANGE_UB_INF; + + if (*ptr == ']') + { + *flags |= RANGE_UB_INC; + ptr++; + } + else if (*ptr == ')') + ptr++; + else /* must be a comma */ + ereturn(escontext, false, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed range literal: \"%s\"", + string), + errdetail("Too many commas."))); + + /* consume whitespace */ + while (*ptr != '\0' && isspace((unsigned char) *ptr)) + ptr++; + + if (*ptr != '\0') + ereturn(escontext, false, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed range literal: \"%s\"", + string), + errdetail("Junk after right parenthesis or bracket."))); + + return true; +} + +/* + * Helper for range_parse: parse and de-quote one bound string. + * + * We scan until finding comma, right parenthesis, or right bracket. + * + * Input parameters: + * string: entire input string (used only for error reports) + * ptr: where to start parsing bound + * Output parameters: + * *bound_str: receives palloc'd bound string, or NULL if none + * *infinite: set true if no bound, else false + * + * The return value is the scan ptr, advanced past the bound string. + * However, if escontext is an ErrorSaveContext, we return NULL on failure. + */ +static const char * +range_parse_bound(const char *string, const char *ptr, + char **bound_str, bool *infinite, Node *escontext) +{ + StringInfoData buf; + + /* Check for null: completely empty input means null */ + if (*ptr == ',' || *ptr == ')' || *ptr == ']') + { + *bound_str = NULL; + *infinite = true; + } + else + { + /* Extract string for this bound */ + bool inquote = false; + + initStringInfo(&buf); + while (inquote || !(*ptr == ',' || *ptr == ')' || *ptr == ']')) + { + char ch = *ptr++; + + if (ch == '\0') + ereturn(escontext, NULL, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed range literal: \"%s\"", + string), + errdetail("Unexpected end of input."))); + if (ch == '\\') + { + if (*ptr == '\0') + ereturn(escontext, NULL, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed range literal: \"%s\"", + string), + errdetail("Unexpected end of input."))); + appendStringInfoChar(&buf, *ptr++); + } + else if (ch == '"') + { + if (!inquote) + inquote = true; + else if (*ptr == '"') + { + /* doubled quote within quote sequence */ + appendStringInfoChar(&buf, *ptr++); + } + else + inquote = false; + } + else + appendStringInfoChar(&buf, ch); + } + + *bound_str = buf.data; + *infinite = false; + } + + return ptr; +} + +/* + * Convert a deserialized range value to text form + * + * Inputs are the flags byte, and the two bound values already converted to + * text (but not yet quoted). If no bound value, pass NULL. + * + * Result is a palloc'd string + */ +static char * +range_deparse(char flags, const char *lbound_str, const char *ubound_str) +{ + StringInfoData buf; + + if (flags & RANGE_EMPTY) + return pstrdup(RANGE_EMPTY_LITERAL); + + initStringInfo(&buf); + + appendStringInfoChar(&buf, (flags & RANGE_LB_INC) ? '[' : '('); + + if (RANGE_HAS_LBOUND(flags)) + appendStringInfoString(&buf, range_bound_escape(lbound_str)); + + appendStringInfoChar(&buf, ','); + + if (RANGE_HAS_UBOUND(flags)) + appendStringInfoString(&buf, range_bound_escape(ubound_str)); + + appendStringInfoChar(&buf, (flags & RANGE_UB_INC) ? ']' : ')'); + + return buf.data; +} + +/* + * Helper for range_deparse: quote a bound value as needed + * + * Result is a palloc'd string + */ +static char * +range_bound_escape(const char *value) +{ + bool nq; + const char *ptr; + StringInfoData buf; + + initStringInfo(&buf); + + /* Detect whether we need double quotes for this value */ + nq = (value[0] == '\0'); /* force quotes for empty string */ + for (ptr = value; *ptr; ptr++) + { + char ch = *ptr; + + if (ch == '"' || ch == '\\' || + ch == '(' || ch == ')' || + ch == '[' || ch == ']' || + ch == ',' || + isspace((unsigned char) ch)) + { + nq = true; + break; + } + } + + /* And emit the string */ + if (nq) + appendStringInfoChar(&buf, '"'); + for (ptr = value; *ptr; ptr++) + { + char ch = *ptr; + + if (ch == '"' || ch == '\\') + appendStringInfoChar(&buf, ch); + appendStringInfoChar(&buf, ch); + } + if (nq) + appendStringInfoChar(&buf, '"'); + + return buf.data; +} + +/* + * Test whether range r1 contains range r2. + * + * Caller has already checked that they are the same range type, and looked up + * the necessary typcache entry. + */ +bool +range_contains_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2) +{ + RangeBound lower1; + RangeBound upper1; + bool empty1; + RangeBound lower2; + RangeBound upper2; + bool empty2; + + /* Different types should be prevented by ANYRANGE matching rules */ + if (RangeTypeGetOid(r1) != RangeTypeGetOid(r2)) + elog(ERROR, "range types do not match"); + + range_deserialize(typcache, r1, &lower1, &upper1, &empty1); + range_deserialize(typcache, r2, &lower2, &upper2, &empty2); + + /* If either range is empty, the answer is easy */ + if (empty2) + return true; + else if (empty1) + return false; + + /* Else we must have lower1 <= lower2 and upper1 >= upper2 */ + if (range_cmp_bounds(typcache, &lower1, &lower2) > 0) + return false; + if (range_cmp_bounds(typcache, &upper1, &upper2) < 0) + return false; + + return true; +} + +bool +range_contained_by_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2) +{ + return range_contains_internal(typcache, r2, r1); +} + +/* + * Test whether range r contains a specific element value. + */ +bool +range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum val) +{ + RangeBound lower; + RangeBound upper; + bool empty; + int32 cmp; + + range_deserialize(typcache, r, &lower, &upper, &empty); + + if (empty) + return false; + + if (!lower.infinite) + { + cmp = DatumGetInt32(FunctionCall2Coll(&typcache->rng_cmp_proc_finfo, + typcache->rng_collation, + lower.val, val)); + if (cmp > 0) + return false; + if (cmp == 0 && !lower.inclusive) + return false; + } + + if (!upper.infinite) + { + cmp = DatumGetInt32(FunctionCall2Coll(&typcache->rng_cmp_proc_finfo, + typcache->rng_collation, + upper.val, val)); + if (cmp < 0) + return false; + if (cmp == 0 && !upper.inclusive) + return false; + } + + return true; +} + + +/* + * datum_compute_size() and datum_write() are used to insert the bound + * values into a range object. They are modeled after heaptuple.c's + * heap_compute_data_size() and heap_fill_tuple(), but we need not handle + * null values here. TYPE_IS_PACKABLE must test the same conditions as + * heaptuple.c's ATT_IS_PACKABLE macro. See the comments thare for more + * details. + */ + +/* Does datatype allow packing into the 1-byte-header varlena format? */ +#define TYPE_IS_PACKABLE(typlen, typstorage) \ + ((typlen) == -1 && (typstorage) != TYPSTORAGE_PLAIN) + +/* + * Increment data_length by the space needed by the datum, including any + * preceding alignment padding. + */ +static Size +datum_compute_size(Size data_length, Datum val, bool typbyval, char typalign, + int16 typlen, char typstorage) +{ + if (TYPE_IS_PACKABLE(typlen, typstorage) && + VARATT_CAN_MAKE_SHORT(DatumGetPointer(val))) + { + /* + * we're anticipating converting to a short varlena header, so adjust + * length and don't count any alignment + */ + data_length += VARATT_CONVERTED_SHORT_SIZE(DatumGetPointer(val)); + } + else + { + data_length = att_align_datum(data_length, typalign, typlen, val); + data_length = att_addlength_datum(data_length, typlen, val); + } + + return data_length; +} + +/* + * Write the given datum beginning at ptr (after advancing to correct + * alignment, if needed). Return the pointer incremented by space used. + */ +static Pointer +datum_write(Pointer ptr, Datum datum, bool typbyval, char typalign, + int16 typlen, char typstorage) +{ + Size data_length; + + if (typbyval) + { + /* pass-by-value */ + ptr = (char *) att_align_nominal(ptr, typalign); + store_att_byval(ptr, datum, typlen); + data_length = typlen; + } + else if (typlen == -1) + { + /* varlena */ + Pointer val = DatumGetPointer(datum); + + if (VARATT_IS_EXTERNAL(val)) + { + /* + * Throw error, because we must never put a toast pointer inside a + * range object. Caller should have detoasted it. + */ + elog(ERROR, "cannot store a toast pointer inside a range"); + data_length = 0; /* keep compiler quiet */ + } + else if (VARATT_IS_SHORT(val)) + { + /* no alignment for short varlenas */ + data_length = VARSIZE_SHORT(val); + memcpy(ptr, val, data_length); + } + else if (TYPE_IS_PACKABLE(typlen, typstorage) && + VARATT_CAN_MAKE_SHORT(val)) + { + /* convert to short varlena -- no alignment */ + data_length = VARATT_CONVERTED_SHORT_SIZE(val); + SET_VARSIZE_SHORT(ptr, data_length); + memcpy(ptr + 1, VARDATA(val), data_length - 1); + } + else + { + /* full 4-byte header varlena */ + ptr = (char *) att_align_nominal(ptr, typalign); + data_length = VARSIZE(val); + memcpy(ptr, val, data_length); + } + } + else if (typlen == -2) + { + /* cstring ... never needs alignment */ + Assert(typalign == TYPALIGN_CHAR); + data_length = strlen(DatumGetCString(datum)) + 1; + memcpy(ptr, DatumGetPointer(datum), data_length); + } + else + { + /* fixed-length pass-by-reference */ + ptr = (char *) att_align_nominal(ptr, typalign); + Assert(typlen > 0); + data_length = typlen; + memcpy(ptr, DatumGetPointer(datum), data_length); + } + + ptr += data_length; + + return ptr; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/rangetypes_gist.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/rangetypes_gist.c new file mode 100644 index 00000000000..08846783818 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/rangetypes_gist.c @@ -0,0 +1,1799 @@ +/*------------------------------------------------------------------------- + * + * rangetypes_gist.c + * GiST support for range types. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/adt/rangetypes_gist.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/gist.h" +#include "access/stratnum.h" +#include "utils/datum.h" +#include "utils/float.h" +#include "utils/fmgrprotos.h" +#include "utils/multirangetypes.h" +#include "utils/rangetypes.h" + +/* + * Range class properties used to segregate different classes of ranges in + * GiST. Each unique combination of properties is a class. CLS_EMPTY cannot + * be combined with anything else. + */ +#define CLS_NORMAL 0 /* Ordinary finite range (no bits set) */ +#define CLS_LOWER_INF 1 /* Lower bound is infinity */ +#define CLS_UPPER_INF 2 /* Upper bound is infinity */ +#define CLS_CONTAIN_EMPTY 4 /* Contains underlying empty ranges */ +#define CLS_EMPTY 8 /* Special class for empty ranges */ + +#define CLS_COUNT 9 /* # of classes; includes all combinations of + * properties. CLS_EMPTY doesn't combine with + * anything else, so it's only 2^3 + 1. */ + +/* + * Minimum accepted ratio of split for items of the same class. If the items + * are of different classes, we will separate along those lines regardless of + * the ratio. + */ +#define LIMIT_RATIO 0.3 + +/* Constants for fixed penalty values */ +#define INFINITE_BOUND_PENALTY 2.0 +#define CONTAIN_EMPTY_PENALTY 1.0 +#define DEFAULT_SUBTYPE_DIFF_PENALTY 1.0 + +/* + * Per-item data for range_gist_single_sorting_split. + */ +typedef struct +{ + int index; + RangeBound bound; +} SingleBoundSortItem; + +/* place on left or right side of split? */ +typedef enum +{ + SPLIT_LEFT = 0, /* makes initialization to SPLIT_LEFT easier */ + SPLIT_RIGHT +} SplitLR; + +/* + * Context for range_gist_consider_split. + */ +typedef struct +{ + TypeCacheEntry *typcache; /* typcache for range type */ + bool has_subtype_diff; /* does it have subtype_diff? */ + int entries_count; /* total number of entries being split */ + + /* Information about currently selected split follows */ + + bool first; /* true if no split was selected yet */ + + RangeBound *left_upper; /* upper bound of left interval */ + RangeBound *right_lower; /* lower bound of right interval */ + + float4 ratio; /* split ratio */ + float4 overlap; /* overlap between left and right predicate */ + int common_left; /* # common entries destined for each side */ + int common_right; +} ConsiderSplitContext; + +/* + * Bounds extracted from a non-empty range, for use in + * range_gist_double_sorting_split. + */ +typedef struct +{ + RangeBound lower; + RangeBound upper; +} NonEmptyRange; + +/* + * Represents information about an entry that can be placed in either group + * without affecting overlap over selected axis ("common entry"). + */ +typedef struct +{ + /* Index of entry in the initial array */ + int index; + /* Delta between closeness of range to each of the two groups */ + double delta; +} CommonEntry; + +/* Helper macros to place an entry in the left or right group during split */ +/* Note direct access to variables v, typcache, left_range, right_range */ +#define PLACE_LEFT(range, off) \ + do { \ + if (v->spl_nleft > 0) \ + left_range = range_super_union(typcache, left_range, range); \ + else \ + left_range = (range); \ + v->spl_left[v->spl_nleft++] = (off); \ + } while(0) + +#define PLACE_RIGHT(range, off) \ + do { \ + if (v->spl_nright > 0) \ + right_range = range_super_union(typcache, right_range, range); \ + else \ + right_range = (range); \ + v->spl_right[v->spl_nright++] = (off); \ + } while(0) + +/* Copy a RangeType datum (hardwires typbyval and typlen for ranges...) */ +#define rangeCopy(r) \ + ((RangeType *) DatumGetPointer(datumCopy(PointerGetDatum(r), \ + false, -1))) + +static RangeType *range_super_union(TypeCacheEntry *typcache, RangeType *r1, + RangeType *r2); +static bool range_gist_consistent_int_range(TypeCacheEntry *typcache, + StrategyNumber strategy, + const RangeType *key, + const RangeType *query); +static bool range_gist_consistent_int_multirange(TypeCacheEntry *typcache, + StrategyNumber strategy, + const RangeType *key, + const MultirangeType *query); +static bool range_gist_consistent_int_element(TypeCacheEntry *typcache, + StrategyNumber strategy, + const RangeType *key, + Datum query); +static bool range_gist_consistent_leaf_range(TypeCacheEntry *typcache, + StrategyNumber strategy, + const RangeType *key, + const RangeType *query); +static bool range_gist_consistent_leaf_multirange(TypeCacheEntry *typcache, + StrategyNumber strategy, + const RangeType *key, + const MultirangeType *query); +static bool range_gist_consistent_leaf_element(TypeCacheEntry *typcache, + StrategyNumber strategy, + const RangeType *key, + Datum query); +static void range_gist_fallback_split(TypeCacheEntry *typcache, + GistEntryVector *entryvec, + GIST_SPLITVEC *v); +static void range_gist_class_split(TypeCacheEntry *typcache, + GistEntryVector *entryvec, + GIST_SPLITVEC *v, + SplitLR *classes_groups); +static void range_gist_single_sorting_split(TypeCacheEntry *typcache, + GistEntryVector *entryvec, + GIST_SPLITVEC *v, + bool use_upper_bound); +static void range_gist_double_sorting_split(TypeCacheEntry *typcache, + GistEntryVector *entryvec, + GIST_SPLITVEC *v); +static void range_gist_consider_split(ConsiderSplitContext *context, + RangeBound *right_lower, int min_left_count, + RangeBound *left_upper, int max_left_count); +static int get_gist_range_class(RangeType *range); +static int single_bound_cmp(const void *a, const void *b, void *arg); +static int interval_cmp_lower(const void *a, const void *b, void *arg); +static int interval_cmp_upper(const void *a, const void *b, void *arg); +static int common_entry_cmp(const void *i1, const void *i2); +static float8 call_subtype_diff(TypeCacheEntry *typcache, + Datum val1, Datum val2); + + +/* GiST query consistency check */ +Datum +range_gist_consistent(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + Datum query = PG_GETARG_DATUM(1); + StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2); + bool result; + Oid subtype = PG_GETARG_OID(3); + bool *recheck = (bool *) PG_GETARG_POINTER(4); + RangeType *key = DatumGetRangeTypeP(entry->key); + TypeCacheEntry *typcache; + + /* All operators served by this function are exact */ + *recheck = false; + + typcache = range_get_typcache(fcinfo, RangeTypeGetOid(key)); + + /* + * Perform consistent checking using function corresponding to key type + * (leaf or internal) and query subtype (range, multirange, or element). + * Note that invalid subtype means that query type matches key type + * (range). + */ + if (GIST_LEAF(entry)) + { + if (!OidIsValid(subtype) || subtype == ANYRANGEOID) + result = range_gist_consistent_leaf_range(typcache, strategy, key, + DatumGetRangeTypeP(query)); + else if (subtype == ANYMULTIRANGEOID) + result = range_gist_consistent_leaf_multirange(typcache, strategy, key, + DatumGetMultirangeTypeP(query)); + else + result = range_gist_consistent_leaf_element(typcache, strategy, + key, query); + } + else + { + if (!OidIsValid(subtype) || subtype == ANYRANGEOID) + result = range_gist_consistent_int_range(typcache, strategy, key, + DatumGetRangeTypeP(query)); + else if (subtype == ANYMULTIRANGEOID) + result = range_gist_consistent_int_multirange(typcache, strategy, key, + DatumGetMultirangeTypeP(query)); + else + result = range_gist_consistent_int_element(typcache, strategy, + key, query); + } + PG_RETURN_BOOL(result); +} + +/* + * GiST compress method for multiranges: multirange is approximated as union + * range with no gaps. + */ +Datum +multirange_gist_compress(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + + if (entry->leafkey) + { + MultirangeType *mr = DatumGetMultirangeTypeP(entry->key); + RangeType *r; + TypeCacheEntry *typcache; + GISTENTRY *retval = palloc(sizeof(GISTENTRY)); + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr)); + r = multirange_get_union_range(typcache->rngtype, mr); + + gistentryinit(*retval, RangeTypePGetDatum(r), + entry->rel, entry->page, entry->offset, false); + + PG_RETURN_POINTER(retval); + } + + PG_RETURN_POINTER(entry); +} + +/* GiST query consistency check for multiranges */ +Datum +multirange_gist_consistent(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + Datum query = PG_GETARG_DATUM(1); + StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2); + bool result; + Oid subtype = PG_GETARG_OID(3); + bool *recheck = (bool *) PG_GETARG_POINTER(4); + RangeType *key = DatumGetRangeTypeP(entry->key); + TypeCacheEntry *typcache; + + /* + * All operators served by this function are inexact because multirange is + * approximated by union range with no gaps. + */ + *recheck = true; + + typcache = range_get_typcache(fcinfo, RangeTypeGetOid(key)); + + /* + * Perform consistent checking using function corresponding to key type + * (leaf or internal) and query subtype (range, multirange, or element). + * Note that invalid subtype means that query type matches key type + * (multirange). + */ + if (GIST_LEAF(entry)) + { + if (!OidIsValid(subtype) || subtype == ANYMULTIRANGEOID) + result = range_gist_consistent_leaf_multirange(typcache, strategy, key, + DatumGetMultirangeTypeP(query)); + else if (subtype == ANYRANGEOID) + result = range_gist_consistent_leaf_range(typcache, strategy, key, + DatumGetRangeTypeP(query)); + else + result = range_gist_consistent_leaf_element(typcache, strategy, + key, query); + } + else + { + if (!OidIsValid(subtype) || subtype == ANYMULTIRANGEOID) + result = range_gist_consistent_int_multirange(typcache, strategy, key, + DatumGetMultirangeTypeP(query)); + else if (subtype == ANYRANGEOID) + result = range_gist_consistent_int_range(typcache, strategy, key, + DatumGetRangeTypeP(query)); + else + result = range_gist_consistent_int_element(typcache, strategy, + key, query); + } + PG_RETURN_BOOL(result); +} + +/* form union range */ +Datum +range_gist_union(PG_FUNCTION_ARGS) +{ + GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); + GISTENTRY *ent = entryvec->vector; + RangeType *result_range; + TypeCacheEntry *typcache; + int i; + + result_range = DatumGetRangeTypeP(ent[0].key); + + typcache = range_get_typcache(fcinfo, RangeTypeGetOid(result_range)); + + for (i = 1; i < entryvec->n; i++) + { + result_range = range_super_union(typcache, result_range, + DatumGetRangeTypeP(ent[i].key)); + } + + PG_RETURN_RANGE_P(result_range); +} + +/* + * We store ranges as ranges in GiST indexes, so we do not need + * compress, decompress, or fetch functions. Note this implies a limit + * on the size of range values that can be indexed. + */ + +/* + * GiST page split penalty function. + * + * The penalty function has the following goals (in order from most to least + * important): + * - Keep normal ranges separate + * - Avoid broadening the class of the original predicate + * - Avoid broadening (as determined by subtype_diff) the original predicate + * - Favor adding ranges to narrower original predicates + */ +Datum +range_gist_penalty(PG_FUNCTION_ARGS) +{ + GISTENTRY *origentry = (GISTENTRY *) PG_GETARG_POINTER(0); + GISTENTRY *newentry = (GISTENTRY *) PG_GETARG_POINTER(1); + float *penalty = (float *) PG_GETARG_POINTER(2); + RangeType *orig = DatumGetRangeTypeP(origentry->key); + RangeType *new = DatumGetRangeTypeP(newentry->key); + TypeCacheEntry *typcache; + bool has_subtype_diff; + RangeBound orig_lower, + new_lower, + orig_upper, + new_upper; + bool orig_empty, + new_empty; + + if (RangeTypeGetOid(orig) != RangeTypeGetOid(new)) + elog(ERROR, "range types do not match"); + + typcache = range_get_typcache(fcinfo, RangeTypeGetOid(orig)); + + has_subtype_diff = OidIsValid(typcache->rng_subdiff_finfo.fn_oid); + + range_deserialize(typcache, orig, &orig_lower, &orig_upper, &orig_empty); + range_deserialize(typcache, new, &new_lower, &new_upper, &new_empty); + + /* + * Distinct branches for handling distinct classes of ranges. Note that + * penalty values only need to be commensurate within the same class of + * new range. + */ + if (new_empty) + { + /* Handle insertion of empty range */ + if (orig_empty) + { + /* + * The best case is to insert it to empty original range. + * Insertion here means no broadening of original range. Also + * original range is the most narrow. + */ + *penalty = 0.0; + } + else if (RangeIsOrContainsEmpty(orig)) + { + /* + * The second case is to insert empty range into range which + * contains at least one underlying empty range. There is still + * no broadening of original range, but original range is not as + * narrow as possible. + */ + *penalty = CONTAIN_EMPTY_PENALTY; + } + else if (orig_lower.infinite && orig_upper.infinite) + { + /* + * Original range requires broadening. (-inf; +inf) is most far + * from normal range in this case. + */ + *penalty = 2 * CONTAIN_EMPTY_PENALTY; + } + else if (orig_lower.infinite || orig_upper.infinite) + { + /* + * (-inf, x) or (x, +inf) original ranges are closer to normal + * ranges, so it's worse to mix it with empty ranges. + */ + *penalty = 3 * CONTAIN_EMPTY_PENALTY; + } + else + { + /* + * The least preferred case is broadening of normal range. + */ + *penalty = 4 * CONTAIN_EMPTY_PENALTY; + } + } + else if (new_lower.infinite && new_upper.infinite) + { + /* Handle insertion of (-inf, +inf) range */ + if (orig_lower.infinite && orig_upper.infinite) + { + /* + * Best case is inserting to (-inf, +inf) original range. + */ + *penalty = 0.0; + } + else if (orig_lower.infinite || orig_upper.infinite) + { + /* + * When original range is (-inf, x) or (x, +inf) it requires + * broadening of original range (extension of one bound to + * infinity). + */ + *penalty = INFINITE_BOUND_PENALTY; + } + else + { + /* + * Insertion to normal original range is least preferred. + */ + *penalty = 2 * INFINITE_BOUND_PENALTY; + } + + if (RangeIsOrContainsEmpty(orig)) + { + /* + * Original range is narrower when it doesn't contain empty + * ranges. Add additional penalty otherwise. + */ + *penalty += CONTAIN_EMPTY_PENALTY; + } + } + else if (new_lower.infinite) + { + /* Handle insertion of (-inf, x) range */ + if (!orig_empty && orig_lower.infinite) + { + if (orig_upper.infinite) + { + /* + * (-inf, +inf) range won't be extended by insertion of (-inf, + * x) range. It's a less desirable case than insertion to + * (-inf, y) original range without extension, because in that + * case original range is narrower. But we can't express that + * in single float value. + */ + *penalty = 0.0; + } + else + { + if (range_cmp_bounds(typcache, &new_upper, &orig_upper) > 0) + { + /* + * Get extension of original range using subtype_diff. Use + * constant if subtype_diff unavailable. + */ + if (has_subtype_diff) + *penalty = call_subtype_diff(typcache, + new_upper.val, + orig_upper.val); + else + *penalty = DEFAULT_SUBTYPE_DIFF_PENALTY; + } + else + { + /* No extension of original range */ + *penalty = 0.0; + } + } + } + else + { + /* + * If lower bound of original range is not -inf, then extension of + * it is infinity. + */ + *penalty = get_float4_infinity(); + } + } + else if (new_upper.infinite) + { + /* Handle insertion of (x, +inf) range */ + if (!orig_empty && orig_upper.infinite) + { + if (orig_lower.infinite) + { + /* + * (-inf, +inf) range won't be extended by insertion of (x, + * +inf) range. It's a less desirable case than insertion to + * (y, +inf) original range without extension, because in that + * case original range is narrower. But we can't express that + * in single float value. + */ + *penalty = 0.0; + } + else + { + if (range_cmp_bounds(typcache, &new_lower, &orig_lower) < 0) + { + /* + * Get extension of original range using subtype_diff. Use + * constant if subtype_diff unavailable. + */ + if (has_subtype_diff) + *penalty = call_subtype_diff(typcache, + orig_lower.val, + new_lower.val); + else + *penalty = DEFAULT_SUBTYPE_DIFF_PENALTY; + } + else + { + /* No extension of original range */ + *penalty = 0.0; + } + } + } + else + { + /* + * If upper bound of original range is not +inf, then extension of + * it is infinity. + */ + *penalty = get_float4_infinity(); + } + } + else + { + /* Handle insertion of normal (non-empty, non-infinite) range */ + if (orig_empty || orig_lower.infinite || orig_upper.infinite) + { + /* + * Avoid mixing normal ranges with infinite and empty ranges. + */ + *penalty = get_float4_infinity(); + } + else + { + /* + * Calculate extension of original range by calling subtype_diff. + * Use constant if subtype_diff unavailable. + */ + float8 diff = 0.0; + + if (range_cmp_bounds(typcache, &new_lower, &orig_lower) < 0) + { + if (has_subtype_diff) + diff += call_subtype_diff(typcache, + orig_lower.val, + new_lower.val); + else + diff += DEFAULT_SUBTYPE_DIFF_PENALTY; + } + if (range_cmp_bounds(typcache, &new_upper, &orig_upper) > 0) + { + if (has_subtype_diff) + diff += call_subtype_diff(typcache, + new_upper.val, + orig_upper.val); + else + diff += DEFAULT_SUBTYPE_DIFF_PENALTY; + } + *penalty = diff; + } + } + + PG_RETURN_POINTER(penalty); +} + +/* + * The GiST PickSplit method for ranges + * + * Primarily, we try to segregate ranges of different classes. If splitting + * ranges of the same class, use the appropriate split method for that class. + */ +Datum +range_gist_picksplit(PG_FUNCTION_ARGS) +{ + GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); + GIST_SPLITVEC *v = (GIST_SPLITVEC *) PG_GETARG_POINTER(1); + TypeCacheEntry *typcache; + OffsetNumber i; + RangeType *pred_left; + int nbytes; + OffsetNumber maxoff; + int count_in_classes[CLS_COUNT]; + int j; + int non_empty_classes_count = 0; + int biggest_class = -1; + int biggest_class_count = 0; + int total_count; + + /* use first item to look up range type's info */ + pred_left = DatumGetRangeTypeP(entryvec->vector[FirstOffsetNumber].key); + typcache = range_get_typcache(fcinfo, RangeTypeGetOid(pred_left)); + + maxoff = entryvec->n - 1; + nbytes = (maxoff + 1) * sizeof(OffsetNumber); + v->spl_left = (OffsetNumber *) palloc(nbytes); + v->spl_right = (OffsetNumber *) palloc(nbytes); + + /* + * Get count distribution of range classes. + */ + memset(count_in_classes, 0, sizeof(count_in_classes)); + for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i)) + { + RangeType *range = DatumGetRangeTypeP(entryvec->vector[i].key); + + count_in_classes[get_gist_range_class(range)]++; + } + + /* + * Count non-empty classes and find biggest class. + */ + total_count = maxoff; + for (j = 0; j < CLS_COUNT; j++) + { + if (count_in_classes[j] > 0) + { + if (count_in_classes[j] > biggest_class_count) + { + biggest_class_count = count_in_classes[j]; + biggest_class = j; + } + non_empty_classes_count++; + } + } + + Assert(non_empty_classes_count > 0); + + if (non_empty_classes_count == 1) + { + /* One non-empty class, so split inside class */ + if ((biggest_class & ~CLS_CONTAIN_EMPTY) == CLS_NORMAL) + { + /* double sorting split for normal ranges */ + range_gist_double_sorting_split(typcache, entryvec, v); + } + else if ((biggest_class & ~CLS_CONTAIN_EMPTY) == CLS_LOWER_INF) + { + /* upper bound sorting split for (-inf, x) ranges */ + range_gist_single_sorting_split(typcache, entryvec, v, true); + } + else if ((biggest_class & ~CLS_CONTAIN_EMPTY) == CLS_UPPER_INF) + { + /* lower bound sorting split for (x, +inf) ranges */ + range_gist_single_sorting_split(typcache, entryvec, v, false); + } + else + { + /* trivial split for all (-inf, +inf) or all empty ranges */ + range_gist_fallback_split(typcache, entryvec, v); + } + } + else + { + /* + * Class based split. + * + * To which side of the split should each class go? Initialize them + * all to go to the left side. + */ + SplitLR classes_groups[CLS_COUNT]; + + memset(classes_groups, 0, sizeof(classes_groups)); + + if (count_in_classes[CLS_NORMAL] > 0) + { + /* separate normal ranges if any */ + classes_groups[CLS_NORMAL] = SPLIT_RIGHT; + } + else + { + /*---------- + * Try to split classes in one of two ways: + * 1) containing infinities - not containing infinities + * 2) containing empty - not containing empty + * + * Select the way which balances the ranges between left and right + * the best. If split in these ways is not possible, there are at + * most 3 classes, so just separate biggest class. + *---------- + */ + int infCount, + nonInfCount; + int emptyCount, + nonEmptyCount; + + nonInfCount = + count_in_classes[CLS_NORMAL] + + count_in_classes[CLS_CONTAIN_EMPTY] + + count_in_classes[CLS_EMPTY]; + infCount = total_count - nonInfCount; + + nonEmptyCount = + count_in_classes[CLS_NORMAL] + + count_in_classes[CLS_LOWER_INF] + + count_in_classes[CLS_UPPER_INF] + + count_in_classes[CLS_LOWER_INF | CLS_UPPER_INF]; + emptyCount = total_count - nonEmptyCount; + + if (infCount > 0 && nonInfCount > 0 && + (abs(infCount - nonInfCount) <= + abs(emptyCount - nonEmptyCount))) + { + classes_groups[CLS_NORMAL] = SPLIT_RIGHT; + classes_groups[CLS_CONTAIN_EMPTY] = SPLIT_RIGHT; + classes_groups[CLS_EMPTY] = SPLIT_RIGHT; + } + else if (emptyCount > 0 && nonEmptyCount > 0) + { + classes_groups[CLS_NORMAL] = SPLIT_RIGHT; + classes_groups[CLS_LOWER_INF] = SPLIT_RIGHT; + classes_groups[CLS_UPPER_INF] = SPLIT_RIGHT; + classes_groups[CLS_LOWER_INF | CLS_UPPER_INF] = SPLIT_RIGHT; + } + else + { + /* + * Either total_count == emptyCount or total_count == + * infCount. + */ + classes_groups[biggest_class] = SPLIT_RIGHT; + } + } + + range_gist_class_split(typcache, entryvec, v, classes_groups); + } + + PG_RETURN_POINTER(v); +} + +/* equality comparator for GiST */ +Datum +range_gist_same(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE_P(0); + RangeType *r2 = PG_GETARG_RANGE_P(1); + bool *result = (bool *) PG_GETARG_POINTER(2); + + /* + * range_eq will ignore the RANGE_CONTAIN_EMPTY flag, so we have to check + * that for ourselves. More generally, if the entries have been properly + * normalized, then unequal flags bytes must mean unequal ranges ... so + * let's just test all the flag bits at once. + */ + if (range_get_flags(r1) != range_get_flags(r2)) + *result = false; + else + { + TypeCacheEntry *typcache; + + typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1)); + + *result = range_eq_internal(typcache, r1, r2); + } + + PG_RETURN_POINTER(result); +} + +/* + *---------------------------------------------------------- + * STATIC FUNCTIONS + *---------------------------------------------------------- + */ + +/* + * Return the smallest range that contains r1 and r2 + * + * This differs from regular range_union in two critical ways: + * 1. It won't throw an error for non-adjacent r1 and r2, but just absorb + * the intervening values into the result range. + * 2. We track whether any empty range has been union'd into the result, + * so that contained_by searches can be indexed. Note that this means + * that *all* unions formed within the GiST index must go through here. + */ +static RangeType * +range_super_union(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2) +{ + RangeType *result; + RangeBound lower1, + lower2; + RangeBound upper1, + upper2; + bool empty1, + empty2; + char flags1, + flags2; + RangeBound *result_lower; + RangeBound *result_upper; + + range_deserialize(typcache, r1, &lower1, &upper1, &empty1); + range_deserialize(typcache, r2, &lower2, &upper2, &empty2); + flags1 = range_get_flags(r1); + flags2 = range_get_flags(r2); + + if (empty1) + { + /* We can return r2 as-is if it already is or contains empty */ + if (flags2 & (RANGE_EMPTY | RANGE_CONTAIN_EMPTY)) + return r2; + /* Else we'd better copy it (modify-in-place isn't safe) */ + r2 = rangeCopy(r2); + range_set_contain_empty(r2); + return r2; + } + if (empty2) + { + /* We can return r1 as-is if it already is or contains empty */ + if (flags1 & (RANGE_EMPTY | RANGE_CONTAIN_EMPTY)) + return r1; + /* Else we'd better copy it (modify-in-place isn't safe) */ + r1 = rangeCopy(r1); + range_set_contain_empty(r1); + return r1; + } + + if (range_cmp_bounds(typcache, &lower1, &lower2) <= 0) + result_lower = &lower1; + else + result_lower = &lower2; + + if (range_cmp_bounds(typcache, &upper1, &upper2) >= 0) + result_upper = &upper1; + else + result_upper = &upper2; + + /* optimization to avoid constructing a new range */ + if (result_lower == &lower1 && result_upper == &upper1 && + ((flags1 & RANGE_CONTAIN_EMPTY) || !(flags2 & RANGE_CONTAIN_EMPTY))) + return r1; + if (result_lower == &lower2 && result_upper == &upper2 && + ((flags2 & RANGE_CONTAIN_EMPTY) || !(flags1 & RANGE_CONTAIN_EMPTY))) + return r2; + + result = make_range(typcache, result_lower, result_upper, false, NULL); + + if ((flags1 & RANGE_CONTAIN_EMPTY) || (flags2 & RANGE_CONTAIN_EMPTY)) + range_set_contain_empty(result); + + return result; +} + +static bool +multirange_union_range_equal(TypeCacheEntry *typcache, + const RangeType *r, + const MultirangeType *mr) +{ + RangeBound lower1, + upper1, + lower2, + upper2, + tmp; + bool empty; + + if (RangeIsEmpty(r) || MultirangeIsEmpty(mr)) + return (RangeIsEmpty(r) && MultirangeIsEmpty(mr)); + + range_deserialize(typcache, r, &lower1, &upper1, &empty); + Assert(!empty); + multirange_get_bounds(typcache, mr, 0, &lower2, &tmp); + multirange_get_bounds(typcache, mr, mr->rangeCount - 1, &tmp, &upper2); + + return (range_cmp_bounds(typcache, &lower1, &lower2) == 0 && + range_cmp_bounds(typcache, &upper1, &upper2) == 0); +} + +/* + * GiST consistent test on an index internal page with range query + */ +static bool +range_gist_consistent_int_range(TypeCacheEntry *typcache, + StrategyNumber strategy, + const RangeType *key, + const RangeType *query) +{ + switch (strategy) + { + case RANGESTRAT_BEFORE: + if (RangeIsEmpty(key) || RangeIsEmpty(query)) + return false; + return (!range_overright_internal(typcache, key, query)); + case RANGESTRAT_OVERLEFT: + if (RangeIsEmpty(key) || RangeIsEmpty(query)) + return false; + return (!range_after_internal(typcache, key, query)); + case RANGESTRAT_OVERLAPS: + return range_overlaps_internal(typcache, key, query); + case RANGESTRAT_OVERRIGHT: + if (RangeIsEmpty(key) || RangeIsEmpty(query)) + return false; + return (!range_before_internal(typcache, key, query)); + case RANGESTRAT_AFTER: + if (RangeIsEmpty(key) || RangeIsEmpty(query)) + return false; + return (!range_overleft_internal(typcache, key, query)); + case RANGESTRAT_ADJACENT: + if (RangeIsEmpty(key) || RangeIsEmpty(query)) + return false; + if (range_adjacent_internal(typcache, key, query)) + return true; + return range_overlaps_internal(typcache, key, query); + case RANGESTRAT_CONTAINS: + return range_contains_internal(typcache, key, query); + case RANGESTRAT_CONTAINED_BY: + + /* + * Empty ranges are contained by anything, so if key is or + * contains any empty ranges, we must descend into it. Otherwise, + * descend only if key overlaps the query. + */ + if (RangeIsOrContainsEmpty(key)) + return true; + return range_overlaps_internal(typcache, key, query); + case RANGESTRAT_EQ: + + /* + * If query is empty, descend only if the key is or contains any + * empty ranges. Otherwise, descend if key contains query. + */ + if (RangeIsEmpty(query)) + return RangeIsOrContainsEmpty(key); + return range_contains_internal(typcache, key, query); + default: + elog(ERROR, "unrecognized range strategy: %d", strategy); + return false; /* keep compiler quiet */ + } +} + +/* + * GiST consistent test on an index internal page with multirange query + */ +static bool +range_gist_consistent_int_multirange(TypeCacheEntry *typcache, + StrategyNumber strategy, + const RangeType *key, + const MultirangeType *query) +{ + switch (strategy) + { + case RANGESTRAT_BEFORE: + if (RangeIsEmpty(key) || MultirangeIsEmpty(query)) + return false; + return (!range_overright_multirange_internal(typcache, key, query)); + case RANGESTRAT_OVERLEFT: + if (RangeIsEmpty(key) || MultirangeIsEmpty(query)) + return false; + return (!range_after_multirange_internal(typcache, key, query)); + case RANGESTRAT_OVERLAPS: + return range_overlaps_multirange_internal(typcache, key, query); + case RANGESTRAT_OVERRIGHT: + if (RangeIsEmpty(key) || MultirangeIsEmpty(query)) + return false; + return (!range_before_multirange_internal(typcache, key, query)); + case RANGESTRAT_AFTER: + if (RangeIsEmpty(key) || MultirangeIsEmpty(query)) + return false; + return (!range_overleft_multirange_internal(typcache, key, query)); + case RANGESTRAT_ADJACENT: + if (RangeIsEmpty(key) || MultirangeIsEmpty(query)) + return false; + if (range_adjacent_multirange_internal(typcache, key, query)) + return true; + return range_overlaps_multirange_internal(typcache, key, query); + case RANGESTRAT_CONTAINS: + return range_contains_multirange_internal(typcache, key, query); + case RANGESTRAT_CONTAINED_BY: + + /* + * Empty ranges are contained by anything, so if key is or + * contains any empty ranges, we must descend into it. Otherwise, + * descend only if key overlaps the query. + */ + if (RangeIsOrContainsEmpty(key)) + return true; + return range_overlaps_multirange_internal(typcache, key, query); + case RANGESTRAT_EQ: + + /* + * If query is empty, descend only if the key is or contains any + * empty ranges. Otherwise, descend if key contains query. + */ + if (MultirangeIsEmpty(query)) + return RangeIsOrContainsEmpty(key); + return range_contains_multirange_internal(typcache, key, query); + default: + elog(ERROR, "unrecognized range strategy: %d", strategy); + return false; /* keep compiler quiet */ + } +} + +/* + * GiST consistent test on an index internal page with element query + */ +static bool +range_gist_consistent_int_element(TypeCacheEntry *typcache, + StrategyNumber strategy, + const RangeType *key, + Datum query) +{ + switch (strategy) + { + case RANGESTRAT_CONTAINS_ELEM: + return range_contains_elem_internal(typcache, key, query); + default: + elog(ERROR, "unrecognized range strategy: %d", strategy); + return false; /* keep compiler quiet */ + } +} + +/* + * GiST consistent test on an index leaf page with range query + */ +static bool +range_gist_consistent_leaf_range(TypeCacheEntry *typcache, + StrategyNumber strategy, + const RangeType *key, + const RangeType *query) +{ + switch (strategy) + { + case RANGESTRAT_BEFORE: + return range_before_internal(typcache, key, query); + case RANGESTRAT_OVERLEFT: + return range_overleft_internal(typcache, key, query); + case RANGESTRAT_OVERLAPS: + return range_overlaps_internal(typcache, key, query); + case RANGESTRAT_OVERRIGHT: + return range_overright_internal(typcache, key, query); + case RANGESTRAT_AFTER: + return range_after_internal(typcache, key, query); + case RANGESTRAT_ADJACENT: + return range_adjacent_internal(typcache, key, query); + case RANGESTRAT_CONTAINS: + return range_contains_internal(typcache, key, query); + case RANGESTRAT_CONTAINED_BY: + return range_contained_by_internal(typcache, key, query); + case RANGESTRAT_EQ: + return range_eq_internal(typcache, key, query); + default: + elog(ERROR, "unrecognized range strategy: %d", strategy); + return false; /* keep compiler quiet */ + } +} + +/* + * GiST consistent test on an index leaf page with multirange query + */ +static bool +range_gist_consistent_leaf_multirange(TypeCacheEntry *typcache, + StrategyNumber strategy, + const RangeType *key, + const MultirangeType *query) +{ + switch (strategy) + { + case RANGESTRAT_BEFORE: + return range_before_multirange_internal(typcache, key, query); + case RANGESTRAT_OVERLEFT: + return range_overleft_multirange_internal(typcache, key, query); + case RANGESTRAT_OVERLAPS: + return range_overlaps_multirange_internal(typcache, key, query); + case RANGESTRAT_OVERRIGHT: + return range_overright_multirange_internal(typcache, key, query); + case RANGESTRAT_AFTER: + return range_after_multirange_internal(typcache, key, query); + case RANGESTRAT_ADJACENT: + return range_adjacent_multirange_internal(typcache, key, query); + case RANGESTRAT_CONTAINS: + return range_contains_multirange_internal(typcache, key, query); + case RANGESTRAT_CONTAINED_BY: + return multirange_contains_range_internal(typcache, query, key); + case RANGESTRAT_EQ: + return multirange_union_range_equal(typcache, key, query); + default: + elog(ERROR, "unrecognized range strategy: %d", strategy); + return false; /* keep compiler quiet */ + } +} + +/* + * GiST consistent test on an index leaf page with element query + */ +static bool +range_gist_consistent_leaf_element(TypeCacheEntry *typcache, + StrategyNumber strategy, + const RangeType *key, + Datum query) +{ + switch (strategy) + { + case RANGESTRAT_CONTAINS_ELEM: + return range_contains_elem_internal(typcache, key, query); + default: + elog(ERROR, "unrecognized range strategy: %d", strategy); + return false; /* keep compiler quiet */ + } +} + +/* + * Trivial split: half of entries will be placed on one page + * and the other half on the other page. + */ +static void +range_gist_fallback_split(TypeCacheEntry *typcache, + GistEntryVector *entryvec, + GIST_SPLITVEC *v) +{ + RangeType *left_range = NULL; + RangeType *right_range = NULL; + OffsetNumber i, + maxoff, + split_idx; + + maxoff = entryvec->n - 1; + /* Split entries before this to left page, after to right: */ + split_idx = (maxoff - FirstOffsetNumber) / 2 + FirstOffsetNumber; + + v->spl_nleft = 0; + v->spl_nright = 0; + for (i = FirstOffsetNumber; i <= maxoff; i++) + { + RangeType *range = DatumGetRangeTypeP(entryvec->vector[i].key); + + if (i < split_idx) + PLACE_LEFT(range, i); + else + PLACE_RIGHT(range, i); + } + + v->spl_ldatum = RangeTypePGetDatum(left_range); + v->spl_rdatum = RangeTypePGetDatum(right_range); +} + +/* + * Split based on classes of ranges. + * + * See get_gist_range_class for class definitions. + * classes_groups is an array of length CLS_COUNT indicating the side of the + * split to which each class should go. + */ +static void +range_gist_class_split(TypeCacheEntry *typcache, + GistEntryVector *entryvec, + GIST_SPLITVEC *v, + SplitLR *classes_groups) +{ + RangeType *left_range = NULL; + RangeType *right_range = NULL; + OffsetNumber i, + maxoff; + + maxoff = entryvec->n - 1; + + v->spl_nleft = 0; + v->spl_nright = 0; + for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i)) + { + RangeType *range = DatumGetRangeTypeP(entryvec->vector[i].key); + int class; + + /* Get class of range */ + class = get_gist_range_class(range); + + /* Place range to appropriate page */ + if (classes_groups[class] == SPLIT_LEFT) + PLACE_LEFT(range, i); + else + { + Assert(classes_groups[class] == SPLIT_RIGHT); + PLACE_RIGHT(range, i); + } + } + + v->spl_ldatum = RangeTypePGetDatum(left_range); + v->spl_rdatum = RangeTypePGetDatum(right_range); +} + +/* + * Sorting based split. First half of entries according to the sort will be + * placed to one page, and second half of entries will be placed to other + * page. use_upper_bound parameter indicates whether to use upper or lower + * bound for sorting. + */ +static void +range_gist_single_sorting_split(TypeCacheEntry *typcache, + GistEntryVector *entryvec, + GIST_SPLITVEC *v, + bool use_upper_bound) +{ + SingleBoundSortItem *sortItems; + RangeType *left_range = NULL; + RangeType *right_range = NULL; + OffsetNumber i, + maxoff, + split_idx; + + maxoff = entryvec->n - 1; + + sortItems = (SingleBoundSortItem *) + palloc(maxoff * sizeof(SingleBoundSortItem)); + + /* + * Prepare auxiliary array and sort the values. + */ + for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i)) + { + RangeType *range = DatumGetRangeTypeP(entryvec->vector[i].key); + RangeBound bound2; + bool empty; + + sortItems[i - 1].index = i; + /* Put appropriate bound into array */ + if (use_upper_bound) + range_deserialize(typcache, range, &bound2, + &sortItems[i - 1].bound, &empty); + else + range_deserialize(typcache, range, &sortItems[i - 1].bound, + &bound2, &empty); + Assert(!empty); + } + + qsort_arg(sortItems, maxoff, sizeof(SingleBoundSortItem), + single_bound_cmp, typcache); + + split_idx = maxoff / 2; + + v->spl_nleft = 0; + v->spl_nright = 0; + + for (i = 0; i < maxoff; i++) + { + int idx = sortItems[i].index; + RangeType *range = DatumGetRangeTypeP(entryvec->vector[idx].key); + + if (i < split_idx) + PLACE_LEFT(range, idx); + else + PLACE_RIGHT(range, idx); + } + + v->spl_ldatum = RangeTypePGetDatum(left_range); + v->spl_rdatum = RangeTypePGetDatum(right_range); +} + +/* + * Double sorting split algorithm. + * + * The algorithm considers dividing ranges into two groups. The first (left) + * group contains general left bound. The second (right) group contains + * general right bound. The challenge is to find upper bound of left group + * and lower bound of right group so that overlap of groups is minimal and + * ratio of distribution is acceptable. Algorithm finds for each lower bound of + * right group minimal upper bound of left group, and for each upper bound of + * left group maximal lower bound of right group. For each found pair + * range_gist_consider_split considers replacement of currently selected + * split with the new one. + * + * After that, all the entries are divided into three groups: + * 1) Entries which should be placed to the left group + * 2) Entries which should be placed to the right group + * 3) "Common entries" which can be placed to either group without affecting + * amount of overlap. + * + * The common ranges are distributed by difference of distance from lower + * bound of common range to lower bound of right group and distance from upper + * bound of common range to upper bound of left group. + * + * For details see: + * "A new double sorting-based node splitting algorithm for R-tree", + * A. Korotkov + * http://syrcose.ispras.ru/2011/files/SYRCoSE2011_Proceedings.pdf#page=36 + */ +static void +range_gist_double_sorting_split(TypeCacheEntry *typcache, + GistEntryVector *entryvec, + GIST_SPLITVEC *v) +{ + ConsiderSplitContext context; + OffsetNumber i, + maxoff; + RangeType *left_range = NULL, + *right_range = NULL; + int common_entries_count; + NonEmptyRange *by_lower, + *by_upper; + CommonEntry *common_entries; + int nentries, + i1, + i2; + RangeBound *right_lower, + *left_upper; + + memset(&context, 0, sizeof(ConsiderSplitContext)); + context.typcache = typcache; + context.has_subtype_diff = OidIsValid(typcache->rng_subdiff_finfo.fn_oid); + + maxoff = entryvec->n - 1; + nentries = context.entries_count = maxoff - FirstOffsetNumber + 1; + context.first = true; + + /* Allocate arrays for sorted range bounds */ + by_lower = (NonEmptyRange *) palloc(nentries * sizeof(NonEmptyRange)); + by_upper = (NonEmptyRange *) palloc(nentries * sizeof(NonEmptyRange)); + + /* Fill arrays of bounds */ + for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i)) + { + RangeType *range = DatumGetRangeTypeP(entryvec->vector[i].key); + bool empty; + + range_deserialize(typcache, range, + &by_lower[i - FirstOffsetNumber].lower, + &by_lower[i - FirstOffsetNumber].upper, + &empty); + Assert(!empty); + } + + /* + * Make two arrays of range bounds: one sorted by lower bound and another + * sorted by upper bound. + */ + memcpy(by_upper, by_lower, nentries * sizeof(NonEmptyRange)); + qsort_arg(by_lower, nentries, sizeof(NonEmptyRange), + interval_cmp_lower, typcache); + qsort_arg(by_upper, nentries, sizeof(NonEmptyRange), + interval_cmp_upper, typcache); + + /*---------- + * The goal is to form a left and right range, so that every entry + * range is contained by either left or right interval (or both). + * + * For example, with the ranges (0,1), (1,3), (2,3), (2,4): + * + * 0 1 2 3 4 + * +-+ + * +---+ + * +-+ + * +---+ + * + * The left and right ranges are of the form (0,a) and (b,4). + * We first consider splits where b is the lower bound of an entry. + * We iterate through all entries, and for each b, calculate the + * smallest possible a. Then we consider splits where a is the + * upper bound of an entry, and for each a, calculate the greatest + * possible b. + * + * In the above example, the first loop would consider splits: + * b=0: (0,1)-(0,4) + * b=1: (0,1)-(1,4) + * b=2: (0,3)-(2,4) + * + * And the second loop: + * a=1: (0,1)-(1,4) + * a=3: (0,3)-(2,4) + * a=4: (0,4)-(2,4) + *---------- + */ + + /* + * Iterate over lower bound of right group, finding smallest possible + * upper bound of left group. + */ + i1 = 0; + i2 = 0; + right_lower = &by_lower[i1].lower; + left_upper = &by_upper[i2].lower; + while (true) + { + /* + * Find next lower bound of right group. + */ + while (i1 < nentries && + range_cmp_bounds(typcache, right_lower, + &by_lower[i1].lower) == 0) + { + if (range_cmp_bounds(typcache, &by_lower[i1].upper, + left_upper) > 0) + left_upper = &by_lower[i1].upper; + i1++; + } + if (i1 >= nentries) + break; + right_lower = &by_lower[i1].lower; + + /* + * Find count of ranges which anyway should be placed to the left + * group. + */ + while (i2 < nentries && + range_cmp_bounds(typcache, &by_upper[i2].upper, + left_upper) <= 0) + i2++; + + /* + * Consider found split to see if it's better than what we had. + */ + range_gist_consider_split(&context, right_lower, i1, left_upper, i2); + } + + /* + * Iterate over upper bound of left group finding greatest possible lower + * bound of right group. + */ + i1 = nentries - 1; + i2 = nentries - 1; + right_lower = &by_lower[i1].upper; + left_upper = &by_upper[i2].upper; + while (true) + { + /* + * Find next upper bound of left group. + */ + while (i2 >= 0 && + range_cmp_bounds(typcache, left_upper, + &by_upper[i2].upper) == 0) + { + if (range_cmp_bounds(typcache, &by_upper[i2].lower, + right_lower) < 0) + right_lower = &by_upper[i2].lower; + i2--; + } + if (i2 < 0) + break; + left_upper = &by_upper[i2].upper; + + /* + * Find count of intervals which anyway should be placed to the right + * group. + */ + while (i1 >= 0 && + range_cmp_bounds(typcache, &by_lower[i1].lower, + right_lower) >= 0) + i1--; + + /* + * Consider found split to see if it's better than what we had. + */ + range_gist_consider_split(&context, right_lower, i1 + 1, + left_upper, i2 + 1); + } + + /* + * If we failed to find any acceptable splits, use trivial split. + */ + if (context.first) + { + range_gist_fallback_split(typcache, entryvec, v); + return; + } + + /* + * Ok, we have now selected bounds of the groups. Now we have to + * distribute entries themselves. At first we distribute entries which can + * be placed unambiguously and collect "common entries" to array. + */ + + /* Allocate vectors for results */ + v->spl_left = (OffsetNumber *) palloc(nentries * sizeof(OffsetNumber)); + v->spl_right = (OffsetNumber *) palloc(nentries * sizeof(OffsetNumber)); + v->spl_nleft = 0; + v->spl_nright = 0; + + /* + * Allocate an array for "common entries" - entries which can be placed to + * either group without affecting overlap along selected axis. + */ + common_entries_count = 0; + common_entries = (CommonEntry *) palloc(nentries * sizeof(CommonEntry)); + + /* + * Distribute entries which can be distributed unambiguously, and collect + * common entries. + */ + for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i)) + { + RangeType *range; + RangeBound lower, + upper; + bool empty; + + /* + * Get upper and lower bounds along selected axis. + */ + range = DatumGetRangeTypeP(entryvec->vector[i].key); + + range_deserialize(typcache, range, &lower, &upper, &empty); + + if (range_cmp_bounds(typcache, &upper, context.left_upper) <= 0) + { + /* Fits in the left group */ + if (range_cmp_bounds(typcache, &lower, context.right_lower) >= 0) + { + /* Fits also in the right group, so "common entry" */ + common_entries[common_entries_count].index = i; + if (context.has_subtype_diff) + { + /* + * delta = (lower - context.right_lower) - + * (context.left_upper - upper) + */ + common_entries[common_entries_count].delta = + call_subtype_diff(typcache, + lower.val, + context.right_lower->val) - + call_subtype_diff(typcache, + context.left_upper->val, + upper.val); + } + else + { + /* Without subtype_diff, take all deltas as zero */ + common_entries[common_entries_count].delta = 0; + } + common_entries_count++; + } + else + { + /* Doesn't fit to the right group, so join to the left group */ + PLACE_LEFT(range, i); + } + } + else + { + /* + * Each entry should fit on either left or right group. Since this + * entry didn't fit in the left group, it better fit in the right + * group. + */ + Assert(range_cmp_bounds(typcache, &lower, + context.right_lower) >= 0); + PLACE_RIGHT(range, i); + } + } + + /* + * Distribute "common entries", if any. + */ + if (common_entries_count > 0) + { + /* + * Sort "common entries" by calculated deltas in order to distribute + * the most ambiguous entries first. + */ + qsort(common_entries, common_entries_count, sizeof(CommonEntry), + common_entry_cmp); + + /* + * Distribute "common entries" between groups according to sorting. + */ + for (i = 0; i < common_entries_count; i++) + { + RangeType *range; + int idx = common_entries[i].index; + + range = DatumGetRangeTypeP(entryvec->vector[idx].key); + + /* + * Check if we have to place this entry in either group to achieve + * LIMIT_RATIO. + */ + if (i < context.common_left) + PLACE_LEFT(range, idx); + else + PLACE_RIGHT(range, idx); + } + } + + v->spl_ldatum = PointerGetDatum(left_range); + v->spl_rdatum = PointerGetDatum(right_range); +} + +/* + * Consider replacement of currently selected split with a better one + * during range_gist_double_sorting_split. + */ +static void +range_gist_consider_split(ConsiderSplitContext *context, + RangeBound *right_lower, int min_left_count, + RangeBound *left_upper, int max_left_count) +{ + int left_count, + right_count; + float4 ratio, + overlap; + + /* + * Calculate entries distribution ratio assuming most uniform distribution + * of common entries. + */ + if (min_left_count >= (context->entries_count + 1) / 2) + left_count = min_left_count; + else if (max_left_count <= context->entries_count / 2) + left_count = max_left_count; + else + left_count = context->entries_count / 2; + right_count = context->entries_count - left_count; + + /* + * Ratio of split: quotient between size of smaller group and total + * entries count. This is necessarily 0.5 or less; if it's less than + * LIMIT_RATIO then we will never accept the new split. + */ + ratio = ((float4) Min(left_count, right_count)) / + ((float4) context->entries_count); + + if (ratio > LIMIT_RATIO) + { + bool selectthis = false; + + /* + * The ratio is acceptable, so compare current split with previously + * selected one. We search for minimal overlap (allowing negative + * values) and minimal ratio secondarily. If subtype_diff is + * available, it's used for overlap measure. Without subtype_diff we + * use number of "common entries" as an overlap measure. + */ + if (context->has_subtype_diff) + overlap = call_subtype_diff(context->typcache, + left_upper->val, + right_lower->val); + else + overlap = max_left_count - min_left_count; + + /* If there is no previous selection, select this split */ + if (context->first) + selectthis = true; + else + { + /* + * Choose the new split if it has a smaller overlap, or same + * overlap but better ratio. + */ + if (overlap < context->overlap || + (overlap == context->overlap && ratio > context->ratio)) + selectthis = true; + } + + if (selectthis) + { + /* save information about selected split */ + context->first = false; + context->ratio = ratio; + context->overlap = overlap; + context->right_lower = right_lower; + context->left_upper = left_upper; + context->common_left = max_left_count - left_count; + context->common_right = left_count - min_left_count; + } + } +} + +/* + * Find class number for range. + * + * The class number is a valid combination of the properties of the + * range. Note: the highest possible number is 8, because CLS_EMPTY + * can't be combined with anything else. + */ +static int +get_gist_range_class(RangeType *range) +{ + int classNumber; + char flags; + + flags = range_get_flags(range); + if (flags & RANGE_EMPTY) + { + classNumber = CLS_EMPTY; + } + else + { + classNumber = 0; + if (flags & RANGE_LB_INF) + classNumber |= CLS_LOWER_INF; + if (flags & RANGE_UB_INF) + classNumber |= CLS_UPPER_INF; + if (flags & RANGE_CONTAIN_EMPTY) + classNumber |= CLS_CONTAIN_EMPTY; + } + return classNumber; +} + +/* + * Comparison function for range_gist_single_sorting_split. + */ +static int +single_bound_cmp(const void *a, const void *b, void *arg) +{ + SingleBoundSortItem *i1 = (SingleBoundSortItem *) a; + SingleBoundSortItem *i2 = (SingleBoundSortItem *) b; + TypeCacheEntry *typcache = (TypeCacheEntry *) arg; + + return range_cmp_bounds(typcache, &i1->bound, &i2->bound); +} + +/* + * Compare NonEmptyRanges by lower bound. + */ +static int +interval_cmp_lower(const void *a, const void *b, void *arg) +{ + NonEmptyRange *i1 = (NonEmptyRange *) a; + NonEmptyRange *i2 = (NonEmptyRange *) b; + TypeCacheEntry *typcache = (TypeCacheEntry *) arg; + + return range_cmp_bounds(typcache, &i1->lower, &i2->lower); +} + +/* + * Compare NonEmptyRanges by upper bound. + */ +static int +interval_cmp_upper(const void *a, const void *b, void *arg) +{ + NonEmptyRange *i1 = (NonEmptyRange *) a; + NonEmptyRange *i2 = (NonEmptyRange *) b; + TypeCacheEntry *typcache = (TypeCacheEntry *) arg; + + return range_cmp_bounds(typcache, &i1->upper, &i2->upper); +} + +/* + * Compare CommonEntrys by their deltas. + */ +static int +common_entry_cmp(const void *i1, const void *i2) +{ + double delta1 = ((CommonEntry *) i1)->delta; + double delta2 = ((CommonEntry *) i2)->delta; + + if (delta1 < delta2) + return -1; + else if (delta1 > delta2) + return 1; + else + return 0; +} + +/* + * Convenience function to invoke type-specific subtype_diff function. + * Caller must have already checked that there is one for the range type. + */ +static float8 +call_subtype_diff(TypeCacheEntry *typcache, Datum val1, Datum val2) +{ + float8 value; + + value = DatumGetFloat8(FunctionCall2Coll(&typcache->rng_subdiff_finfo, + typcache->rng_collation, + val1, val2)); + /* Cope with buggy subtype_diff function by returning zero */ + if (value >= 0.0) + return value; + return 0.0; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/rangetypes_selfuncs.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/rangetypes_selfuncs.c new file mode 100644 index 00000000000..fbabb3e18ce --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/rangetypes_selfuncs.c @@ -0,0 +1,1223 @@ +/*------------------------------------------------------------------------- + * + * rangetypes_selfuncs.c + * Functions for selectivity estimation of range operators + * + * Estimates are based on histograms of lower and upper bounds, and the + * fraction of empty ranges. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/adt/rangetypes_selfuncs.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <math.h> + +#include "access/htup_details.h" +#include "catalog/pg_operator.h" +#include "catalog/pg_statistic.h" +#include "catalog/pg_type.h" +#include "utils/float.h" +#include "utils/fmgrprotos.h" +#include "utils/lsyscache.h" +#include "utils/rangetypes.h" +#include "utils/selfuncs.h" +#include "utils/typcache.h" + +static double calc_rangesel(TypeCacheEntry *typcache, VariableStatData *vardata, + const RangeType *constval, Oid operator); +static double default_range_selectivity(Oid operator); +static double calc_hist_selectivity(TypeCacheEntry *typcache, + VariableStatData *vardata, const RangeType *constval, + Oid operator); +static double calc_hist_selectivity_scalar(TypeCacheEntry *typcache, + const RangeBound *constbound, + const RangeBound *hist, int hist_nvalues, + bool equal); +static int rbound_bsearch(TypeCacheEntry *typcache, const RangeBound *value, + const RangeBound *hist, int hist_length, bool equal); +static float8 get_position(TypeCacheEntry *typcache, const RangeBound *value, + const RangeBound *hist1, const RangeBound *hist2); +static float8 get_len_position(double value, double hist1, double hist2); +static float8 get_distance(TypeCacheEntry *typcache, const RangeBound *bound1, + const RangeBound *bound2); +static int length_hist_bsearch(Datum *length_hist_values, + int length_hist_nvalues, double value, bool equal); +static double calc_length_hist_frac(Datum *length_hist_values, + int length_hist_nvalues, double length1, double length2, bool equal); +static double calc_hist_selectivity_contained(TypeCacheEntry *typcache, + const RangeBound *lower, RangeBound *upper, + const RangeBound *hist_lower, int hist_nvalues, + Datum *length_hist_values, int length_hist_nvalues); +static double calc_hist_selectivity_contains(TypeCacheEntry *typcache, + const RangeBound *lower, const RangeBound *upper, + const RangeBound *hist_lower, int hist_nvalues, + Datum *length_hist_values, int length_hist_nvalues); + +/* + * Returns a default selectivity estimate for given operator, when we don't + * have statistics or cannot use them for some reason. + */ +static double +default_range_selectivity(Oid operator) +{ + switch (operator) + { + case OID_RANGE_OVERLAP_OP: + return 0.01; + + case OID_RANGE_CONTAINS_OP: + case OID_RANGE_CONTAINED_OP: + return 0.005; + + case OID_RANGE_CONTAINS_ELEM_OP: + case OID_RANGE_ELEM_CONTAINED_OP: + + /* + * "range @> elem" is more or less identical to a scalar + * inequality "A >= b AND A <= c". + */ + return DEFAULT_RANGE_INEQ_SEL; + + case OID_RANGE_LESS_OP: + case OID_RANGE_LESS_EQUAL_OP: + case OID_RANGE_GREATER_OP: + case OID_RANGE_GREATER_EQUAL_OP: + case OID_RANGE_LEFT_OP: + case OID_RANGE_RIGHT_OP: + case OID_RANGE_OVERLAPS_LEFT_OP: + case OID_RANGE_OVERLAPS_RIGHT_OP: + /* these are similar to regular scalar inequalities */ + return DEFAULT_INEQ_SEL; + + default: + /* all range operators should be handled above, but just in case */ + return 0.01; + } +} + +/* + * rangesel -- restriction selectivity for range operators + */ +Datum +rangesel(PG_FUNCTION_ARGS) +{ + PlannerInfo *root = (PlannerInfo *) PG_GETARG_POINTER(0); + Oid operator = PG_GETARG_OID(1); + List *args = (List *) PG_GETARG_POINTER(2); + int varRelid = PG_GETARG_INT32(3); + VariableStatData vardata; + Node *other; + bool varonleft; + Selectivity selec; + TypeCacheEntry *typcache = NULL; + RangeType *constrange = NULL; + + /* + * If expression is not (variable op something) or (something op + * variable), then punt and return a default estimate. + */ + if (!get_restriction_variable(root, args, varRelid, + &vardata, &other, &varonleft)) + PG_RETURN_FLOAT8(default_range_selectivity(operator)); + + /* + * Can't do anything useful if the something is not a constant, either. + */ + if (!IsA(other, Const)) + { + ReleaseVariableStats(vardata); + PG_RETURN_FLOAT8(default_range_selectivity(operator)); + } + + /* + * All the range operators are strict, so we can cope with a NULL constant + * right away. + */ + if (((Const *) other)->constisnull) + { + ReleaseVariableStats(vardata); + PG_RETURN_FLOAT8(0.0); + } + + /* + * If var is on the right, commute the operator, so that we can assume the + * var is on the left in what follows. + */ + if (!varonleft) + { + /* we have other Op var, commute to make var Op other */ + operator = get_commutator(operator); + if (!operator) + { + /* Use default selectivity (should we raise an error instead?) */ + ReleaseVariableStats(vardata); + PG_RETURN_FLOAT8(default_range_selectivity(operator)); + } + } + + /* + * OK, there's a Var and a Const we're dealing with here. We need the + * Const to be of same range type as the column, else we can't do anything + * useful. (Such cases will likely fail at runtime, but here we'd rather + * just return a default estimate.) + * + * If the operator is "range @> element", the constant should be of the + * element type of the range column. Convert it to a range that includes + * only that single point, so that we don't need special handling for that + * in what follows. + */ + if (operator == OID_RANGE_CONTAINS_ELEM_OP) + { + typcache = range_get_typcache(fcinfo, vardata.vartype); + + if (((Const *) other)->consttype == typcache->rngelemtype->type_id) + { + RangeBound lower, + upper; + + lower.inclusive = true; + lower.val = ((Const *) other)->constvalue; + lower.infinite = false; + lower.lower = true; + upper.inclusive = true; + upper.val = ((Const *) other)->constvalue; + upper.infinite = false; + upper.lower = false; + constrange = range_serialize(typcache, &lower, &upper, false, NULL); + } + } + else if (operator == OID_RANGE_ELEM_CONTAINED_OP) + { + /* + * Here, the Var is the elem, not the range. For now we just punt and + * return the default estimate. In future we could disassemble the + * range constant and apply scalarineqsel ... + */ + } + else if (((Const *) other)->consttype == vardata.vartype) + { + /* Both sides are the same range type */ + typcache = range_get_typcache(fcinfo, vardata.vartype); + + constrange = DatumGetRangeTypeP(((Const *) other)->constvalue); + } + + /* + * If we got a valid constant on one side of the operator, proceed to + * estimate using statistics. Otherwise punt and return a default constant + * estimate. Note that calc_rangesel need not handle + * OID_RANGE_ELEM_CONTAINED_OP. + */ + if (constrange) + selec = calc_rangesel(typcache, &vardata, constrange, operator); + else + selec = default_range_selectivity(operator); + + ReleaseVariableStats(vardata); + + CLAMP_PROBABILITY(selec); + + PG_RETURN_FLOAT8((float8) selec); +} + +static double +calc_rangesel(TypeCacheEntry *typcache, VariableStatData *vardata, + const RangeType *constval, Oid operator) +{ + double hist_selec; + double selec; + float4 empty_frac, + null_frac; + + /* + * First look up the fraction of NULLs and empty ranges from pg_statistic. + */ + if (HeapTupleIsValid(vardata->statsTuple)) + { + Form_pg_statistic stats; + AttStatsSlot sslot; + + stats = (Form_pg_statistic) GETSTRUCT(vardata->statsTuple); + null_frac = stats->stanullfrac; + + /* Try to get fraction of empty ranges */ + if (get_attstatsslot(&sslot, vardata->statsTuple, + STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM, + InvalidOid, + ATTSTATSSLOT_NUMBERS)) + { + if (sslot.nnumbers != 1) + elog(ERROR, "invalid empty fraction statistic"); /* shouldn't happen */ + empty_frac = sslot.numbers[0]; + free_attstatsslot(&sslot); + } + else + { + /* No empty fraction statistic. Assume no empty ranges. */ + empty_frac = 0.0; + } + } + else + { + /* + * No stats are available. Follow through the calculations below + * anyway, assuming no NULLs and no empty ranges. This still allows us + * to give a better-than-nothing estimate based on whether the + * constant is an empty range or not. + */ + null_frac = 0.0; + empty_frac = 0.0; + } + + if (RangeIsEmpty(constval)) + { + /* + * An empty range matches all ranges, all empty ranges, or nothing, + * depending on the operator + */ + switch (operator) + { + /* these return false if either argument is empty */ + case OID_RANGE_OVERLAP_OP: + case OID_RANGE_OVERLAPS_LEFT_OP: + case OID_RANGE_OVERLAPS_RIGHT_OP: + case OID_RANGE_LEFT_OP: + case OID_RANGE_RIGHT_OP: + /* nothing is less than an empty range */ + case OID_RANGE_LESS_OP: + selec = 0.0; + break; + + /* only empty ranges can be contained by an empty range */ + case OID_RANGE_CONTAINED_OP: + /* only empty ranges are <= an empty range */ + case OID_RANGE_LESS_EQUAL_OP: + selec = empty_frac; + break; + + /* everything contains an empty range */ + case OID_RANGE_CONTAINS_OP: + /* everything is >= an empty range */ + case OID_RANGE_GREATER_EQUAL_OP: + selec = 1.0; + break; + + /* all non-empty ranges are > an empty range */ + case OID_RANGE_GREATER_OP: + selec = 1.0 - empty_frac; + break; + + /* an element cannot be empty */ + case OID_RANGE_CONTAINS_ELEM_OP: + default: + elog(ERROR, "unexpected operator %u", operator); + selec = 0.0; /* keep compiler quiet */ + break; + } + } + else + { + /* + * Calculate selectivity using bound histograms. If that fails for + * some reason, e.g no histogram in pg_statistic, use the default + * constant estimate for the fraction of non-empty values. This is + * still somewhat better than just returning the default estimate, + * because this still takes into account the fraction of empty and + * NULL tuples, if we had statistics for them. + */ + hist_selec = calc_hist_selectivity(typcache, vardata, constval, + operator); + if (hist_selec < 0.0) + hist_selec = default_range_selectivity(operator); + + /* + * Now merge the results for the empty ranges and histogram + * calculations, realizing that the histogram covers only the + * non-null, non-empty values. + */ + if (operator == OID_RANGE_CONTAINED_OP) + { + /* empty is contained by anything non-empty */ + selec = (1.0 - empty_frac) * hist_selec + empty_frac; + } + else + { + /* with any other operator, empty Op non-empty matches nothing */ + selec = (1.0 - empty_frac) * hist_selec; + } + } + + /* all range operators are strict */ + selec *= (1.0 - null_frac); + + /* result should be in range, but make sure... */ + CLAMP_PROBABILITY(selec); + + return selec; +} + +/* + * Calculate range operator selectivity using histograms of range bounds. + * + * This estimate is for the portion of values that are not empty and not + * NULL. + */ +static double +calc_hist_selectivity(TypeCacheEntry *typcache, VariableStatData *vardata, + const RangeType *constval, Oid operator) +{ + AttStatsSlot hslot; + AttStatsSlot lslot; + int nhist; + RangeBound *hist_lower; + RangeBound *hist_upper; + int i; + RangeBound const_lower; + RangeBound const_upper; + bool empty; + double hist_selec; + + /* Can't use the histogram with insecure range support functions */ + if (!statistic_proc_security_check(vardata, + typcache->rng_cmp_proc_finfo.fn_oid)) + return -1; + if (OidIsValid(typcache->rng_subdiff_finfo.fn_oid) && + !statistic_proc_security_check(vardata, + typcache->rng_subdiff_finfo.fn_oid)) + return -1; + + /* Try to get histogram of ranges */ + if (!(HeapTupleIsValid(vardata->statsTuple) && + get_attstatsslot(&hslot, vardata->statsTuple, + STATISTIC_KIND_BOUNDS_HISTOGRAM, InvalidOid, + ATTSTATSSLOT_VALUES))) + return -1.0; + + /* check that it's a histogram, not just a dummy entry */ + if (hslot.nvalues < 2) + { + free_attstatsslot(&hslot); + return -1.0; + } + + /* + * Convert histogram of ranges into histograms of its lower and upper + * bounds. + */ + nhist = hslot.nvalues; + hist_lower = (RangeBound *) palloc(sizeof(RangeBound) * nhist); + hist_upper = (RangeBound *) palloc(sizeof(RangeBound) * nhist); + for (i = 0; i < nhist; i++) + { + range_deserialize(typcache, DatumGetRangeTypeP(hslot.values[i]), + &hist_lower[i], &hist_upper[i], &empty); + /* The histogram should not contain any empty ranges */ + if (empty) + elog(ERROR, "bounds histogram contains an empty range"); + } + + /* @> and @< also need a histogram of range lengths */ + if (operator == OID_RANGE_CONTAINS_OP || + operator == OID_RANGE_CONTAINED_OP) + { + if (!(HeapTupleIsValid(vardata->statsTuple) && + get_attstatsslot(&lslot, vardata->statsTuple, + STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM, + InvalidOid, + ATTSTATSSLOT_VALUES))) + { + free_attstatsslot(&hslot); + return -1.0; + } + + /* check that it's a histogram, not just a dummy entry */ + if (lslot.nvalues < 2) + { + free_attstatsslot(&lslot); + free_attstatsslot(&hslot); + return -1.0; + } + } + else + memset(&lslot, 0, sizeof(lslot)); + + /* Extract the bounds of the constant value. */ + range_deserialize(typcache, constval, &const_lower, &const_upper, &empty); + Assert(!empty); + + /* + * Calculate selectivity comparing the lower or upper bound of the + * constant with the histogram of lower or upper bounds. + */ + switch (operator) + { + case OID_RANGE_LESS_OP: + + /* + * The regular b-tree comparison operators (<, <=, >, >=) compare + * the lower bounds first, and the upper bounds for values with + * equal lower bounds. Estimate that by comparing the lower bounds + * only. This gives a fairly accurate estimate assuming there + * aren't many rows with a lower bound equal to the constant's + * lower bound. + */ + hist_selec = + calc_hist_selectivity_scalar(typcache, &const_lower, + hist_lower, nhist, false); + break; + + case OID_RANGE_LESS_EQUAL_OP: + hist_selec = + calc_hist_selectivity_scalar(typcache, &const_lower, + hist_lower, nhist, true); + break; + + case OID_RANGE_GREATER_OP: + hist_selec = + 1 - calc_hist_selectivity_scalar(typcache, &const_lower, + hist_lower, nhist, false); + break; + + case OID_RANGE_GREATER_EQUAL_OP: + hist_selec = + 1 - calc_hist_selectivity_scalar(typcache, &const_lower, + hist_lower, nhist, true); + break; + + case OID_RANGE_LEFT_OP: + /* var << const when upper(var) < lower(const) */ + hist_selec = + calc_hist_selectivity_scalar(typcache, &const_lower, + hist_upper, nhist, false); + break; + + case OID_RANGE_RIGHT_OP: + /* var >> const when lower(var) > upper(const) */ + hist_selec = + 1 - calc_hist_selectivity_scalar(typcache, &const_upper, + hist_lower, nhist, true); + break; + + case OID_RANGE_OVERLAPS_RIGHT_OP: + /* compare lower bounds */ + hist_selec = + 1 - calc_hist_selectivity_scalar(typcache, &const_lower, + hist_lower, nhist, false); + break; + + case OID_RANGE_OVERLAPS_LEFT_OP: + /* compare upper bounds */ + hist_selec = + calc_hist_selectivity_scalar(typcache, &const_upper, + hist_upper, nhist, true); + break; + + case OID_RANGE_OVERLAP_OP: + case OID_RANGE_CONTAINS_ELEM_OP: + + /* + * A && B <=> NOT (A << B OR A >> B). + * + * Since A << B and A >> B are mutually exclusive events we can + * sum their probabilities to find probability of (A << B OR A >> + * B). + * + * "range @> elem" is equivalent to "range && [elem,elem]". The + * caller already constructed the singular range from the element + * constant, so just treat it the same as &&. + */ + hist_selec = + calc_hist_selectivity_scalar(typcache, &const_lower, hist_upper, + nhist, false); + hist_selec += + (1.0 - calc_hist_selectivity_scalar(typcache, &const_upper, hist_lower, + nhist, true)); + hist_selec = 1.0 - hist_selec; + break; + + case OID_RANGE_CONTAINS_OP: + hist_selec = + calc_hist_selectivity_contains(typcache, &const_lower, + &const_upper, hist_lower, nhist, + lslot.values, lslot.nvalues); + break; + + case OID_RANGE_CONTAINED_OP: + if (const_lower.infinite) + { + /* + * Lower bound no longer matters. Just estimate the fraction + * with an upper bound <= const upper bound + */ + hist_selec = + calc_hist_selectivity_scalar(typcache, &const_upper, + hist_upper, nhist, true); + } + else if (const_upper.infinite) + { + hist_selec = + 1.0 - calc_hist_selectivity_scalar(typcache, &const_lower, + hist_lower, nhist, false); + } + else + { + hist_selec = + calc_hist_selectivity_contained(typcache, &const_lower, + &const_upper, hist_lower, nhist, + lslot.values, lslot.nvalues); + } + break; + + default: + elog(ERROR, "unknown range operator %u", operator); + hist_selec = -1.0; /* keep compiler quiet */ + break; + } + + free_attstatsslot(&lslot); + free_attstatsslot(&hslot); + + return hist_selec; +} + + +/* + * Look up the fraction of values less than (or equal, if 'equal' argument + * is true) a given const in a histogram of range bounds. + */ +static double +calc_hist_selectivity_scalar(TypeCacheEntry *typcache, const RangeBound *constbound, + const RangeBound *hist, int hist_nvalues, bool equal) +{ + Selectivity selec; + int index; + + /* + * Find the histogram bin the given constant falls into. Estimate + * selectivity as the number of preceding whole bins. + */ + index = rbound_bsearch(typcache, constbound, hist, hist_nvalues, equal); + selec = (Selectivity) (Max(index, 0)) / (Selectivity) (hist_nvalues - 1); + + /* Adjust using linear interpolation within the bin */ + if (index >= 0 && index < hist_nvalues - 1) + selec += get_position(typcache, constbound, &hist[index], + &hist[index + 1]) / (Selectivity) (hist_nvalues - 1); + + return selec; +} + +/* + * Binary search on an array of range bounds. Returns greatest index of range + * bound in array which is less(less or equal) than given range bound. If all + * range bounds in array are greater or equal(greater) than given range bound, + * return -1. When "equal" flag is set conditions in brackets are used. + * + * This function is used in scalar operator selectivity estimation. Another + * goal of this function is to find a histogram bin where to stop + * interpolation of portion of bounds which are less than or equal to given bound. + */ +static int +rbound_bsearch(TypeCacheEntry *typcache, const RangeBound *value, const RangeBound *hist, + int hist_length, bool equal) +{ + int lower = -1, + upper = hist_length - 1, + cmp, + middle; + + while (lower < upper) + { + middle = (lower + upper + 1) / 2; + cmp = range_cmp_bounds(typcache, &hist[middle], value); + + if (cmp < 0 || (equal && cmp == 0)) + lower = middle; + else + upper = middle - 1; + } + return lower; +} + + +/* + * Binary search on length histogram. Returns greatest index of range length in + * histogram which is less than (less than or equal) the given length value. If + * all lengths in the histogram are greater than (greater than or equal) the + * given length, returns -1. + */ +static int +length_hist_bsearch(Datum *length_hist_values, int length_hist_nvalues, + double value, bool equal) +{ + int lower = -1, + upper = length_hist_nvalues - 1, + middle; + + while (lower < upper) + { + double middleval; + + middle = (lower + upper + 1) / 2; + + middleval = DatumGetFloat8(length_hist_values[middle]); + if (middleval < value || (equal && middleval <= value)) + lower = middle; + else + upper = middle - 1; + } + return lower; +} + +/* + * Get relative position of value in histogram bin in [0,1] range. + */ +static float8 +get_position(TypeCacheEntry *typcache, const RangeBound *value, const RangeBound *hist1, + const RangeBound *hist2) +{ + bool has_subdiff = OidIsValid(typcache->rng_subdiff_finfo.fn_oid); + float8 position; + + if (!hist1->infinite && !hist2->infinite) + { + float8 bin_width; + + /* + * Both bounds are finite. Assuming the subtype's comparison function + * works sanely, the value must be finite, too, because it lies + * somewhere between the bounds. If it doesn't, arbitrarily return + * 0.5. + */ + if (value->infinite) + return 0.5; + + /* Can't interpolate without subdiff function */ + if (!has_subdiff) + return 0.5; + + /* Calculate relative position using subdiff function. */ + bin_width = DatumGetFloat8(FunctionCall2Coll(&typcache->rng_subdiff_finfo, + typcache->rng_collation, + hist2->val, + hist1->val)); + if (isnan(bin_width) || bin_width <= 0.0) + return 0.5; /* punt for NaN or zero-width bin */ + + position = DatumGetFloat8(FunctionCall2Coll(&typcache->rng_subdiff_finfo, + typcache->rng_collation, + value->val, + hist1->val)) + / bin_width; + + if (isnan(position)) + return 0.5; /* punt for NaN from subdiff, Inf/Inf, etc */ + + /* Relative position must be in [0,1] range */ + position = Max(position, 0.0); + position = Min(position, 1.0); + return position; + } + else if (hist1->infinite && !hist2->infinite) + { + /* + * Lower bin boundary is -infinite, upper is finite. If the value is + * -infinite, return 0.0 to indicate it's equal to the lower bound. + * Otherwise return 1.0 to indicate it's infinitely far from the lower + * bound. + */ + return ((value->infinite && value->lower) ? 0.0 : 1.0); + } + else if (!hist1->infinite && hist2->infinite) + { + /* same as above, but in reverse */ + return ((value->infinite && !value->lower) ? 1.0 : 0.0); + } + else + { + /* + * If both bin boundaries are infinite, they should be equal to each + * other, and the value should also be infinite and equal to both + * bounds. (But don't Assert that, to avoid crashing if a user creates + * a datatype with a broken comparison function). + * + * Assume the value to lie in the middle of the infinite bounds. + */ + return 0.5; + } +} + + +/* + * Get relative position of value in a length histogram bin in [0,1] range. + */ +static double +get_len_position(double value, double hist1, double hist2) +{ + if (!isinf(hist1) && !isinf(hist2)) + { + /* + * Both bounds are finite. The value should be finite too, because it + * lies somewhere between the bounds. If it doesn't, just return + * something. + */ + if (isinf(value)) + return 0.5; + + return 1.0 - (hist2 - value) / (hist2 - hist1); + } + else if (isinf(hist1) && !isinf(hist2)) + { + /* + * Lower bin boundary is -infinite, upper is finite. Return 1.0 to + * indicate the value is infinitely far from the lower bound. + */ + return 1.0; + } + else if (isinf(hist1) && isinf(hist2)) + { + /* same as above, but in reverse */ + return 0.0; + } + else + { + /* + * If both bin boundaries are infinite, they should be equal to each + * other, and the value should also be infinite and equal to both + * bounds. (But don't Assert that, to avoid crashing unnecessarily if + * the caller messes up) + * + * Assume the value to lie in the middle of the infinite bounds. + */ + return 0.5; + } +} + +/* + * Measure distance between two range bounds. + */ +static float8 +get_distance(TypeCacheEntry *typcache, const RangeBound *bound1, const RangeBound *bound2) +{ + bool has_subdiff = OidIsValid(typcache->rng_subdiff_finfo.fn_oid); + + if (!bound1->infinite && !bound2->infinite) + { + /* + * Neither bound is infinite, use subdiff function or return default + * value of 1.0 if no subdiff is available. + */ + if (has_subdiff) + { + float8 res; + + res = DatumGetFloat8(FunctionCall2Coll(&typcache->rng_subdiff_finfo, + typcache->rng_collation, + bound2->val, + bound1->val)); + /* Reject possible NaN result, also negative result */ + if (isnan(res) || res < 0.0) + return 1.0; + else + return res; + } + else + return 1.0; + } + else if (bound1->infinite && bound2->infinite) + { + /* Both bounds are infinite */ + if (bound1->lower == bound2->lower) + return 0.0; + else + return get_float8_infinity(); + } + else + { + /* One bound is infinite, the other is not */ + return get_float8_infinity(); + } +} + +/* + * Calculate the average of function P(x), in the interval [length1, length2], + * where P(x) is the fraction of tuples with length < x (or length <= x if + * 'equal' is true). + */ +static double +calc_length_hist_frac(Datum *length_hist_values, int length_hist_nvalues, + double length1, double length2, bool equal) +{ + double frac; + double A, + B, + PA, + PB; + double pos; + int i; + double area; + + Assert(length2 >= length1); + + if (length2 < 0.0) + return 0.0; /* shouldn't happen, but doesn't hurt to check */ + + /* All lengths in the table are <= infinite. */ + if (isinf(length2) && equal) + return 1.0; + + /*---------- + * The average of a function between A and B can be calculated by the + * formula: + * + * B + * 1 / + * ------- | P(x)dx + * B - A / + * A + * + * The geometrical interpretation of the integral is the area under the + * graph of P(x). P(x) is defined by the length histogram. We calculate + * the area in a piecewise fashion, iterating through the length histogram + * bins. Each bin is a trapezoid: + * + * P(x2) + * /| + * / | + * P(x1)/ | + * | | + * | | + * ---+---+-- + * x1 x2 + * + * where x1 and x2 are the boundaries of the current histogram, and P(x1) + * and P(x1) are the cumulative fraction of tuples at the boundaries. + * + * The area of each trapezoid is 1/2 * (P(x2) + P(x1)) * (x2 - x1) + * + * The first bin contains the lower bound passed by the caller, so we + * use linear interpolation between the previous and next histogram bin + * boundary to calculate P(x1). Likewise for the last bin: we use linear + * interpolation to calculate P(x2). For the bins in between, x1 and x2 + * lie on histogram bin boundaries, so P(x1) and P(x2) are simply: + * P(x1) = (bin index) / (number of bins) + * P(x2) = (bin index + 1 / (number of bins) + */ + + /* First bin, the one that contains lower bound */ + i = length_hist_bsearch(length_hist_values, length_hist_nvalues, length1, equal); + if (i >= length_hist_nvalues - 1) + return 1.0; + + if (i < 0) + { + i = 0; + pos = 0.0; + } + else + { + /* interpolate length1's position in the bin */ + pos = get_len_position(length1, + DatumGetFloat8(length_hist_values[i]), + DatumGetFloat8(length_hist_values[i + 1])); + } + PB = (((double) i) + pos) / (double) (length_hist_nvalues - 1); + B = length1; + + /* + * In the degenerate case that length1 == length2, simply return + * P(length1). This is not merely an optimization: if length1 == length2, + * we'd divide by zero later on. + */ + if (length2 == length1) + return PB; + + /* + * Loop through all the bins, until we hit the last bin, the one that + * contains the upper bound. (if lower and upper bounds are in the same + * bin, this falls out immediately) + */ + area = 0.0; + for (; i < length_hist_nvalues - 1; i++) + { + double bin_upper = DatumGetFloat8(length_hist_values[i + 1]); + + /* check if we've reached the last bin */ + if (!(bin_upper < length2 || (equal && bin_upper <= length2))) + break; + + /* the upper bound of previous bin is the lower bound of this bin */ + A = B; + PA = PB; + + B = bin_upper; + PB = (double) i / (double) (length_hist_nvalues - 1); + + /* + * Add the area of this trapezoid to the total. The point of the + * if-check is to avoid NaN, in the corner case that PA == PB == 0, + * and B - A == Inf. The area of a zero-height trapezoid (PA == PB == + * 0) is zero, regardless of the width (B - A). + */ + if (PA > 0 || PB > 0) + area += 0.5 * (PB + PA) * (B - A); + } + + /* Last bin */ + A = B; + PA = PB; + + B = length2; /* last bin ends at the query upper bound */ + if (i >= length_hist_nvalues - 1) + pos = 0.0; + else + { + if (DatumGetFloat8(length_hist_values[i]) == DatumGetFloat8(length_hist_values[i + 1])) + pos = 0.0; + else + pos = get_len_position(length2, DatumGetFloat8(length_hist_values[i]), DatumGetFloat8(length_hist_values[i + 1])); + } + PB = (((double) i) + pos) / (double) (length_hist_nvalues - 1); + + if (PA > 0 || PB > 0) + area += 0.5 * (PB + PA) * (B - A); + + /* + * Ok, we have calculated the area, ie. the integral. Divide by width to + * get the requested average. + * + * Avoid NaN arising from infinite / infinite. This happens at least if + * length2 is infinite. It's not clear what the correct value would be in + * that case, so 0.5 seems as good as any value. + */ + if (isinf(area) && isinf(length2)) + frac = 0.5; + else + frac = area / (length2 - length1); + + return frac; +} + +/* + * Calculate selectivity of "var <@ const" operator, ie. estimate the fraction + * of ranges that fall within the constant lower and upper bounds. This uses + * the histograms of range lower bounds and range lengths, on the assumption + * that the range lengths are independent of the lower bounds. + * + * The caller has already checked that constant lower and upper bounds are + * finite. + */ +static double +calc_hist_selectivity_contained(TypeCacheEntry *typcache, + const RangeBound *lower, RangeBound *upper, + const RangeBound *hist_lower, int hist_nvalues, + Datum *length_hist_values, int length_hist_nvalues) +{ + int i, + upper_index; + float8 prev_dist; + double bin_width; + double upper_bin_width; + double sum_frac; + + /* + * Begin by finding the bin containing the upper bound, in the lower bound + * histogram. Any range with a lower bound > constant upper bound can't + * match, ie. there are no matches in bins greater than upper_index. + */ + upper->inclusive = !upper->inclusive; + upper->lower = true; + upper_index = rbound_bsearch(typcache, upper, hist_lower, hist_nvalues, + false); + + /* + * If the upper bound value is below the histogram's lower limit, there + * are no matches. + */ + if (upper_index < 0) + return 0.0; + + /* + * If the upper bound value is at or beyond the histogram's upper limit, + * start our loop at the last actual bin, as though the upper bound were + * within that bin; get_position will clamp its result to 1.0 anyway. + * (This corresponds to assuming that the data population above the + * histogram's upper limit is empty, exactly like what we just assumed for + * the lower limit.) + */ + upper_index = Min(upper_index, hist_nvalues - 2); + + /* + * Calculate upper_bin_width, ie. the fraction of the (upper_index, + * upper_index + 1) bin which is greater than upper bound of query range + * using linear interpolation of subdiff function. + */ + upper_bin_width = get_position(typcache, upper, + &hist_lower[upper_index], + &hist_lower[upper_index + 1]); + + /* + * In the loop, dist and prev_dist are the distance of the "current" bin's + * lower and upper bounds from the constant upper bound. + * + * bin_width represents the width of the current bin. Normally it is 1.0, + * meaning a full width bin, but can be less in the corner cases: start + * and end of the loop. We start with bin_width = upper_bin_width, because + * we begin at the bin containing the upper bound. + */ + prev_dist = 0.0; + bin_width = upper_bin_width; + + sum_frac = 0.0; + for (i = upper_index; i >= 0; i--) + { + double dist; + double length_hist_frac; + bool final_bin = false; + + /* + * dist -- distance from upper bound of query range to lower bound of + * the current bin in the lower bound histogram. Or to the lower bound + * of the constant range, if this is the final bin, containing the + * constant lower bound. + */ + if (range_cmp_bounds(typcache, &hist_lower[i], lower) < 0) + { + dist = get_distance(typcache, lower, upper); + + /* + * Subtract from bin_width the portion of this bin that we want to + * ignore. + */ + bin_width -= get_position(typcache, lower, &hist_lower[i], + &hist_lower[i + 1]); + if (bin_width < 0.0) + bin_width = 0.0; + final_bin = true; + } + else + dist = get_distance(typcache, &hist_lower[i], upper); + + /* + * Estimate the fraction of tuples in this bin that are narrow enough + * to not exceed the distance to the upper bound of the query range. + */ + length_hist_frac = calc_length_hist_frac(length_hist_values, + length_hist_nvalues, + prev_dist, dist, true); + + /* + * Add the fraction of tuples in this bin, with a suitable length, to + * the total. + */ + sum_frac += length_hist_frac * bin_width / (double) (hist_nvalues - 1); + + if (final_bin) + break; + + bin_width = 1.0; + prev_dist = dist; + } + + return sum_frac; +} + +/* + * Calculate selectivity of "var @> const" operator, ie. estimate the fraction + * of ranges that contain the constant lower and upper bounds. This uses + * the histograms of range lower bounds and range lengths, on the assumption + * that the range lengths are independent of the lower bounds. + */ +static double +calc_hist_selectivity_contains(TypeCacheEntry *typcache, + const RangeBound *lower, const RangeBound *upper, + const RangeBound *hist_lower, int hist_nvalues, + Datum *length_hist_values, int length_hist_nvalues) +{ + int i, + lower_index; + double bin_width, + lower_bin_width; + double sum_frac; + float8 prev_dist; + + /* Find the bin containing the lower bound of query range. */ + lower_index = rbound_bsearch(typcache, lower, hist_lower, hist_nvalues, + true); + + /* + * If the lower bound value is below the histogram's lower limit, there + * are no matches. + */ + if (lower_index < 0) + return 0.0; + + /* + * If the lower bound value is at or beyond the histogram's upper limit, + * start our loop at the last actual bin, as though the upper bound were + * within that bin; get_position will clamp its result to 1.0 anyway. + * (This corresponds to assuming that the data population above the + * histogram's upper limit is empty, exactly like what we just assumed for + * the lower limit.) + */ + lower_index = Min(lower_index, hist_nvalues - 2); + + /* + * Calculate lower_bin_width, ie. the fraction of the of (lower_index, + * lower_index + 1) bin which is greater than lower bound of query range + * using linear interpolation of subdiff function. + */ + lower_bin_width = get_position(typcache, lower, &hist_lower[lower_index], + &hist_lower[lower_index + 1]); + + /* + * Loop through all the lower bound bins, smaller than the query lower + * bound. In the loop, dist and prev_dist are the distance of the + * "current" bin's lower and upper bounds from the constant upper bound. + * We begin from query lower bound, and walk backwards, so the first bin's + * upper bound is the query lower bound, and its distance to the query + * upper bound is the length of the query range. + * + * bin_width represents the width of the current bin. Normally it is 1.0, + * meaning a full width bin, except for the first bin, which is only + * counted up to the constant lower bound. + */ + prev_dist = get_distance(typcache, lower, upper); + sum_frac = 0.0; + bin_width = lower_bin_width; + for (i = lower_index; i >= 0; i--) + { + float8 dist; + double length_hist_frac; + + /* + * dist -- distance from upper bound of query range to current value + * of lower bound histogram or lower bound of query range (if we've + * reach it). + */ + dist = get_distance(typcache, &hist_lower[i], upper); + + /* + * Get average fraction of length histogram which covers intervals + * longer than (or equal to) distance to upper bound of query range. + */ + length_hist_frac = + 1.0 - calc_length_hist_frac(length_hist_values, + length_hist_nvalues, + prev_dist, dist, false); + + sum_frac += length_hist_frac * bin_width / (double) (hist_nvalues - 1); + + bin_width = 1.0; + prev_dist = dist; + } + + return sum_frac; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/rangetypes_spgist.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/rangetypes_spgist.c new file mode 100644 index 00000000000..834ee0bbd05 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/rangetypes_spgist.c @@ -0,0 +1,998 @@ +/*------------------------------------------------------------------------- + * + * rangetypes_spgist.c + * implementation of quad tree over ranges mapped to 2d-points for SP-GiST. + * + * Quad tree is a data structure similar to a binary tree, but is adapted to + * 2d data. Each inner node of a quad tree contains a point (centroid) which + * divides the 2d-space into 4 quadrants. Each quadrant is associated with a + * child node. + * + * Ranges are mapped to 2d-points so that the lower bound is one dimension, + * and the upper bound is another. By convention, we visualize the lower bound + * to be the horizontal axis, and upper bound the vertical axis. + * + * One quirk with this mapping is the handling of empty ranges. An empty range + * doesn't have lower and upper bounds, so it cannot be mapped to 2d space in + * a straightforward way. To cope with that, the root node can have a 5th + * quadrant, which is reserved for empty ranges. Furthermore, there can be + * inner nodes in the tree with no centroid. They contain only two child nodes, + * one for empty ranges and another for non-empty ones. Such a node can appear + * as the root node, or in the tree under the 5th child of the root node (in + * which case it will only contain empty nodes). + * + * The SP-GiST picksplit function uses medians along both axes as the centroid. + * This implementation only uses the comparison function of the range element + * datatype, therefore it works for any range type. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/utils/adt/rangetypes_spgist.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/spgist.h" +#include "access/stratnum.h" +#include "catalog/pg_type.h" +#include "utils/builtins.h" +#include "utils/datum.h" +#include "utils/rangetypes.h" + +static int16 getQuadrant(TypeCacheEntry *typcache, const RangeType *centroid, + const RangeType *tst); +static int bound_cmp(const void *a, const void *b, void *arg); + +static int adjacent_inner_consistent(TypeCacheEntry *typcache, + const RangeBound *arg, const RangeBound *centroid, + const RangeBound *prev); +static int adjacent_cmp_bounds(TypeCacheEntry *typcache, const RangeBound *arg, + const RangeBound *centroid); + +/* + * SP-GiST 'config' interface function. + */ +Datum +spg_range_quad_config(PG_FUNCTION_ARGS) +{ + /* spgConfigIn *cfgin = (spgConfigIn *) PG_GETARG_POINTER(0); */ + spgConfigOut *cfg = (spgConfigOut *) PG_GETARG_POINTER(1); + + cfg->prefixType = ANYRANGEOID; + cfg->labelType = VOIDOID; /* we don't need node labels */ + cfg->canReturnData = true; + cfg->longValuesOK = false; + PG_RETURN_VOID(); +} + +/*---------- + * Determine which quadrant a 2d-mapped range falls into, relative to the + * centroid. + * + * Quadrants are numbered like this: + * + * 4 | 1 + * ----+---- + * 3 | 2 + * + * Where the lower bound of range is the horizontal axis and upper bound the + * vertical axis. + * + * Ranges on one of the axes are taken to lie in the quadrant with higher value + * along perpendicular axis. That is, a value on the horizontal axis is taken + * to belong to quadrant 1 or 4, and a value on the vertical axis is taken to + * belong to quadrant 1 or 2. A range equal to centroid is taken to lie in + * quadrant 1. + * + * Empty ranges are taken to lie in the special quadrant 5. + *---------- + */ +static int16 +getQuadrant(TypeCacheEntry *typcache, const RangeType *centroid, const RangeType *tst) +{ + RangeBound centroidLower, + centroidUpper; + bool centroidEmpty; + RangeBound lower, + upper; + bool empty; + + range_deserialize(typcache, centroid, ¢roidLower, ¢roidUpper, + ¢roidEmpty); + range_deserialize(typcache, tst, &lower, &upper, &empty); + + if (empty) + return 5; + + if (range_cmp_bounds(typcache, &lower, ¢roidLower) >= 0) + { + if (range_cmp_bounds(typcache, &upper, ¢roidUpper) >= 0) + return 1; + else + return 2; + } + else + { + if (range_cmp_bounds(typcache, &upper, ¢roidUpper) >= 0) + return 4; + else + return 3; + } +} + +/* + * Choose SP-GiST function: choose path for addition of new range. + */ +Datum +spg_range_quad_choose(PG_FUNCTION_ARGS) +{ + spgChooseIn *in = (spgChooseIn *) PG_GETARG_POINTER(0); + spgChooseOut *out = (spgChooseOut *) PG_GETARG_POINTER(1); + RangeType *inRange = DatumGetRangeTypeP(in->datum), + *centroid; + int16 quadrant; + TypeCacheEntry *typcache; + + if (in->allTheSame) + { + out->resultType = spgMatchNode; + /* nodeN will be set by core */ + out->result.matchNode.levelAdd = 0; + out->result.matchNode.restDatum = RangeTypePGetDatum(inRange); + PG_RETURN_VOID(); + } + + typcache = range_get_typcache(fcinfo, RangeTypeGetOid(inRange)); + + /* + * A node with no centroid divides ranges purely on whether they're empty + * or not. All empty ranges go to child node 0, all non-empty ranges go to + * node 1. + */ + if (!in->hasPrefix) + { + out->resultType = spgMatchNode; + if (RangeIsEmpty(inRange)) + out->result.matchNode.nodeN = 0; + else + out->result.matchNode.nodeN = 1; + out->result.matchNode.levelAdd = 1; + out->result.matchNode.restDatum = RangeTypePGetDatum(inRange); + PG_RETURN_VOID(); + } + + centroid = DatumGetRangeTypeP(in->prefixDatum); + quadrant = getQuadrant(typcache, centroid, inRange); + + Assert(quadrant <= in->nNodes); + + /* Select node matching to quadrant number */ + out->resultType = spgMatchNode; + out->result.matchNode.nodeN = quadrant - 1; + out->result.matchNode.levelAdd = 1; + out->result.matchNode.restDatum = RangeTypePGetDatum(inRange); + + PG_RETURN_VOID(); +} + +/* + * Bound comparison for sorting. + */ +static int +bound_cmp(const void *a, const void *b, void *arg) +{ + RangeBound *ba = (RangeBound *) a; + RangeBound *bb = (RangeBound *) b; + TypeCacheEntry *typcache = (TypeCacheEntry *) arg; + + return range_cmp_bounds(typcache, ba, bb); +} + +/* + * Picksplit SP-GiST function: split ranges into nodes. Select "centroid" + * range and distribute ranges according to quadrants. + */ +Datum +spg_range_quad_picksplit(PG_FUNCTION_ARGS) +{ + spgPickSplitIn *in = (spgPickSplitIn *) PG_GETARG_POINTER(0); + spgPickSplitOut *out = (spgPickSplitOut *) PG_GETARG_POINTER(1); + int i; + int j; + int nonEmptyCount; + RangeType *centroid; + bool empty; + TypeCacheEntry *typcache; + + /* Use the median values of lower and upper bounds as the centroid range */ + RangeBound *lowerBounds, + *upperBounds; + + typcache = range_get_typcache(fcinfo, + RangeTypeGetOid(DatumGetRangeTypeP(in->datums[0]))); + + /* Allocate memory for bounds */ + lowerBounds = palloc(sizeof(RangeBound) * in->nTuples); + upperBounds = palloc(sizeof(RangeBound) * in->nTuples); + j = 0; + + /* Deserialize bounds of ranges, count non-empty ranges */ + for (i = 0; i < in->nTuples; i++) + { + range_deserialize(typcache, DatumGetRangeTypeP(in->datums[i]), + &lowerBounds[j], &upperBounds[j], &empty); + if (!empty) + j++; + } + nonEmptyCount = j; + + /* + * All the ranges are empty. The best we can do is to construct an inner + * node with no centroid, and put all ranges into node 0. If non-empty + * ranges are added later, they will be routed to node 1. + */ + if (nonEmptyCount == 0) + { + out->nNodes = 2; + out->hasPrefix = false; + /* Prefix is empty */ + out->prefixDatum = PointerGetDatum(NULL); + out->nodeLabels = NULL; + + out->mapTuplesToNodes = palloc(sizeof(int) * in->nTuples); + out->leafTupleDatums = palloc(sizeof(Datum) * in->nTuples); + + /* Place all ranges into node 0 */ + for (i = 0; i < in->nTuples; i++) + { + RangeType *range = DatumGetRangeTypeP(in->datums[i]); + + out->leafTupleDatums[i] = RangeTypePGetDatum(range); + out->mapTuplesToNodes[i] = 0; + } + PG_RETURN_VOID(); + } + + /* Sort range bounds in order to find medians */ + qsort_arg(lowerBounds, nonEmptyCount, sizeof(RangeBound), + bound_cmp, typcache); + qsort_arg(upperBounds, nonEmptyCount, sizeof(RangeBound), + bound_cmp, typcache); + + /* Construct "centroid" range from medians of lower and upper bounds */ + centroid = range_serialize(typcache, &lowerBounds[nonEmptyCount / 2], + &upperBounds[nonEmptyCount / 2], false, NULL); + out->hasPrefix = true; + out->prefixDatum = RangeTypePGetDatum(centroid); + + /* Create node for empty ranges only if it is a root node */ + out->nNodes = (in->level == 0) ? 5 : 4; + out->nodeLabels = NULL; /* we don't need node labels */ + + out->mapTuplesToNodes = palloc(sizeof(int) * in->nTuples); + out->leafTupleDatums = palloc(sizeof(Datum) * in->nTuples); + + /* + * Assign ranges to corresponding nodes according to quadrants relative to + * "centroid" range. + */ + for (i = 0; i < in->nTuples; i++) + { + RangeType *range = DatumGetRangeTypeP(in->datums[i]); + int16 quadrant = getQuadrant(typcache, centroid, range); + + out->leafTupleDatums[i] = RangeTypePGetDatum(range); + out->mapTuplesToNodes[i] = quadrant - 1; + } + + PG_RETURN_VOID(); +} + +/* + * SP-GiST consistent function for inner nodes: check which nodes are + * consistent with given set of queries. + */ +Datum +spg_range_quad_inner_consistent(PG_FUNCTION_ARGS) +{ + spgInnerConsistentIn *in = (spgInnerConsistentIn *) PG_GETARG_POINTER(0); + spgInnerConsistentOut *out = (spgInnerConsistentOut *) PG_GETARG_POINTER(1); + int which; + int i; + MemoryContext oldCtx; + + /* + * For adjacent search we need also previous centroid (if any) to improve + * the precision of the consistent check. In this case needPrevious flag + * is set and centroid is passed into traversalValue. + */ + bool needPrevious = false; + + if (in->allTheSame) + { + /* Report that all nodes should be visited */ + out->nNodes = in->nNodes; + out->nodeNumbers = (int *) palloc(sizeof(int) * in->nNodes); + for (i = 0; i < in->nNodes; i++) + out->nodeNumbers[i] = i; + PG_RETURN_VOID(); + } + + if (!in->hasPrefix) + { + /* + * No centroid on this inner node. Such a node has two child nodes, + * the first for empty ranges, and the second for non-empty ones. + */ + Assert(in->nNodes == 2); + + /* + * Nth bit of which variable means that (N - 1)th node should be + * visited. Initially all bits are set. Bits of nodes which should be + * skipped will be unset. + */ + which = (1 << 1) | (1 << 2); + for (i = 0; i < in->nkeys; i++) + { + StrategyNumber strategy = in->scankeys[i].sk_strategy; + bool empty; + + /* + * The only strategy when second argument of operator is not range + * is RANGESTRAT_CONTAINS_ELEM. + */ + if (strategy != RANGESTRAT_CONTAINS_ELEM) + empty = RangeIsEmpty(DatumGetRangeTypeP(in->scankeys[i].sk_argument)); + else + empty = false; + + switch (strategy) + { + case RANGESTRAT_BEFORE: + case RANGESTRAT_OVERLEFT: + case RANGESTRAT_OVERLAPS: + case RANGESTRAT_OVERRIGHT: + case RANGESTRAT_AFTER: + case RANGESTRAT_ADJACENT: + /* These strategies return false if any argument is empty */ + if (empty) + which = 0; + else + which &= (1 << 2); + break; + + case RANGESTRAT_CONTAINS: + + /* + * All ranges contain an empty range. Only non-empty + * ranges can contain a non-empty range. + */ + if (!empty) + which &= (1 << 2); + break; + + case RANGESTRAT_CONTAINED_BY: + + /* + * Only an empty range is contained by an empty range. + * Both empty and non-empty ranges can be contained by a + * non-empty range. + */ + if (empty) + which &= (1 << 1); + break; + + case RANGESTRAT_CONTAINS_ELEM: + which &= (1 << 2); + break; + + case RANGESTRAT_EQ: + if (empty) + which &= (1 << 1); + else + which &= (1 << 2); + break; + + default: + elog(ERROR, "unrecognized range strategy: %d", strategy); + break; + } + if (which == 0) + break; /* no need to consider remaining conditions */ + } + } + else + { + RangeBound centroidLower, + centroidUpper; + bool centroidEmpty; + TypeCacheEntry *typcache; + RangeType *centroid; + + /* This node has a centroid. Fetch it. */ + centroid = DatumGetRangeTypeP(in->prefixDatum); + typcache = range_get_typcache(fcinfo, + RangeTypeGetOid(centroid)); + range_deserialize(typcache, centroid, ¢roidLower, ¢roidUpper, + ¢roidEmpty); + + Assert(in->nNodes == 4 || in->nNodes == 5); + + /* + * Nth bit of which variable means that (N - 1)th node (Nth quadrant) + * should be visited. Initially all bits are set. Bits of nodes which + * can be skipped will be unset. + */ + which = (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4) | (1 << 5); + + for (i = 0; i < in->nkeys; i++) + { + StrategyNumber strategy; + RangeBound lower, + upper; + bool empty; + RangeType *range = NULL; + + RangeType *prevCentroid = NULL; + RangeBound prevLower, + prevUpper; + bool prevEmpty; + + /* Restrictions on range bounds according to scan strategy */ + RangeBound *minLower = NULL, + *maxLower = NULL, + *minUpper = NULL, + *maxUpper = NULL; + + /* Are the restrictions on range bounds inclusive? */ + bool inclusive = true; + bool strictEmpty = true; + int cmp, + which1, + which2; + + strategy = in->scankeys[i].sk_strategy; + + /* + * RANGESTRAT_CONTAINS_ELEM is just like RANGESTRAT_CONTAINS, but + * the argument is a single element. Expand the single element to + * a range containing only the element, and treat it like + * RANGESTRAT_CONTAINS. + */ + if (strategy == RANGESTRAT_CONTAINS_ELEM) + { + lower.inclusive = true; + lower.infinite = false; + lower.lower = true; + lower.val = in->scankeys[i].sk_argument; + + upper.inclusive = true; + upper.infinite = false; + upper.lower = false; + upper.val = in->scankeys[i].sk_argument; + + empty = false; + + strategy = RANGESTRAT_CONTAINS; + } + else + { + range = DatumGetRangeTypeP(in->scankeys[i].sk_argument); + range_deserialize(typcache, range, &lower, &upper, &empty); + } + + /* + * Most strategies are handled by forming a bounding box from the + * search key, defined by a minLower, maxLower, minUpper, + * maxUpper. Some modify 'which' directly, to specify exactly + * which quadrants need to be visited. + * + * For most strategies, nothing matches an empty search key, and + * an empty range never matches a non-empty key. If a strategy + * does not behave like that wrt. empty ranges, set strictEmpty to + * false. + */ + switch (strategy) + { + case RANGESTRAT_BEFORE: + + /* + * Range A is before range B if upper bound of A is lower + * than lower bound of B. + */ + maxUpper = &lower; + inclusive = false; + break; + + case RANGESTRAT_OVERLEFT: + + /* + * Range A is overleft to range B if upper bound of A is + * less than or equal to upper bound of B. + */ + maxUpper = &upper; + break; + + case RANGESTRAT_OVERLAPS: + + /* + * Non-empty ranges overlap, if lower bound of each range + * is lower or equal to upper bound of the other range. + */ + maxLower = &upper; + minUpper = &lower; + break; + + case RANGESTRAT_OVERRIGHT: + + /* + * Range A is overright to range B if lower bound of A is + * greater than or equal to lower bound of B. + */ + minLower = &lower; + break; + + case RANGESTRAT_AFTER: + + /* + * Range A is after range B if lower bound of A is greater + * than upper bound of B. + */ + minLower = &upper; + inclusive = false; + break; + + case RANGESTRAT_ADJACENT: + if (empty) + break; /* Skip to strictEmpty check. */ + + /* + * Previously selected quadrant could exclude possibility + * for lower or upper bounds to be adjacent. Deserialize + * previous centroid range if present for checking this. + */ + if (in->traversalValue) + { + prevCentroid = in->traversalValue; + range_deserialize(typcache, prevCentroid, + &prevLower, &prevUpper, &prevEmpty); + } + + /* + * For a range's upper bound to be adjacent to the + * argument's lower bound, it will be found along the line + * adjacent to (and just below) Y=lower. Therefore, if the + * argument's lower bound is less than the centroid's + * upper bound, the line falls in quadrants 2 and 3; if + * greater, the line falls in quadrants 1 and 4. (see + * adjacent_cmp_bounds for description of edge cases). + */ + cmp = adjacent_inner_consistent(typcache, &lower, + ¢roidUpper, + prevCentroid ? &prevUpper : NULL); + if (cmp > 0) + which1 = (1 << 1) | (1 << 4); + else if (cmp < 0) + which1 = (1 << 2) | (1 << 3); + else + which1 = 0; + + /* + * Also search for ranges's adjacent to argument's upper + * bound. They will be found along the line adjacent to + * (and just right of) X=upper, which falls in quadrants 3 + * and 4, or 1 and 2. + */ + cmp = adjacent_inner_consistent(typcache, &upper, + ¢roidLower, + prevCentroid ? &prevLower : NULL); + if (cmp > 0) + which2 = (1 << 1) | (1 << 2); + else if (cmp < 0) + which2 = (1 << 3) | (1 << 4); + else + which2 = 0; + + /* We must chase down ranges adjacent to either bound. */ + which &= which1 | which2; + + needPrevious = true; + break; + + case RANGESTRAT_CONTAINS: + + /* + * Non-empty range A contains non-empty range B if lower + * bound of A is lower or equal to lower bound of range B + * and upper bound of range A is greater than or equal to + * upper bound of range A. + * + * All non-empty ranges contain an empty range. + */ + strictEmpty = false; + if (!empty) + { + which &= (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4); + maxLower = &lower; + minUpper = &upper; + } + break; + + case RANGESTRAT_CONTAINED_BY: + /* The opposite of contains. */ + strictEmpty = false; + if (empty) + { + /* An empty range is only contained by an empty range */ + which &= (1 << 5); + } + else + { + minLower = &lower; + maxUpper = &upper; + } + break; + + case RANGESTRAT_EQ: + + /* + * Equal range can be only in the same quadrant where + * argument would be placed to. + */ + strictEmpty = false; + which &= (1 << getQuadrant(typcache, centroid, range)); + break; + + default: + elog(ERROR, "unrecognized range strategy: %d", strategy); + break; + } + + if (strictEmpty) + { + if (empty) + { + /* Scan key is empty, no branches are satisfying */ + which = 0; + break; + } + else + { + /* Shouldn't visit tree branch with empty ranges */ + which &= (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4); + } + } + + /* + * Using the bounding box, see which quadrants we have to descend + * into. + */ + if (minLower) + { + /* + * If the centroid's lower bound is less than or equal to the + * minimum lower bound, anything in the 3rd and 4th quadrants + * will have an even smaller lower bound, and thus can't + * match. + */ + if (range_cmp_bounds(typcache, ¢roidLower, minLower) <= 0) + which &= (1 << 1) | (1 << 2) | (1 << 5); + } + if (maxLower) + { + /* + * If the centroid's lower bound is greater than the maximum + * lower bound, anything in the 1st and 2nd quadrants will + * also have a greater than or equal lower bound, and thus + * can't match. If the centroid's lower bound is equal to the + * maximum lower bound, we can still exclude the 1st and 2nd + * quadrants if we're looking for a value strictly greater + * than the maximum. + */ + + cmp = range_cmp_bounds(typcache, ¢roidLower, maxLower); + if (cmp > 0 || (!inclusive && cmp == 0)) + which &= (1 << 3) | (1 << 4) | (1 << 5); + } + if (minUpper) + { + /* + * If the centroid's upper bound is less than or equal to the + * minimum upper bound, anything in the 2nd and 3rd quadrants + * will have an even smaller upper bound, and thus can't + * match. + */ + if (range_cmp_bounds(typcache, ¢roidUpper, minUpper) <= 0) + which &= (1 << 1) | (1 << 4) | (1 << 5); + } + if (maxUpper) + { + /* + * If the centroid's upper bound is greater than the maximum + * upper bound, anything in the 1st and 4th quadrants will + * also have a greater than or equal upper bound, and thus + * can't match. If the centroid's upper bound is equal to the + * maximum upper bound, we can still exclude the 1st and 4th + * quadrants if we're looking for a value strictly greater + * than the maximum. + */ + + cmp = range_cmp_bounds(typcache, ¢roidUpper, maxUpper); + if (cmp > 0 || (!inclusive && cmp == 0)) + which &= (1 << 2) | (1 << 3) | (1 << 5); + } + + if (which == 0) + break; /* no need to consider remaining conditions */ + } + } + + /* We must descend into the quadrant(s) identified by 'which' */ + out->nodeNumbers = (int *) palloc(sizeof(int) * in->nNodes); + if (needPrevious) + out->traversalValues = (void **) palloc(sizeof(void *) * in->nNodes); + out->nNodes = 0; + + /* + * Elements of traversalValues should be allocated in + * traversalMemoryContext + */ + oldCtx = MemoryContextSwitchTo(in->traversalMemoryContext); + + for (i = 1; i <= in->nNodes; i++) + { + if (which & (1 << i)) + { + /* Save previous prefix if needed */ + if (needPrevious) + { + Datum previousCentroid; + + /* + * We know, that in->prefixDatum in this place is varlena, + * because it's range + */ + previousCentroid = datumCopy(in->prefixDatum, false, -1); + out->traversalValues[out->nNodes] = (void *) previousCentroid; + } + out->nodeNumbers[out->nNodes] = i - 1; + out->nNodes++; + } + } + + MemoryContextSwitchTo(oldCtx); + + PG_RETURN_VOID(); +} + +/* + * adjacent_cmp_bounds + * + * Given an argument and centroid bound, this function determines if any + * bounds that are adjacent to the argument are smaller than, or greater than + * or equal to centroid. For brevity, we call the arg < centroid "left", and + * arg >= centroid case "right". This corresponds to how the quadrants are + * arranged, if you imagine that "left" is equivalent to "down" and "right" + * is equivalent to "up". + * + * For the "left" case, returns -1, and for the "right" case, returns 1. + */ +static int +adjacent_cmp_bounds(TypeCacheEntry *typcache, const RangeBound *arg, + const RangeBound *centroid) +{ + int cmp; + + Assert(arg->lower != centroid->lower); + + cmp = range_cmp_bounds(typcache, arg, centroid); + + if (centroid->lower) + { + /*------ + * The argument is an upper bound, we are searching for adjacent lower + * bounds. A matching adjacent lower bound must be *larger* than the + * argument, but only just. + * + * The following table illustrates the desired result with a fixed + * argument bound, and different centroids. The CMP column shows + * the value of 'cmp' variable, and ADJ shows whether the argument + * and centroid are adjacent, per bounds_adjacent(). (N) means we + * don't need to check for that case, because it's implied by CMP. + * With the argument range [..., 500), the adjacent range we're + * searching for is [500, ...): + * + * ARGUMENT CENTROID CMP ADJ + * [..., 500) [498, ...) > (N) [500, ...) is to the right + * [..., 500) [499, ...) = (N) [500, ...) is to the right + * [..., 500) [500, ...) < Y [500, ...) is to the right + * [..., 500) [501, ...) < N [500, ...) is to the left + * + * So, we must search left when the argument is smaller than, and not + * adjacent, to the centroid. Otherwise search right. + *------ + */ + if (cmp < 0 && !bounds_adjacent(typcache, *arg, *centroid)) + return -1; + else + return 1; + } + else + { + /*------ + * The argument is a lower bound, we are searching for adjacent upper + * bounds. A matching adjacent upper bound must be *smaller* than the + * argument, but only just. + * + * ARGUMENT CENTROID CMP ADJ + * [500, ...) [..., 499) > (N) [..., 500) is to the right + * [500, ...) [..., 500) > (Y) [..., 500) is to the right + * [500, ...) [..., 501) = (N) [..., 500) is to the left + * [500, ...) [..., 502) < (N) [..., 500) is to the left + * + * We must search left when the argument is smaller than or equal to + * the centroid. Otherwise search right. We don't need to check + * whether the argument is adjacent with the centroid, because it + * doesn't matter. + *------ + */ + if (cmp <= 0) + return -1; + else + return 1; + } +} + +/*---------- + * adjacent_inner_consistent + * + * Like adjacent_cmp_bounds, but also takes into account the previous + * level's centroid. We might've traversed left (or right) at the previous + * node, in search for ranges adjacent to the other bound, even though we + * already ruled out the possibility for any matches in that direction for + * this bound. By comparing the argument with the previous centroid, and + * the previous centroid with the current centroid, we can determine which + * direction we should've moved in at previous level, and which direction we + * actually moved. + * + * If there can be any matches to the left, returns -1. If to the right, + * returns 1. If there can be no matches below this centroid, because we + * already ruled them out at the previous level, returns 0. + * + * XXX: Comparing just the previous and current level isn't foolproof; we + * might still search some branches unnecessarily. For example, imagine that + * we are searching for value 15, and we traverse the following centroids + * (only considering one bound for the moment): + * + * Level 1: 20 + * Level 2: 50 + * Level 3: 25 + * + * At this point, previous centroid is 50, current centroid is 25, and the + * target value is to the left. But because we already moved right from + * centroid 20 to 50 in the first level, there cannot be any values < 20 in + * the current branch. But we don't know that just by looking at the previous + * and current centroid, so we traverse left, unnecessarily. The reason we are + * down this branch is that we're searching for matches with the *other* + * bound. If we kept track of which bound we are searching for explicitly, + * instead of deducing that from the previous and current centroid, we could + * avoid some unnecessary work. + *---------- + */ +static int +adjacent_inner_consistent(TypeCacheEntry *typcache, const RangeBound *arg, + const RangeBound *centroid, const RangeBound *prev) +{ + if (prev) + { + int prevcmp; + int cmp; + + /* + * Which direction were we supposed to traverse at previous level, + * left or right? + */ + prevcmp = adjacent_cmp_bounds(typcache, arg, prev); + + /* and which direction did we actually go? */ + cmp = range_cmp_bounds(typcache, centroid, prev); + + /* if the two don't agree, there's nothing to see here */ + if ((prevcmp < 0 && cmp >= 0) || (prevcmp > 0 && cmp < 0)) + return 0; + } + + return adjacent_cmp_bounds(typcache, arg, centroid); +} + +/* + * SP-GiST consistent function for leaf nodes: check leaf value against query + * using corresponding function. + */ +Datum +spg_range_quad_leaf_consistent(PG_FUNCTION_ARGS) +{ + spgLeafConsistentIn *in = (spgLeafConsistentIn *) PG_GETARG_POINTER(0); + spgLeafConsistentOut *out = (spgLeafConsistentOut *) PG_GETARG_POINTER(1); + RangeType *leafRange = DatumGetRangeTypeP(in->leafDatum); + TypeCacheEntry *typcache; + bool res; + int i; + + /* all tests are exact */ + out->recheck = false; + + /* leafDatum is what it is... */ + out->leafValue = in->leafDatum; + + typcache = range_get_typcache(fcinfo, RangeTypeGetOid(leafRange)); + + /* Perform the required comparison(s) */ + res = true; + for (i = 0; i < in->nkeys; i++) + { + Datum keyDatum = in->scankeys[i].sk_argument; + + /* Call the function corresponding to the scan strategy */ + switch (in->scankeys[i].sk_strategy) + { + case RANGESTRAT_BEFORE: + res = range_before_internal(typcache, leafRange, + DatumGetRangeTypeP(keyDatum)); + break; + case RANGESTRAT_OVERLEFT: + res = range_overleft_internal(typcache, leafRange, + DatumGetRangeTypeP(keyDatum)); + break; + case RANGESTRAT_OVERLAPS: + res = range_overlaps_internal(typcache, leafRange, + DatumGetRangeTypeP(keyDatum)); + break; + case RANGESTRAT_OVERRIGHT: + res = range_overright_internal(typcache, leafRange, + DatumGetRangeTypeP(keyDatum)); + break; + case RANGESTRAT_AFTER: + res = range_after_internal(typcache, leafRange, + DatumGetRangeTypeP(keyDatum)); + break; + case RANGESTRAT_ADJACENT: + res = range_adjacent_internal(typcache, leafRange, + DatumGetRangeTypeP(keyDatum)); + break; + case RANGESTRAT_CONTAINS: + res = range_contains_internal(typcache, leafRange, + DatumGetRangeTypeP(keyDatum)); + break; + case RANGESTRAT_CONTAINED_BY: + res = range_contained_by_internal(typcache, leafRange, + DatumGetRangeTypeP(keyDatum)); + break; + case RANGESTRAT_CONTAINS_ELEM: + res = range_contains_elem_internal(typcache, leafRange, + keyDatum); + break; + case RANGESTRAT_EQ: + res = range_eq_internal(typcache, leafRange, + DatumGetRangeTypeP(keyDatum)); + break; + default: + elog(ERROR, "unrecognized range strategy: %d", + in->scankeys[i].sk_strategy); + break; + } + + /* + * If leaf datum doesn't match to a query key, no need to check + * subsequent keys. + */ + if (!res) + break; + } + + PG_RETURN_BOOL(res); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/rangetypes_typanalyze.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/rangetypes_typanalyze.c new file mode 100644 index 00000000000..86810a1a6e6 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/rangetypes_typanalyze.c @@ -0,0 +1,429 @@ +/*------------------------------------------------------------------------- + * + * rangetypes_typanalyze.c + * Functions for gathering statistics from range columns + * + * For a range type column, histograms of lower and upper bounds, and + * the fraction of NULL and empty ranges are collected. + * + * Both histograms have the same length, and they are combined into a + * single array of ranges. This has the same shape as the histogram that + * std_typanalyze would collect, but the values are different. Each range + * in the array is a valid range, even though the lower and upper bounds + * come from different tuples. In theory, the standard scalar selectivity + * functions could be used with the combined histogram. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/adt/rangetypes_typanalyze.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "catalog/pg_operator.h" +#include "commands/vacuum.h" +#include "utils/float.h" +#include "utils/fmgrprotos.h" +#include "utils/lsyscache.h" +#include "utils/rangetypes.h" +#include "utils/multirangetypes.h" +#include "varatt.h" + +static int float8_qsort_cmp(const void *a1, const void *a2, void *arg); +static int range_bound_qsort_cmp(const void *a1, const void *a2, void *arg); +static void compute_range_stats(VacAttrStats *stats, + AnalyzeAttrFetchFunc fetchfunc, int samplerows, + double totalrows); + +/* + * range_typanalyze -- typanalyze function for range columns + */ +Datum +range_typanalyze(PG_FUNCTION_ARGS) +{ + VacAttrStats *stats = (VacAttrStats *) PG_GETARG_POINTER(0); + TypeCacheEntry *typcache; + Form_pg_attribute attr = stats->attr; + + /* Get information about range type; note column might be a domain */ + typcache = range_get_typcache(fcinfo, getBaseType(stats->attrtypid)); + + if (attr->attstattarget < 0) + attr->attstattarget = default_statistics_target; + + stats->compute_stats = compute_range_stats; + stats->extra_data = typcache; + /* same as in std_typanalyze */ + stats->minrows = 300 * attr->attstattarget; + + PG_RETURN_BOOL(true); +} + +/* + * multirange_typanalyze -- typanalyze function for multirange columns + * + * We do the same analysis as for ranges, but on the smallest range that + * completely includes the multirange. + */ +Datum +multirange_typanalyze(PG_FUNCTION_ARGS) +{ + VacAttrStats *stats = (VacAttrStats *) PG_GETARG_POINTER(0); + TypeCacheEntry *typcache; + Form_pg_attribute attr = stats->attr; + + /* Get information about multirange type; note column might be a domain */ + typcache = multirange_get_typcache(fcinfo, getBaseType(stats->attrtypid)); + + if (attr->attstattarget < 0) + attr->attstattarget = default_statistics_target; + + stats->compute_stats = compute_range_stats; + stats->extra_data = typcache; + /* same as in std_typanalyze */ + stats->minrows = 300 * attr->attstattarget; + + PG_RETURN_BOOL(true); +} + +/* + * Comparison function for sorting float8s, used for range lengths. + */ +static int +float8_qsort_cmp(const void *a1, const void *a2, void *arg) +{ + const float8 *f1 = (const float8 *) a1; + const float8 *f2 = (const float8 *) a2; + + if (*f1 < *f2) + return -1; + else if (*f1 == *f2) + return 0; + else + return 1; +} + +/* + * Comparison function for sorting RangeBounds. + */ +static int +range_bound_qsort_cmp(const void *a1, const void *a2, void *arg) +{ + RangeBound *b1 = (RangeBound *) a1; + RangeBound *b2 = (RangeBound *) a2; + TypeCacheEntry *typcache = (TypeCacheEntry *) arg; + + return range_cmp_bounds(typcache, b1, b2); +} + +/* + * compute_range_stats() -- compute statistics for a range column + */ +static void +compute_range_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc, + int samplerows, double totalrows) +{ + TypeCacheEntry *typcache = (TypeCacheEntry *) stats->extra_data; + TypeCacheEntry *mltrng_typcache = NULL; + bool has_subdiff; + int null_cnt = 0; + int non_null_cnt = 0; + int non_empty_cnt = 0; + int empty_cnt = 0; + int range_no; + int slot_idx; + int num_bins = stats->attr->attstattarget; + int num_hist; + float8 *lengths; + RangeBound *lowers, + *uppers; + double total_width = 0; + + if (typcache->typtype == TYPTYPE_MULTIRANGE) + { + mltrng_typcache = typcache; + typcache = typcache->rngtype; + } + else + Assert(typcache->typtype == TYPTYPE_RANGE); + has_subdiff = OidIsValid(typcache->rng_subdiff_finfo.fn_oid); + + /* Allocate memory to hold range bounds and lengths of the sample ranges. */ + lowers = (RangeBound *) palloc(sizeof(RangeBound) * samplerows); + uppers = (RangeBound *) palloc(sizeof(RangeBound) * samplerows); + lengths = (float8 *) palloc(sizeof(float8) * samplerows); + + /* Loop over the sample ranges. */ + for (range_no = 0; range_no < samplerows; range_no++) + { + Datum value; + bool isnull, + empty; + MultirangeType *multirange; + RangeType *range; + RangeBound lower, + upper; + float8 length; + + vacuum_delay_point(); + + value = fetchfunc(stats, range_no, &isnull); + if (isnull) + { + /* range is null, just count that */ + null_cnt++; + continue; + } + + /* + * XXX: should we ignore wide values, like std_typanalyze does, to + * avoid bloating the statistics table? + */ + total_width += VARSIZE_ANY(DatumGetPointer(value)); + + /* Get range and deserialize it for further analysis. */ + if (mltrng_typcache != NULL) + { + /* Treat multiranges like a big range without gaps. */ + multirange = DatumGetMultirangeTypeP(value); + if (!MultirangeIsEmpty(multirange)) + { + RangeBound tmp; + + multirange_get_bounds(typcache, multirange, 0, + &lower, &tmp); + multirange_get_bounds(typcache, multirange, + multirange->rangeCount - 1, + &tmp, &upper); + empty = false; + } + else + { + empty = true; + } + } + else + { + range = DatumGetRangeTypeP(value); + range_deserialize(typcache, range, &lower, &upper, &empty); + } + + if (!empty) + { + /* Remember bounds and length for further usage in histograms */ + lowers[non_empty_cnt] = lower; + uppers[non_empty_cnt] = upper; + + if (lower.infinite || upper.infinite) + { + /* Length of any kind of an infinite range is infinite */ + length = get_float8_infinity(); + } + else if (has_subdiff) + { + /* + * For an ordinary range, use subdiff function between upper + * and lower bound values. + */ + length = DatumGetFloat8(FunctionCall2Coll(&typcache->rng_subdiff_finfo, + typcache->rng_collation, + upper.val, lower.val)); + } + else + { + /* Use default value of 1.0 if no subdiff is available. */ + length = 1.0; + } + lengths[non_empty_cnt] = length; + + non_empty_cnt++; + } + else + empty_cnt++; + + non_null_cnt++; + } + + slot_idx = 0; + + /* We can only compute real stats if we found some non-null values. */ + if (non_null_cnt > 0) + { + Datum *bound_hist_values; + Datum *length_hist_values; + int pos, + posfrac, + delta, + deltafrac, + i; + MemoryContext old_cxt; + float4 *emptyfrac; + + stats->stats_valid = true; + /* Do the simple null-frac and width stats */ + stats->stanullfrac = (double) null_cnt / (double) samplerows; + stats->stawidth = total_width / (double) non_null_cnt; + + /* Estimate that non-null values are unique */ + stats->stadistinct = -1.0 * (1.0 - stats->stanullfrac); + + /* Must copy the target values into anl_context */ + old_cxt = MemoryContextSwitchTo(stats->anl_context); + + /* + * Generate a bounds histogram slot entry if there are at least two + * values. + */ + if (non_empty_cnt >= 2) + { + /* Sort bound values */ + qsort_interruptible(lowers, non_empty_cnt, sizeof(RangeBound), + range_bound_qsort_cmp, typcache); + qsort_interruptible(uppers, non_empty_cnt, sizeof(RangeBound), + range_bound_qsort_cmp, typcache); + + num_hist = non_empty_cnt; + if (num_hist > num_bins) + num_hist = num_bins + 1; + + bound_hist_values = (Datum *) palloc(num_hist * sizeof(Datum)); + + /* + * The object of this loop is to construct ranges from first and + * last entries in lowers[] and uppers[] along with evenly-spaced + * values in between. So the i'th value is a range of lowers[(i * + * (nvals - 1)) / (num_hist - 1)] and uppers[(i * (nvals - 1)) / + * (num_hist - 1)]. But computing that subscript directly risks + * integer overflow when the stats target is more than a couple + * thousand. Instead we add (nvals - 1) / (num_hist - 1) to pos + * at each step, tracking the integral and fractional parts of the + * sum separately. + */ + delta = (non_empty_cnt - 1) / (num_hist - 1); + deltafrac = (non_empty_cnt - 1) % (num_hist - 1); + pos = posfrac = 0; + + for (i = 0; i < num_hist; i++) + { + bound_hist_values[i] = PointerGetDatum(range_serialize(typcache, + &lowers[pos], + &uppers[pos], + false, + NULL)); + pos += delta; + posfrac += deltafrac; + if (posfrac >= (num_hist - 1)) + { + /* fractional part exceeds 1, carry to integer part */ + pos++; + posfrac -= (num_hist - 1); + } + } + + stats->stakind[slot_idx] = STATISTIC_KIND_BOUNDS_HISTOGRAM; + stats->stavalues[slot_idx] = bound_hist_values; + stats->numvalues[slot_idx] = num_hist; + + /* Store ranges even if we're analyzing a multirange column */ + stats->statypid[slot_idx] = typcache->type_id; + stats->statyplen[slot_idx] = typcache->typlen; + stats->statypbyval[slot_idx] = typcache->typbyval; + stats->statypalign[slot_idx] = typcache->typalign; + + slot_idx++; + } + + /* + * Generate a length histogram slot entry if there are at least two + * values. + */ + if (non_empty_cnt >= 2) + { + /* + * Ascending sort of range lengths for further filling of + * histogram + */ + qsort_interruptible(lengths, non_empty_cnt, sizeof(float8), + float8_qsort_cmp, NULL); + + num_hist = non_empty_cnt; + if (num_hist > num_bins) + num_hist = num_bins + 1; + + length_hist_values = (Datum *) palloc(num_hist * sizeof(Datum)); + + /* + * The object of this loop is to copy the first and last lengths[] + * entries along with evenly-spaced values in between. So the i'th + * value is lengths[(i * (nvals - 1)) / (num_hist - 1)]. But + * computing that subscript directly risks integer overflow when + * the stats target is more than a couple thousand. Instead we + * add (nvals - 1) / (num_hist - 1) to pos at each step, tracking + * the integral and fractional parts of the sum separately. + */ + delta = (non_empty_cnt - 1) / (num_hist - 1); + deltafrac = (non_empty_cnt - 1) % (num_hist - 1); + pos = posfrac = 0; + + for (i = 0; i < num_hist; i++) + { + length_hist_values[i] = Float8GetDatum(lengths[pos]); + pos += delta; + posfrac += deltafrac; + if (posfrac >= (num_hist - 1)) + { + /* fractional part exceeds 1, carry to integer part */ + pos++; + posfrac -= (num_hist - 1); + } + } + } + else + { + /* + * Even when we don't create the histogram, store an empty array + * to mean "no histogram". We can't just leave stavalues NULL, + * because get_attstatsslot() errors if you ask for stavalues, and + * it's NULL. We'll still store the empty fraction in stanumbers. + */ + length_hist_values = palloc(0); + num_hist = 0; + } + stats->staop[slot_idx] = Float8LessOperator; + stats->stacoll[slot_idx] = InvalidOid; + stats->stavalues[slot_idx] = length_hist_values; + stats->numvalues[slot_idx] = num_hist; + stats->statypid[slot_idx] = FLOAT8OID; + stats->statyplen[slot_idx] = sizeof(float8); + stats->statypbyval[slot_idx] = FLOAT8PASSBYVAL; + stats->statypalign[slot_idx] = 'd'; + + /* Store the fraction of empty ranges */ + emptyfrac = (float4 *) palloc(sizeof(float4)); + *emptyfrac = ((double) empty_cnt) / ((double) non_null_cnt); + stats->stanumbers[slot_idx] = emptyfrac; + stats->numnumbers[slot_idx] = 1; + + stats->stakind[slot_idx] = STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM; + slot_idx++; + + MemoryContextSwitchTo(old_cxt); + } + else if (null_cnt > 0) + { + /* We found only nulls; assume the column is entirely null */ + stats->stats_valid = true; + stats->stanullfrac = 1.0; + stats->stawidth = 0; /* "unknown" */ + stats->stadistinct = 0.0; /* "unknown" */ + } + + /* + * We don't need to bother cleaning up any of our temporary palloc's. The + * hashtable should also go away, as it used a child memory context. + */ +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/regexp.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/regexp.c new file mode 100644 index 00000000000..23c295fbc82 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/regexp.c @@ -0,0 +1,2018 @@ +/*------------------------------------------------------------------------- + * + * regexp.c + * Postgres' interface to the regular expression package. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/adt/regexp.c + * + * Alistair Crooks added the code for the regex caching + * agc - cached the regular expressions used - there's a good chance + * that we'll get a hit, so this saves a compile step for every + * attempted match. I haven't actually measured the speed improvement, + * but it `looks' a lot quicker visually when watching regression + * test output. + * + * agc - incorporated Keith Bostic's Berkeley regex code into + * the tree for all ports. To distinguish this regex code from any that + * is existent on a platform, I've prepended the string "pg_" to + * the functions regcomp, regerror, regexec and regfree. + * Fixed a bug that was originally a typo by me, where `i' was used + * instead of `oldest' when compiling regular expressions - benign + * results mostly, although occasionally it bit you... + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "catalog/pg_type.h" +#include "funcapi.h" +#include "miscadmin.h" +#include "regex/regex.h" +#include "utils/array.h" +#include "utils/builtins.h" +#include "utils/memutils.h" +#include "utils/varlena.h" + +#define PG_GETARG_TEXT_PP_IF_EXISTS(_n) \ + (PG_NARGS() > (_n) ? PG_GETARG_TEXT_PP(_n) : NULL) + + +/* all the options of interest for regex functions */ +typedef struct pg_re_flags +{ + int cflags; /* compile flags for Spencer's regex code */ + bool glob; /* do it globally (for each occurrence) */ +} pg_re_flags; + +/* cross-call state for regexp_match and regexp_split functions */ +typedef struct regexp_matches_ctx +{ + text *orig_str; /* data string in original TEXT form */ + int nmatches; /* number of places where pattern matched */ + int npatterns; /* number of capturing subpatterns */ + /* We store start char index and end+1 char index for each match */ + /* so the number of entries in match_locs is nmatches * npatterns * 2 */ + int *match_locs; /* 0-based character indexes */ + int next_match; /* 0-based index of next match to process */ + /* workspace for build_regexp_match_result() */ + Datum *elems; /* has npatterns elements */ + bool *nulls; /* has npatterns elements */ + pg_wchar *wide_str; /* wide-char version of original string */ + char *conv_buf; /* conversion buffer, if needed */ + int conv_bufsiz; /* size thereof */ +} regexp_matches_ctx; + +/* + * We cache precompiled regular expressions using a "self organizing list" + * structure, in which recently-used items tend to be near the front. + * Whenever we use an entry, it's moved up to the front of the list. + * Over time, an item's average position corresponds to its frequency of use. + * + * When we first create an entry, it's inserted at the front of + * the array, dropping the entry at the end of the array if necessary to + * make room. (This might seem to be weighting the new entry too heavily, + * but if we insert new entries further back, we'll be unable to adjust to + * a sudden shift in the query mix where we are presented with MAX_CACHED_RES + * never-before-seen items used circularly. We ought to be able to handle + * that case, so we have to insert at the front.) + * + * Knuth mentions a variant strategy in which a used item is moved up just + * one place in the list. Although he says this uses fewer comparisons on + * average, it seems not to adapt very well to the situation where you have + * both some reusable patterns and a steady stream of non-reusable patterns. + * A reusable pattern that isn't used at least as often as non-reusable + * patterns are seen will "fail to keep up" and will drop off the end of the + * cache. With move-to-front, a reusable pattern is guaranteed to stay in + * the cache as long as it's used at least once in every MAX_CACHED_RES uses. + */ + +/* this is the maximum number of cached regular expressions */ +#ifndef MAX_CACHED_RES +#define MAX_CACHED_RES 32 +#endif + +/* A parent memory context for regular expressions. */ +static __thread MemoryContext RegexpCacheMemoryContext; + +/* this structure describes one cached regular expression */ +typedef struct cached_re_str +{ + MemoryContext cre_context; /* memory context for this regexp */ + char *cre_pat; /* original RE (not null terminated!) */ + int cre_pat_len; /* length of original RE, in bytes */ + int cre_flags; /* compile flags: extended,icase etc */ + Oid cre_collation; /* collation to use */ + regex_t cre_re; /* the compiled regular expression */ +} cached_re_str; + +static __thread int num_res = 0; /* # of cached re's */ +static __thread cached_re_str re_array[MAX_CACHED_RES]; /* cached re's */ + +void RE_cleanup_cache(void) { + if (RegexpCacheMemoryContext) { + MemoryContextDelete(RegexpCacheMemoryContext); + RegexpCacheMemoryContext = NULL; + } + + num_res = 0; +} + +/* Local functions */ +static regexp_matches_ctx *setup_regexp_matches(text *orig_str, text *pattern, + pg_re_flags *re_flags, + int start_search, + Oid collation, + bool use_subpatterns, + bool ignore_degenerate, + bool fetching_unmatched); +static ArrayType *build_regexp_match_result(regexp_matches_ctx *matchctx); +static Datum build_regexp_split_result(regexp_matches_ctx *splitctx); + + +/* + * RE_compile_and_cache - compile a RE, caching if possible + * + * Returns regex_t * + * + * text_re --- the pattern, expressed as a TEXT object + * cflags --- compile options for the pattern + * collation --- collation to use for LC_CTYPE-dependent behavior + * + * Pattern is given in the database encoding. We internally convert to + * an array of pg_wchar, which is what Spencer's regex package wants. + */ +regex_t * +RE_compile_and_cache(text *text_re, int cflags, Oid collation) +{ + int text_re_len = VARSIZE_ANY_EXHDR(text_re); + char *text_re_val = VARDATA_ANY(text_re); + pg_wchar *pattern; + int pattern_len; + int i; + int regcomp_result; + cached_re_str re_temp; + char errMsg[100]; + MemoryContext oldcontext; + + /* + * Look for a match among previously compiled REs. Since the data + * structure is self-organizing with most-used entries at the front, our + * search strategy can just be to scan from the front. + */ + for (i = 0; i < num_res; i++) + { + if (re_array[i].cre_pat_len == text_re_len && + re_array[i].cre_flags == cflags && + re_array[i].cre_collation == collation && + memcmp(re_array[i].cre_pat, text_re_val, text_re_len) == 0) + { + /* + * Found a match; move it to front if not there already. + */ + if (i > 0) + { + re_temp = re_array[i]; + memmove(&re_array[1], &re_array[0], i * sizeof(cached_re_str)); + re_array[0] = re_temp; + } + + return &re_array[0].cre_re; + } + } + + /* Set up the cache memory on first go through. */ + if (unlikely(RegexpCacheMemoryContext == NULL)) + RegexpCacheMemoryContext = + AllocSetContextCreate(TopMemoryContext, + "RegexpCacheMemoryContext", + ALLOCSET_SMALL_SIZES); + + /* + * Couldn't find it, so try to compile the new RE. To avoid leaking + * resources on failure, we build into the re_temp local. + */ + + /* Convert pattern string to wide characters */ + pattern = (pg_wchar *) palloc((text_re_len + 1) * sizeof(pg_wchar)); + pattern_len = pg_mb2wchar_with_len(text_re_val, + pattern, + text_re_len); + + /* + * Make a memory context for this compiled regexp. This is initially a + * child of the current memory context, so it will be cleaned up + * automatically if compilation is interrupted and throws an ERROR. We'll + * re-parent it under the longer lived cache context if we make it to the + * bottom of this function. + */ + re_temp.cre_context = AllocSetContextCreate(CurrentMemoryContext, + "RegexpMemoryContext", + ALLOCSET_SMALL_SIZES); + oldcontext = MemoryContextSwitchTo(re_temp.cre_context); + + regcomp_result = pg_regcomp(&re_temp.cre_re, + pattern, + pattern_len, + cflags, + collation); + + pfree(pattern); + + if (regcomp_result != REG_OKAY) + { + /* re didn't compile (no need for pg_regfree, if so) */ + pg_regerror(regcomp_result, &re_temp.cre_re, errMsg, sizeof(errMsg)); + ereport(ERROR, + (errcode(ERRCODE_INVALID_REGULAR_EXPRESSION), + errmsg("invalid regular expression: %s", errMsg))); + } + + /* Copy the pattern into the per-regexp memory context. */ + re_temp.cre_pat = palloc(text_re_len + 1); + memcpy(re_temp.cre_pat, text_re_val, text_re_len); + + /* + * NUL-terminate it only for the benefit of the identifier used for the + * memory context, visible in the pg_backend_memory_contexts view. + */ + re_temp.cre_pat[text_re_len] = 0; + MemoryContextSetIdentifier(re_temp.cre_context, re_temp.cre_pat); + + re_temp.cre_pat_len = text_re_len; + re_temp.cre_flags = cflags; + re_temp.cre_collation = collation; + + /* + * Okay, we have a valid new item in re_temp; insert it into the storage + * array. Discard last entry if needed. + */ + if (num_res >= MAX_CACHED_RES) + { + --num_res; + Assert(num_res < MAX_CACHED_RES); + /* Delete the memory context holding the regexp and pattern. */ + MemoryContextDelete(re_array[num_res].cre_context); + } + + /* Re-parent the memory context to our long-lived cache context. */ + MemoryContextSetParent(re_temp.cre_context, RegexpCacheMemoryContext); + + if (num_res > 0) + memmove(&re_array[1], &re_array[0], num_res * sizeof(cached_re_str)); + + re_array[0] = re_temp; + num_res++; + + MemoryContextSwitchTo(oldcontext); + + return &re_array[0].cre_re; +} + +/* + * RE_wchar_execute - execute a RE on pg_wchar data + * + * Returns true on match, false on no match + * + * re --- the compiled pattern as returned by RE_compile_and_cache + * data --- the data to match against (need not be null-terminated) + * data_len --- the length of the data string + * start_search -- the offset in the data to start searching + * nmatch, pmatch --- optional return area for match details + * + * Data is given as array of pg_wchar which is what Spencer's regex package + * wants. + */ +static bool +RE_wchar_execute(regex_t *re, pg_wchar *data, int data_len, + int start_search, int nmatch, regmatch_t *pmatch) +{ + int regexec_result; + char errMsg[100]; + + /* Perform RE match and return result */ + regexec_result = pg_regexec(re, + data, + data_len, + start_search, + NULL, /* no details */ + nmatch, + pmatch, + 0); + + if (regexec_result != REG_OKAY && regexec_result != REG_NOMATCH) + { + /* re failed??? */ + pg_regerror(regexec_result, re, errMsg, sizeof(errMsg)); + ereport(ERROR, + (errcode(ERRCODE_INVALID_REGULAR_EXPRESSION), + errmsg("regular expression failed: %s", errMsg))); + } + + return (regexec_result == REG_OKAY); +} + +/* + * RE_execute - execute a RE + * + * Returns true on match, false on no match + * + * re --- the compiled pattern as returned by RE_compile_and_cache + * dat --- the data to match against (need not be null-terminated) + * dat_len --- the length of the data string + * nmatch, pmatch --- optional return area for match details + * + * Data is given in the database encoding. We internally + * convert to array of pg_wchar which is what Spencer's regex package wants. + */ +static bool +RE_execute(regex_t *re, char *dat, int dat_len, + int nmatch, regmatch_t *pmatch) +{ + pg_wchar *data; + int data_len; + bool match; + + /* Convert data string to wide characters */ + data = (pg_wchar *) palloc((dat_len + 1) * sizeof(pg_wchar)); + data_len = pg_mb2wchar_with_len(dat, data, dat_len); + + /* Perform RE match and return result */ + match = RE_wchar_execute(re, data, data_len, 0, nmatch, pmatch); + + pfree(data); + return match; +} + +/* + * RE_compile_and_execute - compile and execute a RE + * + * Returns true on match, false on no match + * + * text_re --- the pattern, expressed as a TEXT object + * dat --- the data to match against (need not be null-terminated) + * dat_len --- the length of the data string + * cflags --- compile options for the pattern + * collation --- collation to use for LC_CTYPE-dependent behavior + * nmatch, pmatch --- optional return area for match details + * + * Both pattern and data are given in the database encoding. We internally + * convert to array of pg_wchar which is what Spencer's regex package wants. + */ +bool +RE_compile_and_execute(text *text_re, char *dat, int dat_len, + int cflags, Oid collation, + int nmatch, regmatch_t *pmatch) +{ + regex_t *re; + + /* Use REG_NOSUB if caller does not want sub-match details */ + if (nmatch < 2) + cflags |= REG_NOSUB; + + /* Compile RE */ + re = RE_compile_and_cache(text_re, cflags, collation); + + return RE_execute(re, dat, dat_len, nmatch, pmatch); +} + + +/* + * parse_re_flags - parse the options argument of regexp_match and friends + * + * flags --- output argument, filled with desired options + * opts --- TEXT object, or NULL for defaults + * + * This accepts all the options allowed by any of the callers; callers that + * don't want some have to reject them after the fact. + */ +static void +parse_re_flags(pg_re_flags *flags, text *opts) +{ + /* regex flavor is always folded into the compile flags */ + flags->cflags = REG_ADVANCED; + flags->glob = false; + + if (opts) + { + char *opt_p = VARDATA_ANY(opts); + int opt_len = VARSIZE_ANY_EXHDR(opts); + int i; + + for (i = 0; i < opt_len; i++) + { + switch (opt_p[i]) + { + case 'g': + flags->glob = true; + break; + case 'b': /* BREs (but why???) */ + flags->cflags &= ~(REG_ADVANCED | REG_EXTENDED | REG_QUOTE); + break; + case 'c': /* case sensitive */ + flags->cflags &= ~REG_ICASE; + break; + case 'e': /* plain EREs */ + flags->cflags |= REG_EXTENDED; + flags->cflags &= ~(REG_ADVANCED | REG_QUOTE); + break; + case 'i': /* case insensitive */ + flags->cflags |= REG_ICASE; + break; + case 'm': /* Perloid synonym for n */ + case 'n': /* \n affects ^ $ . [^ */ + flags->cflags |= REG_NEWLINE; + break; + case 'p': /* ~Perl, \n affects . [^ */ + flags->cflags |= REG_NLSTOP; + flags->cflags &= ~REG_NLANCH; + break; + case 'q': /* literal string */ + flags->cflags |= REG_QUOTE; + flags->cflags &= ~(REG_ADVANCED | REG_EXTENDED); + break; + case 's': /* single line, \n ordinary */ + flags->cflags &= ~REG_NEWLINE; + break; + case 't': /* tight syntax */ + flags->cflags &= ~REG_EXPANDED; + break; + case 'w': /* weird, \n affects ^ $ only */ + flags->cflags &= ~REG_NLSTOP; + flags->cflags |= REG_NLANCH; + break; + case 'x': /* expanded syntax */ + flags->cflags |= REG_EXPANDED; + break; + default: + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid regular expression option: \"%.*s\"", + pg_mblen(opt_p + i), opt_p + i))); + break; + } + } + } +} + + +/* + * interface routines called by the function manager + */ + +Datum +nameregexeq(PG_FUNCTION_ARGS) +{ + Name n = PG_GETARG_NAME(0); + text *p = PG_GETARG_TEXT_PP(1); + + PG_RETURN_BOOL(RE_compile_and_execute(p, + NameStr(*n), + strlen(NameStr(*n)), + REG_ADVANCED, + PG_GET_COLLATION(), + 0, NULL)); +} + +Datum +nameregexne(PG_FUNCTION_ARGS) +{ + Name n = PG_GETARG_NAME(0); + text *p = PG_GETARG_TEXT_PP(1); + + PG_RETURN_BOOL(!RE_compile_and_execute(p, + NameStr(*n), + strlen(NameStr(*n)), + REG_ADVANCED, + PG_GET_COLLATION(), + 0, NULL)); +} + +Datum +textregexeq(PG_FUNCTION_ARGS) +{ + text *s = PG_GETARG_TEXT_PP(0); + text *p = PG_GETARG_TEXT_PP(1); + + PG_RETURN_BOOL(RE_compile_and_execute(p, + VARDATA_ANY(s), + VARSIZE_ANY_EXHDR(s), + REG_ADVANCED, + PG_GET_COLLATION(), + 0, NULL)); +} + +Datum +textregexne(PG_FUNCTION_ARGS) +{ + text *s = PG_GETARG_TEXT_PP(0); + text *p = PG_GETARG_TEXT_PP(1); + + PG_RETURN_BOOL(!RE_compile_and_execute(p, + VARDATA_ANY(s), + VARSIZE_ANY_EXHDR(s), + REG_ADVANCED, + PG_GET_COLLATION(), + 0, NULL)); +} + + +/* + * routines that use the regexp stuff, but ignore the case. + * for this, we use the REG_ICASE flag to pg_regcomp + */ + + +Datum +nameicregexeq(PG_FUNCTION_ARGS) +{ + Name n = PG_GETARG_NAME(0); + text *p = PG_GETARG_TEXT_PP(1); + + PG_RETURN_BOOL(RE_compile_and_execute(p, + NameStr(*n), + strlen(NameStr(*n)), + REG_ADVANCED | REG_ICASE, + PG_GET_COLLATION(), + 0, NULL)); +} + +Datum +nameicregexne(PG_FUNCTION_ARGS) +{ + Name n = PG_GETARG_NAME(0); + text *p = PG_GETARG_TEXT_PP(1); + + PG_RETURN_BOOL(!RE_compile_and_execute(p, + NameStr(*n), + strlen(NameStr(*n)), + REG_ADVANCED | REG_ICASE, + PG_GET_COLLATION(), + 0, NULL)); +} + +Datum +texticregexeq(PG_FUNCTION_ARGS) +{ + text *s = PG_GETARG_TEXT_PP(0); + text *p = PG_GETARG_TEXT_PP(1); + + PG_RETURN_BOOL(RE_compile_and_execute(p, + VARDATA_ANY(s), + VARSIZE_ANY_EXHDR(s), + REG_ADVANCED | REG_ICASE, + PG_GET_COLLATION(), + 0, NULL)); +} + +Datum +texticregexne(PG_FUNCTION_ARGS) +{ + text *s = PG_GETARG_TEXT_PP(0); + text *p = PG_GETARG_TEXT_PP(1); + + PG_RETURN_BOOL(!RE_compile_and_execute(p, + VARDATA_ANY(s), + VARSIZE_ANY_EXHDR(s), + REG_ADVANCED | REG_ICASE, + PG_GET_COLLATION(), + 0, NULL)); +} + + +/* + * textregexsubstr() + * Return a substring matched by a regular expression. + */ +Datum +textregexsubstr(PG_FUNCTION_ARGS) +{ + text *s = PG_GETARG_TEXT_PP(0); + text *p = PG_GETARG_TEXT_PP(1); + regex_t *re; + regmatch_t pmatch[2]; + int so, + eo; + + /* Compile RE */ + re = RE_compile_and_cache(p, REG_ADVANCED, PG_GET_COLLATION()); + + /* + * We pass two regmatch_t structs to get info about the overall match and + * the match for the first parenthesized subexpression (if any). If there + * is a parenthesized subexpression, we return what it matched; else + * return what the whole regexp matched. + */ + if (!RE_execute(re, + VARDATA_ANY(s), VARSIZE_ANY_EXHDR(s), + 2, pmatch)) + PG_RETURN_NULL(); /* definitely no match */ + + if (re->re_nsub > 0) + { + /* has parenthesized subexpressions, use the first one */ + so = pmatch[1].rm_so; + eo = pmatch[1].rm_eo; + } + else + { + /* no parenthesized subexpression, use whole match */ + so = pmatch[0].rm_so; + eo = pmatch[0].rm_eo; + } + + /* + * It is possible to have a match to the whole pattern but no match for a + * subexpression; for example 'foo(bar)?' is considered to match 'foo' but + * there is no subexpression match. So this extra test for match failure + * is not redundant. + */ + if (so < 0 || eo < 0) + PG_RETURN_NULL(); + + return DirectFunctionCall3(text_substr, + PointerGetDatum(s), + Int32GetDatum(so + 1), + Int32GetDatum(eo - so)); +} + +/* + * textregexreplace_noopt() + * Return a string matched by a regular expression, with replacement. + * + * This version doesn't have an option argument: we default to case + * sensitive match, replace the first instance only. + */ +Datum +textregexreplace_noopt(PG_FUNCTION_ARGS) +{ + text *s = PG_GETARG_TEXT_PP(0); + text *p = PG_GETARG_TEXT_PP(1); + text *r = PG_GETARG_TEXT_PP(2); + + PG_RETURN_TEXT_P(replace_text_regexp(s, p, r, + REG_ADVANCED, PG_GET_COLLATION(), + 0, 1)); +} + +/* + * textregexreplace() + * Return a string matched by a regular expression, with replacement. + */ +Datum +textregexreplace(PG_FUNCTION_ARGS) +{ + text *s = PG_GETARG_TEXT_PP(0); + text *p = PG_GETARG_TEXT_PP(1); + text *r = PG_GETARG_TEXT_PP(2); + text *opt = PG_GETARG_TEXT_PP(3); + pg_re_flags flags; + + /* + * regexp_replace() with four arguments will be preferentially resolved as + * this form when the fourth argument is of type UNKNOWN. However, the + * user might have intended to call textregexreplace_extended_no_n. If we + * see flags that look like an integer, emit the same error that + * parse_re_flags would, but add a HINT about how to fix it. + */ + if (VARSIZE_ANY_EXHDR(opt) > 0) + { + char *opt_p = VARDATA_ANY(opt); + + if (*opt_p >= '0' && *opt_p <= '9') + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid regular expression option: \"%.*s\"", + pg_mblen(opt_p), opt_p), + errhint("If you meant to use regexp_replace() with a start parameter, cast the fourth argument to integer explicitly."))); + } + + parse_re_flags(&flags, opt); + + PG_RETURN_TEXT_P(replace_text_regexp(s, p, r, + flags.cflags, PG_GET_COLLATION(), + 0, flags.glob ? 0 : 1)); +} + +/* + * textregexreplace_extended() + * Return a string matched by a regular expression, with replacement. + * Extends textregexreplace by allowing a start position and the + * choice of the occurrence to replace (0 means all occurrences). + */ +Datum +textregexreplace_extended(PG_FUNCTION_ARGS) +{ + text *s = PG_GETARG_TEXT_PP(0); + text *p = PG_GETARG_TEXT_PP(1); + text *r = PG_GETARG_TEXT_PP(2); + int start = 1; + int n = 1; + text *flags = PG_GETARG_TEXT_PP_IF_EXISTS(5); + pg_re_flags re_flags; + + /* Collect optional parameters */ + if (PG_NARGS() > 3) + { + start = PG_GETARG_INT32(3); + if (start <= 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for parameter \"%s\": %d", + "start", start))); + } + if (PG_NARGS() > 4) + { + n = PG_GETARG_INT32(4); + if (n < 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for parameter \"%s\": %d", + "n", n))); + } + + /* Determine options */ + parse_re_flags(&re_flags, flags); + + /* If N was not specified, deduce it from the 'g' flag */ + if (PG_NARGS() <= 4) + n = re_flags.glob ? 0 : 1; + + /* Do the replacement(s) */ + PG_RETURN_TEXT_P(replace_text_regexp(s, p, r, + re_flags.cflags, PG_GET_COLLATION(), + start - 1, n)); +} + +/* This is separate to keep the opr_sanity regression test from complaining */ +Datum +textregexreplace_extended_no_n(PG_FUNCTION_ARGS) +{ + return textregexreplace_extended(fcinfo); +} + +/* This is separate to keep the opr_sanity regression test from complaining */ +Datum +textregexreplace_extended_no_flags(PG_FUNCTION_ARGS) +{ + return textregexreplace_extended(fcinfo); +} + +/* + * similar_to_escape(), similar_escape() + * + * Convert a SQL "SIMILAR TO" regexp pattern to POSIX style, so it can be + * used by our regexp engine. + * + * similar_escape_internal() is the common workhorse for three SQL-exposed + * functions. esc_text can be passed as NULL to select the default escape + * (which is '\'), or as an empty string to select no escape character. + */ +static text * +similar_escape_internal(text *pat_text, text *esc_text) +{ + text *result; + char *p, + *e, + *r; + int plen, + elen; + bool afterescape = false; + bool incharclass = false; + int nquotes = 0; + + p = VARDATA_ANY(pat_text); + plen = VARSIZE_ANY_EXHDR(pat_text); + if (esc_text == NULL) + { + /* No ESCAPE clause provided; default to backslash as escape */ + e = "\\"; + elen = 1; + } + else + { + e = VARDATA_ANY(esc_text); + elen = VARSIZE_ANY_EXHDR(esc_text); + if (elen == 0) + e = NULL; /* no escape character */ + else if (elen > 1) + { + int escape_mblen = pg_mbstrlen_with_len(e, elen); + + if (escape_mblen > 1) + ereport(ERROR, + (errcode(ERRCODE_INVALID_ESCAPE_SEQUENCE), + errmsg("invalid escape string"), + errhint("Escape string must be empty or one character."))); + } + } + + /*---------- + * We surround the transformed input string with + * ^(?: ... )$ + * which requires some explanation. We need "^" and "$" to force + * the pattern to match the entire input string as per the SQL spec. + * The "(?:" and ")" are a non-capturing set of parens; we have to have + * parens in case the string contains "|", else the "^" and "$" will + * be bound into the first and last alternatives which is not what we + * want, and the parens must be non capturing because we don't want them + * to count when selecting output for SUBSTRING. + * + * When the pattern is divided into three parts by escape-double-quotes, + * what we emit is + * ^(?:part1){1,1}?(part2){1,1}(?:part3)$ + * which requires even more explanation. The "{1,1}?" on part1 makes it + * non-greedy so that it will match the smallest possible amount of text + * not the largest, as required by SQL. The plain parens around part2 + * are capturing parens so that that part is what controls the result of + * SUBSTRING. The "{1,1}" forces part2 to be greedy, so that it matches + * the largest possible amount of text; hence part3 must match the + * smallest amount of text, as required by SQL. We don't need an explicit + * greediness marker on part3. Note that this also confines the effects + * of any "|" characters to the respective part, which is what we want. + * + * The SQL spec says that SUBSTRING's pattern must contain exactly two + * escape-double-quotes, but we only complain if there's more than two. + * With none, we act as though part1 and part3 are empty; with one, we + * act as though part3 is empty. Both behaviors fall out of omitting + * the relevant part separators in the above expansion. If the result + * of this function is used in a plain regexp match (SIMILAR TO), the + * escape-double-quotes have no effect on the match behavior. + *---------- + */ + + /* + * We need room for the prefix/postfix and part separators, plus as many + * as 3 output bytes per input byte; since the input is at most 1GB this + * can't overflow size_t. + */ + result = (text *) palloc(VARHDRSZ + 23 + 3 * (size_t) plen); + r = VARDATA(result); + + *r++ = '^'; + *r++ = '('; + *r++ = '?'; + *r++ = ':'; + + while (plen > 0) + { + char pchar = *p; + + /* + * If both the escape character and the current character from the + * pattern are multi-byte, we need to take the slow path. + * + * But if one of them is single-byte, we can process the pattern one + * byte at a time, ignoring multi-byte characters. (This works + * because all server-encodings have the property that a valid + * multi-byte character representation cannot contain the + * representation of a valid single-byte character.) + */ + + if (elen > 1) + { + int mblen = pg_mblen(p); + + if (mblen > 1) + { + /* slow, multi-byte path */ + if (afterescape) + { + *r++ = '\\'; + memcpy(r, p, mblen); + r += mblen; + afterescape = false; + } + else if (e && elen == mblen && memcmp(e, p, mblen) == 0) + { + /* SQL escape character; do not send to output */ + afterescape = true; + } + else + { + /* + * We know it's a multi-byte character, so we don't need + * to do all the comparisons to single-byte characters + * that we do below. + */ + memcpy(r, p, mblen); + r += mblen; + } + + p += mblen; + plen -= mblen; + + continue; + } + } + + /* fast path */ + if (afterescape) + { + if (pchar == '"' && !incharclass) /* escape-double-quote? */ + { + /* emit appropriate part separator, per notes above */ + if (nquotes == 0) + { + *r++ = ')'; + *r++ = '{'; + *r++ = '1'; + *r++ = ','; + *r++ = '1'; + *r++ = '}'; + *r++ = '?'; + *r++ = '('; + } + else if (nquotes == 1) + { + *r++ = ')'; + *r++ = '{'; + *r++ = '1'; + *r++ = ','; + *r++ = '1'; + *r++ = '}'; + *r++ = '('; + *r++ = '?'; + *r++ = ':'; + } + else + ereport(ERROR, + (errcode(ERRCODE_INVALID_USE_OF_ESCAPE_CHARACTER), + errmsg("SQL regular expression may not contain more than two escape-double-quote separators"))); + nquotes++; + } + else + { + /* + * We allow any character at all to be escaped; notably, this + * allows access to POSIX character-class escapes such as + * "\d". The SQL spec is considerably more restrictive. + */ + *r++ = '\\'; + *r++ = pchar; + } + afterescape = false; + } + else if (e && pchar == *e) + { + /* SQL escape character; do not send to output */ + afterescape = true; + } + else if (incharclass) + { + if (pchar == '\\') + *r++ = '\\'; + *r++ = pchar; + if (pchar == ']') + incharclass = false; + } + else if (pchar == '[') + { + *r++ = pchar; + incharclass = true; + } + else if (pchar == '%') + { + *r++ = '.'; + *r++ = '*'; + } + else if (pchar == '_') + *r++ = '.'; + else if (pchar == '(') + { + /* convert to non-capturing parenthesis */ + *r++ = '('; + *r++ = '?'; + *r++ = ':'; + } + else if (pchar == '\\' || pchar == '.' || + pchar == '^' || pchar == '$') + { + *r++ = '\\'; + *r++ = pchar; + } + else + *r++ = pchar; + p++, plen--; + } + + *r++ = ')'; + *r++ = '$'; + + SET_VARSIZE(result, r - ((char *) result)); + + return result; +} + +/* + * similar_to_escape(pattern, escape) + */ +Datum +similar_to_escape_2(PG_FUNCTION_ARGS) +{ + text *pat_text = PG_GETARG_TEXT_PP(0); + text *esc_text = PG_GETARG_TEXT_PP(1); + text *result; + + result = similar_escape_internal(pat_text, esc_text); + + PG_RETURN_TEXT_P(result); +} + +/* + * similar_to_escape(pattern) + * Inserts a default escape character. + */ +Datum +similar_to_escape_1(PG_FUNCTION_ARGS) +{ + text *pat_text = PG_GETARG_TEXT_PP(0); + text *result; + + result = similar_escape_internal(pat_text, NULL); + + PG_RETURN_TEXT_P(result); +} + +/* + * similar_escape(pattern, escape) + * + * Legacy function for compatibility with views stored using the + * pre-v13 expansion of SIMILAR TO. Unlike the above functions, this + * is non-strict, which leads to not-per-spec handling of "ESCAPE NULL". + */ +Datum +similar_escape(PG_FUNCTION_ARGS) +{ + text *pat_text; + text *esc_text; + text *result; + + /* This function is not strict, so must test explicitly */ + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + pat_text = PG_GETARG_TEXT_PP(0); + + if (PG_ARGISNULL(1)) + esc_text = NULL; /* use default escape character */ + else + esc_text = PG_GETARG_TEXT_PP(1); + + result = similar_escape_internal(pat_text, esc_text); + + PG_RETURN_TEXT_P(result); +} + +/* + * regexp_count() + * Return the number of matches of a pattern within a string. + */ +Datum +regexp_count(PG_FUNCTION_ARGS) +{ + text *str = PG_GETARG_TEXT_PP(0); + text *pattern = PG_GETARG_TEXT_PP(1); + int start = 1; + text *flags = PG_GETARG_TEXT_PP_IF_EXISTS(3); + pg_re_flags re_flags; + regexp_matches_ctx *matchctx; + + /* Collect optional parameters */ + if (PG_NARGS() > 2) + { + start = PG_GETARG_INT32(2); + if (start <= 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for parameter \"%s\": %d", + "start", start))); + } + + /* Determine options */ + parse_re_flags(&re_flags, flags); + /* User mustn't specify 'g' */ + if (re_flags.glob) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + /* translator: %s is a SQL function name */ + errmsg("%s does not support the \"global\" option", + "regexp_count()"))); + /* But we find all the matches anyway */ + re_flags.glob = true; + + /* Do the matching */ + matchctx = setup_regexp_matches(str, pattern, &re_flags, start - 1, + PG_GET_COLLATION(), + false, /* can ignore subexprs */ + false, false); + + PG_RETURN_INT32(matchctx->nmatches); +} + +/* This is separate to keep the opr_sanity regression test from complaining */ +Datum +regexp_count_no_start(PG_FUNCTION_ARGS) +{ + return regexp_count(fcinfo); +} + +/* This is separate to keep the opr_sanity regression test from complaining */ +Datum +regexp_count_no_flags(PG_FUNCTION_ARGS) +{ + return regexp_count(fcinfo); +} + +/* + * regexp_instr() + * Return the match's position within the string + */ +Datum +regexp_instr(PG_FUNCTION_ARGS) +{ + text *str = PG_GETARG_TEXT_PP(0); + text *pattern = PG_GETARG_TEXT_PP(1); + int start = 1; + int n = 1; + int endoption = 0; + text *flags = PG_GETARG_TEXT_PP_IF_EXISTS(5); + int subexpr = 0; + int pos; + pg_re_flags re_flags; + regexp_matches_ctx *matchctx; + + /* Collect optional parameters */ + if (PG_NARGS() > 2) + { + start = PG_GETARG_INT32(2); + if (start <= 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for parameter \"%s\": %d", + "start", start))); + } + if (PG_NARGS() > 3) + { + n = PG_GETARG_INT32(3); + if (n <= 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for parameter \"%s\": %d", + "n", n))); + } + if (PG_NARGS() > 4) + { + endoption = PG_GETARG_INT32(4); + if (endoption != 0 && endoption != 1) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for parameter \"%s\": %d", + "endoption", endoption))); + } + if (PG_NARGS() > 6) + { + subexpr = PG_GETARG_INT32(6); + if (subexpr < 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for parameter \"%s\": %d", + "subexpr", subexpr))); + } + + /* Determine options */ + parse_re_flags(&re_flags, flags); + /* User mustn't specify 'g' */ + if (re_flags.glob) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + /* translator: %s is a SQL function name */ + errmsg("%s does not support the \"global\" option", + "regexp_instr()"))); + /* But we find all the matches anyway */ + re_flags.glob = true; + + /* Do the matching */ + matchctx = setup_regexp_matches(str, pattern, &re_flags, start - 1, + PG_GET_COLLATION(), + (subexpr > 0), /* need submatches? */ + false, false); + + /* When n exceeds matches return 0 (includes case of no matches) */ + if (n > matchctx->nmatches) + PG_RETURN_INT32(0); + + /* When subexpr exceeds number of subexpressions return 0 */ + if (subexpr > matchctx->npatterns) + PG_RETURN_INT32(0); + + /* Select the appropriate match position to return */ + pos = (n - 1) * matchctx->npatterns; + if (subexpr > 0) + pos += subexpr - 1; + pos *= 2; + if (endoption == 1) + pos += 1; + + if (matchctx->match_locs[pos] >= 0) + PG_RETURN_INT32(matchctx->match_locs[pos] + 1); + else + PG_RETURN_INT32(0); /* position not identifiable */ +} + +/* This is separate to keep the opr_sanity regression test from complaining */ +Datum +regexp_instr_no_start(PG_FUNCTION_ARGS) +{ + return regexp_instr(fcinfo); +} + +/* This is separate to keep the opr_sanity regression test from complaining */ +Datum +regexp_instr_no_n(PG_FUNCTION_ARGS) +{ + return regexp_instr(fcinfo); +} + +/* This is separate to keep the opr_sanity regression test from complaining */ +Datum +regexp_instr_no_endoption(PG_FUNCTION_ARGS) +{ + return regexp_instr(fcinfo); +} + +/* This is separate to keep the opr_sanity regression test from complaining */ +Datum +regexp_instr_no_flags(PG_FUNCTION_ARGS) +{ + return regexp_instr(fcinfo); +} + +/* This is separate to keep the opr_sanity regression test from complaining */ +Datum +regexp_instr_no_subexpr(PG_FUNCTION_ARGS) +{ + return regexp_instr(fcinfo); +} + +/* + * regexp_like() + * Test for a pattern match within a string. + */ +Datum +regexp_like(PG_FUNCTION_ARGS) +{ + text *str = PG_GETARG_TEXT_PP(0); + text *pattern = PG_GETARG_TEXT_PP(1); + text *flags = PG_GETARG_TEXT_PP_IF_EXISTS(2); + pg_re_flags re_flags; + + /* Determine options */ + parse_re_flags(&re_flags, flags); + /* User mustn't specify 'g' */ + if (re_flags.glob) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + /* translator: %s is a SQL function name */ + errmsg("%s does not support the \"global\" option", + "regexp_like()"))); + + /* Otherwise it's like textregexeq/texticregexeq */ + PG_RETURN_BOOL(RE_compile_and_execute(pattern, + VARDATA_ANY(str), + VARSIZE_ANY_EXHDR(str), + re_flags.cflags, + PG_GET_COLLATION(), + 0, NULL)); +} + +/* This is separate to keep the opr_sanity regression test from complaining */ +Datum +regexp_like_no_flags(PG_FUNCTION_ARGS) +{ + return regexp_like(fcinfo); +} + +/* + * regexp_match() + * Return the first substring(s) matching a pattern within a string. + */ +Datum +regexp_match(PG_FUNCTION_ARGS) +{ + text *orig_str = PG_GETARG_TEXT_PP(0); + text *pattern = PG_GETARG_TEXT_PP(1); + text *flags = PG_GETARG_TEXT_PP_IF_EXISTS(2); + pg_re_flags re_flags; + regexp_matches_ctx *matchctx; + + /* Determine options */ + parse_re_flags(&re_flags, flags); + /* User mustn't specify 'g' */ + if (re_flags.glob) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + /* translator: %s is a SQL function name */ + errmsg("%s does not support the \"global\" option", + "regexp_match()"), + errhint("Use the regexp_matches function instead."))); + + matchctx = setup_regexp_matches(orig_str, pattern, &re_flags, 0, + PG_GET_COLLATION(), true, false, false); + + if (matchctx->nmatches == 0) + PG_RETURN_NULL(); + + Assert(matchctx->nmatches == 1); + + /* Create workspace that build_regexp_match_result needs */ + matchctx->elems = (Datum *) palloc(sizeof(Datum) * matchctx->npatterns); + matchctx->nulls = (bool *) palloc(sizeof(bool) * matchctx->npatterns); + + PG_RETURN_DATUM(PointerGetDatum(build_regexp_match_result(matchctx))); +} + +/* This is separate to keep the opr_sanity regression test from complaining */ +Datum +regexp_match_no_flags(PG_FUNCTION_ARGS) +{ + return regexp_match(fcinfo); +} + +/* + * regexp_matches() + * Return a table of all matches of a pattern within a string. + */ +Datum +regexp_matches(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + regexp_matches_ctx *matchctx; + + if (SRF_IS_FIRSTCALL()) + { + text *pattern = PG_GETARG_TEXT_PP(1); + text *flags = PG_GETARG_TEXT_PP_IF_EXISTS(2); + pg_re_flags re_flags; + MemoryContext oldcontext; + + funcctx = SRF_FIRSTCALL_INIT(); + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + /* Determine options */ + parse_re_flags(&re_flags, flags); + + /* be sure to copy the input string into the multi-call ctx */ + matchctx = setup_regexp_matches(PG_GETARG_TEXT_P_COPY(0), pattern, + &re_flags, 0, + PG_GET_COLLATION(), + true, false, false); + + /* Pre-create workspace that build_regexp_match_result needs */ + matchctx->elems = (Datum *) palloc(sizeof(Datum) * matchctx->npatterns); + matchctx->nulls = (bool *) palloc(sizeof(bool) * matchctx->npatterns); + + MemoryContextSwitchTo(oldcontext); + funcctx->user_fctx = (void *) matchctx; + } + + funcctx = SRF_PERCALL_SETUP(); + matchctx = (regexp_matches_ctx *) funcctx->user_fctx; + + if (matchctx->next_match < matchctx->nmatches) + { + ArrayType *result_ary; + + result_ary = build_regexp_match_result(matchctx); + matchctx->next_match++; + SRF_RETURN_NEXT(funcctx, PointerGetDatum(result_ary)); + } + + SRF_RETURN_DONE(funcctx); +} + +/* This is separate to keep the opr_sanity regression test from complaining */ +Datum +regexp_matches_no_flags(PG_FUNCTION_ARGS) +{ + return regexp_matches(fcinfo); +} + +/* + * setup_regexp_matches --- do the initial matching for regexp_match, + * regexp_split, and related functions + * + * To avoid having to re-find the compiled pattern on each call, we do + * all the matching in one swoop. The returned regexp_matches_ctx contains + * the locations of all the substrings matching the pattern. + * + * start_search: the character (not byte) offset in orig_str at which to + * begin the search. Returned positions are relative to orig_str anyway. + * use_subpatterns: collect data about matches to parenthesized subexpressions. + * ignore_degenerate: ignore zero-length matches. + * fetching_unmatched: caller wants to fetch unmatched substrings. + * + * We don't currently assume that fetching_unmatched is exclusive of fetching + * the matched text too; if it's set, the conversion buffer is large enough to + * fetch any single matched or unmatched string, but not any larger + * substring. (In practice, when splitting the matches are usually small + * anyway, and it didn't seem worth complicating the code further.) + */ +static regexp_matches_ctx * +setup_regexp_matches(text *orig_str, text *pattern, pg_re_flags *re_flags, + int start_search, + Oid collation, + bool use_subpatterns, + bool ignore_degenerate, + bool fetching_unmatched) +{ + regexp_matches_ctx *matchctx = palloc0(sizeof(regexp_matches_ctx)); + int eml = pg_database_encoding_max_length(); + int orig_len; + pg_wchar *wide_str; + int wide_len; + int cflags; + regex_t *cpattern; + regmatch_t *pmatch; + int pmatch_len; + int array_len; + int array_idx; + int prev_match_end; + int prev_valid_match_end; + int maxlen = 0; /* largest fetch length in characters */ + + /* save original string --- we'll extract result substrings from it */ + matchctx->orig_str = orig_str; + + /* convert string to pg_wchar form for matching */ + orig_len = VARSIZE_ANY_EXHDR(orig_str); + wide_str = (pg_wchar *) palloc(sizeof(pg_wchar) * (orig_len + 1)); + wide_len = pg_mb2wchar_with_len(VARDATA_ANY(orig_str), wide_str, orig_len); + + /* set up the compiled pattern */ + cflags = re_flags->cflags; + if (!use_subpatterns) + cflags |= REG_NOSUB; + cpattern = RE_compile_and_cache(pattern, cflags, collation); + + /* do we want to remember subpatterns? */ + if (use_subpatterns && cpattern->re_nsub > 0) + { + matchctx->npatterns = cpattern->re_nsub; + pmatch_len = cpattern->re_nsub + 1; + } + else + { + use_subpatterns = false; + matchctx->npatterns = 1; + pmatch_len = 1; + } + + /* temporary output space for RE package */ + pmatch = palloc(sizeof(regmatch_t) * pmatch_len); + + /* + * the real output space (grown dynamically if needed) + * + * use values 2^n-1, not 2^n, so that we hit the limit at 2^28-1 rather + * than at 2^27 + */ + array_len = re_flags->glob ? 255 : 31; + matchctx->match_locs = (int *) palloc(sizeof(int) * array_len); + array_idx = 0; + + /* search for the pattern, perhaps repeatedly */ + prev_match_end = 0; + prev_valid_match_end = 0; + while (RE_wchar_execute(cpattern, wide_str, wide_len, start_search, + pmatch_len, pmatch)) + { + /* + * If requested, ignore degenerate matches, which are zero-length + * matches occurring at the start or end of a string or just after a + * previous match. + */ + if (!ignore_degenerate || + (pmatch[0].rm_so < wide_len && + pmatch[0].rm_eo > prev_match_end)) + { + /* enlarge output space if needed */ + while (array_idx + matchctx->npatterns * 2 + 1 > array_len) + { + array_len += array_len + 1; /* 2^n-1 => 2^(n+1)-1 */ + if (array_len > MaxAllocSize / sizeof(int)) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("too many regular expression matches"))); + matchctx->match_locs = (int *) repalloc(matchctx->match_locs, + sizeof(int) * array_len); + } + + /* save this match's locations */ + if (use_subpatterns) + { + int i; + + for (i = 1; i <= matchctx->npatterns; i++) + { + int so = pmatch[i].rm_so; + int eo = pmatch[i].rm_eo; + + matchctx->match_locs[array_idx++] = so; + matchctx->match_locs[array_idx++] = eo; + if (so >= 0 && eo >= 0 && (eo - so) > maxlen) + maxlen = (eo - so); + } + } + else + { + int so = pmatch[0].rm_so; + int eo = pmatch[0].rm_eo; + + matchctx->match_locs[array_idx++] = so; + matchctx->match_locs[array_idx++] = eo; + if (so >= 0 && eo >= 0 && (eo - so) > maxlen) + maxlen = (eo - so); + } + matchctx->nmatches++; + + /* + * check length of unmatched portion between end of previous valid + * (nondegenerate, or degenerate but not ignored) match and start + * of current one + */ + if (fetching_unmatched && + pmatch[0].rm_so >= 0 && + (pmatch[0].rm_so - prev_valid_match_end) > maxlen) + maxlen = (pmatch[0].rm_so - prev_valid_match_end); + prev_valid_match_end = pmatch[0].rm_eo; + } + prev_match_end = pmatch[0].rm_eo; + + /* if not glob, stop after one match */ + if (!re_flags->glob) + break; + + /* + * Advance search position. Normally we start the next search at the + * end of the previous match; but if the match was of zero length, we + * have to advance by one character, or we'd just find the same match + * again. + */ + start_search = prev_match_end; + if (pmatch[0].rm_so == pmatch[0].rm_eo) + start_search++; + if (start_search > wide_len) + break; + } + + /* + * check length of unmatched portion between end of last match and end of + * input string + */ + if (fetching_unmatched && + (wide_len - prev_valid_match_end) > maxlen) + maxlen = (wide_len - prev_valid_match_end); + + /* + * Keep a note of the end position of the string for the benefit of + * splitting code. + */ + matchctx->match_locs[array_idx] = wide_len; + + if (eml > 1) + { + int64 maxsiz = eml * (int64) maxlen; + int conv_bufsiz; + + /* + * Make the conversion buffer large enough for any substring of + * interest. + * + * Worst case: assume we need the maximum size (maxlen*eml), but take + * advantage of the fact that the original string length in bytes is + * an upper bound on the byte length of any fetched substring (and we + * know that len+1 is safe to allocate because the varlena header is + * longer than 1 byte). + */ + if (maxsiz > orig_len) + conv_bufsiz = orig_len + 1; + else + conv_bufsiz = maxsiz + 1; /* safe since maxsiz < 2^30 */ + + matchctx->conv_buf = palloc(conv_bufsiz); + matchctx->conv_bufsiz = conv_bufsiz; + matchctx->wide_str = wide_str; + } + else + { + /* No need to keep the wide string if we're in a single-byte charset. */ + pfree(wide_str); + matchctx->wide_str = NULL; + matchctx->conv_buf = NULL; + matchctx->conv_bufsiz = 0; + } + + /* Clean up temp storage */ + pfree(pmatch); + + return matchctx; +} + +/* + * build_regexp_match_result - build output array for current match + */ +static ArrayType * +build_regexp_match_result(regexp_matches_ctx *matchctx) +{ + char *buf = matchctx->conv_buf; + Datum *elems = matchctx->elems; + bool *nulls = matchctx->nulls; + int dims[1]; + int lbs[1]; + int loc; + int i; + + /* Extract matching substrings from the original string */ + loc = matchctx->next_match * matchctx->npatterns * 2; + for (i = 0; i < matchctx->npatterns; i++) + { + int so = matchctx->match_locs[loc++]; + int eo = matchctx->match_locs[loc++]; + + if (so < 0 || eo < 0) + { + elems[i] = (Datum) 0; + nulls[i] = true; + } + else if (buf) + { + int len = pg_wchar2mb_with_len(matchctx->wide_str + so, + buf, + eo - so); + + Assert(len < matchctx->conv_bufsiz); + elems[i] = PointerGetDatum(cstring_to_text_with_len(buf, len)); + nulls[i] = false; + } + else + { + elems[i] = DirectFunctionCall3(text_substr, + PointerGetDatum(matchctx->orig_str), + Int32GetDatum(so + 1), + Int32GetDatum(eo - so)); + nulls[i] = false; + } + } + + /* And form an array */ + dims[0] = matchctx->npatterns; + lbs[0] = 1; + /* XXX: this hardcodes assumptions about the text type */ + return construct_md_array(elems, nulls, 1, dims, lbs, + TEXTOID, -1, false, TYPALIGN_INT); +} + +/* + * regexp_split_to_table() + * Split the string at matches of the pattern, returning the + * split-out substrings as a table. + */ +Datum +regexp_split_to_table(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + regexp_matches_ctx *splitctx; + + if (SRF_IS_FIRSTCALL()) + { + text *pattern = PG_GETARG_TEXT_PP(1); + text *flags = PG_GETARG_TEXT_PP_IF_EXISTS(2); + pg_re_flags re_flags; + MemoryContext oldcontext; + + funcctx = SRF_FIRSTCALL_INIT(); + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + /* Determine options */ + parse_re_flags(&re_flags, flags); + /* User mustn't specify 'g' */ + if (re_flags.glob) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + /* translator: %s is a SQL function name */ + errmsg("%s does not support the \"global\" option", + "regexp_split_to_table()"))); + /* But we find all the matches anyway */ + re_flags.glob = true; + + /* be sure to copy the input string into the multi-call ctx */ + splitctx = setup_regexp_matches(PG_GETARG_TEXT_P_COPY(0), pattern, + &re_flags, 0, + PG_GET_COLLATION(), + false, true, true); + + MemoryContextSwitchTo(oldcontext); + funcctx->user_fctx = (void *) splitctx; + } + + funcctx = SRF_PERCALL_SETUP(); + splitctx = (regexp_matches_ctx *) funcctx->user_fctx; + + if (splitctx->next_match <= splitctx->nmatches) + { + Datum result = build_regexp_split_result(splitctx); + + splitctx->next_match++; + SRF_RETURN_NEXT(funcctx, result); + } + + SRF_RETURN_DONE(funcctx); +} + +/* This is separate to keep the opr_sanity regression test from complaining */ +Datum +regexp_split_to_table_no_flags(PG_FUNCTION_ARGS) +{ + return regexp_split_to_table(fcinfo); +} + +/* + * regexp_split_to_array() + * Split the string at matches of the pattern, returning the + * split-out substrings as an array. + */ +Datum +regexp_split_to_array(PG_FUNCTION_ARGS) +{ + ArrayBuildState *astate = NULL; + pg_re_flags re_flags; + regexp_matches_ctx *splitctx; + + /* Determine options */ + parse_re_flags(&re_flags, PG_GETARG_TEXT_PP_IF_EXISTS(2)); + /* User mustn't specify 'g' */ + if (re_flags.glob) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + /* translator: %s is a SQL function name */ + errmsg("%s does not support the \"global\" option", + "regexp_split_to_array()"))); + /* But we find all the matches anyway */ + re_flags.glob = true; + + splitctx = setup_regexp_matches(PG_GETARG_TEXT_PP(0), + PG_GETARG_TEXT_PP(1), + &re_flags, 0, + PG_GET_COLLATION(), + false, true, true); + + while (splitctx->next_match <= splitctx->nmatches) + { + astate = accumArrayResult(astate, + build_regexp_split_result(splitctx), + false, + TEXTOID, + CurrentMemoryContext); + splitctx->next_match++; + } + + PG_RETURN_DATUM(makeArrayResult(astate, CurrentMemoryContext)); +} + +/* This is separate to keep the opr_sanity regression test from complaining */ +Datum +regexp_split_to_array_no_flags(PG_FUNCTION_ARGS) +{ + return regexp_split_to_array(fcinfo); +} + +/* + * build_regexp_split_result - build output string for current match + * + * We return the string between the current match and the previous one, + * or the string after the last match when next_match == nmatches. + */ +static Datum +build_regexp_split_result(regexp_matches_ctx *splitctx) +{ + char *buf = splitctx->conv_buf; + int startpos; + int endpos; + + if (splitctx->next_match > 0) + startpos = splitctx->match_locs[splitctx->next_match * 2 - 1]; + else + startpos = 0; + if (startpos < 0) + elog(ERROR, "invalid match ending position"); + + endpos = splitctx->match_locs[splitctx->next_match * 2]; + if (endpos < startpos) + elog(ERROR, "invalid match starting position"); + + if (buf) + { + int len; + + len = pg_wchar2mb_with_len(splitctx->wide_str + startpos, + buf, + endpos - startpos); + Assert(len < splitctx->conv_bufsiz); + return PointerGetDatum(cstring_to_text_with_len(buf, len)); + } + else + { + return DirectFunctionCall3(text_substr, + PointerGetDatum(splitctx->orig_str), + Int32GetDatum(startpos + 1), + Int32GetDatum(endpos - startpos)); + } +} + +/* + * regexp_substr() + * Return the substring that matches a regular expression pattern + */ +Datum +regexp_substr(PG_FUNCTION_ARGS) +{ + text *str = PG_GETARG_TEXT_PP(0); + text *pattern = PG_GETARG_TEXT_PP(1); + int start = 1; + int n = 1; + text *flags = PG_GETARG_TEXT_PP_IF_EXISTS(4); + int subexpr = 0; + int so, + eo, + pos; + pg_re_flags re_flags; + regexp_matches_ctx *matchctx; + + /* Collect optional parameters */ + if (PG_NARGS() > 2) + { + start = PG_GETARG_INT32(2); + if (start <= 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for parameter \"%s\": %d", + "start", start))); + } + if (PG_NARGS() > 3) + { + n = PG_GETARG_INT32(3); + if (n <= 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for parameter \"%s\": %d", + "n", n))); + } + if (PG_NARGS() > 5) + { + subexpr = PG_GETARG_INT32(5); + if (subexpr < 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for parameter \"%s\": %d", + "subexpr", subexpr))); + } + + /* Determine options */ + parse_re_flags(&re_flags, flags); + /* User mustn't specify 'g' */ + if (re_flags.glob) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + /* translator: %s is a SQL function name */ + errmsg("%s does not support the \"global\" option", + "regexp_substr()"))); + /* But we find all the matches anyway */ + re_flags.glob = true; + + /* Do the matching */ + matchctx = setup_regexp_matches(str, pattern, &re_flags, start - 1, + PG_GET_COLLATION(), + (subexpr > 0), /* need submatches? */ + false, false); + + /* When n exceeds matches return NULL (includes case of no matches) */ + if (n > matchctx->nmatches) + PG_RETURN_NULL(); + + /* When subexpr exceeds number of subexpressions return NULL */ + if (subexpr > matchctx->npatterns) + PG_RETURN_NULL(); + + /* Select the appropriate match position to return */ + pos = (n - 1) * matchctx->npatterns; + if (subexpr > 0) + pos += subexpr - 1; + pos *= 2; + so = matchctx->match_locs[pos]; + eo = matchctx->match_locs[pos + 1]; + + if (so < 0 || eo < 0) + PG_RETURN_NULL(); /* unidentifiable location */ + + PG_RETURN_DATUM(DirectFunctionCall3(text_substr, + PointerGetDatum(matchctx->orig_str), + Int32GetDatum(so + 1), + Int32GetDatum(eo - so))); +} + +/* This is separate to keep the opr_sanity regression test from complaining */ +Datum +regexp_substr_no_start(PG_FUNCTION_ARGS) +{ + return regexp_substr(fcinfo); +} + +/* This is separate to keep the opr_sanity regression test from complaining */ +Datum +regexp_substr_no_n(PG_FUNCTION_ARGS) +{ + return regexp_substr(fcinfo); +} + +/* This is separate to keep the opr_sanity regression test from complaining */ +Datum +regexp_substr_no_flags(PG_FUNCTION_ARGS) +{ + return regexp_substr(fcinfo); +} + +/* This is separate to keep the opr_sanity regression test from complaining */ +Datum +regexp_substr_no_subexpr(PG_FUNCTION_ARGS) +{ + return regexp_substr(fcinfo); +} + +/* + * regexp_fixed_prefix - extract fixed prefix, if any, for a regexp + * + * The result is NULL if there is no fixed prefix, else a palloc'd string. + * If it is an exact match, not just a prefix, *exact is returned as true. + */ +char * +regexp_fixed_prefix(text *text_re, bool case_insensitive, Oid collation, + bool *exact) +{ + char *result; + regex_t *re; + int cflags; + int re_result; + pg_wchar *str; + size_t slen; + size_t maxlen; + char errMsg[100]; + + *exact = false; /* default result */ + + /* Compile RE */ + cflags = REG_ADVANCED; + if (case_insensitive) + cflags |= REG_ICASE; + + re = RE_compile_and_cache(text_re, cflags | REG_NOSUB, collation); + + /* Examine it to see if there's a fixed prefix */ + re_result = pg_regprefix(re, &str, &slen); + + switch (re_result) + { + case REG_NOMATCH: + return NULL; + + case REG_PREFIX: + /* continue with wchar conversion */ + break; + + case REG_EXACT: + *exact = true; + /* continue with wchar conversion */ + break; + + default: + /* re failed??? */ + pg_regerror(re_result, re, errMsg, sizeof(errMsg)); + ereport(ERROR, + (errcode(ERRCODE_INVALID_REGULAR_EXPRESSION), + errmsg("regular expression failed: %s", errMsg))); + break; + } + + /* Convert pg_wchar result back to database encoding */ + maxlen = pg_database_encoding_max_length() * slen + 1; + result = (char *) palloc(maxlen); + slen = pg_wchar2mb_with_len(str, result, slen); + Assert(slen < maxlen); + + pfree(str); + + return result; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/regproc.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/regproc.c new file mode 100644 index 00000000000..296930eb3bc --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/regproc.c @@ -0,0 +1,2018 @@ +/*------------------------------------------------------------------------- + * + * regproc.c + * Functions for the built-in types regproc, regclass, regtype, etc. + * + * These types are all binary-compatible with type Oid, and rely on Oid + * for comparison and so forth. Their only interesting behavior is in + * special I/O conversion routines. + * + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/adt/regproc.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <ctype.h> + +#include "access/htup_details.h" +#include "catalog/namespace.h" +#include "catalog/pg_class.h" +#include "catalog/pg_collation.h" +#include "catalog/pg_operator.h" +#include "catalog/pg_proc.h" +#include "catalog/pg_ts_config.h" +#include "catalog/pg_ts_dict.h" +#include "catalog/pg_type.h" +#include "lib/stringinfo.h" +#include "mb/pg_wchar.h" +#include "miscadmin.h" +#include "nodes/miscnodes.h" +#include "parser/parse_type.h" +#include "parser/scansup.h" +#include "utils/acl.h" +#include "utils/builtins.h" +#include "utils/lsyscache.h" +#include "utils/regproc.h" +#include "utils/syscache.h" +#include "utils/varlena.h" + +static bool parseNumericOid(char *string, Oid *result, Node *escontext); +static bool parseDashOrOid(char *string, Oid *result, Node *escontext); +static bool parseNameAndArgTypes(const char *string, bool allowNone, + List **names, int *nargs, Oid *argtypes, + Node *escontext); + + +/***************************************************************************** + * USER I/O ROUTINES * + *****************************************************************************/ + +/* + * regprocin - converts "proname" to proc OID + * + * We also accept a numeric OID, for symmetry with the output routine. + * + * '-' signifies unknown (OID 0). In all other cases, the input must + * match an existing pg_proc entry. + */ +Datum +regprocin(PG_FUNCTION_ARGS) +{ + char *pro_name_or_oid = PG_GETARG_CSTRING(0); + Node *escontext = fcinfo->context; + RegProcedure result; + List *names; + FuncCandidateList clist; + + /* Handle "-" or numeric OID */ + if (parseDashOrOid(pro_name_or_oid, &result, escontext)) + PG_RETURN_OID(result); + + /* Else it's a name, possibly schema-qualified */ + + /* + * We should never get here in bootstrap mode, as all references should + * have been resolved by genbki.pl. + */ + if (IsBootstrapProcessingMode()) + elog(ERROR, "regproc values must be OIDs in bootstrap mode"); + + /* + * Normal case: parse the name into components and see if it matches any + * pg_proc entries in the current search path. + */ + names = stringToQualifiedNameList(pro_name_or_oid, escontext); + if (names == NIL) + PG_RETURN_NULL(); + + clist = FuncnameGetCandidates(names, -1, NIL, false, false, false, true); + + if (clist == NULL) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("function \"%s\" does not exist", pro_name_or_oid))); + else if (clist->next != NULL) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_AMBIGUOUS_FUNCTION), + errmsg("more than one function named \"%s\"", + pro_name_or_oid))); + + result = clist->oid; + + PG_RETURN_OID(result); +} + +/* + * to_regproc - converts "proname" to proc OID + * + * If the name is not found, we return NULL. + */ +Datum +to_regproc(PG_FUNCTION_ARGS) +{ + char *pro_name = text_to_cstring(PG_GETARG_TEXT_PP(0)); + Datum result; + ErrorSaveContext escontext = {T_ErrorSaveContext}; + + if (!DirectInputFunctionCallSafe(regprocin, pro_name, + InvalidOid, -1, + (Node *) &escontext, + &result)) + PG_RETURN_NULL(); + PG_RETURN_DATUM(result); +} + +/* + * regprocout - converts proc OID to "pro_name" + */ +Datum +regprocout(PG_FUNCTION_ARGS) +{ + RegProcedure proid = PG_GETARG_OID(0); + char *result; + HeapTuple proctup; + + if (proid == InvalidOid) + { + result = pstrdup("-"); + PG_RETURN_CSTRING(result); + } + + proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(proid)); + + if (HeapTupleIsValid(proctup)) + { + Form_pg_proc procform = (Form_pg_proc) GETSTRUCT(proctup); + char *proname = NameStr(procform->proname); + + /* + * In bootstrap mode, skip the fancy namespace stuff and just return + * the proc name. (This path is only needed for debugging output + * anyway.) + */ + if (IsBootstrapProcessingMode()) + result = pstrdup(proname); + else + { + char *nspname; + FuncCandidateList clist; + + /* + * Would this proc be found (uniquely!) by regprocin? If not, + * qualify it. + */ + clist = FuncnameGetCandidates(list_make1(makeString(proname)), + -1, NIL, false, false, false, false); + if (clist != NULL && clist->next == NULL && + clist->oid == proid) + nspname = NULL; + else + nspname = get_namespace_name(procform->pronamespace); + + result = quote_qualified_identifier(nspname, proname); + } + + ReleaseSysCache(proctup); + } + else + { + /* If OID doesn't match any pg_proc entry, return it numerically */ + result = (char *) palloc(NAMEDATALEN); + snprintf(result, NAMEDATALEN, "%u", proid); + } + + PG_RETURN_CSTRING(result); +} + +/* + * regprocrecv - converts external binary format to regproc + */ +Datum +regprocrecv(PG_FUNCTION_ARGS) +{ + /* Exactly the same as oidrecv, so share code */ + return oidrecv(fcinfo); +} + +/* + * regprocsend - converts regproc to binary format + */ +Datum +regprocsend(PG_FUNCTION_ARGS) +{ + /* Exactly the same as oidsend, so share code */ + return oidsend(fcinfo); +} + + +/* + * regprocedurein - converts "proname(args)" to proc OID + * + * We also accept a numeric OID, for symmetry with the output routine. + * + * '-' signifies unknown (OID 0). In all other cases, the input must + * match an existing pg_proc entry. + */ +Datum +regprocedurein(PG_FUNCTION_ARGS) +{ + char *pro_name_or_oid = PG_GETARG_CSTRING(0); + Node *escontext = fcinfo->context; + RegProcedure result; + List *names; + int nargs; + Oid argtypes[FUNC_MAX_ARGS]; + FuncCandidateList clist; + + /* Handle "-" or numeric OID */ + if (parseDashOrOid(pro_name_or_oid, &result, escontext)) + PG_RETURN_OID(result); + + /* The rest of this wouldn't work in bootstrap mode */ + if (IsBootstrapProcessingMode()) + elog(ERROR, "regprocedure values must be OIDs in bootstrap mode"); + + /* + * Else it's a name and arguments. Parse the name and arguments, look up + * potential matches in the current namespace search list, and scan to see + * which one exactly matches the given argument types. (There will not be + * more than one match.) + */ + if (!parseNameAndArgTypes(pro_name_or_oid, false, + &names, &nargs, argtypes, + escontext)) + PG_RETURN_NULL(); + + clist = FuncnameGetCandidates(names, nargs, NIL, false, false, + false, true); + + for (; clist; clist = clist->next) + { + if (memcmp(clist->args, argtypes, nargs * sizeof(Oid)) == 0) + break; + } + + if (clist == NULL) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("function \"%s\" does not exist", pro_name_or_oid))); + + result = clist->oid; + + PG_RETURN_OID(result); +} + +/* + * to_regprocedure - converts "proname(args)" to proc OID + * + * If the name is not found, we return NULL. + */ +Datum +to_regprocedure(PG_FUNCTION_ARGS) +{ + char *pro_name = text_to_cstring(PG_GETARG_TEXT_PP(0)); + Datum result; + ErrorSaveContext escontext = {T_ErrorSaveContext}; + + if (!DirectInputFunctionCallSafe(regprocedurein, pro_name, + InvalidOid, -1, + (Node *) &escontext, + &result)) + PG_RETURN_NULL(); + PG_RETURN_DATUM(result); +} + +/* + * format_procedure - converts proc OID to "pro_name(args)" + * + * This exports the useful functionality of regprocedureout for use + * in other backend modules. The result is a palloc'd string. + */ +char * +format_procedure(Oid procedure_oid) +{ + return format_procedure_extended(procedure_oid, 0); +} + +char * +format_procedure_qualified(Oid procedure_oid) +{ + return format_procedure_extended(procedure_oid, FORMAT_PROC_FORCE_QUALIFY); +} + +/* + * format_procedure_extended - converts procedure OID to "pro_name(args)" + * + * This exports the useful functionality of regprocedureout for use + * in other backend modules. The result is a palloc'd string, or NULL. + * + * Routine to produce regprocedure names; see format_procedure above. + * + * The following bits in 'flags' modify the behavior: + * - FORMAT_PROC_INVALID_AS_NULL + * if the procedure OID is invalid or unknown, return NULL instead + * of the numeric OID. + * - FORMAT_PROC_FORCE_QUALIFY + * always schema-qualify procedure names, regardless of search_path + */ +char * +format_procedure_extended(Oid procedure_oid, bits16 flags) +{ + char *result; + HeapTuple proctup; + + proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(procedure_oid)); + + if (HeapTupleIsValid(proctup)) + { + Form_pg_proc procform = (Form_pg_proc) GETSTRUCT(proctup); + char *proname = NameStr(procform->proname); + int nargs = procform->pronargs; + int i; + char *nspname; + StringInfoData buf; + + /* XXX no support here for bootstrap mode */ + Assert(!IsBootstrapProcessingMode()); + + initStringInfo(&buf); + + /* + * Would this proc be found (given the right args) by regprocedurein? + * If not, or if caller requests it, we need to qualify it. + */ + if ((flags & FORMAT_PROC_FORCE_QUALIFY) == 0 && + FunctionIsVisible(procedure_oid)) + nspname = NULL; + else + nspname = get_namespace_name(procform->pronamespace); + + appendStringInfo(&buf, "%s(", + quote_qualified_identifier(nspname, proname)); + for (i = 0; i < nargs; i++) + { + Oid thisargtype = procform->proargtypes.values[i]; + + if (i > 0) + appendStringInfoChar(&buf, ','); + appendStringInfoString(&buf, + (flags & FORMAT_PROC_FORCE_QUALIFY) != 0 ? + format_type_be_qualified(thisargtype) : + format_type_be(thisargtype)); + } + appendStringInfoChar(&buf, ')'); + + result = buf.data; + + ReleaseSysCache(proctup); + } + else if ((flags & FORMAT_PROC_INVALID_AS_NULL) != 0) + { + /* If object is undefined, return NULL as wanted by caller */ + result = NULL; + } + else + { + /* If OID doesn't match any pg_proc entry, return it numerically */ + result = (char *) palloc(NAMEDATALEN); + snprintf(result, NAMEDATALEN, "%u", procedure_oid); + } + + return result; +} + +/* + * Output an objname/objargs representation for the procedure with the + * given OID. If it doesn't exist, an error is thrown. + * + * This can be used to feed get_object_address. + */ +void +format_procedure_parts(Oid procedure_oid, List **objnames, List **objargs, + bool missing_ok) +{ + HeapTuple proctup; + Form_pg_proc procform; + int nargs; + int i; + + proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(procedure_oid)); + + if (!HeapTupleIsValid(proctup)) + { + if (!missing_ok) + elog(ERROR, "cache lookup failed for procedure with OID %u", procedure_oid); + return; + } + + procform = (Form_pg_proc) GETSTRUCT(proctup); + nargs = procform->pronargs; + + *objnames = list_make2(get_namespace_name_or_temp(procform->pronamespace), + pstrdup(NameStr(procform->proname))); + *objargs = NIL; + for (i = 0; i < nargs; i++) + { + Oid thisargtype = procform->proargtypes.values[i]; + + *objargs = lappend(*objargs, format_type_be_qualified(thisargtype)); + } + + ReleaseSysCache(proctup); +} + +/* + * regprocedureout - converts proc OID to "pro_name(args)" + */ +Datum +regprocedureout(PG_FUNCTION_ARGS) +{ + RegProcedure proid = PG_GETARG_OID(0); + char *result; + + if (proid == InvalidOid) + result = pstrdup("-"); + else + result = format_procedure(proid); + + PG_RETURN_CSTRING(result); +} + +/* + * regprocedurerecv - converts external binary format to regprocedure + */ +Datum +regprocedurerecv(PG_FUNCTION_ARGS) +{ + /* Exactly the same as oidrecv, so share code */ + return oidrecv(fcinfo); +} + +/* + * regproceduresend - converts regprocedure to binary format + */ +Datum +regproceduresend(PG_FUNCTION_ARGS) +{ + /* Exactly the same as oidsend, so share code */ + return oidsend(fcinfo); +} + + +/* + * regoperin - converts "oprname" to operator OID + * + * We also accept a numeric OID, for symmetry with the output routine. + * + * '0' signifies unknown (OID 0). In all other cases, the input must + * match an existing pg_operator entry. + */ +Datum +regoperin(PG_FUNCTION_ARGS) +{ + char *opr_name_or_oid = PG_GETARG_CSTRING(0); + Node *escontext = fcinfo->context; + Oid result; + List *names; + FuncCandidateList clist; + + /* Handle "0" or numeric OID */ + if (parseNumericOid(opr_name_or_oid, &result, escontext)) + PG_RETURN_OID(result); + + /* Else it's a name, possibly schema-qualified */ + + /* The rest of this wouldn't work in bootstrap mode */ + if (IsBootstrapProcessingMode()) + elog(ERROR, "regoper values must be OIDs in bootstrap mode"); + + /* + * Normal case: parse the name into components and see if it matches any + * pg_operator entries in the current search path. + */ + names = stringToQualifiedNameList(opr_name_or_oid, escontext); + if (names == NIL) + PG_RETURN_NULL(); + + clist = OpernameGetCandidates(names, '\0', true); + + if (clist == NULL) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("operator does not exist: %s", opr_name_or_oid))); + else if (clist->next != NULL) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_AMBIGUOUS_FUNCTION), + errmsg("more than one operator named %s", + opr_name_or_oid))); + + result = clist->oid; + + PG_RETURN_OID(result); +} + +/* + * to_regoper - converts "oprname" to operator OID + * + * If the name is not found, we return NULL. + */ +Datum +to_regoper(PG_FUNCTION_ARGS) +{ + char *opr_name = text_to_cstring(PG_GETARG_TEXT_PP(0)); + Datum result; + ErrorSaveContext escontext = {T_ErrorSaveContext}; + + if (!DirectInputFunctionCallSafe(regoperin, opr_name, + InvalidOid, -1, + (Node *) &escontext, + &result)) + PG_RETURN_NULL(); + PG_RETURN_DATUM(result); +} + +/* + * regoperout - converts operator OID to "opr_name" + */ +Datum +regoperout(PG_FUNCTION_ARGS) +{ + Oid oprid = PG_GETARG_OID(0); + char *result; + HeapTuple opertup; + + if (oprid == InvalidOid) + { + result = pstrdup("0"); + PG_RETURN_CSTRING(result); + } + + opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(oprid)); + + if (HeapTupleIsValid(opertup)) + { + Form_pg_operator operform = (Form_pg_operator) GETSTRUCT(opertup); + char *oprname = NameStr(operform->oprname); + + /* + * In bootstrap mode, skip the fancy namespace stuff and just return + * the oper name. (This path is only needed for debugging output + * anyway.) + */ + if (IsBootstrapProcessingMode()) + result = pstrdup(oprname); + else + { + FuncCandidateList clist; + + /* + * Would this oper be found (uniquely!) by regoperin? If not, + * qualify it. + */ + clist = OpernameGetCandidates(list_make1(makeString(oprname)), + '\0', false); + if (clist != NULL && clist->next == NULL && + clist->oid == oprid) + result = pstrdup(oprname); + else + { + const char *nspname; + + nspname = get_namespace_name(operform->oprnamespace); + nspname = quote_identifier(nspname); + result = (char *) palloc(strlen(nspname) + strlen(oprname) + 2); + sprintf(result, "%s.%s", nspname, oprname); + } + } + + ReleaseSysCache(opertup); + } + else + { + /* + * If OID doesn't match any pg_operator entry, return it numerically + */ + result = (char *) palloc(NAMEDATALEN); + snprintf(result, NAMEDATALEN, "%u", oprid); + } + + PG_RETURN_CSTRING(result); +} + +/* + * regoperrecv - converts external binary format to regoper + */ +Datum +regoperrecv(PG_FUNCTION_ARGS) +{ + /* Exactly the same as oidrecv, so share code */ + return oidrecv(fcinfo); +} + +/* + * regopersend - converts regoper to binary format + */ +Datum +regopersend(PG_FUNCTION_ARGS) +{ + /* Exactly the same as oidsend, so share code */ + return oidsend(fcinfo); +} + + +/* + * regoperatorin - converts "oprname(args)" to operator OID + * + * We also accept a numeric OID, for symmetry with the output routine. + * + * '0' signifies unknown (OID 0). In all other cases, the input must + * match an existing pg_operator entry. + */ +Datum +regoperatorin(PG_FUNCTION_ARGS) +{ + char *opr_name_or_oid = PG_GETARG_CSTRING(0); + Node *escontext = fcinfo->context; + Oid result; + List *names; + int nargs; + Oid argtypes[FUNC_MAX_ARGS]; + + /* Handle "0" or numeric OID */ + if (parseNumericOid(opr_name_or_oid, &result, escontext)) + PG_RETURN_OID(result); + + /* The rest of this wouldn't work in bootstrap mode */ + if (IsBootstrapProcessingMode()) + elog(ERROR, "regoperator values must be OIDs in bootstrap mode"); + + /* + * Else it's a name and arguments. Parse the name and arguments, look up + * potential matches in the current namespace search list, and scan to see + * which one exactly matches the given argument types. (There will not be + * more than one match.) + */ + if (!parseNameAndArgTypes(opr_name_or_oid, true, + &names, &nargs, argtypes, + escontext)) + PG_RETURN_NULL(); + + if (nargs == 1) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_UNDEFINED_PARAMETER), + errmsg("missing argument"), + errhint("Use NONE to denote the missing argument of a unary operator."))); + if (nargs != 2) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_TOO_MANY_ARGUMENTS), + errmsg("too many arguments"), + errhint("Provide two argument types for operator."))); + + result = OpernameGetOprid(names, argtypes[0], argtypes[1]); + + if (!OidIsValid(result)) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("operator does not exist: %s", opr_name_or_oid))); + + PG_RETURN_OID(result); +} + +/* + * to_regoperator - converts "oprname(args)" to operator OID + * + * If the name is not found, we return NULL. + */ +Datum +to_regoperator(PG_FUNCTION_ARGS) +{ + char *opr_name_or_oid = text_to_cstring(PG_GETARG_TEXT_PP(0)); + Datum result; + ErrorSaveContext escontext = {T_ErrorSaveContext}; + + if (!DirectInputFunctionCallSafe(regoperatorin, opr_name_or_oid, + InvalidOid, -1, + (Node *) &escontext, + &result)) + PG_RETURN_NULL(); + PG_RETURN_DATUM(result); +} + +/* + * format_operator_extended - converts operator OID to "opr_name(args)" + * + * This exports the useful functionality of regoperatorout for use + * in other backend modules. The result is a palloc'd string, or NULL. + * + * The following bits in 'flags' modify the behavior: + * - FORMAT_OPERATOR_INVALID_AS_NULL + * if the operator OID is invalid or unknown, return NULL instead + * of the numeric OID. + * - FORMAT_OPERATOR_FORCE_QUALIFY + * always schema-qualify operator names, regardless of search_path + */ +char * +format_operator_extended(Oid operator_oid, bits16 flags) +{ + char *result; + HeapTuple opertup; + + opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(operator_oid)); + + if (HeapTupleIsValid(opertup)) + { + Form_pg_operator operform = (Form_pg_operator) GETSTRUCT(opertup); + char *oprname = NameStr(operform->oprname); + char *nspname; + StringInfoData buf; + + /* XXX no support here for bootstrap mode */ + Assert(!IsBootstrapProcessingMode()); + + initStringInfo(&buf); + + /* + * Would this oper be found (given the right args) by regoperatorin? + * If not, or if caller explicitly requests it, we need to qualify it. + */ + if ((flags & FORMAT_OPERATOR_FORCE_QUALIFY) != 0 || + !OperatorIsVisible(operator_oid)) + { + nspname = get_namespace_name(operform->oprnamespace); + appendStringInfo(&buf, "%s.", + quote_identifier(nspname)); + } + + appendStringInfo(&buf, "%s(", oprname); + + if (operform->oprleft) + appendStringInfo(&buf, "%s,", + (flags & FORMAT_OPERATOR_FORCE_QUALIFY) != 0 ? + format_type_be_qualified(operform->oprleft) : + format_type_be(operform->oprleft)); + else + appendStringInfoString(&buf, "NONE,"); + + if (operform->oprright) + appendStringInfo(&buf, "%s)", + (flags & FORMAT_OPERATOR_FORCE_QUALIFY) != 0 ? + format_type_be_qualified(operform->oprright) : + format_type_be(operform->oprright)); + else + appendStringInfoString(&buf, "NONE)"); + + result = buf.data; + + ReleaseSysCache(opertup); + } + else if ((flags & FORMAT_OPERATOR_INVALID_AS_NULL) != 0) + { + /* If object is undefined, return NULL as wanted by caller */ + result = NULL; + } + else + { + /* + * If OID doesn't match any pg_operator entry, return it numerically + */ + result = (char *) palloc(NAMEDATALEN); + snprintf(result, NAMEDATALEN, "%u", operator_oid); + } + + return result; +} + +char * +format_operator(Oid operator_oid) +{ + return format_operator_extended(operator_oid, 0); +} + +char * +format_operator_qualified(Oid operator_oid) +{ + return format_operator_extended(operator_oid, + FORMAT_OPERATOR_FORCE_QUALIFY); +} + +void +format_operator_parts(Oid operator_oid, List **objnames, List **objargs, + bool missing_ok) +{ + HeapTuple opertup; + Form_pg_operator oprForm; + + opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(operator_oid)); + if (!HeapTupleIsValid(opertup)) + { + if (!missing_ok) + elog(ERROR, "cache lookup failed for operator with OID %u", + operator_oid); + return; + } + + oprForm = (Form_pg_operator) GETSTRUCT(opertup); + *objnames = list_make2(get_namespace_name_or_temp(oprForm->oprnamespace), + pstrdup(NameStr(oprForm->oprname))); + *objargs = NIL; + if (oprForm->oprleft) + *objargs = lappend(*objargs, + format_type_be_qualified(oprForm->oprleft)); + if (oprForm->oprright) + *objargs = lappend(*objargs, + format_type_be_qualified(oprForm->oprright)); + + ReleaseSysCache(opertup); +} + +/* + * regoperatorout - converts operator OID to "opr_name(args)" + */ +Datum +regoperatorout(PG_FUNCTION_ARGS) +{ + Oid oprid = PG_GETARG_OID(0); + char *result; + + if (oprid == InvalidOid) + result = pstrdup("0"); + else + result = format_operator(oprid); + + PG_RETURN_CSTRING(result); +} + +/* + * regoperatorrecv - converts external binary format to regoperator + */ +Datum +regoperatorrecv(PG_FUNCTION_ARGS) +{ + /* Exactly the same as oidrecv, so share code */ + return oidrecv(fcinfo); +} + +/* + * regoperatorsend - converts regoperator to binary format + */ +Datum +regoperatorsend(PG_FUNCTION_ARGS) +{ + /* Exactly the same as oidsend, so share code */ + return oidsend(fcinfo); +} + + +/* + * regclassin - converts "classname" to class OID + * + * We also accept a numeric OID, for symmetry with the output routine. + * + * '-' signifies unknown (OID 0). In all other cases, the input must + * match an existing pg_class entry. + */ +Datum +regclassin(PG_FUNCTION_ARGS) +{ + char *class_name_or_oid = PG_GETARG_CSTRING(0); + Node *escontext = fcinfo->context; + Oid result; + List *names; + + /* Handle "-" or numeric OID */ + if (parseDashOrOid(class_name_or_oid, &result, escontext)) + PG_RETURN_OID(result); + + /* Else it's a name, possibly schema-qualified */ + + /* The rest of this wouldn't work in bootstrap mode */ + if (IsBootstrapProcessingMode()) + elog(ERROR, "regclass values must be OIDs in bootstrap mode"); + + /* + * Normal case: parse the name into components and see if it matches any + * pg_class entries in the current search path. + */ + names = stringToQualifiedNameList(class_name_or_oid, escontext); + if (names == NIL) + PG_RETURN_NULL(); + + /* We might not even have permissions on this relation; don't lock it. */ + result = RangeVarGetRelid(makeRangeVarFromNameList(names), NoLock, true); + + if (!OidIsValid(result)) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("relation \"%s\" does not exist", + NameListToString(names)))); + + PG_RETURN_OID(result); +} + +/* + * to_regclass - converts "classname" to class OID + * + * If the name is not found, we return NULL. + */ +Datum +to_regclass(PG_FUNCTION_ARGS) +{ + char *class_name = text_to_cstring(PG_GETARG_TEXT_PP(0)); + Datum result; + ErrorSaveContext escontext = {T_ErrorSaveContext}; + + if (!DirectInputFunctionCallSafe(regclassin, class_name, + InvalidOid, -1, + (Node *) &escontext, + &result)) + PG_RETURN_NULL(); + PG_RETURN_DATUM(result); +} + +/* + * regclassout - converts class OID to "class_name" + */ +Datum +regclassout(PG_FUNCTION_ARGS) +{ + Oid classid = PG_GETARG_OID(0); + char *result; + HeapTuple classtup; + + if (classid == InvalidOid) + { + result = pstrdup("-"); + PG_RETURN_CSTRING(result); + } + + classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(classid)); + + if (HeapTupleIsValid(classtup)) + { + Form_pg_class classform = (Form_pg_class) GETSTRUCT(classtup); + char *classname = NameStr(classform->relname); + + /* + * In bootstrap mode, skip the fancy namespace stuff and just return + * the class name. (This path is only needed for debugging output + * anyway.) + */ + if (IsBootstrapProcessingMode()) + result = pstrdup(classname); + else + { + char *nspname; + + /* + * Would this class be found by regclassin? If not, qualify it. + */ + if (RelationIsVisible(classid)) + nspname = NULL; + else + nspname = get_namespace_name(classform->relnamespace); + + result = quote_qualified_identifier(nspname, classname); + } + + ReleaseSysCache(classtup); + } + else + { + /* If OID doesn't match any pg_class entry, return it numerically */ + result = (char *) palloc(NAMEDATALEN); + snprintf(result, NAMEDATALEN, "%u", classid); + } + + PG_RETURN_CSTRING(result); +} + +/* + * regclassrecv - converts external binary format to regclass + */ +Datum +regclassrecv(PG_FUNCTION_ARGS) +{ + /* Exactly the same as oidrecv, so share code */ + return oidrecv(fcinfo); +} + +/* + * regclasssend - converts regclass to binary format + */ +Datum +regclasssend(PG_FUNCTION_ARGS) +{ + /* Exactly the same as oidsend, so share code */ + return oidsend(fcinfo); +} + + +/* + * regcollationin - converts "collationname" to collation OID + * + * We also accept a numeric OID, for symmetry with the output routine. + * + * '-' signifies unknown (OID 0). In all other cases, the input must + * match an existing pg_collation entry. + */ +Datum +regcollationin(PG_FUNCTION_ARGS) +{ + char *collation_name_or_oid = PG_GETARG_CSTRING(0); + Node *escontext = fcinfo->context; + Oid result; + List *names; + + /* Handle "-" or numeric OID */ + if (parseDashOrOid(collation_name_or_oid, &result, escontext)) + PG_RETURN_OID(result); + + /* Else it's a name, possibly schema-qualified */ + + /* The rest of this wouldn't work in bootstrap mode */ + if (IsBootstrapProcessingMode()) + elog(ERROR, "regcollation values must be OIDs in bootstrap mode"); + + /* + * Normal case: parse the name into components and see if it matches any + * pg_collation entries in the current search path. + */ + names = stringToQualifiedNameList(collation_name_or_oid, escontext); + if (names == NIL) + PG_RETURN_NULL(); + + result = get_collation_oid(names, true); + + if (!OidIsValid(result)) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("collation \"%s\" for encoding \"%s\" does not exist", + NameListToString(names), GetDatabaseEncodingName()))); + + PG_RETURN_OID(result); +} + +/* + * to_regcollation - converts "collationname" to collation OID + * + * If the name is not found, we return NULL. + */ +Datum +to_regcollation(PG_FUNCTION_ARGS) +{ + char *collation_name = text_to_cstring(PG_GETARG_TEXT_PP(0)); + Datum result; + ErrorSaveContext escontext = {T_ErrorSaveContext}; + + if (!DirectInputFunctionCallSafe(regcollationin, collation_name, + InvalidOid, -1, + (Node *) &escontext, + &result)) + PG_RETURN_NULL(); + PG_RETURN_DATUM(result); +} + +/* + * regcollationout - converts collation OID to "collation_name" + */ +Datum +regcollationout(PG_FUNCTION_ARGS) +{ + Oid collationid = PG_GETARG_OID(0); + char *result; + HeapTuple collationtup; + + if (collationid == InvalidOid) + { + result = pstrdup("-"); + PG_RETURN_CSTRING(result); + } + + collationtup = SearchSysCache1(COLLOID, ObjectIdGetDatum(collationid)); + + if (HeapTupleIsValid(collationtup)) + { + Form_pg_collation collationform = (Form_pg_collation) GETSTRUCT(collationtup); + char *collationname = NameStr(collationform->collname); + + /* + * In bootstrap mode, skip the fancy namespace stuff and just return + * the collation name. (This path is only needed for debugging output + * anyway.) + */ + if (IsBootstrapProcessingMode()) + result = pstrdup(collationname); + else + { + char *nspname; + + /* + * Would this collation be found by regcollationin? If not, + * qualify it. + */ + if (CollationIsVisible(collationid)) + nspname = NULL; + else + nspname = get_namespace_name(collationform->collnamespace); + + result = quote_qualified_identifier(nspname, collationname); + } + + ReleaseSysCache(collationtup); + } + else + { + /* If OID doesn't match any pg_collation entry, return it numerically */ + result = (char *) palloc(NAMEDATALEN); + snprintf(result, NAMEDATALEN, "%u", collationid); + } + + PG_RETURN_CSTRING(result); +} + +/* + * regcollationrecv - converts external binary format to regcollation + */ +Datum +regcollationrecv(PG_FUNCTION_ARGS) +{ + /* Exactly the same as oidrecv, so share code */ + return oidrecv(fcinfo); +} + +/* + * regcollationsend - converts regcollation to binary format + */ +Datum +regcollationsend(PG_FUNCTION_ARGS) +{ + /* Exactly the same as oidsend, so share code */ + return oidsend(fcinfo); +} + + +/* + * regtypein - converts "typename" to type OID + * + * The type name can be specified using the full type syntax recognized by + * the parser; for example, DOUBLE PRECISION and INTEGER[] will work and be + * translated to the correct type names. (We ignore any typmod info + * generated by the parser, however.) + * + * We also accept a numeric OID, for symmetry with the output routine, + * and for possible use in bootstrap mode. + * + * '-' signifies unknown (OID 0). In all other cases, the input must + * match an existing pg_type entry. + */ +Datum +regtypein(PG_FUNCTION_ARGS) +{ + char *typ_name_or_oid = PG_GETARG_CSTRING(0); + Node *escontext = fcinfo->context; + Oid result; + int32 typmod; + + /* Handle "-" or numeric OID */ + if (parseDashOrOid(typ_name_or_oid, &result, escontext)) + PG_RETURN_OID(result); + + /* Else it's a type name, possibly schema-qualified or decorated */ + + /* The rest of this wouldn't work in bootstrap mode */ + if (IsBootstrapProcessingMode()) + elog(ERROR, "regtype values must be OIDs in bootstrap mode"); + + /* + * Normal case: invoke the full parser to deal with special cases such as + * array syntax. We don't need to check for parseTypeString failure, + * since we'll just return anyway. + */ + (void) parseTypeString(typ_name_or_oid, &result, &typmod, escontext); + + PG_RETURN_OID(result); +} + +/* + * to_regtype - converts "typename" to type OID + * + * If the name is not found, we return NULL. + */ +Datum +to_regtype(PG_FUNCTION_ARGS) +{ + char *typ_name = text_to_cstring(PG_GETARG_TEXT_PP(0)); + Datum result; + ErrorSaveContext escontext = {T_ErrorSaveContext}; + + if (!DirectInputFunctionCallSafe(regtypein, typ_name, + InvalidOid, -1, + (Node *) &escontext, + &result)) + PG_RETURN_NULL(); + PG_RETURN_DATUM(result); +} + +/* + * regtypeout - converts type OID to "typ_name" + */ +Datum +regtypeout(PG_FUNCTION_ARGS) +{ + Oid typid = PG_GETARG_OID(0); + char *result; + HeapTuple typetup; + + if (typid == InvalidOid) + { + result = pstrdup("-"); + PG_RETURN_CSTRING(result); + } + + typetup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid)); + + if (HeapTupleIsValid(typetup)) + { + Form_pg_type typeform = (Form_pg_type) GETSTRUCT(typetup); + + /* + * In bootstrap mode, skip the fancy namespace stuff and just return + * the type name. (This path is only needed for debugging output + * anyway.) + */ + if (IsBootstrapProcessingMode()) + { + char *typname = NameStr(typeform->typname); + + result = pstrdup(typname); + } + else + result = format_type_be(typid); + + ReleaseSysCache(typetup); + } + else + { + /* If OID doesn't match any pg_type entry, return it numerically */ + result = (char *) palloc(NAMEDATALEN); + snprintf(result, NAMEDATALEN, "%u", typid); + } + + PG_RETURN_CSTRING(result); +} + +/* + * regtyperecv - converts external binary format to regtype + */ +Datum +regtyperecv(PG_FUNCTION_ARGS) +{ + /* Exactly the same as oidrecv, so share code */ + return oidrecv(fcinfo); +} + +/* + * regtypesend - converts regtype to binary format + */ +Datum +regtypesend(PG_FUNCTION_ARGS) +{ + /* Exactly the same as oidsend, so share code */ + return oidsend(fcinfo); +} + + +/* + * regconfigin - converts "tsconfigname" to tsconfig OID + * + * We also accept a numeric OID, for symmetry with the output routine. + * + * '-' signifies unknown (OID 0). In all other cases, the input must + * match an existing pg_ts_config entry. + */ +Datum +regconfigin(PG_FUNCTION_ARGS) +{ + char *cfg_name_or_oid = PG_GETARG_CSTRING(0); + Node *escontext = fcinfo->context; + Oid result; + List *names; + + /* Handle "-" or numeric OID */ + if (parseDashOrOid(cfg_name_or_oid, &result, escontext)) + PG_RETURN_OID(result); + + /* The rest of this wouldn't work in bootstrap mode */ + if (IsBootstrapProcessingMode()) + elog(ERROR, "regconfig values must be OIDs in bootstrap mode"); + + /* + * Normal case: parse the name into components and see if it matches any + * pg_ts_config entries in the current search path. + */ + names = stringToQualifiedNameList(cfg_name_or_oid, escontext); + if (names == NIL) + PG_RETURN_NULL(); + + result = get_ts_config_oid(names, true); + + if (!OidIsValid(result)) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("text search configuration \"%s\" does not exist", + NameListToString(names)))); + + PG_RETURN_OID(result); +} + +/* + * regconfigout - converts tsconfig OID to "tsconfigname" + */ +Datum +regconfigout(PG_FUNCTION_ARGS) +{ + Oid cfgid = PG_GETARG_OID(0); + char *result; + HeapTuple cfgtup; + + if (cfgid == InvalidOid) + { + result = pstrdup("-"); + PG_RETURN_CSTRING(result); + } + + cfgtup = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(cfgid)); + + if (HeapTupleIsValid(cfgtup)) + { + Form_pg_ts_config cfgform = (Form_pg_ts_config) GETSTRUCT(cfgtup); + char *cfgname = NameStr(cfgform->cfgname); + char *nspname; + + /* + * Would this config be found by regconfigin? If not, qualify it. + */ + if (TSConfigIsVisible(cfgid)) + nspname = NULL; + else + nspname = get_namespace_name(cfgform->cfgnamespace); + + result = quote_qualified_identifier(nspname, cfgname); + + ReleaseSysCache(cfgtup); + } + else + { + /* If OID doesn't match any pg_ts_config row, return it numerically */ + result = (char *) palloc(NAMEDATALEN); + snprintf(result, NAMEDATALEN, "%u", cfgid); + } + + PG_RETURN_CSTRING(result); +} + +/* + * regconfigrecv - converts external binary format to regconfig + */ +Datum +regconfigrecv(PG_FUNCTION_ARGS) +{ + /* Exactly the same as oidrecv, so share code */ + return oidrecv(fcinfo); +} + +/* + * regconfigsend - converts regconfig to binary format + */ +Datum +regconfigsend(PG_FUNCTION_ARGS) +{ + /* Exactly the same as oidsend, so share code */ + return oidsend(fcinfo); +} + + +/* + * regdictionaryin - converts "tsdictionaryname" to tsdictionary OID + * + * We also accept a numeric OID, for symmetry with the output routine. + * + * '-' signifies unknown (OID 0). In all other cases, the input must + * match an existing pg_ts_dict entry. + */ +Datum +regdictionaryin(PG_FUNCTION_ARGS) +{ + char *dict_name_or_oid = PG_GETARG_CSTRING(0); + Node *escontext = fcinfo->context; + Oid result; + List *names; + + /* Handle "-" or numeric OID */ + if (parseDashOrOid(dict_name_or_oid, &result, escontext)) + PG_RETURN_OID(result); + + /* The rest of this wouldn't work in bootstrap mode */ + if (IsBootstrapProcessingMode()) + elog(ERROR, "regdictionary values must be OIDs in bootstrap mode"); + + /* + * Normal case: parse the name into components and see if it matches any + * pg_ts_dict entries in the current search path. + */ + names = stringToQualifiedNameList(dict_name_or_oid, escontext); + if (names == NIL) + PG_RETURN_NULL(); + + result = get_ts_dict_oid(names, true); + + if (!OidIsValid(result)) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("text search dictionary \"%s\" does not exist", + NameListToString(names)))); + + PG_RETURN_OID(result); +} + +/* + * regdictionaryout - converts tsdictionary OID to "tsdictionaryname" + */ +Datum +regdictionaryout(PG_FUNCTION_ARGS) +{ + Oid dictid = PG_GETARG_OID(0); + char *result; + HeapTuple dicttup; + + if (dictid == InvalidOid) + { + result = pstrdup("-"); + PG_RETURN_CSTRING(result); + } + + dicttup = SearchSysCache1(TSDICTOID, ObjectIdGetDatum(dictid)); + + if (HeapTupleIsValid(dicttup)) + { + Form_pg_ts_dict dictform = (Form_pg_ts_dict) GETSTRUCT(dicttup); + char *dictname = NameStr(dictform->dictname); + char *nspname; + + /* + * Would this dictionary be found by regdictionaryin? If not, qualify + * it. + */ + if (TSDictionaryIsVisible(dictid)) + nspname = NULL; + else + nspname = get_namespace_name(dictform->dictnamespace); + + result = quote_qualified_identifier(nspname, dictname); + + ReleaseSysCache(dicttup); + } + else + { + /* If OID doesn't match any pg_ts_dict row, return it numerically */ + result = (char *) palloc(NAMEDATALEN); + snprintf(result, NAMEDATALEN, "%u", dictid); + } + + PG_RETURN_CSTRING(result); +} + +/* + * regdictionaryrecv - converts external binary format to regdictionary + */ +Datum +regdictionaryrecv(PG_FUNCTION_ARGS) +{ + /* Exactly the same as oidrecv, so share code */ + return oidrecv(fcinfo); +} + +/* + * regdictionarysend - converts regdictionary to binary format + */ +Datum +regdictionarysend(PG_FUNCTION_ARGS) +{ + /* Exactly the same as oidsend, so share code */ + return oidsend(fcinfo); +} + +/* + * regrolein - converts "rolename" to role OID + * + * We also accept a numeric OID, for symmetry with the output routine. + * + * '-' signifies unknown (OID 0). In all other cases, the input must + * match an existing pg_authid entry. + */ +Datum +regrolein(PG_FUNCTION_ARGS) +{ + char *role_name_or_oid = PG_GETARG_CSTRING(0); + Node *escontext = fcinfo->context; + Oid result; + List *names; + + /* Handle "-" or numeric OID */ + if (parseDashOrOid(role_name_or_oid, &result, escontext)) + PG_RETURN_OID(result); + + /* The rest of this wouldn't work in bootstrap mode */ + if (IsBootstrapProcessingMode()) + elog(ERROR, "regrole values must be OIDs in bootstrap mode"); + + /* Normal case: see if the name matches any pg_authid entry. */ + names = stringToQualifiedNameList(role_name_or_oid, escontext); + if (names == NIL) + PG_RETURN_NULL(); + + if (list_length(names) != 1) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_NAME), + errmsg("invalid name syntax"))); + + result = get_role_oid(strVal(linitial(names)), true); + + if (!OidIsValid(result)) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("role \"%s\" does not exist", + strVal(linitial(names))))); + + PG_RETURN_OID(result); +} + +/* + * to_regrole - converts "rolename" to role OID + * + * If the name is not found, we return NULL. + */ +Datum +to_regrole(PG_FUNCTION_ARGS) +{ + char *role_name = text_to_cstring(PG_GETARG_TEXT_PP(0)); + Datum result; + ErrorSaveContext escontext = {T_ErrorSaveContext}; + + if (!DirectInputFunctionCallSafe(regrolein, role_name, + InvalidOid, -1, + (Node *) &escontext, + &result)) + PG_RETURN_NULL(); + PG_RETURN_DATUM(result); +} + +/* + * regroleout - converts role OID to "role_name" + */ +Datum +regroleout(PG_FUNCTION_ARGS) +{ + Oid roleoid = PG_GETARG_OID(0); + char *result; + + if (roleoid == InvalidOid) + { + result = pstrdup("-"); + PG_RETURN_CSTRING(result); + } + + result = GetUserNameFromId(roleoid, true); + + if (result) + { + /* pstrdup is not really necessary, but it avoids a compiler warning */ + result = pstrdup(quote_identifier(result)); + } + else + { + /* If OID doesn't match any role, return it numerically */ + result = (char *) palloc(NAMEDATALEN); + snprintf(result, NAMEDATALEN, "%u", roleoid); + } + + PG_RETURN_CSTRING(result); +} + +/* + * regrolerecv - converts external binary format to regrole + */ +Datum +regrolerecv(PG_FUNCTION_ARGS) +{ + /* Exactly the same as oidrecv, so share code */ + return oidrecv(fcinfo); +} + +/* + * regrolesend - converts regrole to binary format + */ +Datum +regrolesend(PG_FUNCTION_ARGS) +{ + /* Exactly the same as oidsend, so share code */ + return oidsend(fcinfo); +} + +/* + * regnamespacein - converts "nspname" to namespace OID + * + * We also accept a numeric OID, for symmetry with the output routine. + * + * '-' signifies unknown (OID 0). In all other cases, the input must + * match an existing pg_namespace entry. + */ +Datum +regnamespacein(PG_FUNCTION_ARGS) +{ + char *nsp_name_or_oid = PG_GETARG_CSTRING(0); + Node *escontext = fcinfo->context; + Oid result; + List *names; + + /* Handle "-" or numeric OID */ + if (parseDashOrOid(nsp_name_or_oid, &result, escontext)) + PG_RETURN_OID(result); + + /* The rest of this wouldn't work in bootstrap mode */ + if (IsBootstrapProcessingMode()) + elog(ERROR, "regnamespace values must be OIDs in bootstrap mode"); + + /* Normal case: see if the name matches any pg_namespace entry. */ + names = stringToQualifiedNameList(nsp_name_or_oid, escontext); + if (names == NIL) + PG_RETURN_NULL(); + + if (list_length(names) != 1) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_NAME), + errmsg("invalid name syntax"))); + + result = get_namespace_oid(strVal(linitial(names)), true); + + if (!OidIsValid(result)) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_UNDEFINED_SCHEMA), + errmsg("schema \"%s\" does not exist", + strVal(linitial(names))))); + + PG_RETURN_OID(result); +} + +/* + * to_regnamespace - converts "nspname" to namespace OID + * + * If the name is not found, we return NULL. + */ +Datum +to_regnamespace(PG_FUNCTION_ARGS) +{ + char *nsp_name = text_to_cstring(PG_GETARG_TEXT_PP(0)); + Datum result; + ErrorSaveContext escontext = {T_ErrorSaveContext}; + + if (!DirectInputFunctionCallSafe(regnamespacein, nsp_name, + InvalidOid, -1, + (Node *) &escontext, + &result)) + PG_RETURN_NULL(); + PG_RETURN_DATUM(result); +} + +/* + * regnamespaceout - converts namespace OID to "nsp_name" + */ +Datum +regnamespaceout(PG_FUNCTION_ARGS) +{ + Oid nspid = PG_GETARG_OID(0); + char *result; + + if (nspid == InvalidOid) + { + result = pstrdup("-"); + PG_RETURN_CSTRING(result); + } + + result = get_namespace_name(nspid); + + if (result) + { + /* pstrdup is not really necessary, but it avoids a compiler warning */ + result = pstrdup(quote_identifier(result)); + } + else + { + /* If OID doesn't match any namespace, return it numerically */ + result = (char *) palloc(NAMEDATALEN); + snprintf(result, NAMEDATALEN, "%u", nspid); + } + + PG_RETURN_CSTRING(result); +} + +/* + * regnamespacerecv - converts external binary format to regnamespace + */ +Datum +regnamespacerecv(PG_FUNCTION_ARGS) +{ + /* Exactly the same as oidrecv, so share code */ + return oidrecv(fcinfo); +} + +/* + * regnamespacesend - converts regnamespace to binary format + */ +Datum +regnamespacesend(PG_FUNCTION_ARGS) +{ + /* Exactly the same as oidsend, so share code */ + return oidsend(fcinfo); +} + +/* + * text_regclass: convert text to regclass + * + * This could be replaced by CoerceViaIO, except that we need to treat + * text-to-regclass as an implicit cast to support legacy forms of nextval() + * and related functions. + */ +Datum +text_regclass(PG_FUNCTION_ARGS) +{ + text *relname = PG_GETARG_TEXT_PP(0); + Oid result; + RangeVar *rv; + + rv = makeRangeVarFromNameList(textToQualifiedNameList(relname)); + + /* We might not even have permissions on this relation; don't lock it. */ + result = RangeVarGetRelid(rv, NoLock, false); + + PG_RETURN_OID(result); +} + + +/* + * Given a C string, parse it into a qualified-name list. + * + * If escontext is an ErrorSaveContext node, invalid input will be + * reported there instead of being thrown, and we return NIL. + * (NIL is not possible as a success return, since empty-input is an error.) + */ +List * +stringToQualifiedNameList(const char *string, Node *escontext) +{ + char *rawname; + List *result = NIL; + List *namelist; + ListCell *l; + + /* We need a modifiable copy of the input string. */ + rawname = pstrdup(string); + + if (!SplitIdentifierString(rawname, '.', &namelist)) + ereturn(escontext, NIL, + (errcode(ERRCODE_INVALID_NAME), + errmsg("invalid name syntax"))); + + if (namelist == NIL) + ereturn(escontext, NIL, + (errcode(ERRCODE_INVALID_NAME), + errmsg("invalid name syntax"))); + + foreach(l, namelist) + { + char *curname = (char *) lfirst(l); + + result = lappend(result, makeString(pstrdup(curname))); + } + + pfree(rawname); + list_free(namelist); + + return result; +} + +/***************************************************************************** + * SUPPORT ROUTINES * + *****************************************************************************/ + +/* + * Given a C string, see if it is all-digits (and not empty). + * If so, convert directly to OID and return true. + * If it is not all-digits, return false. + * + * If escontext is an ErrorSaveContext node, any error in oidin() will be + * reported there instead of being thrown (but we still return true). + */ +static bool +parseNumericOid(char *string, Oid *result, Node *escontext) +{ + if (string[0] >= '0' && string[0] <= '9' && + strspn(string, "0123456789") == strlen(string)) + { + Datum oid_datum; + + /* We need not care here whether oidin() fails or not. */ + (void) DirectInputFunctionCallSafe(oidin, string, + InvalidOid, -1, + escontext, + &oid_datum); + *result = DatumGetObjectId(oid_datum); + return true; + } + + /* Prevent uninitialized-variable warnings from stupider compilers. */ + *result = InvalidOid; + return false; +} + +/* + * As above, but also accept "-" as meaning 0 (InvalidOid). + */ +static bool +parseDashOrOid(char *string, Oid *result, Node *escontext) +{ + /* '-' ? */ + if (strcmp(string, "-") == 0) + { + *result = InvalidOid; + return true; + } + + /* Numeric OID? */ + return parseNumericOid(string, result, escontext); +} + +/* + * Given a C string, parse it into a qualified function or operator name + * followed by a parenthesized list of type names. Reduce the + * type names to an array of OIDs (returned into *nargs and *argtypes; + * the argtypes array should be of size FUNC_MAX_ARGS). The function or + * operator name is returned to *names as a List of Strings. + * + * If allowNone is true, accept "NONE" and return it as InvalidOid (this is + * for unary operators). + * + * Returns true on success, false on failure (the latter only possible + * if escontext is an ErrorSaveContext node). + */ +static bool +parseNameAndArgTypes(const char *string, bool allowNone, List **names, + int *nargs, Oid *argtypes, + Node *escontext) +{ + char *rawname; + char *ptr; + char *ptr2; + char *typename; + bool in_quote; + bool had_comma; + int paren_count; + Oid typeid; + int32 typmod; + + /* We need a modifiable copy of the input string. */ + rawname = pstrdup(string); + + /* Scan to find the expected left paren; mustn't be quoted */ + in_quote = false; + for (ptr = rawname; *ptr; ptr++) + { + if (*ptr == '"') + in_quote = !in_quote; + else if (*ptr == '(' && !in_quote) + break; + } + if (*ptr == '\0') + ereturn(escontext, false, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("expected a left parenthesis"))); + + /* Separate the name and parse it into a list */ + *ptr++ = '\0'; + *names = stringToQualifiedNameList(rawname, escontext); + if (*names == NIL) + return false; + + /* Check for the trailing right parenthesis and remove it */ + ptr2 = ptr + strlen(ptr); + while (--ptr2 > ptr) + { + if (!scanner_isspace(*ptr2)) + break; + } + if (*ptr2 != ')') + ereturn(escontext, false, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("expected a right parenthesis"))); + + *ptr2 = '\0'; + + /* Separate the remaining string into comma-separated type names */ + *nargs = 0; + had_comma = false; + + for (;;) + { + /* allow leading whitespace */ + while (scanner_isspace(*ptr)) + ptr++; + if (*ptr == '\0') + { + /* End of string. Okay unless we had a comma before. */ + if (had_comma) + ereturn(escontext, false, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("expected a type name"))); + break; + } + typename = ptr; + /* Find end of type name --- end of string or comma */ + /* ... but not a quoted or parenthesized comma */ + in_quote = false; + paren_count = 0; + for (; *ptr; ptr++) + { + if (*ptr == '"') + in_quote = !in_quote; + else if (*ptr == ',' && !in_quote && paren_count == 0) + break; + else if (!in_quote) + { + switch (*ptr) + { + case '(': + case '[': + paren_count++; + break; + case ')': + case ']': + paren_count--; + break; + } + } + } + if (in_quote || paren_count != 0) + ereturn(escontext, false, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("improper type name"))); + + ptr2 = ptr; + if (*ptr == ',') + { + had_comma = true; + *ptr++ = '\0'; + } + else + { + had_comma = false; + Assert(*ptr == '\0'); + } + /* Lop off trailing whitespace */ + while (--ptr2 >= typename) + { + if (!scanner_isspace(*ptr2)) + break; + *ptr2 = '\0'; + } + + if (allowNone && pg_strcasecmp(typename, "none") == 0) + { + /* Special case for NONE */ + typeid = InvalidOid; + typmod = -1; + } + else + { + /* Use full parser to resolve the type name */ + if (!parseTypeString(typename, &typeid, &typmod, escontext)) + return false; + } + if (*nargs >= FUNC_MAX_ARGS) + ereturn(escontext, false, + (errcode(ERRCODE_TOO_MANY_ARGUMENTS), + errmsg("too many arguments"))); + + argtypes[*nargs] = typeid; + (*nargs)++; + } + + pfree(rawname); + + return true; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/ri_triggers.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/ri_triggers.c new file mode 100644 index 00000000000..d93b9c18611 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/ri_triggers.c @@ -0,0 +1,3032 @@ +/*------------------------------------------------------------------------- + * + * ri_triggers.c + * + * Generic trigger procedures for referential integrity constraint + * checks. + * + * Note about memory management: the private hashtables kept here live + * across query and transaction boundaries, in fact they live as long as + * the backend does. This works because the hashtable structures + * themselves are allocated by dynahash.c in its permanent DynaHashCxt, + * and the SPI plans they point to are saved using SPI_keepplan(). + * There is not currently any provision for throwing away a no-longer-needed + * plan --- consider improving this someday. + * + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * + * src/backend/utils/adt/ri_triggers.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/htup_details.h" +#include "access/sysattr.h" +#include "access/table.h" +#include "access/tableam.h" +#include "access/xact.h" +#include "catalog/pg_collation.h" +#include "catalog/pg_constraint.h" +#include "catalog/pg_operator.h" +#include "catalog/pg_type.h" +#include "commands/trigger.h" +#include "executor/executor.h" +#include "executor/spi.h" +#include "lib/ilist.h" +#include "miscadmin.h" +#include "parser/parse_coerce.h" +#include "parser/parse_relation.h" +#include "storage/bufmgr.h" +#include "utils/acl.h" +#include "utils/builtins.h" +#include "utils/datum.h" +#include "utils/fmgroids.h" +#include "utils/guc.h" +#include "utils/inval.h" +#include "utils/lsyscache.h" +#include "utils/memutils.h" +#include "utils/rel.h" +#include "utils/rls.h" +#include "utils/ruleutils.h" +#include "utils/snapmgr.h" +#include "utils/syscache.h" + +/* + * Local definitions + */ + +#define RI_MAX_NUMKEYS INDEX_MAX_KEYS + +#define RI_INIT_CONSTRAINTHASHSIZE 64 +#define RI_INIT_QUERYHASHSIZE (RI_INIT_CONSTRAINTHASHSIZE * 4) + +#define RI_KEYS_ALL_NULL 0 +#define RI_KEYS_SOME_NULL 1 +#define RI_KEYS_NONE_NULL 2 + +/* RI query type codes */ +/* these queries are executed against the PK (referenced) table: */ +#define RI_PLAN_CHECK_LOOKUPPK 1 +#define RI_PLAN_CHECK_LOOKUPPK_FROM_PK 2 +#define RI_PLAN_LAST_ON_PK RI_PLAN_CHECK_LOOKUPPK_FROM_PK +/* these queries are executed against the FK (referencing) table: */ +#define RI_PLAN_CASCADE_ONDELETE 3 +#define RI_PLAN_CASCADE_ONUPDATE 4 +/* For RESTRICT, the same plan can be used for both ON DELETE and ON UPDATE triggers. */ +#define RI_PLAN_RESTRICT 5 +#define RI_PLAN_SETNULL_ONDELETE 6 +#define RI_PLAN_SETNULL_ONUPDATE 7 +#define RI_PLAN_SETDEFAULT_ONDELETE 8 +#define RI_PLAN_SETDEFAULT_ONUPDATE 9 + +#define MAX_QUOTED_NAME_LEN (NAMEDATALEN*2+3) +#define MAX_QUOTED_REL_NAME_LEN (MAX_QUOTED_NAME_LEN*2) + +#define RIAttName(rel, attnum) NameStr(*attnumAttName(rel, attnum)) +#define RIAttType(rel, attnum) attnumTypeId(rel, attnum) +#define RIAttCollation(rel, attnum) attnumCollationId(rel, attnum) + +#define RI_TRIGTYPE_INSERT 1 +#define RI_TRIGTYPE_UPDATE 2 +#define RI_TRIGTYPE_DELETE 3 + + +/* + * RI_ConstraintInfo + * + * Information extracted from an FK pg_constraint entry. This is cached in + * ri_constraint_cache. + */ +typedef struct RI_ConstraintInfo +{ + Oid constraint_id; /* OID of pg_constraint entry (hash key) */ + bool valid; /* successfully initialized? */ + Oid constraint_root_id; /* OID of topmost ancestor constraint; + * same as constraint_id if not inherited */ + uint32 oidHashValue; /* hash value of constraint_id */ + uint32 rootHashValue; /* hash value of constraint_root_id */ + NameData conname; /* name of the FK constraint */ + Oid pk_relid; /* referenced relation */ + Oid fk_relid; /* referencing relation */ + char confupdtype; /* foreign key's ON UPDATE action */ + char confdeltype; /* foreign key's ON DELETE action */ + int ndelsetcols; /* number of columns referenced in ON DELETE + * SET clause */ + int16 confdelsetcols[RI_MAX_NUMKEYS]; /* attnums of cols to set on + * delete */ + char confmatchtype; /* foreign key's match type */ + int nkeys; /* number of key columns */ + int16 pk_attnums[RI_MAX_NUMKEYS]; /* attnums of referenced cols */ + int16 fk_attnums[RI_MAX_NUMKEYS]; /* attnums of referencing cols */ + Oid pf_eq_oprs[RI_MAX_NUMKEYS]; /* equality operators (PK = FK) */ + Oid pp_eq_oprs[RI_MAX_NUMKEYS]; /* equality operators (PK = PK) */ + Oid ff_eq_oprs[RI_MAX_NUMKEYS]; /* equality operators (FK = FK) */ + dlist_node valid_link; /* Link in list of valid entries */ +} RI_ConstraintInfo; + +/* + * RI_QueryKey + * + * The key identifying a prepared SPI plan in our query hashtable + */ +typedef struct RI_QueryKey +{ + Oid constr_id; /* OID of pg_constraint entry */ + int32 constr_queryno; /* query type ID, see RI_PLAN_XXX above */ +} RI_QueryKey; + +/* + * RI_QueryHashEntry + */ +typedef struct RI_QueryHashEntry +{ + RI_QueryKey key; + SPIPlanPtr plan; +} RI_QueryHashEntry; + +/* + * RI_CompareKey + * + * The key identifying an entry showing how to compare two values + */ +typedef struct RI_CompareKey +{ + Oid eq_opr; /* the equality operator to apply */ + Oid typeid; /* the data type to apply it to */ +} RI_CompareKey; + +/* + * RI_CompareHashEntry + */ +typedef struct RI_CompareHashEntry +{ + RI_CompareKey key; + bool valid; /* successfully initialized? */ + FmgrInfo eq_opr_finfo; /* call info for equality fn */ + FmgrInfo cast_func_finfo; /* in case we must coerce input */ +} RI_CompareHashEntry; + + +/* + * Local data + */ +static __thread HTAB *ri_constraint_cache = NULL; +static __thread HTAB *ri_query_cache = NULL; +static __thread HTAB *ri_compare_cache = NULL; +static __thread dclist_head ri_constraint_cache_valid_list; + + +/* + * Local function prototypes + */ +static bool ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel, + TupleTableSlot *oldslot, + const RI_ConstraintInfo *riinfo); +static Datum ri_restrict(TriggerData *trigdata, bool is_no_action); +static Datum ri_set(TriggerData *trigdata, bool is_set_null, int tgkind); +static void quoteOneName(char *buffer, const char *name); +static void quoteRelationName(char *buffer, Relation rel); +static void ri_GenerateQual(StringInfo buf, + const char *sep, + const char *leftop, Oid leftoptype, + Oid opoid, + const char *rightop, Oid rightoptype); +static void ri_GenerateQualCollation(StringInfo buf, Oid collation); +static int ri_NullCheck(TupleDesc tupDesc, TupleTableSlot *slot, + const RI_ConstraintInfo *riinfo, bool rel_is_pk); +static void ri_BuildQueryKey(RI_QueryKey *key, + const RI_ConstraintInfo *riinfo, + int32 constr_queryno); +static bool ri_KeysEqual(Relation rel, TupleTableSlot *oldslot, TupleTableSlot *newslot, + const RI_ConstraintInfo *riinfo, bool rel_is_pk); +static bool ri_AttributesEqual(Oid eq_opr, Oid typeid, + Datum oldvalue, Datum newvalue); + +static void ri_InitHashTables(void); +static void InvalidateConstraintCacheCallBack(Datum arg, int cacheid, uint32 hashvalue); +static SPIPlanPtr ri_FetchPreparedPlan(RI_QueryKey *key); +static void ri_HashPreparedPlan(RI_QueryKey *key, SPIPlanPtr plan); +static RI_CompareHashEntry *ri_HashCompareOp(Oid eq_opr, Oid typeid); + +static void ri_CheckTrigger(FunctionCallInfo fcinfo, const char *funcname, + int tgkind); +static const RI_ConstraintInfo *ri_FetchConstraintInfo(Trigger *trigger, + Relation trig_rel, bool rel_is_pk); +static const RI_ConstraintInfo *ri_LoadConstraintInfo(Oid constraintOid); +static Oid get_ri_constraint_root(Oid constrOid); +static SPIPlanPtr ri_PlanCheck(const char *querystr, int nargs, Oid *argtypes, + RI_QueryKey *qkey, Relation fk_rel, Relation pk_rel); +static bool ri_PerformCheck(const RI_ConstraintInfo *riinfo, + RI_QueryKey *qkey, SPIPlanPtr qplan, + Relation fk_rel, Relation pk_rel, + TupleTableSlot *oldslot, TupleTableSlot *newslot, + bool detectNewRows, int expect_OK); +static void ri_ExtractValues(Relation rel, TupleTableSlot *slot, + const RI_ConstraintInfo *riinfo, bool rel_is_pk, + Datum *vals, char *nulls); +static void ri_ReportViolation(const RI_ConstraintInfo *riinfo, + Relation pk_rel, Relation fk_rel, + TupleTableSlot *violatorslot, TupleDesc tupdesc, + int queryno, bool partgone) pg_attribute_noreturn(); + + +/* + * RI_FKey_check - + * + * Check foreign key existence (combined for INSERT and UPDATE). + */ +static Datum +RI_FKey_check(TriggerData *trigdata) +{ + const RI_ConstraintInfo *riinfo; + Relation fk_rel; + Relation pk_rel; + TupleTableSlot *newslot; + RI_QueryKey qkey; + SPIPlanPtr qplan; + + riinfo = ri_FetchConstraintInfo(trigdata->tg_trigger, + trigdata->tg_relation, false); + + if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event)) + newslot = trigdata->tg_newslot; + else + newslot = trigdata->tg_trigslot; + + /* + * We should not even consider checking the row if it is no longer valid, + * since it was either deleted (so the deferred check should be skipped) + * or updated (in which case only the latest version of the row should be + * checked). Test its liveness according to SnapshotSelf. We need pin + * and lock on the buffer to call HeapTupleSatisfiesVisibility. Caller + * should be holding pin, but not lock. + */ + if (!table_tuple_satisfies_snapshot(trigdata->tg_relation, newslot, SnapshotSelf)) + return PointerGetDatum(NULL); + + /* + * Get the relation descriptors of the FK and PK tables. + * + * pk_rel is opened in RowShareLock mode since that's what our eventual + * SELECT FOR KEY SHARE will get on it. + */ + fk_rel = trigdata->tg_relation; + pk_rel = table_open(riinfo->pk_relid, RowShareLock); + + switch (ri_NullCheck(RelationGetDescr(fk_rel), newslot, riinfo, false)) + { + case RI_KEYS_ALL_NULL: + + /* + * No further check needed - an all-NULL key passes every type of + * foreign key constraint. + */ + table_close(pk_rel, RowShareLock); + return PointerGetDatum(NULL); + + case RI_KEYS_SOME_NULL: + + /* + * This is the only case that differs between the three kinds of + * MATCH. + */ + switch (riinfo->confmatchtype) + { + case FKCONSTR_MATCH_FULL: + + /* + * Not allowed - MATCH FULL says either all or none of the + * attributes can be NULLs + */ + ereport(ERROR, + (errcode(ERRCODE_FOREIGN_KEY_VIOLATION), + errmsg("insert or update on table \"%s\" violates foreign key constraint \"%s\"", + RelationGetRelationName(fk_rel), + NameStr(riinfo->conname)), + errdetail("MATCH FULL does not allow mixing of null and nonnull key values."), + errtableconstraint(fk_rel, + NameStr(riinfo->conname)))); + table_close(pk_rel, RowShareLock); + return PointerGetDatum(NULL); + + case FKCONSTR_MATCH_SIMPLE: + + /* + * MATCH SIMPLE - if ANY column is null, the key passes + * the constraint. + */ + table_close(pk_rel, RowShareLock); + return PointerGetDatum(NULL); + +#ifdef NOT_USED + case FKCONSTR_MATCH_PARTIAL: + + /* + * MATCH PARTIAL - all non-null columns must match. (not + * implemented, can be done by modifying the query below + * to only include non-null columns, or by writing a + * special version here) + */ + break; +#endif + } + + case RI_KEYS_NONE_NULL: + + /* + * Have a full qualified key - continue below for all three kinds + * of MATCH. + */ + break; + } + + if (SPI_connect() != SPI_OK_CONNECT) + elog(ERROR, "SPI_connect failed"); + + /* Fetch or prepare a saved plan for the real check */ + ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_CHECK_LOOKUPPK); + + if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL) + { + StringInfoData querybuf; + char pkrelname[MAX_QUOTED_REL_NAME_LEN]; + char attname[MAX_QUOTED_NAME_LEN]; + char paramname[16]; + const char *querysep; + Oid queryoids[RI_MAX_NUMKEYS]; + const char *pk_only; + + /* ---------- + * The query string built is + * SELECT 1 FROM [ONLY] <pktable> x WHERE pkatt1 = $1 [AND ...] + * FOR KEY SHARE OF x + * The type id's for the $ parameters are those of the + * corresponding FK attributes. + * ---------- + */ + initStringInfo(&querybuf); + pk_only = pk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ? + "" : "ONLY "; + quoteRelationName(pkrelname, pk_rel); + appendStringInfo(&querybuf, "SELECT 1 FROM %s%s x", + pk_only, pkrelname); + querysep = "WHERE"; + for (int i = 0; i < riinfo->nkeys; i++) + { + Oid pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]); + Oid fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]); + + quoteOneName(attname, + RIAttName(pk_rel, riinfo->pk_attnums[i])); + sprintf(paramname, "$%d", i + 1); + ri_GenerateQual(&querybuf, querysep, + attname, pk_type, + riinfo->pf_eq_oprs[i], + paramname, fk_type); + querysep = "AND"; + queryoids[i] = fk_type; + } + appendStringInfoString(&querybuf, " FOR KEY SHARE OF x"); + + /* Prepare and save the plan */ + qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids, + &qkey, fk_rel, pk_rel); + } + + /* + * Now check that foreign key exists in PK table + * + * XXX detectNewRows must be true when a partitioned table is on the + * referenced side. The reason is that our snapshot must be fresh in + * order for the hack in find_inheritance_children() to work. + */ + ri_PerformCheck(riinfo, &qkey, qplan, + fk_rel, pk_rel, + NULL, newslot, + pk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE, + SPI_OK_SELECT); + + if (SPI_finish() != SPI_OK_FINISH) + elog(ERROR, "SPI_finish failed"); + + table_close(pk_rel, RowShareLock); + + return PointerGetDatum(NULL); +} + + +/* + * RI_FKey_check_ins - + * + * Check foreign key existence at insert event on FK table. + */ +Datum +RI_FKey_check_ins(PG_FUNCTION_ARGS) +{ + /* Check that this is a valid trigger call on the right time and event. */ + ri_CheckTrigger(fcinfo, "RI_FKey_check_ins", RI_TRIGTYPE_INSERT); + + /* Share code with UPDATE case. */ + return RI_FKey_check((TriggerData *) fcinfo->context); +} + + +/* + * RI_FKey_check_upd - + * + * Check foreign key existence at update event on FK table. + */ +Datum +RI_FKey_check_upd(PG_FUNCTION_ARGS) +{ + /* Check that this is a valid trigger call on the right time and event. */ + ri_CheckTrigger(fcinfo, "RI_FKey_check_upd", RI_TRIGTYPE_UPDATE); + + /* Share code with INSERT case. */ + return RI_FKey_check((TriggerData *) fcinfo->context); +} + + +/* + * ri_Check_Pk_Match + * + * Check to see if another PK row has been created that provides the same + * key values as the "oldslot" that's been modified or deleted in our trigger + * event. Returns true if a match is found in the PK table. + * + * We assume the caller checked that the oldslot contains no NULL key values, + * since otherwise a match is impossible. + */ +static bool +ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel, + TupleTableSlot *oldslot, + const RI_ConstraintInfo *riinfo) +{ + SPIPlanPtr qplan; + RI_QueryKey qkey; + bool result; + + /* Only called for non-null rows */ + Assert(ri_NullCheck(RelationGetDescr(pk_rel), oldslot, riinfo, true) == RI_KEYS_NONE_NULL); + + if (SPI_connect() != SPI_OK_CONNECT) + elog(ERROR, "SPI_connect failed"); + + /* + * Fetch or prepare a saved plan for checking PK table with values coming + * from a PK row + */ + ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_CHECK_LOOKUPPK_FROM_PK); + + if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL) + { + StringInfoData querybuf; + char pkrelname[MAX_QUOTED_REL_NAME_LEN]; + char attname[MAX_QUOTED_NAME_LEN]; + char paramname[16]; + const char *querysep; + const char *pk_only; + Oid queryoids[RI_MAX_NUMKEYS]; + + /* ---------- + * The query string built is + * SELECT 1 FROM [ONLY] <pktable> x WHERE pkatt1 = $1 [AND ...] + * FOR KEY SHARE OF x + * The type id's for the $ parameters are those of the + * PK attributes themselves. + * ---------- + */ + initStringInfo(&querybuf); + pk_only = pk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ? + "" : "ONLY "; + quoteRelationName(pkrelname, pk_rel); + appendStringInfo(&querybuf, "SELECT 1 FROM %s%s x", + pk_only, pkrelname); + querysep = "WHERE"; + for (int i = 0; i < riinfo->nkeys; i++) + { + Oid pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]); + + quoteOneName(attname, + RIAttName(pk_rel, riinfo->pk_attnums[i])); + sprintf(paramname, "$%d", i + 1); + ri_GenerateQual(&querybuf, querysep, + attname, pk_type, + riinfo->pp_eq_oprs[i], + paramname, pk_type); + querysep = "AND"; + queryoids[i] = pk_type; + } + appendStringInfoString(&querybuf, " FOR KEY SHARE OF x"); + + /* Prepare and save the plan */ + qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids, + &qkey, fk_rel, pk_rel); + } + + /* + * We have a plan now. Run it. + */ + result = ri_PerformCheck(riinfo, &qkey, qplan, + fk_rel, pk_rel, + oldslot, NULL, + true, /* treat like update */ + SPI_OK_SELECT); + + if (SPI_finish() != SPI_OK_FINISH) + elog(ERROR, "SPI_finish failed"); + + return result; +} + + +/* + * RI_FKey_noaction_del - + * + * Give an error and roll back the current transaction if the + * delete has resulted in a violation of the given referential + * integrity constraint. + */ +Datum +RI_FKey_noaction_del(PG_FUNCTION_ARGS) +{ + /* Check that this is a valid trigger call on the right time and event. */ + ri_CheckTrigger(fcinfo, "RI_FKey_noaction_del", RI_TRIGTYPE_DELETE); + + /* Share code with RESTRICT/UPDATE cases. */ + return ri_restrict((TriggerData *) fcinfo->context, true); +} + +/* + * RI_FKey_restrict_del - + * + * Restrict delete from PK table to rows unreferenced by foreign key. + * + * The SQL standard intends that this referential action occur exactly when + * the delete is performed, rather than after. This appears to be + * the only difference between "NO ACTION" and "RESTRICT". In Postgres + * we still implement this as an AFTER trigger, but it's non-deferrable. + */ +Datum +RI_FKey_restrict_del(PG_FUNCTION_ARGS) +{ + /* Check that this is a valid trigger call on the right time and event. */ + ri_CheckTrigger(fcinfo, "RI_FKey_restrict_del", RI_TRIGTYPE_DELETE); + + /* Share code with NO ACTION/UPDATE cases. */ + return ri_restrict((TriggerData *) fcinfo->context, false); +} + +/* + * RI_FKey_noaction_upd - + * + * Give an error and roll back the current transaction if the + * update has resulted in a violation of the given referential + * integrity constraint. + */ +Datum +RI_FKey_noaction_upd(PG_FUNCTION_ARGS) +{ + /* Check that this is a valid trigger call on the right time and event. */ + ri_CheckTrigger(fcinfo, "RI_FKey_noaction_upd", RI_TRIGTYPE_UPDATE); + + /* Share code with RESTRICT/DELETE cases. */ + return ri_restrict((TriggerData *) fcinfo->context, true); +} + +/* + * RI_FKey_restrict_upd - + * + * Restrict update of PK to rows unreferenced by foreign key. + * + * The SQL standard intends that this referential action occur exactly when + * the update is performed, rather than after. This appears to be + * the only difference between "NO ACTION" and "RESTRICT". In Postgres + * we still implement this as an AFTER trigger, but it's non-deferrable. + */ +Datum +RI_FKey_restrict_upd(PG_FUNCTION_ARGS) +{ + /* Check that this is a valid trigger call on the right time and event. */ + ri_CheckTrigger(fcinfo, "RI_FKey_restrict_upd", RI_TRIGTYPE_UPDATE); + + /* Share code with NO ACTION/DELETE cases. */ + return ri_restrict((TriggerData *) fcinfo->context, false); +} + +/* + * ri_restrict - + * + * Common code for ON DELETE RESTRICT, ON DELETE NO ACTION, + * ON UPDATE RESTRICT, and ON UPDATE NO ACTION. + */ +static Datum +ri_restrict(TriggerData *trigdata, bool is_no_action) +{ + const RI_ConstraintInfo *riinfo; + Relation fk_rel; + Relation pk_rel; + TupleTableSlot *oldslot; + RI_QueryKey qkey; + SPIPlanPtr qplan; + + riinfo = ri_FetchConstraintInfo(trigdata->tg_trigger, + trigdata->tg_relation, true); + + /* + * Get the relation descriptors of the FK and PK tables and the old tuple. + * + * fk_rel is opened in RowShareLock mode since that's what our eventual + * SELECT FOR KEY SHARE will get on it. + */ + fk_rel = table_open(riinfo->fk_relid, RowShareLock); + pk_rel = trigdata->tg_relation; + oldslot = trigdata->tg_trigslot; + + /* + * If another PK row now exists providing the old key values, we should + * not do anything. However, this check should only be made in the NO + * ACTION case; in RESTRICT cases we don't wish to allow another row to be + * substituted. + */ + if (is_no_action && + ri_Check_Pk_Match(pk_rel, fk_rel, oldslot, riinfo)) + { + table_close(fk_rel, RowShareLock); + return PointerGetDatum(NULL); + } + + if (SPI_connect() != SPI_OK_CONNECT) + elog(ERROR, "SPI_connect failed"); + + /* + * Fetch or prepare a saved plan for the restrict lookup (it's the same + * query for delete and update cases) + */ + ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_RESTRICT); + + if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL) + { + StringInfoData querybuf; + char fkrelname[MAX_QUOTED_REL_NAME_LEN]; + char attname[MAX_QUOTED_NAME_LEN]; + char paramname[16]; + const char *querysep; + Oid queryoids[RI_MAX_NUMKEYS]; + const char *fk_only; + + /* ---------- + * The query string built is + * SELECT 1 FROM [ONLY] <fktable> x WHERE $1 = fkatt1 [AND ...] + * FOR KEY SHARE OF x + * The type id's for the $ parameters are those of the + * corresponding PK attributes. + * ---------- + */ + initStringInfo(&querybuf); + fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ? + "" : "ONLY "; + quoteRelationName(fkrelname, fk_rel); + appendStringInfo(&querybuf, "SELECT 1 FROM %s%s x", + fk_only, fkrelname); + querysep = "WHERE"; + for (int i = 0; i < riinfo->nkeys; i++) + { + Oid pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]); + Oid fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]); + Oid pk_coll = RIAttCollation(pk_rel, riinfo->pk_attnums[i]); + Oid fk_coll = RIAttCollation(fk_rel, riinfo->fk_attnums[i]); + + quoteOneName(attname, + RIAttName(fk_rel, riinfo->fk_attnums[i])); + sprintf(paramname, "$%d", i + 1); + ri_GenerateQual(&querybuf, querysep, + paramname, pk_type, + riinfo->pf_eq_oprs[i], + attname, fk_type); + if (pk_coll != fk_coll && !get_collation_isdeterministic(pk_coll)) + ri_GenerateQualCollation(&querybuf, pk_coll); + querysep = "AND"; + queryoids[i] = pk_type; + } + appendStringInfoString(&querybuf, " FOR KEY SHARE OF x"); + + /* Prepare and save the plan */ + qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids, + &qkey, fk_rel, pk_rel); + } + + /* + * We have a plan now. Run it to check for existing references. + */ + ri_PerformCheck(riinfo, &qkey, qplan, + fk_rel, pk_rel, + oldslot, NULL, + true, /* must detect new rows */ + SPI_OK_SELECT); + + if (SPI_finish() != SPI_OK_FINISH) + elog(ERROR, "SPI_finish failed"); + + table_close(fk_rel, RowShareLock); + + return PointerGetDatum(NULL); +} + + +/* + * RI_FKey_cascade_del - + * + * Cascaded delete foreign key references at delete event on PK table. + */ +Datum +RI_FKey_cascade_del(PG_FUNCTION_ARGS) +{ + TriggerData *trigdata = (TriggerData *) fcinfo->context; + const RI_ConstraintInfo *riinfo; + Relation fk_rel; + Relation pk_rel; + TupleTableSlot *oldslot; + RI_QueryKey qkey; + SPIPlanPtr qplan; + + /* Check that this is a valid trigger call on the right time and event. */ + ri_CheckTrigger(fcinfo, "RI_FKey_cascade_del", RI_TRIGTYPE_DELETE); + + riinfo = ri_FetchConstraintInfo(trigdata->tg_trigger, + trigdata->tg_relation, true); + + /* + * Get the relation descriptors of the FK and PK tables and the old tuple. + * + * fk_rel is opened in RowExclusiveLock mode since that's what our + * eventual DELETE will get on it. + */ + fk_rel = table_open(riinfo->fk_relid, RowExclusiveLock); + pk_rel = trigdata->tg_relation; + oldslot = trigdata->tg_trigslot; + + if (SPI_connect() != SPI_OK_CONNECT) + elog(ERROR, "SPI_connect failed"); + + /* Fetch or prepare a saved plan for the cascaded delete */ + ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_CASCADE_ONDELETE); + + if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL) + { + StringInfoData querybuf; + char fkrelname[MAX_QUOTED_REL_NAME_LEN]; + char attname[MAX_QUOTED_NAME_LEN]; + char paramname[16]; + const char *querysep; + Oid queryoids[RI_MAX_NUMKEYS]; + const char *fk_only; + + /* ---------- + * The query string built is + * DELETE FROM [ONLY] <fktable> WHERE $1 = fkatt1 [AND ...] + * The type id's for the $ parameters are those of the + * corresponding PK attributes. + * ---------- + */ + initStringInfo(&querybuf); + fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ? + "" : "ONLY "; + quoteRelationName(fkrelname, fk_rel); + appendStringInfo(&querybuf, "DELETE FROM %s%s", + fk_only, fkrelname); + querysep = "WHERE"; + for (int i = 0; i < riinfo->nkeys; i++) + { + Oid pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]); + Oid fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]); + Oid pk_coll = RIAttCollation(pk_rel, riinfo->pk_attnums[i]); + Oid fk_coll = RIAttCollation(fk_rel, riinfo->fk_attnums[i]); + + quoteOneName(attname, + RIAttName(fk_rel, riinfo->fk_attnums[i])); + sprintf(paramname, "$%d", i + 1); + ri_GenerateQual(&querybuf, querysep, + paramname, pk_type, + riinfo->pf_eq_oprs[i], + attname, fk_type); + if (pk_coll != fk_coll && !get_collation_isdeterministic(pk_coll)) + ri_GenerateQualCollation(&querybuf, pk_coll); + querysep = "AND"; + queryoids[i] = pk_type; + } + + /* Prepare and save the plan */ + qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids, + &qkey, fk_rel, pk_rel); + } + + /* + * We have a plan now. Build up the arguments from the key values in the + * deleted PK tuple and delete the referencing rows + */ + ri_PerformCheck(riinfo, &qkey, qplan, + fk_rel, pk_rel, + oldslot, NULL, + true, /* must detect new rows */ + SPI_OK_DELETE); + + if (SPI_finish() != SPI_OK_FINISH) + elog(ERROR, "SPI_finish failed"); + + table_close(fk_rel, RowExclusiveLock); + + return PointerGetDatum(NULL); +} + + +/* + * RI_FKey_cascade_upd - + * + * Cascaded update foreign key references at update event on PK table. + */ +Datum +RI_FKey_cascade_upd(PG_FUNCTION_ARGS) +{ + TriggerData *trigdata = (TriggerData *) fcinfo->context; + const RI_ConstraintInfo *riinfo; + Relation fk_rel; + Relation pk_rel; + TupleTableSlot *newslot; + TupleTableSlot *oldslot; + RI_QueryKey qkey; + SPIPlanPtr qplan; + + /* Check that this is a valid trigger call on the right time and event. */ + ri_CheckTrigger(fcinfo, "RI_FKey_cascade_upd", RI_TRIGTYPE_UPDATE); + + riinfo = ri_FetchConstraintInfo(trigdata->tg_trigger, + trigdata->tg_relation, true); + + /* + * Get the relation descriptors of the FK and PK tables and the new and + * old tuple. + * + * fk_rel is opened in RowExclusiveLock mode since that's what our + * eventual UPDATE will get on it. + */ + fk_rel = table_open(riinfo->fk_relid, RowExclusiveLock); + pk_rel = trigdata->tg_relation; + newslot = trigdata->tg_newslot; + oldslot = trigdata->tg_trigslot; + + if (SPI_connect() != SPI_OK_CONNECT) + elog(ERROR, "SPI_connect failed"); + + /* Fetch or prepare a saved plan for the cascaded update */ + ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_CASCADE_ONUPDATE); + + if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL) + { + StringInfoData querybuf; + StringInfoData qualbuf; + char fkrelname[MAX_QUOTED_REL_NAME_LEN]; + char attname[MAX_QUOTED_NAME_LEN]; + char paramname[16]; + const char *querysep; + const char *qualsep; + Oid queryoids[RI_MAX_NUMKEYS * 2]; + const char *fk_only; + + /* ---------- + * The query string built is + * UPDATE [ONLY] <fktable> SET fkatt1 = $1 [, ...] + * WHERE $n = fkatt1 [AND ...] + * The type id's for the $ parameters are those of the + * corresponding PK attributes. Note that we are assuming + * there is an assignment cast from the PK to the FK type; + * else the parser will fail. + * ---------- + */ + initStringInfo(&querybuf); + initStringInfo(&qualbuf); + fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ? + "" : "ONLY "; + quoteRelationName(fkrelname, fk_rel); + appendStringInfo(&querybuf, "UPDATE %s%s SET", + fk_only, fkrelname); + querysep = ""; + qualsep = "WHERE"; + for (int i = 0, j = riinfo->nkeys; i < riinfo->nkeys; i++, j++) + { + Oid pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]); + Oid fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]); + Oid pk_coll = RIAttCollation(pk_rel, riinfo->pk_attnums[i]); + Oid fk_coll = RIAttCollation(fk_rel, riinfo->fk_attnums[i]); + + quoteOneName(attname, + RIAttName(fk_rel, riinfo->fk_attnums[i])); + appendStringInfo(&querybuf, + "%s %s = $%d", + querysep, attname, i + 1); + sprintf(paramname, "$%d", j + 1); + ri_GenerateQual(&qualbuf, qualsep, + paramname, pk_type, + riinfo->pf_eq_oprs[i], + attname, fk_type); + if (pk_coll != fk_coll && !get_collation_isdeterministic(pk_coll)) + ri_GenerateQualCollation(&querybuf, pk_coll); + querysep = ","; + qualsep = "AND"; + queryoids[i] = pk_type; + queryoids[j] = pk_type; + } + appendBinaryStringInfo(&querybuf, qualbuf.data, qualbuf.len); + + /* Prepare and save the plan */ + qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys * 2, queryoids, + &qkey, fk_rel, pk_rel); + } + + /* + * We have a plan now. Run it to update the existing references. + */ + ri_PerformCheck(riinfo, &qkey, qplan, + fk_rel, pk_rel, + oldslot, newslot, + true, /* must detect new rows */ + SPI_OK_UPDATE); + + if (SPI_finish() != SPI_OK_FINISH) + elog(ERROR, "SPI_finish failed"); + + table_close(fk_rel, RowExclusiveLock); + + return PointerGetDatum(NULL); +} + + +/* + * RI_FKey_setnull_del - + * + * Set foreign key references to NULL values at delete event on PK table. + */ +Datum +RI_FKey_setnull_del(PG_FUNCTION_ARGS) +{ + /* Check that this is a valid trigger call on the right time and event. */ + ri_CheckTrigger(fcinfo, "RI_FKey_setnull_del", RI_TRIGTYPE_DELETE); + + /* Share code with UPDATE case */ + return ri_set((TriggerData *) fcinfo->context, true, RI_TRIGTYPE_DELETE); +} + +/* + * RI_FKey_setnull_upd - + * + * Set foreign key references to NULL at update event on PK table. + */ +Datum +RI_FKey_setnull_upd(PG_FUNCTION_ARGS) +{ + /* Check that this is a valid trigger call on the right time and event. */ + ri_CheckTrigger(fcinfo, "RI_FKey_setnull_upd", RI_TRIGTYPE_UPDATE); + + /* Share code with DELETE case */ + return ri_set((TriggerData *) fcinfo->context, true, RI_TRIGTYPE_UPDATE); +} + +/* + * RI_FKey_setdefault_del - + * + * Set foreign key references to defaults at delete event on PK table. + */ +Datum +RI_FKey_setdefault_del(PG_FUNCTION_ARGS) +{ + /* Check that this is a valid trigger call on the right time and event. */ + ri_CheckTrigger(fcinfo, "RI_FKey_setdefault_del", RI_TRIGTYPE_DELETE); + + /* Share code with UPDATE case */ + return ri_set((TriggerData *) fcinfo->context, false, RI_TRIGTYPE_DELETE); +} + +/* + * RI_FKey_setdefault_upd - + * + * Set foreign key references to defaults at update event on PK table. + */ +Datum +RI_FKey_setdefault_upd(PG_FUNCTION_ARGS) +{ + /* Check that this is a valid trigger call on the right time and event. */ + ri_CheckTrigger(fcinfo, "RI_FKey_setdefault_upd", RI_TRIGTYPE_UPDATE); + + /* Share code with DELETE case */ + return ri_set((TriggerData *) fcinfo->context, false, RI_TRIGTYPE_UPDATE); +} + +/* + * ri_set - + * + * Common code for ON DELETE SET NULL, ON DELETE SET DEFAULT, ON UPDATE SET + * NULL, and ON UPDATE SET DEFAULT. + */ +static Datum +ri_set(TriggerData *trigdata, bool is_set_null, int tgkind) +{ + const RI_ConstraintInfo *riinfo; + Relation fk_rel; + Relation pk_rel; + TupleTableSlot *oldslot; + RI_QueryKey qkey; + SPIPlanPtr qplan; + int32 queryno; + + riinfo = ri_FetchConstraintInfo(trigdata->tg_trigger, + trigdata->tg_relation, true); + + /* + * Get the relation descriptors of the FK and PK tables and the old tuple. + * + * fk_rel is opened in RowExclusiveLock mode since that's what our + * eventual UPDATE will get on it. + */ + fk_rel = table_open(riinfo->fk_relid, RowExclusiveLock); + pk_rel = trigdata->tg_relation; + oldslot = trigdata->tg_trigslot; + + if (SPI_connect() != SPI_OK_CONNECT) + elog(ERROR, "SPI_connect failed"); + + /* + * Fetch or prepare a saved plan for the trigger. + */ + switch (tgkind) + { + case RI_TRIGTYPE_UPDATE: + queryno = is_set_null + ? RI_PLAN_SETNULL_ONUPDATE + : RI_PLAN_SETDEFAULT_ONUPDATE; + break; + case RI_TRIGTYPE_DELETE: + queryno = is_set_null + ? RI_PLAN_SETNULL_ONDELETE + : RI_PLAN_SETDEFAULT_ONDELETE; + break; + default: + elog(ERROR, "invalid tgkind passed to ri_set"); + } + + ri_BuildQueryKey(&qkey, riinfo, queryno); + + if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL) + { + StringInfoData querybuf; + char fkrelname[MAX_QUOTED_REL_NAME_LEN]; + char attname[MAX_QUOTED_NAME_LEN]; + char paramname[16]; + const char *querysep; + const char *qualsep; + Oid queryoids[RI_MAX_NUMKEYS]; + const char *fk_only; + int num_cols_to_set; + const int16 *set_cols; + + switch (tgkind) + { + case RI_TRIGTYPE_UPDATE: + num_cols_to_set = riinfo->nkeys; + set_cols = riinfo->fk_attnums; + break; + case RI_TRIGTYPE_DELETE: + + /* + * If confdelsetcols are present, then we only update the + * columns specified in that array, otherwise we update all + * the referencing columns. + */ + if (riinfo->ndelsetcols != 0) + { + num_cols_to_set = riinfo->ndelsetcols; + set_cols = riinfo->confdelsetcols; + } + else + { + num_cols_to_set = riinfo->nkeys; + set_cols = riinfo->fk_attnums; + } + break; + default: + elog(ERROR, "invalid tgkind passed to ri_set"); + } + + /* ---------- + * The query string built is + * UPDATE [ONLY] <fktable> SET fkatt1 = {NULL|DEFAULT} [, ...] + * WHERE $1 = fkatt1 [AND ...] + * The type id's for the $ parameters are those of the + * corresponding PK attributes. + * ---------- + */ + initStringInfo(&querybuf); + fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ? + "" : "ONLY "; + quoteRelationName(fkrelname, fk_rel); + appendStringInfo(&querybuf, "UPDATE %s%s SET", + fk_only, fkrelname); + + /* + * Add assignment clauses + */ + querysep = ""; + for (int i = 0; i < num_cols_to_set; i++) + { + quoteOneName(attname, RIAttName(fk_rel, set_cols[i])); + appendStringInfo(&querybuf, + "%s %s = %s", + querysep, attname, + is_set_null ? "NULL" : "DEFAULT"); + querysep = ","; + } + + /* + * Add WHERE clause + */ + qualsep = "WHERE"; + for (int i = 0; i < riinfo->nkeys; i++) + { + Oid pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]); + Oid fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]); + Oid pk_coll = RIAttCollation(pk_rel, riinfo->pk_attnums[i]); + Oid fk_coll = RIAttCollation(fk_rel, riinfo->fk_attnums[i]); + + quoteOneName(attname, + RIAttName(fk_rel, riinfo->fk_attnums[i])); + + sprintf(paramname, "$%d", i + 1); + ri_GenerateQual(&querybuf, qualsep, + paramname, pk_type, + riinfo->pf_eq_oprs[i], + attname, fk_type); + if (pk_coll != fk_coll && !get_collation_isdeterministic(pk_coll)) + ri_GenerateQualCollation(&querybuf, pk_coll); + qualsep = "AND"; + queryoids[i] = pk_type; + } + + /* Prepare and save the plan */ + qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids, + &qkey, fk_rel, pk_rel); + } + + /* + * We have a plan now. Run it to update the existing references. + */ + ri_PerformCheck(riinfo, &qkey, qplan, + fk_rel, pk_rel, + oldslot, NULL, + true, /* must detect new rows */ + SPI_OK_UPDATE); + + if (SPI_finish() != SPI_OK_FINISH) + elog(ERROR, "SPI_finish failed"); + + table_close(fk_rel, RowExclusiveLock); + + if (is_set_null) + return PointerGetDatum(NULL); + else + { + /* + * If we just deleted or updated the PK row whose key was equal to the + * FK columns' default values, and a referencing row exists in the FK + * table, we would have updated that row to the same values it already + * had --- and RI_FKey_fk_upd_check_required would hence believe no + * check is necessary. So we need to do another lookup now and in + * case a reference still exists, abort the operation. That is + * already implemented in the NO ACTION trigger, so just run it. (This + * recheck is only needed in the SET DEFAULT case, since CASCADE would + * remove such rows in case of a DELETE operation or would change the + * FK key values in case of an UPDATE, while SET NULL is certain to + * result in rows that satisfy the FK constraint.) + */ + return ri_restrict(trigdata, true); + } +} + + +/* + * RI_FKey_pk_upd_check_required - + * + * Check if we really need to fire the RI trigger for an update or delete to a PK + * relation. This is called by the AFTER trigger queue manager to see if + * it can skip queuing an instance of an RI trigger. Returns true if the + * trigger must be fired, false if we can prove the constraint will still + * be satisfied. + * + * newslot will be NULL if this is called for a delete. + */ +bool +RI_FKey_pk_upd_check_required(Trigger *trigger, Relation pk_rel, + TupleTableSlot *oldslot, TupleTableSlot *newslot) +{ + const RI_ConstraintInfo *riinfo; + + riinfo = ri_FetchConstraintInfo(trigger, pk_rel, true); + + /* + * If any old key value is NULL, the row could not have been referenced by + * an FK row, so no check is needed. + */ + if (ri_NullCheck(RelationGetDescr(pk_rel), oldslot, riinfo, true) != RI_KEYS_NONE_NULL) + return false; + + /* If all old and new key values are equal, no check is needed */ + if (newslot && ri_KeysEqual(pk_rel, oldslot, newslot, riinfo, true)) + return false; + + /* Else we need to fire the trigger. */ + return true; +} + +/* + * RI_FKey_fk_upd_check_required - + * + * Check if we really need to fire the RI trigger for an update to an FK + * relation. This is called by the AFTER trigger queue manager to see if + * it can skip queuing an instance of an RI trigger. Returns true if the + * trigger must be fired, false if we can prove the constraint will still + * be satisfied. + */ +bool +RI_FKey_fk_upd_check_required(Trigger *trigger, Relation fk_rel, + TupleTableSlot *oldslot, TupleTableSlot *newslot) +{ + const RI_ConstraintInfo *riinfo; + int ri_nullcheck; + Datum xminDatum; + TransactionId xmin; + bool isnull; + + /* + * AfterTriggerSaveEvent() handles things such that this function is never + * called for partitioned tables. + */ + Assert(fk_rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE); + + riinfo = ri_FetchConstraintInfo(trigger, fk_rel, false); + + ri_nullcheck = ri_NullCheck(RelationGetDescr(fk_rel), newslot, riinfo, false); + + /* + * If all new key values are NULL, the row satisfies the constraint, so no + * check is needed. + */ + if (ri_nullcheck == RI_KEYS_ALL_NULL) + return false; + + /* + * If some new key values are NULL, the behavior depends on the match + * type. + */ + else if (ri_nullcheck == RI_KEYS_SOME_NULL) + { + switch (riinfo->confmatchtype) + { + case FKCONSTR_MATCH_SIMPLE: + + /* + * If any new key value is NULL, the row must satisfy the + * constraint, so no check is needed. + */ + return false; + + case FKCONSTR_MATCH_PARTIAL: + + /* + * Don't know, must run full check. + */ + break; + + case FKCONSTR_MATCH_FULL: + + /* + * If some new key values are NULL, the row fails the + * constraint. We must not throw error here, because the row + * might get invalidated before the constraint is to be + * checked, but we should queue the event to apply the check + * later. + */ + return true; + } + } + + /* + * Continues here for no new key values are NULL, or we couldn't decide + * yet. + */ + + /* + * If the original row was inserted by our own transaction, we must fire + * the trigger whether or not the keys are equal. This is because our + * UPDATE will invalidate the INSERT so that the INSERT RI trigger will + * not do anything; so we had better do the UPDATE check. (We could skip + * this if we knew the INSERT trigger already fired, but there is no easy + * way to know that.) + */ + xminDatum = slot_getsysattr(oldslot, MinTransactionIdAttributeNumber, &isnull); + Assert(!isnull); + xmin = DatumGetTransactionId(xminDatum); + if (TransactionIdIsCurrentTransactionId(xmin)) + return true; + + /* If all old and new key values are equal, no check is needed */ + if (ri_KeysEqual(fk_rel, oldslot, newslot, riinfo, false)) + return false; + + /* Else we need to fire the trigger. */ + return true; +} + +/* + * RI_Initial_Check - + * + * Check an entire table for non-matching values using a single query. + * This is not a trigger procedure, but is called during ALTER TABLE + * ADD FOREIGN KEY to validate the initial table contents. + * + * We expect that the caller has made provision to prevent any problems + * caused by concurrent actions. This could be either by locking rel and + * pkrel at ShareRowExclusiveLock or higher, or by otherwise ensuring + * that triggers implementing the checks are already active. + * Hence, we do not need to lock individual rows for the check. + * + * If the check fails because the current user doesn't have permissions + * to read both tables, return false to let our caller know that they will + * need to do something else to check the constraint. + */ +bool +RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel) +{ + const RI_ConstraintInfo *riinfo; + StringInfoData querybuf; + char pkrelname[MAX_QUOTED_REL_NAME_LEN]; + char fkrelname[MAX_QUOTED_REL_NAME_LEN]; + char pkattname[MAX_QUOTED_NAME_LEN + 3]; + char fkattname[MAX_QUOTED_NAME_LEN + 3]; + RangeTblEntry *rte; + RTEPermissionInfo *pk_perminfo; + RTEPermissionInfo *fk_perminfo; + List *rtes = NIL; + List *perminfos = NIL; + const char *sep; + const char *fk_only; + const char *pk_only; + int save_nestlevel; + char workmembuf[32]; + int spi_result; + SPIPlanPtr qplan; + + riinfo = ri_FetchConstraintInfo(trigger, fk_rel, false); + + /* + * Check to make sure current user has enough permissions to do the test + * query. (If not, caller can fall back to the trigger method, which + * works because it changes user IDs on the fly.) + * + * XXX are there any other show-stopper conditions to check? + */ + pk_perminfo = makeNode(RTEPermissionInfo); + pk_perminfo->relid = RelationGetRelid(pk_rel); + pk_perminfo->requiredPerms = ACL_SELECT; + perminfos = lappend(perminfos, pk_perminfo); + rte = makeNode(RangeTblEntry); + rte->rtekind = RTE_RELATION; + rte->relid = RelationGetRelid(pk_rel); + rte->relkind = pk_rel->rd_rel->relkind; + rte->rellockmode = AccessShareLock; + rte->perminfoindex = list_length(perminfos); + rtes = lappend(rtes, rte); + + fk_perminfo = makeNode(RTEPermissionInfo); + fk_perminfo->relid = RelationGetRelid(fk_rel); + fk_perminfo->requiredPerms = ACL_SELECT; + perminfos = lappend(perminfos, fk_perminfo); + rte = makeNode(RangeTblEntry); + rte->rtekind = RTE_RELATION; + rte->relid = RelationGetRelid(fk_rel); + rte->relkind = fk_rel->rd_rel->relkind; + rte->rellockmode = AccessShareLock; + rte->perminfoindex = list_length(perminfos); + rtes = lappend(rtes, rte); + + for (int i = 0; i < riinfo->nkeys; i++) + { + int attno; + + attno = riinfo->pk_attnums[i] - FirstLowInvalidHeapAttributeNumber; + pk_perminfo->selectedCols = bms_add_member(pk_perminfo->selectedCols, attno); + + attno = riinfo->fk_attnums[i] - FirstLowInvalidHeapAttributeNumber; + fk_perminfo->selectedCols = bms_add_member(fk_perminfo->selectedCols, attno); + } + + if (!ExecCheckPermissions(rtes, perminfos, false)) + return false; + + /* + * Also punt if RLS is enabled on either table unless this role has the + * bypassrls right or is the table owner of the table(s) involved which + * have RLS enabled. + */ + if (!has_bypassrls_privilege(GetUserId()) && + ((pk_rel->rd_rel->relrowsecurity && + !object_ownercheck(RelationRelationId, RelationGetRelid(pk_rel), + GetUserId())) || + (fk_rel->rd_rel->relrowsecurity && + !object_ownercheck(RelationRelationId, RelationGetRelid(fk_rel), + GetUserId())))) + return false; + + /*---------- + * The query string built is: + * SELECT fk.keycols FROM [ONLY] relname fk + * LEFT OUTER JOIN [ONLY] pkrelname pk + * ON (pk.pkkeycol1=fk.keycol1 [AND ...]) + * WHERE pk.pkkeycol1 IS NULL AND + * For MATCH SIMPLE: + * (fk.keycol1 IS NOT NULL [AND ...]) + * For MATCH FULL: + * (fk.keycol1 IS NOT NULL [OR ...]) + * + * We attach COLLATE clauses to the operators when comparing columns + * that have different collations. + *---------- + */ + initStringInfo(&querybuf); + appendStringInfoString(&querybuf, "SELECT "); + sep = ""; + for (int i = 0; i < riinfo->nkeys; i++) + { + quoteOneName(fkattname, + RIAttName(fk_rel, riinfo->fk_attnums[i])); + appendStringInfo(&querybuf, "%sfk.%s", sep, fkattname); + sep = ", "; + } + + quoteRelationName(pkrelname, pk_rel); + quoteRelationName(fkrelname, fk_rel); + fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ? + "" : "ONLY "; + pk_only = pk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ? + "" : "ONLY "; + appendStringInfo(&querybuf, + " FROM %s%s fk LEFT OUTER JOIN %s%s pk ON", + fk_only, fkrelname, pk_only, pkrelname); + + strcpy(pkattname, "pk."); + strcpy(fkattname, "fk."); + sep = "("; + for (int i = 0; i < riinfo->nkeys; i++) + { + Oid pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]); + Oid fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]); + Oid pk_coll = RIAttCollation(pk_rel, riinfo->pk_attnums[i]); + Oid fk_coll = RIAttCollation(fk_rel, riinfo->fk_attnums[i]); + + quoteOneName(pkattname + 3, + RIAttName(pk_rel, riinfo->pk_attnums[i])); + quoteOneName(fkattname + 3, + RIAttName(fk_rel, riinfo->fk_attnums[i])); + ri_GenerateQual(&querybuf, sep, + pkattname, pk_type, + riinfo->pf_eq_oprs[i], + fkattname, fk_type); + if (pk_coll != fk_coll) + ri_GenerateQualCollation(&querybuf, pk_coll); + sep = "AND"; + } + + /* + * It's sufficient to test any one pk attribute for null to detect a join + * failure. + */ + quoteOneName(pkattname, RIAttName(pk_rel, riinfo->pk_attnums[0])); + appendStringInfo(&querybuf, ") WHERE pk.%s IS NULL AND (", pkattname); + + sep = ""; + for (int i = 0; i < riinfo->nkeys; i++) + { + quoteOneName(fkattname, RIAttName(fk_rel, riinfo->fk_attnums[i])); + appendStringInfo(&querybuf, + "%sfk.%s IS NOT NULL", + sep, fkattname); + switch (riinfo->confmatchtype) + { + case FKCONSTR_MATCH_SIMPLE: + sep = " AND "; + break; + case FKCONSTR_MATCH_FULL: + sep = " OR "; + break; + } + } + appendStringInfoChar(&querybuf, ')'); + + /* + * Temporarily increase work_mem so that the check query can be executed + * more efficiently. It seems okay to do this because the query is simple + * enough to not use a multiple of work_mem, and one typically would not + * have many large foreign-key validations happening concurrently. So + * this seems to meet the criteria for being considered a "maintenance" + * operation, and accordingly we use maintenance_work_mem. However, we + * must also set hash_mem_multiplier to 1, since it is surely not okay to + * let that get applied to the maintenance_work_mem value. + * + * We use the equivalent of a function SET option to allow the setting to + * persist for exactly the duration of the check query. guc.c also takes + * care of undoing the setting on error. + */ + save_nestlevel = NewGUCNestLevel(); + + snprintf(workmembuf, sizeof(workmembuf), "%d", maintenance_work_mem); + (void) set_config_option("work_mem", workmembuf, + PGC_USERSET, PGC_S_SESSION, + GUC_ACTION_SAVE, true, 0, false); + (void) set_config_option("hash_mem_multiplier", "1", + PGC_USERSET, PGC_S_SESSION, + GUC_ACTION_SAVE, true, 0, false); + + if (SPI_connect() != SPI_OK_CONNECT) + elog(ERROR, "SPI_connect failed"); + + /* + * Generate the plan. We don't need to cache it, and there are no + * arguments to the plan. + */ + qplan = SPI_prepare(querybuf.data, 0, NULL); + + if (qplan == NULL) + elog(ERROR, "SPI_prepare returned %s for %s", + SPI_result_code_string(SPI_result), querybuf.data); + + /* + * Run the plan. For safety we force a current snapshot to be used. (In + * transaction-snapshot mode, this arguably violates transaction isolation + * rules, but we really haven't got much choice.) We don't need to + * register the snapshot, because SPI_execute_snapshot will see to it. We + * need at most one tuple returned, so pass limit = 1. + */ + spi_result = SPI_execute_snapshot(qplan, + NULL, NULL, + GetLatestSnapshot(), + InvalidSnapshot, + true, false, 1); + + /* Check result */ + if (spi_result != SPI_OK_SELECT) + elog(ERROR, "SPI_execute_snapshot returned %s", SPI_result_code_string(spi_result)); + + /* Did we find a tuple violating the constraint? */ + if (SPI_processed > 0) + { + TupleTableSlot *slot; + HeapTuple tuple = SPI_tuptable->vals[0]; + TupleDesc tupdesc = SPI_tuptable->tupdesc; + RI_ConstraintInfo fake_riinfo; + + slot = MakeSingleTupleTableSlot(tupdesc, &TTSOpsVirtual); + + heap_deform_tuple(tuple, tupdesc, + slot->tts_values, slot->tts_isnull); + ExecStoreVirtualTuple(slot); + + /* + * The columns to look at in the result tuple are 1..N, not whatever + * they are in the fk_rel. Hack up riinfo so that the subroutines + * called here will behave properly. + * + * In addition to this, we have to pass the correct tupdesc to + * ri_ReportViolation, overriding its normal habit of using the pk_rel + * or fk_rel's tupdesc. + */ + memcpy(&fake_riinfo, riinfo, sizeof(RI_ConstraintInfo)); + for (int i = 0; i < fake_riinfo.nkeys; i++) + fake_riinfo.fk_attnums[i] = i + 1; + + /* + * If it's MATCH FULL, and there are any nulls in the FK keys, + * complain about that rather than the lack of a match. MATCH FULL + * disallows partially-null FK rows. + */ + if (fake_riinfo.confmatchtype == FKCONSTR_MATCH_FULL && + ri_NullCheck(tupdesc, slot, &fake_riinfo, false) != RI_KEYS_NONE_NULL) + ereport(ERROR, + (errcode(ERRCODE_FOREIGN_KEY_VIOLATION), + errmsg("insert or update on table \"%s\" violates foreign key constraint \"%s\"", + RelationGetRelationName(fk_rel), + NameStr(fake_riinfo.conname)), + errdetail("MATCH FULL does not allow mixing of null and nonnull key values."), + errtableconstraint(fk_rel, + NameStr(fake_riinfo.conname)))); + + /* + * We tell ri_ReportViolation we were doing the RI_PLAN_CHECK_LOOKUPPK + * query, which isn't true, but will cause it to use + * fake_riinfo.fk_attnums as we need. + */ + ri_ReportViolation(&fake_riinfo, + pk_rel, fk_rel, + slot, tupdesc, + RI_PLAN_CHECK_LOOKUPPK, false); + + ExecDropSingleTupleTableSlot(slot); + } + + if (SPI_finish() != SPI_OK_FINISH) + elog(ERROR, "SPI_finish failed"); + + /* + * Restore work_mem and hash_mem_multiplier. + */ + AtEOXact_GUC(true, save_nestlevel); + + return true; +} + +/* + * RI_PartitionRemove_Check - + * + * Verify no referencing values exist, when a partition is detached on + * the referenced side of a foreign key constraint. + */ +void +RI_PartitionRemove_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel) +{ + const RI_ConstraintInfo *riinfo; + StringInfoData querybuf; + char *constraintDef; + char pkrelname[MAX_QUOTED_REL_NAME_LEN]; + char fkrelname[MAX_QUOTED_REL_NAME_LEN]; + char pkattname[MAX_QUOTED_NAME_LEN + 3]; + char fkattname[MAX_QUOTED_NAME_LEN + 3]; + const char *sep; + const char *fk_only; + int save_nestlevel; + char workmembuf[32]; + int spi_result; + SPIPlanPtr qplan; + int i; + + riinfo = ri_FetchConstraintInfo(trigger, fk_rel, false); + + /* + * We don't check permissions before displaying the error message, on the + * assumption that the user detaching the partition must have enough + * privileges to examine the table contents anyhow. + */ + + /*---------- + * The query string built is: + * SELECT fk.keycols FROM [ONLY] relname fk + * JOIN pkrelname pk + * ON (pk.pkkeycol1=fk.keycol1 [AND ...]) + * WHERE (<partition constraint>) AND + * For MATCH SIMPLE: + * (fk.keycol1 IS NOT NULL [AND ...]) + * For MATCH FULL: + * (fk.keycol1 IS NOT NULL [OR ...]) + * + * We attach COLLATE clauses to the operators when comparing columns + * that have different collations. + *---------- + */ + initStringInfo(&querybuf); + appendStringInfoString(&querybuf, "SELECT "); + sep = ""; + for (i = 0; i < riinfo->nkeys; i++) + { + quoteOneName(fkattname, + RIAttName(fk_rel, riinfo->fk_attnums[i])); + appendStringInfo(&querybuf, "%sfk.%s", sep, fkattname); + sep = ", "; + } + + quoteRelationName(pkrelname, pk_rel); + quoteRelationName(fkrelname, fk_rel); + fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ? + "" : "ONLY "; + appendStringInfo(&querybuf, + " FROM %s%s fk JOIN %s pk ON", + fk_only, fkrelname, pkrelname); + strcpy(pkattname, "pk."); + strcpy(fkattname, "fk."); + sep = "("; + for (i = 0; i < riinfo->nkeys; i++) + { + Oid pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]); + Oid fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]); + Oid pk_coll = RIAttCollation(pk_rel, riinfo->pk_attnums[i]); + Oid fk_coll = RIAttCollation(fk_rel, riinfo->fk_attnums[i]); + + quoteOneName(pkattname + 3, + RIAttName(pk_rel, riinfo->pk_attnums[i])); + quoteOneName(fkattname + 3, + RIAttName(fk_rel, riinfo->fk_attnums[i])); + ri_GenerateQual(&querybuf, sep, + pkattname, pk_type, + riinfo->pf_eq_oprs[i], + fkattname, fk_type); + if (pk_coll != fk_coll) + ri_GenerateQualCollation(&querybuf, pk_coll); + sep = "AND"; + } + + /* + * Start the WHERE clause with the partition constraint (except if this is + * the default partition and there's no other partition, because the + * partition constraint is the empty string in that case.) + */ + constraintDef = pg_get_partconstrdef_string(RelationGetRelid(pk_rel), "pk"); + if (constraintDef && constraintDef[0] != '\0') + appendStringInfo(&querybuf, ") WHERE %s AND (", + constraintDef); + else + appendStringInfoString(&querybuf, ") WHERE ("); + + sep = ""; + for (i = 0; i < riinfo->nkeys; i++) + { + quoteOneName(fkattname, RIAttName(fk_rel, riinfo->fk_attnums[i])); + appendStringInfo(&querybuf, + "%sfk.%s IS NOT NULL", + sep, fkattname); + switch (riinfo->confmatchtype) + { + case FKCONSTR_MATCH_SIMPLE: + sep = " AND "; + break; + case FKCONSTR_MATCH_FULL: + sep = " OR "; + break; + } + } + appendStringInfoChar(&querybuf, ')'); + + /* + * Temporarily increase work_mem so that the check query can be executed + * more efficiently. It seems okay to do this because the query is simple + * enough to not use a multiple of work_mem, and one typically would not + * have many large foreign-key validations happening concurrently. So + * this seems to meet the criteria for being considered a "maintenance" + * operation, and accordingly we use maintenance_work_mem. However, we + * must also set hash_mem_multiplier to 1, since it is surely not okay to + * let that get applied to the maintenance_work_mem value. + * + * We use the equivalent of a function SET option to allow the setting to + * persist for exactly the duration of the check query. guc.c also takes + * care of undoing the setting on error. + */ + save_nestlevel = NewGUCNestLevel(); + + snprintf(workmembuf, sizeof(workmembuf), "%d", maintenance_work_mem); + (void) set_config_option("work_mem", workmembuf, + PGC_USERSET, PGC_S_SESSION, + GUC_ACTION_SAVE, true, 0, false); + (void) set_config_option("hash_mem_multiplier", "1", + PGC_USERSET, PGC_S_SESSION, + GUC_ACTION_SAVE, true, 0, false); + + if (SPI_connect() != SPI_OK_CONNECT) + elog(ERROR, "SPI_connect failed"); + + /* + * Generate the plan. We don't need to cache it, and there are no + * arguments to the plan. + */ + qplan = SPI_prepare(querybuf.data, 0, NULL); + + if (qplan == NULL) + elog(ERROR, "SPI_prepare returned %s for %s", + SPI_result_code_string(SPI_result), querybuf.data); + + /* + * Run the plan. For safety we force a current snapshot to be used. (In + * transaction-snapshot mode, this arguably violates transaction isolation + * rules, but we really haven't got much choice.) We don't need to + * register the snapshot, because SPI_execute_snapshot will see to it. We + * need at most one tuple returned, so pass limit = 1. + */ + spi_result = SPI_execute_snapshot(qplan, + NULL, NULL, + GetLatestSnapshot(), + InvalidSnapshot, + true, false, 1); + + /* Check result */ + if (spi_result != SPI_OK_SELECT) + elog(ERROR, "SPI_execute_snapshot returned %s", SPI_result_code_string(spi_result)); + + /* Did we find a tuple that would violate the constraint? */ + if (SPI_processed > 0) + { + TupleTableSlot *slot; + HeapTuple tuple = SPI_tuptable->vals[0]; + TupleDesc tupdesc = SPI_tuptable->tupdesc; + RI_ConstraintInfo fake_riinfo; + + slot = MakeSingleTupleTableSlot(tupdesc, &TTSOpsVirtual); + + heap_deform_tuple(tuple, tupdesc, + slot->tts_values, slot->tts_isnull); + ExecStoreVirtualTuple(slot); + + /* + * The columns to look at in the result tuple are 1..N, not whatever + * they are in the fk_rel. Hack up riinfo so that ri_ReportViolation + * will behave properly. + * + * In addition to this, we have to pass the correct tupdesc to + * ri_ReportViolation, overriding its normal habit of using the pk_rel + * or fk_rel's tupdesc. + */ + memcpy(&fake_riinfo, riinfo, sizeof(RI_ConstraintInfo)); + for (i = 0; i < fake_riinfo.nkeys; i++) + fake_riinfo.pk_attnums[i] = i + 1; + + ri_ReportViolation(&fake_riinfo, pk_rel, fk_rel, + slot, tupdesc, 0, true); + } + + if (SPI_finish() != SPI_OK_FINISH) + elog(ERROR, "SPI_finish failed"); + + /* + * Restore work_mem and hash_mem_multiplier. + */ + AtEOXact_GUC(true, save_nestlevel); +} + + +/* ---------- + * Local functions below + * ---------- + */ + + +/* + * quoteOneName --- safely quote a single SQL name + * + * buffer must be MAX_QUOTED_NAME_LEN long (includes room for \0) + */ +static void +quoteOneName(char *buffer, const char *name) +{ + /* Rather than trying to be smart, just always quote it. */ + *buffer++ = '"'; + while (*name) + { + if (*name == '"') + *buffer++ = '"'; + *buffer++ = *name++; + } + *buffer++ = '"'; + *buffer = '\0'; +} + +/* + * quoteRelationName --- safely quote a fully qualified relation name + * + * buffer must be MAX_QUOTED_REL_NAME_LEN long (includes room for \0) + */ +static void +quoteRelationName(char *buffer, Relation rel) +{ + quoteOneName(buffer, get_namespace_name(RelationGetNamespace(rel))); + buffer += strlen(buffer); + *buffer++ = '.'; + quoteOneName(buffer, RelationGetRelationName(rel)); +} + +/* + * ri_GenerateQual --- generate a WHERE clause equating two variables + * + * This basically appends " sep leftop op rightop" to buf, adding casts + * and schema qualification as needed to ensure that the parser will select + * the operator we specify. leftop and rightop should be parenthesized + * if they aren't variables or parameters. + */ +static void +ri_GenerateQual(StringInfo buf, + const char *sep, + const char *leftop, Oid leftoptype, + Oid opoid, + const char *rightop, Oid rightoptype) +{ + appendStringInfo(buf, " %s ", sep); + generate_operator_clause(buf, leftop, leftoptype, opoid, + rightop, rightoptype); +} + +/* + * ri_GenerateQualCollation --- add a COLLATE spec to a WHERE clause + * + * At present, we intentionally do not use this function for RI queries that + * compare a variable to a $n parameter. Since parameter symbols always have + * default collation, the effect will be to use the variable's collation. + * Now that is only strictly correct when testing the referenced column, since + * the SQL standard specifies that RI comparisons should use the referenced + * column's collation. However, so long as all collations have the same + * notion of equality (which they do, because texteq reduces to bitwise + * equality), there's no visible semantic impact from using the referencing + * column's collation when testing it, and this is a good thing to do because + * it lets us use a normal index on the referencing column. However, we do + * have to use this function when directly comparing the referencing and + * referenced columns, if they are of different collations; else the parser + * will fail to resolve the collation to use. + */ +static void +ri_GenerateQualCollation(StringInfo buf, Oid collation) +{ + HeapTuple tp; + Form_pg_collation colltup; + char *collname; + char onename[MAX_QUOTED_NAME_LEN]; + + /* Nothing to do if it's a noncollatable data type */ + if (!OidIsValid(collation)) + return; + + tp = SearchSysCache1(COLLOID, ObjectIdGetDatum(collation)); + if (!HeapTupleIsValid(tp)) + elog(ERROR, "cache lookup failed for collation %u", collation); + colltup = (Form_pg_collation) GETSTRUCT(tp); + collname = NameStr(colltup->collname); + + /* + * We qualify the name always, for simplicity and to ensure the query is + * not search-path-dependent. + */ + quoteOneName(onename, get_namespace_name(colltup->collnamespace)); + appendStringInfo(buf, " COLLATE %s", onename); + quoteOneName(onename, collname); + appendStringInfo(buf, ".%s", onename); + + ReleaseSysCache(tp); +} + +/* ---------- + * ri_BuildQueryKey - + * + * Construct a hashtable key for a prepared SPI plan of an FK constraint. + * + * key: output argument, *key is filled in based on the other arguments + * riinfo: info derived from pg_constraint entry + * constr_queryno: an internal number identifying the query type + * (see RI_PLAN_XXX constants at head of file) + * ---------- + */ +static void +ri_BuildQueryKey(RI_QueryKey *key, const RI_ConstraintInfo *riinfo, + int32 constr_queryno) +{ + /* + * Inherited constraints with a common ancestor can share ri_query_cache + * entries for all query types except RI_PLAN_CHECK_LOOKUPPK_FROM_PK. + * Except in that case, the query processes the other table involved in + * the FK constraint (i.e., not the table on which the trigger has been + * fired), and so it will be the same for all members of the inheritance + * tree. So we may use the root constraint's OID in the hash key, rather + * than the constraint's own OID. This avoids creating duplicate SPI + * plans, saving lots of work and memory when there are many partitions + * with similar FK constraints. + * + * (Note that we must still have a separate RI_ConstraintInfo for each + * constraint, because partitions can have different column orders, + * resulting in different pk_attnums[] or fk_attnums[] array contents.) + * + * We assume struct RI_QueryKey contains no padding bytes, else we'd need + * to use memset to clear them. + */ + if (constr_queryno != RI_PLAN_CHECK_LOOKUPPK_FROM_PK) + key->constr_id = riinfo->constraint_root_id; + else + key->constr_id = riinfo->constraint_id; + key->constr_queryno = constr_queryno; +} + +/* + * Check that RI trigger function was called in expected context + */ +static void +ri_CheckTrigger(FunctionCallInfo fcinfo, const char *funcname, int tgkind) +{ + TriggerData *trigdata = (TriggerData *) fcinfo->context; + + if (!CALLED_AS_TRIGGER(fcinfo)) + ereport(ERROR, + (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED), + errmsg("function \"%s\" was not called by trigger manager", funcname))); + + /* + * Check proper event + */ + if (!TRIGGER_FIRED_AFTER(trigdata->tg_event) || + !TRIGGER_FIRED_FOR_ROW(trigdata->tg_event)) + ereport(ERROR, + (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED), + errmsg("function \"%s\" must be fired AFTER ROW", funcname))); + + switch (tgkind) + { + case RI_TRIGTYPE_INSERT: + if (!TRIGGER_FIRED_BY_INSERT(trigdata->tg_event)) + ereport(ERROR, + (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED), + errmsg("function \"%s\" must be fired for INSERT", funcname))); + break; + case RI_TRIGTYPE_UPDATE: + if (!TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event)) + ereport(ERROR, + (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED), + errmsg("function \"%s\" must be fired for UPDATE", funcname))); + break; + case RI_TRIGTYPE_DELETE: + if (!TRIGGER_FIRED_BY_DELETE(trigdata->tg_event)) + ereport(ERROR, + (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED), + errmsg("function \"%s\" must be fired for DELETE", funcname))); + break; + } +} + + +/* + * Fetch the RI_ConstraintInfo struct for the trigger's FK constraint. + */ +static const RI_ConstraintInfo * +ri_FetchConstraintInfo(Trigger *trigger, Relation trig_rel, bool rel_is_pk) +{ + Oid constraintOid = trigger->tgconstraint; + const RI_ConstraintInfo *riinfo; + + /* + * Check that the FK constraint's OID is available; it might not be if + * we've been invoked via an ordinary trigger or an old-style "constraint + * trigger". + */ + if (!OidIsValid(constraintOid)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("no pg_constraint entry for trigger \"%s\" on table \"%s\"", + trigger->tgname, RelationGetRelationName(trig_rel)), + errhint("Remove this referential integrity trigger and its mates, then do ALTER TABLE ADD CONSTRAINT."))); + + /* Find or create a hashtable entry for the constraint */ + riinfo = ri_LoadConstraintInfo(constraintOid); + + /* Do some easy cross-checks against the trigger call data */ + if (rel_is_pk) + { + if (riinfo->fk_relid != trigger->tgconstrrelid || + riinfo->pk_relid != RelationGetRelid(trig_rel)) + elog(ERROR, "wrong pg_constraint entry for trigger \"%s\" on table \"%s\"", + trigger->tgname, RelationGetRelationName(trig_rel)); + } + else + { + if (riinfo->fk_relid != RelationGetRelid(trig_rel) || + riinfo->pk_relid != trigger->tgconstrrelid) + elog(ERROR, "wrong pg_constraint entry for trigger \"%s\" on table \"%s\"", + trigger->tgname, RelationGetRelationName(trig_rel)); + } + + if (riinfo->confmatchtype != FKCONSTR_MATCH_FULL && + riinfo->confmatchtype != FKCONSTR_MATCH_PARTIAL && + riinfo->confmatchtype != FKCONSTR_MATCH_SIMPLE) + elog(ERROR, "unrecognized confmatchtype: %d", + riinfo->confmatchtype); + + if (riinfo->confmatchtype == FKCONSTR_MATCH_PARTIAL) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("MATCH PARTIAL not yet implemented"))); + + return riinfo; +} + +/* + * Fetch or create the RI_ConstraintInfo struct for an FK constraint. + */ +static const RI_ConstraintInfo * +ri_LoadConstraintInfo(Oid constraintOid) +{ + RI_ConstraintInfo *riinfo; + bool found; + HeapTuple tup; + Form_pg_constraint conForm; + + /* + * On the first call initialize the hashtable + */ + if (!ri_constraint_cache) + ri_InitHashTables(); + + /* + * Find or create a hash entry. If we find a valid one, just return it. + */ + riinfo = (RI_ConstraintInfo *) hash_search(ri_constraint_cache, + &constraintOid, + HASH_ENTER, &found); + if (!found) + riinfo->valid = false; + else if (riinfo->valid) + return riinfo; + + /* + * Fetch the pg_constraint row so we can fill in the entry. + */ + tup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(constraintOid)); + if (!HeapTupleIsValid(tup)) /* should not happen */ + elog(ERROR, "cache lookup failed for constraint %u", constraintOid); + conForm = (Form_pg_constraint) GETSTRUCT(tup); + + if (conForm->contype != CONSTRAINT_FOREIGN) /* should not happen */ + elog(ERROR, "constraint %u is not a foreign key constraint", + constraintOid); + + /* And extract data */ + Assert(riinfo->constraint_id == constraintOid); + if (OidIsValid(conForm->conparentid)) + riinfo->constraint_root_id = + get_ri_constraint_root(conForm->conparentid); + else + riinfo->constraint_root_id = constraintOid; + riinfo->oidHashValue = GetSysCacheHashValue1(CONSTROID, + ObjectIdGetDatum(constraintOid)); + riinfo->rootHashValue = GetSysCacheHashValue1(CONSTROID, + ObjectIdGetDatum(riinfo->constraint_root_id)); + memcpy(&riinfo->conname, &conForm->conname, sizeof(NameData)); + riinfo->pk_relid = conForm->confrelid; + riinfo->fk_relid = conForm->conrelid; + riinfo->confupdtype = conForm->confupdtype; + riinfo->confdeltype = conForm->confdeltype; + riinfo->confmatchtype = conForm->confmatchtype; + + DeconstructFkConstraintRow(tup, + &riinfo->nkeys, + riinfo->fk_attnums, + riinfo->pk_attnums, + riinfo->pf_eq_oprs, + riinfo->pp_eq_oprs, + riinfo->ff_eq_oprs, + &riinfo->ndelsetcols, + riinfo->confdelsetcols); + + ReleaseSysCache(tup); + + /* + * For efficient processing of invalidation messages below, we keep a + * doubly-linked count list of all currently valid entries. + */ + dclist_push_tail(&ri_constraint_cache_valid_list, &riinfo->valid_link); + + riinfo->valid = true; + + return riinfo; +} + +/* + * get_ri_constraint_root + * Returns the OID of the constraint's root parent + */ +static Oid +get_ri_constraint_root(Oid constrOid) +{ + for (;;) + { + HeapTuple tuple; + Oid constrParentOid; + + tuple = SearchSysCache1(CONSTROID, ObjectIdGetDatum(constrOid)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for constraint %u", constrOid); + constrParentOid = ((Form_pg_constraint) GETSTRUCT(tuple))->conparentid; + ReleaseSysCache(tuple); + if (!OidIsValid(constrParentOid)) + break; /* we reached the root constraint */ + constrOid = constrParentOid; + } + return constrOid; +} + +/* + * Callback for pg_constraint inval events + * + * While most syscache callbacks just flush all their entries, pg_constraint + * gets enough update traffic that it's probably worth being smarter. + * Invalidate any ri_constraint_cache entry associated with the syscache + * entry with the specified hash value, or all entries if hashvalue == 0. + * + * Note: at the time a cache invalidation message is processed there may be + * active references to the cache. Because of this we never remove entries + * from the cache, but only mark them invalid, which is harmless to active + * uses. (Any query using an entry should hold a lock sufficient to keep that + * data from changing under it --- but we may get cache flushes anyway.) + */ +static void +InvalidateConstraintCacheCallBack(Datum arg, int cacheid, uint32 hashvalue) +{ + dlist_mutable_iter iter; + + Assert(ri_constraint_cache != NULL); + + /* + * If the list of currently valid entries gets excessively large, we mark + * them all invalid so we can empty the list. This arrangement avoids + * O(N^2) behavior in situations where a session touches many foreign keys + * and also does many ALTER TABLEs, such as a restore from pg_dump. + */ + if (dclist_count(&ri_constraint_cache_valid_list) > 1000) + hashvalue = 0; /* pretend it's a cache reset */ + + dclist_foreach_modify(iter, &ri_constraint_cache_valid_list) + { + RI_ConstraintInfo *riinfo = dclist_container(RI_ConstraintInfo, + valid_link, iter.cur); + + /* + * We must invalidate not only entries directly matching the given + * hash value, but also child entries, in case the invalidation + * affects a root constraint. + */ + if (hashvalue == 0 || + riinfo->oidHashValue == hashvalue || + riinfo->rootHashValue == hashvalue) + { + riinfo->valid = false; + /* Remove invalidated entries from the list, too */ + dclist_delete_from(&ri_constraint_cache_valid_list, iter.cur); + } + } +} + + +/* + * Prepare execution plan for a query to enforce an RI restriction + */ +static SPIPlanPtr +ri_PlanCheck(const char *querystr, int nargs, Oid *argtypes, + RI_QueryKey *qkey, Relation fk_rel, Relation pk_rel) +{ + SPIPlanPtr qplan; + Relation query_rel; + Oid save_userid; + int save_sec_context; + + /* + * Use the query type code to determine whether the query is run against + * the PK or FK table; we'll do the check as that table's owner + */ + if (qkey->constr_queryno <= RI_PLAN_LAST_ON_PK) + query_rel = pk_rel; + else + query_rel = fk_rel; + + /* Switch to proper UID to perform check as */ + GetUserIdAndSecContext(&save_userid, &save_sec_context); + SetUserIdAndSecContext(RelationGetForm(query_rel)->relowner, + save_sec_context | SECURITY_LOCAL_USERID_CHANGE | + SECURITY_NOFORCE_RLS); + + /* Create the plan */ + qplan = SPI_prepare(querystr, nargs, argtypes); + + if (qplan == NULL) + elog(ERROR, "SPI_prepare returned %s for %s", SPI_result_code_string(SPI_result), querystr); + + /* Restore UID and security context */ + SetUserIdAndSecContext(save_userid, save_sec_context); + + /* Save the plan */ + SPI_keepplan(qplan); + ri_HashPreparedPlan(qkey, qplan); + + return qplan; +} + +/* + * Perform a query to enforce an RI restriction + */ +static bool +ri_PerformCheck(const RI_ConstraintInfo *riinfo, + RI_QueryKey *qkey, SPIPlanPtr qplan, + Relation fk_rel, Relation pk_rel, + TupleTableSlot *oldslot, TupleTableSlot *newslot, + bool detectNewRows, int expect_OK) +{ + Relation query_rel, + source_rel; + bool source_is_pk; + Snapshot test_snapshot; + Snapshot crosscheck_snapshot; + int limit; + int spi_result; + Oid save_userid; + int save_sec_context; + Datum vals[RI_MAX_NUMKEYS * 2]; + char nulls[RI_MAX_NUMKEYS * 2]; + + /* + * Use the query type code to determine whether the query is run against + * the PK or FK table; we'll do the check as that table's owner + */ + if (qkey->constr_queryno <= RI_PLAN_LAST_ON_PK) + query_rel = pk_rel; + else + query_rel = fk_rel; + + /* + * The values for the query are taken from the table on which the trigger + * is called - it is normally the other one with respect to query_rel. An + * exception is ri_Check_Pk_Match(), which uses the PK table for both (and + * sets queryno to RI_PLAN_CHECK_LOOKUPPK_FROM_PK). We might eventually + * need some less klugy way to determine this. + */ + if (qkey->constr_queryno == RI_PLAN_CHECK_LOOKUPPK) + { + source_rel = fk_rel; + source_is_pk = false; + } + else + { + source_rel = pk_rel; + source_is_pk = true; + } + + /* Extract the parameters to be passed into the query */ + if (newslot) + { + ri_ExtractValues(source_rel, newslot, riinfo, source_is_pk, + vals, nulls); + if (oldslot) + ri_ExtractValues(source_rel, oldslot, riinfo, source_is_pk, + vals + riinfo->nkeys, nulls + riinfo->nkeys); + } + else + { + ri_ExtractValues(source_rel, oldslot, riinfo, source_is_pk, + vals, nulls); + } + + /* + * In READ COMMITTED mode, we just need to use an up-to-date regular + * snapshot, and we will see all rows that could be interesting. But in + * transaction-snapshot mode, we can't change the transaction snapshot. If + * the caller passes detectNewRows == false then it's okay to do the query + * with the transaction snapshot; otherwise we use a current snapshot, and + * tell the executor to error out if it finds any rows under the current + * snapshot that wouldn't be visible per the transaction snapshot. Note + * that SPI_execute_snapshot will register the snapshots, so we don't need + * to bother here. + */ + if (IsolationUsesXactSnapshot() && detectNewRows) + { + CommandCounterIncrement(); /* be sure all my own work is visible */ + test_snapshot = GetLatestSnapshot(); + crosscheck_snapshot = GetTransactionSnapshot(); + } + else + { + /* the default SPI behavior is okay */ + test_snapshot = InvalidSnapshot; + crosscheck_snapshot = InvalidSnapshot; + } + + /* + * If this is a select query (e.g., for a 'no action' or 'restrict' + * trigger), we only need to see if there is a single row in the table, + * matching the key. Otherwise, limit = 0 - because we want the query to + * affect ALL the matching rows. + */ + limit = (expect_OK == SPI_OK_SELECT) ? 1 : 0; + + /* Switch to proper UID to perform check as */ + GetUserIdAndSecContext(&save_userid, &save_sec_context); + SetUserIdAndSecContext(RelationGetForm(query_rel)->relowner, + save_sec_context | SECURITY_LOCAL_USERID_CHANGE | + SECURITY_NOFORCE_RLS); + + /* Finally we can run the query. */ + spi_result = SPI_execute_snapshot(qplan, + vals, nulls, + test_snapshot, crosscheck_snapshot, + false, false, limit); + + /* Restore UID and security context */ + SetUserIdAndSecContext(save_userid, save_sec_context); + + /* Check result */ + if (spi_result < 0) + elog(ERROR, "SPI_execute_snapshot returned %s", SPI_result_code_string(spi_result)); + + if (expect_OK >= 0 && spi_result != expect_OK) + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("referential integrity query on \"%s\" from constraint \"%s\" on \"%s\" gave unexpected result", + RelationGetRelationName(pk_rel), + NameStr(riinfo->conname), + RelationGetRelationName(fk_rel)), + errhint("This is most likely due to a rule having rewritten the query."))); + + /* XXX wouldn't it be clearer to do this part at the caller? */ + if (qkey->constr_queryno != RI_PLAN_CHECK_LOOKUPPK_FROM_PK && + expect_OK == SPI_OK_SELECT && + (SPI_processed == 0) == (qkey->constr_queryno == RI_PLAN_CHECK_LOOKUPPK)) + ri_ReportViolation(riinfo, + pk_rel, fk_rel, + newslot ? newslot : oldslot, + NULL, + qkey->constr_queryno, false); + + return SPI_processed != 0; +} + +/* + * Extract fields from a tuple into Datum/nulls arrays + */ +static void +ri_ExtractValues(Relation rel, TupleTableSlot *slot, + const RI_ConstraintInfo *riinfo, bool rel_is_pk, + Datum *vals, char *nulls) +{ + const int16 *attnums; + bool isnull; + + if (rel_is_pk) + attnums = riinfo->pk_attnums; + else + attnums = riinfo->fk_attnums; + + for (int i = 0; i < riinfo->nkeys; i++) + { + vals[i] = slot_getattr(slot, attnums[i], &isnull); + nulls[i] = isnull ? 'n' : ' '; + } +} + +/* + * Produce an error report + * + * If the failed constraint was on insert/update to the FK table, + * we want the key names and values extracted from there, and the error + * message to look like 'key blah is not present in PK'. + * Otherwise, the attr names and values come from the PK table and the + * message looks like 'key blah is still referenced from FK'. + */ +static void +ri_ReportViolation(const RI_ConstraintInfo *riinfo, + Relation pk_rel, Relation fk_rel, + TupleTableSlot *violatorslot, TupleDesc tupdesc, + int queryno, bool partgone) +{ + StringInfoData key_names; + StringInfoData key_values; + bool onfk; + const int16 *attnums; + Oid rel_oid; + AclResult aclresult; + bool has_perm = true; + + /* + * Determine which relation to complain about. If tupdesc wasn't passed + * by caller, assume the violator tuple came from there. + */ + onfk = (queryno == RI_PLAN_CHECK_LOOKUPPK); + if (onfk) + { + attnums = riinfo->fk_attnums; + rel_oid = fk_rel->rd_id; + if (tupdesc == NULL) + tupdesc = fk_rel->rd_att; + } + else + { + attnums = riinfo->pk_attnums; + rel_oid = pk_rel->rd_id; + if (tupdesc == NULL) + tupdesc = pk_rel->rd_att; + } + + /* + * Check permissions- if the user does not have access to view the data in + * any of the key columns then we don't include the errdetail() below. + * + * Check if RLS is enabled on the relation first. If so, we don't return + * any specifics to avoid leaking data. + * + * Check table-level permissions next and, failing that, column-level + * privileges. + * + * When a partition at the referenced side is being detached/dropped, we + * needn't check, since the user must be the table owner anyway. + */ + if (partgone) + has_perm = true; + else if (check_enable_rls(rel_oid, InvalidOid, true) != RLS_ENABLED) + { + aclresult = pg_class_aclcheck(rel_oid, GetUserId(), ACL_SELECT); + if (aclresult != ACLCHECK_OK) + { + /* Try for column-level permissions */ + for (int idx = 0; idx < riinfo->nkeys; idx++) + { + aclresult = pg_attribute_aclcheck(rel_oid, attnums[idx], + GetUserId(), + ACL_SELECT); + + /* No access to the key */ + if (aclresult != ACLCHECK_OK) + { + has_perm = false; + break; + } + } + } + } + else + has_perm = false; + + if (has_perm) + { + /* Get printable versions of the keys involved */ + initStringInfo(&key_names); + initStringInfo(&key_values); + for (int idx = 0; idx < riinfo->nkeys; idx++) + { + int fnum = attnums[idx]; + Form_pg_attribute att = TupleDescAttr(tupdesc, fnum - 1); + char *name, + *val; + Datum datum; + bool isnull; + + name = NameStr(att->attname); + + datum = slot_getattr(violatorslot, fnum, &isnull); + if (!isnull) + { + Oid foutoid; + bool typisvarlena; + + getTypeOutputInfo(att->atttypid, &foutoid, &typisvarlena); + val = OidOutputFunctionCall(foutoid, datum); + } + else + val = "null"; + + if (idx > 0) + { + appendStringInfoString(&key_names, ", "); + appendStringInfoString(&key_values, ", "); + } + appendStringInfoString(&key_names, name); + appendStringInfoString(&key_values, val); + } + } + + if (partgone) + ereport(ERROR, + (errcode(ERRCODE_FOREIGN_KEY_VIOLATION), + errmsg("removing partition \"%s\" violates foreign key constraint \"%s\"", + RelationGetRelationName(pk_rel), + NameStr(riinfo->conname)), + errdetail("Key (%s)=(%s) is still referenced from table \"%s\".", + key_names.data, key_values.data, + RelationGetRelationName(fk_rel)), + errtableconstraint(fk_rel, NameStr(riinfo->conname)))); + else if (onfk) + ereport(ERROR, + (errcode(ERRCODE_FOREIGN_KEY_VIOLATION), + errmsg("insert or update on table \"%s\" violates foreign key constraint \"%s\"", + RelationGetRelationName(fk_rel), + NameStr(riinfo->conname)), + has_perm ? + errdetail("Key (%s)=(%s) is not present in table \"%s\".", + key_names.data, key_values.data, + RelationGetRelationName(pk_rel)) : + errdetail("Key is not present in table \"%s\".", + RelationGetRelationName(pk_rel)), + errtableconstraint(fk_rel, NameStr(riinfo->conname)))); + else + ereport(ERROR, + (errcode(ERRCODE_FOREIGN_KEY_VIOLATION), + errmsg("update or delete on table \"%s\" violates foreign key constraint \"%s\" on table \"%s\"", + RelationGetRelationName(pk_rel), + NameStr(riinfo->conname), + RelationGetRelationName(fk_rel)), + has_perm ? + errdetail("Key (%s)=(%s) is still referenced from table \"%s\".", + key_names.data, key_values.data, + RelationGetRelationName(fk_rel)) : + errdetail("Key is still referenced from table \"%s\".", + RelationGetRelationName(fk_rel)), + errtableconstraint(fk_rel, NameStr(riinfo->conname)))); +} + + +/* + * ri_NullCheck - + * + * Determine the NULL state of all key values in a tuple + * + * Returns one of RI_KEYS_ALL_NULL, RI_KEYS_NONE_NULL or RI_KEYS_SOME_NULL. + */ +static int +ri_NullCheck(TupleDesc tupDesc, + TupleTableSlot *slot, + const RI_ConstraintInfo *riinfo, bool rel_is_pk) +{ + const int16 *attnums; + bool allnull = true; + bool nonenull = true; + + if (rel_is_pk) + attnums = riinfo->pk_attnums; + else + attnums = riinfo->fk_attnums; + + for (int i = 0; i < riinfo->nkeys; i++) + { + if (slot_attisnull(slot, attnums[i])) + nonenull = false; + else + allnull = false; + } + + if (allnull) + return RI_KEYS_ALL_NULL; + + if (nonenull) + return RI_KEYS_NONE_NULL; + + return RI_KEYS_SOME_NULL; +} + + +/* + * ri_InitHashTables - + * + * Initialize our internal hash tables. + */ +static void +ri_InitHashTables(void) +{ + HASHCTL ctl; + + ctl.keysize = sizeof(Oid); + ctl.entrysize = sizeof(RI_ConstraintInfo); + ri_constraint_cache = hash_create("RI constraint cache", + RI_INIT_CONSTRAINTHASHSIZE, + &ctl, HASH_ELEM | HASH_BLOBS); + + /* Arrange to flush cache on pg_constraint changes */ + CacheRegisterSyscacheCallback(CONSTROID, + InvalidateConstraintCacheCallBack, + (Datum) 0); + + ctl.keysize = sizeof(RI_QueryKey); + ctl.entrysize = sizeof(RI_QueryHashEntry); + ri_query_cache = hash_create("RI query cache", + RI_INIT_QUERYHASHSIZE, + &ctl, HASH_ELEM | HASH_BLOBS); + + ctl.keysize = sizeof(RI_CompareKey); + ctl.entrysize = sizeof(RI_CompareHashEntry); + ri_compare_cache = hash_create("RI compare cache", + RI_INIT_QUERYHASHSIZE, + &ctl, HASH_ELEM | HASH_BLOBS); +} + + +/* + * ri_FetchPreparedPlan - + * + * Lookup for a query key in our private hash table of prepared + * and saved SPI execution plans. Return the plan if found or NULL. + */ +static SPIPlanPtr +ri_FetchPreparedPlan(RI_QueryKey *key) +{ + RI_QueryHashEntry *entry; + SPIPlanPtr plan; + + /* + * On the first call initialize the hashtable + */ + if (!ri_query_cache) + ri_InitHashTables(); + + /* + * Lookup for the key + */ + entry = (RI_QueryHashEntry *) hash_search(ri_query_cache, + key, + HASH_FIND, NULL); + if (entry == NULL) + return NULL; + + /* + * Check whether the plan is still valid. If it isn't, we don't want to + * simply rely on plancache.c to regenerate it; rather we should start + * from scratch and rebuild the query text too. This is to cover cases + * such as table/column renames. We depend on the plancache machinery to + * detect possible invalidations, though. + * + * CAUTION: this check is only trustworthy if the caller has already + * locked both FK and PK rels. + */ + plan = entry->plan; + if (plan && SPI_plan_is_valid(plan)) + return plan; + + /* + * Otherwise we might as well flush the cached plan now, to free a little + * memory space before we make a new one. + */ + entry->plan = NULL; + if (plan) + SPI_freeplan(plan); + + return NULL; +} + + +/* + * ri_HashPreparedPlan - + * + * Add another plan to our private SPI query plan hashtable. + */ +static void +ri_HashPreparedPlan(RI_QueryKey *key, SPIPlanPtr plan) +{ + RI_QueryHashEntry *entry; + bool found; + + /* + * On the first call initialize the hashtable + */ + if (!ri_query_cache) + ri_InitHashTables(); + + /* + * Add the new plan. We might be overwriting an entry previously found + * invalid by ri_FetchPreparedPlan. + */ + entry = (RI_QueryHashEntry *) hash_search(ri_query_cache, + key, + HASH_ENTER, &found); + Assert(!found || entry->plan == NULL); + entry->plan = plan; +} + + +/* + * ri_KeysEqual - + * + * Check if all key values in OLD and NEW are equal. + * + * Note: at some point we might wish to redefine this as checking for + * "IS NOT DISTINCT" rather than "=", that is, allow two nulls to be + * considered equal. Currently there is no need since all callers have + * previously found at least one of the rows to contain no nulls. + */ +static bool +ri_KeysEqual(Relation rel, TupleTableSlot *oldslot, TupleTableSlot *newslot, + const RI_ConstraintInfo *riinfo, bool rel_is_pk) +{ + const int16 *attnums; + + if (rel_is_pk) + attnums = riinfo->pk_attnums; + else + attnums = riinfo->fk_attnums; + + /* XXX: could be worthwhile to fetch all necessary attrs at once */ + for (int i = 0; i < riinfo->nkeys; i++) + { + Datum oldvalue; + Datum newvalue; + bool isnull; + + /* + * Get one attribute's oldvalue. If it is NULL - they're not equal. + */ + oldvalue = slot_getattr(oldslot, attnums[i], &isnull); + if (isnull) + return false; + + /* + * Get one attribute's newvalue. If it is NULL - they're not equal. + */ + newvalue = slot_getattr(newslot, attnums[i], &isnull); + if (isnull) + return false; + + if (rel_is_pk) + { + /* + * If we are looking at the PK table, then do a bytewise + * comparison. We must propagate PK changes if the value is + * changed to one that "looks" different but would compare as + * equal using the equality operator. This only makes a + * difference for ON UPDATE CASCADE, but for consistency we treat + * all changes to the PK the same. + */ + Form_pg_attribute att = TupleDescAttr(oldslot->tts_tupleDescriptor, attnums[i] - 1); + + if (!datum_image_eq(oldvalue, newvalue, att->attbyval, att->attlen)) + return false; + } + else + { + /* + * For the FK table, compare with the appropriate equality + * operator. Changes that compare equal will still satisfy the + * constraint after the update. + */ + if (!ri_AttributesEqual(riinfo->ff_eq_oprs[i], RIAttType(rel, attnums[i]), + oldvalue, newvalue)) + return false; + } + } + + return true; +} + + +/* + * ri_AttributesEqual - + * + * Call the appropriate equality comparison operator for two values. + * + * NB: we have already checked that neither value is null. + */ +static bool +ri_AttributesEqual(Oid eq_opr, Oid typeid, + Datum oldvalue, Datum newvalue) +{ + RI_CompareHashEntry *entry = ri_HashCompareOp(eq_opr, typeid); + + /* Do we need to cast the values? */ + if (OidIsValid(entry->cast_func_finfo.fn_oid)) + { + oldvalue = FunctionCall3(&entry->cast_func_finfo, + oldvalue, + Int32GetDatum(-1), /* typmod */ + BoolGetDatum(false)); /* implicit coercion */ + newvalue = FunctionCall3(&entry->cast_func_finfo, + newvalue, + Int32GetDatum(-1), /* typmod */ + BoolGetDatum(false)); /* implicit coercion */ + } + + /* + * Apply the comparison operator. + * + * Note: This function is part of a call stack that determines whether an + * update to a row is significant enough that it needs checking or action + * on the other side of a foreign-key constraint. Therefore, the + * comparison here would need to be done with the collation of the *other* + * table. For simplicity (e.g., we might not even have the other table + * open), we'll just use the default collation here, which could lead to + * some false negatives. All this would break if we ever allow + * database-wide collations to be nondeterministic. + */ + return DatumGetBool(FunctionCall2Coll(&entry->eq_opr_finfo, + DEFAULT_COLLATION_OID, + oldvalue, newvalue)); +} + +/* + * ri_HashCompareOp - + * + * See if we know how to compare two values, and create a new hash entry + * if not. + */ +static RI_CompareHashEntry * +ri_HashCompareOp(Oid eq_opr, Oid typeid) +{ + RI_CompareKey key; + RI_CompareHashEntry *entry; + bool found; + + /* + * On the first call initialize the hashtable + */ + if (!ri_compare_cache) + ri_InitHashTables(); + + /* + * Find or create a hash entry. Note we're assuming RI_CompareKey + * contains no struct padding. + */ + key.eq_opr = eq_opr; + key.typeid = typeid; + entry = (RI_CompareHashEntry *) hash_search(ri_compare_cache, + &key, + HASH_ENTER, &found); + if (!found) + entry->valid = false; + + /* + * If not already initialized, do so. Since we'll keep this hash entry + * for the life of the backend, put any subsidiary info for the function + * cache structs into TopMemoryContext. + */ + if (!entry->valid) + { + Oid lefttype, + righttype, + castfunc; + CoercionPathType pathtype; + + /* We always need to know how to call the equality operator */ + fmgr_info_cxt(get_opcode(eq_opr), &entry->eq_opr_finfo, + TopMemoryContext); + + /* + * If we chose to use a cast from FK to PK type, we may have to apply + * the cast function to get to the operator's input type. + * + * XXX eventually it would be good to support array-coercion cases + * here and in ri_AttributesEqual(). At the moment there is no point + * because cases involving nonidentical array types will be rejected + * at constraint creation time. + * + * XXX perhaps also consider supporting CoerceViaIO? No need at the + * moment since that will never be generated for implicit coercions. + */ + op_input_types(eq_opr, &lefttype, &righttype); + Assert(lefttype == righttype); + if (typeid == lefttype) + castfunc = InvalidOid; /* simplest case */ + else + { + pathtype = find_coercion_pathway(lefttype, typeid, + COERCION_IMPLICIT, + &castfunc); + if (pathtype != COERCION_PATH_FUNC && + pathtype != COERCION_PATH_RELABELTYPE) + { + /* + * The declared input type of the eq_opr might be a + * polymorphic type such as ANYARRAY or ANYENUM, or other + * special cases such as RECORD; find_coercion_pathway + * currently doesn't subsume these special cases. + */ + if (!IsBinaryCoercible(typeid, lefttype)) + elog(ERROR, "no conversion function from %s to %s", + format_type_be(typeid), + format_type_be(lefttype)); + } + } + if (OidIsValid(castfunc)) + fmgr_info_cxt(castfunc, &entry->cast_func_finfo, + TopMemoryContext); + else + entry->cast_func_finfo.fn_oid = InvalidOid; + entry->valid = true; + } + + return entry; +} + + +/* + * Given a trigger function OID, determine whether it is an RI trigger, + * and if so whether it is attached to PK or FK relation. + */ +int +RI_FKey_trigger_type(Oid tgfoid) +{ + switch (tgfoid) + { + case F_RI_FKEY_CASCADE_DEL: + case F_RI_FKEY_CASCADE_UPD: + case F_RI_FKEY_RESTRICT_DEL: + case F_RI_FKEY_RESTRICT_UPD: + case F_RI_FKEY_SETNULL_DEL: + case F_RI_FKEY_SETNULL_UPD: + case F_RI_FKEY_SETDEFAULT_DEL: + case F_RI_FKEY_SETDEFAULT_UPD: + case F_RI_FKEY_NOACTION_DEL: + case F_RI_FKEY_NOACTION_UPD: + return RI_TRIGGER_PK; + + case F_RI_FKEY_CHECK_INS: + case F_RI_FKEY_CHECK_UPD: + return RI_TRIGGER_FK; + } + + return RI_TRIGGER_NONE; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/rowtypes.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/rowtypes.c new file mode 100644 index 00000000000..ad176651d85 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/rowtypes.c @@ -0,0 +1,2044 @@ +/*------------------------------------------------------------------------- + * + * rowtypes.c + * I/O and comparison functions for generic composite types. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/adt/rowtypes.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <ctype.h> + +#include "access/detoast.h" +#include "access/htup_details.h" +#include "catalog/pg_type.h" +#include "common/hashfn.h" +#include "funcapi.h" +#include "libpq/pqformat.h" +#include "miscadmin.h" +#include "utils/builtins.h" +#include "utils/datum.h" +#include "utils/lsyscache.h" +#include "utils/typcache.h" + + +/* + * structure to cache metadata needed for record I/O + */ +typedef struct ColumnIOData +{ + Oid column_type; + Oid typiofunc; + Oid typioparam; + bool typisvarlena; + FmgrInfo proc; +} ColumnIOData; + +typedef struct RecordIOData +{ + Oid record_type; + int32 record_typmod; + int ncolumns; + ColumnIOData columns[FLEXIBLE_ARRAY_MEMBER]; +} RecordIOData; + +/* + * structure to cache metadata needed for record comparison + */ +typedef struct ColumnCompareData +{ + TypeCacheEntry *typentry; /* has everything we need, actually */ +} ColumnCompareData; + +typedef struct RecordCompareData +{ + int ncolumns; /* allocated length of columns[] */ + Oid record1_type; + int32 record1_typmod; + Oid record2_type; + int32 record2_typmod; + ColumnCompareData columns[FLEXIBLE_ARRAY_MEMBER]; +} RecordCompareData; + + +/* + * record_in - input routine for any composite type. + */ +Datum +record_in(PG_FUNCTION_ARGS) +{ + char *string = PG_GETARG_CSTRING(0); + Oid tupType = PG_GETARG_OID(1); + int32 tupTypmod = PG_GETARG_INT32(2); + Node *escontext = fcinfo->context; + HeapTupleHeader result; + TupleDesc tupdesc; + HeapTuple tuple; + RecordIOData *my_extra; + bool needComma = false; + int ncolumns; + int i; + char *ptr; + Datum *values; + bool *nulls; + StringInfoData buf; + + check_stack_depth(); /* recurses for record-type columns */ + + /* + * Give a friendly error message if we did not get enough info to identify + * the target record type. (lookup_rowtype_tupdesc would fail anyway, but + * with a non-user-friendly message.) In ordinary SQL usage, we'll get -1 + * for typmod, since composite types and RECORD have no type modifiers at + * the SQL level, and thus must fail for RECORD. However some callers can + * supply a valid typmod, and then we can do something useful for RECORD. + */ + if (tupType == RECORDOID && tupTypmod < 0) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("input of anonymous composite types is not implemented"))); + + /* + * This comes from the composite type's pg_type.oid and stores system oids + * in user tables, specifically DatumTupleFields. This oid must be + * preserved by binary upgrades. + */ + tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); + ncolumns = tupdesc->natts; + + /* + * We arrange to look up the needed I/O info just once per series of + * calls, assuming the record type doesn't change underneath us. + */ + my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra; + if (my_extra == NULL || + my_extra->ncolumns != ncolumns) + { + fcinfo->flinfo->fn_extra = + MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, + offsetof(RecordIOData, columns) + + ncolumns * sizeof(ColumnIOData)); + my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra; + my_extra->record_type = InvalidOid; + my_extra->record_typmod = 0; + } + + if (my_extra->record_type != tupType || + my_extra->record_typmod != tupTypmod) + { + MemSet(my_extra, 0, + offsetof(RecordIOData, columns) + + ncolumns * sizeof(ColumnIOData)); + my_extra->record_type = tupType; + my_extra->record_typmod = tupTypmod; + my_extra->ncolumns = ncolumns; + } + + values = (Datum *) palloc(ncolumns * sizeof(Datum)); + nulls = (bool *) palloc(ncolumns * sizeof(bool)); + + /* + * Scan the string. We use "buf" to accumulate the de-quoted data for + * each column, which is then fed to the appropriate input converter. + */ + ptr = string; + /* Allow leading whitespace */ + while (*ptr && isspace((unsigned char) *ptr)) + ptr++; + if (*ptr++ != '(') + { + errsave(escontext, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed record literal: \"%s\"", string), + errdetail("Missing left parenthesis."))); + goto fail; + } + + initStringInfo(&buf); + + for (i = 0; i < ncolumns; i++) + { + Form_pg_attribute att = TupleDescAttr(tupdesc, i); + ColumnIOData *column_info = &my_extra->columns[i]; + Oid column_type = att->atttypid; + char *column_data; + + /* Ignore dropped columns in datatype, but fill with nulls */ + if (att->attisdropped) + { + values[i] = (Datum) 0; + nulls[i] = true; + continue; + } + + if (needComma) + { + /* Skip comma that separates prior field from this one */ + if (*ptr == ',') + ptr++; + else + /* *ptr must be ')' */ + { + errsave(escontext, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed record literal: \"%s\"", string), + errdetail("Too few columns."))); + goto fail; + } + } + + /* Check for null: completely empty input means null */ + if (*ptr == ',' || *ptr == ')') + { + column_data = NULL; + nulls[i] = true; + } + else + { + /* Extract string for this column */ + bool inquote = false; + + resetStringInfo(&buf); + while (inquote || !(*ptr == ',' || *ptr == ')')) + { + char ch = *ptr++; + + if (ch == '\0') + { + errsave(escontext, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed record literal: \"%s\"", + string), + errdetail("Unexpected end of input."))); + goto fail; + } + if (ch == '\\') + { + if (*ptr == '\0') + { + errsave(escontext, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed record literal: \"%s\"", + string), + errdetail("Unexpected end of input."))); + goto fail; + } + appendStringInfoChar(&buf, *ptr++); + } + else if (ch == '"') + { + if (!inquote) + inquote = true; + else if (*ptr == '"') + { + /* doubled quote within quote sequence */ + appendStringInfoChar(&buf, *ptr++); + } + else + inquote = false; + } + else + appendStringInfoChar(&buf, ch); + } + + column_data = buf.data; + nulls[i] = false; + } + + /* + * Convert the column value + */ + if (column_info->column_type != column_type) + { + getTypeInputInfo(column_type, + &column_info->typiofunc, + &column_info->typioparam); + fmgr_info_cxt(column_info->typiofunc, &column_info->proc, + fcinfo->flinfo->fn_mcxt); + column_info->column_type = column_type; + } + + if (!InputFunctionCallSafe(&column_info->proc, + column_data, + column_info->typioparam, + att->atttypmod, + escontext, + &values[i])) + goto fail; + + /* + * Prep for next column + */ + needComma = true; + } + + if (*ptr++ != ')') + { + errsave(escontext, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed record literal: \"%s\"", string), + errdetail("Too many columns."))); + goto fail; + } + /* Allow trailing whitespace */ + while (*ptr && isspace((unsigned char) *ptr)) + ptr++; + if (*ptr) + { + errsave(escontext, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed record literal: \"%s\"", string), + errdetail("Junk after right parenthesis."))); + goto fail; + } + + tuple = heap_form_tuple(tupdesc, values, nulls); + + /* + * We cannot return tuple->t_data because heap_form_tuple allocates it as + * part of a larger chunk, and our caller may expect to be able to pfree + * our result. So must copy the info into a new palloc chunk. + */ + result = (HeapTupleHeader) palloc(tuple->t_len); + memcpy(result, tuple->t_data, tuple->t_len); + + heap_freetuple(tuple); + pfree(buf.data); + pfree(values); + pfree(nulls); + ReleaseTupleDesc(tupdesc); + + PG_RETURN_HEAPTUPLEHEADER(result); + + /* exit here once we've done lookup_rowtype_tupdesc */ +fail: + ReleaseTupleDesc(tupdesc); + PG_RETURN_NULL(); +} + +/* + * record_out - output routine for any composite type. + */ +Datum +record_out(PG_FUNCTION_ARGS) +{ + HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(0); + Oid tupType; + int32 tupTypmod; + TupleDesc tupdesc; + HeapTupleData tuple; + RecordIOData *my_extra; + bool needComma = false; + int ncolumns; + int i; + Datum *values; + bool *nulls; + StringInfoData buf; + + check_stack_depth(); /* recurses for record-type columns */ + + /* Extract type info from the tuple itself */ + tupType = HeapTupleHeaderGetTypeId(rec); + tupTypmod = HeapTupleHeaderGetTypMod(rec); + tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); + ncolumns = tupdesc->natts; + + /* Build a temporary HeapTuple control structure */ + tuple.t_len = HeapTupleHeaderGetDatumLength(rec); + ItemPointerSetInvalid(&(tuple.t_self)); + tuple.t_tableOid = InvalidOid; + tuple.t_data = rec; + + /* + * We arrange to look up the needed I/O info just once per series of + * calls, assuming the record type doesn't change underneath us. + */ + my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra; + if (my_extra == NULL || + my_extra->ncolumns != ncolumns) + { + fcinfo->flinfo->fn_extra = + MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, + offsetof(RecordIOData, columns) + + ncolumns * sizeof(ColumnIOData)); + my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra; + my_extra->record_type = InvalidOid; + my_extra->record_typmod = 0; + } + + if (my_extra->record_type != tupType || + my_extra->record_typmod != tupTypmod) + { + MemSet(my_extra, 0, + offsetof(RecordIOData, columns) + + ncolumns * sizeof(ColumnIOData)); + my_extra->record_type = tupType; + my_extra->record_typmod = tupTypmod; + my_extra->ncolumns = ncolumns; + } + + values = (Datum *) palloc(ncolumns * sizeof(Datum)); + nulls = (bool *) palloc(ncolumns * sizeof(bool)); + + /* Break down the tuple into fields */ + heap_deform_tuple(&tuple, tupdesc, values, nulls); + + /* And build the result string */ + initStringInfo(&buf); + + appendStringInfoChar(&buf, '('); + + for (i = 0; i < ncolumns; i++) + { + Form_pg_attribute att = TupleDescAttr(tupdesc, i); + ColumnIOData *column_info = &my_extra->columns[i]; + Oid column_type = att->atttypid; + Datum attr; + char *value; + char *tmp; + bool nq; + + /* Ignore dropped columns in datatype */ + if (att->attisdropped) + continue; + + if (needComma) + appendStringInfoChar(&buf, ','); + needComma = true; + + if (nulls[i]) + { + /* emit nothing... */ + continue; + } + + /* + * Convert the column value to text + */ + if (column_info->column_type != column_type) + { + getTypeOutputInfo(column_type, + &column_info->typiofunc, + &column_info->typisvarlena); + fmgr_info_cxt(column_info->typiofunc, &column_info->proc, + fcinfo->flinfo->fn_mcxt); + column_info->column_type = column_type; + } + + attr = values[i]; + value = OutputFunctionCall(&column_info->proc, attr); + + /* Detect whether we need double quotes for this value */ + nq = (value[0] == '\0'); /* force quotes for empty string */ + for (tmp = value; *tmp; tmp++) + { + char ch = *tmp; + + if (ch == '"' || ch == '\\' || + ch == '(' || ch == ')' || ch == ',' || + isspace((unsigned char) ch)) + { + nq = true; + break; + } + } + + /* And emit the string */ + if (nq) + appendStringInfoCharMacro(&buf, '"'); + for (tmp = value; *tmp; tmp++) + { + char ch = *tmp; + + if (ch == '"' || ch == '\\') + appendStringInfoCharMacro(&buf, ch); + appendStringInfoCharMacro(&buf, ch); + } + if (nq) + appendStringInfoCharMacro(&buf, '"'); + } + + appendStringInfoChar(&buf, ')'); + + pfree(values); + pfree(nulls); + ReleaseTupleDesc(tupdesc); + + PG_RETURN_CSTRING(buf.data); +} + +/* + * record_recv - binary input routine for any composite type. + */ +Datum +record_recv(PG_FUNCTION_ARGS) +{ + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + Oid tupType = PG_GETARG_OID(1); + int32 tupTypmod = PG_GETARG_INT32(2); + HeapTupleHeader result; + TupleDesc tupdesc; + HeapTuple tuple; + RecordIOData *my_extra; + int ncolumns; + int usercols; + int validcols; + int i; + Datum *values; + bool *nulls; + + check_stack_depth(); /* recurses for record-type columns */ + + /* + * Give a friendly error message if we did not get enough info to identify + * the target record type. (lookup_rowtype_tupdesc would fail anyway, but + * with a non-user-friendly message.) In ordinary SQL usage, we'll get -1 + * for typmod, since composite types and RECORD have no type modifiers at + * the SQL level, and thus must fail for RECORD. However some callers can + * supply a valid typmod, and then we can do something useful for RECORD. + */ + if (tupType == RECORDOID && tupTypmod < 0) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("input of anonymous composite types is not implemented"))); + + tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); + ncolumns = tupdesc->natts; + + /* + * We arrange to look up the needed I/O info just once per series of + * calls, assuming the record type doesn't change underneath us. + */ + my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra; + if (my_extra == NULL || + my_extra->ncolumns != ncolumns) + { + fcinfo->flinfo->fn_extra = + MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, + offsetof(RecordIOData, columns) + + ncolumns * sizeof(ColumnIOData)); + my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra; + my_extra->record_type = InvalidOid; + my_extra->record_typmod = 0; + } + + if (my_extra->record_type != tupType || + my_extra->record_typmod != tupTypmod) + { + MemSet(my_extra, 0, + offsetof(RecordIOData, columns) + + ncolumns * sizeof(ColumnIOData)); + my_extra->record_type = tupType; + my_extra->record_typmod = tupTypmod; + my_extra->ncolumns = ncolumns; + } + + values = (Datum *) palloc(ncolumns * sizeof(Datum)); + nulls = (bool *) palloc(ncolumns * sizeof(bool)); + + /* Fetch number of columns user thinks it has */ + usercols = pq_getmsgint(buf, 4); + + /* Need to scan to count nondeleted columns */ + validcols = 0; + for (i = 0; i < ncolumns; i++) + { + if (!TupleDescAttr(tupdesc, i)->attisdropped) + validcols++; + } + if (usercols != validcols) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("wrong number of columns: %d, expected %d", + usercols, validcols))); + + /* Process each column */ + for (i = 0; i < ncolumns; i++) + { + Form_pg_attribute att = TupleDescAttr(tupdesc, i); + ColumnIOData *column_info = &my_extra->columns[i]; + Oid column_type = att->atttypid; + Oid coltypoid; + int itemlen; + StringInfoData item_buf; + StringInfo bufptr; + char csave; + + /* Ignore dropped columns in datatype, but fill with nulls */ + if (att->attisdropped) + { + values[i] = (Datum) 0; + nulls[i] = true; + continue; + } + + /* Check column type recorded in the data */ + coltypoid = pq_getmsgint(buf, sizeof(Oid)); + + /* + * From a security standpoint, it doesn't matter whether the input's + * column type matches what we expect: the column type's receive + * function has to be robust enough to cope with invalid data. + * However, from a user-friendliness standpoint, it's nicer to + * complain about type mismatches than to throw "improper binary + * format" errors. But there's a problem: only built-in types have + * OIDs that are stable enough to believe that a mismatch is a real + * issue. So complain only if both OIDs are in the built-in range. + * Otherwise, carry on with the column type we "should" be getting. + */ + if (coltypoid != column_type && + coltypoid < FirstGenbkiObjectId && + column_type < FirstGenbkiObjectId) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("binary data has type %u (%s) instead of expected %u (%s) in record column %d", + coltypoid, + format_type_extended(coltypoid, -1, + FORMAT_TYPE_ALLOW_INVALID), + column_type, + format_type_extended(column_type, -1, + FORMAT_TYPE_ALLOW_INVALID), + i + 1))); + + /* Get and check the item length */ + itemlen = pq_getmsgint(buf, 4); + if (itemlen < -1 || itemlen > (buf->len - buf->cursor)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION), + errmsg("insufficient data left in message"))); + + if (itemlen == -1) + { + /* -1 length means NULL */ + bufptr = NULL; + nulls[i] = true; + csave = 0; /* keep compiler quiet */ + } + else + { + /* + * Rather than copying data around, we just set up a phony + * StringInfo pointing to the correct portion of the input buffer. + * We assume we can scribble on the input buffer so as to maintain + * the convention that StringInfos have a trailing null. + */ + item_buf.data = &buf->data[buf->cursor]; + item_buf.maxlen = itemlen + 1; + item_buf.len = itemlen; + item_buf.cursor = 0; + + buf->cursor += itemlen; + + csave = buf->data[buf->cursor]; + buf->data[buf->cursor] = '\0'; + + bufptr = &item_buf; + nulls[i] = false; + } + + /* Now call the column's receiveproc */ + if (column_info->column_type != column_type) + { + getTypeBinaryInputInfo(column_type, + &column_info->typiofunc, + &column_info->typioparam); + fmgr_info_cxt(column_info->typiofunc, &column_info->proc, + fcinfo->flinfo->fn_mcxt); + column_info->column_type = column_type; + } + + values[i] = ReceiveFunctionCall(&column_info->proc, + bufptr, + column_info->typioparam, + att->atttypmod); + + if (bufptr) + { + /* Trouble if it didn't eat the whole buffer */ + if (item_buf.cursor != itemlen) + ereport(ERROR, + (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION), + errmsg("improper binary format in record column %d", + i + 1))); + + buf->data[buf->cursor] = csave; + } + } + + tuple = heap_form_tuple(tupdesc, values, nulls); + + /* + * We cannot return tuple->t_data because heap_form_tuple allocates it as + * part of a larger chunk, and our caller may expect to be able to pfree + * our result. So must copy the info into a new palloc chunk. + */ + result = (HeapTupleHeader) palloc(tuple->t_len); + memcpy(result, tuple->t_data, tuple->t_len); + + heap_freetuple(tuple); + pfree(values); + pfree(nulls); + ReleaseTupleDesc(tupdesc); + + PG_RETURN_HEAPTUPLEHEADER(result); +} + +/* + * record_send - binary output routine for any composite type. + */ +Datum +record_send(PG_FUNCTION_ARGS) +{ + HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(0); + Oid tupType; + int32 tupTypmod; + TupleDesc tupdesc; + HeapTupleData tuple; + RecordIOData *my_extra; + int ncolumns; + int validcols; + int i; + Datum *values; + bool *nulls; + StringInfoData buf; + + check_stack_depth(); /* recurses for record-type columns */ + + /* Extract type info from the tuple itself */ + tupType = HeapTupleHeaderGetTypeId(rec); + tupTypmod = HeapTupleHeaderGetTypMod(rec); + tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); + ncolumns = tupdesc->natts; + + /* Build a temporary HeapTuple control structure */ + tuple.t_len = HeapTupleHeaderGetDatumLength(rec); + ItemPointerSetInvalid(&(tuple.t_self)); + tuple.t_tableOid = InvalidOid; + tuple.t_data = rec; + + /* + * We arrange to look up the needed I/O info just once per series of + * calls, assuming the record type doesn't change underneath us. + */ + my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra; + if (my_extra == NULL || + my_extra->ncolumns != ncolumns) + { + fcinfo->flinfo->fn_extra = + MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, + offsetof(RecordIOData, columns) + + ncolumns * sizeof(ColumnIOData)); + my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra; + my_extra->record_type = InvalidOid; + my_extra->record_typmod = 0; + } + + if (my_extra->record_type != tupType || + my_extra->record_typmod != tupTypmod) + { + MemSet(my_extra, 0, + offsetof(RecordIOData, columns) + + ncolumns * sizeof(ColumnIOData)); + my_extra->record_type = tupType; + my_extra->record_typmod = tupTypmod; + my_extra->ncolumns = ncolumns; + } + + values = (Datum *) palloc(ncolumns * sizeof(Datum)); + nulls = (bool *) palloc(ncolumns * sizeof(bool)); + + /* Break down the tuple into fields */ + heap_deform_tuple(&tuple, tupdesc, values, nulls); + + /* And build the result string */ + pq_begintypsend(&buf); + + /* Need to scan to count nondeleted columns */ + validcols = 0; + for (i = 0; i < ncolumns; i++) + { + if (!TupleDescAttr(tupdesc, i)->attisdropped) + validcols++; + } + pq_sendint32(&buf, validcols); + + for (i = 0; i < ncolumns; i++) + { + Form_pg_attribute att = TupleDescAttr(tupdesc, i); + ColumnIOData *column_info = &my_extra->columns[i]; + Oid column_type = att->atttypid; + Datum attr; + bytea *outputbytes; + + /* Ignore dropped columns in datatype */ + if (att->attisdropped) + continue; + + pq_sendint32(&buf, column_type); + + if (nulls[i]) + { + /* emit -1 data length to signify a NULL */ + pq_sendint32(&buf, -1); + continue; + } + + /* + * Convert the column value to binary + */ + if (column_info->column_type != column_type) + { + getTypeBinaryOutputInfo(column_type, + &column_info->typiofunc, + &column_info->typisvarlena); + fmgr_info_cxt(column_info->typiofunc, &column_info->proc, + fcinfo->flinfo->fn_mcxt); + column_info->column_type = column_type; + } + + attr = values[i]; + outputbytes = SendFunctionCall(&column_info->proc, attr); + pq_sendint32(&buf, VARSIZE(outputbytes) - VARHDRSZ); + pq_sendbytes(&buf, VARDATA(outputbytes), + VARSIZE(outputbytes) - VARHDRSZ); + } + + pfree(values); + pfree(nulls); + ReleaseTupleDesc(tupdesc); + + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} + + +/* + * record_cmp() + * Internal comparison function for records. + * + * Returns -1, 0 or 1 + * + * Do not assume that the two inputs are exactly the same record type; + * for instance we might be comparing an anonymous ROW() construct against a + * named composite type. We will compare as long as they have the same number + * of non-dropped columns of the same types. + */ +static int +record_cmp(FunctionCallInfo fcinfo) +{ + HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0); + HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1); + int result = 0; + Oid tupType1; + Oid tupType2; + int32 tupTypmod1; + int32 tupTypmod2; + TupleDesc tupdesc1; + TupleDesc tupdesc2; + HeapTupleData tuple1; + HeapTupleData tuple2; + int ncolumns1; + int ncolumns2; + RecordCompareData *my_extra; + int ncols; + Datum *values1; + Datum *values2; + bool *nulls1; + bool *nulls2; + int i1; + int i2; + int j; + + check_stack_depth(); /* recurses for record-type columns */ + + /* Extract type info from the tuples */ + tupType1 = HeapTupleHeaderGetTypeId(record1); + tupTypmod1 = HeapTupleHeaderGetTypMod(record1); + tupdesc1 = lookup_rowtype_tupdesc(tupType1, tupTypmod1); + ncolumns1 = tupdesc1->natts; + tupType2 = HeapTupleHeaderGetTypeId(record2); + tupTypmod2 = HeapTupleHeaderGetTypMod(record2); + tupdesc2 = lookup_rowtype_tupdesc(tupType2, tupTypmod2); + ncolumns2 = tupdesc2->natts; + + /* Build temporary HeapTuple control structures */ + tuple1.t_len = HeapTupleHeaderGetDatumLength(record1); + ItemPointerSetInvalid(&(tuple1.t_self)); + tuple1.t_tableOid = InvalidOid; + tuple1.t_data = record1; + tuple2.t_len = HeapTupleHeaderGetDatumLength(record2); + ItemPointerSetInvalid(&(tuple2.t_self)); + tuple2.t_tableOid = InvalidOid; + tuple2.t_data = record2; + + /* + * We arrange to look up the needed comparison info just once per series + * of calls, assuming the record types don't change underneath us. + */ + ncols = Max(ncolumns1, ncolumns2); + my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra; + if (my_extra == NULL || + my_extra->ncolumns < ncols) + { + fcinfo->flinfo->fn_extra = + MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, + offsetof(RecordCompareData, columns) + + ncols * sizeof(ColumnCompareData)); + my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra; + my_extra->ncolumns = ncols; + my_extra->record1_type = InvalidOid; + my_extra->record1_typmod = 0; + my_extra->record2_type = InvalidOid; + my_extra->record2_typmod = 0; + } + + if (my_extra->record1_type != tupType1 || + my_extra->record1_typmod != tupTypmod1 || + my_extra->record2_type != tupType2 || + my_extra->record2_typmod != tupTypmod2) + { + MemSet(my_extra->columns, 0, ncols * sizeof(ColumnCompareData)); + my_extra->record1_type = tupType1; + my_extra->record1_typmod = tupTypmod1; + my_extra->record2_type = tupType2; + my_extra->record2_typmod = tupTypmod2; + } + + /* Break down the tuples into fields */ + values1 = (Datum *) palloc(ncolumns1 * sizeof(Datum)); + nulls1 = (bool *) palloc(ncolumns1 * sizeof(bool)); + heap_deform_tuple(&tuple1, tupdesc1, values1, nulls1); + values2 = (Datum *) palloc(ncolumns2 * sizeof(Datum)); + nulls2 = (bool *) palloc(ncolumns2 * sizeof(bool)); + heap_deform_tuple(&tuple2, tupdesc2, values2, nulls2); + + /* + * Scan corresponding columns, allowing for dropped columns in different + * places in the two rows. i1 and i2 are physical column indexes, j is + * the logical column index. + */ + i1 = i2 = j = 0; + while (i1 < ncolumns1 || i2 < ncolumns2) + { + Form_pg_attribute att1; + Form_pg_attribute att2; + TypeCacheEntry *typentry; + Oid collation; + + /* + * Skip dropped columns + */ + if (i1 < ncolumns1 && TupleDescAttr(tupdesc1, i1)->attisdropped) + { + i1++; + continue; + } + if (i2 < ncolumns2 && TupleDescAttr(tupdesc2, i2)->attisdropped) + { + i2++; + continue; + } + if (i1 >= ncolumns1 || i2 >= ncolumns2) + break; /* we'll deal with mismatch below loop */ + + att1 = TupleDescAttr(tupdesc1, i1); + att2 = TupleDescAttr(tupdesc2, i2); + + /* + * Have two matching columns, they must be same type + */ + if (att1->atttypid != att2->atttypid) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare dissimilar column types %s and %s at record column %d", + format_type_be(att1->atttypid), + format_type_be(att2->atttypid), + j + 1))); + + /* + * If they're not same collation, we don't complain here, but the + * comparison function might. + */ + collation = att1->attcollation; + if (collation != att2->attcollation) + collation = InvalidOid; + + /* + * Lookup the comparison function if not done already + */ + typentry = my_extra->columns[j].typentry; + if (typentry == NULL || + typentry->type_id != att1->atttypid) + { + typentry = lookup_type_cache(att1->atttypid, + TYPECACHE_CMP_PROC_FINFO); + if (!OidIsValid(typentry->cmp_proc_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify a comparison function for type %s", + format_type_be(typentry->type_id)))); + my_extra->columns[j].typentry = typentry; + } + + /* + * We consider two NULLs equal; NULL > not-NULL. + */ + if (!nulls1[i1] || !nulls2[i2]) + { + LOCAL_FCINFO(locfcinfo, 2); + int32 cmpresult; + + if (nulls1[i1]) + { + /* arg1 is greater than arg2 */ + result = 1; + break; + } + if (nulls2[i2]) + { + /* arg1 is less than arg2 */ + result = -1; + break; + } + + /* Compare the pair of elements */ + InitFunctionCallInfoData(*locfcinfo, &typentry->cmp_proc_finfo, 2, + collation, NULL, NULL); + locfcinfo->args[0].value = values1[i1]; + locfcinfo->args[0].isnull = false; + locfcinfo->args[1].value = values2[i2]; + locfcinfo->args[1].isnull = false; + cmpresult = DatumGetInt32(FunctionCallInvoke(locfcinfo)); + + /* We don't expect comparison support functions to return null */ + Assert(!locfcinfo->isnull); + + if (cmpresult < 0) + { + /* arg1 is less than arg2 */ + result = -1; + break; + } + else if (cmpresult > 0) + { + /* arg1 is greater than arg2 */ + result = 1; + break; + } + } + + /* equal, so continue to next column */ + i1++, i2++, j++; + } + + /* + * If we didn't break out of the loop early, check for column count + * mismatch. (We do not report such mismatch if we found unequal column + * values; is that a feature or a bug?) + */ + if (result == 0) + { + if (i1 != ncolumns1 || i2 != ncolumns2) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare record types with different numbers of columns"))); + } + + pfree(values1); + pfree(nulls1); + pfree(values2); + pfree(nulls2); + ReleaseTupleDesc(tupdesc1); + ReleaseTupleDesc(tupdesc2); + + /* Avoid leaking memory when handed toasted input. */ + PG_FREE_IF_COPY(record1, 0); + PG_FREE_IF_COPY(record2, 1); + + return result; +} + +/* + * record_eq : + * compares two records for equality + * result : + * returns true if the records are equal, false otherwise. + * + * Note: we do not use record_cmp here, since equality may be meaningful in + * datatypes that don't have a total ordering (and hence no btree support). + */ +Datum +record_eq(PG_FUNCTION_ARGS) +{ + HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0); + HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1); + bool result = true; + Oid tupType1; + Oid tupType2; + int32 tupTypmod1; + int32 tupTypmod2; + TupleDesc tupdesc1; + TupleDesc tupdesc2; + HeapTupleData tuple1; + HeapTupleData tuple2; + int ncolumns1; + int ncolumns2; + RecordCompareData *my_extra; + int ncols; + Datum *values1; + Datum *values2; + bool *nulls1; + bool *nulls2; + int i1; + int i2; + int j; + + check_stack_depth(); /* recurses for record-type columns */ + + /* Extract type info from the tuples */ + tupType1 = HeapTupleHeaderGetTypeId(record1); + tupTypmod1 = HeapTupleHeaderGetTypMod(record1); + tupdesc1 = lookup_rowtype_tupdesc(tupType1, tupTypmod1); + ncolumns1 = tupdesc1->natts; + tupType2 = HeapTupleHeaderGetTypeId(record2); + tupTypmod2 = HeapTupleHeaderGetTypMod(record2); + tupdesc2 = lookup_rowtype_tupdesc(tupType2, tupTypmod2); + ncolumns2 = tupdesc2->natts; + + /* Build temporary HeapTuple control structures */ + tuple1.t_len = HeapTupleHeaderGetDatumLength(record1); + ItemPointerSetInvalid(&(tuple1.t_self)); + tuple1.t_tableOid = InvalidOid; + tuple1.t_data = record1; + tuple2.t_len = HeapTupleHeaderGetDatumLength(record2); + ItemPointerSetInvalid(&(tuple2.t_self)); + tuple2.t_tableOid = InvalidOid; + tuple2.t_data = record2; + + /* + * We arrange to look up the needed comparison info just once per series + * of calls, assuming the record types don't change underneath us. + */ + ncols = Max(ncolumns1, ncolumns2); + my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra; + if (my_extra == NULL || + my_extra->ncolumns < ncols) + { + fcinfo->flinfo->fn_extra = + MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, + offsetof(RecordCompareData, columns) + + ncols * sizeof(ColumnCompareData)); + my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra; + my_extra->ncolumns = ncols; + my_extra->record1_type = InvalidOid; + my_extra->record1_typmod = 0; + my_extra->record2_type = InvalidOid; + my_extra->record2_typmod = 0; + } + + if (my_extra->record1_type != tupType1 || + my_extra->record1_typmod != tupTypmod1 || + my_extra->record2_type != tupType2 || + my_extra->record2_typmod != tupTypmod2) + { + MemSet(my_extra->columns, 0, ncols * sizeof(ColumnCompareData)); + my_extra->record1_type = tupType1; + my_extra->record1_typmod = tupTypmod1; + my_extra->record2_type = tupType2; + my_extra->record2_typmod = tupTypmod2; + } + + /* Break down the tuples into fields */ + values1 = (Datum *) palloc(ncolumns1 * sizeof(Datum)); + nulls1 = (bool *) palloc(ncolumns1 * sizeof(bool)); + heap_deform_tuple(&tuple1, tupdesc1, values1, nulls1); + values2 = (Datum *) palloc(ncolumns2 * sizeof(Datum)); + nulls2 = (bool *) palloc(ncolumns2 * sizeof(bool)); + heap_deform_tuple(&tuple2, tupdesc2, values2, nulls2); + + /* + * Scan corresponding columns, allowing for dropped columns in different + * places in the two rows. i1 and i2 are physical column indexes, j is + * the logical column index. + */ + i1 = i2 = j = 0; + while (i1 < ncolumns1 || i2 < ncolumns2) + { + LOCAL_FCINFO(locfcinfo, 2); + Form_pg_attribute att1; + Form_pg_attribute att2; + TypeCacheEntry *typentry; + Oid collation; + bool oprresult; + + /* + * Skip dropped columns + */ + if (i1 < ncolumns1 && TupleDescAttr(tupdesc1, i1)->attisdropped) + { + i1++; + continue; + } + if (i2 < ncolumns2 && TupleDescAttr(tupdesc2, i2)->attisdropped) + { + i2++; + continue; + } + if (i1 >= ncolumns1 || i2 >= ncolumns2) + break; /* we'll deal with mismatch below loop */ + + att1 = TupleDescAttr(tupdesc1, i1); + att2 = TupleDescAttr(tupdesc2, i2); + + /* + * Have two matching columns, they must be same type + */ + if (att1->atttypid != att2->atttypid) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare dissimilar column types %s and %s at record column %d", + format_type_be(att1->atttypid), + format_type_be(att2->atttypid), + j + 1))); + + /* + * If they're not same collation, we don't complain here, but the + * equality function might. + */ + collation = att1->attcollation; + if (collation != att2->attcollation) + collation = InvalidOid; + + /* + * Lookup the equality function if not done already + */ + typentry = my_extra->columns[j].typentry; + if (typentry == NULL || + typentry->type_id != att1->atttypid) + { + typentry = lookup_type_cache(att1->atttypid, + TYPECACHE_EQ_OPR_FINFO); + if (!OidIsValid(typentry->eq_opr_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify an equality operator for type %s", + format_type_be(typentry->type_id)))); + my_extra->columns[j].typentry = typentry; + } + + /* + * We consider two NULLs equal; NULL > not-NULL. + */ + if (!nulls1[i1] || !nulls2[i2]) + { + if (nulls1[i1] || nulls2[i2]) + { + result = false; + break; + } + + /* Compare the pair of elements */ + InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2, + collation, NULL, NULL); + locfcinfo->args[0].value = values1[i1]; + locfcinfo->args[0].isnull = false; + locfcinfo->args[1].value = values2[i2]; + locfcinfo->args[1].isnull = false; + oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo)); + if (locfcinfo->isnull || !oprresult) + { + result = false; + break; + } + } + + /* equal, so continue to next column */ + i1++, i2++, j++; + } + + /* + * If we didn't break out of the loop early, check for column count + * mismatch. (We do not report such mismatch if we found unequal column + * values; is that a feature or a bug?) + */ + if (result) + { + if (i1 != ncolumns1 || i2 != ncolumns2) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare record types with different numbers of columns"))); + } + + pfree(values1); + pfree(nulls1); + pfree(values2); + pfree(nulls2); + ReleaseTupleDesc(tupdesc1); + ReleaseTupleDesc(tupdesc2); + + /* Avoid leaking memory when handed toasted input. */ + PG_FREE_IF_COPY(record1, 0); + PG_FREE_IF_COPY(record2, 1); + + PG_RETURN_BOOL(result); +} + +Datum +record_ne(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(!DatumGetBool(record_eq(fcinfo))); +} + +Datum +record_lt(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(record_cmp(fcinfo) < 0); +} + +Datum +record_gt(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(record_cmp(fcinfo) > 0); +} + +Datum +record_le(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(record_cmp(fcinfo) <= 0); +} + +Datum +record_ge(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(record_cmp(fcinfo) >= 0); +} + +Datum +btrecordcmp(PG_FUNCTION_ARGS) +{ + PG_RETURN_INT32(record_cmp(fcinfo)); +} + + +/* + * record_image_cmp : + * Internal byte-oriented comparison function for records. + * + * Returns -1, 0 or 1 + * + * Note: The normal concepts of "equality" do not apply here; different + * representation of values considered to be equal are not considered to be + * identical. As an example, for the citext type 'A' and 'a' are equal, but + * they are not identical. + */ +static int +record_image_cmp(FunctionCallInfo fcinfo) +{ + HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0); + HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1); + int result = 0; + Oid tupType1; + Oid tupType2; + int32 tupTypmod1; + int32 tupTypmod2; + TupleDesc tupdesc1; + TupleDesc tupdesc2; + HeapTupleData tuple1; + HeapTupleData tuple2; + int ncolumns1; + int ncolumns2; + RecordCompareData *my_extra; + int ncols; + Datum *values1; + Datum *values2; + bool *nulls1; + bool *nulls2; + int i1; + int i2; + int j; + + /* Extract type info from the tuples */ + tupType1 = HeapTupleHeaderGetTypeId(record1); + tupTypmod1 = HeapTupleHeaderGetTypMod(record1); + tupdesc1 = lookup_rowtype_tupdesc(tupType1, tupTypmod1); + ncolumns1 = tupdesc1->natts; + tupType2 = HeapTupleHeaderGetTypeId(record2); + tupTypmod2 = HeapTupleHeaderGetTypMod(record2); + tupdesc2 = lookup_rowtype_tupdesc(tupType2, tupTypmod2); + ncolumns2 = tupdesc2->natts; + + /* Build temporary HeapTuple control structures */ + tuple1.t_len = HeapTupleHeaderGetDatumLength(record1); + ItemPointerSetInvalid(&(tuple1.t_self)); + tuple1.t_tableOid = InvalidOid; + tuple1.t_data = record1; + tuple2.t_len = HeapTupleHeaderGetDatumLength(record2); + ItemPointerSetInvalid(&(tuple2.t_self)); + tuple2.t_tableOid = InvalidOid; + tuple2.t_data = record2; + + /* + * We arrange to look up the needed comparison info just once per series + * of calls, assuming the record types don't change underneath us. + */ + ncols = Max(ncolumns1, ncolumns2); + my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra; + if (my_extra == NULL || + my_extra->ncolumns < ncols) + { + fcinfo->flinfo->fn_extra = + MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, + offsetof(RecordCompareData, columns) + + ncols * sizeof(ColumnCompareData)); + my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra; + my_extra->ncolumns = ncols; + my_extra->record1_type = InvalidOid; + my_extra->record1_typmod = 0; + my_extra->record2_type = InvalidOid; + my_extra->record2_typmod = 0; + } + + if (my_extra->record1_type != tupType1 || + my_extra->record1_typmod != tupTypmod1 || + my_extra->record2_type != tupType2 || + my_extra->record2_typmod != tupTypmod2) + { + MemSet(my_extra->columns, 0, ncols * sizeof(ColumnCompareData)); + my_extra->record1_type = tupType1; + my_extra->record1_typmod = tupTypmod1; + my_extra->record2_type = tupType2; + my_extra->record2_typmod = tupTypmod2; + } + + /* Break down the tuples into fields */ + values1 = (Datum *) palloc(ncolumns1 * sizeof(Datum)); + nulls1 = (bool *) palloc(ncolumns1 * sizeof(bool)); + heap_deform_tuple(&tuple1, tupdesc1, values1, nulls1); + values2 = (Datum *) palloc(ncolumns2 * sizeof(Datum)); + nulls2 = (bool *) palloc(ncolumns2 * sizeof(bool)); + heap_deform_tuple(&tuple2, tupdesc2, values2, nulls2); + + /* + * Scan corresponding columns, allowing for dropped columns in different + * places in the two rows. i1 and i2 are physical column indexes, j is + * the logical column index. + */ + i1 = i2 = j = 0; + while (i1 < ncolumns1 || i2 < ncolumns2) + { + Form_pg_attribute att1; + Form_pg_attribute att2; + + /* + * Skip dropped columns + */ + if (i1 < ncolumns1 && TupleDescAttr(tupdesc1, i1)->attisdropped) + { + i1++; + continue; + } + if (i2 < ncolumns2 && TupleDescAttr(tupdesc2, i2)->attisdropped) + { + i2++; + continue; + } + if (i1 >= ncolumns1 || i2 >= ncolumns2) + break; /* we'll deal with mismatch below loop */ + + att1 = TupleDescAttr(tupdesc1, i1); + att2 = TupleDescAttr(tupdesc2, i2); + + /* + * Have two matching columns, they must be same type + */ + if (att1->atttypid != att2->atttypid) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare dissimilar column types %s and %s at record column %d", + format_type_be(att1->atttypid), + format_type_be(att2->atttypid), + j + 1))); + + /* + * The same type should have the same length (or both should be + * variable). + */ + Assert(att1->attlen == att2->attlen); + + /* + * We consider two NULLs equal; NULL > not-NULL. + */ + if (!nulls1[i1] || !nulls2[i2]) + { + int cmpresult = 0; + + if (nulls1[i1]) + { + /* arg1 is greater than arg2 */ + result = 1; + break; + } + if (nulls2[i2]) + { + /* arg1 is less than arg2 */ + result = -1; + break; + } + + /* Compare the pair of elements */ + if (att1->attbyval) + { + if (values1[i1] != values2[i2]) + cmpresult = (values1[i1] < values2[i2]) ? -1 : 1; + } + else if (att1->attlen > 0) + { + cmpresult = memcmp(DatumGetPointer(values1[i1]), + DatumGetPointer(values2[i2]), + att1->attlen); + } + else if (att1->attlen == -1) + { + Size len1, + len2; + struct varlena *arg1val; + struct varlena *arg2val; + + len1 = toast_raw_datum_size(values1[i1]); + len2 = toast_raw_datum_size(values2[i2]); + arg1val = PG_DETOAST_DATUM_PACKED(values1[i1]); + arg2val = PG_DETOAST_DATUM_PACKED(values2[i2]); + + cmpresult = memcmp(VARDATA_ANY(arg1val), + VARDATA_ANY(arg2val), + Min(len1, len2) - VARHDRSZ); + if ((cmpresult == 0) && (len1 != len2)) + cmpresult = (len1 < len2) ? -1 : 1; + + if ((Pointer) arg1val != (Pointer) values1[i1]) + pfree(arg1val); + if ((Pointer) arg2val != (Pointer) values2[i2]) + pfree(arg2val); + } + else + elog(ERROR, "unexpected attlen: %d", att1->attlen); + + if (cmpresult < 0) + { + /* arg1 is less than arg2 */ + result = -1; + break; + } + else if (cmpresult > 0) + { + /* arg1 is greater than arg2 */ + result = 1; + break; + } + } + + /* equal, so continue to next column */ + i1++, i2++, j++; + } + + /* + * If we didn't break out of the loop early, check for column count + * mismatch. (We do not report such mismatch if we found unequal column + * values; is that a feature or a bug?) + */ + if (result == 0) + { + if (i1 != ncolumns1 || i2 != ncolumns2) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare record types with different numbers of columns"))); + } + + pfree(values1); + pfree(nulls1); + pfree(values2); + pfree(nulls2); + ReleaseTupleDesc(tupdesc1); + ReleaseTupleDesc(tupdesc2); + + /* Avoid leaking memory when handed toasted input. */ + PG_FREE_IF_COPY(record1, 0); + PG_FREE_IF_COPY(record2, 1); + + return result; +} + +/* + * record_image_eq : + * compares two records for identical contents, based on byte images + * result : + * returns true if the records are identical, false otherwise. + * + * Note: we do not use record_image_cmp here, since we can avoid + * de-toasting for unequal lengths this way. + */ +Datum +record_image_eq(PG_FUNCTION_ARGS) +{ + HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0); + HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1); + bool result = true; + Oid tupType1; + Oid tupType2; + int32 tupTypmod1; + int32 tupTypmod2; + TupleDesc tupdesc1; + TupleDesc tupdesc2; + HeapTupleData tuple1; + HeapTupleData tuple2; + int ncolumns1; + int ncolumns2; + RecordCompareData *my_extra; + int ncols; + Datum *values1; + Datum *values2; + bool *nulls1; + bool *nulls2; + int i1; + int i2; + int j; + + /* Extract type info from the tuples */ + tupType1 = HeapTupleHeaderGetTypeId(record1); + tupTypmod1 = HeapTupleHeaderGetTypMod(record1); + tupdesc1 = lookup_rowtype_tupdesc(tupType1, tupTypmod1); + ncolumns1 = tupdesc1->natts; + tupType2 = HeapTupleHeaderGetTypeId(record2); + tupTypmod2 = HeapTupleHeaderGetTypMod(record2); + tupdesc2 = lookup_rowtype_tupdesc(tupType2, tupTypmod2); + ncolumns2 = tupdesc2->natts; + + /* Build temporary HeapTuple control structures */ + tuple1.t_len = HeapTupleHeaderGetDatumLength(record1); + ItemPointerSetInvalid(&(tuple1.t_self)); + tuple1.t_tableOid = InvalidOid; + tuple1.t_data = record1; + tuple2.t_len = HeapTupleHeaderGetDatumLength(record2); + ItemPointerSetInvalid(&(tuple2.t_self)); + tuple2.t_tableOid = InvalidOid; + tuple2.t_data = record2; + + /* + * We arrange to look up the needed comparison info just once per series + * of calls, assuming the record types don't change underneath us. + */ + ncols = Max(ncolumns1, ncolumns2); + my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra; + if (my_extra == NULL || + my_extra->ncolumns < ncols) + { + fcinfo->flinfo->fn_extra = + MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, + offsetof(RecordCompareData, columns) + + ncols * sizeof(ColumnCompareData)); + my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra; + my_extra->ncolumns = ncols; + my_extra->record1_type = InvalidOid; + my_extra->record1_typmod = 0; + my_extra->record2_type = InvalidOid; + my_extra->record2_typmod = 0; + } + + if (my_extra->record1_type != tupType1 || + my_extra->record1_typmod != tupTypmod1 || + my_extra->record2_type != tupType2 || + my_extra->record2_typmod != tupTypmod2) + { + MemSet(my_extra->columns, 0, ncols * sizeof(ColumnCompareData)); + my_extra->record1_type = tupType1; + my_extra->record1_typmod = tupTypmod1; + my_extra->record2_type = tupType2; + my_extra->record2_typmod = tupTypmod2; + } + + /* Break down the tuples into fields */ + values1 = (Datum *) palloc(ncolumns1 * sizeof(Datum)); + nulls1 = (bool *) palloc(ncolumns1 * sizeof(bool)); + heap_deform_tuple(&tuple1, tupdesc1, values1, nulls1); + values2 = (Datum *) palloc(ncolumns2 * sizeof(Datum)); + nulls2 = (bool *) palloc(ncolumns2 * sizeof(bool)); + heap_deform_tuple(&tuple2, tupdesc2, values2, nulls2); + + /* + * Scan corresponding columns, allowing for dropped columns in different + * places in the two rows. i1 and i2 are physical column indexes, j is + * the logical column index. + */ + i1 = i2 = j = 0; + while (i1 < ncolumns1 || i2 < ncolumns2) + { + Form_pg_attribute att1; + Form_pg_attribute att2; + + /* + * Skip dropped columns + */ + if (i1 < ncolumns1 && TupleDescAttr(tupdesc1, i1)->attisdropped) + { + i1++; + continue; + } + if (i2 < ncolumns2 && TupleDescAttr(tupdesc2, i2)->attisdropped) + { + i2++; + continue; + } + if (i1 >= ncolumns1 || i2 >= ncolumns2) + break; /* we'll deal with mismatch below loop */ + + att1 = TupleDescAttr(tupdesc1, i1); + att2 = TupleDescAttr(tupdesc2, i2); + + /* + * Have two matching columns, they must be same type + */ + if (att1->atttypid != att2->atttypid) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare dissimilar column types %s and %s at record column %d", + format_type_be(att1->atttypid), + format_type_be(att2->atttypid), + j + 1))); + + /* + * We consider two NULLs equal; NULL > not-NULL. + */ + if (!nulls1[i1] || !nulls2[i2]) + { + if (nulls1[i1] || nulls2[i2]) + { + result = false; + break; + } + + /* Compare the pair of elements */ + result = datum_image_eq(values1[i1], values2[i2], att1->attbyval, att2->attlen); + if (!result) + break; + } + + /* equal, so continue to next column */ + i1++, i2++, j++; + } + + /* + * If we didn't break out of the loop early, check for column count + * mismatch. (We do not report such mismatch if we found unequal column + * values; is that a feature or a bug?) + */ + if (result) + { + if (i1 != ncolumns1 || i2 != ncolumns2) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare record types with different numbers of columns"))); + } + + pfree(values1); + pfree(nulls1); + pfree(values2); + pfree(nulls2); + ReleaseTupleDesc(tupdesc1); + ReleaseTupleDesc(tupdesc2); + + /* Avoid leaking memory when handed toasted input. */ + PG_FREE_IF_COPY(record1, 0); + PG_FREE_IF_COPY(record2, 1); + + PG_RETURN_BOOL(result); +} + +Datum +record_image_ne(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(!DatumGetBool(record_image_eq(fcinfo))); +} + +Datum +record_image_lt(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(record_image_cmp(fcinfo) < 0); +} + +Datum +record_image_gt(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(record_image_cmp(fcinfo) > 0); +} + +Datum +record_image_le(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(record_image_cmp(fcinfo) <= 0); +} + +Datum +record_image_ge(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(record_image_cmp(fcinfo) >= 0); +} + +Datum +btrecordimagecmp(PG_FUNCTION_ARGS) +{ + PG_RETURN_INT32(record_image_cmp(fcinfo)); +} + + +/* + * Row type hash functions + */ + +Datum +hash_record(PG_FUNCTION_ARGS) +{ + HeapTupleHeader record = PG_GETARG_HEAPTUPLEHEADER(0); + uint32 result = 0; + Oid tupType; + int32 tupTypmod; + TupleDesc tupdesc; + HeapTupleData tuple; + int ncolumns; + RecordCompareData *my_extra; + Datum *values; + bool *nulls; + + check_stack_depth(); /* recurses for record-type columns */ + + /* Extract type info from tuple */ + tupType = HeapTupleHeaderGetTypeId(record); + tupTypmod = HeapTupleHeaderGetTypMod(record); + tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); + ncolumns = tupdesc->natts; + + /* Build temporary HeapTuple control structure */ + tuple.t_len = HeapTupleHeaderGetDatumLength(record); + ItemPointerSetInvalid(&(tuple.t_self)); + tuple.t_tableOid = InvalidOid; + tuple.t_data = record; + + /* + * We arrange to look up the needed hashing info just once per series of + * calls, assuming the record type doesn't change underneath us. + */ + my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra; + if (my_extra == NULL || + my_extra->ncolumns < ncolumns) + { + fcinfo->flinfo->fn_extra = + MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, + offsetof(RecordCompareData, columns) + + ncolumns * sizeof(ColumnCompareData)); + my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra; + my_extra->ncolumns = ncolumns; + my_extra->record1_type = InvalidOid; + my_extra->record1_typmod = 0; + } + + if (my_extra->record1_type != tupType || + my_extra->record1_typmod != tupTypmod) + { + MemSet(my_extra->columns, 0, ncolumns * sizeof(ColumnCompareData)); + my_extra->record1_type = tupType; + my_extra->record1_typmod = tupTypmod; + } + + /* Break down the tuple into fields */ + values = (Datum *) palloc(ncolumns * sizeof(Datum)); + nulls = (bool *) palloc(ncolumns * sizeof(bool)); + heap_deform_tuple(&tuple, tupdesc, values, nulls); + + for (int i = 0; i < ncolumns; i++) + { + Form_pg_attribute att; + TypeCacheEntry *typentry; + uint32 element_hash; + + att = TupleDescAttr(tupdesc, i); + + if (att->attisdropped) + continue; + + /* + * Lookup the hash function if not done already + */ + typentry = my_extra->columns[i].typentry; + if (typentry == NULL || + typentry->type_id != att->atttypid) + { + typentry = lookup_type_cache(att->atttypid, + TYPECACHE_HASH_PROC_FINFO); + if (!OidIsValid(typentry->hash_proc_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify a hash function for type %s", + format_type_be(typentry->type_id)))); + my_extra->columns[i].typentry = typentry; + } + + /* Compute hash of element */ + if (nulls[i]) + { + element_hash = 0; + } + else + { + LOCAL_FCINFO(locfcinfo, 1); + + InitFunctionCallInfoData(*locfcinfo, &typentry->hash_proc_finfo, 1, + att->attcollation, NULL, NULL); + locfcinfo->args[0].value = values[i]; + locfcinfo->args[0].isnull = false; + element_hash = DatumGetUInt32(FunctionCallInvoke(locfcinfo)); + + /* We don't expect hash support functions to return null */ + Assert(!locfcinfo->isnull); + } + + /* see hash_array() */ + result = (result << 5) - result + element_hash; + } + + pfree(values); + pfree(nulls); + ReleaseTupleDesc(tupdesc); + + /* Avoid leaking memory when handed toasted input. */ + PG_FREE_IF_COPY(record, 0); + + PG_RETURN_UINT32(result); +} + +Datum +hash_record_extended(PG_FUNCTION_ARGS) +{ + HeapTupleHeader record = PG_GETARG_HEAPTUPLEHEADER(0); + uint64 seed = PG_GETARG_INT64(1); + uint64 result = 0; + Oid tupType; + int32 tupTypmod; + TupleDesc tupdesc; + HeapTupleData tuple; + int ncolumns; + RecordCompareData *my_extra; + Datum *values; + bool *nulls; + + check_stack_depth(); /* recurses for record-type columns */ + + /* Extract type info from tuple */ + tupType = HeapTupleHeaderGetTypeId(record); + tupTypmod = HeapTupleHeaderGetTypMod(record); + tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); + ncolumns = tupdesc->natts; + + /* Build temporary HeapTuple control structure */ + tuple.t_len = HeapTupleHeaderGetDatumLength(record); + ItemPointerSetInvalid(&(tuple.t_self)); + tuple.t_tableOid = InvalidOid; + tuple.t_data = record; + + /* + * We arrange to look up the needed hashing info just once per series of + * calls, assuming the record type doesn't change underneath us. + */ + my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra; + if (my_extra == NULL || + my_extra->ncolumns < ncolumns) + { + fcinfo->flinfo->fn_extra = + MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, + offsetof(RecordCompareData, columns) + + ncolumns * sizeof(ColumnCompareData)); + my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra; + my_extra->ncolumns = ncolumns; + my_extra->record1_type = InvalidOid; + my_extra->record1_typmod = 0; + } + + if (my_extra->record1_type != tupType || + my_extra->record1_typmod != tupTypmod) + { + MemSet(my_extra->columns, 0, ncolumns * sizeof(ColumnCompareData)); + my_extra->record1_type = tupType; + my_extra->record1_typmod = tupTypmod; + } + + /* Break down the tuple into fields */ + values = (Datum *) palloc(ncolumns * sizeof(Datum)); + nulls = (bool *) palloc(ncolumns * sizeof(bool)); + heap_deform_tuple(&tuple, tupdesc, values, nulls); + + for (int i = 0; i < ncolumns; i++) + { + Form_pg_attribute att; + TypeCacheEntry *typentry; + uint64 element_hash; + + att = TupleDescAttr(tupdesc, i); + + if (att->attisdropped) + continue; + + /* + * Lookup the hash function if not done already + */ + typentry = my_extra->columns[i].typentry; + if (typentry == NULL || + typentry->type_id != att->atttypid) + { + typentry = lookup_type_cache(att->atttypid, + TYPECACHE_HASH_EXTENDED_PROC_FINFO); + if (!OidIsValid(typentry->hash_extended_proc_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify an extended hash function for type %s", + format_type_be(typentry->type_id)))); + my_extra->columns[i].typentry = typentry; + } + + /* Compute hash of element */ + if (nulls[i]) + { + element_hash = 0; + } + else + { + LOCAL_FCINFO(locfcinfo, 2); + + InitFunctionCallInfoData(*locfcinfo, &typentry->hash_extended_proc_finfo, 2, + att->attcollation, NULL, NULL); + locfcinfo->args[0].value = values[i]; + locfcinfo->args[0].isnull = false; + locfcinfo->args[1].value = Int64GetDatum(seed); + locfcinfo->args[0].isnull = false; + element_hash = DatumGetUInt64(FunctionCallInvoke(locfcinfo)); + + /* We don't expect hash support functions to return null */ + Assert(!locfcinfo->isnull); + } + + /* see hash_array_extended() */ + result = (result << 5) - result + element_hash; + } + + pfree(values); + pfree(nulls); + ReleaseTupleDesc(tupdesc); + + /* Avoid leaking memory when handed toasted input. */ + PG_FREE_IF_COPY(record, 0); + + PG_RETURN_UINT64(result); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/ruleutils.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/ruleutils.c new file mode 100644 index 00000000000..400b3795827 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/ruleutils.c @@ -0,0 +1,12616 @@ +/*------------------------------------------------------------------------- + * + * ruleutils.c + * Functions to convert stored expressions/querytrees back to + * source text + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/adt/ruleutils.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <ctype.h> +#include <unistd.h> +#include <fcntl.h> + +#include "access/amapi.h" +#include "access/htup_details.h" +#include "access/relation.h" +#include "access/sysattr.h" +#include "access/table.h" +#include "catalog/pg_aggregate.h" +#include "catalog/pg_am.h" +#include "catalog/pg_authid.h" +#include "catalog/pg_collation.h" +#include "catalog/pg_constraint.h" +#include "catalog/pg_depend.h" +#include "catalog/pg_language.h" +#include "catalog/pg_opclass.h" +#include "catalog/pg_operator.h" +#include "catalog/pg_partitioned_table.h" +#include "catalog/pg_proc.h" +#include "catalog/pg_statistic_ext.h" +#include "catalog/pg_trigger.h" +#include "catalog/pg_type.h" +#include "commands/defrem.h" +#include "commands/tablespace.h" +#include "common/keywords.h" +#include "executor/spi.h" +#include "funcapi.h" +#include "mb/pg_wchar.h" +#include "miscadmin.h" +#include "nodes/makefuncs.h" +#include "nodes/nodeFuncs.h" +#include "nodes/pathnodes.h" +#include "optimizer/optimizer.h" +#include "parser/parse_agg.h" +#include "parser/parse_func.h" +#include "parser/parse_node.h" +#include "parser/parse_oper.h" +#include "parser/parse_relation.h" +#include "parser/parser.h" +#include "parser/parsetree.h" +#include "rewrite/rewriteHandler.h" +#include "rewrite/rewriteManip.h" +#include "rewrite/rewriteSupport.h" +#include "utils/array.h" +#include "utils/builtins.h" +#include "utils/fmgroids.h" +#include "utils/guc.h" +#include "utils/hsearch.h" +#include "utils/lsyscache.h" +#include "utils/partcache.h" +#include "utils/rel.h" +#include "utils/ruleutils.h" +#include "utils/snapmgr.h" +#include "utils/syscache.h" +#include "utils/typcache.h" +#include "utils/varlena.h" +#include "utils/xml.h" + +/* ---------- + * Pretty formatting constants + * ---------- + */ + +/* Indent counts */ +#define PRETTYINDENT_STD 8 +#define PRETTYINDENT_JOIN 4 +#define PRETTYINDENT_VAR 4 + +#define PRETTYINDENT_LIMIT 40 /* wrap limit */ + +/* Pretty flags */ +#define PRETTYFLAG_PAREN 0x0001 +#define PRETTYFLAG_INDENT 0x0002 +#define PRETTYFLAG_SCHEMA 0x0004 + +/* Standard conversion of a "bool pretty" option to detailed flags */ +#define GET_PRETTY_FLAGS(pretty) \ + ((pretty) ? (PRETTYFLAG_PAREN | PRETTYFLAG_INDENT | PRETTYFLAG_SCHEMA) \ + : PRETTYFLAG_INDENT) + +/* Default line length for pretty-print wrapping: 0 means wrap always */ +#define WRAP_COLUMN_DEFAULT 0 + +/* macros to test if pretty action needed */ +#define PRETTY_PAREN(context) ((context)->prettyFlags & PRETTYFLAG_PAREN) +#define PRETTY_INDENT(context) ((context)->prettyFlags & PRETTYFLAG_INDENT) +#define PRETTY_SCHEMA(context) ((context)->prettyFlags & PRETTYFLAG_SCHEMA) + + +/* ---------- + * Local data types + * ---------- + */ + +/* Context info needed for invoking a recursive querytree display routine */ +typedef struct +{ + StringInfo buf; /* output buffer to append to */ + List *namespaces; /* List of deparse_namespace nodes */ + List *windowClause; /* Current query level's WINDOW clause */ + List *windowTList; /* targetlist for resolving WINDOW clause */ + int prettyFlags; /* enabling of pretty-print functions */ + int wrapColumn; /* max line length, or -1 for no limit */ + int indentLevel; /* current indent level for pretty-print */ + bool varprefix; /* true to print prefixes on Vars */ + ParseExprKind special_exprkind; /* set only for exprkinds needing special + * handling */ + Bitmapset *appendparents; /* if not null, map child Vars of these relids + * back to the parent rel */ +} deparse_context; + +/* + * Each level of query context around a subtree needs a level of Var namespace. + * A Var having varlevelsup=N refers to the N'th item (counting from 0) in + * the current context's namespaces list. + * + * rtable is the list of actual RTEs from the Query or PlannedStmt. + * rtable_names holds the alias name to be used for each RTE (either a C + * string, or NULL for nameless RTEs such as unnamed joins). + * rtable_columns holds the column alias names to be used for each RTE. + * + * subplans is a list of Plan trees for SubPlans and CTEs (it's only used + * in the PlannedStmt case). + * ctes is a list of CommonTableExpr nodes (only used in the Query case). + * appendrels, if not null (it's only used in the PlannedStmt case), is an + * array of AppendRelInfo nodes, indexed by child relid. We use that to map + * child-table Vars to their inheritance parents. + * + * In some cases we need to make names of merged JOIN USING columns unique + * across the whole query, not only per-RTE. If so, unique_using is true + * and using_names is a list of C strings representing names already assigned + * to USING columns. + * + * When deparsing plan trees, there is always just a single item in the + * deparse_namespace list (since a plan tree never contains Vars with + * varlevelsup > 0). We store the Plan node that is the immediate + * parent of the expression to be deparsed, as well as a list of that + * Plan's ancestors. In addition, we store its outer and inner subplan nodes, + * as well as their targetlists, and the index tlist if the current plan node + * might contain INDEX_VAR Vars. (These fields could be derived on-the-fly + * from the current Plan node, but it seems notationally clearer to set them + * up as separate fields.) + */ +typedef struct +{ + List *rtable; /* List of RangeTblEntry nodes */ + List *rtable_names; /* Parallel list of names for RTEs */ + List *rtable_columns; /* Parallel list of deparse_columns structs */ + List *subplans; /* List of Plan trees for SubPlans */ + List *ctes; /* List of CommonTableExpr nodes */ + AppendRelInfo **appendrels; /* Array of AppendRelInfo nodes, or NULL */ + /* Workspace for column alias assignment: */ + bool unique_using; /* Are we making USING names globally unique */ + List *using_names; /* List of assigned names for USING columns */ + /* Remaining fields are used only when deparsing a Plan tree: */ + Plan *plan; /* immediate parent of current expression */ + List *ancestors; /* ancestors of plan */ + Plan *outer_plan; /* outer subnode, or NULL if none */ + Plan *inner_plan; /* inner subnode, or NULL if none */ + List *outer_tlist; /* referent for OUTER_VAR Vars */ + List *inner_tlist; /* referent for INNER_VAR Vars */ + List *index_tlist; /* referent for INDEX_VAR Vars */ + /* Special namespace representing a function signature: */ + char *funcname; + int numargs; + char **argnames; +} deparse_namespace; + +/* + * Per-relation data about column alias names. + * + * Selecting aliases is unreasonably complicated because of the need to dump + * rules/views whose underlying tables may have had columns added, deleted, or + * renamed since the query was parsed. We must nonetheless print the rule/view + * in a form that can be reloaded and will produce the same results as before. + * + * For each RTE used in the query, we must assign column aliases that are + * unique within that RTE. SQL does not require this of the original query, + * but due to factors such as *-expansion we need to be able to uniquely + * reference every column in a decompiled query. As long as we qualify all + * column references, per-RTE uniqueness is sufficient for that. + * + * However, we can't ensure per-column name uniqueness for unnamed join RTEs, + * since they just inherit column names from their input RTEs, and we can't + * rename the columns at the join level. Most of the time this isn't an issue + * because we don't need to reference the join's output columns as such; we + * can reference the input columns instead. That approach can fail for merged + * JOIN USING columns, however, so when we have one of those in an unnamed + * join, we have to make that column's alias globally unique across the whole + * query to ensure it can be referenced unambiguously. + * + * Another problem is that a JOIN USING clause requires the columns to be + * merged to have the same aliases in both input RTEs, and that no other + * columns in those RTEs or their children conflict with the USING names. + * To handle that, we do USING-column alias assignment in a recursive + * traversal of the query's jointree. When descending through a JOIN with + * USING, we preassign the USING column names to the child columns, overriding + * other rules for column alias assignment. We also mark each RTE with a list + * of all USING column names selected for joins containing that RTE, so that + * when we assign other columns' aliases later, we can avoid conflicts. + * + * Another problem is that if a JOIN's input tables have had columns added or + * deleted since the query was parsed, we must generate a column alias list + * for the join that matches the current set of input columns --- otherwise, a + * change in the number of columns in the left input would throw off matching + * of aliases to columns of the right input. Thus, positions in the printable + * column alias list are not necessarily one-for-one with varattnos of the + * JOIN, so we need a separate new_colnames[] array for printing purposes. + */ +typedef struct +{ + /* + * colnames is an array containing column aliases to use for columns that + * existed when the query was parsed. Dropped columns have NULL entries. + * This array can be directly indexed by varattno to get a Var's name. + * + * Non-NULL entries are guaranteed unique within the RTE, *except* when + * this is for an unnamed JOIN RTE. In that case we merely copy up names + * from the two input RTEs. + * + * During the recursive descent in set_using_names(), forcible assignment + * of a child RTE's column name is represented by pre-setting that element + * of the child's colnames array. So at that stage, NULL entries in this + * array just mean that no name has been preassigned, not necessarily that + * the column is dropped. + */ + int num_cols; /* length of colnames[] array */ + char **colnames; /* array of C strings and NULLs */ + + /* + * new_colnames is an array containing column aliases to use for columns + * that would exist if the query was re-parsed against the current + * definitions of its base tables. This is what to print as the column + * alias list for the RTE. This array does not include dropped columns, + * but it will include columns added since original parsing. Indexes in + * it therefore have little to do with current varattno values. As above, + * entries are unique unless this is for an unnamed JOIN RTE. (In such an + * RTE, we never actually print this array, but we must compute it anyway + * for possible use in computing column names of upper joins.) The + * parallel array is_new_col marks which of these columns are new since + * original parsing. Entries with is_new_col false must match the + * non-NULL colnames entries one-for-one. + */ + int num_new_cols; /* length of new_colnames[] array */ + char **new_colnames; /* array of C strings */ + bool *is_new_col; /* array of bool flags */ + + /* This flag tells whether we should actually print a column alias list */ + bool printaliases; + + /* This list has all names used as USING names in joins above this RTE */ + List *parentUsing; /* names assigned to parent merged columns */ + + /* + * If this struct is for a JOIN RTE, we fill these fields during the + * set_using_names() pass to describe its relationship to its child RTEs. + * + * leftattnos and rightattnos are arrays with one entry per existing + * output column of the join (hence, indexable by join varattno). For a + * simple reference to a column of the left child, leftattnos[i] is the + * child RTE's attno and rightattnos[i] is zero; and conversely for a + * column of the right child. But for merged columns produced by JOIN + * USING/NATURAL JOIN, both leftattnos[i] and rightattnos[i] are nonzero. + * Note that a simple reference might be to a child RTE column that's been + * dropped; but that's OK since the column could not be used in the query. + * + * If it's a JOIN USING, usingNames holds the alias names selected for the + * merged columns (these might be different from the original USING list, + * if we had to modify names to achieve uniqueness). + */ + int leftrti; /* rangetable index of left child */ + int rightrti; /* rangetable index of right child */ + int *leftattnos; /* left-child varattnos of join cols, or 0 */ + int *rightattnos; /* right-child varattnos of join cols, or 0 */ + List *usingNames; /* names assigned to merged columns */ +} deparse_columns; + +/* This macro is analogous to rt_fetch(), but for deparse_columns structs */ +#define deparse_columns_fetch(rangetable_index, dpns) \ + ((deparse_columns *) list_nth((dpns)->rtable_columns, (rangetable_index)-1)) + +/* + * Entry in set_rtable_names' hash table + */ +typedef struct +{ + char name[NAMEDATALEN]; /* Hash key --- must be first */ + int counter; /* Largest addition used so far for name */ +} NameHashEntry; + +/* Callback signature for resolve_special_varno() */ +typedef void (*rsv_callback) (Node *node, deparse_context *context, + void *callback_arg); + + +/* ---------- + * Global data + * ---------- + */ +static __thread SPIPlanPtr plan_getrulebyoid = NULL; +static __thread const char *query_getrulebyoid = "SELECT * FROM pg_catalog.pg_rewrite WHERE oid = $1"; +static __thread SPIPlanPtr plan_getviewrule = NULL; +static __thread const char *query_getviewrule = "SELECT * FROM pg_catalog.pg_rewrite WHERE ev_class = $1 AND rulename = $2"; + +/* GUC parameters */ +__thread bool quote_all_identifiers = false; + + +/* ---------- + * Local functions + * + * Most of these functions used to use fixed-size buffers to build their + * results. Now, they take an (already initialized) StringInfo object + * as a parameter, and append their text output to its contents. + * ---------- + */ +static char *deparse_expression_pretty(Node *expr, List *dpcontext, + bool forceprefix, bool showimplicit, + int prettyFlags, int startIndent); +static char *pg_get_viewdef_worker(Oid viewoid, + int prettyFlags, int wrapColumn); +static char *pg_get_triggerdef_worker(Oid trigid, bool pretty); +static int decompile_column_index_array(Datum column_index_array, Oid relId, + StringInfo buf); +static char *pg_get_ruledef_worker(Oid ruleoid, int prettyFlags); +static char *pg_get_indexdef_worker(Oid indexrelid, int colno, + const Oid *excludeOps, + bool attrsOnly, bool keysOnly, + bool showTblSpc, bool inherits, + int prettyFlags, bool missing_ok); +static char *pg_get_statisticsobj_worker(Oid statextid, bool columns_only, + bool missing_ok); +static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags, + bool attrsOnly, bool missing_ok); +static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand, + int prettyFlags, bool missing_ok); +static text *pg_get_expr_worker(text *expr, Oid relid, int prettyFlags); +static int print_function_arguments(StringInfo buf, HeapTuple proctup, + bool print_table_args, bool print_defaults); +static void print_function_rettype(StringInfo buf, HeapTuple proctup); +static void print_function_trftypes(StringInfo buf, HeapTuple proctup); +static void print_function_sqlbody(StringInfo buf, HeapTuple proctup); +static void set_rtable_names(deparse_namespace *dpns, List *parent_namespaces, + Bitmapset *rels_used); +static void set_deparse_for_query(deparse_namespace *dpns, Query *query, + List *parent_namespaces); +static void set_simple_column_names(deparse_namespace *dpns); +static bool has_dangerous_join_using(deparse_namespace *dpns, Node *jtnode); +static void set_using_names(deparse_namespace *dpns, Node *jtnode, + List *parentUsing); +static void set_relation_column_names(deparse_namespace *dpns, + RangeTblEntry *rte, + deparse_columns *colinfo); +static void set_join_column_names(deparse_namespace *dpns, RangeTblEntry *rte, + deparse_columns *colinfo); +static bool colname_is_unique(const char *colname, deparse_namespace *dpns, + deparse_columns *colinfo); +static char *make_colname_unique(char *colname, deparse_namespace *dpns, + deparse_columns *colinfo); +static void expand_colnames_array_to(deparse_columns *colinfo, int n); +static void identify_join_columns(JoinExpr *j, RangeTblEntry *jrte, + deparse_columns *colinfo); +static char *get_rtable_name(int rtindex, deparse_context *context); +static void set_deparse_plan(deparse_namespace *dpns, Plan *plan); +static Plan *find_recursive_union(deparse_namespace *dpns, + WorkTableScan *wtscan); +static void push_child_plan(deparse_namespace *dpns, Plan *plan, + deparse_namespace *save_dpns); +static void pop_child_plan(deparse_namespace *dpns, + deparse_namespace *save_dpns); +static void push_ancestor_plan(deparse_namespace *dpns, ListCell *ancestor_cell, + deparse_namespace *save_dpns); +static void pop_ancestor_plan(deparse_namespace *dpns, + deparse_namespace *save_dpns); +static void make_ruledef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc, + int prettyFlags); +static void make_viewdef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc, + int prettyFlags, int wrapColumn); +static void get_query_def(Query *query, StringInfo buf, List *parentnamespace, + TupleDesc resultDesc, bool colNamesVisible, + int prettyFlags, int wrapColumn, int startIndent); +static void get_values_def(List *values_lists, deparse_context *context); +static void get_with_clause(Query *query, deparse_context *context); +static void get_select_query_def(Query *query, deparse_context *context, + TupleDesc resultDesc, bool colNamesVisible); +static void get_insert_query_def(Query *query, deparse_context *context, + bool colNamesVisible); +static void get_update_query_def(Query *query, deparse_context *context, + bool colNamesVisible); +static void get_update_query_targetlist_def(Query *query, List *targetList, + deparse_context *context, + RangeTblEntry *rte); +static void get_delete_query_def(Query *query, deparse_context *context, + bool colNamesVisible); +static void get_merge_query_def(Query *query, deparse_context *context, + bool colNamesVisible); +static void get_utility_query_def(Query *query, deparse_context *context); +static void get_basic_select_query(Query *query, deparse_context *context, + TupleDesc resultDesc, bool colNamesVisible); +static void get_target_list(List *targetList, deparse_context *context, + TupleDesc resultDesc, bool colNamesVisible); +static void get_setop_query(Node *setOp, Query *query, + deparse_context *context, + TupleDesc resultDesc, bool colNamesVisible); +static Node *get_rule_sortgroupclause(Index ref, List *tlist, + bool force_colno, + deparse_context *context); +static void get_rule_groupingset(GroupingSet *gset, List *targetlist, + bool omit_parens, deparse_context *context); +static void get_rule_orderby(List *orderList, List *targetList, + bool force_colno, deparse_context *context); +static void get_rule_windowclause(Query *query, deparse_context *context); +static void get_rule_windowspec(WindowClause *wc, List *targetList, + deparse_context *context); +static char *get_variable(Var *var, int levelsup, bool istoplevel, + deparse_context *context); +static void get_special_variable(Node *node, deparse_context *context, + void *callback_arg); +static void resolve_special_varno(Node *node, deparse_context *context, + rsv_callback callback, void *callback_arg); +static Node *find_param_referent(Param *param, deparse_context *context, + deparse_namespace **dpns_p, ListCell **ancestor_cell_p); +static void get_parameter(Param *param, deparse_context *context); +static const char *get_simple_binary_op_name(OpExpr *expr); +static bool isSimpleNode(Node *node, Node *parentNode, int prettyFlags); +static void appendContextKeyword(deparse_context *context, const char *str, + int indentBefore, int indentAfter, int indentPlus); +static void removeStringInfoSpaces(StringInfo str); +static void get_rule_expr(Node *node, deparse_context *context, + bool showimplicit); +static void get_rule_expr_toplevel(Node *node, deparse_context *context, + bool showimplicit); +static void get_rule_list_toplevel(List *lst, deparse_context *context, + bool showimplicit); +static void get_rule_expr_funccall(Node *node, deparse_context *context, + bool showimplicit); +static bool looks_like_function(Node *node); +static void get_oper_expr(OpExpr *expr, deparse_context *context); +static void get_func_expr(FuncExpr *expr, deparse_context *context, + bool showimplicit); +static void get_agg_expr(Aggref *aggref, deparse_context *context, + Aggref *original_aggref); +static void get_agg_expr_helper(Aggref *aggref, deparse_context *context, + Aggref *original_aggref, const char *funcname, + const char *options, bool is_json_objectagg); +static void get_agg_combine_expr(Node *node, deparse_context *context, + void *callback_arg); +static void get_windowfunc_expr(WindowFunc *wfunc, deparse_context *context); +static void get_windowfunc_expr_helper(WindowFunc *wfunc, deparse_context *context, + const char *funcname, const char *options, + bool is_json_objectagg); +static bool get_func_sql_syntax(FuncExpr *expr, deparse_context *context); +static void get_coercion_expr(Node *arg, deparse_context *context, + Oid resulttype, int32 resulttypmod, + Node *parentNode); +static void get_const_expr(Const *constval, deparse_context *context, + int showtype); +static void get_const_collation(Const *constval, deparse_context *context); +static void get_json_format(JsonFormat *format, StringInfo buf); +static void get_json_constructor(JsonConstructorExpr *ctor, + deparse_context *context, bool showimplicit); +static void get_json_constructor_options(JsonConstructorExpr *ctor, + StringInfo buf); +static void get_json_agg_constructor(JsonConstructorExpr *ctor, + deparse_context *context, + const char *funcname, + bool is_json_objectagg); +static void simple_quote_literal(StringInfo buf, const char *val); +static void get_sublink_expr(SubLink *sublink, deparse_context *context); +static void get_tablefunc(TableFunc *tf, deparse_context *context, + bool showimplicit); +static void get_from_clause(Query *query, const char *prefix, + deparse_context *context); +static void get_from_clause_item(Node *jtnode, Query *query, + deparse_context *context); +static void get_rte_alias(RangeTblEntry *rte, int varno, bool use_as, + deparse_context *context); +static void get_column_alias_list(deparse_columns *colinfo, + deparse_context *context); +static void get_from_clause_coldeflist(RangeTblFunction *rtfunc, + deparse_columns *colinfo, + deparse_context *context); +static void get_tablesample_def(TableSampleClause *tablesample, + deparse_context *context); +static void get_opclass_name(Oid opclass, Oid actual_datatype, + StringInfo buf); +static Node *processIndirection(Node *node, deparse_context *context); +static void printSubscripts(SubscriptingRef *sbsref, deparse_context *context); +static char *get_relation_name(Oid relid); +static char *generate_relation_name(Oid relid, List *namespaces); +static char *generate_qualified_relation_name(Oid relid); +static char *generate_function_name(Oid funcid, int nargs, + List *argnames, Oid *argtypes, + bool has_variadic, bool *use_variadic_p, + ParseExprKind special_exprkind); +static char *generate_operator_name(Oid operid, Oid arg1, Oid arg2); +static void add_cast_to(StringInfo buf, Oid typid); +static char *generate_qualified_type_name(Oid typid); +static text *string_to_text(char *str); +static char *flatten_reloptions(Oid relid); +static void get_reloptions(StringInfo buf, Datum reloptions); + +#define only_marker(rte) ((rte)->inh ? "" : "ONLY ") + + +/* ---------- + * pg_get_ruledef - Do it all and return a text + * that could be used as a statement + * to recreate the rule + * ---------- + */ +Datum +pg_get_ruledef(PG_FUNCTION_ARGS) +{ + Oid ruleoid = PG_GETARG_OID(0); + int prettyFlags; + char *res; + + prettyFlags = PRETTYFLAG_INDENT; + + res = pg_get_ruledef_worker(ruleoid, prettyFlags); + + if (res == NULL) + PG_RETURN_NULL(); + + PG_RETURN_TEXT_P(string_to_text(res)); +} + + +Datum +pg_get_ruledef_ext(PG_FUNCTION_ARGS) +{ + Oid ruleoid = PG_GETARG_OID(0); + bool pretty = PG_GETARG_BOOL(1); + int prettyFlags; + char *res; + + prettyFlags = GET_PRETTY_FLAGS(pretty); + + res = pg_get_ruledef_worker(ruleoid, prettyFlags); + + if (res == NULL) + PG_RETURN_NULL(); + + PG_RETURN_TEXT_P(string_to_text(res)); +} + + +static char * +pg_get_ruledef_worker(Oid ruleoid, int prettyFlags) +{ + Datum args[1]; + char nulls[1]; + int spirc; + HeapTuple ruletup; + TupleDesc rulettc; + StringInfoData buf; + + /* + * Do this first so that string is alloc'd in outer context not SPI's. + */ + initStringInfo(&buf); + + /* + * Connect to SPI manager + */ + if (SPI_connect() != SPI_OK_CONNECT) + elog(ERROR, "SPI_connect failed"); + + /* + * On the first call prepare the plan to lookup pg_rewrite. We read + * pg_rewrite over the SPI manager instead of using the syscache to be + * checked for read access on pg_rewrite. + */ + if (plan_getrulebyoid == NULL) + { + Oid argtypes[1]; + SPIPlanPtr plan; + + argtypes[0] = OIDOID; + plan = SPI_prepare(query_getrulebyoid, 1, argtypes); + if (plan == NULL) + elog(ERROR, "SPI_prepare failed for \"%s\"", query_getrulebyoid); + SPI_keepplan(plan); + plan_getrulebyoid = plan; + } + + /* + * Get the pg_rewrite tuple for this rule + */ + args[0] = ObjectIdGetDatum(ruleoid); + nulls[0] = ' '; + spirc = SPI_execute_plan(plan_getrulebyoid, args, nulls, true, 0); + if (spirc != SPI_OK_SELECT) + elog(ERROR, "failed to get pg_rewrite tuple for rule %u", ruleoid); + if (SPI_processed != 1) + { + /* + * There is no tuple data available here, just keep the output buffer + * empty. + */ + } + else + { + /* + * Get the rule's definition and put it into executor's memory + */ + ruletup = SPI_tuptable->vals[0]; + rulettc = SPI_tuptable->tupdesc; + make_ruledef(&buf, ruletup, rulettc, prettyFlags); + } + + /* + * Disconnect from SPI manager + */ + if (SPI_finish() != SPI_OK_FINISH) + elog(ERROR, "SPI_finish failed"); + + if (buf.len == 0) + return NULL; + + return buf.data; +} + + +/* ---------- + * pg_get_viewdef - Mainly the same thing, but we + * only return the SELECT part of a view + * ---------- + */ +Datum +pg_get_viewdef(PG_FUNCTION_ARGS) +{ + /* By OID */ + Oid viewoid = PG_GETARG_OID(0); + int prettyFlags; + char *res; + + prettyFlags = PRETTYFLAG_INDENT; + + res = pg_get_viewdef_worker(viewoid, prettyFlags, WRAP_COLUMN_DEFAULT); + + if (res == NULL) + PG_RETURN_NULL(); + + PG_RETURN_TEXT_P(string_to_text(res)); +} + + +Datum +pg_get_viewdef_ext(PG_FUNCTION_ARGS) +{ + /* By OID */ + Oid viewoid = PG_GETARG_OID(0); + bool pretty = PG_GETARG_BOOL(1); + int prettyFlags; + char *res; + + prettyFlags = GET_PRETTY_FLAGS(pretty); + + res = pg_get_viewdef_worker(viewoid, prettyFlags, WRAP_COLUMN_DEFAULT); + + if (res == NULL) + PG_RETURN_NULL(); + + PG_RETURN_TEXT_P(string_to_text(res)); +} + +Datum +pg_get_viewdef_wrap(PG_FUNCTION_ARGS) +{ + /* By OID */ + Oid viewoid = PG_GETARG_OID(0); + int wrap = PG_GETARG_INT32(1); + int prettyFlags; + char *res; + + /* calling this implies we want pretty printing */ + prettyFlags = GET_PRETTY_FLAGS(true); + + res = pg_get_viewdef_worker(viewoid, prettyFlags, wrap); + + if (res == NULL) + PG_RETURN_NULL(); + + PG_RETURN_TEXT_P(string_to_text(res)); +} + +Datum +pg_get_viewdef_name(PG_FUNCTION_ARGS) +{ + /* By qualified name */ + text *viewname = PG_GETARG_TEXT_PP(0); + int prettyFlags; + RangeVar *viewrel; + Oid viewoid; + char *res; + + prettyFlags = PRETTYFLAG_INDENT; + + /* Look up view name. Can't lock it - we might not have privileges. */ + viewrel = makeRangeVarFromNameList(textToQualifiedNameList(viewname)); + viewoid = RangeVarGetRelid(viewrel, NoLock, false); + + res = pg_get_viewdef_worker(viewoid, prettyFlags, WRAP_COLUMN_DEFAULT); + + if (res == NULL) + PG_RETURN_NULL(); + + PG_RETURN_TEXT_P(string_to_text(res)); +} + + +Datum +pg_get_viewdef_name_ext(PG_FUNCTION_ARGS) +{ + /* By qualified name */ + text *viewname = PG_GETARG_TEXT_PP(0); + bool pretty = PG_GETARG_BOOL(1); + int prettyFlags; + RangeVar *viewrel; + Oid viewoid; + char *res; + + prettyFlags = GET_PRETTY_FLAGS(pretty); + + /* Look up view name. Can't lock it - we might not have privileges. */ + viewrel = makeRangeVarFromNameList(textToQualifiedNameList(viewname)); + viewoid = RangeVarGetRelid(viewrel, NoLock, false); + + res = pg_get_viewdef_worker(viewoid, prettyFlags, WRAP_COLUMN_DEFAULT); + + if (res == NULL) + PG_RETURN_NULL(); + + PG_RETURN_TEXT_P(string_to_text(res)); +} + +/* + * Common code for by-OID and by-name variants of pg_get_viewdef + */ +static char * +pg_get_viewdef_worker(Oid viewoid, int prettyFlags, int wrapColumn) +{ + Datum args[2]; + char nulls[2]; + int spirc; + HeapTuple ruletup; + TupleDesc rulettc; + StringInfoData buf; + + /* + * Do this first so that string is alloc'd in outer context not SPI's. + */ + initStringInfo(&buf); + + /* + * Connect to SPI manager + */ + if (SPI_connect() != SPI_OK_CONNECT) + elog(ERROR, "SPI_connect failed"); + + /* + * On the first call prepare the plan to lookup pg_rewrite. We read + * pg_rewrite over the SPI manager instead of using the syscache to be + * checked for read access on pg_rewrite. + */ + if (plan_getviewrule == NULL) + { + Oid argtypes[2]; + SPIPlanPtr plan; + + argtypes[0] = OIDOID; + argtypes[1] = NAMEOID; + plan = SPI_prepare(query_getviewrule, 2, argtypes); + if (plan == NULL) + elog(ERROR, "SPI_prepare failed for \"%s\"", query_getviewrule); + SPI_keepplan(plan); + plan_getviewrule = plan; + } + + /* + * Get the pg_rewrite tuple for the view's SELECT rule + */ + args[0] = ObjectIdGetDatum(viewoid); + args[1] = DirectFunctionCall1(namein, CStringGetDatum(ViewSelectRuleName)); + nulls[0] = ' '; + nulls[1] = ' '; + spirc = SPI_execute_plan(plan_getviewrule, args, nulls, true, 0); + if (spirc != SPI_OK_SELECT) + elog(ERROR, "failed to get pg_rewrite tuple for view %u", viewoid); + if (SPI_processed != 1) + { + /* + * There is no tuple data available here, just keep the output buffer + * empty. + */ + } + else + { + /* + * Get the rule's definition and put it into executor's memory + */ + ruletup = SPI_tuptable->vals[0]; + rulettc = SPI_tuptable->tupdesc; + make_viewdef(&buf, ruletup, rulettc, prettyFlags, wrapColumn); + } + + /* + * Disconnect from SPI manager + */ + if (SPI_finish() != SPI_OK_FINISH) + elog(ERROR, "SPI_finish failed"); + + if (buf.len == 0) + return NULL; + + return buf.data; +} + +/* ---------- + * pg_get_triggerdef - Get the definition of a trigger + * ---------- + */ +Datum +pg_get_triggerdef(PG_FUNCTION_ARGS) +{ + Oid trigid = PG_GETARG_OID(0); + char *res; + + res = pg_get_triggerdef_worker(trigid, false); + + if (res == NULL) + PG_RETURN_NULL(); + + PG_RETURN_TEXT_P(string_to_text(res)); +} + +Datum +pg_get_triggerdef_ext(PG_FUNCTION_ARGS) +{ + Oid trigid = PG_GETARG_OID(0); + bool pretty = PG_GETARG_BOOL(1); + char *res; + + res = pg_get_triggerdef_worker(trigid, pretty); + + if (res == NULL) + PG_RETURN_NULL(); + + PG_RETURN_TEXT_P(string_to_text(res)); +} + +static char * +pg_get_triggerdef_worker(Oid trigid, bool pretty) +{ + HeapTuple ht_trig; + Form_pg_trigger trigrec; + StringInfoData buf; + Relation tgrel; + ScanKeyData skey[1]; + SysScanDesc tgscan; + int findx = 0; + char *tgname; + char *tgoldtable; + char *tgnewtable; + Datum value; + bool isnull; + + /* + * Fetch the pg_trigger tuple by the Oid of the trigger + */ + tgrel = table_open(TriggerRelationId, AccessShareLock); + + ScanKeyInit(&skey[0], + Anum_pg_trigger_oid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(trigid)); + + tgscan = systable_beginscan(tgrel, TriggerOidIndexId, true, + NULL, 1, skey); + + ht_trig = systable_getnext(tgscan); + + if (!HeapTupleIsValid(ht_trig)) + { + systable_endscan(tgscan); + table_close(tgrel, AccessShareLock); + return NULL; + } + + trigrec = (Form_pg_trigger) GETSTRUCT(ht_trig); + + /* + * Start the trigger definition. Note that the trigger's name should never + * be schema-qualified, but the trigger rel's name may be. + */ + initStringInfo(&buf); + + tgname = NameStr(trigrec->tgname); + appendStringInfo(&buf, "CREATE %sTRIGGER %s ", + OidIsValid(trigrec->tgconstraint) ? "CONSTRAINT " : "", + quote_identifier(tgname)); + + if (TRIGGER_FOR_BEFORE(trigrec->tgtype)) + appendStringInfoString(&buf, "BEFORE"); + else if (TRIGGER_FOR_AFTER(trigrec->tgtype)) + appendStringInfoString(&buf, "AFTER"); + else if (TRIGGER_FOR_INSTEAD(trigrec->tgtype)) + appendStringInfoString(&buf, "INSTEAD OF"); + else + elog(ERROR, "unexpected tgtype value: %d", trigrec->tgtype); + + if (TRIGGER_FOR_INSERT(trigrec->tgtype)) + { + appendStringInfoString(&buf, " INSERT"); + findx++; + } + if (TRIGGER_FOR_DELETE(trigrec->tgtype)) + { + if (findx > 0) + appendStringInfoString(&buf, " OR DELETE"); + else + appendStringInfoString(&buf, " DELETE"); + findx++; + } + if (TRIGGER_FOR_UPDATE(trigrec->tgtype)) + { + if (findx > 0) + appendStringInfoString(&buf, " OR UPDATE"); + else + appendStringInfoString(&buf, " UPDATE"); + findx++; + /* tgattr is first var-width field, so OK to access directly */ + if (trigrec->tgattr.dim1 > 0) + { + int i; + + appendStringInfoString(&buf, " OF "); + for (i = 0; i < trigrec->tgattr.dim1; i++) + { + char *attname; + + if (i > 0) + appendStringInfoString(&buf, ", "); + attname = get_attname(trigrec->tgrelid, + trigrec->tgattr.values[i], false); + appendStringInfoString(&buf, quote_identifier(attname)); + } + } + } + if (TRIGGER_FOR_TRUNCATE(trigrec->tgtype)) + { + if (findx > 0) + appendStringInfoString(&buf, " OR TRUNCATE"); + else + appendStringInfoString(&buf, " TRUNCATE"); + findx++; + } + + /* + * In non-pretty mode, always schema-qualify the target table name for + * safety. In pretty mode, schema-qualify only if not visible. + */ + appendStringInfo(&buf, " ON %s ", + pretty ? + generate_relation_name(trigrec->tgrelid, NIL) : + generate_qualified_relation_name(trigrec->tgrelid)); + + if (OidIsValid(trigrec->tgconstraint)) + { + if (OidIsValid(trigrec->tgconstrrelid)) + appendStringInfo(&buf, "FROM %s ", + generate_relation_name(trigrec->tgconstrrelid, NIL)); + if (!trigrec->tgdeferrable) + appendStringInfoString(&buf, "NOT "); + appendStringInfoString(&buf, "DEFERRABLE INITIALLY "); + if (trigrec->tginitdeferred) + appendStringInfoString(&buf, "DEFERRED "); + else + appendStringInfoString(&buf, "IMMEDIATE "); + } + + value = fastgetattr(ht_trig, Anum_pg_trigger_tgoldtable, + tgrel->rd_att, &isnull); + if (!isnull) + tgoldtable = NameStr(*DatumGetName(value)); + else + tgoldtable = NULL; + value = fastgetattr(ht_trig, Anum_pg_trigger_tgnewtable, + tgrel->rd_att, &isnull); + if (!isnull) + tgnewtable = NameStr(*DatumGetName(value)); + else + tgnewtable = NULL; + if (tgoldtable != NULL || tgnewtable != NULL) + { + appendStringInfoString(&buf, "REFERENCING "); + if (tgoldtable != NULL) + appendStringInfo(&buf, "OLD TABLE AS %s ", + quote_identifier(tgoldtable)); + if (tgnewtable != NULL) + appendStringInfo(&buf, "NEW TABLE AS %s ", + quote_identifier(tgnewtable)); + } + + if (TRIGGER_FOR_ROW(trigrec->tgtype)) + appendStringInfoString(&buf, "FOR EACH ROW "); + else + appendStringInfoString(&buf, "FOR EACH STATEMENT "); + + /* If the trigger has a WHEN qualification, add that */ + value = fastgetattr(ht_trig, Anum_pg_trigger_tgqual, + tgrel->rd_att, &isnull); + if (!isnull) + { + Node *qual; + char relkind; + deparse_context context; + deparse_namespace dpns; + RangeTblEntry *oldrte; + RangeTblEntry *newrte; + + appendStringInfoString(&buf, "WHEN ("); + + qual = stringToNode(TextDatumGetCString(value)); + + relkind = get_rel_relkind(trigrec->tgrelid); + + /* Build minimal OLD and NEW RTEs for the rel */ + oldrte = makeNode(RangeTblEntry); + oldrte->rtekind = RTE_RELATION; + oldrte->relid = trigrec->tgrelid; + oldrte->relkind = relkind; + oldrte->rellockmode = AccessShareLock; + oldrte->alias = makeAlias("old", NIL); + oldrte->eref = oldrte->alias; + oldrte->lateral = false; + oldrte->inh = false; + oldrte->inFromCl = true; + + newrte = makeNode(RangeTblEntry); + newrte->rtekind = RTE_RELATION; + newrte->relid = trigrec->tgrelid; + newrte->relkind = relkind; + newrte->rellockmode = AccessShareLock; + newrte->alias = makeAlias("new", NIL); + newrte->eref = newrte->alias; + newrte->lateral = false; + newrte->inh = false; + newrte->inFromCl = true; + + /* Build two-element rtable */ + memset(&dpns, 0, sizeof(dpns)); + dpns.rtable = list_make2(oldrte, newrte); + dpns.subplans = NIL; + dpns.ctes = NIL; + dpns.appendrels = NULL; + set_rtable_names(&dpns, NIL, NULL); + set_simple_column_names(&dpns); + + /* Set up context with one-deep namespace stack */ + context.buf = &buf; + context.namespaces = list_make1(&dpns); + context.windowClause = NIL; + context.windowTList = NIL; + context.varprefix = true; + context.prettyFlags = GET_PRETTY_FLAGS(pretty); + context.wrapColumn = WRAP_COLUMN_DEFAULT; + context.indentLevel = PRETTYINDENT_STD; + context.special_exprkind = EXPR_KIND_NONE; + context.appendparents = NULL; + + get_rule_expr(qual, &context, false); + + appendStringInfoString(&buf, ") "); + } + + appendStringInfo(&buf, "EXECUTE FUNCTION %s(", + generate_function_name(trigrec->tgfoid, 0, + NIL, NULL, + false, NULL, EXPR_KIND_NONE)); + + if (trigrec->tgnargs > 0) + { + char *p; + int i; + + value = fastgetattr(ht_trig, Anum_pg_trigger_tgargs, + tgrel->rd_att, &isnull); + if (isnull) + elog(ERROR, "tgargs is null for trigger %u", trigid); + p = (char *) VARDATA_ANY(DatumGetByteaPP(value)); + for (i = 0; i < trigrec->tgnargs; i++) + { + if (i > 0) + appendStringInfoString(&buf, ", "); + simple_quote_literal(&buf, p); + /* advance p to next string embedded in tgargs */ + while (*p) + p++; + p++; + } + } + + /* We deliberately do not put semi-colon at end */ + appendStringInfoChar(&buf, ')'); + + /* Clean up */ + systable_endscan(tgscan); + + table_close(tgrel, AccessShareLock); + + return buf.data; +} + +/* ---------- + * pg_get_indexdef - Get the definition of an index + * + * In the extended version, there is a colno argument as well as pretty bool. + * if colno == 0, we want a complete index definition. + * if colno > 0, we only want the Nth index key's variable or expression. + * + * Note that the SQL-function versions of this omit any info about the + * index tablespace; this is intentional because pg_dump wants it that way. + * However pg_get_indexdef_string() includes the index tablespace. + * ---------- + */ +Datum +pg_get_indexdef(PG_FUNCTION_ARGS) +{ + Oid indexrelid = PG_GETARG_OID(0); + int prettyFlags; + char *res; + + prettyFlags = PRETTYFLAG_INDENT; + + res = pg_get_indexdef_worker(indexrelid, 0, NULL, + false, false, + false, false, + prettyFlags, true); + + if (res == NULL) + PG_RETURN_NULL(); + + PG_RETURN_TEXT_P(string_to_text(res)); +} + +Datum +pg_get_indexdef_ext(PG_FUNCTION_ARGS) +{ + Oid indexrelid = PG_GETARG_OID(0); + int32 colno = PG_GETARG_INT32(1); + bool pretty = PG_GETARG_BOOL(2); + int prettyFlags; + char *res; + + prettyFlags = GET_PRETTY_FLAGS(pretty); + + res = pg_get_indexdef_worker(indexrelid, colno, NULL, + colno != 0, false, + false, false, + prettyFlags, true); + + if (res == NULL) + PG_RETURN_NULL(); + + PG_RETURN_TEXT_P(string_to_text(res)); +} + +/* + * Internal version for use by ALTER TABLE. + * Includes a tablespace clause in the result. + * Returns a palloc'd C string; no pretty-printing. + */ +char * +pg_get_indexdef_string(Oid indexrelid) +{ + return pg_get_indexdef_worker(indexrelid, 0, NULL, + false, false, + true, true, + 0, false); +} + +/* Internal version that just reports the key-column definitions */ +char * +pg_get_indexdef_columns(Oid indexrelid, bool pretty) +{ + int prettyFlags; + + prettyFlags = GET_PRETTY_FLAGS(pretty); + + return pg_get_indexdef_worker(indexrelid, 0, NULL, + true, true, + false, false, + prettyFlags, false); +} + +/* Internal version, extensible with flags to control its behavior */ +char * +pg_get_indexdef_columns_extended(Oid indexrelid, bits16 flags) +{ + bool pretty = ((flags & RULE_INDEXDEF_PRETTY) != 0); + bool keys_only = ((flags & RULE_INDEXDEF_KEYS_ONLY) != 0); + int prettyFlags; + + prettyFlags = GET_PRETTY_FLAGS(pretty); + + return pg_get_indexdef_worker(indexrelid, 0, NULL, + true, keys_only, + false, false, + prettyFlags, false); +} + +/* + * Internal workhorse to decompile an index definition. + * + * This is now used for exclusion constraints as well: if excludeOps is not + * NULL then it points to an array of exclusion operator OIDs. + */ +static char * +pg_get_indexdef_worker(Oid indexrelid, int colno, + const Oid *excludeOps, + bool attrsOnly, bool keysOnly, + bool showTblSpc, bool inherits, + int prettyFlags, bool missing_ok) +{ + /* might want a separate isConstraint parameter later */ + bool isConstraint = (excludeOps != NULL); + HeapTuple ht_idx; + HeapTuple ht_idxrel; + HeapTuple ht_am; + Form_pg_index idxrec; + Form_pg_class idxrelrec; + Form_pg_am amrec; + IndexAmRoutine *amroutine; + List *indexprs; + ListCell *indexpr_item; + List *context; + Oid indrelid; + int keyno; + Datum indcollDatum; + Datum indclassDatum; + Datum indoptionDatum; + oidvector *indcollation; + oidvector *indclass; + int2vector *indoption; + StringInfoData buf; + char *str; + char *sep; + + /* + * Fetch the pg_index tuple by the Oid of the index + */ + ht_idx = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexrelid)); + if (!HeapTupleIsValid(ht_idx)) + { + if (missing_ok) + return NULL; + elog(ERROR, "cache lookup failed for index %u", indexrelid); + } + idxrec = (Form_pg_index) GETSTRUCT(ht_idx); + + indrelid = idxrec->indrelid; + Assert(indexrelid == idxrec->indexrelid); + + /* Must get indcollation, indclass, and indoption the hard way */ + indcollDatum = SysCacheGetAttrNotNull(INDEXRELID, ht_idx, + Anum_pg_index_indcollation); + indcollation = (oidvector *) DatumGetPointer(indcollDatum); + + indclassDatum = SysCacheGetAttrNotNull(INDEXRELID, ht_idx, + Anum_pg_index_indclass); + indclass = (oidvector *) DatumGetPointer(indclassDatum); + + indoptionDatum = SysCacheGetAttrNotNull(INDEXRELID, ht_idx, + Anum_pg_index_indoption); + indoption = (int2vector *) DatumGetPointer(indoptionDatum); + + /* + * Fetch the pg_class tuple of the index relation + */ + ht_idxrel = SearchSysCache1(RELOID, ObjectIdGetDatum(indexrelid)); + if (!HeapTupleIsValid(ht_idxrel)) + elog(ERROR, "cache lookup failed for relation %u", indexrelid); + idxrelrec = (Form_pg_class) GETSTRUCT(ht_idxrel); + + /* + * Fetch the pg_am tuple of the index' access method + */ + ht_am = SearchSysCache1(AMOID, ObjectIdGetDatum(idxrelrec->relam)); + if (!HeapTupleIsValid(ht_am)) + elog(ERROR, "cache lookup failed for access method %u", + idxrelrec->relam); + amrec = (Form_pg_am) GETSTRUCT(ht_am); + + /* Fetch the index AM's API struct */ + amroutine = GetIndexAmRoutine(amrec->amhandler); + + /* + * Get the index expressions, if any. (NOTE: we do not use the relcache + * versions of the expressions and predicate, because we want to display + * non-const-folded expressions.) + */ + if (!heap_attisnull(ht_idx, Anum_pg_index_indexprs, NULL)) + { + Datum exprsDatum; + char *exprsString; + + exprsDatum = SysCacheGetAttrNotNull(INDEXRELID, ht_idx, + Anum_pg_index_indexprs); + exprsString = TextDatumGetCString(exprsDatum); + indexprs = (List *) stringToNode(exprsString); + pfree(exprsString); + } + else + indexprs = NIL; + + indexpr_item = list_head(indexprs); + + context = deparse_context_for(get_relation_name(indrelid), indrelid); + + /* + * Start the index definition. Note that the index's name should never be + * schema-qualified, but the indexed rel's name may be. + */ + initStringInfo(&buf); + + if (!attrsOnly) + { + if (!isConstraint) + appendStringInfo(&buf, "CREATE %sINDEX %s ON %s%s USING %s (", + idxrec->indisunique ? "UNIQUE " : "", + quote_identifier(NameStr(idxrelrec->relname)), + idxrelrec->relkind == RELKIND_PARTITIONED_INDEX + && !inherits ? "ONLY " : "", + (prettyFlags & PRETTYFLAG_SCHEMA) ? + generate_relation_name(indrelid, NIL) : + generate_qualified_relation_name(indrelid), + quote_identifier(NameStr(amrec->amname))); + else /* currently, must be EXCLUDE constraint */ + appendStringInfo(&buf, "EXCLUDE USING %s (", + quote_identifier(NameStr(amrec->amname))); + } + + /* + * Report the indexed attributes + */ + sep = ""; + for (keyno = 0; keyno < idxrec->indnatts; keyno++) + { + AttrNumber attnum = idxrec->indkey.values[keyno]; + Oid keycoltype; + Oid keycolcollation; + + /* + * Ignore non-key attributes if told to. + */ + if (keysOnly && keyno >= idxrec->indnkeyatts) + break; + + /* Otherwise, print INCLUDE to divide key and non-key attrs. */ + if (!colno && keyno == idxrec->indnkeyatts) + { + appendStringInfoString(&buf, ") INCLUDE ("); + sep = ""; + } + + if (!colno) + appendStringInfoString(&buf, sep); + sep = ", "; + + if (attnum != 0) + { + /* Simple index column */ + char *attname; + int32 keycoltypmod; + + attname = get_attname(indrelid, attnum, false); + if (!colno || colno == keyno + 1) + appendStringInfoString(&buf, quote_identifier(attname)); + get_atttypetypmodcoll(indrelid, attnum, + &keycoltype, &keycoltypmod, + &keycolcollation); + } + else + { + /* expressional index */ + Node *indexkey; + + if (indexpr_item == NULL) + elog(ERROR, "too few entries in indexprs list"); + indexkey = (Node *) lfirst(indexpr_item); + indexpr_item = lnext(indexprs, indexpr_item); + /* Deparse */ + str = deparse_expression_pretty(indexkey, context, false, false, + prettyFlags, 0); + if (!colno || colno == keyno + 1) + { + /* Need parens if it's not a bare function call */ + if (looks_like_function(indexkey)) + appendStringInfoString(&buf, str); + else + appendStringInfo(&buf, "(%s)", str); + } + keycoltype = exprType(indexkey); + keycolcollation = exprCollation(indexkey); + } + + /* Print additional decoration for (selected) key columns */ + if (!attrsOnly && keyno < idxrec->indnkeyatts && + (!colno || colno == keyno + 1)) + { + int16 opt = indoption->values[keyno]; + Oid indcoll = indcollation->values[keyno]; + Datum attoptions = get_attoptions(indexrelid, keyno + 1); + bool has_options = attoptions != (Datum) 0; + + /* Add collation, if not default for column */ + if (OidIsValid(indcoll) && indcoll != keycolcollation) + appendStringInfo(&buf, " COLLATE %s", + generate_collation_name((indcoll))); + + /* Add the operator class name, if not default */ + get_opclass_name(indclass->values[keyno], + has_options ? InvalidOid : keycoltype, &buf); + + if (has_options) + { + appendStringInfoString(&buf, " ("); + get_reloptions(&buf, attoptions); + appendStringInfoChar(&buf, ')'); + } + + /* Add options if relevant */ + if (amroutine->amcanorder) + { + /* if it supports sort ordering, report DESC and NULLS opts */ + if (opt & INDOPTION_DESC) + { + appendStringInfoString(&buf, " DESC"); + /* NULLS FIRST is the default in this case */ + if (!(opt & INDOPTION_NULLS_FIRST)) + appendStringInfoString(&buf, " NULLS LAST"); + } + else + { + if (opt & INDOPTION_NULLS_FIRST) + appendStringInfoString(&buf, " NULLS FIRST"); + } + } + + /* Add the exclusion operator if relevant */ + if (excludeOps != NULL) + appendStringInfo(&buf, " WITH %s", + generate_operator_name(excludeOps[keyno], + keycoltype, + keycoltype)); + } + } + + if (!attrsOnly) + { + appendStringInfoChar(&buf, ')'); + + if (idxrec->indnullsnotdistinct) + appendStringInfoString(&buf, " NULLS NOT DISTINCT"); + + /* + * If it has options, append "WITH (options)" + */ + str = flatten_reloptions(indexrelid); + if (str) + { + appendStringInfo(&buf, " WITH (%s)", str); + pfree(str); + } + + /* + * Print tablespace, but only if requested + */ + if (showTblSpc) + { + Oid tblspc; + + tblspc = get_rel_tablespace(indexrelid); + if (OidIsValid(tblspc)) + { + if (isConstraint) + appendStringInfoString(&buf, " USING INDEX"); + appendStringInfo(&buf, " TABLESPACE %s", + quote_identifier(get_tablespace_name(tblspc))); + } + } + + /* + * If it's a partial index, decompile and append the predicate + */ + if (!heap_attisnull(ht_idx, Anum_pg_index_indpred, NULL)) + { + Node *node; + Datum predDatum; + char *predString; + + /* Convert text string to node tree */ + predDatum = SysCacheGetAttrNotNull(INDEXRELID, ht_idx, + Anum_pg_index_indpred); + predString = TextDatumGetCString(predDatum); + node = (Node *) stringToNode(predString); + pfree(predString); + + /* Deparse */ + str = deparse_expression_pretty(node, context, false, false, + prettyFlags, 0); + if (isConstraint) + appendStringInfo(&buf, " WHERE (%s)", str); + else + appendStringInfo(&buf, " WHERE %s", str); + } + } + + /* Clean up */ + ReleaseSysCache(ht_idx); + ReleaseSysCache(ht_idxrel); + ReleaseSysCache(ht_am); + + return buf.data; +} + +/* ---------- + * pg_get_querydef + * + * Public entry point to deparse one query parsetree. + * The pretty flags are determined by GET_PRETTY_FLAGS(pretty). + * + * The result is a palloc'd C string. + * ---------- + */ +char * +pg_get_querydef(Query *query, bool pretty) +{ + StringInfoData buf; + int prettyFlags; + + prettyFlags = GET_PRETTY_FLAGS(pretty); + + initStringInfo(&buf); + + get_query_def(query, &buf, NIL, NULL, true, + prettyFlags, WRAP_COLUMN_DEFAULT, 0); + + return buf.data; +} + +/* + * pg_get_statisticsobjdef + * Get the definition of an extended statistics object + */ +Datum +pg_get_statisticsobjdef(PG_FUNCTION_ARGS) +{ + Oid statextid = PG_GETARG_OID(0); + char *res; + + res = pg_get_statisticsobj_worker(statextid, false, true); + + if (res == NULL) + PG_RETURN_NULL(); + + PG_RETURN_TEXT_P(string_to_text(res)); +} + +/* + * Internal version for use by ALTER TABLE. + * Includes a tablespace clause in the result. + * Returns a palloc'd C string; no pretty-printing. + */ +char * +pg_get_statisticsobjdef_string(Oid statextid) +{ + return pg_get_statisticsobj_worker(statextid, false, false); +} + +/* + * pg_get_statisticsobjdef_columns + * Get columns and expressions for an extended statistics object + */ +Datum +pg_get_statisticsobjdef_columns(PG_FUNCTION_ARGS) +{ + Oid statextid = PG_GETARG_OID(0); + char *res; + + res = pg_get_statisticsobj_worker(statextid, true, true); + + if (res == NULL) + PG_RETURN_NULL(); + + PG_RETURN_TEXT_P(string_to_text(res)); +} + +/* + * Internal workhorse to decompile an extended statistics object. + */ +static char * +pg_get_statisticsobj_worker(Oid statextid, bool columns_only, bool missing_ok) +{ + Form_pg_statistic_ext statextrec; + HeapTuple statexttup; + StringInfoData buf; + int colno; + char *nsp; + ArrayType *arr; + char *enabled; + Datum datum; + bool ndistinct_enabled; + bool dependencies_enabled; + bool mcv_enabled; + int i; + List *context; + ListCell *lc; + List *exprs = NIL; + bool has_exprs; + int ncolumns; + + statexttup = SearchSysCache1(STATEXTOID, ObjectIdGetDatum(statextid)); + + if (!HeapTupleIsValid(statexttup)) + { + if (missing_ok) + return NULL; + elog(ERROR, "cache lookup failed for statistics object %u", statextid); + } + + /* has the statistics expressions? */ + has_exprs = !heap_attisnull(statexttup, Anum_pg_statistic_ext_stxexprs, NULL); + + statextrec = (Form_pg_statistic_ext) GETSTRUCT(statexttup); + + /* + * Get the statistics expressions, if any. (NOTE: we do not use the + * relcache versions of the expressions, because we want to display + * non-const-folded expressions.) + */ + if (has_exprs) + { + Datum exprsDatum; + char *exprsString; + + exprsDatum = SysCacheGetAttrNotNull(STATEXTOID, statexttup, + Anum_pg_statistic_ext_stxexprs); + exprsString = TextDatumGetCString(exprsDatum); + exprs = (List *) stringToNode(exprsString); + pfree(exprsString); + } + else + exprs = NIL; + + /* count the number of columns (attributes and expressions) */ + ncolumns = statextrec->stxkeys.dim1 + list_length(exprs); + + initStringInfo(&buf); + + if (!columns_only) + { + nsp = get_namespace_name_or_temp(statextrec->stxnamespace); + appendStringInfo(&buf, "CREATE STATISTICS %s", + quote_qualified_identifier(nsp, + NameStr(statextrec->stxname))); + + /* + * Decode the stxkind column so that we know which stats types to + * print. + */ + datum = SysCacheGetAttrNotNull(STATEXTOID, statexttup, + Anum_pg_statistic_ext_stxkind); + arr = DatumGetArrayTypeP(datum); + if (ARR_NDIM(arr) != 1 || + ARR_HASNULL(arr) || + ARR_ELEMTYPE(arr) != CHAROID) + elog(ERROR, "stxkind is not a 1-D char array"); + enabled = (char *) ARR_DATA_PTR(arr); + + ndistinct_enabled = false; + dependencies_enabled = false; + mcv_enabled = false; + + for (i = 0; i < ARR_DIMS(arr)[0]; i++) + { + if (enabled[i] == STATS_EXT_NDISTINCT) + ndistinct_enabled = true; + else if (enabled[i] == STATS_EXT_DEPENDENCIES) + dependencies_enabled = true; + else if (enabled[i] == STATS_EXT_MCV) + mcv_enabled = true; + + /* ignore STATS_EXT_EXPRESSIONS (it's built automatically) */ + } + + /* + * If any option is disabled, then we'll need to append the types + * clause to show which options are enabled. We omit the types clause + * on purpose when all options are enabled, so a pg_dump/pg_restore + * will create all statistics types on a newer postgres version, if + * the statistics had all options enabled on the original version. + * + * But if the statistics is defined on just a single column, it has to + * be an expression statistics. In that case we don't need to specify + * kinds. + */ + if ((!ndistinct_enabled || !dependencies_enabled || !mcv_enabled) && + (ncolumns > 1)) + { + bool gotone = false; + + appendStringInfoString(&buf, " ("); + + if (ndistinct_enabled) + { + appendStringInfoString(&buf, "ndistinct"); + gotone = true; + } + + if (dependencies_enabled) + { + appendStringInfo(&buf, "%sdependencies", gotone ? ", " : ""); + gotone = true; + } + + if (mcv_enabled) + appendStringInfo(&buf, "%smcv", gotone ? ", " : ""); + + appendStringInfoChar(&buf, ')'); + } + + appendStringInfoString(&buf, " ON "); + } + + /* decode simple column references */ + for (colno = 0; colno < statextrec->stxkeys.dim1; colno++) + { + AttrNumber attnum = statextrec->stxkeys.values[colno]; + char *attname; + + if (colno > 0) + appendStringInfoString(&buf, ", "); + + attname = get_attname(statextrec->stxrelid, attnum, false); + + appendStringInfoString(&buf, quote_identifier(attname)); + } + + context = deparse_context_for(get_relation_name(statextrec->stxrelid), + statextrec->stxrelid); + + foreach(lc, exprs) + { + Node *expr = (Node *) lfirst(lc); + char *str; + int prettyFlags = PRETTYFLAG_PAREN; + + str = deparse_expression_pretty(expr, context, false, false, + prettyFlags, 0); + + if (colno > 0) + appendStringInfoString(&buf, ", "); + + /* Need parens if it's not a bare function call */ + if (looks_like_function(expr)) + appendStringInfoString(&buf, str); + else + appendStringInfo(&buf, "(%s)", str); + + colno++; + } + + if (!columns_only) + appendStringInfo(&buf, " FROM %s", + generate_relation_name(statextrec->stxrelid, NIL)); + + ReleaseSysCache(statexttup); + + return buf.data; +} + +/* + * Generate text array of expressions for statistics object. + */ +Datum +pg_get_statisticsobjdef_expressions(PG_FUNCTION_ARGS) +{ + Oid statextid = PG_GETARG_OID(0); + Form_pg_statistic_ext statextrec; + HeapTuple statexttup; + Datum datum; + List *context; + ListCell *lc; + List *exprs = NIL; + bool has_exprs; + char *tmp; + ArrayBuildState *astate = NULL; + + statexttup = SearchSysCache1(STATEXTOID, ObjectIdGetDatum(statextid)); + + if (!HeapTupleIsValid(statexttup)) + PG_RETURN_NULL(); + + /* Does the stats object have expressions? */ + has_exprs = !heap_attisnull(statexttup, Anum_pg_statistic_ext_stxexprs, NULL); + + /* no expressions? we're done */ + if (!has_exprs) + { + ReleaseSysCache(statexttup); + PG_RETURN_NULL(); + } + + statextrec = (Form_pg_statistic_ext) GETSTRUCT(statexttup); + + /* + * Get the statistics expressions, and deparse them into text values. + */ + datum = SysCacheGetAttrNotNull(STATEXTOID, statexttup, + Anum_pg_statistic_ext_stxexprs); + tmp = TextDatumGetCString(datum); + exprs = (List *) stringToNode(tmp); + pfree(tmp); + + context = deparse_context_for(get_relation_name(statextrec->stxrelid), + statextrec->stxrelid); + + foreach(lc, exprs) + { + Node *expr = (Node *) lfirst(lc); + char *str; + int prettyFlags = PRETTYFLAG_INDENT; + + str = deparse_expression_pretty(expr, context, false, false, + prettyFlags, 0); + + astate = accumArrayResult(astate, + PointerGetDatum(cstring_to_text(str)), + false, + TEXTOID, + CurrentMemoryContext); + } + + ReleaseSysCache(statexttup); + + PG_RETURN_DATUM(makeArrayResult(astate, CurrentMemoryContext)); +} + +/* + * pg_get_partkeydef + * + * Returns the partition key specification, ie, the following: + * + * { RANGE | LIST | HASH } (column opt_collation opt_opclass [, ...]) + */ +Datum +pg_get_partkeydef(PG_FUNCTION_ARGS) +{ + Oid relid = PG_GETARG_OID(0); + char *res; + + res = pg_get_partkeydef_worker(relid, PRETTYFLAG_INDENT, false, true); + + if (res == NULL) + PG_RETURN_NULL(); + + PG_RETURN_TEXT_P(string_to_text(res)); +} + +/* Internal version that just reports the column definitions */ +char * +pg_get_partkeydef_columns(Oid relid, bool pretty) +{ + int prettyFlags; + + prettyFlags = GET_PRETTY_FLAGS(pretty); + + return pg_get_partkeydef_worker(relid, prettyFlags, true, false); +} + +/* + * Internal workhorse to decompile a partition key definition. + */ +static char * +pg_get_partkeydef_worker(Oid relid, int prettyFlags, + bool attrsOnly, bool missing_ok) +{ + Form_pg_partitioned_table form; + HeapTuple tuple; + oidvector *partclass; + oidvector *partcollation; + List *partexprs; + ListCell *partexpr_item; + List *context; + Datum datum; + StringInfoData buf; + int keyno; + char *str; + char *sep; + + tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(tuple)) + { + if (missing_ok) + return NULL; + elog(ERROR, "cache lookup failed for partition key of %u", relid); + } + + form = (Form_pg_partitioned_table) GETSTRUCT(tuple); + + Assert(form->partrelid == relid); + + /* Must get partclass and partcollation the hard way */ + datum = SysCacheGetAttrNotNull(PARTRELID, tuple, + Anum_pg_partitioned_table_partclass); + partclass = (oidvector *) DatumGetPointer(datum); + + datum = SysCacheGetAttrNotNull(PARTRELID, tuple, + Anum_pg_partitioned_table_partcollation); + partcollation = (oidvector *) DatumGetPointer(datum); + + + /* + * Get the expressions, if any. (NOTE: we do not use the relcache + * versions of the expressions, because we want to display + * non-const-folded expressions.) + */ + if (!heap_attisnull(tuple, Anum_pg_partitioned_table_partexprs, NULL)) + { + Datum exprsDatum; + char *exprsString; + + exprsDatum = SysCacheGetAttrNotNull(PARTRELID, tuple, + Anum_pg_partitioned_table_partexprs); + exprsString = TextDatumGetCString(exprsDatum); + partexprs = (List *) stringToNode(exprsString); + + if (!IsA(partexprs, List)) + elog(ERROR, "unexpected node type found in partexprs: %d", + (int) nodeTag(partexprs)); + + pfree(exprsString); + } + else + partexprs = NIL; + + partexpr_item = list_head(partexprs); + context = deparse_context_for(get_relation_name(relid), relid); + + initStringInfo(&buf); + + switch (form->partstrat) + { + case PARTITION_STRATEGY_HASH: + if (!attrsOnly) + appendStringInfoString(&buf, "HASH"); + break; + case PARTITION_STRATEGY_LIST: + if (!attrsOnly) + appendStringInfoString(&buf, "LIST"); + break; + case PARTITION_STRATEGY_RANGE: + if (!attrsOnly) + appendStringInfoString(&buf, "RANGE"); + break; + default: + elog(ERROR, "unexpected partition strategy: %d", + (int) form->partstrat); + } + + if (!attrsOnly) + appendStringInfoString(&buf, " ("); + sep = ""; + for (keyno = 0; keyno < form->partnatts; keyno++) + { + AttrNumber attnum = form->partattrs.values[keyno]; + Oid keycoltype; + Oid keycolcollation; + Oid partcoll; + + appendStringInfoString(&buf, sep); + sep = ", "; + if (attnum != 0) + { + /* Simple attribute reference */ + char *attname; + int32 keycoltypmod; + + attname = get_attname(relid, attnum, false); + appendStringInfoString(&buf, quote_identifier(attname)); + get_atttypetypmodcoll(relid, attnum, + &keycoltype, &keycoltypmod, + &keycolcollation); + } + else + { + /* Expression */ + Node *partkey; + + if (partexpr_item == NULL) + elog(ERROR, "too few entries in partexprs list"); + partkey = (Node *) lfirst(partexpr_item); + partexpr_item = lnext(partexprs, partexpr_item); + + /* Deparse */ + str = deparse_expression_pretty(partkey, context, false, false, + prettyFlags, 0); + /* Need parens if it's not a bare function call */ + if (looks_like_function(partkey)) + appendStringInfoString(&buf, str); + else + appendStringInfo(&buf, "(%s)", str); + + keycoltype = exprType(partkey); + keycolcollation = exprCollation(partkey); + } + + /* Add collation, if not default for column */ + partcoll = partcollation->values[keyno]; + if (!attrsOnly && OidIsValid(partcoll) && partcoll != keycolcollation) + appendStringInfo(&buf, " COLLATE %s", + generate_collation_name((partcoll))); + + /* Add the operator class name, if not default */ + if (!attrsOnly) + get_opclass_name(partclass->values[keyno], keycoltype, &buf); + } + + if (!attrsOnly) + appendStringInfoChar(&buf, ')'); + + /* Clean up */ + ReleaseSysCache(tuple); + + return buf.data; +} + +/* + * pg_get_partition_constraintdef + * + * Returns partition constraint expression as a string for the input relation + */ +Datum +pg_get_partition_constraintdef(PG_FUNCTION_ARGS) +{ + Oid relationId = PG_GETARG_OID(0); + Expr *constr_expr; + int prettyFlags; + List *context; + char *consrc; + + constr_expr = get_partition_qual_relid(relationId); + + /* Quick exit if no partition constraint */ + if (constr_expr == NULL) + PG_RETURN_NULL(); + + /* + * Deparse and return the constraint expression. + */ + prettyFlags = PRETTYFLAG_INDENT; + context = deparse_context_for(get_relation_name(relationId), relationId); + consrc = deparse_expression_pretty((Node *) constr_expr, context, false, + false, prettyFlags, 0); + + PG_RETURN_TEXT_P(string_to_text(consrc)); +} + +/* + * pg_get_partconstrdef_string + * + * Returns the partition constraint as a C-string for the input relation, with + * the given alias. No pretty-printing. + */ +char * +pg_get_partconstrdef_string(Oid partitionId, char *aliasname) +{ + Expr *constr_expr; + List *context; + + constr_expr = get_partition_qual_relid(partitionId); + context = deparse_context_for(aliasname, partitionId); + + return deparse_expression((Node *) constr_expr, context, true, false); +} + +/* + * pg_get_constraintdef + * + * Returns the definition for the constraint, ie, everything that needs to + * appear after "ALTER TABLE ... ADD CONSTRAINT <constraintname>". + */ +Datum +pg_get_constraintdef(PG_FUNCTION_ARGS) +{ + Oid constraintId = PG_GETARG_OID(0); + int prettyFlags; + char *res; + + prettyFlags = PRETTYFLAG_INDENT; + + res = pg_get_constraintdef_worker(constraintId, false, prettyFlags, true); + + if (res == NULL) + PG_RETURN_NULL(); + + PG_RETURN_TEXT_P(string_to_text(res)); +} + +Datum +pg_get_constraintdef_ext(PG_FUNCTION_ARGS) +{ + Oid constraintId = PG_GETARG_OID(0); + bool pretty = PG_GETARG_BOOL(1); + int prettyFlags; + char *res; + + prettyFlags = GET_PRETTY_FLAGS(pretty); + + res = pg_get_constraintdef_worker(constraintId, false, prettyFlags, true); + + if (res == NULL) + PG_RETURN_NULL(); + + PG_RETURN_TEXT_P(string_to_text(res)); +} + +/* + * Internal version that returns a full ALTER TABLE ... ADD CONSTRAINT command + */ +char * +pg_get_constraintdef_command(Oid constraintId) +{ + return pg_get_constraintdef_worker(constraintId, true, 0, false); +} + +/* + * As of 9.4, we now use an MVCC snapshot for this. + */ +static char * +pg_get_constraintdef_worker(Oid constraintId, bool fullCommand, + int prettyFlags, bool missing_ok) +{ + HeapTuple tup; + Form_pg_constraint conForm; + StringInfoData buf; + SysScanDesc scandesc; + ScanKeyData scankey[1]; + Snapshot snapshot = RegisterSnapshot(GetTransactionSnapshot()); + Relation relation = table_open(ConstraintRelationId, AccessShareLock); + + ScanKeyInit(&scankey[0], + Anum_pg_constraint_oid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(constraintId)); + + scandesc = systable_beginscan(relation, + ConstraintOidIndexId, + true, + snapshot, + 1, + scankey); + + /* + * We later use the tuple with SysCacheGetAttr() as if we had obtained it + * via SearchSysCache, which works fine. + */ + tup = systable_getnext(scandesc); + + UnregisterSnapshot(snapshot); + + if (!HeapTupleIsValid(tup)) + { + if (missing_ok) + { + systable_endscan(scandesc); + table_close(relation, AccessShareLock); + return NULL; + } + elog(ERROR, "could not find tuple for constraint %u", constraintId); + } + + conForm = (Form_pg_constraint) GETSTRUCT(tup); + + initStringInfo(&buf); + + if (fullCommand) + { + if (OidIsValid(conForm->conrelid)) + { + /* + * Currently, callers want ALTER TABLE (without ONLY) for CHECK + * constraints, and other types of constraints don't inherit + * anyway so it doesn't matter whether we say ONLY or not. Someday + * we might need to let callers specify whether to put ONLY in the + * command. + */ + appendStringInfo(&buf, "ALTER TABLE %s ADD CONSTRAINT %s ", + generate_qualified_relation_name(conForm->conrelid), + quote_identifier(NameStr(conForm->conname))); + } + else + { + /* Must be a domain constraint */ + Assert(OidIsValid(conForm->contypid)); + appendStringInfo(&buf, "ALTER DOMAIN %s ADD CONSTRAINT %s ", + generate_qualified_type_name(conForm->contypid), + quote_identifier(NameStr(conForm->conname))); + } + } + + switch (conForm->contype) + { + case CONSTRAINT_FOREIGN: + { + Datum val; + bool isnull; + const char *string; + + /* Start off the constraint definition */ + appendStringInfoString(&buf, "FOREIGN KEY ("); + + /* Fetch and build referencing-column list */ + val = SysCacheGetAttrNotNull(CONSTROID, tup, + Anum_pg_constraint_conkey); + + decompile_column_index_array(val, conForm->conrelid, &buf); + + /* add foreign relation name */ + appendStringInfo(&buf, ") REFERENCES %s(", + generate_relation_name(conForm->confrelid, + NIL)); + + /* Fetch and build referenced-column list */ + val = SysCacheGetAttrNotNull(CONSTROID, tup, + Anum_pg_constraint_confkey); + + decompile_column_index_array(val, conForm->confrelid, &buf); + + appendStringInfoChar(&buf, ')'); + + /* Add match type */ + switch (conForm->confmatchtype) + { + case FKCONSTR_MATCH_FULL: + string = " MATCH FULL"; + break; + case FKCONSTR_MATCH_PARTIAL: + string = " MATCH PARTIAL"; + break; + case FKCONSTR_MATCH_SIMPLE: + string = ""; + break; + default: + elog(ERROR, "unrecognized confmatchtype: %d", + conForm->confmatchtype); + string = ""; /* keep compiler quiet */ + break; + } + appendStringInfoString(&buf, string); + + /* Add ON UPDATE and ON DELETE clauses, if needed */ + switch (conForm->confupdtype) + { + case FKCONSTR_ACTION_NOACTION: + string = NULL; /* suppress default */ + break; + case FKCONSTR_ACTION_RESTRICT: + string = "RESTRICT"; + break; + case FKCONSTR_ACTION_CASCADE: + string = "CASCADE"; + break; + case FKCONSTR_ACTION_SETNULL: + string = "SET NULL"; + break; + case FKCONSTR_ACTION_SETDEFAULT: + string = "SET DEFAULT"; + break; + default: + elog(ERROR, "unrecognized confupdtype: %d", + conForm->confupdtype); + string = NULL; /* keep compiler quiet */ + break; + } + if (string) + appendStringInfo(&buf, " ON UPDATE %s", string); + + switch (conForm->confdeltype) + { + case FKCONSTR_ACTION_NOACTION: + string = NULL; /* suppress default */ + break; + case FKCONSTR_ACTION_RESTRICT: + string = "RESTRICT"; + break; + case FKCONSTR_ACTION_CASCADE: + string = "CASCADE"; + break; + case FKCONSTR_ACTION_SETNULL: + string = "SET NULL"; + break; + case FKCONSTR_ACTION_SETDEFAULT: + string = "SET DEFAULT"; + break; + default: + elog(ERROR, "unrecognized confdeltype: %d", + conForm->confdeltype); + string = NULL; /* keep compiler quiet */ + break; + } + if (string) + appendStringInfo(&buf, " ON DELETE %s", string); + + /* + * Add columns specified to SET NULL or SET DEFAULT if + * provided. + */ + val = SysCacheGetAttr(CONSTROID, tup, + Anum_pg_constraint_confdelsetcols, &isnull); + if (!isnull) + { + appendStringInfoString(&buf, " ("); + decompile_column_index_array(val, conForm->conrelid, &buf); + appendStringInfoChar(&buf, ')'); + } + + break; + } + case CONSTRAINT_PRIMARY: + case CONSTRAINT_UNIQUE: + { + Datum val; + Oid indexId; + int keyatts; + HeapTuple indtup; + + /* Start off the constraint definition */ + if (conForm->contype == CONSTRAINT_PRIMARY) + appendStringInfoString(&buf, "PRIMARY KEY "); + else + appendStringInfoString(&buf, "UNIQUE "); + + indexId = conForm->conindid; + + indtup = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexId)); + if (!HeapTupleIsValid(indtup)) + elog(ERROR, "cache lookup failed for index %u", indexId); + if (conForm->contype == CONSTRAINT_UNIQUE && + ((Form_pg_index) GETSTRUCT(indtup))->indnullsnotdistinct) + appendStringInfoString(&buf, "NULLS NOT DISTINCT "); + + appendStringInfoChar(&buf, '('); + + /* Fetch and build target column list */ + val = SysCacheGetAttrNotNull(CONSTROID, tup, + Anum_pg_constraint_conkey); + + keyatts = decompile_column_index_array(val, conForm->conrelid, &buf); + + appendStringInfoChar(&buf, ')'); + + /* Build including column list (from pg_index.indkeys) */ + val = SysCacheGetAttrNotNull(INDEXRELID, indtup, + Anum_pg_index_indnatts); + if (DatumGetInt32(val) > keyatts) + { + Datum cols; + Datum *keys; + int nKeys; + int j; + + appendStringInfoString(&buf, " INCLUDE ("); + + cols = SysCacheGetAttrNotNull(INDEXRELID, indtup, + Anum_pg_index_indkey); + + deconstruct_array_builtin(DatumGetArrayTypeP(cols), INT2OID, + &keys, NULL, &nKeys); + + for (j = keyatts; j < nKeys; j++) + { + char *colName; + + colName = get_attname(conForm->conrelid, + DatumGetInt16(keys[j]), false); + if (j > keyatts) + appendStringInfoString(&buf, ", "); + appendStringInfoString(&buf, quote_identifier(colName)); + } + + appendStringInfoChar(&buf, ')'); + } + ReleaseSysCache(indtup); + + /* XXX why do we only print these bits if fullCommand? */ + if (fullCommand && OidIsValid(indexId)) + { + char *options = flatten_reloptions(indexId); + Oid tblspc; + + if (options) + { + appendStringInfo(&buf, " WITH (%s)", options); + pfree(options); + } + + /* + * Print the tablespace, unless it's the database default. + * This is to help ALTER TABLE usage of this facility, + * which needs this behavior to recreate exact catalog + * state. + */ + tblspc = get_rel_tablespace(indexId); + if (OidIsValid(tblspc)) + appendStringInfo(&buf, " USING INDEX TABLESPACE %s", + quote_identifier(get_tablespace_name(tblspc))); + } + + break; + } + case CONSTRAINT_CHECK: + { + Datum val; + char *conbin; + char *consrc; + Node *expr; + List *context; + + /* Fetch constraint expression in parsetree form */ + val = SysCacheGetAttrNotNull(CONSTROID, tup, + Anum_pg_constraint_conbin); + + conbin = TextDatumGetCString(val); + expr = stringToNode(conbin); + + /* Set up deparsing context for Var nodes in constraint */ + if (conForm->conrelid != InvalidOid) + { + /* relation constraint */ + context = deparse_context_for(get_relation_name(conForm->conrelid), + conForm->conrelid); + } + else + { + /* domain constraint --- can't have Vars */ + context = NIL; + } + + consrc = deparse_expression_pretty(expr, context, false, false, + prettyFlags, 0); + + /* + * Now emit the constraint definition, adding NO INHERIT if + * necessary. + * + * There are cases where the constraint expression will be + * fully parenthesized and we don't need the outer parens ... + * but there are other cases where we do need 'em. Be + * conservative for now. + * + * Note that simply checking for leading '(' and trailing ')' + * would NOT be good enough, consider "(x > 0) AND (y > 0)". + */ + appendStringInfo(&buf, "CHECK (%s)%s", + consrc, + conForm->connoinherit ? " NO INHERIT" : ""); + break; + } + case CONSTRAINT_TRIGGER: + + /* + * There isn't an ALTER TABLE syntax for creating a user-defined + * constraint trigger, but it seems better to print something than + * throw an error; if we throw error then this function couldn't + * safely be applied to all rows of pg_constraint. + */ + appendStringInfoString(&buf, "TRIGGER"); + break; + case CONSTRAINT_EXCLUSION: + { + Oid indexOid = conForm->conindid; + Datum val; + Datum *elems; + int nElems; + int i; + Oid *operators; + + /* Extract operator OIDs from the pg_constraint tuple */ + val = SysCacheGetAttrNotNull(CONSTROID, tup, + Anum_pg_constraint_conexclop); + + deconstruct_array_builtin(DatumGetArrayTypeP(val), OIDOID, + &elems, NULL, &nElems); + + operators = (Oid *) palloc(nElems * sizeof(Oid)); + for (i = 0; i < nElems; i++) + operators[i] = DatumGetObjectId(elems[i]); + + /* pg_get_indexdef_worker does the rest */ + /* suppress tablespace because pg_dump wants it that way */ + appendStringInfoString(&buf, + pg_get_indexdef_worker(indexOid, + 0, + operators, + false, + false, + false, + false, + prettyFlags, + false)); + break; + } + default: + elog(ERROR, "invalid constraint type \"%c\"", conForm->contype); + break; + } + + if (conForm->condeferrable) + appendStringInfoString(&buf, " DEFERRABLE"); + if (conForm->condeferred) + appendStringInfoString(&buf, " INITIALLY DEFERRED"); + if (!conForm->convalidated) + appendStringInfoString(&buf, " NOT VALID"); + + /* Cleanup */ + systable_endscan(scandesc); + table_close(relation, AccessShareLock); + + return buf.data; +} + + +/* + * Convert an int16[] Datum into a comma-separated list of column names + * for the indicated relation; append the list to buf. Returns the number + * of keys. + */ +static int +decompile_column_index_array(Datum column_index_array, Oid relId, + StringInfo buf) +{ + Datum *keys; + int nKeys; + int j; + + /* Extract data from array of int16 */ + deconstruct_array_builtin(DatumGetArrayTypeP(column_index_array), INT2OID, + &keys, NULL, &nKeys); + + for (j = 0; j < nKeys; j++) + { + char *colName; + + colName = get_attname(relId, DatumGetInt16(keys[j]), false); + + if (j == 0) + appendStringInfoString(buf, quote_identifier(colName)); + else + appendStringInfo(buf, ", %s", quote_identifier(colName)); + } + + return nKeys; +} + + +/* ---------- + * pg_get_expr - Decompile an expression tree + * + * Input: an expression tree in nodeToString form, and a relation OID + * + * Output: reverse-listed expression + * + * Currently, the expression can only refer to a single relation, namely + * the one specified by the second parameter. This is sufficient for + * partial indexes, column default expressions, etc. We also support + * Var-free expressions, for which the OID can be InvalidOid. + * + * If the OID is nonzero but not actually valid, don't throw an error, + * just return NULL. This is a bit questionable, but it's what we've + * done historically, and it can help avoid unwanted failures when + * examining catalog entries for just-deleted relations. + * + * We expect this function to work, or throw a reasonably clean error, + * for any node tree that can appear in a catalog pg_node_tree column. + * Query trees, such as those appearing in pg_rewrite.ev_action, are + * not supported. Nor are expressions in more than one relation, which + * can appear in places like pg_rewrite.ev_qual. + * ---------- + */ +Datum +pg_get_expr(PG_FUNCTION_ARGS) +{ + text *expr = PG_GETARG_TEXT_PP(0); + Oid relid = PG_GETARG_OID(1); + text *result; + int prettyFlags; + + prettyFlags = PRETTYFLAG_INDENT; + + result = pg_get_expr_worker(expr, relid, prettyFlags); + if (result) + PG_RETURN_TEXT_P(result); + else + PG_RETURN_NULL(); +} + +Datum +pg_get_expr_ext(PG_FUNCTION_ARGS) +{ + text *expr = PG_GETARG_TEXT_PP(0); + Oid relid = PG_GETARG_OID(1); + bool pretty = PG_GETARG_BOOL(2); + text *result; + int prettyFlags; + + prettyFlags = GET_PRETTY_FLAGS(pretty); + + result = pg_get_expr_worker(expr, relid, prettyFlags); + if (result) + PG_RETURN_TEXT_P(result); + else + PG_RETURN_NULL(); +} + +static text * +pg_get_expr_worker(text *expr, Oid relid, int prettyFlags) +{ + Node *node; + Node *tst; + Relids relids; + List *context; + char *exprstr; + Relation rel = NULL; + char *str; + + /* Convert input pg_node_tree (really TEXT) object to C string */ + exprstr = text_to_cstring(expr); + + /* Convert expression to node tree */ + node = (Node *) stringToNode(exprstr); + + pfree(exprstr); + + /* + * Throw error if the input is a querytree rather than an expression tree. + * While we could support queries here, there seems no very good reason + * to. In most such catalog columns, we'll see a List of Query nodes, or + * even nested Lists, so drill down to a non-List node before checking. + */ + tst = node; + while (tst && IsA(tst, List)) + tst = linitial((List *) tst); + if (tst && IsA(tst, Query)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("input is a query, not an expression"))); + + /* + * Throw error if the expression contains Vars we won't be able to + * deparse. + */ + relids = pull_varnos(NULL, node); + if (OidIsValid(relid)) + { + if (!bms_is_subset(relids, bms_make_singleton(1))) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("expression contains variables of more than one relation"))); + } + else + { + if (!bms_is_empty(relids)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("expression contains variables"))); + } + + /* + * Prepare deparse context if needed. If we are deparsing with a relid, + * we need to transiently open and lock the rel, to make sure it won't go + * away underneath us. (set_relation_column_names would lock it anyway, + * so this isn't really introducing any new behavior.) + */ + if (OidIsValid(relid)) + { + rel = try_relation_open(relid, AccessShareLock); + if (rel == NULL) + return NULL; + context = deparse_context_for(RelationGetRelationName(rel), relid); + } + else + context = NIL; + + /* Deparse */ + str = deparse_expression_pretty(node, context, false, false, + prettyFlags, 0); + + if (rel != NULL) + relation_close(rel, AccessShareLock); + + return string_to_text(str); +} + + +/* ---------- + * pg_get_userbyid - Get a user name by roleid and + * fallback to 'unknown (OID=n)' + * ---------- + */ +Datum +pg_get_userbyid(PG_FUNCTION_ARGS) +{ + Oid roleid = PG_GETARG_OID(0); + Name result; + HeapTuple roletup; + Form_pg_authid role_rec; + + /* + * Allocate space for the result + */ + result = (Name) palloc(NAMEDATALEN); + memset(NameStr(*result), 0, NAMEDATALEN); + + /* + * Get the pg_authid entry and print the result + */ + roletup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid)); + if (HeapTupleIsValid(roletup)) + { + role_rec = (Form_pg_authid) GETSTRUCT(roletup); + *result = role_rec->rolname; + ReleaseSysCache(roletup); + } + else + sprintf(NameStr(*result), "unknown (OID=%u)", roleid); + + PG_RETURN_NAME(result); +} + + +/* + * pg_get_serial_sequence + * Get the name of the sequence used by an identity or serial column, + * formatted suitably for passing to setval, nextval or currval. + * First parameter is not treated as double-quoted, second parameter + * is --- see documentation for reason. + */ +Datum +pg_get_serial_sequence(PG_FUNCTION_ARGS) +{ + text *tablename = PG_GETARG_TEXT_PP(0); + text *columnname = PG_GETARG_TEXT_PP(1); + RangeVar *tablerv; + Oid tableOid; + char *column; + AttrNumber attnum; + Oid sequenceId = InvalidOid; + Relation depRel; + ScanKeyData key[3]; + SysScanDesc scan; + HeapTuple tup; + + /* Look up table name. Can't lock it - we might not have privileges. */ + tablerv = makeRangeVarFromNameList(textToQualifiedNameList(tablename)); + tableOid = RangeVarGetRelid(tablerv, NoLock, false); + + /* Get the number of the column */ + column = text_to_cstring(columnname); + + attnum = get_attnum(tableOid, column); + if (attnum == InvalidAttrNumber) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column \"%s\" of relation \"%s\" does not exist", + column, tablerv->relname))); + + /* Search the dependency table for the dependent sequence */ + depRel = table_open(DependRelationId, AccessShareLock); + + ScanKeyInit(&key[0], + Anum_pg_depend_refclassid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(RelationRelationId)); + ScanKeyInit(&key[1], + Anum_pg_depend_refobjid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(tableOid)); + ScanKeyInit(&key[2], + Anum_pg_depend_refobjsubid, + BTEqualStrategyNumber, F_INT4EQ, + Int32GetDatum(attnum)); + + scan = systable_beginscan(depRel, DependReferenceIndexId, true, + NULL, 3, key); + + while (HeapTupleIsValid(tup = systable_getnext(scan))) + { + Form_pg_depend deprec = (Form_pg_depend) GETSTRUCT(tup); + + /* + * Look for an auto dependency (serial column) or internal dependency + * (identity column) of a sequence on a column. (We need the relkind + * test because indexes can also have auto dependencies on columns.) + */ + if (deprec->classid == RelationRelationId && + deprec->objsubid == 0 && + (deprec->deptype == DEPENDENCY_AUTO || + deprec->deptype == DEPENDENCY_INTERNAL) && + get_rel_relkind(deprec->objid) == RELKIND_SEQUENCE) + { + sequenceId = deprec->objid; + break; + } + } + + systable_endscan(scan); + table_close(depRel, AccessShareLock); + + if (OidIsValid(sequenceId)) + { + char *result; + + result = generate_qualified_relation_name(sequenceId); + + PG_RETURN_TEXT_P(string_to_text(result)); + } + + PG_RETURN_NULL(); +} + + +/* + * pg_get_functiondef + * Returns the complete "CREATE OR REPLACE FUNCTION ..." statement for + * the specified function. + * + * Note: if you change the output format of this function, be careful not + * to break psql's rules (in \ef and \sf) for identifying the start of the + * function body. To wit: the function body starts on a line that begins with + * "AS ", "BEGIN ", or "RETURN ", and no preceding line will look like that. + */ +Datum +pg_get_functiondef(PG_FUNCTION_ARGS) +{ + Oid funcid = PG_GETARG_OID(0); + StringInfoData buf; + StringInfoData dq; + HeapTuple proctup; + Form_pg_proc proc; + bool isfunction; + Datum tmp; + bool isnull; + const char *prosrc; + const char *name; + const char *nsp; + float4 procost; + int oldlen; + + initStringInfo(&buf); + + /* Look up the function */ + proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid)); + if (!HeapTupleIsValid(proctup)) + PG_RETURN_NULL(); + + proc = (Form_pg_proc) GETSTRUCT(proctup); + name = NameStr(proc->proname); + + if (proc->prokind == PROKIND_AGGREGATE) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is an aggregate function", name))); + + isfunction = (proc->prokind != PROKIND_PROCEDURE); + + /* + * We always qualify the function name, to ensure the right function gets + * replaced. + */ + nsp = get_namespace_name_or_temp(proc->pronamespace); + appendStringInfo(&buf, "CREATE OR REPLACE %s %s(", + isfunction ? "FUNCTION" : "PROCEDURE", + quote_qualified_identifier(nsp, name)); + (void) print_function_arguments(&buf, proctup, false, true); + appendStringInfoString(&buf, ")\n"); + if (isfunction) + { + appendStringInfoString(&buf, " RETURNS "); + print_function_rettype(&buf, proctup); + appendStringInfoChar(&buf, '\n'); + } + + print_function_trftypes(&buf, proctup); + + appendStringInfo(&buf, " LANGUAGE %s\n", + quote_identifier(get_language_name(proc->prolang, false))); + + /* Emit some miscellaneous options on one line */ + oldlen = buf.len; + + if (proc->prokind == PROKIND_WINDOW) + appendStringInfoString(&buf, " WINDOW"); + switch (proc->provolatile) + { + case PROVOLATILE_IMMUTABLE: + appendStringInfoString(&buf, " IMMUTABLE"); + break; + case PROVOLATILE_STABLE: + appendStringInfoString(&buf, " STABLE"); + break; + case PROVOLATILE_VOLATILE: + break; + } + + switch (proc->proparallel) + { + case PROPARALLEL_SAFE: + appendStringInfoString(&buf, " PARALLEL SAFE"); + break; + case PROPARALLEL_RESTRICTED: + appendStringInfoString(&buf, " PARALLEL RESTRICTED"); + break; + case PROPARALLEL_UNSAFE: + break; + } + + if (proc->proisstrict) + appendStringInfoString(&buf, " STRICT"); + if (proc->prosecdef) + appendStringInfoString(&buf, " SECURITY DEFINER"); + if (proc->proleakproof) + appendStringInfoString(&buf, " LEAKPROOF"); + + /* This code for the default cost and rows should match functioncmds.c */ + if (proc->prolang == INTERNALlanguageId || + proc->prolang == ClanguageId) + procost = 1; + else + procost = 100; + if (proc->procost != procost) + appendStringInfo(&buf, " COST %g", proc->procost); + + if (proc->prorows > 0 && proc->prorows != 1000) + appendStringInfo(&buf, " ROWS %g", proc->prorows); + + if (proc->prosupport) + { + Oid argtypes[1]; + + /* + * We should qualify the support function's name if it wouldn't be + * resolved by lookup in the current search path. + */ + argtypes[0] = INTERNALOID; + appendStringInfo(&buf, " SUPPORT %s", + generate_function_name(proc->prosupport, 1, + NIL, argtypes, + false, NULL, EXPR_KIND_NONE)); + } + + if (oldlen != buf.len) + appendStringInfoChar(&buf, '\n'); + + /* Emit any proconfig options, one per line */ + tmp = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_proconfig, &isnull); + if (!isnull) + { + ArrayType *a = DatumGetArrayTypeP(tmp); + int i; + + Assert(ARR_ELEMTYPE(a) == TEXTOID); + Assert(ARR_NDIM(a) == 1); + Assert(ARR_LBOUND(a)[0] == 1); + + for (i = 1; i <= ARR_DIMS(a)[0]; i++) + { + Datum d; + + d = array_ref(a, 1, &i, + -1 /* varlenarray */ , + -1 /* TEXT's typlen */ , + false /* TEXT's typbyval */ , + TYPALIGN_INT /* TEXT's typalign */ , + &isnull); + if (!isnull) + { + char *configitem = TextDatumGetCString(d); + char *pos; + + pos = strchr(configitem, '='); + if (pos == NULL) + continue; + *pos++ = '\0'; + + appendStringInfo(&buf, " SET %s TO ", + quote_identifier(configitem)); + + /* + * Variables that are marked GUC_LIST_QUOTE were already fully + * quoted by flatten_set_variable_args() before they were put + * into the proconfig array. However, because the quoting + * rules used there aren't exactly like SQL's, we have to + * break the list value apart and then quote the elements as + * string literals. (The elements may be double-quoted as-is, + * but we can't just feed them to the SQL parser; it would do + * the wrong thing with elements that are zero-length or + * longer than NAMEDATALEN.) + * + * Variables that are not so marked should just be emitted as + * simple string literals. If the variable is not known to + * guc.c, we'll do that; this makes it unsafe to use + * GUC_LIST_QUOTE for extension variables. + */ + if (GetConfigOptionFlags(configitem, true) & GUC_LIST_QUOTE) + { + List *namelist; + ListCell *lc; + + /* Parse string into list of identifiers */ + if (!SplitGUCList(pos, ',', &namelist)) + { + /* this shouldn't fail really */ + elog(ERROR, "invalid list syntax in proconfig item"); + } + foreach(lc, namelist) + { + char *curname = (char *) lfirst(lc); + + simple_quote_literal(&buf, curname); + if (lnext(namelist, lc)) + appendStringInfoString(&buf, ", "); + } + } + else + simple_quote_literal(&buf, pos); + appendStringInfoChar(&buf, '\n'); + } + } + } + + /* And finally the function definition ... */ + (void) SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_prosqlbody, &isnull); + if (proc->prolang == SQLlanguageId && !isnull) + { + print_function_sqlbody(&buf, proctup); + } + else + { + appendStringInfoString(&buf, "AS "); + + tmp = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_probin, &isnull); + if (!isnull) + { + simple_quote_literal(&buf, TextDatumGetCString(tmp)); + appendStringInfoString(&buf, ", "); /* assume prosrc isn't null */ + } + + tmp = SysCacheGetAttrNotNull(PROCOID, proctup, Anum_pg_proc_prosrc); + prosrc = TextDatumGetCString(tmp); + + /* + * We always use dollar quoting. Figure out a suitable delimiter. + * + * Since the user is likely to be editing the function body string, we + * shouldn't use a short delimiter that he might easily create a + * conflict with. Hence prefer "$function$"/"$procedure$", but extend + * if needed. + */ + initStringInfo(&dq); + appendStringInfoChar(&dq, '$'); + appendStringInfoString(&dq, (isfunction ? "function" : "procedure")); + while (strstr(prosrc, dq.data) != NULL) + appendStringInfoChar(&dq, 'x'); + appendStringInfoChar(&dq, '$'); + + appendBinaryStringInfo(&buf, dq.data, dq.len); + appendStringInfoString(&buf, prosrc); + appendBinaryStringInfo(&buf, dq.data, dq.len); + } + + appendStringInfoChar(&buf, '\n'); + + ReleaseSysCache(proctup); + + PG_RETURN_TEXT_P(string_to_text(buf.data)); +} + +/* + * pg_get_function_arguments + * Get a nicely-formatted list of arguments for a function. + * This is everything that would go between the parentheses in + * CREATE FUNCTION. + */ +Datum +pg_get_function_arguments(PG_FUNCTION_ARGS) +{ + Oid funcid = PG_GETARG_OID(0); + StringInfoData buf; + HeapTuple proctup; + + proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid)); + if (!HeapTupleIsValid(proctup)) + PG_RETURN_NULL(); + + initStringInfo(&buf); + + (void) print_function_arguments(&buf, proctup, false, true); + + ReleaseSysCache(proctup); + + PG_RETURN_TEXT_P(string_to_text(buf.data)); +} + +/* + * pg_get_function_identity_arguments + * Get a formatted list of arguments for a function. + * This is everything that would go between the parentheses in + * ALTER FUNCTION, etc. In particular, don't print defaults. + */ +Datum +pg_get_function_identity_arguments(PG_FUNCTION_ARGS) +{ + Oid funcid = PG_GETARG_OID(0); + StringInfoData buf; + HeapTuple proctup; + + proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid)); + if (!HeapTupleIsValid(proctup)) + PG_RETURN_NULL(); + + initStringInfo(&buf); + + (void) print_function_arguments(&buf, proctup, false, false); + + ReleaseSysCache(proctup); + + PG_RETURN_TEXT_P(string_to_text(buf.data)); +} + +/* + * pg_get_function_result + * Get a nicely-formatted version of the result type of a function. + * This is what would appear after RETURNS in CREATE FUNCTION. + */ +Datum +pg_get_function_result(PG_FUNCTION_ARGS) +{ + Oid funcid = PG_GETARG_OID(0); + StringInfoData buf; + HeapTuple proctup; + + proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid)); + if (!HeapTupleIsValid(proctup)) + PG_RETURN_NULL(); + + if (((Form_pg_proc) GETSTRUCT(proctup))->prokind == PROKIND_PROCEDURE) + { + ReleaseSysCache(proctup); + PG_RETURN_NULL(); + } + + initStringInfo(&buf); + + print_function_rettype(&buf, proctup); + + ReleaseSysCache(proctup); + + PG_RETURN_TEXT_P(string_to_text(buf.data)); +} + +/* + * Guts of pg_get_function_result: append the function's return type + * to the specified buffer. + */ +static void +print_function_rettype(StringInfo buf, HeapTuple proctup) +{ + Form_pg_proc proc = (Form_pg_proc) GETSTRUCT(proctup); + int ntabargs = 0; + StringInfoData rbuf; + + initStringInfo(&rbuf); + + if (proc->proretset) + { + /* It might be a table function; try to print the arguments */ + appendStringInfoString(&rbuf, "TABLE("); + ntabargs = print_function_arguments(&rbuf, proctup, true, false); + if (ntabargs > 0) + appendStringInfoChar(&rbuf, ')'); + else + resetStringInfo(&rbuf); + } + + if (ntabargs == 0) + { + /* Not a table function, so do the normal thing */ + if (proc->proretset) + appendStringInfoString(&rbuf, "SETOF "); + appendStringInfoString(&rbuf, format_type_be(proc->prorettype)); + } + + appendBinaryStringInfo(buf, rbuf.data, rbuf.len); +} + +/* + * Common code for pg_get_function_arguments and pg_get_function_result: + * append the desired subset of arguments to buf. We print only TABLE + * arguments when print_table_args is true, and all the others when it's false. + * We print argument defaults only if print_defaults is true. + * Function return value is the number of arguments printed. + */ +static int +print_function_arguments(StringInfo buf, HeapTuple proctup, + bool print_table_args, bool print_defaults) +{ + Form_pg_proc proc = (Form_pg_proc) GETSTRUCT(proctup); + int numargs; + Oid *argtypes; + char **argnames; + char *argmodes; + int insertorderbyat = -1; + int argsprinted; + int inputargno; + int nlackdefaults; + List *argdefaults = NIL; + ListCell *nextargdefault = NULL; + int i; + + numargs = get_func_arg_info(proctup, + &argtypes, &argnames, &argmodes); + + nlackdefaults = numargs; + if (print_defaults && proc->pronargdefaults > 0) + { + Datum proargdefaults; + bool isnull; + + proargdefaults = SysCacheGetAttr(PROCOID, proctup, + Anum_pg_proc_proargdefaults, + &isnull); + if (!isnull) + { + char *str; + + str = TextDatumGetCString(proargdefaults); + argdefaults = castNode(List, stringToNode(str)); + pfree(str); + nextargdefault = list_head(argdefaults); + /* nlackdefaults counts only *input* arguments lacking defaults */ + nlackdefaults = proc->pronargs - list_length(argdefaults); + } + } + + /* Check for special treatment of ordered-set aggregates */ + if (proc->prokind == PROKIND_AGGREGATE) + { + HeapTuple aggtup; + Form_pg_aggregate agg; + + aggtup = SearchSysCache1(AGGFNOID, proc->oid); + if (!HeapTupleIsValid(aggtup)) + elog(ERROR, "cache lookup failed for aggregate %u", + proc->oid); + agg = (Form_pg_aggregate) GETSTRUCT(aggtup); + if (AGGKIND_IS_ORDERED_SET(agg->aggkind)) + insertorderbyat = agg->aggnumdirectargs; + ReleaseSysCache(aggtup); + } + + argsprinted = 0; + inputargno = 0; + for (i = 0; i < numargs; i++) + { + Oid argtype = argtypes[i]; + char *argname = argnames ? argnames[i] : NULL; + char argmode = argmodes ? argmodes[i] : PROARGMODE_IN; + const char *modename; + bool isinput; + + switch (argmode) + { + case PROARGMODE_IN: + + /* + * For procedures, explicitly mark all argument modes, so as + * to avoid ambiguity with the SQL syntax for DROP PROCEDURE. + */ + if (proc->prokind == PROKIND_PROCEDURE) + modename = "IN "; + else + modename = ""; + isinput = true; + break; + case PROARGMODE_INOUT: + modename = "INOUT "; + isinput = true; + break; + case PROARGMODE_OUT: + modename = "OUT "; + isinput = false; + break; + case PROARGMODE_VARIADIC: + modename = "VARIADIC "; + isinput = true; + break; + case PROARGMODE_TABLE: + modename = ""; + isinput = false; + break; + default: + elog(ERROR, "invalid parameter mode '%c'", argmode); + modename = NULL; /* keep compiler quiet */ + isinput = false; + break; + } + if (isinput) + inputargno++; /* this is a 1-based counter */ + + if (print_table_args != (argmode == PROARGMODE_TABLE)) + continue; + + if (argsprinted == insertorderbyat) + { + if (argsprinted) + appendStringInfoChar(buf, ' '); + appendStringInfoString(buf, "ORDER BY "); + } + else if (argsprinted) + appendStringInfoString(buf, ", "); + + appendStringInfoString(buf, modename); + if (argname && argname[0]) + appendStringInfo(buf, "%s ", quote_identifier(argname)); + appendStringInfoString(buf, format_type_be(argtype)); + if (print_defaults && isinput && inputargno > nlackdefaults) + { + Node *expr; + + Assert(nextargdefault != NULL); + expr = (Node *) lfirst(nextargdefault); + nextargdefault = lnext(argdefaults, nextargdefault); + + appendStringInfo(buf, " DEFAULT %s", + deparse_expression(expr, NIL, false, false)); + } + argsprinted++; + + /* nasty hack: print the last arg twice for variadic ordered-set agg */ + if (argsprinted == insertorderbyat && i == numargs - 1) + { + i--; + /* aggs shouldn't have defaults anyway, but just to be sure ... */ + print_defaults = false; + } + } + + return argsprinted; +} + +static bool +is_input_argument(int nth, const char *argmodes) +{ + return (!argmodes + || argmodes[nth] == PROARGMODE_IN + || argmodes[nth] == PROARGMODE_INOUT + || argmodes[nth] == PROARGMODE_VARIADIC); +} + +/* + * Append used transformed types to specified buffer + */ +static void +print_function_trftypes(StringInfo buf, HeapTuple proctup) +{ + Oid *trftypes; + int ntypes; + + ntypes = get_func_trftypes(proctup, &trftypes); + if (ntypes > 0) + { + int i; + + appendStringInfoString(buf, " TRANSFORM "); + for (i = 0; i < ntypes; i++) + { + if (i != 0) + appendStringInfoString(buf, ", "); + appendStringInfo(buf, "FOR TYPE %s", format_type_be(trftypes[i])); + } + appendStringInfoChar(buf, '\n'); + } +} + +/* + * Get textual representation of a function argument's default value. The + * second argument of this function is the argument number among all arguments + * (i.e. proallargtypes, *not* proargtypes), starting with 1, because that's + * how information_schema.sql uses it. + */ +Datum +pg_get_function_arg_default(PG_FUNCTION_ARGS) +{ + Oid funcid = PG_GETARG_OID(0); + int32 nth_arg = PG_GETARG_INT32(1); + HeapTuple proctup; + Form_pg_proc proc; + int numargs; + Oid *argtypes; + char **argnames; + char *argmodes; + int i; + List *argdefaults; + Node *node; + char *str; + int nth_inputarg; + Datum proargdefaults; + bool isnull; + int nth_default; + + proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid)); + if (!HeapTupleIsValid(proctup)) + PG_RETURN_NULL(); + + numargs = get_func_arg_info(proctup, &argtypes, &argnames, &argmodes); + if (nth_arg < 1 || nth_arg > numargs || !is_input_argument(nth_arg - 1, argmodes)) + { + ReleaseSysCache(proctup); + PG_RETURN_NULL(); + } + + nth_inputarg = 0; + for (i = 0; i < nth_arg; i++) + if (is_input_argument(i, argmodes)) + nth_inputarg++; + + proargdefaults = SysCacheGetAttr(PROCOID, proctup, + Anum_pg_proc_proargdefaults, + &isnull); + if (isnull) + { + ReleaseSysCache(proctup); + PG_RETURN_NULL(); + } + + str = TextDatumGetCString(proargdefaults); + argdefaults = castNode(List, stringToNode(str)); + pfree(str); + + proc = (Form_pg_proc) GETSTRUCT(proctup); + + /* + * Calculate index into proargdefaults: proargdefaults corresponds to the + * last N input arguments, where N = pronargdefaults. + */ + nth_default = nth_inputarg - 1 - (proc->pronargs - proc->pronargdefaults); + + if (nth_default < 0 || nth_default >= list_length(argdefaults)) + { + ReleaseSysCache(proctup); + PG_RETURN_NULL(); + } + node = list_nth(argdefaults, nth_default); + str = deparse_expression(node, NIL, false, false); + + ReleaseSysCache(proctup); + + PG_RETURN_TEXT_P(string_to_text(str)); +} + +static void +print_function_sqlbody(StringInfo buf, HeapTuple proctup) +{ + int numargs; + Oid *argtypes; + char **argnames; + char *argmodes; + deparse_namespace dpns = {0}; + Datum tmp; + Node *n; + + dpns.funcname = pstrdup(NameStr(((Form_pg_proc) GETSTRUCT(proctup))->proname)); + numargs = get_func_arg_info(proctup, + &argtypes, &argnames, &argmodes); + dpns.numargs = numargs; + dpns.argnames = argnames; + + tmp = SysCacheGetAttrNotNull(PROCOID, proctup, Anum_pg_proc_prosqlbody); + n = stringToNode(TextDatumGetCString(tmp)); + + if (IsA(n, List)) + { + List *stmts; + ListCell *lc; + + stmts = linitial(castNode(List, n)); + + appendStringInfoString(buf, "BEGIN ATOMIC\n"); + + foreach(lc, stmts) + { + Query *query = lfirst_node(Query, lc); + + /* It seems advisable to get at least AccessShareLock on rels */ + AcquireRewriteLocks(query, false, false); + get_query_def(query, buf, list_make1(&dpns), NULL, false, + PRETTYFLAG_INDENT, WRAP_COLUMN_DEFAULT, 1); + appendStringInfoChar(buf, ';'); + appendStringInfoChar(buf, '\n'); + } + + appendStringInfoString(buf, "END"); + } + else + { + Query *query = castNode(Query, n); + + /* It seems advisable to get at least AccessShareLock on rels */ + AcquireRewriteLocks(query, false, false); + get_query_def(query, buf, list_make1(&dpns), NULL, false, + 0, WRAP_COLUMN_DEFAULT, 0); + } +} + +Datum +pg_get_function_sqlbody(PG_FUNCTION_ARGS) +{ + Oid funcid = PG_GETARG_OID(0); + StringInfoData buf; + HeapTuple proctup; + bool isnull; + + initStringInfo(&buf); + + /* Look up the function */ + proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid)); + if (!HeapTupleIsValid(proctup)) + PG_RETURN_NULL(); + + (void) SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_prosqlbody, &isnull); + if (isnull) + { + ReleaseSysCache(proctup); + PG_RETURN_NULL(); + } + + print_function_sqlbody(&buf, proctup); + + ReleaseSysCache(proctup); + + PG_RETURN_TEXT_P(cstring_to_text_with_len(buf.data, buf.len)); +} + + +/* + * deparse_expression - General utility for deparsing expressions + * + * calls deparse_expression_pretty with all prettyPrinting disabled + */ +char * +deparse_expression(Node *expr, List *dpcontext, + bool forceprefix, bool showimplicit) +{ + return deparse_expression_pretty(expr, dpcontext, forceprefix, + showimplicit, 0, 0); +} + +/* ---------- + * deparse_expression_pretty - General utility for deparsing expressions + * + * expr is the node tree to be deparsed. It must be a transformed expression + * tree (ie, not the raw output of gram.y). + * + * dpcontext is a list of deparse_namespace nodes representing the context + * for interpreting Vars in the node tree. It can be NIL if no Vars are + * expected. + * + * forceprefix is true to force all Vars to be prefixed with their table names. + * + * showimplicit is true to force all implicit casts to be shown explicitly. + * + * Tries to pretty up the output according to prettyFlags and startIndent. + * + * The result is a palloc'd string. + * ---------- + */ +static char * +deparse_expression_pretty(Node *expr, List *dpcontext, + bool forceprefix, bool showimplicit, + int prettyFlags, int startIndent) +{ + StringInfoData buf; + deparse_context context; + + initStringInfo(&buf); + context.buf = &buf; + context.namespaces = dpcontext; + context.windowClause = NIL; + context.windowTList = NIL; + context.varprefix = forceprefix; + context.prettyFlags = prettyFlags; + context.wrapColumn = WRAP_COLUMN_DEFAULT; + context.indentLevel = startIndent; + context.special_exprkind = EXPR_KIND_NONE; + context.appendparents = NULL; + + get_rule_expr(expr, &context, showimplicit); + + return buf.data; +} + +/* ---------- + * deparse_context_for - Build deparse context for a single relation + * + * Given the reference name (alias) and OID of a relation, build deparsing + * context for an expression referencing only that relation (as varno 1, + * varlevelsup 0). This is sufficient for many uses of deparse_expression. + * ---------- + */ +List * +deparse_context_for(const char *aliasname, Oid relid) +{ + deparse_namespace *dpns; + RangeTblEntry *rte; + + dpns = (deparse_namespace *) palloc0(sizeof(deparse_namespace)); + + /* Build a minimal RTE for the rel */ + rte = makeNode(RangeTblEntry); + rte->rtekind = RTE_RELATION; + rte->relid = relid; + rte->relkind = RELKIND_RELATION; /* no need for exactness here */ + rte->rellockmode = AccessShareLock; + rte->alias = makeAlias(aliasname, NIL); + rte->eref = rte->alias; + rte->lateral = false; + rte->inh = false; + rte->inFromCl = true; + + /* Build one-element rtable */ + dpns->rtable = list_make1(rte); + dpns->subplans = NIL; + dpns->ctes = NIL; + dpns->appendrels = NULL; + set_rtable_names(dpns, NIL, NULL); + set_simple_column_names(dpns); + + /* Return a one-deep namespace stack */ + return list_make1(dpns); +} + +/* + * deparse_context_for_plan_tree - Build deparse context for a Plan tree + * + * When deparsing an expression in a Plan tree, we use the plan's rangetable + * to resolve names of simple Vars. The initialization of column names for + * this is rather expensive if the rangetable is large, and it'll be the same + * for every expression in the Plan tree; so we do it just once and re-use + * the result of this function for each expression. (Note that the result + * is not usable until set_deparse_context_plan() is applied to it.) + * + * In addition to the PlannedStmt, pass the per-RTE alias names + * assigned by a previous call to select_rtable_names_for_explain. + */ +List * +deparse_context_for_plan_tree(PlannedStmt *pstmt, List *rtable_names) +{ + deparse_namespace *dpns; + + dpns = (deparse_namespace *) palloc0(sizeof(deparse_namespace)); + + /* Initialize fields that stay the same across the whole plan tree */ + dpns->rtable = pstmt->rtable; + dpns->rtable_names = rtable_names; + dpns->subplans = pstmt->subplans; + dpns->ctes = NIL; + if (pstmt->appendRelations) + { + /* Set up the array, indexed by child relid */ + int ntables = list_length(dpns->rtable); + ListCell *lc; + + dpns->appendrels = (AppendRelInfo **) + palloc0((ntables + 1) * sizeof(AppendRelInfo *)); + foreach(lc, pstmt->appendRelations) + { + AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc); + Index crelid = appinfo->child_relid; + + Assert(crelid > 0 && crelid <= ntables); + Assert(dpns->appendrels[crelid] == NULL); + dpns->appendrels[crelid] = appinfo; + } + } + else + dpns->appendrels = NULL; /* don't need it */ + + /* + * Set up column name aliases. We will get rather bogus results for join + * RTEs, but that doesn't matter because plan trees don't contain any join + * alias Vars. + */ + set_simple_column_names(dpns); + + /* Return a one-deep namespace stack */ + return list_make1(dpns); +} + +/* + * set_deparse_context_plan - Specify Plan node containing expression + * + * When deparsing an expression in a Plan tree, we might have to resolve + * OUTER_VAR, INNER_VAR, or INDEX_VAR references. To do this, the caller must + * provide the parent Plan node. Then OUTER_VAR and INNER_VAR references + * can be resolved by drilling down into the left and right child plans. + * Similarly, INDEX_VAR references can be resolved by reference to the + * indextlist given in a parent IndexOnlyScan node, or to the scan tlist in + * ForeignScan and CustomScan nodes. (Note that we don't currently support + * deparsing of indexquals in regular IndexScan or BitmapIndexScan nodes; + * for those, we can only deparse the indexqualorig fields, which won't + * contain INDEX_VAR Vars.) + * + * The ancestors list is a list of the Plan's parent Plan and SubPlan nodes, + * the most-closely-nested first. This is needed to resolve PARAM_EXEC + * Params. Note we assume that all the Plan nodes share the same rtable. + * + * Once this function has been called, deparse_expression() can be called on + * subsidiary expression(s) of the specified Plan node. To deparse + * expressions of a different Plan node in the same Plan tree, re-call this + * function to identify the new parent Plan node. + * + * The result is the same List passed in; this is a notational convenience. + */ +List * +set_deparse_context_plan(List *dpcontext, Plan *plan, List *ancestors) +{ + deparse_namespace *dpns; + + /* Should always have one-entry namespace list for Plan deparsing */ + Assert(list_length(dpcontext) == 1); + dpns = (deparse_namespace *) linitial(dpcontext); + + /* Set our attention on the specific plan node passed in */ + dpns->ancestors = ancestors; + set_deparse_plan(dpns, plan); + + return dpcontext; +} + +/* + * select_rtable_names_for_explain - Select RTE aliases for EXPLAIN + * + * Determine the relation aliases we'll use during an EXPLAIN operation. + * This is just a frontend to set_rtable_names. We have to expose the aliases + * to EXPLAIN because EXPLAIN needs to know the right alias names to print. + */ +List * +select_rtable_names_for_explain(List *rtable, Bitmapset *rels_used) +{ + deparse_namespace dpns; + + memset(&dpns, 0, sizeof(dpns)); + dpns.rtable = rtable; + dpns.subplans = NIL; + dpns.ctes = NIL; + dpns.appendrels = NULL; + set_rtable_names(&dpns, NIL, rels_used); + /* We needn't bother computing column aliases yet */ + + return dpns.rtable_names; +} + +/* + * set_rtable_names: select RTE aliases to be used in printing a query + * + * We fill in dpns->rtable_names with a list of names that is one-for-one with + * the already-filled dpns->rtable list. Each RTE name is unique among those + * in the new namespace plus any ancestor namespaces listed in + * parent_namespaces. + * + * If rels_used isn't NULL, only RTE indexes listed in it are given aliases. + * + * Note that this function is only concerned with relation names, not column + * names. + */ +static void +set_rtable_names(deparse_namespace *dpns, List *parent_namespaces, + Bitmapset *rels_used) +{ + HASHCTL hash_ctl; + HTAB *names_hash; + NameHashEntry *hentry; + bool found; + int rtindex; + ListCell *lc; + + dpns->rtable_names = NIL; + /* nothing more to do if empty rtable */ + if (dpns->rtable == NIL) + return; + + /* + * We use a hash table to hold known names, so that this process is O(N) + * not O(N^2) for N names. + */ + hash_ctl.keysize = NAMEDATALEN; + hash_ctl.entrysize = sizeof(NameHashEntry); + hash_ctl.hcxt = CurrentMemoryContext; + names_hash = hash_create("set_rtable_names names", + list_length(dpns->rtable), + &hash_ctl, + HASH_ELEM | HASH_STRINGS | HASH_CONTEXT); + + /* Preload the hash table with names appearing in parent_namespaces */ + foreach(lc, parent_namespaces) + { + deparse_namespace *olddpns = (deparse_namespace *) lfirst(lc); + ListCell *lc2; + + foreach(lc2, olddpns->rtable_names) + { + char *oldname = (char *) lfirst(lc2); + + if (oldname == NULL) + continue; + hentry = (NameHashEntry *) hash_search(names_hash, + oldname, + HASH_ENTER, + &found); + /* we do not complain about duplicate names in parent namespaces */ + hentry->counter = 0; + } + } + + /* Now we can scan the rtable */ + rtindex = 1; + foreach(lc, dpns->rtable) + { + RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc); + char *refname; + + /* Just in case this takes an unreasonable amount of time ... */ + CHECK_FOR_INTERRUPTS(); + + if (rels_used && !bms_is_member(rtindex, rels_used)) + { + /* Ignore unreferenced RTE */ + refname = NULL; + } + else if (rte->alias) + { + /* If RTE has a user-defined alias, prefer that */ + refname = rte->alias->aliasname; + } + else if (rte->rtekind == RTE_RELATION) + { + /* Use the current actual name of the relation */ + refname = get_rel_name(rte->relid); + } + else if (rte->rtekind == RTE_JOIN) + { + /* Unnamed join has no refname */ + refname = NULL; + } + else + { + /* Otherwise use whatever the parser assigned */ + refname = rte->eref->aliasname; + } + + /* + * If the selected name isn't unique, append digits to make it so, and + * make a new hash entry for it once we've got a unique name. For a + * very long input name, we might have to truncate to stay within + * NAMEDATALEN. + */ + if (refname) + { + hentry = (NameHashEntry *) hash_search(names_hash, + refname, + HASH_ENTER, + &found); + if (found) + { + /* Name already in use, must choose a new one */ + int refnamelen = strlen(refname); + char *modname = (char *) palloc(refnamelen + 16); + NameHashEntry *hentry2; + + do + { + hentry->counter++; + for (;;) + { + memcpy(modname, refname, refnamelen); + sprintf(modname + refnamelen, "_%d", hentry->counter); + if (strlen(modname) < NAMEDATALEN) + break; + /* drop chars from refname to keep all the digits */ + refnamelen = pg_mbcliplen(refname, refnamelen, + refnamelen - 1); + } + hentry2 = (NameHashEntry *) hash_search(names_hash, + modname, + HASH_ENTER, + &found); + } while (found); + hentry2->counter = 0; /* init new hash entry */ + refname = modname; + } + else + { + /* Name not previously used, need only initialize hentry */ + hentry->counter = 0; + } + } + + dpns->rtable_names = lappend(dpns->rtable_names, refname); + rtindex++; + } + + hash_destroy(names_hash); +} + +/* + * set_deparse_for_query: set up deparse_namespace for deparsing a Query tree + * + * For convenience, this is defined to initialize the deparse_namespace struct + * from scratch. + */ +static void +set_deparse_for_query(deparse_namespace *dpns, Query *query, + List *parent_namespaces) +{ + ListCell *lc; + ListCell *lc2; + + /* Initialize *dpns and fill rtable/ctes links */ + memset(dpns, 0, sizeof(deparse_namespace)); + dpns->rtable = query->rtable; + dpns->subplans = NIL; + dpns->ctes = query->cteList; + dpns->appendrels = NULL; + + /* Assign a unique relation alias to each RTE */ + set_rtable_names(dpns, parent_namespaces, NULL); + + /* Initialize dpns->rtable_columns to contain zeroed structs */ + dpns->rtable_columns = NIL; + while (list_length(dpns->rtable_columns) < list_length(dpns->rtable)) + dpns->rtable_columns = lappend(dpns->rtable_columns, + palloc0(sizeof(deparse_columns))); + + /* If it's a utility query, it won't have a jointree */ + if (query->jointree) + { + /* Detect whether global uniqueness of USING names is needed */ + dpns->unique_using = + has_dangerous_join_using(dpns, (Node *) query->jointree); + + /* + * Select names for columns merged by USING, via a recursive pass over + * the query jointree. + */ + set_using_names(dpns, (Node *) query->jointree, NIL); + } + + /* + * Now assign remaining column aliases for each RTE. We do this in a + * linear scan of the rtable, so as to process RTEs whether or not they + * are in the jointree (we mustn't miss NEW.*, INSERT target relations, + * etc). JOIN RTEs must be processed after their children, but this is + * okay because they appear later in the rtable list than their children + * (cf Asserts in identify_join_columns()). + */ + forboth(lc, dpns->rtable, lc2, dpns->rtable_columns) + { + RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc); + deparse_columns *colinfo = (deparse_columns *) lfirst(lc2); + + if (rte->rtekind == RTE_JOIN) + set_join_column_names(dpns, rte, colinfo); + else + set_relation_column_names(dpns, rte, colinfo); + } +} + +/* + * set_simple_column_names: fill in column aliases for non-query situations + * + * This handles EXPLAIN and cases where we only have relation RTEs. Without + * a join tree, we can't do anything smart about join RTEs, but we don't + * need to (note that EXPLAIN should never see join alias Vars anyway). + * If we do hit a join RTE we'll just process it like a non-table base RTE. + */ +static void +set_simple_column_names(deparse_namespace *dpns) +{ + ListCell *lc; + ListCell *lc2; + + /* Initialize dpns->rtable_columns to contain zeroed structs */ + dpns->rtable_columns = NIL; + while (list_length(dpns->rtable_columns) < list_length(dpns->rtable)) + dpns->rtable_columns = lappend(dpns->rtable_columns, + palloc0(sizeof(deparse_columns))); + + /* Assign unique column aliases within each RTE */ + forboth(lc, dpns->rtable, lc2, dpns->rtable_columns) + { + RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc); + deparse_columns *colinfo = (deparse_columns *) lfirst(lc2); + + set_relation_column_names(dpns, rte, colinfo); + } +} + +/* + * has_dangerous_join_using: search jointree for unnamed JOIN USING + * + * Merged columns of a JOIN USING may act differently from either of the input + * columns, either because they are merged with COALESCE (in a FULL JOIN) or + * because an implicit coercion of the underlying input column is required. + * In such a case the column must be referenced as a column of the JOIN not as + * a column of either input. And this is problematic if the join is unnamed + * (alias-less): we cannot qualify the column's name with an RTE name, since + * there is none. (Forcibly assigning an alias to the join is not a solution, + * since that will prevent legal references to tables below the join.) + * To ensure that every column in the query is unambiguously referenceable, + * we must assign such merged columns names that are globally unique across + * the whole query, aliasing other columns out of the way as necessary. + * + * Because the ensuing re-aliasing is fairly damaging to the readability of + * the query, we don't do this unless we have to. So, we must pre-scan + * the join tree to see if we have to, before starting set_using_names(). + */ +static bool +has_dangerous_join_using(deparse_namespace *dpns, Node *jtnode) +{ + if (IsA(jtnode, RangeTblRef)) + { + /* nothing to do here */ + } + else if (IsA(jtnode, FromExpr)) + { + FromExpr *f = (FromExpr *) jtnode; + ListCell *lc; + + foreach(lc, f->fromlist) + { + if (has_dangerous_join_using(dpns, (Node *) lfirst(lc))) + return true; + } + } + else if (IsA(jtnode, JoinExpr)) + { + JoinExpr *j = (JoinExpr *) jtnode; + + /* Is it an unnamed JOIN with USING? */ + if (j->alias == NULL && j->usingClause) + { + /* + * Yes, so check each join alias var to see if any of them are not + * simple references to underlying columns. If so, we have a + * dangerous situation and must pick unique aliases. + */ + RangeTblEntry *jrte = rt_fetch(j->rtindex, dpns->rtable); + + /* We need only examine the merged columns */ + for (int i = 0; i < jrte->joinmergedcols; i++) + { + Node *aliasvar = list_nth(jrte->joinaliasvars, i); + + if (!IsA(aliasvar, Var)) + return true; + } + } + + /* Nope, but inspect children */ + if (has_dangerous_join_using(dpns, j->larg)) + return true; + if (has_dangerous_join_using(dpns, j->rarg)) + return true; + } + else + elog(ERROR, "unrecognized node type: %d", + (int) nodeTag(jtnode)); + return false; +} + +/* + * set_using_names: select column aliases to be used for merged USING columns + * + * We do this during a recursive descent of the query jointree. + * dpns->unique_using must already be set to determine the global strategy. + * + * Column alias info is saved in the dpns->rtable_columns list, which is + * assumed to be filled with pre-zeroed deparse_columns structs. + * + * parentUsing is a list of all USING aliases assigned in parent joins of + * the current jointree node. (The passed-in list must not be modified.) + */ +static void +set_using_names(deparse_namespace *dpns, Node *jtnode, List *parentUsing) +{ + if (IsA(jtnode, RangeTblRef)) + { + /* nothing to do now */ + } + else if (IsA(jtnode, FromExpr)) + { + FromExpr *f = (FromExpr *) jtnode; + ListCell *lc; + + foreach(lc, f->fromlist) + set_using_names(dpns, (Node *) lfirst(lc), parentUsing); + } + else if (IsA(jtnode, JoinExpr)) + { + JoinExpr *j = (JoinExpr *) jtnode; + RangeTblEntry *rte = rt_fetch(j->rtindex, dpns->rtable); + deparse_columns *colinfo = deparse_columns_fetch(j->rtindex, dpns); + int *leftattnos; + int *rightattnos; + deparse_columns *leftcolinfo; + deparse_columns *rightcolinfo; + int i; + ListCell *lc; + + /* Get info about the shape of the join */ + identify_join_columns(j, rte, colinfo); + leftattnos = colinfo->leftattnos; + rightattnos = colinfo->rightattnos; + + /* Look up the not-yet-filled-in child deparse_columns structs */ + leftcolinfo = deparse_columns_fetch(colinfo->leftrti, dpns); + rightcolinfo = deparse_columns_fetch(colinfo->rightrti, dpns); + + /* + * If this join is unnamed, then we cannot substitute new aliases at + * this level, so any name requirements pushed down to here must be + * pushed down again to the children. + */ + if (rte->alias == NULL) + { + for (i = 0; i < colinfo->num_cols; i++) + { + char *colname = colinfo->colnames[i]; + + if (colname == NULL) + continue; + + /* Push down to left column, unless it's a system column */ + if (leftattnos[i] > 0) + { + expand_colnames_array_to(leftcolinfo, leftattnos[i]); + leftcolinfo->colnames[leftattnos[i] - 1] = colname; + } + + /* Same on the righthand side */ + if (rightattnos[i] > 0) + { + expand_colnames_array_to(rightcolinfo, rightattnos[i]); + rightcolinfo->colnames[rightattnos[i] - 1] = colname; + } + } + } + + /* + * If there's a USING clause, select the USING column names and push + * those names down to the children. We have two strategies: + * + * If dpns->unique_using is true, we force all USING names to be + * unique across the whole query level. In principle we'd only need + * the names of dangerous USING columns to be globally unique, but to + * safely assign all USING names in a single pass, we have to enforce + * the same uniqueness rule for all of them. However, if a USING + * column's name has been pushed down from the parent, we should use + * it as-is rather than making a uniqueness adjustment. This is + * necessary when we're at an unnamed join, and it creates no risk of + * ambiguity. Also, if there's a user-written output alias for a + * merged column, we prefer to use that rather than the input name; + * this simplifies the logic and seems likely to lead to less aliasing + * overall. + * + * If dpns->unique_using is false, we only need USING names to be + * unique within their own join RTE. We still need to honor + * pushed-down names, though. + * + * Though significantly different in results, these two strategies are + * implemented by the same code, with only the difference of whether + * to put assigned names into dpns->using_names. + */ + if (j->usingClause) + { + /* Copy the input parentUsing list so we don't modify it */ + parentUsing = list_copy(parentUsing); + + /* USING names must correspond to the first join output columns */ + expand_colnames_array_to(colinfo, list_length(j->usingClause)); + i = 0; + foreach(lc, j->usingClause) + { + char *colname = strVal(lfirst(lc)); + + /* Assert it's a merged column */ + Assert(leftattnos[i] != 0 && rightattnos[i] != 0); + + /* Adopt passed-down name if any, else select unique name */ + if (colinfo->colnames[i] != NULL) + colname = colinfo->colnames[i]; + else + { + /* Prefer user-written output alias if any */ + if (rte->alias && i < list_length(rte->alias->colnames)) + colname = strVal(list_nth(rte->alias->colnames, i)); + /* Make it appropriately unique */ + colname = make_colname_unique(colname, dpns, colinfo); + if (dpns->unique_using) + dpns->using_names = lappend(dpns->using_names, + colname); + /* Save it as output column name, too */ + colinfo->colnames[i] = colname; + } + + /* Remember selected names for use later */ + colinfo->usingNames = lappend(colinfo->usingNames, colname); + parentUsing = lappend(parentUsing, colname); + + /* Push down to left column, unless it's a system column */ + if (leftattnos[i] > 0) + { + expand_colnames_array_to(leftcolinfo, leftattnos[i]); + leftcolinfo->colnames[leftattnos[i] - 1] = colname; + } + + /* Same on the righthand side */ + if (rightattnos[i] > 0) + { + expand_colnames_array_to(rightcolinfo, rightattnos[i]); + rightcolinfo->colnames[rightattnos[i] - 1] = colname; + } + + i++; + } + } + + /* Mark child deparse_columns structs with correct parentUsing info */ + leftcolinfo->parentUsing = parentUsing; + rightcolinfo->parentUsing = parentUsing; + + /* Now recursively assign USING column names in children */ + set_using_names(dpns, j->larg, parentUsing); + set_using_names(dpns, j->rarg, parentUsing); + } + else + elog(ERROR, "unrecognized node type: %d", + (int) nodeTag(jtnode)); +} + +/* + * set_relation_column_names: select column aliases for a non-join RTE + * + * Column alias info is saved in *colinfo, which is assumed to be pre-zeroed. + * If any colnames entries are already filled in, those override local + * choices. + */ +static void +set_relation_column_names(deparse_namespace *dpns, RangeTblEntry *rte, + deparse_columns *colinfo) +{ + int ncolumns; + char **real_colnames; + bool changed_any; + int noldcolumns; + int i; + int j; + + /* + * Construct an array of the current "real" column names of the RTE. + * real_colnames[] will be indexed by physical column number, with NULL + * entries for dropped columns. + */ + if (rte->rtekind == RTE_RELATION) + { + /* Relation --- look to the system catalogs for up-to-date info */ + Relation rel; + TupleDesc tupdesc; + + rel = relation_open(rte->relid, AccessShareLock); + tupdesc = RelationGetDescr(rel); + + ncolumns = tupdesc->natts; + real_colnames = (char **) palloc(ncolumns * sizeof(char *)); + + for (i = 0; i < ncolumns; i++) + { + Form_pg_attribute attr = TupleDescAttr(tupdesc, i); + + if (attr->attisdropped) + real_colnames[i] = NULL; + else + real_colnames[i] = pstrdup(NameStr(attr->attname)); + } + relation_close(rel, AccessShareLock); + } + else + { + /* Otherwise get the column names from eref or expandRTE() */ + List *colnames; + ListCell *lc; + + /* + * Functions returning composites have the annoying property that some + * of the composite type's columns might have been dropped since the + * query was parsed. If possible, use expandRTE() to handle that + * case, since it has the tedious logic needed to find out about + * dropped columns. However, if we're explaining a plan, then we + * don't have rte->functions because the planner thinks that won't be + * needed later, and that breaks expandRTE(). So in that case we have + * to rely on rte->eref, which may lead us to report a dropped + * column's old name; that seems close enough for EXPLAIN's purposes. + * + * For non-RELATION, non-FUNCTION RTEs, we can just look at rte->eref, + * which should be sufficiently up-to-date: no other RTE types can + * have columns get dropped from under them after parsing. + */ + if (rte->rtekind == RTE_FUNCTION && rte->functions != NIL) + { + /* Since we're not creating Vars, rtindex etc. don't matter */ + expandRTE(rte, 1, 0, -1, true /* include dropped */ , + &colnames, NULL); + } + else + colnames = rte->eref->colnames; + + ncolumns = list_length(colnames); + real_colnames = (char **) palloc(ncolumns * sizeof(char *)); + + i = 0; + foreach(lc, colnames) + { + /* + * If the column name we find here is an empty string, then it's a + * dropped column, so change to NULL. + */ + char *cname = strVal(lfirst(lc)); + + if (cname[0] == '\0') + cname = NULL; + real_colnames[i] = cname; + i++; + } + } + + /* + * Ensure colinfo->colnames has a slot for each column. (It could be long + * enough already, if we pushed down a name for the last column.) Note: + * it's possible that there are now more columns than there were when the + * query was parsed, ie colnames could be longer than rte->eref->colnames. + * We must assign unique aliases to the new columns too, else there could + * be unresolved conflicts when the view/rule is reloaded. + */ + expand_colnames_array_to(colinfo, ncolumns); + Assert(colinfo->num_cols == ncolumns); + + /* + * Make sufficiently large new_colnames and is_new_col arrays, too. + * + * Note: because we leave colinfo->num_new_cols zero until after the loop, + * colname_is_unique will not consult that array, which is fine because it + * would only be duplicate effort. + */ + colinfo->new_colnames = (char **) palloc(ncolumns * sizeof(char *)); + colinfo->is_new_col = (bool *) palloc(ncolumns * sizeof(bool)); + + /* + * Scan the columns, select a unique alias for each one, and store it in + * colinfo->colnames and colinfo->new_colnames. The former array has NULL + * entries for dropped columns, the latter omits them. Also mark + * new_colnames entries as to whether they are new since parse time; this + * is the case for entries beyond the length of rte->eref->colnames. + */ + noldcolumns = list_length(rte->eref->colnames); + changed_any = false; + j = 0; + for (i = 0; i < ncolumns; i++) + { + char *real_colname = real_colnames[i]; + char *colname = colinfo->colnames[i]; + + /* Skip dropped columns */ + if (real_colname == NULL) + { + Assert(colname == NULL); /* colnames[i] is already NULL */ + continue; + } + + /* If alias already assigned, that's what to use */ + if (colname == NULL) + { + /* If user wrote an alias, prefer that over real column name */ + if (rte->alias && i < list_length(rte->alias->colnames)) + colname = strVal(list_nth(rte->alias->colnames, i)); + else + colname = real_colname; + + /* Unique-ify and insert into colinfo */ + colname = make_colname_unique(colname, dpns, colinfo); + + colinfo->colnames[i] = colname; + } + + /* Put names of non-dropped columns in new_colnames[] too */ + colinfo->new_colnames[j] = colname; + /* And mark them as new or not */ + colinfo->is_new_col[j] = (i >= noldcolumns); + j++; + + /* Remember if any assigned aliases differ from "real" name */ + if (!changed_any && strcmp(colname, real_colname) != 0) + changed_any = true; + } + + /* + * Set correct length for new_colnames[] array. (Note: if columns have + * been added, colinfo->num_cols includes them, which is not really quite + * right but is harmless, since any new columns must be at the end where + * they won't affect varattnos of pre-existing columns.) + */ + colinfo->num_new_cols = j; + + /* + * For a relation RTE, we need only print the alias column names if any + * are different from the underlying "real" names. For a function RTE, + * always emit a complete column alias list; this is to protect against + * possible instability of the default column names (eg, from altering + * parameter names). For tablefunc RTEs, we never print aliases, because + * the column names are part of the clause itself. For other RTE types, + * print if we changed anything OR if there were user-written column + * aliases (since the latter would be part of the underlying "reality"). + */ + if (rte->rtekind == RTE_RELATION) + colinfo->printaliases = changed_any; + else if (rte->rtekind == RTE_FUNCTION) + colinfo->printaliases = true; + else if (rte->rtekind == RTE_TABLEFUNC) + colinfo->printaliases = false; + else if (rte->alias && rte->alias->colnames != NIL) + colinfo->printaliases = true; + else + colinfo->printaliases = changed_any; +} + +/* + * set_join_column_names: select column aliases for a join RTE + * + * Column alias info is saved in *colinfo, which is assumed to be pre-zeroed. + * If any colnames entries are already filled in, those override local + * choices. Also, names for USING columns were already chosen by + * set_using_names(). We further expect that column alias selection has been + * completed for both input RTEs. + */ +static void +set_join_column_names(deparse_namespace *dpns, RangeTblEntry *rte, + deparse_columns *colinfo) +{ + deparse_columns *leftcolinfo; + deparse_columns *rightcolinfo; + bool changed_any; + int noldcolumns; + int nnewcolumns; + Bitmapset *leftmerged = NULL; + Bitmapset *rightmerged = NULL; + int i; + int j; + int ic; + int jc; + + /* Look up the previously-filled-in child deparse_columns structs */ + leftcolinfo = deparse_columns_fetch(colinfo->leftrti, dpns); + rightcolinfo = deparse_columns_fetch(colinfo->rightrti, dpns); + + /* + * Ensure colinfo->colnames has a slot for each column. (It could be long + * enough already, if we pushed down a name for the last column.) Note: + * it's possible that one or both inputs now have more columns than there + * were when the query was parsed, but we'll deal with that below. We + * only need entries in colnames for pre-existing columns. + */ + noldcolumns = list_length(rte->eref->colnames); + expand_colnames_array_to(colinfo, noldcolumns); + Assert(colinfo->num_cols == noldcolumns); + + /* + * Scan the join output columns, select an alias for each one, and store + * it in colinfo->colnames. If there are USING columns, set_using_names() + * already selected their names, so we can start the loop at the first + * non-merged column. + */ + changed_any = false; + for (i = list_length(colinfo->usingNames); i < noldcolumns; i++) + { + char *colname = colinfo->colnames[i]; + char *real_colname; + + /* Join column must refer to at least one input column */ + Assert(colinfo->leftattnos[i] != 0 || colinfo->rightattnos[i] != 0); + + /* Get the child column name */ + if (colinfo->leftattnos[i] > 0) + real_colname = leftcolinfo->colnames[colinfo->leftattnos[i] - 1]; + else if (colinfo->rightattnos[i] > 0) + real_colname = rightcolinfo->colnames[colinfo->rightattnos[i] - 1]; + else + { + /* We're joining system columns --- use eref name */ + real_colname = strVal(list_nth(rte->eref->colnames, i)); + } + + /* If child col has been dropped, no need to assign a join colname */ + if (real_colname == NULL) + { + colinfo->colnames[i] = NULL; + continue; + } + + /* In an unnamed join, just report child column names as-is */ + if (rte->alias == NULL) + { + colinfo->colnames[i] = real_colname; + continue; + } + + /* If alias already assigned, that's what to use */ + if (colname == NULL) + { + /* If user wrote an alias, prefer that over real column name */ + if (rte->alias && i < list_length(rte->alias->colnames)) + colname = strVal(list_nth(rte->alias->colnames, i)); + else + colname = real_colname; + + /* Unique-ify and insert into colinfo */ + colname = make_colname_unique(colname, dpns, colinfo); + + colinfo->colnames[i] = colname; + } + + /* Remember if any assigned aliases differ from "real" name */ + if (!changed_any && strcmp(colname, real_colname) != 0) + changed_any = true; + } + + /* + * Calculate number of columns the join would have if it were re-parsed + * now, and create storage for the new_colnames and is_new_col arrays. + * + * Note: colname_is_unique will be consulting new_colnames[] during the + * loops below, so its not-yet-filled entries must be zeroes. + */ + nnewcolumns = leftcolinfo->num_new_cols + rightcolinfo->num_new_cols - + list_length(colinfo->usingNames); + colinfo->num_new_cols = nnewcolumns; + colinfo->new_colnames = (char **) palloc0(nnewcolumns * sizeof(char *)); + colinfo->is_new_col = (bool *) palloc0(nnewcolumns * sizeof(bool)); + + /* + * Generating the new_colnames array is a bit tricky since any new columns + * added since parse time must be inserted in the right places. This code + * must match the parser, which will order a join's columns as merged + * columns first (in USING-clause order), then non-merged columns from the + * left input (in attnum order), then non-merged columns from the right + * input (ditto). If one of the inputs is itself a join, its columns will + * be ordered according to the same rule, which means newly-added columns + * might not be at the end. We can figure out what's what by consulting + * the leftattnos and rightattnos arrays plus the input is_new_col arrays. + * + * In these loops, i indexes leftattnos/rightattnos (so it's join varattno + * less one), j indexes new_colnames/is_new_col, and ic/jc have similar + * meanings for the current child RTE. + */ + + /* Handle merged columns; they are first and can't be new */ + i = j = 0; + while (i < noldcolumns && + colinfo->leftattnos[i] != 0 && + colinfo->rightattnos[i] != 0) + { + /* column name is already determined and known unique */ + colinfo->new_colnames[j] = colinfo->colnames[i]; + colinfo->is_new_col[j] = false; + + /* build bitmapsets of child attnums of merged columns */ + if (colinfo->leftattnos[i] > 0) + leftmerged = bms_add_member(leftmerged, colinfo->leftattnos[i]); + if (colinfo->rightattnos[i] > 0) + rightmerged = bms_add_member(rightmerged, colinfo->rightattnos[i]); + + i++, j++; + } + + /* Handle non-merged left-child columns */ + ic = 0; + for (jc = 0; jc < leftcolinfo->num_new_cols; jc++) + { + char *child_colname = leftcolinfo->new_colnames[jc]; + + if (!leftcolinfo->is_new_col[jc]) + { + /* Advance ic to next non-dropped old column of left child */ + while (ic < leftcolinfo->num_cols && + leftcolinfo->colnames[ic] == NULL) + ic++; + Assert(ic < leftcolinfo->num_cols); + ic++; + /* If it is a merged column, we already processed it */ + if (bms_is_member(ic, leftmerged)) + continue; + /* Else, advance i to the corresponding existing join column */ + while (i < colinfo->num_cols && + colinfo->colnames[i] == NULL) + i++; + Assert(i < colinfo->num_cols); + Assert(ic == colinfo->leftattnos[i]); + /* Use the already-assigned name of this column */ + colinfo->new_colnames[j] = colinfo->colnames[i]; + i++; + } + else + { + /* + * Unique-ify the new child column name and assign, unless we're + * in an unnamed join, in which case just copy + */ + if (rte->alias != NULL) + { + colinfo->new_colnames[j] = + make_colname_unique(child_colname, dpns, colinfo); + if (!changed_any && + strcmp(colinfo->new_colnames[j], child_colname) != 0) + changed_any = true; + } + else + colinfo->new_colnames[j] = child_colname; + } + + colinfo->is_new_col[j] = leftcolinfo->is_new_col[jc]; + j++; + } + + /* Handle non-merged right-child columns in exactly the same way */ + ic = 0; + for (jc = 0; jc < rightcolinfo->num_new_cols; jc++) + { + char *child_colname = rightcolinfo->new_colnames[jc]; + + if (!rightcolinfo->is_new_col[jc]) + { + /* Advance ic to next non-dropped old column of right child */ + while (ic < rightcolinfo->num_cols && + rightcolinfo->colnames[ic] == NULL) + ic++; + Assert(ic < rightcolinfo->num_cols); + ic++; + /* If it is a merged column, we already processed it */ + if (bms_is_member(ic, rightmerged)) + continue; + /* Else, advance i to the corresponding existing join column */ + while (i < colinfo->num_cols && + colinfo->colnames[i] == NULL) + i++; + Assert(i < colinfo->num_cols); + Assert(ic == colinfo->rightattnos[i]); + /* Use the already-assigned name of this column */ + colinfo->new_colnames[j] = colinfo->colnames[i]; + i++; + } + else + { + /* + * Unique-ify the new child column name and assign, unless we're + * in an unnamed join, in which case just copy + */ + if (rte->alias != NULL) + { + colinfo->new_colnames[j] = + make_colname_unique(child_colname, dpns, colinfo); + if (!changed_any && + strcmp(colinfo->new_colnames[j], child_colname) != 0) + changed_any = true; + } + else + colinfo->new_colnames[j] = child_colname; + } + + colinfo->is_new_col[j] = rightcolinfo->is_new_col[jc]; + j++; + } + + /* Assert we processed the right number of columns */ +#ifdef USE_ASSERT_CHECKING + while (i < colinfo->num_cols && colinfo->colnames[i] == NULL) + i++; + Assert(i == colinfo->num_cols); + Assert(j == nnewcolumns); +#endif + + /* + * For a named join, print column aliases if we changed any from the child + * names. Unnamed joins cannot print aliases. + */ + if (rte->alias != NULL) + colinfo->printaliases = changed_any; + else + colinfo->printaliases = false; +} + +/* + * colname_is_unique: is colname distinct from already-chosen column names? + * + * dpns is query-wide info, colinfo is for the column's RTE + */ +static bool +colname_is_unique(const char *colname, deparse_namespace *dpns, + deparse_columns *colinfo) +{ + int i; + ListCell *lc; + + /* Check against already-assigned column aliases within RTE */ + for (i = 0; i < colinfo->num_cols; i++) + { + char *oldname = colinfo->colnames[i]; + + if (oldname && strcmp(oldname, colname) == 0) + return false; + } + + /* + * If we're building a new_colnames array, check that too (this will be + * partially but not completely redundant with the previous checks) + */ + for (i = 0; i < colinfo->num_new_cols; i++) + { + char *oldname = colinfo->new_colnames[i]; + + if (oldname && strcmp(oldname, colname) == 0) + return false; + } + + /* Also check against USING-column names that must be globally unique */ + foreach(lc, dpns->using_names) + { + char *oldname = (char *) lfirst(lc); + + if (strcmp(oldname, colname) == 0) + return false; + } + + /* Also check against names already assigned for parent-join USING cols */ + foreach(lc, colinfo->parentUsing) + { + char *oldname = (char *) lfirst(lc); + + if (strcmp(oldname, colname) == 0) + return false; + } + + return true; +} + +/* + * make_colname_unique: modify colname if necessary to make it unique + * + * dpns is query-wide info, colinfo is for the column's RTE + */ +static char * +make_colname_unique(char *colname, deparse_namespace *dpns, + deparse_columns *colinfo) +{ + /* + * If the selected name isn't unique, append digits to make it so. For a + * very long input name, we might have to truncate to stay within + * NAMEDATALEN. + */ + if (!colname_is_unique(colname, dpns, colinfo)) + { + int colnamelen = strlen(colname); + char *modname = (char *) palloc(colnamelen + 16); + int i = 0; + + do + { + i++; + for (;;) + { + memcpy(modname, colname, colnamelen); + sprintf(modname + colnamelen, "_%d", i); + if (strlen(modname) < NAMEDATALEN) + break; + /* drop chars from colname to keep all the digits */ + colnamelen = pg_mbcliplen(colname, colnamelen, + colnamelen - 1); + } + } while (!colname_is_unique(modname, dpns, colinfo)); + colname = modname; + } + return colname; +} + +/* + * expand_colnames_array_to: make colinfo->colnames at least n items long + * + * Any added array entries are initialized to zero. + */ +static void +expand_colnames_array_to(deparse_columns *colinfo, int n) +{ + if (n > colinfo->num_cols) + { + if (colinfo->colnames == NULL) + colinfo->colnames = palloc0_array(char *, n); + else + colinfo->colnames = repalloc0_array(colinfo->colnames, char *, colinfo->num_cols, n); + colinfo->num_cols = n; + } +} + +/* + * identify_join_columns: figure out where columns of a join come from + * + * Fills the join-specific fields of the colinfo struct, except for + * usingNames which is filled later. + */ +static void +identify_join_columns(JoinExpr *j, RangeTblEntry *jrte, + deparse_columns *colinfo) +{ + int numjoincols; + int jcolno; + int rcolno; + ListCell *lc; + + /* Extract left/right child RT indexes */ + if (IsA(j->larg, RangeTblRef)) + colinfo->leftrti = ((RangeTblRef *) j->larg)->rtindex; + else if (IsA(j->larg, JoinExpr)) + colinfo->leftrti = ((JoinExpr *) j->larg)->rtindex; + else + elog(ERROR, "unrecognized node type in jointree: %d", + (int) nodeTag(j->larg)); + if (IsA(j->rarg, RangeTblRef)) + colinfo->rightrti = ((RangeTblRef *) j->rarg)->rtindex; + else if (IsA(j->rarg, JoinExpr)) + colinfo->rightrti = ((JoinExpr *) j->rarg)->rtindex; + else + elog(ERROR, "unrecognized node type in jointree: %d", + (int) nodeTag(j->rarg)); + + /* Assert children will be processed earlier than join in second pass */ + Assert(colinfo->leftrti < j->rtindex); + Assert(colinfo->rightrti < j->rtindex); + + /* Initialize result arrays with zeroes */ + numjoincols = list_length(jrte->joinaliasvars); + Assert(numjoincols == list_length(jrte->eref->colnames)); + colinfo->leftattnos = (int *) palloc0(numjoincols * sizeof(int)); + colinfo->rightattnos = (int *) palloc0(numjoincols * sizeof(int)); + + /* + * Deconstruct RTE's joinleftcols/joinrightcols into desired format. + * Recall that the column(s) merged due to USING are the first column(s) + * of the join output. We need not do anything special while scanning + * joinleftcols, but while scanning joinrightcols we must distinguish + * merged from unmerged columns. + */ + jcolno = 0; + foreach(lc, jrte->joinleftcols) + { + int leftattno = lfirst_int(lc); + + colinfo->leftattnos[jcolno++] = leftattno; + } + rcolno = 0; + foreach(lc, jrte->joinrightcols) + { + int rightattno = lfirst_int(lc); + + if (rcolno < jrte->joinmergedcols) /* merged column? */ + colinfo->rightattnos[rcolno] = rightattno; + else + colinfo->rightattnos[jcolno++] = rightattno; + rcolno++; + } + Assert(jcolno == numjoincols); +} + +/* + * get_rtable_name: convenience function to get a previously assigned RTE alias + * + * The RTE must belong to the topmost namespace level in "context". + */ +static char * +get_rtable_name(int rtindex, deparse_context *context) +{ + deparse_namespace *dpns = (deparse_namespace *) linitial(context->namespaces); + + Assert(rtindex > 0 && rtindex <= list_length(dpns->rtable_names)); + return (char *) list_nth(dpns->rtable_names, rtindex - 1); +} + +/* + * set_deparse_plan: set up deparse_namespace to parse subexpressions + * of a given Plan node + * + * This sets the plan, outer_plan, inner_plan, outer_tlist, inner_tlist, + * and index_tlist fields. Caller must already have adjusted the ancestors + * list if necessary. Note that the rtable, subplans, and ctes fields do + * not need to change when shifting attention to different plan nodes in a + * single plan tree. + */ +static void +set_deparse_plan(deparse_namespace *dpns, Plan *plan) +{ + dpns->plan = plan; + + /* + * We special-case Append and MergeAppend to pretend that the first child + * plan is the OUTER referent; we have to interpret OUTER Vars in their + * tlists according to one of the children, and the first one is the most + * natural choice. + */ + if (IsA(plan, Append)) + dpns->outer_plan = linitial(((Append *) plan)->appendplans); + else if (IsA(plan, MergeAppend)) + dpns->outer_plan = linitial(((MergeAppend *) plan)->mergeplans); + else + dpns->outer_plan = outerPlan(plan); + + if (dpns->outer_plan) + dpns->outer_tlist = dpns->outer_plan->targetlist; + else + dpns->outer_tlist = NIL; + + /* + * For a SubqueryScan, pretend the subplan is INNER referent. (We don't + * use OUTER because that could someday conflict with the normal meaning.) + * Likewise, for a CteScan, pretend the subquery's plan is INNER referent. + * For a WorkTableScan, locate the parent RecursiveUnion plan node and use + * that as INNER referent. + * + * For MERGE, pretend the ModifyTable's source plan (its outer plan) is + * INNER referent. This is the join from the target relation to the data + * source, and all INNER_VAR Vars in other parts of the query refer to its + * targetlist. + * + * For ON CONFLICT .. UPDATE we just need the inner tlist to point to the + * excluded expression's tlist. (Similar to the SubqueryScan we don't want + * to reuse OUTER, it's used for RETURNING in some modify table cases, + * although not INSERT .. CONFLICT). + */ + if (IsA(plan, SubqueryScan)) + dpns->inner_plan = ((SubqueryScan *) plan)->subplan; + else if (IsA(plan, CteScan)) + dpns->inner_plan = list_nth(dpns->subplans, + ((CteScan *) plan)->ctePlanId - 1); + else if (IsA(plan, WorkTableScan)) + dpns->inner_plan = find_recursive_union(dpns, + (WorkTableScan *) plan); + else if (IsA(plan, ModifyTable)) + { + if (((ModifyTable *) plan)->operation == CMD_MERGE) + dpns->inner_plan = outerPlan(plan); + else + dpns->inner_plan = plan; + } + else + dpns->inner_plan = innerPlan(plan); + + if (IsA(plan, ModifyTable) && ((ModifyTable *) plan)->operation == CMD_INSERT) + dpns->inner_tlist = ((ModifyTable *) plan)->exclRelTlist; + else if (dpns->inner_plan) + dpns->inner_tlist = dpns->inner_plan->targetlist; + else + dpns->inner_tlist = NIL; + + /* Set up referent for INDEX_VAR Vars, if needed */ + if (IsA(plan, IndexOnlyScan)) + dpns->index_tlist = ((IndexOnlyScan *) plan)->indextlist; + else if (IsA(plan, ForeignScan)) + dpns->index_tlist = ((ForeignScan *) plan)->fdw_scan_tlist; + else if (IsA(plan, CustomScan)) + dpns->index_tlist = ((CustomScan *) plan)->custom_scan_tlist; + else + dpns->index_tlist = NIL; +} + +/* + * Locate the ancestor plan node that is the RecursiveUnion generating + * the WorkTableScan's work table. We can match on wtParam, since that + * should be unique within the plan tree. + */ +static Plan * +find_recursive_union(deparse_namespace *dpns, WorkTableScan *wtscan) +{ + ListCell *lc; + + foreach(lc, dpns->ancestors) + { + Plan *ancestor = (Plan *) lfirst(lc); + + if (IsA(ancestor, RecursiveUnion) && + ((RecursiveUnion *) ancestor)->wtParam == wtscan->wtParam) + return ancestor; + } + elog(ERROR, "could not find RecursiveUnion for WorkTableScan with wtParam %d", + wtscan->wtParam); + return NULL; +} + +/* + * push_child_plan: temporarily transfer deparsing attention to a child plan + * + * When expanding an OUTER_VAR or INNER_VAR reference, we must adjust the + * deparse context in case the referenced expression itself uses + * OUTER_VAR/INNER_VAR. We modify the top stack entry in-place to avoid + * affecting levelsup issues (although in a Plan tree there really shouldn't + * be any). + * + * Caller must provide a local deparse_namespace variable to save the + * previous state for pop_child_plan. + */ +static void +push_child_plan(deparse_namespace *dpns, Plan *plan, + deparse_namespace *save_dpns) +{ + /* Save state for restoration later */ + *save_dpns = *dpns; + + /* Link current plan node into ancestors list */ + dpns->ancestors = lcons(dpns->plan, dpns->ancestors); + + /* Set attention on selected child */ + set_deparse_plan(dpns, plan); +} + +/* + * pop_child_plan: undo the effects of push_child_plan + */ +static void +pop_child_plan(deparse_namespace *dpns, deparse_namespace *save_dpns) +{ + List *ancestors; + + /* Get rid of ancestors list cell added by push_child_plan */ + ancestors = list_delete_first(dpns->ancestors); + + /* Restore fields changed by push_child_plan */ + *dpns = *save_dpns; + + /* Make sure dpns->ancestors is right (may be unnecessary) */ + dpns->ancestors = ancestors; +} + +/* + * push_ancestor_plan: temporarily transfer deparsing attention to an + * ancestor plan + * + * When expanding a Param reference, we must adjust the deparse context + * to match the plan node that contains the expression being printed; + * otherwise we'd fail if that expression itself contains a Param or + * OUTER_VAR/INNER_VAR/INDEX_VAR variable. + * + * The target ancestor is conveniently identified by the ListCell holding it + * in dpns->ancestors. + * + * Caller must provide a local deparse_namespace variable to save the + * previous state for pop_ancestor_plan. + */ +static void +push_ancestor_plan(deparse_namespace *dpns, ListCell *ancestor_cell, + deparse_namespace *save_dpns) +{ + Plan *plan = (Plan *) lfirst(ancestor_cell); + + /* Save state for restoration later */ + *save_dpns = *dpns; + + /* Build a new ancestor list with just this node's ancestors */ + dpns->ancestors = + list_copy_tail(dpns->ancestors, + list_cell_number(dpns->ancestors, ancestor_cell) + 1); + + /* Set attention on selected ancestor */ + set_deparse_plan(dpns, plan); +} + +/* + * pop_ancestor_plan: undo the effects of push_ancestor_plan + */ +static void +pop_ancestor_plan(deparse_namespace *dpns, deparse_namespace *save_dpns) +{ + /* Free the ancestor list made in push_ancestor_plan */ + list_free(dpns->ancestors); + + /* Restore fields changed by push_ancestor_plan */ + *dpns = *save_dpns; +} + + +/* ---------- + * make_ruledef - reconstruct the CREATE RULE command + * for a given pg_rewrite tuple + * ---------- + */ +static void +make_ruledef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc, + int prettyFlags) +{ + char *rulename; + char ev_type; + Oid ev_class; + bool is_instead; + char *ev_qual; + char *ev_action; + List *actions; + Relation ev_relation; + TupleDesc viewResultDesc = NULL; + int fno; + Datum dat; + bool isnull; + + /* + * Get the attribute values from the rules tuple + */ + fno = SPI_fnumber(rulettc, "rulename"); + dat = SPI_getbinval(ruletup, rulettc, fno, &isnull); + Assert(!isnull); + rulename = NameStr(*(DatumGetName(dat))); + + fno = SPI_fnumber(rulettc, "ev_type"); + dat = SPI_getbinval(ruletup, rulettc, fno, &isnull); + Assert(!isnull); + ev_type = DatumGetChar(dat); + + fno = SPI_fnumber(rulettc, "ev_class"); + dat = SPI_getbinval(ruletup, rulettc, fno, &isnull); + Assert(!isnull); + ev_class = DatumGetObjectId(dat); + + fno = SPI_fnumber(rulettc, "is_instead"); + dat = SPI_getbinval(ruletup, rulettc, fno, &isnull); + Assert(!isnull); + is_instead = DatumGetBool(dat); + + fno = SPI_fnumber(rulettc, "ev_qual"); + ev_qual = SPI_getvalue(ruletup, rulettc, fno); + Assert(ev_qual != NULL); + + fno = SPI_fnumber(rulettc, "ev_action"); + ev_action = SPI_getvalue(ruletup, rulettc, fno); + Assert(ev_action != NULL); + actions = (List *) stringToNode(ev_action); + if (actions == NIL) + elog(ERROR, "invalid empty ev_action list"); + + ev_relation = table_open(ev_class, AccessShareLock); + + /* + * Build the rules definition text + */ + appendStringInfo(buf, "CREATE RULE %s AS", + quote_identifier(rulename)); + + if (prettyFlags & PRETTYFLAG_INDENT) + appendStringInfoString(buf, "\n ON "); + else + appendStringInfoString(buf, " ON "); + + /* The event the rule is fired for */ + switch (ev_type) + { + case '1': + appendStringInfoString(buf, "SELECT"); + viewResultDesc = RelationGetDescr(ev_relation); + break; + + case '2': + appendStringInfoString(buf, "UPDATE"); + break; + + case '3': + appendStringInfoString(buf, "INSERT"); + break; + + case '4': + appendStringInfoString(buf, "DELETE"); + break; + + default: + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("rule \"%s\" has unsupported event type %d", + rulename, ev_type))); + break; + } + + /* The relation the rule is fired on */ + appendStringInfo(buf, " TO %s", + (prettyFlags & PRETTYFLAG_SCHEMA) ? + generate_relation_name(ev_class, NIL) : + generate_qualified_relation_name(ev_class)); + + /* If the rule has an event qualification, add it */ + if (strcmp(ev_qual, "<>") != 0) + { + Node *qual; + Query *query; + deparse_context context; + deparse_namespace dpns; + + if (prettyFlags & PRETTYFLAG_INDENT) + appendStringInfoString(buf, "\n "); + appendStringInfoString(buf, " WHERE "); + + qual = stringToNode(ev_qual); + + /* + * We need to make a context for recognizing any Vars in the qual + * (which can only be references to OLD and NEW). Use the rtable of + * the first query in the action list for this purpose. + */ + query = (Query *) linitial(actions); + + /* + * If the action is INSERT...SELECT, OLD/NEW have been pushed down + * into the SELECT, and that's what we need to look at. (Ugly kluge + * ... try to fix this when we redesign querytrees.) + */ + query = getInsertSelectQuery(query, NULL); + + /* Must acquire locks right away; see notes in get_query_def() */ + AcquireRewriteLocks(query, false, false); + + context.buf = buf; + context.namespaces = list_make1(&dpns); + context.windowClause = NIL; + context.windowTList = NIL; + context.varprefix = (list_length(query->rtable) != 1); + context.prettyFlags = prettyFlags; + context.wrapColumn = WRAP_COLUMN_DEFAULT; + context.indentLevel = PRETTYINDENT_STD; + context.special_exprkind = EXPR_KIND_NONE; + context.appendparents = NULL; + + set_deparse_for_query(&dpns, query, NIL); + + get_rule_expr(qual, &context, false); + } + + appendStringInfoString(buf, " DO "); + + /* The INSTEAD keyword (if so) */ + if (is_instead) + appendStringInfoString(buf, "INSTEAD "); + + /* Finally the rules actions */ + if (list_length(actions) > 1) + { + ListCell *action; + Query *query; + + appendStringInfoChar(buf, '('); + foreach(action, actions) + { + query = (Query *) lfirst(action); + get_query_def(query, buf, NIL, viewResultDesc, true, + prettyFlags, WRAP_COLUMN_DEFAULT, 0); + if (prettyFlags) + appendStringInfoString(buf, ";\n"); + else + appendStringInfoString(buf, "; "); + } + appendStringInfoString(buf, ");"); + } + else + { + Query *query; + + query = (Query *) linitial(actions); + get_query_def(query, buf, NIL, viewResultDesc, true, + prettyFlags, WRAP_COLUMN_DEFAULT, 0); + appendStringInfoChar(buf, ';'); + } + + table_close(ev_relation, AccessShareLock); +} + + +/* ---------- + * make_viewdef - reconstruct the SELECT part of a + * view rewrite rule + * ---------- + */ +static void +make_viewdef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc, + int prettyFlags, int wrapColumn) +{ + Query *query; + char ev_type; + Oid ev_class; + bool is_instead; + char *ev_qual; + char *ev_action; + List *actions; + Relation ev_relation; + int fno; + Datum dat; + bool isnull; + + /* + * Get the attribute values from the rules tuple + */ + fno = SPI_fnumber(rulettc, "ev_type"); + dat = SPI_getbinval(ruletup, rulettc, fno, &isnull); + Assert(!isnull); + ev_type = DatumGetChar(dat); + + fno = SPI_fnumber(rulettc, "ev_class"); + dat = SPI_getbinval(ruletup, rulettc, fno, &isnull); + Assert(!isnull); + ev_class = DatumGetObjectId(dat); + + fno = SPI_fnumber(rulettc, "is_instead"); + dat = SPI_getbinval(ruletup, rulettc, fno, &isnull); + Assert(!isnull); + is_instead = DatumGetBool(dat); + + fno = SPI_fnumber(rulettc, "ev_qual"); + ev_qual = SPI_getvalue(ruletup, rulettc, fno); + Assert(ev_qual != NULL); + + fno = SPI_fnumber(rulettc, "ev_action"); + ev_action = SPI_getvalue(ruletup, rulettc, fno); + Assert(ev_action != NULL); + actions = (List *) stringToNode(ev_action); + + if (list_length(actions) != 1) + { + /* keep output buffer empty and leave */ + return; + } + + query = (Query *) linitial(actions); + + if (ev_type != '1' || !is_instead || + strcmp(ev_qual, "<>") != 0 || query->commandType != CMD_SELECT) + { + /* keep output buffer empty and leave */ + return; + } + + ev_relation = table_open(ev_class, AccessShareLock); + + get_query_def(query, buf, NIL, RelationGetDescr(ev_relation), true, + prettyFlags, wrapColumn, 0); + appendStringInfoChar(buf, ';'); + + table_close(ev_relation, AccessShareLock); +} + + +/* ---------- + * get_query_def - Parse back one query parsetree + * + * query: parsetree to be displayed + * buf: output text is appended to buf + * parentnamespace: list (initially empty) of outer-level deparse_namespace's + * resultDesc: if not NULL, the output tuple descriptor for the view + * represented by a SELECT query. We use the column names from it + * to label SELECT output columns, in preference to names in the query + * colNamesVisible: true if the surrounding context cares about the output + * column names at all (as, for example, an EXISTS() context does not); + * when false, we can suppress dummy column labels such as "?column?" + * prettyFlags: bitmask of PRETTYFLAG_XXX options + * wrapColumn: maximum line length, or -1 to disable wrapping + * startIndent: initial indentation amount + * ---------- + */ +static void +get_query_def(Query *query, StringInfo buf, List *parentnamespace, + TupleDesc resultDesc, bool colNamesVisible, + int prettyFlags, int wrapColumn, int startIndent) +{ + deparse_context context; + deparse_namespace dpns; + + /* Guard against excessively long or deeply-nested queries */ + CHECK_FOR_INTERRUPTS(); + check_stack_depth(); + + /* + * Before we begin to examine the query, acquire locks on referenced + * relations, and fix up deleted columns in JOIN RTEs. This ensures + * consistent results. Note we assume it's OK to scribble on the passed + * querytree! + * + * We are only deparsing the query (we are not about to execute it), so we + * only need AccessShareLock on the relations it mentions. + */ + AcquireRewriteLocks(query, false, false); + + context.buf = buf; + context.namespaces = lcons(&dpns, list_copy(parentnamespace)); + context.windowClause = NIL; + context.windowTList = NIL; + context.varprefix = (parentnamespace != NIL || + list_length(query->rtable) != 1); + context.prettyFlags = prettyFlags; + context.wrapColumn = wrapColumn; + context.indentLevel = startIndent; + context.special_exprkind = EXPR_KIND_NONE; + context.appendparents = NULL; + + set_deparse_for_query(&dpns, query, parentnamespace); + + switch (query->commandType) + { + case CMD_SELECT: + get_select_query_def(query, &context, resultDesc, colNamesVisible); + break; + + case CMD_UPDATE: + get_update_query_def(query, &context, colNamesVisible); + break; + + case CMD_INSERT: + get_insert_query_def(query, &context, colNamesVisible); + break; + + case CMD_DELETE: + get_delete_query_def(query, &context, colNamesVisible); + break; + + case CMD_MERGE: + get_merge_query_def(query, &context, colNamesVisible); + break; + + case CMD_NOTHING: + appendStringInfoString(buf, "NOTHING"); + break; + + case CMD_UTILITY: + get_utility_query_def(query, &context); + break; + + default: + elog(ERROR, "unrecognized query command type: %d", + query->commandType); + break; + } +} + +/* ---------- + * get_values_def - Parse back a VALUES list + * ---------- + */ +static void +get_values_def(List *values_lists, deparse_context *context) +{ + StringInfo buf = context->buf; + bool first_list = true; + ListCell *vtl; + + appendStringInfoString(buf, "VALUES "); + + foreach(vtl, values_lists) + { + List *sublist = (List *) lfirst(vtl); + bool first_col = true; + ListCell *lc; + + if (first_list) + first_list = false; + else + appendStringInfoString(buf, ", "); + + appendStringInfoChar(buf, '('); + foreach(lc, sublist) + { + Node *col = (Node *) lfirst(lc); + + if (first_col) + first_col = false; + else + appendStringInfoChar(buf, ','); + + /* + * Print the value. Whole-row Vars need special treatment. + */ + get_rule_expr_toplevel(col, context, false); + } + appendStringInfoChar(buf, ')'); + } +} + +/* ---------- + * get_with_clause - Parse back a WITH clause + * ---------- + */ +static void +get_with_clause(Query *query, deparse_context *context) +{ + StringInfo buf = context->buf; + const char *sep; + ListCell *l; + + if (query->cteList == NIL) + return; + + if (PRETTY_INDENT(context)) + { + context->indentLevel += PRETTYINDENT_STD; + appendStringInfoChar(buf, ' '); + } + + if (query->hasRecursive) + sep = "WITH RECURSIVE "; + else + sep = "WITH "; + foreach(l, query->cteList) + { + CommonTableExpr *cte = (CommonTableExpr *) lfirst(l); + + appendStringInfoString(buf, sep); + appendStringInfoString(buf, quote_identifier(cte->ctename)); + if (cte->aliascolnames) + { + bool first = true; + ListCell *col; + + appendStringInfoChar(buf, '('); + foreach(col, cte->aliascolnames) + { + if (first) + first = false; + else + appendStringInfoString(buf, ", "); + appendStringInfoString(buf, + quote_identifier(strVal(lfirst(col)))); + } + appendStringInfoChar(buf, ')'); + } + appendStringInfoString(buf, " AS "); + switch (cte->ctematerialized) + { + case CTEMaterializeDefault: + break; + case CTEMaterializeAlways: + appendStringInfoString(buf, "MATERIALIZED "); + break; + case CTEMaterializeNever: + appendStringInfoString(buf, "NOT MATERIALIZED "); + break; + } + appendStringInfoChar(buf, '('); + if (PRETTY_INDENT(context)) + appendContextKeyword(context, "", 0, 0, 0); + get_query_def((Query *) cte->ctequery, buf, context->namespaces, NULL, + true, + context->prettyFlags, context->wrapColumn, + context->indentLevel); + if (PRETTY_INDENT(context)) + appendContextKeyword(context, "", 0, 0, 0); + appendStringInfoChar(buf, ')'); + + if (cte->search_clause) + { + bool first = true; + ListCell *lc; + + appendStringInfo(buf, " SEARCH %s FIRST BY ", + cte->search_clause->search_breadth_first ? "BREADTH" : "DEPTH"); + + foreach(lc, cte->search_clause->search_col_list) + { + if (first) + first = false; + else + appendStringInfoString(buf, ", "); + appendStringInfoString(buf, + quote_identifier(strVal(lfirst(lc)))); + } + + appendStringInfo(buf, " SET %s", quote_identifier(cte->search_clause->search_seq_column)); + } + + if (cte->cycle_clause) + { + bool first = true; + ListCell *lc; + + appendStringInfoString(buf, " CYCLE "); + + foreach(lc, cte->cycle_clause->cycle_col_list) + { + if (first) + first = false; + else + appendStringInfoString(buf, ", "); + appendStringInfoString(buf, + quote_identifier(strVal(lfirst(lc)))); + } + + appendStringInfo(buf, " SET %s", quote_identifier(cte->cycle_clause->cycle_mark_column)); + + { + Const *cmv = castNode(Const, cte->cycle_clause->cycle_mark_value); + Const *cmd = castNode(Const, cte->cycle_clause->cycle_mark_default); + + if (!(cmv->consttype == BOOLOID && !cmv->constisnull && DatumGetBool(cmv->constvalue) == true && + cmd->consttype == BOOLOID && !cmd->constisnull && DatumGetBool(cmd->constvalue) == false)) + { + appendStringInfoString(buf, " TO "); + get_rule_expr(cte->cycle_clause->cycle_mark_value, context, false); + appendStringInfoString(buf, " DEFAULT "); + get_rule_expr(cte->cycle_clause->cycle_mark_default, context, false); + } + } + + appendStringInfo(buf, " USING %s", quote_identifier(cte->cycle_clause->cycle_path_column)); + } + + sep = ", "; + } + + if (PRETTY_INDENT(context)) + { + context->indentLevel -= PRETTYINDENT_STD; + appendContextKeyword(context, "", 0, 0, 0); + } + else + appendStringInfoChar(buf, ' '); +} + +/* ---------- + * get_select_query_def - Parse back a SELECT parsetree + * ---------- + */ +static void +get_select_query_def(Query *query, deparse_context *context, + TupleDesc resultDesc, bool colNamesVisible) +{ + StringInfo buf = context->buf; + List *save_windowclause; + List *save_windowtlist; + bool force_colno; + ListCell *l; + + /* Insert the WITH clause if given */ + get_with_clause(query, context); + + /* Set up context for possible window functions */ + save_windowclause = context->windowClause; + context->windowClause = query->windowClause; + save_windowtlist = context->windowTList; + context->windowTList = query->targetList; + + /* + * If the Query node has a setOperations tree, then it's the top level of + * a UNION/INTERSECT/EXCEPT query; only the WITH, ORDER BY and LIMIT + * fields are interesting in the top query itself. + */ + if (query->setOperations) + { + get_setop_query(query->setOperations, query, context, resultDesc, + colNamesVisible); + /* ORDER BY clauses must be simple in this case */ + force_colno = true; + } + else + { + get_basic_select_query(query, context, resultDesc, colNamesVisible); + force_colno = false; + } + + /* Add the ORDER BY clause if given */ + if (query->sortClause != NIL) + { + appendContextKeyword(context, " ORDER BY ", + -PRETTYINDENT_STD, PRETTYINDENT_STD, 1); + get_rule_orderby(query->sortClause, query->targetList, + force_colno, context); + } + + /* + * Add the LIMIT/OFFSET clauses if given. If non-default options, use the + * standard spelling of LIMIT. + */ + if (query->limitOffset != NULL) + { + appendContextKeyword(context, " OFFSET ", + -PRETTYINDENT_STD, PRETTYINDENT_STD, 0); + get_rule_expr(query->limitOffset, context, false); + } + if (query->limitCount != NULL) + { + if (query->limitOption == LIMIT_OPTION_WITH_TIES) + { + appendContextKeyword(context, " FETCH FIRST ", + -PRETTYINDENT_STD, PRETTYINDENT_STD, 0); + get_rule_expr(query->limitCount, context, false); + appendStringInfoString(buf, " ROWS WITH TIES"); + } + else + { + appendContextKeyword(context, " LIMIT ", + -PRETTYINDENT_STD, PRETTYINDENT_STD, 0); + if (IsA(query->limitCount, Const) && + ((Const *) query->limitCount)->constisnull) + appendStringInfoString(buf, "ALL"); + else + get_rule_expr(query->limitCount, context, false); + } + } + + /* Add FOR [KEY] UPDATE/SHARE clauses if present */ + if (query->hasForUpdate) + { + foreach(l, query->rowMarks) + { + RowMarkClause *rc = (RowMarkClause *) lfirst(l); + + /* don't print implicit clauses */ + if (rc->pushedDown) + continue; + + switch (rc->strength) + { + case LCS_NONE: + /* we intentionally throw an error for LCS_NONE */ + elog(ERROR, "unrecognized LockClauseStrength %d", + (int) rc->strength); + break; + case LCS_FORKEYSHARE: + appendContextKeyword(context, " FOR KEY SHARE", + -PRETTYINDENT_STD, PRETTYINDENT_STD, 0); + break; + case LCS_FORSHARE: + appendContextKeyword(context, " FOR SHARE", + -PRETTYINDENT_STD, PRETTYINDENT_STD, 0); + break; + case LCS_FORNOKEYUPDATE: + appendContextKeyword(context, " FOR NO KEY UPDATE", + -PRETTYINDENT_STD, PRETTYINDENT_STD, 0); + break; + case LCS_FORUPDATE: + appendContextKeyword(context, " FOR UPDATE", + -PRETTYINDENT_STD, PRETTYINDENT_STD, 0); + break; + } + + appendStringInfo(buf, " OF %s", + quote_identifier(get_rtable_name(rc->rti, + context))); + if (rc->waitPolicy == LockWaitError) + appendStringInfoString(buf, " NOWAIT"); + else if (rc->waitPolicy == LockWaitSkip) + appendStringInfoString(buf, " SKIP LOCKED"); + } + } + + context->windowClause = save_windowclause; + context->windowTList = save_windowtlist; +} + +/* + * Detect whether query looks like SELECT ... FROM VALUES(), + * with no need to rename the output columns of the VALUES RTE. + * If so, return the VALUES RTE. Otherwise return NULL. + */ +static RangeTblEntry * +get_simple_values_rte(Query *query, TupleDesc resultDesc) +{ + RangeTblEntry *result = NULL; + ListCell *lc; + + /* + * We want to detect a match even if the Query also contains OLD or NEW + * rule RTEs. So the idea is to scan the rtable and see if there is only + * one inFromCl RTE that is a VALUES RTE. + */ + foreach(lc, query->rtable) + { + RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc); + + if (rte->rtekind == RTE_VALUES && rte->inFromCl) + { + if (result) + return NULL; /* multiple VALUES (probably not possible) */ + result = rte; + } + else if (rte->rtekind == RTE_RELATION && !rte->inFromCl) + continue; /* ignore rule entries */ + else + return NULL; /* something else -> not simple VALUES */ + } + + /* + * We don't need to check the targetlist in any great detail, because + * parser/analyze.c will never generate a "bare" VALUES RTE --- they only + * appear inside auto-generated sub-queries with very restricted + * structure. However, DefineView might have modified the tlist by + * injecting new column aliases, or we might have some other column + * aliases forced by a resultDesc. We can only simplify if the RTE's + * column names match the names that get_target_list() would select. + */ + if (result) + { + ListCell *lcn; + int colno; + + if (list_length(query->targetList) != list_length(result->eref->colnames)) + return NULL; /* this probably cannot happen */ + colno = 0; + forboth(lc, query->targetList, lcn, result->eref->colnames) + { + TargetEntry *tle = (TargetEntry *) lfirst(lc); + char *cname = strVal(lfirst(lcn)); + char *colname; + + if (tle->resjunk) + return NULL; /* this probably cannot happen */ + + /* compute name that get_target_list would use for column */ + colno++; + if (resultDesc && colno <= resultDesc->natts) + colname = NameStr(TupleDescAttr(resultDesc, colno - 1)->attname); + else + colname = tle->resname; + + /* does it match the VALUES RTE? */ + if (colname == NULL || strcmp(colname, cname) != 0) + return NULL; /* column name has been changed */ + } + } + + return result; +} + +static void +get_basic_select_query(Query *query, deparse_context *context, + TupleDesc resultDesc, bool colNamesVisible) +{ + StringInfo buf = context->buf; + RangeTblEntry *values_rte; + char *sep; + ListCell *l; + + if (PRETTY_INDENT(context)) + { + context->indentLevel += PRETTYINDENT_STD; + appendStringInfoChar(buf, ' '); + } + + /* + * If the query looks like SELECT * FROM (VALUES ...), then print just the + * VALUES part. This reverses what transformValuesClause() did at parse + * time. + */ + values_rte = get_simple_values_rte(query, resultDesc); + if (values_rte) + { + get_values_def(values_rte->values_lists, context); + return; + } + + /* + * Build up the query string - first we say SELECT + */ + if (query->isReturn) + appendStringInfoString(buf, "RETURN"); + else + appendStringInfoString(buf, "SELECT"); + + /* Add the DISTINCT clause if given */ + if (query->distinctClause != NIL) + { + if (query->hasDistinctOn) + { + appendStringInfoString(buf, " DISTINCT ON ("); + sep = ""; + foreach(l, query->distinctClause) + { + SortGroupClause *srt = (SortGroupClause *) lfirst(l); + + appendStringInfoString(buf, sep); + get_rule_sortgroupclause(srt->tleSortGroupRef, query->targetList, + false, context); + sep = ", "; + } + appendStringInfoChar(buf, ')'); + } + else + appendStringInfoString(buf, " DISTINCT"); + } + + /* Then we tell what to select (the targetlist) */ + get_target_list(query->targetList, context, resultDesc, colNamesVisible); + + /* Add the FROM clause if needed */ + get_from_clause(query, " FROM ", context); + + /* Add the WHERE clause if given */ + if (query->jointree->quals != NULL) + { + appendContextKeyword(context, " WHERE ", + -PRETTYINDENT_STD, PRETTYINDENT_STD, 1); + get_rule_expr(query->jointree->quals, context, false); + } + + /* Add the GROUP BY clause if given */ + if (query->groupClause != NULL || query->groupingSets != NULL) + { + ParseExprKind save_exprkind; + + appendContextKeyword(context, " GROUP BY ", + -PRETTYINDENT_STD, PRETTYINDENT_STD, 1); + if (query->groupDistinct) + appendStringInfoString(buf, "DISTINCT "); + + save_exprkind = context->special_exprkind; + context->special_exprkind = EXPR_KIND_GROUP_BY; + + if (query->groupingSets == NIL) + { + sep = ""; + foreach(l, query->groupClause) + { + SortGroupClause *grp = (SortGroupClause *) lfirst(l); + + appendStringInfoString(buf, sep); + get_rule_sortgroupclause(grp->tleSortGroupRef, query->targetList, + false, context); + sep = ", "; + } + } + else + { + sep = ""; + foreach(l, query->groupingSets) + { + GroupingSet *grp = lfirst(l); + + appendStringInfoString(buf, sep); + get_rule_groupingset(grp, query->targetList, true, context); + sep = ", "; + } + } + + context->special_exprkind = save_exprkind; + } + + /* Add the HAVING clause if given */ + if (query->havingQual != NULL) + { + appendContextKeyword(context, " HAVING ", + -PRETTYINDENT_STD, PRETTYINDENT_STD, 0); + get_rule_expr(query->havingQual, context, false); + } + + /* Add the WINDOW clause if needed */ + if (query->windowClause != NIL) + get_rule_windowclause(query, context); +} + +/* ---------- + * get_target_list - Parse back a SELECT target list + * + * This is also used for RETURNING lists in INSERT/UPDATE/DELETE. + * + * resultDesc and colNamesVisible are as for get_query_def() + * ---------- + */ +static void +get_target_list(List *targetList, deparse_context *context, + TupleDesc resultDesc, bool colNamesVisible) +{ + StringInfo buf = context->buf; + StringInfoData targetbuf; + bool last_was_multiline = false; + char *sep; + int colno; + ListCell *l; + + /* we use targetbuf to hold each TLE's text temporarily */ + initStringInfo(&targetbuf); + + sep = " "; + colno = 0; + foreach(l, targetList) + { + TargetEntry *tle = (TargetEntry *) lfirst(l); + char *colname; + char *attname; + + if (tle->resjunk) + continue; /* ignore junk entries */ + + appendStringInfoString(buf, sep); + sep = ", "; + colno++; + + /* + * Put the new field text into targetbuf so we can decide after we've + * got it whether or not it needs to go on a new line. + */ + resetStringInfo(&targetbuf); + context->buf = &targetbuf; + + /* + * We special-case Var nodes rather than using get_rule_expr. This is + * needed because get_rule_expr will display a whole-row Var as + * "foo.*", which is the preferred notation in most contexts, but at + * the top level of a SELECT list it's not right (the parser will + * expand that notation into multiple columns, yielding behavior + * different from a whole-row Var). We need to call get_variable + * directly so that we can tell it to do the right thing, and so that + * we can get the attribute name which is the default AS label. + */ + if (tle->expr && (IsA(tle->expr, Var))) + { + attname = get_variable((Var *) tle->expr, 0, true, context); + } + else + { + get_rule_expr((Node *) tle->expr, context, true); + + /* + * When colNamesVisible is true, we should always show the + * assigned column name explicitly. Otherwise, show it only if + * it's not FigureColname's fallback. + */ + attname = colNamesVisible ? NULL : "?column?"; + } + + /* + * Figure out what the result column should be called. In the context + * of a view, use the view's tuple descriptor (so as to pick up the + * effects of any column RENAME that's been done on the view). + * Otherwise, just use what we can find in the TLE. + */ + if (resultDesc && colno <= resultDesc->natts) + colname = NameStr(TupleDescAttr(resultDesc, colno - 1)->attname); + else + colname = tle->resname; + + /* Show AS unless the column's name is correct as-is */ + if (colname) /* resname could be NULL */ + { + if (attname == NULL || strcmp(attname, colname) != 0) + appendStringInfo(&targetbuf, " AS %s", quote_identifier(colname)); + } + + /* Restore context's output buffer */ + context->buf = buf; + + /* Consider line-wrapping if enabled */ + if (PRETTY_INDENT(context) && context->wrapColumn >= 0) + { + int leading_nl_pos; + + /* Does the new field start with a new line? */ + if (targetbuf.len > 0 && targetbuf.data[0] == '\n') + leading_nl_pos = 0; + else + leading_nl_pos = -1; + + /* If so, we shouldn't add anything */ + if (leading_nl_pos >= 0) + { + /* instead, remove any trailing spaces currently in buf */ + removeStringInfoSpaces(buf); + } + else + { + char *trailing_nl; + + /* Locate the start of the current line in the output buffer */ + trailing_nl = strrchr(buf->data, '\n'); + if (trailing_nl == NULL) + trailing_nl = buf->data; + else + trailing_nl++; + + /* + * Add a newline, plus some indentation, if the new field is + * not the first and either the new field would cause an + * overflow or the last field used more than one line. + */ + if (colno > 1 && + ((strlen(trailing_nl) + targetbuf.len > context->wrapColumn) || + last_was_multiline)) + appendContextKeyword(context, "", -PRETTYINDENT_STD, + PRETTYINDENT_STD, PRETTYINDENT_VAR); + } + + /* Remember this field's multiline status for next iteration */ + last_was_multiline = + (strchr(targetbuf.data + leading_nl_pos + 1, '\n') != NULL); + } + + /* Add the new field */ + appendBinaryStringInfo(buf, targetbuf.data, targetbuf.len); + } + + /* clean up */ + pfree(targetbuf.data); +} + +static void +get_setop_query(Node *setOp, Query *query, deparse_context *context, + TupleDesc resultDesc, bool colNamesVisible) +{ + StringInfo buf = context->buf; + bool need_paren; + + /* Guard against excessively long or deeply-nested queries */ + CHECK_FOR_INTERRUPTS(); + check_stack_depth(); + + if (IsA(setOp, RangeTblRef)) + { + RangeTblRef *rtr = (RangeTblRef *) setOp; + RangeTblEntry *rte = rt_fetch(rtr->rtindex, query->rtable); + Query *subquery = rte->subquery; + + Assert(subquery != NULL); + Assert(subquery->setOperations == NULL); + /* Need parens if WITH, ORDER BY, FOR UPDATE, or LIMIT; see gram.y */ + need_paren = (subquery->cteList || + subquery->sortClause || + subquery->rowMarks || + subquery->limitOffset || + subquery->limitCount); + if (need_paren) + appendStringInfoChar(buf, '('); + get_query_def(subquery, buf, context->namespaces, resultDesc, + colNamesVisible, + context->prettyFlags, context->wrapColumn, + context->indentLevel); + if (need_paren) + appendStringInfoChar(buf, ')'); + } + else if (IsA(setOp, SetOperationStmt)) + { + SetOperationStmt *op = (SetOperationStmt *) setOp; + int subindent; + + /* + * We force parens when nesting two SetOperationStmts, except when the + * lefthand input is another setop of the same kind. Syntactically, + * we could omit parens in rather more cases, but it seems best to use + * parens to flag cases where the setop operator changes. If we use + * parens, we also increase the indentation level for the child query. + * + * There are some cases in which parens are needed around a leaf query + * too, but those are more easily handled at the next level down (see + * code above). + */ + if (IsA(op->larg, SetOperationStmt)) + { + SetOperationStmt *lop = (SetOperationStmt *) op->larg; + + if (op->op == lop->op && op->all == lop->all) + need_paren = false; + else + need_paren = true; + } + else + need_paren = false; + + if (need_paren) + { + appendStringInfoChar(buf, '('); + subindent = PRETTYINDENT_STD; + appendContextKeyword(context, "", subindent, 0, 0); + } + else + subindent = 0; + + get_setop_query(op->larg, query, context, resultDesc, colNamesVisible); + + if (need_paren) + appendContextKeyword(context, ") ", -subindent, 0, 0); + else if (PRETTY_INDENT(context)) + appendContextKeyword(context, "", -subindent, 0, 0); + else + appendStringInfoChar(buf, ' '); + + switch (op->op) + { + case SETOP_UNION: + appendStringInfoString(buf, "UNION "); + break; + case SETOP_INTERSECT: + appendStringInfoString(buf, "INTERSECT "); + break; + case SETOP_EXCEPT: + appendStringInfoString(buf, "EXCEPT "); + break; + default: + elog(ERROR, "unrecognized set op: %d", + (int) op->op); + } + if (op->all) + appendStringInfoString(buf, "ALL "); + + /* Always parenthesize if RHS is another setop */ + need_paren = IsA(op->rarg, SetOperationStmt); + + /* + * The indentation code here is deliberately a bit different from that + * for the lefthand input, because we want the line breaks in + * different places. + */ + if (need_paren) + { + appendStringInfoChar(buf, '('); + subindent = PRETTYINDENT_STD; + } + else + subindent = 0; + appendContextKeyword(context, "", subindent, 0, 0); + + get_setop_query(op->rarg, query, context, resultDesc, false); + + if (PRETTY_INDENT(context)) + context->indentLevel -= subindent; + if (need_paren) + appendContextKeyword(context, ")", 0, 0, 0); + } + else + { + elog(ERROR, "unrecognized node type: %d", + (int) nodeTag(setOp)); + } +} + +/* + * Display a sort/group clause. + * + * Also returns the expression tree, so caller need not find it again. + */ +static Node * +get_rule_sortgroupclause(Index ref, List *tlist, bool force_colno, + deparse_context *context) +{ + StringInfo buf = context->buf; + TargetEntry *tle; + Node *expr; + + tle = get_sortgroupref_tle(ref, tlist); + expr = (Node *) tle->expr; + + /* + * Use column-number form if requested by caller. Otherwise, if + * expression is a constant, force it to be dumped with an explicit cast + * as decoration --- this is because a simple integer constant is + * ambiguous (and will be misinterpreted by findTargetlistEntry()) if we + * dump it without any decoration. If it's anything more complex than a + * simple Var, then force extra parens around it, to ensure it can't be + * misinterpreted as a cube() or rollup() construct. + */ + if (force_colno) + { + Assert(!tle->resjunk); + appendStringInfo(buf, "%d", tle->resno); + } + else if (expr && IsA(expr, Const)) + get_const_expr((Const *) expr, context, 1); + else if (!expr || IsA(expr, Var)) + get_rule_expr(expr, context, true); + else + { + /* + * We must force parens for function-like expressions even if + * PRETTY_PAREN is off, since those are the ones in danger of + * misparsing. For other expressions we need to force them only if + * PRETTY_PAREN is on, since otherwise the expression will output them + * itself. (We can't skip the parens.) + */ + bool need_paren = (PRETTY_PAREN(context) + || IsA(expr, FuncExpr) + || IsA(expr, Aggref) + || IsA(expr, WindowFunc) + || IsA(expr, JsonConstructorExpr)); + + if (need_paren) + appendStringInfoChar(context->buf, '('); + get_rule_expr(expr, context, true); + if (need_paren) + appendStringInfoChar(context->buf, ')'); + } + + return expr; +} + +/* + * Display a GroupingSet + */ +static void +get_rule_groupingset(GroupingSet *gset, List *targetlist, + bool omit_parens, deparse_context *context) +{ + ListCell *l; + StringInfo buf = context->buf; + bool omit_child_parens = true; + char *sep = ""; + + switch (gset->kind) + { + case GROUPING_SET_EMPTY: + appendStringInfoString(buf, "()"); + return; + + case GROUPING_SET_SIMPLE: + { + if (!omit_parens || list_length(gset->content) != 1) + appendStringInfoChar(buf, '('); + + foreach(l, gset->content) + { + Index ref = lfirst_int(l); + + appendStringInfoString(buf, sep); + get_rule_sortgroupclause(ref, targetlist, + false, context); + sep = ", "; + } + + if (!omit_parens || list_length(gset->content) != 1) + appendStringInfoChar(buf, ')'); + } + return; + + case GROUPING_SET_ROLLUP: + appendStringInfoString(buf, "ROLLUP("); + break; + case GROUPING_SET_CUBE: + appendStringInfoString(buf, "CUBE("); + break; + case GROUPING_SET_SETS: + appendStringInfoString(buf, "GROUPING SETS ("); + omit_child_parens = false; + break; + } + + foreach(l, gset->content) + { + appendStringInfoString(buf, sep); + get_rule_groupingset(lfirst(l), targetlist, omit_child_parens, context); + sep = ", "; + } + + appendStringInfoChar(buf, ')'); +} + +/* + * Display an ORDER BY list. + */ +static void +get_rule_orderby(List *orderList, List *targetList, + bool force_colno, deparse_context *context) +{ + StringInfo buf = context->buf; + const char *sep; + ListCell *l; + + sep = ""; + foreach(l, orderList) + { + SortGroupClause *srt = (SortGroupClause *) lfirst(l); + Node *sortexpr; + Oid sortcoltype; + TypeCacheEntry *typentry; + + appendStringInfoString(buf, sep); + sortexpr = get_rule_sortgroupclause(srt->tleSortGroupRef, targetList, + force_colno, context); + sortcoltype = exprType(sortexpr); + /* See whether operator is default < or > for datatype */ + typentry = lookup_type_cache(sortcoltype, + TYPECACHE_LT_OPR | TYPECACHE_GT_OPR); + if (srt->sortop == typentry->lt_opr) + { + /* ASC is default, so emit nothing for it */ + if (srt->nulls_first) + appendStringInfoString(buf, " NULLS FIRST"); + } + else if (srt->sortop == typentry->gt_opr) + { + appendStringInfoString(buf, " DESC"); + /* DESC defaults to NULLS FIRST */ + if (!srt->nulls_first) + appendStringInfoString(buf, " NULLS LAST"); + } + else + { + appendStringInfo(buf, " USING %s", + generate_operator_name(srt->sortop, + sortcoltype, + sortcoltype)); + /* be specific to eliminate ambiguity */ + if (srt->nulls_first) + appendStringInfoString(buf, " NULLS FIRST"); + else + appendStringInfoString(buf, " NULLS LAST"); + } + sep = ", "; + } +} + +/* + * Display a WINDOW clause. + * + * Note that the windowClause list might contain only anonymous window + * specifications, in which case we should print nothing here. + */ +static void +get_rule_windowclause(Query *query, deparse_context *context) +{ + StringInfo buf = context->buf; + const char *sep; + ListCell *l; + + sep = NULL; + foreach(l, query->windowClause) + { + WindowClause *wc = (WindowClause *) lfirst(l); + + if (wc->name == NULL) + continue; /* ignore anonymous windows */ + + if (sep == NULL) + appendContextKeyword(context, " WINDOW ", + -PRETTYINDENT_STD, PRETTYINDENT_STD, 1); + else + appendStringInfoString(buf, sep); + + appendStringInfo(buf, "%s AS ", quote_identifier(wc->name)); + + get_rule_windowspec(wc, query->targetList, context); + + sep = ", "; + } +} + +/* + * Display a window definition + */ +static void +get_rule_windowspec(WindowClause *wc, List *targetList, + deparse_context *context) +{ + StringInfo buf = context->buf; + bool needspace = false; + const char *sep; + ListCell *l; + + appendStringInfoChar(buf, '('); + if (wc->refname) + { + appendStringInfoString(buf, quote_identifier(wc->refname)); + needspace = true; + } + /* partition clauses are always inherited, so only print if no refname */ + if (wc->partitionClause && !wc->refname) + { + if (needspace) + appendStringInfoChar(buf, ' '); + appendStringInfoString(buf, "PARTITION BY "); + sep = ""; + foreach(l, wc->partitionClause) + { + SortGroupClause *grp = (SortGroupClause *) lfirst(l); + + appendStringInfoString(buf, sep); + get_rule_sortgroupclause(grp->tleSortGroupRef, targetList, + false, context); + sep = ", "; + } + needspace = true; + } + /* print ordering clause only if not inherited */ + if (wc->orderClause && !wc->copiedOrder) + { + if (needspace) + appendStringInfoChar(buf, ' '); + appendStringInfoString(buf, "ORDER BY "); + get_rule_orderby(wc->orderClause, targetList, false, context); + needspace = true; + } + /* framing clause is never inherited, so print unless it's default */ + if (wc->frameOptions & FRAMEOPTION_NONDEFAULT) + { + if (needspace) + appendStringInfoChar(buf, ' '); + if (wc->frameOptions & FRAMEOPTION_RANGE) + appendStringInfoString(buf, "RANGE "); + else if (wc->frameOptions & FRAMEOPTION_ROWS) + appendStringInfoString(buf, "ROWS "); + else if (wc->frameOptions & FRAMEOPTION_GROUPS) + appendStringInfoString(buf, "GROUPS "); + else + Assert(false); + if (wc->frameOptions & FRAMEOPTION_BETWEEN) + appendStringInfoString(buf, "BETWEEN "); + if (wc->frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING) + appendStringInfoString(buf, "UNBOUNDED PRECEDING "); + else if (wc->frameOptions & FRAMEOPTION_START_CURRENT_ROW) + appendStringInfoString(buf, "CURRENT ROW "); + else if (wc->frameOptions & FRAMEOPTION_START_OFFSET) + { + get_rule_expr(wc->startOffset, context, false); + if (wc->frameOptions & FRAMEOPTION_START_OFFSET_PRECEDING) + appendStringInfoString(buf, " PRECEDING "); + else if (wc->frameOptions & FRAMEOPTION_START_OFFSET_FOLLOWING) + appendStringInfoString(buf, " FOLLOWING "); + else + Assert(false); + } + else + Assert(false); + if (wc->frameOptions & FRAMEOPTION_BETWEEN) + { + appendStringInfoString(buf, "AND "); + if (wc->frameOptions & FRAMEOPTION_END_UNBOUNDED_FOLLOWING) + appendStringInfoString(buf, "UNBOUNDED FOLLOWING "); + else if (wc->frameOptions & FRAMEOPTION_END_CURRENT_ROW) + appendStringInfoString(buf, "CURRENT ROW "); + else if (wc->frameOptions & FRAMEOPTION_END_OFFSET) + { + get_rule_expr(wc->endOffset, context, false); + if (wc->frameOptions & FRAMEOPTION_END_OFFSET_PRECEDING) + appendStringInfoString(buf, " PRECEDING "); + else if (wc->frameOptions & FRAMEOPTION_END_OFFSET_FOLLOWING) + appendStringInfoString(buf, " FOLLOWING "); + else + Assert(false); + } + else + Assert(false); + } + if (wc->frameOptions & FRAMEOPTION_EXCLUDE_CURRENT_ROW) + appendStringInfoString(buf, "EXCLUDE CURRENT ROW "); + else if (wc->frameOptions & FRAMEOPTION_EXCLUDE_GROUP) + appendStringInfoString(buf, "EXCLUDE GROUP "); + else if (wc->frameOptions & FRAMEOPTION_EXCLUDE_TIES) + appendStringInfoString(buf, "EXCLUDE TIES "); + /* we will now have a trailing space; remove it */ + buf->len--; + } + appendStringInfoChar(buf, ')'); +} + +/* ---------- + * get_insert_query_def - Parse back an INSERT parsetree + * ---------- + */ +static void +get_insert_query_def(Query *query, deparse_context *context, + bool colNamesVisible) +{ + StringInfo buf = context->buf; + RangeTblEntry *select_rte = NULL; + RangeTblEntry *values_rte = NULL; + RangeTblEntry *rte; + char *sep; + ListCell *l; + List *strippedexprs; + + /* Insert the WITH clause if given */ + get_with_clause(query, context); + + /* + * If it's an INSERT ... SELECT or multi-row VALUES, there will be a + * single RTE for the SELECT or VALUES. Plain VALUES has neither. + */ + foreach(l, query->rtable) + { + rte = (RangeTblEntry *) lfirst(l); + + if (rte->rtekind == RTE_SUBQUERY) + { + if (select_rte) + elog(ERROR, "too many subquery RTEs in INSERT"); + select_rte = rte; + } + + if (rte->rtekind == RTE_VALUES) + { + if (values_rte) + elog(ERROR, "too many values RTEs in INSERT"); + values_rte = rte; + } + } + if (select_rte && values_rte) + elog(ERROR, "both subquery and values RTEs in INSERT"); + + /* + * Start the query with INSERT INTO relname + */ + rte = rt_fetch(query->resultRelation, query->rtable); + Assert(rte->rtekind == RTE_RELATION); + + if (PRETTY_INDENT(context)) + { + context->indentLevel += PRETTYINDENT_STD; + appendStringInfoChar(buf, ' '); + } + appendStringInfo(buf, "INSERT INTO %s", + generate_relation_name(rte->relid, NIL)); + + /* Print the relation alias, if needed; INSERT requires explicit AS */ + get_rte_alias(rte, query->resultRelation, true, context); + + /* always want a space here */ + appendStringInfoChar(buf, ' '); + + /* + * Add the insert-column-names list. Any indirection decoration needed on + * the column names can be inferred from the top targetlist. + */ + strippedexprs = NIL; + sep = ""; + if (query->targetList) + appendStringInfoChar(buf, '('); + foreach(l, query->targetList) + { + TargetEntry *tle = (TargetEntry *) lfirst(l); + + if (tle->resjunk) + continue; /* ignore junk entries */ + + appendStringInfoString(buf, sep); + sep = ", "; + + /* + * Put out name of target column; look in the catalogs, not at + * tle->resname, since resname will fail to track RENAME. + */ + appendStringInfoString(buf, + quote_identifier(get_attname(rte->relid, + tle->resno, + false))); + + /* + * Print any indirection needed (subfields or subscripts), and strip + * off the top-level nodes representing the indirection assignments. + * Add the stripped expressions to strippedexprs. (If it's a + * single-VALUES statement, the stripped expressions are the VALUES to + * print below. Otherwise they're just Vars and not really + * interesting.) + */ + strippedexprs = lappend(strippedexprs, + processIndirection((Node *) tle->expr, + context)); + } + if (query->targetList) + appendStringInfoString(buf, ") "); + + if (query->override) + { + if (query->override == OVERRIDING_SYSTEM_VALUE) + appendStringInfoString(buf, "OVERRIDING SYSTEM VALUE "); + else if (query->override == OVERRIDING_USER_VALUE) + appendStringInfoString(buf, "OVERRIDING USER VALUE "); + } + + if (select_rte) + { + /* Add the SELECT */ + get_query_def(select_rte->subquery, buf, context->namespaces, NULL, + false, + context->prettyFlags, context->wrapColumn, + context->indentLevel); + } + else if (values_rte) + { + /* Add the multi-VALUES expression lists */ + get_values_def(values_rte->values_lists, context); + } + else if (strippedexprs) + { + /* Add the single-VALUES expression list */ + appendContextKeyword(context, "VALUES (", + -PRETTYINDENT_STD, PRETTYINDENT_STD, 2); + get_rule_list_toplevel(strippedexprs, context, false); + appendStringInfoChar(buf, ')'); + } + else + { + /* No expressions, so it must be DEFAULT VALUES */ + appendStringInfoString(buf, "DEFAULT VALUES"); + } + + /* Add ON CONFLICT if present */ + if (query->onConflict) + { + OnConflictExpr *confl = query->onConflict; + + appendStringInfoString(buf, " ON CONFLICT"); + + if (confl->arbiterElems) + { + /* Add the single-VALUES expression list */ + appendStringInfoChar(buf, '('); + get_rule_expr((Node *) confl->arbiterElems, context, false); + appendStringInfoChar(buf, ')'); + + /* Add a WHERE clause (for partial indexes) if given */ + if (confl->arbiterWhere != NULL) + { + bool save_varprefix; + + /* + * Force non-prefixing of Vars, since parser assumes that they + * belong to target relation. WHERE clause does not use + * InferenceElem, so this is separately required. + */ + save_varprefix = context->varprefix; + context->varprefix = false; + + appendContextKeyword(context, " WHERE ", + -PRETTYINDENT_STD, PRETTYINDENT_STD, 1); + get_rule_expr(confl->arbiterWhere, context, false); + + context->varprefix = save_varprefix; + } + } + else if (OidIsValid(confl->constraint)) + { + char *constraint = get_constraint_name(confl->constraint); + + if (!constraint) + elog(ERROR, "cache lookup failed for constraint %u", + confl->constraint); + appendStringInfo(buf, " ON CONSTRAINT %s", + quote_identifier(constraint)); + } + + if (confl->action == ONCONFLICT_NOTHING) + { + appendStringInfoString(buf, " DO NOTHING"); + } + else + { + appendStringInfoString(buf, " DO UPDATE SET "); + /* Deparse targetlist */ + get_update_query_targetlist_def(query, confl->onConflictSet, + context, rte); + + /* Add a WHERE clause if given */ + if (confl->onConflictWhere != NULL) + { + appendContextKeyword(context, " WHERE ", + -PRETTYINDENT_STD, PRETTYINDENT_STD, 1); + get_rule_expr(confl->onConflictWhere, context, false); + } + } + } + + /* Add RETURNING if present */ + if (query->returningList) + { + appendContextKeyword(context, " RETURNING", + -PRETTYINDENT_STD, PRETTYINDENT_STD, 1); + get_target_list(query->returningList, context, NULL, colNamesVisible); + } +} + + +/* ---------- + * get_update_query_def - Parse back an UPDATE parsetree + * ---------- + */ +static void +get_update_query_def(Query *query, deparse_context *context, + bool colNamesVisible) +{ + StringInfo buf = context->buf; + RangeTblEntry *rte; + + /* Insert the WITH clause if given */ + get_with_clause(query, context); + + /* + * Start the query with UPDATE relname SET + */ + rte = rt_fetch(query->resultRelation, query->rtable); + Assert(rte->rtekind == RTE_RELATION); + if (PRETTY_INDENT(context)) + { + appendStringInfoChar(buf, ' '); + context->indentLevel += PRETTYINDENT_STD; + } + appendStringInfo(buf, "UPDATE %s%s", + only_marker(rte), + generate_relation_name(rte->relid, NIL)); + + /* Print the relation alias, if needed */ + get_rte_alias(rte, query->resultRelation, false, context); + + appendStringInfoString(buf, " SET "); + + /* Deparse targetlist */ + get_update_query_targetlist_def(query, query->targetList, context, rte); + + /* Add the FROM clause if needed */ + get_from_clause(query, " FROM ", context); + + /* Add a WHERE clause if given */ + if (query->jointree->quals != NULL) + { + appendContextKeyword(context, " WHERE ", + -PRETTYINDENT_STD, PRETTYINDENT_STD, 1); + get_rule_expr(query->jointree->quals, context, false); + } + + /* Add RETURNING if present */ + if (query->returningList) + { + appendContextKeyword(context, " RETURNING", + -PRETTYINDENT_STD, PRETTYINDENT_STD, 1); + get_target_list(query->returningList, context, NULL, colNamesVisible); + } +} + + +/* ---------- + * get_update_query_targetlist_def - Parse back an UPDATE targetlist + * ---------- + */ +static void +get_update_query_targetlist_def(Query *query, List *targetList, + deparse_context *context, RangeTblEntry *rte) +{ + StringInfo buf = context->buf; + ListCell *l; + ListCell *next_ma_cell; + int remaining_ma_columns; + const char *sep; + SubLink *cur_ma_sublink; + List *ma_sublinks; + + /* + * Prepare to deal with MULTIEXPR assignments: collect the source SubLinks + * into a list. We expect them to appear, in ID order, in resjunk tlist + * entries. + */ + ma_sublinks = NIL; + if (query->hasSubLinks) /* else there can't be any */ + { + foreach(l, targetList) + { + TargetEntry *tle = (TargetEntry *) lfirst(l); + + if (tle->resjunk && IsA(tle->expr, SubLink)) + { + SubLink *sl = (SubLink *) tle->expr; + + if (sl->subLinkType == MULTIEXPR_SUBLINK) + { + ma_sublinks = lappend(ma_sublinks, sl); + Assert(sl->subLinkId == list_length(ma_sublinks)); + } + } + } + } + next_ma_cell = list_head(ma_sublinks); + cur_ma_sublink = NULL; + remaining_ma_columns = 0; + + /* Add the comma separated list of 'attname = value' */ + sep = ""; + foreach(l, targetList) + { + TargetEntry *tle = (TargetEntry *) lfirst(l); + Node *expr; + + if (tle->resjunk) + continue; /* ignore junk entries */ + + /* Emit separator (OK whether we're in multiassignment or not) */ + appendStringInfoString(buf, sep); + sep = ", "; + + /* + * Check to see if we're starting a multiassignment group: if so, + * output a left paren. + */ + if (next_ma_cell != NULL && cur_ma_sublink == NULL) + { + /* + * We must dig down into the expr to see if it's a PARAM_MULTIEXPR + * Param. That could be buried under FieldStores and + * SubscriptingRefs and CoerceToDomains (cf processIndirection()), + * and underneath those there could be an implicit type coercion. + * Because we would ignore implicit type coercions anyway, we + * don't need to be as careful as processIndirection() is about + * descending past implicit CoerceToDomains. + */ + expr = (Node *) tle->expr; + while (expr) + { + if (IsA(expr, FieldStore)) + { + FieldStore *fstore = (FieldStore *) expr; + + expr = (Node *) linitial(fstore->newvals); + } + else if (IsA(expr, SubscriptingRef)) + { + SubscriptingRef *sbsref = (SubscriptingRef *) expr; + + if (sbsref->refassgnexpr == NULL) + break; + + expr = (Node *) sbsref->refassgnexpr; + } + else if (IsA(expr, CoerceToDomain)) + { + CoerceToDomain *cdomain = (CoerceToDomain *) expr; + + if (cdomain->coercionformat != COERCE_IMPLICIT_CAST) + break; + expr = (Node *) cdomain->arg; + } + else + break; + } + expr = strip_implicit_coercions(expr); + + if (expr && IsA(expr, Param) && + ((Param *) expr)->paramkind == PARAM_MULTIEXPR) + { + cur_ma_sublink = (SubLink *) lfirst(next_ma_cell); + next_ma_cell = lnext(ma_sublinks, next_ma_cell); + remaining_ma_columns = count_nonjunk_tlist_entries(((Query *) cur_ma_sublink->subselect)->targetList); + Assert(((Param *) expr)->paramid == + ((cur_ma_sublink->subLinkId << 16) | 1)); + appendStringInfoChar(buf, '('); + } + } + + /* + * Put out name of target column; look in the catalogs, not at + * tle->resname, since resname will fail to track RENAME. + */ + appendStringInfoString(buf, + quote_identifier(get_attname(rte->relid, + tle->resno, + false))); + + /* + * Print any indirection needed (subfields or subscripts), and strip + * off the top-level nodes representing the indirection assignments. + */ + expr = processIndirection((Node *) tle->expr, context); + + /* + * If we're in a multiassignment, skip printing anything more, unless + * this is the last column; in which case, what we print should be the + * sublink, not the Param. + */ + if (cur_ma_sublink != NULL) + { + if (--remaining_ma_columns > 0) + continue; /* not the last column of multiassignment */ + appendStringInfoChar(buf, ')'); + expr = (Node *) cur_ma_sublink; + cur_ma_sublink = NULL; + } + + appendStringInfoString(buf, " = "); + + get_rule_expr(expr, context, false); + } +} + + +/* ---------- + * get_delete_query_def - Parse back a DELETE parsetree + * ---------- + */ +static void +get_delete_query_def(Query *query, deparse_context *context, + bool colNamesVisible) +{ + StringInfo buf = context->buf; + RangeTblEntry *rte; + + /* Insert the WITH clause if given */ + get_with_clause(query, context); + + /* + * Start the query with DELETE FROM relname + */ + rte = rt_fetch(query->resultRelation, query->rtable); + Assert(rte->rtekind == RTE_RELATION); + if (PRETTY_INDENT(context)) + { + appendStringInfoChar(buf, ' '); + context->indentLevel += PRETTYINDENT_STD; + } + appendStringInfo(buf, "DELETE FROM %s%s", + only_marker(rte), + generate_relation_name(rte->relid, NIL)); + + /* Print the relation alias, if needed */ + get_rte_alias(rte, query->resultRelation, false, context); + + /* Add the USING clause if given */ + get_from_clause(query, " USING ", context); + + /* Add a WHERE clause if given */ + if (query->jointree->quals != NULL) + { + appendContextKeyword(context, " WHERE ", + -PRETTYINDENT_STD, PRETTYINDENT_STD, 1); + get_rule_expr(query->jointree->quals, context, false); + } + + /* Add RETURNING if present */ + if (query->returningList) + { + appendContextKeyword(context, " RETURNING", + -PRETTYINDENT_STD, PRETTYINDENT_STD, 1); + get_target_list(query->returningList, context, NULL, colNamesVisible); + } +} + + +/* ---------- + * get_merge_query_def - Parse back a MERGE parsetree + * ---------- + */ +static void +get_merge_query_def(Query *query, deparse_context *context, + bool colNamesVisible) +{ + StringInfo buf = context->buf; + RangeTblEntry *rte; + ListCell *lc; + + /* Insert the WITH clause if given */ + get_with_clause(query, context); + + /* + * Start the query with MERGE INTO relname + */ + rte = rt_fetch(query->resultRelation, query->rtable); + Assert(rte->rtekind == RTE_RELATION); + if (PRETTY_INDENT(context)) + { + appendStringInfoChar(buf, ' '); + context->indentLevel += PRETTYINDENT_STD; + } + appendStringInfo(buf, "MERGE INTO %s%s", + only_marker(rte), + generate_relation_name(rte->relid, NIL)); + + /* Print the relation alias, if needed */ + get_rte_alias(rte, query->resultRelation, false, context); + + /* Print the source relation and join clause */ + get_from_clause(query, " USING ", context); + appendContextKeyword(context, " ON ", + -PRETTYINDENT_STD, PRETTYINDENT_STD, 2); + get_rule_expr(query->jointree->quals, context, false); + + /* Print each merge action */ + foreach(lc, query->mergeActionList) + { + MergeAction *action = lfirst_node(MergeAction, lc); + + appendContextKeyword(context, " WHEN ", + -PRETTYINDENT_STD, PRETTYINDENT_STD, 2); + appendStringInfo(buf, "%sMATCHED", action->matched ? "" : "NOT "); + + if (action->qual) + { + appendContextKeyword(context, " AND ", + -PRETTYINDENT_STD, PRETTYINDENT_STD, 3); + get_rule_expr(action->qual, context, false); + } + appendContextKeyword(context, " THEN ", + -PRETTYINDENT_STD, PRETTYINDENT_STD, 3); + + if (action->commandType == CMD_INSERT) + { + /* This generally matches get_insert_query_def() */ + List *strippedexprs = NIL; + const char *sep = ""; + ListCell *lc2; + + appendStringInfoString(buf, "INSERT"); + + if (action->targetList) + appendStringInfoString(buf, " ("); + foreach(lc2, action->targetList) + { + TargetEntry *tle = (TargetEntry *) lfirst(lc2); + + Assert(!tle->resjunk); + + appendStringInfoString(buf, sep); + sep = ", "; + + appendStringInfoString(buf, + quote_identifier(get_attname(rte->relid, + tle->resno, + false))); + strippedexprs = lappend(strippedexprs, + processIndirection((Node *) tle->expr, + context)); + } + if (action->targetList) + appendStringInfoChar(buf, ')'); + + if (action->override) + { + if (action->override == OVERRIDING_SYSTEM_VALUE) + appendStringInfoString(buf, " OVERRIDING SYSTEM VALUE"); + else if (action->override == OVERRIDING_USER_VALUE) + appendStringInfoString(buf, " OVERRIDING USER VALUE"); + } + + if (strippedexprs) + { + appendContextKeyword(context, " VALUES (", + -PRETTYINDENT_STD, PRETTYINDENT_STD, 4); + get_rule_list_toplevel(strippedexprs, context, false); + appendStringInfoChar(buf, ')'); + } + else + appendStringInfoString(buf, " DEFAULT VALUES"); + } + else if (action->commandType == CMD_UPDATE) + { + appendStringInfoString(buf, "UPDATE SET "); + get_update_query_targetlist_def(query, action->targetList, + context, rte); + } + else if (action->commandType == CMD_DELETE) + appendStringInfoString(buf, "DELETE"); + else if (action->commandType == CMD_NOTHING) + appendStringInfoString(buf, "DO NOTHING"); + } + + /* No RETURNING support in MERGE yet */ + Assert(query->returningList == NIL); +} + + +/* ---------- + * get_utility_query_def - Parse back a UTILITY parsetree + * ---------- + */ +static void +get_utility_query_def(Query *query, deparse_context *context) +{ + StringInfo buf = context->buf; + + if (query->utilityStmt && IsA(query->utilityStmt, NotifyStmt)) + { + NotifyStmt *stmt = (NotifyStmt *) query->utilityStmt; + + appendContextKeyword(context, "", + 0, PRETTYINDENT_STD, 1); + appendStringInfo(buf, "NOTIFY %s", + quote_identifier(stmt->conditionname)); + if (stmt->payload) + { + appendStringInfoString(buf, ", "); + simple_quote_literal(buf, stmt->payload); + } + } + else + { + /* Currently only NOTIFY utility commands can appear in rules */ + elog(ERROR, "unexpected utility statement type"); + } +} + +/* + * Display a Var appropriately. + * + * In some cases (currently only when recursing into an unnamed join) + * the Var's varlevelsup has to be interpreted with respect to a context + * above the current one; levelsup indicates the offset. + * + * If istoplevel is true, the Var is at the top level of a SELECT's + * targetlist, which means we need special treatment of whole-row Vars. + * Instead of the normal "tab.*", we'll print "tab.*::typename", which is a + * dirty hack to prevent "tab.*" from being expanded into multiple columns. + * (The parser will strip the useless coercion, so no inefficiency is added in + * dump and reload.) We used to print just "tab" in such cases, but that is + * ambiguous and will yield the wrong result if "tab" is also a plain column + * name in the query. + * + * Returns the attname of the Var, or NULL if the Var has no attname (because + * it is a whole-row Var or a subplan output reference). + */ +static char * +get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context) +{ + StringInfo buf = context->buf; + RangeTblEntry *rte; + AttrNumber attnum; + int netlevelsup; + deparse_namespace *dpns; + int varno; + AttrNumber varattno; + deparse_columns *colinfo; + char *refname; + char *attname; + + /* Find appropriate nesting depth */ + netlevelsup = var->varlevelsup + levelsup; + if (netlevelsup >= list_length(context->namespaces)) + elog(ERROR, "bogus varlevelsup: %d offset %d", + var->varlevelsup, levelsup); + dpns = (deparse_namespace *) list_nth(context->namespaces, + netlevelsup); + + /* + * If we have a syntactic referent for the Var, and we're working from a + * parse tree, prefer to use the syntactic referent. Otherwise, fall back + * on the semantic referent. (Forcing use of the semantic referent when + * printing plan trees is a design choice that's perhaps more motivated by + * backwards compatibility than anything else. But it does have the + * advantage of making plans more explicit.) + */ + if (var->varnosyn > 0 && dpns->plan == NULL) + { + varno = var->varnosyn; + varattno = var->varattnosyn; + } + else + { + varno = var->varno; + varattno = var->varattno; + } + + /* + * Try to find the relevant RTE in this rtable. In a plan tree, it's + * likely that varno is OUTER_VAR or INNER_VAR, in which case we must dig + * down into the subplans, or INDEX_VAR, which is resolved similarly. Also + * find the aliases previously assigned for this RTE. + */ + if (varno >= 1 && varno <= list_length(dpns->rtable)) + { + /* + * We might have been asked to map child Vars to some parent relation. + */ + if (context->appendparents && dpns->appendrels) + { + int pvarno = varno; + AttrNumber pvarattno = varattno; + AppendRelInfo *appinfo = dpns->appendrels[pvarno]; + bool found = false; + + /* Only map up to inheritance parents, not UNION ALL appendrels */ + while (appinfo && + rt_fetch(appinfo->parent_relid, + dpns->rtable)->rtekind == RTE_RELATION) + { + found = false; + if (pvarattno > 0) /* system columns stay as-is */ + { + if (pvarattno > appinfo->num_child_cols) + break; /* safety check */ + pvarattno = appinfo->parent_colnos[pvarattno - 1]; + if (pvarattno == 0) + break; /* Var is local to child */ + } + + pvarno = appinfo->parent_relid; + found = true; + + /* If the parent is itself a child, continue up. */ + Assert(pvarno > 0 && pvarno <= list_length(dpns->rtable)); + appinfo = dpns->appendrels[pvarno]; + } + + /* + * If we found an ancestral rel, and that rel is included in + * appendparents, print that column not the original one. + */ + if (found && bms_is_member(pvarno, context->appendparents)) + { + varno = pvarno; + varattno = pvarattno; + } + } + + rte = rt_fetch(varno, dpns->rtable); + refname = (char *) list_nth(dpns->rtable_names, varno - 1); + colinfo = deparse_columns_fetch(varno, dpns); + attnum = varattno; + } + else + { + resolve_special_varno((Node *) var, context, + get_special_variable, NULL); + return NULL; + } + + /* + * The planner will sometimes emit Vars referencing resjunk elements of a + * subquery's target list (this is currently only possible if it chooses + * to generate a "physical tlist" for a SubqueryScan or CteScan node). + * Although we prefer to print subquery-referencing Vars using the + * subquery's alias, that's not possible for resjunk items since they have + * no alias. So in that case, drill down to the subplan and print the + * contents of the referenced tlist item. This works because in a plan + * tree, such Vars can only occur in a SubqueryScan or CteScan node, and + * we'll have set dpns->inner_plan to reference the child plan node. + */ + if ((rte->rtekind == RTE_SUBQUERY || rte->rtekind == RTE_CTE) && + attnum > list_length(rte->eref->colnames) && + dpns->inner_plan) + { + TargetEntry *tle; + deparse_namespace save_dpns; + + tle = get_tle_by_resno(dpns->inner_tlist, attnum); + if (!tle) + elog(ERROR, "invalid attnum %d for relation \"%s\"", + attnum, rte->eref->aliasname); + + Assert(netlevelsup == 0); + push_child_plan(dpns, dpns->inner_plan, &save_dpns); + + /* + * Force parentheses because our caller probably assumed a Var is a + * simple expression. + */ + if (!IsA(tle->expr, Var)) + appendStringInfoChar(buf, '('); + get_rule_expr((Node *) tle->expr, context, true); + if (!IsA(tle->expr, Var)) + appendStringInfoChar(buf, ')'); + + pop_child_plan(dpns, &save_dpns); + return NULL; + } + + /* + * If it's an unnamed join, look at the expansion of the alias variable. + * If it's a simple reference to one of the input vars, then recursively + * print the name of that var instead. When it's not a simple reference, + * we have to just print the unqualified join column name. (This can only + * happen with "dangerous" merged columns in a JOIN USING; we took pains + * previously to make the unqualified column name unique in such cases.) + * + * This wouldn't work in decompiling plan trees, because we don't store + * joinaliasvars lists after planning; but a plan tree should never + * contain a join alias variable. + */ + if (rte->rtekind == RTE_JOIN && rte->alias == NULL) + { + if (rte->joinaliasvars == NIL) + elog(ERROR, "cannot decompile join alias var in plan tree"); + if (attnum > 0) + { + Var *aliasvar; + + aliasvar = (Var *) list_nth(rte->joinaliasvars, attnum - 1); + /* we intentionally don't strip implicit coercions here */ + if (aliasvar && IsA(aliasvar, Var)) + { + return get_variable(aliasvar, var->varlevelsup + levelsup, + istoplevel, context); + } + } + + /* + * Unnamed join has no refname. (Note: since it's unnamed, there is + * no way the user could have referenced it to create a whole-row Var + * for it. So we don't have to cover that case below.) + */ + Assert(refname == NULL); + } + + if (attnum == InvalidAttrNumber) + attname = NULL; + else if (attnum > 0) + { + /* Get column name to use from the colinfo struct */ + if (attnum > colinfo->num_cols) + elog(ERROR, "invalid attnum %d for relation \"%s\"", + attnum, rte->eref->aliasname); + attname = colinfo->colnames[attnum - 1]; + + /* + * If we find a Var referencing a dropped column, it seems better to + * print something (anything) than to fail. In general this should + * not happen, but it used to be possible for some cases involving + * functions returning named composite types, and perhaps there are + * still bugs out there. + */ + if (attname == NULL) + attname = "?dropped?column?"; + } + else + { + /* System column - name is fixed, get it from the catalog */ + attname = get_rte_attribute_name(rte, attnum); + } + + if (refname && (context->varprefix || attname == NULL)) + { + appendStringInfoString(buf, quote_identifier(refname)); + appendStringInfoChar(buf, '.'); + } + if (attname) + appendStringInfoString(buf, quote_identifier(attname)); + else + { + appendStringInfoChar(buf, '*'); + if (istoplevel) + appendStringInfo(buf, "::%s", + format_type_with_typemod(var->vartype, + var->vartypmod)); + } + + return attname; +} + +/* + * Deparse a Var which references OUTER_VAR, INNER_VAR, or INDEX_VAR. This + * routine is actually a callback for resolve_special_varno, which handles + * finding the correct TargetEntry. We get the expression contained in that + * TargetEntry and just need to deparse it, a job we can throw back on + * get_rule_expr. + */ +static void +get_special_variable(Node *node, deparse_context *context, void *callback_arg) +{ + StringInfo buf = context->buf; + + /* + * For a non-Var referent, force parentheses because our caller probably + * assumed a Var is a simple expression. + */ + if (!IsA(node, Var)) + appendStringInfoChar(buf, '('); + get_rule_expr(node, context, true); + if (!IsA(node, Var)) + appendStringInfoChar(buf, ')'); +} + +/* + * Chase through plan references to special varnos (OUTER_VAR, INNER_VAR, + * INDEX_VAR) until we find a real Var or some kind of non-Var node; then, + * invoke the callback provided. + */ +static void +resolve_special_varno(Node *node, deparse_context *context, + rsv_callback callback, void *callback_arg) +{ + Var *var; + deparse_namespace *dpns; + + /* This function is recursive, so let's be paranoid. */ + check_stack_depth(); + + /* If it's not a Var, invoke the callback. */ + if (!IsA(node, Var)) + { + (*callback) (node, context, callback_arg); + return; + } + + /* Find appropriate nesting depth */ + var = (Var *) node; + dpns = (deparse_namespace *) list_nth(context->namespaces, + var->varlevelsup); + + /* + * If varno is special, recurse. (Don't worry about varnosyn; if we're + * here, we already decided not to use that.) + */ + if (var->varno == OUTER_VAR && dpns->outer_tlist) + { + TargetEntry *tle; + deparse_namespace save_dpns; + Bitmapset *save_appendparents; + + tle = get_tle_by_resno(dpns->outer_tlist, var->varattno); + if (!tle) + elog(ERROR, "bogus varattno for OUTER_VAR var: %d", var->varattno); + + /* + * If we're descending to the first child of an Append or MergeAppend, + * update appendparents. This will affect deparsing of all Vars + * appearing within the eventually-resolved subexpression. + */ + save_appendparents = context->appendparents; + + if (IsA(dpns->plan, Append)) + context->appendparents = bms_union(context->appendparents, + ((Append *) dpns->plan)->apprelids); + else if (IsA(dpns->plan, MergeAppend)) + context->appendparents = bms_union(context->appendparents, + ((MergeAppend *) dpns->plan)->apprelids); + + push_child_plan(dpns, dpns->outer_plan, &save_dpns); + resolve_special_varno((Node *) tle->expr, context, + callback, callback_arg); + pop_child_plan(dpns, &save_dpns); + context->appendparents = save_appendparents; + return; + } + else if (var->varno == INNER_VAR && dpns->inner_tlist) + { + TargetEntry *tle; + deparse_namespace save_dpns; + + tle = get_tle_by_resno(dpns->inner_tlist, var->varattno); + if (!tle) + elog(ERROR, "bogus varattno for INNER_VAR var: %d", var->varattno); + + push_child_plan(dpns, dpns->inner_plan, &save_dpns); + resolve_special_varno((Node *) tle->expr, context, + callback, callback_arg); + pop_child_plan(dpns, &save_dpns); + return; + } + else if (var->varno == INDEX_VAR && dpns->index_tlist) + { + TargetEntry *tle; + + tle = get_tle_by_resno(dpns->index_tlist, var->varattno); + if (!tle) + elog(ERROR, "bogus varattno for INDEX_VAR var: %d", var->varattno); + + resolve_special_varno((Node *) tle->expr, context, + callback, callback_arg); + return; + } + else if (var->varno < 1 || var->varno > list_length(dpns->rtable)) + elog(ERROR, "bogus varno: %d", var->varno); + + /* Not special. Just invoke the callback. */ + (*callback) (node, context, callback_arg); +} + +/* + * Get the name of a field of an expression of composite type. The + * expression is usually a Var, but we handle other cases too. + * + * levelsup is an extra offset to interpret the Var's varlevelsup correctly. + * + * This is fairly straightforward when the expression has a named composite + * type; we need only look up the type in the catalogs. However, the type + * could also be RECORD. Since no actual table or view column is allowed to + * have type RECORD, a Var of type RECORD must refer to a JOIN or FUNCTION RTE + * or to a subquery output. We drill down to find the ultimate defining + * expression and attempt to infer the field name from it. We ereport if we + * can't determine the name. + * + * Similarly, a PARAM of type RECORD has to refer to some expression of + * a determinable composite type. + */ +static const char * +get_name_for_var_field(Var *var, int fieldno, + int levelsup, deparse_context *context) +{ + RangeTblEntry *rte; + AttrNumber attnum; + int netlevelsup; + deparse_namespace *dpns; + int varno; + AttrNumber varattno; + TupleDesc tupleDesc; + Node *expr; + + /* + * If it's a RowExpr that was expanded from a whole-row Var, use the + * column names attached to it. (We could let get_expr_result_tupdesc() + * handle this, but it's much cheaper to just pull out the name we need.) + */ + if (IsA(var, RowExpr)) + { + RowExpr *r = (RowExpr *) var; + + if (fieldno > 0 && fieldno <= list_length(r->colnames)) + return strVal(list_nth(r->colnames, fieldno - 1)); + } + + /* + * If it's a Param of type RECORD, try to find what the Param refers to. + */ + if (IsA(var, Param)) + { + Param *param = (Param *) var; + ListCell *ancestor_cell; + + expr = find_param_referent(param, context, &dpns, &ancestor_cell); + if (expr) + { + /* Found a match, so recurse to decipher the field name */ + deparse_namespace save_dpns; + const char *result; + + push_ancestor_plan(dpns, ancestor_cell, &save_dpns); + result = get_name_for_var_field((Var *) expr, fieldno, + 0, context); + pop_ancestor_plan(dpns, &save_dpns); + return result; + } + } + + /* + * If it's a Var of type RECORD, we have to find what the Var refers to; + * if not, we can use get_expr_result_tupdesc(). + */ + if (!IsA(var, Var) || + var->vartype != RECORDOID) + { + tupleDesc = get_expr_result_tupdesc((Node *) var, false); + /* Got the tupdesc, so we can extract the field name */ + Assert(fieldno >= 1 && fieldno <= tupleDesc->natts); + return NameStr(TupleDescAttr(tupleDesc, fieldno - 1)->attname); + } + + /* Find appropriate nesting depth */ + netlevelsup = var->varlevelsup + levelsup; + if (netlevelsup >= list_length(context->namespaces)) + elog(ERROR, "bogus varlevelsup: %d offset %d", + var->varlevelsup, levelsup); + dpns = (deparse_namespace *) list_nth(context->namespaces, + netlevelsup); + + /* + * If we have a syntactic referent for the Var, and we're working from a + * parse tree, prefer to use the syntactic referent. Otherwise, fall back + * on the semantic referent. (See comments in get_variable().) + */ + if (var->varnosyn > 0 && dpns->plan == NULL) + { + varno = var->varnosyn; + varattno = var->varattnosyn; + } + else + { + varno = var->varno; + varattno = var->varattno; + } + + /* + * Try to find the relevant RTE in this rtable. In a plan tree, it's + * likely that varno is OUTER_VAR or INNER_VAR, in which case we must dig + * down into the subplans, or INDEX_VAR, which is resolved similarly. + * + * Note: unlike get_variable and resolve_special_varno, we need not worry + * about inheritance mapping: a child Var should have the same datatype as + * its parent, and here we're really only interested in the Var's type. + */ + if (varno >= 1 && varno <= list_length(dpns->rtable)) + { + rte = rt_fetch(varno, dpns->rtable); + attnum = varattno; + } + else if (varno == OUTER_VAR && dpns->outer_tlist) + { + TargetEntry *tle; + deparse_namespace save_dpns; + const char *result; + + tle = get_tle_by_resno(dpns->outer_tlist, varattno); + if (!tle) + elog(ERROR, "bogus varattno for OUTER_VAR var: %d", varattno); + + Assert(netlevelsup == 0); + push_child_plan(dpns, dpns->outer_plan, &save_dpns); + + result = get_name_for_var_field((Var *) tle->expr, fieldno, + levelsup, context); + + pop_child_plan(dpns, &save_dpns); + return result; + } + else if (varno == INNER_VAR && dpns->inner_tlist) + { + TargetEntry *tle; + deparse_namespace save_dpns; + const char *result; + + tle = get_tle_by_resno(dpns->inner_tlist, varattno); + if (!tle) + elog(ERROR, "bogus varattno for INNER_VAR var: %d", varattno); + + Assert(netlevelsup == 0); + push_child_plan(dpns, dpns->inner_plan, &save_dpns); + + result = get_name_for_var_field((Var *) tle->expr, fieldno, + levelsup, context); + + pop_child_plan(dpns, &save_dpns); + return result; + } + else if (varno == INDEX_VAR && dpns->index_tlist) + { + TargetEntry *tle; + const char *result; + + tle = get_tle_by_resno(dpns->index_tlist, varattno); + if (!tle) + elog(ERROR, "bogus varattno for INDEX_VAR var: %d", varattno); + + Assert(netlevelsup == 0); + + result = get_name_for_var_field((Var *) tle->expr, fieldno, + levelsup, context); + + return result; + } + else + { + elog(ERROR, "bogus varno: %d", varno); + return NULL; /* keep compiler quiet */ + } + + if (attnum == InvalidAttrNumber) + { + /* Var is whole-row reference to RTE, so select the right field */ + return get_rte_attribute_name(rte, fieldno); + } + + /* + * This part has essentially the same logic as the parser's + * expandRecordVariable() function, but we are dealing with a different + * representation of the input context, and we only need one field name + * not a TupleDesc. Also, we need special cases for finding subquery and + * CTE subplans when deparsing Plan trees. + */ + expr = (Node *) var; /* default if we can't drill down */ + + switch (rte->rtekind) + { + case RTE_RELATION: + case RTE_VALUES: + case RTE_NAMEDTUPLESTORE: + case RTE_RESULT: + + /* + * This case should not occur: a column of a table, values list, + * or ENR shouldn't have type RECORD. Fall through and fail (most + * likely) at the bottom. + */ + break; + case RTE_SUBQUERY: + /* Subselect-in-FROM: examine sub-select's output expr */ + { + if (rte->subquery) + { + TargetEntry *ste = get_tle_by_resno(rte->subquery->targetList, + attnum); + + if (ste == NULL || ste->resjunk) + elog(ERROR, "subquery %s does not have attribute %d", + rte->eref->aliasname, attnum); + expr = (Node *) ste->expr; + if (IsA(expr, Var)) + { + /* + * Recurse into the sub-select to see what its Var + * refers to. We have to build an additional level of + * namespace to keep in step with varlevelsup in the + * subselect; furthermore, the subquery RTE might be + * from an outer query level, in which case the + * namespace for the subselect must have that outer + * level as parent namespace. + */ + List *save_nslist = context->namespaces; + List *parent_namespaces; + deparse_namespace mydpns; + const char *result; + + parent_namespaces = list_copy_tail(context->namespaces, + netlevelsup); + + set_deparse_for_query(&mydpns, rte->subquery, + parent_namespaces); + + context->namespaces = lcons(&mydpns, parent_namespaces); + + result = get_name_for_var_field((Var *) expr, fieldno, + 0, context); + + context->namespaces = save_nslist; + + return result; + } + /* else fall through to inspect the expression */ + } + else + { + /* + * We're deparsing a Plan tree so we don't have complete + * RTE entries (in particular, rte->subquery is NULL). But + * the only place we'd see a Var directly referencing a + * SUBQUERY RTE is in a SubqueryScan plan node, and we can + * look into the child plan's tlist instead. + */ + TargetEntry *tle; + deparse_namespace save_dpns; + const char *result; + + if (!dpns->inner_plan) + elog(ERROR, "failed to find plan for subquery %s", + rte->eref->aliasname); + tle = get_tle_by_resno(dpns->inner_tlist, attnum); + if (!tle) + elog(ERROR, "bogus varattno for subquery var: %d", + attnum); + Assert(netlevelsup == 0); + push_child_plan(dpns, dpns->inner_plan, &save_dpns); + + result = get_name_for_var_field((Var *) tle->expr, fieldno, + levelsup, context); + + pop_child_plan(dpns, &save_dpns); + return result; + } + } + break; + case RTE_JOIN: + /* Join RTE --- recursively inspect the alias variable */ + if (rte->joinaliasvars == NIL) + elog(ERROR, "cannot decompile join alias var in plan tree"); + Assert(attnum > 0 && attnum <= list_length(rte->joinaliasvars)); + expr = (Node *) list_nth(rte->joinaliasvars, attnum - 1); + Assert(expr != NULL); + /* we intentionally don't strip implicit coercions here */ + if (IsA(expr, Var)) + return get_name_for_var_field((Var *) expr, fieldno, + var->varlevelsup + levelsup, + context); + /* else fall through to inspect the expression */ + break; + case RTE_FUNCTION: + case RTE_TABLEFUNC: + + /* + * We couldn't get here unless a function is declared with one of + * its result columns as RECORD, which is not allowed. + */ + break; + case RTE_CTE: + /* CTE reference: examine subquery's output expr */ + { + CommonTableExpr *cte = NULL; + Index ctelevelsup; + ListCell *lc; + + /* + * Try to find the referenced CTE using the namespace stack. + */ + ctelevelsup = rte->ctelevelsup + netlevelsup; + if (ctelevelsup >= list_length(context->namespaces)) + lc = NULL; + else + { + deparse_namespace *ctedpns; + + ctedpns = (deparse_namespace *) + list_nth(context->namespaces, ctelevelsup); + foreach(lc, ctedpns->ctes) + { + cte = (CommonTableExpr *) lfirst(lc); + if (strcmp(cte->ctename, rte->ctename) == 0) + break; + } + } + if (lc != NULL) + { + Query *ctequery = (Query *) cte->ctequery; + TargetEntry *ste = get_tle_by_resno(GetCTETargetList(cte), + attnum); + + if (ste == NULL || ste->resjunk) + elog(ERROR, "CTE %s does not have attribute %d", + rte->eref->aliasname, attnum); + expr = (Node *) ste->expr; + if (IsA(expr, Var)) + { + /* + * Recurse into the CTE to see what its Var refers to. + * We have to build an additional level of namespace + * to keep in step with varlevelsup in the CTE; + * furthermore it could be an outer CTE (compare + * SUBQUERY case above). + */ + List *save_nslist = context->namespaces; + List *parent_namespaces; + deparse_namespace mydpns; + const char *result; + + parent_namespaces = list_copy_tail(context->namespaces, + ctelevelsup); + + set_deparse_for_query(&mydpns, ctequery, + parent_namespaces); + + context->namespaces = lcons(&mydpns, parent_namespaces); + + result = get_name_for_var_field((Var *) expr, fieldno, + 0, context); + + context->namespaces = save_nslist; + + return result; + } + /* else fall through to inspect the expression */ + } + else + { + /* + * We're deparsing a Plan tree so we don't have a CTE + * list. But the only places we'd see a Var directly + * referencing a CTE RTE are in CteScan or WorkTableScan + * plan nodes. For those cases, set_deparse_plan arranged + * for dpns->inner_plan to be the plan node that emits the + * CTE or RecursiveUnion result, and we can look at its + * tlist instead. + */ + TargetEntry *tle; + deparse_namespace save_dpns; + const char *result; + + if (!dpns->inner_plan) + elog(ERROR, "failed to find plan for CTE %s", + rte->eref->aliasname); + tle = get_tle_by_resno(dpns->inner_tlist, attnum); + if (!tle) + elog(ERROR, "bogus varattno for subquery var: %d", + attnum); + Assert(netlevelsup == 0); + push_child_plan(dpns, dpns->inner_plan, &save_dpns); + + result = get_name_for_var_field((Var *) tle->expr, fieldno, + levelsup, context); + + pop_child_plan(dpns, &save_dpns); + return result; + } + } + break; + } + + /* + * We now have an expression we can't expand any more, so see if + * get_expr_result_tupdesc() can do anything with it. + */ + tupleDesc = get_expr_result_tupdesc(expr, false); + /* Got the tupdesc, so we can extract the field name */ + Assert(fieldno >= 1 && fieldno <= tupleDesc->natts); + return NameStr(TupleDescAttr(tupleDesc, fieldno - 1)->attname); +} + +/* + * Try to find the referenced expression for a PARAM_EXEC Param that might + * reference a parameter supplied by an upper NestLoop or SubPlan plan node. + * + * If successful, return the expression and set *dpns_p and *ancestor_cell_p + * appropriately for calling push_ancestor_plan(). If no referent can be + * found, return NULL. + */ +static Node * +find_param_referent(Param *param, deparse_context *context, + deparse_namespace **dpns_p, ListCell **ancestor_cell_p) +{ + /* Initialize output parameters to prevent compiler warnings */ + *dpns_p = NULL; + *ancestor_cell_p = NULL; + + /* + * If it's a PARAM_EXEC parameter, look for a matching NestLoopParam or + * SubPlan argument. This will necessarily be in some ancestor of the + * current expression's Plan node. + */ + if (param->paramkind == PARAM_EXEC) + { + deparse_namespace *dpns; + Plan *child_plan; + ListCell *lc; + + dpns = (deparse_namespace *) linitial(context->namespaces); + child_plan = dpns->plan; + + foreach(lc, dpns->ancestors) + { + Node *ancestor = (Node *) lfirst(lc); + ListCell *lc2; + + /* + * NestLoops transmit params to their inner child only. + */ + if (IsA(ancestor, NestLoop) && + child_plan == innerPlan(ancestor)) + { + NestLoop *nl = (NestLoop *) ancestor; + + foreach(lc2, nl->nestParams) + { + NestLoopParam *nlp = (NestLoopParam *) lfirst(lc2); + + if (nlp->paramno == param->paramid) + { + /* Found a match, so return it */ + *dpns_p = dpns; + *ancestor_cell_p = lc; + return (Node *) nlp->paramval; + } + } + } + + /* + * If ancestor is a SubPlan, check the arguments it provides. + */ + if (IsA(ancestor, SubPlan)) + { + SubPlan *subplan = (SubPlan *) ancestor; + ListCell *lc3; + ListCell *lc4; + + forboth(lc3, subplan->parParam, lc4, subplan->args) + { + int paramid = lfirst_int(lc3); + Node *arg = (Node *) lfirst(lc4); + + if (paramid == param->paramid) + { + /* + * Found a match, so return it. But, since Vars in + * the arg are to be evaluated in the surrounding + * context, we have to point to the next ancestor item + * that is *not* a SubPlan. + */ + ListCell *rest; + + for_each_cell(rest, dpns->ancestors, + lnext(dpns->ancestors, lc)) + { + Node *ancestor2 = (Node *) lfirst(rest); + + if (!IsA(ancestor2, SubPlan)) + { + *dpns_p = dpns; + *ancestor_cell_p = rest; + return arg; + } + } + elog(ERROR, "SubPlan cannot be outermost ancestor"); + } + } + + /* SubPlan isn't a kind of Plan, so skip the rest */ + continue; + } + + /* + * We need not consider the ancestor's initPlan list, since + * initplans never have any parParams. + */ + + /* No luck, crawl up to next ancestor */ + child_plan = (Plan *) ancestor; + } + } + + /* No referent found */ + return NULL; +} + +/* + * Display a Param appropriately. + */ +static void +get_parameter(Param *param, deparse_context *context) +{ + Node *expr; + deparse_namespace *dpns; + ListCell *ancestor_cell; + + /* + * If it's a PARAM_EXEC parameter, try to locate the expression from which + * the parameter was computed. Note that failing to find a referent isn't + * an error, since the Param might well be a subplan output rather than an + * input. + */ + expr = find_param_referent(param, context, &dpns, &ancestor_cell); + if (expr) + { + /* Found a match, so print it */ + deparse_namespace save_dpns; + bool save_varprefix; + bool need_paren; + + /* Switch attention to the ancestor plan node */ + push_ancestor_plan(dpns, ancestor_cell, &save_dpns); + + /* + * Force prefixing of Vars, since they won't belong to the relation + * being scanned in the original plan node. + */ + save_varprefix = context->varprefix; + context->varprefix = true; + + /* + * A Param's expansion is typically a Var, Aggref, GroupingFunc, or + * upper-level Param, which wouldn't need extra parentheses. + * Otherwise, insert parens to ensure the expression looks atomic. + */ + need_paren = !(IsA(expr, Var) || + IsA(expr, Aggref) || + IsA(expr, GroupingFunc) || + IsA(expr, Param)); + if (need_paren) + appendStringInfoChar(context->buf, '('); + + get_rule_expr(expr, context, false); + + if (need_paren) + appendStringInfoChar(context->buf, ')'); + + context->varprefix = save_varprefix; + + pop_ancestor_plan(dpns, &save_dpns); + + return; + } + + /* + * If it's an external parameter, see if the outermost namespace provides + * function argument names. + */ + if (param->paramkind == PARAM_EXTERN && context->namespaces != NIL) + { + dpns = llast(context->namespaces); + if (dpns->argnames && + param->paramid > 0 && + param->paramid <= dpns->numargs) + { + char *argname = dpns->argnames[param->paramid - 1]; + + if (argname) + { + bool should_qualify = false; + ListCell *lc; + + /* + * Qualify the parameter name if there are any other deparse + * namespaces with range tables. This avoids qualifying in + * trivial cases like "RETURN a + b", but makes it safe in all + * other cases. + */ + foreach(lc, context->namespaces) + { + deparse_namespace *depns = lfirst(lc); + + if (depns->rtable_names != NIL) + { + should_qualify = true; + break; + } + } + if (should_qualify) + { + appendStringInfoString(context->buf, quote_identifier(dpns->funcname)); + appendStringInfoChar(context->buf, '.'); + } + + appendStringInfoString(context->buf, quote_identifier(argname)); + return; + } + } + } + + /* + * Not PARAM_EXEC, or couldn't find referent: just print $N. + */ + appendStringInfo(context->buf, "$%d", param->paramid); +} + +/* + * get_simple_binary_op_name + * + * helper function for isSimpleNode + * will return single char binary operator name, or NULL if it's not + */ +static const char * +get_simple_binary_op_name(OpExpr *expr) +{ + List *args = expr->args; + + if (list_length(args) == 2) + { + /* binary operator */ + Node *arg1 = (Node *) linitial(args); + Node *arg2 = (Node *) lsecond(args); + const char *op; + + op = generate_operator_name(expr->opno, exprType(arg1), exprType(arg2)); + if (strlen(op) == 1) + return op; + } + return NULL; +} + + +/* + * isSimpleNode - check if given node is simple (doesn't need parenthesizing) + * + * true : simple in the context of parent node's type + * false : not simple + */ +static bool +isSimpleNode(Node *node, Node *parentNode, int prettyFlags) +{ + if (!node) + return false; + + switch (nodeTag(node)) + { + case T_Var: + case T_Const: + case T_Param: + case T_CoerceToDomainValue: + case T_SetToDefault: + case T_CurrentOfExpr: + /* single words: always simple */ + return true; + + case T_SubscriptingRef: + case T_ArrayExpr: + case T_RowExpr: + case T_CoalesceExpr: + case T_MinMaxExpr: + case T_SQLValueFunction: + case T_XmlExpr: + case T_NextValueExpr: + case T_NullIfExpr: + case T_Aggref: + case T_GroupingFunc: + case T_WindowFunc: + case T_FuncExpr: + case T_JsonConstructorExpr: + /* function-like: name(..) or name[..] */ + return true; + + /* CASE keywords act as parentheses */ + case T_CaseExpr: + return true; + + case T_FieldSelect: + + /* + * appears simple since . has top precedence, unless parent is + * T_FieldSelect itself! + */ + return !IsA(parentNode, FieldSelect); + + case T_FieldStore: + + /* + * treat like FieldSelect (probably doesn't matter) + */ + return !IsA(parentNode, FieldStore); + + case T_CoerceToDomain: + /* maybe simple, check args */ + return isSimpleNode((Node *) ((CoerceToDomain *) node)->arg, + node, prettyFlags); + case T_RelabelType: + return isSimpleNode((Node *) ((RelabelType *) node)->arg, + node, prettyFlags); + case T_CoerceViaIO: + return isSimpleNode((Node *) ((CoerceViaIO *) node)->arg, + node, prettyFlags); + case T_ArrayCoerceExpr: + return isSimpleNode((Node *) ((ArrayCoerceExpr *) node)->arg, + node, prettyFlags); + case T_ConvertRowtypeExpr: + return isSimpleNode((Node *) ((ConvertRowtypeExpr *) node)->arg, + node, prettyFlags); + + case T_OpExpr: + { + /* depends on parent node type; needs further checking */ + if (prettyFlags & PRETTYFLAG_PAREN && IsA(parentNode, OpExpr)) + { + const char *op; + const char *parentOp; + bool is_lopriop; + bool is_hipriop; + bool is_lopriparent; + bool is_hipriparent; + + op = get_simple_binary_op_name((OpExpr *) node); + if (!op) + return false; + + /* We know only the basic operators + - and * / % */ + is_lopriop = (strchr("+-", *op) != NULL); + is_hipriop = (strchr("*/%", *op) != NULL); + if (!(is_lopriop || is_hipriop)) + return false; + + parentOp = get_simple_binary_op_name((OpExpr *) parentNode); + if (!parentOp) + return false; + + is_lopriparent = (strchr("+-", *parentOp) != NULL); + is_hipriparent = (strchr("*/%", *parentOp) != NULL); + if (!(is_lopriparent || is_hipriparent)) + return false; + + if (is_hipriop && is_lopriparent) + return true; /* op binds tighter than parent */ + + if (is_lopriop && is_hipriparent) + return false; + + /* + * Operators are same priority --- can skip parens only if + * we have (a - b) - c, not a - (b - c). + */ + if (node == (Node *) linitial(((OpExpr *) parentNode)->args)) + return true; + + return false; + } + /* else do the same stuff as for T_SubLink et al. */ + } + /* FALLTHROUGH */ + + case T_SubLink: + case T_NullTest: + case T_BooleanTest: + case T_DistinctExpr: + case T_JsonIsPredicate: + switch (nodeTag(parentNode)) + { + case T_FuncExpr: + { + /* special handling for casts and COERCE_SQL_SYNTAX */ + CoercionForm type = ((FuncExpr *) parentNode)->funcformat; + + if (type == COERCE_EXPLICIT_CAST || + type == COERCE_IMPLICIT_CAST || + type == COERCE_SQL_SYNTAX) + return false; + return true; /* own parentheses */ + } + case T_BoolExpr: /* lower precedence */ + case T_SubscriptingRef: /* other separators */ + case T_ArrayExpr: /* other separators */ + case T_RowExpr: /* other separators */ + case T_CoalesceExpr: /* own parentheses */ + case T_MinMaxExpr: /* own parentheses */ + case T_XmlExpr: /* own parentheses */ + case T_NullIfExpr: /* other separators */ + case T_Aggref: /* own parentheses */ + case T_GroupingFunc: /* own parentheses */ + case T_WindowFunc: /* own parentheses */ + case T_CaseExpr: /* other separators */ + return true; + default: + return false; + } + + case T_BoolExpr: + switch (nodeTag(parentNode)) + { + case T_BoolExpr: + if (prettyFlags & PRETTYFLAG_PAREN) + { + BoolExprType type; + BoolExprType parentType; + + type = ((BoolExpr *) node)->boolop; + parentType = ((BoolExpr *) parentNode)->boolop; + switch (type) + { + case NOT_EXPR: + case AND_EXPR: + if (parentType == AND_EXPR || parentType == OR_EXPR) + return true; + break; + case OR_EXPR: + if (parentType == OR_EXPR) + return true; + break; + } + } + return false; + case T_FuncExpr: + { + /* special handling for casts and COERCE_SQL_SYNTAX */ + CoercionForm type = ((FuncExpr *) parentNode)->funcformat; + + if (type == COERCE_EXPLICIT_CAST || + type == COERCE_IMPLICIT_CAST || + type == COERCE_SQL_SYNTAX) + return false; + return true; /* own parentheses */ + } + case T_SubscriptingRef: /* other separators */ + case T_ArrayExpr: /* other separators */ + case T_RowExpr: /* other separators */ + case T_CoalesceExpr: /* own parentheses */ + case T_MinMaxExpr: /* own parentheses */ + case T_XmlExpr: /* own parentheses */ + case T_NullIfExpr: /* other separators */ + case T_Aggref: /* own parentheses */ + case T_GroupingFunc: /* own parentheses */ + case T_WindowFunc: /* own parentheses */ + case T_CaseExpr: /* other separators */ + return true; + default: + return false; + } + + case T_JsonValueExpr: + /* maybe simple, check args */ + return isSimpleNode((Node *) ((JsonValueExpr *) node)->raw_expr, + node, prettyFlags); + + default: + break; + } + /* those we don't know: in dubio complexo */ + return false; +} + + +/* + * appendContextKeyword - append a keyword to buffer + * + * If prettyPrint is enabled, perform a line break, and adjust indentation. + * Otherwise, just append the keyword. + */ +static void +appendContextKeyword(deparse_context *context, const char *str, + int indentBefore, int indentAfter, int indentPlus) +{ + StringInfo buf = context->buf; + + if (PRETTY_INDENT(context)) + { + int indentAmount; + + context->indentLevel += indentBefore; + + /* remove any trailing spaces currently in the buffer ... */ + removeStringInfoSpaces(buf); + /* ... then add a newline and some spaces */ + appendStringInfoChar(buf, '\n'); + + if (context->indentLevel < PRETTYINDENT_LIMIT) + indentAmount = Max(context->indentLevel, 0) + indentPlus; + else + { + /* + * If we're indented more than PRETTYINDENT_LIMIT characters, try + * to conserve horizontal space by reducing the per-level + * indentation. For best results the scale factor here should + * divide all the indent amounts that get added to indentLevel + * (PRETTYINDENT_STD, etc). It's important that the indentation + * not grow unboundedly, else deeply-nested trees use O(N^2) + * whitespace; so we also wrap modulo PRETTYINDENT_LIMIT. + */ + indentAmount = PRETTYINDENT_LIMIT + + (context->indentLevel - PRETTYINDENT_LIMIT) / + (PRETTYINDENT_STD / 2); + indentAmount %= PRETTYINDENT_LIMIT; + /* scale/wrap logic affects indentLevel, but not indentPlus */ + indentAmount += indentPlus; + } + appendStringInfoSpaces(buf, indentAmount); + + appendStringInfoString(buf, str); + + context->indentLevel += indentAfter; + if (context->indentLevel < 0) + context->indentLevel = 0; + } + else + appendStringInfoString(buf, str); +} + +/* + * removeStringInfoSpaces - delete trailing spaces from a buffer. + * + * Possibly this should move to stringinfo.c at some point. + */ +static void +removeStringInfoSpaces(StringInfo str) +{ + while (str->len > 0 && str->data[str->len - 1] == ' ') + str->data[--(str->len)] = '\0'; +} + + +/* + * get_rule_expr_paren - deparse expr using get_rule_expr, + * embracing the string with parentheses if necessary for prettyPrint. + * + * Never embrace if prettyFlags=0, because it's done in the calling node. + * + * Any node that does *not* embrace its argument node by sql syntax (with + * parentheses, non-operator keywords like CASE/WHEN/ON, or comma etc) should + * use get_rule_expr_paren instead of get_rule_expr so parentheses can be + * added. + */ +static void +get_rule_expr_paren(Node *node, deparse_context *context, + bool showimplicit, Node *parentNode) +{ + bool need_paren; + + need_paren = PRETTY_PAREN(context) && + !isSimpleNode(node, parentNode, context->prettyFlags); + + if (need_paren) + appendStringInfoChar(context->buf, '('); + + get_rule_expr(node, context, showimplicit); + + if (need_paren) + appendStringInfoChar(context->buf, ')'); +} + + +/* ---------- + * get_rule_expr - Parse back an expression + * + * Note: showimplicit determines whether we display any implicit cast that + * is present at the top of the expression tree. It is a passed argument, + * not a field of the context struct, because we change the value as we + * recurse down into the expression. In general we suppress implicit casts + * when the result type is known with certainty (eg, the arguments of an + * OR must be boolean). We display implicit casts for arguments of functions + * and operators, since this is needed to be certain that the same function + * or operator will be chosen when the expression is re-parsed. + * ---------- + */ +static void +get_rule_expr(Node *node, deparse_context *context, + bool showimplicit) +{ + StringInfo buf = context->buf; + + if (node == NULL) + return; + + /* Guard against excessively long or deeply-nested queries */ + CHECK_FOR_INTERRUPTS(); + check_stack_depth(); + + /* + * Each level of get_rule_expr must emit an indivisible term + * (parenthesized if necessary) to ensure result is reparsed into the same + * expression tree. The only exception is that when the input is a List, + * we emit the component items comma-separated with no surrounding + * decoration; this is convenient for most callers. + */ + switch (nodeTag(node)) + { + case T_Var: + (void) get_variable((Var *) node, 0, false, context); + break; + + case T_Const: + get_const_expr((Const *) node, context, 0); + break; + + case T_Param: + get_parameter((Param *) node, context); + break; + + case T_Aggref: + get_agg_expr((Aggref *) node, context, (Aggref *) node); + break; + + case T_GroupingFunc: + { + GroupingFunc *gexpr = (GroupingFunc *) node; + + appendStringInfoString(buf, "GROUPING("); + get_rule_expr((Node *) gexpr->args, context, true); + appendStringInfoChar(buf, ')'); + } + break; + + case T_WindowFunc: + get_windowfunc_expr((WindowFunc *) node, context); + break; + + case T_SubscriptingRef: + { + SubscriptingRef *sbsref = (SubscriptingRef *) node; + bool need_parens; + + /* + * If the argument is a CaseTestExpr, we must be inside a + * FieldStore, ie, we are assigning to an element of an array + * within a composite column. Since we already punted on + * displaying the FieldStore's target information, just punt + * here too, and display only the assignment source + * expression. + */ + if (IsA(sbsref->refexpr, CaseTestExpr)) + { + Assert(sbsref->refassgnexpr); + get_rule_expr((Node *) sbsref->refassgnexpr, + context, showimplicit); + break; + } + + /* + * Parenthesize the argument unless it's a simple Var or a + * FieldSelect. (In particular, if it's another + * SubscriptingRef, we *must* parenthesize to avoid + * confusion.) + */ + need_parens = !IsA(sbsref->refexpr, Var) && + !IsA(sbsref->refexpr, FieldSelect); + if (need_parens) + appendStringInfoChar(buf, '('); + get_rule_expr((Node *) sbsref->refexpr, context, showimplicit); + if (need_parens) + appendStringInfoChar(buf, ')'); + + /* + * If there's a refassgnexpr, we want to print the node in the + * format "container[subscripts] := refassgnexpr". This is + * not legal SQL, so decompilation of INSERT or UPDATE + * statements should always use processIndirection as part of + * the statement-level syntax. We should only see this when + * EXPLAIN tries to print the targetlist of a plan resulting + * from such a statement. + */ + if (sbsref->refassgnexpr) + { + Node *refassgnexpr; + + /* + * Use processIndirection to print this node's subscripts + * as well as any additional field selections or + * subscripting in immediate descendants. It returns the + * RHS expr that is actually being "assigned". + */ + refassgnexpr = processIndirection(node, context); + appendStringInfoString(buf, " := "); + get_rule_expr(refassgnexpr, context, showimplicit); + } + else + { + /* Just an ordinary container fetch, so print subscripts */ + printSubscripts(sbsref, context); + } + } + break; + + case T_FuncExpr: + get_func_expr((FuncExpr *) node, context, showimplicit); + break; + + case T_NamedArgExpr: + { + NamedArgExpr *na = (NamedArgExpr *) node; + + appendStringInfo(buf, "%s => ", quote_identifier(na->name)); + get_rule_expr((Node *) na->arg, context, showimplicit); + } + break; + + case T_OpExpr: + get_oper_expr((OpExpr *) node, context); + break; + + case T_DistinctExpr: + { + DistinctExpr *expr = (DistinctExpr *) node; + List *args = expr->args; + Node *arg1 = (Node *) linitial(args); + Node *arg2 = (Node *) lsecond(args); + + if (!PRETTY_PAREN(context)) + appendStringInfoChar(buf, '('); + get_rule_expr_paren(arg1, context, true, node); + appendStringInfoString(buf, " IS DISTINCT FROM "); + get_rule_expr_paren(arg2, context, true, node); + if (!PRETTY_PAREN(context)) + appendStringInfoChar(buf, ')'); + } + break; + + case T_NullIfExpr: + { + NullIfExpr *nullifexpr = (NullIfExpr *) node; + + appendStringInfoString(buf, "NULLIF("); + get_rule_expr((Node *) nullifexpr->args, context, true); + appendStringInfoChar(buf, ')'); + } + break; + + case T_ScalarArrayOpExpr: + { + ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) node; + List *args = expr->args; + Node *arg1 = (Node *) linitial(args); + Node *arg2 = (Node *) lsecond(args); + + if (!PRETTY_PAREN(context)) + appendStringInfoChar(buf, '('); + get_rule_expr_paren(arg1, context, true, node); + appendStringInfo(buf, " %s %s (", + generate_operator_name(expr->opno, + exprType(arg1), + get_base_element_type(exprType(arg2))), + expr->useOr ? "ANY" : "ALL"); + get_rule_expr_paren(arg2, context, true, node); + + /* + * There's inherent ambiguity in "x op ANY/ALL (y)" when y is + * a bare sub-SELECT. Since we're here, the sub-SELECT must + * be meant as a scalar sub-SELECT yielding an array value to + * be used in ScalarArrayOpExpr; but the grammar will + * preferentially interpret such a construct as an ANY/ALL + * SubLink. To prevent misparsing the output that way, insert + * a dummy coercion (which will be stripped by parse analysis, + * so no inefficiency is added in dump and reload). This is + * indeed most likely what the user wrote to get the construct + * accepted in the first place. + */ + if (IsA(arg2, SubLink) && + ((SubLink *) arg2)->subLinkType == EXPR_SUBLINK) + appendStringInfo(buf, "::%s", + format_type_with_typemod(exprType(arg2), + exprTypmod(arg2))); + appendStringInfoChar(buf, ')'); + if (!PRETTY_PAREN(context)) + appendStringInfoChar(buf, ')'); + } + break; + + case T_BoolExpr: + { + BoolExpr *expr = (BoolExpr *) node; + Node *first_arg = linitial(expr->args); + ListCell *arg; + + switch (expr->boolop) + { + case AND_EXPR: + if (!PRETTY_PAREN(context)) + appendStringInfoChar(buf, '('); + get_rule_expr_paren(first_arg, context, + false, node); + for_each_from(arg, expr->args, 1) + { + appendStringInfoString(buf, " AND "); + get_rule_expr_paren((Node *) lfirst(arg), context, + false, node); + } + if (!PRETTY_PAREN(context)) + appendStringInfoChar(buf, ')'); + break; + + case OR_EXPR: + if (!PRETTY_PAREN(context)) + appendStringInfoChar(buf, '('); + get_rule_expr_paren(first_arg, context, + false, node); + for_each_from(arg, expr->args, 1) + { + appendStringInfoString(buf, " OR "); + get_rule_expr_paren((Node *) lfirst(arg), context, + false, node); + } + if (!PRETTY_PAREN(context)) + appendStringInfoChar(buf, ')'); + break; + + case NOT_EXPR: + if (!PRETTY_PAREN(context)) + appendStringInfoChar(buf, '('); + appendStringInfoString(buf, "NOT "); + get_rule_expr_paren(first_arg, context, + false, node); + if (!PRETTY_PAREN(context)) + appendStringInfoChar(buf, ')'); + break; + + default: + elog(ERROR, "unrecognized boolop: %d", + (int) expr->boolop); + } + } + break; + + case T_SubLink: + get_sublink_expr((SubLink *) node, context); + break; + + case T_SubPlan: + { + SubPlan *subplan = (SubPlan *) node; + + /* + * We cannot see an already-planned subplan in rule deparsing, + * only while EXPLAINing a query plan. We don't try to + * reconstruct the original SQL, just reference the subplan + * that appears elsewhere in EXPLAIN's result. + */ + if (subplan->useHashTable) + appendStringInfo(buf, "(hashed %s)", subplan->plan_name); + else + appendStringInfo(buf, "(%s)", subplan->plan_name); + } + break; + + case T_AlternativeSubPlan: + { + AlternativeSubPlan *asplan = (AlternativeSubPlan *) node; + ListCell *lc; + + /* + * This case cannot be reached in normal usage, since no + * AlternativeSubPlan can appear either in parsetrees or + * finished plan trees. We keep it just in case somebody + * wants to use this code to print planner data structures. + */ + appendStringInfoString(buf, "(alternatives: "); + foreach(lc, asplan->subplans) + { + SubPlan *splan = lfirst_node(SubPlan, lc); + + if (splan->useHashTable) + appendStringInfo(buf, "hashed %s", splan->plan_name); + else + appendStringInfoString(buf, splan->plan_name); + if (lnext(asplan->subplans, lc)) + appendStringInfoString(buf, " or "); + } + appendStringInfoChar(buf, ')'); + } + break; + + case T_FieldSelect: + { + FieldSelect *fselect = (FieldSelect *) node; + Node *arg = (Node *) fselect->arg; + int fno = fselect->fieldnum; + const char *fieldname; + bool need_parens; + + /* + * Parenthesize the argument unless it's an SubscriptingRef or + * another FieldSelect. Note in particular that it would be + * WRONG to not parenthesize a Var argument; simplicity is not + * the issue here, having the right number of names is. + */ + need_parens = !IsA(arg, SubscriptingRef) && + !IsA(arg, FieldSelect); + if (need_parens) + appendStringInfoChar(buf, '('); + get_rule_expr(arg, context, true); + if (need_parens) + appendStringInfoChar(buf, ')'); + + /* + * Get and print the field name. + */ + fieldname = get_name_for_var_field((Var *) arg, fno, + 0, context); + appendStringInfo(buf, ".%s", quote_identifier(fieldname)); + } + break; + + case T_FieldStore: + { + FieldStore *fstore = (FieldStore *) node; + bool need_parens; + + /* + * There is no good way to represent a FieldStore as real SQL, + * so decompilation of INSERT or UPDATE statements should + * always use processIndirection as part of the + * statement-level syntax. We should only get here when + * EXPLAIN tries to print the targetlist of a plan resulting + * from such a statement. The plan case is even harder than + * ordinary rules would be, because the planner tries to + * collapse multiple assignments to the same field or subfield + * into one FieldStore; so we can see a list of target fields + * not just one, and the arguments could be FieldStores + * themselves. We don't bother to try to print the target + * field names; we just print the source arguments, with a + * ROW() around them if there's more than one. This isn't + * terribly complete, but it's probably good enough for + * EXPLAIN's purposes; especially since anything more would be + * either hopelessly confusing or an even poorer + * representation of what the plan is actually doing. + */ + need_parens = (list_length(fstore->newvals) != 1); + if (need_parens) + appendStringInfoString(buf, "ROW("); + get_rule_expr((Node *) fstore->newvals, context, showimplicit); + if (need_parens) + appendStringInfoChar(buf, ')'); + } + break; + + case T_RelabelType: + { + RelabelType *relabel = (RelabelType *) node; + Node *arg = (Node *) relabel->arg; + + if (relabel->relabelformat == COERCE_IMPLICIT_CAST && + !showimplicit) + { + /* don't show the implicit cast */ + get_rule_expr_paren(arg, context, false, node); + } + else + { + get_coercion_expr(arg, context, + relabel->resulttype, + relabel->resulttypmod, + node); + } + } + break; + + case T_CoerceViaIO: + { + CoerceViaIO *iocoerce = (CoerceViaIO *) node; + Node *arg = (Node *) iocoerce->arg; + + if (iocoerce->coerceformat == COERCE_IMPLICIT_CAST && + !showimplicit) + { + /* don't show the implicit cast */ + get_rule_expr_paren(arg, context, false, node); + } + else + { + get_coercion_expr(arg, context, + iocoerce->resulttype, + -1, + node); + } + } + break; + + case T_ArrayCoerceExpr: + { + ArrayCoerceExpr *acoerce = (ArrayCoerceExpr *) node; + Node *arg = (Node *) acoerce->arg; + + if (acoerce->coerceformat == COERCE_IMPLICIT_CAST && + !showimplicit) + { + /* don't show the implicit cast */ + get_rule_expr_paren(arg, context, false, node); + } + else + { + get_coercion_expr(arg, context, + acoerce->resulttype, + acoerce->resulttypmod, + node); + } + } + break; + + case T_ConvertRowtypeExpr: + { + ConvertRowtypeExpr *convert = (ConvertRowtypeExpr *) node; + Node *arg = (Node *) convert->arg; + + if (convert->convertformat == COERCE_IMPLICIT_CAST && + !showimplicit) + { + /* don't show the implicit cast */ + get_rule_expr_paren(arg, context, false, node); + } + else + { + get_coercion_expr(arg, context, + convert->resulttype, -1, + node); + } + } + break; + + case T_CollateExpr: + { + CollateExpr *collate = (CollateExpr *) node; + Node *arg = (Node *) collate->arg; + + if (!PRETTY_PAREN(context)) + appendStringInfoChar(buf, '('); + get_rule_expr_paren(arg, context, showimplicit, node); + appendStringInfo(buf, " COLLATE %s", + generate_collation_name(collate->collOid)); + if (!PRETTY_PAREN(context)) + appendStringInfoChar(buf, ')'); + } + break; + + case T_CaseExpr: + { + CaseExpr *caseexpr = (CaseExpr *) node; + ListCell *temp; + + appendContextKeyword(context, "CASE", + 0, PRETTYINDENT_VAR, 0); + if (caseexpr->arg) + { + appendStringInfoChar(buf, ' '); + get_rule_expr((Node *) caseexpr->arg, context, true); + } + foreach(temp, caseexpr->args) + { + CaseWhen *when = (CaseWhen *) lfirst(temp); + Node *w = (Node *) when->expr; + + if (caseexpr->arg) + { + /* + * The parser should have produced WHEN clauses of the + * form "CaseTestExpr = RHS", possibly with an + * implicit coercion inserted above the CaseTestExpr. + * For accurate decompilation of rules it's essential + * that we show just the RHS. However in an + * expression that's been through the optimizer, the + * WHEN clause could be almost anything (since the + * equality operator could have been expanded into an + * inline function). If we don't recognize the form + * of the WHEN clause, just punt and display it as-is. + */ + if (IsA(w, OpExpr)) + { + List *args = ((OpExpr *) w)->args; + + if (list_length(args) == 2 && + IsA(strip_implicit_coercions(linitial(args)), + CaseTestExpr)) + w = (Node *) lsecond(args); + } + } + + if (!PRETTY_INDENT(context)) + appendStringInfoChar(buf, ' '); + appendContextKeyword(context, "WHEN ", + 0, 0, 0); + get_rule_expr(w, context, false); + appendStringInfoString(buf, " THEN "); + get_rule_expr((Node *) when->result, context, true); + } + if (!PRETTY_INDENT(context)) + appendStringInfoChar(buf, ' '); + appendContextKeyword(context, "ELSE ", + 0, 0, 0); + get_rule_expr((Node *) caseexpr->defresult, context, true); + if (!PRETTY_INDENT(context)) + appendStringInfoChar(buf, ' '); + appendContextKeyword(context, "END", + -PRETTYINDENT_VAR, 0, 0); + } + break; + + case T_CaseTestExpr: + { + /* + * Normally we should never get here, since for expressions + * that can contain this node type we attempt to avoid + * recursing to it. But in an optimized expression we might + * be unable to avoid that (see comments for CaseExpr). If we + * do see one, print it as CASE_TEST_EXPR. + */ + appendStringInfoString(buf, "CASE_TEST_EXPR"); + } + break; + + case T_ArrayExpr: + { + ArrayExpr *arrayexpr = (ArrayExpr *) node; + + appendStringInfoString(buf, "ARRAY["); + get_rule_expr((Node *) arrayexpr->elements, context, true); + appendStringInfoChar(buf, ']'); + + /* + * If the array isn't empty, we assume its elements are + * coerced to the desired type. If it's empty, though, we + * need an explicit coercion to the array type. + */ + if (arrayexpr->elements == NIL) + appendStringInfo(buf, "::%s", + format_type_with_typemod(arrayexpr->array_typeid, -1)); + } + break; + + case T_RowExpr: + { + RowExpr *rowexpr = (RowExpr *) node; + TupleDesc tupdesc = NULL; + ListCell *arg; + int i; + char *sep; + + /* + * If it's a named type and not RECORD, we may have to skip + * dropped columns and/or claim there are NULLs for added + * columns. + */ + if (rowexpr->row_typeid != RECORDOID) + { + tupdesc = lookup_rowtype_tupdesc(rowexpr->row_typeid, -1); + Assert(list_length(rowexpr->args) <= tupdesc->natts); + } + + /* + * SQL99 allows "ROW" to be omitted when there is more than + * one column, but for simplicity we always print it. + */ + appendStringInfoString(buf, "ROW("); + sep = ""; + i = 0; + foreach(arg, rowexpr->args) + { + Node *e = (Node *) lfirst(arg); + + if (tupdesc == NULL || + !TupleDescAttr(tupdesc, i)->attisdropped) + { + appendStringInfoString(buf, sep); + /* Whole-row Vars need special treatment here */ + get_rule_expr_toplevel(e, context, true); + sep = ", "; + } + i++; + } + if (tupdesc != NULL) + { + while (i < tupdesc->natts) + { + if (!TupleDescAttr(tupdesc, i)->attisdropped) + { + appendStringInfoString(buf, sep); + appendStringInfoString(buf, "NULL"); + sep = ", "; + } + i++; + } + + ReleaseTupleDesc(tupdesc); + } + appendStringInfoChar(buf, ')'); + if (rowexpr->row_format == COERCE_EXPLICIT_CAST) + appendStringInfo(buf, "::%s", + format_type_with_typemod(rowexpr->row_typeid, -1)); + } + break; + + case T_RowCompareExpr: + { + RowCompareExpr *rcexpr = (RowCompareExpr *) node; + + /* + * SQL99 allows "ROW" to be omitted when there is more than + * one column, but for simplicity we always print it. Within + * a ROW expression, whole-row Vars need special treatment, so + * use get_rule_list_toplevel. + */ + appendStringInfoString(buf, "(ROW("); + get_rule_list_toplevel(rcexpr->largs, context, true); + + /* + * We assume that the name of the first-column operator will + * do for all the rest too. This is definitely open to + * failure, eg if some but not all operators were renamed + * since the construct was parsed, but there seems no way to + * be perfect. + */ + appendStringInfo(buf, ") %s ROW(", + generate_operator_name(linitial_oid(rcexpr->opnos), + exprType(linitial(rcexpr->largs)), + exprType(linitial(rcexpr->rargs)))); + get_rule_list_toplevel(rcexpr->rargs, context, true); + appendStringInfoString(buf, "))"); + } + break; + + case T_CoalesceExpr: + { + CoalesceExpr *coalesceexpr = (CoalesceExpr *) node; + + appendStringInfoString(buf, "COALESCE("); + get_rule_expr((Node *) coalesceexpr->args, context, true); + appendStringInfoChar(buf, ')'); + } + break; + + case T_MinMaxExpr: + { + MinMaxExpr *minmaxexpr = (MinMaxExpr *) node; + + switch (minmaxexpr->op) + { + case IS_GREATEST: + appendStringInfoString(buf, "GREATEST("); + break; + case IS_LEAST: + appendStringInfoString(buf, "LEAST("); + break; + } + get_rule_expr((Node *) minmaxexpr->args, context, true); + appendStringInfoChar(buf, ')'); + } + break; + + case T_SQLValueFunction: + { + SQLValueFunction *svf = (SQLValueFunction *) node; + + /* + * Note: this code knows that typmod for time, timestamp, and + * timestamptz just prints as integer. + */ + switch (svf->op) + { + case SVFOP_CURRENT_DATE: + appendStringInfoString(buf, "CURRENT_DATE"); + break; + case SVFOP_CURRENT_TIME: + appendStringInfoString(buf, "CURRENT_TIME"); + break; + case SVFOP_CURRENT_TIME_N: + appendStringInfo(buf, "CURRENT_TIME(%d)", svf->typmod); + break; + case SVFOP_CURRENT_TIMESTAMP: + appendStringInfoString(buf, "CURRENT_TIMESTAMP"); + break; + case SVFOP_CURRENT_TIMESTAMP_N: + appendStringInfo(buf, "CURRENT_TIMESTAMP(%d)", + svf->typmod); + break; + case SVFOP_LOCALTIME: + appendStringInfoString(buf, "LOCALTIME"); + break; + case SVFOP_LOCALTIME_N: + appendStringInfo(buf, "LOCALTIME(%d)", svf->typmod); + break; + case SVFOP_LOCALTIMESTAMP: + appendStringInfoString(buf, "LOCALTIMESTAMP"); + break; + case SVFOP_LOCALTIMESTAMP_N: + appendStringInfo(buf, "LOCALTIMESTAMP(%d)", + svf->typmod); + break; + case SVFOP_CURRENT_ROLE: + appendStringInfoString(buf, "CURRENT_ROLE"); + break; + case SVFOP_CURRENT_USER: + appendStringInfoString(buf, "CURRENT_USER"); + break; + case SVFOP_USER: + appendStringInfoString(buf, "USER"); + break; + case SVFOP_SESSION_USER: + appendStringInfoString(buf, "SESSION_USER"); + break; + case SVFOP_CURRENT_CATALOG: + appendStringInfoString(buf, "CURRENT_CATALOG"); + break; + case SVFOP_CURRENT_SCHEMA: + appendStringInfoString(buf, "CURRENT_SCHEMA"); + break; + } + } + break; + + case T_XmlExpr: + { + XmlExpr *xexpr = (XmlExpr *) node; + bool needcomma = false; + ListCell *arg; + ListCell *narg; + Const *con; + + switch (xexpr->op) + { + case IS_XMLCONCAT: + appendStringInfoString(buf, "XMLCONCAT("); + break; + case IS_XMLELEMENT: + appendStringInfoString(buf, "XMLELEMENT("); + break; + case IS_XMLFOREST: + appendStringInfoString(buf, "XMLFOREST("); + break; + case IS_XMLPARSE: + appendStringInfoString(buf, "XMLPARSE("); + break; + case IS_XMLPI: + appendStringInfoString(buf, "XMLPI("); + break; + case IS_XMLROOT: + appendStringInfoString(buf, "XMLROOT("); + break; + case IS_XMLSERIALIZE: + appendStringInfoString(buf, "XMLSERIALIZE("); + break; + case IS_DOCUMENT: + break; + } + if (xexpr->op == IS_XMLPARSE || xexpr->op == IS_XMLSERIALIZE) + { + if (xexpr->xmloption == XMLOPTION_DOCUMENT) + appendStringInfoString(buf, "DOCUMENT "); + else + appendStringInfoString(buf, "CONTENT "); + } + if (xexpr->name) + { + appendStringInfo(buf, "NAME %s", + quote_identifier(map_xml_name_to_sql_identifier(xexpr->name))); + needcomma = true; + } + if (xexpr->named_args) + { + if (xexpr->op != IS_XMLFOREST) + { + if (needcomma) + appendStringInfoString(buf, ", "); + appendStringInfoString(buf, "XMLATTRIBUTES("); + needcomma = false; + } + forboth(arg, xexpr->named_args, narg, xexpr->arg_names) + { + Node *e = (Node *) lfirst(arg); + char *argname = strVal(lfirst(narg)); + + if (needcomma) + appendStringInfoString(buf, ", "); + get_rule_expr((Node *) e, context, true); + appendStringInfo(buf, " AS %s", + quote_identifier(map_xml_name_to_sql_identifier(argname))); + needcomma = true; + } + if (xexpr->op != IS_XMLFOREST) + appendStringInfoChar(buf, ')'); + } + if (xexpr->args) + { + if (needcomma) + appendStringInfoString(buf, ", "); + switch (xexpr->op) + { + case IS_XMLCONCAT: + case IS_XMLELEMENT: + case IS_XMLFOREST: + case IS_XMLPI: + case IS_XMLSERIALIZE: + /* no extra decoration needed */ + get_rule_expr((Node *) xexpr->args, context, true); + break; + case IS_XMLPARSE: + Assert(list_length(xexpr->args) == 2); + + get_rule_expr((Node *) linitial(xexpr->args), + context, true); + + con = lsecond_node(Const, xexpr->args); + Assert(!con->constisnull); + if (DatumGetBool(con->constvalue)) + appendStringInfoString(buf, + " PRESERVE WHITESPACE"); + else + appendStringInfoString(buf, + " STRIP WHITESPACE"); + break; + case IS_XMLROOT: + Assert(list_length(xexpr->args) == 3); + + get_rule_expr((Node *) linitial(xexpr->args), + context, true); + + appendStringInfoString(buf, ", VERSION "); + con = (Const *) lsecond(xexpr->args); + if (IsA(con, Const) && + con->constisnull) + appendStringInfoString(buf, "NO VALUE"); + else + get_rule_expr((Node *) con, context, false); + + con = lthird_node(Const, xexpr->args); + if (con->constisnull) + /* suppress STANDALONE NO VALUE */ ; + else + { + switch (DatumGetInt32(con->constvalue)) + { + case XML_STANDALONE_YES: + appendStringInfoString(buf, + ", STANDALONE YES"); + break; + case XML_STANDALONE_NO: + appendStringInfoString(buf, + ", STANDALONE NO"); + break; + case XML_STANDALONE_NO_VALUE: + appendStringInfoString(buf, + ", STANDALONE NO VALUE"); + break; + default: + break; + } + } + break; + case IS_DOCUMENT: + get_rule_expr_paren((Node *) xexpr->args, context, false, node); + break; + } + } + if (xexpr->op == IS_XMLSERIALIZE) + appendStringInfo(buf, " AS %s", + format_type_with_typemod(xexpr->type, + xexpr->typmod)); + if (xexpr->op == IS_DOCUMENT) + appendStringInfoString(buf, " IS DOCUMENT"); + else + appendStringInfoChar(buf, ')'); + } + break; + + case T_NullTest: + { + NullTest *ntest = (NullTest *) node; + + if (!PRETTY_PAREN(context)) + appendStringInfoChar(buf, '('); + get_rule_expr_paren((Node *) ntest->arg, context, true, node); + + /* + * For scalar inputs, we prefer to print as IS [NOT] NULL, + * which is shorter and traditional. If it's a rowtype input + * but we're applying a scalar test, must print IS [NOT] + * DISTINCT FROM NULL to be semantically correct. + */ + if (ntest->argisrow || + !type_is_rowtype(exprType((Node *) ntest->arg))) + { + switch (ntest->nulltesttype) + { + case IS_NULL: + appendStringInfoString(buf, " IS NULL"); + break; + case IS_NOT_NULL: + appendStringInfoString(buf, " IS NOT NULL"); + break; + default: + elog(ERROR, "unrecognized nulltesttype: %d", + (int) ntest->nulltesttype); + } + } + else + { + switch (ntest->nulltesttype) + { + case IS_NULL: + appendStringInfoString(buf, " IS NOT DISTINCT FROM NULL"); + break; + case IS_NOT_NULL: + appendStringInfoString(buf, " IS DISTINCT FROM NULL"); + break; + default: + elog(ERROR, "unrecognized nulltesttype: %d", + (int) ntest->nulltesttype); + } + } + if (!PRETTY_PAREN(context)) + appendStringInfoChar(buf, ')'); + } + break; + + case T_BooleanTest: + { + BooleanTest *btest = (BooleanTest *) node; + + if (!PRETTY_PAREN(context)) + appendStringInfoChar(buf, '('); + get_rule_expr_paren((Node *) btest->arg, context, false, node); + switch (btest->booltesttype) + { + case IS_TRUE: + appendStringInfoString(buf, " IS TRUE"); + break; + case IS_NOT_TRUE: + appendStringInfoString(buf, " IS NOT TRUE"); + break; + case IS_FALSE: + appendStringInfoString(buf, " IS FALSE"); + break; + case IS_NOT_FALSE: + appendStringInfoString(buf, " IS NOT FALSE"); + break; + case IS_UNKNOWN: + appendStringInfoString(buf, " IS UNKNOWN"); + break; + case IS_NOT_UNKNOWN: + appendStringInfoString(buf, " IS NOT UNKNOWN"); + break; + default: + elog(ERROR, "unrecognized booltesttype: %d", + (int) btest->booltesttype); + } + if (!PRETTY_PAREN(context)) + appendStringInfoChar(buf, ')'); + } + break; + + case T_CoerceToDomain: + { + CoerceToDomain *ctest = (CoerceToDomain *) node; + Node *arg = (Node *) ctest->arg; + + if (ctest->coercionformat == COERCE_IMPLICIT_CAST && + !showimplicit) + { + /* don't show the implicit cast */ + get_rule_expr(arg, context, false); + } + else + { + get_coercion_expr(arg, context, + ctest->resulttype, + ctest->resulttypmod, + node); + } + } + break; + + case T_CoerceToDomainValue: + appendStringInfoString(buf, "VALUE"); + break; + + case T_SetToDefault: + appendStringInfoString(buf, "DEFAULT"); + break; + + case T_CurrentOfExpr: + { + CurrentOfExpr *cexpr = (CurrentOfExpr *) node; + + if (cexpr->cursor_name) + appendStringInfo(buf, "CURRENT OF %s", + quote_identifier(cexpr->cursor_name)); + else + appendStringInfo(buf, "CURRENT OF $%d", + cexpr->cursor_param); + } + break; + + case T_NextValueExpr: + { + NextValueExpr *nvexpr = (NextValueExpr *) node; + + /* + * This isn't exactly nextval(), but that seems close enough + * for EXPLAIN's purposes. + */ + appendStringInfoString(buf, "nextval("); + simple_quote_literal(buf, + generate_relation_name(nvexpr->seqid, + NIL)); + appendStringInfoChar(buf, ')'); + } + break; + + case T_InferenceElem: + { + InferenceElem *iexpr = (InferenceElem *) node; + bool save_varprefix; + bool need_parens; + + /* + * InferenceElem can only refer to target relation, so a + * prefix is not useful, and indeed would cause parse errors. + */ + save_varprefix = context->varprefix; + context->varprefix = false; + + /* + * Parenthesize the element unless it's a simple Var or a bare + * function call. Follows pg_get_indexdef_worker(). + */ + need_parens = !IsA(iexpr->expr, Var); + if (IsA(iexpr->expr, FuncExpr) && + ((FuncExpr *) iexpr->expr)->funcformat == + COERCE_EXPLICIT_CALL) + need_parens = false; + + if (need_parens) + appendStringInfoChar(buf, '('); + get_rule_expr((Node *) iexpr->expr, + context, false); + if (need_parens) + appendStringInfoChar(buf, ')'); + + context->varprefix = save_varprefix; + + if (iexpr->infercollid) + appendStringInfo(buf, " COLLATE %s", + generate_collation_name(iexpr->infercollid)); + + /* Add the operator class name, if not default */ + if (iexpr->inferopclass) + { + Oid inferopclass = iexpr->inferopclass; + Oid inferopcinputtype = get_opclass_input_type(iexpr->inferopclass); + + get_opclass_name(inferopclass, inferopcinputtype, buf); + } + } + break; + + case T_PartitionBoundSpec: + { + PartitionBoundSpec *spec = (PartitionBoundSpec *) node; + ListCell *cell; + char *sep; + + if (spec->is_default) + { + appendStringInfoString(buf, "DEFAULT"); + break; + } + + switch (spec->strategy) + { + case PARTITION_STRATEGY_HASH: + Assert(spec->modulus > 0 && spec->remainder >= 0); + Assert(spec->modulus > spec->remainder); + + appendStringInfoString(buf, "FOR VALUES"); + appendStringInfo(buf, " WITH (modulus %d, remainder %d)", + spec->modulus, spec->remainder); + break; + + case PARTITION_STRATEGY_LIST: + Assert(spec->listdatums != NIL); + + appendStringInfoString(buf, "FOR VALUES IN ("); + sep = ""; + foreach(cell, spec->listdatums) + { + Const *val = lfirst_node(Const, cell); + + appendStringInfoString(buf, sep); + get_const_expr(val, context, -1); + sep = ", "; + } + + appendStringInfoChar(buf, ')'); + break; + + case PARTITION_STRATEGY_RANGE: + Assert(spec->lowerdatums != NIL && + spec->upperdatums != NIL && + list_length(spec->lowerdatums) == + list_length(spec->upperdatums)); + + appendStringInfo(buf, "FOR VALUES FROM %s TO %s", + get_range_partbound_string(spec->lowerdatums), + get_range_partbound_string(spec->upperdatums)); + break; + + default: + elog(ERROR, "unrecognized partition strategy: %d", + (int) spec->strategy); + break; + } + } + break; + + case T_JsonValueExpr: + { + JsonValueExpr *jve = (JsonValueExpr *) node; + + get_rule_expr((Node *) jve->raw_expr, context, false); + get_json_format(jve->format, context->buf); + } + break; + + case T_JsonConstructorExpr: + get_json_constructor((JsonConstructorExpr *) node, context, false); + break; + + case T_JsonIsPredicate: + { + JsonIsPredicate *pred = (JsonIsPredicate *) node; + + if (!PRETTY_PAREN(context)) + appendStringInfoChar(context->buf, '('); + + get_rule_expr_paren(pred->expr, context, true, node); + + appendStringInfoString(context->buf, " IS JSON"); + + /* TODO: handle FORMAT clause */ + + switch (pred->item_type) + { + case JS_TYPE_SCALAR: + appendStringInfoString(context->buf, " SCALAR"); + break; + case JS_TYPE_ARRAY: + appendStringInfoString(context->buf, " ARRAY"); + break; + case JS_TYPE_OBJECT: + appendStringInfoString(context->buf, " OBJECT"); + break; + default: + break; + } + + if (pred->unique_keys) + appendStringInfoString(context->buf, " WITH UNIQUE KEYS"); + + if (!PRETTY_PAREN(context)) + appendStringInfoChar(context->buf, ')'); + } + break; + + case T_List: + { + char *sep; + ListCell *l; + + sep = ""; + foreach(l, (List *) node) + { + appendStringInfoString(buf, sep); + get_rule_expr((Node *) lfirst(l), context, showimplicit); + sep = ", "; + } + } + break; + + case T_TableFunc: + get_tablefunc((TableFunc *) node, context, showimplicit); + break; + + default: + elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node)); + break; + } +} + +/* + * get_rule_expr_toplevel - Parse back a toplevel expression + * + * Same as get_rule_expr(), except that if the expr is just a Var, we pass + * istoplevel = true not false to get_variable(). This causes whole-row Vars + * to get printed with decoration that will prevent expansion of "*". + * We need to use this in contexts such as ROW() and VALUES(), where the + * parser would expand "foo.*" appearing at top level. (In principle we'd + * use this in get_target_list() too, but that has additional worries about + * whether to print AS, so it needs to invoke get_variable() directly anyway.) + */ +static void +get_rule_expr_toplevel(Node *node, deparse_context *context, + bool showimplicit) +{ + if (node && IsA(node, Var)) + (void) get_variable((Var *) node, 0, true, context); + else + get_rule_expr(node, context, showimplicit); +} + +/* + * get_rule_list_toplevel - Parse back a list of toplevel expressions + * + * Apply get_rule_expr_toplevel() to each element of a List. + * + * This adds commas between the expressions, but caller is responsible + * for printing surrounding decoration. + */ +static void +get_rule_list_toplevel(List *lst, deparse_context *context, + bool showimplicit) +{ + const char *sep; + ListCell *lc; + + sep = ""; + foreach(lc, lst) + { + Node *e = (Node *) lfirst(lc); + + appendStringInfoString(context->buf, sep); + get_rule_expr_toplevel(e, context, showimplicit); + sep = ", "; + } +} + +/* + * get_rule_expr_funccall - Parse back a function-call expression + * + * Same as get_rule_expr(), except that we guarantee that the output will + * look like a function call, or like one of the things the grammar treats as + * equivalent to a function call (see the func_expr_windowless production). + * This is needed in places where the grammar uses func_expr_windowless and + * you can't substitute a parenthesized a_expr. If what we have isn't going + * to look like a function call, wrap it in a dummy CAST() expression, which + * will satisfy the grammar --- and, indeed, is likely what the user wrote to + * produce such a thing. + */ +static void +get_rule_expr_funccall(Node *node, deparse_context *context, + bool showimplicit) +{ + if (looks_like_function(node)) + get_rule_expr(node, context, showimplicit); + else + { + StringInfo buf = context->buf; + + appendStringInfoString(buf, "CAST("); + /* no point in showing any top-level implicit cast */ + get_rule_expr(node, context, false); + appendStringInfo(buf, " AS %s)", + format_type_with_typemod(exprType(node), + exprTypmod(node))); + } +} + +/* + * Helper function to identify node types that satisfy func_expr_windowless. + * If in doubt, "false" is always a safe answer. + */ +static bool +looks_like_function(Node *node) +{ + if (node == NULL) + return false; /* probably shouldn't happen */ + switch (nodeTag(node)) + { + case T_FuncExpr: + /* OK, unless it's going to deparse as a cast */ + return (((FuncExpr *) node)->funcformat == COERCE_EXPLICIT_CALL || + ((FuncExpr *) node)->funcformat == COERCE_SQL_SYNTAX); + case T_NullIfExpr: + case T_CoalesceExpr: + case T_MinMaxExpr: + case T_SQLValueFunction: + case T_XmlExpr: + /* these are all accepted by func_expr_common_subexpr */ + return true; + default: + break; + } + return false; +} + + +/* + * get_oper_expr - Parse back an OpExpr node + */ +static void +get_oper_expr(OpExpr *expr, deparse_context *context) +{ + StringInfo buf = context->buf; + Oid opno = expr->opno; + List *args = expr->args; + + if (!PRETTY_PAREN(context)) + appendStringInfoChar(buf, '('); + if (list_length(args) == 2) + { + /* binary operator */ + Node *arg1 = (Node *) linitial(args); + Node *arg2 = (Node *) lsecond(args); + + get_rule_expr_paren(arg1, context, true, (Node *) expr); + appendStringInfo(buf, " %s ", + generate_operator_name(opno, + exprType(arg1), + exprType(arg2))); + get_rule_expr_paren(arg2, context, true, (Node *) expr); + } + else + { + /* prefix operator */ + Node *arg = (Node *) linitial(args); + + appendStringInfo(buf, "%s ", + generate_operator_name(opno, + InvalidOid, + exprType(arg))); + get_rule_expr_paren(arg, context, true, (Node *) expr); + } + if (!PRETTY_PAREN(context)) + appendStringInfoChar(buf, ')'); +} + +/* + * get_func_expr - Parse back a FuncExpr node + */ +static void +get_func_expr(FuncExpr *expr, deparse_context *context, + bool showimplicit) +{ + StringInfo buf = context->buf; + Oid funcoid = expr->funcid; + Oid argtypes[FUNC_MAX_ARGS]; + int nargs; + List *argnames; + bool use_variadic; + ListCell *l; + + /* + * If the function call came from an implicit coercion, then just show the + * first argument --- unless caller wants to see implicit coercions. + */ + if (expr->funcformat == COERCE_IMPLICIT_CAST && !showimplicit) + { + get_rule_expr_paren((Node *) linitial(expr->args), context, + false, (Node *) expr); + return; + } + + /* + * If the function call came from a cast, then show the first argument + * plus an explicit cast operation. + */ + if (expr->funcformat == COERCE_EXPLICIT_CAST || + expr->funcformat == COERCE_IMPLICIT_CAST) + { + Node *arg = linitial(expr->args); + Oid rettype = expr->funcresulttype; + int32 coercedTypmod; + + /* Get the typmod if this is a length-coercion function */ + (void) exprIsLengthCoercion((Node *) expr, &coercedTypmod); + + get_coercion_expr(arg, context, + rettype, coercedTypmod, + (Node *) expr); + + return; + } + + /* + * If the function was called using one of the SQL spec's random special + * syntaxes, try to reproduce that. If we don't recognize the function, + * fall through. + */ + if (expr->funcformat == COERCE_SQL_SYNTAX) + { + if (get_func_sql_syntax(expr, context)) + return; + } + + /* + * Normal function: display as proname(args). First we need to extract + * the argument datatypes. + */ + if (list_length(expr->args) > FUNC_MAX_ARGS) + ereport(ERROR, + (errcode(ERRCODE_TOO_MANY_ARGUMENTS), + errmsg("too many arguments"))); + nargs = 0; + argnames = NIL; + foreach(l, expr->args) + { + Node *arg = (Node *) lfirst(l); + + if (IsA(arg, NamedArgExpr)) + argnames = lappend(argnames, ((NamedArgExpr *) arg)->name); + argtypes[nargs] = exprType(arg); + nargs++; + } + + appendStringInfo(buf, "%s(", + generate_function_name(funcoid, nargs, + argnames, argtypes, + expr->funcvariadic, + &use_variadic, + context->special_exprkind)); + nargs = 0; + foreach(l, expr->args) + { + if (nargs++ > 0) + appendStringInfoString(buf, ", "); + if (use_variadic && lnext(expr->args, l) == NULL) + appendStringInfoString(buf, "VARIADIC "); + get_rule_expr((Node *) lfirst(l), context, true); + } + appendStringInfoChar(buf, ')'); +} + +/* + * get_agg_expr - Parse back an Aggref node + */ +static void +get_agg_expr(Aggref *aggref, deparse_context *context, + Aggref *original_aggref) +{ + get_agg_expr_helper(aggref, context, original_aggref, NULL, NULL, + false); +} + +/* + * get_agg_expr_helper - subroutine for get_agg_expr and + * get_json_agg_constructor + */ +static void +get_agg_expr_helper(Aggref *aggref, deparse_context *context, + Aggref *original_aggref, const char *funcname, + const char *options, bool is_json_objectagg) +{ + StringInfo buf = context->buf; + Oid argtypes[FUNC_MAX_ARGS]; + int nargs; + bool use_variadic = false; + + /* + * For a combining aggregate, we look up and deparse the corresponding + * partial aggregate instead. This is necessary because our input + * argument list has been replaced; the new argument list always has just + * one element, which will point to a partial Aggref that supplies us with + * transition states to combine. + */ + if (DO_AGGSPLIT_COMBINE(aggref->aggsplit)) + { + TargetEntry *tle; + + Assert(list_length(aggref->args) == 1); + tle = linitial_node(TargetEntry, aggref->args); + resolve_special_varno((Node *) tle->expr, context, + get_agg_combine_expr, original_aggref); + return; + } + + /* + * Mark as PARTIAL, if appropriate. We look to the original aggref so as + * to avoid printing this when recursing from the code just above. + */ + if (DO_AGGSPLIT_SKIPFINAL(original_aggref->aggsplit)) + appendStringInfoString(buf, "PARTIAL "); + + /* Extract the argument types as seen by the parser */ + nargs = get_aggregate_argtypes(aggref, argtypes); + + if (!funcname) + funcname = generate_function_name(aggref->aggfnoid, nargs, NIL, + argtypes, aggref->aggvariadic, + &use_variadic, + context->special_exprkind); + + /* Print the aggregate name, schema-qualified if needed */ + appendStringInfo(buf, "%s(%s", funcname, + (aggref->aggdistinct != NIL) ? "DISTINCT " : ""); + + if (AGGKIND_IS_ORDERED_SET(aggref->aggkind)) + { + /* + * Ordered-set aggregates do not use "*" syntax. Also, we needn't + * worry about inserting VARIADIC. So we can just dump the direct + * args as-is. + */ + Assert(!aggref->aggvariadic); + get_rule_expr((Node *) aggref->aggdirectargs, context, true); + Assert(aggref->aggorder != NIL); + appendStringInfoString(buf, ") WITHIN GROUP (ORDER BY "); + get_rule_orderby(aggref->aggorder, aggref->args, false, context); + } + else + { + /* aggstar can be set only in zero-argument aggregates */ + if (aggref->aggstar) + appendStringInfoChar(buf, '*'); + else + { + ListCell *l; + int i; + + i = 0; + foreach(l, aggref->args) + { + TargetEntry *tle = (TargetEntry *) lfirst(l); + Node *arg = (Node *) tle->expr; + + Assert(!IsA(arg, NamedArgExpr)); + if (tle->resjunk) + continue; + if (i++ > 0) + { + if (is_json_objectagg) + { + /* + * the ABSENT ON NULL and WITH UNIQUE args are printed + * separately, so ignore them here + */ + if (i > 2) + break; + + appendStringInfoString(buf, " : "); + } + else + appendStringInfoString(buf, ", "); + } + if (use_variadic && i == nargs) + appendStringInfoString(buf, "VARIADIC "); + get_rule_expr(arg, context, true); + } + } + + if (aggref->aggorder != NIL) + { + appendStringInfoString(buf, " ORDER BY "); + get_rule_orderby(aggref->aggorder, aggref->args, false, context); + } + } + + if (options) + appendStringInfoString(buf, options); + + if (aggref->aggfilter != NULL) + { + appendStringInfoString(buf, ") FILTER (WHERE "); + get_rule_expr((Node *) aggref->aggfilter, context, false); + } + + appendStringInfoChar(buf, ')'); +} + +/* + * This is a helper function for get_agg_expr(). It's used when we deparse + * a combining Aggref; resolve_special_varno locates the corresponding partial + * Aggref and then calls this. + */ +static void +get_agg_combine_expr(Node *node, deparse_context *context, void *callback_arg) +{ + Aggref *aggref; + Aggref *original_aggref = callback_arg; + + if (!IsA(node, Aggref)) + elog(ERROR, "combining Aggref does not point to an Aggref"); + + aggref = (Aggref *) node; + get_agg_expr(aggref, context, original_aggref); +} + +/* + * get_windowfunc_expr - Parse back a WindowFunc node + */ +static void +get_windowfunc_expr(WindowFunc *wfunc, deparse_context *context) +{ + get_windowfunc_expr_helper(wfunc, context, NULL, NULL, false); +} + + +/* + * get_windowfunc_expr_helper - subroutine for get_windowfunc_expr and + * get_json_agg_constructor + */ +static void +get_windowfunc_expr_helper(WindowFunc *wfunc, deparse_context *context, + const char *funcname, const char *options, + bool is_json_objectagg) +{ + StringInfo buf = context->buf; + Oid argtypes[FUNC_MAX_ARGS]; + int nargs; + List *argnames; + ListCell *l; + + if (list_length(wfunc->args) > FUNC_MAX_ARGS) + ereport(ERROR, + (errcode(ERRCODE_TOO_MANY_ARGUMENTS), + errmsg("too many arguments"))); + nargs = 0; + argnames = NIL; + foreach(l, wfunc->args) + { + Node *arg = (Node *) lfirst(l); + + if (IsA(arg, NamedArgExpr)) + argnames = lappend(argnames, ((NamedArgExpr *) arg)->name); + argtypes[nargs] = exprType(arg); + nargs++; + } + + if (!funcname) + funcname = generate_function_name(wfunc->winfnoid, nargs, argnames, + argtypes, false, NULL, + context->special_exprkind); + + appendStringInfo(buf, "%s(", funcname); + + /* winstar can be set only in zero-argument aggregates */ + if (wfunc->winstar) + appendStringInfoChar(buf, '*'); + else + { + if (is_json_objectagg) + { + get_rule_expr((Node *) linitial(wfunc->args), context, false); + appendStringInfoString(buf, " : "); + get_rule_expr((Node *) lsecond(wfunc->args), context, false); + } + else + get_rule_expr((Node *) wfunc->args, context, true); + } + + if (options) + appendStringInfoString(buf, options); + + if (wfunc->aggfilter != NULL) + { + appendStringInfoString(buf, ") FILTER (WHERE "); + get_rule_expr((Node *) wfunc->aggfilter, context, false); + } + + appendStringInfoString(buf, ") OVER "); + + foreach(l, context->windowClause) + { + WindowClause *wc = (WindowClause *) lfirst(l); + + if (wc->winref == wfunc->winref) + { + if (wc->name) + appendStringInfoString(buf, quote_identifier(wc->name)); + else + get_rule_windowspec(wc, context->windowTList, context); + break; + } + } + if (l == NULL) + { + if (context->windowClause) + elog(ERROR, "could not find window clause for winref %u", + wfunc->winref); + + /* + * In EXPLAIN, we don't have window context information available, so + * we have to settle for this: + */ + appendStringInfoString(buf, "(?)"); + } +} + +/* + * get_func_sql_syntax - Parse back a SQL-syntax function call + * + * Returns true if we successfully deparsed, false if we did not + * recognize the function. + */ +static bool +get_func_sql_syntax(FuncExpr *expr, deparse_context *context) +{ + StringInfo buf = context->buf; + Oid funcoid = expr->funcid; + + switch (funcoid) + { + case F_TIMEZONE_INTERVAL_TIMESTAMP: + case F_TIMEZONE_INTERVAL_TIMESTAMPTZ: + case F_TIMEZONE_INTERVAL_TIMETZ: + case F_TIMEZONE_TEXT_TIMESTAMP: + case F_TIMEZONE_TEXT_TIMESTAMPTZ: + case F_TIMEZONE_TEXT_TIMETZ: + /* AT TIME ZONE ... note reversed argument order */ + appendStringInfoChar(buf, '('); + get_rule_expr_paren((Node *) lsecond(expr->args), context, false, + (Node *) expr); + appendStringInfoString(buf, " AT TIME ZONE "); + get_rule_expr_paren((Node *) linitial(expr->args), context, false, + (Node *) expr); + appendStringInfoChar(buf, ')'); + return true; + + case F_OVERLAPS_TIMESTAMPTZ_INTERVAL_TIMESTAMPTZ_INTERVAL: + case F_OVERLAPS_TIMESTAMPTZ_INTERVAL_TIMESTAMPTZ_TIMESTAMPTZ: + case F_OVERLAPS_TIMESTAMPTZ_TIMESTAMPTZ_TIMESTAMPTZ_INTERVAL: + case F_OVERLAPS_TIMESTAMPTZ_TIMESTAMPTZ_TIMESTAMPTZ_TIMESTAMPTZ: + case F_OVERLAPS_TIMESTAMP_INTERVAL_TIMESTAMP_INTERVAL: + case F_OVERLAPS_TIMESTAMP_INTERVAL_TIMESTAMP_TIMESTAMP: + case F_OVERLAPS_TIMESTAMP_TIMESTAMP_TIMESTAMP_INTERVAL: + case F_OVERLAPS_TIMESTAMP_TIMESTAMP_TIMESTAMP_TIMESTAMP: + case F_OVERLAPS_TIMETZ_TIMETZ_TIMETZ_TIMETZ: + case F_OVERLAPS_TIME_INTERVAL_TIME_INTERVAL: + case F_OVERLAPS_TIME_INTERVAL_TIME_TIME: + case F_OVERLAPS_TIME_TIME_TIME_INTERVAL: + case F_OVERLAPS_TIME_TIME_TIME_TIME: + /* (x1, x2) OVERLAPS (y1, y2) */ + appendStringInfoString(buf, "(("); + get_rule_expr((Node *) linitial(expr->args), context, false); + appendStringInfoString(buf, ", "); + get_rule_expr((Node *) lsecond(expr->args), context, false); + appendStringInfoString(buf, ") OVERLAPS ("); + get_rule_expr((Node *) lthird(expr->args), context, false); + appendStringInfoString(buf, ", "); + get_rule_expr((Node *) lfourth(expr->args), context, false); + appendStringInfoString(buf, "))"); + return true; + + case F_EXTRACT_TEXT_DATE: + case F_EXTRACT_TEXT_TIME: + case F_EXTRACT_TEXT_TIMETZ: + case F_EXTRACT_TEXT_TIMESTAMP: + case F_EXTRACT_TEXT_TIMESTAMPTZ: + case F_EXTRACT_TEXT_INTERVAL: + /* EXTRACT (x FROM y) */ + appendStringInfoString(buf, "EXTRACT("); + { + Const *con = (Const *) linitial(expr->args); + + Assert(IsA(con, Const) && + con->consttype == TEXTOID && + !con->constisnull); + appendStringInfoString(buf, TextDatumGetCString(con->constvalue)); + } + appendStringInfoString(buf, " FROM "); + get_rule_expr((Node *) lsecond(expr->args), context, false); + appendStringInfoChar(buf, ')'); + return true; + + case F_IS_NORMALIZED: + /* IS xxx NORMALIZED */ + appendStringInfoString(buf, "("); + get_rule_expr_paren((Node *) linitial(expr->args), context, false, + (Node *) expr); + appendStringInfoString(buf, " IS"); + if (list_length(expr->args) == 2) + { + Const *con = (Const *) lsecond(expr->args); + + Assert(IsA(con, Const) && + con->consttype == TEXTOID && + !con->constisnull); + appendStringInfo(buf, " %s", + TextDatumGetCString(con->constvalue)); + } + appendStringInfoString(buf, " NORMALIZED)"); + return true; + + case F_PG_COLLATION_FOR: + /* COLLATION FOR */ + appendStringInfoString(buf, "COLLATION FOR ("); + get_rule_expr((Node *) linitial(expr->args), context, false); + appendStringInfoChar(buf, ')'); + return true; + + case F_NORMALIZE: + /* NORMALIZE() */ + appendStringInfoString(buf, "NORMALIZE("); + get_rule_expr((Node *) linitial(expr->args), context, false); + if (list_length(expr->args) == 2) + { + Const *con = (Const *) lsecond(expr->args); + + Assert(IsA(con, Const) && + con->consttype == TEXTOID && + !con->constisnull); + appendStringInfo(buf, ", %s", + TextDatumGetCString(con->constvalue)); + } + appendStringInfoChar(buf, ')'); + return true; + + case F_OVERLAY_BIT_BIT_INT4: + case F_OVERLAY_BIT_BIT_INT4_INT4: + case F_OVERLAY_BYTEA_BYTEA_INT4: + case F_OVERLAY_BYTEA_BYTEA_INT4_INT4: + case F_OVERLAY_TEXT_TEXT_INT4: + case F_OVERLAY_TEXT_TEXT_INT4_INT4: + /* OVERLAY() */ + appendStringInfoString(buf, "OVERLAY("); + get_rule_expr((Node *) linitial(expr->args), context, false); + appendStringInfoString(buf, " PLACING "); + get_rule_expr((Node *) lsecond(expr->args), context, false); + appendStringInfoString(buf, " FROM "); + get_rule_expr((Node *) lthird(expr->args), context, false); + if (list_length(expr->args) == 4) + { + appendStringInfoString(buf, " FOR "); + get_rule_expr((Node *) lfourth(expr->args), context, false); + } + appendStringInfoChar(buf, ')'); + return true; + + case F_POSITION_BIT_BIT: + case F_POSITION_BYTEA_BYTEA: + case F_POSITION_TEXT_TEXT: + /* POSITION() ... extra parens since args are b_expr not a_expr */ + appendStringInfoString(buf, "POSITION(("); + get_rule_expr((Node *) lsecond(expr->args), context, false); + appendStringInfoString(buf, ") IN ("); + get_rule_expr((Node *) linitial(expr->args), context, false); + appendStringInfoString(buf, "))"); + return true; + + case F_SUBSTRING_BIT_INT4: + case F_SUBSTRING_BIT_INT4_INT4: + case F_SUBSTRING_BYTEA_INT4: + case F_SUBSTRING_BYTEA_INT4_INT4: + case F_SUBSTRING_TEXT_INT4: + case F_SUBSTRING_TEXT_INT4_INT4: + /* SUBSTRING FROM/FOR (i.e., integer-position variants) */ + appendStringInfoString(buf, "SUBSTRING("); + get_rule_expr((Node *) linitial(expr->args), context, false); + appendStringInfoString(buf, " FROM "); + get_rule_expr((Node *) lsecond(expr->args), context, false); + if (list_length(expr->args) == 3) + { + appendStringInfoString(buf, " FOR "); + get_rule_expr((Node *) lthird(expr->args), context, false); + } + appendStringInfoChar(buf, ')'); + return true; + + case F_SUBSTRING_TEXT_TEXT_TEXT: + /* SUBSTRING SIMILAR/ESCAPE */ + appendStringInfoString(buf, "SUBSTRING("); + get_rule_expr((Node *) linitial(expr->args), context, false); + appendStringInfoString(buf, " SIMILAR "); + get_rule_expr((Node *) lsecond(expr->args), context, false); + appendStringInfoString(buf, " ESCAPE "); + get_rule_expr((Node *) lthird(expr->args), context, false); + appendStringInfoChar(buf, ')'); + return true; + + case F_BTRIM_BYTEA_BYTEA: + case F_BTRIM_TEXT: + case F_BTRIM_TEXT_TEXT: + /* TRIM() */ + appendStringInfoString(buf, "TRIM(BOTH"); + if (list_length(expr->args) == 2) + { + appendStringInfoChar(buf, ' '); + get_rule_expr((Node *) lsecond(expr->args), context, false); + } + appendStringInfoString(buf, " FROM "); + get_rule_expr((Node *) linitial(expr->args), context, false); + appendStringInfoChar(buf, ')'); + return true; + + case F_LTRIM_BYTEA_BYTEA: + case F_LTRIM_TEXT: + case F_LTRIM_TEXT_TEXT: + /* TRIM() */ + appendStringInfoString(buf, "TRIM(LEADING"); + if (list_length(expr->args) == 2) + { + appendStringInfoChar(buf, ' '); + get_rule_expr((Node *) lsecond(expr->args), context, false); + } + appendStringInfoString(buf, " FROM "); + get_rule_expr((Node *) linitial(expr->args), context, false); + appendStringInfoChar(buf, ')'); + return true; + + case F_RTRIM_BYTEA_BYTEA: + case F_RTRIM_TEXT: + case F_RTRIM_TEXT_TEXT: + /* TRIM() */ + appendStringInfoString(buf, "TRIM(TRAILING"); + if (list_length(expr->args) == 2) + { + appendStringInfoChar(buf, ' '); + get_rule_expr((Node *) lsecond(expr->args), context, false); + } + appendStringInfoString(buf, " FROM "); + get_rule_expr((Node *) linitial(expr->args), context, false); + appendStringInfoChar(buf, ')'); + return true; + + case F_SYSTEM_USER: + appendStringInfoString(buf, "SYSTEM_USER"); + return true; + + case F_XMLEXISTS: + /* XMLEXISTS ... extra parens because args are c_expr */ + appendStringInfoString(buf, "XMLEXISTS(("); + get_rule_expr((Node *) linitial(expr->args), context, false); + appendStringInfoString(buf, ") PASSING ("); + get_rule_expr((Node *) lsecond(expr->args), context, false); + appendStringInfoString(buf, "))"); + return true; + } + return false; +} + +/* ---------- + * get_coercion_expr + * + * Make a string representation of a value coerced to a specific type + * ---------- + */ +static void +get_coercion_expr(Node *arg, deparse_context *context, + Oid resulttype, int32 resulttypmod, + Node *parentNode) +{ + StringInfo buf = context->buf; + + /* + * Since parse_coerce.c doesn't immediately collapse application of + * length-coercion functions to constants, what we'll typically see in + * such cases is a Const with typmod -1 and a length-coercion function + * right above it. Avoid generating redundant output. However, beware of + * suppressing casts when the user actually wrote something like + * 'foo'::text::char(3). + * + * Note: it might seem that we are missing the possibility of needing to + * print a COLLATE clause for such a Const. However, a Const could only + * have nondefault collation in a post-constant-folding tree, in which the + * length coercion would have been folded too. See also the special + * handling of CollateExpr in coerce_to_target_type(): any collation + * marking will be above the coercion node, not below it. + */ + if (arg && IsA(arg, Const) && + ((Const *) arg)->consttype == resulttype && + ((Const *) arg)->consttypmod == -1) + { + /* Show the constant without normal ::typename decoration */ + get_const_expr((Const *) arg, context, -1); + } + else + { + if (!PRETTY_PAREN(context)) + appendStringInfoChar(buf, '('); + get_rule_expr_paren(arg, context, false, parentNode); + if (!PRETTY_PAREN(context)) + appendStringInfoChar(buf, ')'); + } + + /* + * Never emit resulttype(arg) functional notation. A pg_proc entry could + * take precedence, and a resulttype in pg_temp would require schema + * qualification that format_type_with_typemod() would usually omit. We've + * standardized on arg::resulttype, but CAST(arg AS resulttype) notation + * would work fine. + */ + appendStringInfo(buf, "::%s", + format_type_with_typemod(resulttype, resulttypmod)); +} + +/* ---------- + * get_const_expr + * + * Make a string representation of a Const + * + * showtype can be -1 to never show "::typename" decoration, or +1 to always + * show it, or 0 to show it only if the constant wouldn't be assumed to be + * the right type by default. + * + * If the Const's collation isn't default for its type, show that too. + * We mustn't do this when showtype is -1 (since that means the caller will + * print "::typename", and we can't put a COLLATE clause in between). It's + * caller's responsibility that collation isn't missed in such cases. + * ---------- + */ +static void +get_const_expr(Const *constval, deparse_context *context, int showtype) +{ + StringInfo buf = context->buf; + Oid typoutput; + bool typIsVarlena; + char *extval; + bool needlabel = false; + + if (constval->constisnull) + { + /* + * Always label the type of a NULL constant to prevent misdecisions + * about type when reparsing. + */ + appendStringInfoString(buf, "NULL"); + if (showtype >= 0) + { + appendStringInfo(buf, "::%s", + format_type_with_typemod(constval->consttype, + constval->consttypmod)); + get_const_collation(constval, context); + } + return; + } + + getTypeOutputInfo(constval->consttype, + &typoutput, &typIsVarlena); + + extval = OidOutputFunctionCall(typoutput, constval->constvalue); + + switch (constval->consttype) + { + case INT4OID: + + /* + * INT4 can be printed without any decoration, unless it is + * negative; in that case print it as '-nnn'::integer to ensure + * that the output will re-parse as a constant, not as a constant + * plus operator. In most cases we could get away with printing + * (-nnn) instead, because of the way that gram.y handles negative + * literals; but that doesn't work for INT_MIN, and it doesn't + * seem that much prettier anyway. + */ + if (extval[0] != '-') + appendStringInfoString(buf, extval); + else + { + appendStringInfo(buf, "'%s'", extval); + needlabel = true; /* we must attach a cast */ + } + break; + + case NUMERICOID: + + /* + * NUMERIC can be printed without quotes if it looks like a float + * constant (not an integer, and not Infinity or NaN) and doesn't + * have a leading sign (for the same reason as for INT4). + */ + if (isdigit((unsigned char) extval[0]) && + strcspn(extval, "eE.") != strlen(extval)) + { + appendStringInfoString(buf, extval); + } + else + { + appendStringInfo(buf, "'%s'", extval); + needlabel = true; /* we must attach a cast */ + } + break; + + case BOOLOID: + if (strcmp(extval, "t") == 0) + appendStringInfoString(buf, "true"); + else + appendStringInfoString(buf, "false"); + break; + + default: + simple_quote_literal(buf, extval); + break; + } + + pfree(extval); + + if (showtype < 0) + return; + + /* + * For showtype == 0, append ::typename unless the constant will be + * implicitly typed as the right type when it is read in. + * + * XXX this code has to be kept in sync with the behavior of the parser, + * especially make_const. + */ + switch (constval->consttype) + { + case BOOLOID: + case UNKNOWNOID: + /* These types can be left unlabeled */ + needlabel = false; + break; + case INT4OID: + /* We determined above whether a label is needed */ + break; + case NUMERICOID: + + /* + * Float-looking constants will be typed as numeric, which we + * checked above; but if there's a nondefault typmod we need to + * show it. + */ + needlabel |= (constval->consttypmod >= 0); + break; + default: + needlabel = true; + break; + } + if (needlabel || showtype > 0) + appendStringInfo(buf, "::%s", + format_type_with_typemod(constval->consttype, + constval->consttypmod)); + + get_const_collation(constval, context); +} + +/* + * helper for get_const_expr: append COLLATE if needed + */ +static void +get_const_collation(Const *constval, deparse_context *context) +{ + StringInfo buf = context->buf; + + if (OidIsValid(constval->constcollid)) + { + Oid typcollation = get_typcollation(constval->consttype); + + if (constval->constcollid != typcollation) + { + appendStringInfo(buf, " COLLATE %s", + generate_collation_name(constval->constcollid)); + } + } +} + +/* + * get_json_format - Parse back a JsonFormat node + */ +static void +get_json_format(JsonFormat *format, StringInfo buf) +{ + if (format->format_type == JS_FORMAT_DEFAULT) + return; + + appendStringInfoString(buf, + format->format_type == JS_FORMAT_JSONB ? + " FORMAT JSONB" : " FORMAT JSON"); + + if (format->encoding != JS_ENC_DEFAULT) + { + const char *encoding; + + encoding = + format->encoding == JS_ENC_UTF16 ? "UTF16" : + format->encoding == JS_ENC_UTF32 ? "UTF32" : "UTF8"; + + appendStringInfo(buf, " ENCODING %s", encoding); + } +} + +/* + * get_json_returning - Parse back a JsonReturning structure + */ +static void +get_json_returning(JsonReturning *returning, StringInfo buf, + bool json_format_by_default) +{ + if (!OidIsValid(returning->typid)) + return; + + appendStringInfo(buf, " RETURNING %s", + format_type_with_typemod(returning->typid, + returning->typmod)); + + if (!json_format_by_default || + returning->format->format_type != + (returning->typid == JSONBOID ? JS_FORMAT_JSONB : JS_FORMAT_JSON)) + get_json_format(returning->format, buf); +} + +/* + * get_json_constructor - Parse back a JsonConstructorExpr node + */ +static void +get_json_constructor(JsonConstructorExpr *ctor, deparse_context *context, + bool showimplicit) +{ + StringInfo buf = context->buf; + const char *funcname; + bool is_json_object; + int curridx; + ListCell *lc; + + if (ctor->type == JSCTOR_JSON_OBJECTAGG) + { + get_json_agg_constructor(ctor, context, "JSON_OBJECTAGG", true); + return; + } + else if (ctor->type == JSCTOR_JSON_ARRAYAGG) + { + get_json_agg_constructor(ctor, context, "JSON_ARRAYAGG", false); + return; + } + + switch (ctor->type) + { + case JSCTOR_JSON_OBJECT: + funcname = "JSON_OBJECT"; + break; + case JSCTOR_JSON_ARRAY: + funcname = "JSON_ARRAY"; + break; + default: + elog(ERROR, "invalid JsonConstructorType %d", ctor->type); + } + + appendStringInfo(buf, "%s(", funcname); + + is_json_object = ctor->type == JSCTOR_JSON_OBJECT; + foreach(lc, ctor->args) + { + curridx = foreach_current_index(lc); + if (curridx > 0) + { + const char *sep; + + sep = (is_json_object && (curridx % 2) != 0) ? " : " : ", "; + appendStringInfoString(buf, sep); + } + + get_rule_expr((Node *) lfirst(lc), context, true); + } + + get_json_constructor_options(ctor, buf); + appendStringInfo(buf, ")"); +} + +/* + * Append options, if any, to the JSON constructor being deparsed + */ +static void +get_json_constructor_options(JsonConstructorExpr *ctor, StringInfo buf) +{ + if (ctor->absent_on_null) + { + if (ctor->type == JSCTOR_JSON_OBJECT || + ctor->type == JSCTOR_JSON_OBJECTAGG) + appendStringInfoString(buf, " ABSENT ON NULL"); + } + else + { + if (ctor->type == JSCTOR_JSON_ARRAY || + ctor->type == JSCTOR_JSON_ARRAYAGG) + appendStringInfoString(buf, " NULL ON NULL"); + } + + if (ctor->unique) + appendStringInfoString(buf, " WITH UNIQUE KEYS"); + + get_json_returning(ctor->returning, buf, true); +} + +/* + * get_json_agg_constructor - Parse back an aggregate JsonConstructorExpr node + */ +static void +get_json_agg_constructor(JsonConstructorExpr *ctor, deparse_context *context, + const char *funcname, bool is_json_objectagg) +{ + StringInfoData options; + + initStringInfo(&options); + get_json_constructor_options(ctor, &options); + + if (IsA(ctor->func, Aggref)) + get_agg_expr_helper((Aggref *) ctor->func, context, + (Aggref *) ctor->func, + funcname, options.data, is_json_objectagg); + else if (IsA(ctor->func, WindowFunc)) + get_windowfunc_expr_helper((WindowFunc *) ctor->func, context, + funcname, options.data, + is_json_objectagg); + else + elog(ERROR, "invalid JsonConstructorExpr underlying node type: %d", + nodeTag(ctor->func)); +} + +/* + * simple_quote_literal - Format a string as a SQL literal, append to buf + */ +static void +simple_quote_literal(StringInfo buf, const char *val) +{ + const char *valptr; + + /* + * We form the string literal according to the prevailing setting of + * standard_conforming_strings; we never use E''. User is responsible for + * making sure result is used correctly. + */ + appendStringInfoChar(buf, '\''); + for (valptr = val; *valptr; valptr++) + { + char ch = *valptr; + + if (SQL_STR_DOUBLE(ch, !standard_conforming_strings)) + appendStringInfoChar(buf, ch); + appendStringInfoChar(buf, ch); + } + appendStringInfoChar(buf, '\''); +} + + +/* ---------- + * get_sublink_expr - Parse back a sublink + * ---------- + */ +static void +get_sublink_expr(SubLink *sublink, deparse_context *context) +{ + StringInfo buf = context->buf; + Query *query = (Query *) (sublink->subselect); + char *opname = NULL; + bool need_paren; + + if (sublink->subLinkType == ARRAY_SUBLINK) + appendStringInfoString(buf, "ARRAY("); + else + appendStringInfoChar(buf, '('); + + /* + * Note that we print the name of only the first operator, when there are + * multiple combining operators. This is an approximation that could go + * wrong in various scenarios (operators in different schemas, renamed + * operators, etc) but there is not a whole lot we can do about it, since + * the syntax allows only one operator to be shown. + */ + if (sublink->testexpr) + { + if (IsA(sublink->testexpr, OpExpr)) + { + /* single combining operator */ + OpExpr *opexpr = (OpExpr *) sublink->testexpr; + + get_rule_expr(linitial(opexpr->args), context, true); + opname = generate_operator_name(opexpr->opno, + exprType(linitial(opexpr->args)), + exprType(lsecond(opexpr->args))); + } + else if (IsA(sublink->testexpr, BoolExpr)) + { + /* multiple combining operators, = or <> cases */ + char *sep; + ListCell *l; + + appendStringInfoChar(buf, '('); + sep = ""; + foreach(l, ((BoolExpr *) sublink->testexpr)->args) + { + OpExpr *opexpr = lfirst_node(OpExpr, l); + + appendStringInfoString(buf, sep); + get_rule_expr(linitial(opexpr->args), context, true); + if (!opname) + opname = generate_operator_name(opexpr->opno, + exprType(linitial(opexpr->args)), + exprType(lsecond(opexpr->args))); + sep = ", "; + } + appendStringInfoChar(buf, ')'); + } + else if (IsA(sublink->testexpr, RowCompareExpr)) + { + /* multiple combining operators, < <= > >= cases */ + RowCompareExpr *rcexpr = (RowCompareExpr *) sublink->testexpr; + + appendStringInfoChar(buf, '('); + get_rule_expr((Node *) rcexpr->largs, context, true); + opname = generate_operator_name(linitial_oid(rcexpr->opnos), + exprType(linitial(rcexpr->largs)), + exprType(linitial(rcexpr->rargs))); + appendStringInfoChar(buf, ')'); + } + else + elog(ERROR, "unrecognized testexpr type: %d", + (int) nodeTag(sublink->testexpr)); + } + + need_paren = true; + + switch (sublink->subLinkType) + { + case EXISTS_SUBLINK: + appendStringInfoString(buf, "EXISTS "); + break; + + case ANY_SUBLINK: + if (strcmp(opname, "=") == 0) /* Represent = ANY as IN */ + appendStringInfoString(buf, " IN "); + else + appendStringInfo(buf, " %s ANY ", opname); + break; + + case ALL_SUBLINK: + appendStringInfo(buf, " %s ALL ", opname); + break; + + case ROWCOMPARE_SUBLINK: + appendStringInfo(buf, " %s ", opname); + break; + + case EXPR_SUBLINK: + case MULTIEXPR_SUBLINK: + case ARRAY_SUBLINK: + need_paren = false; + break; + + case CTE_SUBLINK: /* shouldn't occur in a SubLink */ + default: + elog(ERROR, "unrecognized sublink type: %d", + (int) sublink->subLinkType); + break; + } + + if (need_paren) + appendStringInfoChar(buf, '('); + + get_query_def(query, buf, context->namespaces, NULL, false, + context->prettyFlags, context->wrapColumn, + context->indentLevel); + + if (need_paren) + appendStringInfoString(buf, "))"); + else + appendStringInfoChar(buf, ')'); +} + + +/* ---------- + * get_tablefunc - Parse back a table function + * ---------- + */ +static void +get_tablefunc(TableFunc *tf, deparse_context *context, bool showimplicit) +{ + StringInfo buf = context->buf; + + /* XMLTABLE is the only existing implementation. */ + + appendStringInfoString(buf, "XMLTABLE("); + + if (tf->ns_uris != NIL) + { + ListCell *lc1, + *lc2; + bool first = true; + + appendStringInfoString(buf, "XMLNAMESPACES ("); + forboth(lc1, tf->ns_uris, lc2, tf->ns_names) + { + Node *expr = (Node *) lfirst(lc1); + String *ns_node = lfirst_node(String, lc2); + + if (!first) + appendStringInfoString(buf, ", "); + else + first = false; + + if (ns_node != NULL) + { + get_rule_expr(expr, context, showimplicit); + appendStringInfo(buf, " AS %s", strVal(ns_node)); + } + else + { + appendStringInfoString(buf, "DEFAULT "); + get_rule_expr(expr, context, showimplicit); + } + } + appendStringInfoString(buf, "), "); + } + + appendStringInfoChar(buf, '('); + get_rule_expr((Node *) tf->rowexpr, context, showimplicit); + appendStringInfoString(buf, ") PASSING ("); + get_rule_expr((Node *) tf->docexpr, context, showimplicit); + appendStringInfoChar(buf, ')'); + + if (tf->colexprs != NIL) + { + ListCell *l1; + ListCell *l2; + ListCell *l3; + ListCell *l4; + ListCell *l5; + int colnum = 0; + + appendStringInfoString(buf, " COLUMNS "); + forfive(l1, tf->colnames, l2, tf->coltypes, l3, tf->coltypmods, + l4, tf->colexprs, l5, tf->coldefexprs) + { + char *colname = strVal(lfirst(l1)); + Oid typid = lfirst_oid(l2); + int32 typmod = lfirst_int(l3); + Node *colexpr = (Node *) lfirst(l4); + Node *coldefexpr = (Node *) lfirst(l5); + bool ordinality = (tf->ordinalitycol == colnum); + bool notnull = bms_is_member(colnum, tf->notnulls); + + if (colnum > 0) + appendStringInfoString(buf, ", "); + colnum++; + + appendStringInfo(buf, "%s %s", quote_identifier(colname), + ordinality ? "FOR ORDINALITY" : + format_type_with_typemod(typid, typmod)); + if (ordinality) + continue; + + if (coldefexpr != NULL) + { + appendStringInfoString(buf, " DEFAULT ("); + get_rule_expr((Node *) coldefexpr, context, showimplicit); + appendStringInfoChar(buf, ')'); + } + if (colexpr != NULL) + { + appendStringInfoString(buf, " PATH ("); + get_rule_expr((Node *) colexpr, context, showimplicit); + appendStringInfoChar(buf, ')'); + } + if (notnull) + appendStringInfoString(buf, " NOT NULL"); + } + } + + appendStringInfoChar(buf, ')'); +} + +/* ---------- + * get_from_clause - Parse back a FROM clause + * + * "prefix" is the keyword that denotes the start of the list of FROM + * elements. It is FROM when used to parse back SELECT and UPDATE, but + * is USING when parsing back DELETE. + * ---------- + */ +static void +get_from_clause(Query *query, const char *prefix, deparse_context *context) +{ + StringInfo buf = context->buf; + bool first = true; + ListCell *l; + + /* + * We use the query's jointree as a guide to what to print. However, we + * must ignore auto-added RTEs that are marked not inFromCl. (These can + * only appear at the top level of the jointree, so it's sufficient to + * check here.) This check also ensures we ignore the rule pseudo-RTEs + * for NEW and OLD. + */ + foreach(l, query->jointree->fromlist) + { + Node *jtnode = (Node *) lfirst(l); + + if (IsA(jtnode, RangeTblRef)) + { + int varno = ((RangeTblRef *) jtnode)->rtindex; + RangeTblEntry *rte = rt_fetch(varno, query->rtable); + + if (!rte->inFromCl) + continue; + } + + if (first) + { + appendContextKeyword(context, prefix, + -PRETTYINDENT_STD, PRETTYINDENT_STD, 2); + first = false; + + get_from_clause_item(jtnode, query, context); + } + else + { + StringInfoData itembuf; + + appendStringInfoString(buf, ", "); + + /* + * Put the new FROM item's text into itembuf so we can decide + * after we've got it whether or not it needs to go on a new line. + */ + initStringInfo(&itembuf); + context->buf = &itembuf; + + get_from_clause_item(jtnode, query, context); + + /* Restore context's output buffer */ + context->buf = buf; + + /* Consider line-wrapping if enabled */ + if (PRETTY_INDENT(context) && context->wrapColumn >= 0) + { + /* Does the new item start with a new line? */ + if (itembuf.len > 0 && itembuf.data[0] == '\n') + { + /* If so, we shouldn't add anything */ + /* instead, remove any trailing spaces currently in buf */ + removeStringInfoSpaces(buf); + } + else + { + char *trailing_nl; + + /* Locate the start of the current line in the buffer */ + trailing_nl = strrchr(buf->data, '\n'); + if (trailing_nl == NULL) + trailing_nl = buf->data; + else + trailing_nl++; + + /* + * Add a newline, plus some indentation, if the new item + * would cause an overflow. + */ + if (strlen(trailing_nl) + itembuf.len > context->wrapColumn) + appendContextKeyword(context, "", -PRETTYINDENT_STD, + PRETTYINDENT_STD, + PRETTYINDENT_VAR); + } + } + + /* Add the new item */ + appendBinaryStringInfo(buf, itembuf.data, itembuf.len); + + /* clean up */ + pfree(itembuf.data); + } + } +} + +static void +get_from_clause_item(Node *jtnode, Query *query, deparse_context *context) +{ + StringInfo buf = context->buf; + deparse_namespace *dpns = (deparse_namespace *) linitial(context->namespaces); + + if (IsA(jtnode, RangeTblRef)) + { + int varno = ((RangeTblRef *) jtnode)->rtindex; + RangeTblEntry *rte = rt_fetch(varno, query->rtable); + deparse_columns *colinfo = deparse_columns_fetch(varno, dpns); + RangeTblFunction *rtfunc1 = NULL; + + if (rte->lateral) + appendStringInfoString(buf, "LATERAL "); + + /* Print the FROM item proper */ + switch (rte->rtekind) + { + case RTE_RELATION: + /* Normal relation RTE */ + appendStringInfo(buf, "%s%s", + only_marker(rte), + generate_relation_name(rte->relid, + context->namespaces)); + break; + case RTE_SUBQUERY: + /* Subquery RTE */ + appendStringInfoChar(buf, '('); + get_query_def(rte->subquery, buf, context->namespaces, NULL, + true, + context->prettyFlags, context->wrapColumn, + context->indentLevel); + appendStringInfoChar(buf, ')'); + break; + case RTE_FUNCTION: + /* Function RTE */ + rtfunc1 = (RangeTblFunction *) linitial(rte->functions); + + /* + * Omit ROWS FROM() syntax for just one function, unless it + * has both a coldeflist and WITH ORDINALITY. If it has both, + * we must use ROWS FROM() syntax to avoid ambiguity about + * whether the coldeflist includes the ordinality column. + */ + if (list_length(rte->functions) == 1 && + (rtfunc1->funccolnames == NIL || !rte->funcordinality)) + { + get_rule_expr_funccall(rtfunc1->funcexpr, context, true); + /* we'll print the coldeflist below, if it has one */ + } + else + { + bool all_unnest; + ListCell *lc; + + /* + * If all the function calls in the list are to unnest, + * and none need a coldeflist, then collapse the list back + * down to UNNEST(args). (If we had more than one + * built-in unnest function, this would get more + * difficult.) + * + * XXX This is pretty ugly, since it makes not-terribly- + * future-proof assumptions about what the parser would do + * with the output; but the alternative is to emit our + * nonstandard ROWS FROM() notation for what might have + * been a perfectly spec-compliant multi-argument + * UNNEST(). + */ + all_unnest = true; + foreach(lc, rte->functions) + { + RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc); + + if (!IsA(rtfunc->funcexpr, FuncExpr) || + ((FuncExpr *) rtfunc->funcexpr)->funcid != F_UNNEST_ANYARRAY || + rtfunc->funccolnames != NIL) + { + all_unnest = false; + break; + } + } + + if (all_unnest) + { + List *allargs = NIL; + + foreach(lc, rte->functions) + { + RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc); + List *args = ((FuncExpr *) rtfunc->funcexpr)->args; + + allargs = list_concat(allargs, args); + } + + appendStringInfoString(buf, "UNNEST("); + get_rule_expr((Node *) allargs, context, true); + appendStringInfoChar(buf, ')'); + } + else + { + int funcno = 0; + + appendStringInfoString(buf, "ROWS FROM("); + foreach(lc, rte->functions) + { + RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc); + + if (funcno > 0) + appendStringInfoString(buf, ", "); + get_rule_expr_funccall(rtfunc->funcexpr, context, true); + if (rtfunc->funccolnames != NIL) + { + /* Reconstruct the column definition list */ + appendStringInfoString(buf, " AS "); + get_from_clause_coldeflist(rtfunc, + NULL, + context); + } + funcno++; + } + appendStringInfoChar(buf, ')'); + } + /* prevent printing duplicate coldeflist below */ + rtfunc1 = NULL; + } + if (rte->funcordinality) + appendStringInfoString(buf, " WITH ORDINALITY"); + break; + case RTE_TABLEFUNC: + get_tablefunc(rte->tablefunc, context, true); + break; + case RTE_VALUES: + /* Values list RTE */ + appendStringInfoChar(buf, '('); + get_values_def(rte->values_lists, context); + appendStringInfoChar(buf, ')'); + break; + case RTE_CTE: + appendStringInfoString(buf, quote_identifier(rte->ctename)); + break; + default: + elog(ERROR, "unrecognized RTE kind: %d", (int) rte->rtekind); + break; + } + + /* Print the relation alias, if needed */ + get_rte_alias(rte, varno, false, context); + + /* Print the column definitions or aliases, if needed */ + if (rtfunc1 && rtfunc1->funccolnames != NIL) + { + /* Reconstruct the columndef list, which is also the aliases */ + get_from_clause_coldeflist(rtfunc1, colinfo, context); + } + else + { + /* Else print column aliases as needed */ + get_column_alias_list(colinfo, context); + } + + /* Tablesample clause must go after any alias */ + if (rte->rtekind == RTE_RELATION && rte->tablesample) + get_tablesample_def(rte->tablesample, context); + } + else if (IsA(jtnode, JoinExpr)) + { + JoinExpr *j = (JoinExpr *) jtnode; + deparse_columns *colinfo = deparse_columns_fetch(j->rtindex, dpns); + bool need_paren_on_right; + + need_paren_on_right = PRETTY_PAREN(context) && + !IsA(j->rarg, RangeTblRef) && + !(IsA(j->rarg, JoinExpr) && ((JoinExpr *) j->rarg)->alias != NULL); + + if (!PRETTY_PAREN(context) || j->alias != NULL) + appendStringInfoChar(buf, '('); + + get_from_clause_item(j->larg, query, context); + + switch (j->jointype) + { + case JOIN_INNER: + if (j->quals) + appendContextKeyword(context, " JOIN ", + -PRETTYINDENT_STD, + PRETTYINDENT_STD, + PRETTYINDENT_JOIN); + else + appendContextKeyword(context, " CROSS JOIN ", + -PRETTYINDENT_STD, + PRETTYINDENT_STD, + PRETTYINDENT_JOIN); + break; + case JOIN_LEFT: + appendContextKeyword(context, " LEFT JOIN ", + -PRETTYINDENT_STD, + PRETTYINDENT_STD, + PRETTYINDENT_JOIN); + break; + case JOIN_FULL: + appendContextKeyword(context, " FULL JOIN ", + -PRETTYINDENT_STD, + PRETTYINDENT_STD, + PRETTYINDENT_JOIN); + break; + case JOIN_RIGHT: + appendContextKeyword(context, " RIGHT JOIN ", + -PRETTYINDENT_STD, + PRETTYINDENT_STD, + PRETTYINDENT_JOIN); + break; + default: + elog(ERROR, "unrecognized join type: %d", + (int) j->jointype); + } + + if (need_paren_on_right) + appendStringInfoChar(buf, '('); + get_from_clause_item(j->rarg, query, context); + if (need_paren_on_right) + appendStringInfoChar(buf, ')'); + + if (j->usingClause) + { + ListCell *lc; + bool first = true; + + appendStringInfoString(buf, " USING ("); + /* Use the assigned names, not what's in usingClause */ + foreach(lc, colinfo->usingNames) + { + char *colname = (char *) lfirst(lc); + + if (first) + first = false; + else + appendStringInfoString(buf, ", "); + appendStringInfoString(buf, quote_identifier(colname)); + } + appendStringInfoChar(buf, ')'); + + if (j->join_using_alias) + appendStringInfo(buf, " AS %s", + quote_identifier(j->join_using_alias->aliasname)); + } + else if (j->quals) + { + appendStringInfoString(buf, " ON "); + if (!PRETTY_PAREN(context)) + appendStringInfoChar(buf, '('); + get_rule_expr(j->quals, context, false); + if (!PRETTY_PAREN(context)) + appendStringInfoChar(buf, ')'); + } + else if (j->jointype != JOIN_INNER) + { + /* If we didn't say CROSS JOIN above, we must provide an ON */ + appendStringInfoString(buf, " ON TRUE"); + } + + if (!PRETTY_PAREN(context) || j->alias != NULL) + appendStringInfoChar(buf, ')'); + + /* Yes, it's correct to put alias after the right paren ... */ + if (j->alias != NULL) + { + /* + * Note that it's correct to emit an alias clause if and only if + * there was one originally. Otherwise we'd be converting a named + * join to unnamed or vice versa, which creates semantic + * subtleties we don't want. However, we might print a different + * alias name than was there originally. + */ + appendStringInfo(buf, " %s", + quote_identifier(get_rtable_name(j->rtindex, + context))); + get_column_alias_list(colinfo, context); + } + } + else + elog(ERROR, "unrecognized node type: %d", + (int) nodeTag(jtnode)); +} + +/* + * get_rte_alias - print the relation's alias, if needed + * + * If printed, the alias is preceded by a space, or by " AS " if use_as is true. + */ +static void +get_rte_alias(RangeTblEntry *rte, int varno, bool use_as, + deparse_context *context) +{ + deparse_namespace *dpns = (deparse_namespace *) linitial(context->namespaces); + char *refname = get_rtable_name(varno, context); + deparse_columns *colinfo = deparse_columns_fetch(varno, dpns); + bool printalias = false; + + if (rte->alias != NULL) + { + /* Always print alias if user provided one */ + printalias = true; + } + else if (colinfo->printaliases) + { + /* Always print alias if we need to print column aliases */ + printalias = true; + } + else if (rte->rtekind == RTE_RELATION) + { + /* + * No need to print alias if it's same as relation name (this would + * normally be the case, but not if set_rtable_names had to resolve a + * conflict). + */ + if (strcmp(refname, get_relation_name(rte->relid)) != 0) + printalias = true; + } + else if (rte->rtekind == RTE_FUNCTION) + { + /* + * For a function RTE, always print alias. This covers possible + * renaming of the function and/or instability of the FigureColname + * rules for things that aren't simple functions. Note we'd need to + * force it anyway for the columndef list case. + */ + printalias = true; + } + else if (rte->rtekind == RTE_SUBQUERY || + rte->rtekind == RTE_VALUES) + { + /* + * For a subquery, always print alias. This makes the output + * SQL-spec-compliant, even though we allow such aliases to be omitted + * on input. + */ + printalias = true; + } + else if (rte->rtekind == RTE_CTE) + { + /* + * No need to print alias if it's same as CTE name (this would + * normally be the case, but not if set_rtable_names had to resolve a + * conflict). + */ + if (strcmp(refname, rte->ctename) != 0) + printalias = true; + } + + if (printalias) + appendStringInfo(context->buf, "%s%s", + use_as ? " AS " : " ", + quote_identifier(refname)); +} + +/* + * get_column_alias_list - print column alias list for an RTE + * + * Caller must already have printed the relation's alias name. + */ +static void +get_column_alias_list(deparse_columns *colinfo, deparse_context *context) +{ + StringInfo buf = context->buf; + int i; + bool first = true; + + /* Don't print aliases if not needed */ + if (!colinfo->printaliases) + return; + + for (i = 0; i < colinfo->num_new_cols; i++) + { + char *colname = colinfo->new_colnames[i]; + + if (first) + { + appendStringInfoChar(buf, '('); + first = false; + } + else + appendStringInfoString(buf, ", "); + appendStringInfoString(buf, quote_identifier(colname)); + } + if (!first) + appendStringInfoChar(buf, ')'); +} + +/* + * get_from_clause_coldeflist - reproduce FROM clause coldeflist + * + * When printing a top-level coldeflist (which is syntactically also the + * relation's column alias list), use column names from colinfo. But when + * printing a coldeflist embedded inside ROWS FROM(), we prefer to use the + * original coldeflist's names, which are available in rtfunc->funccolnames. + * Pass NULL for colinfo to select the latter behavior. + * + * The coldeflist is appended immediately (no space) to buf. Caller is + * responsible for ensuring that an alias or AS is present before it. + */ +static void +get_from_clause_coldeflist(RangeTblFunction *rtfunc, + deparse_columns *colinfo, + deparse_context *context) +{ + StringInfo buf = context->buf; + ListCell *l1; + ListCell *l2; + ListCell *l3; + ListCell *l4; + int i; + + appendStringInfoChar(buf, '('); + + i = 0; + forfour(l1, rtfunc->funccoltypes, + l2, rtfunc->funccoltypmods, + l3, rtfunc->funccolcollations, + l4, rtfunc->funccolnames) + { + Oid atttypid = lfirst_oid(l1); + int32 atttypmod = lfirst_int(l2); + Oid attcollation = lfirst_oid(l3); + char *attname; + + if (colinfo) + attname = colinfo->colnames[i]; + else + attname = strVal(lfirst(l4)); + + Assert(attname); /* shouldn't be any dropped columns here */ + + if (i > 0) + appendStringInfoString(buf, ", "); + appendStringInfo(buf, "%s %s", + quote_identifier(attname), + format_type_with_typemod(atttypid, atttypmod)); + if (OidIsValid(attcollation) && + attcollation != get_typcollation(atttypid)) + appendStringInfo(buf, " COLLATE %s", + generate_collation_name(attcollation)); + + i++; + } + + appendStringInfoChar(buf, ')'); +} + +/* + * get_tablesample_def - print a TableSampleClause + */ +static void +get_tablesample_def(TableSampleClause *tablesample, deparse_context *context) +{ + StringInfo buf = context->buf; + Oid argtypes[1]; + int nargs; + ListCell *l; + + /* + * We should qualify the handler's function name if it wouldn't be + * resolved by lookup in the current search path. + */ + argtypes[0] = INTERNALOID; + appendStringInfo(buf, " TABLESAMPLE %s (", + generate_function_name(tablesample->tsmhandler, 1, + NIL, argtypes, + false, NULL, EXPR_KIND_NONE)); + + nargs = 0; + foreach(l, tablesample->args) + { + if (nargs++ > 0) + appendStringInfoString(buf, ", "); + get_rule_expr((Node *) lfirst(l), context, false); + } + appendStringInfoChar(buf, ')'); + + if (tablesample->repeatable != NULL) + { + appendStringInfoString(buf, " REPEATABLE ("); + get_rule_expr((Node *) tablesample->repeatable, context, false); + appendStringInfoChar(buf, ')'); + } +} + +/* + * get_opclass_name - fetch name of an index operator class + * + * The opclass name is appended (after a space) to buf. + * + * Output is suppressed if the opclass is the default for the given + * actual_datatype. (If you don't want this behavior, just pass + * InvalidOid for actual_datatype.) + */ +static void +get_opclass_name(Oid opclass, Oid actual_datatype, + StringInfo buf) +{ + HeapTuple ht_opc; + Form_pg_opclass opcrec; + char *opcname; + char *nspname; + + ht_opc = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass)); + if (!HeapTupleIsValid(ht_opc)) + elog(ERROR, "cache lookup failed for opclass %u", opclass); + opcrec = (Form_pg_opclass) GETSTRUCT(ht_opc); + + if (!OidIsValid(actual_datatype) || + GetDefaultOpClass(actual_datatype, opcrec->opcmethod) != opclass) + { + /* Okay, we need the opclass name. Do we need to qualify it? */ + opcname = NameStr(opcrec->opcname); + if (OpclassIsVisible(opclass)) + appendStringInfo(buf, " %s", quote_identifier(opcname)); + else + { + nspname = get_namespace_name_or_temp(opcrec->opcnamespace); + appendStringInfo(buf, " %s.%s", + quote_identifier(nspname), + quote_identifier(opcname)); + } + } + ReleaseSysCache(ht_opc); +} + +/* + * generate_opclass_name + * Compute the name to display for an opclass specified by OID + * + * The result includes all necessary quoting and schema-prefixing. + */ +char * +generate_opclass_name(Oid opclass) +{ + StringInfoData buf; + + initStringInfo(&buf); + get_opclass_name(opclass, InvalidOid, &buf); + + return &buf.data[1]; /* get_opclass_name() prepends space */ +} + +/* + * processIndirection - take care of array and subfield assignment + * + * We strip any top-level FieldStore or assignment SubscriptingRef nodes that + * appear in the input, printing them as decoration for the base column + * name (which we assume the caller just printed). We might also need to + * strip CoerceToDomain nodes, but only ones that appear above assignment + * nodes. + * + * Returns the subexpression that's to be assigned. + */ +static Node * +processIndirection(Node *node, deparse_context *context) +{ + StringInfo buf = context->buf; + CoerceToDomain *cdomain = NULL; + + for (;;) + { + if (node == NULL) + break; + if (IsA(node, FieldStore)) + { + FieldStore *fstore = (FieldStore *) node; + Oid typrelid; + char *fieldname; + + /* lookup tuple type */ + typrelid = get_typ_typrelid(fstore->resulttype); + if (!OidIsValid(typrelid)) + elog(ERROR, "argument type %s of FieldStore is not a tuple type", + format_type_be(fstore->resulttype)); + + /* + * Print the field name. There should only be one target field in + * stored rules. There could be more than that in executable + * target lists, but this function cannot be used for that case. + */ + Assert(list_length(fstore->fieldnums) == 1); + fieldname = get_attname(typrelid, + linitial_int(fstore->fieldnums), false); + appendStringInfo(buf, ".%s", quote_identifier(fieldname)); + + /* + * We ignore arg since it should be an uninteresting reference to + * the target column or subcolumn. + */ + node = (Node *) linitial(fstore->newvals); + } + else if (IsA(node, SubscriptingRef)) + { + SubscriptingRef *sbsref = (SubscriptingRef *) node; + + if (sbsref->refassgnexpr == NULL) + break; + + printSubscripts(sbsref, context); + + /* + * We ignore refexpr since it should be an uninteresting reference + * to the target column or subcolumn. + */ + node = (Node *) sbsref->refassgnexpr; + } + else if (IsA(node, CoerceToDomain)) + { + cdomain = (CoerceToDomain *) node; + /* If it's an explicit domain coercion, we're done */ + if (cdomain->coercionformat != COERCE_IMPLICIT_CAST) + break; + /* Tentatively descend past the CoerceToDomain */ + node = (Node *) cdomain->arg; + } + else + break; + } + + /* + * If we descended past a CoerceToDomain whose argument turned out not to + * be a FieldStore or array assignment, back up to the CoerceToDomain. + * (This is not enough to be fully correct if there are nested implicit + * CoerceToDomains, but such cases shouldn't ever occur.) + */ + if (cdomain && node == (Node *) cdomain->arg) + node = (Node *) cdomain; + + return node; +} + +static void +printSubscripts(SubscriptingRef *sbsref, deparse_context *context) +{ + StringInfo buf = context->buf; + ListCell *lowlist_item; + ListCell *uplist_item; + + lowlist_item = list_head(sbsref->reflowerindexpr); /* could be NULL */ + foreach(uplist_item, sbsref->refupperindexpr) + { + appendStringInfoChar(buf, '['); + if (lowlist_item) + { + /* If subexpression is NULL, get_rule_expr prints nothing */ + get_rule_expr((Node *) lfirst(lowlist_item), context, false); + appendStringInfoChar(buf, ':'); + lowlist_item = lnext(sbsref->reflowerindexpr, lowlist_item); + } + /* If subexpression is NULL, get_rule_expr prints nothing */ + get_rule_expr((Node *) lfirst(uplist_item), context, false); + appendStringInfoChar(buf, ']'); + } +} + +/* + * quote_identifier - Quote an identifier only if needed + * + * When quotes are needed, we palloc the required space; slightly + * space-wasteful but well worth it for notational simplicity. + */ +const char * +quote_identifier(const char *ident) +{ + /* + * Can avoid quoting if ident starts with a lowercase letter or underscore + * and contains only lowercase letters, digits, and underscores, *and* is + * not any SQL keyword. Otherwise, supply quotes. + */ + int nquotes = 0; + bool safe; + const char *ptr; + char *result; + char *optr; + + /* + * would like to use <ctype.h> macros here, but they might yield unwanted + * locale-specific results... + */ + safe = ((ident[0] >= 'a' && ident[0] <= 'z') || ident[0] == '_'); + + for (ptr = ident; *ptr; ptr++) + { + char ch = *ptr; + + if ((ch >= 'a' && ch <= 'z') || + (ch >= '0' && ch <= '9') || + (ch == '_')) + { + /* okay */ + } + else + { + safe = false; + if (ch == '"') + nquotes++; + } + } + + if (quote_all_identifiers) + safe = false; + + if (safe) + { + /* + * Check for keyword. We quote keywords except for unreserved ones. + * (In some cases we could avoid quoting a col_name or type_func_name + * keyword, but it seems much harder than it's worth to tell that.) + * + * Note: ScanKeywordLookup() does case-insensitive comparison, but + * that's fine, since we already know we have all-lower-case. + */ + int kwnum = ScanKeywordLookup(ident, &ScanKeywords); + + if (kwnum >= 0 && ScanKeywordCategories[kwnum] != UNRESERVED_KEYWORD) + safe = false; + } + + if (safe) + return ident; /* no change needed */ + + result = (char *) palloc(strlen(ident) + nquotes + 2 + 1); + + optr = result; + *optr++ = '"'; + for (ptr = ident; *ptr; ptr++) + { + char ch = *ptr; + + if (ch == '"') + *optr++ = '"'; + *optr++ = ch; + } + *optr++ = '"'; + *optr = '\0'; + + return result; +} + +/* + * quote_qualified_identifier - Quote a possibly-qualified identifier + * + * Return a name of the form qualifier.ident, or just ident if qualifier + * is NULL, quoting each component if necessary. The result is palloc'd. + */ +char * +quote_qualified_identifier(const char *qualifier, + const char *ident) +{ + StringInfoData buf; + + initStringInfo(&buf); + if (qualifier) + appendStringInfo(&buf, "%s.", quote_identifier(qualifier)); + appendStringInfoString(&buf, quote_identifier(ident)); + return buf.data; +} + +/* + * get_relation_name + * Get the unqualified name of a relation specified by OID + * + * This differs from the underlying get_rel_name() function in that it will + * throw error instead of silently returning NULL if the OID is bad. + */ +static char * +get_relation_name(Oid relid) +{ + char *relname = get_rel_name(relid); + + if (!relname) + elog(ERROR, "cache lookup failed for relation %u", relid); + return relname; +} + +/* + * generate_relation_name + * Compute the name to display for a relation specified by OID + * + * The result includes all necessary quoting and schema-prefixing. + * + * If namespaces isn't NIL, it must be a list of deparse_namespace nodes. + * We will forcibly qualify the relation name if it equals any CTE name + * visible in the namespace list. + */ +static char * +generate_relation_name(Oid relid, List *namespaces) +{ + HeapTuple tp; + Form_pg_class reltup; + bool need_qual; + ListCell *nslist; + char *relname; + char *nspname; + char *result; + + tp = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(tp)) + elog(ERROR, "cache lookup failed for relation %u", relid); + reltup = (Form_pg_class) GETSTRUCT(tp); + relname = NameStr(reltup->relname); + + /* Check for conflicting CTE name */ + need_qual = false; + foreach(nslist, namespaces) + { + deparse_namespace *dpns = (deparse_namespace *) lfirst(nslist); + ListCell *ctlist; + + foreach(ctlist, dpns->ctes) + { + CommonTableExpr *cte = (CommonTableExpr *) lfirst(ctlist); + + if (strcmp(cte->ctename, relname) == 0) + { + need_qual = true; + break; + } + } + if (need_qual) + break; + } + + /* Otherwise, qualify the name if not visible in search path */ + if (!need_qual) + need_qual = !RelationIsVisible(relid); + + if (need_qual) + nspname = get_namespace_name_or_temp(reltup->relnamespace); + else + nspname = NULL; + + result = quote_qualified_identifier(nspname, relname); + + ReleaseSysCache(tp); + + return result; +} + +/* + * generate_qualified_relation_name + * Compute the name to display for a relation specified by OID + * + * As above, but unconditionally schema-qualify the name. + */ +static char * +generate_qualified_relation_name(Oid relid) +{ + HeapTuple tp; + Form_pg_class reltup; + char *relname; + char *nspname; + char *result; + + tp = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(tp)) + elog(ERROR, "cache lookup failed for relation %u", relid); + reltup = (Form_pg_class) GETSTRUCT(tp); + relname = NameStr(reltup->relname); + + nspname = get_namespace_name_or_temp(reltup->relnamespace); + if (!nspname) + elog(ERROR, "cache lookup failed for namespace %u", + reltup->relnamespace); + + result = quote_qualified_identifier(nspname, relname); + + ReleaseSysCache(tp); + + return result; +} + +/* + * generate_function_name + * Compute the name to display for a function specified by OID, + * given that it is being called with the specified actual arg names and + * types. (Those matter because of ambiguous-function resolution rules.) + * + * If we're dealing with a potentially variadic function (in practice, this + * means a FuncExpr or Aggref, not some other way of calling a function), then + * has_variadic must specify whether variadic arguments have been merged, + * and *use_variadic_p will be set to indicate whether to print VARIADIC in + * the output. For non-FuncExpr cases, has_variadic should be false and + * use_variadic_p can be NULL. + * + * The result includes all necessary quoting and schema-prefixing. + */ +static char * +generate_function_name(Oid funcid, int nargs, List *argnames, Oid *argtypes, + bool has_variadic, bool *use_variadic_p, + ParseExprKind special_exprkind) +{ + char *result; + HeapTuple proctup; + Form_pg_proc procform; + char *proname; + bool use_variadic; + char *nspname; + FuncDetailCode p_result; + Oid p_funcid; + Oid p_rettype; + bool p_retset; + int p_nvargs; + Oid p_vatype; + Oid *p_true_typeids; + bool force_qualify = false; + + proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid)); + if (!HeapTupleIsValid(proctup)) + elog(ERROR, "cache lookup failed for function %u", funcid); + procform = (Form_pg_proc) GETSTRUCT(proctup); + proname = NameStr(procform->proname); + + /* + * Due to parser hacks to avoid needing to reserve CUBE, we need to force + * qualification in some special cases. + */ + if (special_exprkind == EXPR_KIND_GROUP_BY) + { + if (strcmp(proname, "cube") == 0 || strcmp(proname, "rollup") == 0) + force_qualify = true; + } + + /* + * Determine whether VARIADIC should be printed. We must do this first + * since it affects the lookup rules in func_get_detail(). + * + * We always print VARIADIC if the function has a merged variadic-array + * argument. Note that this is always the case for functions taking a + * VARIADIC argument type other than VARIADIC ANY. If we omitted VARIADIC + * and printed the array elements as separate arguments, the call could + * match a newer non-VARIADIC function. + */ + if (use_variadic_p) + { + /* Parser should not have set funcvariadic unless fn is variadic */ + Assert(!has_variadic || OidIsValid(procform->provariadic)); + use_variadic = has_variadic; + *use_variadic_p = use_variadic; + } + else + { + Assert(!has_variadic); + use_variadic = false; + } + + /* + * The idea here is to schema-qualify only if the parser would fail to + * resolve the correct function given the unqualified func name with the + * specified argtypes and VARIADIC flag. But if we already decided to + * force qualification, then we can skip the lookup and pretend we didn't + * find it. + */ + if (!force_qualify) + p_result = func_get_detail(list_make1(makeString(proname)), + NIL, argnames, nargs, argtypes, + !use_variadic, true, false, + &p_funcid, &p_rettype, + &p_retset, &p_nvargs, &p_vatype, + &p_true_typeids, NULL); + else + { + p_result = FUNCDETAIL_NOTFOUND; + p_funcid = InvalidOid; + } + + if ((p_result == FUNCDETAIL_NORMAL || + p_result == FUNCDETAIL_AGGREGATE || + p_result == FUNCDETAIL_WINDOWFUNC) && + p_funcid == funcid) + nspname = NULL; + else + nspname = get_namespace_name_or_temp(procform->pronamespace); + + result = quote_qualified_identifier(nspname, proname); + + ReleaseSysCache(proctup); + + return result; +} + +/* + * generate_operator_name + * Compute the name to display for an operator specified by OID, + * given that it is being called with the specified actual arg types. + * (Arg types matter because of ambiguous-operator resolution rules. + * Pass InvalidOid for unused arg of a unary operator.) + * + * The result includes all necessary quoting and schema-prefixing, + * plus the OPERATOR() decoration needed to use a qualified operator name + * in an expression. + */ +static char * +generate_operator_name(Oid operid, Oid arg1, Oid arg2) +{ + StringInfoData buf; + HeapTuple opertup; + Form_pg_operator operform; + char *oprname; + char *nspname; + Operator p_result; + + initStringInfo(&buf); + + opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(operid)); + if (!HeapTupleIsValid(opertup)) + elog(ERROR, "cache lookup failed for operator %u", operid); + operform = (Form_pg_operator) GETSTRUCT(opertup); + oprname = NameStr(operform->oprname); + + /* + * The idea here is to schema-qualify only if the parser would fail to + * resolve the correct operator given the unqualified op name with the + * specified argtypes. + */ + switch (operform->oprkind) + { + case 'b': + p_result = oper(NULL, list_make1(makeString(oprname)), arg1, arg2, + true, -1); + break; + case 'l': + p_result = left_oper(NULL, list_make1(makeString(oprname)), arg2, + true, -1); + break; + default: + elog(ERROR, "unrecognized oprkind: %d", operform->oprkind); + p_result = NULL; /* keep compiler quiet */ + break; + } + + if (p_result != NULL && oprid(p_result) == operid) + nspname = NULL; + else + { + nspname = get_namespace_name_or_temp(operform->oprnamespace); + appendStringInfo(&buf, "OPERATOR(%s.", quote_identifier(nspname)); + } + + appendStringInfoString(&buf, oprname); + + if (nspname) + appendStringInfoChar(&buf, ')'); + + if (p_result != NULL) + ReleaseSysCache(p_result); + + ReleaseSysCache(opertup); + + return buf.data; +} + +/* + * generate_operator_clause --- generate a binary-operator WHERE clause + * + * This is used for internally-generated-and-executed SQL queries, where + * precision is essential and readability is secondary. The basic + * requirement is to append "leftop op rightop" to buf, where leftop and + * rightop are given as strings and are assumed to yield types leftoptype + * and rightoptype; the operator is identified by OID. The complexity + * comes from needing to be sure that the parser will select the desired + * operator when the query is parsed. We always name the operator using + * OPERATOR(schema.op) syntax, so as to avoid search-path uncertainties. + * We have to emit casts too, if either input isn't already the input type + * of the operator; else we are at the mercy of the parser's heuristics for + * ambiguous-operator resolution. The caller must ensure that leftop and + * rightop are suitable arguments for a cast operation; it's best to insert + * parentheses if they aren't just variables or parameters. + */ +void +generate_operator_clause(StringInfo buf, + const char *leftop, Oid leftoptype, + Oid opoid, + const char *rightop, Oid rightoptype) +{ + HeapTuple opertup; + Form_pg_operator operform; + char *oprname; + char *nspname; + + opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(opoid)); + if (!HeapTupleIsValid(opertup)) + elog(ERROR, "cache lookup failed for operator %u", opoid); + operform = (Form_pg_operator) GETSTRUCT(opertup); + Assert(operform->oprkind == 'b'); + oprname = NameStr(operform->oprname); + + nspname = get_namespace_name(operform->oprnamespace); + + appendStringInfoString(buf, leftop); + if (leftoptype != operform->oprleft) + add_cast_to(buf, operform->oprleft); + appendStringInfo(buf, " OPERATOR(%s.", quote_identifier(nspname)); + appendStringInfoString(buf, oprname); + appendStringInfo(buf, ") %s", rightop); + if (rightoptype != operform->oprright) + add_cast_to(buf, operform->oprright); + + ReleaseSysCache(opertup); +} + +/* + * Add a cast specification to buf. We spell out the type name the hard way, + * intentionally not using format_type_be(). This is to avoid corner cases + * for CHARACTER, BIT, and perhaps other types, where specifying the type + * using SQL-standard syntax results in undesirable data truncation. By + * doing it this way we can be certain that the cast will have default (-1) + * target typmod. + */ +static void +add_cast_to(StringInfo buf, Oid typid) +{ + HeapTuple typetup; + Form_pg_type typform; + char *typname; + char *nspname; + + typetup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid)); + if (!HeapTupleIsValid(typetup)) + elog(ERROR, "cache lookup failed for type %u", typid); + typform = (Form_pg_type) GETSTRUCT(typetup); + + typname = NameStr(typform->typname); + nspname = get_namespace_name_or_temp(typform->typnamespace); + + appendStringInfo(buf, "::%s.%s", + quote_identifier(nspname), quote_identifier(typname)); + + ReleaseSysCache(typetup); +} + +/* + * generate_qualified_type_name + * Compute the name to display for a type specified by OID + * + * This is different from format_type_be() in that we unconditionally + * schema-qualify the name. That also means no special syntax for + * SQL-standard type names ... although in current usage, this should + * only get used for domains, so such cases wouldn't occur anyway. + */ +static char * +generate_qualified_type_name(Oid typid) +{ + HeapTuple tp; + Form_pg_type typtup; + char *typname; + char *nspname; + char *result; + + tp = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid)); + if (!HeapTupleIsValid(tp)) + elog(ERROR, "cache lookup failed for type %u", typid); + typtup = (Form_pg_type) GETSTRUCT(tp); + typname = NameStr(typtup->typname); + + nspname = get_namespace_name_or_temp(typtup->typnamespace); + if (!nspname) + elog(ERROR, "cache lookup failed for namespace %u", + typtup->typnamespace); + + result = quote_qualified_identifier(nspname, typname); + + ReleaseSysCache(tp); + + return result; +} + +/* + * generate_collation_name + * Compute the name to display for a collation specified by OID + * + * The result includes all necessary quoting and schema-prefixing. + */ +char * +generate_collation_name(Oid collid) +{ + HeapTuple tp; + Form_pg_collation colltup; + char *collname; + char *nspname; + char *result; + + tp = SearchSysCache1(COLLOID, ObjectIdGetDatum(collid)); + if (!HeapTupleIsValid(tp)) + elog(ERROR, "cache lookup failed for collation %u", collid); + colltup = (Form_pg_collation) GETSTRUCT(tp); + collname = NameStr(colltup->collname); + + if (!CollationIsVisible(collid)) + nspname = get_namespace_name_or_temp(colltup->collnamespace); + else + nspname = NULL; + + result = quote_qualified_identifier(nspname, collname); + + ReleaseSysCache(tp); + + return result; +} + +/* + * Given a C string, produce a TEXT datum. + * + * We assume that the input was palloc'd and may be freed. + */ +static text * +string_to_text(char *str) +{ + text *result; + + result = cstring_to_text(str); + pfree(str); + return result; +} + +/* + * Generate a C string representing a relation options from text[] datum. + */ +static void +get_reloptions(StringInfo buf, Datum reloptions) +{ + Datum *options; + int noptions; + int i; + + deconstruct_array_builtin(DatumGetArrayTypeP(reloptions), TEXTOID, + &options, NULL, &noptions); + + for (i = 0; i < noptions; i++) + { + char *option = TextDatumGetCString(options[i]); + char *name; + char *separator; + char *value; + + /* + * Each array element should have the form name=value. If the "=" is + * missing for some reason, treat it like an empty value. + */ + name = option; + separator = strchr(option, '='); + if (separator) + { + *separator = '\0'; + value = separator + 1; + } + else + value = ""; + + if (i > 0) + appendStringInfoString(buf, ", "); + appendStringInfo(buf, "%s=", quote_identifier(name)); + + /* + * In general we need to quote the value; but to avoid unnecessary + * clutter, do not quote if it is an identifier that would not need + * quoting. (We could also allow numbers, but that is a bit trickier + * than it looks --- for example, are leading zeroes significant? We + * don't want to assume very much here about what custom reloptions + * might mean.) + */ + if (quote_identifier(value) == value) + appendStringInfoString(buf, value); + else + simple_quote_literal(buf, value); + + pfree(option); + } +} + +/* + * Generate a C string representing a relation's reloptions, or NULL if none. + */ +static char * +flatten_reloptions(Oid relid) +{ + char *result = NULL; + HeapTuple tuple; + Datum reloptions; + bool isnull; + + tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for relation %u", relid); + + reloptions = SysCacheGetAttr(RELOID, tuple, + Anum_pg_class_reloptions, &isnull); + if (!isnull) + { + StringInfoData buf; + + initStringInfo(&buf); + get_reloptions(&buf, reloptions); + + result = buf.data; + } + + ReleaseSysCache(tuple); + + return result; +} + +/* + * get_range_partbound_string + * A C string representation of one range partition bound + */ +char * +get_range_partbound_string(List *bound_datums) +{ + deparse_context context; + StringInfo buf = makeStringInfo(); + ListCell *cell; + char *sep; + + memset(&context, 0, sizeof(deparse_context)); + context.buf = buf; + + appendStringInfoChar(buf, '('); + sep = ""; + foreach(cell, bound_datums) + { + PartitionRangeDatum *datum = + lfirst_node(PartitionRangeDatum, cell); + + appendStringInfoString(buf, sep); + if (datum->kind == PARTITION_RANGE_DATUM_MINVALUE) + appendStringInfoString(buf, "MINVALUE"); + else if (datum->kind == PARTITION_RANGE_DATUM_MAXVALUE) + appendStringInfoString(buf, "MAXVALUE"); + else + { + Const *val = castNode(Const, datum->value); + + get_const_expr(val, &context, -1); + } + sep = ", "; + } + appendStringInfoChar(buf, ')'); + + return buf->data; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/selfuncs.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/selfuncs.c new file mode 100644 index 00000000000..b24bf5979d5 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/selfuncs.c @@ -0,0 +1,8030 @@ +/*------------------------------------------------------------------------- + * + * selfuncs.c + * Selectivity functions and index cost estimation functions for + * standard operators and index access methods. + * + * Selectivity routines are registered in the pg_operator catalog + * in the "oprrest" and "oprjoin" attributes. + * + * Index cost functions are located via the index AM's API struct, + * which is obtained from the handler function registered in pg_am. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/adt/selfuncs.c + * + *------------------------------------------------------------------------- + */ + +/*---------- + * Operator selectivity estimation functions are called to estimate the + * selectivity of WHERE clauses whose top-level operator is their operator. + * We divide the problem into two cases: + * Restriction clause estimation: the clause involves vars of just + * one relation. + * Join clause estimation: the clause involves vars of multiple rels. + * Join selectivity estimation is far more difficult and usually less accurate + * than restriction estimation. + * + * When dealing with the inner scan of a nestloop join, we consider the + * join's joinclauses as restriction clauses for the inner relation, and + * treat vars of the outer relation as parameters (a/k/a constants of unknown + * values). So, restriction estimators need to be able to accept an argument + * telling which relation is to be treated as the variable. + * + * The call convention for a restriction estimator (oprrest function) is + * + * Selectivity oprrest (PlannerInfo *root, + * Oid operator, + * List *args, + * int varRelid); + * + * root: general information about the query (rtable and RelOptInfo lists + * are particularly important for the estimator). + * operator: OID of the specific operator in question. + * args: argument list from the operator clause. + * varRelid: if not zero, the relid (rtable index) of the relation to + * be treated as the variable relation. May be zero if the args list + * is known to contain vars of only one relation. + * + * This is represented at the SQL level (in pg_proc) as + * + * float8 oprrest (internal, oid, internal, int4); + * + * The result is a selectivity, that is, a fraction (0 to 1) of the rows + * of the relation that are expected to produce a TRUE result for the + * given operator. + * + * The call convention for a join estimator (oprjoin function) is similar + * except that varRelid is not needed, and instead join information is + * supplied: + * + * Selectivity oprjoin (PlannerInfo *root, + * Oid operator, + * List *args, + * JoinType jointype, + * SpecialJoinInfo *sjinfo); + * + * float8 oprjoin (internal, oid, internal, int2, internal); + * + * (Before Postgres 8.4, join estimators had only the first four of these + * parameters. That signature is still allowed, but deprecated.) The + * relationship between jointype and sjinfo is explained in the comments for + * clause_selectivity() --- the short version is that jointype is usually + * best ignored in favor of examining sjinfo. + * + * Join selectivity for regular inner and outer joins is defined as the + * fraction (0 to 1) of the cross product of the relations that is expected + * to produce a TRUE result for the given operator. For both semi and anti + * joins, however, the selectivity is defined as the fraction of the left-hand + * side relation's rows that are expected to have a match (ie, at least one + * row with a TRUE result) in the right-hand side. + * + * For both oprrest and oprjoin functions, the operator's input collation OID + * (if any) is passed using the standard fmgr mechanism, so that the estimator + * function can fetch it with PG_GET_COLLATION(). Note, however, that all + * statistics in pg_statistic are currently built using the relevant column's + * collation. + *---------- + */ + +#include "postgres.h" + +#include <ctype.h> +#include <math.h> + +#include "access/brin.h" +#include "access/brin_page.h" +#include "access/gin.h" +#include "access/table.h" +#include "access/tableam.h" +#include "access/visibilitymap.h" +#include "catalog/pg_am.h" +#include "catalog/pg_collation.h" +#include "catalog/pg_operator.h" +#include "catalog/pg_statistic.h" +#include "catalog/pg_statistic_ext.h" +#include "executor/nodeAgg.h" +#include "miscadmin.h" +#include "nodes/makefuncs.h" +#include "nodes/nodeFuncs.h" +#include "optimizer/clauses.h" +#include "optimizer/cost.h" +#include "optimizer/optimizer.h" +#include "optimizer/pathnode.h" +#include "optimizer/paths.h" +#include "optimizer/plancat.h" +#include "parser/parse_clause.h" +#include "parser/parsetree.h" +#include "statistics/statistics.h" +#include "storage/bufmgr.h" +#include "utils/acl.h" +#include "utils/array.h" +#include "utils/builtins.h" +#include "utils/date.h" +#include "utils/datum.h" +#include "utils/fmgroids.h" +#include "utils/index_selfuncs.h" +#include "utils/lsyscache.h" +#include "utils/memutils.h" +#include "utils/pg_locale.h" +#include "utils/rel.h" +#include "utils/selfuncs.h" +#include "utils/snapmgr.h" +#include "utils/spccache.h" +#include "utils/syscache.h" +#include "utils/timestamp.h" +#include "utils/typcache.h" + +#define DEFAULT_PAGE_CPU_MULTIPLIER 50.0 + +/* Hooks for plugins to get control when we ask for stats */ +__thread get_relation_stats_hook_type get_relation_stats_hook = NULL; +__thread get_index_stats_hook_type get_index_stats_hook = NULL; + +static double eqsel_internal(PG_FUNCTION_ARGS, bool negate); +static double eqjoinsel_inner(Oid opfuncoid, Oid collation, + VariableStatData *vardata1, VariableStatData *vardata2, + double nd1, double nd2, + bool isdefault1, bool isdefault2, + AttStatsSlot *sslot1, AttStatsSlot *sslot2, + Form_pg_statistic stats1, Form_pg_statistic stats2, + bool have_mcvs1, bool have_mcvs2); +static double eqjoinsel_semi(Oid opfuncoid, Oid collation, + VariableStatData *vardata1, VariableStatData *vardata2, + double nd1, double nd2, + bool isdefault1, bool isdefault2, + AttStatsSlot *sslot1, AttStatsSlot *sslot2, + Form_pg_statistic stats1, Form_pg_statistic stats2, + bool have_mcvs1, bool have_mcvs2, + RelOptInfo *inner_rel); +static bool estimate_multivariate_ndistinct(PlannerInfo *root, + RelOptInfo *rel, List **varinfos, double *ndistinct); +static bool convert_to_scalar(Datum value, Oid valuetypid, Oid collid, + double *scaledvalue, + Datum lobound, Datum hibound, Oid boundstypid, + double *scaledlobound, double *scaledhibound); +static double convert_numeric_to_scalar(Datum value, Oid typid, bool *failure); +static void convert_string_to_scalar(char *value, + double *scaledvalue, + char *lobound, + double *scaledlobound, + char *hibound, + double *scaledhibound); +static void convert_bytea_to_scalar(Datum value, + double *scaledvalue, + Datum lobound, + double *scaledlobound, + Datum hibound, + double *scaledhibound); +static double convert_one_string_to_scalar(char *value, + int rangelo, int rangehi); +static double convert_one_bytea_to_scalar(unsigned char *value, int valuelen, + int rangelo, int rangehi); +static char *convert_string_datum(Datum value, Oid typid, Oid collid, + bool *failure); +static double convert_timevalue_to_scalar(Datum value, Oid typid, + bool *failure); +static void examine_simple_variable(PlannerInfo *root, Var *var, + VariableStatData *vardata); +static bool get_variable_range(PlannerInfo *root, VariableStatData *vardata, + Oid sortop, Oid collation, + Datum *min, Datum *max); +static void get_stats_slot_range(AttStatsSlot *sslot, + Oid opfuncoid, FmgrInfo *opproc, + Oid collation, int16 typLen, bool typByVal, + Datum *min, Datum *max, bool *p_have_data); +static bool get_actual_variable_range(PlannerInfo *root, + VariableStatData *vardata, + Oid sortop, Oid collation, + Datum *min, Datum *max); +static bool get_actual_variable_endpoint(Relation heapRel, + Relation indexRel, + ScanDirection indexscandir, + ScanKey scankeys, + int16 typLen, + bool typByVal, + TupleTableSlot *tableslot, + MemoryContext outercontext, + Datum *endpointDatum); +static RelOptInfo *find_join_input_rel(PlannerInfo *root, Relids relids); + + +/* + * eqsel - Selectivity of "=" for any data types. + * + * Note: this routine is also used to estimate selectivity for some + * operators that are not "=" but have comparable selectivity behavior, + * such as "~=" (geometric approximate-match). Even for "=", we must + * keep in mind that the left and right datatypes may differ. + */ +Datum +eqsel(PG_FUNCTION_ARGS) +{ + PG_RETURN_FLOAT8((float8) eqsel_internal(fcinfo, false)); +} + +/* + * Common code for eqsel() and neqsel() + */ +static double +eqsel_internal(PG_FUNCTION_ARGS, bool negate) +{ + PlannerInfo *root = (PlannerInfo *) PG_GETARG_POINTER(0); + Oid operator = PG_GETARG_OID(1); + List *args = (List *) PG_GETARG_POINTER(2); + int varRelid = PG_GETARG_INT32(3); + Oid collation = PG_GET_COLLATION(); + VariableStatData vardata; + Node *other; + bool varonleft; + double selec; + + /* + * When asked about <>, we do the estimation using the corresponding = + * operator, then convert to <> via "1.0 - eq_selectivity - nullfrac". + */ + if (negate) + { + operator = get_negator(operator); + if (!OidIsValid(operator)) + { + /* Use default selectivity (should we raise an error instead?) */ + return 1.0 - DEFAULT_EQ_SEL; + } + } + + /* + * If expression is not variable = something or something = variable, then + * punt and return a default estimate. + */ + if (!get_restriction_variable(root, args, varRelid, + &vardata, &other, &varonleft)) + return negate ? (1.0 - DEFAULT_EQ_SEL) : DEFAULT_EQ_SEL; + + /* + * We can do a lot better if the something is a constant. (Note: the + * Const might result from estimation rather than being a simple constant + * in the query.) + */ + if (IsA(other, Const)) + selec = var_eq_const(&vardata, operator, collation, + ((Const *) other)->constvalue, + ((Const *) other)->constisnull, + varonleft, negate); + else + selec = var_eq_non_const(&vardata, operator, collation, other, + varonleft, negate); + + ReleaseVariableStats(vardata); + + return selec; +} + +/* + * var_eq_const --- eqsel for var = const case + * + * This is exported so that some other estimation functions can use it. + */ +double +var_eq_const(VariableStatData *vardata, Oid oproid, Oid collation, + Datum constval, bool constisnull, + bool varonleft, bool negate) +{ + double selec; + double nullfrac = 0.0; + bool isdefault; + Oid opfuncoid; + + /* + * If the constant is NULL, assume operator is strict and return zero, ie, + * operator will never return TRUE. (It's zero even for a negator op.) + */ + if (constisnull) + return 0.0; + + /* + * Grab the nullfrac for use below. Note we allow use of nullfrac + * regardless of security check. + */ + if (HeapTupleIsValid(vardata->statsTuple)) + { + Form_pg_statistic stats; + + stats = (Form_pg_statistic) GETSTRUCT(vardata->statsTuple); + nullfrac = stats->stanullfrac; + } + + /* + * If we matched the var to a unique index or DISTINCT clause, assume + * there is exactly one match regardless of anything else. (This is + * slightly bogus, since the index or clause's equality operator might be + * different from ours, but it's much more likely to be right than + * ignoring the information.) + */ + if (vardata->isunique && vardata->rel && vardata->rel->tuples >= 1.0) + { + selec = 1.0 / vardata->rel->tuples; + } + else if (HeapTupleIsValid(vardata->statsTuple) && + statistic_proc_security_check(vardata, + (opfuncoid = get_opcode(oproid)))) + { + AttStatsSlot sslot; + bool match = false; + int i; + + /* + * Is the constant "=" to any of the column's most common values? + * (Although the given operator may not really be "=", we will assume + * that seeing whether it returns TRUE is an appropriate test. If you + * don't like this, maybe you shouldn't be using eqsel for your + * operator...) + */ + if (get_attstatsslot(&sslot, vardata->statsTuple, + STATISTIC_KIND_MCV, InvalidOid, + ATTSTATSSLOT_VALUES | ATTSTATSSLOT_NUMBERS)) + { + LOCAL_FCINFO(fcinfo, 2); + FmgrInfo eqproc; + + fmgr_info(opfuncoid, &eqproc); + + /* + * Save a few cycles by setting up the fcinfo struct just once. + * Using FunctionCallInvoke directly also avoids failure if the + * eqproc returns NULL, though really equality functions should + * never do that. + */ + InitFunctionCallInfoData(*fcinfo, &eqproc, 2, collation, + NULL, NULL); + fcinfo->args[0].isnull = false; + fcinfo->args[1].isnull = false; + /* be careful to apply operator right way 'round */ + if (varonleft) + fcinfo->args[1].value = constval; + else + fcinfo->args[0].value = constval; + + for (i = 0; i < sslot.nvalues; i++) + { + Datum fresult; + + if (varonleft) + fcinfo->args[0].value = sslot.values[i]; + else + fcinfo->args[1].value = sslot.values[i]; + fcinfo->isnull = false; + fresult = FunctionCallInvoke(fcinfo); + if (!fcinfo->isnull && DatumGetBool(fresult)) + { + match = true; + break; + } + } + } + else + { + /* no most-common-value info available */ + i = 0; /* keep compiler quiet */ + } + + if (match) + { + /* + * Constant is "=" to this common value. We know selectivity + * exactly (or as exactly as ANALYZE could calculate it, anyway). + */ + selec = sslot.numbers[i]; + } + else + { + /* + * Comparison is against a constant that is neither NULL nor any + * of the common values. Its selectivity cannot be more than + * this: + */ + double sumcommon = 0.0; + double otherdistinct; + + for (i = 0; i < sslot.nnumbers; i++) + sumcommon += sslot.numbers[i]; + selec = 1.0 - sumcommon - nullfrac; + CLAMP_PROBABILITY(selec); + + /* + * and in fact it's probably a good deal less. We approximate that + * all the not-common values share this remaining fraction + * equally, so we divide by the number of other distinct values. + */ + otherdistinct = get_variable_numdistinct(vardata, &isdefault) - + sslot.nnumbers; + if (otherdistinct > 1) + selec /= otherdistinct; + + /* + * Another cross-check: selectivity shouldn't be estimated as more + * than the least common "most common value". + */ + if (sslot.nnumbers > 0 && selec > sslot.numbers[sslot.nnumbers - 1]) + selec = sslot.numbers[sslot.nnumbers - 1]; + } + + free_attstatsslot(&sslot); + } + else + { + /* + * No ANALYZE stats available, so make a guess using estimated number + * of distinct values and assuming they are equally common. (The guess + * is unlikely to be very good, but we do know a few special cases.) + */ + selec = 1.0 / get_variable_numdistinct(vardata, &isdefault); + } + + /* now adjust if we wanted <> rather than = */ + if (negate) + selec = 1.0 - selec - nullfrac; + + /* result should be in range, but make sure... */ + CLAMP_PROBABILITY(selec); + + return selec; +} + +/* + * var_eq_non_const --- eqsel for var = something-other-than-const case + * + * This is exported so that some other estimation functions can use it. + */ +double +var_eq_non_const(VariableStatData *vardata, Oid oproid, Oid collation, + Node *other, + bool varonleft, bool negate) +{ + double selec; + double nullfrac = 0.0; + bool isdefault; + + /* + * Grab the nullfrac for use below. + */ + if (HeapTupleIsValid(vardata->statsTuple)) + { + Form_pg_statistic stats; + + stats = (Form_pg_statistic) GETSTRUCT(vardata->statsTuple); + nullfrac = stats->stanullfrac; + } + + /* + * If we matched the var to a unique index or DISTINCT clause, assume + * there is exactly one match regardless of anything else. (This is + * slightly bogus, since the index or clause's equality operator might be + * different from ours, but it's much more likely to be right than + * ignoring the information.) + */ + if (vardata->isunique && vardata->rel && vardata->rel->tuples >= 1.0) + { + selec = 1.0 / vardata->rel->tuples; + } + else if (HeapTupleIsValid(vardata->statsTuple)) + { + double ndistinct; + AttStatsSlot sslot; + + /* + * Search is for a value that we do not know a priori, but we will + * assume it is not NULL. Estimate the selectivity as non-null + * fraction divided by number of distinct values, so that we get a + * result averaged over all possible values whether common or + * uncommon. (Essentially, we are assuming that the not-yet-known + * comparison value is equally likely to be any of the possible + * values, regardless of their frequency in the table. Is that a good + * idea?) + */ + selec = 1.0 - nullfrac; + ndistinct = get_variable_numdistinct(vardata, &isdefault); + if (ndistinct > 1) + selec /= ndistinct; + + /* + * Cross-check: selectivity should never be estimated as more than the + * most common value's. + */ + if (get_attstatsslot(&sslot, vardata->statsTuple, + STATISTIC_KIND_MCV, InvalidOid, + ATTSTATSSLOT_NUMBERS)) + { + if (sslot.nnumbers > 0 && selec > sslot.numbers[0]) + selec = sslot.numbers[0]; + free_attstatsslot(&sslot); + } + } + else + { + /* + * No ANALYZE stats available, so make a guess using estimated number + * of distinct values and assuming they are equally common. (The guess + * is unlikely to be very good, but we do know a few special cases.) + */ + selec = 1.0 / get_variable_numdistinct(vardata, &isdefault); + } + + /* now adjust if we wanted <> rather than = */ + if (negate) + selec = 1.0 - selec - nullfrac; + + /* result should be in range, but make sure... */ + CLAMP_PROBABILITY(selec); + + return selec; +} + +/* + * neqsel - Selectivity of "!=" for any data types. + * + * This routine is also used for some operators that are not "!=" + * but have comparable selectivity behavior. See above comments + * for eqsel(). + */ +Datum +neqsel(PG_FUNCTION_ARGS) +{ + PG_RETURN_FLOAT8((float8) eqsel_internal(fcinfo, true)); +} + +/* + * scalarineqsel - Selectivity of "<", "<=", ">", ">=" for scalars. + * + * This is the guts of scalarltsel/scalarlesel/scalargtsel/scalargesel. + * The isgt and iseq flags distinguish which of the four cases apply. + * + * The caller has commuted the clause, if necessary, so that we can treat + * the variable as being on the left. The caller must also make sure that + * the other side of the clause is a non-null Const, and dissect that into + * a value and datatype. (This definition simplifies some callers that + * want to estimate against a computed value instead of a Const node.) + * + * This routine works for any datatype (or pair of datatypes) known to + * convert_to_scalar(). If it is applied to some other datatype, + * it will return an approximate estimate based on assuming that the constant + * value falls in the middle of the bin identified by binary search. + */ +static double +scalarineqsel(PlannerInfo *root, Oid operator, bool isgt, bool iseq, + Oid collation, + VariableStatData *vardata, Datum constval, Oid consttype) +{ + Form_pg_statistic stats; + FmgrInfo opproc; + double mcv_selec, + hist_selec, + sumcommon; + double selec; + + if (!HeapTupleIsValid(vardata->statsTuple)) + { + /* + * No stats are available. Typically this means we have to fall back + * on the default estimate; but if the variable is CTID then we can + * make an estimate based on comparing the constant to the table size. + */ + if (vardata->var && IsA(vardata->var, Var) && + ((Var *) vardata->var)->varattno == SelfItemPointerAttributeNumber) + { + ItemPointer itemptr; + double block; + double density; + + /* + * If the relation's empty, we're going to include all of it. + * (This is mostly to avoid divide-by-zero below.) + */ + if (vardata->rel->pages == 0) + return 1.0; + + itemptr = (ItemPointer) DatumGetPointer(constval); + block = ItemPointerGetBlockNumberNoCheck(itemptr); + + /* + * Determine the average number of tuples per page (density). + * + * Since the last page will, on average, be only half full, we can + * estimate it to have half as many tuples as earlier pages. So + * give it half the weight of a regular page. + */ + density = vardata->rel->tuples / (vardata->rel->pages - 0.5); + + /* If target is the last page, use half the density. */ + if (block >= vardata->rel->pages - 1) + density *= 0.5; + + /* + * Using the average tuples per page, calculate how far into the + * page the itemptr is likely to be and adjust block accordingly, + * by adding that fraction of a whole block (but never more than a + * whole block, no matter how high the itemptr's offset is). Here + * we are ignoring the possibility of dead-tuple line pointers, + * which is fairly bogus, but we lack the info to do better. + */ + if (density > 0.0) + { + OffsetNumber offset = ItemPointerGetOffsetNumberNoCheck(itemptr); + + block += Min(offset / density, 1.0); + } + + /* + * Convert relative block number to selectivity. Again, the last + * page has only half weight. + */ + selec = block / (vardata->rel->pages - 0.5); + + /* + * The calculation so far gave us a selectivity for the "<=" case. + * We'll have one fewer tuple for "<" and one additional tuple for + * ">=", the latter of which we'll reverse the selectivity for + * below, so we can simply subtract one tuple for both cases. The + * cases that need this adjustment can be identified by iseq being + * equal to isgt. + */ + if (iseq == isgt && vardata->rel->tuples >= 1.0) + selec -= (1.0 / vardata->rel->tuples); + + /* Finally, reverse the selectivity for the ">", ">=" cases. */ + if (isgt) + selec = 1.0 - selec; + + CLAMP_PROBABILITY(selec); + return selec; + } + + /* no stats available, so default result */ + return DEFAULT_INEQ_SEL; + } + stats = (Form_pg_statistic) GETSTRUCT(vardata->statsTuple); + + fmgr_info(get_opcode(operator), &opproc); + + /* + * If we have most-common-values info, add up the fractions of the MCV + * entries that satisfy MCV OP CONST. These fractions contribute directly + * to the result selectivity. Also add up the total fraction represented + * by MCV entries. + */ + mcv_selec = mcv_selectivity(vardata, &opproc, collation, constval, true, + &sumcommon); + + /* + * If there is a histogram, determine which bin the constant falls in, and + * compute the resulting contribution to selectivity. + */ + hist_selec = ineq_histogram_selectivity(root, vardata, + operator, &opproc, isgt, iseq, + collation, + constval, consttype); + + /* + * Now merge the results from the MCV and histogram calculations, + * realizing that the histogram covers only the non-null values that are + * not listed in MCV. + */ + selec = 1.0 - stats->stanullfrac - sumcommon; + + if (hist_selec >= 0.0) + selec *= hist_selec; + else + { + /* + * If no histogram but there are values not accounted for by MCV, + * arbitrarily assume half of them will match. + */ + selec *= 0.5; + } + + selec += mcv_selec; + + /* result should be in range, but make sure... */ + CLAMP_PROBABILITY(selec); + + return selec; +} + +/* + * mcv_selectivity - Examine the MCV list for selectivity estimates + * + * Determine the fraction of the variable's MCV population that satisfies + * the predicate (VAR OP CONST), or (CONST OP VAR) if !varonleft. Also + * compute the fraction of the total column population represented by the MCV + * list. This code will work for any boolean-returning predicate operator. + * + * The function result is the MCV selectivity, and the fraction of the + * total population is returned into *sumcommonp. Zeroes are returned + * if there is no MCV list. + */ +double +mcv_selectivity(VariableStatData *vardata, FmgrInfo *opproc, Oid collation, + Datum constval, bool varonleft, + double *sumcommonp) +{ + double mcv_selec, + sumcommon; + AttStatsSlot sslot; + int i; + + mcv_selec = 0.0; + sumcommon = 0.0; + + if (HeapTupleIsValid(vardata->statsTuple) && + statistic_proc_security_check(vardata, opproc->fn_oid) && + get_attstatsslot(&sslot, vardata->statsTuple, + STATISTIC_KIND_MCV, InvalidOid, + ATTSTATSSLOT_VALUES | ATTSTATSSLOT_NUMBERS)) + { + LOCAL_FCINFO(fcinfo, 2); + + /* + * We invoke the opproc "by hand" so that we won't fail on NULL + * results. Such cases won't arise for normal comparison functions, + * but generic_restriction_selectivity could perhaps be used with + * operators that can return NULL. A small side benefit is to not + * need to re-initialize the fcinfo struct from scratch each time. + */ + InitFunctionCallInfoData(*fcinfo, opproc, 2, collation, + NULL, NULL); + fcinfo->args[0].isnull = false; + fcinfo->args[1].isnull = false; + /* be careful to apply operator right way 'round */ + if (varonleft) + fcinfo->args[1].value = constval; + else + fcinfo->args[0].value = constval; + + for (i = 0; i < sslot.nvalues; i++) + { + Datum fresult; + + if (varonleft) + fcinfo->args[0].value = sslot.values[i]; + else + fcinfo->args[1].value = sslot.values[i]; + fcinfo->isnull = false; + fresult = FunctionCallInvoke(fcinfo); + if (!fcinfo->isnull && DatumGetBool(fresult)) + mcv_selec += sslot.numbers[i]; + sumcommon += sslot.numbers[i]; + } + free_attstatsslot(&sslot); + } + + *sumcommonp = sumcommon; + return mcv_selec; +} + +/* + * histogram_selectivity - Examine the histogram for selectivity estimates + * + * Determine the fraction of the variable's histogram entries that satisfy + * the predicate (VAR OP CONST), or (CONST OP VAR) if !varonleft. + * + * This code will work for any boolean-returning predicate operator, whether + * or not it has anything to do with the histogram sort operator. We are + * essentially using the histogram just as a representative sample. However, + * small histograms are unlikely to be all that representative, so the caller + * should be prepared to fall back on some other estimation approach when the + * histogram is missing or very small. It may also be prudent to combine this + * approach with another one when the histogram is small. + * + * If the actual histogram size is not at least min_hist_size, we won't bother + * to do the calculation at all. Also, if the n_skip parameter is > 0, we + * ignore the first and last n_skip histogram elements, on the grounds that + * they are outliers and hence not very representative. Typical values for + * these parameters are 10 and 1. + * + * The function result is the selectivity, or -1 if there is no histogram + * or it's smaller than min_hist_size. + * + * The output parameter *hist_size receives the actual histogram size, + * or zero if no histogram. Callers may use this number to decide how + * much faith to put in the function result. + * + * Note that the result disregards both the most-common-values (if any) and + * null entries. The caller is expected to combine this result with + * statistics for those portions of the column population. It may also be + * prudent to clamp the result range, ie, disbelieve exact 0 or 1 outputs. + */ +double +histogram_selectivity(VariableStatData *vardata, + FmgrInfo *opproc, Oid collation, + Datum constval, bool varonleft, + int min_hist_size, int n_skip, + int *hist_size) +{ + double result; + AttStatsSlot sslot; + + /* check sanity of parameters */ + Assert(n_skip >= 0); + Assert(min_hist_size > 2 * n_skip); + + if (HeapTupleIsValid(vardata->statsTuple) && + statistic_proc_security_check(vardata, opproc->fn_oid) && + get_attstatsslot(&sslot, vardata->statsTuple, + STATISTIC_KIND_HISTOGRAM, InvalidOid, + ATTSTATSSLOT_VALUES)) + { + *hist_size = sslot.nvalues; + if (sslot.nvalues >= min_hist_size) + { + LOCAL_FCINFO(fcinfo, 2); + int nmatch = 0; + int i; + + /* + * We invoke the opproc "by hand" so that we won't fail on NULL + * results. Such cases won't arise for normal comparison + * functions, but generic_restriction_selectivity could perhaps be + * used with operators that can return NULL. A small side benefit + * is to not need to re-initialize the fcinfo struct from scratch + * each time. + */ + InitFunctionCallInfoData(*fcinfo, opproc, 2, collation, + NULL, NULL); + fcinfo->args[0].isnull = false; + fcinfo->args[1].isnull = false; + /* be careful to apply operator right way 'round */ + if (varonleft) + fcinfo->args[1].value = constval; + else + fcinfo->args[0].value = constval; + + for (i = n_skip; i < sslot.nvalues - n_skip; i++) + { + Datum fresult; + + if (varonleft) + fcinfo->args[0].value = sslot.values[i]; + else + fcinfo->args[1].value = sslot.values[i]; + fcinfo->isnull = false; + fresult = FunctionCallInvoke(fcinfo); + if (!fcinfo->isnull && DatumGetBool(fresult)) + nmatch++; + } + result = ((double) nmatch) / ((double) (sslot.nvalues - 2 * n_skip)); + } + else + result = -1; + free_attstatsslot(&sslot); + } + else + { + *hist_size = 0; + result = -1; + } + + return result; +} + +/* + * generic_restriction_selectivity - Selectivity for almost anything + * + * This function estimates selectivity for operators that we don't have any + * special knowledge about, but are on data types that we collect standard + * MCV and/or histogram statistics for. (Additional assumptions are that + * the operator is strict and immutable, or at least stable.) + * + * If we have "VAR OP CONST" or "CONST OP VAR", selectivity is estimated by + * applying the operator to each element of the column's MCV and/or histogram + * stats, and merging the results using the assumption that the histogram is + * a reasonable random sample of the column's non-MCV population. Note that + * if the operator's semantics are related to the histogram ordering, this + * might not be such a great assumption; other functions such as + * scalarineqsel() are probably a better match in such cases. + * + * Otherwise, fall back to the default selectivity provided by the caller. + */ +double +generic_restriction_selectivity(PlannerInfo *root, Oid oproid, Oid collation, + List *args, int varRelid, + double default_selectivity) +{ + double selec; + VariableStatData vardata; + Node *other; + bool varonleft; + + /* + * If expression is not variable OP something or something OP variable, + * then punt and return the default estimate. + */ + if (!get_restriction_variable(root, args, varRelid, + &vardata, &other, &varonleft)) + return default_selectivity; + + /* + * If the something is a NULL constant, assume operator is strict and + * return zero, ie, operator will never return TRUE. + */ + if (IsA(other, Const) && + ((Const *) other)->constisnull) + { + ReleaseVariableStats(vardata); + return 0.0; + } + + if (IsA(other, Const)) + { + /* Variable is being compared to a known non-null constant */ + Datum constval = ((Const *) other)->constvalue; + FmgrInfo opproc; + double mcvsum; + double mcvsel; + double nullfrac; + int hist_size; + + fmgr_info(get_opcode(oproid), &opproc); + + /* + * Calculate the selectivity for the column's most common values. + */ + mcvsel = mcv_selectivity(&vardata, &opproc, collation, + constval, varonleft, + &mcvsum); + + /* + * If the histogram is large enough, see what fraction of it matches + * the query, and assume that's representative of the non-MCV + * population. Otherwise use the default selectivity for the non-MCV + * population. + */ + selec = histogram_selectivity(&vardata, &opproc, collation, + constval, varonleft, + 10, 1, &hist_size); + if (selec < 0) + { + /* Nope, fall back on default */ + selec = default_selectivity; + } + else if (hist_size < 100) + { + /* + * For histogram sizes from 10 to 100, we combine the histogram + * and default selectivities, putting increasingly more trust in + * the histogram for larger sizes. + */ + double hist_weight = hist_size / 100.0; + + selec = selec * hist_weight + + default_selectivity * (1.0 - hist_weight); + } + + /* In any case, don't believe extremely small or large estimates. */ + if (selec < 0.0001) + selec = 0.0001; + else if (selec > 0.9999) + selec = 0.9999; + + /* Don't forget to account for nulls. */ + if (HeapTupleIsValid(vardata.statsTuple)) + nullfrac = ((Form_pg_statistic) GETSTRUCT(vardata.statsTuple))->stanullfrac; + else + nullfrac = 0.0; + + /* + * Now merge the results from the MCV and histogram calculations, + * realizing that the histogram covers only the non-null values that + * are not listed in MCV. + */ + selec *= 1.0 - nullfrac - mcvsum; + selec += mcvsel; + } + else + { + /* Comparison value is not constant, so we can't do anything */ + selec = default_selectivity; + } + + ReleaseVariableStats(vardata); + + /* result should be in range, but make sure... */ + CLAMP_PROBABILITY(selec); + + return selec; +} + +/* + * ineq_histogram_selectivity - Examine the histogram for scalarineqsel + * + * Determine the fraction of the variable's histogram population that + * satisfies the inequality condition, ie, VAR < (or <=, >, >=) CONST. + * The isgt and iseq flags distinguish which of the four cases apply. + * + * While opproc could be looked up from the operator OID, common callers + * also need to call it separately, so we make the caller pass both. + * + * Returns -1 if there is no histogram (valid results will always be >= 0). + * + * Note that the result disregards both the most-common-values (if any) and + * null entries. The caller is expected to combine this result with + * statistics for those portions of the column population. + * + * This is exported so that some other estimation functions can use it. + */ +double +ineq_histogram_selectivity(PlannerInfo *root, + VariableStatData *vardata, + Oid opoid, FmgrInfo *opproc, bool isgt, bool iseq, + Oid collation, + Datum constval, Oid consttype) +{ + double hist_selec; + AttStatsSlot sslot; + + hist_selec = -1.0; + + /* + * Someday, ANALYZE might store more than one histogram per rel/att, + * corresponding to more than one possible sort ordering defined for the + * column type. Right now, we know there is only one, so just grab it and + * see if it matches the query. + * + * Note that we can't use opoid as search argument; the staop appearing in + * pg_statistic will be for the relevant '<' operator, but what we have + * might be some other inequality operator such as '>='. (Even if opoid + * is a '<' operator, it could be cross-type.) Hence we must use + * comparison_ops_are_compatible() to see if the operators match. + */ + if (HeapTupleIsValid(vardata->statsTuple) && + statistic_proc_security_check(vardata, opproc->fn_oid) && + get_attstatsslot(&sslot, vardata->statsTuple, + STATISTIC_KIND_HISTOGRAM, InvalidOid, + ATTSTATSSLOT_VALUES)) + { + if (sslot.nvalues > 1 && + sslot.stacoll == collation && + comparison_ops_are_compatible(sslot.staop, opoid)) + { + /* + * Use binary search to find the desired location, namely the + * right end of the histogram bin containing the comparison value, + * which is the leftmost entry for which the comparison operator + * succeeds (if isgt) or fails (if !isgt). + * + * In this loop, we pay no attention to whether the operator iseq + * or not; that detail will be mopped up below. (We cannot tell, + * anyway, whether the operator thinks the values are equal.) + * + * If the binary search accesses the first or last histogram + * entry, we try to replace that endpoint with the true column min + * or max as found by get_actual_variable_range(). This + * ameliorates misestimates when the min or max is moving as a + * result of changes since the last ANALYZE. Note that this could + * result in effectively including MCVs into the histogram that + * weren't there before, but we don't try to correct for that. + */ + double histfrac; + int lobound = 0; /* first possible slot to search */ + int hibound = sslot.nvalues; /* last+1 slot to search */ + bool have_end = false; + + /* + * If there are only two histogram entries, we'll want up-to-date + * values for both. (If there are more than two, we need at most + * one of them to be updated, so we deal with that within the + * loop.) + */ + if (sslot.nvalues == 2) + have_end = get_actual_variable_range(root, + vardata, + sslot.staop, + collation, + &sslot.values[0], + &sslot.values[1]); + + while (lobound < hibound) + { + int probe = (lobound + hibound) / 2; + bool ltcmp; + + /* + * If we find ourselves about to compare to the first or last + * histogram entry, first try to replace it with the actual + * current min or max (unless we already did so above). + */ + if (probe == 0 && sslot.nvalues > 2) + have_end = get_actual_variable_range(root, + vardata, + sslot.staop, + collation, + &sslot.values[0], + NULL); + else if (probe == sslot.nvalues - 1 && sslot.nvalues > 2) + have_end = get_actual_variable_range(root, + vardata, + sslot.staop, + collation, + NULL, + &sslot.values[probe]); + + ltcmp = DatumGetBool(FunctionCall2Coll(opproc, + collation, + sslot.values[probe], + constval)); + if (isgt) + ltcmp = !ltcmp; + if (ltcmp) + lobound = probe + 1; + else + hibound = probe; + } + + if (lobound <= 0) + { + /* + * Constant is below lower histogram boundary. More + * precisely, we have found that no entry in the histogram + * satisfies the inequality clause (if !isgt) or they all do + * (if isgt). We estimate that that's true of the entire + * table, so set histfrac to 0.0 (which we'll flip to 1.0 + * below, if isgt). + */ + histfrac = 0.0; + } + else if (lobound >= sslot.nvalues) + { + /* + * Inverse case: constant is above upper histogram boundary. + */ + histfrac = 1.0; + } + else + { + /* We have values[i-1] <= constant <= values[i]. */ + int i = lobound; + double eq_selec = 0; + double val, + high, + low; + double binfrac; + + /* + * In the cases where we'll need it below, obtain an estimate + * of the selectivity of "x = constval". We use a calculation + * similar to what var_eq_const() does for a non-MCV constant, + * ie, estimate that all distinct non-MCV values occur equally + * often. But multiplication by "1.0 - sumcommon - nullfrac" + * will be done by our caller, so we shouldn't do that here. + * Therefore we can't try to clamp the estimate by reference + * to the least common MCV; the result would be too small. + * + * Note: since this is effectively assuming that constval + * isn't an MCV, it's logically dubious if constval in fact is + * one. But we have to apply *some* correction for equality, + * and anyway we cannot tell if constval is an MCV, since we + * don't have a suitable equality operator at hand. + */ + if (i == 1 || isgt == iseq) + { + double otherdistinct; + bool isdefault; + AttStatsSlot mcvslot; + + /* Get estimated number of distinct values */ + otherdistinct = get_variable_numdistinct(vardata, + &isdefault); + + /* Subtract off the number of known MCVs */ + if (get_attstatsslot(&mcvslot, vardata->statsTuple, + STATISTIC_KIND_MCV, InvalidOid, + ATTSTATSSLOT_NUMBERS)) + { + otherdistinct -= mcvslot.nnumbers; + free_attstatsslot(&mcvslot); + } + + /* If result doesn't seem sane, leave eq_selec at 0 */ + if (otherdistinct > 1) + eq_selec = 1.0 / otherdistinct; + } + + /* + * Convert the constant and the two nearest bin boundary + * values to a uniform comparison scale, and do a linear + * interpolation within this bin. + */ + if (convert_to_scalar(constval, consttype, collation, + &val, + sslot.values[i - 1], sslot.values[i], + vardata->vartype, + &low, &high)) + { + if (high <= low) + { + /* cope if bin boundaries appear identical */ + binfrac = 0.5; + } + else if (val <= low) + binfrac = 0.0; + else if (val >= high) + binfrac = 1.0; + else + { + binfrac = (val - low) / (high - low); + + /* + * Watch out for the possibility that we got a NaN or + * Infinity from the division. This can happen + * despite the previous checks, if for example "low" + * is -Infinity. + */ + if (isnan(binfrac) || + binfrac < 0.0 || binfrac > 1.0) + binfrac = 0.5; + } + } + else + { + /* + * Ideally we'd produce an error here, on the grounds that + * the given operator shouldn't have scalarXXsel + * registered as its selectivity func unless we can deal + * with its operand types. But currently, all manner of + * stuff is invoking scalarXXsel, so give a default + * estimate until that can be fixed. + */ + binfrac = 0.5; + } + + /* + * Now, compute the overall selectivity across the values + * represented by the histogram. We have i-1 full bins and + * binfrac partial bin below the constant. + */ + histfrac = (double) (i - 1) + binfrac; + histfrac /= (double) (sslot.nvalues - 1); + + /* + * At this point, histfrac is an estimate of the fraction of + * the population represented by the histogram that satisfies + * "x <= constval". Somewhat remarkably, this statement is + * true regardless of which operator we were doing the probes + * with, so long as convert_to_scalar() delivers reasonable + * results. If the probe constant is equal to some histogram + * entry, we would have considered the bin to the left of that + * entry if probing with "<" or ">=", or the bin to the right + * if probing with "<=" or ">"; but binfrac would have come + * out as 1.0 in the first case and 0.0 in the second, leading + * to the same histfrac in either case. For probe constants + * between histogram entries, we find the same bin and get the + * same estimate with any operator. + * + * The fact that the estimate corresponds to "x <= constval" + * and not "x < constval" is because of the way that ANALYZE + * constructs the histogram: each entry is, effectively, the + * rightmost value in its sample bucket. So selectivity + * values that are exact multiples of 1/(histogram_size-1) + * should be understood as estimates including a histogram + * entry plus everything to its left. + * + * However, that breaks down for the first histogram entry, + * which necessarily is the leftmost value in its sample + * bucket. That means the first histogram bin is slightly + * narrower than the rest, by an amount equal to eq_selec. + * Another way to say that is that we want "x <= leftmost" to + * be estimated as eq_selec not zero. So, if we're dealing + * with the first bin (i==1), rescale to make that true while + * adjusting the rest of that bin linearly. + */ + if (i == 1) + histfrac += eq_selec * (1.0 - binfrac); + + /* + * "x <= constval" is good if we want an estimate for "<=" or + * ">", but if we are estimating for "<" or ">=", we now need + * to decrease the estimate by eq_selec. + */ + if (isgt == iseq) + histfrac -= eq_selec; + } + + /* + * Now the estimate is finished for "<" and "<=" cases. If we are + * estimating for ">" or ">=", flip it. + */ + hist_selec = isgt ? (1.0 - histfrac) : histfrac; + + /* + * The histogram boundaries are only approximate to begin with, + * and may well be out of date anyway. Therefore, don't believe + * extremely small or large selectivity estimates --- unless we + * got actual current endpoint values from the table, in which + * case just do the usual sanity clamp. Somewhat arbitrarily, we + * set the cutoff for other cases at a hundredth of the histogram + * resolution. + */ + if (have_end) + CLAMP_PROBABILITY(hist_selec); + else + { + double cutoff = 0.01 / (double) (sslot.nvalues - 1); + + if (hist_selec < cutoff) + hist_selec = cutoff; + else if (hist_selec > 1.0 - cutoff) + hist_selec = 1.0 - cutoff; + } + } + else if (sslot.nvalues > 1) + { + /* + * If we get here, we have a histogram but it's not sorted the way + * we want. Do a brute-force search to see how many of the + * entries satisfy the comparison condition, and take that + * fraction as our estimate. (This is identical to the inner loop + * of histogram_selectivity; maybe share code?) + */ + LOCAL_FCINFO(fcinfo, 2); + int nmatch = 0; + + InitFunctionCallInfoData(*fcinfo, opproc, 2, collation, + NULL, NULL); + fcinfo->args[0].isnull = false; + fcinfo->args[1].isnull = false; + fcinfo->args[1].value = constval; + for (int i = 0; i < sslot.nvalues; i++) + { + Datum fresult; + + fcinfo->args[0].value = sslot.values[i]; + fcinfo->isnull = false; + fresult = FunctionCallInvoke(fcinfo); + if (!fcinfo->isnull && DatumGetBool(fresult)) + nmatch++; + } + hist_selec = ((double) nmatch) / ((double) sslot.nvalues); + + /* + * As above, clamp to a hundredth of the histogram resolution. + * This case is surely even less trustworthy than the normal one, + * so we shouldn't believe exact 0 or 1 selectivity. (Maybe the + * clamp should be more restrictive in this case?) + */ + { + double cutoff = 0.01 / (double) (sslot.nvalues - 1); + + if (hist_selec < cutoff) + hist_selec = cutoff; + else if (hist_selec > 1.0 - cutoff) + hist_selec = 1.0 - cutoff; + } + } + + free_attstatsslot(&sslot); + } + + return hist_selec; +} + +/* + * Common wrapper function for the selectivity estimators that simply + * invoke scalarineqsel(). + */ +static Datum +scalarineqsel_wrapper(PG_FUNCTION_ARGS, bool isgt, bool iseq) +{ + PlannerInfo *root = (PlannerInfo *) PG_GETARG_POINTER(0); + Oid operator = PG_GETARG_OID(1); + List *args = (List *) PG_GETARG_POINTER(2); + int varRelid = PG_GETARG_INT32(3); + Oid collation = PG_GET_COLLATION(); + VariableStatData vardata; + Node *other; + bool varonleft; + Datum constval; + Oid consttype; + double selec; + + /* + * If expression is not variable op something or something op variable, + * then punt and return a default estimate. + */ + if (!get_restriction_variable(root, args, varRelid, + &vardata, &other, &varonleft)) + PG_RETURN_FLOAT8(DEFAULT_INEQ_SEL); + + /* + * Can't do anything useful if the something is not a constant, either. + */ + if (!IsA(other, Const)) + { + ReleaseVariableStats(vardata); + PG_RETURN_FLOAT8(DEFAULT_INEQ_SEL); + } + + /* + * If the constant is NULL, assume operator is strict and return zero, ie, + * operator will never return TRUE. + */ + if (((Const *) other)->constisnull) + { + ReleaseVariableStats(vardata); + PG_RETURN_FLOAT8(0.0); + } + constval = ((Const *) other)->constvalue; + consttype = ((Const *) other)->consttype; + + /* + * Force the var to be on the left to simplify logic in scalarineqsel. + */ + if (!varonleft) + { + operator = get_commutator(operator); + if (!operator) + { + /* Use default selectivity (should we raise an error instead?) */ + ReleaseVariableStats(vardata); + PG_RETURN_FLOAT8(DEFAULT_INEQ_SEL); + } + isgt = !isgt; + } + + /* The rest of the work is done by scalarineqsel(). */ + selec = scalarineqsel(root, operator, isgt, iseq, collation, + &vardata, constval, consttype); + + ReleaseVariableStats(vardata); + + PG_RETURN_FLOAT8((float8) selec); +} + +/* + * scalarltsel - Selectivity of "<" for scalars. + */ +Datum +scalarltsel(PG_FUNCTION_ARGS) +{ + return scalarineqsel_wrapper(fcinfo, false, false); +} + +/* + * scalarlesel - Selectivity of "<=" for scalars. + */ +Datum +scalarlesel(PG_FUNCTION_ARGS) +{ + return scalarineqsel_wrapper(fcinfo, false, true); +} + +/* + * scalargtsel - Selectivity of ">" for scalars. + */ +Datum +scalargtsel(PG_FUNCTION_ARGS) +{ + return scalarineqsel_wrapper(fcinfo, true, false); +} + +/* + * scalargesel - Selectivity of ">=" for scalars. + */ +Datum +scalargesel(PG_FUNCTION_ARGS) +{ + return scalarineqsel_wrapper(fcinfo, true, true); +} + +/* + * boolvarsel - Selectivity of Boolean variable. + * + * This can actually be called on any boolean-valued expression. If it + * involves only Vars of the specified relation, and if there are statistics + * about the Var or expression (the latter is possible if it's indexed) then + * we'll produce a real estimate; otherwise it's just a default. + */ +Selectivity +boolvarsel(PlannerInfo *root, Node *arg, int varRelid) +{ + VariableStatData vardata; + double selec; + + examine_variable(root, arg, varRelid, &vardata); + if (HeapTupleIsValid(vardata.statsTuple)) + { + /* + * A boolean variable V is equivalent to the clause V = 't', so we + * compute the selectivity as if that is what we have. + */ + selec = var_eq_const(&vardata, BooleanEqualOperator, InvalidOid, + BoolGetDatum(true), false, true, false); + } + else + { + /* Otherwise, the default estimate is 0.5 */ + selec = 0.5; + } + ReleaseVariableStats(vardata); + return selec; +} + +/* + * booltestsel - Selectivity of BooleanTest Node. + */ +Selectivity +booltestsel(PlannerInfo *root, BoolTestType booltesttype, Node *arg, + int varRelid, JoinType jointype, SpecialJoinInfo *sjinfo) +{ + VariableStatData vardata; + double selec; + + examine_variable(root, arg, varRelid, &vardata); + + if (HeapTupleIsValid(vardata.statsTuple)) + { + Form_pg_statistic stats; + double freq_null; + AttStatsSlot sslot; + + stats = (Form_pg_statistic) GETSTRUCT(vardata.statsTuple); + freq_null = stats->stanullfrac; + + if (get_attstatsslot(&sslot, vardata.statsTuple, + STATISTIC_KIND_MCV, InvalidOid, + ATTSTATSSLOT_VALUES | ATTSTATSSLOT_NUMBERS) + && sslot.nnumbers > 0) + { + double freq_true; + double freq_false; + + /* + * Get first MCV frequency and derive frequency for true. + */ + if (DatumGetBool(sslot.values[0])) + freq_true = sslot.numbers[0]; + else + freq_true = 1.0 - sslot.numbers[0] - freq_null; + + /* + * Next derive frequency for false. Then use these as appropriate + * to derive frequency for each case. + */ + freq_false = 1.0 - freq_true - freq_null; + + switch (booltesttype) + { + case IS_UNKNOWN: + /* select only NULL values */ + selec = freq_null; + break; + case IS_NOT_UNKNOWN: + /* select non-NULL values */ + selec = 1.0 - freq_null; + break; + case IS_TRUE: + /* select only TRUE values */ + selec = freq_true; + break; + case IS_NOT_TRUE: + /* select non-TRUE values */ + selec = 1.0 - freq_true; + break; + case IS_FALSE: + /* select only FALSE values */ + selec = freq_false; + break; + case IS_NOT_FALSE: + /* select non-FALSE values */ + selec = 1.0 - freq_false; + break; + default: + elog(ERROR, "unrecognized booltesttype: %d", + (int) booltesttype); + selec = 0.0; /* Keep compiler quiet */ + break; + } + + free_attstatsslot(&sslot); + } + else + { + /* + * No most-common-value info available. Still have null fraction + * information, so use it for IS [NOT] UNKNOWN. Otherwise adjust + * for null fraction and assume a 50-50 split of TRUE and FALSE. + */ + switch (booltesttype) + { + case IS_UNKNOWN: + /* select only NULL values */ + selec = freq_null; + break; + case IS_NOT_UNKNOWN: + /* select non-NULL values */ + selec = 1.0 - freq_null; + break; + case IS_TRUE: + case IS_FALSE: + /* Assume we select half of the non-NULL values */ + selec = (1.0 - freq_null) / 2.0; + break; + case IS_NOT_TRUE: + case IS_NOT_FALSE: + /* Assume we select NULLs plus half of the non-NULLs */ + /* equiv. to freq_null + (1.0 - freq_null) / 2.0 */ + selec = (freq_null + 1.0) / 2.0; + break; + default: + elog(ERROR, "unrecognized booltesttype: %d", + (int) booltesttype); + selec = 0.0; /* Keep compiler quiet */ + break; + } + } + } + else + { + /* + * If we can't get variable statistics for the argument, perhaps + * clause_selectivity can do something with it. We ignore the + * possibility of a NULL value when using clause_selectivity, and just + * assume the value is either TRUE or FALSE. + */ + switch (booltesttype) + { + case IS_UNKNOWN: + selec = DEFAULT_UNK_SEL; + break; + case IS_NOT_UNKNOWN: + selec = DEFAULT_NOT_UNK_SEL; + break; + case IS_TRUE: + case IS_NOT_FALSE: + selec = (double) clause_selectivity(root, arg, + varRelid, + jointype, sjinfo); + break; + case IS_FALSE: + case IS_NOT_TRUE: + selec = 1.0 - (double) clause_selectivity(root, arg, + varRelid, + jointype, sjinfo); + break; + default: + elog(ERROR, "unrecognized booltesttype: %d", + (int) booltesttype); + selec = 0.0; /* Keep compiler quiet */ + break; + } + } + + ReleaseVariableStats(vardata); + + /* result should be in range, but make sure... */ + CLAMP_PROBABILITY(selec); + + return (Selectivity) selec; +} + +/* + * nulltestsel - Selectivity of NullTest Node. + */ +Selectivity +nulltestsel(PlannerInfo *root, NullTestType nulltesttype, Node *arg, + int varRelid, JoinType jointype, SpecialJoinInfo *sjinfo) +{ + VariableStatData vardata; + double selec; + + examine_variable(root, arg, varRelid, &vardata); + + if (HeapTupleIsValid(vardata.statsTuple)) + { + Form_pg_statistic stats; + double freq_null; + + stats = (Form_pg_statistic) GETSTRUCT(vardata.statsTuple); + freq_null = stats->stanullfrac; + + switch (nulltesttype) + { + case IS_NULL: + + /* + * Use freq_null directly. + */ + selec = freq_null; + break; + case IS_NOT_NULL: + + /* + * Select not unknown (not null) values. Calculate from + * freq_null. + */ + selec = 1.0 - freq_null; + break; + default: + elog(ERROR, "unrecognized nulltesttype: %d", + (int) nulltesttype); + return (Selectivity) 0; /* keep compiler quiet */ + } + } + else if (vardata.var && IsA(vardata.var, Var) && + ((Var *) vardata.var)->varattno < 0) + { + /* + * There are no stats for system columns, but we know they are never + * NULL. + */ + selec = (nulltesttype == IS_NULL) ? 0.0 : 1.0; + } + else + { + /* + * No ANALYZE stats available, so make a guess + */ + switch (nulltesttype) + { + case IS_NULL: + selec = DEFAULT_UNK_SEL; + break; + case IS_NOT_NULL: + selec = DEFAULT_NOT_UNK_SEL; + break; + default: + elog(ERROR, "unrecognized nulltesttype: %d", + (int) nulltesttype); + return (Selectivity) 0; /* keep compiler quiet */ + } + } + + ReleaseVariableStats(vardata); + + /* result should be in range, but make sure... */ + CLAMP_PROBABILITY(selec); + + return (Selectivity) selec; +} + +/* + * strip_array_coercion - strip binary-compatible relabeling from an array expr + * + * For array values, the parser normally generates ArrayCoerceExpr conversions, + * but it seems possible that RelabelType might show up. Also, the planner + * is not currently tense about collapsing stacked ArrayCoerceExpr nodes, + * so we need to be ready to deal with more than one level. + */ +static Node * +strip_array_coercion(Node *node) +{ + for (;;) + { + if (node && IsA(node, ArrayCoerceExpr)) + { + ArrayCoerceExpr *acoerce = (ArrayCoerceExpr *) node; + + /* + * If the per-element expression is just a RelabelType on top of + * CaseTestExpr, then we know it's a binary-compatible relabeling. + */ + if (IsA(acoerce->elemexpr, RelabelType) && + IsA(((RelabelType *) acoerce->elemexpr)->arg, CaseTestExpr)) + node = (Node *) acoerce->arg; + else + break; + } + else if (node && IsA(node, RelabelType)) + { + /* We don't really expect this case, but may as well cope */ + node = (Node *) ((RelabelType *) node)->arg; + } + else + break; + } + return node; +} + +/* + * scalararraysel - Selectivity of ScalarArrayOpExpr Node. + */ +Selectivity +scalararraysel(PlannerInfo *root, + ScalarArrayOpExpr *clause, + bool is_join_clause, + int varRelid, + JoinType jointype, + SpecialJoinInfo *sjinfo) +{ + Oid operator = clause->opno; + bool useOr = clause->useOr; + bool isEquality = false; + bool isInequality = false; + Node *leftop; + Node *rightop; + Oid nominal_element_type; + Oid nominal_element_collation; + TypeCacheEntry *typentry; + RegProcedure oprsel; + FmgrInfo oprselproc; + Selectivity s1; + Selectivity s1disjoint; + + /* First, deconstruct the expression */ + Assert(list_length(clause->args) == 2); + leftop = (Node *) linitial(clause->args); + rightop = (Node *) lsecond(clause->args); + + /* aggressively reduce both sides to constants */ + leftop = estimate_expression_value(root, leftop); + rightop = estimate_expression_value(root, rightop); + + /* get nominal (after relabeling) element type of rightop */ + nominal_element_type = get_base_element_type(exprType(rightop)); + if (!OidIsValid(nominal_element_type)) + return (Selectivity) 0.5; /* probably shouldn't happen */ + /* get nominal collation, too, for generating constants */ + nominal_element_collation = exprCollation(rightop); + + /* look through any binary-compatible relabeling of rightop */ + rightop = strip_array_coercion(rightop); + + /* + * Detect whether the operator is the default equality or inequality + * operator of the array element type. + */ + typentry = lookup_type_cache(nominal_element_type, TYPECACHE_EQ_OPR); + if (OidIsValid(typentry->eq_opr)) + { + if (operator == typentry->eq_opr) + isEquality = true; + else if (get_negator(operator) == typentry->eq_opr) + isInequality = true; + } + + /* + * If it is equality or inequality, we might be able to estimate this as a + * form of array containment; for instance "const = ANY(column)" can be + * treated as "ARRAY[const] <@ column". scalararraysel_containment tries + * that, and returns the selectivity estimate if successful, or -1 if not. + */ + if ((isEquality || isInequality) && !is_join_clause) + { + s1 = scalararraysel_containment(root, leftop, rightop, + nominal_element_type, + isEquality, useOr, varRelid); + if (s1 >= 0.0) + return s1; + } + + /* + * Look up the underlying operator's selectivity estimator. Punt if it + * hasn't got one. + */ + if (is_join_clause) + oprsel = get_oprjoin(operator); + else + oprsel = get_oprrest(operator); + if (!oprsel) + return (Selectivity) 0.5; + fmgr_info(oprsel, &oprselproc); + + /* + * In the array-containment check above, we must only believe that an + * operator is equality or inequality if it is the default btree equality + * operator (or its negator) for the element type, since those are the + * operators that array containment will use. But in what follows, we can + * be a little laxer, and also believe that any operators using eqsel() or + * neqsel() as selectivity estimator act like equality or inequality. + */ + if (oprsel == F_EQSEL || oprsel == F_EQJOINSEL) + isEquality = true; + else if (oprsel == F_NEQSEL || oprsel == F_NEQJOINSEL) + isInequality = true; + + /* + * We consider three cases: + * + * 1. rightop is an Array constant: deconstruct the array, apply the + * operator's selectivity function for each array element, and merge the + * results in the same way that clausesel.c does for AND/OR combinations. + * + * 2. rightop is an ARRAY[] construct: apply the operator's selectivity + * function for each element of the ARRAY[] construct, and merge. + * + * 3. otherwise, make a guess ... + */ + if (rightop && IsA(rightop, Const)) + { + Datum arraydatum = ((Const *) rightop)->constvalue; + bool arrayisnull = ((Const *) rightop)->constisnull; + ArrayType *arrayval; + int16 elmlen; + bool elmbyval; + char elmalign; + int num_elems; + Datum *elem_values; + bool *elem_nulls; + int i; + + if (arrayisnull) /* qual can't succeed if null array */ + return (Selectivity) 0.0; + arrayval = DatumGetArrayTypeP(arraydatum); + get_typlenbyvalalign(ARR_ELEMTYPE(arrayval), + &elmlen, &elmbyval, &elmalign); + deconstruct_array(arrayval, + ARR_ELEMTYPE(arrayval), + elmlen, elmbyval, elmalign, + &elem_values, &elem_nulls, &num_elems); + + /* + * For generic operators, we assume the probability of success is + * independent for each array element. But for "= ANY" or "<> ALL", + * if the array elements are distinct (which'd typically be the case) + * then the probabilities are disjoint, and we should just sum them. + * + * If we were being really tense we would try to confirm that the + * elements are all distinct, but that would be expensive and it + * doesn't seem to be worth the cycles; it would amount to penalizing + * well-written queries in favor of poorly-written ones. However, we + * do protect ourselves a little bit by checking whether the + * disjointness assumption leads to an impossible (out of range) + * probability; if so, we fall back to the normal calculation. + */ + s1 = s1disjoint = (useOr ? 0.0 : 1.0); + + for (i = 0; i < num_elems; i++) + { + List *args; + Selectivity s2; + + args = list_make2(leftop, + makeConst(nominal_element_type, + -1, + nominal_element_collation, + elmlen, + elem_values[i], + elem_nulls[i], + elmbyval)); + if (is_join_clause) + s2 = DatumGetFloat8(FunctionCall5Coll(&oprselproc, + clause->inputcollid, + PointerGetDatum(root), + ObjectIdGetDatum(operator), + PointerGetDatum(args), + Int16GetDatum(jointype), + PointerGetDatum(sjinfo))); + else + s2 = DatumGetFloat8(FunctionCall4Coll(&oprselproc, + clause->inputcollid, + PointerGetDatum(root), + ObjectIdGetDatum(operator), + PointerGetDatum(args), + Int32GetDatum(varRelid))); + + if (useOr) + { + s1 = s1 + s2 - s1 * s2; + if (isEquality) + s1disjoint += s2; + } + else + { + s1 = s1 * s2; + if (isInequality) + s1disjoint += s2 - 1.0; + } + } + + /* accept disjoint-probability estimate if in range */ + if ((useOr ? isEquality : isInequality) && + s1disjoint >= 0.0 && s1disjoint <= 1.0) + s1 = s1disjoint; + } + else if (rightop && IsA(rightop, ArrayExpr) && + !((ArrayExpr *) rightop)->multidims) + { + ArrayExpr *arrayexpr = (ArrayExpr *) rightop; + int16 elmlen; + bool elmbyval; + ListCell *l; + + get_typlenbyval(arrayexpr->element_typeid, + &elmlen, &elmbyval); + + /* + * We use the assumption of disjoint probabilities here too, although + * the odds of equal array elements are rather higher if the elements + * are not all constants (which they won't be, else constant folding + * would have reduced the ArrayExpr to a Const). In this path it's + * critical to have the sanity check on the s1disjoint estimate. + */ + s1 = s1disjoint = (useOr ? 0.0 : 1.0); + + foreach(l, arrayexpr->elements) + { + Node *elem = (Node *) lfirst(l); + List *args; + Selectivity s2; + + /* + * Theoretically, if elem isn't of nominal_element_type we should + * insert a RelabelType, but it seems unlikely that any operator + * estimation function would really care ... + */ + args = list_make2(leftop, elem); + if (is_join_clause) + s2 = DatumGetFloat8(FunctionCall5Coll(&oprselproc, + clause->inputcollid, + PointerGetDatum(root), + ObjectIdGetDatum(operator), + PointerGetDatum(args), + Int16GetDatum(jointype), + PointerGetDatum(sjinfo))); + else + s2 = DatumGetFloat8(FunctionCall4Coll(&oprselproc, + clause->inputcollid, + PointerGetDatum(root), + ObjectIdGetDatum(operator), + PointerGetDatum(args), + Int32GetDatum(varRelid))); + + if (useOr) + { + s1 = s1 + s2 - s1 * s2; + if (isEquality) + s1disjoint += s2; + } + else + { + s1 = s1 * s2; + if (isInequality) + s1disjoint += s2 - 1.0; + } + } + + /* accept disjoint-probability estimate if in range */ + if ((useOr ? isEquality : isInequality) && + s1disjoint >= 0.0 && s1disjoint <= 1.0) + s1 = s1disjoint; + } + else + { + CaseTestExpr *dummyexpr; + List *args; + Selectivity s2; + int i; + + /* + * We need a dummy rightop to pass to the operator selectivity + * routine. It can be pretty much anything that doesn't look like a + * constant; CaseTestExpr is a convenient choice. + */ + dummyexpr = makeNode(CaseTestExpr); + dummyexpr->typeId = nominal_element_type; + dummyexpr->typeMod = -1; + dummyexpr->collation = clause->inputcollid; + args = list_make2(leftop, dummyexpr); + if (is_join_clause) + s2 = DatumGetFloat8(FunctionCall5Coll(&oprselproc, + clause->inputcollid, + PointerGetDatum(root), + ObjectIdGetDatum(operator), + PointerGetDatum(args), + Int16GetDatum(jointype), + PointerGetDatum(sjinfo))); + else + s2 = DatumGetFloat8(FunctionCall4Coll(&oprselproc, + clause->inputcollid, + PointerGetDatum(root), + ObjectIdGetDatum(operator), + PointerGetDatum(args), + Int32GetDatum(varRelid))); + s1 = useOr ? 0.0 : 1.0; + + /* + * Arbitrarily assume 10 elements in the eventual array value (see + * also estimate_array_length). We don't risk an assumption of + * disjoint probabilities here. + */ + for (i = 0; i < 10; i++) + { + if (useOr) + s1 = s1 + s2 - s1 * s2; + else + s1 = s1 * s2; + } + } + + /* result should be in range, but make sure... */ + CLAMP_PROBABILITY(s1); + + return s1; +} + +/* + * Estimate number of elements in the array yielded by an expression. + * + * It's important that this agree with scalararraysel. + */ +int +estimate_array_length(Node *arrayexpr) +{ + /* look through any binary-compatible relabeling of arrayexpr */ + arrayexpr = strip_array_coercion(arrayexpr); + + if (arrayexpr && IsA(arrayexpr, Const)) + { + Datum arraydatum = ((Const *) arrayexpr)->constvalue; + bool arrayisnull = ((Const *) arrayexpr)->constisnull; + ArrayType *arrayval; + + if (arrayisnull) + return 0; + arrayval = DatumGetArrayTypeP(arraydatum); + return ArrayGetNItems(ARR_NDIM(arrayval), ARR_DIMS(arrayval)); + } + else if (arrayexpr && IsA(arrayexpr, ArrayExpr) && + !((ArrayExpr *) arrayexpr)->multidims) + { + return list_length(((ArrayExpr *) arrayexpr)->elements); + } + else + { + /* default guess --- see also scalararraysel */ + return 10; + } +} + +/* + * rowcomparesel - Selectivity of RowCompareExpr Node. + * + * We estimate RowCompare selectivity by considering just the first (high + * order) columns, which makes it equivalent to an ordinary OpExpr. While + * this estimate could be refined by considering additional columns, it + * seems unlikely that we could do a lot better without multi-column + * statistics. + */ +Selectivity +rowcomparesel(PlannerInfo *root, + RowCompareExpr *clause, + int varRelid, JoinType jointype, SpecialJoinInfo *sjinfo) +{ + Selectivity s1; + Oid opno = linitial_oid(clause->opnos); + Oid inputcollid = linitial_oid(clause->inputcollids); + List *opargs; + bool is_join_clause; + + /* Build equivalent arg list for single operator */ + opargs = list_make2(linitial(clause->largs), linitial(clause->rargs)); + + /* + * Decide if it's a join clause. This should match clausesel.c's + * treat_as_join_clause(), except that we intentionally consider only the + * leading columns and not the rest of the clause. + */ + if (varRelid != 0) + { + /* + * Caller is forcing restriction mode (eg, because we are examining an + * inner indexscan qual). + */ + is_join_clause = false; + } + else if (sjinfo == NULL) + { + /* + * It must be a restriction clause, since it's being evaluated at a + * scan node. + */ + is_join_clause = false; + } + else + { + /* + * Otherwise, it's a join if there's more than one base relation used. + */ + is_join_clause = (NumRelids(root, (Node *) opargs) > 1); + } + + if (is_join_clause) + { + /* Estimate selectivity for a join clause. */ + s1 = join_selectivity(root, opno, + opargs, + inputcollid, + jointype, + sjinfo); + } + else + { + /* Estimate selectivity for a restriction clause. */ + s1 = restriction_selectivity(root, opno, + opargs, + inputcollid, + varRelid); + } + + return s1; +} + +/* + * eqjoinsel - Join selectivity of "=" + */ +Datum +eqjoinsel(PG_FUNCTION_ARGS) +{ + PlannerInfo *root = (PlannerInfo *) PG_GETARG_POINTER(0); + Oid operator = PG_GETARG_OID(1); + List *args = (List *) PG_GETARG_POINTER(2); + +#ifdef NOT_USED + JoinType jointype = (JoinType) PG_GETARG_INT16(3); +#endif + SpecialJoinInfo *sjinfo = (SpecialJoinInfo *) PG_GETARG_POINTER(4); + Oid collation = PG_GET_COLLATION(); + double selec; + double selec_inner; + VariableStatData vardata1; + VariableStatData vardata2; + double nd1; + double nd2; + bool isdefault1; + bool isdefault2; + Oid opfuncoid; + AttStatsSlot sslot1; + AttStatsSlot sslot2; + Form_pg_statistic stats1 = NULL; + Form_pg_statistic stats2 = NULL; + bool have_mcvs1 = false; + bool have_mcvs2 = false; + bool get_mcv_stats; + bool join_is_reversed; + RelOptInfo *inner_rel; + + get_join_variables(root, args, sjinfo, + &vardata1, &vardata2, &join_is_reversed); + + nd1 = get_variable_numdistinct(&vardata1, &isdefault1); + nd2 = get_variable_numdistinct(&vardata2, &isdefault2); + + opfuncoid = get_opcode(operator); + + memset(&sslot1, 0, sizeof(sslot1)); + memset(&sslot2, 0, sizeof(sslot2)); + + /* + * There is no use in fetching one side's MCVs if we lack MCVs for the + * other side, so do a quick check to verify that both stats exist. + */ + get_mcv_stats = (HeapTupleIsValid(vardata1.statsTuple) && + HeapTupleIsValid(vardata2.statsTuple) && + get_attstatsslot(&sslot1, vardata1.statsTuple, + STATISTIC_KIND_MCV, InvalidOid, + 0) && + get_attstatsslot(&sslot2, vardata2.statsTuple, + STATISTIC_KIND_MCV, InvalidOid, + 0)); + + if (HeapTupleIsValid(vardata1.statsTuple)) + { + /* note we allow use of nullfrac regardless of security check */ + stats1 = (Form_pg_statistic) GETSTRUCT(vardata1.statsTuple); + if (get_mcv_stats && + statistic_proc_security_check(&vardata1, opfuncoid)) + have_mcvs1 = get_attstatsslot(&sslot1, vardata1.statsTuple, + STATISTIC_KIND_MCV, InvalidOid, + ATTSTATSSLOT_VALUES | ATTSTATSSLOT_NUMBERS); + } + + if (HeapTupleIsValid(vardata2.statsTuple)) + { + /* note we allow use of nullfrac regardless of security check */ + stats2 = (Form_pg_statistic) GETSTRUCT(vardata2.statsTuple); + if (get_mcv_stats && + statistic_proc_security_check(&vardata2, opfuncoid)) + have_mcvs2 = get_attstatsslot(&sslot2, vardata2.statsTuple, + STATISTIC_KIND_MCV, InvalidOid, + ATTSTATSSLOT_VALUES | ATTSTATSSLOT_NUMBERS); + } + + /* We need to compute the inner-join selectivity in all cases */ + selec_inner = eqjoinsel_inner(opfuncoid, collation, + &vardata1, &vardata2, + nd1, nd2, + isdefault1, isdefault2, + &sslot1, &sslot2, + stats1, stats2, + have_mcvs1, have_mcvs2); + + switch (sjinfo->jointype) + { + case JOIN_INNER: + case JOIN_LEFT: + case JOIN_FULL: + selec = selec_inner; + break; + case JOIN_SEMI: + case JOIN_ANTI: + + /* + * Look up the join's inner relation. min_righthand is sufficient + * information because neither SEMI nor ANTI joins permit any + * reassociation into or out of their RHS, so the righthand will + * always be exactly that set of rels. + */ + inner_rel = find_join_input_rel(root, sjinfo->min_righthand); + + if (!join_is_reversed) + selec = eqjoinsel_semi(opfuncoid, collation, + &vardata1, &vardata2, + nd1, nd2, + isdefault1, isdefault2, + &sslot1, &sslot2, + stats1, stats2, + have_mcvs1, have_mcvs2, + inner_rel); + else + { + Oid commop = get_commutator(operator); + Oid commopfuncoid = OidIsValid(commop) ? get_opcode(commop) : InvalidOid; + + selec = eqjoinsel_semi(commopfuncoid, collation, + &vardata2, &vardata1, + nd2, nd1, + isdefault2, isdefault1, + &sslot2, &sslot1, + stats2, stats1, + have_mcvs2, have_mcvs1, + inner_rel); + } + + /* + * We should never estimate the output of a semijoin to be more + * rows than we estimate for an inner join with the same input + * rels and join condition; it's obviously impossible for that to + * happen. The former estimate is N1 * Ssemi while the latter is + * N1 * N2 * Sinner, so we may clamp Ssemi <= N2 * Sinner. Doing + * this is worthwhile because of the shakier estimation rules we + * use in eqjoinsel_semi, particularly in cases where it has to + * punt entirely. + */ + selec = Min(selec, inner_rel->rows * selec_inner); + break; + default: + /* other values not expected here */ + elog(ERROR, "unrecognized join type: %d", + (int) sjinfo->jointype); + selec = 0; /* keep compiler quiet */ + break; + } + + free_attstatsslot(&sslot1); + free_attstatsslot(&sslot2); + + ReleaseVariableStats(vardata1); + ReleaseVariableStats(vardata2); + + CLAMP_PROBABILITY(selec); + + PG_RETURN_FLOAT8((float8) selec); +} + +/* + * eqjoinsel_inner --- eqjoinsel for normal inner join + * + * We also use this for LEFT/FULL outer joins; it's not presently clear + * that it's worth trying to distinguish them here. + */ +static double +eqjoinsel_inner(Oid opfuncoid, Oid collation, + VariableStatData *vardata1, VariableStatData *vardata2, + double nd1, double nd2, + bool isdefault1, bool isdefault2, + AttStatsSlot *sslot1, AttStatsSlot *sslot2, + Form_pg_statistic stats1, Form_pg_statistic stats2, + bool have_mcvs1, bool have_mcvs2) +{ + double selec; + + if (have_mcvs1 && have_mcvs2) + { + /* + * We have most-common-value lists for both relations. Run through + * the lists to see which MCVs actually join to each other with the + * given operator. This allows us to determine the exact join + * selectivity for the portion of the relations represented by the MCV + * lists. We still have to estimate for the remaining population, but + * in a skewed distribution this gives us a big leg up in accuracy. + * For motivation see the analysis in Y. Ioannidis and S. + * Christodoulakis, "On the propagation of errors in the size of join + * results", Technical Report 1018, Computer Science Dept., University + * of Wisconsin, Madison, March 1991 (available from ftp.cs.wisc.edu). + */ + LOCAL_FCINFO(fcinfo, 2); + FmgrInfo eqproc; + bool *hasmatch1; + bool *hasmatch2; + double nullfrac1 = stats1->stanullfrac; + double nullfrac2 = stats2->stanullfrac; + double matchprodfreq, + matchfreq1, + matchfreq2, + unmatchfreq1, + unmatchfreq2, + otherfreq1, + otherfreq2, + totalsel1, + totalsel2; + int i, + nmatches; + + fmgr_info(opfuncoid, &eqproc); + + /* + * Save a few cycles by setting up the fcinfo struct just once. Using + * FunctionCallInvoke directly also avoids failure if the eqproc + * returns NULL, though really equality functions should never do + * that. + */ + InitFunctionCallInfoData(*fcinfo, &eqproc, 2, collation, + NULL, NULL); + fcinfo->args[0].isnull = false; + fcinfo->args[1].isnull = false; + + hasmatch1 = (bool *) palloc0(sslot1->nvalues * sizeof(bool)); + hasmatch2 = (bool *) palloc0(sslot2->nvalues * sizeof(bool)); + + /* + * Note we assume that each MCV will match at most one member of the + * other MCV list. If the operator isn't really equality, there could + * be multiple matches --- but we don't look for them, both for speed + * and because the math wouldn't add up... + */ + matchprodfreq = 0.0; + nmatches = 0; + for (i = 0; i < sslot1->nvalues; i++) + { + int j; + + fcinfo->args[0].value = sslot1->values[i]; + + for (j = 0; j < sslot2->nvalues; j++) + { + Datum fresult; + + if (hasmatch2[j]) + continue; + fcinfo->args[1].value = sslot2->values[j]; + fcinfo->isnull = false; + fresult = FunctionCallInvoke(fcinfo); + if (!fcinfo->isnull && DatumGetBool(fresult)) + { + hasmatch1[i] = hasmatch2[j] = true; + matchprodfreq += sslot1->numbers[i] * sslot2->numbers[j]; + nmatches++; + break; + } + } + } + CLAMP_PROBABILITY(matchprodfreq); + /* Sum up frequencies of matched and unmatched MCVs */ + matchfreq1 = unmatchfreq1 = 0.0; + for (i = 0; i < sslot1->nvalues; i++) + { + if (hasmatch1[i]) + matchfreq1 += sslot1->numbers[i]; + else + unmatchfreq1 += sslot1->numbers[i]; + } + CLAMP_PROBABILITY(matchfreq1); + CLAMP_PROBABILITY(unmatchfreq1); + matchfreq2 = unmatchfreq2 = 0.0; + for (i = 0; i < sslot2->nvalues; i++) + { + if (hasmatch2[i]) + matchfreq2 += sslot2->numbers[i]; + else + unmatchfreq2 += sslot2->numbers[i]; + } + CLAMP_PROBABILITY(matchfreq2); + CLAMP_PROBABILITY(unmatchfreq2); + pfree(hasmatch1); + pfree(hasmatch2); + + /* + * Compute total frequency of non-null values that are not in the MCV + * lists. + */ + otherfreq1 = 1.0 - nullfrac1 - matchfreq1 - unmatchfreq1; + otherfreq2 = 1.0 - nullfrac2 - matchfreq2 - unmatchfreq2; + CLAMP_PROBABILITY(otherfreq1); + CLAMP_PROBABILITY(otherfreq2); + + /* + * We can estimate the total selectivity from the point of view of + * relation 1 as: the known selectivity for matched MCVs, plus + * unmatched MCVs that are assumed to match against random members of + * relation 2's non-MCV population, plus non-MCV values that are + * assumed to match against random members of relation 2's unmatched + * MCVs plus non-MCV values. + */ + totalsel1 = matchprodfreq; + if (nd2 > sslot2->nvalues) + totalsel1 += unmatchfreq1 * otherfreq2 / (nd2 - sslot2->nvalues); + if (nd2 > nmatches) + totalsel1 += otherfreq1 * (otherfreq2 + unmatchfreq2) / + (nd2 - nmatches); + /* Same estimate from the point of view of relation 2. */ + totalsel2 = matchprodfreq; + if (nd1 > sslot1->nvalues) + totalsel2 += unmatchfreq2 * otherfreq1 / (nd1 - sslot1->nvalues); + if (nd1 > nmatches) + totalsel2 += otherfreq2 * (otherfreq1 + unmatchfreq1) / + (nd1 - nmatches); + + /* + * Use the smaller of the two estimates. This can be justified in + * essentially the same terms as given below for the no-stats case: to + * a first approximation, we are estimating from the point of view of + * the relation with smaller nd. + */ + selec = (totalsel1 < totalsel2) ? totalsel1 : totalsel2; + } + else + { + /* + * We do not have MCV lists for both sides. Estimate the join + * selectivity as MIN(1/nd1,1/nd2)*(1-nullfrac1)*(1-nullfrac2). This + * is plausible if we assume that the join operator is strict and the + * non-null values are about equally distributed: a given non-null + * tuple of rel1 will join to either zero or N2*(1-nullfrac2)/nd2 rows + * of rel2, so total join rows are at most + * N1*(1-nullfrac1)*N2*(1-nullfrac2)/nd2 giving a join selectivity of + * not more than (1-nullfrac1)*(1-nullfrac2)/nd2. By the same logic it + * is not more than (1-nullfrac1)*(1-nullfrac2)/nd1, so the expression + * with MIN() is an upper bound. Using the MIN() means we estimate + * from the point of view of the relation with smaller nd (since the + * larger nd is determining the MIN). It is reasonable to assume that + * most tuples in this rel will have join partners, so the bound is + * probably reasonably tight and should be taken as-is. + * + * XXX Can we be smarter if we have an MCV list for just one side? It + * seems that if we assume equal distribution for the other side, we + * end up with the same answer anyway. + */ + double nullfrac1 = stats1 ? stats1->stanullfrac : 0.0; + double nullfrac2 = stats2 ? stats2->stanullfrac : 0.0; + + selec = (1.0 - nullfrac1) * (1.0 - nullfrac2); + if (nd1 > nd2) + selec /= nd1; + else + selec /= nd2; + } + + return selec; +} + +/* + * eqjoinsel_semi --- eqjoinsel for semi join + * + * (Also used for anti join, which we are supposed to estimate the same way.) + * Caller has ensured that vardata1 is the LHS variable. + * Unlike eqjoinsel_inner, we have to cope with opfuncoid being InvalidOid. + */ +static double +eqjoinsel_semi(Oid opfuncoid, Oid collation, + VariableStatData *vardata1, VariableStatData *vardata2, + double nd1, double nd2, + bool isdefault1, bool isdefault2, + AttStatsSlot *sslot1, AttStatsSlot *sslot2, + Form_pg_statistic stats1, Form_pg_statistic stats2, + bool have_mcvs1, bool have_mcvs2, + RelOptInfo *inner_rel) +{ + double selec; + + /* + * We clamp nd2 to be not more than what we estimate the inner relation's + * size to be. This is intuitively somewhat reasonable since obviously + * there can't be more than that many distinct values coming from the + * inner rel. The reason for the asymmetry (ie, that we don't clamp nd1 + * likewise) is that this is the only pathway by which restriction clauses + * applied to the inner rel will affect the join result size estimate, + * since set_joinrel_size_estimates will multiply SEMI/ANTI selectivity by + * only the outer rel's size. If we clamped nd1 we'd be double-counting + * the selectivity of outer-rel restrictions. + * + * We can apply this clamping both with respect to the base relation from + * which the join variable comes (if there is just one), and to the + * immediate inner input relation of the current join. + * + * If we clamp, we can treat nd2 as being a non-default estimate; it's not + * great, maybe, but it didn't come out of nowhere either. This is most + * helpful when the inner relation is empty and consequently has no stats. + */ + if (vardata2->rel) + { + if (nd2 >= vardata2->rel->rows) + { + nd2 = vardata2->rel->rows; + isdefault2 = false; + } + } + if (nd2 >= inner_rel->rows) + { + nd2 = inner_rel->rows; + isdefault2 = false; + } + + if (have_mcvs1 && have_mcvs2 && OidIsValid(opfuncoid)) + { + /* + * We have most-common-value lists for both relations. Run through + * the lists to see which MCVs actually join to each other with the + * given operator. This allows us to determine the exact join + * selectivity for the portion of the relations represented by the MCV + * lists. We still have to estimate for the remaining population, but + * in a skewed distribution this gives us a big leg up in accuracy. + */ + LOCAL_FCINFO(fcinfo, 2); + FmgrInfo eqproc; + bool *hasmatch1; + bool *hasmatch2; + double nullfrac1 = stats1->stanullfrac; + double matchfreq1, + uncertainfrac, + uncertain; + int i, + nmatches, + clamped_nvalues2; + + /* + * The clamping above could have resulted in nd2 being less than + * sslot2->nvalues; in which case, we assume that precisely the nd2 + * most common values in the relation will appear in the join input, + * and so compare to only the first nd2 members of the MCV list. Of + * course this is frequently wrong, but it's the best bet we can make. + */ + clamped_nvalues2 = Min(sslot2->nvalues, nd2); + + fmgr_info(opfuncoid, &eqproc); + + /* + * Save a few cycles by setting up the fcinfo struct just once. Using + * FunctionCallInvoke directly also avoids failure if the eqproc + * returns NULL, though really equality functions should never do + * that. + */ + InitFunctionCallInfoData(*fcinfo, &eqproc, 2, collation, + NULL, NULL); + fcinfo->args[0].isnull = false; + fcinfo->args[1].isnull = false; + + hasmatch1 = (bool *) palloc0(sslot1->nvalues * sizeof(bool)); + hasmatch2 = (bool *) palloc0(clamped_nvalues2 * sizeof(bool)); + + /* + * Note we assume that each MCV will match at most one member of the + * other MCV list. If the operator isn't really equality, there could + * be multiple matches --- but we don't look for them, both for speed + * and because the math wouldn't add up... + */ + nmatches = 0; + for (i = 0; i < sslot1->nvalues; i++) + { + int j; + + fcinfo->args[0].value = sslot1->values[i]; + + for (j = 0; j < clamped_nvalues2; j++) + { + Datum fresult; + + if (hasmatch2[j]) + continue; + fcinfo->args[1].value = sslot2->values[j]; + fcinfo->isnull = false; + fresult = FunctionCallInvoke(fcinfo); + if (!fcinfo->isnull && DatumGetBool(fresult)) + { + hasmatch1[i] = hasmatch2[j] = true; + nmatches++; + break; + } + } + } + /* Sum up frequencies of matched MCVs */ + matchfreq1 = 0.0; + for (i = 0; i < sslot1->nvalues; i++) + { + if (hasmatch1[i]) + matchfreq1 += sslot1->numbers[i]; + } + CLAMP_PROBABILITY(matchfreq1); + pfree(hasmatch1); + pfree(hasmatch2); + + /* + * Now we need to estimate the fraction of relation 1 that has at + * least one join partner. We know for certain that the matched MCVs + * do, so that gives us a lower bound, but we're really in the dark + * about everything else. Our crude approach is: if nd1 <= nd2 then + * assume all non-null rel1 rows have join partners, else assume for + * the uncertain rows that a fraction nd2/nd1 have join partners. We + * can discount the known-matched MCVs from the distinct-values counts + * before doing the division. + * + * Crude as the above is, it's completely useless if we don't have + * reliable ndistinct values for both sides. Hence, if either nd1 or + * nd2 is default, punt and assume half of the uncertain rows have + * join partners. + */ + if (!isdefault1 && !isdefault2) + { + nd1 -= nmatches; + nd2 -= nmatches; + if (nd1 <= nd2 || nd2 < 0) + uncertainfrac = 1.0; + else + uncertainfrac = nd2 / nd1; + } + else + uncertainfrac = 0.5; + uncertain = 1.0 - matchfreq1 - nullfrac1; + CLAMP_PROBABILITY(uncertain); + selec = matchfreq1 + uncertainfrac * uncertain; + } + else + { + /* + * Without MCV lists for both sides, we can only use the heuristic + * about nd1 vs nd2. + */ + double nullfrac1 = stats1 ? stats1->stanullfrac : 0.0; + + if (!isdefault1 && !isdefault2) + { + if (nd1 <= nd2 || nd2 < 0) + selec = 1.0 - nullfrac1; + else + selec = (nd2 / nd1) * (1.0 - nullfrac1); + } + else + selec = 0.5 * (1.0 - nullfrac1); + } + + return selec; +} + +/* + * neqjoinsel - Join selectivity of "!=" + */ +Datum +neqjoinsel(PG_FUNCTION_ARGS) +{ + PlannerInfo *root = (PlannerInfo *) PG_GETARG_POINTER(0); + Oid operator = PG_GETARG_OID(1); + List *args = (List *) PG_GETARG_POINTER(2); + JoinType jointype = (JoinType) PG_GETARG_INT16(3); + SpecialJoinInfo *sjinfo = (SpecialJoinInfo *) PG_GETARG_POINTER(4); + Oid collation = PG_GET_COLLATION(); + float8 result; + + if (jointype == JOIN_SEMI || jointype == JOIN_ANTI) + { + /* + * For semi-joins, if there is more than one distinct value in the RHS + * relation then every non-null LHS row must find a row to join since + * it can only be equal to one of them. We'll assume that there is + * always more than one distinct RHS value for the sake of stability, + * though in theory we could have special cases for empty RHS + * (selectivity = 0) and single-distinct-value RHS (selectivity = + * fraction of LHS that has the same value as the single RHS value). + * + * For anti-joins, if we use the same assumption that there is more + * than one distinct key in the RHS relation, then every non-null LHS + * row must be suppressed by the anti-join. + * + * So either way, the selectivity estimate should be 1 - nullfrac. + */ + VariableStatData leftvar; + VariableStatData rightvar; + bool reversed; + HeapTuple statsTuple; + double nullfrac; + + get_join_variables(root, args, sjinfo, &leftvar, &rightvar, &reversed); + statsTuple = reversed ? rightvar.statsTuple : leftvar.statsTuple; + if (HeapTupleIsValid(statsTuple)) + nullfrac = ((Form_pg_statistic) GETSTRUCT(statsTuple))->stanullfrac; + else + nullfrac = 0.0; + ReleaseVariableStats(leftvar); + ReleaseVariableStats(rightvar); + + result = 1.0 - nullfrac; + } + else + { + /* + * We want 1 - eqjoinsel() where the equality operator is the one + * associated with this != operator, that is, its negator. + */ + Oid eqop = get_negator(operator); + + if (eqop) + { + result = + DatumGetFloat8(DirectFunctionCall5Coll(eqjoinsel, + collation, + PointerGetDatum(root), + ObjectIdGetDatum(eqop), + PointerGetDatum(args), + Int16GetDatum(jointype), + PointerGetDatum(sjinfo))); + } + else + { + /* Use default selectivity (should we raise an error instead?) */ + result = DEFAULT_EQ_SEL; + } + result = 1.0 - result; + } + + PG_RETURN_FLOAT8(result); +} + +/* + * scalarltjoinsel - Join selectivity of "<" for scalars + */ +Datum +scalarltjoinsel(PG_FUNCTION_ARGS) +{ + PG_RETURN_FLOAT8(DEFAULT_INEQ_SEL); +} + +/* + * scalarlejoinsel - Join selectivity of "<=" for scalars + */ +Datum +scalarlejoinsel(PG_FUNCTION_ARGS) +{ + PG_RETURN_FLOAT8(DEFAULT_INEQ_SEL); +} + +/* + * scalargtjoinsel - Join selectivity of ">" for scalars + */ +Datum +scalargtjoinsel(PG_FUNCTION_ARGS) +{ + PG_RETURN_FLOAT8(DEFAULT_INEQ_SEL); +} + +/* + * scalargejoinsel - Join selectivity of ">=" for scalars + */ +Datum +scalargejoinsel(PG_FUNCTION_ARGS) +{ + PG_RETURN_FLOAT8(DEFAULT_INEQ_SEL); +} + + +/* + * mergejoinscansel - Scan selectivity of merge join. + * + * A merge join will stop as soon as it exhausts either input stream. + * Therefore, if we can estimate the ranges of both input variables, + * we can estimate how much of the input will actually be read. This + * can have a considerable impact on the cost when using indexscans. + * + * Also, we can estimate how much of each input has to be read before the + * first join pair is found, which will affect the join's startup time. + * + * clause should be a clause already known to be mergejoinable. opfamily, + * strategy, and nulls_first specify the sort ordering being used. + * + * The outputs are: + * *leftstart is set to the fraction of the left-hand variable expected + * to be scanned before the first join pair is found (0 to 1). + * *leftend is set to the fraction of the left-hand variable expected + * to be scanned before the join terminates (0 to 1). + * *rightstart, *rightend similarly for the right-hand variable. + */ +void +mergejoinscansel(PlannerInfo *root, Node *clause, + Oid opfamily, int strategy, bool nulls_first, + Selectivity *leftstart, Selectivity *leftend, + Selectivity *rightstart, Selectivity *rightend) +{ + Node *left, + *right; + VariableStatData leftvar, + rightvar; + int op_strategy; + Oid op_lefttype; + Oid op_righttype; + Oid opno, + collation, + lsortop, + rsortop, + lstatop, + rstatop, + ltop, + leop, + revltop, + revleop; + bool isgt; + Datum leftmin, + leftmax, + rightmin, + rightmax; + double selec; + + /* Set default results if we can't figure anything out. */ + /* XXX should default "start" fraction be a bit more than 0? */ + *leftstart = *rightstart = 0.0; + *leftend = *rightend = 1.0; + + /* Deconstruct the merge clause */ + if (!is_opclause(clause)) + return; /* shouldn't happen */ + opno = ((OpExpr *) clause)->opno; + collation = ((OpExpr *) clause)->inputcollid; + left = get_leftop((Expr *) clause); + right = get_rightop((Expr *) clause); + if (!right) + return; /* shouldn't happen */ + + /* Look for stats for the inputs */ + examine_variable(root, left, 0, &leftvar); + examine_variable(root, right, 0, &rightvar); + + /* Extract the operator's declared left/right datatypes */ + get_op_opfamily_properties(opno, opfamily, false, + &op_strategy, + &op_lefttype, + &op_righttype); + Assert(op_strategy == BTEqualStrategyNumber); + + /* + * Look up the various operators we need. If we don't find them all, it + * probably means the opfamily is broken, but we just fail silently. + * + * Note: we expect that pg_statistic histograms will be sorted by the '<' + * operator, regardless of which sort direction we are considering. + */ + switch (strategy) + { + case BTLessStrategyNumber: + isgt = false; + if (op_lefttype == op_righttype) + { + /* easy case */ + ltop = get_opfamily_member(opfamily, + op_lefttype, op_righttype, + BTLessStrategyNumber); + leop = get_opfamily_member(opfamily, + op_lefttype, op_righttype, + BTLessEqualStrategyNumber); + lsortop = ltop; + rsortop = ltop; + lstatop = lsortop; + rstatop = rsortop; + revltop = ltop; + revleop = leop; + } + else + { + ltop = get_opfamily_member(opfamily, + op_lefttype, op_righttype, + BTLessStrategyNumber); + leop = get_opfamily_member(opfamily, + op_lefttype, op_righttype, + BTLessEqualStrategyNumber); + lsortop = get_opfamily_member(opfamily, + op_lefttype, op_lefttype, + BTLessStrategyNumber); + rsortop = get_opfamily_member(opfamily, + op_righttype, op_righttype, + BTLessStrategyNumber); + lstatop = lsortop; + rstatop = rsortop; + revltop = get_opfamily_member(opfamily, + op_righttype, op_lefttype, + BTLessStrategyNumber); + revleop = get_opfamily_member(opfamily, + op_righttype, op_lefttype, + BTLessEqualStrategyNumber); + } + break; + case BTGreaterStrategyNumber: + /* descending-order case */ + isgt = true; + if (op_lefttype == op_righttype) + { + /* easy case */ + ltop = get_opfamily_member(opfamily, + op_lefttype, op_righttype, + BTGreaterStrategyNumber); + leop = get_opfamily_member(opfamily, + op_lefttype, op_righttype, + BTGreaterEqualStrategyNumber); + lsortop = ltop; + rsortop = ltop; + lstatop = get_opfamily_member(opfamily, + op_lefttype, op_lefttype, + BTLessStrategyNumber); + rstatop = lstatop; + revltop = ltop; + revleop = leop; + } + else + { + ltop = get_opfamily_member(opfamily, + op_lefttype, op_righttype, + BTGreaterStrategyNumber); + leop = get_opfamily_member(opfamily, + op_lefttype, op_righttype, + BTGreaterEqualStrategyNumber); + lsortop = get_opfamily_member(opfamily, + op_lefttype, op_lefttype, + BTGreaterStrategyNumber); + rsortop = get_opfamily_member(opfamily, + op_righttype, op_righttype, + BTGreaterStrategyNumber); + lstatop = get_opfamily_member(opfamily, + op_lefttype, op_lefttype, + BTLessStrategyNumber); + rstatop = get_opfamily_member(opfamily, + op_righttype, op_righttype, + BTLessStrategyNumber); + revltop = get_opfamily_member(opfamily, + op_righttype, op_lefttype, + BTGreaterStrategyNumber); + revleop = get_opfamily_member(opfamily, + op_righttype, op_lefttype, + BTGreaterEqualStrategyNumber); + } + break; + default: + goto fail; /* shouldn't get here */ + } + + if (!OidIsValid(lsortop) || + !OidIsValid(rsortop) || + !OidIsValid(lstatop) || + !OidIsValid(rstatop) || + !OidIsValid(ltop) || + !OidIsValid(leop) || + !OidIsValid(revltop) || + !OidIsValid(revleop)) + goto fail; /* insufficient info in catalogs */ + + /* Try to get ranges of both inputs */ + if (!isgt) + { + if (!get_variable_range(root, &leftvar, lstatop, collation, + &leftmin, &leftmax)) + goto fail; /* no range available from stats */ + if (!get_variable_range(root, &rightvar, rstatop, collation, + &rightmin, &rightmax)) + goto fail; /* no range available from stats */ + } + else + { + /* need to swap the max and min */ + if (!get_variable_range(root, &leftvar, lstatop, collation, + &leftmax, &leftmin)) + goto fail; /* no range available from stats */ + if (!get_variable_range(root, &rightvar, rstatop, collation, + &rightmax, &rightmin)) + goto fail; /* no range available from stats */ + } + + /* + * Now, the fraction of the left variable that will be scanned is the + * fraction that's <= the right-side maximum value. But only believe + * non-default estimates, else stick with our 1.0. + */ + selec = scalarineqsel(root, leop, isgt, true, collation, &leftvar, + rightmax, op_righttype); + if (selec != DEFAULT_INEQ_SEL) + *leftend = selec; + + /* And similarly for the right variable. */ + selec = scalarineqsel(root, revleop, isgt, true, collation, &rightvar, + leftmax, op_lefttype); + if (selec != DEFAULT_INEQ_SEL) + *rightend = selec; + + /* + * Only one of the two "end" fractions can really be less than 1.0; + * believe the smaller estimate and reset the other one to exactly 1.0. If + * we get exactly equal estimates (as can easily happen with self-joins), + * believe neither. + */ + if (*leftend > *rightend) + *leftend = 1.0; + else if (*leftend < *rightend) + *rightend = 1.0; + else + *leftend = *rightend = 1.0; + + /* + * Also, the fraction of the left variable that will be scanned before the + * first join pair is found is the fraction that's < the right-side + * minimum value. But only believe non-default estimates, else stick with + * our own default. + */ + selec = scalarineqsel(root, ltop, isgt, false, collation, &leftvar, + rightmin, op_righttype); + if (selec != DEFAULT_INEQ_SEL) + *leftstart = selec; + + /* And similarly for the right variable. */ + selec = scalarineqsel(root, revltop, isgt, false, collation, &rightvar, + leftmin, op_lefttype); + if (selec != DEFAULT_INEQ_SEL) + *rightstart = selec; + + /* + * Only one of the two "start" fractions can really be more than zero; + * believe the larger estimate and reset the other one to exactly 0.0. If + * we get exactly equal estimates (as can easily happen with self-joins), + * believe neither. + */ + if (*leftstart < *rightstart) + *leftstart = 0.0; + else if (*leftstart > *rightstart) + *rightstart = 0.0; + else + *leftstart = *rightstart = 0.0; + + /* + * If the sort order is nulls-first, we're going to have to skip over any + * nulls too. These would not have been counted by scalarineqsel, and we + * can safely add in this fraction regardless of whether we believe + * scalarineqsel's results or not. But be sure to clamp the sum to 1.0! + */ + if (nulls_first) + { + Form_pg_statistic stats; + + if (HeapTupleIsValid(leftvar.statsTuple)) + { + stats = (Form_pg_statistic) GETSTRUCT(leftvar.statsTuple); + *leftstart += stats->stanullfrac; + CLAMP_PROBABILITY(*leftstart); + *leftend += stats->stanullfrac; + CLAMP_PROBABILITY(*leftend); + } + if (HeapTupleIsValid(rightvar.statsTuple)) + { + stats = (Form_pg_statistic) GETSTRUCT(rightvar.statsTuple); + *rightstart += stats->stanullfrac; + CLAMP_PROBABILITY(*rightstart); + *rightend += stats->stanullfrac; + CLAMP_PROBABILITY(*rightend); + } + } + + /* Disbelieve start >= end, just in case that can happen */ + if (*leftstart >= *leftend) + { + *leftstart = 0.0; + *leftend = 1.0; + } + if (*rightstart >= *rightend) + { + *rightstart = 0.0; + *rightend = 1.0; + } + +fail: + ReleaseVariableStats(leftvar); + ReleaseVariableStats(rightvar); +} + + +/* + * matchingsel -- generic matching-operator selectivity support + * + * Use these for any operators that (a) are on data types for which we collect + * standard statistics, and (b) have behavior for which the default estimate + * (twice DEFAULT_EQ_SEL) is sane. Typically that is good for match-like + * operators. + */ + +Datum +matchingsel(PG_FUNCTION_ARGS) +{ + PlannerInfo *root = (PlannerInfo *) PG_GETARG_POINTER(0); + Oid operator = PG_GETARG_OID(1); + List *args = (List *) PG_GETARG_POINTER(2); + int varRelid = PG_GETARG_INT32(3); + Oid collation = PG_GET_COLLATION(); + double selec; + + /* Use generic restriction selectivity logic. */ + selec = generic_restriction_selectivity(root, operator, collation, + args, varRelid, + DEFAULT_MATCHING_SEL); + + PG_RETURN_FLOAT8((float8) selec); +} + +Datum +matchingjoinsel(PG_FUNCTION_ARGS) +{ + /* Just punt, for the moment. */ + PG_RETURN_FLOAT8(DEFAULT_MATCHING_SEL); +} + + +/* + * Helper routine for estimate_num_groups: add an item to a list of + * GroupVarInfos, but only if it's not known equal to any of the existing + * entries. + */ +typedef struct +{ + Node *var; /* might be an expression, not just a Var */ + RelOptInfo *rel; /* relation it belongs to */ + double ndistinct; /* # distinct values */ + bool isdefault; /* true if DEFAULT_NUM_DISTINCT was used */ +} GroupVarInfo; + +static List * +add_unique_group_var(PlannerInfo *root, List *varinfos, + Node *var, VariableStatData *vardata) +{ + GroupVarInfo *varinfo; + double ndistinct; + bool isdefault; + ListCell *lc; + + ndistinct = get_variable_numdistinct(vardata, &isdefault); + + foreach(lc, varinfos) + { + varinfo = (GroupVarInfo *) lfirst(lc); + + /* Drop exact duplicates */ + if (equal(var, varinfo->var)) + return varinfos; + + /* + * Drop known-equal vars, but only if they belong to different + * relations (see comments for estimate_num_groups) + */ + if (vardata->rel != varinfo->rel && + exprs_known_equal(root, var, varinfo->var)) + { + if (varinfo->ndistinct <= ndistinct) + { + /* Keep older item, forget new one */ + return varinfos; + } + else + { + /* Delete the older item */ + varinfos = foreach_delete_current(varinfos, lc); + } + } + } + + varinfo = (GroupVarInfo *) palloc(sizeof(GroupVarInfo)); + + varinfo->var = var; + varinfo->rel = vardata->rel; + varinfo->ndistinct = ndistinct; + varinfo->isdefault = isdefault; + varinfos = lappend(varinfos, varinfo); + return varinfos; +} + +/* + * estimate_num_groups - Estimate number of groups in a grouped query + * + * Given a query having a GROUP BY clause, estimate how many groups there + * will be --- ie, the number of distinct combinations of the GROUP BY + * expressions. + * + * This routine is also used to estimate the number of rows emitted by + * a DISTINCT filtering step; that is an isomorphic problem. (Note: + * actually, we only use it for DISTINCT when there's no grouping or + * aggregation ahead of the DISTINCT.) + * + * Inputs: + * root - the query + * groupExprs - list of expressions being grouped by + * input_rows - number of rows estimated to arrive at the group/unique + * filter step + * pgset - NULL, or a List** pointing to a grouping set to filter the + * groupExprs against + * + * Outputs: + * estinfo - When passed as non-NULL, the function will set bits in the + * "flags" field in order to provide callers with additional information + * about the estimation. Currently, we only set the SELFLAG_USED_DEFAULT + * bit if we used any default values in the estimation. + * + * Given the lack of any cross-correlation statistics in the system, it's + * impossible to do anything really trustworthy with GROUP BY conditions + * involving multiple Vars. We should however avoid assuming the worst + * case (all possible cross-product terms actually appear as groups) since + * very often the grouped-by Vars are highly correlated. Our current approach + * is as follows: + * 1. Expressions yielding boolean are assumed to contribute two groups, + * independently of their content, and are ignored in the subsequent + * steps. This is mainly because tests like "col IS NULL" break the + * heuristic used in step 2 especially badly. + * 2. Reduce the given expressions to a list of unique Vars used. For + * example, GROUP BY a, a + b is treated the same as GROUP BY a, b. + * It is clearly correct not to count the same Var more than once. + * It is also reasonable to treat f(x) the same as x: f() cannot + * increase the number of distinct values (unless it is volatile, + * which we consider unlikely for grouping), but it probably won't + * reduce the number of distinct values much either. + * As a special case, if a GROUP BY expression can be matched to an + * expressional index for which we have statistics, then we treat the + * whole expression as though it were just a Var. + * 3. If the list contains Vars of different relations that are known equal + * due to equivalence classes, then drop all but one of the Vars from each + * known-equal set, keeping the one with smallest estimated # of values + * (since the extra values of the others can't appear in joined rows). + * Note the reason we only consider Vars of different relations is that + * if we considered ones of the same rel, we'd be double-counting the + * restriction selectivity of the equality in the next step. + * 4. For Vars within a single source rel, we multiply together the numbers + * of values, clamp to the number of rows in the rel (divided by 10 if + * more than one Var), and then multiply by a factor based on the + * selectivity of the restriction clauses for that rel. When there's + * more than one Var, the initial product is probably too high (it's the + * worst case) but clamping to a fraction of the rel's rows seems to be a + * helpful heuristic for not letting the estimate get out of hand. (The + * factor of 10 is derived from pre-Postgres-7.4 practice.) The factor + * we multiply by to adjust for the restriction selectivity assumes that + * the restriction clauses are independent of the grouping, which may not + * be a valid assumption, but it's hard to do better. + * 5. If there are Vars from multiple rels, we repeat step 4 for each such + * rel, and multiply the results together. + * Note that rels not containing grouped Vars are ignored completely, as are + * join clauses. Such rels cannot increase the number of groups, and we + * assume such clauses do not reduce the number either (somewhat bogus, + * but we don't have the info to do better). + */ +double +estimate_num_groups(PlannerInfo *root, List *groupExprs, double input_rows, + List **pgset, EstimationInfo *estinfo) +{ + List *varinfos = NIL; + double srf_multiplier = 1.0; + double numdistinct; + ListCell *l; + int i; + + /* Zero the estinfo output parameter, if non-NULL */ + if (estinfo != NULL) + memset(estinfo, 0, sizeof(EstimationInfo)); + + /* + * We don't ever want to return an estimate of zero groups, as that tends + * to lead to division-by-zero and other unpleasantness. The input_rows + * estimate is usually already at least 1, but clamp it just in case it + * isn't. + */ + input_rows = clamp_row_est(input_rows); + + /* + * If no grouping columns, there's exactly one group. (This can't happen + * for normal cases with GROUP BY or DISTINCT, but it is possible for + * corner cases with set operations.) + */ + if (groupExprs == NIL || (pgset && *pgset == NIL)) + return 1.0; + + /* + * Count groups derived from boolean grouping expressions. For other + * expressions, find the unique Vars used, treating an expression as a Var + * if we can find stats for it. For each one, record the statistical + * estimate of number of distinct values (total in its table, without + * regard for filtering). + */ + numdistinct = 1.0; + + i = 0; + foreach(l, groupExprs) + { + Node *groupexpr = (Node *) lfirst(l); + double this_srf_multiplier; + VariableStatData vardata; + List *varshere; + ListCell *l2; + + /* is expression in this grouping set? */ + if (pgset && !list_member_int(*pgset, i++)) + continue; + + /* + * Set-returning functions in grouping columns are a bit problematic. + * The code below will effectively ignore their SRF nature and come up + * with a numdistinct estimate as though they were scalar functions. + * We compensate by scaling up the end result by the largest SRF + * rowcount estimate. (This will be an overestimate if the SRF + * produces multiple copies of any output value, but it seems best to + * assume the SRF's outputs are distinct. In any case, it's probably + * pointless to worry too much about this without much better + * estimates for SRF output rowcounts than we have today.) + */ + this_srf_multiplier = expression_returns_set_rows(root, groupexpr); + if (srf_multiplier < this_srf_multiplier) + srf_multiplier = this_srf_multiplier; + + /* Short-circuit for expressions returning boolean */ + if (exprType(groupexpr) == BOOLOID) + { + numdistinct *= 2.0; + continue; + } + + /* + * If examine_variable is able to deduce anything about the GROUP BY + * expression, treat it as a single variable even if it's really more + * complicated. + * + * XXX This has the consequence that if there's a statistics object on + * the expression, we don't split it into individual Vars. This + * affects our selection of statistics in + * estimate_multivariate_ndistinct, because it's probably better to + * use more accurate estimate for each expression and treat them as + * independent, than to combine estimates for the extracted variables + * when we don't know how that relates to the expressions. + */ + examine_variable(root, groupexpr, 0, &vardata); + if (HeapTupleIsValid(vardata.statsTuple) || vardata.isunique) + { + varinfos = add_unique_group_var(root, varinfos, + groupexpr, &vardata); + ReleaseVariableStats(vardata); + continue; + } + ReleaseVariableStats(vardata); + + /* + * Else pull out the component Vars. Handle PlaceHolderVars by + * recursing into their arguments (effectively assuming that the + * PlaceHolderVar doesn't change the number of groups, which boils + * down to ignoring the possible addition of nulls to the result set). + */ + varshere = pull_var_clause(groupexpr, + PVC_RECURSE_AGGREGATES | + PVC_RECURSE_WINDOWFUNCS | + PVC_RECURSE_PLACEHOLDERS); + + /* + * If we find any variable-free GROUP BY item, then either it is a + * constant (and we can ignore it) or it contains a volatile function; + * in the latter case we punt and assume that each input row will + * yield a distinct group. + */ + if (varshere == NIL) + { + if (contain_volatile_functions(groupexpr)) + return input_rows; + continue; + } + + /* + * Else add variables to varinfos list + */ + foreach(l2, varshere) + { + Node *var = (Node *) lfirst(l2); + + examine_variable(root, var, 0, &vardata); + varinfos = add_unique_group_var(root, varinfos, var, &vardata); + ReleaseVariableStats(vardata); + } + } + + /* + * If now no Vars, we must have an all-constant or all-boolean GROUP BY + * list. + */ + if (varinfos == NIL) + { + /* Apply SRF multiplier as we would do in the long path */ + numdistinct *= srf_multiplier; + /* Round off */ + numdistinct = ceil(numdistinct); + /* Guard against out-of-range answers */ + if (numdistinct > input_rows) + numdistinct = input_rows; + if (numdistinct < 1.0) + numdistinct = 1.0; + return numdistinct; + } + + /* + * Group Vars by relation and estimate total numdistinct. + * + * For each iteration of the outer loop, we process the frontmost Var in + * varinfos, plus all other Vars in the same relation. We remove these + * Vars from the newvarinfos list for the next iteration. This is the + * easiest way to group Vars of same rel together. + */ + do + { + GroupVarInfo *varinfo1 = (GroupVarInfo *) linitial(varinfos); + RelOptInfo *rel = varinfo1->rel; + double reldistinct = 1; + double relmaxndistinct = reldistinct; + int relvarcount = 0; + List *newvarinfos = NIL; + List *relvarinfos = NIL; + + /* + * Split the list of varinfos in two - one for the current rel, one + * for remaining Vars on other rels. + */ + relvarinfos = lappend(relvarinfos, varinfo1); + for_each_from(l, varinfos, 1) + { + GroupVarInfo *varinfo2 = (GroupVarInfo *) lfirst(l); + + if (varinfo2->rel == varinfo1->rel) + { + /* varinfos on current rel */ + relvarinfos = lappend(relvarinfos, varinfo2); + } + else + { + /* not time to process varinfo2 yet */ + newvarinfos = lappend(newvarinfos, varinfo2); + } + } + + /* + * Get the numdistinct estimate for the Vars of this rel. We + * iteratively search for multivariate n-distinct with maximum number + * of vars; assuming that each var group is independent of the others, + * we multiply them together. Any remaining relvarinfos after no more + * multivariate matches are found are assumed independent too, so + * their individual ndistinct estimates are multiplied also. + * + * While iterating, count how many separate numdistinct values we + * apply. We apply a fudge factor below, but only if we multiplied + * more than one such values. + */ + while (relvarinfos) + { + double mvndistinct; + + if (estimate_multivariate_ndistinct(root, rel, &relvarinfos, + &mvndistinct)) + { + reldistinct *= mvndistinct; + if (relmaxndistinct < mvndistinct) + relmaxndistinct = mvndistinct; + relvarcount++; + } + else + { + foreach(l, relvarinfos) + { + GroupVarInfo *varinfo2 = (GroupVarInfo *) lfirst(l); + + reldistinct *= varinfo2->ndistinct; + if (relmaxndistinct < varinfo2->ndistinct) + relmaxndistinct = varinfo2->ndistinct; + relvarcount++; + + /* + * When varinfo2's isdefault is set then we'd better set + * the SELFLAG_USED_DEFAULT bit in the EstimationInfo. + */ + if (estinfo != NULL && varinfo2->isdefault) + estinfo->flags |= SELFLAG_USED_DEFAULT; + } + + /* we're done with this relation */ + relvarinfos = NIL; + } + } + + /* + * Sanity check --- don't divide by zero if empty relation. + */ + Assert(IS_SIMPLE_REL(rel)); + if (rel->tuples > 0) + { + /* + * Clamp to size of rel, or size of rel / 10 if multiple Vars. The + * fudge factor is because the Vars are probably correlated but we + * don't know by how much. We should never clamp to less than the + * largest ndistinct value for any of the Vars, though, since + * there will surely be at least that many groups. + */ + double clamp = rel->tuples; + + if (relvarcount > 1) + { + clamp *= 0.1; + if (clamp < relmaxndistinct) + { + clamp = relmaxndistinct; + /* for sanity in case some ndistinct is too large: */ + if (clamp > rel->tuples) + clamp = rel->tuples; + } + } + if (reldistinct > clamp) + reldistinct = clamp; + + /* + * Update the estimate based on the restriction selectivity, + * guarding against division by zero when reldistinct is zero. + * Also skip this if we know that we are returning all rows. + */ + if (reldistinct > 0 && rel->rows < rel->tuples) + { + /* + * Given a table containing N rows with n distinct values in a + * uniform distribution, if we select p rows at random then + * the expected number of distinct values selected is + * + * n * (1 - product((N-N/n-i)/(N-i), i=0..p-1)) + * + * = n * (1 - (N-N/n)! / (N-N/n-p)! * (N-p)! / N!) + * + * See "Approximating block accesses in database + * organizations", S. B. Yao, Communications of the ACM, + * Volume 20 Issue 4, April 1977 Pages 260-261. + * + * Alternatively, re-arranging the terms from the factorials, + * this may be written as + * + * n * (1 - product((N-p-i)/(N-i), i=0..N/n-1)) + * + * This form of the formula is more efficient to compute in + * the common case where p is larger than N/n. Additionally, + * as pointed out by Dell'Era, if i << N for all terms in the + * product, it can be approximated by + * + * n * (1 - ((N-p)/N)^(N/n)) + * + * See "Expected distinct values when selecting from a bag + * without replacement", Alberto Dell'Era, + * http://www.adellera.it/investigations/distinct_balls/. + * + * The condition i << N is equivalent to n >> 1, so this is a + * good approximation when the number of distinct values in + * the table is large. It turns out that this formula also + * works well even when n is small. + */ + reldistinct *= + (1 - pow((rel->tuples - rel->rows) / rel->tuples, + rel->tuples / reldistinct)); + } + reldistinct = clamp_row_est(reldistinct); + + /* + * Update estimate of total distinct groups. + */ + numdistinct *= reldistinct; + } + + varinfos = newvarinfos; + } while (varinfos != NIL); + + /* Now we can account for the effects of any SRFs */ + numdistinct *= srf_multiplier; + + /* Round off */ + numdistinct = ceil(numdistinct); + + /* Guard against out-of-range answers */ + if (numdistinct > input_rows) + numdistinct = input_rows; + if (numdistinct < 1.0) + numdistinct = 1.0; + + return numdistinct; +} + +/* + * Estimate hash bucket statistics when the specified expression is used + * as a hash key for the given number of buckets. + * + * This attempts to determine two values: + * + * 1. The frequency of the most common value of the expression (returns + * zero into *mcv_freq if we can't get that). + * + * 2. The "bucketsize fraction", ie, average number of entries in a bucket + * divided by total tuples in relation. + * + * XXX This is really pretty bogus since we're effectively assuming that the + * distribution of hash keys will be the same after applying restriction + * clauses as it was in the underlying relation. However, we are not nearly + * smart enough to figure out how the restrict clauses might change the + * distribution, so this will have to do for now. + * + * We are passed the number of buckets the executor will use for the given + * input relation. If the data were perfectly distributed, with the same + * number of tuples going into each available bucket, then the bucketsize + * fraction would be 1/nbuckets. But this happy state of affairs will occur + * only if (a) there are at least nbuckets distinct data values, and (b) + * we have a not-too-skewed data distribution. Otherwise the buckets will + * be nonuniformly occupied. If the other relation in the join has a key + * distribution similar to this one's, then the most-loaded buckets are + * exactly those that will be probed most often. Therefore, the "average" + * bucket size for costing purposes should really be taken as something close + * to the "worst case" bucket size. We try to estimate this by adjusting the + * fraction if there are too few distinct data values, and then scaling up + * by the ratio of the most common value's frequency to the average frequency. + * + * If no statistics are available, use a default estimate of 0.1. This will + * discourage use of a hash rather strongly if the inner relation is large, + * which is what we want. We do not want to hash unless we know that the + * inner rel is well-dispersed (or the alternatives seem much worse). + * + * The caller should also check that the mcv_freq is not so large that the + * most common value would by itself require an impractically large bucket. + * In a hash join, the executor can split buckets if they get too big, but + * obviously that doesn't help for a bucket that contains many duplicates of + * the same value. + */ +void +estimate_hash_bucket_stats(PlannerInfo *root, Node *hashkey, double nbuckets, + Selectivity *mcv_freq, + Selectivity *bucketsize_frac) +{ + VariableStatData vardata; + double estfract, + ndistinct, + stanullfrac, + avgfreq; + bool isdefault; + AttStatsSlot sslot; + + examine_variable(root, hashkey, 0, &vardata); + + /* Look up the frequency of the most common value, if available */ + *mcv_freq = 0.0; + + if (HeapTupleIsValid(vardata.statsTuple)) + { + if (get_attstatsslot(&sslot, vardata.statsTuple, + STATISTIC_KIND_MCV, InvalidOid, + ATTSTATSSLOT_NUMBERS)) + { + /* + * The first MCV stat is for the most common value. + */ + if (sslot.nnumbers > 0) + *mcv_freq = sslot.numbers[0]; + free_attstatsslot(&sslot); + } + } + + /* Get number of distinct values */ + ndistinct = get_variable_numdistinct(&vardata, &isdefault); + + /* + * If ndistinct isn't real, punt. We normally return 0.1, but if the + * mcv_freq is known to be even higher than that, use it instead. + */ + if (isdefault) + { + *bucketsize_frac = (Selectivity) Max(0.1, *mcv_freq); + ReleaseVariableStats(vardata); + return; + } + + /* Get fraction that are null */ + if (HeapTupleIsValid(vardata.statsTuple)) + { + Form_pg_statistic stats; + + stats = (Form_pg_statistic) GETSTRUCT(vardata.statsTuple); + stanullfrac = stats->stanullfrac; + } + else + stanullfrac = 0.0; + + /* Compute avg freq of all distinct data values in raw relation */ + avgfreq = (1.0 - stanullfrac) / ndistinct; + + /* + * Adjust ndistinct to account for restriction clauses. Observe we are + * assuming that the data distribution is affected uniformly by the + * restriction clauses! + * + * XXX Possibly better way, but much more expensive: multiply by + * selectivity of rel's restriction clauses that mention the target Var. + */ + if (vardata.rel && vardata.rel->tuples > 0) + { + ndistinct *= vardata.rel->rows / vardata.rel->tuples; + ndistinct = clamp_row_est(ndistinct); + } + + /* + * Initial estimate of bucketsize fraction is 1/nbuckets as long as the + * number of buckets is less than the expected number of distinct values; + * otherwise it is 1/ndistinct. + */ + if (ndistinct > nbuckets) + estfract = 1.0 / nbuckets; + else + estfract = 1.0 / ndistinct; + + /* + * Adjust estimated bucketsize upward to account for skewed distribution. + */ + if (avgfreq > 0.0 && *mcv_freq > avgfreq) + estfract *= *mcv_freq / avgfreq; + + /* + * Clamp bucketsize to sane range (the above adjustment could easily + * produce an out-of-range result). We set the lower bound a little above + * zero, since zero isn't a very sane result. + */ + if (estfract < 1.0e-6) + estfract = 1.0e-6; + else if (estfract > 1.0) + estfract = 1.0; + + *bucketsize_frac = (Selectivity) estfract; + + ReleaseVariableStats(vardata); +} + +/* + * estimate_hashagg_tablesize + * estimate the number of bytes that a hash aggregate hashtable will + * require based on the agg_costs, path width and number of groups. + * + * We return the result as "double" to forestall any possible overflow + * problem in the multiplication by dNumGroups. + * + * XXX this may be over-estimating the size now that hashagg knows to omit + * unneeded columns from the hashtable. Also for mixed-mode grouping sets, + * grouping columns not in the hashed set are counted here even though hashagg + * won't store them. Is this a problem? + */ +double +estimate_hashagg_tablesize(PlannerInfo *root, Path *path, + const AggClauseCosts *agg_costs, double dNumGroups) +{ + Size hashentrysize; + + hashentrysize = hash_agg_entry_size(list_length(root->aggtransinfos), + path->pathtarget->width, + agg_costs->transitionSpace); + + /* + * Note that this disregards the effect of fill-factor and growth policy + * of the hash table. That's probably ok, given that the default + * fill-factor is relatively high. It'd be hard to meaningfully factor in + * "double-in-size" growth policies here. + */ + return hashentrysize * dNumGroups; +} + + +/*------------------------------------------------------------------------- + * + * Support routines + * + *------------------------------------------------------------------------- + */ + +/* + * Find applicable ndistinct statistics for the given list of VarInfos (which + * must all belong to the given rel), and update *ndistinct to the estimate of + * the MVNDistinctItem that best matches. If a match it found, *varinfos is + * updated to remove the list of matched varinfos. + * + * Varinfos that aren't for simple Vars are ignored. + * + * Return true if we're able to find a match, false otherwise. + */ +static bool +estimate_multivariate_ndistinct(PlannerInfo *root, RelOptInfo *rel, + List **varinfos, double *ndistinct) +{ + ListCell *lc; + int nmatches_vars; + int nmatches_exprs; + Oid statOid = InvalidOid; + MVNDistinct *stats; + StatisticExtInfo *matched_info = NULL; + RangeTblEntry *rte = planner_rt_fetch(rel->relid, root); + + /* bail out immediately if the table has no extended statistics */ + if (!rel->statlist) + return false; + + /* look for the ndistinct statistics object matching the most vars */ + nmatches_vars = 0; /* we require at least two matches */ + nmatches_exprs = 0; + foreach(lc, rel->statlist) + { + ListCell *lc2; + StatisticExtInfo *info = (StatisticExtInfo *) lfirst(lc); + int nshared_vars = 0; + int nshared_exprs = 0; + + /* skip statistics of other kinds */ + if (info->kind != STATS_EXT_NDISTINCT) + continue; + + /* skip statistics with mismatching stxdinherit value */ + if (info->inherit != rte->inh) + continue; + + /* + * Determine how many expressions (and variables in non-matched + * expressions) match. We'll then use these numbers to pick the + * statistics object that best matches the clauses. + */ + foreach(lc2, *varinfos) + { + ListCell *lc3; + GroupVarInfo *varinfo = (GroupVarInfo *) lfirst(lc2); + AttrNumber attnum; + + Assert(varinfo->rel == rel); + + /* simple Var, search in statistics keys directly */ + if (IsA(varinfo->var, Var)) + { + attnum = ((Var *) varinfo->var)->varattno; + + /* + * Ignore system attributes - we don't support statistics on + * them, so can't match them (and it'd fail as the values are + * negative). + */ + if (!AttrNumberIsForUserDefinedAttr(attnum)) + continue; + + if (bms_is_member(attnum, info->keys)) + nshared_vars++; + + continue; + } + + /* expression - see if it's in the statistics object */ + foreach(lc3, info->exprs) + { + Node *expr = (Node *) lfirst(lc3); + + if (equal(varinfo->var, expr)) + { + nshared_exprs++; + break; + } + } + } + + if (nshared_vars + nshared_exprs < 2) + continue; + + /* + * Does this statistics object match more columns than the currently + * best object? If so, use this one instead. + * + * XXX This should break ties using name of the object, or something + * like that, to make the outcome stable. + */ + if ((nshared_exprs > nmatches_exprs) || + (((nshared_exprs == nmatches_exprs)) && (nshared_vars > nmatches_vars))) + { + statOid = info->statOid; + nmatches_vars = nshared_vars; + nmatches_exprs = nshared_exprs; + matched_info = info; + } + } + + /* No match? */ + if (statOid == InvalidOid) + return false; + + Assert(nmatches_vars + nmatches_exprs > 1); + + stats = statext_ndistinct_load(statOid, rte->inh); + + /* + * If we have a match, search it for the specific item that matches (there + * must be one), and construct the output values. + */ + if (stats) + { + int i; + List *newlist = NIL; + MVNDistinctItem *item = NULL; + ListCell *lc2; + Bitmapset *matched = NULL; + AttrNumber attnum_offset; + + /* + * How much we need to offset the attnums? If there are no + * expressions, no offset is needed. Otherwise offset enough to move + * the lowest one (which is equal to number of expressions) to 1. + */ + if (matched_info->exprs) + attnum_offset = (list_length(matched_info->exprs) + 1); + else + attnum_offset = 0; + + /* see what actually matched */ + foreach(lc2, *varinfos) + { + ListCell *lc3; + int idx; + bool found = false; + + GroupVarInfo *varinfo = (GroupVarInfo *) lfirst(lc2); + + /* + * Process a simple Var expression, by matching it to keys + * directly. If there's a matching expression, we'll try matching + * it later. + */ + if (IsA(varinfo->var, Var)) + { + AttrNumber attnum = ((Var *) varinfo->var)->varattno; + + /* + * Ignore expressions on system attributes. Can't rely on the + * bms check for negative values. + */ + if (!AttrNumberIsForUserDefinedAttr(attnum)) + continue; + + /* Is the variable covered by the statistics object? */ + if (!bms_is_member(attnum, matched_info->keys)) + continue; + + attnum = attnum + attnum_offset; + + /* ensure sufficient offset */ + Assert(AttrNumberIsForUserDefinedAttr(attnum)); + + matched = bms_add_member(matched, attnum); + + found = true; + } + + /* + * XXX Maybe we should allow searching the expressions even if we + * found an attribute matching the expression? That would handle + * trivial expressions like "(a)" but it seems fairly useless. + */ + if (found) + continue; + + /* expression - see if it's in the statistics object */ + idx = 0; + foreach(lc3, matched_info->exprs) + { + Node *expr = (Node *) lfirst(lc3); + + if (equal(varinfo->var, expr)) + { + AttrNumber attnum = -(idx + 1); + + attnum = attnum + attnum_offset; + + /* ensure sufficient offset */ + Assert(AttrNumberIsForUserDefinedAttr(attnum)); + + matched = bms_add_member(matched, attnum); + + /* there should be just one matching expression */ + break; + } + + idx++; + } + } + + /* Find the specific item that exactly matches the combination */ + for (i = 0; i < stats->nitems; i++) + { + int j; + MVNDistinctItem *tmpitem = &stats->items[i]; + + if (tmpitem->nattributes != bms_num_members(matched)) + continue; + + /* assume it's the right item */ + item = tmpitem; + + /* check that all item attributes/expressions fit the match */ + for (j = 0; j < tmpitem->nattributes; j++) + { + AttrNumber attnum = tmpitem->attributes[j]; + + /* + * Thanks to how we constructed the matched bitmap above, we + * can just offset all attnums the same way. + */ + attnum = attnum + attnum_offset; + + if (!bms_is_member(attnum, matched)) + { + /* nah, it's not this item */ + item = NULL; + break; + } + } + + /* + * If the item has all the matched attributes, we know it's the + * right one - there can't be a better one. matching more. + */ + if (item) + break; + } + + /* + * Make sure we found an item. There has to be one, because ndistinct + * statistics includes all combinations of attributes. + */ + if (!item) + elog(ERROR, "corrupt MVNDistinct entry"); + + /* Form the output varinfo list, keeping only unmatched ones */ + foreach(lc, *varinfos) + { + GroupVarInfo *varinfo = (GroupVarInfo *) lfirst(lc); + ListCell *lc3; + bool found = false; + + /* + * Let's look at plain variables first, because it's the most + * common case and the check is quite cheap. We can simply get the + * attnum and check (with an offset) matched bitmap. + */ + if (IsA(varinfo->var, Var)) + { + AttrNumber attnum = ((Var *) varinfo->var)->varattno; + + /* + * If it's a system attribute, we're done. We don't support + * extended statistics on system attributes, so it's clearly + * not matched. Just keep the expression and continue. + */ + if (!AttrNumberIsForUserDefinedAttr(attnum)) + { + newlist = lappend(newlist, varinfo); + continue; + } + + /* apply the same offset as above */ + attnum += attnum_offset; + + /* if it's not matched, keep the varinfo */ + if (!bms_is_member(attnum, matched)) + newlist = lappend(newlist, varinfo); + + /* The rest of the loop deals with complex expressions. */ + continue; + } + + /* + * Process complex expressions, not just simple Vars. + * + * First, we search for an exact match of an expression. If we + * find one, we can just discard the whole GroupVarInfo, with all + * the variables we extracted from it. + * + * Otherwise we inspect the individual vars, and try matching it + * to variables in the item. + */ + foreach(lc3, matched_info->exprs) + { + Node *expr = (Node *) lfirst(lc3); + + if (equal(varinfo->var, expr)) + { + found = true; + break; + } + } + + /* found exact match, skip */ + if (found) + continue; + + newlist = lappend(newlist, varinfo); + } + + *varinfos = newlist; + *ndistinct = item->ndistinct; + return true; + } + + return false; +} + +/* + * convert_to_scalar + * Convert non-NULL values of the indicated types to the comparison + * scale needed by scalarineqsel(). + * Returns "true" if successful. + * + * XXX this routine is a hack: ideally we should look up the conversion + * subroutines in pg_type. + * + * All numeric datatypes are simply converted to their equivalent + * "double" values. (NUMERIC values that are outside the range of "double" + * are clamped to +/- HUGE_VAL.) + * + * String datatypes are converted by convert_string_to_scalar(), + * which is explained below. The reason why this routine deals with + * three values at a time, not just one, is that we need it for strings. + * + * The bytea datatype is just enough different from strings that it has + * to be treated separately. + * + * The several datatypes representing absolute times are all converted + * to Timestamp, which is actually an int64, and then we promote that to + * a double. Note this will give correct results even for the "special" + * values of Timestamp, since those are chosen to compare correctly; + * see timestamp_cmp. + * + * The several datatypes representing relative times (intervals) are all + * converted to measurements expressed in seconds. + */ +static bool +convert_to_scalar(Datum value, Oid valuetypid, Oid collid, double *scaledvalue, + Datum lobound, Datum hibound, Oid boundstypid, + double *scaledlobound, double *scaledhibound) +{ + bool failure = false; + + /* + * Both the valuetypid and the boundstypid should exactly match the + * declared input type(s) of the operator we are invoked for. However, + * extensions might try to use scalarineqsel as estimator for operators + * with input type(s) we don't handle here; in such cases, we want to + * return false, not fail. In any case, we mustn't assume that valuetypid + * and boundstypid are identical. + * + * XXX The histogram we are interpolating between points of could belong + * to a column that's only binary-compatible with the declared type. In + * essence we are assuming that the semantics of binary-compatible types + * are enough alike that we can use a histogram generated with one type's + * operators to estimate selectivity for the other's. This is outright + * wrong in some cases --- in particular signed versus unsigned + * interpretation could trip us up. But it's useful enough in the + * majority of cases that we do it anyway. Should think about more + * rigorous ways to do it. + */ + switch (valuetypid) + { + /* + * Built-in numeric types + */ + case BOOLOID: + case INT2OID: + case INT4OID: + case INT8OID: + case FLOAT4OID: + case FLOAT8OID: + case NUMERICOID: + case OIDOID: + case REGPROCOID: + case REGPROCEDUREOID: + case REGOPEROID: + case REGOPERATOROID: + case REGCLASSOID: + case REGTYPEOID: + case REGCOLLATIONOID: + case REGCONFIGOID: + case REGDICTIONARYOID: + case REGROLEOID: + case REGNAMESPACEOID: + *scaledvalue = convert_numeric_to_scalar(value, valuetypid, + &failure); + *scaledlobound = convert_numeric_to_scalar(lobound, boundstypid, + &failure); + *scaledhibound = convert_numeric_to_scalar(hibound, boundstypid, + &failure); + return !failure; + + /* + * Built-in string types + */ + case CHAROID: + case BPCHAROID: + case VARCHAROID: + case TEXTOID: + case NAMEOID: + { + char *valstr = convert_string_datum(value, valuetypid, + collid, &failure); + char *lostr = convert_string_datum(lobound, boundstypid, + collid, &failure); + char *histr = convert_string_datum(hibound, boundstypid, + collid, &failure); + + /* + * Bail out if any of the values is not of string type. We + * might leak converted strings for the other value(s), but + * that's not worth troubling over. + */ + if (failure) + return false; + + convert_string_to_scalar(valstr, scaledvalue, + lostr, scaledlobound, + histr, scaledhibound); + pfree(valstr); + pfree(lostr); + pfree(histr); + return true; + } + + /* + * Built-in bytea type + */ + case BYTEAOID: + { + /* We only support bytea vs bytea comparison */ + if (boundstypid != BYTEAOID) + return false; + convert_bytea_to_scalar(value, scaledvalue, + lobound, scaledlobound, + hibound, scaledhibound); + return true; + } + + /* + * Built-in time types + */ + case TIMESTAMPOID: + case TIMESTAMPTZOID: + case DATEOID: + case INTERVALOID: + case TIMEOID: + case TIMETZOID: + *scaledvalue = convert_timevalue_to_scalar(value, valuetypid, + &failure); + *scaledlobound = convert_timevalue_to_scalar(lobound, boundstypid, + &failure); + *scaledhibound = convert_timevalue_to_scalar(hibound, boundstypid, + &failure); + return !failure; + + /* + * Built-in network types + */ + case INETOID: + case CIDROID: + case MACADDROID: + case MACADDR8OID: + *scaledvalue = convert_network_to_scalar(value, valuetypid, + &failure); + *scaledlobound = convert_network_to_scalar(lobound, boundstypid, + &failure); + *scaledhibound = convert_network_to_scalar(hibound, boundstypid, + &failure); + return !failure; + } + /* Don't know how to convert */ + *scaledvalue = *scaledlobound = *scaledhibound = 0; + return false; +} + +/* + * Do convert_to_scalar()'s work for any numeric data type. + * + * On failure (e.g., unsupported typid), set *failure to true; + * otherwise, that variable is not changed. + */ +static double +convert_numeric_to_scalar(Datum value, Oid typid, bool *failure) +{ + switch (typid) + { + case BOOLOID: + return (double) DatumGetBool(value); + case INT2OID: + return (double) DatumGetInt16(value); + case INT4OID: + return (double) DatumGetInt32(value); + case INT8OID: + return (double) DatumGetInt64(value); + case FLOAT4OID: + return (double) DatumGetFloat4(value); + case FLOAT8OID: + return (double) DatumGetFloat8(value); + case NUMERICOID: + /* Note: out-of-range values will be clamped to +-HUGE_VAL */ + return (double) + DatumGetFloat8(DirectFunctionCall1(numeric_float8_no_overflow, + value)); + case OIDOID: + case REGPROCOID: + case REGPROCEDUREOID: + case REGOPEROID: + case REGOPERATOROID: + case REGCLASSOID: + case REGTYPEOID: + case REGCOLLATIONOID: + case REGCONFIGOID: + case REGDICTIONARYOID: + case REGROLEOID: + case REGNAMESPACEOID: + /* we can treat OIDs as integers... */ + return (double) DatumGetObjectId(value); + } + + *failure = true; + return 0; +} + +/* + * Do convert_to_scalar()'s work for any character-string data type. + * + * String datatypes are converted to a scale that ranges from 0 to 1, + * where we visualize the bytes of the string as fractional digits. + * + * We do not want the base to be 256, however, since that tends to + * generate inflated selectivity estimates; few databases will have + * occurrences of all 256 possible byte values at each position. + * Instead, use the smallest and largest byte values seen in the bounds + * as the estimated range for each byte, after some fudging to deal with + * the fact that we probably aren't going to see the full range that way. + * + * An additional refinement is that we discard any common prefix of the + * three strings before computing the scaled values. This allows us to + * "zoom in" when we encounter a narrow data range. An example is a phone + * number database where all the values begin with the same area code. + * (Actually, the bounds will be adjacent histogram-bin-boundary values, + * so this is more likely to happen than you might think.) + */ +static void +convert_string_to_scalar(char *value, + double *scaledvalue, + char *lobound, + double *scaledlobound, + char *hibound, + double *scaledhibound) +{ + int rangelo, + rangehi; + char *sptr; + + rangelo = rangehi = (unsigned char) hibound[0]; + for (sptr = lobound; *sptr; sptr++) + { + if (rangelo > (unsigned char) *sptr) + rangelo = (unsigned char) *sptr; + if (rangehi < (unsigned char) *sptr) + rangehi = (unsigned char) *sptr; + } + for (sptr = hibound; *sptr; sptr++) + { + if (rangelo > (unsigned char) *sptr) + rangelo = (unsigned char) *sptr; + if (rangehi < (unsigned char) *sptr) + rangehi = (unsigned char) *sptr; + } + /* If range includes any upper-case ASCII chars, make it include all */ + if (rangelo <= 'Z' && rangehi >= 'A') + { + if (rangelo > 'A') + rangelo = 'A'; + if (rangehi < 'Z') + rangehi = 'Z'; + } + /* Ditto lower-case */ + if (rangelo <= 'z' && rangehi >= 'a') + { + if (rangelo > 'a') + rangelo = 'a'; + if (rangehi < 'z') + rangehi = 'z'; + } + /* Ditto digits */ + if (rangelo <= '9' && rangehi >= '0') + { + if (rangelo > '0') + rangelo = '0'; + if (rangehi < '9') + rangehi = '9'; + } + + /* + * If range includes less than 10 chars, assume we have not got enough + * data, and make it include regular ASCII set. + */ + if (rangehi - rangelo < 9) + { + rangelo = ' '; + rangehi = 127; + } + + /* + * Now strip any common prefix of the three strings. + */ + while (*lobound) + { + if (*lobound != *hibound || *lobound != *value) + break; + lobound++, hibound++, value++; + } + + /* + * Now we can do the conversions. + */ + *scaledvalue = convert_one_string_to_scalar(value, rangelo, rangehi); + *scaledlobound = convert_one_string_to_scalar(lobound, rangelo, rangehi); + *scaledhibound = convert_one_string_to_scalar(hibound, rangelo, rangehi); +} + +static double +convert_one_string_to_scalar(char *value, int rangelo, int rangehi) +{ + int slen = strlen(value); + double num, + denom, + base; + + if (slen <= 0) + return 0.0; /* empty string has scalar value 0 */ + + /* + * There seems little point in considering more than a dozen bytes from + * the string. Since base is at least 10, that will give us nominal + * resolution of at least 12 decimal digits, which is surely far more + * precision than this estimation technique has got anyway (especially in + * non-C locales). Also, even with the maximum possible base of 256, this + * ensures denom cannot grow larger than 256^13 = 2.03e31, which will not + * overflow on any known machine. + */ + if (slen > 12) + slen = 12; + + /* Convert initial characters to fraction */ + base = rangehi - rangelo + 1; + num = 0.0; + denom = base; + while (slen-- > 0) + { + int ch = (unsigned char) *value++; + + if (ch < rangelo) + ch = rangelo - 1; + else if (ch > rangehi) + ch = rangehi + 1; + num += ((double) (ch - rangelo)) / denom; + denom *= base; + } + + return num; +} + +/* + * Convert a string-type Datum into a palloc'd, null-terminated string. + * + * On failure (e.g., unsupported typid), set *failure to true; + * otherwise, that variable is not changed. (We'll return NULL on failure.) + * + * When using a non-C locale, we must pass the string through strxfrm() + * before continuing, so as to generate correct locale-specific results. + */ +static char * +convert_string_datum(Datum value, Oid typid, Oid collid, bool *failure) +{ + char *val; + + switch (typid) + { + case CHAROID: + val = (char *) palloc(2); + val[0] = DatumGetChar(value); + val[1] = '\0'; + break; + case BPCHAROID: + case VARCHAROID: + case TEXTOID: + val = TextDatumGetCString(value); + break; + case NAMEOID: + { + NameData *nm = (NameData *) DatumGetPointer(value); + + val = pstrdup(NameStr(*nm)); + break; + } + default: + *failure = true; + return NULL; + } + + if (!lc_collate_is_c(collid)) + { + char *xfrmstr; + size_t xfrmlen; + size_t xfrmlen2 PG_USED_FOR_ASSERTS_ONLY; + + /* + * XXX: We could guess at a suitable output buffer size and only call + * strxfrm twice if our guess is too small. + * + * XXX: strxfrm doesn't support UTF-8 encoding on Win32, it can return + * bogus data or set an error. This is not really a problem unless it + * crashes since it will only give an estimation error and nothing + * fatal. + */ + xfrmlen = strxfrm(NULL, val, 0); +#ifdef WIN32 + + /* + * On Windows, strxfrm returns INT_MAX when an error occurs. Instead + * of trying to allocate this much memory (and fail), just return the + * original string unmodified as if we were in the C locale. + */ + if (xfrmlen == INT_MAX) + return val; +#endif + xfrmstr = (char *) palloc(xfrmlen + 1); + xfrmlen2 = strxfrm(xfrmstr, val, xfrmlen + 1); + + /* + * Some systems (e.g., glibc) can return a smaller value from the + * second call than the first; thus the Assert must be <= not ==. + */ + Assert(xfrmlen2 <= xfrmlen); + pfree(val); + val = xfrmstr; + } + + return val; +} + +/* + * Do convert_to_scalar()'s work for any bytea data type. + * + * Very similar to convert_string_to_scalar except we can't assume + * null-termination and therefore pass explicit lengths around. + * + * Also, assumptions about likely "normal" ranges of characters have been + * removed - a data range of 0..255 is always used, for now. (Perhaps + * someday we will add information about actual byte data range to + * pg_statistic.) + */ +static void +convert_bytea_to_scalar(Datum value, + double *scaledvalue, + Datum lobound, + double *scaledlobound, + Datum hibound, + double *scaledhibound) +{ + bytea *valuep = DatumGetByteaPP(value); + bytea *loboundp = DatumGetByteaPP(lobound); + bytea *hiboundp = DatumGetByteaPP(hibound); + int rangelo, + rangehi, + valuelen = VARSIZE_ANY_EXHDR(valuep), + loboundlen = VARSIZE_ANY_EXHDR(loboundp), + hiboundlen = VARSIZE_ANY_EXHDR(hiboundp), + i, + minlen; + unsigned char *valstr = (unsigned char *) VARDATA_ANY(valuep); + unsigned char *lostr = (unsigned char *) VARDATA_ANY(loboundp); + unsigned char *histr = (unsigned char *) VARDATA_ANY(hiboundp); + + /* + * Assume bytea data is uniformly distributed across all byte values. + */ + rangelo = 0; + rangehi = 255; + + /* + * Now strip any common prefix of the three strings. + */ + minlen = Min(Min(valuelen, loboundlen), hiboundlen); + for (i = 0; i < minlen; i++) + { + if (*lostr != *histr || *lostr != *valstr) + break; + lostr++, histr++, valstr++; + loboundlen--, hiboundlen--, valuelen--; + } + + /* + * Now we can do the conversions. + */ + *scaledvalue = convert_one_bytea_to_scalar(valstr, valuelen, rangelo, rangehi); + *scaledlobound = convert_one_bytea_to_scalar(lostr, loboundlen, rangelo, rangehi); + *scaledhibound = convert_one_bytea_to_scalar(histr, hiboundlen, rangelo, rangehi); +} + +static double +convert_one_bytea_to_scalar(unsigned char *value, int valuelen, + int rangelo, int rangehi) +{ + double num, + denom, + base; + + if (valuelen <= 0) + return 0.0; /* empty string has scalar value 0 */ + + /* + * Since base is 256, need not consider more than about 10 chars (even + * this many seems like overkill) + */ + if (valuelen > 10) + valuelen = 10; + + /* Convert initial characters to fraction */ + base = rangehi - rangelo + 1; + num = 0.0; + denom = base; + while (valuelen-- > 0) + { + int ch = *value++; + + if (ch < rangelo) + ch = rangelo - 1; + else if (ch > rangehi) + ch = rangehi + 1; + num += ((double) (ch - rangelo)) / denom; + denom *= base; + } + + return num; +} + +/* + * Do convert_to_scalar()'s work for any timevalue data type. + * + * On failure (e.g., unsupported typid), set *failure to true; + * otherwise, that variable is not changed. + */ +static double +convert_timevalue_to_scalar(Datum value, Oid typid, bool *failure) +{ + switch (typid) + { + case TIMESTAMPOID: + return DatumGetTimestamp(value); + case TIMESTAMPTZOID: + return DatumGetTimestampTz(value); + case DATEOID: + return date2timestamp_no_overflow(DatumGetDateADT(value)); + case INTERVALOID: + { + Interval *interval = DatumGetIntervalP(value); + + /* + * Convert the month part of Interval to days using assumed + * average month length of 365.25/12.0 days. Not too + * accurate, but plenty good enough for our purposes. + */ + return interval->time + interval->day * (double) USECS_PER_DAY + + interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY); + } + case TIMEOID: + return DatumGetTimeADT(value); + case TIMETZOID: + { + TimeTzADT *timetz = DatumGetTimeTzADTP(value); + + /* use GMT-equivalent time */ + return (double) (timetz->time + (timetz->zone * 1000000.0)); + } + } + + *failure = true; + return 0; +} + + +/* + * get_restriction_variable + * Examine the args of a restriction clause to see if it's of the + * form (variable op pseudoconstant) or (pseudoconstant op variable), + * where "variable" could be either a Var or an expression in vars of a + * single relation. If so, extract information about the variable, + * and also indicate which side it was on and the other argument. + * + * Inputs: + * root: the planner info + * args: clause argument list + * varRelid: see specs for restriction selectivity functions + * + * Outputs: (these are valid only if true is returned) + * *vardata: gets information about variable (see examine_variable) + * *other: gets other clause argument, aggressively reduced to a constant + * *varonleft: set true if variable is on the left, false if on the right + * + * Returns true if a variable is identified, otherwise false. + * + * Note: if there are Vars on both sides of the clause, we must fail, because + * callers are expecting that the other side will act like a pseudoconstant. + */ +bool +get_restriction_variable(PlannerInfo *root, List *args, int varRelid, + VariableStatData *vardata, Node **other, + bool *varonleft) +{ + Node *left, + *right; + VariableStatData rdata; + + /* Fail if not a binary opclause (probably shouldn't happen) */ + if (list_length(args) != 2) + return false; + + left = (Node *) linitial(args); + right = (Node *) lsecond(args); + + /* + * Examine both sides. Note that when varRelid is nonzero, Vars of other + * relations will be treated as pseudoconstants. + */ + examine_variable(root, left, varRelid, vardata); + examine_variable(root, right, varRelid, &rdata); + + /* + * If one side is a variable and the other not, we win. + */ + if (vardata->rel && rdata.rel == NULL) + { + *varonleft = true; + *other = estimate_expression_value(root, rdata.var); + /* Assume we need no ReleaseVariableStats(rdata) here */ + return true; + } + + if (vardata->rel == NULL && rdata.rel) + { + *varonleft = false; + *other = estimate_expression_value(root, vardata->var); + /* Assume we need no ReleaseVariableStats(*vardata) here */ + *vardata = rdata; + return true; + } + + /* Oops, clause has wrong structure (probably var op var) */ + ReleaseVariableStats(*vardata); + ReleaseVariableStats(rdata); + + return false; +} + +/* + * get_join_variables + * Apply examine_variable() to each side of a join clause. + * Also, attempt to identify whether the join clause has the same + * or reversed sense compared to the SpecialJoinInfo. + * + * We consider the join clause "normal" if it is "lhs_var OP rhs_var", + * or "reversed" if it is "rhs_var OP lhs_var". In complicated cases + * where we can't tell for sure, we default to assuming it's normal. + */ +void +get_join_variables(PlannerInfo *root, List *args, SpecialJoinInfo *sjinfo, + VariableStatData *vardata1, VariableStatData *vardata2, + bool *join_is_reversed) +{ + Node *left, + *right; + + if (list_length(args) != 2) + elog(ERROR, "join operator should take two arguments"); + + left = (Node *) linitial(args); + right = (Node *) lsecond(args); + + examine_variable(root, left, 0, vardata1); + examine_variable(root, right, 0, vardata2); + + if (vardata1->rel && + bms_is_subset(vardata1->rel->relids, sjinfo->syn_righthand)) + *join_is_reversed = true; /* var1 is on RHS */ + else if (vardata2->rel && + bms_is_subset(vardata2->rel->relids, sjinfo->syn_lefthand)) + *join_is_reversed = true; /* var2 is on LHS */ + else + *join_is_reversed = false; +} + +/* statext_expressions_load copies the tuple, so just pfree it. */ +static void +ReleaseDummy(HeapTuple tuple) +{ + pfree(tuple); +} + +/* + * examine_variable + * Try to look up statistical data about an expression. + * Fill in a VariableStatData struct to describe the expression. + * + * Inputs: + * root: the planner info + * node: the expression tree to examine + * varRelid: see specs for restriction selectivity functions + * + * Outputs: *vardata is filled as follows: + * var: the input expression (with any binary relabeling stripped, if + * it is or contains a variable; but otherwise the type is preserved) + * rel: RelOptInfo for relation containing variable; NULL if expression + * contains no Vars (NOTE this could point to a RelOptInfo of a + * subquery, not one in the current query). + * statsTuple: the pg_statistic entry for the variable, if one exists; + * otherwise NULL. + * freefunc: pointer to a function to release statsTuple with. + * vartype: exposed type of the expression; this should always match + * the declared input type of the operator we are estimating for. + * atttype, atttypmod: actual type/typmod of the "var" expression. This is + * commonly the same as the exposed type of the variable argument, + * but can be different in binary-compatible-type cases. + * isunique: true if we were able to match the var to a unique index or a + * single-column DISTINCT clause, implying its values are unique for + * this query. (Caution: this should be trusted for statistical + * purposes only, since we do not check indimmediate nor verify that + * the exact same definition of equality applies.) + * acl_ok: true if current user has permission to read the column(s) + * underlying the pg_statistic entry. This is consulted by + * statistic_proc_security_check(). + * + * Caller is responsible for doing ReleaseVariableStats() before exiting. + */ +void +examine_variable(PlannerInfo *root, Node *node, int varRelid, + VariableStatData *vardata) +{ + Node *basenode; + Relids varnos; + RelOptInfo *onerel; + + /* Make sure we don't return dangling pointers in vardata */ + MemSet(vardata, 0, sizeof(VariableStatData)); + + /* Save the exposed type of the expression */ + vardata->vartype = exprType(node); + + /* Look inside any binary-compatible relabeling */ + + if (IsA(node, RelabelType)) + basenode = (Node *) ((RelabelType *) node)->arg; + else + basenode = node; + + /* Fast path for a simple Var */ + + if (IsA(basenode, Var) && + (varRelid == 0 || varRelid == ((Var *) basenode)->varno)) + { + Var *var = (Var *) basenode; + + /* Set up result fields other than the stats tuple */ + vardata->var = basenode; /* return Var without relabeling */ + vardata->rel = find_base_rel(root, var->varno); + vardata->atttype = var->vartype; + vardata->atttypmod = var->vartypmod; + vardata->isunique = has_unique_index(vardata->rel, var->varattno); + + /* Try to locate some stats */ + examine_simple_variable(root, var, vardata); + + return; + } + + /* + * Okay, it's a more complicated expression. Determine variable + * membership. Note that when varRelid isn't zero, only vars of that + * relation are considered "real" vars. + */ + varnos = pull_varnos(root, basenode); + + onerel = NULL; + + switch (bms_membership(varnos)) + { + case BMS_EMPTY_SET: + /* No Vars at all ... must be pseudo-constant clause */ + break; + case BMS_SINGLETON: + if (varRelid == 0 || bms_is_member(varRelid, varnos)) + { + onerel = find_base_rel(root, + (varRelid ? varRelid : bms_singleton_member(varnos))); + vardata->rel = onerel; + node = basenode; /* strip any relabeling */ + } + /* else treat it as a constant */ + break; + case BMS_MULTIPLE: + if (varRelid == 0) + { + /* treat it as a variable of a join relation */ + vardata->rel = find_join_rel(root, varnos); + node = basenode; /* strip any relabeling */ + } + else if (bms_is_member(varRelid, varnos)) + { + /* ignore the vars belonging to other relations */ + vardata->rel = find_base_rel(root, varRelid); + node = basenode; /* strip any relabeling */ + /* note: no point in expressional-index search here */ + } + /* else treat it as a constant */ + break; + } + + bms_free(varnos); + + vardata->var = node; + vardata->atttype = exprType(node); + vardata->atttypmod = exprTypmod(node); + + if (onerel) + { + /* + * We have an expression in vars of a single relation. Try to match + * it to expressional index columns, in hopes of finding some + * statistics. + * + * Note that we consider all index columns including INCLUDE columns, + * since there could be stats for such columns. But the test for + * uniqueness needs to be warier. + * + * XXX it's conceivable that there are multiple matches with different + * index opfamilies; if so, we need to pick one that matches the + * operator we are estimating for. FIXME later. + */ + ListCell *ilist; + ListCell *slist; + Oid userid; + + /* + * Determine the user ID to use for privilege checks: either + * onerel->userid if it's set (e.g., in case we're accessing the table + * via a view), or the current user otherwise. + * + * If we drill down to child relations, we keep using the same userid: + * it's going to be the same anyway, due to how we set up the relation + * tree (q.v. build_simple_rel). + */ + userid = OidIsValid(onerel->userid) ? onerel->userid : GetUserId(); + + foreach(ilist, onerel->indexlist) + { + IndexOptInfo *index = (IndexOptInfo *) lfirst(ilist); + ListCell *indexpr_item; + int pos; + + indexpr_item = list_head(index->indexprs); + if (indexpr_item == NULL) + continue; /* no expressions here... */ + + for (pos = 0; pos < index->ncolumns; pos++) + { + if (index->indexkeys[pos] == 0) + { + Node *indexkey; + + if (indexpr_item == NULL) + elog(ERROR, "too few entries in indexprs list"); + indexkey = (Node *) lfirst(indexpr_item); + if (indexkey && IsA(indexkey, RelabelType)) + indexkey = (Node *) ((RelabelType *) indexkey)->arg; + if (equal(node, indexkey)) + { + /* + * Found a match ... is it a unique index? Tests here + * should match has_unique_index(). + */ + if (index->unique && + index->nkeycolumns == 1 && + pos == 0 && + (index->indpred == NIL || index->predOK)) + vardata->isunique = true; + + /* + * Has it got stats? We only consider stats for + * non-partial indexes, since partial indexes probably + * don't reflect whole-relation statistics; the above + * check for uniqueness is the only info we take from + * a partial index. + * + * An index stats hook, however, must make its own + * decisions about what to do with partial indexes. + */ + if (get_index_stats_hook && + (*get_index_stats_hook) (root, index->indexoid, + pos + 1, vardata)) + { + /* + * The hook took control of acquiring a stats + * tuple. If it did supply a tuple, it'd better + * have supplied a freefunc. + */ + if (HeapTupleIsValid(vardata->statsTuple) && + !vardata->freefunc) + elog(ERROR, "no function provided to release variable stats with"); + } + else if (index->indpred == NIL) + { + vardata->statsTuple = + SearchSysCache3(STATRELATTINH, + ObjectIdGetDatum(index->indexoid), + Int16GetDatum(pos + 1), + BoolGetDatum(false)); + vardata->freefunc = ReleaseSysCache; + + if (HeapTupleIsValid(vardata->statsTuple)) + { + /* Get index's table for permission check */ + RangeTblEntry *rte; + + rte = planner_rt_fetch(index->rel->relid, root); + Assert(rte->rtekind == RTE_RELATION); + + /* + * For simplicity, we insist on the whole + * table being selectable, rather than trying + * to identify which column(s) the index + * depends on. Also require all rows to be + * selectable --- there must be no + * securityQuals from security barrier views + * or RLS policies. + */ + vardata->acl_ok = + rte->securityQuals == NIL && + (pg_class_aclcheck(rte->relid, userid, + ACL_SELECT) == ACLCHECK_OK); + + /* + * If the user doesn't have permissions to + * access an inheritance child relation, check + * the permissions of the table actually + * mentioned in the query, since most likely + * the user does have that permission. Note + * that whole-table select privilege on the + * parent doesn't quite guarantee that the + * user could read all columns of the child. + * But in practice it's unlikely that any + * interesting security violation could result + * from allowing access to the expression + * index's stats, so we allow it anyway. See + * similar code in examine_simple_variable() + * for additional comments. + */ + if (!vardata->acl_ok && + root->append_rel_array != NULL) + { + AppendRelInfo *appinfo; + Index varno = index->rel->relid; + + appinfo = root->append_rel_array[varno]; + while (appinfo && + planner_rt_fetch(appinfo->parent_relid, + root)->rtekind == RTE_RELATION) + { + varno = appinfo->parent_relid; + appinfo = root->append_rel_array[varno]; + } + if (varno != index->rel->relid) + { + /* Repeat access check on this rel */ + rte = planner_rt_fetch(varno, root); + Assert(rte->rtekind == RTE_RELATION); + + vardata->acl_ok = + rte->securityQuals == NIL && + (pg_class_aclcheck(rte->relid, + userid, + ACL_SELECT) == ACLCHECK_OK); + } + } + } + else + { + /* suppress leakproofness checks later */ + vardata->acl_ok = true; + } + } + if (vardata->statsTuple) + break; + } + indexpr_item = lnext(index->indexprs, indexpr_item); + } + } + if (vardata->statsTuple) + break; + } + + /* + * Search extended statistics for one with a matching expression. + * There might be multiple ones, so just grab the first one. In the + * future, we might consider the statistics target (and pick the most + * accurate statistics) and maybe some other parameters. + */ + foreach(slist, onerel->statlist) + { + StatisticExtInfo *info = (StatisticExtInfo *) lfirst(slist); + RangeTblEntry *rte = planner_rt_fetch(onerel->relid, root); + ListCell *expr_item; + int pos; + + /* + * Stop once we've found statistics for the expression (either + * from extended stats, or for an index in the preceding loop). + */ + if (vardata->statsTuple) + break; + + /* skip stats without per-expression stats */ + if (info->kind != STATS_EXT_EXPRESSIONS) + continue; + + /* skip stats with mismatching stxdinherit value */ + if (info->inherit != rte->inh) + continue; + + pos = 0; + foreach(expr_item, info->exprs) + { + Node *expr = (Node *) lfirst(expr_item); + + Assert(expr); + + /* strip RelabelType before comparing it */ + if (expr && IsA(expr, RelabelType)) + expr = (Node *) ((RelabelType *) expr)->arg; + + /* found a match, see if we can extract pg_statistic row */ + if (equal(node, expr)) + { + /* + * XXX Not sure if we should cache the tuple somewhere. + * Now we just create a new copy every time. + */ + vardata->statsTuple = + statext_expressions_load(info->statOid, rte->inh, pos); + + vardata->freefunc = ReleaseDummy; + + /* + * For simplicity, we insist on the whole table being + * selectable, rather than trying to identify which + * column(s) the statistics object depends on. Also + * require all rows to be selectable --- there must be no + * securityQuals from security barrier views or RLS + * policies. + */ + vardata->acl_ok = + rte->securityQuals == NIL && + (pg_class_aclcheck(rte->relid, userid, + ACL_SELECT) == ACLCHECK_OK); + + /* + * If the user doesn't have permissions to access an + * inheritance child relation, check the permissions of + * the table actually mentioned in the query, since most + * likely the user does have that permission. Note that + * whole-table select privilege on the parent doesn't + * quite guarantee that the user could read all columns of + * the child. But in practice it's unlikely that any + * interesting security violation could result from + * allowing access to the expression stats, so we allow it + * anyway. See similar code in examine_simple_variable() + * for additional comments. + */ + if (!vardata->acl_ok && + root->append_rel_array != NULL) + { + AppendRelInfo *appinfo; + Index varno = onerel->relid; + + appinfo = root->append_rel_array[varno]; + while (appinfo && + planner_rt_fetch(appinfo->parent_relid, + root)->rtekind == RTE_RELATION) + { + varno = appinfo->parent_relid; + appinfo = root->append_rel_array[varno]; + } + if (varno != onerel->relid) + { + /* Repeat access check on this rel */ + rte = planner_rt_fetch(varno, root); + Assert(rte->rtekind == RTE_RELATION); + + vardata->acl_ok = + rte->securityQuals == NIL && + (pg_class_aclcheck(rte->relid, + userid, + ACL_SELECT) == ACLCHECK_OK); + } + } + + break; + } + + pos++; + } + } + } +} + +/* + * examine_simple_variable + * Handle a simple Var for examine_variable + * + * This is split out as a subroutine so that we can recurse to deal with + * Vars referencing subqueries. + * + * We already filled in all the fields of *vardata except for the stats tuple. + */ +static void +examine_simple_variable(PlannerInfo *root, Var *var, + VariableStatData *vardata) +{ + RangeTblEntry *rte = root->simple_rte_array[var->varno]; + + Assert(IsA(rte, RangeTblEntry)); + + if (get_relation_stats_hook && + (*get_relation_stats_hook) (root, rte, var->varattno, vardata)) + { + /* + * The hook took control of acquiring a stats tuple. If it did supply + * a tuple, it'd better have supplied a freefunc. + */ + if (HeapTupleIsValid(vardata->statsTuple) && + !vardata->freefunc) + elog(ERROR, "no function provided to release variable stats with"); + } + else if (rte->rtekind == RTE_RELATION) + { + /* + * Plain table or parent of an inheritance appendrel, so look up the + * column in pg_statistic + */ + vardata->statsTuple = SearchSysCache3(STATRELATTINH, + ObjectIdGetDatum(rte->relid), + Int16GetDatum(var->varattno), + BoolGetDatum(rte->inh)); + vardata->freefunc = ReleaseSysCache; + + if (HeapTupleIsValid(vardata->statsTuple)) + { + RelOptInfo *onerel = find_base_rel(root, var->varno); + Oid userid; + + /* + * Check if user has permission to read this column. We require + * all rows to be accessible, so there must be no securityQuals + * from security barrier views or RLS policies. Use + * onerel->userid if it's set, in case we're accessing the table + * via a view. + */ + userid = OidIsValid(onerel->userid) ? onerel->userid : GetUserId(); + + vardata->acl_ok = + rte->securityQuals == NIL && + ((pg_class_aclcheck(rte->relid, userid, + ACL_SELECT) == ACLCHECK_OK) || + (pg_attribute_aclcheck(rte->relid, var->varattno, userid, + ACL_SELECT) == ACLCHECK_OK)); + + /* + * If the user doesn't have permissions to access an inheritance + * child relation or specifically this attribute, check the + * permissions of the table/column actually mentioned in the + * query, since most likely the user does have that permission + * (else the query will fail at runtime), and if the user can read + * the column there then he can get the values of the child table + * too. To do that, we must find out which of the root parent's + * attributes the child relation's attribute corresponds to. + */ + if (!vardata->acl_ok && var->varattno > 0 && + root->append_rel_array != NULL) + { + AppendRelInfo *appinfo; + Index varno = var->varno; + int varattno = var->varattno; + bool found = false; + + appinfo = root->append_rel_array[varno]; + + /* + * Partitions are mapped to their immediate parent, not the + * root parent, so must be ready to walk up multiple + * AppendRelInfos. But stop if we hit a parent that is not + * RTE_RELATION --- that's a flattened UNION ALL subquery, not + * an inheritance parent. + */ + while (appinfo && + planner_rt_fetch(appinfo->parent_relid, + root)->rtekind == RTE_RELATION) + { + int parent_varattno; + + found = false; + if (varattno <= 0 || varattno > appinfo->num_child_cols) + break; /* safety check */ + parent_varattno = appinfo->parent_colnos[varattno - 1]; + if (parent_varattno == 0) + break; /* Var is local to child */ + + varno = appinfo->parent_relid; + varattno = parent_varattno; + found = true; + + /* If the parent is itself a child, continue up. */ + appinfo = root->append_rel_array[varno]; + } + + /* + * In rare cases, the Var may be local to the child table, in + * which case, we've got to live with having no access to this + * column's stats. + */ + if (!found) + return; + + /* Repeat the access check on this parent rel & column */ + rte = planner_rt_fetch(varno, root); + Assert(rte->rtekind == RTE_RELATION); + + /* + * Fine to use the same userid as it's the same in all + * relations of a given inheritance tree. + */ + vardata->acl_ok = + rte->securityQuals == NIL && + ((pg_class_aclcheck(rte->relid, userid, + ACL_SELECT) == ACLCHECK_OK) || + (pg_attribute_aclcheck(rte->relid, varattno, userid, + ACL_SELECT) == ACLCHECK_OK)); + } + } + else + { + /* suppress any possible leakproofness checks later */ + vardata->acl_ok = true; + } + } + else if (rte->rtekind == RTE_SUBQUERY && !rte->inh) + { + /* + * Plain subquery (not one that was converted to an appendrel). + */ + Query *subquery = rte->subquery; + RelOptInfo *rel; + TargetEntry *ste; + + /* + * Punt if it's a whole-row var rather than a plain column reference. + */ + if (var->varattno == InvalidAttrNumber) + return; + + /* + * Punt if subquery uses set operations or GROUP BY, as these will + * mash underlying columns' stats beyond recognition. (Set ops are + * particularly nasty; if we forged ahead, we would return stats + * relevant to only the leftmost subselect...) DISTINCT is also + * problematic, but we check that later because there is a possibility + * of learning something even with it. + */ + if (subquery->setOperations || + subquery->groupClause || + subquery->groupingSets) + return; + + /* + * OK, fetch RelOptInfo for subquery. Note that we don't change the + * rel returned in vardata, since caller expects it to be a rel of the + * caller's query level. Because we might already be recursing, we + * can't use that rel pointer either, but have to look up the Var's + * rel afresh. + */ + rel = find_base_rel(root, var->varno); + + /* If the subquery hasn't been planned yet, we have to punt */ + if (rel->subroot == NULL) + return; + Assert(IsA(rel->subroot, PlannerInfo)); + + /* + * Switch our attention to the subquery as mangled by the planner. It + * was okay to look at the pre-planning version for the tests above, + * but now we need a Var that will refer to the subroot's live + * RelOptInfos. For instance, if any subquery pullup happened during + * planning, Vars in the targetlist might have gotten replaced, and we + * need to see the replacement expressions. + */ + subquery = rel->subroot->parse; + Assert(IsA(subquery, Query)); + + /* Get the subquery output expression referenced by the upper Var */ + ste = get_tle_by_resno(subquery->targetList, var->varattno); + if (ste == NULL || ste->resjunk) + elog(ERROR, "subquery %s does not have attribute %d", + rte->eref->aliasname, var->varattno); + var = (Var *) ste->expr; + + /* + * If subquery uses DISTINCT, we can't make use of any stats for the + * variable ... but, if it's the only DISTINCT column, we are entitled + * to consider it unique. We do the test this way so that it works + * for cases involving DISTINCT ON. + */ + if (subquery->distinctClause) + { + if (list_length(subquery->distinctClause) == 1 && + targetIsInSortList(ste, InvalidOid, subquery->distinctClause)) + vardata->isunique = true; + /* cannot go further */ + return; + } + + /* + * If the sub-query originated from a view with the security_barrier + * attribute, we must not look at the variable's statistics, though it + * seems all right to notice the existence of a DISTINCT clause. So + * stop here. + * + * This is probably a harsher restriction than necessary; it's + * certainly OK for the selectivity estimator (which is a C function, + * and therefore omnipotent anyway) to look at the statistics. But + * many selectivity estimators will happily *invoke the operator + * function* to try to work out a good estimate - and that's not OK. + * So for now, don't dig down for stats. + */ + if (rte->security_barrier) + return; + + /* Can only handle a simple Var of subquery's query level */ + if (var && IsA(var, Var) && + var->varlevelsup == 0) + { + /* + * OK, recurse into the subquery. Note that the original setting + * of vardata->isunique (which will surely be false) is left + * unchanged in this situation. That's what we want, since even + * if the underlying column is unique, the subquery may have + * joined to other tables in a way that creates duplicates. + */ + examine_simple_variable(rel->subroot, var, vardata); + } + } + else + { + /* + * Otherwise, the Var comes from a FUNCTION, VALUES, or CTE RTE. (We + * won't see RTE_JOIN here because join alias Vars have already been + * flattened.) There's not much we can do with function outputs, but + * maybe someday try to be smarter about VALUES and/or CTEs. + */ + } +} + +/* + * Check whether it is permitted to call func_oid passing some of the + * pg_statistic data in vardata. We allow this either if the user has SELECT + * privileges on the table or column underlying the pg_statistic data or if + * the function is marked leak-proof. + */ +bool +statistic_proc_security_check(VariableStatData *vardata, Oid func_oid) +{ + if (vardata->acl_ok) + return true; + + if (!OidIsValid(func_oid)) + return false; + + if (get_func_leakproof(func_oid)) + return true; + + ereport(DEBUG2, + (errmsg_internal("not using statistics because function \"%s\" is not leak-proof", + get_func_name(func_oid)))); + return false; +} + +/* + * get_variable_numdistinct + * Estimate the number of distinct values of a variable. + * + * vardata: results of examine_variable + * *isdefault: set to true if the result is a default rather than based on + * anything meaningful. + * + * NB: be careful to produce a positive integral result, since callers may + * compare the result to exact integer counts, or might divide by it. + */ +double +get_variable_numdistinct(VariableStatData *vardata, bool *isdefault) +{ + double stadistinct; + double stanullfrac = 0.0; + double ntuples; + + *isdefault = false; + + /* + * Determine the stadistinct value to use. There are cases where we can + * get an estimate even without a pg_statistic entry, or can get a better + * value than is in pg_statistic. Grab stanullfrac too if we can find it + * (otherwise, assume no nulls, for lack of any better idea). + */ + if (HeapTupleIsValid(vardata->statsTuple)) + { + /* Use the pg_statistic entry */ + Form_pg_statistic stats; + + stats = (Form_pg_statistic) GETSTRUCT(vardata->statsTuple); + stadistinct = stats->stadistinct; + stanullfrac = stats->stanullfrac; + } + else if (vardata->vartype == BOOLOID) + { + /* + * Special-case boolean columns: presumably, two distinct values. + * + * Are there any other datatypes we should wire in special estimates + * for? + */ + stadistinct = 2.0; + } + else if (vardata->rel && vardata->rel->rtekind == RTE_VALUES) + { + /* + * If the Var represents a column of a VALUES RTE, assume it's unique. + * This could of course be very wrong, but it should tend to be true + * in well-written queries. We could consider examining the VALUES' + * contents to get some real statistics; but that only works if the + * entries are all constants, and it would be pretty expensive anyway. + */ + stadistinct = -1.0; /* unique (and all non null) */ + } + else + { + /* + * We don't keep statistics for system columns, but in some cases we + * can infer distinctness anyway. + */ + if (vardata->var && IsA(vardata->var, Var)) + { + switch (((Var *) vardata->var)->varattno) + { + case SelfItemPointerAttributeNumber: + stadistinct = -1.0; /* unique (and all non null) */ + break; + case TableOidAttributeNumber: + stadistinct = 1.0; /* only 1 value */ + break; + default: + stadistinct = 0.0; /* means "unknown" */ + break; + } + } + else + stadistinct = 0.0; /* means "unknown" */ + + /* + * XXX consider using estimate_num_groups on expressions? + */ + } + + /* + * If there is a unique index or DISTINCT clause for the variable, assume + * it is unique no matter what pg_statistic says; the statistics could be + * out of date, or we might have found a partial unique index that proves + * the var is unique for this query. However, we'd better still believe + * the null-fraction statistic. + */ + if (vardata->isunique) + stadistinct = -1.0 * (1.0 - stanullfrac); + + /* + * If we had an absolute estimate, use that. + */ + if (stadistinct > 0.0) + return clamp_row_est(stadistinct); + + /* + * Otherwise we need to get the relation size; punt if not available. + */ + if (vardata->rel == NULL) + { + *isdefault = true; + return DEFAULT_NUM_DISTINCT; + } + ntuples = vardata->rel->tuples; + if (ntuples <= 0.0) + { + *isdefault = true; + return DEFAULT_NUM_DISTINCT; + } + + /* + * If we had a relative estimate, use that. + */ + if (stadistinct < 0.0) + return clamp_row_est(-stadistinct * ntuples); + + /* + * With no data, estimate ndistinct = ntuples if the table is small, else + * use default. We use DEFAULT_NUM_DISTINCT as the cutoff for "small" so + * that the behavior isn't discontinuous. + */ + if (ntuples < DEFAULT_NUM_DISTINCT) + return clamp_row_est(ntuples); + + *isdefault = true; + return DEFAULT_NUM_DISTINCT; +} + +/* + * get_variable_range + * Estimate the minimum and maximum value of the specified variable. + * If successful, store values in *min and *max, and return true. + * If no data available, return false. + * + * sortop is the "<" comparison operator to use. This should generally + * be "<" not ">", as only the former is likely to be found in pg_statistic. + * The collation must be specified too. + */ +static bool +get_variable_range(PlannerInfo *root, VariableStatData *vardata, + Oid sortop, Oid collation, + Datum *min, Datum *max) +{ + Datum tmin = 0; + Datum tmax = 0; + bool have_data = false; + int16 typLen; + bool typByVal; + Oid opfuncoid; + FmgrInfo opproc; + AttStatsSlot sslot; + + /* + * XXX It's very tempting to try to use the actual column min and max, if + * we can get them relatively-cheaply with an index probe. However, since + * this function is called many times during join planning, that could + * have unpleasant effects on planning speed. Need more investigation + * before enabling this. + */ +#ifdef NOT_USED + if (get_actual_variable_range(root, vardata, sortop, collation, min, max)) + return true; +#endif + + if (!HeapTupleIsValid(vardata->statsTuple)) + { + /* no stats available, so default result */ + return false; + } + + /* + * If we can't apply the sortop to the stats data, just fail. In + * principle, if there's a histogram and no MCVs, we could return the + * histogram endpoints without ever applying the sortop ... but it's + * probably not worth trying, because whatever the caller wants to do with + * the endpoints would likely fail the security check too. + */ + if (!statistic_proc_security_check(vardata, + (opfuncoid = get_opcode(sortop)))) + return false; + + opproc.fn_oid = InvalidOid; /* mark this as not looked up yet */ + + get_typlenbyval(vardata->atttype, &typLen, &typByVal); + + /* + * If there is a histogram with the ordering we want, grab the first and + * last values. + */ + if (get_attstatsslot(&sslot, vardata->statsTuple, + STATISTIC_KIND_HISTOGRAM, sortop, + ATTSTATSSLOT_VALUES)) + { + if (sslot.stacoll == collation && sslot.nvalues > 0) + { + tmin = datumCopy(sslot.values[0], typByVal, typLen); + tmax = datumCopy(sslot.values[sslot.nvalues - 1], typByVal, typLen); + have_data = true; + } + free_attstatsslot(&sslot); + } + + /* + * Otherwise, if there is a histogram with some other ordering, scan it + * and get the min and max values according to the ordering we want. This + * of course may not find values that are really extremal according to our + * ordering, but it beats ignoring available data. + */ + if (!have_data && + get_attstatsslot(&sslot, vardata->statsTuple, + STATISTIC_KIND_HISTOGRAM, InvalidOid, + ATTSTATSSLOT_VALUES)) + { + get_stats_slot_range(&sslot, opfuncoid, &opproc, + collation, typLen, typByVal, + &tmin, &tmax, &have_data); + free_attstatsslot(&sslot); + } + + /* + * If we have most-common-values info, look for extreme MCVs. This is + * needed even if we also have a histogram, since the histogram excludes + * the MCVs. However, if we *only* have MCVs and no histogram, we should + * be pretty wary of deciding that that is a full representation of the + * data. Proceed only if the MCVs represent the whole table (to within + * roundoff error). + */ + if (get_attstatsslot(&sslot, vardata->statsTuple, + STATISTIC_KIND_MCV, InvalidOid, + have_data ? ATTSTATSSLOT_VALUES : + (ATTSTATSSLOT_VALUES | ATTSTATSSLOT_NUMBERS))) + { + bool use_mcvs = have_data; + + if (!have_data) + { + double sumcommon = 0.0; + double nullfrac; + int i; + + for (i = 0; i < sslot.nnumbers; i++) + sumcommon += sslot.numbers[i]; + nullfrac = ((Form_pg_statistic) GETSTRUCT(vardata->statsTuple))->stanullfrac; + if (sumcommon + nullfrac > 0.99999) + use_mcvs = true; + } + + if (use_mcvs) + get_stats_slot_range(&sslot, opfuncoid, &opproc, + collation, typLen, typByVal, + &tmin, &tmax, &have_data); + free_attstatsslot(&sslot); + } + + *min = tmin; + *max = tmax; + return have_data; +} + +/* + * get_stats_slot_range: scan sslot for min/max values + * + * Subroutine for get_variable_range: update min/max/have_data according + * to what we find in the statistics array. + */ +static void +get_stats_slot_range(AttStatsSlot *sslot, Oid opfuncoid, FmgrInfo *opproc, + Oid collation, int16 typLen, bool typByVal, + Datum *min, Datum *max, bool *p_have_data) +{ + Datum tmin = *min; + Datum tmax = *max; + bool have_data = *p_have_data; + bool found_tmin = false; + bool found_tmax = false; + + /* Look up the comparison function, if we didn't already do so */ + if (opproc->fn_oid != opfuncoid) + fmgr_info(opfuncoid, opproc); + + /* Scan all the slot's values */ + for (int i = 0; i < sslot->nvalues; i++) + { + if (!have_data) + { + tmin = tmax = sslot->values[i]; + found_tmin = found_tmax = true; + *p_have_data = have_data = true; + continue; + } + if (DatumGetBool(FunctionCall2Coll(opproc, + collation, + sslot->values[i], tmin))) + { + tmin = sslot->values[i]; + found_tmin = true; + } + if (DatumGetBool(FunctionCall2Coll(opproc, + collation, + tmax, sslot->values[i]))) + { + tmax = sslot->values[i]; + found_tmax = true; + } + } + + /* + * Copy the slot's values, if we found new extreme values. + */ + if (found_tmin) + *min = datumCopy(tmin, typByVal, typLen); + if (found_tmax) + *max = datumCopy(tmax, typByVal, typLen); +} + + +/* + * get_actual_variable_range + * Attempt to identify the current *actual* minimum and/or maximum + * of the specified variable, by looking for a suitable btree index + * and fetching its low and/or high values. + * If successful, store values in *min and *max, and return true. + * (Either pointer can be NULL if that endpoint isn't needed.) + * If unsuccessful, return false. + * + * sortop is the "<" comparison operator to use. + * collation is the required collation. + */ +static bool +get_actual_variable_range(PlannerInfo *root, VariableStatData *vardata, + Oid sortop, Oid collation, + Datum *min, Datum *max) +{ + bool have_data = false; + RelOptInfo *rel = vardata->rel; + RangeTblEntry *rte; + ListCell *lc; + + /* No hope if no relation or it doesn't have indexes */ + if (rel == NULL || rel->indexlist == NIL) + return false; + /* If it has indexes it must be a plain relation */ + rte = root->simple_rte_array[rel->relid]; + Assert(rte->rtekind == RTE_RELATION); + + /* ignore partitioned tables. Any indexes here are not real indexes */ + if (rte->relkind == RELKIND_PARTITIONED_TABLE) + return false; + + /* Search through the indexes to see if any match our problem */ + foreach(lc, rel->indexlist) + { + IndexOptInfo *index = (IndexOptInfo *) lfirst(lc); + ScanDirection indexscandir; + + /* Ignore non-btree indexes */ + if (index->relam != BTREE_AM_OID) + continue; + + /* + * Ignore partial indexes --- we only want stats that cover the entire + * relation. + */ + if (index->indpred != NIL) + continue; + + /* + * The index list might include hypothetical indexes inserted by a + * get_relation_info hook --- don't try to access them. + */ + if (index->hypothetical) + continue; + + /* + * The first index column must match the desired variable, sortop, and + * collation --- but we can use a descending-order index. + */ + if (collation != index->indexcollations[0]) + continue; /* test first 'cause it's cheapest */ + if (!match_index_to_operand(vardata->var, 0, index)) + continue; + switch (get_op_opfamily_strategy(sortop, index->sortopfamily[0])) + { + case BTLessStrategyNumber: + if (index->reverse_sort[0]) + indexscandir = BackwardScanDirection; + else + indexscandir = ForwardScanDirection; + break; + case BTGreaterStrategyNumber: + if (index->reverse_sort[0]) + indexscandir = ForwardScanDirection; + else + indexscandir = BackwardScanDirection; + break; + default: + /* index doesn't match the sortop */ + continue; + } + + /* + * Found a suitable index to extract data from. Set up some data that + * can be used by both invocations of get_actual_variable_endpoint. + */ + { + MemoryContext tmpcontext; + MemoryContext oldcontext; + Relation heapRel; + Relation indexRel; + TupleTableSlot *slot; + int16 typLen; + bool typByVal; + ScanKeyData scankeys[1]; + + /* Make sure any cruft gets recycled when we're done */ + tmpcontext = AllocSetContextCreate(CurrentMemoryContext, + "get_actual_variable_range workspace", + ALLOCSET_DEFAULT_SIZES); + oldcontext = MemoryContextSwitchTo(tmpcontext); + + /* + * Open the table and index so we can read from them. We should + * already have some type of lock on each. + */ + heapRel = table_open(rte->relid, NoLock); + indexRel = index_open(index->indexoid, NoLock); + + /* build some stuff needed for indexscan execution */ + slot = table_slot_create(heapRel, NULL); + get_typlenbyval(vardata->atttype, &typLen, &typByVal); + + /* set up an IS NOT NULL scan key so that we ignore nulls */ + ScanKeyEntryInitialize(&scankeys[0], + SK_ISNULL | SK_SEARCHNOTNULL, + 1, /* index col to scan */ + InvalidStrategy, /* no strategy */ + InvalidOid, /* no strategy subtype */ + InvalidOid, /* no collation */ + InvalidOid, /* no reg proc for this */ + (Datum) 0); /* constant */ + + /* If min is requested ... */ + if (min) + { + have_data = get_actual_variable_endpoint(heapRel, + indexRel, + indexscandir, + scankeys, + typLen, + typByVal, + slot, + oldcontext, + min); + } + else + { + /* If min not requested, still want to fetch max */ + have_data = true; + } + + /* If max is requested, and we didn't already fail ... */ + if (max && have_data) + { + /* scan in the opposite direction; all else is the same */ + have_data = get_actual_variable_endpoint(heapRel, + indexRel, + -indexscandir, + scankeys, + typLen, + typByVal, + slot, + oldcontext, + max); + } + + /* Clean everything up */ + ExecDropSingleTupleTableSlot(slot); + + index_close(indexRel, NoLock); + table_close(heapRel, NoLock); + + MemoryContextSwitchTo(oldcontext); + MemoryContextDelete(tmpcontext); + + /* And we're done */ + break; + } + } + + return have_data; +} + +/* + * Get one endpoint datum (min or max depending on indexscandir) from the + * specified index. Return true if successful, false if not. + * On success, endpoint value is stored to *endpointDatum (and copied into + * outercontext). + * + * scankeys is a 1-element scankey array set up to reject nulls. + * typLen/typByVal describe the datatype of the index's first column. + * tableslot is a slot suitable to hold table tuples, in case we need + * to probe the heap. + * (We could compute these values locally, but that would mean computing them + * twice when get_actual_variable_range needs both the min and the max.) + * + * Failure occurs either when the index is empty, or we decide that it's + * taking too long to find a suitable tuple. + */ +static bool +get_actual_variable_endpoint(Relation heapRel, + Relation indexRel, + ScanDirection indexscandir, + ScanKey scankeys, + int16 typLen, + bool typByVal, + TupleTableSlot *tableslot, + MemoryContext outercontext, + Datum *endpointDatum) +{ + bool have_data = false; + SnapshotData SnapshotNonVacuumable; + IndexScanDesc index_scan; + Buffer vmbuffer = InvalidBuffer; + BlockNumber last_heap_block = InvalidBlockNumber; + int n_visited_heap_pages = 0; + ItemPointer tid; + Datum values[INDEX_MAX_KEYS]; + bool isnull[INDEX_MAX_KEYS]; + MemoryContext oldcontext; + + /* + * We use the index-only-scan machinery for this. With mostly-static + * tables that's a win because it avoids a heap visit. It's also a win + * for dynamic data, but the reason is less obvious; read on for details. + * + * In principle, we should scan the index with our current active + * snapshot, which is the best approximation we've got to what the query + * will see when executed. But that won't be exact if a new snap is taken + * before running the query, and it can be very expensive if a lot of + * recently-dead or uncommitted rows exist at the beginning or end of the + * index (because we'll laboriously fetch each one and reject it). + * Instead, we use SnapshotNonVacuumable. That will accept recently-dead + * and uncommitted rows as well as normal visible rows. On the other + * hand, it will reject known-dead rows, and thus not give a bogus answer + * when the extreme value has been deleted (unless the deletion was quite + * recent); that case motivates not using SnapshotAny here. + * + * A crucial point here is that SnapshotNonVacuumable, with + * GlobalVisTestFor(heapRel) as horizon, yields the inverse of the + * condition that the indexscan will use to decide that index entries are + * killable (see heap_hot_search_buffer()). Therefore, if the snapshot + * rejects a tuple (or more precisely, all tuples of a HOT chain) and we + * have to continue scanning past it, we know that the indexscan will mark + * that index entry killed. That means that the next + * get_actual_variable_endpoint() call will not have to re-consider that + * index entry. In this way we avoid repetitive work when this function + * is used a lot during planning. + * + * But using SnapshotNonVacuumable creates a hazard of its own. In a + * recently-created index, some index entries may point at "broken" HOT + * chains in which not all the tuple versions contain data matching the + * index entry. The live tuple version(s) certainly do match the index, + * but SnapshotNonVacuumable can accept recently-dead tuple versions that + * don't match. Hence, if we took data from the selected heap tuple, we + * might get a bogus answer that's not close to the index extremal value, + * or could even be NULL. We avoid this hazard because we take the data + * from the index entry not the heap. + * + * Despite all this care, there are situations where we might find many + * non-visible tuples near the end of the index. We don't want to expend + * a huge amount of time here, so we give up once we've read too many heap + * pages. When we fail for that reason, the caller will end up using + * whatever extremal value is recorded in pg_statistic. + */ + InitNonVacuumableSnapshot(SnapshotNonVacuumable, + GlobalVisTestFor(heapRel)); + + index_scan = index_beginscan(heapRel, indexRel, + &SnapshotNonVacuumable, + 1, 0); + /* Set it up for index-only scan */ + index_scan->xs_want_itup = true; + index_rescan(index_scan, scankeys, 1, NULL, 0); + + /* Fetch first/next tuple in specified direction */ + while ((tid = index_getnext_tid(index_scan, indexscandir)) != NULL) + { + BlockNumber block = ItemPointerGetBlockNumber(tid); + + if (!VM_ALL_VISIBLE(heapRel, + block, + &vmbuffer)) + { + /* Rats, we have to visit the heap to check visibility */ + if (!index_fetch_heap(index_scan, tableslot)) + { + /* + * No visible tuple for this index entry, so we need to + * advance to the next entry. Before doing so, count heap + * page fetches and give up if we've done too many. + * + * We don't charge a page fetch if this is the same heap page + * as the previous tuple. This is on the conservative side, + * since other recently-accessed pages are probably still in + * buffers too; but it's good enough for this heuristic. + */ +#define VISITED_PAGES_LIMIT 100 + + if (block != last_heap_block) + { + last_heap_block = block; + n_visited_heap_pages++; + if (n_visited_heap_pages > VISITED_PAGES_LIMIT) + break; + } + + continue; /* no visible tuple, try next index entry */ + } + + /* We don't actually need the heap tuple for anything */ + ExecClearTuple(tableslot); + + /* + * We don't care whether there's more than one visible tuple in + * the HOT chain; if any are visible, that's good enough. + */ + } + + /* + * We expect that btree will return data in IndexTuple not HeapTuple + * format. It's not lossy either. + */ + if (!index_scan->xs_itup) + elog(ERROR, "no data returned for index-only scan"); + if (index_scan->xs_recheck) + elog(ERROR, "unexpected recheck indication from btree"); + + /* OK to deconstruct the index tuple */ + index_deform_tuple(index_scan->xs_itup, + index_scan->xs_itupdesc, + values, isnull); + + /* Shouldn't have got a null, but be careful */ + if (isnull[0]) + elog(ERROR, "found unexpected null value in index \"%s\"", + RelationGetRelationName(indexRel)); + + /* Copy the index column value out to caller's context */ + oldcontext = MemoryContextSwitchTo(outercontext); + *endpointDatum = datumCopy(values[0], typByVal, typLen); + MemoryContextSwitchTo(oldcontext); + have_data = true; + break; + } + + if (vmbuffer != InvalidBuffer) + ReleaseBuffer(vmbuffer); + index_endscan(index_scan); + + return have_data; +} + +/* + * find_join_input_rel + * Look up the input relation for a join. + * + * We assume that the input relation's RelOptInfo must have been constructed + * already. + */ +static RelOptInfo * +find_join_input_rel(PlannerInfo *root, Relids relids) +{ + RelOptInfo *rel = NULL; + + switch (bms_membership(relids)) + { + case BMS_EMPTY_SET: + /* should not happen */ + break; + case BMS_SINGLETON: + rel = find_base_rel(root, bms_singleton_member(relids)); + break; + case BMS_MULTIPLE: + rel = find_join_rel(root, relids); + break; + } + + if (rel == NULL) + elog(ERROR, "could not find RelOptInfo for given relids"); + + return rel; +} + + +/*------------------------------------------------------------------------- + * + * Index cost estimation functions + * + *------------------------------------------------------------------------- + */ + +/* + * Extract the actual indexquals (as RestrictInfos) from an IndexClause list + */ +List * +get_quals_from_indexclauses(List *indexclauses) +{ + List *result = NIL; + ListCell *lc; + + foreach(lc, indexclauses) + { + IndexClause *iclause = lfirst_node(IndexClause, lc); + ListCell *lc2; + + foreach(lc2, iclause->indexquals) + { + RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc2); + + result = lappend(result, rinfo); + } + } + return result; +} + +/* + * Compute the total evaluation cost of the comparison operands in a list + * of index qual expressions. Since we know these will be evaluated just + * once per scan, there's no need to distinguish startup from per-row cost. + * + * This can be used either on the result of get_quals_from_indexclauses(), + * or directly on an indexorderbys list. In both cases, we expect that the + * index key expression is on the left side of binary clauses. + */ +Cost +index_other_operands_eval_cost(PlannerInfo *root, List *indexquals) +{ + Cost qual_arg_cost = 0; + ListCell *lc; + + foreach(lc, indexquals) + { + Expr *clause = (Expr *) lfirst(lc); + Node *other_operand; + QualCost index_qual_cost; + + /* + * Index quals will have RestrictInfos, indexorderbys won't. Look + * through RestrictInfo if present. + */ + if (IsA(clause, RestrictInfo)) + clause = ((RestrictInfo *) clause)->clause; + + if (IsA(clause, OpExpr)) + { + OpExpr *op = (OpExpr *) clause; + + other_operand = (Node *) lsecond(op->args); + } + else if (IsA(clause, RowCompareExpr)) + { + RowCompareExpr *rc = (RowCompareExpr *) clause; + + other_operand = (Node *) rc->rargs; + } + else if (IsA(clause, ScalarArrayOpExpr)) + { + ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) clause; + + other_operand = (Node *) lsecond(saop->args); + } + else if (IsA(clause, NullTest)) + { + other_operand = NULL; + } + else + { + elog(ERROR, "unsupported indexqual type: %d", + (int) nodeTag(clause)); + other_operand = NULL; /* keep compiler quiet */ + } + + cost_qual_eval_node(&index_qual_cost, other_operand, root); + qual_arg_cost += index_qual_cost.startup + index_qual_cost.per_tuple; + } + return qual_arg_cost; +} + +void +genericcostestimate(PlannerInfo *root, + IndexPath *path, + double loop_count, + GenericCosts *costs) +{ + IndexOptInfo *index = path->indexinfo; + List *indexQuals = get_quals_from_indexclauses(path->indexclauses); + List *indexOrderBys = path->indexorderbys; + Cost indexStartupCost; + Cost indexTotalCost; + Selectivity indexSelectivity; + double indexCorrelation; + double numIndexPages; + double numIndexTuples; + double spc_random_page_cost; + double num_sa_scans; + double num_outer_scans; + double num_scans; + double qual_op_cost; + double qual_arg_cost; + List *selectivityQuals; + ListCell *l; + + /* + * If the index is partial, AND the index predicate with the explicitly + * given indexquals to produce a more accurate idea of the index + * selectivity. + */ + selectivityQuals = add_predicate_to_index_quals(index, indexQuals); + + /* + * Check for ScalarArrayOpExpr index quals, and estimate the number of + * index scans that will be performed. + */ + num_sa_scans = 1; + foreach(l, indexQuals) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(l); + + if (IsA(rinfo->clause, ScalarArrayOpExpr)) + { + ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) rinfo->clause; + int alength = estimate_array_length(lsecond(saop->args)); + + if (alength > 1) + num_sa_scans *= alength; + } + } + + /* Estimate the fraction of main-table tuples that will be visited */ + indexSelectivity = clauselist_selectivity(root, selectivityQuals, + index->rel->relid, + JOIN_INNER, + NULL); + + /* + * If caller didn't give us an estimate, estimate the number of index + * tuples that will be visited. We do it in this rather peculiar-looking + * way in order to get the right answer for partial indexes. + */ + numIndexTuples = costs->numIndexTuples; + if (numIndexTuples <= 0.0) + { + numIndexTuples = indexSelectivity * index->rel->tuples; + + /* + * The above calculation counts all the tuples visited across all + * scans induced by ScalarArrayOpExpr nodes. We want to consider the + * average per-indexscan number, so adjust. This is a handy place to + * round to integer, too. (If caller supplied tuple estimate, it's + * responsible for handling these considerations.) + */ + numIndexTuples = rint(numIndexTuples / num_sa_scans); + } + + /* + * We can bound the number of tuples by the index size in any case. Also, + * always estimate at least one tuple is touched, even when + * indexSelectivity estimate is tiny. + */ + if (numIndexTuples > index->tuples) + numIndexTuples = index->tuples; + if (numIndexTuples < 1.0) + numIndexTuples = 1.0; + + /* + * Estimate the number of index pages that will be retrieved. + * + * We use the simplistic method of taking a pro-rata fraction of the total + * number of index pages. In effect, this counts only leaf pages and not + * any overhead such as index metapage or upper tree levels. + * + * In practice access to upper index levels is often nearly free because + * those tend to stay in cache under load; moreover, the cost involved is + * highly dependent on index type. We therefore ignore such costs here + * and leave it to the caller to add a suitable charge if needed. + */ + if (index->pages > 1 && index->tuples > 1) + numIndexPages = ceil(numIndexTuples * index->pages / index->tuples); + else + numIndexPages = 1.0; + + /* fetch estimated page cost for tablespace containing index */ + get_tablespace_page_costs(index->reltablespace, + &spc_random_page_cost, + NULL); + + /* + * Now compute the disk access costs. + * + * The above calculations are all per-index-scan. However, if we are in a + * nestloop inner scan, we can expect the scan to be repeated (with + * different search keys) for each row of the outer relation. Likewise, + * ScalarArrayOpExpr quals result in multiple index scans. This creates + * the potential for cache effects to reduce the number of disk page + * fetches needed. We want to estimate the average per-scan I/O cost in + * the presence of caching. + * + * We use the Mackert-Lohman formula (see costsize.c for details) to + * estimate the total number of page fetches that occur. While this + * wasn't what it was designed for, it seems a reasonable model anyway. + * Note that we are counting pages not tuples anymore, so we take N = T = + * index size, as if there were one "tuple" per page. + */ + num_outer_scans = loop_count; + num_scans = num_sa_scans * num_outer_scans; + + if (num_scans > 1) + { + double pages_fetched; + + /* total page fetches ignoring cache effects */ + pages_fetched = numIndexPages * num_scans; + + /* use Mackert and Lohman formula to adjust for cache effects */ + pages_fetched = index_pages_fetched(pages_fetched, + index->pages, + (double) index->pages, + root); + + /* + * Now compute the total disk access cost, and then report a pro-rated + * share for each outer scan. (Don't pro-rate for ScalarArrayOpExpr, + * since that's internal to the indexscan.) + */ + indexTotalCost = (pages_fetched * spc_random_page_cost) + / num_outer_scans; + } + else + { + /* + * For a single index scan, we just charge spc_random_page_cost per + * page touched. + */ + indexTotalCost = numIndexPages * spc_random_page_cost; + } + + /* + * CPU cost: any complex expressions in the indexquals will need to be + * evaluated once at the start of the scan to reduce them to runtime keys + * to pass to the index AM (see nodeIndexscan.c). We model the per-tuple + * CPU costs as cpu_index_tuple_cost plus one cpu_operator_cost per + * indexqual operator. Because we have numIndexTuples as a per-scan + * number, we have to multiply by num_sa_scans to get the correct result + * for ScalarArrayOpExpr cases. Similarly add in costs for any index + * ORDER BY expressions. + * + * Note: this neglects the possible costs of rechecking lossy operators. + * Detecting that that might be needed seems more expensive than it's + * worth, though, considering all the other inaccuracies here ... + */ + qual_arg_cost = index_other_operands_eval_cost(root, indexQuals) + + index_other_operands_eval_cost(root, indexOrderBys); + qual_op_cost = cpu_operator_cost * + (list_length(indexQuals) + list_length(indexOrderBys)); + + indexStartupCost = qual_arg_cost; + indexTotalCost += qual_arg_cost; + indexTotalCost += numIndexTuples * num_sa_scans * (cpu_index_tuple_cost + qual_op_cost); + + /* + * Generic assumption about index correlation: there isn't any. + */ + indexCorrelation = 0.0; + + /* + * Return everything to caller. + */ + costs->indexStartupCost = indexStartupCost; + costs->indexTotalCost = indexTotalCost; + costs->indexSelectivity = indexSelectivity; + costs->indexCorrelation = indexCorrelation; + costs->numIndexPages = numIndexPages; + costs->numIndexTuples = numIndexTuples; + costs->spc_random_page_cost = spc_random_page_cost; + costs->num_sa_scans = num_sa_scans; +} + +/* + * If the index is partial, add its predicate to the given qual list. + * + * ANDing the index predicate with the explicitly given indexquals produces + * a more accurate idea of the index's selectivity. However, we need to be + * careful not to insert redundant clauses, because clauselist_selectivity() + * is easily fooled into computing a too-low selectivity estimate. Our + * approach is to add only the predicate clause(s) that cannot be proven to + * be implied by the given indexquals. This successfully handles cases such + * as a qual "x = 42" used with a partial index "WHERE x >= 40 AND x < 50". + * There are many other cases where we won't detect redundancy, leading to a + * too-low selectivity estimate, which will bias the system in favor of using + * partial indexes where possible. That is not necessarily bad though. + * + * Note that indexQuals contains RestrictInfo nodes while the indpred + * does not, so the output list will be mixed. This is OK for both + * predicate_implied_by() and clauselist_selectivity(), but might be + * problematic if the result were passed to other things. + */ +List * +add_predicate_to_index_quals(IndexOptInfo *index, List *indexQuals) +{ + List *predExtraQuals = NIL; + ListCell *lc; + + if (index->indpred == NIL) + return indexQuals; + + foreach(lc, index->indpred) + { + Node *predQual = (Node *) lfirst(lc); + List *oneQual = list_make1(predQual); + + if (!predicate_implied_by(oneQual, indexQuals, false)) + predExtraQuals = list_concat(predExtraQuals, oneQual); + } + return list_concat(predExtraQuals, indexQuals); +} + + +void +btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count, + Cost *indexStartupCost, Cost *indexTotalCost, + Selectivity *indexSelectivity, double *indexCorrelation, + double *indexPages) +{ + IndexOptInfo *index = path->indexinfo; + GenericCosts costs = {0}; + Oid relid; + AttrNumber colnum; + VariableStatData vardata = {0}; + double numIndexTuples; + Cost descentCost; + List *indexBoundQuals; + int indexcol; + bool eqQualHere; + bool found_saop; + bool found_is_null_op; + double num_sa_scans; + ListCell *lc; + + /* + * For a btree scan, only leading '=' quals plus inequality quals for the + * immediately next attribute contribute to index selectivity (these are + * the "boundary quals" that determine the starting and stopping points of + * the index scan). Additional quals can suppress visits to the heap, so + * it's OK to count them in indexSelectivity, but they should not count + * for estimating numIndexTuples. So we must examine the given indexquals + * to find out which ones count as boundary quals. We rely on the + * knowledge that they are given in index column order. + * + * For a RowCompareExpr, we consider only the first column, just as + * rowcomparesel() does. + * + * If there's a ScalarArrayOpExpr in the quals, we'll actually perform N + * index scans not one, but the ScalarArrayOpExpr's operator can be + * considered to act the same as it normally does. + */ + indexBoundQuals = NIL; + indexcol = 0; + eqQualHere = false; + found_saop = false; + found_is_null_op = false; + num_sa_scans = 1; + foreach(lc, path->indexclauses) + { + IndexClause *iclause = lfirst_node(IndexClause, lc); + ListCell *lc2; + + if (indexcol != iclause->indexcol) + { + /* Beginning of a new column's quals */ + if (!eqQualHere) + break; /* done if no '=' qual for indexcol */ + eqQualHere = false; + indexcol++; + if (indexcol != iclause->indexcol) + break; /* no quals at all for indexcol */ + } + + /* Examine each indexqual associated with this index clause */ + foreach(lc2, iclause->indexquals) + { + RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc2); + Expr *clause = rinfo->clause; + Oid clause_op = InvalidOid; + int op_strategy; + + if (IsA(clause, OpExpr)) + { + OpExpr *op = (OpExpr *) clause; + + clause_op = op->opno; + } + else if (IsA(clause, RowCompareExpr)) + { + RowCompareExpr *rc = (RowCompareExpr *) clause; + + clause_op = linitial_oid(rc->opnos); + } + else if (IsA(clause, ScalarArrayOpExpr)) + { + ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) clause; + Node *other_operand = (Node *) lsecond(saop->args); + int alength = estimate_array_length(other_operand); + + clause_op = saop->opno; + found_saop = true; + /* count number of SA scans induced by indexBoundQuals only */ + if (alength > 1) + num_sa_scans *= alength; + } + else if (IsA(clause, NullTest)) + { + NullTest *nt = (NullTest *) clause; + + if (nt->nulltesttype == IS_NULL) + { + found_is_null_op = true; + /* IS NULL is like = for selectivity purposes */ + eqQualHere = true; + } + } + else + elog(ERROR, "unsupported indexqual type: %d", + (int) nodeTag(clause)); + + /* check for equality operator */ + if (OidIsValid(clause_op)) + { + op_strategy = get_op_opfamily_strategy(clause_op, + index->opfamily[indexcol]); + Assert(op_strategy != 0); /* not a member of opfamily?? */ + if (op_strategy == BTEqualStrategyNumber) + eqQualHere = true; + } + + indexBoundQuals = lappend(indexBoundQuals, rinfo); + } + } + + /* + * If index is unique and we found an '=' clause for each column, we can + * just assume numIndexTuples = 1 and skip the expensive + * clauselist_selectivity calculations. However, a ScalarArrayOp or + * NullTest invalidates that theory, even though it sets eqQualHere. + */ + if (index->unique && + indexcol == index->nkeycolumns - 1 && + eqQualHere && + !found_saop && + !found_is_null_op) + numIndexTuples = 1.0; + else + { + List *selectivityQuals; + Selectivity btreeSelectivity; + + /* + * If the index is partial, AND the index predicate with the + * index-bound quals to produce a more accurate idea of the number of + * rows covered by the bound conditions. + */ + selectivityQuals = add_predicate_to_index_quals(index, indexBoundQuals); + + btreeSelectivity = clauselist_selectivity(root, selectivityQuals, + index->rel->relid, + JOIN_INNER, + NULL); + numIndexTuples = btreeSelectivity * index->rel->tuples; + + /* + * As in genericcostestimate(), we have to adjust for any + * ScalarArrayOpExpr quals included in indexBoundQuals, and then round + * to integer. + */ + numIndexTuples = rint(numIndexTuples / num_sa_scans); + } + + /* + * Now do generic index cost estimation. + */ + costs.numIndexTuples = numIndexTuples; + + genericcostestimate(root, path, loop_count, &costs); + + /* + * Add a CPU-cost component to represent the costs of initial btree + * descent. We don't charge any I/O cost for touching upper btree levels, + * since they tend to stay in cache, but we still have to do about log2(N) + * comparisons to descend a btree of N leaf tuples. We charge one + * cpu_operator_cost per comparison. + * + * If there are ScalarArrayOpExprs, charge this once per SA scan. The + * ones after the first one are not startup cost so far as the overall + * plan is concerned, so add them only to "total" cost. + */ + if (index->tuples > 1) /* avoid computing log(0) */ + { + descentCost = ceil(log(index->tuples) / log(2.0)) * cpu_operator_cost; + costs.indexStartupCost += descentCost; + costs.indexTotalCost += costs.num_sa_scans * descentCost; + } + + /* + * Even though we're not charging I/O cost for touching upper btree pages, + * it's still reasonable to charge some CPU cost per page descended + * through. Moreover, if we had no such charge at all, bloated indexes + * would appear to have the same search cost as unbloated ones, at least + * in cases where only a single leaf page is expected to be visited. This + * cost is somewhat arbitrarily set at 50x cpu_operator_cost per page + * touched. The number of such pages is btree tree height plus one (ie, + * we charge for the leaf page too). As above, charge once per SA scan. + */ + descentCost = (index->tree_height + 1) * DEFAULT_PAGE_CPU_MULTIPLIER * cpu_operator_cost; + costs.indexStartupCost += descentCost; + costs.indexTotalCost += costs.num_sa_scans * descentCost; + + /* + * If we can get an estimate of the first column's ordering correlation C + * from pg_statistic, estimate the index correlation as C for a + * single-column index, or C * 0.75 for multiple columns. (The idea here + * is that multiple columns dilute the importance of the first column's + * ordering, but don't negate it entirely. Before 8.0 we divided the + * correlation by the number of columns, but that seems too strong.) + */ + if (index->indexkeys[0] != 0) + { + /* Simple variable --- look to stats for the underlying table */ + RangeTblEntry *rte = planner_rt_fetch(index->rel->relid, root); + + Assert(rte->rtekind == RTE_RELATION); + relid = rte->relid; + Assert(relid != InvalidOid); + colnum = index->indexkeys[0]; + + if (get_relation_stats_hook && + (*get_relation_stats_hook) (root, rte, colnum, &vardata)) + { + /* + * The hook took control of acquiring a stats tuple. If it did + * supply a tuple, it'd better have supplied a freefunc. + */ + if (HeapTupleIsValid(vardata.statsTuple) && + !vardata.freefunc) + elog(ERROR, "no function provided to release variable stats with"); + } + else + { + vardata.statsTuple = SearchSysCache3(STATRELATTINH, + ObjectIdGetDatum(relid), + Int16GetDatum(colnum), + BoolGetDatum(rte->inh)); + vardata.freefunc = ReleaseSysCache; + } + } + else + { + /* Expression --- maybe there are stats for the index itself */ + relid = index->indexoid; + colnum = 1; + + if (get_index_stats_hook && + (*get_index_stats_hook) (root, relid, colnum, &vardata)) + { + /* + * The hook took control of acquiring a stats tuple. If it did + * supply a tuple, it'd better have supplied a freefunc. + */ + if (HeapTupleIsValid(vardata.statsTuple) && + !vardata.freefunc) + elog(ERROR, "no function provided to release variable stats with"); + } + else + { + vardata.statsTuple = SearchSysCache3(STATRELATTINH, + ObjectIdGetDatum(relid), + Int16GetDatum(colnum), + BoolGetDatum(false)); + vardata.freefunc = ReleaseSysCache; + } + } + + if (HeapTupleIsValid(vardata.statsTuple)) + { + Oid sortop; + AttStatsSlot sslot; + + sortop = get_opfamily_member(index->opfamily[0], + index->opcintype[0], + index->opcintype[0], + BTLessStrategyNumber); + if (OidIsValid(sortop) && + get_attstatsslot(&sslot, vardata.statsTuple, + STATISTIC_KIND_CORRELATION, sortop, + ATTSTATSSLOT_NUMBERS)) + { + double varCorrelation; + + Assert(sslot.nnumbers == 1); + varCorrelation = sslot.numbers[0]; + + if (index->reverse_sort[0]) + varCorrelation = -varCorrelation; + + if (index->nkeycolumns > 1) + costs.indexCorrelation = varCorrelation * 0.75; + else + costs.indexCorrelation = varCorrelation; + + free_attstatsslot(&sslot); + } + } + + ReleaseVariableStats(vardata); + + *indexStartupCost = costs.indexStartupCost; + *indexTotalCost = costs.indexTotalCost; + *indexSelectivity = costs.indexSelectivity; + *indexCorrelation = costs.indexCorrelation; + *indexPages = costs.numIndexPages; +} + +void +hashcostestimate(PlannerInfo *root, IndexPath *path, double loop_count, + Cost *indexStartupCost, Cost *indexTotalCost, + Selectivity *indexSelectivity, double *indexCorrelation, + double *indexPages) +{ + GenericCosts costs = {0}; + + genericcostestimate(root, path, loop_count, &costs); + + /* + * A hash index has no descent costs as such, since the index AM can go + * directly to the target bucket after computing the hash value. There + * are a couple of other hash-specific costs that we could conceivably add + * here, though: + * + * Ideally we'd charge spc_random_page_cost for each page in the target + * bucket, not just the numIndexPages pages that genericcostestimate + * thought we'd visit. However in most cases we don't know which bucket + * that will be. There's no point in considering the average bucket size + * because the hash AM makes sure that's always one page. + * + * Likewise, we could consider charging some CPU for each index tuple in + * the bucket, if we knew how many there were. But the per-tuple cost is + * just a hash value comparison, not a general datatype-dependent + * comparison, so any such charge ought to be quite a bit less than + * cpu_operator_cost; which makes it probably not worth worrying about. + * + * A bigger issue is that chance hash-value collisions will result in + * wasted probes into the heap. We don't currently attempt to model this + * cost on the grounds that it's rare, but maybe it's not rare enough. + * (Any fix for this ought to consider the generic lossy-operator problem, + * though; it's not entirely hash-specific.) + */ + + *indexStartupCost = costs.indexStartupCost; + *indexTotalCost = costs.indexTotalCost; + *indexSelectivity = costs.indexSelectivity; + *indexCorrelation = costs.indexCorrelation; + *indexPages = costs.numIndexPages; +} + +void +gistcostestimate(PlannerInfo *root, IndexPath *path, double loop_count, + Cost *indexStartupCost, Cost *indexTotalCost, + Selectivity *indexSelectivity, double *indexCorrelation, + double *indexPages) +{ + IndexOptInfo *index = path->indexinfo; + GenericCosts costs = {0}; + Cost descentCost; + + genericcostestimate(root, path, loop_count, &costs); + + /* + * We model index descent costs similarly to those for btree, but to do + * that we first need an idea of the tree height. We somewhat arbitrarily + * assume that the fanout is 100, meaning the tree height is at most + * log100(index->pages). + * + * Although this computation isn't really expensive enough to require + * caching, we might as well use index->tree_height to cache it. + */ + if (index->tree_height < 0) /* unknown? */ + { + if (index->pages > 1) /* avoid computing log(0) */ + index->tree_height = (int) (log(index->pages) / log(100.0)); + else + index->tree_height = 0; + } + + /* + * Add a CPU-cost component to represent the costs of initial descent. We + * just use log(N) here not log2(N) since the branching factor isn't + * necessarily two anyway. As for btree, charge once per SA scan. + */ + if (index->tuples > 1) /* avoid computing log(0) */ + { + descentCost = ceil(log(index->tuples)) * cpu_operator_cost; + costs.indexStartupCost += descentCost; + costs.indexTotalCost += costs.num_sa_scans * descentCost; + } + + /* + * Likewise add a per-page charge, calculated the same as for btrees. + */ + descentCost = (index->tree_height + 1) * DEFAULT_PAGE_CPU_MULTIPLIER * cpu_operator_cost; + costs.indexStartupCost += descentCost; + costs.indexTotalCost += costs.num_sa_scans * descentCost; + + *indexStartupCost = costs.indexStartupCost; + *indexTotalCost = costs.indexTotalCost; + *indexSelectivity = costs.indexSelectivity; + *indexCorrelation = costs.indexCorrelation; + *indexPages = costs.numIndexPages; +} + +void +spgcostestimate(PlannerInfo *root, IndexPath *path, double loop_count, + Cost *indexStartupCost, Cost *indexTotalCost, + Selectivity *indexSelectivity, double *indexCorrelation, + double *indexPages) +{ + IndexOptInfo *index = path->indexinfo; + GenericCosts costs = {0}; + Cost descentCost; + + genericcostestimate(root, path, loop_count, &costs); + + /* + * We model index descent costs similarly to those for btree, but to do + * that we first need an idea of the tree height. We somewhat arbitrarily + * assume that the fanout is 100, meaning the tree height is at most + * log100(index->pages). + * + * Although this computation isn't really expensive enough to require + * caching, we might as well use index->tree_height to cache it. + */ + if (index->tree_height < 0) /* unknown? */ + { + if (index->pages > 1) /* avoid computing log(0) */ + index->tree_height = (int) (log(index->pages) / log(100.0)); + else + index->tree_height = 0; + } + + /* + * Add a CPU-cost component to represent the costs of initial descent. We + * just use log(N) here not log2(N) since the branching factor isn't + * necessarily two anyway. As for btree, charge once per SA scan. + */ + if (index->tuples > 1) /* avoid computing log(0) */ + { + descentCost = ceil(log(index->tuples)) * cpu_operator_cost; + costs.indexStartupCost += descentCost; + costs.indexTotalCost += costs.num_sa_scans * descentCost; + } + + /* + * Likewise add a per-page charge, calculated the same as for btrees. + */ + descentCost = (index->tree_height + 1) * DEFAULT_PAGE_CPU_MULTIPLIER * cpu_operator_cost; + costs.indexStartupCost += descentCost; + costs.indexTotalCost += costs.num_sa_scans * descentCost; + + *indexStartupCost = costs.indexStartupCost; + *indexTotalCost = costs.indexTotalCost; + *indexSelectivity = costs.indexSelectivity; + *indexCorrelation = costs.indexCorrelation; + *indexPages = costs.numIndexPages; +} + + +/* + * Support routines for gincostestimate + */ + +typedef struct +{ + bool attHasFullScan[INDEX_MAX_KEYS]; + bool attHasNormalScan[INDEX_MAX_KEYS]; + double partialEntries; + double exactEntries; + double searchEntries; + double arrayScans; +} GinQualCounts; + +/* + * Estimate the number of index terms that need to be searched for while + * testing the given GIN query, and increment the counts in *counts + * appropriately. If the query is unsatisfiable, return false. + */ +static bool +gincost_pattern(IndexOptInfo *index, int indexcol, + Oid clause_op, Datum query, + GinQualCounts *counts) +{ + FmgrInfo flinfo; + Oid extractProcOid; + Oid collation; + int strategy_op; + Oid lefttype, + righttype; + int32 nentries = 0; + bool *partial_matches = NULL; + Pointer *extra_data = NULL; + bool *nullFlags = NULL; + int32 searchMode = GIN_SEARCH_MODE_DEFAULT; + int32 i; + + Assert(indexcol < index->nkeycolumns); + + /* + * Get the operator's strategy number and declared input data types within + * the index opfamily. (We don't need the latter, but we use + * get_op_opfamily_properties because it will throw error if it fails to + * find a matching pg_amop entry.) + */ + get_op_opfamily_properties(clause_op, index->opfamily[indexcol], false, + &strategy_op, &lefttype, &righttype); + + /* + * GIN always uses the "default" support functions, which are those with + * lefttype == righttype == the opclass' opcintype (see + * IndexSupportInitialize in relcache.c). + */ + extractProcOid = get_opfamily_proc(index->opfamily[indexcol], + index->opcintype[indexcol], + index->opcintype[indexcol], + GIN_EXTRACTQUERY_PROC); + + if (!OidIsValid(extractProcOid)) + { + /* should not happen; throw same error as index_getprocinfo */ + elog(ERROR, "missing support function %d for attribute %d of index \"%s\"", + GIN_EXTRACTQUERY_PROC, indexcol + 1, + get_rel_name(index->indexoid)); + } + + /* + * Choose collation to pass to extractProc (should match initGinState). + */ + if (OidIsValid(index->indexcollations[indexcol])) + collation = index->indexcollations[indexcol]; + else + collation = DEFAULT_COLLATION_OID; + + fmgr_info(extractProcOid, &flinfo); + + set_fn_opclass_options(&flinfo, index->opclassoptions[indexcol]); + + FunctionCall7Coll(&flinfo, + collation, + query, + PointerGetDatum(&nentries), + UInt16GetDatum(strategy_op), + PointerGetDatum(&partial_matches), + PointerGetDatum(&extra_data), + PointerGetDatum(&nullFlags), + PointerGetDatum(&searchMode)); + + if (nentries <= 0 && searchMode == GIN_SEARCH_MODE_DEFAULT) + { + /* No match is possible */ + return false; + } + + for (i = 0; i < nentries; i++) + { + /* + * For partial match we haven't any information to estimate number of + * matched entries in index, so, we just estimate it as 100 + */ + if (partial_matches && partial_matches[i]) + counts->partialEntries += 100; + else + counts->exactEntries++; + + counts->searchEntries++; + } + + if (searchMode == GIN_SEARCH_MODE_DEFAULT) + { + counts->attHasNormalScan[indexcol] = true; + } + else if (searchMode == GIN_SEARCH_MODE_INCLUDE_EMPTY) + { + /* Treat "include empty" like an exact-match item */ + counts->attHasNormalScan[indexcol] = true; + counts->exactEntries++; + counts->searchEntries++; + } + else + { + /* It's GIN_SEARCH_MODE_ALL */ + counts->attHasFullScan[indexcol] = true; + } + + return true; +} + +/* + * Estimate the number of index terms that need to be searched for while + * testing the given GIN index clause, and increment the counts in *counts + * appropriately. If the query is unsatisfiable, return false. + */ +static bool +gincost_opexpr(PlannerInfo *root, + IndexOptInfo *index, + int indexcol, + OpExpr *clause, + GinQualCounts *counts) +{ + Oid clause_op = clause->opno; + Node *operand = (Node *) lsecond(clause->args); + + /* aggressively reduce to a constant, and look through relabeling */ + operand = estimate_expression_value(root, operand); + + if (IsA(operand, RelabelType)) + operand = (Node *) ((RelabelType *) operand)->arg; + + /* + * It's impossible to call extractQuery method for unknown operand. So + * unless operand is a Const we can't do much; just assume there will be + * one ordinary search entry from the operand at runtime. + */ + if (!IsA(operand, Const)) + { + counts->exactEntries++; + counts->searchEntries++; + return true; + } + + /* If Const is null, there can be no matches */ + if (((Const *) operand)->constisnull) + return false; + + /* Otherwise, apply extractQuery and get the actual term counts */ + return gincost_pattern(index, indexcol, clause_op, + ((Const *) operand)->constvalue, + counts); +} + +/* + * Estimate the number of index terms that need to be searched for while + * testing the given GIN index clause, and increment the counts in *counts + * appropriately. If the query is unsatisfiable, return false. + * + * A ScalarArrayOpExpr will give rise to N separate indexscans at runtime, + * each of which involves one value from the RHS array, plus all the + * non-array quals (if any). To model this, we average the counts across + * the RHS elements, and add the averages to the counts in *counts (which + * correspond to per-indexscan costs). We also multiply counts->arrayScans + * by N, causing gincostestimate to scale up its estimates accordingly. + */ +static bool +gincost_scalararrayopexpr(PlannerInfo *root, + IndexOptInfo *index, + int indexcol, + ScalarArrayOpExpr *clause, + double numIndexEntries, + GinQualCounts *counts) +{ + Oid clause_op = clause->opno; + Node *rightop = (Node *) lsecond(clause->args); + ArrayType *arrayval; + int16 elmlen; + bool elmbyval; + char elmalign; + int numElems; + Datum *elemValues; + bool *elemNulls; + GinQualCounts arraycounts; + int numPossible = 0; + int i; + + Assert(clause->useOr); + + /* aggressively reduce to a constant, and look through relabeling */ + rightop = estimate_expression_value(root, rightop); + + if (IsA(rightop, RelabelType)) + rightop = (Node *) ((RelabelType *) rightop)->arg; + + /* + * It's impossible to call extractQuery method for unknown operand. So + * unless operand is a Const we can't do much; just assume there will be + * one ordinary search entry from each array entry at runtime, and fall + * back on a probably-bad estimate of the number of array entries. + */ + if (!IsA(rightop, Const)) + { + counts->exactEntries++; + counts->searchEntries++; + counts->arrayScans *= estimate_array_length(rightop); + return true; + } + + /* If Const is null, there can be no matches */ + if (((Const *) rightop)->constisnull) + return false; + + /* Otherwise, extract the array elements and iterate over them */ + arrayval = DatumGetArrayTypeP(((Const *) rightop)->constvalue); + get_typlenbyvalalign(ARR_ELEMTYPE(arrayval), + &elmlen, &elmbyval, &elmalign); + deconstruct_array(arrayval, + ARR_ELEMTYPE(arrayval), + elmlen, elmbyval, elmalign, + &elemValues, &elemNulls, &numElems); + + memset(&arraycounts, 0, sizeof(arraycounts)); + + for (i = 0; i < numElems; i++) + { + GinQualCounts elemcounts; + + /* NULL can't match anything, so ignore, as the executor will */ + if (elemNulls[i]) + continue; + + /* Otherwise, apply extractQuery and get the actual term counts */ + memset(&elemcounts, 0, sizeof(elemcounts)); + + if (gincost_pattern(index, indexcol, clause_op, elemValues[i], + &elemcounts)) + { + /* We ignore array elements that are unsatisfiable patterns */ + numPossible++; + + if (elemcounts.attHasFullScan[indexcol] && + !elemcounts.attHasNormalScan[indexcol]) + { + /* + * Full index scan will be required. We treat this as if + * every key in the index had been listed in the query; is + * that reasonable? + */ + elemcounts.partialEntries = 0; + elemcounts.exactEntries = numIndexEntries; + elemcounts.searchEntries = numIndexEntries; + } + arraycounts.partialEntries += elemcounts.partialEntries; + arraycounts.exactEntries += elemcounts.exactEntries; + arraycounts.searchEntries += elemcounts.searchEntries; + } + } + + if (numPossible == 0) + { + /* No satisfiable patterns in the array */ + return false; + } + + /* + * Now add the averages to the global counts. This will give us an + * estimate of the average number of terms searched for in each indexscan, + * including contributions from both array and non-array quals. + */ + counts->partialEntries += arraycounts.partialEntries / numPossible; + counts->exactEntries += arraycounts.exactEntries / numPossible; + counts->searchEntries += arraycounts.searchEntries / numPossible; + + counts->arrayScans *= numPossible; + + return true; +} + +/* + * GIN has search behavior completely different from other index types + */ +void +gincostestimate(PlannerInfo *root, IndexPath *path, double loop_count, + Cost *indexStartupCost, Cost *indexTotalCost, + Selectivity *indexSelectivity, double *indexCorrelation, + double *indexPages) +{ + IndexOptInfo *index = path->indexinfo; + List *indexQuals = get_quals_from_indexclauses(path->indexclauses); + List *selectivityQuals; + double numPages = index->pages, + numTuples = index->tuples; + double numEntryPages, + numDataPages, + numPendingPages, + numEntries; + GinQualCounts counts; + bool matchPossible; + bool fullIndexScan; + double partialScale; + double entryPagesFetched, + dataPagesFetched, + dataPagesFetchedBySel; + double qual_op_cost, + qual_arg_cost, + spc_random_page_cost, + outer_scans; + Cost descentCost; + Relation indexRel; + GinStatsData ginStats; + ListCell *lc; + int i; + + /* + * Obtain statistical information from the meta page, if possible. Else + * set ginStats to zeroes, and we'll cope below. + */ + if (!index->hypothetical) + { + /* Lock should have already been obtained in plancat.c */ + indexRel = index_open(index->indexoid, NoLock); + ginGetStats(indexRel, &ginStats); + index_close(indexRel, NoLock); + } + else + { + memset(&ginStats, 0, sizeof(ginStats)); + } + + /* + * Assuming we got valid (nonzero) stats at all, nPendingPages can be + * trusted, but the other fields are data as of the last VACUUM. We can + * scale them up to account for growth since then, but that method only + * goes so far; in the worst case, the stats might be for a completely + * empty index, and scaling them will produce pretty bogus numbers. + * Somewhat arbitrarily, set the cutoff for doing scaling at 4X growth; if + * it's grown more than that, fall back to estimating things only from the + * assumed-accurate index size. But we'll trust nPendingPages in any case + * so long as it's not clearly insane, ie, more than the index size. + */ + if (ginStats.nPendingPages < numPages) + numPendingPages = ginStats.nPendingPages; + else + numPendingPages = 0; + + if (numPages > 0 && ginStats.nTotalPages <= numPages && + ginStats.nTotalPages > numPages / 4 && + ginStats.nEntryPages > 0 && ginStats.nEntries > 0) + { + /* + * OK, the stats seem close enough to sane to be trusted. But we + * still need to scale them by the ratio numPages / nTotalPages to + * account for growth since the last VACUUM. + */ + double scale = numPages / ginStats.nTotalPages; + + numEntryPages = ceil(ginStats.nEntryPages * scale); + numDataPages = ceil(ginStats.nDataPages * scale); + numEntries = ceil(ginStats.nEntries * scale); + /* ensure we didn't round up too much */ + numEntryPages = Min(numEntryPages, numPages - numPendingPages); + numDataPages = Min(numDataPages, + numPages - numPendingPages - numEntryPages); + } + else + { + /* + * We might get here because it's a hypothetical index, or an index + * created pre-9.1 and never vacuumed since upgrading (in which case + * its stats would read as zeroes), or just because it's grown too + * much since the last VACUUM for us to put our faith in scaling. + * + * Invent some plausible internal statistics based on the index page + * count (and clamp that to at least 10 pages, just in case). We + * estimate that 90% of the index is entry pages, and the rest is data + * pages. Estimate 100 entries per entry page; this is rather bogus + * since it'll depend on the size of the keys, but it's more robust + * than trying to predict the number of entries per heap tuple. + */ + numPages = Max(numPages, 10); + numEntryPages = floor((numPages - numPendingPages) * 0.90); + numDataPages = numPages - numPendingPages - numEntryPages; + numEntries = floor(numEntryPages * 100); + } + + /* In an empty index, numEntries could be zero. Avoid divide-by-zero */ + if (numEntries < 1) + numEntries = 1; + + /* + * If the index is partial, AND the index predicate with the index-bound + * quals to produce a more accurate idea of the number of rows covered by + * the bound conditions. + */ + selectivityQuals = add_predicate_to_index_quals(index, indexQuals); + + /* Estimate the fraction of main-table tuples that will be visited */ + *indexSelectivity = clauselist_selectivity(root, selectivityQuals, + index->rel->relid, + JOIN_INNER, + NULL); + + /* fetch estimated page cost for tablespace containing index */ + get_tablespace_page_costs(index->reltablespace, + &spc_random_page_cost, + NULL); + + /* + * Generic assumption about index correlation: there isn't any. + */ + *indexCorrelation = 0.0; + + /* + * Examine quals to estimate number of search entries & partial matches + */ + memset(&counts, 0, sizeof(counts)); + counts.arrayScans = 1; + matchPossible = true; + + foreach(lc, path->indexclauses) + { + IndexClause *iclause = lfirst_node(IndexClause, lc); + ListCell *lc2; + + foreach(lc2, iclause->indexquals) + { + RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc2); + Expr *clause = rinfo->clause; + + if (IsA(clause, OpExpr)) + { + matchPossible = gincost_opexpr(root, + index, + iclause->indexcol, + (OpExpr *) clause, + &counts); + if (!matchPossible) + break; + } + else if (IsA(clause, ScalarArrayOpExpr)) + { + matchPossible = gincost_scalararrayopexpr(root, + index, + iclause->indexcol, + (ScalarArrayOpExpr *) clause, + numEntries, + &counts); + if (!matchPossible) + break; + } + else + { + /* shouldn't be anything else for a GIN index */ + elog(ERROR, "unsupported GIN indexqual type: %d", + (int) nodeTag(clause)); + } + } + } + + /* Fall out if there were any provably-unsatisfiable quals */ + if (!matchPossible) + { + *indexStartupCost = 0; + *indexTotalCost = 0; + *indexSelectivity = 0; + return; + } + + /* + * If attribute has a full scan and at the same time doesn't have normal + * scan, then we'll have to scan all non-null entries of that attribute. + * Currently, we don't have per-attribute statistics for GIN. Thus, we + * must assume the whole GIN index has to be scanned in this case. + */ + fullIndexScan = false; + for (i = 0; i < index->nkeycolumns; i++) + { + if (counts.attHasFullScan[i] && !counts.attHasNormalScan[i]) + { + fullIndexScan = true; + break; + } + } + + if (fullIndexScan || indexQuals == NIL) + { + /* + * Full index scan will be required. We treat this as if every key in + * the index had been listed in the query; is that reasonable? + */ + counts.partialEntries = 0; + counts.exactEntries = numEntries; + counts.searchEntries = numEntries; + } + + /* Will we have more than one iteration of a nestloop scan? */ + outer_scans = loop_count; + + /* + * Compute cost to begin scan, first of all, pay attention to pending + * list. + */ + entryPagesFetched = numPendingPages; + + /* + * Estimate number of entry pages read. We need to do + * counts.searchEntries searches. Use a power function as it should be, + * but tuples on leaf pages usually is much greater. Here we include all + * searches in entry tree, including search of first entry in partial + * match algorithm + */ + entryPagesFetched += ceil(counts.searchEntries * rint(pow(numEntryPages, 0.15))); + + /* + * Add an estimate of entry pages read by partial match algorithm. It's a + * scan over leaf pages in entry tree. We haven't any useful stats here, + * so estimate it as proportion. Because counts.partialEntries is really + * pretty bogus (see code above), it's possible that it is more than + * numEntries; clamp the proportion to ensure sanity. + */ + partialScale = counts.partialEntries / numEntries; + partialScale = Min(partialScale, 1.0); + + entryPagesFetched += ceil(numEntryPages * partialScale); + + /* + * Partial match algorithm reads all data pages before doing actual scan, + * so it's a startup cost. Again, we haven't any useful stats here, so + * estimate it as proportion. + */ + dataPagesFetched = ceil(numDataPages * partialScale); + + *indexStartupCost = 0; + *indexTotalCost = 0; + + /* + * Add a CPU-cost component to represent the costs of initial entry btree + * descent. We don't charge any I/O cost for touching upper btree levels, + * since they tend to stay in cache, but we still have to do about log2(N) + * comparisons to descend a btree of N leaf tuples. We charge one + * cpu_operator_cost per comparison. + * + * If there are ScalarArrayOpExprs, charge this once per SA scan. The + * ones after the first one are not startup cost so far as the overall + * plan is concerned, so add them only to "total" cost. + */ + if (numEntries > 1) /* avoid computing log(0) */ + { + descentCost = ceil(log(numEntries) / log(2.0)) * cpu_operator_cost; + *indexStartupCost += descentCost * counts.searchEntries; + *indexTotalCost += counts.arrayScans * descentCost * counts.searchEntries; + } + + /* + * Add a cpu cost per entry-page fetched. This is not amortized over a + * loop. + */ + *indexStartupCost += entryPagesFetched * DEFAULT_PAGE_CPU_MULTIPLIER * cpu_operator_cost; + *indexTotalCost += entryPagesFetched * counts.arrayScans * DEFAULT_PAGE_CPU_MULTIPLIER * cpu_operator_cost; + + /* + * Add a cpu cost per data-page fetched. This is also not amortized over a + * loop. Since those are the data pages from the partial match algorithm, + * charge them as startup cost. + */ + *indexStartupCost += DEFAULT_PAGE_CPU_MULTIPLIER * cpu_operator_cost * dataPagesFetched; + + /* + * Since we add the startup cost to the total cost later on, remove the + * initial arrayscan from the total. + */ + *indexTotalCost += dataPagesFetched * (counts.arrayScans - 1) * DEFAULT_PAGE_CPU_MULTIPLIER * cpu_operator_cost; + + /* + * Calculate cache effects if more than one scan due to nestloops or array + * quals. The result is pro-rated per nestloop scan, but the array qual + * factor shouldn't be pro-rated (compare genericcostestimate). + */ + if (outer_scans > 1 || counts.arrayScans > 1) + { + entryPagesFetched *= outer_scans * counts.arrayScans; + entryPagesFetched = index_pages_fetched(entryPagesFetched, + (BlockNumber) numEntryPages, + numEntryPages, root); + entryPagesFetched /= outer_scans; + dataPagesFetched *= outer_scans * counts.arrayScans; + dataPagesFetched = index_pages_fetched(dataPagesFetched, + (BlockNumber) numDataPages, + numDataPages, root); + dataPagesFetched /= outer_scans; + } + + /* + * Here we use random page cost because logically-close pages could be far + * apart on disk. + */ + *indexStartupCost += (entryPagesFetched + dataPagesFetched) * spc_random_page_cost; + + /* + * Now compute the number of data pages fetched during the scan. + * + * We assume every entry to have the same number of items, and that there + * is no overlap between them. (XXX: tsvector and array opclasses collect + * statistics on the frequency of individual keys; it would be nice to use + * those here.) + */ + dataPagesFetched = ceil(numDataPages * counts.exactEntries / numEntries); + + /* + * If there is a lot of overlap among the entries, in particular if one of + * the entries is very frequent, the above calculation can grossly + * under-estimate. As a simple cross-check, calculate a lower bound based + * on the overall selectivity of the quals. At a minimum, we must read + * one item pointer for each matching entry. + * + * The width of each item pointer varies, based on the level of + * compression. We don't have statistics on that, but an average of + * around 3 bytes per item is fairly typical. + */ + dataPagesFetchedBySel = ceil(*indexSelectivity * + (numTuples / (BLCKSZ / 3))); + if (dataPagesFetchedBySel > dataPagesFetched) + dataPagesFetched = dataPagesFetchedBySel; + + /* Add one page cpu-cost to the startup cost */ + *indexStartupCost += DEFAULT_PAGE_CPU_MULTIPLIER * cpu_operator_cost * counts.searchEntries; + + /* + * Add once again a CPU-cost for those data pages, before amortizing for + * cache. + */ + *indexTotalCost += dataPagesFetched * counts.arrayScans * DEFAULT_PAGE_CPU_MULTIPLIER * cpu_operator_cost; + + /* Account for cache effects, the same as above */ + if (outer_scans > 1 || counts.arrayScans > 1) + { + dataPagesFetched *= outer_scans * counts.arrayScans; + dataPagesFetched = index_pages_fetched(dataPagesFetched, + (BlockNumber) numDataPages, + numDataPages, root); + dataPagesFetched /= outer_scans; + } + + /* And apply random_page_cost as the cost per page */ + *indexTotalCost += *indexStartupCost + + dataPagesFetched * spc_random_page_cost; + + /* + * Add on index qual eval costs, much as in genericcostestimate. We charge + * cpu but we can disregard indexorderbys, since GIN doesn't support + * those. + */ + qual_arg_cost = index_other_operands_eval_cost(root, indexQuals); + qual_op_cost = cpu_operator_cost * list_length(indexQuals); + + *indexStartupCost += qual_arg_cost; + *indexTotalCost += qual_arg_cost; + + /* + * Add a cpu cost per search entry, corresponding to the actual visited + * entries. + */ + *indexTotalCost += (counts.searchEntries * counts.arrayScans) * (qual_op_cost); + /* Now add a cpu cost per tuple in the posting lists / trees */ + *indexTotalCost += (numTuples * *indexSelectivity) * (cpu_index_tuple_cost); + *indexPages = dataPagesFetched; +} + +/* + * BRIN has search behavior completely different from other index types + */ +void +brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count, + Cost *indexStartupCost, Cost *indexTotalCost, + Selectivity *indexSelectivity, double *indexCorrelation, + double *indexPages) +{ + IndexOptInfo *index = path->indexinfo; + List *indexQuals = get_quals_from_indexclauses(path->indexclauses); + double numPages = index->pages; + RelOptInfo *baserel = index->rel; + RangeTblEntry *rte = planner_rt_fetch(baserel->relid, root); + Cost spc_seq_page_cost; + Cost spc_random_page_cost; + double qual_arg_cost; + double qualSelectivity; + BrinStatsData statsData; + double indexRanges; + double minimalRanges; + double estimatedRanges; + double selec; + Relation indexRel; + ListCell *l; + VariableStatData vardata; + + Assert(rte->rtekind == RTE_RELATION); + + /* fetch estimated page cost for the tablespace containing the index */ + get_tablespace_page_costs(index->reltablespace, + &spc_random_page_cost, + &spc_seq_page_cost); + + /* + * Obtain some data from the index itself, if possible. Otherwise invent + * some plausible internal statistics based on the relation page count. + */ + if (!index->hypothetical) + { + /* + * A lock should have already been obtained on the index in plancat.c. + */ + indexRel = index_open(index->indexoid, NoLock); + brinGetStats(indexRel, &statsData); + index_close(indexRel, NoLock); + + /* work out the actual number of ranges in the index */ + indexRanges = Max(ceil((double) baserel->pages / + statsData.pagesPerRange), 1.0); + } + else + { + /* + * Assume default number of pages per range, and estimate the number + * of ranges based on that. + */ + indexRanges = Max(ceil((double) baserel->pages / + BRIN_DEFAULT_PAGES_PER_RANGE), 1.0); + + statsData.pagesPerRange = BRIN_DEFAULT_PAGES_PER_RANGE; + statsData.revmapNumPages = (indexRanges / REVMAP_PAGE_MAXITEMS) + 1; + } + + /* + * Compute index correlation + * + * Because we can use all index quals equally when scanning, we can use + * the largest correlation (in absolute value) among columns used by the + * query. Start at zero, the worst possible case. If we cannot find any + * correlation statistics, we will keep it as 0. + */ + *indexCorrelation = 0; + + foreach(l, path->indexclauses) + { + IndexClause *iclause = lfirst_node(IndexClause, l); + AttrNumber attnum = index->indexkeys[iclause->indexcol]; + + /* attempt to lookup stats in relation for this index column */ + if (attnum != 0) + { + /* Simple variable -- look to stats for the underlying table */ + if (get_relation_stats_hook && + (*get_relation_stats_hook) (root, rte, attnum, &vardata)) + { + /* + * The hook took control of acquiring a stats tuple. If it + * did supply a tuple, it'd better have supplied a freefunc. + */ + if (HeapTupleIsValid(vardata.statsTuple) && !vardata.freefunc) + elog(ERROR, + "no function provided to release variable stats with"); + } + else + { + vardata.statsTuple = + SearchSysCache3(STATRELATTINH, + ObjectIdGetDatum(rte->relid), + Int16GetDatum(attnum), + BoolGetDatum(false)); + vardata.freefunc = ReleaseSysCache; + } + } + else + { + /* + * Looks like we've found an expression column in the index. Let's + * see if there's any stats for it. + */ + + /* get the attnum from the 0-based index. */ + attnum = iclause->indexcol + 1; + + if (get_index_stats_hook && + (*get_index_stats_hook) (root, index->indexoid, attnum, &vardata)) + { + /* + * The hook took control of acquiring a stats tuple. If it + * did supply a tuple, it'd better have supplied a freefunc. + */ + if (HeapTupleIsValid(vardata.statsTuple) && + !vardata.freefunc) + elog(ERROR, "no function provided to release variable stats with"); + } + else + { + vardata.statsTuple = SearchSysCache3(STATRELATTINH, + ObjectIdGetDatum(index->indexoid), + Int16GetDatum(attnum), + BoolGetDatum(false)); + vardata.freefunc = ReleaseSysCache; + } + } + + if (HeapTupleIsValid(vardata.statsTuple)) + { + AttStatsSlot sslot; + + if (get_attstatsslot(&sslot, vardata.statsTuple, + STATISTIC_KIND_CORRELATION, InvalidOid, + ATTSTATSSLOT_NUMBERS)) + { + double varCorrelation = 0.0; + + if (sslot.nnumbers > 0) + varCorrelation = fabs(sslot.numbers[0]); + + if (varCorrelation > *indexCorrelation) + *indexCorrelation = varCorrelation; + + free_attstatsslot(&sslot); + } + } + + ReleaseVariableStats(vardata); + } + + qualSelectivity = clauselist_selectivity(root, indexQuals, + baserel->relid, + JOIN_INNER, NULL); + + /* + * Now calculate the minimum possible ranges we could match with if all of + * the rows were in the perfect order in the table's heap. + */ + minimalRanges = ceil(indexRanges * qualSelectivity); + + /* + * Now estimate the number of ranges that we'll touch by using the + * indexCorrelation from the stats. Careful not to divide by zero (note + * we're using the absolute value of the correlation). + */ + if (*indexCorrelation < 1.0e-10) + estimatedRanges = indexRanges; + else + estimatedRanges = Min(minimalRanges / *indexCorrelation, indexRanges); + + /* we expect to visit this portion of the table */ + selec = estimatedRanges / indexRanges; + + CLAMP_PROBABILITY(selec); + + *indexSelectivity = selec; + + /* + * Compute the index qual costs, much as in genericcostestimate, to add to + * the index costs. We can disregard indexorderbys, since BRIN doesn't + * support those. + */ + qual_arg_cost = index_other_operands_eval_cost(root, indexQuals); + + /* + * Compute the startup cost as the cost to read the whole revmap + * sequentially, including the cost to execute the index quals. + */ + *indexStartupCost = + spc_seq_page_cost * statsData.revmapNumPages * loop_count; + *indexStartupCost += qual_arg_cost; + + /* + * To read a BRIN index there might be a bit of back and forth over + * regular pages, as revmap might point to them out of sequential order; + * calculate the total cost as reading the whole index in random order. + */ + *indexTotalCost = *indexStartupCost + + spc_random_page_cost * (numPages - statsData.revmapNumPages) * loop_count; + + /* + * Charge a small amount per range tuple which we expect to match to. This + * is meant to reflect the costs of manipulating the bitmap. The BRIN scan + * will set a bit for each page in the range when we find a matching + * range, so we must multiply the charge by the number of pages in the + * range. + */ + *indexTotalCost += 0.1 * cpu_operator_cost * estimatedRanges * + statsData.pagesPerRange; + + *indexPages = index->pages; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/tid.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/tid.c new file mode 100644 index 00000000000..77fb74ab0c1 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/tid.c @@ -0,0 +1,425 @@ +/*------------------------------------------------------------------------- + * + * tid.c + * Functions for the built-in type tuple id + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/adt/tid.c + * + * NOTES + * input routine largely stolen from boxin(). + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <math.h> +#include <limits.h> + +#include "access/heapam.h" +#include "access/sysattr.h" +#include "access/tableam.h" +#include "catalog/namespace.h" +#include "catalog/pg_type.h" +#include "common/hashfn.h" +#include "libpq/pqformat.h" +#include "miscadmin.h" +#include "parser/parsetree.h" +#include "utils/acl.h" +#include "utils/builtins.h" +#include "utils/lsyscache.h" +#include "utils/rel.h" +#include "utils/snapmgr.h" +#include "utils/varlena.h" + + +#define LDELIM '(' +#define RDELIM ')' +#define DELIM ',' +#define NTIDARGS 2 + +static ItemPointer currtid_for_view(Relation viewrel, ItemPointer tid); + +/* ---------------------------------------------------------------- + * tidin + * ---------------------------------------------------------------- + */ +Datum +tidin(PG_FUNCTION_ARGS) +{ + char *str = PG_GETARG_CSTRING(0); + Node *escontext = fcinfo->context; + char *p, + *coord[NTIDARGS]; + int i; + ItemPointer result; + BlockNumber blockNumber; + OffsetNumber offsetNumber; + char *badp; + unsigned long cvt; + + for (i = 0, p = str; *p && i < NTIDARGS && *p != RDELIM; p++) + if (*p == DELIM || (*p == LDELIM && i == 0)) + coord[i++] = p + 1; + + if (i < NTIDARGS) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s: \"%s\"", + "tid", str))); + + errno = 0; + cvt = strtoul(coord[0], &badp, 10); + if (errno || *badp != DELIM) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s: \"%s\"", + "tid", str))); + blockNumber = (BlockNumber) cvt; + + /* + * Cope with possibility that unsigned long is wider than BlockNumber, in + * which case strtoul will not raise an error for some values that are out + * of the range of BlockNumber. (See similar code in oidin().) + */ +#if SIZEOF_LONG > 4 + if (cvt != (unsigned long) blockNumber && + cvt != (unsigned long) ((int32) blockNumber)) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s: \"%s\"", + "tid", str))); +#endif + + cvt = strtoul(coord[1], &badp, 10); + if (errno || *badp != RDELIM || + cvt > USHRT_MAX) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s: \"%s\"", + "tid", str))); + offsetNumber = (OffsetNumber) cvt; + + result = (ItemPointer) palloc(sizeof(ItemPointerData)); + + ItemPointerSet(result, blockNumber, offsetNumber); + + PG_RETURN_ITEMPOINTER(result); +} + +/* ---------------------------------------------------------------- + * tidout + * ---------------------------------------------------------------- + */ +Datum +tidout(PG_FUNCTION_ARGS) +{ + ItemPointer itemPtr = PG_GETARG_ITEMPOINTER(0); + BlockNumber blockNumber; + OffsetNumber offsetNumber; + char buf[32]; + + blockNumber = ItemPointerGetBlockNumberNoCheck(itemPtr); + offsetNumber = ItemPointerGetOffsetNumberNoCheck(itemPtr); + + /* Perhaps someday we should output this as a record. */ + snprintf(buf, sizeof(buf), "(%u,%u)", blockNumber, offsetNumber); + + PG_RETURN_CSTRING(pstrdup(buf)); +} + +/* + * tidrecv - converts external binary format to tid + */ +Datum +tidrecv(PG_FUNCTION_ARGS) +{ + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + ItemPointer result; + BlockNumber blockNumber; + OffsetNumber offsetNumber; + + blockNumber = pq_getmsgint(buf, sizeof(blockNumber)); + offsetNumber = pq_getmsgint(buf, sizeof(offsetNumber)); + + result = (ItemPointer) palloc(sizeof(ItemPointerData)); + + ItemPointerSet(result, blockNumber, offsetNumber); + + PG_RETURN_ITEMPOINTER(result); +} + +/* + * tidsend - converts tid to binary format + */ +Datum +tidsend(PG_FUNCTION_ARGS) +{ + ItemPointer itemPtr = PG_GETARG_ITEMPOINTER(0); + StringInfoData buf; + + pq_begintypsend(&buf); + pq_sendint32(&buf, ItemPointerGetBlockNumberNoCheck(itemPtr)); + pq_sendint16(&buf, ItemPointerGetOffsetNumberNoCheck(itemPtr)); + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} + +/***************************************************************************** + * PUBLIC ROUTINES * + *****************************************************************************/ + +Datum +tideq(PG_FUNCTION_ARGS) +{ + ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0); + ItemPointer arg2 = PG_GETARG_ITEMPOINTER(1); + + PG_RETURN_BOOL(ItemPointerCompare(arg1, arg2) == 0); +} + +Datum +tidne(PG_FUNCTION_ARGS) +{ + ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0); + ItemPointer arg2 = PG_GETARG_ITEMPOINTER(1); + + PG_RETURN_BOOL(ItemPointerCompare(arg1, arg2) != 0); +} + +Datum +tidlt(PG_FUNCTION_ARGS) +{ + ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0); + ItemPointer arg2 = PG_GETARG_ITEMPOINTER(1); + + PG_RETURN_BOOL(ItemPointerCompare(arg1, arg2) < 0); +} + +Datum +tidle(PG_FUNCTION_ARGS) +{ + ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0); + ItemPointer arg2 = PG_GETARG_ITEMPOINTER(1); + + PG_RETURN_BOOL(ItemPointerCompare(arg1, arg2) <= 0); +} + +Datum +tidgt(PG_FUNCTION_ARGS) +{ + ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0); + ItemPointer arg2 = PG_GETARG_ITEMPOINTER(1); + + PG_RETURN_BOOL(ItemPointerCompare(arg1, arg2) > 0); +} + +Datum +tidge(PG_FUNCTION_ARGS) +{ + ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0); + ItemPointer arg2 = PG_GETARG_ITEMPOINTER(1); + + PG_RETURN_BOOL(ItemPointerCompare(arg1, arg2) >= 0); +} + +Datum +bttidcmp(PG_FUNCTION_ARGS) +{ + ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0); + ItemPointer arg2 = PG_GETARG_ITEMPOINTER(1); + + PG_RETURN_INT32(ItemPointerCompare(arg1, arg2)); +} + +Datum +tidlarger(PG_FUNCTION_ARGS) +{ + ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0); + ItemPointer arg2 = PG_GETARG_ITEMPOINTER(1); + + PG_RETURN_ITEMPOINTER(ItemPointerCompare(arg1, arg2) >= 0 ? arg1 : arg2); +} + +Datum +tidsmaller(PG_FUNCTION_ARGS) +{ + ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0); + ItemPointer arg2 = PG_GETARG_ITEMPOINTER(1); + + PG_RETURN_ITEMPOINTER(ItemPointerCompare(arg1, arg2) <= 0 ? arg1 : arg2); +} + +Datum +hashtid(PG_FUNCTION_ARGS) +{ + ItemPointer key = PG_GETARG_ITEMPOINTER(0); + + /* + * While you'll probably have a lot of trouble with a compiler that + * insists on appending pad space to struct ItemPointerData, we can at + * least make this code work, by not using sizeof(ItemPointerData). + * Instead rely on knowing the sizes of the component fields. + */ + return hash_any((unsigned char *) key, + sizeof(BlockIdData) + sizeof(OffsetNumber)); +} + +Datum +hashtidextended(PG_FUNCTION_ARGS) +{ + ItemPointer key = PG_GETARG_ITEMPOINTER(0); + uint64 seed = PG_GETARG_INT64(1); + + /* As above */ + return hash_any_extended((unsigned char *) key, + sizeof(BlockIdData) + sizeof(OffsetNumber), + seed); +} + + +/* + * Functions to get latest tid of a specified tuple. + * + * Maybe these implementations should be moved to another place + */ + +/* + * Utility wrapper for current CTID functions. + * Returns the latest version of a tuple pointing at "tid" for + * relation "rel". + */ +static ItemPointer +currtid_internal(Relation rel, ItemPointer tid) +{ + ItemPointer result; + AclResult aclresult; + Snapshot snapshot; + TableScanDesc scan; + + result = (ItemPointer) palloc(sizeof(ItemPointerData)); + + aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(), + ACL_SELECT); + if (aclresult != ACLCHECK_OK) + aclcheck_error(aclresult, get_relkind_objtype(rel->rd_rel->relkind), + RelationGetRelationName(rel)); + + if (rel->rd_rel->relkind == RELKIND_VIEW) + return currtid_for_view(rel, tid); + + if (!RELKIND_HAS_STORAGE(rel->rd_rel->relkind)) + elog(ERROR, "cannot look at latest visible tid for relation \"%s.%s\"", + get_namespace_name(RelationGetNamespace(rel)), + RelationGetRelationName(rel)); + + ItemPointerCopy(tid, result); + + snapshot = RegisterSnapshot(GetLatestSnapshot()); + scan = table_beginscan_tid(rel, snapshot); + table_tuple_get_latest_tid(scan, result); + table_endscan(scan); + UnregisterSnapshot(snapshot); + + return result; +} + +/* + * Handle CTIDs of views. + * CTID should be defined in the view and it must + * correspond to the CTID of a base relation. + */ +static ItemPointer +currtid_for_view(Relation viewrel, ItemPointer tid) +{ + TupleDesc att = RelationGetDescr(viewrel); + RuleLock *rulelock; + RewriteRule *rewrite; + int i, + natts = att->natts, + tididx = -1; + + for (i = 0; i < natts; i++) + { + Form_pg_attribute attr = TupleDescAttr(att, i); + + if (strcmp(NameStr(attr->attname), "ctid") == 0) + { + if (attr->atttypid != TIDOID) + elog(ERROR, "ctid isn't of type TID"); + tididx = i; + break; + } + } + if (tididx < 0) + elog(ERROR, "currtid cannot handle views with no CTID"); + rulelock = viewrel->rd_rules; + if (!rulelock) + elog(ERROR, "the view has no rules"); + for (i = 0; i < rulelock->numLocks; i++) + { + rewrite = rulelock->rules[i]; + if (rewrite->event == CMD_SELECT) + { + Query *query; + TargetEntry *tle; + + if (list_length(rewrite->actions) != 1) + elog(ERROR, "only one select rule is allowed in views"); + query = (Query *) linitial(rewrite->actions); + tle = get_tle_by_resno(query->targetList, tididx + 1); + if (tle && tle->expr && IsA(tle->expr, Var)) + { + Var *var = (Var *) tle->expr; + RangeTblEntry *rte; + + if (!IS_SPECIAL_VARNO(var->varno) && + var->varattno == SelfItemPointerAttributeNumber) + { + rte = rt_fetch(var->varno, query->rtable); + if (rte) + { + ItemPointer result; + Relation rel; + + rel = table_open(rte->relid, AccessShareLock); + result = currtid_internal(rel, tid); + table_close(rel, AccessShareLock); + return result; + } + } + } + break; + } + } + elog(ERROR, "currtid cannot handle this view"); + return NULL; +} + +/* + * currtid_byrelname + * Get the latest tuple version of the tuple pointing at a CTID, for a + * given relation name. + */ +Datum +currtid_byrelname(PG_FUNCTION_ARGS) +{ + text *relname = PG_GETARG_TEXT_PP(0); + ItemPointer tid = PG_GETARG_ITEMPOINTER(1); + ItemPointer result; + RangeVar *relrv; + Relation rel; + + relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname)); + rel = table_openrv(relrv, AccessShareLock); + + /* grab the latest tuple version associated to this CTID */ + result = currtid_internal(rel, tid); + + table_close(rel, AccessShareLock); + + PG_RETURN_ITEMPOINTER(result); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/timestamp.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/timestamp.c new file mode 100644 index 00000000000..b585551bca8 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/timestamp.c @@ -0,0 +1,6015 @@ +/*------------------------------------------------------------------------- + * + * timestamp.c + * Functions for the built-in SQL types "timestamp" and "interval". + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/adt/timestamp.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include <ctype.h> +#include <math.h> +#include <limits.h> +#include <sys/time.h> + +#include "access/xact.h" +#include "catalog/pg_type.h" +#include "common/int.h" +#include "common/int128.h" +#include "funcapi.h" +#include "libpq/pqformat.h" +#include "miscadmin.h" +#include "nodes/makefuncs.h" +#include "nodes/nodeFuncs.h" +#include "nodes/supportnodes.h" +#include "parser/scansup.h" +#include "utils/array.h" +#include "utils/builtins.h" +#include "utils/date.h" +#include "utils/datetime.h" +#include "utils/float.h" +#include "utils/numeric.h" +#include "utils/sortsupport.h" + +/* + * gcc's -ffast-math switch breaks routines that expect exact results from + * expressions like timeval / SECS_PER_HOUR, where timeval is double. + */ +#ifdef __FAST_MATH__ +#error -ffast-math is known to break this code +#endif + +#define SAMESIGN(a,b) (((a) < 0) == ((b) < 0)) + +/* Set at postmaster start */ +__thread TimestampTz PgStartTime; + +/* Set at configuration reload */ +__thread TimestampTz PgReloadTime; + +typedef struct +{ + Timestamp current; + Timestamp finish; + Interval step; + int step_sign; +} generate_series_timestamp_fctx; + +typedef struct +{ + TimestampTz current; + TimestampTz finish; + Interval step; + int step_sign; + pg_tz *attimezone; +} generate_series_timestamptz_fctx; + + +static TimeOffset time2t(const int hour, const int min, const int sec, const fsec_t fsec); +static Timestamp dt2local(Timestamp dt, int timezone); +static bool AdjustIntervalForTypmod(Interval *interval, int32 typmod, + Node *escontext); +static TimestampTz timestamp2timestamptz(Timestamp timestamp); +static Timestamp timestamptz2timestamp(TimestampTz timestamp); + + +/* common code for timestamptypmodin and timestamptztypmodin */ +static int32 +anytimestamp_typmodin(bool istz, ArrayType *ta) +{ + int32 *tl; + int n; + + tl = ArrayGetIntegerTypmods(ta, &n); + + /* + * we're not too tense about good error message here because grammar + * shouldn't allow wrong number of modifiers for TIMESTAMP + */ + if (n != 1) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid type modifier"))); + + return anytimestamp_typmod_check(istz, tl[0]); +} + +/* exported so parse_expr.c can use it */ +int32 +anytimestamp_typmod_check(bool istz, int32 typmod) +{ + if (typmod < 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("TIMESTAMP(%d)%s precision must not be negative", + typmod, (istz ? " WITH TIME ZONE" : "")))); + if (typmod > MAX_TIMESTAMP_PRECISION) + { + ereport(WARNING, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("TIMESTAMP(%d)%s precision reduced to maximum allowed, %d", + typmod, (istz ? " WITH TIME ZONE" : ""), + MAX_TIMESTAMP_PRECISION))); + typmod = MAX_TIMESTAMP_PRECISION; + } + + return typmod; +} + +/* common code for timestamptypmodout and timestamptztypmodout */ +static char * +anytimestamp_typmodout(bool istz, int32 typmod) +{ + const char *tz = istz ? " with time zone" : " without time zone"; + + if (typmod >= 0) + return psprintf("(%d)%s", (int) typmod, tz); + else + return pstrdup(tz); +} + + +/***************************************************************************** + * USER I/O ROUTINES * + *****************************************************************************/ + +/* timestamp_in() + * Convert a string to internal form. + */ +Datum +timestamp_in(PG_FUNCTION_ARGS) +{ + char *str = PG_GETARG_CSTRING(0); +#ifdef NOT_USED + Oid typelem = PG_GETARG_OID(1); +#endif + int32 typmod = PG_GETARG_INT32(2); + Node *escontext = fcinfo->context; + Timestamp result; + fsec_t fsec; + struct pg_tm tt, + *tm = &tt; + int tz; + int dtype; + int nf; + int dterr; + char *field[MAXDATEFIELDS]; + int ftype[MAXDATEFIELDS]; + char workbuf[MAXDATELEN + MAXDATEFIELDS]; + DateTimeErrorExtra extra; + + dterr = ParseDateTime(str, workbuf, sizeof(workbuf), + field, ftype, MAXDATEFIELDS, &nf); + if (dterr == 0) + dterr = DecodeDateTime(field, ftype, nf, + &dtype, tm, &fsec, &tz, &extra); + if (dterr != 0) + { + DateTimeParseError(dterr, &extra, str, "timestamp", escontext); + PG_RETURN_NULL(); + } + + switch (dtype) + { + case DTK_DATE: + if (tm2timestamp(tm, fsec, NULL, &result) != 0) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range: \"%s\"", str))); + break; + + case DTK_EPOCH: + result = SetEpochTimestamp(); + break; + + case DTK_LATE: + TIMESTAMP_NOEND(result); + break; + + case DTK_EARLY: + TIMESTAMP_NOBEGIN(result); + break; + + default: + elog(ERROR, "unexpected dtype %d while parsing timestamp \"%s\"", + dtype, str); + TIMESTAMP_NOEND(result); + } + + AdjustTimestampForTypmod(&result, typmod, escontext); + + PG_RETURN_TIMESTAMP(result); +} + +/* timestamp_out() + * Convert a timestamp to external form. + */ +Datum +timestamp_out(PG_FUNCTION_ARGS) +{ + Timestamp timestamp = PG_GETARG_TIMESTAMP(0); + char *result; + struct pg_tm tt, + *tm = &tt; + fsec_t fsec; + char buf[MAXDATELEN + 1]; + + if (TIMESTAMP_NOT_FINITE(timestamp)) + EncodeSpecialTimestamp(timestamp, buf); + else if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) == 0) + EncodeDateTime(tm, fsec, false, 0, NULL, DateStyle, buf); + else + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + + result = pstrdup(buf); + PG_RETURN_CSTRING(result); +} + +/* + * timestamp_recv - converts external binary format to timestamp + */ +Datum +timestamp_recv(PG_FUNCTION_ARGS) +{ + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + +#ifdef NOT_USED + Oid typelem = PG_GETARG_OID(1); +#endif + int32 typmod = PG_GETARG_INT32(2); + Timestamp timestamp; + struct pg_tm tt, + *tm = &tt; + fsec_t fsec; + + timestamp = (Timestamp) pq_getmsgint64(buf); + + /* range check: see if timestamp_out would like it */ + if (TIMESTAMP_NOT_FINITE(timestamp)) + /* ok */ ; + else if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0 || + !IS_VALID_TIMESTAMP(timestamp)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + + AdjustTimestampForTypmod(×tamp, typmod, NULL); + + PG_RETURN_TIMESTAMP(timestamp); +} + +/* + * timestamp_send - converts timestamp to binary format + */ +Datum +timestamp_send(PG_FUNCTION_ARGS) +{ + Timestamp timestamp = PG_GETARG_TIMESTAMP(0); + StringInfoData buf; + + pq_begintypsend(&buf); + pq_sendint64(&buf, timestamp); + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} + +Datum +timestamptypmodin(PG_FUNCTION_ARGS) +{ + ArrayType *ta = PG_GETARG_ARRAYTYPE_P(0); + + PG_RETURN_INT32(anytimestamp_typmodin(false, ta)); +} + +Datum +timestamptypmodout(PG_FUNCTION_ARGS) +{ + int32 typmod = PG_GETARG_INT32(0); + + PG_RETURN_CSTRING(anytimestamp_typmodout(false, typmod)); +} + + +/* + * timestamp_support() + * + * Planner support function for the timestamp_scale() and timestamptz_scale() + * length coercion functions (we need not distinguish them here). + */ +Datum +timestamp_support(PG_FUNCTION_ARGS) +{ + Node *rawreq = (Node *) PG_GETARG_POINTER(0); + Node *ret = NULL; + + if (IsA(rawreq, SupportRequestSimplify)) + { + SupportRequestSimplify *req = (SupportRequestSimplify *) rawreq; + + ret = TemporalSimplify(MAX_TIMESTAMP_PRECISION, (Node *) req->fcall); + } + + PG_RETURN_POINTER(ret); +} + +/* timestamp_scale() + * Adjust time type for specified scale factor. + * Used by PostgreSQL type system to stuff columns. + */ +Datum +timestamp_scale(PG_FUNCTION_ARGS) +{ + Timestamp timestamp = PG_GETARG_TIMESTAMP(0); + int32 typmod = PG_GETARG_INT32(1); + Timestamp result; + + result = timestamp; + + AdjustTimestampForTypmod(&result, typmod, NULL); + + PG_RETURN_TIMESTAMP(result); +} + +/* + * AdjustTimestampForTypmod --- round off a timestamp to suit given typmod + * Works for either timestamp or timestamptz. + * + * Returns true on success, false on failure (if escontext points to an + * ErrorSaveContext; otherwise errors are thrown). + */ +bool +AdjustTimestampForTypmod(Timestamp *time, int32 typmod, Node *escontext) +{ + static const int64 TimestampScales[MAX_TIMESTAMP_PRECISION + 1] = { + INT64CONST(1000000), + INT64CONST(100000), + INT64CONST(10000), + INT64CONST(1000), + INT64CONST(100), + INT64CONST(10), + INT64CONST(1) + }; + + static const int64 TimestampOffsets[MAX_TIMESTAMP_PRECISION + 1] = { + INT64CONST(500000), + INT64CONST(50000), + INT64CONST(5000), + INT64CONST(500), + INT64CONST(50), + INT64CONST(5), + INT64CONST(0) + }; + + if (!TIMESTAMP_NOT_FINITE(*time) + && (typmod != -1) && (typmod != MAX_TIMESTAMP_PRECISION)) + { + if (typmod < 0 || typmod > MAX_TIMESTAMP_PRECISION) + ereturn(escontext, false, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("timestamp(%d) precision must be between %d and %d", + typmod, 0, MAX_TIMESTAMP_PRECISION))); + + if (*time >= INT64CONST(0)) + { + *time = ((*time + TimestampOffsets[typmod]) / TimestampScales[typmod]) * + TimestampScales[typmod]; + } + else + { + *time = -((((-*time) + TimestampOffsets[typmod]) / TimestampScales[typmod]) + * TimestampScales[typmod]); + } + } + + return true; +} + +/* timestamptz_in() + * Convert a string to internal form. + */ +Datum +timestamptz_in(PG_FUNCTION_ARGS) +{ + char *str = PG_GETARG_CSTRING(0); +#ifdef NOT_USED + Oid typelem = PG_GETARG_OID(1); +#endif + int32 typmod = PG_GETARG_INT32(2); + Node *escontext = fcinfo->context; + TimestampTz result; + fsec_t fsec; + struct pg_tm tt, + *tm = &tt; + int tz; + int dtype; + int nf; + int dterr; + char *field[MAXDATEFIELDS]; + int ftype[MAXDATEFIELDS]; + char workbuf[MAXDATELEN + MAXDATEFIELDS]; + DateTimeErrorExtra extra; + + dterr = ParseDateTime(str, workbuf, sizeof(workbuf), + field, ftype, MAXDATEFIELDS, &nf); + if (dterr == 0) + dterr = DecodeDateTime(field, ftype, nf, + &dtype, tm, &fsec, &tz, &extra); + if (dterr != 0) + { + DateTimeParseError(dterr, &extra, str, "timestamp with time zone", + escontext); + PG_RETURN_NULL(); + } + + switch (dtype) + { + case DTK_DATE: + if (tm2timestamp(tm, fsec, &tz, &result) != 0) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range: \"%s\"", str))); + break; + + case DTK_EPOCH: + result = SetEpochTimestamp(); + break; + + case DTK_LATE: + TIMESTAMP_NOEND(result); + break; + + case DTK_EARLY: + TIMESTAMP_NOBEGIN(result); + break; + + default: + elog(ERROR, "unexpected dtype %d while parsing timestamptz \"%s\"", + dtype, str); + TIMESTAMP_NOEND(result); + } + + AdjustTimestampForTypmod(&result, typmod, escontext); + + PG_RETURN_TIMESTAMPTZ(result); +} + +/* + * Try to parse a timezone specification, and return its timezone offset value + * if it's acceptable. Otherwise, an error is thrown. + * + * Note: some code paths update tm->tm_isdst, and some don't; current callers + * don't care, so we don't bother being consistent. + */ +static int +parse_sane_timezone(struct pg_tm *tm, text *zone) +{ + char tzname[TZ_STRLEN_MAX + 1]; + int dterr; + int tz; + + text_to_cstring_buffer(zone, tzname, sizeof(tzname)); + + /* + * Look up the requested timezone. First we try to interpret it as a + * numeric timezone specification; if DecodeTimezone decides it doesn't + * like the format, we try timezone abbreviations and names. + * + * Note pg_tzset happily parses numeric input that DecodeTimezone would + * reject. To avoid having it accept input that would otherwise be seen + * as invalid, it's enough to disallow having a digit in the first + * position of our input string. + */ + if (isdigit((unsigned char) *tzname)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid input syntax for type %s: \"%s\"", + "numeric time zone", tzname), + errhint("Numeric time zones must have \"-\" or \"+\" as first character."))); + + dterr = DecodeTimezone(tzname, &tz); + if (dterr != 0) + { + int type, + val; + pg_tz *tzp; + + if (dterr == DTERR_TZDISP_OVERFLOW) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("numeric time zone \"%s\" out of range", tzname))); + else if (dterr != DTERR_BAD_FORMAT) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("time zone \"%s\" not recognized", tzname))); + + type = DecodeTimezoneName(tzname, &val, &tzp); + + if (type == TZNAME_FIXED_OFFSET) + { + /* fixed-offset abbreviation */ + tz = -val; + } + else if (type == TZNAME_DYNTZ) + { + /* dynamic-offset abbreviation, resolve using specified time */ + tz = DetermineTimeZoneAbbrevOffset(tm, tzname, tzp); + } + else + { + /* full zone name */ + tz = DetermineTimeZoneOffset(tm, tzp); + } + } + + return tz; +} + +/* + * Look up the requested timezone, returning a pg_tz struct. + * + * This is the same as DecodeTimezoneNameToTz, but starting with a text Datum. + */ +static pg_tz * +lookup_timezone(text *zone) +{ + char tzname[TZ_STRLEN_MAX + 1]; + + text_to_cstring_buffer(zone, tzname, sizeof(tzname)); + + return DecodeTimezoneNameToTz(tzname); +} + +/* + * make_timestamp_internal + * workhorse for make_timestamp and make_timestamptz + */ +static Timestamp +make_timestamp_internal(int year, int month, int day, + int hour, int min, double sec) +{ + struct pg_tm tm; + TimeOffset date; + TimeOffset time; + int dterr; + bool bc = false; + Timestamp result; + + tm.tm_year = year; + tm.tm_mon = month; + tm.tm_mday = day; + + /* Handle negative years as BC */ + if (tm.tm_year < 0) + { + bc = true; + tm.tm_year = -tm.tm_year; + } + + dterr = ValidateDate(DTK_DATE_M, false, false, bc, &tm); + + if (dterr != 0) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_FIELD_OVERFLOW), + errmsg("date field value out of range: %d-%02d-%02d", + year, month, day))); + + if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("date out of range: %d-%02d-%02d", + year, month, day))); + + date = date2j(tm.tm_year, tm.tm_mon, tm.tm_mday) - POSTGRES_EPOCH_JDATE; + + /* Check for time overflow */ + if (float_time_overflows(hour, min, sec)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_FIELD_OVERFLOW), + errmsg("time field value out of range: %d:%02d:%02g", + hour, min, sec))); + + /* This should match tm2time */ + time = (((hour * MINS_PER_HOUR + min) * SECS_PER_MINUTE) + * USECS_PER_SEC) + (int64) rint(sec * USECS_PER_SEC); + + result = date * USECS_PER_DAY + time; + /* check for major overflow */ + if ((result - time) / USECS_PER_DAY != date) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range: %d-%02d-%02d %d:%02d:%02g", + year, month, day, + hour, min, sec))); + + /* check for just-barely overflow (okay except time-of-day wraps) */ + /* caution: we want to allow 1999-12-31 24:00:00 */ + if ((result < 0 && date > 0) || + (result > 0 && date < -1)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range: %d-%02d-%02d %d:%02d:%02g", + year, month, day, + hour, min, sec))); + + /* final range check catches just-out-of-range timestamps */ + if (!IS_VALID_TIMESTAMP(result)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range: %d-%02d-%02d %d:%02d:%02g", + year, month, day, + hour, min, sec))); + + return result; +} + +/* + * make_timestamp() - timestamp constructor + */ +Datum +make_timestamp(PG_FUNCTION_ARGS) +{ + int32 year = PG_GETARG_INT32(0); + int32 month = PG_GETARG_INT32(1); + int32 mday = PG_GETARG_INT32(2); + int32 hour = PG_GETARG_INT32(3); + int32 min = PG_GETARG_INT32(4); + float8 sec = PG_GETARG_FLOAT8(5); + Timestamp result; + + result = make_timestamp_internal(year, month, mday, + hour, min, sec); + + PG_RETURN_TIMESTAMP(result); +} + +/* + * make_timestamptz() - timestamp with time zone constructor + */ +Datum +make_timestamptz(PG_FUNCTION_ARGS) +{ + int32 year = PG_GETARG_INT32(0); + int32 month = PG_GETARG_INT32(1); + int32 mday = PG_GETARG_INT32(2); + int32 hour = PG_GETARG_INT32(3); + int32 min = PG_GETARG_INT32(4); + float8 sec = PG_GETARG_FLOAT8(5); + Timestamp result; + + result = make_timestamp_internal(year, month, mday, + hour, min, sec); + + PG_RETURN_TIMESTAMPTZ(timestamp2timestamptz(result)); +} + +/* + * Construct a timestamp with time zone. + * As above, but the time zone is specified as seventh argument. + */ +Datum +make_timestamptz_at_timezone(PG_FUNCTION_ARGS) +{ + int32 year = PG_GETARG_INT32(0); + int32 month = PG_GETARG_INT32(1); + int32 mday = PG_GETARG_INT32(2); + int32 hour = PG_GETARG_INT32(3); + int32 min = PG_GETARG_INT32(4); + float8 sec = PG_GETARG_FLOAT8(5); + text *zone = PG_GETARG_TEXT_PP(6); + TimestampTz result; + Timestamp timestamp; + struct pg_tm tt; + int tz; + fsec_t fsec; + + timestamp = make_timestamp_internal(year, month, mday, + hour, min, sec); + + if (timestamp2tm(timestamp, NULL, &tt, &fsec, NULL, NULL) != 0) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + + tz = parse_sane_timezone(&tt, zone); + + result = dt2local(timestamp, -tz); + + if (!IS_VALID_TIMESTAMP(result)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + + PG_RETURN_TIMESTAMPTZ(result); +} + +/* + * to_timestamp(double precision) + * Convert UNIX epoch to timestamptz. + */ +Datum +float8_timestamptz(PG_FUNCTION_ARGS) +{ + float8 seconds = PG_GETARG_FLOAT8(0); + TimestampTz result; + + /* Deal with NaN and infinite inputs ... */ + if (isnan(seconds)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp cannot be NaN"))); + + if (isinf(seconds)) + { + if (seconds < 0) + TIMESTAMP_NOBEGIN(result); + else + TIMESTAMP_NOEND(result); + } + else + { + /* Out of range? */ + if (seconds < + (float8) SECS_PER_DAY * (DATETIME_MIN_JULIAN - UNIX_EPOCH_JDATE) + || seconds >= + (float8) SECS_PER_DAY * (TIMESTAMP_END_JULIAN - UNIX_EPOCH_JDATE)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range: \"%g\"", seconds))); + + /* Convert UNIX epoch to Postgres epoch */ + seconds -= ((POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * SECS_PER_DAY); + + seconds = rint(seconds * USECS_PER_SEC); + result = (int64) seconds; + + /* Recheck in case roundoff produces something just out of range */ + if (!IS_VALID_TIMESTAMP(result)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range: \"%g\"", + PG_GETARG_FLOAT8(0)))); + } + + PG_RETURN_TIMESTAMP(result); +} + +/* timestamptz_out() + * Convert a timestamp to external form. + */ +Datum +timestamptz_out(PG_FUNCTION_ARGS) +{ + TimestampTz dt = PG_GETARG_TIMESTAMPTZ(0); + char *result; + int tz; + struct pg_tm tt, + *tm = &tt; + fsec_t fsec; + const char *tzn; + char buf[MAXDATELEN + 1]; + + if (TIMESTAMP_NOT_FINITE(dt)) + EncodeSpecialTimestamp(dt, buf); + else if (timestamp2tm(dt, &tz, tm, &fsec, &tzn, NULL) == 0) + EncodeDateTime(tm, fsec, true, tz, tzn, DateStyle, buf); + else + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + + result = pstrdup(buf); + PG_RETURN_CSTRING(result); +} + +/* + * timestamptz_recv - converts external binary format to timestamptz + */ +Datum +timestamptz_recv(PG_FUNCTION_ARGS) +{ + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + +#ifdef NOT_USED + Oid typelem = PG_GETARG_OID(1); +#endif + int32 typmod = PG_GETARG_INT32(2); + TimestampTz timestamp; + int tz; + struct pg_tm tt, + *tm = &tt; + fsec_t fsec; + + timestamp = (TimestampTz) pq_getmsgint64(buf); + + /* range check: see if timestamptz_out would like it */ + if (TIMESTAMP_NOT_FINITE(timestamp)) + /* ok */ ; + else if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0 || + !IS_VALID_TIMESTAMP(timestamp)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + + AdjustTimestampForTypmod(×tamp, typmod, NULL); + + PG_RETURN_TIMESTAMPTZ(timestamp); +} + +/* + * timestamptz_send - converts timestamptz to binary format + */ +Datum +timestamptz_send(PG_FUNCTION_ARGS) +{ + TimestampTz timestamp = PG_GETARG_TIMESTAMPTZ(0); + StringInfoData buf; + + pq_begintypsend(&buf); + pq_sendint64(&buf, timestamp); + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} + +Datum +timestamptztypmodin(PG_FUNCTION_ARGS) +{ + ArrayType *ta = PG_GETARG_ARRAYTYPE_P(0); + + PG_RETURN_INT32(anytimestamp_typmodin(true, ta)); +} + +Datum +timestamptztypmodout(PG_FUNCTION_ARGS) +{ + int32 typmod = PG_GETARG_INT32(0); + + PG_RETURN_CSTRING(anytimestamp_typmodout(true, typmod)); +} + + +/* timestamptz_scale() + * Adjust time type for specified scale factor. + * Used by PostgreSQL type system to stuff columns. + */ +Datum +timestamptz_scale(PG_FUNCTION_ARGS) +{ + TimestampTz timestamp = PG_GETARG_TIMESTAMPTZ(0); + int32 typmod = PG_GETARG_INT32(1); + TimestampTz result; + + result = timestamp; + + AdjustTimestampForTypmod(&result, typmod, NULL); + + PG_RETURN_TIMESTAMPTZ(result); +} + + +/* interval_in() + * Convert a string to internal form. + * + * External format(s): + * Uses the generic date/time parsing and decoding routines. + */ +Datum +interval_in(PG_FUNCTION_ARGS) +{ + char *str = PG_GETARG_CSTRING(0); +#ifdef NOT_USED + Oid typelem = PG_GETARG_OID(1); +#endif + int32 typmod = PG_GETARG_INT32(2); + Node *escontext = fcinfo->context; + Interval *result; + struct pg_itm_in tt, + *itm_in = &tt; + int dtype; + int nf; + int range; + int dterr; + char *field[MAXDATEFIELDS]; + int ftype[MAXDATEFIELDS]; + char workbuf[256]; + DateTimeErrorExtra extra; + + itm_in->tm_year = 0; + itm_in->tm_mon = 0; + itm_in->tm_mday = 0; + itm_in->tm_usec = 0; + + if (typmod >= 0) + range = INTERVAL_RANGE(typmod); + else + range = INTERVAL_FULL_RANGE; + + dterr = ParseDateTime(str, workbuf, sizeof(workbuf), field, + ftype, MAXDATEFIELDS, &nf); + if (dterr == 0) + dterr = DecodeInterval(field, ftype, nf, range, + &dtype, itm_in); + + /* if those functions think it's a bad format, try ISO8601 style */ + if (dterr == DTERR_BAD_FORMAT) + dterr = DecodeISO8601Interval(str, + &dtype, itm_in); + + if (dterr != 0) + { + if (dterr == DTERR_FIELD_OVERFLOW) + dterr = DTERR_INTERVAL_OVERFLOW; + DateTimeParseError(dterr, &extra, str, "interval", escontext); + PG_RETURN_NULL(); + } + + result = (Interval *) palloc(sizeof(Interval)); + + switch (dtype) + { + case DTK_DELTA: + if (itmin2interval(itm_in, result) != 0) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("interval out of range"))); + break; + + default: + elog(ERROR, "unexpected dtype %d while parsing interval \"%s\"", + dtype, str); + } + + AdjustIntervalForTypmod(result, typmod, escontext); + + PG_RETURN_INTERVAL_P(result); +} + +/* interval_out() + * Convert a time span to external form. + */ +Datum +interval_out(PG_FUNCTION_ARGS) +{ + Interval *span = PG_GETARG_INTERVAL_P(0); + char *result; + struct pg_itm tt, + *itm = &tt; + char buf[MAXDATELEN + 1]; + + interval2itm(*span, itm); + EncodeInterval(itm, IntervalStyle, buf); + + result = pstrdup(buf); + PG_RETURN_CSTRING(result); +} + +/* + * interval_recv - converts external binary format to interval + */ +Datum +interval_recv(PG_FUNCTION_ARGS) +{ + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + +#ifdef NOT_USED + Oid typelem = PG_GETARG_OID(1); +#endif + int32 typmod = PG_GETARG_INT32(2); + Interval *interval; + + interval = (Interval *) palloc(sizeof(Interval)); + + interval->time = pq_getmsgint64(buf); + interval->day = pq_getmsgint(buf, sizeof(interval->day)); + interval->month = pq_getmsgint(buf, sizeof(interval->month)); + + AdjustIntervalForTypmod(interval, typmod, NULL); + + PG_RETURN_INTERVAL_P(interval); +} + +/* + * interval_send - converts interval to binary format + */ +Datum +interval_send(PG_FUNCTION_ARGS) +{ + Interval *interval = PG_GETARG_INTERVAL_P(0); + StringInfoData buf; + + pq_begintypsend(&buf); + pq_sendint64(&buf, interval->time); + pq_sendint32(&buf, interval->day); + pq_sendint32(&buf, interval->month); + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} + +/* + * The interval typmod stores a "range" in its high 16 bits and a "precision" + * in its low 16 bits. Both contribute to defining the resolution of the + * type. Range addresses resolution granules larger than one second, and + * precision specifies resolution below one second. This representation can + * express all SQL standard resolutions, but we implement them all in terms of + * truncating rightward from some position. Range is a bitmap of permitted + * fields, but only the temporally-smallest such field is significant to our + * calculations. Precision is a count of sub-second decimal places to retain. + * Setting all bits (INTERVAL_FULL_PRECISION) gives the same truncation + * semantics as choosing MAX_INTERVAL_PRECISION. + */ +Datum +intervaltypmodin(PG_FUNCTION_ARGS) +{ + ArrayType *ta = PG_GETARG_ARRAYTYPE_P(0); + int32 *tl; + int n; + int32 typmod; + + tl = ArrayGetIntegerTypmods(ta, &n); + + /* + * tl[0] - interval range (fields bitmask) tl[1] - precision (optional) + * + * Note we must validate tl[0] even though it's normally guaranteed + * correct by the grammar --- consider SELECT 'foo'::"interval"(1000). + */ + if (n > 0) + { + switch (tl[0]) + { + case INTERVAL_MASK(YEAR): + case INTERVAL_MASK(MONTH): + case INTERVAL_MASK(DAY): + case INTERVAL_MASK(HOUR): + case INTERVAL_MASK(MINUTE): + case INTERVAL_MASK(SECOND): + case INTERVAL_MASK(YEAR) | INTERVAL_MASK(MONTH): + case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR): + case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE): + case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND): + case INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE): + case INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND): + case INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND): + case INTERVAL_FULL_RANGE: + /* all OK */ + break; + default: + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid INTERVAL type modifier"))); + } + } + + if (n == 1) + { + if (tl[0] != INTERVAL_FULL_RANGE) + typmod = INTERVAL_TYPMOD(INTERVAL_FULL_PRECISION, tl[0]); + else + typmod = -1; + } + else if (n == 2) + { + if (tl[1] < 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("INTERVAL(%d) precision must not be negative", + tl[1]))); + if (tl[1] > MAX_INTERVAL_PRECISION) + { + ereport(WARNING, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("INTERVAL(%d) precision reduced to maximum allowed, %d", + tl[1], MAX_INTERVAL_PRECISION))); + typmod = INTERVAL_TYPMOD(MAX_INTERVAL_PRECISION, tl[0]); + } + else + typmod = INTERVAL_TYPMOD(tl[1], tl[0]); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid INTERVAL type modifier"))); + typmod = 0; /* keep compiler quiet */ + } + + PG_RETURN_INT32(typmod); +} + +Datum +intervaltypmodout(PG_FUNCTION_ARGS) +{ + int32 typmod = PG_GETARG_INT32(0); + char *res = (char *) palloc(64); + int fields; + int precision; + const char *fieldstr; + + if (typmod < 0) + { + *res = '\0'; + PG_RETURN_CSTRING(res); + } + + fields = INTERVAL_RANGE(typmod); + precision = INTERVAL_PRECISION(typmod); + + switch (fields) + { + case INTERVAL_MASK(YEAR): + fieldstr = " year"; + break; + case INTERVAL_MASK(MONTH): + fieldstr = " month"; + break; + case INTERVAL_MASK(DAY): + fieldstr = " day"; + break; + case INTERVAL_MASK(HOUR): + fieldstr = " hour"; + break; + case INTERVAL_MASK(MINUTE): + fieldstr = " minute"; + break; + case INTERVAL_MASK(SECOND): + fieldstr = " second"; + break; + case INTERVAL_MASK(YEAR) | INTERVAL_MASK(MONTH): + fieldstr = " year to month"; + break; + case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR): + fieldstr = " day to hour"; + break; + case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE): + fieldstr = " day to minute"; + break; + case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND): + fieldstr = " day to second"; + break; + case INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE): + fieldstr = " hour to minute"; + break; + case INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND): + fieldstr = " hour to second"; + break; + case INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND): + fieldstr = " minute to second"; + break; + case INTERVAL_FULL_RANGE: + fieldstr = ""; + break; + default: + elog(ERROR, "invalid INTERVAL typmod: 0x%x", typmod); + fieldstr = ""; + break; + } + + if (precision != INTERVAL_FULL_PRECISION) + snprintf(res, 64, "%s(%d)", fieldstr, precision); + else + snprintf(res, 64, "%s", fieldstr); + + PG_RETURN_CSTRING(res); +} + +/* + * Given an interval typmod value, return a code for the least-significant + * field that the typmod allows to be nonzero, for instance given + * INTERVAL DAY TO HOUR we want to identify "hour". + * + * The results should be ordered by field significance, which means + * we can't use the dt.h macros YEAR etc, because for some odd reason + * they aren't ordered that way. Instead, arbitrarily represent + * SECOND = 0, MINUTE = 1, HOUR = 2, DAY = 3, MONTH = 4, YEAR = 5. + */ +static int +intervaltypmodleastfield(int32 typmod) +{ + if (typmod < 0) + return 0; /* SECOND */ + + switch (INTERVAL_RANGE(typmod)) + { + case INTERVAL_MASK(YEAR): + return 5; /* YEAR */ + case INTERVAL_MASK(MONTH): + return 4; /* MONTH */ + case INTERVAL_MASK(DAY): + return 3; /* DAY */ + case INTERVAL_MASK(HOUR): + return 2; /* HOUR */ + case INTERVAL_MASK(MINUTE): + return 1; /* MINUTE */ + case INTERVAL_MASK(SECOND): + return 0; /* SECOND */ + case INTERVAL_MASK(YEAR) | INTERVAL_MASK(MONTH): + return 4; /* MONTH */ + case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR): + return 2; /* HOUR */ + case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE): + return 1; /* MINUTE */ + case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND): + return 0; /* SECOND */ + case INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE): + return 1; /* MINUTE */ + case INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND): + return 0; /* SECOND */ + case INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND): + return 0; /* SECOND */ + case INTERVAL_FULL_RANGE: + return 0; /* SECOND */ + default: + elog(ERROR, "invalid INTERVAL typmod: 0x%x", typmod); + break; + } + return 0; /* can't get here, but keep compiler quiet */ +} + + +/* + * interval_support() + * + * Planner support function for interval_scale(). + * + * Flatten superfluous calls to interval_scale(). The interval typmod is + * complex to permit accepting and regurgitating all SQL standard variations. + * For truncation purposes, it boils down to a single, simple granularity. + */ +Datum +interval_support(PG_FUNCTION_ARGS) +{ + Node *rawreq = (Node *) PG_GETARG_POINTER(0); + Node *ret = NULL; + + if (IsA(rawreq, SupportRequestSimplify)) + { + SupportRequestSimplify *req = (SupportRequestSimplify *) rawreq; + FuncExpr *expr = req->fcall; + Node *typmod; + + Assert(list_length(expr->args) >= 2); + + typmod = (Node *) lsecond(expr->args); + + if (IsA(typmod, Const) && !((Const *) typmod)->constisnull) + { + Node *source = (Node *) linitial(expr->args); + int32 new_typmod = DatumGetInt32(((Const *) typmod)->constvalue); + bool noop; + + if (new_typmod < 0) + noop = true; + else + { + int32 old_typmod = exprTypmod(source); + int old_least_field; + int new_least_field; + int old_precis; + int new_precis; + + old_least_field = intervaltypmodleastfield(old_typmod); + new_least_field = intervaltypmodleastfield(new_typmod); + if (old_typmod < 0) + old_precis = INTERVAL_FULL_PRECISION; + else + old_precis = INTERVAL_PRECISION(old_typmod); + new_precis = INTERVAL_PRECISION(new_typmod); + + /* + * Cast is a no-op if least field stays the same or decreases + * while precision stays the same or increases. But + * precision, which is to say, sub-second precision, only + * affects ranges that include SECOND. + */ + noop = (new_least_field <= old_least_field) && + (old_least_field > 0 /* SECOND */ || + new_precis >= MAX_INTERVAL_PRECISION || + new_precis >= old_precis); + } + if (noop) + ret = relabel_to_typmod(source, new_typmod); + } + } + + PG_RETURN_POINTER(ret); +} + +/* interval_scale() + * Adjust interval type for specified fields. + * Used by PostgreSQL type system to stuff columns. + */ +Datum +interval_scale(PG_FUNCTION_ARGS) +{ + Interval *interval = PG_GETARG_INTERVAL_P(0); + int32 typmod = PG_GETARG_INT32(1); + Interval *result; + + result = palloc(sizeof(Interval)); + *result = *interval; + + AdjustIntervalForTypmod(result, typmod, NULL); + + PG_RETURN_INTERVAL_P(result); +} + +/* + * Adjust interval for specified precision, in both YEAR to SECOND + * range and sub-second precision. + * + * Returns true on success, false on failure (if escontext points to an + * ErrorSaveContext; otherwise errors are thrown). + */ +static bool +AdjustIntervalForTypmod(Interval *interval, int32 typmod, + Node *escontext) +{ + static const int64 IntervalScales[MAX_INTERVAL_PRECISION + 1] = { + INT64CONST(1000000), + INT64CONST(100000), + INT64CONST(10000), + INT64CONST(1000), + INT64CONST(100), + INT64CONST(10), + INT64CONST(1) + }; + + static const int64 IntervalOffsets[MAX_INTERVAL_PRECISION + 1] = { + INT64CONST(500000), + INT64CONST(50000), + INT64CONST(5000), + INT64CONST(500), + INT64CONST(50), + INT64CONST(5), + INT64CONST(0) + }; + + /* + * Unspecified range and precision? Then not necessary to adjust. Setting + * typmod to -1 is the convention for all data types. + */ + if (typmod >= 0) + { + int range = INTERVAL_RANGE(typmod); + int precision = INTERVAL_PRECISION(typmod); + + /* + * Our interpretation of intervals with a limited set of fields is + * that fields to the right of the last one specified are zeroed out, + * but those to the left of it remain valid. Thus for example there + * is no operational difference between INTERVAL YEAR TO MONTH and + * INTERVAL MONTH. In some cases we could meaningfully enforce that + * higher-order fields are zero; for example INTERVAL DAY could reject + * nonzero "month" field. However that seems a bit pointless when we + * can't do it consistently. (We cannot enforce a range limit on the + * highest expected field, since we do not have any equivalent of + * SQL's <interval leading field precision>.) If we ever decide to + * revisit this, interval_support will likely require adjusting. + * + * Note: before PG 8.4 we interpreted a limited set of fields as + * actually causing a "modulo" operation on a given value, potentially + * losing high-order as well as low-order information. But there is + * no support for such behavior in the standard, and it seems fairly + * undesirable on data consistency grounds anyway. Now we only + * perform truncation or rounding of low-order fields. + */ + if (range == INTERVAL_FULL_RANGE) + { + /* Do nothing... */ + } + else if (range == INTERVAL_MASK(YEAR)) + { + interval->month = (interval->month / MONTHS_PER_YEAR) * MONTHS_PER_YEAR; + interval->day = 0; + interval->time = 0; + } + else if (range == INTERVAL_MASK(MONTH)) + { + interval->day = 0; + interval->time = 0; + } + /* YEAR TO MONTH */ + else if (range == (INTERVAL_MASK(YEAR) | INTERVAL_MASK(MONTH))) + { + interval->day = 0; + interval->time = 0; + } + else if (range == INTERVAL_MASK(DAY)) + { + interval->time = 0; + } + else if (range == INTERVAL_MASK(HOUR)) + { + interval->time = (interval->time / USECS_PER_HOUR) * + USECS_PER_HOUR; + } + else if (range == INTERVAL_MASK(MINUTE)) + { + interval->time = (interval->time / USECS_PER_MINUTE) * + USECS_PER_MINUTE; + } + else if (range == INTERVAL_MASK(SECOND)) + { + /* fractional-second rounding will be dealt with below */ + } + /* DAY TO HOUR */ + else if (range == (INTERVAL_MASK(DAY) | + INTERVAL_MASK(HOUR))) + { + interval->time = (interval->time / USECS_PER_HOUR) * + USECS_PER_HOUR; + } + /* DAY TO MINUTE */ + else if (range == (INTERVAL_MASK(DAY) | + INTERVAL_MASK(HOUR) | + INTERVAL_MASK(MINUTE))) + { + interval->time = (interval->time / USECS_PER_MINUTE) * + USECS_PER_MINUTE; + } + /* DAY TO SECOND */ + else if (range == (INTERVAL_MASK(DAY) | + INTERVAL_MASK(HOUR) | + INTERVAL_MASK(MINUTE) | + INTERVAL_MASK(SECOND))) + { + /* fractional-second rounding will be dealt with below */ + } + /* HOUR TO MINUTE */ + else if (range == (INTERVAL_MASK(HOUR) | + INTERVAL_MASK(MINUTE))) + { + interval->time = (interval->time / USECS_PER_MINUTE) * + USECS_PER_MINUTE; + } + /* HOUR TO SECOND */ + else if (range == (INTERVAL_MASK(HOUR) | + INTERVAL_MASK(MINUTE) | + INTERVAL_MASK(SECOND))) + { + /* fractional-second rounding will be dealt with below */ + } + /* MINUTE TO SECOND */ + else if (range == (INTERVAL_MASK(MINUTE) | + INTERVAL_MASK(SECOND))) + { + /* fractional-second rounding will be dealt with below */ + } + else + elog(ERROR, "unrecognized interval typmod: %d", typmod); + + /* Need to adjust sub-second precision? */ + if (precision != INTERVAL_FULL_PRECISION) + { + if (precision < 0 || precision > MAX_INTERVAL_PRECISION) + ereturn(escontext, false, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("interval(%d) precision must be between %d and %d", + precision, 0, MAX_INTERVAL_PRECISION))); + + if (interval->time >= INT64CONST(0)) + { + interval->time = ((interval->time + + IntervalOffsets[precision]) / + IntervalScales[precision]) * + IntervalScales[precision]; + } + else + { + interval->time = -(((-interval->time + + IntervalOffsets[precision]) / + IntervalScales[precision]) * + IntervalScales[precision]); + } + } + } + + return true; +} + +/* + * make_interval - numeric Interval constructor + */ +Datum +make_interval(PG_FUNCTION_ARGS) +{ + int32 years = PG_GETARG_INT32(0); + int32 months = PG_GETARG_INT32(1); + int32 weeks = PG_GETARG_INT32(2); + int32 days = PG_GETARG_INT32(3); + int32 hours = PG_GETARG_INT32(4); + int32 mins = PG_GETARG_INT32(5); + double secs = PG_GETARG_FLOAT8(6); + Interval *result; + + /* + * Reject out-of-range inputs. We really ought to check the integer + * inputs as well, but it's not entirely clear what limits to apply. + */ + if (isinf(secs) || isnan(secs)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("interval out of range"))); + + result = (Interval *) palloc(sizeof(Interval)); + result->month = years * MONTHS_PER_YEAR + months; + result->day = weeks * 7 + days; + + secs = rint(secs * USECS_PER_SEC); + result->time = hours * ((int64) SECS_PER_HOUR * USECS_PER_SEC) + + mins * ((int64) SECS_PER_MINUTE * USECS_PER_SEC) + + (int64) secs; + + PG_RETURN_INTERVAL_P(result); +} + +/* EncodeSpecialTimestamp() + * Convert reserved timestamp data type to string. + */ +void +EncodeSpecialTimestamp(Timestamp dt, char *str) +{ + if (TIMESTAMP_IS_NOBEGIN(dt)) + strcpy(str, EARLY); + else if (TIMESTAMP_IS_NOEND(dt)) + strcpy(str, LATE); + else /* shouldn't happen */ + elog(ERROR, "invalid argument for EncodeSpecialTimestamp"); +} + +Datum +now(PG_FUNCTION_ARGS) +{ + PG_RETURN_TIMESTAMPTZ(GetCurrentTransactionStartTimestamp()); +} + +Datum +statement_timestamp(PG_FUNCTION_ARGS) +{ + PG_RETURN_TIMESTAMPTZ(GetCurrentStatementStartTimestamp()); +} + +Datum +clock_timestamp(PG_FUNCTION_ARGS) +{ + PG_RETURN_TIMESTAMPTZ(GetCurrentTimestamp()); +} + +Datum +pg_postmaster_start_time(PG_FUNCTION_ARGS) +{ + PG_RETURN_TIMESTAMPTZ(PgStartTime); +} + +Datum +pg_conf_load_time(PG_FUNCTION_ARGS) +{ + PG_RETURN_TIMESTAMPTZ(PgReloadTime); +} + +/* + * GetCurrentTimestamp -- get the current operating system time + * + * Result is in the form of a TimestampTz value, and is expressed to the + * full precision of the gettimeofday() syscall + */ +TimestampTz +GetCurrentTimestamp(void) +{ + TimestampTz result; + struct timeval tp; + + gettimeofday(&tp, NULL); + + result = (TimestampTz) tp.tv_sec - + ((POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * SECS_PER_DAY); + result = (result * USECS_PER_SEC) + tp.tv_usec; + + return result; +} + +/* + * GetSQLCurrentTimestamp -- implements CURRENT_TIMESTAMP, CURRENT_TIMESTAMP(n) + */ +TimestampTz +GetSQLCurrentTimestamp(int32 typmod) +{ + TimestampTz ts; + + ts = GetCurrentTransactionStartTimestamp(); + if (typmod >= 0) + AdjustTimestampForTypmod(&ts, typmod, NULL); + return ts; +} + +/* + * GetSQLLocalTimestamp -- implements LOCALTIMESTAMP, LOCALTIMESTAMP(n) + */ +Timestamp +GetSQLLocalTimestamp(int32 typmod) +{ + Timestamp ts; + + ts = timestamptz2timestamp(GetCurrentTransactionStartTimestamp()); + if (typmod >= 0) + AdjustTimestampForTypmod(&ts, typmod, NULL); + return ts; +} + +/* + * timeofday(*) -- returns the current time as a text. + */ +Datum +timeofday(PG_FUNCTION_ARGS) +{ + struct timeval tp; + char templ[128]; + char buf[128]; + pg_time_t tt; + + gettimeofday(&tp, NULL); + tt = (pg_time_t) tp.tv_sec; + pg_strftime(templ, sizeof(templ), "%a %b %d %H:%M:%S.%%06d %Y %Z", + pg_localtime(&tt, session_timezone)); + snprintf(buf, sizeof(buf), templ, tp.tv_usec); + + PG_RETURN_TEXT_P(cstring_to_text(buf)); +} + +/* + * TimestampDifference -- convert the difference between two timestamps + * into integer seconds and microseconds + * + * This is typically used to calculate a wait timeout for select(2), + * which explains the otherwise-odd choice of output format. + * + * Both inputs must be ordinary finite timestamps (in current usage, + * they'll be results from GetCurrentTimestamp()). + * + * We expect start_time <= stop_time. If not, we return zeros, + * since then we're already past the previously determined stop_time. + */ +void +TimestampDifference(TimestampTz start_time, TimestampTz stop_time, + long *secs, int *microsecs) +{ + TimestampTz diff = stop_time - start_time; + + if (diff <= 0) + { + *secs = 0; + *microsecs = 0; + } + else + { + *secs = (long) (diff / USECS_PER_SEC); + *microsecs = (int) (diff % USECS_PER_SEC); + } +} + +/* + * TimestampDifferenceMilliseconds -- convert the difference between two + * timestamps into integer milliseconds + * + * This is typically used to calculate a wait timeout for WaitLatch() + * or a related function. The choice of "long" as the result type + * is to harmonize with that; furthermore, we clamp the result to at most + * INT_MAX milliseconds, because that's all that WaitLatch() allows. + * + * We expect start_time <= stop_time. If not, we return zero, + * since then we're already past the previously determined stop_time. + * + * Subtracting finite and infinite timestamps works correctly, returning + * zero or INT_MAX as appropriate. + * + * Note we round up any fractional millisecond, since waiting for just + * less than the intended timeout is undesirable. + */ +long +TimestampDifferenceMilliseconds(TimestampTz start_time, TimestampTz stop_time) +{ + TimestampTz diff; + + /* Deal with zero or negative elapsed time quickly. */ + if (start_time >= stop_time) + return 0; + /* To not fail with timestamp infinities, we must detect overflow. */ + if (pg_sub_s64_overflow(stop_time, start_time, &diff)) + return (long) INT_MAX; + if (diff >= (INT_MAX * INT64CONST(1000) - 999)) + return (long) INT_MAX; + else + return (long) ((diff + 999) / 1000); +} + +/* + * TimestampDifferenceExceeds -- report whether the difference between two + * timestamps is >= a threshold (expressed in milliseconds) + * + * Both inputs must be ordinary finite timestamps (in current usage, + * they'll be results from GetCurrentTimestamp()). + */ +bool +TimestampDifferenceExceeds(TimestampTz start_time, + TimestampTz stop_time, + int msec) +{ + TimestampTz diff = stop_time - start_time; + + return (diff >= msec * INT64CONST(1000)); +} + +/* + * Convert a time_t to TimestampTz. + * + * We do not use time_t internally in Postgres, but this is provided for use + * by functions that need to interpret, say, a stat(2) result. + * + * To avoid having the function's ABI vary depending on the width of time_t, + * we declare the argument as pg_time_t, which is cast-compatible with + * time_t but always 64 bits wide (unless the platform has no 64-bit type). + * This detail should be invisible to callers, at least at source code level. + */ +TimestampTz +time_t_to_timestamptz(pg_time_t tm) +{ + TimestampTz result; + + result = (TimestampTz) tm - + ((POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * SECS_PER_DAY); + result *= USECS_PER_SEC; + + return result; +} + +/* + * Convert a TimestampTz to time_t. + * + * This too is just marginally useful, but some places need it. + * + * To avoid having the function's ABI vary depending on the width of time_t, + * we declare the result as pg_time_t, which is cast-compatible with + * time_t but always 64 bits wide (unless the platform has no 64-bit type). + * This detail should be invisible to callers, at least at source code level. + */ +pg_time_t +timestamptz_to_time_t(TimestampTz t) +{ + pg_time_t result; + + result = (pg_time_t) (t / USECS_PER_SEC + + ((POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * SECS_PER_DAY)); + + return result; +} + +/* + * Produce a C-string representation of a TimestampTz. + * + * This is mostly for use in emitting messages. The primary difference + * from timestamptz_out is that we force the output format to ISO. Note + * also that the result is in a static buffer, not pstrdup'd. + * + * See also pg_strftime. + */ +const char * +timestamptz_to_str(TimestampTz t) +{ + static __thread char buf[MAXDATELEN + 1]; + int tz; + struct pg_tm tt, + *tm = &tt; + fsec_t fsec; + const char *tzn; + + if (TIMESTAMP_NOT_FINITE(t)) + EncodeSpecialTimestamp(t, buf); + else if (timestamp2tm(t, &tz, tm, &fsec, &tzn, NULL) == 0) + EncodeDateTime(tm, fsec, true, tz, tzn, USE_ISO_DATES, buf); + else + strlcpy(buf, "(timestamp out of range)", sizeof(buf)); + + return buf; +} + + +void +dt2time(Timestamp jd, int *hour, int *min, int *sec, fsec_t *fsec) +{ + TimeOffset time; + + time = jd; + + *hour = time / USECS_PER_HOUR; + time -= (*hour) * USECS_PER_HOUR; + *min = time / USECS_PER_MINUTE; + time -= (*min) * USECS_PER_MINUTE; + *sec = time / USECS_PER_SEC; + *fsec = time - (*sec * USECS_PER_SEC); +} /* dt2time() */ + + +/* + * timestamp2tm() - Convert timestamp data type to POSIX time structure. + * + * Note that year is _not_ 1900-based, but is an explicit full value. + * Also, month is one-based, _not_ zero-based. + * Returns: + * 0 on success + * -1 on out of range + * + * If attimezone is NULL, the global timezone setting will be used. + */ +int +timestamp2tm(Timestamp dt, int *tzp, struct pg_tm *tm, fsec_t *fsec, const char **tzn, pg_tz *attimezone) +{ + Timestamp date; + Timestamp time; + pg_time_t utime; + + /* Use session timezone if caller asks for default */ + if (attimezone == NULL) + attimezone = session_timezone; + + time = dt; + TMODULO(time, date, USECS_PER_DAY); + + if (time < INT64CONST(0)) + { + time += USECS_PER_DAY; + date -= 1; + } + + /* add offset to go from J2000 back to standard Julian date */ + date += POSTGRES_EPOCH_JDATE; + + /* Julian day routine does not work for negative Julian days */ + if (date < 0 || date > (Timestamp) INT_MAX) + return -1; + + j2date((int) date, &tm->tm_year, &tm->tm_mon, &tm->tm_mday); + dt2time(time, &tm->tm_hour, &tm->tm_min, &tm->tm_sec, fsec); + + /* Done if no TZ conversion wanted */ + if (tzp == NULL) + { + tm->tm_isdst = -1; + tm->tm_gmtoff = 0; + tm->tm_zone = NULL; + if (tzn != NULL) + *tzn = NULL; + return 0; + } + + /* + * If the time falls within the range of pg_time_t, use pg_localtime() to + * rotate to the local time zone. + * + * First, convert to an integral timestamp, avoiding possibly + * platform-specific roundoff-in-wrong-direction errors, and adjust to + * Unix epoch. Then see if we can convert to pg_time_t without loss. This + * coding avoids hardwiring any assumptions about the width of pg_time_t, + * so it should behave sanely on machines without int64. + */ + dt = (dt - *fsec) / USECS_PER_SEC + + (POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * SECS_PER_DAY; + utime = (pg_time_t) dt; + if ((Timestamp) utime == dt) + { + struct pg_tm *tx = pg_localtime(&utime, attimezone); + + tm->tm_year = tx->tm_year + 1900; + tm->tm_mon = tx->tm_mon + 1; + tm->tm_mday = tx->tm_mday; + tm->tm_hour = tx->tm_hour; + tm->tm_min = tx->tm_min; + tm->tm_sec = tx->tm_sec; + tm->tm_isdst = tx->tm_isdst; + tm->tm_gmtoff = tx->tm_gmtoff; + tm->tm_zone = tx->tm_zone; + *tzp = -tm->tm_gmtoff; + if (tzn != NULL) + *tzn = tm->tm_zone; + } + else + { + /* + * When out of range of pg_time_t, treat as GMT + */ + *tzp = 0; + /* Mark this as *no* time zone available */ + tm->tm_isdst = -1; + tm->tm_gmtoff = 0; + tm->tm_zone = NULL; + if (tzn != NULL) + *tzn = NULL; + } + + return 0; +} + + +/* tm2timestamp() + * Convert a tm structure to a timestamp data type. + * Note that year is _not_ 1900-based, but is an explicit full value. + * Also, month is one-based, _not_ zero-based. + * + * Returns -1 on failure (value out of range). + */ +int +tm2timestamp(struct pg_tm *tm, fsec_t fsec, int *tzp, Timestamp *result) +{ + TimeOffset date; + TimeOffset time; + + /* Prevent overflow in Julian-day routines */ + if (!IS_VALID_JULIAN(tm->tm_year, tm->tm_mon, tm->tm_mday)) + { + *result = 0; /* keep compiler quiet */ + return -1; + } + + date = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - POSTGRES_EPOCH_JDATE; + time = time2t(tm->tm_hour, tm->tm_min, tm->tm_sec, fsec); + + *result = date * USECS_PER_DAY + time; + /* check for major overflow */ + if ((*result - time) / USECS_PER_DAY != date) + { + *result = 0; /* keep compiler quiet */ + return -1; + } + /* check for just-barely overflow (okay except time-of-day wraps) */ + /* caution: we want to allow 1999-12-31 24:00:00 */ + if ((*result < 0 && date > 0) || + (*result > 0 && date < -1)) + { + *result = 0; /* keep compiler quiet */ + return -1; + } + if (tzp != NULL) + *result = dt2local(*result, -(*tzp)); + + /* final range check catches just-out-of-range timestamps */ + if (!IS_VALID_TIMESTAMP(*result)) + { + *result = 0; /* keep compiler quiet */ + return -1; + } + + return 0; +} + + +/* interval2itm() + * Convert an Interval to a pg_itm structure. + * Note: overflow is not possible, because the pg_itm fields are + * wide enough for all possible conversion results. + */ +void +interval2itm(Interval span, struct pg_itm *itm) +{ + TimeOffset time; + TimeOffset tfrac; + + itm->tm_year = span.month / MONTHS_PER_YEAR; + itm->tm_mon = span.month % MONTHS_PER_YEAR; + itm->tm_mday = span.day; + time = span.time; + + tfrac = time / USECS_PER_HOUR; + time -= tfrac * USECS_PER_HOUR; + itm->tm_hour = tfrac; + tfrac = time / USECS_PER_MINUTE; + time -= tfrac * USECS_PER_MINUTE; + itm->tm_min = (int) tfrac; + tfrac = time / USECS_PER_SEC; + time -= tfrac * USECS_PER_SEC; + itm->tm_sec = (int) tfrac; + itm->tm_usec = (int) time; +} + +/* itm2interval() + * Convert a pg_itm structure to an Interval. + * Returns 0 if OK, -1 on overflow. + */ +int +itm2interval(struct pg_itm *itm, Interval *span) +{ + int64 total_months = (int64) itm->tm_year * MONTHS_PER_YEAR + itm->tm_mon; + + if (total_months > INT_MAX || total_months < INT_MIN) + return -1; + span->month = (int32) total_months; + span->day = itm->tm_mday; + if (pg_mul_s64_overflow(itm->tm_hour, USECS_PER_HOUR, + &span->time)) + return -1; + /* tm_min, tm_sec are 32 bits, so intermediate products can't overflow */ + if (pg_add_s64_overflow(span->time, itm->tm_min * USECS_PER_MINUTE, + &span->time)) + return -1; + if (pg_add_s64_overflow(span->time, itm->tm_sec * USECS_PER_SEC, + &span->time)) + return -1; + if (pg_add_s64_overflow(span->time, itm->tm_usec, + &span->time)) + return -1; + return 0; +} + +/* itmin2interval() + * Convert a pg_itm_in structure to an Interval. + * Returns 0 if OK, -1 on overflow. + */ +int +itmin2interval(struct pg_itm_in *itm_in, Interval *span) +{ + int64 total_months = (int64) itm_in->tm_year * MONTHS_PER_YEAR + itm_in->tm_mon; + + if (total_months > INT_MAX || total_months < INT_MIN) + return -1; + span->month = (int32) total_months; + span->day = itm_in->tm_mday; + span->time = itm_in->tm_usec; + return 0; +} + +static TimeOffset +time2t(const int hour, const int min, const int sec, const fsec_t fsec) +{ + return (((((hour * MINS_PER_HOUR) + min) * SECS_PER_MINUTE) + sec) * USECS_PER_SEC) + fsec; +} + +static Timestamp +dt2local(Timestamp dt, int timezone) +{ + dt -= (timezone * USECS_PER_SEC); + return dt; +} + + +/***************************************************************************** + * PUBLIC ROUTINES * + *****************************************************************************/ + + +Datum +timestamp_finite(PG_FUNCTION_ARGS) +{ + Timestamp timestamp = PG_GETARG_TIMESTAMP(0); + + PG_RETURN_BOOL(!TIMESTAMP_NOT_FINITE(timestamp)); +} + +Datum +interval_finite(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(true); +} + + +/*---------------------------------------------------------- + * Relational operators for timestamp. + *---------------------------------------------------------*/ + +void +GetEpochTime(struct pg_tm *tm) +{ + struct pg_tm *t0; + pg_time_t epoch = 0; + + t0 = pg_gmtime(&epoch); + + if (t0 == NULL) + elog(ERROR, "could not convert epoch to timestamp: %m"); + + tm->tm_year = t0->tm_year; + tm->tm_mon = t0->tm_mon; + tm->tm_mday = t0->tm_mday; + tm->tm_hour = t0->tm_hour; + tm->tm_min = t0->tm_min; + tm->tm_sec = t0->tm_sec; + + tm->tm_year += 1900; + tm->tm_mon++; +} + +Timestamp +SetEpochTimestamp(void) +{ + Timestamp dt; + struct pg_tm tt, + *tm = &tt; + + GetEpochTime(tm); + /* we don't bother to test for failure ... */ + tm2timestamp(tm, 0, NULL, &dt); + + return dt; +} /* SetEpochTimestamp() */ + +/* + * We are currently sharing some code between timestamp and timestamptz. + * The comparison functions are among them. - thomas 2001-09-25 + * + * timestamp_relop - is timestamp1 relop timestamp2 + */ +int +timestamp_cmp_internal(Timestamp dt1, Timestamp dt2) +{ + return (dt1 < dt2) ? -1 : ((dt1 > dt2) ? 1 : 0); +} + +Datum +timestamp_eq(PG_FUNCTION_ARGS) +{ + Timestamp dt1 = PG_GETARG_TIMESTAMP(0); + Timestamp dt2 = PG_GETARG_TIMESTAMP(1); + + PG_RETURN_BOOL(timestamp_cmp_internal(dt1, dt2) == 0); +} + +Datum +timestamp_ne(PG_FUNCTION_ARGS) +{ + Timestamp dt1 = PG_GETARG_TIMESTAMP(0); + Timestamp dt2 = PG_GETARG_TIMESTAMP(1); + + PG_RETURN_BOOL(timestamp_cmp_internal(dt1, dt2) != 0); +} + +Datum +timestamp_lt(PG_FUNCTION_ARGS) +{ + Timestamp dt1 = PG_GETARG_TIMESTAMP(0); + Timestamp dt2 = PG_GETARG_TIMESTAMP(1); + + PG_RETURN_BOOL(timestamp_cmp_internal(dt1, dt2) < 0); +} + +Datum +timestamp_gt(PG_FUNCTION_ARGS) +{ + Timestamp dt1 = PG_GETARG_TIMESTAMP(0); + Timestamp dt2 = PG_GETARG_TIMESTAMP(1); + + PG_RETURN_BOOL(timestamp_cmp_internal(dt1, dt2) > 0); +} + +Datum +timestamp_le(PG_FUNCTION_ARGS) +{ + Timestamp dt1 = PG_GETARG_TIMESTAMP(0); + Timestamp dt2 = PG_GETARG_TIMESTAMP(1); + + PG_RETURN_BOOL(timestamp_cmp_internal(dt1, dt2) <= 0); +} + +Datum +timestamp_ge(PG_FUNCTION_ARGS) +{ + Timestamp dt1 = PG_GETARG_TIMESTAMP(0); + Timestamp dt2 = PG_GETARG_TIMESTAMP(1); + + PG_RETURN_BOOL(timestamp_cmp_internal(dt1, dt2) >= 0); +} + +Datum +timestamp_cmp(PG_FUNCTION_ARGS) +{ + Timestamp dt1 = PG_GETARG_TIMESTAMP(0); + Timestamp dt2 = PG_GETARG_TIMESTAMP(1); + + PG_RETURN_INT32(timestamp_cmp_internal(dt1, dt2)); +} + +#if SIZEOF_DATUM < 8 +/* note: this is used for timestamptz also */ +static int +timestamp_fastcmp(Datum x, Datum y, SortSupport ssup) +{ + Timestamp a = DatumGetTimestamp(x); + Timestamp b = DatumGetTimestamp(y); + + return timestamp_cmp_internal(a, b); +} +#endif + +Datum +timestamp_sortsupport(PG_FUNCTION_ARGS) +{ + SortSupport ssup = (SortSupport) PG_GETARG_POINTER(0); + +#if SIZEOF_DATUM >= 8 + + /* + * If this build has pass-by-value timestamps, then we can use a standard + * comparator function. + */ + ssup->comparator = ssup_datum_signed_cmp; +#else + ssup->comparator = timestamp_fastcmp; +#endif + PG_RETURN_VOID(); +} + +Datum +timestamp_hash(PG_FUNCTION_ARGS) +{ + return hashint8(fcinfo); +} + +Datum +timestamp_hash_extended(PG_FUNCTION_ARGS) +{ + return hashint8extended(fcinfo); +} + +/* + * Cross-type comparison functions for timestamp vs timestamptz + */ + +int32 +timestamp_cmp_timestamptz_internal(Timestamp timestampVal, TimestampTz dt2) +{ + TimestampTz dt1; + int overflow; + + dt1 = timestamp2timestamptz_opt_overflow(timestampVal, &overflow); + if (overflow > 0) + { + /* dt1 is larger than any finite timestamp, but less than infinity */ + return TIMESTAMP_IS_NOEND(dt2) ? -1 : +1; + } + if (overflow < 0) + { + /* dt1 is less than any finite timestamp, but more than -infinity */ + return TIMESTAMP_IS_NOBEGIN(dt2) ? +1 : -1; + } + + return timestamptz_cmp_internal(dt1, dt2); +} + +Datum +timestamp_eq_timestamptz(PG_FUNCTION_ARGS) +{ + Timestamp timestampVal = PG_GETARG_TIMESTAMP(0); + TimestampTz dt2 = PG_GETARG_TIMESTAMPTZ(1); + + PG_RETURN_BOOL(timestamp_cmp_timestamptz_internal(timestampVal, dt2) == 0); +} + +Datum +timestamp_ne_timestamptz(PG_FUNCTION_ARGS) +{ + Timestamp timestampVal = PG_GETARG_TIMESTAMP(0); + TimestampTz dt2 = PG_GETARG_TIMESTAMPTZ(1); + + PG_RETURN_BOOL(timestamp_cmp_timestamptz_internal(timestampVal, dt2) != 0); +} + +Datum +timestamp_lt_timestamptz(PG_FUNCTION_ARGS) +{ + Timestamp timestampVal = PG_GETARG_TIMESTAMP(0); + TimestampTz dt2 = PG_GETARG_TIMESTAMPTZ(1); + + PG_RETURN_BOOL(timestamp_cmp_timestamptz_internal(timestampVal, dt2) < 0); +} + +Datum +timestamp_gt_timestamptz(PG_FUNCTION_ARGS) +{ + Timestamp timestampVal = PG_GETARG_TIMESTAMP(0); + TimestampTz dt2 = PG_GETARG_TIMESTAMPTZ(1); + + PG_RETURN_BOOL(timestamp_cmp_timestamptz_internal(timestampVal, dt2) > 0); +} + +Datum +timestamp_le_timestamptz(PG_FUNCTION_ARGS) +{ + Timestamp timestampVal = PG_GETARG_TIMESTAMP(0); + TimestampTz dt2 = PG_GETARG_TIMESTAMPTZ(1); + + PG_RETURN_BOOL(timestamp_cmp_timestamptz_internal(timestampVal, dt2) <= 0); +} + +Datum +timestamp_ge_timestamptz(PG_FUNCTION_ARGS) +{ + Timestamp timestampVal = PG_GETARG_TIMESTAMP(0); + TimestampTz dt2 = PG_GETARG_TIMESTAMPTZ(1); + + PG_RETURN_BOOL(timestamp_cmp_timestamptz_internal(timestampVal, dt2) >= 0); +} + +Datum +timestamp_cmp_timestamptz(PG_FUNCTION_ARGS) +{ + Timestamp timestampVal = PG_GETARG_TIMESTAMP(0); + TimestampTz dt2 = PG_GETARG_TIMESTAMPTZ(1); + + PG_RETURN_INT32(timestamp_cmp_timestamptz_internal(timestampVal, dt2)); +} + +Datum +timestamptz_eq_timestamp(PG_FUNCTION_ARGS) +{ + TimestampTz dt1 = PG_GETARG_TIMESTAMPTZ(0); + Timestamp timestampVal = PG_GETARG_TIMESTAMP(1); + + PG_RETURN_BOOL(timestamp_cmp_timestamptz_internal(timestampVal, dt1) == 0); +} + +Datum +timestamptz_ne_timestamp(PG_FUNCTION_ARGS) +{ + TimestampTz dt1 = PG_GETARG_TIMESTAMPTZ(0); + Timestamp timestampVal = PG_GETARG_TIMESTAMP(1); + + PG_RETURN_BOOL(timestamp_cmp_timestamptz_internal(timestampVal, dt1) != 0); +} + +Datum +timestamptz_lt_timestamp(PG_FUNCTION_ARGS) +{ + TimestampTz dt1 = PG_GETARG_TIMESTAMPTZ(0); + Timestamp timestampVal = PG_GETARG_TIMESTAMP(1); + + PG_RETURN_BOOL(timestamp_cmp_timestamptz_internal(timestampVal, dt1) > 0); +} + +Datum +timestamptz_gt_timestamp(PG_FUNCTION_ARGS) +{ + TimestampTz dt1 = PG_GETARG_TIMESTAMPTZ(0); + Timestamp timestampVal = PG_GETARG_TIMESTAMP(1); + + PG_RETURN_BOOL(timestamp_cmp_timestamptz_internal(timestampVal, dt1) < 0); +} + +Datum +timestamptz_le_timestamp(PG_FUNCTION_ARGS) +{ + TimestampTz dt1 = PG_GETARG_TIMESTAMPTZ(0); + Timestamp timestampVal = PG_GETARG_TIMESTAMP(1); + + PG_RETURN_BOOL(timestamp_cmp_timestamptz_internal(timestampVal, dt1) >= 0); +} + +Datum +timestamptz_ge_timestamp(PG_FUNCTION_ARGS) +{ + TimestampTz dt1 = PG_GETARG_TIMESTAMPTZ(0); + Timestamp timestampVal = PG_GETARG_TIMESTAMP(1); + + PG_RETURN_BOOL(timestamp_cmp_timestamptz_internal(timestampVal, dt1) <= 0); +} + +Datum +timestamptz_cmp_timestamp(PG_FUNCTION_ARGS) +{ + TimestampTz dt1 = PG_GETARG_TIMESTAMPTZ(0); + Timestamp timestampVal = PG_GETARG_TIMESTAMP(1); + + PG_RETURN_INT32(-timestamp_cmp_timestamptz_internal(timestampVal, dt1)); +} + + +/* + * interval_relop - is interval1 relop interval2 + * + * Interval comparison is based on converting interval values to a linear + * representation expressed in the units of the time field (microseconds, + * in the case of integer timestamps) with days assumed to be always 24 hours + * and months assumed to be always 30 days. To avoid overflow, we need a + * wider-than-int64 datatype for the linear representation, so use INT128. + */ + +static inline INT128 +interval_cmp_value(const Interval *interval) +{ + INT128 span; + int64 days; + + /* + * Combine the month and day fields into an integral number of days. + * Because the inputs are int32, int64 arithmetic suffices here. + */ + days = interval->month * INT64CONST(30); + days += interval->day; + + /* Widen time field to 128 bits */ + span = int64_to_int128(interval->time); + + /* Scale up days to microseconds, forming a 128-bit product */ + int128_add_int64_mul_int64(&span, days, USECS_PER_DAY); + + return span; +} + +static int +interval_cmp_internal(const Interval *interval1, const Interval *interval2) +{ + INT128 span1 = interval_cmp_value(interval1); + INT128 span2 = interval_cmp_value(interval2); + + return int128_compare(span1, span2); +} + +Datum +interval_eq(PG_FUNCTION_ARGS) +{ + Interval *interval1 = PG_GETARG_INTERVAL_P(0); + Interval *interval2 = PG_GETARG_INTERVAL_P(1); + + PG_RETURN_BOOL(interval_cmp_internal(interval1, interval2) == 0); +} + +Datum +interval_ne(PG_FUNCTION_ARGS) +{ + Interval *interval1 = PG_GETARG_INTERVAL_P(0); + Interval *interval2 = PG_GETARG_INTERVAL_P(1); + + PG_RETURN_BOOL(interval_cmp_internal(interval1, interval2) != 0); +} + +Datum +interval_lt(PG_FUNCTION_ARGS) +{ + Interval *interval1 = PG_GETARG_INTERVAL_P(0); + Interval *interval2 = PG_GETARG_INTERVAL_P(1); + + PG_RETURN_BOOL(interval_cmp_internal(interval1, interval2) < 0); +} + +Datum +interval_gt(PG_FUNCTION_ARGS) +{ + Interval *interval1 = PG_GETARG_INTERVAL_P(0); + Interval *interval2 = PG_GETARG_INTERVAL_P(1); + + PG_RETURN_BOOL(interval_cmp_internal(interval1, interval2) > 0); +} + +Datum +interval_le(PG_FUNCTION_ARGS) +{ + Interval *interval1 = PG_GETARG_INTERVAL_P(0); + Interval *interval2 = PG_GETARG_INTERVAL_P(1); + + PG_RETURN_BOOL(interval_cmp_internal(interval1, interval2) <= 0); +} + +Datum +interval_ge(PG_FUNCTION_ARGS) +{ + Interval *interval1 = PG_GETARG_INTERVAL_P(0); + Interval *interval2 = PG_GETARG_INTERVAL_P(1); + + PG_RETURN_BOOL(interval_cmp_internal(interval1, interval2) >= 0); +} + +Datum +interval_cmp(PG_FUNCTION_ARGS) +{ + Interval *interval1 = PG_GETARG_INTERVAL_P(0); + Interval *interval2 = PG_GETARG_INTERVAL_P(1); + + PG_RETURN_INT32(interval_cmp_internal(interval1, interval2)); +} + +/* + * Hashing for intervals + * + * We must produce equal hashvals for values that interval_cmp_internal() + * considers equal. So, compute the net span the same way it does, + * and then hash that. + */ +Datum +interval_hash(PG_FUNCTION_ARGS) +{ + Interval *interval = PG_GETARG_INTERVAL_P(0); + INT128 span = interval_cmp_value(interval); + int64 span64; + + /* + * Use only the least significant 64 bits for hashing. The upper 64 bits + * seldom add any useful information, and besides we must do it like this + * for compatibility with hashes calculated before use of INT128 was + * introduced. + */ + span64 = int128_to_int64(span); + + return DirectFunctionCall1(hashint8, Int64GetDatumFast(span64)); +} + +Datum +interval_hash_extended(PG_FUNCTION_ARGS) +{ + Interval *interval = PG_GETARG_INTERVAL_P(0); + INT128 span = interval_cmp_value(interval); + int64 span64; + + /* Same approach as interval_hash */ + span64 = int128_to_int64(span); + + return DirectFunctionCall2(hashint8extended, Int64GetDatumFast(span64), + PG_GETARG_DATUM(1)); +} + +/* overlaps_timestamp() --- implements the SQL OVERLAPS operator. + * + * Algorithm is per SQL spec. This is much harder than you'd think + * because the spec requires us to deliver a non-null answer in some cases + * where some of the inputs are null. + */ +Datum +overlaps_timestamp(PG_FUNCTION_ARGS) +{ + /* + * The arguments are Timestamps, but we leave them as generic Datums to + * avoid unnecessary conversions between value and reference forms --- not + * to mention possible dereferences of null pointers. + */ + Datum ts1 = PG_GETARG_DATUM(0); + Datum te1 = PG_GETARG_DATUM(1); + Datum ts2 = PG_GETARG_DATUM(2); + Datum te2 = PG_GETARG_DATUM(3); + bool ts1IsNull = PG_ARGISNULL(0); + bool te1IsNull = PG_ARGISNULL(1); + bool ts2IsNull = PG_ARGISNULL(2); + bool te2IsNull = PG_ARGISNULL(3); + +#define TIMESTAMP_GT(t1,t2) \ + DatumGetBool(DirectFunctionCall2(timestamp_gt,t1,t2)) +#define TIMESTAMP_LT(t1,t2) \ + DatumGetBool(DirectFunctionCall2(timestamp_lt,t1,t2)) + + /* + * If both endpoints of interval 1 are null, the result is null (unknown). + * If just one endpoint is null, take ts1 as the non-null one. Otherwise, + * take ts1 as the lesser endpoint. + */ + if (ts1IsNull) + { + if (te1IsNull) + PG_RETURN_NULL(); + /* swap null for non-null */ + ts1 = te1; + te1IsNull = true; + } + else if (!te1IsNull) + { + if (TIMESTAMP_GT(ts1, te1)) + { + Datum tt = ts1; + + ts1 = te1; + te1 = tt; + } + } + + /* Likewise for interval 2. */ + if (ts2IsNull) + { + if (te2IsNull) + PG_RETURN_NULL(); + /* swap null for non-null */ + ts2 = te2; + te2IsNull = true; + } + else if (!te2IsNull) + { + if (TIMESTAMP_GT(ts2, te2)) + { + Datum tt = ts2; + + ts2 = te2; + te2 = tt; + } + } + + /* + * At this point neither ts1 nor ts2 is null, so we can consider three + * cases: ts1 > ts2, ts1 < ts2, ts1 = ts2 + */ + if (TIMESTAMP_GT(ts1, ts2)) + { + /* + * This case is ts1 < te2 OR te1 < te2, which may look redundant but + * in the presence of nulls it's not quite completely so. + */ + if (te2IsNull) + PG_RETURN_NULL(); + if (TIMESTAMP_LT(ts1, te2)) + PG_RETURN_BOOL(true); + if (te1IsNull) + PG_RETURN_NULL(); + + /* + * If te1 is not null then we had ts1 <= te1 above, and we just found + * ts1 >= te2, hence te1 >= te2. + */ + PG_RETURN_BOOL(false); + } + else if (TIMESTAMP_LT(ts1, ts2)) + { + /* This case is ts2 < te1 OR te2 < te1 */ + if (te1IsNull) + PG_RETURN_NULL(); + if (TIMESTAMP_LT(ts2, te1)) + PG_RETURN_BOOL(true); + if (te2IsNull) + PG_RETURN_NULL(); + + /* + * If te2 is not null then we had ts2 <= te2 above, and we just found + * ts2 >= te1, hence te2 >= te1. + */ + PG_RETURN_BOOL(false); + } + else + { + /* + * For ts1 = ts2 the spec says te1 <> te2 OR te1 = te2, which is a + * rather silly way of saying "true if both are non-null, else null". + */ + if (te1IsNull || te2IsNull) + PG_RETURN_NULL(); + PG_RETURN_BOOL(true); + } + +#undef TIMESTAMP_GT +#undef TIMESTAMP_LT +} + + +/*---------------------------------------------------------- + * "Arithmetic" operators on date/times. + *---------------------------------------------------------*/ + +Datum +timestamp_smaller(PG_FUNCTION_ARGS) +{ + Timestamp dt1 = PG_GETARG_TIMESTAMP(0); + Timestamp dt2 = PG_GETARG_TIMESTAMP(1); + Timestamp result; + + /* use timestamp_cmp_internal to be sure this agrees with comparisons */ + if (timestamp_cmp_internal(dt1, dt2) < 0) + result = dt1; + else + result = dt2; + PG_RETURN_TIMESTAMP(result); +} + +Datum +timestamp_larger(PG_FUNCTION_ARGS) +{ + Timestamp dt1 = PG_GETARG_TIMESTAMP(0); + Timestamp dt2 = PG_GETARG_TIMESTAMP(1); + Timestamp result; + + if (timestamp_cmp_internal(dt1, dt2) > 0) + result = dt1; + else + result = dt2; + PG_RETURN_TIMESTAMP(result); +} + + +Datum +timestamp_mi(PG_FUNCTION_ARGS) +{ + Timestamp dt1 = PG_GETARG_TIMESTAMP(0); + Timestamp dt2 = PG_GETARG_TIMESTAMP(1); + Interval *result; + + result = (Interval *) palloc(sizeof(Interval)); + + if (TIMESTAMP_NOT_FINITE(dt1) || TIMESTAMP_NOT_FINITE(dt2)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("cannot subtract infinite timestamps"))); + + if (unlikely(pg_sub_s64_overflow(dt1, dt2, &result->time))) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("interval out of range"))); + + result->month = 0; + result->day = 0; + + /*---------- + * This is wrong, but removing it breaks a lot of regression tests. + * For example: + * + * test=> SET timezone = 'EST5EDT'; + * test=> SELECT + * test-> ('2005-10-30 13:22:00-05'::timestamptz - + * test(> '2005-10-29 13:22:00-04'::timestamptz); + * ?column? + * ---------------- + * 1 day 01:00:00 + * (1 row) + * + * so adding that to the first timestamp gets: + * + * test=> SELECT + * test-> ('2005-10-29 13:22:00-04'::timestamptz + + * test(> ('2005-10-30 13:22:00-05'::timestamptz - + * test(> '2005-10-29 13:22:00-04'::timestamptz)) at time zone 'EST'; + * timezone + * -------------------- + * 2005-10-30 14:22:00 + * (1 row) + *---------- + */ + result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours, + IntervalPGetDatum(result))); + + PG_RETURN_INTERVAL_P(result); +} + +/* + * interval_justify_interval() + * + * Adjust interval so 'month', 'day', and 'time' portions are within + * customary bounds. Specifically: + * + * 0 <= abs(time) < 24 hours + * 0 <= abs(day) < 30 days + * + * Also, the sign bit on all three fields is made equal, so either + * all three fields are negative or all are positive. + */ +Datum +interval_justify_interval(PG_FUNCTION_ARGS) +{ + Interval *span = PG_GETARG_INTERVAL_P(0); + Interval *result; + TimeOffset wholeday; + int32 wholemonth; + + result = (Interval *) palloc(sizeof(Interval)); + result->month = span->month; + result->day = span->day; + result->time = span->time; + + /* pre-justify days if it might prevent overflow */ + if ((result->day > 0 && result->time > 0) || + (result->day < 0 && result->time < 0)) + { + wholemonth = result->day / DAYS_PER_MONTH; + result->day -= wholemonth * DAYS_PER_MONTH; + if (pg_add_s32_overflow(result->month, wholemonth, &result->month)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("interval out of range"))); + } + + /* + * Since TimeOffset is int64, abs(wholeday) can't exceed about 1.07e8. If + * we pre-justified then abs(result->day) is less than DAYS_PER_MONTH, so + * this addition can't overflow. If we didn't pre-justify, then day and + * time are of different signs, so it still can't overflow. + */ + TMODULO(result->time, wholeday, USECS_PER_DAY); + result->day += wholeday; + + wholemonth = result->day / DAYS_PER_MONTH; + result->day -= wholemonth * DAYS_PER_MONTH; + if (pg_add_s32_overflow(result->month, wholemonth, &result->month)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("interval out of range"))); + + if (result->month > 0 && + (result->day < 0 || (result->day == 0 && result->time < 0))) + { + result->day += DAYS_PER_MONTH; + result->month--; + } + else if (result->month < 0 && + (result->day > 0 || (result->day == 0 && result->time > 0))) + { + result->day -= DAYS_PER_MONTH; + result->month++; + } + + if (result->day > 0 && result->time < 0) + { + result->time += USECS_PER_DAY; + result->day--; + } + else if (result->day < 0 && result->time > 0) + { + result->time -= USECS_PER_DAY; + result->day++; + } + + PG_RETURN_INTERVAL_P(result); +} + +/* + * interval_justify_hours() + * + * Adjust interval so 'time' contains less than a whole day, adding + * the excess to 'day'. This is useful for + * situations (such as non-TZ) where '1 day' = '24 hours' is valid, + * e.g. interval subtraction and division. + */ +Datum +interval_justify_hours(PG_FUNCTION_ARGS) +{ + Interval *span = PG_GETARG_INTERVAL_P(0); + Interval *result; + TimeOffset wholeday; + + result = (Interval *) palloc(sizeof(Interval)); + result->month = span->month; + result->day = span->day; + result->time = span->time; + + TMODULO(result->time, wholeday, USECS_PER_DAY); + if (pg_add_s32_overflow(result->day, wholeday, &result->day)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("interval out of range"))); + + if (result->day > 0 && result->time < 0) + { + result->time += USECS_PER_DAY; + result->day--; + } + else if (result->day < 0 && result->time > 0) + { + result->time -= USECS_PER_DAY; + result->day++; + } + + PG_RETURN_INTERVAL_P(result); +} + +/* + * interval_justify_days() + * + * Adjust interval so 'day' contains less than 30 days, adding + * the excess to 'month'. + */ +Datum +interval_justify_days(PG_FUNCTION_ARGS) +{ + Interval *span = PG_GETARG_INTERVAL_P(0); + Interval *result; + int32 wholemonth; + + result = (Interval *) palloc(sizeof(Interval)); + result->month = span->month; + result->day = span->day; + result->time = span->time; + + wholemonth = result->day / DAYS_PER_MONTH; + result->day -= wholemonth * DAYS_PER_MONTH; + if (pg_add_s32_overflow(result->month, wholemonth, &result->month)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("interval out of range"))); + + if (result->month > 0 && result->day < 0) + { + result->day += DAYS_PER_MONTH; + result->month--; + } + else if (result->month < 0 && result->day > 0) + { + result->day -= DAYS_PER_MONTH; + result->month++; + } + + PG_RETURN_INTERVAL_P(result); +} + +/* timestamp_pl_interval() + * Add an interval to a timestamp data type. + * Note that interval has provisions for qualitative year/month and day + * units, so try to do the right thing with them. + * To add a month, increment the month, and use the same day of month. + * Then, if the next month has fewer days, set the day of month + * to the last day of month. + * To add a day, increment the mday, and use the same time of day. + * Lastly, add in the "quantitative time". + */ +Datum +timestamp_pl_interval(PG_FUNCTION_ARGS) +{ + Timestamp timestamp = PG_GETARG_TIMESTAMP(0); + Interval *span = PG_GETARG_INTERVAL_P(1); + Timestamp result; + + if (TIMESTAMP_NOT_FINITE(timestamp)) + result = timestamp; + else + { + if (span->month != 0) + { + struct pg_tm tt, + *tm = &tt; + fsec_t fsec; + + if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + + if (pg_add_s32_overflow(tm->tm_mon, span->month, &tm->tm_mon)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + if (tm->tm_mon > MONTHS_PER_YEAR) + { + tm->tm_year += (tm->tm_mon - 1) / MONTHS_PER_YEAR; + tm->tm_mon = ((tm->tm_mon - 1) % MONTHS_PER_YEAR) + 1; + } + else if (tm->tm_mon < 1) + { + tm->tm_year += tm->tm_mon / MONTHS_PER_YEAR - 1; + tm->tm_mon = tm->tm_mon % MONTHS_PER_YEAR + MONTHS_PER_YEAR; + } + + /* adjust for end of month boundary problems... */ + if (tm->tm_mday > day_tab[isleap(tm->tm_year)][tm->tm_mon - 1]) + tm->tm_mday = (day_tab[isleap(tm->tm_year)][tm->tm_mon - 1]); + + if (tm2timestamp(tm, fsec, NULL, ×tamp) != 0) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + } + + if (span->day != 0) + { + struct pg_tm tt, + *tm = &tt; + fsec_t fsec; + int julian; + + if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + + /* + * Add days by converting to and from Julian. We need an overflow + * check here since j2date expects a non-negative integer input. + */ + julian = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday); + if (pg_add_s32_overflow(julian, span->day, &julian) || + julian < 0) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + j2date(julian, &tm->tm_year, &tm->tm_mon, &tm->tm_mday); + + if (tm2timestamp(tm, fsec, NULL, ×tamp) != 0) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + } + + if (pg_add_s64_overflow(timestamp, span->time, ×tamp)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + + if (!IS_VALID_TIMESTAMP(timestamp)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + + result = timestamp; + } + + PG_RETURN_TIMESTAMP(result); +} + +Datum +timestamp_mi_interval(PG_FUNCTION_ARGS) +{ + Timestamp timestamp = PG_GETARG_TIMESTAMP(0); + Interval *span = PG_GETARG_INTERVAL_P(1); + Interval tspan; + + tspan.month = -span->month; + tspan.day = -span->day; + tspan.time = -span->time; + + return DirectFunctionCall2(timestamp_pl_interval, + TimestampGetDatum(timestamp), + PointerGetDatum(&tspan)); +} + + +/* timestamptz_pl_interval_internal() + * Add an interval to a timestamptz, in the given (or session) timezone. + * + * Note that interval has provisions for qualitative year/month and day + * units, so try to do the right thing with them. + * To add a month, increment the month, and use the same day of month. + * Then, if the next month has fewer days, set the day of month + * to the last day of month. + * To add a day, increment the mday, and use the same time of day. + * Lastly, add in the "quantitative time". + */ +static TimestampTz +timestamptz_pl_interval_internal(TimestampTz timestamp, + Interval *span, + pg_tz *attimezone) +{ + TimestampTz result; + int tz; + + if (TIMESTAMP_NOT_FINITE(timestamp)) + result = timestamp; + else + { + /* Use session timezone if caller asks for default */ + if (attimezone == NULL) + attimezone = session_timezone; + + if (span->month != 0) + { + struct pg_tm tt, + *tm = &tt; + fsec_t fsec; + + if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, attimezone) != 0) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + + if (pg_add_s32_overflow(tm->tm_mon, span->month, &tm->tm_mon)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + if (tm->tm_mon > MONTHS_PER_YEAR) + { + tm->tm_year += (tm->tm_mon - 1) / MONTHS_PER_YEAR; + tm->tm_mon = ((tm->tm_mon - 1) % MONTHS_PER_YEAR) + 1; + } + else if (tm->tm_mon < 1) + { + tm->tm_year += tm->tm_mon / MONTHS_PER_YEAR - 1; + tm->tm_mon = tm->tm_mon % MONTHS_PER_YEAR + MONTHS_PER_YEAR; + } + + /* adjust for end of month boundary problems... */ + if (tm->tm_mday > day_tab[isleap(tm->tm_year)][tm->tm_mon - 1]) + tm->tm_mday = (day_tab[isleap(tm->tm_year)][tm->tm_mon - 1]); + + tz = DetermineTimeZoneOffset(tm, attimezone); + + if (tm2timestamp(tm, fsec, &tz, ×tamp) != 0) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + } + + if (span->day != 0) + { + struct pg_tm tt, + *tm = &tt; + fsec_t fsec; + int julian; + + if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, attimezone) != 0) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + + /* + * Add days by converting to and from Julian. We need an overflow + * check here since j2date expects a non-negative integer input. + * In practice though, it will give correct answers for small + * negative Julian dates; we should allow -1 to avoid + * timezone-dependent failures, as discussed in timestamp.h. + */ + julian = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday); + if (pg_add_s32_overflow(julian, span->day, &julian) || + julian < -1) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + j2date(julian, &tm->tm_year, &tm->tm_mon, &tm->tm_mday); + + tz = DetermineTimeZoneOffset(tm, attimezone); + + if (tm2timestamp(tm, fsec, &tz, ×tamp) != 0) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + } + + if (pg_add_s64_overflow(timestamp, span->time, ×tamp)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + + if (!IS_VALID_TIMESTAMP(timestamp)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + + result = timestamp; + } + + return result; +} + +/* timestamptz_mi_interval_internal() + * As above, but subtract the interval. + */ +static TimestampTz +timestamptz_mi_interval_internal(TimestampTz timestamp, + Interval *span, + pg_tz *attimezone) +{ + Interval tspan; + + tspan.month = -span->month; + tspan.day = -span->day; + tspan.time = -span->time; + + return timestamptz_pl_interval_internal(timestamp, &tspan, attimezone); +} + +/* timestamptz_pl_interval() + * Add an interval to a timestamptz, in the session timezone. + */ +Datum +timestamptz_pl_interval(PG_FUNCTION_ARGS) +{ + TimestampTz timestamp = PG_GETARG_TIMESTAMPTZ(0); + Interval *span = PG_GETARG_INTERVAL_P(1); + + PG_RETURN_TIMESTAMP(timestamptz_pl_interval_internal(timestamp, span, NULL)); +} + +Datum +timestamptz_mi_interval(PG_FUNCTION_ARGS) +{ + TimestampTz timestamp = PG_GETARG_TIMESTAMPTZ(0); + Interval *span = PG_GETARG_INTERVAL_P(1); + + PG_RETURN_TIMESTAMP(timestamptz_mi_interval_internal(timestamp, span, NULL)); +} + +/* timestamptz_pl_interval_at_zone() + * Add an interval to a timestamptz, in the specified timezone. + */ +Datum +timestamptz_pl_interval_at_zone(PG_FUNCTION_ARGS) +{ + TimestampTz timestamp = PG_GETARG_TIMESTAMPTZ(0); + Interval *span = PG_GETARG_INTERVAL_P(1); + text *zone = PG_GETARG_TEXT_PP(2); + pg_tz *attimezone = lookup_timezone(zone); + + PG_RETURN_TIMESTAMP(timestamptz_pl_interval_internal(timestamp, span, attimezone)); +} + +Datum +timestamptz_mi_interval_at_zone(PG_FUNCTION_ARGS) +{ + TimestampTz timestamp = PG_GETARG_TIMESTAMPTZ(0); + Interval *span = PG_GETARG_INTERVAL_P(1); + text *zone = PG_GETARG_TEXT_PP(2); + pg_tz *attimezone = lookup_timezone(zone); + + PG_RETURN_TIMESTAMP(timestamptz_mi_interval_internal(timestamp, span, attimezone)); +} + +Datum +interval_um(PG_FUNCTION_ARGS) +{ + Interval *interval = PG_GETARG_INTERVAL_P(0); + Interval *result; + + result = (Interval *) palloc(sizeof(Interval)); + + result->time = -interval->time; + /* overflow check copied from int4um */ + if (interval->time != 0 && SAMESIGN(result->time, interval->time)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("interval out of range"))); + result->day = -interval->day; + if (interval->day != 0 && SAMESIGN(result->day, interval->day)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("interval out of range"))); + result->month = -interval->month; + if (interval->month != 0 && SAMESIGN(result->month, interval->month)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("interval out of range"))); + + PG_RETURN_INTERVAL_P(result); +} + + +Datum +interval_smaller(PG_FUNCTION_ARGS) +{ + Interval *interval1 = PG_GETARG_INTERVAL_P(0); + Interval *interval2 = PG_GETARG_INTERVAL_P(1); + Interval *result; + + /* use interval_cmp_internal to be sure this agrees with comparisons */ + if (interval_cmp_internal(interval1, interval2) < 0) + result = interval1; + else + result = interval2; + PG_RETURN_INTERVAL_P(result); +} + +Datum +interval_larger(PG_FUNCTION_ARGS) +{ + Interval *interval1 = PG_GETARG_INTERVAL_P(0); + Interval *interval2 = PG_GETARG_INTERVAL_P(1); + Interval *result; + + if (interval_cmp_internal(interval1, interval2) > 0) + result = interval1; + else + result = interval2; + PG_RETURN_INTERVAL_P(result); +} + +Datum +interval_pl(PG_FUNCTION_ARGS) +{ + Interval *span1 = PG_GETARG_INTERVAL_P(0); + Interval *span2 = PG_GETARG_INTERVAL_P(1); + Interval *result; + + result = (Interval *) palloc(sizeof(Interval)); + + result->month = span1->month + span2->month; + /* overflow check copied from int4pl */ + if (SAMESIGN(span1->month, span2->month) && + !SAMESIGN(result->month, span1->month)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("interval out of range"))); + + result->day = span1->day + span2->day; + if (SAMESIGN(span1->day, span2->day) && + !SAMESIGN(result->day, span1->day)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("interval out of range"))); + + result->time = span1->time + span2->time; + if (SAMESIGN(span1->time, span2->time) && + !SAMESIGN(result->time, span1->time)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("interval out of range"))); + + PG_RETURN_INTERVAL_P(result); +} + +Datum +interval_mi(PG_FUNCTION_ARGS) +{ + Interval *span1 = PG_GETARG_INTERVAL_P(0); + Interval *span2 = PG_GETARG_INTERVAL_P(1); + Interval *result; + + result = (Interval *) palloc(sizeof(Interval)); + + result->month = span1->month - span2->month; + /* overflow check copied from int4mi */ + if (!SAMESIGN(span1->month, span2->month) && + !SAMESIGN(result->month, span1->month)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("interval out of range"))); + + result->day = span1->day - span2->day; + if (!SAMESIGN(span1->day, span2->day) && + !SAMESIGN(result->day, span1->day)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("interval out of range"))); + + result->time = span1->time - span2->time; + if (!SAMESIGN(span1->time, span2->time) && + !SAMESIGN(result->time, span1->time)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("interval out of range"))); + + PG_RETURN_INTERVAL_P(result); +} + +/* + * There is no interval_abs(): it is unclear what value to return: + * http://archives.postgresql.org/pgsql-general/2009-10/msg01031.php + * http://archives.postgresql.org/pgsql-general/2009-11/msg00041.php + */ + +Datum +interval_mul(PG_FUNCTION_ARGS) +{ + Interval *span = PG_GETARG_INTERVAL_P(0); + float8 factor = PG_GETARG_FLOAT8(1); + double month_remainder_days, + sec_remainder, + result_double; + int32 orig_month = span->month, + orig_day = span->day; + Interval *result; + + result = (Interval *) palloc(sizeof(Interval)); + + result_double = span->month * factor; + if (isnan(result_double) || !FLOAT8_FITS_IN_INT32(result_double)) + goto out_of_range; + result->month = (int32) result_double; + + result_double = span->day * factor; + if (isnan(result_double) || !FLOAT8_FITS_IN_INT32(result_double)) + goto out_of_range; + result->day = (int32) result_double; + + /* + * The above correctly handles the whole-number part of the month and day + * products, but we have to do something with any fractional part + * resulting when the factor is non-integral. We cascade the fractions + * down to lower units using the conversion factors DAYS_PER_MONTH and + * SECS_PER_DAY. Note we do NOT cascade up, since we are not forced to do + * so by the representation. The user can choose to cascade up later, + * using justify_hours and/or justify_days. + */ + + /* + * Fractional months full days into days. + * + * Floating point calculation are inherently imprecise, so these + * calculations are crafted to produce the most reliable result possible. + * TSROUND() is needed to more accurately produce whole numbers where + * appropriate. + */ + month_remainder_days = (orig_month * factor - result->month) * DAYS_PER_MONTH; + month_remainder_days = TSROUND(month_remainder_days); + sec_remainder = (orig_day * factor - result->day + + month_remainder_days - (int) month_remainder_days) * SECS_PER_DAY; + sec_remainder = TSROUND(sec_remainder); + + /* + * Might have 24:00:00 hours due to rounding, or >24 hours because of time + * cascade from months and days. It might still be >24 if the combination + * of cascade and the seconds factor operation itself. + */ + if (fabs(sec_remainder) >= SECS_PER_DAY) + { + if (pg_add_s32_overflow(result->day, + (int) (sec_remainder / SECS_PER_DAY), + &result->day)) + goto out_of_range; + sec_remainder -= (int) (sec_remainder / SECS_PER_DAY) * SECS_PER_DAY; + } + + /* cascade units down */ + if (pg_add_s32_overflow(result->day, (int32) month_remainder_days, + &result->day)) + goto out_of_range; + result_double = rint(span->time * factor + sec_remainder * USECS_PER_SEC); + if (isnan(result_double) || !FLOAT8_FITS_IN_INT64(result_double)) + goto out_of_range; + result->time = (int64) result_double; + + PG_RETURN_INTERVAL_P(result); + +out_of_range: + ereport(ERROR, + errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("interval out of range")); + + PG_RETURN_NULL(); /* keep compiler quiet */ +} + +Datum +mul_d_interval(PG_FUNCTION_ARGS) +{ + /* Args are float8 and Interval *, but leave them as generic Datum */ + Datum factor = PG_GETARG_DATUM(0); + Datum span = PG_GETARG_DATUM(1); + + return DirectFunctionCall2(interval_mul, span, factor); +} + +Datum +interval_div(PG_FUNCTION_ARGS) +{ + Interval *span = PG_GETARG_INTERVAL_P(0); + float8 factor = PG_GETARG_FLOAT8(1); + double month_remainder_days, + sec_remainder, + result_double; + int32 orig_month = span->month, + orig_day = span->day; + Interval *result; + + result = (Interval *) palloc(sizeof(Interval)); + + if (factor == 0.0) + ereport(ERROR, + (errcode(ERRCODE_DIVISION_BY_ZERO), + errmsg("division by zero"))); + + result_double = span->month / factor; + if (isnan(result_double) || !FLOAT8_FITS_IN_INT32(result_double)) + goto out_of_range; + result->month = (int32) result_double; + + result_double = span->day / factor; + if (isnan(result_double) || !FLOAT8_FITS_IN_INT32(result_double)) + goto out_of_range; + result->day = (int32) result_double; + + /* + * Fractional months full days into days. See comment in interval_mul(). + */ + month_remainder_days = (orig_month / factor - result->month) * DAYS_PER_MONTH; + month_remainder_days = TSROUND(month_remainder_days); + sec_remainder = (orig_day / factor - result->day + + month_remainder_days - (int) month_remainder_days) * SECS_PER_DAY; + sec_remainder = TSROUND(sec_remainder); + if (fabs(sec_remainder) >= SECS_PER_DAY) + { + if (pg_add_s32_overflow(result->day, + (int) (sec_remainder / SECS_PER_DAY), + &result->day)) + goto out_of_range; + sec_remainder -= (int) (sec_remainder / SECS_PER_DAY) * SECS_PER_DAY; + } + + /* cascade units down */ + if (pg_add_s32_overflow(result->day, (int32) month_remainder_days, + &result->day)) + goto out_of_range; + result_double = rint(span->time / factor + sec_remainder * USECS_PER_SEC); + if (isnan(result_double) || !FLOAT8_FITS_IN_INT64(result_double)) + goto out_of_range; + result->time = (int64) result_double; + + PG_RETURN_INTERVAL_P(result); + +out_of_range: + ereport(ERROR, + errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("interval out of range")); + + PG_RETURN_NULL(); /* keep compiler quiet */ +} + + +/* + * in_range support functions for timestamps and intervals. + * + * Per SQL spec, we support these with interval as the offset type. + * The spec's restriction that the offset not be negative is a bit hard to + * decipher for intervals, but we choose to interpret it the same as our + * interval comparison operators would. + */ + +Datum +in_range_timestamptz_interval(PG_FUNCTION_ARGS) +{ + TimestampTz val = PG_GETARG_TIMESTAMPTZ(0); + TimestampTz base = PG_GETARG_TIMESTAMPTZ(1); + Interval *offset = PG_GETARG_INTERVAL_P(2); + bool sub = PG_GETARG_BOOL(3); + bool less = PG_GETARG_BOOL(4); + TimestampTz sum; + + if (int128_compare(interval_cmp_value(offset), int64_to_int128(0)) < 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE), + errmsg("invalid preceding or following size in window function"))); + + /* We don't currently bother to avoid overflow hazards here */ + if (sub) + sum = timestamptz_mi_interval_internal(base, offset, NULL); + else + sum = timestamptz_pl_interval_internal(base, offset, NULL); + + if (less) + PG_RETURN_BOOL(val <= sum); + else + PG_RETURN_BOOL(val >= sum); +} + +Datum +in_range_timestamp_interval(PG_FUNCTION_ARGS) +{ + Timestamp val = PG_GETARG_TIMESTAMP(0); + Timestamp base = PG_GETARG_TIMESTAMP(1); + Interval *offset = PG_GETARG_INTERVAL_P(2); + bool sub = PG_GETARG_BOOL(3); + bool less = PG_GETARG_BOOL(4); + Timestamp sum; + + if (int128_compare(interval_cmp_value(offset), int64_to_int128(0)) < 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE), + errmsg("invalid preceding or following size in window function"))); + + /* We don't currently bother to avoid overflow hazards here */ + if (sub) + sum = DatumGetTimestamp(DirectFunctionCall2(timestamp_mi_interval, + TimestampGetDatum(base), + IntervalPGetDatum(offset))); + else + sum = DatumGetTimestamp(DirectFunctionCall2(timestamp_pl_interval, + TimestampGetDatum(base), + IntervalPGetDatum(offset))); + + if (less) + PG_RETURN_BOOL(val <= sum); + else + PG_RETURN_BOOL(val >= sum); +} + +Datum +in_range_interval_interval(PG_FUNCTION_ARGS) +{ + Interval *val = PG_GETARG_INTERVAL_P(0); + Interval *base = PG_GETARG_INTERVAL_P(1); + Interval *offset = PG_GETARG_INTERVAL_P(2); + bool sub = PG_GETARG_BOOL(3); + bool less = PG_GETARG_BOOL(4); + Interval *sum; + + if (int128_compare(interval_cmp_value(offset), int64_to_int128(0)) < 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE), + errmsg("invalid preceding or following size in window function"))); + + /* We don't currently bother to avoid overflow hazards here */ + if (sub) + sum = DatumGetIntervalP(DirectFunctionCall2(interval_mi, + IntervalPGetDatum(base), + IntervalPGetDatum(offset))); + else + sum = DatumGetIntervalP(DirectFunctionCall2(interval_pl, + IntervalPGetDatum(base), + IntervalPGetDatum(offset))); + + if (less) + PG_RETURN_BOOL(interval_cmp_internal(val, sum) <= 0); + else + PG_RETURN_BOOL(interval_cmp_internal(val, sum) >= 0); +} + + +/* + * interval_accum, interval_accum_inv, and interval_avg implement the + * AVG(interval) aggregate. + * + * The transition datatype for this aggregate is a 2-element array of + * intervals, where the first is the running sum and the second contains + * the number of values so far in its 'time' field. This is a bit ugly + * but it beats inventing a specialized datatype for the purpose. + */ + +Datum +interval_accum(PG_FUNCTION_ARGS) +{ + ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0); + Interval *newval = PG_GETARG_INTERVAL_P(1); + Datum *transdatums; + int ndatums; + Interval sumX, + N; + Interval *newsum; + ArrayType *result; + + deconstruct_array(transarray, + INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE, + &transdatums, NULL, &ndatums); + if (ndatums != 2) + elog(ERROR, "expected 2-element interval array"); + + sumX = *(DatumGetIntervalP(transdatums[0])); + N = *(DatumGetIntervalP(transdatums[1])); + + newsum = DatumGetIntervalP(DirectFunctionCall2(interval_pl, + IntervalPGetDatum(&sumX), + IntervalPGetDatum(newval))); + N.time += 1; + + transdatums[0] = IntervalPGetDatum(newsum); + transdatums[1] = IntervalPGetDatum(&N); + + result = construct_array(transdatums, 2, + INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE); + + PG_RETURN_ARRAYTYPE_P(result); +} + +Datum +interval_combine(PG_FUNCTION_ARGS) +{ + ArrayType *transarray1 = PG_GETARG_ARRAYTYPE_P(0); + ArrayType *transarray2 = PG_GETARG_ARRAYTYPE_P(1); + Datum *transdatums1; + Datum *transdatums2; + int ndatums1; + int ndatums2; + Interval sum1, + N1; + Interval sum2, + N2; + + Interval *newsum; + ArrayType *result; + + deconstruct_array(transarray1, + INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE, + &transdatums1, NULL, &ndatums1); + if (ndatums1 != 2) + elog(ERROR, "expected 2-element interval array"); + + sum1 = *(DatumGetIntervalP(transdatums1[0])); + N1 = *(DatumGetIntervalP(transdatums1[1])); + + deconstruct_array(transarray2, + INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE, + &transdatums2, NULL, &ndatums2); + if (ndatums2 != 2) + elog(ERROR, "expected 2-element interval array"); + + sum2 = *(DatumGetIntervalP(transdatums2[0])); + N2 = *(DatumGetIntervalP(transdatums2[1])); + + newsum = DatumGetIntervalP(DirectFunctionCall2(interval_pl, + IntervalPGetDatum(&sum1), + IntervalPGetDatum(&sum2))); + N1.time += N2.time; + + transdatums1[0] = IntervalPGetDatum(newsum); + transdatums1[1] = IntervalPGetDatum(&N1); + + result = construct_array(transdatums1, 2, + INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE); + + PG_RETURN_ARRAYTYPE_P(result); +} + +Datum +interval_accum_inv(PG_FUNCTION_ARGS) +{ + ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0); + Interval *newval = PG_GETARG_INTERVAL_P(1); + Datum *transdatums; + int ndatums; + Interval sumX, + N; + Interval *newsum; + ArrayType *result; + + deconstruct_array(transarray, + INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE, + &transdatums, NULL, &ndatums); + if (ndatums != 2) + elog(ERROR, "expected 2-element interval array"); + + sumX = *(DatumGetIntervalP(transdatums[0])); + N = *(DatumGetIntervalP(transdatums[1])); + + newsum = DatumGetIntervalP(DirectFunctionCall2(interval_mi, + IntervalPGetDatum(&sumX), + IntervalPGetDatum(newval))); + N.time -= 1; + + transdatums[0] = IntervalPGetDatum(newsum); + transdatums[1] = IntervalPGetDatum(&N); + + result = construct_array(transdatums, 2, + INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE); + + PG_RETURN_ARRAYTYPE_P(result); +} + +Datum +interval_avg(PG_FUNCTION_ARGS) +{ + ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0); + Datum *transdatums; + int ndatums; + Interval sumX, + N; + + deconstruct_array(transarray, + INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE, + &transdatums, NULL, &ndatums); + if (ndatums != 2) + elog(ERROR, "expected 2-element interval array"); + + sumX = *(DatumGetIntervalP(transdatums[0])); + N = *(DatumGetIntervalP(transdatums[1])); + + /* SQL defines AVG of no values to be NULL */ + if (N.time == 0) + PG_RETURN_NULL(); + + return DirectFunctionCall2(interval_div, + IntervalPGetDatum(&sumX), + Float8GetDatum((double) N.time)); +} + + +/* timestamp_age() + * Calculate time difference while retaining year/month fields. + * Note that this does not result in an accurate absolute time span + * since year and month are out of context once the arithmetic + * is done. + */ +Datum +timestamp_age(PG_FUNCTION_ARGS) +{ + Timestamp dt1 = PG_GETARG_TIMESTAMP(0); + Timestamp dt2 = PG_GETARG_TIMESTAMP(1); + Interval *result; + fsec_t fsec1, + fsec2; + struct pg_itm tt, + *tm = &tt; + struct pg_tm tt1, + *tm1 = &tt1; + struct pg_tm tt2, + *tm2 = &tt2; + + result = (Interval *) palloc(sizeof(Interval)); + + if (timestamp2tm(dt1, NULL, tm1, &fsec1, NULL, NULL) == 0 && + timestamp2tm(dt2, NULL, tm2, &fsec2, NULL, NULL) == 0) + { + /* form the symbolic difference */ + tm->tm_usec = fsec1 - fsec2; + tm->tm_sec = tm1->tm_sec - tm2->tm_sec; + tm->tm_min = tm1->tm_min - tm2->tm_min; + tm->tm_hour = tm1->tm_hour - tm2->tm_hour; + tm->tm_mday = tm1->tm_mday - tm2->tm_mday; + tm->tm_mon = tm1->tm_mon - tm2->tm_mon; + tm->tm_year = tm1->tm_year - tm2->tm_year; + + /* flip sign if necessary... */ + if (dt1 < dt2) + { + tm->tm_usec = -tm->tm_usec; + tm->tm_sec = -tm->tm_sec; + tm->tm_min = -tm->tm_min; + tm->tm_hour = -tm->tm_hour; + tm->tm_mday = -tm->tm_mday; + tm->tm_mon = -tm->tm_mon; + tm->tm_year = -tm->tm_year; + } + + /* propagate any negative fields into the next higher field */ + while (tm->tm_usec < 0) + { + tm->tm_usec += USECS_PER_SEC; + tm->tm_sec--; + } + + while (tm->tm_sec < 0) + { + tm->tm_sec += SECS_PER_MINUTE; + tm->tm_min--; + } + + while (tm->tm_min < 0) + { + tm->tm_min += MINS_PER_HOUR; + tm->tm_hour--; + } + + while (tm->tm_hour < 0) + { + tm->tm_hour += HOURS_PER_DAY; + tm->tm_mday--; + } + + while (tm->tm_mday < 0) + { + if (dt1 < dt2) + { + tm->tm_mday += day_tab[isleap(tm1->tm_year)][tm1->tm_mon - 1]; + tm->tm_mon--; + } + else + { + tm->tm_mday += day_tab[isleap(tm2->tm_year)][tm2->tm_mon - 1]; + tm->tm_mon--; + } + } + + while (tm->tm_mon < 0) + { + tm->tm_mon += MONTHS_PER_YEAR; + tm->tm_year--; + } + + /* recover sign if necessary... */ + if (dt1 < dt2) + { + tm->tm_usec = -tm->tm_usec; + tm->tm_sec = -tm->tm_sec; + tm->tm_min = -tm->tm_min; + tm->tm_hour = -tm->tm_hour; + tm->tm_mday = -tm->tm_mday; + tm->tm_mon = -tm->tm_mon; + tm->tm_year = -tm->tm_year; + } + + if (itm2interval(tm, result) != 0) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("interval out of range"))); + } + else + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + + PG_RETURN_INTERVAL_P(result); +} + + +/* timestamptz_age() + * Calculate time difference while retaining year/month fields. + * Note that this does not result in an accurate absolute time span + * since year and month are out of context once the arithmetic + * is done. + */ +Datum +timestamptz_age(PG_FUNCTION_ARGS) +{ + TimestampTz dt1 = PG_GETARG_TIMESTAMPTZ(0); + TimestampTz dt2 = PG_GETARG_TIMESTAMPTZ(1); + Interval *result; + fsec_t fsec1, + fsec2; + struct pg_itm tt, + *tm = &tt; + struct pg_tm tt1, + *tm1 = &tt1; + struct pg_tm tt2, + *tm2 = &tt2; + int tz1; + int tz2; + + result = (Interval *) palloc(sizeof(Interval)); + + if (timestamp2tm(dt1, &tz1, tm1, &fsec1, NULL, NULL) == 0 && + timestamp2tm(dt2, &tz2, tm2, &fsec2, NULL, NULL) == 0) + { + /* form the symbolic difference */ + tm->tm_usec = fsec1 - fsec2; + tm->tm_sec = tm1->tm_sec - tm2->tm_sec; + tm->tm_min = tm1->tm_min - tm2->tm_min; + tm->tm_hour = tm1->tm_hour - tm2->tm_hour; + tm->tm_mday = tm1->tm_mday - tm2->tm_mday; + tm->tm_mon = tm1->tm_mon - tm2->tm_mon; + tm->tm_year = tm1->tm_year - tm2->tm_year; + + /* flip sign if necessary... */ + if (dt1 < dt2) + { + tm->tm_usec = -tm->tm_usec; + tm->tm_sec = -tm->tm_sec; + tm->tm_min = -tm->tm_min; + tm->tm_hour = -tm->tm_hour; + tm->tm_mday = -tm->tm_mday; + tm->tm_mon = -tm->tm_mon; + tm->tm_year = -tm->tm_year; + } + + /* propagate any negative fields into the next higher field */ + while (tm->tm_usec < 0) + { + tm->tm_usec += USECS_PER_SEC; + tm->tm_sec--; + } + + while (tm->tm_sec < 0) + { + tm->tm_sec += SECS_PER_MINUTE; + tm->tm_min--; + } + + while (tm->tm_min < 0) + { + tm->tm_min += MINS_PER_HOUR; + tm->tm_hour--; + } + + while (tm->tm_hour < 0) + { + tm->tm_hour += HOURS_PER_DAY; + tm->tm_mday--; + } + + while (tm->tm_mday < 0) + { + if (dt1 < dt2) + { + tm->tm_mday += day_tab[isleap(tm1->tm_year)][tm1->tm_mon - 1]; + tm->tm_mon--; + } + else + { + tm->tm_mday += day_tab[isleap(tm2->tm_year)][tm2->tm_mon - 1]; + tm->tm_mon--; + } + } + + while (tm->tm_mon < 0) + { + tm->tm_mon += MONTHS_PER_YEAR; + tm->tm_year--; + } + + /* + * Note: we deliberately ignore any difference between tz1 and tz2. + */ + + /* recover sign if necessary... */ + if (dt1 < dt2) + { + tm->tm_usec = -tm->tm_usec; + tm->tm_sec = -tm->tm_sec; + tm->tm_min = -tm->tm_min; + tm->tm_hour = -tm->tm_hour; + tm->tm_mday = -tm->tm_mday; + tm->tm_mon = -tm->tm_mon; + tm->tm_year = -tm->tm_year; + } + + if (itm2interval(tm, result) != 0) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("interval out of range"))); + } + else + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + + PG_RETURN_INTERVAL_P(result); +} + + +/*---------------------------------------------------------- + * Conversion operators. + *---------------------------------------------------------*/ + + +/* timestamp_bin() + * Bin timestamp into specified interval. + */ +Datum +timestamp_bin(PG_FUNCTION_ARGS) +{ + Interval *stride = PG_GETARG_INTERVAL_P(0); + Timestamp timestamp = PG_GETARG_TIMESTAMP(1); + Timestamp origin = PG_GETARG_TIMESTAMP(2); + Timestamp result, + stride_usecs, + tm_diff, + tm_modulo, + tm_delta; + + if (TIMESTAMP_NOT_FINITE(timestamp)) + PG_RETURN_TIMESTAMP(timestamp); + + if (TIMESTAMP_NOT_FINITE(origin)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("origin out of range"))); + + if (stride->month != 0) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("timestamps cannot be binned into intervals containing months or years"))); + + if (unlikely(pg_mul_s64_overflow(stride->day, USECS_PER_DAY, &stride_usecs)) || + unlikely(pg_add_s64_overflow(stride_usecs, stride->time, &stride_usecs))) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("interval out of range"))); + + if (stride_usecs <= 0) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("stride must be greater than zero"))); + + if (unlikely(pg_sub_s64_overflow(timestamp, origin, &tm_diff))) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("interval out of range"))); + + /* These calculations cannot overflow */ + tm_modulo = tm_diff % stride_usecs; + tm_delta = tm_diff - tm_modulo; + result = origin + tm_delta; + + /* + * We want to round towards -infinity, not 0, when tm_diff is negative and + * not a multiple of stride_usecs. This adjustment *can* cause overflow, + * since the result might now be out of the range origin .. timestamp. + */ + if (tm_modulo < 0) + { + if (unlikely(pg_sub_s64_overflow(result, stride_usecs, &result)) || + !IS_VALID_TIMESTAMP(result)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + } + + PG_RETURN_TIMESTAMP(result); +} + +/* timestamp_trunc() + * Truncate timestamp to specified units. + */ +Datum +timestamp_trunc(PG_FUNCTION_ARGS) +{ + text *units = PG_GETARG_TEXT_PP(0); + Timestamp timestamp = PG_GETARG_TIMESTAMP(1); + Timestamp result; + int type, + val; + char *lowunits; + fsec_t fsec; + struct pg_tm tt, + *tm = &tt; + + if (TIMESTAMP_NOT_FINITE(timestamp)) + PG_RETURN_TIMESTAMP(timestamp); + + lowunits = downcase_truncate_identifier(VARDATA_ANY(units), + VARSIZE_ANY_EXHDR(units), + false); + + type = DecodeUnits(0, lowunits, &val); + + if (type == UNITS) + { + if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + + switch (val) + { + case DTK_WEEK: + { + int woy; + + woy = date2isoweek(tm->tm_year, tm->tm_mon, tm->tm_mday); + + /* + * If it is week 52/53 and the month is January, then the + * week must belong to the previous year. Also, some + * December dates belong to the next year. + */ + if (woy >= 52 && tm->tm_mon == 1) + --tm->tm_year; + if (woy <= 1 && tm->tm_mon == MONTHS_PER_YEAR) + ++tm->tm_year; + isoweek2date(woy, &(tm->tm_year), &(tm->tm_mon), &(tm->tm_mday)); + tm->tm_hour = 0; + tm->tm_min = 0; + tm->tm_sec = 0; + fsec = 0; + break; + } + case DTK_MILLENNIUM: + /* see comments in timestamptz_trunc */ + if (tm->tm_year > 0) + tm->tm_year = ((tm->tm_year + 999) / 1000) * 1000 - 999; + else + tm->tm_year = -((999 - (tm->tm_year - 1)) / 1000) * 1000 + 1; + /* FALL THRU */ + case DTK_CENTURY: + /* see comments in timestamptz_trunc */ + if (tm->tm_year > 0) + tm->tm_year = ((tm->tm_year + 99) / 100) * 100 - 99; + else + tm->tm_year = -((99 - (tm->tm_year - 1)) / 100) * 100 + 1; + /* FALL THRU */ + case DTK_DECADE: + /* see comments in timestamptz_trunc */ + if (val != DTK_MILLENNIUM && val != DTK_CENTURY) + { + if (tm->tm_year > 0) + tm->tm_year = (tm->tm_year / 10) * 10; + else + tm->tm_year = -((8 - (tm->tm_year - 1)) / 10) * 10; + } + /* FALL THRU */ + case DTK_YEAR: + tm->tm_mon = 1; + /* FALL THRU */ + case DTK_QUARTER: + tm->tm_mon = (3 * ((tm->tm_mon - 1) / 3)) + 1; + /* FALL THRU */ + case DTK_MONTH: + tm->tm_mday = 1; + /* FALL THRU */ + case DTK_DAY: + tm->tm_hour = 0; + /* FALL THRU */ + case DTK_HOUR: + tm->tm_min = 0; + /* FALL THRU */ + case DTK_MINUTE: + tm->tm_sec = 0; + /* FALL THRU */ + case DTK_SECOND: + fsec = 0; + break; + + case DTK_MILLISEC: + fsec = (fsec / 1000) * 1000; + break; + + case DTK_MICROSEC: + break; + + default: + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("unit \"%s\" not supported for type %s", + lowunits, format_type_be(TIMESTAMPOID)))); + result = 0; + } + + if (tm2timestamp(tm, fsec, NULL, &result) != 0) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unit \"%s\" not recognized for type %s", + lowunits, format_type_be(TIMESTAMPOID)))); + result = 0; + } + + PG_RETURN_TIMESTAMP(result); +} + +/* timestamptz_bin() + * Bin timestamptz into specified interval using specified origin. + */ +Datum +timestamptz_bin(PG_FUNCTION_ARGS) +{ + Interval *stride = PG_GETARG_INTERVAL_P(0); + TimestampTz timestamp = PG_GETARG_TIMESTAMPTZ(1); + TimestampTz origin = PG_GETARG_TIMESTAMPTZ(2); + TimestampTz result, + stride_usecs, + tm_diff, + tm_modulo, + tm_delta; + + if (TIMESTAMP_NOT_FINITE(timestamp)) + PG_RETURN_TIMESTAMPTZ(timestamp); + + if (TIMESTAMP_NOT_FINITE(origin)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("origin out of range"))); + + if (stride->month != 0) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("timestamps cannot be binned into intervals containing months or years"))); + + if (unlikely(pg_mul_s64_overflow(stride->day, USECS_PER_DAY, &stride_usecs)) || + unlikely(pg_add_s64_overflow(stride_usecs, stride->time, &stride_usecs))) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("interval out of range"))); + + if (stride_usecs <= 0) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("stride must be greater than zero"))); + + if (unlikely(pg_sub_s64_overflow(timestamp, origin, &tm_diff))) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("interval out of range"))); + + /* These calculations cannot overflow */ + tm_modulo = tm_diff % stride_usecs; + tm_delta = tm_diff - tm_modulo; + result = origin + tm_delta; + + /* + * We want to round towards -infinity, not 0, when tm_diff is negative and + * not a multiple of stride_usecs. This adjustment *can* cause overflow, + * since the result might now be out of the range origin .. timestamp. + */ + if (tm_modulo < 0) + { + if (unlikely(pg_sub_s64_overflow(result, stride_usecs, &result)) || + !IS_VALID_TIMESTAMP(result)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + } + + PG_RETURN_TIMESTAMPTZ(result); +} + +/* + * Common code for timestamptz_trunc() and timestamptz_trunc_zone(). + * + * tzp identifies the zone to truncate with respect to. We assume + * infinite timestamps have already been rejected. + */ +static TimestampTz +timestamptz_trunc_internal(text *units, TimestampTz timestamp, pg_tz *tzp) +{ + TimestampTz result; + int tz; + int type, + val; + bool redotz = false; + char *lowunits; + fsec_t fsec; + struct pg_tm tt, + *tm = &tt; + + lowunits = downcase_truncate_identifier(VARDATA_ANY(units), + VARSIZE_ANY_EXHDR(units), + false); + + type = DecodeUnits(0, lowunits, &val); + + if (type == UNITS) + { + if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, tzp) != 0) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + + switch (val) + { + case DTK_WEEK: + { + int woy; + + woy = date2isoweek(tm->tm_year, tm->tm_mon, tm->tm_mday); + + /* + * If it is week 52/53 and the month is January, then the + * week must belong to the previous year. Also, some + * December dates belong to the next year. + */ + if (woy >= 52 && tm->tm_mon == 1) + --tm->tm_year; + if (woy <= 1 && tm->tm_mon == MONTHS_PER_YEAR) + ++tm->tm_year; + isoweek2date(woy, &(tm->tm_year), &(tm->tm_mon), &(tm->tm_mday)); + tm->tm_hour = 0; + tm->tm_min = 0; + tm->tm_sec = 0; + fsec = 0; + redotz = true; + break; + } + /* one may consider DTK_THOUSAND and DTK_HUNDRED... */ + case DTK_MILLENNIUM: + + /* + * truncating to the millennium? what is this supposed to + * mean? let us put the first year of the millennium... i.e. + * -1000, 1, 1001, 2001... + */ + if (tm->tm_year > 0) + tm->tm_year = ((tm->tm_year + 999) / 1000) * 1000 - 999; + else + tm->tm_year = -((999 - (tm->tm_year - 1)) / 1000) * 1000 + 1; + /* FALL THRU */ + case DTK_CENTURY: + /* truncating to the century? as above: -100, 1, 101... */ + if (tm->tm_year > 0) + tm->tm_year = ((tm->tm_year + 99) / 100) * 100 - 99; + else + tm->tm_year = -((99 - (tm->tm_year - 1)) / 100) * 100 + 1; + /* FALL THRU */ + case DTK_DECADE: + + /* + * truncating to the decade? first year of the decade. must + * not be applied if year was truncated before! + */ + if (val != DTK_MILLENNIUM && val != DTK_CENTURY) + { + if (tm->tm_year > 0) + tm->tm_year = (tm->tm_year / 10) * 10; + else + tm->tm_year = -((8 - (tm->tm_year - 1)) / 10) * 10; + } + /* FALL THRU */ + case DTK_YEAR: + tm->tm_mon = 1; + /* FALL THRU */ + case DTK_QUARTER: + tm->tm_mon = (3 * ((tm->tm_mon - 1) / 3)) + 1; + /* FALL THRU */ + case DTK_MONTH: + tm->tm_mday = 1; + /* FALL THRU */ + case DTK_DAY: + tm->tm_hour = 0; + redotz = true; /* for all cases >= DAY */ + /* FALL THRU */ + case DTK_HOUR: + tm->tm_min = 0; + /* FALL THRU */ + case DTK_MINUTE: + tm->tm_sec = 0; + /* FALL THRU */ + case DTK_SECOND: + fsec = 0; + break; + case DTK_MILLISEC: + fsec = (fsec / 1000) * 1000; + break; + case DTK_MICROSEC: + break; + + default: + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("unit \"%s\" not supported for type %s", + lowunits, format_type_be(TIMESTAMPTZOID)))); + result = 0; + } + + if (redotz) + tz = DetermineTimeZoneOffset(tm, tzp); + + if (tm2timestamp(tm, fsec, &tz, &result) != 0) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unit \"%s\" not recognized for type %s", + lowunits, format_type_be(TIMESTAMPTZOID)))); + result = 0; + } + + return result; +} + +/* timestamptz_trunc() + * Truncate timestamptz to specified units in session timezone. + */ +Datum +timestamptz_trunc(PG_FUNCTION_ARGS) +{ + text *units = PG_GETARG_TEXT_PP(0); + TimestampTz timestamp = PG_GETARG_TIMESTAMPTZ(1); + TimestampTz result; + + if (TIMESTAMP_NOT_FINITE(timestamp)) + PG_RETURN_TIMESTAMPTZ(timestamp); + + result = timestamptz_trunc_internal(units, timestamp, session_timezone); + + PG_RETURN_TIMESTAMPTZ(result); +} + +/* timestamptz_trunc_zone() + * Truncate timestamptz to specified units in specified timezone. + */ +Datum +timestamptz_trunc_zone(PG_FUNCTION_ARGS) +{ + text *units = PG_GETARG_TEXT_PP(0); + TimestampTz timestamp = PG_GETARG_TIMESTAMPTZ(1); + text *zone = PG_GETARG_TEXT_PP(2); + TimestampTz result; + pg_tz *tzp; + + /* + * timestamptz_zone() doesn't look up the zone for infinite inputs, so we + * don't do so here either. + */ + if (TIMESTAMP_NOT_FINITE(timestamp)) + PG_RETURN_TIMESTAMP(timestamp); + + /* + * Look up the requested timezone. + */ + tzp = lookup_timezone(zone); + + result = timestamptz_trunc_internal(units, timestamp, tzp); + + PG_RETURN_TIMESTAMPTZ(result); +} + +/* interval_trunc() + * Extract specified field from interval. + */ +Datum +interval_trunc(PG_FUNCTION_ARGS) +{ + text *units = PG_GETARG_TEXT_PP(0); + Interval *interval = PG_GETARG_INTERVAL_P(1); + Interval *result; + int type, + val; + char *lowunits; + struct pg_itm tt, + *tm = &tt; + + result = (Interval *) palloc(sizeof(Interval)); + + lowunits = downcase_truncate_identifier(VARDATA_ANY(units), + VARSIZE_ANY_EXHDR(units), + false); + + type = DecodeUnits(0, lowunits, &val); + + if (type == UNITS) + { + interval2itm(*interval, tm); + switch (val) + { + case DTK_MILLENNIUM: + /* caution: C division may have negative remainder */ + tm->tm_year = (tm->tm_year / 1000) * 1000; + /* FALL THRU */ + case DTK_CENTURY: + /* caution: C division may have negative remainder */ + tm->tm_year = (tm->tm_year / 100) * 100; + /* FALL THRU */ + case DTK_DECADE: + /* caution: C division may have negative remainder */ + tm->tm_year = (tm->tm_year / 10) * 10; + /* FALL THRU */ + case DTK_YEAR: + tm->tm_mon = 0; + /* FALL THRU */ + case DTK_QUARTER: + tm->tm_mon = 3 * (tm->tm_mon / 3); + /* FALL THRU */ + case DTK_MONTH: + tm->tm_mday = 0; + /* FALL THRU */ + case DTK_DAY: + tm->tm_hour = 0; + /* FALL THRU */ + case DTK_HOUR: + tm->tm_min = 0; + /* FALL THRU */ + case DTK_MINUTE: + tm->tm_sec = 0; + /* FALL THRU */ + case DTK_SECOND: + tm->tm_usec = 0; + break; + case DTK_MILLISEC: + tm->tm_usec = (tm->tm_usec / 1000) * 1000; + break; + case DTK_MICROSEC: + break; + + default: + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("unit \"%s\" not supported for type %s", + lowunits, format_type_be(INTERVALOID)), + (val == DTK_WEEK) ? errdetail("Months usually have fractional weeks.") : 0)); + } + + if (itm2interval(tm, result) != 0) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("interval out of range"))); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unit \"%s\" not recognized for type %s", + lowunits, format_type_be(INTERVALOID)))); + } + + PG_RETURN_INTERVAL_P(result); +} + +/* isoweek2j() + * + * Return the Julian day which corresponds to the first day (Monday) of the given ISO 8601 year and week. + * Julian days are used to convert between ISO week dates and Gregorian dates. + */ +int +isoweek2j(int year, int week) +{ + int day0, + day4; + + /* fourth day of current year */ + day4 = date2j(year, 1, 4); + + /* day0 == offset to first day of week (Monday) */ + day0 = j2day(day4 - 1); + + return ((week - 1) * 7) + (day4 - day0); +} + +/* isoweek2date() + * Convert ISO week of year number to date. + * The year field must be specified with the ISO year! + * karel 2000/08/07 + */ +void +isoweek2date(int woy, int *year, int *mon, int *mday) +{ + j2date(isoweek2j(*year, woy), year, mon, mday); +} + +/* isoweekdate2date() + * + * Convert an ISO 8601 week date (ISO year, ISO week) into a Gregorian date. + * Gregorian day of week sent so weekday strings can be supplied. + * Populates year, mon, and mday with the correct Gregorian values. + * year must be passed in as the ISO year. + */ +void +isoweekdate2date(int isoweek, int wday, int *year, int *mon, int *mday) +{ + int jday; + + jday = isoweek2j(*year, isoweek); + /* convert Gregorian week start (Sunday=1) to ISO week start (Monday=1) */ + if (wday > 1) + jday += wday - 2; + else + jday += 6; + j2date(jday, year, mon, mday); +} + +/* date2isoweek() + * + * Returns ISO week number of year. + */ +int +date2isoweek(int year, int mon, int mday) +{ + float8 result; + int day0, + day4, + dayn; + + /* current day */ + dayn = date2j(year, mon, mday); + + /* fourth day of current year */ + day4 = date2j(year, 1, 4); + + /* day0 == offset to first day of week (Monday) */ + day0 = j2day(day4 - 1); + + /* + * We need the first week containing a Thursday, otherwise this day falls + * into the previous year for purposes of counting weeks + */ + if (dayn < day4 - day0) + { + day4 = date2j(year - 1, 1, 4); + + /* day0 == offset to first day of week (Monday) */ + day0 = j2day(day4 - 1); + } + + result = (dayn - (day4 - day0)) / 7 + 1; + + /* + * Sometimes the last few days in a year will fall into the first week of + * the next year, so check for this. + */ + if (result >= 52) + { + day4 = date2j(year + 1, 1, 4); + + /* day0 == offset to first day of week (Monday) */ + day0 = j2day(day4 - 1); + + if (dayn >= day4 - day0) + result = (dayn - (day4 - day0)) / 7 + 1; + } + + return (int) result; +} + + +/* date2isoyear() + * + * Returns ISO 8601 year number. + * Note: zero or negative results follow the year-zero-exists convention. + */ +int +date2isoyear(int year, int mon, int mday) +{ + float8 result; + int day0, + day4, + dayn; + + /* current day */ + dayn = date2j(year, mon, mday); + + /* fourth day of current year */ + day4 = date2j(year, 1, 4); + + /* day0 == offset to first day of week (Monday) */ + day0 = j2day(day4 - 1); + + /* + * We need the first week containing a Thursday, otherwise this day falls + * into the previous year for purposes of counting weeks + */ + if (dayn < day4 - day0) + { + day4 = date2j(year - 1, 1, 4); + + /* day0 == offset to first day of week (Monday) */ + day0 = j2day(day4 - 1); + + year--; + } + + result = (dayn - (day4 - day0)) / 7 + 1; + + /* + * Sometimes the last few days in a year will fall into the first week of + * the next year, so check for this. + */ + if (result >= 52) + { + day4 = date2j(year + 1, 1, 4); + + /* day0 == offset to first day of week (Monday) */ + day0 = j2day(day4 - 1); + + if (dayn >= day4 - day0) + year++; + } + + return year; +} + + +/* date2isoyearday() + * + * Returns the ISO 8601 day-of-year, given a Gregorian year, month and day. + * Possible return values are 1 through 371 (364 in non-leap years). + */ +int +date2isoyearday(int year, int mon, int mday) +{ + return date2j(year, mon, mday) - isoweek2j(date2isoyear(year, mon, mday), 1) + 1; +} + +/* + * NonFiniteTimestampTzPart + * + * Used by timestamp_part and timestamptz_part when extracting from infinite + * timestamp[tz]. Returns +/-Infinity if that is the appropriate result, + * otherwise returns zero (which should be taken as meaning to return NULL). + * + * Errors thrown here for invalid units should exactly match those that + * would be thrown in the calling functions, else there will be unexpected + * discrepancies between finite- and infinite-input cases. + */ +static float8 +NonFiniteTimestampTzPart(int type, int unit, char *lowunits, + bool isNegative, bool isTz) +{ + if ((type != UNITS) && (type != RESERV)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unit \"%s\" not recognized for type %s", + lowunits, + format_type_be(isTz ? TIMESTAMPTZOID : TIMESTAMPOID)))); + + switch (unit) + { + /* Oscillating units */ + case DTK_MICROSEC: + case DTK_MILLISEC: + case DTK_SECOND: + case DTK_MINUTE: + case DTK_HOUR: + case DTK_DAY: + case DTK_MONTH: + case DTK_QUARTER: + case DTK_WEEK: + case DTK_DOW: + case DTK_ISODOW: + case DTK_DOY: + case DTK_TZ: + case DTK_TZ_MINUTE: + case DTK_TZ_HOUR: + return 0.0; + + /* Monotonically-increasing units */ + case DTK_YEAR: + case DTK_DECADE: + case DTK_CENTURY: + case DTK_MILLENNIUM: + case DTK_JULIAN: + case DTK_ISOYEAR: + case DTK_EPOCH: + if (isNegative) + return -get_float8_infinity(); + else + return get_float8_infinity(); + + default: + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("unit \"%s\" not supported for type %s", + lowunits, + format_type_be(isTz ? TIMESTAMPTZOID : TIMESTAMPOID)))); + return 0.0; /* keep compiler quiet */ + } +} + +/* timestamp_part() and extract_timestamp() + * Extract specified field from timestamp. + */ +static Datum +timestamp_part_common(PG_FUNCTION_ARGS, bool retnumeric) +{ + text *units = PG_GETARG_TEXT_PP(0); + Timestamp timestamp = PG_GETARG_TIMESTAMP(1); + int64 intresult; + Timestamp epoch; + int type, + val; + char *lowunits; + fsec_t fsec; + struct pg_tm tt, + *tm = &tt; + + lowunits = downcase_truncate_identifier(VARDATA_ANY(units), + VARSIZE_ANY_EXHDR(units), + false); + + type = DecodeUnits(0, lowunits, &val); + if (type == UNKNOWN_FIELD) + type = DecodeSpecial(0, lowunits, &val); + + if (TIMESTAMP_NOT_FINITE(timestamp)) + { + double r = NonFiniteTimestampTzPart(type, val, lowunits, + TIMESTAMP_IS_NOBEGIN(timestamp), + false); + + if (r) + { + if (retnumeric) + { + if (r < 0) + return DirectFunctionCall3(numeric_in, + CStringGetDatum("-Infinity"), + ObjectIdGetDatum(InvalidOid), + Int32GetDatum(-1)); + else if (r > 0) + return DirectFunctionCall3(numeric_in, + CStringGetDatum("Infinity"), + ObjectIdGetDatum(InvalidOid), + Int32GetDatum(-1)); + } + else + PG_RETURN_FLOAT8(r); + } + else + PG_RETURN_NULL(); + } + + if (type == UNITS) + { + if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + + switch (val) + { + case DTK_MICROSEC: + intresult = tm->tm_sec * INT64CONST(1000000) + fsec; + break; + + case DTK_MILLISEC: + if (retnumeric) + /*--- + * tm->tm_sec * 1000 + fsec / 1000 + * = (tm->tm_sec * 1'000'000 + fsec) / 1000 + */ + PG_RETURN_NUMERIC(int64_div_fast_to_numeric(tm->tm_sec * INT64CONST(1000000) + fsec, 3)); + else + PG_RETURN_FLOAT8(tm->tm_sec * 1000.0 + fsec / 1000.0); + break; + + case DTK_SECOND: + if (retnumeric) + /*--- + * tm->tm_sec + fsec / 1'000'000 + * = (tm->tm_sec * 1'000'000 + fsec) / 1'000'000 + */ + PG_RETURN_NUMERIC(int64_div_fast_to_numeric(tm->tm_sec * INT64CONST(1000000) + fsec, 6)); + else + PG_RETURN_FLOAT8(tm->tm_sec + fsec / 1000000.0); + break; + + case DTK_MINUTE: + intresult = tm->tm_min; + break; + + case DTK_HOUR: + intresult = tm->tm_hour; + break; + + case DTK_DAY: + intresult = tm->tm_mday; + break; + + case DTK_MONTH: + intresult = tm->tm_mon; + break; + + case DTK_QUARTER: + intresult = (tm->tm_mon - 1) / 3 + 1; + break; + + case DTK_WEEK: + intresult = date2isoweek(tm->tm_year, tm->tm_mon, tm->tm_mday); + break; + + case DTK_YEAR: + if (tm->tm_year > 0) + intresult = tm->tm_year; + else + /* there is no year 0, just 1 BC and 1 AD */ + intresult = tm->tm_year - 1; + break; + + case DTK_DECADE: + + /* + * what is a decade wrt dates? let us assume that decade 199 + * is 1990 thru 1999... decade 0 starts on year 1 BC, and -1 + * is 11 BC thru 2 BC... + */ + if (tm->tm_year >= 0) + intresult = tm->tm_year / 10; + else + intresult = -((8 - (tm->tm_year - 1)) / 10); + break; + + case DTK_CENTURY: + + /* ---- + * centuries AD, c>0: year in [ (c-1)* 100 + 1 : c*100 ] + * centuries BC, c<0: year in [ c*100 : (c+1) * 100 - 1] + * there is no number 0 century. + * ---- + */ + if (tm->tm_year > 0) + intresult = (tm->tm_year + 99) / 100; + else + /* caution: C division may have negative remainder */ + intresult = -((99 - (tm->tm_year - 1)) / 100); + break; + + case DTK_MILLENNIUM: + /* see comments above. */ + if (tm->tm_year > 0) + intresult = (tm->tm_year + 999) / 1000; + else + intresult = -((999 - (tm->tm_year - 1)) / 1000); + break; + + case DTK_JULIAN: + if (retnumeric) + PG_RETURN_NUMERIC(numeric_add_opt_error(int64_to_numeric(date2j(tm->tm_year, tm->tm_mon, tm->tm_mday)), + numeric_div_opt_error(int64_to_numeric(((((tm->tm_hour * MINS_PER_HOUR) + tm->tm_min) * SECS_PER_MINUTE) + tm->tm_sec) * INT64CONST(1000000) + fsec), + int64_to_numeric(SECS_PER_DAY * INT64CONST(1000000)), + NULL), + NULL)); + else + PG_RETURN_FLOAT8(date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) + + ((((tm->tm_hour * MINS_PER_HOUR) + tm->tm_min) * SECS_PER_MINUTE) + + tm->tm_sec + (fsec / 1000000.0)) / (double) SECS_PER_DAY); + break; + + case DTK_ISOYEAR: + intresult = date2isoyear(tm->tm_year, tm->tm_mon, tm->tm_mday); + /* Adjust BC years */ + if (intresult <= 0) + intresult -= 1; + break; + + case DTK_DOW: + case DTK_ISODOW: + intresult = j2day(date2j(tm->tm_year, tm->tm_mon, tm->tm_mday)); + if (val == DTK_ISODOW && intresult == 0) + intresult = 7; + break; + + case DTK_DOY: + intresult = (date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) + - date2j(tm->tm_year, 1, 1) + 1); + break; + + case DTK_TZ: + case DTK_TZ_MINUTE: + case DTK_TZ_HOUR: + default: + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("unit \"%s\" not supported for type %s", + lowunits, format_type_be(TIMESTAMPOID)))); + intresult = 0; + } + } + else if (type == RESERV) + { + switch (val) + { + case DTK_EPOCH: + epoch = SetEpochTimestamp(); + /* (timestamp - epoch) / 1000000 */ + if (retnumeric) + { + Numeric result; + + if (timestamp < (PG_INT64_MAX + epoch)) + result = int64_div_fast_to_numeric(timestamp - epoch, 6); + else + { + result = numeric_div_opt_error(numeric_sub_opt_error(int64_to_numeric(timestamp), + int64_to_numeric(epoch), + NULL), + int64_to_numeric(1000000), + NULL); + result = DatumGetNumeric(DirectFunctionCall2(numeric_round, + NumericGetDatum(result), + Int32GetDatum(6))); + } + PG_RETURN_NUMERIC(result); + } + else + { + float8 result; + + /* try to avoid precision loss in subtraction */ + if (timestamp < (PG_INT64_MAX + epoch)) + result = (timestamp - epoch) / 1000000.0; + else + result = ((float8) timestamp - epoch) / 1000000.0; + PG_RETURN_FLOAT8(result); + } + break; + + default: + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("unit \"%s\" not supported for type %s", + lowunits, format_type_be(TIMESTAMPOID)))); + intresult = 0; + } + } + else + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unit \"%s\" not recognized for type %s", + lowunits, format_type_be(TIMESTAMPOID)))); + intresult = 0; + } + + if (retnumeric) + PG_RETURN_NUMERIC(int64_to_numeric(intresult)); + else + PG_RETURN_FLOAT8(intresult); +} + +Datum +timestamp_part(PG_FUNCTION_ARGS) +{ + return timestamp_part_common(fcinfo, false); +} + +Datum +extract_timestamp(PG_FUNCTION_ARGS) +{ + return timestamp_part_common(fcinfo, true); +} + +/* timestamptz_part() and extract_timestamptz() + * Extract specified field from timestamp with time zone. + */ +static Datum +timestamptz_part_common(PG_FUNCTION_ARGS, bool retnumeric) +{ + text *units = PG_GETARG_TEXT_PP(0); + TimestampTz timestamp = PG_GETARG_TIMESTAMPTZ(1); + int64 intresult; + Timestamp epoch; + int tz; + int type, + val; + char *lowunits; + fsec_t fsec; + struct pg_tm tt, + *tm = &tt; + + lowunits = downcase_truncate_identifier(VARDATA_ANY(units), + VARSIZE_ANY_EXHDR(units), + false); + + type = DecodeUnits(0, lowunits, &val); + if (type == UNKNOWN_FIELD) + type = DecodeSpecial(0, lowunits, &val); + + if (TIMESTAMP_NOT_FINITE(timestamp)) + { + double r = NonFiniteTimestampTzPart(type, val, lowunits, + TIMESTAMP_IS_NOBEGIN(timestamp), + true); + + if (r) + { + if (retnumeric) + { + if (r < 0) + return DirectFunctionCall3(numeric_in, + CStringGetDatum("-Infinity"), + ObjectIdGetDatum(InvalidOid), + Int32GetDatum(-1)); + else if (r > 0) + return DirectFunctionCall3(numeric_in, + CStringGetDatum("Infinity"), + ObjectIdGetDatum(InvalidOid), + Int32GetDatum(-1)); + } + else + PG_RETURN_FLOAT8(r); + } + else + PG_RETURN_NULL(); + } + + if (type == UNITS) + { + if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + + switch (val) + { + case DTK_TZ: + intresult = -tz; + break; + + case DTK_TZ_MINUTE: + intresult = (-tz / SECS_PER_MINUTE) % MINS_PER_HOUR; + break; + + case DTK_TZ_HOUR: + intresult = -tz / SECS_PER_HOUR; + break; + + case DTK_MICROSEC: + intresult = tm->tm_sec * INT64CONST(1000000) + fsec; + break; + + case DTK_MILLISEC: + if (retnumeric) + /*--- + * tm->tm_sec * 1000 + fsec / 1000 + * = (tm->tm_sec * 1'000'000 + fsec) / 1000 + */ + PG_RETURN_NUMERIC(int64_div_fast_to_numeric(tm->tm_sec * INT64CONST(1000000) + fsec, 3)); + else + PG_RETURN_FLOAT8(tm->tm_sec * 1000.0 + fsec / 1000.0); + break; + + case DTK_SECOND: + if (retnumeric) + /*--- + * tm->tm_sec + fsec / 1'000'000 + * = (tm->tm_sec * 1'000'000 + fsec) / 1'000'000 + */ + PG_RETURN_NUMERIC(int64_div_fast_to_numeric(tm->tm_sec * INT64CONST(1000000) + fsec, 6)); + else + PG_RETURN_FLOAT8(tm->tm_sec + fsec / 1000000.0); + break; + + case DTK_MINUTE: + intresult = tm->tm_min; + break; + + case DTK_HOUR: + intresult = tm->tm_hour; + break; + + case DTK_DAY: + intresult = tm->tm_mday; + break; + + case DTK_MONTH: + intresult = tm->tm_mon; + break; + + case DTK_QUARTER: + intresult = (tm->tm_mon - 1) / 3 + 1; + break; + + case DTK_WEEK: + intresult = date2isoweek(tm->tm_year, tm->tm_mon, tm->tm_mday); + break; + + case DTK_YEAR: + if (tm->tm_year > 0) + intresult = tm->tm_year; + else + /* there is no year 0, just 1 BC and 1 AD */ + intresult = tm->tm_year - 1; + break; + + case DTK_DECADE: + /* see comments in timestamp_part */ + if (tm->tm_year > 0) + intresult = tm->tm_year / 10; + else + intresult = -((8 - (tm->tm_year - 1)) / 10); + break; + + case DTK_CENTURY: + /* see comments in timestamp_part */ + if (tm->tm_year > 0) + intresult = (tm->tm_year + 99) / 100; + else + intresult = -((99 - (tm->tm_year - 1)) / 100); + break; + + case DTK_MILLENNIUM: + /* see comments in timestamp_part */ + if (tm->tm_year > 0) + intresult = (tm->tm_year + 999) / 1000; + else + intresult = -((999 - (tm->tm_year - 1)) / 1000); + break; + + case DTK_JULIAN: + if (retnumeric) + PG_RETURN_NUMERIC(numeric_add_opt_error(int64_to_numeric(date2j(tm->tm_year, tm->tm_mon, tm->tm_mday)), + numeric_div_opt_error(int64_to_numeric(((((tm->tm_hour * MINS_PER_HOUR) + tm->tm_min) * SECS_PER_MINUTE) + tm->tm_sec) * INT64CONST(1000000) + fsec), + int64_to_numeric(SECS_PER_DAY * INT64CONST(1000000)), + NULL), + NULL)); + else + PG_RETURN_FLOAT8(date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) + + ((((tm->tm_hour * MINS_PER_HOUR) + tm->tm_min) * SECS_PER_MINUTE) + + tm->tm_sec + (fsec / 1000000.0)) / (double) SECS_PER_DAY); + break; + + case DTK_ISOYEAR: + intresult = date2isoyear(tm->tm_year, tm->tm_mon, tm->tm_mday); + /* Adjust BC years */ + if (intresult <= 0) + intresult -= 1; + break; + + case DTK_DOW: + case DTK_ISODOW: + intresult = j2day(date2j(tm->tm_year, tm->tm_mon, tm->tm_mday)); + if (val == DTK_ISODOW && intresult == 0) + intresult = 7; + break; + + case DTK_DOY: + intresult = (date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) + - date2j(tm->tm_year, 1, 1) + 1); + break; + + default: + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("unit \"%s\" not supported for type %s", + lowunits, format_type_be(TIMESTAMPTZOID)))); + intresult = 0; + } + } + else if (type == RESERV) + { + switch (val) + { + case DTK_EPOCH: + epoch = SetEpochTimestamp(); + /* (timestamp - epoch) / 1000000 */ + if (retnumeric) + { + Numeric result; + + if (timestamp < (PG_INT64_MAX + epoch)) + result = int64_div_fast_to_numeric(timestamp - epoch, 6); + else + { + result = numeric_div_opt_error(numeric_sub_opt_error(int64_to_numeric(timestamp), + int64_to_numeric(epoch), + NULL), + int64_to_numeric(1000000), + NULL); + result = DatumGetNumeric(DirectFunctionCall2(numeric_round, + NumericGetDatum(result), + Int32GetDatum(6))); + } + PG_RETURN_NUMERIC(result); + } + else + { + float8 result; + + /* try to avoid precision loss in subtraction */ + if (timestamp < (PG_INT64_MAX + epoch)) + result = (timestamp - epoch) / 1000000.0; + else + result = ((float8) timestamp - epoch) / 1000000.0; + PG_RETURN_FLOAT8(result); + } + break; + + default: + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("unit \"%s\" not supported for type %s", + lowunits, format_type_be(TIMESTAMPTZOID)))); + intresult = 0; + } + } + else + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unit \"%s\" not recognized for type %s", + lowunits, format_type_be(TIMESTAMPTZOID)))); + + intresult = 0; + } + + if (retnumeric) + PG_RETURN_NUMERIC(int64_to_numeric(intresult)); + else + PG_RETURN_FLOAT8(intresult); +} + +Datum +timestamptz_part(PG_FUNCTION_ARGS) +{ + return timestamptz_part_common(fcinfo, false); +} + +Datum +extract_timestamptz(PG_FUNCTION_ARGS) +{ + return timestamptz_part_common(fcinfo, true); +} + + +/* interval_part() and extract_interval() + * Extract specified field from interval. + */ +static Datum +interval_part_common(PG_FUNCTION_ARGS, bool retnumeric) +{ + text *units = PG_GETARG_TEXT_PP(0); + Interval *interval = PG_GETARG_INTERVAL_P(1); + int64 intresult; + int type, + val; + char *lowunits; + struct pg_itm tt, + *tm = &tt; + + lowunits = downcase_truncate_identifier(VARDATA_ANY(units), + VARSIZE_ANY_EXHDR(units), + false); + + type = DecodeUnits(0, lowunits, &val); + if (type == UNKNOWN_FIELD) + type = DecodeSpecial(0, lowunits, &val); + + if (type == UNITS) + { + interval2itm(*interval, tm); + switch (val) + { + case DTK_MICROSEC: + intresult = tm->tm_sec * INT64CONST(1000000) + tm->tm_usec; + break; + + case DTK_MILLISEC: + if (retnumeric) + /*--- + * tm->tm_sec * 1000 + fsec / 1000 + * = (tm->tm_sec * 1'000'000 + fsec) / 1000 + */ + PG_RETURN_NUMERIC(int64_div_fast_to_numeric(tm->tm_sec * INT64CONST(1000000) + tm->tm_usec, 3)); + else + PG_RETURN_FLOAT8(tm->tm_sec * 1000.0 + tm->tm_usec / 1000.0); + break; + + case DTK_SECOND: + if (retnumeric) + /*--- + * tm->tm_sec + fsec / 1'000'000 + * = (tm->tm_sec * 1'000'000 + fsec) / 1'000'000 + */ + PG_RETURN_NUMERIC(int64_div_fast_to_numeric(tm->tm_sec * INT64CONST(1000000) + tm->tm_usec, 6)); + else + PG_RETURN_FLOAT8(tm->tm_sec + tm->tm_usec / 1000000.0); + break; + + case DTK_MINUTE: + intresult = tm->tm_min; + break; + + case DTK_HOUR: + intresult = tm->tm_hour; + break; + + case DTK_DAY: + intresult = tm->tm_mday; + break; + + case DTK_MONTH: + intresult = tm->tm_mon; + break; + + case DTK_QUARTER: + intresult = (tm->tm_mon / 3) + 1; + break; + + case DTK_YEAR: + intresult = tm->tm_year; + break; + + case DTK_DECADE: + /* caution: C division may have negative remainder */ + intresult = tm->tm_year / 10; + break; + + case DTK_CENTURY: + /* caution: C division may have negative remainder */ + intresult = tm->tm_year / 100; + break; + + case DTK_MILLENNIUM: + /* caution: C division may have negative remainder */ + intresult = tm->tm_year / 1000; + break; + + default: + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("unit \"%s\" not supported for type %s", + lowunits, format_type_be(INTERVALOID)))); + intresult = 0; + } + } + else if (type == RESERV && val == DTK_EPOCH) + { + if (retnumeric) + { + Numeric result; + int64 secs_from_day_month; + int64 val; + + /* + * To do this calculation in integer arithmetic even though + * DAYS_PER_YEAR is fractional, multiply everything by 4 and then + * divide by 4 again at the end. This relies on DAYS_PER_YEAR + * being a multiple of 0.25 and on SECS_PER_DAY being a multiple + * of 4. + */ + secs_from_day_month = ((int64) (4 * DAYS_PER_YEAR) * (interval->month / MONTHS_PER_YEAR) + + (int64) (4 * DAYS_PER_MONTH) * (interval->month % MONTHS_PER_YEAR) + + (int64) 4 * interval->day) * (SECS_PER_DAY / 4); + + /*--- + * result = secs_from_day_month + interval->time / 1'000'000 + * = (secs_from_day_month * 1'000'000 + interval->time) / 1'000'000 + */ + + /* + * Try the computation inside int64; if it overflows, do it in + * numeric (slower). This overflow happens around 10^9 days, so + * not common in practice. + */ + if (!pg_mul_s64_overflow(secs_from_day_month, 1000000, &val) && + !pg_add_s64_overflow(val, interval->time, &val)) + result = int64_div_fast_to_numeric(val, 6); + else + result = + numeric_add_opt_error(int64_div_fast_to_numeric(interval->time, 6), + int64_to_numeric(secs_from_day_month), + NULL); + + PG_RETURN_NUMERIC(result); + } + else + { + float8 result; + + result = interval->time / 1000000.0; + result += ((double) DAYS_PER_YEAR * SECS_PER_DAY) * (interval->month / MONTHS_PER_YEAR); + result += ((double) DAYS_PER_MONTH * SECS_PER_DAY) * (interval->month % MONTHS_PER_YEAR); + result += ((double) SECS_PER_DAY) * interval->day; + + PG_RETURN_FLOAT8(result); + } + } + else + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unit \"%s\" not recognized for type %s", + lowunits, format_type_be(INTERVALOID)))); + intresult = 0; + } + + if (retnumeric) + PG_RETURN_NUMERIC(int64_to_numeric(intresult)); + else + PG_RETURN_FLOAT8(intresult); +} + +Datum +interval_part(PG_FUNCTION_ARGS) +{ + return interval_part_common(fcinfo, false); +} + +Datum +extract_interval(PG_FUNCTION_ARGS) +{ + return interval_part_common(fcinfo, true); +} + + +/* timestamp_zone() + * Encode timestamp type with specified time zone. + * This function is just timestamp2timestamptz() except instead of + * shifting to the global timezone, we shift to the specified timezone. + * This is different from the other AT TIME ZONE cases because instead + * of shifting _to_ a new time zone, it sets the time to _be_ the + * specified timezone. + */ +Datum +timestamp_zone(PG_FUNCTION_ARGS) +{ + text *zone = PG_GETARG_TEXT_PP(0); + Timestamp timestamp = PG_GETARG_TIMESTAMP(1); + TimestampTz result; + int tz; + char tzname[TZ_STRLEN_MAX + 1]; + int type, + val; + pg_tz *tzp; + struct pg_tm tm; + fsec_t fsec; + + if (TIMESTAMP_NOT_FINITE(timestamp)) + PG_RETURN_TIMESTAMPTZ(timestamp); + + /* + * Look up the requested timezone. + */ + text_to_cstring_buffer(zone, tzname, sizeof(tzname)); + + type = DecodeTimezoneName(tzname, &val, &tzp); + + if (type == TZNAME_FIXED_OFFSET) + { + /* fixed-offset abbreviation */ + tz = val; + result = dt2local(timestamp, tz); + } + else if (type == TZNAME_DYNTZ) + { + /* dynamic-offset abbreviation, resolve using specified time */ + if (timestamp2tm(timestamp, NULL, &tm, &fsec, NULL, tzp) != 0) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + tz = -DetermineTimeZoneAbbrevOffset(&tm, tzname, tzp); + result = dt2local(timestamp, tz); + } + else + { + /* full zone name, rotate to that zone */ + if (timestamp2tm(timestamp, NULL, &tm, &fsec, NULL, tzp) != 0) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + tz = DetermineTimeZoneOffset(&tm, tzp); + if (tm2timestamp(&tm, fsec, &tz, &result) != 0) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + } + + if (!IS_VALID_TIMESTAMP(result)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + + PG_RETURN_TIMESTAMPTZ(result); +} + +/* timestamp_izone() + * Encode timestamp type with specified time interval as time zone. + */ +Datum +timestamp_izone(PG_FUNCTION_ARGS) +{ + Interval *zone = PG_GETARG_INTERVAL_P(0); + Timestamp timestamp = PG_GETARG_TIMESTAMP(1); + TimestampTz result; + int tz; + + if (TIMESTAMP_NOT_FINITE(timestamp)) + PG_RETURN_TIMESTAMPTZ(timestamp); + + if (zone->month != 0 || zone->day != 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("interval time zone \"%s\" must not include months or days", + DatumGetCString(DirectFunctionCall1(interval_out, + PointerGetDatum(zone)))))); + + tz = zone->time / USECS_PER_SEC; + + result = dt2local(timestamp, tz); + + if (!IS_VALID_TIMESTAMP(result)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + + PG_RETURN_TIMESTAMPTZ(result); +} /* timestamp_izone() */ + +/* TimestampTimestampTzRequiresRewrite() + * + * Returns false if the TimeZone GUC setting causes timestamp_timestamptz and + * timestamptz_timestamp to be no-ops, where the return value has the same + * bits as the argument. Since project convention is to assume a GUC changes + * no more often than STABLE functions change, the answer is valid that long. + */ +bool +TimestampTimestampTzRequiresRewrite(void) +{ + long offset; + + if (pg_get_timezone_offset(session_timezone, &offset) && offset == 0) + return false; + return true; +} + +/* timestamp_timestamptz() + * Convert local timestamp to timestamp at GMT + */ +Datum +timestamp_timestamptz(PG_FUNCTION_ARGS) +{ + Timestamp timestamp = PG_GETARG_TIMESTAMP(0); + + PG_RETURN_TIMESTAMPTZ(timestamp2timestamptz(timestamp)); +} + +/* + * Convert timestamp to timestamp with time zone. + * + * On successful conversion, *overflow is set to zero if it's not NULL. + * + * If the timestamp is finite but out of the valid range for timestamptz, then: + * if overflow is NULL, we throw an out-of-range error. + * if overflow is not NULL, we store +1 or -1 there to indicate the sign + * of the overflow, and return the appropriate timestamptz infinity. + */ +TimestampTz +timestamp2timestamptz_opt_overflow(Timestamp timestamp, int *overflow) +{ + TimestampTz result; + struct pg_tm tt, + *tm = &tt; + fsec_t fsec; + int tz; + + if (overflow) + *overflow = 0; + + if (TIMESTAMP_NOT_FINITE(timestamp)) + return timestamp; + + /* We don't expect this to fail, but check it pro forma */ + if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) == 0) + { + tz = DetermineTimeZoneOffset(tm, session_timezone); + + result = dt2local(timestamp, -tz); + + if (IS_VALID_TIMESTAMP(result)) + { + return result; + } + else if (overflow) + { + if (result < MIN_TIMESTAMP) + { + *overflow = -1; + TIMESTAMP_NOBEGIN(result); + } + else + { + *overflow = 1; + TIMESTAMP_NOEND(result); + } + return result; + } + } + + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + + return 0; +} + +/* + * Promote timestamp to timestamptz, throwing error for overflow. + */ +static TimestampTz +timestamp2timestamptz(Timestamp timestamp) +{ + return timestamp2timestamptz_opt_overflow(timestamp, NULL); +} + +/* timestamptz_timestamp() + * Convert timestamp at GMT to local timestamp + */ +Datum +timestamptz_timestamp(PG_FUNCTION_ARGS) +{ + TimestampTz timestamp = PG_GETARG_TIMESTAMPTZ(0); + + PG_RETURN_TIMESTAMP(timestamptz2timestamp(timestamp)); +} + +static Timestamp +timestamptz2timestamp(TimestampTz timestamp) +{ + Timestamp result; + struct pg_tm tt, + *tm = &tt; + fsec_t fsec; + int tz; + + if (TIMESTAMP_NOT_FINITE(timestamp)) + result = timestamp; + else + { + if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + if (tm2timestamp(tm, fsec, NULL, &result) != 0) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + } + return result; +} + +/* timestamptz_zone() + * Evaluate timestamp with time zone type at the specified time zone. + * Returns a timestamp without time zone. + */ +Datum +timestamptz_zone(PG_FUNCTION_ARGS) +{ + text *zone = PG_GETARG_TEXT_PP(0); + TimestampTz timestamp = PG_GETARG_TIMESTAMPTZ(1); + Timestamp result; + int tz; + char tzname[TZ_STRLEN_MAX + 1]; + int type, + val; + pg_tz *tzp; + + if (TIMESTAMP_NOT_FINITE(timestamp)) + PG_RETURN_TIMESTAMP(timestamp); + + /* + * Look up the requested timezone. + */ + text_to_cstring_buffer(zone, tzname, sizeof(tzname)); + + type = DecodeTimezoneName(tzname, &val, &tzp); + + if (type == TZNAME_FIXED_OFFSET) + { + /* fixed-offset abbreviation */ + tz = -val; + result = dt2local(timestamp, tz); + } + else if (type == TZNAME_DYNTZ) + { + /* dynamic-offset abbreviation, resolve using specified time */ + int isdst; + + tz = DetermineTimeZoneAbbrevOffsetTS(timestamp, tzname, tzp, &isdst); + result = dt2local(timestamp, tz); + } + else + { + /* full zone name, rotate from that zone */ + struct pg_tm tm; + fsec_t fsec; + + if (timestamp2tm(timestamp, &tz, &tm, &fsec, NULL, tzp) != 0) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + if (tm2timestamp(&tm, fsec, NULL, &result) != 0) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + } + + if (!IS_VALID_TIMESTAMP(result)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + + PG_RETURN_TIMESTAMP(result); +} + +/* timestamptz_izone() + * Encode timestamp with time zone type with specified time interval as time zone. + * Returns a timestamp without time zone. + */ +Datum +timestamptz_izone(PG_FUNCTION_ARGS) +{ + Interval *zone = PG_GETARG_INTERVAL_P(0); + TimestampTz timestamp = PG_GETARG_TIMESTAMPTZ(1); + Timestamp result; + int tz; + + if (TIMESTAMP_NOT_FINITE(timestamp)) + PG_RETURN_TIMESTAMP(timestamp); + + if (zone->month != 0 || zone->day != 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("interval time zone \"%s\" must not include months or days", + DatumGetCString(DirectFunctionCall1(interval_out, + PointerGetDatum(zone)))))); + + tz = -(zone->time / USECS_PER_SEC); + + result = dt2local(timestamp, tz); + + if (!IS_VALID_TIMESTAMP(result)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + + PG_RETURN_TIMESTAMP(result); +} + +/* generate_series_timestamp() + * Generate the set of timestamps from start to finish by step + */ +Datum +generate_series_timestamp(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + generate_series_timestamp_fctx *fctx; + Timestamp result; + + /* stuff done only on the first call of the function */ + if (SRF_IS_FIRSTCALL()) + { + Timestamp start = PG_GETARG_TIMESTAMP(0); + Timestamp finish = PG_GETARG_TIMESTAMP(1); + Interval *step = PG_GETARG_INTERVAL_P(2); + MemoryContext oldcontext; + const Interval interval_zero = {0}; + + /* 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 */ + fctx = (generate_series_timestamp_fctx *) + palloc(sizeof(generate_series_timestamp_fctx)); + + /* + * Use fctx to keep state from call to call. Seed current with the + * original start value + */ + fctx->current = start; + fctx->finish = finish; + fctx->step = *step; + + /* Determine sign of the interval */ + fctx->step_sign = interval_cmp_internal(&fctx->step, &interval_zero); + + if (fctx->step_sign == 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("step size cannot equal zero"))); + + funcctx->user_fctx = fctx; + MemoryContextSwitchTo(oldcontext); + } + + /* stuff done on every call of the function */ + funcctx = SRF_PERCALL_SETUP(); + + /* + * get the saved state and use current as the result for this iteration + */ + fctx = funcctx->user_fctx; + result = fctx->current; + + if (fctx->step_sign > 0 ? + timestamp_cmp_internal(result, fctx->finish) <= 0 : + timestamp_cmp_internal(result, fctx->finish) >= 0) + { + /* increment current in preparation for next iteration */ + fctx->current = DatumGetTimestamp(DirectFunctionCall2(timestamp_pl_interval, + TimestampGetDatum(fctx->current), + PointerGetDatum(&fctx->step))); + + /* do when there is more left to send */ + SRF_RETURN_NEXT(funcctx, TimestampGetDatum(result)); + } + else + { + /* do when there is no more left */ + SRF_RETURN_DONE(funcctx); + } +} + +/* generate_series_timestamptz() + * Generate the set of timestamps from start to finish by step, + * doing arithmetic in the specified or session timezone. + */ +static Datum +generate_series_timestamptz_internal(FunctionCallInfo fcinfo) +{ + FuncCallContext *funcctx; + generate_series_timestamptz_fctx *fctx; + TimestampTz result; + + /* stuff done only on the first call of the function */ + if (SRF_IS_FIRSTCALL()) + { + TimestampTz start = PG_GETARG_TIMESTAMPTZ(0); + TimestampTz finish = PG_GETARG_TIMESTAMPTZ(1); + Interval *step = PG_GETARG_INTERVAL_P(2); + text *zone = (PG_NARGS() == 4) ? PG_GETARG_TEXT_PP(3) : NULL; + MemoryContext oldcontext; + const Interval interval_zero = {0}; + + /* 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 */ + fctx = (generate_series_timestamptz_fctx *) + palloc(sizeof(generate_series_timestamptz_fctx)); + + /* + * Use fctx to keep state from call to call. Seed current with the + * original start value + */ + fctx->current = start; + fctx->finish = finish; + fctx->step = *step; + fctx->attimezone = zone ? lookup_timezone(zone) : session_timezone; + + /* Determine sign of the interval */ + fctx->step_sign = interval_cmp_internal(&fctx->step, &interval_zero); + + if (fctx->step_sign == 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("step size cannot equal zero"))); + + funcctx->user_fctx = fctx; + MemoryContextSwitchTo(oldcontext); + } + + /* stuff done on every call of the function */ + funcctx = SRF_PERCALL_SETUP(); + + /* + * get the saved state and use current as the result for this iteration + */ + fctx = funcctx->user_fctx; + result = fctx->current; + + if (fctx->step_sign > 0 ? + timestamp_cmp_internal(result, fctx->finish) <= 0 : + timestamp_cmp_internal(result, fctx->finish) >= 0) + { + /* increment current in preparation for next iteration */ + fctx->current = timestamptz_pl_interval_internal(fctx->current, + &fctx->step, + fctx->attimezone); + + /* do when there is more left to send */ + SRF_RETURN_NEXT(funcctx, TimestampTzGetDatum(result)); + } + else + { + /* do when there is no more left */ + SRF_RETURN_DONE(funcctx); + } +} + +Datum +generate_series_timestamptz(PG_FUNCTION_ARGS) +{ + return generate_series_timestamptz_internal(fcinfo); +} + +Datum +generate_series_timestamptz_at_zone(PG_FUNCTION_ARGS) +{ + return generate_series_timestamptz_internal(fcinfo); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/trigfuncs.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/trigfuncs.c new file mode 100644 index 00000000000..d9a616f603d --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/trigfuncs.c @@ -0,0 +1,85 @@ +/*------------------------------------------------------------------------- + * + * trigfuncs.c + * Builtin functions for useful trigger support. + * + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/backend/utils/adt/trigfuncs.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/htup_details.h" +#include "commands/trigger.h" +#include "utils/builtins.h" +#include "utils/rel.h" + + +/* + * suppress_redundant_updates_trigger + * + * This trigger function will inhibit an update from being done + * if the OLD and NEW records are identical. + */ +Datum +suppress_redundant_updates_trigger(PG_FUNCTION_ARGS) +{ + TriggerData *trigdata = (TriggerData *) fcinfo->context; + HeapTuple newtuple, + oldtuple, + rettuple; + HeapTupleHeader newheader, + oldheader; + + /* make sure it's called as a trigger */ + if (!CALLED_AS_TRIGGER(fcinfo)) + ereport(ERROR, + (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED), + errmsg("suppress_redundant_updates_trigger: must be called as trigger"))); + + /* and that it's called on update */ + if (!TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event)) + ereport(ERROR, + (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED), + errmsg("suppress_redundant_updates_trigger: must be called on update"))); + + /* and that it's called before update */ + if (!TRIGGER_FIRED_BEFORE(trigdata->tg_event)) + ereport(ERROR, + (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED), + errmsg("suppress_redundant_updates_trigger: must be called before update"))); + + /* and that it's called for each row */ + if (!TRIGGER_FIRED_FOR_ROW(trigdata->tg_event)) + ereport(ERROR, + (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED), + errmsg("suppress_redundant_updates_trigger: must be called for each row"))); + + /* get tuple data, set default result */ + rettuple = newtuple = trigdata->tg_newtuple; + oldtuple = trigdata->tg_trigtuple; + + newheader = newtuple->t_data; + oldheader = oldtuple->t_data; + + /* if the tuple payload is the same ... */ + if (newtuple->t_len == oldtuple->t_len && + newheader->t_hoff == oldheader->t_hoff && + (HeapTupleHeaderGetNatts(newheader) == + HeapTupleHeaderGetNatts(oldheader)) && + ((newheader->t_infomask & ~HEAP_XACT_MASK) == + (oldheader->t_infomask & ~HEAP_XACT_MASK)) && + memcmp(((char *) newheader) + SizeofHeapTupleHeader, + ((char *) oldheader) + SizeofHeapTupleHeader, + newtuple->t_len - SizeofHeapTupleHeader) == 0) + { + /* ... then suppress the update */ + rettuple = NULL; + } + + return PointerGetDatum(rettuple); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/tsginidx.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/tsginidx.c new file mode 100644 index 00000000000..484a003827d --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/tsginidx.c @@ -0,0 +1,355 @@ +/*------------------------------------------------------------------------- + * + * tsginidx.c + * GIN support functions for tsvector_ops + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/utils/adt/tsginidx.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/gin.h" +#include "access/stratnum.h" +#include "miscadmin.h" +#include "tsearch/ts_type.h" +#include "tsearch/ts_utils.h" +#include "utils/builtins.h" +#include "varatt.h" + + +Datum +gin_cmp_tslexeme(PG_FUNCTION_ARGS) +{ + text *a = PG_GETARG_TEXT_PP(0); + text *b = PG_GETARG_TEXT_PP(1); + int cmp; + + cmp = tsCompareString(VARDATA_ANY(a), VARSIZE_ANY_EXHDR(a), + VARDATA_ANY(b), VARSIZE_ANY_EXHDR(b), + false); + + PG_FREE_IF_COPY(a, 0); + PG_FREE_IF_COPY(b, 1); + PG_RETURN_INT32(cmp); +} + +Datum +gin_cmp_prefix(PG_FUNCTION_ARGS) +{ + text *a = PG_GETARG_TEXT_PP(0); + text *b = PG_GETARG_TEXT_PP(1); + +#ifdef NOT_USED + StrategyNumber strategy = PG_GETARG_UINT16(2); + Pointer extra_data = PG_GETARG_POINTER(3); +#endif + int cmp; + + cmp = tsCompareString(VARDATA_ANY(a), VARSIZE_ANY_EXHDR(a), + VARDATA_ANY(b), VARSIZE_ANY_EXHDR(b), + true); + + if (cmp < 0) + cmp = 1; /* prevent continue scan */ + + PG_FREE_IF_COPY(a, 0); + PG_FREE_IF_COPY(b, 1); + PG_RETURN_INT32(cmp); +} + +Datum +gin_extract_tsvector(PG_FUNCTION_ARGS) +{ + TSVector vector = PG_GETARG_TSVECTOR(0); + int32 *nentries = (int32 *) PG_GETARG_POINTER(1); + Datum *entries = NULL; + + *nentries = vector->size; + if (vector->size > 0) + { + int i; + WordEntry *we = ARRPTR(vector); + + entries = (Datum *) palloc(sizeof(Datum) * vector->size); + + for (i = 0; i < vector->size; i++) + { + text *txt; + + txt = cstring_to_text_with_len(STRPTR(vector) + we->pos, we->len); + entries[i] = PointerGetDatum(txt); + + we++; + } + } + + PG_FREE_IF_COPY(vector, 0); + PG_RETURN_POINTER(entries); +} + +Datum +gin_extract_tsquery(PG_FUNCTION_ARGS) +{ + TSQuery query = PG_GETARG_TSQUERY(0); + int32 *nentries = (int32 *) PG_GETARG_POINTER(1); + + /* StrategyNumber strategy = PG_GETARG_UINT16(2); */ + bool **ptr_partialmatch = (bool **) PG_GETARG_POINTER(3); + Pointer **extra_data = (Pointer **) PG_GETARG_POINTER(4); + + /* bool **nullFlags = (bool **) PG_GETARG_POINTER(5); */ + int32 *searchMode = (int32 *) PG_GETARG_POINTER(6); + Datum *entries = NULL; + + *nentries = 0; + + if (query->size > 0) + { + QueryItem *item = GETQUERY(query); + int32 i, + j; + bool *partialmatch; + int *map_item_operand; + + /* + * If the query doesn't have any required positive matches (for + * instance, it's something like '! foo'), we have to do a full index + * scan. + */ + if (tsquery_requires_match(item)) + *searchMode = GIN_SEARCH_MODE_DEFAULT; + else + *searchMode = GIN_SEARCH_MODE_ALL; + + /* count number of VAL items */ + j = 0; + for (i = 0; i < query->size; i++) + { + if (item[i].type == QI_VAL) + j++; + } + *nentries = j; + + entries = (Datum *) palloc(sizeof(Datum) * j); + partialmatch = *ptr_partialmatch = (bool *) palloc(sizeof(bool) * j); + + /* + * Make map to convert item's number to corresponding operand's (the + * same, entry's) number. Entry's number is used in check array in + * consistent method. We use the same map for each entry. + */ + *extra_data = (Pointer *) palloc(sizeof(Pointer) * j); + map_item_operand = (int *) palloc0(sizeof(int) * query->size); + + /* Now rescan the VAL items and fill in the arrays */ + j = 0; + for (i = 0; i < query->size; i++) + { + if (item[i].type == QI_VAL) + { + QueryOperand *val = &item[i].qoperand; + text *txt; + + txt = cstring_to_text_with_len(GETOPERAND(query) + val->distance, + val->length); + entries[j] = PointerGetDatum(txt); + partialmatch[j] = val->prefix; + (*extra_data)[j] = (Pointer) map_item_operand; + map_item_operand[i] = j; + j++; + } + } + } + + PG_FREE_IF_COPY(query, 0); + + PG_RETURN_POINTER(entries); +} + +typedef struct +{ + QueryItem *first_item; + GinTernaryValue *check; + int *map_item_operand; +} GinChkVal; + +/* + * TS_execute callback for matching a tsquery operand to GIN index data + */ +static TSTernaryValue +checkcondition_gin(void *checkval, QueryOperand *val, ExecPhraseData *data) +{ + GinChkVal *gcv = (GinChkVal *) checkval; + int j; + GinTernaryValue result; + + /* convert item's number to corresponding entry's (operand's) number */ + j = gcv->map_item_operand[((QueryItem *) val) - gcv->first_item]; + + /* determine presence of current entry in indexed value */ + result = gcv->check[j]; + + /* + * If any val requiring a weight is used or caller needs position + * information then we must recheck, so replace TRUE with MAYBE. + */ + if (result == GIN_TRUE) + { + if (val->weight != 0 || data != NULL) + result = GIN_MAYBE; + } + + /* + * We rely on GinTernaryValue and TSTernaryValue using equivalent value + * assignments. We could use a switch statement to map the values if that + * ever stops being true, but it seems unlikely to happen. + */ + return (TSTernaryValue) result; +} + +Datum +gin_tsquery_consistent(PG_FUNCTION_ARGS) +{ + bool *check = (bool *) PG_GETARG_POINTER(0); + + /* StrategyNumber strategy = PG_GETARG_UINT16(1); */ + TSQuery query = PG_GETARG_TSQUERY(2); + + /* int32 nkeys = PG_GETARG_INT32(3); */ + Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); + bool *recheck = (bool *) PG_GETARG_POINTER(5); + bool res = false; + + /* Initially assume query doesn't require recheck */ + *recheck = false; + + if (query->size > 0) + { + GinChkVal gcv; + + /* + * check-parameter array has one entry for each value (operand) in the + * query. + */ + gcv.first_item = GETQUERY(query); + gcv.check = (GinTernaryValue *) check; + gcv.map_item_operand = (int *) (extra_data[0]); + + switch (TS_execute_ternary(GETQUERY(query), + &gcv, + TS_EXEC_PHRASE_NO_POS, + checkcondition_gin)) + { + case TS_NO: + res = false; + break; + case TS_YES: + res = true; + break; + case TS_MAYBE: + res = true; + *recheck = true; + break; + } + } + + PG_RETURN_BOOL(res); +} + +Datum +gin_tsquery_triconsistent(PG_FUNCTION_ARGS) +{ + GinTernaryValue *check = (GinTernaryValue *) PG_GETARG_POINTER(0); + + /* StrategyNumber strategy = PG_GETARG_UINT16(1); */ + TSQuery query = PG_GETARG_TSQUERY(2); + + /* int32 nkeys = PG_GETARG_INT32(3); */ + Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); + GinTernaryValue res = GIN_FALSE; + + if (query->size > 0) + { + GinChkVal gcv; + + /* + * check-parameter array has one entry for each value (operand) in the + * query. + */ + gcv.first_item = GETQUERY(query); + gcv.check = check; + gcv.map_item_operand = (int *) (extra_data[0]); + + res = TS_execute_ternary(GETQUERY(query), + &gcv, + TS_EXEC_PHRASE_NO_POS, + checkcondition_gin); + } + + PG_RETURN_GIN_TERNARY_VALUE(res); +} + +/* + * Formerly, gin_extract_tsvector had only two arguments. Now it has three, + * but we still need a pg_proc entry with two args to support reloading + * pre-9.1 contrib/tsearch2 opclass declarations. This compatibility + * function should go away eventually. (Note: you might say "hey, but the + * code above is only *using* two args, so let's just declare it that way". + * If you try that you'll find the opr_sanity regression test complains.) + */ +Datum +gin_extract_tsvector_2args(PG_FUNCTION_ARGS) +{ + if (PG_NARGS() < 3) /* should not happen */ + elog(ERROR, "gin_extract_tsvector requires three arguments"); + return gin_extract_tsvector(fcinfo); +} + +/* + * Likewise, we need a stub version of gin_extract_tsquery declared with + * only five arguments. + */ +Datum +gin_extract_tsquery_5args(PG_FUNCTION_ARGS) +{ + if (PG_NARGS() < 7) /* should not happen */ + elog(ERROR, "gin_extract_tsquery requires seven arguments"); + return gin_extract_tsquery(fcinfo); +} + +/* + * Likewise, we need a stub version of gin_tsquery_consistent declared with + * only six arguments. + */ +Datum +gin_tsquery_consistent_6args(PG_FUNCTION_ARGS) +{ + if (PG_NARGS() < 8) /* should not happen */ + elog(ERROR, "gin_tsquery_consistent requires eight arguments"); + return gin_tsquery_consistent(fcinfo); +} + +/* + * Likewise, a stub version of gin_extract_tsquery declared with argument + * types that are no longer considered appropriate. + */ +Datum +gin_extract_tsquery_oldsig(PG_FUNCTION_ARGS) +{ + return gin_extract_tsquery(fcinfo); +} + +/* + * Likewise, a stub version of gin_tsquery_consistent declared with argument + * types that are no longer considered appropriate. + */ +Datum +gin_tsquery_consistent_oldsig(PG_FUNCTION_ARGS) +{ + return gin_tsquery_consistent(fcinfo); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/tsgistidx.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/tsgistidx.c new file mode 100644 index 00000000000..f76fe608be2 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/tsgistidx.c @@ -0,0 +1,818 @@ +/*------------------------------------------------------------------------- + * + * tsgistidx.c + * GiST support functions for tsvector_ops + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/utils/adt/tsgistidx.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/gist.h" +#include "access/heaptoast.h" +#include "access/reloptions.h" +#include "lib/qunique.h" +#include "port/pg_bitutils.h" +#include "tsearch/ts_utils.h" +#include "utils/builtins.h" +#include "utils/pg_crc.h" + + +/* tsvector_ops opclass options */ +typedef struct +{ + int32 vl_len_; /* varlena header (do not touch directly!) */ + int siglen; /* signature length */ +} GistTsVectorOptions; + +#define SIGLEN_DEFAULT (31 * 4) +#define SIGLEN_MAX GISTMaxIndexKeySize +#define GET_SIGLEN() (PG_HAS_OPCLASS_OPTIONS() ? \ + ((GistTsVectorOptions *) PG_GET_OPCLASS_OPTIONS())->siglen : \ + SIGLEN_DEFAULT) + +#define SIGLENBIT(siglen) ((siglen) * BITS_PER_BYTE) + +typedef char *BITVECP; + +#define LOOPBYTE(siglen) \ + for (i = 0; i < siglen; i++) + +#define GETBYTE(x,i) ( *( (BITVECP)(x) + (int)( (i) / BITS_PER_BYTE ) ) ) +#define GETBITBYTE(x,i) ( ((char)(x)) >> (i) & 0x01 ) +#define CLRBIT(x,i) GETBYTE(x,i) &= ~( 0x01 << ( (i) % BITS_PER_BYTE ) ) +#define SETBIT(x,i) GETBYTE(x,i) |= ( 0x01 << ( (i) % BITS_PER_BYTE ) ) +#define GETBIT(x,i) ( (GETBYTE(x,i) >> ( (i) % BITS_PER_BYTE )) & 0x01 ) + +#define HASHVAL(val, siglen) (((unsigned int)(val)) % SIGLENBIT(siglen)) +#define HASH(sign, val, siglen) SETBIT((sign), HASHVAL(val, siglen)) + +#define GETENTRY(vec,pos) ((SignTSVector *) DatumGetPointer((vec)->vector[(pos)].key)) + +/* + * type of GiST index key + */ + +typedef struct +{ + int32 vl_len_; /* varlena header (do not touch directly!) */ + int32 flag; + char data[FLEXIBLE_ARRAY_MEMBER]; +} SignTSVector; + +#define ARRKEY 0x01 +#define SIGNKEY 0x02 +#define ALLISTRUE 0x04 + +#define ISARRKEY(x) ( ((SignTSVector*)(x))->flag & ARRKEY ) +#define ISSIGNKEY(x) ( ((SignTSVector*)(x))->flag & SIGNKEY ) +#define ISALLTRUE(x) ( ((SignTSVector*)(x))->flag & ALLISTRUE ) + +#define GTHDRSIZE ( VARHDRSZ + sizeof(int32) ) +#define CALCGTSIZE(flag, len) ( GTHDRSIZE + ( ( (flag) & ARRKEY ) ? ((len)*sizeof(int32)) : (((flag) & ALLISTRUE) ? 0 : (len)) ) ) + +#define GETSIGN(x) ( (BITVECP)( (char*)(x)+GTHDRSIZE ) ) +#define GETSIGLEN(x)( VARSIZE(x) - GTHDRSIZE ) +#define GETARR(x) ( (int32*)( (char*)(x)+GTHDRSIZE ) ) +#define ARRNELEM(x) ( ( VARSIZE(x) - GTHDRSIZE )/sizeof(int32) ) + +static int32 sizebitvec(BITVECP sign, int siglen); + +Datum +gtsvectorin(PG_FUNCTION_ARGS) +{ + /* There's no need to support input of gtsvectors */ + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot accept a value of type %s", "gtsvector"))); + + PG_RETURN_VOID(); /* keep compiler quiet */ +} + +#define SINGOUTSTR "%d true bits, %d false bits" +#define ARROUTSTR "%d unique words" +#define EXTRALEN ( 2*13 ) + +static __thread int outbuf_maxlen = 0; + +Datum +gtsvectorout(PG_FUNCTION_ARGS) +{ + SignTSVector *key = (SignTSVector *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); + char *outbuf; + + if (outbuf_maxlen == 0) + outbuf_maxlen = 2 * EXTRALEN + Max(strlen(SINGOUTSTR), strlen(ARROUTSTR)) + 1; + outbuf = palloc(outbuf_maxlen); + + if (ISARRKEY(key)) + sprintf(outbuf, ARROUTSTR, (int) ARRNELEM(key)); + else + { + int siglen = GETSIGLEN(key); + int cnttrue = (ISALLTRUE(key)) ? SIGLENBIT(siglen) : sizebitvec(GETSIGN(key), siglen); + + sprintf(outbuf, SINGOUTSTR, cnttrue, (int) SIGLENBIT(siglen) - cnttrue); + } + + PG_FREE_IF_COPY(key, 0); + PG_RETURN_POINTER(outbuf); +} + +static int +compareint(const void *va, const void *vb) +{ + int32 a = *((const int32 *) va); + int32 b = *((const int32 *) vb); + + if (a == b) + return 0; + return (a > b) ? 1 : -1; +} + +static void +makesign(BITVECP sign, SignTSVector *a, int siglen) +{ + int32 k, + len = ARRNELEM(a); + int32 *ptr = GETARR(a); + + MemSet(sign, 0, siglen); + for (k = 0; k < len; k++) + HASH(sign, ptr[k], siglen); +} + +static SignTSVector * +gtsvector_alloc(int flag, int len, BITVECP sign) +{ + int size = CALCGTSIZE(flag, len); + SignTSVector *res = palloc(size); + + SET_VARSIZE(res, size); + res->flag = flag; + + if ((flag & (SIGNKEY | ALLISTRUE)) == SIGNKEY && sign) + memcpy(GETSIGN(res), sign, len); + + return res; +} + + +Datum +gtsvector_compress(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + int siglen = GET_SIGLEN(); + GISTENTRY *retval = entry; + + if (entry->leafkey) + { /* tsvector */ + TSVector val = DatumGetTSVector(entry->key); + SignTSVector *res = gtsvector_alloc(ARRKEY, val->size, NULL); + int32 len; + int32 *arr; + WordEntry *ptr = ARRPTR(val); + char *words = STRPTR(val); + + arr = GETARR(res); + len = val->size; + while (len--) + { + pg_crc32 c; + + INIT_LEGACY_CRC32(c); + COMP_LEGACY_CRC32(c, words + ptr->pos, ptr->len); + FIN_LEGACY_CRC32(c); + + *arr = *(int32 *) &c; + arr++; + ptr++; + } + + qsort(GETARR(res), val->size, sizeof(int), compareint); + len = qunique(GETARR(res), val->size, sizeof(int), compareint); + if (len != val->size) + { + /* + * there is a collision of hash-function; len is always less than + * val->size + */ + len = CALCGTSIZE(ARRKEY, len); + res = (SignTSVector *) repalloc(res, len); + SET_VARSIZE(res, len); + } + + /* make signature, if array is too long */ + if (VARSIZE(res) > TOAST_INDEX_TARGET) + { + SignTSVector *ressign = gtsvector_alloc(SIGNKEY, siglen, NULL); + + makesign(GETSIGN(ressign), res, siglen); + res = ressign; + } + + retval = (GISTENTRY *) palloc(sizeof(GISTENTRY)); + gistentryinit(*retval, PointerGetDatum(res), + entry->rel, entry->page, + entry->offset, false); + } + else if (ISSIGNKEY(DatumGetPointer(entry->key)) && + !ISALLTRUE(DatumGetPointer(entry->key))) + { + int32 i; + SignTSVector *res; + BITVECP sign = GETSIGN(DatumGetPointer(entry->key)); + + LOOPBYTE(siglen) + { + if ((sign[i] & 0xff) != 0xff) + PG_RETURN_POINTER(retval); + } + + res = gtsvector_alloc(SIGNKEY | ALLISTRUE, siglen, sign); + retval = (GISTENTRY *) palloc(sizeof(GISTENTRY)); + gistentryinit(*retval, PointerGetDatum(res), + entry->rel, entry->page, + entry->offset, false); + } + PG_RETURN_POINTER(retval); +} + +Datum +gtsvector_decompress(PG_FUNCTION_ARGS) +{ + /* + * We need to detoast the stored value, because the other gtsvector + * support functions don't cope with toasted values. + */ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + SignTSVector *key = (SignTSVector *) PG_DETOAST_DATUM(entry->key); + + if (key != (SignTSVector *) DatumGetPointer(entry->key)) + { + GISTENTRY *retval = (GISTENTRY *) palloc(sizeof(GISTENTRY)); + + gistentryinit(*retval, PointerGetDatum(key), + entry->rel, entry->page, + entry->offset, false); + + PG_RETURN_POINTER(retval); + } + + PG_RETURN_POINTER(entry); +} + +typedef struct +{ + int32 *arrb; + int32 *arre; +} CHKVAL; + +/* + * TS_execute callback for matching a tsquery operand to GIST leaf-page data + */ +static TSTernaryValue +checkcondition_arr(void *checkval, QueryOperand *val, ExecPhraseData *data) +{ + int32 *StopLow = ((CHKVAL *) checkval)->arrb; + int32 *StopHigh = ((CHKVAL *) checkval)->arre; + int32 *StopMiddle; + + /* Loop invariant: StopLow <= val < StopHigh */ + + /* + * we are not able to find a prefix by hash value + */ + if (val->prefix) + return TS_MAYBE; + + while (StopLow < StopHigh) + { + StopMiddle = StopLow + (StopHigh - StopLow) / 2; + if (*StopMiddle == val->valcrc) + return TS_MAYBE; + else if (*StopMiddle < val->valcrc) + StopLow = StopMiddle + 1; + else + StopHigh = StopMiddle; + } + + return TS_NO; +} + +/* + * TS_execute callback for matching a tsquery operand to GIST non-leaf data + */ +static TSTernaryValue +checkcondition_bit(void *checkval, QueryOperand *val, ExecPhraseData *data) +{ + void *key = (SignTSVector *) checkval; + + /* + * we are not able to find a prefix in signature tree + */ + if (val->prefix) + return TS_MAYBE; + + if (GETBIT(GETSIGN(key), HASHVAL(val->valcrc, GETSIGLEN(key)))) + return TS_MAYBE; + else + return TS_NO; +} + +Datum +gtsvector_consistent(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + TSQuery query = PG_GETARG_TSQUERY(1); + + /* StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2); */ + /* Oid subtype = PG_GETARG_OID(3); */ + bool *recheck = (bool *) PG_GETARG_POINTER(4); + SignTSVector *key = (SignTSVector *) DatumGetPointer(entry->key); + + /* All cases served by this function are inexact */ + *recheck = true; + + if (!query->size) + PG_RETURN_BOOL(false); + + if (ISSIGNKEY(key)) + { + if (ISALLTRUE(key)) + PG_RETURN_BOOL(true); + + PG_RETURN_BOOL(TS_execute(GETQUERY(query), + key, + TS_EXEC_PHRASE_NO_POS, + checkcondition_bit)); + } + else + { /* only leaf pages */ + CHKVAL chkval; + + chkval.arrb = GETARR(key); + chkval.arre = chkval.arrb + ARRNELEM(key); + PG_RETURN_BOOL(TS_execute(GETQUERY(query), + (void *) &chkval, + TS_EXEC_PHRASE_NO_POS, + checkcondition_arr)); + } +} + +static int32 +unionkey(BITVECP sbase, SignTSVector *add, int siglen) +{ + int32 i; + + if (ISSIGNKEY(add)) + { + BITVECP sadd = GETSIGN(add); + + if (ISALLTRUE(add)) + return 1; + + Assert(GETSIGLEN(add) == siglen); + + LOOPBYTE(siglen) + sbase[i] |= sadd[i]; + } + else + { + int32 *ptr = GETARR(add); + + for (i = 0; i < ARRNELEM(add); i++) + HASH(sbase, ptr[i], siglen); + } + return 0; +} + + +Datum +gtsvector_union(PG_FUNCTION_ARGS) +{ + GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); + int *size = (int *) PG_GETARG_POINTER(1); + int siglen = GET_SIGLEN(); + SignTSVector *result = gtsvector_alloc(SIGNKEY, siglen, NULL); + BITVECP base = GETSIGN(result); + int32 i; + + memset(base, 0, siglen); + + for (i = 0; i < entryvec->n; i++) + { + if (unionkey(base, GETENTRY(entryvec, i), siglen)) + { + result->flag |= ALLISTRUE; + SET_VARSIZE(result, CALCGTSIZE(result->flag, siglen)); + break; + } + } + + *size = VARSIZE(result); + + PG_RETURN_POINTER(result); +} + +Datum +gtsvector_same(PG_FUNCTION_ARGS) +{ + SignTSVector *a = (SignTSVector *) PG_GETARG_POINTER(0); + SignTSVector *b = (SignTSVector *) PG_GETARG_POINTER(1); + bool *result = (bool *) PG_GETARG_POINTER(2); + int siglen = GET_SIGLEN(); + + if (ISSIGNKEY(a)) + { /* then b also ISSIGNKEY */ + if (ISALLTRUE(a) && ISALLTRUE(b)) + *result = true; + else if (ISALLTRUE(a)) + *result = false; + else if (ISALLTRUE(b)) + *result = false; + else + { + int32 i; + BITVECP sa = GETSIGN(a), + sb = GETSIGN(b); + + Assert(GETSIGLEN(a) == siglen && GETSIGLEN(b) == siglen); + + *result = true; + LOOPBYTE(siglen) + { + if (sa[i] != sb[i]) + { + *result = false; + break; + } + } + } + } + else + { /* a and b ISARRKEY */ + int32 lena = ARRNELEM(a), + lenb = ARRNELEM(b); + + if (lena != lenb) + *result = false; + else + { + int32 *ptra = GETARR(a), + *ptrb = GETARR(b); + int32 i; + + *result = true; + for (i = 0; i < lena; i++) + if (ptra[i] != ptrb[i]) + { + *result = false; + break; + } + } + } + + PG_RETURN_POINTER(result); +} + +static int32 +sizebitvec(BITVECP sign, int siglen) +{ + return pg_popcount(sign, siglen); +} + +static int +hemdistsign(BITVECP a, BITVECP b, int siglen) +{ + int i, + diff, + dist = 0; + + LOOPBYTE(siglen) + { + diff = (unsigned char) (a[i] ^ b[i]); + /* Using the popcount functions here isn't likely to win */ + dist += pg_number_of_ones[diff]; + } + return dist; +} + +static int +hemdist(SignTSVector *a, SignTSVector *b) +{ + int siglena = GETSIGLEN(a); + int siglenb = GETSIGLEN(b); + + if (ISALLTRUE(a)) + { + if (ISALLTRUE(b)) + return 0; + else + return SIGLENBIT(siglenb) - sizebitvec(GETSIGN(b), siglenb); + } + else if (ISALLTRUE(b)) + return SIGLENBIT(siglena) - sizebitvec(GETSIGN(a), siglena); + + Assert(siglena == siglenb); + + return hemdistsign(GETSIGN(a), GETSIGN(b), siglena); +} + +Datum +gtsvector_penalty(PG_FUNCTION_ARGS) +{ + GISTENTRY *origentry = (GISTENTRY *) PG_GETARG_POINTER(0); /* always ISSIGNKEY */ + GISTENTRY *newentry = (GISTENTRY *) PG_GETARG_POINTER(1); + float *penalty = (float *) PG_GETARG_POINTER(2); + int siglen = GET_SIGLEN(); + SignTSVector *origval = (SignTSVector *) DatumGetPointer(origentry->key); + SignTSVector *newval = (SignTSVector *) DatumGetPointer(newentry->key); + BITVECP orig = GETSIGN(origval); + + *penalty = 0.0; + + if (ISARRKEY(newval)) + { + BITVECP sign = palloc(siglen); + + makesign(sign, newval, siglen); + + if (ISALLTRUE(origval)) + { + int siglenbit = SIGLENBIT(siglen); + + *penalty = + (float) (siglenbit - sizebitvec(sign, siglen)) / + (float) (siglenbit + 1); + } + else + *penalty = hemdistsign(sign, orig, siglen); + + pfree(sign); + } + else + *penalty = hemdist(origval, newval); + PG_RETURN_POINTER(penalty); +} + +typedef struct +{ + bool allistrue; + BITVECP sign; +} CACHESIGN; + +static void +fillcache(CACHESIGN *item, SignTSVector *key, int siglen) +{ + item->allistrue = false; + if (ISARRKEY(key)) + makesign(item->sign, key, siglen); + else if (ISALLTRUE(key)) + item->allistrue = true; + else + memcpy(item->sign, GETSIGN(key), siglen); +} + +#define WISH_F(a,b,c) (double)( -(double)(((a)-(b))*((a)-(b))*((a)-(b)))*(c) ) +typedef struct +{ + OffsetNumber pos; + int32 cost; +} SPLITCOST; + +static int +comparecost(const void *va, const void *vb) +{ + const SPLITCOST *a = (const SPLITCOST *) va; + const SPLITCOST *b = (const SPLITCOST *) vb; + + if (a->cost == b->cost) + return 0; + else + return (a->cost > b->cost) ? 1 : -1; +} + + +static int +hemdistcache(CACHESIGN *a, CACHESIGN *b, int siglen) +{ + if (a->allistrue) + { + if (b->allistrue) + return 0; + else + return SIGLENBIT(siglen) - sizebitvec(b->sign, siglen); + } + else if (b->allistrue) + return SIGLENBIT(siglen) - sizebitvec(a->sign, siglen); + + return hemdistsign(a->sign, b->sign, siglen); +} + +Datum +gtsvector_picksplit(PG_FUNCTION_ARGS) +{ + GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); + GIST_SPLITVEC *v = (GIST_SPLITVEC *) PG_GETARG_POINTER(1); + int siglen = GET_SIGLEN(); + OffsetNumber k, + j; + SignTSVector *datum_l, + *datum_r; + BITVECP union_l, + union_r; + int32 size_alpha, + size_beta; + int32 size_waste, + waste = -1; + int32 nbytes; + OffsetNumber seed_1 = 0, + seed_2 = 0; + OffsetNumber *left, + *right; + OffsetNumber maxoff; + BITVECP ptr; + int i; + CACHESIGN *cache; + char *cache_sign; + SPLITCOST *costvector; + + maxoff = entryvec->n - 2; + nbytes = (maxoff + 2) * sizeof(OffsetNumber); + v->spl_left = (OffsetNumber *) palloc(nbytes); + v->spl_right = (OffsetNumber *) palloc(nbytes); + + cache = (CACHESIGN *) palloc(sizeof(CACHESIGN) * (maxoff + 2)); + cache_sign = palloc(siglen * (maxoff + 2)); + + for (j = 0; j < maxoff + 2; j++) + cache[j].sign = &cache_sign[siglen * j]; + + fillcache(&cache[FirstOffsetNumber], GETENTRY(entryvec, FirstOffsetNumber), + siglen); + + for (k = FirstOffsetNumber; k < maxoff; k = OffsetNumberNext(k)) + { + for (j = OffsetNumberNext(k); j <= maxoff; j = OffsetNumberNext(j)) + { + if (k == FirstOffsetNumber) + fillcache(&cache[j], GETENTRY(entryvec, j), siglen); + + size_waste = hemdistcache(&(cache[j]), &(cache[k]), siglen); + if (size_waste > waste) + { + waste = size_waste; + seed_1 = k; + seed_2 = j; + } + } + } + + left = v->spl_left; + v->spl_nleft = 0; + right = v->spl_right; + v->spl_nright = 0; + + if (seed_1 == 0 || seed_2 == 0) + { + seed_1 = 1; + seed_2 = 2; + } + + /* form initial .. */ + datum_l = gtsvector_alloc(SIGNKEY | (cache[seed_1].allistrue ? ALLISTRUE : 0), + siglen, cache[seed_1].sign); + datum_r = gtsvector_alloc(SIGNKEY | (cache[seed_2].allistrue ? ALLISTRUE : 0), + siglen, cache[seed_2].sign); + union_l = GETSIGN(datum_l); + union_r = GETSIGN(datum_r); + maxoff = OffsetNumberNext(maxoff); + fillcache(&cache[maxoff], GETENTRY(entryvec, maxoff), siglen); + /* sort before ... */ + costvector = (SPLITCOST *) palloc(sizeof(SPLITCOST) * maxoff); + for (j = FirstOffsetNumber; j <= maxoff; j = OffsetNumberNext(j)) + { + costvector[j - 1].pos = j; + size_alpha = hemdistcache(&(cache[seed_1]), &(cache[j]), siglen); + size_beta = hemdistcache(&(cache[seed_2]), &(cache[j]), siglen); + costvector[j - 1].cost = abs(size_alpha - size_beta); + } + qsort(costvector, maxoff, sizeof(SPLITCOST), comparecost); + + for (k = 0; k < maxoff; k++) + { + j = costvector[k].pos; + if (j == seed_1) + { + *left++ = j; + v->spl_nleft++; + continue; + } + else if (j == seed_2) + { + *right++ = j; + v->spl_nright++; + continue; + } + + if (ISALLTRUE(datum_l) || cache[j].allistrue) + { + if (ISALLTRUE(datum_l) && cache[j].allistrue) + size_alpha = 0; + else + size_alpha = SIGLENBIT(siglen) - + sizebitvec((cache[j].allistrue) ? + GETSIGN(datum_l) : + cache[j].sign, + siglen); + } + else + size_alpha = hemdistsign(cache[j].sign, GETSIGN(datum_l), siglen); + + if (ISALLTRUE(datum_r) || cache[j].allistrue) + { + if (ISALLTRUE(datum_r) && cache[j].allistrue) + size_beta = 0; + else + size_beta = SIGLENBIT(siglen) - + sizebitvec((cache[j].allistrue) ? + GETSIGN(datum_r) : + cache[j].sign, + siglen); + } + else + size_beta = hemdistsign(cache[j].sign, GETSIGN(datum_r), siglen); + + if (size_alpha < size_beta + WISH_F(v->spl_nleft, v->spl_nright, 0.1)) + { + if (ISALLTRUE(datum_l) || cache[j].allistrue) + { + if (!ISALLTRUE(datum_l)) + memset(GETSIGN(datum_l), 0xff, siglen); + } + else + { + ptr = cache[j].sign; + LOOPBYTE(siglen) + union_l[i] |= ptr[i]; + } + *left++ = j; + v->spl_nleft++; + } + else + { + if (ISALLTRUE(datum_r) || cache[j].allistrue) + { + if (!ISALLTRUE(datum_r)) + memset(GETSIGN(datum_r), 0xff, siglen); + } + else + { + ptr = cache[j].sign; + LOOPBYTE(siglen) + union_r[i] |= ptr[i]; + } + *right++ = j; + v->spl_nright++; + } + } + + *right = *left = FirstOffsetNumber; + v->spl_ldatum = PointerGetDatum(datum_l); + v->spl_rdatum = PointerGetDatum(datum_r); + + PG_RETURN_POINTER(v); +} + +/* + * Formerly, gtsvector_consistent was declared in pg_proc.h with arguments + * that did not match the documented conventions for GiST support functions. + * We fixed that, but we still need a pg_proc entry with the old signature + * to support reloading pre-9.6 contrib/tsearch2 opclass declarations. + * This compatibility function should go away eventually. + */ +Datum +gtsvector_consistent_oldsig(PG_FUNCTION_ARGS) +{ + return gtsvector_consistent(fcinfo); +} + +Datum +gtsvector_options(PG_FUNCTION_ARGS) +{ + local_relopts *relopts = (local_relopts *) PG_GETARG_POINTER(0); + + init_local_reloptions(relopts, sizeof(GistTsVectorOptions)); + add_local_int_reloption(relopts, "siglen", "signature length", + SIGLEN_DEFAULT, 1, SIGLEN_MAX, + offsetof(GistTsVectorOptions, siglen)); + + PG_RETURN_VOID(); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/tsquery.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/tsquery.c new file mode 100644 index 00000000000..67ad876a27c --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/tsquery.c @@ -0,0 +1,1402 @@ +/*------------------------------------------------------------------------- + * + * tsquery.c + * I/O functions for tsquery + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/utils/adt/tsquery.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "libpq/pqformat.h" +#include "miscadmin.h" +#include "nodes/miscnodes.h" +#include "tsearch/ts_locale.h" +#include "tsearch/ts_type.h" +#include "tsearch/ts_utils.h" +#include "utils/builtins.h" +#include "utils/memutils.h" +#include "utils/pg_crc.h" +#include "varatt.h" + +/* FTS operator priorities, see ts_type.h */ +const int tsearch_op_priority[OP_COUNT] = +{ + 4, /* OP_NOT */ + 2, /* OP_AND */ + 1, /* OP_OR */ + 3 /* OP_PHRASE */ +}; + +/* + * parser's states + */ +typedef enum +{ + WAITOPERAND = 1, + WAITOPERATOR = 2, + WAITFIRSTOPERAND = 3 +} ts_parserstate; + +/* + * token types for parsing + */ +typedef enum +{ + PT_END = 0, + PT_ERR = 1, + PT_VAL = 2, + PT_OPR = 3, + PT_OPEN = 4, + PT_CLOSE = 5 +} ts_tokentype; + +/* + * get token from query string + * + * All arguments except "state" are output arguments. + * + * If return value is PT_OPR, then *operator is filled with an OP_* code + * and *weight will contain a distance value in case of phrase operator. + * + * If return value is PT_VAL, then *lenval, *strval, *weight, and *prefix + * are filled. + * + * If PT_ERR is returned then a soft error has occurred. If state->escontext + * isn't already filled then this should be reported as a generic parse error. + */ +typedef ts_tokentype (*ts_tokenizer) (TSQueryParserState state, int8 *operator, + int *lenval, char **strval, + int16 *weight, bool *prefix); + +struct TSQueryParserStateData +{ + /* Tokenizer used for parsing tsquery */ + ts_tokenizer gettoken; + + /* State of tokenizer function */ + char *buffer; /* entire string we are scanning */ + char *buf; /* current scan point */ + int count; /* nesting count, incremented by (, + * decremented by ) */ + ts_parserstate state; + + /* polish (prefix) notation in list, filled in by push* functions */ + List *polstr; + + /* + * Strings from operands are collected in op. curop is a pointer to the + * end of used space of op. + */ + char *op; + char *curop; + int lenop; /* allocated size of op */ + int sumlen; /* used size of op */ + + /* state for value's parser */ + TSVectorParseState valstate; + + /* context object for soft errors - must match valstate's escontext */ + Node *escontext; +}; + +/* + * subroutine to parse the modifiers (weight and prefix flag currently) + * part, like ':AB*' of a query. + */ +static char * +get_modifiers(char *buf, int16 *weight, bool *prefix) +{ + *weight = 0; + *prefix = false; + + if (!t_iseq(buf, ':')) + return buf; + + buf++; + while (*buf && pg_mblen(buf) == 1) + { + switch (*buf) + { + case 'a': + case 'A': + *weight |= 1 << 3; + break; + case 'b': + case 'B': + *weight |= 1 << 2; + break; + case 'c': + case 'C': + *weight |= 1 << 1; + break; + case 'd': + case 'D': + *weight |= 1; + break; + case '*': + *prefix = true; + break; + default: + return buf; + } + buf++; + } + + return buf; +} + +/* + * Parse phrase operator. The operator + * may take the following forms: + * + * a <N> b (distance is exactly N lexemes) + * a <-> b (default distance = 1) + * + * The buffer should begin with '<' char + */ +static bool +parse_phrase_operator(TSQueryParserState pstate, int16 *distance) +{ + enum + { + PHRASE_OPEN = 0, + PHRASE_DIST, + PHRASE_CLOSE, + PHRASE_FINISH + } state = PHRASE_OPEN; + char *ptr = pstate->buf; + char *endptr; + long l = 1; /* default distance */ + + while (*ptr) + { + switch (state) + { + case PHRASE_OPEN: + if (t_iseq(ptr, '<')) + { + state = PHRASE_DIST; + ptr++; + } + else + return false; + break; + + case PHRASE_DIST: + if (t_iseq(ptr, '-')) + { + state = PHRASE_CLOSE; + ptr++; + continue; + } + + if (!t_isdigit(ptr)) + return false; + + errno = 0; + l = strtol(ptr, &endptr, 10); + if (ptr == endptr) + return false; + else if (errno == ERANGE || l < 0 || l > MAXENTRYPOS) + ereturn(pstate->escontext, false, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("distance in phrase operator must be an integer value between zero and %d inclusive", + MAXENTRYPOS))); + else + { + state = PHRASE_CLOSE; + ptr = endptr; + } + break; + + case PHRASE_CLOSE: + if (t_iseq(ptr, '>')) + { + state = PHRASE_FINISH; + ptr++; + } + else + return false; + break; + + case PHRASE_FINISH: + *distance = (int16) l; + pstate->buf = ptr; + return true; + } + } + + return false; +} + +/* + * Parse OR operator used in websearch_to_tsquery(), returns true if we + * believe that "OR" literal could be an operator OR + */ +static bool +parse_or_operator(TSQueryParserState pstate) +{ + char *ptr = pstate->buf; + + /* it should begin with "OR" literal */ + if (pg_strncasecmp(ptr, "or", 2) != 0) + return false; + + ptr += 2; + + /* + * it shouldn't be a part of any word but somewhere later it should be + * some operand + */ + if (*ptr == '\0') /* no operand */ + return false; + + /* it shouldn't be a part of any word */ + if (t_iseq(ptr, '-') || t_iseq(ptr, '_') || t_isalnum(ptr)) + return false; + + for (;;) + { + ptr += pg_mblen(ptr); + + if (*ptr == '\0') /* got end of string without operand */ + return false; + + /* + * Suppose, we found an operand, but could be a not correct operand. + * So we still treat OR literal as operation with possibly incorrect + * operand and will not search it as lexeme + */ + if (!t_isspace(ptr)) + break; + } + + pstate->buf += 2; + return true; +} + +static ts_tokentype +gettoken_query_standard(TSQueryParserState state, int8 *operator, + int *lenval, char **strval, + int16 *weight, bool *prefix) +{ + *weight = 0; + *prefix = false; + + while (true) + { + switch (state->state) + { + case WAITFIRSTOPERAND: + case WAITOPERAND: + if (t_iseq(state->buf, '!')) + { + state->buf++; + state->state = WAITOPERAND; + *operator = OP_NOT; + return PT_OPR; + } + else if (t_iseq(state->buf, '(')) + { + state->buf++; + state->state = WAITOPERAND; + state->count++; + return PT_OPEN; + } + else if (t_iseq(state->buf, ':')) + { + /* generic syntax error message is fine */ + return PT_ERR; + } + else if (!t_isspace(state->buf)) + { + /* + * We rely on the tsvector parser to parse the value for + * us + */ + reset_tsvector_parser(state->valstate, state->buf); + if (gettoken_tsvector(state->valstate, strval, lenval, + NULL, NULL, &state->buf)) + { + state->buf = get_modifiers(state->buf, weight, prefix); + state->state = WAITOPERATOR; + return PT_VAL; + } + else if (SOFT_ERROR_OCCURRED(state->escontext)) + { + /* gettoken_tsvector reported a soft error */ + return PT_ERR; + } + else if (state->state == WAITFIRSTOPERAND) + { + return PT_END; + } + else + ereturn(state->escontext, PT_ERR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("no operand in tsquery: \"%s\"", + state->buffer))); + } + break; + + case WAITOPERATOR: + if (t_iseq(state->buf, '&')) + { + state->buf++; + state->state = WAITOPERAND; + *operator = OP_AND; + return PT_OPR; + } + else if (t_iseq(state->buf, '|')) + { + state->buf++; + state->state = WAITOPERAND; + *operator = OP_OR; + return PT_OPR; + } + else if (parse_phrase_operator(state, weight)) + { + /* weight var is used as storage for distance */ + state->state = WAITOPERAND; + *operator = OP_PHRASE; + return PT_OPR; + } + else if (SOFT_ERROR_OCCURRED(state->escontext)) + { + /* parse_phrase_operator reported a soft error */ + return PT_ERR; + } + else if (t_iseq(state->buf, ')')) + { + state->buf++; + state->count--; + return (state->count < 0) ? PT_ERR : PT_CLOSE; + } + else if (*state->buf == '\0') + { + return (state->count) ? PT_ERR : PT_END; + } + else if (!t_isspace(state->buf)) + { + return PT_ERR; + } + break; + } + + state->buf += pg_mblen(state->buf); + } +} + +static ts_tokentype +gettoken_query_websearch(TSQueryParserState state, int8 *operator, + int *lenval, char **strval, + int16 *weight, bool *prefix) +{ + *weight = 0; + *prefix = false; + + while (true) + { + switch (state->state) + { + case WAITFIRSTOPERAND: + case WAITOPERAND: + if (t_iseq(state->buf, '-')) + { + state->buf++; + state->state = WAITOPERAND; + + *operator = OP_NOT; + return PT_OPR; + } + else if (t_iseq(state->buf, '"')) + { + /* Everything in quotes is processed as a single token */ + + /* skip opening quote */ + state->buf++; + *strval = state->buf; + + /* iterate to the closing quote or end of the string */ + while (*state->buf != '\0' && !t_iseq(state->buf, '"')) + state->buf++; + *lenval = state->buf - *strval; + + /* skip closing quote if not end of the string */ + if (*state->buf != '\0') + state->buf++; + + state->state = WAITOPERATOR; + state->count++; + return PT_VAL; + } + else if (ISOPERATOR(state->buf)) + { + /* or else gettoken_tsvector() will raise an error */ + state->buf++; + state->state = WAITOPERAND; + continue; + } + else if (!t_isspace(state->buf)) + { + /* + * We rely on the tsvector parser to parse the value for + * us + */ + reset_tsvector_parser(state->valstate, state->buf); + if (gettoken_tsvector(state->valstate, strval, lenval, + NULL, NULL, &state->buf)) + { + state->state = WAITOPERATOR; + return PT_VAL; + } + else if (SOFT_ERROR_OCCURRED(state->escontext)) + { + /* gettoken_tsvector reported a soft error */ + return PT_ERR; + } + else if (state->state == WAITFIRSTOPERAND) + { + return PT_END; + } + else + { + /* finally, we have to provide an operand */ + pushStop(state); + return PT_END; + } + } + break; + + case WAITOPERATOR: + if (t_iseq(state->buf, '"')) + { + /* + * put implicit AND after an operand and handle this quote + * in WAITOPERAND + */ + state->state = WAITOPERAND; + *operator = OP_AND; + return PT_OPR; + } + else if (parse_or_operator(state)) + { + state->state = WAITOPERAND; + *operator = OP_OR; + return PT_OPR; + } + else if (*state->buf == '\0') + { + return PT_END; + } + else if (!t_isspace(state->buf)) + { + /* put implicit AND after an operand */ + *operator = OP_AND; + state->state = WAITOPERAND; + return PT_OPR; + } + break; + } + + state->buf += pg_mblen(state->buf); + } +} + +static ts_tokentype +gettoken_query_plain(TSQueryParserState state, int8 *operator, + int *lenval, char **strval, + int16 *weight, bool *prefix) +{ + *weight = 0; + *prefix = false; + + if (*state->buf == '\0') + return PT_END; + + *strval = state->buf; + *lenval = strlen(state->buf); + state->buf += *lenval; + state->count++; + return PT_VAL; +} + +/* + * Push an operator to state->polstr + */ +void +pushOperator(TSQueryParserState state, int8 oper, int16 distance) +{ + QueryOperator *tmp; + + Assert(oper == OP_NOT || oper == OP_AND || oper == OP_OR || oper == OP_PHRASE); + + tmp = (QueryOperator *) palloc0(sizeof(QueryOperator)); + tmp->type = QI_OPR; + tmp->oper = oper; + tmp->distance = (oper == OP_PHRASE) ? distance : 0; + /* left is filled in later with findoprnd */ + + state->polstr = lcons(tmp, state->polstr); +} + +static void +pushValue_internal(TSQueryParserState state, pg_crc32 valcrc, int distance, int lenval, int weight, bool prefix) +{ + QueryOperand *tmp; + + if (distance >= MAXSTRPOS) + ereturn(state->escontext,, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("value is too big in tsquery: \"%s\"", + state->buffer))); + if (lenval >= MAXSTRLEN) + ereturn(state->escontext,, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("operand is too long in tsquery: \"%s\"", + state->buffer))); + + tmp = (QueryOperand *) palloc0(sizeof(QueryOperand)); + tmp->type = QI_VAL; + tmp->weight = weight; + tmp->prefix = prefix; + tmp->valcrc = (int32) valcrc; + tmp->length = lenval; + tmp->distance = distance; + + state->polstr = lcons(tmp, state->polstr); +} + +/* + * Push an operand to state->polstr. + * + * strval must point to a string equal to state->curop. lenval is the length + * of the string. + */ +void +pushValue(TSQueryParserState state, char *strval, int lenval, int16 weight, bool prefix) +{ + pg_crc32 valcrc; + + if (lenval >= MAXSTRLEN) + ereturn(state->escontext,, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("word is too long in tsquery: \"%s\"", + state->buffer))); + + INIT_LEGACY_CRC32(valcrc); + COMP_LEGACY_CRC32(valcrc, strval, lenval); + FIN_LEGACY_CRC32(valcrc); + pushValue_internal(state, valcrc, state->curop - state->op, lenval, weight, prefix); + + /* append the value string to state.op, enlarging buffer if needed first */ + while (state->curop - state->op + lenval + 1 >= state->lenop) + { + int used = state->curop - state->op; + + state->lenop *= 2; + state->op = (char *) repalloc(state->op, state->lenop); + state->curop = state->op + used; + } + memcpy(state->curop, strval, lenval); + state->curop += lenval; + *(state->curop) = '\0'; + state->curop++; + state->sumlen += lenval + 1 /* \0 */ ; +} + + +/* + * Push a stopword placeholder to state->polstr + */ +void +pushStop(TSQueryParserState state) +{ + QueryOperand *tmp; + + tmp = (QueryOperand *) palloc0(sizeof(QueryOperand)); + tmp->type = QI_VALSTOP; + + state->polstr = lcons(tmp, state->polstr); +} + + +#define STACKDEPTH 32 + +typedef struct OperatorElement +{ + int8 op; + int16 distance; +} OperatorElement; + +static void +pushOpStack(OperatorElement *stack, int *lenstack, int8 op, int16 distance) +{ + if (*lenstack == STACKDEPTH) /* internal error */ + elog(ERROR, "tsquery stack too small"); + + stack[*lenstack].op = op; + stack[*lenstack].distance = distance; + + (*lenstack)++; +} + +static void +cleanOpStack(TSQueryParserState state, + OperatorElement *stack, int *lenstack, int8 op) +{ + int opPriority = OP_PRIORITY(op); + + while (*lenstack) + { + /* NOT is right associative unlike to others */ + if ((op != OP_NOT && opPriority > OP_PRIORITY(stack[*lenstack - 1].op)) || + (op == OP_NOT && opPriority >= OP_PRIORITY(stack[*lenstack - 1].op))) + break; + + (*lenstack)--; + pushOperator(state, stack[*lenstack].op, + stack[*lenstack].distance); + } +} + +/* + * Make polish (prefix) notation of query. + * + * See parse_tsquery for explanation of pushval. + */ +static void +makepol(TSQueryParserState state, + PushFunction pushval, + Datum opaque) +{ + int8 operator = 0; + ts_tokentype type; + int lenval = 0; + char *strval = NULL; + OperatorElement opstack[STACKDEPTH]; + int lenstack = 0; + int16 weight = 0; + bool prefix; + + /* since this function recurses, it could be driven to stack overflow */ + check_stack_depth(); + + while ((type = state->gettoken(state, &operator, + &lenval, &strval, + &weight, &prefix)) != PT_END) + { + switch (type) + { + case PT_VAL: + pushval(opaque, state, strval, lenval, weight, prefix); + break; + case PT_OPR: + cleanOpStack(state, opstack, &lenstack, operator); + pushOpStack(opstack, &lenstack, operator, weight); + break; + case PT_OPEN: + makepol(state, pushval, opaque); + break; + case PT_CLOSE: + cleanOpStack(state, opstack, &lenstack, OP_OR /* lowest */ ); + return; + case PT_ERR: + default: + /* don't overwrite a soft error saved by gettoken function */ + if (!SOFT_ERROR_OCCURRED(state->escontext)) + errsave(state->escontext, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("syntax error in tsquery: \"%s\"", + state->buffer))); + return; + } + /* detect soft error in pushval or recursion */ + if (SOFT_ERROR_OCCURRED(state->escontext)) + return; + } + + cleanOpStack(state, opstack, &lenstack, OP_OR /* lowest */ ); +} + +static void +findoprnd_recurse(QueryItem *ptr, uint32 *pos, int nnodes, bool *needcleanup) +{ + /* since this function recurses, it could be driven to stack overflow. */ + check_stack_depth(); + + if (*pos >= nnodes) + elog(ERROR, "malformed tsquery: operand not found"); + + if (ptr[*pos].type == QI_VAL) + { + (*pos)++; + } + else if (ptr[*pos].type == QI_VALSTOP) + { + *needcleanup = true; /* we'll have to remove stop words */ + (*pos)++; + } + else + { + Assert(ptr[*pos].type == QI_OPR); + + if (ptr[*pos].qoperator.oper == OP_NOT) + { + ptr[*pos].qoperator.left = 1; /* fixed offset */ + (*pos)++; + + /* process the only argument */ + findoprnd_recurse(ptr, pos, nnodes, needcleanup); + } + else + { + QueryOperator *curitem = &ptr[*pos].qoperator; + int tmp = *pos; /* save current position */ + + Assert(curitem->oper == OP_AND || + curitem->oper == OP_OR || + curitem->oper == OP_PHRASE); + + (*pos)++; + + /* process RIGHT argument */ + findoprnd_recurse(ptr, pos, nnodes, needcleanup); + + curitem->left = *pos - tmp; /* set LEFT arg's offset */ + + /* process LEFT argument */ + findoprnd_recurse(ptr, pos, nnodes, needcleanup); + } + } +} + + +/* + * Fill in the left-fields previously left unfilled. + * The input QueryItems must be in polish (prefix) notation. + * Also, set *needcleanup to true if there are any QI_VALSTOP nodes. + */ +static void +findoprnd(QueryItem *ptr, int size, bool *needcleanup) +{ + uint32 pos; + + *needcleanup = false; + pos = 0; + findoprnd_recurse(ptr, &pos, size, needcleanup); + + if (pos != size) + elog(ERROR, "malformed tsquery: extra nodes"); +} + + +/* + * Parse the tsquery stored in "buf". + * + * Each value (operand) in the query is passed to pushval. pushval can + * transform the simple value to an arbitrarily complex expression using + * pushValue and pushOperator. It must push a single value with pushValue, + * a complete expression with all operands, or a stopword placeholder + * with pushStop, otherwise the prefix notation representation will be broken, + * having an operator with no operand. + * + * opaque is passed on to pushval as is, pushval can use it to store its + * private state. + * + * The pushval function can record soft errors via escontext. + * Callers must check SOFT_ERROR_OCCURRED to detect that. + * + * A bitmask of flags (see ts_utils.h) and an error context object + * can be provided as well. If a soft error occurs, NULL is returned. + */ +TSQuery +parse_tsquery(char *buf, + PushFunction pushval, + Datum opaque, + int flags, + Node *escontext) +{ + struct TSQueryParserStateData state; + int i; + TSQuery query; + int commonlen; + QueryItem *ptr; + ListCell *cell; + bool noisy; + bool needcleanup; + int tsv_flags = P_TSV_OPR_IS_DELIM | P_TSV_IS_TSQUERY; + + /* plain should not be used with web */ + Assert((flags & (P_TSQ_PLAIN | P_TSQ_WEB)) != (P_TSQ_PLAIN | P_TSQ_WEB)); + + /* select suitable tokenizer */ + if (flags & P_TSQ_PLAIN) + state.gettoken = gettoken_query_plain; + else if (flags & P_TSQ_WEB) + { + state.gettoken = gettoken_query_websearch; + tsv_flags |= P_TSV_IS_WEB; + } + else + state.gettoken = gettoken_query_standard; + + /* emit nuisance NOTICEs only if not doing soft errors */ + noisy = !(escontext && IsA(escontext, ErrorSaveContext)); + + /* init state */ + state.buffer = buf; + state.buf = buf; + state.count = 0; + state.state = WAITFIRSTOPERAND; + state.polstr = NIL; + state.escontext = escontext; + + /* init value parser's state */ + state.valstate = init_tsvector_parser(state.buffer, tsv_flags, escontext); + + /* init list of operand */ + state.sumlen = 0; + state.lenop = 64; + state.curop = state.op = (char *) palloc(state.lenop); + *(state.curop) = '\0'; + + /* parse query & make polish notation (postfix, but in reverse order) */ + makepol(&state, pushval, opaque); + + close_tsvector_parser(state.valstate); + + if (SOFT_ERROR_OCCURRED(escontext)) + return NULL; + + if (state.polstr == NIL) + { + if (noisy) + ereport(NOTICE, + (errmsg("text-search query doesn't contain lexemes: \"%s\"", + state.buffer))); + query = (TSQuery) palloc(HDRSIZETQ); + SET_VARSIZE(query, HDRSIZETQ); + query->size = 0; + return query; + } + + if (TSQUERY_TOO_BIG(list_length(state.polstr), state.sumlen)) + ereturn(escontext, NULL, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("tsquery is too large"))); + commonlen = COMPUTESIZE(list_length(state.polstr), state.sumlen); + + /* Pack the QueryItems in the final TSQuery struct to return to caller */ + query = (TSQuery) palloc0(commonlen); + SET_VARSIZE(query, commonlen); + query->size = list_length(state.polstr); + ptr = GETQUERY(query); + + /* Copy QueryItems to TSQuery */ + i = 0; + foreach(cell, state.polstr) + { + QueryItem *item = (QueryItem *) lfirst(cell); + + switch (item->type) + { + case QI_VAL: + memcpy(&ptr[i], item, sizeof(QueryOperand)); + break; + case QI_VALSTOP: + ptr[i].type = QI_VALSTOP; + break; + case QI_OPR: + memcpy(&ptr[i], item, sizeof(QueryOperator)); + break; + default: + elog(ERROR, "unrecognized QueryItem type: %d", item->type); + } + i++; + } + + /* Copy all the operand strings to TSQuery */ + memcpy(GETOPERAND(query), state.op, state.sumlen); + pfree(state.op); + + /* + * Set left operand pointers for every operator. While we're at it, + * detect whether there are any QI_VALSTOP nodes. + */ + findoprnd(ptr, query->size, &needcleanup); + + /* + * If there are QI_VALSTOP nodes, delete them and simplify the tree. + */ + if (needcleanup) + query = cleanup_tsquery_stopwords(query, noisy); + + return query; +} + +static void +pushval_asis(Datum opaque, TSQueryParserState state, char *strval, int lenval, + int16 weight, bool prefix) +{ + pushValue(state, strval, lenval, weight, prefix); +} + +/* + * in without morphology + */ +Datum +tsqueryin(PG_FUNCTION_ARGS) +{ + char *in = PG_GETARG_CSTRING(0); + Node *escontext = fcinfo->context; + + PG_RETURN_TSQUERY(parse_tsquery(in, + pushval_asis, + PointerGetDatum(NULL), + 0, + escontext)); +} + +/* + * out function + */ +typedef struct +{ + QueryItem *curpol; + char *buf; + char *cur; + char *op; + int buflen; +} INFIX; + +/* Makes sure inf->buf is large enough for adding 'addsize' bytes */ +#define RESIZEBUF(inf, addsize) \ +while( ( (inf)->cur - (inf)->buf ) + (addsize) + 1 >= (inf)->buflen ) \ +{ \ + int len = (inf)->cur - (inf)->buf; \ + (inf)->buflen *= 2; \ + (inf)->buf = (char*) repalloc( (void*)(inf)->buf, (inf)->buflen ); \ + (inf)->cur = (inf)->buf + len; \ +} + +/* + * recursively traverse the tree and + * print it in infix (human-readable) form + */ +static void +infix(INFIX *in, int parentPriority, bool rightPhraseOp) +{ + /* since this function recurses, it could be driven to stack overflow. */ + check_stack_depth(); + + if (in->curpol->type == QI_VAL) + { + QueryOperand *curpol = &in->curpol->qoperand; + char *op = in->op + curpol->distance; + int clen; + + RESIZEBUF(in, curpol->length * (pg_database_encoding_max_length() + 1) + 2 + 6); + *(in->cur) = '\''; + in->cur++; + while (*op) + { + if (t_iseq(op, '\'')) + { + *(in->cur) = '\''; + in->cur++; + } + else if (t_iseq(op, '\\')) + { + *(in->cur) = '\\'; + in->cur++; + } + COPYCHAR(in->cur, op); + + clen = pg_mblen(op); + op += clen; + in->cur += clen; + } + *(in->cur) = '\''; + in->cur++; + if (curpol->weight || curpol->prefix) + { + *(in->cur) = ':'; + in->cur++; + if (curpol->prefix) + { + *(in->cur) = '*'; + in->cur++; + } + if (curpol->weight & (1 << 3)) + { + *(in->cur) = 'A'; + in->cur++; + } + if (curpol->weight & (1 << 2)) + { + *(in->cur) = 'B'; + in->cur++; + } + if (curpol->weight & (1 << 1)) + { + *(in->cur) = 'C'; + in->cur++; + } + if (curpol->weight & 1) + { + *(in->cur) = 'D'; + in->cur++; + } + } + *(in->cur) = '\0'; + in->curpol++; + } + else if (in->curpol->qoperator.oper == OP_NOT) + { + int priority = QO_PRIORITY(in->curpol); + + if (priority < parentPriority) + { + RESIZEBUF(in, 2); + sprintf(in->cur, "( "); + in->cur = strchr(in->cur, '\0'); + } + RESIZEBUF(in, 1); + *(in->cur) = '!'; + in->cur++; + *(in->cur) = '\0'; + in->curpol++; + + infix(in, priority, false); + if (priority < parentPriority) + { + RESIZEBUF(in, 2); + sprintf(in->cur, " )"); + in->cur = strchr(in->cur, '\0'); + } + } + else + { + int8 op = in->curpol->qoperator.oper; + int priority = QO_PRIORITY(in->curpol); + int16 distance = in->curpol->qoperator.distance; + INFIX nrm; + bool needParenthesis = false; + + in->curpol++; + if (priority < parentPriority || + /* phrase operator depends on order */ + (op == OP_PHRASE && rightPhraseOp)) + { + needParenthesis = true; + RESIZEBUF(in, 2); + sprintf(in->cur, "( "); + in->cur = strchr(in->cur, '\0'); + } + + nrm.curpol = in->curpol; + nrm.op = in->op; + nrm.buflen = 16; + nrm.cur = nrm.buf = (char *) palloc(sizeof(char) * nrm.buflen); + + /* get right operand */ + infix(&nrm, priority, (op == OP_PHRASE)); + + /* get & print left operand */ + in->curpol = nrm.curpol; + infix(in, priority, false); + + /* print operator & right operand */ + RESIZEBUF(in, 3 + (2 + 10 /* distance */ ) + (nrm.cur - nrm.buf)); + switch (op) + { + case OP_OR: + sprintf(in->cur, " | %s", nrm.buf); + break; + case OP_AND: + sprintf(in->cur, " & %s", nrm.buf); + break; + case OP_PHRASE: + if (distance != 1) + sprintf(in->cur, " <%d> %s", distance, nrm.buf); + else + sprintf(in->cur, " <-> %s", nrm.buf); + break; + default: + /* OP_NOT is handled in above if-branch */ + elog(ERROR, "unrecognized operator type: %d", op); + } + in->cur = strchr(in->cur, '\0'); + pfree(nrm.buf); + + if (needParenthesis) + { + RESIZEBUF(in, 2); + sprintf(in->cur, " )"); + in->cur = strchr(in->cur, '\0'); + } + } +} + +Datum +tsqueryout(PG_FUNCTION_ARGS) +{ + TSQuery query = PG_GETARG_TSQUERY(0); + INFIX nrm; + + if (query->size == 0) + { + char *b = palloc(1); + + *b = '\0'; + PG_RETURN_POINTER(b); + } + nrm.curpol = GETQUERY(query); + nrm.buflen = 32; + nrm.cur = nrm.buf = (char *) palloc(sizeof(char) * nrm.buflen); + *(nrm.cur) = '\0'; + nrm.op = GETOPERAND(query); + infix(&nrm, -1 /* lowest priority */ , false); + + PG_FREE_IF_COPY(query, 0); + PG_RETURN_CSTRING(nrm.buf); +} + +/* + * Binary Input / Output functions. The binary format is as follows: + * + * uint32 number of operators/operands in the query + * + * Followed by the operators and operands, in prefix notation. For each + * operand: + * + * uint8 type, QI_VAL + * uint8 weight + * operand text in client encoding, null-terminated + * uint8 prefix + * + * For each operator: + * uint8 type, QI_OPR + * uint8 operator, one of OP_AND, OP_PHRASE OP_OR, OP_NOT. + * uint16 distance (only for OP_PHRASE) + */ +Datum +tsquerysend(PG_FUNCTION_ARGS) +{ + TSQuery query = PG_GETARG_TSQUERY(0); + StringInfoData buf; + int i; + QueryItem *item = GETQUERY(query); + + pq_begintypsend(&buf); + + pq_sendint32(&buf, query->size); + for (i = 0; i < query->size; i++) + { + pq_sendint8(&buf, item->type); + + switch (item->type) + { + case QI_VAL: + pq_sendint8(&buf, item->qoperand.weight); + pq_sendint8(&buf, item->qoperand.prefix); + pq_sendstring(&buf, GETOPERAND(query) + item->qoperand.distance); + break; + case QI_OPR: + pq_sendint8(&buf, item->qoperator.oper); + if (item->qoperator.oper == OP_PHRASE) + pq_sendint16(&buf, item->qoperator.distance); + break; + default: + elog(ERROR, "unrecognized tsquery node type: %d", item->type); + } + item++; + } + + PG_FREE_IF_COPY(query, 0); + + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} + +Datum +tsqueryrecv(PG_FUNCTION_ARGS) +{ + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + TSQuery query; + int i, + len; + QueryItem *item; + int datalen; + char *ptr; + uint32 size; + const char **operands; + bool needcleanup; + + size = pq_getmsgint(buf, sizeof(uint32)); + if (size > (MaxAllocSize / sizeof(QueryItem))) + elog(ERROR, "invalid size of tsquery"); + + /* Allocate space to temporarily hold operand strings */ + operands = palloc(size * sizeof(char *)); + + /* Allocate space for all the QueryItems. */ + len = HDRSIZETQ + sizeof(QueryItem) * size; + query = (TSQuery) palloc0(len); + query->size = size; + item = GETQUERY(query); + + datalen = 0; + for (i = 0; i < size; i++) + { + item->type = (int8) pq_getmsgint(buf, sizeof(int8)); + + if (item->type == QI_VAL) + { + size_t val_len; /* length after recoding to server + * encoding */ + uint8 weight; + uint8 prefix; + const char *val; + pg_crc32 valcrc; + + weight = (uint8) pq_getmsgint(buf, sizeof(uint8)); + prefix = (uint8) pq_getmsgint(buf, sizeof(uint8)); + val = pq_getmsgstring(buf); + val_len = strlen(val); + + /* Sanity checks */ + + if (weight > 0xF) + elog(ERROR, "invalid tsquery: invalid weight bitmap"); + + if (val_len > MAXSTRLEN) + elog(ERROR, "invalid tsquery: operand too long"); + + if (datalen > MAXSTRPOS) + elog(ERROR, "invalid tsquery: total operand length exceeded"); + + /* Looks valid. */ + + INIT_LEGACY_CRC32(valcrc); + COMP_LEGACY_CRC32(valcrc, val, val_len); + FIN_LEGACY_CRC32(valcrc); + + item->qoperand.weight = weight; + item->qoperand.prefix = (prefix) ? true : false; + item->qoperand.valcrc = (int32) valcrc; + item->qoperand.length = val_len; + item->qoperand.distance = datalen; + + /* + * Operand strings are copied to the final struct after this loop; + * here we just collect them to an array + */ + operands[i] = val; + + datalen += val_len + 1; /* + 1 for the '\0' terminator */ + } + else if (item->type == QI_OPR) + { + int8 oper; + + oper = (int8) pq_getmsgint(buf, sizeof(int8)); + if (oper != OP_NOT && oper != OP_OR && oper != OP_AND && oper != OP_PHRASE) + elog(ERROR, "invalid tsquery: unrecognized operator type %d", + (int) oper); + if (i == size - 1) + elog(ERROR, "invalid pointer to right operand"); + + item->qoperator.oper = oper; + if (oper == OP_PHRASE) + item->qoperator.distance = (int16) pq_getmsgint(buf, sizeof(int16)); + } + else + elog(ERROR, "unrecognized tsquery node type: %d", item->type); + + item++; + } + + /* Enlarge buffer to make room for the operand values. */ + query = (TSQuery) repalloc(query, len + datalen); + item = GETQUERY(query); + ptr = GETOPERAND(query); + + /* + * Fill in the left-pointers. Checks that the tree is well-formed as a + * side-effect. + */ + findoprnd(item, size, &needcleanup); + + /* Can't have found any QI_VALSTOP nodes */ + Assert(!needcleanup); + + /* Copy operands to output struct */ + for (i = 0; i < size; i++) + { + if (item->type == QI_VAL) + { + memcpy(ptr, operands[i], item->qoperand.length + 1); + ptr += item->qoperand.length + 1; + } + item++; + } + + pfree(operands); + + Assert(ptr - GETOPERAND(query) == datalen); + + SET_VARSIZE(query, len + datalen); + + PG_RETURN_TSQUERY(query); +} + +/* + * debug function, used only for view query + * which will be executed in non-leaf pages in index + */ +Datum +tsquerytree(PG_FUNCTION_ARGS) +{ + TSQuery query = PG_GETARG_TSQUERY(0); + INFIX nrm; + text *res; + QueryItem *q; + int len; + + if (query->size == 0) + { + res = (text *) palloc(VARHDRSZ); + SET_VARSIZE(res, VARHDRSZ); + PG_RETURN_POINTER(res); + } + + q = clean_NOT(GETQUERY(query), &len); + + if (!q) + { + res = cstring_to_text("T"); + } + else + { + nrm.curpol = q; + nrm.buflen = 32; + nrm.cur = nrm.buf = (char *) palloc(sizeof(char) * nrm.buflen); + *(nrm.cur) = '\0'; + nrm.op = GETOPERAND(query); + infix(&nrm, -1, false); + res = cstring_to_text_with_len(nrm.buf, nrm.cur - nrm.buf); + pfree(q); + } + + PG_FREE_IF_COPY(query, 0); + + PG_RETURN_TEXT_P(res); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/tsquery_cleanup.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/tsquery_cleanup.c new file mode 100644 index 00000000000..dc316657706 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/tsquery_cleanup.c @@ -0,0 +1,446 @@ +/*------------------------------------------------------------------------- + * + * tsquery_cleanup.c + * Cleanup query from NOT values and/or stopword + * Utility functions to correct work. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/utils/adt/tsquery_cleanup.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "miscadmin.h" +#include "tsearch/ts_utils.h" +#include "varatt.h" + +typedef struct NODE +{ + struct NODE *left; + struct NODE *right; + QueryItem *valnode; +} NODE; + +/* + * make query tree from plain view of query + */ +static NODE * +maketree(QueryItem *in) +{ + NODE *node = (NODE *) palloc(sizeof(NODE)); + + /* since this function recurses, it could be driven to stack overflow. */ + check_stack_depth(); + + node->valnode = in; + node->right = node->left = NULL; + if (in->type == QI_OPR) + { + node->right = maketree(in + 1); + if (in->qoperator.oper != OP_NOT) + node->left = maketree(in + in->qoperator.left); + } + return node; +} + +/* + * Internal state for plaintree and plainnode + */ +typedef struct +{ + QueryItem *ptr; + int len; /* allocated size of ptr */ + int cur; /* number of elements in ptr */ +} PLAINTREE; + +static void +plainnode(PLAINTREE *state, NODE *node) +{ + /* since this function recurses, it could be driven to stack overflow. */ + check_stack_depth(); + + if (state->cur == state->len) + { + state->len *= 2; + state->ptr = (QueryItem *) repalloc(state->ptr, state->len * sizeof(QueryItem)); + } + memcpy(&(state->ptr[state->cur]), node->valnode, sizeof(QueryItem)); + if (node->valnode->type == QI_VAL) + state->cur++; + else if (node->valnode->qoperator.oper == OP_NOT) + { + state->ptr[state->cur].qoperator.left = 1; + state->cur++; + plainnode(state, node->right); + } + else + { + int cur = state->cur; + + state->cur++; + plainnode(state, node->right); + state->ptr[cur].qoperator.left = state->cur - cur; + plainnode(state, node->left); + } + pfree(node); +} + +/* + * make plain view of tree from a NODE-tree representation + */ +static QueryItem * +plaintree(NODE *root, int *len) +{ + PLAINTREE pl; + + pl.cur = 0; + pl.len = 16; + if (root && (root->valnode->type == QI_VAL || root->valnode->type == QI_OPR)) + { + pl.ptr = (QueryItem *) palloc(pl.len * sizeof(QueryItem)); + plainnode(&pl, root); + } + else + pl.ptr = NULL; + *len = pl.cur; + return pl.ptr; +} + +static void +freetree(NODE *node) +{ + /* since this function recurses, it could be driven to stack overflow. */ + check_stack_depth(); + + if (!node) + return; + if (node->left) + freetree(node->left); + if (node->right) + freetree(node->right); + pfree(node); +} + +/* + * clean tree for ! operator. + * It's useful for debug, but in + * other case, such view is used with search in index. + * Operator ! always return TRUE + */ +static NODE * +clean_NOT_intree(NODE *node) +{ + /* since this function recurses, it could be driven to stack overflow. */ + check_stack_depth(); + + if (node->valnode->type == QI_VAL) + return node; + + if (node->valnode->qoperator.oper == OP_NOT) + { + freetree(node); + return NULL; + } + + /* operator & or | */ + if (node->valnode->qoperator.oper == OP_OR) + { + if ((node->left = clean_NOT_intree(node->left)) == NULL || + (node->right = clean_NOT_intree(node->right)) == NULL) + { + freetree(node); + return NULL; + } + } + else + { + NODE *res = node; + + Assert(node->valnode->qoperator.oper == OP_AND || + node->valnode->qoperator.oper == OP_PHRASE); + + node->left = clean_NOT_intree(node->left); + node->right = clean_NOT_intree(node->right); + if (node->left == NULL && node->right == NULL) + { + pfree(node); + res = NULL; + } + else if (node->left == NULL) + { + res = node->right; + pfree(node); + } + else if (node->right == NULL) + { + res = node->left; + pfree(node); + } + return res; + } + return node; +} + +QueryItem * +clean_NOT(QueryItem *ptr, int *len) +{ + NODE *root = maketree(ptr); + + return plaintree(clean_NOT_intree(root), len); +} + + +/* + * Remove QI_VALSTOP (stopword) nodes from query tree. + * + * Returns NULL if the query degenerates to nothing. Input must not be NULL. + * + * When we remove a phrase operator due to removing one or both of its + * arguments, we might need to adjust the distance of a parent phrase + * operator. For example, 'a' is a stopword, so: + * (b <-> a) <-> c should become b <2> c + * b <-> (a <-> c) should become b <2> c + * (b <-> (a <-> a)) <-> c should become b <3> c + * b <-> ((a <-> a) <-> c) should become b <3> c + * To handle that, we define two output parameters: + * ladd: amount to add to a phrase distance to the left of this node + * radd: amount to add to a phrase distance to the right of this node + * We need two outputs because we could need to bubble up adjustments to two + * different parent phrase operators. Consider + * w <-> (((a <-> x) <2> (y <3> a)) <-> z) + * After we've removed the two a's and are considering the <2> node (which is + * now just x <2> y), we have an ladd distance of 1 that needs to propagate + * up to the topmost (leftmost) <->, and an radd distance of 3 that needs to + * propagate to the rightmost <->, so that we'll end up with + * w <2> ((x <2> y) <4> z) + * Near the bottom of the tree, we may have subtrees consisting only of + * stopwords. The distances of any phrase operators within such a subtree are + * summed and propagated to both ladd and radd, since we don't know which side + * of the lowest surviving phrase operator we are in. The rule is that any + * subtree that degenerates to NULL must return equal values of ladd and radd, + * and the parent node dealing with it should incorporate only one of those. + * + * Currently, we only implement this adjustment for adjacent phrase operators. + * Thus for example 'x <-> ((a <-> y) | z)' will become 'x <-> (y | z)', which + * isn't ideal, but there is no way to represent the really desired semantics + * without some redesign of the tsquery structure. Certainly it would not be + * any better to convert that to 'x <2> (y | z)'. Since this is such a weird + * corner case, let it go for now. But we can fix it in cases where the + * intervening non-phrase operator also gets removed, for example + * '((x <-> a) | a) <-> y' will become 'x <2> y'. + */ +static NODE * +clean_stopword_intree(NODE *node, int *ladd, int *radd) +{ + /* since this function recurses, it could be driven to stack overflow. */ + check_stack_depth(); + + /* default output parameters indicate no change in parent distance */ + *ladd = *radd = 0; + + if (node->valnode->type == QI_VAL) + return node; + else if (node->valnode->type == QI_VALSTOP) + { + pfree(node); + return NULL; + } + + Assert(node->valnode->type == QI_OPR); + + if (node->valnode->qoperator.oper == OP_NOT) + { + /* NOT doesn't change pattern width, so just report child distances */ + node->right = clean_stopword_intree(node->right, ladd, radd); + if (!node->right) + { + freetree(node); + return NULL; + } + } + else + { + NODE *res = node; + bool isphrase; + int ndistance, + lladd, + lradd, + rladd, + rradd; + + /* First, recurse */ + node->left = clean_stopword_intree(node->left, &lladd, &lradd); + node->right = clean_stopword_intree(node->right, &rladd, &rradd); + + /* Check if current node is OP_PHRASE, get its distance */ + isphrase = (node->valnode->qoperator.oper == OP_PHRASE); + ndistance = isphrase ? node->valnode->qoperator.distance : 0; + + if (node->left == NULL && node->right == NULL) + { + /* + * When we collapse out a phrase node entirely, propagate its own + * distance into both *ladd and *radd; it is the responsibility of + * the parent node to count it only once. Also, for a phrase + * node, distances coming from children are summed and propagated + * up to parent (we assume lladd == lradd and rladd == rradd, else + * rule was broken at a lower level). But if this isn't a phrase + * node, take the larger of the two child distances; that + * corresponds to what TS_execute will do in non-stopword cases. + */ + if (isphrase) + *ladd = *radd = lladd + ndistance + rladd; + else + *ladd = *radd = Max(lladd, rladd); + freetree(node); + return NULL; + } + else if (node->left == NULL) + { + /* Removing this operator and left subnode */ + /* lladd and lradd are equal/redundant, don't count both */ + if (isphrase) + { + /* operator's own distance must propagate to left */ + *ladd = lladd + ndistance + rladd; + *radd = rradd; + } + else + { + /* at non-phrase op, just forget the left subnode entirely */ + *ladd = rladd; + *radd = rradd; + } + res = node->right; + pfree(node); + } + else if (node->right == NULL) + { + /* Removing this operator and right subnode */ + /* rladd and rradd are equal/redundant, don't count both */ + if (isphrase) + { + /* operator's own distance must propagate to right */ + *ladd = lladd; + *radd = lradd + ndistance + rradd; + } + else + { + /* at non-phrase op, just forget the right subnode entirely */ + *ladd = lladd; + *radd = lradd; + } + res = node->left; + pfree(node); + } + else if (isphrase) + { + /* Absorb appropriate corrections at this level */ + node->valnode->qoperator.distance += lradd + rladd; + /* Propagate up any unaccounted-for corrections */ + *ladd = lladd; + *radd = rradd; + } + else + { + /* We're keeping a non-phrase operator, so ladd/radd remain 0 */ + } + + return res; + } + return node; +} + +/* + * Number of elements in query tree + */ +static int32 +calcstrlen(NODE *node) +{ + int32 size = 0; + + if (node->valnode->type == QI_VAL) + { + size = node->valnode->qoperand.length + 1; + } + else + { + Assert(node->valnode->type == QI_OPR); + + size = calcstrlen(node->right); + if (node->valnode->qoperator.oper != OP_NOT) + size += calcstrlen(node->left); + } + + return size; +} + +/* + * Remove QI_VALSTOP (stopword) nodes from TSQuery. + */ +TSQuery +cleanup_tsquery_stopwords(TSQuery in, bool noisy) +{ + int32 len, + lenstr, + commonlen, + i; + NODE *root; + int ladd, + radd; + TSQuery out; + QueryItem *items; + char *operands; + + if (in->size == 0) + return in; + + /* eliminate stop words */ + root = clean_stopword_intree(maketree(GETQUERY(in)), &ladd, &radd); + if (root == NULL) + { + if (noisy) + ereport(NOTICE, + (errmsg("text-search query contains only stop words or doesn't contain lexemes, ignored"))); + out = palloc(HDRSIZETQ); + out->size = 0; + SET_VARSIZE(out, HDRSIZETQ); + return out; + } + + /* + * Build TSQuery from plain view + */ + + lenstr = calcstrlen(root); + items = plaintree(root, &len); + commonlen = COMPUTESIZE(len, lenstr); + + out = palloc(commonlen); + SET_VARSIZE(out, commonlen); + out->size = len; + + memcpy(GETQUERY(out), items, len * sizeof(QueryItem)); + + items = GETQUERY(out); + operands = GETOPERAND(out); + for (i = 0; i < out->size; i++) + { + QueryOperand *op = (QueryOperand *) &items[i]; + + if (op->type != QI_VAL) + continue; + + memcpy(operands, GETOPERAND(in) + op->distance, op->length); + operands[op->length] = '\0'; + op->distance = operands - GETOPERAND(out); + operands += op->length + 1; + } + + return out; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/tsquery_gist.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/tsquery_gist.c new file mode 100644 index 00000000000..7c99348d44c --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/tsquery_gist.c @@ -0,0 +1,277 @@ +/*------------------------------------------------------------------------- + * + * tsquery_gist.c + * GiST index support for tsquery + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/utils/adt/tsquery_gist.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/gist.h" +#include "access/stratnum.h" +#include "tsearch/ts_utils.h" +#include "utils/builtins.h" + +#define GETENTRY(vec,pos) DatumGetTSQuerySign((vec)->vector[pos].key) + + +Datum +gtsquery_compress(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + GISTENTRY *retval = entry; + + if (entry->leafkey) + { + TSQuerySign sign; + + retval = (GISTENTRY *) palloc(sizeof(GISTENTRY)); + sign = makeTSQuerySign(DatumGetTSQuery(entry->key)); + + gistentryinit(*retval, TSQuerySignGetDatum(sign), + entry->rel, entry->page, + entry->offset, false); + } + + PG_RETURN_POINTER(retval); +} + +/* + * We do not need a decompress function, because the other gtsquery + * support functions work with the compressed representation. + */ + +Datum +gtsquery_consistent(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + TSQuery query = PG_GETARG_TSQUERY(1); + StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2); + + /* Oid subtype = PG_GETARG_OID(3); */ + bool *recheck = (bool *) PG_GETARG_POINTER(4); + TSQuerySign key = DatumGetTSQuerySign(entry->key); + TSQuerySign sq = makeTSQuerySign(query); + bool retval; + + /* All cases served by this function are inexact */ + *recheck = true; + + switch (strategy) + { + case RTContainsStrategyNumber: + if (GIST_LEAF(entry)) + retval = (key & sq) == sq; + else + retval = (key & sq) != 0; + break; + case RTContainedByStrategyNumber: + if (GIST_LEAF(entry)) + retval = (key & sq) == key; + else + retval = (key & sq) != 0; + break; + default: + retval = false; + } + PG_RETURN_BOOL(retval); +} + +Datum +gtsquery_union(PG_FUNCTION_ARGS) +{ + GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); + int *size = (int *) PG_GETARG_POINTER(1); + TSQuerySign sign; + int i; + + sign = 0; + + for (i = 0; i < entryvec->n; i++) + sign |= GETENTRY(entryvec, i); + + *size = sizeof(TSQuerySign); + + PG_RETURN_TSQUERYSIGN(sign); +} + +Datum +gtsquery_same(PG_FUNCTION_ARGS) +{ + TSQuerySign a = PG_GETARG_TSQUERYSIGN(0); + TSQuerySign b = PG_GETARG_TSQUERYSIGN(1); + bool *result = (bool *) PG_GETARG_POINTER(2); + + *result = (a == b); + + PG_RETURN_POINTER(result); +} + +static int +sizebitvec(TSQuerySign sign) +{ + int size = 0, + i; + + for (i = 0; i < TSQS_SIGLEN; i++) + size += 0x01 & (sign >> i); + + return size; +} + +static int +hemdist(TSQuerySign a, TSQuerySign b) +{ + TSQuerySign res = a ^ b; + + return sizebitvec(res); +} + +Datum +gtsquery_penalty(PG_FUNCTION_ARGS) +{ + TSQuerySign origval = DatumGetTSQuerySign(((GISTENTRY *) PG_GETARG_POINTER(0))->key); + TSQuerySign newval = DatumGetTSQuerySign(((GISTENTRY *) PG_GETARG_POINTER(1))->key); + float *penalty = (float *) PG_GETARG_POINTER(2); + + *penalty = hemdist(origval, newval); + + PG_RETURN_POINTER(penalty); +} + + +typedef struct +{ + OffsetNumber pos; + int32 cost; +} SPLITCOST; + +static int +comparecost(const void *a, const void *b) +{ + if (((const SPLITCOST *) a)->cost == ((const SPLITCOST *) b)->cost) + return 0; + else + return (((const SPLITCOST *) a)->cost > ((const SPLITCOST *) b)->cost) ? 1 : -1; +} + +#define WISH_F(a,b,c) (double)( -(double)(((a)-(b))*((a)-(b))*((a)-(b)))*(c) ) + +Datum +gtsquery_picksplit(PG_FUNCTION_ARGS) +{ + GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); + GIST_SPLITVEC *v = (GIST_SPLITVEC *) PG_GETARG_POINTER(1); + OffsetNumber maxoff = entryvec->n - 2; + OffsetNumber k, + j; + TSQuerySign datum_l, + datum_r; + int32 size_alpha, + size_beta; + int32 size_waste, + waste = -1; + int32 nbytes; + OffsetNumber seed_1 = 0, + seed_2 = 0; + OffsetNumber *left, + *right; + + SPLITCOST *costvector; + + nbytes = (maxoff + 2) * sizeof(OffsetNumber); + left = v->spl_left = (OffsetNumber *) palloc(nbytes); + right = v->spl_right = (OffsetNumber *) palloc(nbytes); + v->spl_nleft = v->spl_nright = 0; + + for (k = FirstOffsetNumber; k < maxoff; k = OffsetNumberNext(k)) + for (j = OffsetNumberNext(k); j <= maxoff; j = OffsetNumberNext(j)) + { + size_waste = hemdist(GETENTRY(entryvec, j), GETENTRY(entryvec, k)); + if (size_waste > waste) + { + waste = size_waste; + seed_1 = k; + seed_2 = j; + } + } + + + if (seed_1 == 0 || seed_2 == 0) + { + seed_1 = 1; + seed_2 = 2; + } + + datum_l = GETENTRY(entryvec, seed_1); + datum_r = GETENTRY(entryvec, seed_2); + + maxoff = OffsetNumberNext(maxoff); + costvector = (SPLITCOST *) palloc(sizeof(SPLITCOST) * maxoff); + for (j = FirstOffsetNumber; j <= maxoff; j = OffsetNumberNext(j)) + { + costvector[j - 1].pos = j; + size_alpha = hemdist(GETENTRY(entryvec, seed_1), GETENTRY(entryvec, j)); + size_beta = hemdist(GETENTRY(entryvec, seed_2), GETENTRY(entryvec, j)); + costvector[j - 1].cost = abs(size_alpha - size_beta); + } + qsort(costvector, maxoff, sizeof(SPLITCOST), comparecost); + + for (k = 0; k < maxoff; k++) + { + j = costvector[k].pos; + if (j == seed_1) + { + *left++ = j; + v->spl_nleft++; + continue; + } + else if (j == seed_2) + { + *right++ = j; + v->spl_nright++; + continue; + } + size_alpha = hemdist(datum_l, GETENTRY(entryvec, j)); + size_beta = hemdist(datum_r, GETENTRY(entryvec, j)); + + if (size_alpha < size_beta + WISH_F(v->spl_nleft, v->spl_nright, 0.05)) + { + datum_l |= GETENTRY(entryvec, j); + *left++ = j; + v->spl_nleft++; + } + else + { + datum_r |= GETENTRY(entryvec, j); + *right++ = j; + v->spl_nright++; + } + } + + *right = *left = FirstOffsetNumber; + v->spl_ldatum = TSQuerySignGetDatum(datum_l); + v->spl_rdatum = TSQuerySignGetDatum(datum_r); + + PG_RETURN_POINTER(v); +} + +/* + * Formerly, gtsquery_consistent was declared in pg_proc.h with arguments + * that did not match the documented conventions for GiST support functions. + * We fixed that, but we still need a pg_proc entry with the old signature + * to support reloading pre-9.6 contrib/tsearch2 opclass declarations. + * This compatibility function should go away eventually. + */ +Datum +gtsquery_consistent_oldsig(PG_FUNCTION_ARGS) +{ + return gtsquery_consistent(fcinfo); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/tsquery_op.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/tsquery_op.c new file mode 100644 index 00000000000..2bc4ec904fe --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/tsquery_op.c @@ -0,0 +1,359 @@ +/*------------------------------------------------------------------------- + * + * tsquery_op.c + * Various operations with tsquery + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/utils/adt/tsquery_op.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "lib/qunique.h" +#include "tsearch/ts_utils.h" +#include "utils/builtins.h" +#include "varatt.h" + +Datum +tsquery_numnode(PG_FUNCTION_ARGS) +{ + TSQuery query = PG_GETARG_TSQUERY(0); + int nnode = query->size; + + PG_FREE_IF_COPY(query, 0); + PG_RETURN_INT32(nnode); +} + +static QTNode * +join_tsqueries(TSQuery a, TSQuery b, int8 operator, uint16 distance) +{ + QTNode *res = (QTNode *) palloc0(sizeof(QTNode)); + + res->flags |= QTN_NEEDFREE; + + res->valnode = (QueryItem *) palloc0(sizeof(QueryItem)); + res->valnode->type = QI_OPR; + res->valnode->qoperator.oper = operator; + if (operator == OP_PHRASE) + res->valnode->qoperator.distance = distance; + + res->child = (QTNode **) palloc0(sizeof(QTNode *) * 2); + res->child[0] = QT2QTN(GETQUERY(b), GETOPERAND(b)); + res->child[1] = QT2QTN(GETQUERY(a), GETOPERAND(a)); + res->nchild = 2; + + return res; +} + +Datum +tsquery_and(PG_FUNCTION_ARGS) +{ + TSQuery a = PG_GETARG_TSQUERY_COPY(0); + TSQuery b = PG_GETARG_TSQUERY_COPY(1); + QTNode *res; + TSQuery query; + + if (a->size == 0) + { + PG_FREE_IF_COPY(a, 1); + PG_RETURN_POINTER(b); + } + else if (b->size == 0) + { + PG_FREE_IF_COPY(b, 1); + PG_RETURN_POINTER(a); + } + + res = join_tsqueries(a, b, OP_AND, 0); + + query = QTN2QT(res); + + QTNFree(res); + PG_FREE_IF_COPY(a, 0); + PG_FREE_IF_COPY(b, 1); + + PG_RETURN_TSQUERY(query); +} + +Datum +tsquery_or(PG_FUNCTION_ARGS) +{ + TSQuery a = PG_GETARG_TSQUERY_COPY(0); + TSQuery b = PG_GETARG_TSQUERY_COPY(1); + QTNode *res; + TSQuery query; + + if (a->size == 0) + { + PG_FREE_IF_COPY(a, 1); + PG_RETURN_POINTER(b); + } + else if (b->size == 0) + { + PG_FREE_IF_COPY(b, 1); + PG_RETURN_POINTER(a); + } + + res = join_tsqueries(a, b, OP_OR, 0); + + query = QTN2QT(res); + + QTNFree(res); + PG_FREE_IF_COPY(a, 0); + PG_FREE_IF_COPY(b, 1); + + PG_RETURN_TSQUERY(query); +} + +Datum +tsquery_phrase_distance(PG_FUNCTION_ARGS) +{ + TSQuery a = PG_GETARG_TSQUERY_COPY(0); + TSQuery b = PG_GETARG_TSQUERY_COPY(1); + QTNode *res; + TSQuery query; + int32 distance = PG_GETARG_INT32(2); + + if (distance < 0 || distance > MAXENTRYPOS) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("distance in phrase operator must be an integer value between zero and %d inclusive", + MAXENTRYPOS))); + if (a->size == 0) + { + PG_FREE_IF_COPY(a, 1); + PG_RETURN_POINTER(b); + } + else if (b->size == 0) + { + PG_FREE_IF_COPY(b, 1); + PG_RETURN_POINTER(a); + } + + res = join_tsqueries(a, b, OP_PHRASE, (uint16) distance); + + query = QTN2QT(res); + + QTNFree(res); + PG_FREE_IF_COPY(a, 0); + PG_FREE_IF_COPY(b, 1); + + PG_RETURN_TSQUERY(query); +} + +Datum +tsquery_phrase(PG_FUNCTION_ARGS) +{ + PG_RETURN_DATUM(DirectFunctionCall3(tsquery_phrase_distance, + PG_GETARG_DATUM(0), + PG_GETARG_DATUM(1), + Int32GetDatum(1))); +} + +Datum +tsquery_not(PG_FUNCTION_ARGS) +{ + TSQuery a = PG_GETARG_TSQUERY_COPY(0); + QTNode *res; + TSQuery query; + + if (a->size == 0) + PG_RETURN_POINTER(a); + + res = (QTNode *) palloc0(sizeof(QTNode)); + + res->flags |= QTN_NEEDFREE; + + res->valnode = (QueryItem *) palloc0(sizeof(QueryItem)); + res->valnode->type = QI_OPR; + res->valnode->qoperator.oper = OP_NOT; + + res->child = (QTNode **) palloc0(sizeof(QTNode *)); + res->child[0] = QT2QTN(GETQUERY(a), GETOPERAND(a)); + res->nchild = 1; + + query = QTN2QT(res); + + QTNFree(res); + PG_FREE_IF_COPY(a, 0); + + PG_RETURN_POINTER(query); +} + +static int +CompareTSQ(TSQuery a, TSQuery b) +{ + if (a->size != b->size) + { + return (a->size < b->size) ? -1 : 1; + } + else if (VARSIZE(a) != VARSIZE(b)) + { + return (VARSIZE(a) < VARSIZE(b)) ? -1 : 1; + } + else if (a->size != 0) + { + QTNode *an = QT2QTN(GETQUERY(a), GETOPERAND(a)); + QTNode *bn = QT2QTN(GETQUERY(b), GETOPERAND(b)); + int res = QTNodeCompare(an, bn); + + QTNFree(an); + QTNFree(bn); + + return res; + } + + return 0; +} + +Datum +tsquery_cmp(PG_FUNCTION_ARGS) +{ + TSQuery a = PG_GETARG_TSQUERY_COPY(0); + TSQuery b = PG_GETARG_TSQUERY_COPY(1); + int res = CompareTSQ(a, b); + + PG_FREE_IF_COPY(a, 0); + PG_FREE_IF_COPY(b, 1); + + PG_RETURN_INT32(res); +} + +#define CMPFUNC( NAME, CONDITION ) \ +Datum \ +NAME(PG_FUNCTION_ARGS) { \ + TSQuery a = PG_GETARG_TSQUERY_COPY(0); \ + TSQuery b = PG_GETARG_TSQUERY_COPY(1); \ + int res = CompareTSQ(a,b); \ + \ + PG_FREE_IF_COPY(a,0); \ + PG_FREE_IF_COPY(b,1); \ + \ + PG_RETURN_BOOL( CONDITION ); \ +} \ +/* keep compiler quiet - no extra ; */ \ +extern int no_such_variable + +CMPFUNC(tsquery_lt, res < 0); +CMPFUNC(tsquery_le, res <= 0); +CMPFUNC(tsquery_eq, res == 0); +CMPFUNC(tsquery_ge, res >= 0); +CMPFUNC(tsquery_gt, res > 0); +CMPFUNC(tsquery_ne, res != 0); + +TSQuerySign +makeTSQuerySign(TSQuery a) +{ + int i; + QueryItem *ptr = GETQUERY(a); + TSQuerySign sign = 0; + + for (i = 0; i < a->size; i++) + { + if (ptr->type == QI_VAL) + sign |= ((TSQuerySign) 1) << (((unsigned int) ptr->qoperand.valcrc) % TSQS_SIGLEN); + ptr++; + } + + return sign; +} + +static char ** +collectTSQueryValues(TSQuery a, int *nvalues_p) +{ + QueryItem *ptr = GETQUERY(a); + char *operand = GETOPERAND(a); + char **values; + int nvalues = 0; + int i; + + values = (char **) palloc(sizeof(char *) * a->size); + + for (i = 0; i < a->size; i++) + { + if (ptr->type == QI_VAL) + { + int len = ptr->qoperand.length; + char *val; + + val = palloc(len + 1); + memcpy(val, operand + ptr->qoperand.distance, len); + val[len] = '\0'; + + values[nvalues++] = val; + } + ptr++; + } + + *nvalues_p = nvalues; + return values; +} + +static int +cmp_string(const void *a, const void *b) +{ + const char *sa = *((char *const *) a); + const char *sb = *((char *const *) b); + + return strcmp(sa, sb); +} + +Datum +tsq_mcontains(PG_FUNCTION_ARGS) +{ + TSQuery query = PG_GETARG_TSQUERY(0); + TSQuery ex = PG_GETARG_TSQUERY(1); + char **query_values; + int query_nvalues; + char **ex_values; + int ex_nvalues; + bool result = true; + + /* Extract the query terms into arrays */ + query_values = collectTSQueryValues(query, &query_nvalues); + ex_values = collectTSQueryValues(ex, &ex_nvalues); + + /* Sort and remove duplicates from both arrays */ + qsort(query_values, query_nvalues, sizeof(char *), cmp_string); + query_nvalues = qunique(query_values, query_nvalues, sizeof(char *), + cmp_string); + qsort(ex_values, ex_nvalues, sizeof(char *), cmp_string); + ex_nvalues = qunique(ex_values, ex_nvalues, sizeof(char *), cmp_string); + + if (ex_nvalues > query_nvalues) + result = false; + else + { + int i; + int j = 0; + + for (i = 0; i < ex_nvalues; i++) + { + for (; j < query_nvalues; j++) + { + if (strcmp(ex_values[i], query_values[j]) == 0) + break; + } + if (j == query_nvalues) + { + result = false; + break; + } + } + } + + PG_RETURN_BOOL(result); +} + +Datum +tsq_mcontained(PG_FUNCTION_ARGS) +{ + PG_RETURN_DATUM(DirectFunctionCall2(tsq_mcontains, + PG_GETARG_DATUM(1), + PG_GETARG_DATUM(0))); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/tsquery_rewrite.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/tsquery_rewrite.c new file mode 100644 index 00000000000..7e736351628 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/tsquery_rewrite.c @@ -0,0 +1,462 @@ +/*------------------------------------------------------------------------- + * + * tsquery_rewrite.c + * Utilities for reconstructing tsquery + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/utils/adt/tsquery_rewrite.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "catalog/pg_type.h" +#include "executor/spi.h" +#include "miscadmin.h" +#include "tsearch/ts_utils.h" +#include "utils/builtins.h" + + +/* + * If "node" is equal to "ex", return a copy of "subs" instead. + * If "ex" matches a subset of node's children, return a modified version + * of "node" in which those children are replaced with a copy of "subs". + * Otherwise return "node" unmodified. + * + * The QTN_NOCHANGE bit is set in successfully modified nodes, so that + * we won't uselessly recurse into them. + * Also, set *isfind true if we make a replacement. + */ +static QTNode * +findeq(QTNode *node, QTNode *ex, QTNode *subs, bool *isfind) +{ + /* Can't match unless signature matches and node type matches. */ + if ((node->sign & ex->sign) != ex->sign || + node->valnode->type != ex->valnode->type) + return node; + + /* Ignore nodes marked NOCHANGE, too. */ + if (node->flags & QTN_NOCHANGE) + return node; + + if (node->valnode->type == QI_OPR) + { + /* Must be same operator. */ + if (node->valnode->qoperator.oper != ex->valnode->qoperator.oper) + return node; + + if (node->nchild == ex->nchild) + { + /* + * Simple case: when same number of children, match if equal. + * (This is reliable when the children were sorted earlier.) + */ + if (QTNEq(node, ex)) + { + /* Match; delete node and return a copy of subs instead. */ + QTNFree(node); + if (subs) + { + node = QTNCopy(subs); + node->flags |= QTN_NOCHANGE; + } + else + node = NULL; + *isfind = true; + } + } + else if (node->nchild > ex->nchild && ex->nchild > 0) + { + /* + * AND and OR are commutative/associative, so we should check if a + * subset of the children match. For example, if node is A|B|C, + * and ex is B|C, we have a match after we notionally convert node + * to A|(B|C). This does not work for NOT or PHRASE nodes, but we + * can't get here for those node types because they have a fixed + * number of children. + * + * Because we expect that the children are sorted, it suffices to + * make one pass through the two lists to find the matches. + */ + bool *matched; + int nmatched; + int i, + j; + + /* Assert that the subset rule is OK */ + Assert(node->valnode->qoperator.oper == OP_AND || + node->valnode->qoperator.oper == OP_OR); + + /* matched[] will record which children of node matched */ + matched = (bool *) palloc0(node->nchild * sizeof(bool)); + nmatched = 0; + i = j = 0; + while (i < node->nchild && j < ex->nchild) + { + int cmp = QTNodeCompare(node->child[i], ex->child[j]); + + if (cmp == 0) + { + /* match! */ + matched[i] = true; + nmatched++; + i++, j++; + } + else if (cmp < 0) + { + /* node->child[i] has no match, ignore it */ + i++; + } + else + { + /* ex->child[j] has no match; we can give up immediately */ + break; + } + } + + if (nmatched == ex->nchild) + { + /* collapse out the matched children of node */ + j = 0; + for (i = 0; i < node->nchild; i++) + { + if (matched[i]) + QTNFree(node->child[i]); + else + node->child[j++] = node->child[i]; + } + + /* and instead insert a copy of subs */ + if (subs) + { + subs = QTNCopy(subs); + subs->flags |= QTN_NOCHANGE; + node->child[j++] = subs; + } + + node->nchild = j; + + /* + * At this point we might have a node with zero or one child, + * which should be simplified. But we leave it to our caller + * (dofindsubquery) to take care of that. + */ + + /* + * Re-sort the node to put new child in the right place. This + * is a bit bogus, because it won't matter for findsubquery's + * remaining processing, and it's insufficient to prepare the + * tree for another search (we would need to re-flatten as + * well, and we don't want to do that because we'd lose the + * QTN_NOCHANGE marking on the new child). But it's needed to + * keep the results the same as the regression tests expect. + */ + QTNSort(node); + + *isfind = true; + } + + pfree(matched); + } + } + else + { + Assert(node->valnode->type == QI_VAL); + + if (node->valnode->qoperand.valcrc != ex->valnode->qoperand.valcrc) + return node; + else if (QTNEq(node, ex)) + { + QTNFree(node); + if (subs) + { + node = QTNCopy(subs); + node->flags |= QTN_NOCHANGE; + } + else + { + node = NULL; + } + *isfind = true; + } + } + + return node; +} + +/* + * Recursive guts of findsubquery(): attempt to replace "ex" with "subs" + * at the root node, and if we failed to do so, recursively match against + * child nodes. + * + * Delete any void subtrees resulting from the replacement. + * In the following example '5' is replaced by empty operand: + * + * AND -> 6 + * / \ + * 5 OR + * / \ + * 6 5 + */ +static QTNode * +dofindsubquery(QTNode *root, QTNode *ex, QTNode *subs, bool *isfind) +{ + /* since this function recurses, it could be driven to stack overflow. */ + check_stack_depth(); + + /* also, since it's a bit expensive, let's check for query cancel. */ + CHECK_FOR_INTERRUPTS(); + + /* match at the node itself */ + root = findeq(root, ex, subs, isfind); + + /* unless we matched here, consider matches at child nodes */ + if (root && (root->flags & QTN_NOCHANGE) == 0 && + root->valnode->type == QI_OPR) + { + int i, + j = 0; + + /* + * Any subtrees that are replaced by NULL must be dropped from the + * tree. + */ + for (i = 0; i < root->nchild; i++) + { + root->child[j] = dofindsubquery(root->child[i], ex, subs, isfind); + if (root->child[j]) + j++; + } + + root->nchild = j; + + /* + * If we have just zero or one remaining child node, simplify out this + * operator node. + */ + if (root->nchild == 0) + { + QTNFree(root); + root = NULL; + } + else if (root->nchild == 1 && root->valnode->qoperator.oper != OP_NOT) + { + QTNode *nroot = root->child[0]; + + pfree(root); + root = nroot; + } + } + + return root; +} + +/* + * Substitute "subs" for "ex" throughout the QTNode tree at root. + * + * If isfind isn't NULL, set *isfind to show whether we made any substitution. + * + * Both "root" and "ex" must have been through QTNTernary and QTNSort + * to ensure reliable matching. + */ +QTNode * +findsubquery(QTNode *root, QTNode *ex, QTNode *subs, bool *isfind) +{ + bool DidFind = false; + + root = dofindsubquery(root, ex, subs, &DidFind); + + if (isfind) + *isfind = DidFind; + + return root; +} + +Datum +tsquery_rewrite_query(PG_FUNCTION_ARGS) +{ + TSQuery query = PG_GETARG_TSQUERY_COPY(0); + text *in = PG_GETARG_TEXT_PP(1); + TSQuery rewrited = query; + MemoryContext outercontext = CurrentMemoryContext; + MemoryContext oldcontext; + QTNode *tree; + char *buf; + SPIPlanPtr plan; + Portal portal; + bool isnull; + + if (query->size == 0) + { + PG_FREE_IF_COPY(in, 1); + PG_RETURN_POINTER(rewrited); + } + + tree = QT2QTN(GETQUERY(query), GETOPERAND(query)); + QTNTernary(tree); + QTNSort(tree); + + buf = text_to_cstring(in); + + SPI_connect(); + + if ((plan = SPI_prepare(buf, 0, NULL)) == NULL) + elog(ERROR, "SPI_prepare(\"%s\") failed", buf); + + if ((portal = SPI_cursor_open(NULL, plan, NULL, NULL, true)) == NULL) + elog(ERROR, "SPI_cursor_open(\"%s\") failed", buf); + + SPI_cursor_fetch(portal, true, 100); + + if (SPI_tuptable == NULL || + SPI_tuptable->tupdesc->natts != 2 || + SPI_gettypeid(SPI_tuptable->tupdesc, 1) != TSQUERYOID || + SPI_gettypeid(SPI_tuptable->tupdesc, 2) != TSQUERYOID) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("ts_rewrite query must return two tsquery columns"))); + + while (SPI_processed > 0 && tree) + { + uint64 i; + + for (i = 0; i < SPI_processed && tree; i++) + { + Datum qdata = SPI_getbinval(SPI_tuptable->vals[i], SPI_tuptable->tupdesc, 1, &isnull); + Datum sdata; + + if (isnull) + continue; + + sdata = SPI_getbinval(SPI_tuptable->vals[i], SPI_tuptable->tupdesc, 2, &isnull); + + if (!isnull) + { + TSQuery qtex = DatumGetTSQuery(qdata); + TSQuery qtsubs = DatumGetTSQuery(sdata); + QTNode *qex, + *qsubs = NULL; + + if (qtex->size == 0) + { + if (qtex != (TSQuery) DatumGetPointer(qdata)) + pfree(qtex); + if (qtsubs != (TSQuery) DatumGetPointer(sdata)) + pfree(qtsubs); + continue; + } + + qex = QT2QTN(GETQUERY(qtex), GETOPERAND(qtex)); + + QTNTernary(qex); + QTNSort(qex); + + if (qtsubs->size) + qsubs = QT2QTN(GETQUERY(qtsubs), GETOPERAND(qtsubs)); + + oldcontext = MemoryContextSwitchTo(outercontext); + tree = findsubquery(tree, qex, qsubs, NULL); + MemoryContextSwitchTo(oldcontext); + + QTNFree(qex); + if (qtex != (TSQuery) DatumGetPointer(qdata)) + pfree(qtex); + QTNFree(qsubs); + if (qtsubs != (TSQuery) DatumGetPointer(sdata)) + pfree(qtsubs); + + if (tree) + { + /* ready the tree for another pass */ + QTNClearFlags(tree, QTN_NOCHANGE); + QTNTernary(tree); + QTNSort(tree); + } + } + } + + SPI_freetuptable(SPI_tuptable); + SPI_cursor_fetch(portal, true, 100); + } + + SPI_freetuptable(SPI_tuptable); + SPI_cursor_close(portal); + SPI_freeplan(plan); + SPI_finish(); + + if (tree) + { + QTNBinary(tree); + rewrited = QTN2QT(tree); + QTNFree(tree); + PG_FREE_IF_COPY(query, 0); + } + else + { + SET_VARSIZE(rewrited, HDRSIZETQ); + rewrited->size = 0; + } + + pfree(buf); + PG_FREE_IF_COPY(in, 1); + PG_RETURN_POINTER(rewrited); +} + +Datum +tsquery_rewrite(PG_FUNCTION_ARGS) +{ + TSQuery query = PG_GETARG_TSQUERY_COPY(0); + TSQuery ex = PG_GETARG_TSQUERY(1); + TSQuery subst = PG_GETARG_TSQUERY(2); + TSQuery rewrited = query; + QTNode *tree, + *qex, + *subs = NULL; + + if (query->size == 0 || ex->size == 0) + { + PG_FREE_IF_COPY(ex, 1); + PG_FREE_IF_COPY(subst, 2); + PG_RETURN_POINTER(rewrited); + } + + tree = QT2QTN(GETQUERY(query), GETOPERAND(query)); + QTNTernary(tree); + QTNSort(tree); + + qex = QT2QTN(GETQUERY(ex), GETOPERAND(ex)); + QTNTernary(qex); + QTNSort(qex); + + if (subst->size) + subs = QT2QTN(GETQUERY(subst), GETOPERAND(subst)); + + tree = findsubquery(tree, qex, subs, NULL); + + QTNFree(qex); + QTNFree(subs); + + if (!tree) + { + SET_VARSIZE(rewrited, HDRSIZETQ); + rewrited->size = 0; + PG_FREE_IF_COPY(ex, 1); + PG_FREE_IF_COPY(subst, 2); + PG_RETURN_POINTER(rewrited); + } + else + { + QTNBinary(tree); + rewrited = QTN2QT(tree); + QTNFree(tree); + } + + PG_FREE_IF_COPY(query, 0); + PG_FREE_IF_COPY(ex, 1); + PG_FREE_IF_COPY(subst, 2); + PG_RETURN_POINTER(rewrited); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/tsquery_util.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/tsquery_util.c new file mode 100644 index 00000000000..7b6970a6f82 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/tsquery_util.c @@ -0,0 +1,448 @@ +/*------------------------------------------------------------------------- + * + * tsquery_util.c + * Utilities for tsquery datatype + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/utils/adt/tsquery_util.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "miscadmin.h" +#include "tsearch/ts_utils.h" +#include "varatt.h" + +/* + * Build QTNode tree for a tsquery given in QueryItem array format. + */ +QTNode * +QT2QTN(QueryItem *in, char *operand) +{ + QTNode *node = (QTNode *) palloc0(sizeof(QTNode)); + + /* since this function recurses, it could be driven to stack overflow. */ + check_stack_depth(); + + node->valnode = in; + + if (in->type == QI_OPR) + { + node->child = (QTNode **) palloc0(sizeof(QTNode *) * 2); + node->child[0] = QT2QTN(in + 1, operand); + node->sign = node->child[0]->sign; + if (in->qoperator.oper == OP_NOT) + node->nchild = 1; + else + { + node->nchild = 2; + node->child[1] = QT2QTN(in + in->qoperator.left, operand); + node->sign |= node->child[1]->sign; + } + } + else if (operand) + { + node->word = operand + in->qoperand.distance; + node->sign = ((uint32) 1) << (((unsigned int) in->qoperand.valcrc) % 32); + } + + return node; +} + +/* + * Free a QTNode tree. + * + * Referenced "word" and "valnode" items are freed if marked as transient + * by flags. + */ +void +QTNFree(QTNode *in) +{ + if (!in) + return; + + /* since this function recurses, it could be driven to stack overflow. */ + check_stack_depth(); + + if (in->valnode->type == QI_VAL && in->word && (in->flags & QTN_WORDFREE) != 0) + pfree(in->word); + + if (in->valnode->type == QI_OPR) + { + int i; + + for (i = 0; i < in->nchild; i++) + QTNFree(in->child[i]); + } + if (in->child) + pfree(in->child); + + if (in->flags & QTN_NEEDFREE) + pfree(in->valnode); + + pfree(in); +} + +/* + * Sort comparator for QTNodes. + * + * The sort order is somewhat arbitrary. + */ +int +QTNodeCompare(QTNode *an, QTNode *bn) +{ + /* since this function recurses, it could be driven to stack overflow. */ + check_stack_depth(); + + if (an->valnode->type != bn->valnode->type) + return (an->valnode->type > bn->valnode->type) ? -1 : 1; + + if (an->valnode->type == QI_OPR) + { + QueryOperator *ao = &an->valnode->qoperator; + QueryOperator *bo = &bn->valnode->qoperator; + + if (ao->oper != bo->oper) + return (ao->oper > bo->oper) ? -1 : 1; + + if (an->nchild != bn->nchild) + return (an->nchild > bn->nchild) ? -1 : 1; + + { + int i, + res; + + for (i = 0; i < an->nchild; i++) + if ((res = QTNodeCompare(an->child[i], bn->child[i])) != 0) + return res; + } + + if (ao->oper == OP_PHRASE && ao->distance != bo->distance) + return (ao->distance > bo->distance) ? -1 : 1; + + return 0; + } + else if (an->valnode->type == QI_VAL) + { + QueryOperand *ao = &an->valnode->qoperand; + QueryOperand *bo = &bn->valnode->qoperand; + + if (ao->valcrc != bo->valcrc) + { + return (ao->valcrc > bo->valcrc) ? -1 : 1; + } + + return tsCompareString(an->word, ao->length, bn->word, bo->length, false); + } + else + { + elog(ERROR, "unrecognized QueryItem type: %d", an->valnode->type); + return 0; /* keep compiler quiet */ + } +} + +/* + * qsort comparator for QTNode pointers. + */ +static int +cmpQTN(const void *a, const void *b) +{ + return QTNodeCompare(*(QTNode *const *) a, *(QTNode *const *) b); +} + +/* + * Canonicalize a QTNode tree by sorting the children of AND/OR nodes + * into an arbitrary but well-defined order. + */ +void +QTNSort(QTNode *in) +{ + int i; + + /* since this function recurses, it could be driven to stack overflow. */ + check_stack_depth(); + + if (in->valnode->type != QI_OPR) + return; + + for (i = 0; i < in->nchild; i++) + QTNSort(in->child[i]); + if (in->nchild > 1 && in->valnode->qoperator.oper != OP_PHRASE) + qsort(in->child, in->nchild, sizeof(QTNode *), cmpQTN); +} + +/* + * Are two QTNode trees equal according to QTNodeCompare? + */ +bool +QTNEq(QTNode *a, QTNode *b) +{ + uint32 sign = a->sign & b->sign; + + if (!(sign == a->sign && sign == b->sign)) + return false; + + return (QTNodeCompare(a, b) == 0); +} + +/* + * Remove unnecessary intermediate nodes. For example: + * + * OR OR + * a OR -> a b c + * b c + */ +void +QTNTernary(QTNode *in) +{ + int i; + + /* since this function recurses, it could be driven to stack overflow. */ + check_stack_depth(); + + if (in->valnode->type != QI_OPR) + return; + + for (i = 0; i < in->nchild; i++) + QTNTernary(in->child[i]); + + /* Only AND and OR are associative, so don't flatten other node types */ + if (in->valnode->qoperator.oper != OP_AND && + in->valnode->qoperator.oper != OP_OR) + return; + + for (i = 0; i < in->nchild; i++) + { + QTNode *cc = in->child[i]; + + if (cc->valnode->type == QI_OPR && + in->valnode->qoperator.oper == cc->valnode->qoperator.oper) + { + int oldnchild = in->nchild; + + in->nchild += cc->nchild - 1; + in->child = (QTNode **) repalloc(in->child, in->nchild * sizeof(QTNode *)); + + if (i + 1 != oldnchild) + memmove(in->child + i + cc->nchild, in->child + i + 1, + (oldnchild - i - 1) * sizeof(QTNode *)); + + memcpy(in->child + i, cc->child, cc->nchild * sizeof(QTNode *)); + i += cc->nchild - 1; + + if (cc->flags & QTN_NEEDFREE) + pfree(cc->valnode); + pfree(cc); + } + } +} + +/* + * Convert a tree to binary tree by inserting intermediate nodes. + * (Opposite of QTNTernary) + */ +void +QTNBinary(QTNode *in) +{ + int i; + + /* since this function recurses, it could be driven to stack overflow. */ + check_stack_depth(); + + if (in->valnode->type != QI_OPR) + return; + + for (i = 0; i < in->nchild; i++) + QTNBinary(in->child[i]); + + while (in->nchild > 2) + { + QTNode *nn = (QTNode *) palloc0(sizeof(QTNode)); + + nn->valnode = (QueryItem *) palloc0(sizeof(QueryItem)); + nn->child = (QTNode **) palloc0(sizeof(QTNode *) * 2); + + nn->nchild = 2; + nn->flags = QTN_NEEDFREE; + + nn->child[0] = in->child[0]; + nn->child[1] = in->child[1]; + nn->sign = nn->child[0]->sign | nn->child[1]->sign; + + nn->valnode->type = in->valnode->type; + nn->valnode->qoperator.oper = in->valnode->qoperator.oper; + + in->child[0] = nn; + in->child[1] = in->child[in->nchild - 1]; + in->nchild--; + } +} + +/* + * Count the total length of operand strings in tree (including '\0'- + * terminators) and the total number of nodes. + * Caller must initialize *sumlen and *nnode to zeroes. + */ +static void +cntsize(QTNode *in, int *sumlen, int *nnode) +{ + /* since this function recurses, it could be driven to stack overflow. */ + check_stack_depth(); + + *nnode += 1; + if (in->valnode->type == QI_OPR) + { + int i; + + for (i = 0; i < in->nchild; i++) + cntsize(in->child[i], sumlen, nnode); + } + else + { + *sumlen += in->valnode->qoperand.length + 1; + } +} + +typedef struct +{ + QueryItem *curitem; + char *operand; + char *curoperand; +} QTN2QTState; + +/* + * Recursively convert a QTNode tree into flat tsquery format. + * Caller must have allocated arrays of the correct size. + */ +static void +fillQT(QTN2QTState *state, QTNode *in) +{ + /* since this function recurses, it could be driven to stack overflow. */ + check_stack_depth(); + + if (in->valnode->type == QI_VAL) + { + memcpy(state->curitem, in->valnode, sizeof(QueryOperand)); + + memcpy(state->curoperand, in->word, in->valnode->qoperand.length); + state->curitem->qoperand.distance = state->curoperand - state->operand; + state->curoperand[in->valnode->qoperand.length] = '\0'; + state->curoperand += in->valnode->qoperand.length + 1; + state->curitem++; + } + else + { + QueryItem *curitem = state->curitem; + + Assert(in->valnode->type == QI_OPR); + + memcpy(state->curitem, in->valnode, sizeof(QueryOperator)); + + Assert(in->nchild <= 2); + state->curitem++; + + fillQT(state, in->child[0]); + + if (in->nchild == 2) + { + curitem->qoperator.left = state->curitem - curitem; + fillQT(state, in->child[1]); + } + } +} + +/* + * Build flat tsquery from a QTNode tree. + */ +TSQuery +QTN2QT(QTNode *in) +{ + TSQuery out; + int len; + int sumlen = 0, + nnode = 0; + QTN2QTState state; + + cntsize(in, &sumlen, &nnode); + + if (TSQUERY_TOO_BIG(nnode, sumlen)) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("tsquery is too large"))); + len = COMPUTESIZE(nnode, sumlen); + + out = (TSQuery) palloc0(len); + SET_VARSIZE(out, len); + out->size = nnode; + + state.curitem = GETQUERY(out); + state.operand = state.curoperand = GETOPERAND(out); + + fillQT(&state, in); + return out; +} + +/* + * Copy a QTNode tree. + * + * Modifiable copies of the words and valnodes are made, too. + */ +QTNode * +QTNCopy(QTNode *in) +{ + QTNode *out; + + /* since this function recurses, it could be driven to stack overflow. */ + check_stack_depth(); + + out = (QTNode *) palloc(sizeof(QTNode)); + + *out = *in; + out->valnode = (QueryItem *) palloc(sizeof(QueryItem)); + *(out->valnode) = *(in->valnode); + out->flags |= QTN_NEEDFREE; + + if (in->valnode->type == QI_VAL) + { + out->word = palloc(in->valnode->qoperand.length + 1); + memcpy(out->word, in->word, in->valnode->qoperand.length); + out->word[in->valnode->qoperand.length] = '\0'; + out->flags |= QTN_WORDFREE; + } + else + { + int i; + + out->child = (QTNode **) palloc(sizeof(QTNode *) * in->nchild); + + for (i = 0; i < in->nchild; i++) + out->child[i] = QTNCopy(in->child[i]); + } + + return out; +} + +/* + * Clear the specified flag bit(s) in all nodes of a QTNode tree. + */ +void +QTNClearFlags(QTNode *in, uint32 flags) +{ + /* since this function recurses, it could be driven to stack overflow. */ + check_stack_depth(); + + in->flags &= ~flags; + + if (in->valnode->type != QI_VAL) + { + int i; + + for (i = 0; i < in->nchild; i++) + QTNClearFlags(in->child[i], flags); + } +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/tsrank.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/tsrank.c new file mode 100644 index 00000000000..a5db96f3c89 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/tsrank.c @@ -0,0 +1,1012 @@ +/*------------------------------------------------------------------------- + * + * tsrank.c + * rank tsvector by tsquery + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/utils/adt/tsrank.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <limits.h> +#include <math.h> + +#include "miscadmin.h" +#include "tsearch/ts_utils.h" +#include "utils/array.h" +#include "utils/builtins.h" + +static const float weights[] = {0.1f, 0.2f, 0.4f, 1.0f}; + +#define wpos(wep) ( w[ WEP_GETWEIGHT(wep) ] ) + +#define RANK_NO_NORM 0x00 +#define RANK_NORM_LOGLENGTH 0x01 +#define RANK_NORM_LENGTH 0x02 +#define RANK_NORM_EXTDIST 0x04 +#define RANK_NORM_UNIQ 0x08 +#define RANK_NORM_LOGUNIQ 0x10 +#define RANK_NORM_RDIVRPLUS1 0x20 +#define DEF_NORM_METHOD RANK_NO_NORM + +static float calc_rank_or(const float *w, TSVector t, TSQuery q); +static float calc_rank_and(const float *w, TSVector t, TSQuery q); + +/* + * Returns a weight of a word collocation + */ +static float4 +word_distance(int32 w) +{ + if (w > 100) + return 1e-30f; + + return 1.0 / (1.005 + 0.05 * exp(((float4) w) / 1.5 - 2)); +} + +static int +cnt_length(TSVector t) +{ + WordEntry *ptr = ARRPTR(t), + *end = (WordEntry *) STRPTR(t); + int len = 0; + + while (ptr < end) + { + int clen = POSDATALEN(t, ptr); + + if (clen == 0) + len += 1; + else + len += clen; + + ptr++; + } + + return len; +} + + +#define WordECompareQueryItem(e,q,p,i,m) \ + tsCompareString((q) + (i)->distance, (i)->length, \ + (e) + (p)->pos, (p)->len, (m)) + + +/* + * Returns a pointer to a WordEntry's array corresponding to 'item' from + * tsvector 't'. 'q' is the TSQuery containing 'item'. + * Returns NULL if not found. + */ +static WordEntry * +find_wordentry(TSVector t, TSQuery q, QueryOperand *item, int32 *nitem) +{ + WordEntry *StopLow = ARRPTR(t); + WordEntry *StopHigh = (WordEntry *) STRPTR(t); + WordEntry *StopMiddle = StopHigh; + int difference; + + *nitem = 0; + + /* Loop invariant: StopLow <= item < StopHigh */ + while (StopLow < StopHigh) + { + StopMiddle = StopLow + (StopHigh - StopLow) / 2; + difference = WordECompareQueryItem(STRPTR(t), GETOPERAND(q), StopMiddle, item, false); + if (difference == 0) + { + StopHigh = StopMiddle; + *nitem = 1; + break; + } + else if (difference > 0) + StopLow = StopMiddle + 1; + else + StopHigh = StopMiddle; + } + + if (item->prefix) + { + if (StopLow >= StopHigh) + StopMiddle = StopHigh; + + *nitem = 0; + + while (StopMiddle < (WordEntry *) STRPTR(t) && + WordECompareQueryItem(STRPTR(t), GETOPERAND(q), StopMiddle, item, true) == 0) + { + (*nitem)++; + StopMiddle++; + } + } + + return (*nitem > 0) ? StopHigh : NULL; +} + + +/* + * sort QueryOperands by (length, word) + */ +static int +compareQueryOperand(const void *a, const void *b, void *arg) +{ + char *operand = (char *) arg; + QueryOperand *qa = (*(QueryOperand *const *) a); + QueryOperand *qb = (*(QueryOperand *const *) b); + + return tsCompareString(operand + qa->distance, qa->length, + operand + qb->distance, qb->length, + false); +} + +/* + * Returns a sorted, de-duplicated array of QueryOperands in a query. + * The returned QueryOperands are pointers to the original QueryOperands + * in the query. + * + * Length of the returned array is stored in *size + */ +static QueryOperand ** +SortAndUniqItems(TSQuery q, int *size) +{ + char *operand = GETOPERAND(q); + QueryItem *item = GETQUERY(q); + QueryOperand **res, + **ptr, + **prevptr; + + ptr = res = (QueryOperand **) palloc(sizeof(QueryOperand *) * *size); + + /* Collect all operands from the tree to res */ + while ((*size)--) + { + if (item->type == QI_VAL) + { + *ptr = (QueryOperand *) item; + ptr++; + } + item++; + } + + *size = ptr - res; + if (*size < 2) + return res; + + qsort_arg(res, *size, sizeof(QueryOperand *), compareQueryOperand, operand); + + ptr = res + 1; + prevptr = res; + + /* remove duplicates */ + while (ptr - res < *size) + { + if (compareQueryOperand((void *) ptr, (void *) prevptr, (void *) operand) != 0) + { + prevptr++; + *prevptr = *ptr; + } + ptr++; + } + + *size = prevptr + 1 - res; + return res; +} + +static float +calc_rank_and(const float *w, TSVector t, TSQuery q) +{ + WordEntryPosVector **pos; + WordEntryPosVector1 posnull; + WordEntryPosVector *POSNULL; + int i, + k, + l, + p; + WordEntry *entry, + *firstentry; + WordEntryPos *post, + *ct; + int32 dimt, + lenct, + dist, + nitem; + float res = -1.0; + QueryOperand **item; + int size = q->size; + + item = SortAndUniqItems(q, &size); + if (size < 2) + { + pfree(item); + return calc_rank_or(w, t, q); + } + pos = (WordEntryPosVector **) palloc0(sizeof(WordEntryPosVector *) * q->size); + + /* A dummy WordEntryPos array to use when haspos is false */ + posnull.npos = 1; + posnull.pos[0] = 0; + WEP_SETPOS(posnull.pos[0], MAXENTRYPOS - 1); + POSNULL = (WordEntryPosVector *) &posnull; + + for (i = 0; i < size; i++) + { + firstentry = entry = find_wordentry(t, q, item[i], &nitem); + if (!entry) + continue; + + while (entry - firstentry < nitem) + { + if (entry->haspos) + pos[i] = _POSVECPTR(t, entry); + else + pos[i] = POSNULL; + + dimt = pos[i]->npos; + post = pos[i]->pos; + for (k = 0; k < i; k++) + { + if (!pos[k]) + continue; + lenct = pos[k]->npos; + ct = pos[k]->pos; + for (l = 0; l < dimt; l++) + { + for (p = 0; p < lenct; p++) + { + dist = abs((int) WEP_GETPOS(post[l]) - (int) WEP_GETPOS(ct[p])); + if (dist || (dist == 0 && (pos[i] == POSNULL || pos[k] == POSNULL))) + { + float curw; + + if (!dist) + dist = MAXENTRYPOS; + curw = sqrt(wpos(post[l]) * wpos(ct[p]) * word_distance(dist)); + res = (res < 0) ? curw : 1.0 - (1.0 - res) * (1.0 - curw); + } + } + } + } + + entry++; + } + } + pfree(pos); + pfree(item); + return res; +} + +static float +calc_rank_or(const float *w, TSVector t, TSQuery q) +{ + WordEntry *entry, + *firstentry; + WordEntryPosVector1 posnull; + WordEntryPos *post; + int32 dimt, + j, + i, + nitem; + float res = 0.0; + QueryOperand **item; + int size = q->size; + + /* A dummy WordEntryPos array to use when haspos is false */ + posnull.npos = 1; + posnull.pos[0] = 0; + + item = SortAndUniqItems(q, &size); + + for (i = 0; i < size; i++) + { + float resj, + wjm; + int32 jm; + + firstentry = entry = find_wordentry(t, q, item[i], &nitem); + if (!entry) + continue; + + while (entry - firstentry < nitem) + { + if (entry->haspos) + { + dimt = POSDATALEN(t, entry); + post = POSDATAPTR(t, entry); + } + else + { + dimt = posnull.npos; + post = posnull.pos; + } + + resj = 0.0; + wjm = -1.0; + jm = 0; + for (j = 0; j < dimt; j++) + { + resj = resj + wpos(post[j]) / ((j + 1) * (j + 1)); + if (wpos(post[j]) > wjm) + { + wjm = wpos(post[j]); + jm = j; + } + } +/* + limit (sum(1/i^2),i=1,inf) = pi^2/6 + resj = sum(wi/i^2),i=1,noccurrence, + wi - should be sorted desc, + don't sort for now, just choose maximum weight. This should be corrected + Oleg Bartunov +*/ + res = res + (wjm + resj - wjm / ((jm + 1) * (jm + 1))) / 1.64493406685; + + entry++; + } + } + if (size > 0) + res = res / size; + pfree(item); + return res; +} + +static float +calc_rank(const float *w, TSVector t, TSQuery q, int32 method) +{ + QueryItem *item = GETQUERY(q); + float res = 0.0; + int len; + + if (!t->size || !q->size) + return 0.0; + + /* XXX: What about NOT? */ + res = (item->type == QI_OPR && (item->qoperator.oper == OP_AND || + item->qoperator.oper == OP_PHRASE)) ? + calc_rank_and(w, t, q) : + calc_rank_or(w, t, q); + + if (res < 0) + res = 1e-20f; + + if ((method & RANK_NORM_LOGLENGTH) && t->size > 0) + res /= log((double) (cnt_length(t) + 1)) / log(2.0); + + if (method & RANK_NORM_LENGTH) + { + len = cnt_length(t); + if (len > 0) + res /= (float) len; + } + + /* RANK_NORM_EXTDIST not applicable */ + + if ((method & RANK_NORM_UNIQ) && t->size > 0) + res /= (float) (t->size); + + if ((method & RANK_NORM_LOGUNIQ) && t->size > 0) + res /= log((double) (t->size + 1)) / log(2.0); + + if (method & RANK_NORM_RDIVRPLUS1) + res /= (res + 1); + + return res; +} + +static const float * +getWeights(ArrayType *win) +{ + static __thread float ws[lengthof(weights)]; + int i; + float4 *arrdata; + + if (win == NULL) + return weights; + + if (ARR_NDIM(win) != 1) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("array of weight must be one-dimensional"))); + + if (ArrayGetNItems(ARR_NDIM(win), ARR_DIMS(win)) < lengthof(weights)) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("array of weight is too short"))); + + if (array_contains_nulls(win)) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("array of weight must not contain nulls"))); + + arrdata = (float4 *) ARR_DATA_PTR(win); + for (i = 0; i < lengthof(weights); i++) + { + ws[i] = (arrdata[i] >= 0) ? arrdata[i] : weights[i]; + if (ws[i] > 1.0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("weight out of range"))); + } + + return ws; +} + +Datum +ts_rank_wttf(PG_FUNCTION_ARGS) +{ + ArrayType *win = (ArrayType *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); + TSVector txt = PG_GETARG_TSVECTOR(1); + TSQuery query = PG_GETARG_TSQUERY(2); + int method = PG_GETARG_INT32(3); + float res; + + res = calc_rank(getWeights(win), txt, query, method); + + PG_FREE_IF_COPY(win, 0); + PG_FREE_IF_COPY(txt, 1); + PG_FREE_IF_COPY(query, 2); + PG_RETURN_FLOAT4(res); +} + +Datum +ts_rank_wtt(PG_FUNCTION_ARGS) +{ + ArrayType *win = (ArrayType *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); + TSVector txt = PG_GETARG_TSVECTOR(1); + TSQuery query = PG_GETARG_TSQUERY(2); + float res; + + res = calc_rank(getWeights(win), txt, query, DEF_NORM_METHOD); + + PG_FREE_IF_COPY(win, 0); + PG_FREE_IF_COPY(txt, 1); + PG_FREE_IF_COPY(query, 2); + PG_RETURN_FLOAT4(res); +} + +Datum +ts_rank_ttf(PG_FUNCTION_ARGS) +{ + TSVector txt = PG_GETARG_TSVECTOR(0); + TSQuery query = PG_GETARG_TSQUERY(1); + int method = PG_GETARG_INT32(2); + float res; + + res = calc_rank(getWeights(NULL), txt, query, method); + + PG_FREE_IF_COPY(txt, 0); + PG_FREE_IF_COPY(query, 1); + PG_RETURN_FLOAT4(res); +} + +Datum +ts_rank_tt(PG_FUNCTION_ARGS) +{ + TSVector txt = PG_GETARG_TSVECTOR(0); + TSQuery query = PG_GETARG_TSQUERY(1); + float res; + + res = calc_rank(getWeights(NULL), txt, query, DEF_NORM_METHOD); + + PG_FREE_IF_COPY(txt, 0); + PG_FREE_IF_COPY(query, 1); + PG_RETURN_FLOAT4(res); +} + +typedef struct +{ + union + { + struct + { /* compiled doc representation */ + QueryItem **items; + int16 nitem; + } query; + struct + { /* struct is used for preparing doc + * representation */ + QueryItem *item; + WordEntry *entry; + } map; + } data; + WordEntryPos pos; +} DocRepresentation; + +static int +compareDocR(const void *va, const void *vb) +{ + const DocRepresentation *a = (const DocRepresentation *) va; + const DocRepresentation *b = (const DocRepresentation *) vb; + + if (WEP_GETPOS(a->pos) == WEP_GETPOS(b->pos)) + { + if (WEP_GETWEIGHT(a->pos) == WEP_GETWEIGHT(b->pos)) + { + if (a->data.map.entry == b->data.map.entry) + return 0; + + return (a->data.map.entry > b->data.map.entry) ? 1 : -1; + } + + return (WEP_GETWEIGHT(a->pos) > WEP_GETWEIGHT(b->pos)) ? 1 : -1; + } + + return (WEP_GETPOS(a->pos) > WEP_GETPOS(b->pos)) ? 1 : -1; +} + +#define MAXQROPOS MAXENTRYPOS +typedef struct +{ + bool operandexists; + bool reverseinsert; /* indicates insert order, true means + * descending order */ + uint32 npos; + WordEntryPos pos[MAXQROPOS]; +} QueryRepresentationOperand; + +typedef struct +{ + TSQuery query; + QueryRepresentationOperand *operandData; +} QueryRepresentation; + +#define QR_GET_OPERAND_DATA(q, v) \ + ( (q)->operandData + (((QueryItem*)(v)) - GETQUERY((q)->query)) ) + +/* + * TS_execute callback for matching a tsquery operand to QueryRepresentation + */ +static TSTernaryValue +checkcondition_QueryOperand(void *checkval, QueryOperand *val, + ExecPhraseData *data) +{ + QueryRepresentation *qr = (QueryRepresentation *) checkval; + QueryRepresentationOperand *opData = QR_GET_OPERAND_DATA(qr, val); + + if (!opData->operandexists) + return TS_NO; + + if (data) + { + data->npos = opData->npos; + data->pos = opData->pos; + if (opData->reverseinsert) + data->pos += MAXQROPOS - opData->npos; + } + + return TS_YES; +} + +typedef struct +{ + int pos; + int p; + int q; + DocRepresentation *begin; + DocRepresentation *end; +} CoverExt; + +static void +resetQueryRepresentation(QueryRepresentation *qr, bool reverseinsert) +{ + int i; + + for (i = 0; i < qr->query->size; i++) + { + qr->operandData[i].operandexists = false; + qr->operandData[i].reverseinsert = reverseinsert; + qr->operandData[i].npos = 0; + } +} + +static void +fillQueryRepresentationData(QueryRepresentation *qr, DocRepresentation *entry) +{ + int i; + int lastPos; + QueryRepresentationOperand *opData; + + for (i = 0; i < entry->data.query.nitem; i++) + { + if (entry->data.query.items[i]->type != QI_VAL) + continue; + + opData = QR_GET_OPERAND_DATA(qr, entry->data.query.items[i]); + + opData->operandexists = true; + + if (opData->npos == 0) + { + lastPos = (opData->reverseinsert) ? (MAXQROPOS - 1) : 0; + opData->pos[lastPos] = entry->pos; + opData->npos++; + continue; + } + + lastPos = opData->reverseinsert ? + (MAXQROPOS - opData->npos) : + (opData->npos - 1); + + if (WEP_GETPOS(opData->pos[lastPos]) != WEP_GETPOS(entry->pos)) + { + lastPos = opData->reverseinsert ? + (MAXQROPOS - 1 - opData->npos) : + (opData->npos); + + opData->pos[lastPos] = entry->pos; + opData->npos++; + } + } +} + +static bool +Cover(DocRepresentation *doc, int len, QueryRepresentation *qr, CoverExt *ext) +{ + DocRepresentation *ptr; + int lastpos = ext->pos; + bool found = false; + + /* + * since this function recurses, it could be driven to stack overflow. + * (though any decent compiler will optimize away the tail-recursion. + */ + check_stack_depth(); + + resetQueryRepresentation(qr, false); + + ext->p = INT_MAX; + ext->q = 0; + ptr = doc + ext->pos; + + /* find upper bound of cover from current position, move up */ + while (ptr - doc < len) + { + fillQueryRepresentationData(qr, ptr); + + if (TS_execute(GETQUERY(qr->query), (void *) qr, + TS_EXEC_EMPTY, checkcondition_QueryOperand)) + { + if (WEP_GETPOS(ptr->pos) > ext->q) + { + ext->q = WEP_GETPOS(ptr->pos); + ext->end = ptr; + lastpos = ptr - doc; + found = true; + } + break; + } + ptr++; + } + + if (!found) + return false; + + resetQueryRepresentation(qr, true); + + ptr = doc + lastpos; + + /* find lower bound of cover from found upper bound, move down */ + while (ptr >= doc + ext->pos) + { + /* + * we scan doc from right to left, so pos info in reverse order! + */ + fillQueryRepresentationData(qr, ptr); + + if (TS_execute(GETQUERY(qr->query), (void *) qr, + TS_EXEC_EMPTY, checkcondition_QueryOperand)) + { + if (WEP_GETPOS(ptr->pos) < ext->p) + { + ext->begin = ptr; + ext->p = WEP_GETPOS(ptr->pos); + } + break; + } + ptr--; + } + + if (ext->p <= ext->q) + { + /* + * set position for next try to next lexeme after beginning of found + * cover + */ + ext->pos = (ptr - doc) + 1; + return true; + } + + ext->pos++; + return Cover(doc, len, qr, ext); +} + +static DocRepresentation * +get_docrep(TSVector txt, QueryRepresentation *qr, int *doclen) +{ + QueryItem *item = GETQUERY(qr->query); + WordEntry *entry, + *firstentry; + WordEntryPos *post; + int32 dimt, /* number of 'post' items */ + j, + i, + nitem; + int len = qr->query->size * 4, + cur = 0; + DocRepresentation *doc; + + doc = (DocRepresentation *) palloc(sizeof(DocRepresentation) * len); + + /* + * Iterate through query to make DocRepresentation for words and it's + * entries satisfied by query + */ + for (i = 0; i < qr->query->size; i++) + { + QueryOperand *curoperand; + + if (item[i].type != QI_VAL) + continue; + + curoperand = &item[i].qoperand; + + firstentry = entry = find_wordentry(txt, qr->query, curoperand, &nitem); + if (!entry) + continue; + + /* iterations over entries in tsvector */ + while (entry - firstentry < nitem) + { + if (entry->haspos) + { + dimt = POSDATALEN(txt, entry); + post = POSDATAPTR(txt, entry); + } + else + { + /* ignore words without positions */ + entry++; + continue; + } + + while (cur + dimt >= len) + { + len *= 2; + doc = (DocRepresentation *) repalloc(doc, sizeof(DocRepresentation) * len); + } + + /* iterations over entry's positions */ + for (j = 0; j < dimt; j++) + { + if (curoperand->weight == 0 || + curoperand->weight & (1 << WEP_GETWEIGHT(post[j]))) + { + doc[cur].pos = post[j]; + doc[cur].data.map.entry = entry; + doc[cur].data.map.item = (QueryItem *) curoperand; + cur++; + } + } + + entry++; + } + } + + if (cur > 0) + { + DocRepresentation *rptr = doc + 1, + *wptr = doc, + storage; + + /* + * Sort representation in ascending order by pos and entry + */ + qsort(doc, cur, sizeof(DocRepresentation), compareDocR); + + /* + * Join QueryItem per WordEntry and it's position + */ + storage.pos = doc->pos; + storage.data.query.items = palloc(sizeof(QueryItem *) * qr->query->size); + storage.data.query.items[0] = doc->data.map.item; + storage.data.query.nitem = 1; + + while (rptr - doc < cur) + { + if (rptr->pos == (rptr - 1)->pos && + rptr->data.map.entry == (rptr - 1)->data.map.entry) + { + storage.data.query.items[storage.data.query.nitem] = rptr->data.map.item; + storage.data.query.nitem++; + } + else + { + *wptr = storage; + wptr++; + storage.pos = rptr->pos; + storage.data.query.items = palloc(sizeof(QueryItem *) * qr->query->size); + storage.data.query.items[0] = rptr->data.map.item; + storage.data.query.nitem = 1; + } + + rptr++; + } + + *wptr = storage; + wptr++; + + *doclen = wptr - doc; + return doc; + } + + pfree(doc); + return NULL; +} + +static float4 +calc_rank_cd(const float4 *arrdata, TSVector txt, TSQuery query, int method) +{ + DocRepresentation *doc; + int len, + i, + doclen = 0; + CoverExt ext; + double Wdoc = 0.0; + double invws[lengthof(weights)]; + double SumDist = 0.0, + PrevExtPos = 0.0; + int NExtent = 0; + QueryRepresentation qr; + + + for (i = 0; i < lengthof(weights); i++) + { + invws[i] = ((double) ((arrdata[i] >= 0) ? arrdata[i] : weights[i])); + if (invws[i] > 1.0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("weight out of range"))); + invws[i] = 1.0 / invws[i]; + } + + qr.query = query; + qr.operandData = (QueryRepresentationOperand *) + palloc0(sizeof(QueryRepresentationOperand) * query->size); + + doc = get_docrep(txt, &qr, &doclen); + if (!doc) + { + pfree(qr.operandData); + return 0.0; + } + + MemSet(&ext, 0, sizeof(CoverExt)); + while (Cover(doc, doclen, &qr, &ext)) + { + double Cpos = 0.0; + double InvSum = 0.0; + double CurExtPos; + int nNoise; + DocRepresentation *ptr = ext.begin; + + while (ptr <= ext.end) + { + InvSum += invws[WEP_GETWEIGHT(ptr->pos)]; + ptr++; + } + + Cpos = ((double) (ext.end - ext.begin + 1)) / InvSum; + + /* + * if doc are big enough then ext.q may be equal to ext.p due to limit + * of positional information. In this case we approximate number of + * noise word as half cover's length + */ + nNoise = (ext.q - ext.p) - (ext.end - ext.begin); + if (nNoise < 0) + nNoise = (ext.end - ext.begin) / 2; + Wdoc += Cpos / ((double) (1 + nNoise)); + + CurExtPos = ((double) (ext.q + ext.p)) / 2.0; + if (NExtent > 0 && CurExtPos > PrevExtPos /* prevent division by + * zero in a case of + * multiple lexize */ ) + SumDist += 1.0 / (CurExtPos - PrevExtPos); + + PrevExtPos = CurExtPos; + NExtent++; + } + + if ((method & RANK_NORM_LOGLENGTH) && txt->size > 0) + Wdoc /= log((double) (cnt_length(txt) + 1)); + + if (method & RANK_NORM_LENGTH) + { + len = cnt_length(txt); + if (len > 0) + Wdoc /= (double) len; + } + + if ((method & RANK_NORM_EXTDIST) && NExtent > 0 && SumDist > 0) + Wdoc /= ((double) NExtent) / SumDist; + + if ((method & RANK_NORM_UNIQ) && txt->size > 0) + Wdoc /= (double) (txt->size); + + if ((method & RANK_NORM_LOGUNIQ) && txt->size > 0) + Wdoc /= log((double) (txt->size + 1)) / log(2.0); + + if (method & RANK_NORM_RDIVRPLUS1) + Wdoc /= (Wdoc + 1); + + pfree(doc); + + pfree(qr.operandData); + + return (float4) Wdoc; +} + +Datum +ts_rankcd_wttf(PG_FUNCTION_ARGS) +{ + ArrayType *win = (ArrayType *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); + TSVector txt = PG_GETARG_TSVECTOR(1); + TSQuery query = PG_GETARG_TSQUERY(2); + int method = PG_GETARG_INT32(3); + float res; + + res = calc_rank_cd(getWeights(win), txt, query, method); + + PG_FREE_IF_COPY(win, 0); + PG_FREE_IF_COPY(txt, 1); + PG_FREE_IF_COPY(query, 2); + PG_RETURN_FLOAT4(res); +} + +Datum +ts_rankcd_wtt(PG_FUNCTION_ARGS) +{ + ArrayType *win = (ArrayType *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); + TSVector txt = PG_GETARG_TSVECTOR(1); + TSQuery query = PG_GETARG_TSQUERY(2); + float res; + + res = calc_rank_cd(getWeights(win), txt, query, DEF_NORM_METHOD); + + PG_FREE_IF_COPY(win, 0); + PG_FREE_IF_COPY(txt, 1); + PG_FREE_IF_COPY(query, 2); + PG_RETURN_FLOAT4(res); +} + +Datum +ts_rankcd_ttf(PG_FUNCTION_ARGS) +{ + TSVector txt = PG_GETARG_TSVECTOR(0); + TSQuery query = PG_GETARG_TSQUERY(1); + int method = PG_GETARG_INT32(2); + float res; + + res = calc_rank_cd(getWeights(NULL), txt, query, method); + + PG_FREE_IF_COPY(txt, 0); + PG_FREE_IF_COPY(query, 1); + PG_RETURN_FLOAT4(res); +} + +Datum +ts_rankcd_tt(PG_FUNCTION_ARGS) +{ + TSVector txt = PG_GETARG_TSVECTOR(0); + TSQuery query = PG_GETARG_TSQUERY(1); + float res; + + res = calc_rank_cd(getWeights(NULL), txt, query, DEF_NORM_METHOD); + + PG_FREE_IF_COPY(txt, 0); + PG_FREE_IF_COPY(query, 1); + PG_RETURN_FLOAT4(res); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/tsvector.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/tsvector.c new file mode 100644 index 00000000000..dff0bfe41fc --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/tsvector.c @@ -0,0 +1,558 @@ +/*------------------------------------------------------------------------- + * + * tsvector.c + * I/O functions for tsvector + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/utils/adt/tsvector.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "libpq/pqformat.h" +#include "nodes/miscnodes.h" +#include "tsearch/ts_locale.h" +#include "tsearch/ts_utils.h" +#include "utils/builtins.h" +#include "utils/memutils.h" +#include "varatt.h" + +typedef struct +{ + WordEntry entry; /* must be first! */ + WordEntryPos *pos; + int poslen; /* number of elements in pos */ +} WordEntryIN; + + +/* Compare two WordEntryPos values for qsort */ +int +compareWordEntryPos(const void *a, const void *b) +{ + int apos = WEP_GETPOS(*(const WordEntryPos *) a); + int bpos = WEP_GETPOS(*(const WordEntryPos *) b); + + if (apos == bpos) + return 0; + return (apos > bpos) ? 1 : -1; +} + +/* + * Removes duplicate pos entries. If there's two entries with same pos but + * different weight, the higher weight is retained, so we can't use + * qunique here. + * + * Returns new length. + */ +static int +uniquePos(WordEntryPos *a, int l) +{ + WordEntryPos *ptr, + *res; + + if (l <= 1) + return l; + + qsort(a, l, sizeof(WordEntryPos), compareWordEntryPos); + + res = a; + ptr = a + 1; + while (ptr - a < l) + { + if (WEP_GETPOS(*ptr) != WEP_GETPOS(*res)) + { + res++; + *res = *ptr; + if (res - a >= MAXNUMPOS - 1 || + WEP_GETPOS(*res) == MAXENTRYPOS - 1) + break; + } + else if (WEP_GETWEIGHT(*ptr) > WEP_GETWEIGHT(*res)) + WEP_SETWEIGHT(*res, WEP_GETWEIGHT(*ptr)); + ptr++; + } + + return res + 1 - a; +} + +/* Compare two WordEntryIN values for qsort */ +static int +compareentry(const void *va, const void *vb, void *arg) +{ + const WordEntryIN *a = (const WordEntryIN *) va; + const WordEntryIN *b = (const WordEntryIN *) vb; + char *BufferStr = (char *) arg; + + return tsCompareString(&BufferStr[a->entry.pos], a->entry.len, + &BufferStr[b->entry.pos], b->entry.len, + false); +} + +/* + * Sort an array of WordEntryIN, remove duplicates. + * *outbuflen receives the amount of space needed for strings and positions. + */ +static int +uniqueentry(WordEntryIN *a, int l, char *buf, int *outbuflen) +{ + int buflen; + WordEntryIN *ptr, + *res; + + Assert(l >= 1); + + if (l > 1) + qsort_arg(a, l, sizeof(WordEntryIN), compareentry, buf); + + buflen = 0; + res = a; + ptr = a + 1; + while (ptr - a < l) + { + if (!(ptr->entry.len == res->entry.len && + strncmp(&buf[ptr->entry.pos], &buf[res->entry.pos], + res->entry.len) == 0)) + { + /* done accumulating data into *res, count space needed */ + buflen += res->entry.len; + if (res->entry.haspos) + { + res->poslen = uniquePos(res->pos, res->poslen); + buflen = SHORTALIGN(buflen); + buflen += res->poslen * sizeof(WordEntryPos) + sizeof(uint16); + } + res++; + if (res != ptr) + memcpy(res, ptr, sizeof(WordEntryIN)); + } + else if (ptr->entry.haspos) + { + if (res->entry.haspos) + { + /* append ptr's positions to res's positions */ + int newlen = ptr->poslen + res->poslen; + + res->pos = (WordEntryPos *) + repalloc(res->pos, newlen * sizeof(WordEntryPos)); + memcpy(&res->pos[res->poslen], ptr->pos, + ptr->poslen * sizeof(WordEntryPos)); + res->poslen = newlen; + pfree(ptr->pos); + } + else + { + /* just give ptr's positions to pos */ + res->entry.haspos = 1; + res->pos = ptr->pos; + res->poslen = ptr->poslen; + } + } + ptr++; + } + + /* count space needed for last item */ + buflen += res->entry.len; + if (res->entry.haspos) + { + res->poslen = uniquePos(res->pos, res->poslen); + buflen = SHORTALIGN(buflen); + buflen += res->poslen * sizeof(WordEntryPos) + sizeof(uint16); + } + + *outbuflen = buflen; + return res + 1 - a; +} + +static int +WordEntryCMP(WordEntry *a, WordEntry *b, char *buf) +{ + return compareentry(a, b, buf); +} + + +Datum +tsvectorin(PG_FUNCTION_ARGS) +{ + char *buf = PG_GETARG_CSTRING(0); + Node *escontext = fcinfo->context; + TSVectorParseState state; + WordEntryIN *arr; + int totallen; + int arrlen; /* allocated size of arr */ + WordEntry *inarr; + int len = 0; + TSVector in; + int i; + char *token; + int toklen; + WordEntryPos *pos; + int poslen; + char *strbuf; + int stroff; + + /* + * Tokens are appended to tmpbuf, cur is a pointer to the end of used + * space in tmpbuf. + */ + char *tmpbuf; + char *cur; + int buflen = 256; /* allocated size of tmpbuf */ + + state = init_tsvector_parser(buf, 0, escontext); + + arrlen = 64; + arr = (WordEntryIN *) palloc(sizeof(WordEntryIN) * arrlen); + cur = tmpbuf = (char *) palloc(buflen); + + while (gettoken_tsvector(state, &token, &toklen, &pos, &poslen, NULL)) + { + if (toklen >= MAXSTRLEN) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("word is too long (%ld bytes, max %ld bytes)", + (long) toklen, + (long) (MAXSTRLEN - 1)))); + + if (cur - tmpbuf > MAXSTRPOS) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("string is too long for tsvector (%ld bytes, max %ld bytes)", + (long) (cur - tmpbuf), (long) MAXSTRPOS))); + + /* + * Enlarge buffers if needed + */ + if (len >= arrlen) + { + arrlen *= 2; + arr = (WordEntryIN *) + repalloc(arr, sizeof(WordEntryIN) * arrlen); + } + while ((cur - tmpbuf) + toklen >= buflen) + { + int dist = cur - tmpbuf; + + buflen *= 2; + tmpbuf = (char *) repalloc(tmpbuf, buflen); + cur = tmpbuf + dist; + } + arr[len].entry.len = toklen; + arr[len].entry.pos = cur - tmpbuf; + memcpy(cur, token, toklen); + cur += toklen; + + if (poslen != 0) + { + arr[len].entry.haspos = 1; + arr[len].pos = pos; + arr[len].poslen = poslen; + } + else + { + arr[len].entry.haspos = 0; + arr[len].pos = NULL; + arr[len].poslen = 0; + } + len++; + } + + close_tsvector_parser(state); + + /* Did gettoken_tsvector fail? */ + if (SOFT_ERROR_OCCURRED(escontext)) + PG_RETURN_NULL(); + + if (len > 0) + len = uniqueentry(arr, len, tmpbuf, &buflen); + else + buflen = 0; + + if (buflen > MAXSTRPOS) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("string is too long for tsvector (%d bytes, max %d bytes)", buflen, MAXSTRPOS))); + + totallen = CALCDATASIZE(len, buflen); + in = (TSVector) palloc0(totallen); + SET_VARSIZE(in, totallen); + in->size = len; + inarr = ARRPTR(in); + strbuf = STRPTR(in); + stroff = 0; + for (i = 0; i < len; i++) + { + memcpy(strbuf + stroff, &tmpbuf[arr[i].entry.pos], arr[i].entry.len); + arr[i].entry.pos = stroff; + stroff += arr[i].entry.len; + if (arr[i].entry.haspos) + { + /* This should be unreachable because of MAXNUMPOS restrictions */ + if (arr[i].poslen > 0xFFFF) + elog(ERROR, "positions array too long"); + + /* Copy number of positions */ + stroff = SHORTALIGN(stroff); + *(uint16 *) (strbuf + stroff) = (uint16) arr[i].poslen; + stroff += sizeof(uint16); + + /* Copy positions */ + memcpy(strbuf + stroff, arr[i].pos, arr[i].poslen * sizeof(WordEntryPos)); + stroff += arr[i].poslen * sizeof(WordEntryPos); + + pfree(arr[i].pos); + } + inarr[i] = arr[i].entry; + } + + Assert((strbuf + stroff - (char *) in) == totallen); + + PG_RETURN_TSVECTOR(in); +} + +Datum +tsvectorout(PG_FUNCTION_ARGS) +{ + TSVector out = PG_GETARG_TSVECTOR(0); + char *outbuf; + int32 i, + lenbuf = 0, + pp; + WordEntry *ptr = ARRPTR(out); + char *curbegin, + *curin, + *curout; + + lenbuf = out->size * 2 /* '' */ + out->size - 1 /* space */ + 2 /* \0 */ ; + for (i = 0; i < out->size; i++) + { + lenbuf += ptr[i].len * 2 * pg_database_encoding_max_length() /* for escape */ ; + if (ptr[i].haspos) + lenbuf += 1 /* : */ + 7 /* int2 + , + weight */ * POSDATALEN(out, &(ptr[i])); + } + + curout = outbuf = (char *) palloc(lenbuf); + for (i = 0; i < out->size; i++) + { + curbegin = curin = STRPTR(out) + ptr->pos; + if (i != 0) + *curout++ = ' '; + *curout++ = '\''; + while (curin - curbegin < ptr->len) + { + int len = pg_mblen(curin); + + if (t_iseq(curin, '\'')) + *curout++ = '\''; + else if (t_iseq(curin, '\\')) + *curout++ = '\\'; + + while (len--) + *curout++ = *curin++; + } + + *curout++ = '\''; + if ((pp = POSDATALEN(out, ptr)) != 0) + { + WordEntryPos *wptr; + + *curout++ = ':'; + wptr = POSDATAPTR(out, ptr); + while (pp) + { + curout += sprintf(curout, "%d", WEP_GETPOS(*wptr)); + switch (WEP_GETWEIGHT(*wptr)) + { + case 3: + *curout++ = 'A'; + break; + case 2: + *curout++ = 'B'; + break; + case 1: + *curout++ = 'C'; + break; + case 0: + default: + break; + } + + if (pp > 1) + *curout++ = ','; + pp--; + wptr++; + } + } + ptr++; + } + + *curout = '\0'; + PG_FREE_IF_COPY(out, 0); + PG_RETURN_CSTRING(outbuf); +} + +/* + * Binary Input / Output functions. The binary format is as follows: + * + * uint32 number of lexemes + * + * for each lexeme: + * lexeme text in client encoding, null-terminated + * uint16 number of positions + * for each position: + * uint16 WordEntryPos + */ + +Datum +tsvectorsend(PG_FUNCTION_ARGS) +{ + TSVector vec = PG_GETARG_TSVECTOR(0); + StringInfoData buf; + int i, + j; + WordEntry *weptr = ARRPTR(vec); + + pq_begintypsend(&buf); + + pq_sendint32(&buf, vec->size); + for (i = 0; i < vec->size; i++) + { + uint16 npos; + + /* + * the strings in the TSVector array are not null-terminated, so we + * have to send the null-terminator separately + */ + pq_sendtext(&buf, STRPTR(vec) + weptr->pos, weptr->len); + pq_sendbyte(&buf, '\0'); + + npos = POSDATALEN(vec, weptr); + pq_sendint16(&buf, npos); + + if (npos > 0) + { + WordEntryPos *wepptr = POSDATAPTR(vec, weptr); + + for (j = 0; j < npos; j++) + pq_sendint16(&buf, wepptr[j]); + } + weptr++; + } + + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} + +Datum +tsvectorrecv(PG_FUNCTION_ARGS) +{ + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + TSVector vec; + int i; + int32 nentries; + int datalen; /* number of bytes used in the variable size + * area after fixed size TSVector header and + * WordEntries */ + Size hdrlen; + Size len; /* allocated size of vec */ + bool needSort = false; + + nentries = pq_getmsgint(buf, sizeof(int32)); + if (nentries < 0 || nentries > (MaxAllocSize / sizeof(WordEntry))) + elog(ERROR, "invalid size of tsvector"); + + hdrlen = DATAHDRSIZE + sizeof(WordEntry) * nentries; + + len = hdrlen * 2; /* times two to make room for lexemes */ + vec = (TSVector) palloc0(len); + vec->size = nentries; + + datalen = 0; + for (i = 0; i < nentries; i++) + { + const char *lexeme; + uint16 npos; + size_t lex_len; + + lexeme = pq_getmsgstring(buf); + npos = (uint16) pq_getmsgint(buf, sizeof(uint16)); + + /* sanity checks */ + + lex_len = strlen(lexeme); + if (lex_len > MAXSTRLEN) + elog(ERROR, "invalid tsvector: lexeme too long"); + + if (datalen > MAXSTRPOS) + elog(ERROR, "invalid tsvector: maximum total lexeme length exceeded"); + + if (npos > MAXNUMPOS) + elog(ERROR, "unexpected number of tsvector positions"); + + /* + * Looks valid. Fill the WordEntry struct, and copy lexeme. + * + * But make sure the buffer is large enough first. + */ + while (hdrlen + SHORTALIGN(datalen + lex_len) + + sizeof(uint16) + npos * sizeof(WordEntryPos) >= len) + { + len *= 2; + vec = (TSVector) repalloc(vec, len); + } + + vec->entries[i].haspos = (npos > 0) ? 1 : 0; + vec->entries[i].len = lex_len; + vec->entries[i].pos = datalen; + + memcpy(STRPTR(vec) + datalen, lexeme, lex_len); + + datalen += lex_len; + + if (i > 0 && WordEntryCMP(&vec->entries[i], + &vec->entries[i - 1], + STRPTR(vec)) <= 0) + needSort = true; + + /* Receive positions */ + if (npos > 0) + { + uint16 j; + WordEntryPos *wepptr; + + /* + * Pad to 2-byte alignment if necessary. Though we used palloc0 + * for the initial allocation, subsequent repalloc'd memory areas + * are not initialized to zero. + */ + if (datalen != SHORTALIGN(datalen)) + { + *(STRPTR(vec) + datalen) = '\0'; + datalen = SHORTALIGN(datalen); + } + + memcpy(STRPTR(vec) + datalen, &npos, sizeof(uint16)); + + wepptr = POSDATAPTR(vec, &vec->entries[i]); + for (j = 0; j < npos; j++) + { + wepptr[j] = (WordEntryPos) pq_getmsgint(buf, sizeof(WordEntryPos)); + if (j > 0 && WEP_GETPOS(wepptr[j]) <= WEP_GETPOS(wepptr[j - 1])) + elog(ERROR, "position information is misordered"); + } + + datalen += sizeof(uint16) + npos * sizeof(WordEntryPos); + } + } + + SET_VARSIZE(vec, hdrlen + datalen); + + if (needSort) + qsort_arg(ARRPTR(vec), vec->size, sizeof(WordEntry), + compareentry, STRPTR(vec)); + + PG_RETURN_TSVECTOR(vec); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/tsvector_op.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/tsvector_op.c new file mode 100644 index 00000000000..4457c5d4f9f --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/tsvector_op.c @@ -0,0 +1,2893 @@ +/*------------------------------------------------------------------------- + * + * tsvector_op.c + * operations over tsvector + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/utils/adt/tsvector_op.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <limits.h> + +#include "access/htup_details.h" +#include "catalog/namespace.h" +#include "catalog/pg_type.h" +#include "commands/trigger.h" +#include "executor/spi.h" +#include "funcapi.h" +#include "lib/qunique.h" +#include "mb/pg_wchar.h" +#include "miscadmin.h" +#include "parser/parse_coerce.h" +#include "tsearch/ts_utils.h" +#include "utils/array.h" +#include "utils/builtins.h" +#include "utils/lsyscache.h" +#include "utils/regproc.h" +#include "utils/rel.h" + + +typedef struct +{ + WordEntry *arrb; + WordEntry *arre; + char *values; + char *operand; +} CHKVAL; + + +typedef struct StatEntry +{ + uint32 ndoc; /* zero indicates that we were already here + * while walking through the tree */ + uint32 nentry; + struct StatEntry *left; + struct StatEntry *right; + uint32 lenlexeme; + char lexeme[FLEXIBLE_ARRAY_MEMBER]; +} StatEntry; + +#define STATENTRYHDRSZ (offsetof(StatEntry, lexeme)) + +typedef struct +{ + int32 weight; + + uint32 maxdepth; + + StatEntry **stack; + uint32 stackpos; + + StatEntry *root; +} TSVectorStat; + + +static TSTernaryValue TS_execute_recurse(QueryItem *curitem, void *arg, + uint32 flags, + TSExecuteCallback chkcond); +static bool TS_execute_locations_recurse(QueryItem *curitem, + void *arg, + TSExecuteCallback chkcond, + List **locations); +static int tsvector_bsearch(const TSVector tsv, char *lexeme, int lexeme_len); +static Datum tsvector_update_trigger(PG_FUNCTION_ARGS, bool config_column); + + +/* + * Order: haspos, len, word, for all positions (pos, weight) + */ +static int +silly_cmp_tsvector(const TSVector a, const TSVector b) +{ + if (VARSIZE(a) < VARSIZE(b)) + return -1; + else if (VARSIZE(a) > VARSIZE(b)) + return 1; + else if (a->size < b->size) + return -1; + else if (a->size > b->size) + return 1; + else + { + WordEntry *aptr = ARRPTR(a); + WordEntry *bptr = ARRPTR(b); + int i = 0; + int res; + + + for (i = 0; i < a->size; i++) + { + if (aptr->haspos != bptr->haspos) + { + return (aptr->haspos > bptr->haspos) ? -1 : 1; + } + else if ((res = tsCompareString(STRPTR(a) + aptr->pos, aptr->len, STRPTR(b) + bptr->pos, bptr->len, false)) != 0) + { + return res; + } + else if (aptr->haspos) + { + WordEntryPos *ap = POSDATAPTR(a, aptr); + WordEntryPos *bp = POSDATAPTR(b, bptr); + int j; + + if (POSDATALEN(a, aptr) != POSDATALEN(b, bptr)) + return (POSDATALEN(a, aptr) > POSDATALEN(b, bptr)) ? -1 : 1; + + for (j = 0; j < POSDATALEN(a, aptr); j++) + { + if (WEP_GETPOS(*ap) != WEP_GETPOS(*bp)) + { + return (WEP_GETPOS(*ap) > WEP_GETPOS(*bp)) ? -1 : 1; + } + else if (WEP_GETWEIGHT(*ap) != WEP_GETWEIGHT(*bp)) + { + return (WEP_GETWEIGHT(*ap) > WEP_GETWEIGHT(*bp)) ? -1 : 1; + } + ap++, bp++; + } + } + + aptr++; + bptr++; + } + } + + return 0; +} + +#define TSVECTORCMPFUNC( type, action, ret ) \ +Datum \ +tsvector_##type(PG_FUNCTION_ARGS) \ +{ \ + TSVector a = PG_GETARG_TSVECTOR(0); \ + TSVector b = PG_GETARG_TSVECTOR(1); \ + int res = silly_cmp_tsvector(a, b); \ + PG_FREE_IF_COPY(a,0); \ + PG_FREE_IF_COPY(b,1); \ + PG_RETURN_##ret( res action 0 ); \ +} \ +/* keep compiler quiet - no extra ; */ \ +extern int no_such_variable + +TSVECTORCMPFUNC(lt, <, BOOL); +TSVECTORCMPFUNC(le, <=, BOOL); +TSVECTORCMPFUNC(eq, ==, BOOL); +TSVECTORCMPFUNC(ge, >=, BOOL); +TSVECTORCMPFUNC(gt, >, BOOL); +TSVECTORCMPFUNC(ne, !=, BOOL); +TSVECTORCMPFUNC(cmp, +, INT32); + +Datum +tsvector_strip(PG_FUNCTION_ARGS) +{ + TSVector in = PG_GETARG_TSVECTOR(0); + TSVector out; + int i, + len = 0; + WordEntry *arrin = ARRPTR(in), + *arrout; + char *cur; + + for (i = 0; i < in->size; i++) + len += arrin[i].len; + + len = CALCDATASIZE(in->size, len); + out = (TSVector) palloc0(len); + SET_VARSIZE(out, len); + out->size = in->size; + arrout = ARRPTR(out); + cur = STRPTR(out); + for (i = 0; i < in->size; i++) + { + memcpy(cur, STRPTR(in) + arrin[i].pos, arrin[i].len); + arrout[i].haspos = 0; + arrout[i].len = arrin[i].len; + arrout[i].pos = cur - STRPTR(out); + cur += arrout[i].len; + } + + PG_FREE_IF_COPY(in, 0); + PG_RETURN_POINTER(out); +} + +Datum +tsvector_length(PG_FUNCTION_ARGS) +{ + TSVector in = PG_GETARG_TSVECTOR(0); + int32 ret = in->size; + + PG_FREE_IF_COPY(in, 0); + PG_RETURN_INT32(ret); +} + +Datum +tsvector_setweight(PG_FUNCTION_ARGS) +{ + TSVector in = PG_GETARG_TSVECTOR(0); + char cw = PG_GETARG_CHAR(1); + TSVector out; + int i, + j; + WordEntry *entry; + WordEntryPos *p; + int w = 0; + + switch (cw) + { + case 'A': + case 'a': + w = 3; + break; + case 'B': + case 'b': + w = 2; + break; + case 'C': + case 'c': + w = 1; + break; + case 'D': + case 'd': + w = 0; + break; + default: + /* internal error */ + elog(ERROR, "unrecognized weight: %d", cw); + } + + out = (TSVector) palloc(VARSIZE(in)); + memcpy(out, in, VARSIZE(in)); + entry = ARRPTR(out); + i = out->size; + while (i--) + { + if ((j = POSDATALEN(out, entry)) != 0) + { + p = POSDATAPTR(out, entry); + while (j--) + { + WEP_SETWEIGHT(*p, w); + p++; + } + } + entry++; + } + + PG_FREE_IF_COPY(in, 0); + PG_RETURN_POINTER(out); +} + +/* + * setweight(tsin tsvector, char_weight "char", lexemes "text"[]) + * + * Assign weight w to elements of tsin that are listed in lexemes. + */ +Datum +tsvector_setweight_by_filter(PG_FUNCTION_ARGS) +{ + TSVector tsin = PG_GETARG_TSVECTOR(0); + char char_weight = PG_GETARG_CHAR(1); + ArrayType *lexemes = PG_GETARG_ARRAYTYPE_P(2); + + TSVector tsout; + int i, + j, + nlexemes, + weight; + WordEntry *entry; + Datum *dlexemes; + bool *nulls; + + switch (char_weight) + { + case 'A': + case 'a': + weight = 3; + break; + case 'B': + case 'b': + weight = 2; + break; + case 'C': + case 'c': + weight = 1; + break; + case 'D': + case 'd': + weight = 0; + break; + default: + /* internal error */ + elog(ERROR, "unrecognized weight: %c", char_weight); + } + + tsout = (TSVector) palloc(VARSIZE(tsin)); + memcpy(tsout, tsin, VARSIZE(tsin)); + entry = ARRPTR(tsout); + + deconstruct_array_builtin(lexemes, TEXTOID, &dlexemes, &nulls, &nlexemes); + + /* + * Assuming that lexemes array is significantly shorter than tsvector we + * can iterate through lexemes performing binary search of each lexeme + * from lexemes in tsvector. + */ + for (i = 0; i < nlexemes; i++) + { + char *lex; + int lex_len, + lex_pos; + + /* Ignore null array elements, they surely don't match */ + if (nulls[i]) + continue; + + lex = VARDATA(dlexemes[i]); + lex_len = VARSIZE(dlexemes[i]) - VARHDRSZ; + lex_pos = tsvector_bsearch(tsout, lex, lex_len); + + if (lex_pos >= 0 && (j = POSDATALEN(tsout, entry + lex_pos)) != 0) + { + WordEntryPos *p = POSDATAPTR(tsout, entry + lex_pos); + + while (j--) + { + WEP_SETWEIGHT(*p, weight); + p++; + } + } + } + + PG_FREE_IF_COPY(tsin, 0); + PG_FREE_IF_COPY(lexemes, 2); + + PG_RETURN_POINTER(tsout); +} + +#define compareEntry(pa, a, pb, b) \ + tsCompareString((pa) + (a)->pos, (a)->len, \ + (pb) + (b)->pos, (b)->len, \ + false) + +/* + * Add positions from src to dest after offsetting them by maxpos. + * Return the number added (might be less than expected due to overflow) + */ +static int32 +add_pos(TSVector src, WordEntry *srcptr, + TSVector dest, WordEntry *destptr, + int32 maxpos) +{ + uint16 *clen = &_POSVECPTR(dest, destptr)->npos; + int i; + uint16 slen = POSDATALEN(src, srcptr), + startlen; + WordEntryPos *spos = POSDATAPTR(src, srcptr), + *dpos = POSDATAPTR(dest, destptr); + + if (!destptr->haspos) + *clen = 0; + + startlen = *clen; + for (i = 0; + i < slen && *clen < MAXNUMPOS && + (*clen == 0 || WEP_GETPOS(dpos[*clen - 1]) != MAXENTRYPOS - 1); + i++) + { + WEP_SETWEIGHT(dpos[*clen], WEP_GETWEIGHT(spos[i])); + WEP_SETPOS(dpos[*clen], LIMITPOS(WEP_GETPOS(spos[i]) + maxpos)); + (*clen)++; + } + + if (*clen != startlen) + destptr->haspos = 1; + return *clen - startlen; +} + +/* + * Perform binary search of given lexeme in TSVector. + * Returns lexeme position in TSVector's entry array or -1 if lexeme wasn't + * found. + */ +static int +tsvector_bsearch(const TSVector tsv, char *lexeme, int lexeme_len) +{ + WordEntry *arrin = ARRPTR(tsv); + int StopLow = 0, + StopHigh = tsv->size, + StopMiddle, + cmp; + + while (StopLow < StopHigh) + { + StopMiddle = (StopLow + StopHigh) / 2; + + cmp = tsCompareString(lexeme, lexeme_len, + STRPTR(tsv) + arrin[StopMiddle].pos, + arrin[StopMiddle].len, + false); + + if (cmp < 0) + StopHigh = StopMiddle; + else if (cmp > 0) + StopLow = StopMiddle + 1; + else /* found it */ + return StopMiddle; + } + + return -1; +} + +/* + * qsort comparator functions + */ + +static int +compare_int(const void *va, const void *vb) +{ + int a = *((const int *) va); + int b = *((const int *) vb); + + if (a == b) + return 0; + return (a > b) ? 1 : -1; +} + +static int +compare_text_lexemes(const void *va, const void *vb) +{ + Datum a = *((const Datum *) va); + Datum b = *((const Datum *) vb); + char *alex = VARDATA_ANY(a); + int alex_len = VARSIZE_ANY_EXHDR(a); + char *blex = VARDATA_ANY(b); + int blex_len = VARSIZE_ANY_EXHDR(b); + + return tsCompareString(alex, alex_len, blex, blex_len, false); +} + +/* + * Internal routine to delete lexemes from TSVector by array of offsets. + * + * int *indices_to_delete -- array of lexeme offsets to delete (modified here!) + * int indices_count -- size of that array + * + * Returns new TSVector without given lexemes along with their positions + * and weights. + */ +static TSVector +tsvector_delete_by_indices(TSVector tsv, int *indices_to_delete, + int indices_count) +{ + TSVector tsout; + WordEntry *arrin = ARRPTR(tsv), + *arrout; + char *data = STRPTR(tsv), + *dataout; + int i, /* index in arrin */ + j, /* index in arrout */ + k, /* index in indices_to_delete */ + curoff; /* index in dataout area */ + + /* + * Sort the filter array to simplify membership checks below. Also, get + * rid of any duplicate entries, so that we can assume that indices_count + * is exactly equal to the number of lexemes that will be removed. + */ + if (indices_count > 1) + { + qsort(indices_to_delete, indices_count, sizeof(int), compare_int); + indices_count = qunique(indices_to_delete, indices_count, sizeof(int), + compare_int); + } + + /* + * Here we overestimate tsout size, since we don't know how much space is + * used by the deleted lexeme(s). We will set exact size below. + */ + tsout = (TSVector) palloc0(VARSIZE(tsv)); + + /* This count must be correct because STRPTR(tsout) relies on it. */ + tsout->size = tsv->size - indices_count; + + /* + * Copy tsv to tsout, skipping lexemes listed in indices_to_delete. + */ + arrout = ARRPTR(tsout); + dataout = STRPTR(tsout); + curoff = 0; + for (i = j = k = 0; i < tsv->size; i++) + { + /* + * If current i is present in indices_to_delete, skip this lexeme. + * Since indices_to_delete is already sorted, we only need to check + * the current (k'th) entry. + */ + if (k < indices_count && i == indices_to_delete[k]) + { + k++; + continue; + } + + /* Copy lexeme and its positions and weights */ + memcpy(dataout + curoff, data + arrin[i].pos, arrin[i].len); + arrout[j].haspos = arrin[i].haspos; + arrout[j].len = arrin[i].len; + arrout[j].pos = curoff; + curoff += arrin[i].len; + if (arrin[i].haspos) + { + int len = POSDATALEN(tsv, arrin + i) * sizeof(WordEntryPos) + + sizeof(uint16); + + curoff = SHORTALIGN(curoff); + memcpy(dataout + curoff, + STRPTR(tsv) + SHORTALIGN(arrin[i].pos + arrin[i].len), + len); + curoff += len; + } + + j++; + } + + /* + * k should now be exactly equal to indices_count. If it isn't then the + * caller provided us with indices outside of [0, tsv->size) range and + * estimation of tsout's size is wrong. + */ + Assert(k == indices_count); + + SET_VARSIZE(tsout, CALCDATASIZE(tsout->size, curoff)); + return tsout; +} + +/* + * Delete given lexeme from tsvector. + * Implementation of user-level ts_delete(tsvector, text). + */ +Datum +tsvector_delete_str(PG_FUNCTION_ARGS) +{ + TSVector tsin = PG_GETARG_TSVECTOR(0), + tsout; + text *tlexeme = PG_GETARG_TEXT_PP(1); + char *lexeme = VARDATA_ANY(tlexeme); + int lexeme_len = VARSIZE_ANY_EXHDR(tlexeme), + skip_index; + + if ((skip_index = tsvector_bsearch(tsin, lexeme, lexeme_len)) == -1) + PG_RETURN_POINTER(tsin); + + tsout = tsvector_delete_by_indices(tsin, &skip_index, 1); + + PG_FREE_IF_COPY(tsin, 0); + PG_FREE_IF_COPY(tlexeme, 1); + PG_RETURN_POINTER(tsout); +} + +/* + * Delete given array of lexemes from tsvector. + * Implementation of user-level ts_delete(tsvector, text[]). + */ +Datum +tsvector_delete_arr(PG_FUNCTION_ARGS) +{ + TSVector tsin = PG_GETARG_TSVECTOR(0), + tsout; + ArrayType *lexemes = PG_GETARG_ARRAYTYPE_P(1); + int i, + nlex, + skip_count, + *skip_indices; + Datum *dlexemes; + bool *nulls; + + deconstruct_array_builtin(lexemes, TEXTOID, &dlexemes, &nulls, &nlex); + + /* + * In typical use case array of lexemes to delete is relatively small. So + * here we optimize things for that scenario: iterate through lexarr + * performing binary search of each lexeme from lexarr in tsvector. + */ + skip_indices = palloc0(nlex * sizeof(int)); + for (i = skip_count = 0; i < nlex; i++) + { + char *lex; + int lex_len, + lex_pos; + + /* Ignore null array elements, they surely don't match */ + if (nulls[i]) + continue; + + lex = VARDATA(dlexemes[i]); + lex_len = VARSIZE(dlexemes[i]) - VARHDRSZ; + lex_pos = tsvector_bsearch(tsin, lex, lex_len); + + if (lex_pos >= 0) + skip_indices[skip_count++] = lex_pos; + } + + tsout = tsvector_delete_by_indices(tsin, skip_indices, skip_count); + + pfree(skip_indices); + PG_FREE_IF_COPY(tsin, 0); + PG_FREE_IF_COPY(lexemes, 1); + + PG_RETURN_POINTER(tsout); +} + +/* + * Expand tsvector as table with following columns: + * lexeme: lexeme text + * positions: integer array of lexeme positions + * weights: char array of weights corresponding to positions + */ +Datum +tsvector_unnest(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + TSVector tsin; + + if (SRF_IS_FIRSTCALL()) + { + MemoryContext oldcontext; + TupleDesc tupdesc; + + funcctx = SRF_FIRSTCALL_INIT(); + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + tupdesc = CreateTemplateTupleDesc(3); + TupleDescInitEntry(tupdesc, (AttrNumber) 1, "lexeme", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 2, "positions", + INT2ARRAYOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 3, "weights", + TEXTARRAYOID, -1, 0); + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + funcctx->tuple_desc = tupdesc; + + funcctx->user_fctx = PG_GETARG_TSVECTOR_COPY(0); + + MemoryContextSwitchTo(oldcontext); + } + + funcctx = SRF_PERCALL_SETUP(); + tsin = (TSVector) funcctx->user_fctx; + + if (funcctx->call_cntr < tsin->size) + { + WordEntry *arrin = ARRPTR(tsin); + char *data = STRPTR(tsin); + HeapTuple tuple; + int j, + i = funcctx->call_cntr; + bool nulls[] = {false, false, false}; + Datum values[3]; + + values[0] = PointerGetDatum(cstring_to_text_with_len(data + arrin[i].pos, arrin[i].len)); + + if (arrin[i].haspos) + { + WordEntryPosVector *posv; + Datum *positions; + Datum *weights; + char weight; + + /* + * Internally tsvector stores position and weight in the same + * uint16 (2 bits for weight, 14 for position). Here we extract + * that in two separate arrays. + */ + posv = _POSVECPTR(tsin, arrin + i); + positions = palloc(posv->npos * sizeof(Datum)); + weights = palloc(posv->npos * sizeof(Datum)); + for (j = 0; j < posv->npos; j++) + { + positions[j] = Int16GetDatum(WEP_GETPOS(posv->pos[j])); + weight = 'D' - WEP_GETWEIGHT(posv->pos[j]); + weights[j] = PointerGetDatum(cstring_to_text_with_len(&weight, + 1)); + } + + values[1] = PointerGetDatum(construct_array_builtin(positions, posv->npos, INT2OID)); + values[2] = PointerGetDatum(construct_array_builtin(weights, posv->npos, TEXTOID)); + } + else + { + nulls[1] = nulls[2] = true; + } + + tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); + SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple)); + } + else + { + SRF_RETURN_DONE(funcctx); + } +} + +/* + * Convert tsvector to array of lexemes. + */ +Datum +tsvector_to_array(PG_FUNCTION_ARGS) +{ + TSVector tsin = PG_GETARG_TSVECTOR(0); + WordEntry *arrin = ARRPTR(tsin); + Datum *elements; + int i; + ArrayType *array; + + elements = palloc(tsin->size * sizeof(Datum)); + + for (i = 0; i < tsin->size; i++) + { + elements[i] = PointerGetDatum(cstring_to_text_with_len(STRPTR(tsin) + arrin[i].pos, + arrin[i].len)); + } + + array = construct_array_builtin(elements, tsin->size, TEXTOID); + + pfree(elements); + PG_FREE_IF_COPY(tsin, 0); + PG_RETURN_POINTER(array); +} + +/* + * Build tsvector from array of lexemes. + */ +Datum +array_to_tsvector(PG_FUNCTION_ARGS) +{ + ArrayType *v = PG_GETARG_ARRAYTYPE_P(0); + TSVector tsout; + Datum *dlexemes; + WordEntry *arrout; + bool *nulls; + int nitems, + i, + tslen, + datalen = 0; + char *cur; + + deconstruct_array_builtin(v, TEXTOID, &dlexemes, &nulls, &nitems); + + /* + * Reject nulls and zero length strings (maybe we should just ignore them, + * instead?) + */ + for (i = 0; i < nitems; i++) + { + if (nulls[i]) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("lexeme array may not contain nulls"))); + + if (VARSIZE(dlexemes[i]) - VARHDRSZ == 0) + ereport(ERROR, + (errcode(ERRCODE_ZERO_LENGTH_CHARACTER_STRING), + errmsg("lexeme array may not contain empty strings"))); + } + + /* Sort and de-dup, because this is required for a valid tsvector. */ + if (nitems > 1) + { + qsort(dlexemes, nitems, sizeof(Datum), compare_text_lexemes); + nitems = qunique(dlexemes, nitems, sizeof(Datum), + compare_text_lexemes); + } + + /* Calculate space needed for surviving lexemes. */ + for (i = 0; i < nitems; i++) + datalen += VARSIZE(dlexemes[i]) - VARHDRSZ; + tslen = CALCDATASIZE(nitems, datalen); + + /* Allocate and fill tsvector. */ + tsout = (TSVector) palloc0(tslen); + SET_VARSIZE(tsout, tslen); + tsout->size = nitems; + + arrout = ARRPTR(tsout); + cur = STRPTR(tsout); + for (i = 0; i < nitems; i++) + { + char *lex = VARDATA(dlexemes[i]); + int lex_len = VARSIZE(dlexemes[i]) - VARHDRSZ; + + memcpy(cur, lex, lex_len); + arrout[i].haspos = 0; + arrout[i].len = lex_len; + arrout[i].pos = cur - STRPTR(tsout); + cur += lex_len; + } + + PG_FREE_IF_COPY(v, 0); + PG_RETURN_POINTER(tsout); +} + +/* + * ts_filter(): keep only lexemes with given weights in tsvector. + */ +Datum +tsvector_filter(PG_FUNCTION_ARGS) +{ + TSVector tsin = PG_GETARG_TSVECTOR(0), + tsout; + ArrayType *weights = PG_GETARG_ARRAYTYPE_P(1); + WordEntry *arrin = ARRPTR(tsin), + *arrout; + char *datain = STRPTR(tsin), + *dataout; + Datum *dweights; + bool *nulls; + int nweights; + int i, + j; + int cur_pos = 0; + char mask = 0; + + deconstruct_array_builtin(weights, CHAROID, &dweights, &nulls, &nweights); + + for (i = 0; i < nweights; i++) + { + char char_weight; + + if (nulls[i]) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("weight array may not contain nulls"))); + + char_weight = DatumGetChar(dweights[i]); + switch (char_weight) + { + case 'A': + case 'a': + mask = mask | 8; + break; + case 'B': + case 'b': + mask = mask | 4; + break; + case 'C': + case 'c': + mask = mask | 2; + break; + case 'D': + case 'd': + mask = mask | 1; + break; + default: + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unrecognized weight: \"%c\"", char_weight))); + } + } + + tsout = (TSVector) palloc0(VARSIZE(tsin)); + tsout->size = tsin->size; + arrout = ARRPTR(tsout); + dataout = STRPTR(tsout); + + for (i = j = 0; i < tsin->size; i++) + { + WordEntryPosVector *posvin, + *posvout; + int npos = 0; + int k; + + if (!arrin[i].haspos) + continue; + + posvin = _POSVECPTR(tsin, arrin + i); + posvout = (WordEntryPosVector *) + (dataout + SHORTALIGN(cur_pos + arrin[i].len)); + + for (k = 0; k < posvin->npos; k++) + { + if (mask & (1 << WEP_GETWEIGHT(posvin->pos[k]))) + posvout->pos[npos++] = posvin->pos[k]; + } + + /* if no satisfactory positions found, skip lexeme */ + if (!npos) + continue; + + arrout[j].haspos = true; + arrout[j].len = arrin[i].len; + arrout[j].pos = cur_pos; + + memcpy(dataout + cur_pos, datain + arrin[i].pos, arrin[i].len); + posvout->npos = npos; + cur_pos += SHORTALIGN(arrin[i].len); + cur_pos += POSDATALEN(tsout, arrout + j) * sizeof(WordEntryPos) + + sizeof(uint16); + j++; + } + + tsout->size = j; + if (dataout != STRPTR(tsout)) + memmove(STRPTR(tsout), dataout, cur_pos); + + SET_VARSIZE(tsout, CALCDATASIZE(tsout->size, cur_pos)); + + PG_FREE_IF_COPY(tsin, 0); + PG_RETURN_POINTER(tsout); +} + +Datum +tsvector_concat(PG_FUNCTION_ARGS) +{ + TSVector in1 = PG_GETARG_TSVECTOR(0); + TSVector in2 = PG_GETARG_TSVECTOR(1); + TSVector out; + WordEntry *ptr; + WordEntry *ptr1, + *ptr2; + WordEntryPos *p; + int maxpos = 0, + i, + j, + i1, + i2, + dataoff, + output_bytes, + output_size; + char *data, + *data1, + *data2; + + /* Get max position in in1; we'll need this to offset in2's positions */ + ptr = ARRPTR(in1); + i = in1->size; + while (i--) + { + if ((j = POSDATALEN(in1, ptr)) != 0) + { + p = POSDATAPTR(in1, ptr); + while (j--) + { + if (WEP_GETPOS(*p) > maxpos) + maxpos = WEP_GETPOS(*p); + p++; + } + } + ptr++; + } + + ptr1 = ARRPTR(in1); + ptr2 = ARRPTR(in2); + data1 = STRPTR(in1); + data2 = STRPTR(in2); + i1 = in1->size; + i2 = in2->size; + + /* + * Conservative estimate of space needed. We might need all the data in + * both inputs, and conceivably add a pad byte before position data for + * each item where there was none before. + */ + output_bytes = VARSIZE(in1) + VARSIZE(in2) + i1 + i2; + + out = (TSVector) palloc0(output_bytes); + SET_VARSIZE(out, output_bytes); + + /* + * We must make out->size valid so that STRPTR(out) is sensible. We'll + * collapse out any unused space at the end. + */ + out->size = in1->size + in2->size; + + ptr = ARRPTR(out); + data = STRPTR(out); + dataoff = 0; + while (i1 && i2) + { + int cmp = compareEntry(data1, ptr1, data2, ptr2); + + if (cmp < 0) + { /* in1 first */ + ptr->haspos = ptr1->haspos; + ptr->len = ptr1->len; + memcpy(data + dataoff, data1 + ptr1->pos, ptr1->len); + ptr->pos = dataoff; + dataoff += ptr1->len; + if (ptr->haspos) + { + dataoff = SHORTALIGN(dataoff); + memcpy(data + dataoff, _POSVECPTR(in1, ptr1), POSDATALEN(in1, ptr1) * sizeof(WordEntryPos) + sizeof(uint16)); + dataoff += POSDATALEN(in1, ptr1) * sizeof(WordEntryPos) + sizeof(uint16); + } + + ptr++; + ptr1++; + i1--; + } + else if (cmp > 0) + { /* in2 first */ + ptr->haspos = ptr2->haspos; + ptr->len = ptr2->len; + memcpy(data + dataoff, data2 + ptr2->pos, ptr2->len); + ptr->pos = dataoff; + dataoff += ptr2->len; + if (ptr->haspos) + { + int addlen = add_pos(in2, ptr2, out, ptr, maxpos); + + if (addlen == 0) + ptr->haspos = 0; + else + { + dataoff = SHORTALIGN(dataoff); + dataoff += addlen * sizeof(WordEntryPos) + sizeof(uint16); + } + } + + ptr++; + ptr2++; + i2--; + } + else + { + ptr->haspos = ptr1->haspos | ptr2->haspos; + ptr->len = ptr1->len; + memcpy(data + dataoff, data1 + ptr1->pos, ptr1->len); + ptr->pos = dataoff; + dataoff += ptr1->len; + if (ptr->haspos) + { + if (ptr1->haspos) + { + dataoff = SHORTALIGN(dataoff); + memcpy(data + dataoff, _POSVECPTR(in1, ptr1), POSDATALEN(in1, ptr1) * sizeof(WordEntryPos) + sizeof(uint16)); + dataoff += POSDATALEN(in1, ptr1) * sizeof(WordEntryPos) + sizeof(uint16); + if (ptr2->haspos) + dataoff += add_pos(in2, ptr2, out, ptr, maxpos) * sizeof(WordEntryPos); + } + else /* must have ptr2->haspos */ + { + int addlen = add_pos(in2, ptr2, out, ptr, maxpos); + + if (addlen == 0) + ptr->haspos = 0; + else + { + dataoff = SHORTALIGN(dataoff); + dataoff += addlen * sizeof(WordEntryPos) + sizeof(uint16); + } + } + } + + ptr++; + ptr1++; + ptr2++; + i1--; + i2--; + } + } + + while (i1) + { + ptr->haspos = ptr1->haspos; + ptr->len = ptr1->len; + memcpy(data + dataoff, data1 + ptr1->pos, ptr1->len); + ptr->pos = dataoff; + dataoff += ptr1->len; + if (ptr->haspos) + { + dataoff = SHORTALIGN(dataoff); + memcpy(data + dataoff, _POSVECPTR(in1, ptr1), POSDATALEN(in1, ptr1) * sizeof(WordEntryPos) + sizeof(uint16)); + dataoff += POSDATALEN(in1, ptr1) * sizeof(WordEntryPos) + sizeof(uint16); + } + + ptr++; + ptr1++; + i1--; + } + + while (i2) + { + ptr->haspos = ptr2->haspos; + ptr->len = ptr2->len; + memcpy(data + dataoff, data2 + ptr2->pos, ptr2->len); + ptr->pos = dataoff; + dataoff += ptr2->len; + if (ptr->haspos) + { + int addlen = add_pos(in2, ptr2, out, ptr, maxpos); + + if (addlen == 0) + ptr->haspos = 0; + else + { + dataoff = SHORTALIGN(dataoff); + dataoff += addlen * sizeof(WordEntryPos) + sizeof(uint16); + } + } + + ptr++; + ptr2++; + i2--; + } + + /* + * Instead of checking each offset individually, we check for overflow of + * pos fields once at the end. + */ + if (dataoff > MAXSTRPOS) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("string is too long for tsvector (%d bytes, max %d bytes)", dataoff, MAXSTRPOS))); + + /* + * Adjust sizes (asserting that we didn't overrun the original estimates) + * and collapse out any unused array entries. + */ + output_size = ptr - ARRPTR(out); + Assert(output_size <= out->size); + out->size = output_size; + if (data != STRPTR(out)) + memmove(STRPTR(out), data, dataoff); + output_bytes = CALCDATASIZE(out->size, dataoff); + Assert(output_bytes <= VARSIZE(out)); + SET_VARSIZE(out, output_bytes); + + PG_FREE_IF_COPY(in1, 0); + PG_FREE_IF_COPY(in2, 1); + PG_RETURN_POINTER(out); +} + +/* + * Compare two strings by tsvector rules. + * + * if prefix = true then it returns zero value iff b has prefix a + */ +int32 +tsCompareString(char *a, int lena, char *b, int lenb, bool prefix) +{ + int cmp; + + if (lena == 0) + { + if (prefix) + cmp = 0; /* empty string is prefix of anything */ + else + cmp = (lenb > 0) ? -1 : 0; + } + else if (lenb == 0) + { + cmp = (lena > 0) ? 1 : 0; + } + else + { + cmp = memcmp(a, b, Min((unsigned int) lena, (unsigned int) lenb)); + + if (prefix) + { + if (cmp == 0 && lena > lenb) + cmp = 1; /* a is longer, so not a prefix of b */ + } + else if (cmp == 0 && lena != lenb) + { + cmp = (lena < lenb) ? -1 : 1; + } + } + + return cmp; +} + +/* + * Check weight info or/and fill 'data' with the required positions + */ +static TSTernaryValue +checkclass_str(CHKVAL *chkval, WordEntry *entry, QueryOperand *val, + ExecPhraseData *data) +{ + TSTernaryValue result = TS_NO; + + Assert(data == NULL || data->npos == 0); + + if (entry->haspos) + { + WordEntryPosVector *posvec; + + /* + * We can't use the _POSVECPTR macro here because the pointer to the + * tsvector's lexeme storage is already contained in chkval->values. + */ + posvec = (WordEntryPosVector *) + (chkval->values + SHORTALIGN(entry->pos + entry->len)); + + if (val->weight && data) + { + WordEntryPos *posvec_iter = posvec->pos; + WordEntryPos *dptr; + + /* + * Filter position information by weights + */ + dptr = data->pos = palloc(sizeof(WordEntryPos) * posvec->npos); + data->allocated = true; + + /* Is there a position with a matching weight? */ + while (posvec_iter < posvec->pos + posvec->npos) + { + /* If true, append this position to the data->pos */ + if (val->weight & (1 << WEP_GETWEIGHT(*posvec_iter))) + { + *dptr = WEP_GETPOS(*posvec_iter); + dptr++; + } + + posvec_iter++; + } + + data->npos = dptr - data->pos; + + if (data->npos > 0) + result = TS_YES; + else + { + pfree(data->pos); + data->pos = NULL; + data->allocated = false; + } + } + else if (val->weight) + { + WordEntryPos *posvec_iter = posvec->pos; + + /* Is there a position with a matching weight? */ + while (posvec_iter < posvec->pos + posvec->npos) + { + if (val->weight & (1 << WEP_GETWEIGHT(*posvec_iter))) + { + result = TS_YES; + break; /* no need to go further */ + } + + posvec_iter++; + } + } + else if (data) + { + data->npos = posvec->npos; + data->pos = posvec->pos; + data->allocated = false; + result = TS_YES; + } + else + { + /* simplest case: no weight check, positions not needed */ + result = TS_YES; + } + } + else + { + /* + * Position info is lacking, so if the caller requires it, we can only + * say that maybe there is a match. + * + * Notice, however, that we *don't* check val->weight here. + * Historically, stripped tsvectors are considered to match queries + * whether or not the query has a weight restriction; that's a little + * dubious but we'll preserve the behavior. + */ + if (data) + result = TS_MAYBE; + else + result = TS_YES; + } + + return result; +} + +/* + * TS_execute callback for matching a tsquery operand to plain tsvector data + */ +static TSTernaryValue +checkcondition_str(void *checkval, QueryOperand *val, ExecPhraseData *data) +{ + CHKVAL *chkval = (CHKVAL *) checkval; + WordEntry *StopLow = chkval->arrb; + WordEntry *StopHigh = chkval->arre; + WordEntry *StopMiddle = StopHigh; + TSTernaryValue res = TS_NO; + + /* Loop invariant: StopLow <= val < StopHigh */ + while (StopLow < StopHigh) + { + int difference; + + StopMiddle = StopLow + (StopHigh - StopLow) / 2; + difference = tsCompareString(chkval->operand + val->distance, + val->length, + chkval->values + StopMiddle->pos, + StopMiddle->len, + false); + + if (difference == 0) + { + /* Check weight info & fill 'data' with positions */ + res = checkclass_str(chkval, StopMiddle, val, data); + break; + } + else if (difference > 0) + StopLow = StopMiddle + 1; + else + StopHigh = StopMiddle; + } + + /* + * If it's a prefix search, we should also consider lexemes that the + * search term is a prefix of (which will necessarily immediately follow + * the place we found in the above loop). But we can skip them if there + * was a definite match on the exact term AND the caller doesn't need + * position info. + */ + if (val->prefix && (res != TS_YES || data)) + { + WordEntryPos *allpos = NULL; + int npos = 0, + totalpos = 0; + + /* adjust start position for corner case */ + if (StopLow >= StopHigh) + StopMiddle = StopHigh; + + /* we don't try to re-use any data from the initial match */ + if (data) + { + if (data->allocated) + pfree(data->pos); + data->pos = NULL; + data->allocated = false; + data->npos = 0; + } + res = TS_NO; + + while ((res != TS_YES || data) && + StopMiddle < chkval->arre && + tsCompareString(chkval->operand + val->distance, + val->length, + chkval->values + StopMiddle->pos, + StopMiddle->len, + true) == 0) + { + TSTernaryValue subres; + + subres = checkclass_str(chkval, StopMiddle, val, data); + + if (subres != TS_NO) + { + if (data) + { + /* + * We need to join position information + */ + if (subres == TS_MAYBE) + { + /* + * No position info for this match, so we must report + * MAYBE overall. + */ + res = TS_MAYBE; + /* forget any previous positions */ + npos = 0; + /* don't leak storage */ + if (allpos) + pfree(allpos); + break; + } + + while (npos + data->npos > totalpos) + { + if (totalpos == 0) + { + totalpos = 256; + allpos = palloc(sizeof(WordEntryPos) * totalpos); + } + else + { + totalpos *= 2; + allpos = repalloc(allpos, sizeof(WordEntryPos) * totalpos); + } + } + + memcpy(allpos + npos, data->pos, sizeof(WordEntryPos) * data->npos); + npos += data->npos; + + /* don't leak storage from individual matches */ + if (data->allocated) + pfree(data->pos); + data->pos = NULL; + data->allocated = false; + /* it's important to reset data->npos before next loop */ + data->npos = 0; + } + else + { + /* Don't need positions, just handle YES/MAYBE */ + if (subres == TS_YES || res == TS_NO) + res = subres; + } + } + + StopMiddle++; + } + + if (data && npos > 0) + { + /* Sort and make unique array of found positions */ + data->pos = allpos; + qsort(data->pos, npos, sizeof(WordEntryPos), compareWordEntryPos); + data->npos = qunique(data->pos, npos, sizeof(WordEntryPos), + compareWordEntryPos); + data->allocated = true; + res = TS_YES; + } + } + + return res; +} + +/* + * Compute output position list for a tsquery operator in phrase mode. + * + * Merge the position lists in Ldata and Rdata as specified by "emit", + * returning the result list into *data. The input position lists must be + * sorted and unique, and the output will be as well. + * + * data: pointer to initially-all-zeroes output struct, or NULL + * Ldata, Rdata: input position lists + * emit: bitmask of TSPO_XXX flags + * Loffset: offset to be added to Ldata positions before comparing/outputting + * Roffset: offset to be added to Rdata positions before comparing/outputting + * max_npos: maximum possible required size of output position array + * + * Loffset and Roffset should not be negative, else we risk trying to output + * negative positions, which won't fit into WordEntryPos. + * + * The result is boolean (TS_YES or TS_NO), but for the caller's convenience + * we return it as TSTernaryValue. + * + * Returns TS_YES if any positions were emitted to *data; or if data is NULL, + * returns TS_YES if any positions would have been emitted. + */ +#define TSPO_L_ONLY 0x01 /* emit positions appearing only in L */ +#define TSPO_R_ONLY 0x02 /* emit positions appearing only in R */ +#define TSPO_BOTH 0x04 /* emit positions appearing in both L&R */ + +static TSTernaryValue +TS_phrase_output(ExecPhraseData *data, + ExecPhraseData *Ldata, + ExecPhraseData *Rdata, + int emit, + int Loffset, + int Roffset, + int max_npos) +{ + int Lindex, + Rindex; + + /* Loop until both inputs are exhausted */ + Lindex = Rindex = 0; + while (Lindex < Ldata->npos || Rindex < Rdata->npos) + { + int Lpos, + Rpos; + int output_pos = 0; + + /* + * Fetch current values to compare. WEP_GETPOS() is needed because + * ExecPhraseData->data can point to a tsvector's WordEntryPosVector. + */ + if (Lindex < Ldata->npos) + Lpos = WEP_GETPOS(Ldata->pos[Lindex]) + Loffset; + else + { + /* L array exhausted, so we're done if R_ONLY isn't set */ + if (!(emit & TSPO_R_ONLY)) + break; + Lpos = INT_MAX; + } + if (Rindex < Rdata->npos) + Rpos = WEP_GETPOS(Rdata->pos[Rindex]) + Roffset; + else + { + /* R array exhausted, so we're done if L_ONLY isn't set */ + if (!(emit & TSPO_L_ONLY)) + break; + Rpos = INT_MAX; + } + + /* Merge-join the two input lists */ + if (Lpos < Rpos) + { + /* Lpos is not matched in Rdata, should we output it? */ + if (emit & TSPO_L_ONLY) + output_pos = Lpos; + Lindex++; + } + else if (Lpos == Rpos) + { + /* Lpos and Rpos match ... should we output it? */ + if (emit & TSPO_BOTH) + output_pos = Rpos; + Lindex++; + Rindex++; + } + else /* Lpos > Rpos */ + { + /* Rpos is not matched in Ldata, should we output it? */ + if (emit & TSPO_R_ONLY) + output_pos = Rpos; + Rindex++; + } + + if (output_pos > 0) + { + if (data) + { + /* Store position, first allocating output array if needed */ + if (data->pos == NULL) + { + data->pos = (WordEntryPos *) + palloc(max_npos * sizeof(WordEntryPos)); + data->allocated = true; + } + data->pos[data->npos++] = output_pos; + } + else + { + /* + * Exact positions not needed, so return TS_YES as soon as we + * know there is at least one. + */ + return TS_YES; + } + } + } + + if (data && data->npos > 0) + { + /* Let's assert we didn't overrun the array */ + Assert(data->npos <= max_npos); + return TS_YES; + } + return TS_NO; +} + +/* + * Execute tsquery at or below an OP_PHRASE operator. + * + * This handles tsquery execution at recursion levels where we need to care + * about match locations. + * + * In addition to the same arguments used for TS_execute, the caller may pass + * a preinitialized-to-zeroes ExecPhraseData struct, to be filled with lexeme + * match position info on success. data == NULL if no position data need be + * returned. + * Note: the function assumes data != NULL for operators other than OP_PHRASE. + * This is OK because an outside call always starts from an OP_PHRASE node, + * and all internal recursion cases pass data != NULL. + * + * The detailed semantics of the match data, given that the function returned + * TS_YES (successful match), are: + * + * npos > 0, negate = false: + * query is matched at specified position(s) (and only those positions) + * npos > 0, negate = true: + * query is matched at all positions *except* specified position(s) + * npos = 0, negate = true: + * query is matched at all positions + * npos = 0, negate = false: + * disallowed (this should result in TS_NO or TS_MAYBE, as appropriate) + * + * Successful matches also return a "width" value which is the match width in + * lexemes, less one. Hence, "width" is zero for simple one-lexeme matches, + * and is the sum of the phrase operator distances for phrase matches. Note + * that when width > 0, the listed positions represent the ends of matches not + * the starts. (This unintuitive rule is needed to avoid possibly generating + * negative positions, which wouldn't fit into the WordEntryPos arrays.) + * + * If the TSExecuteCallback function reports that an operand is present + * but fails to provide position(s) for it, we will return TS_MAYBE when + * it is possible but not certain that the query is matched. + * + * When the function returns TS_NO or TS_MAYBE, it must return npos = 0, + * negate = false (which is the state initialized by the caller); but the + * "width" output in such cases is undefined. + */ +static TSTernaryValue +TS_phrase_execute(QueryItem *curitem, void *arg, uint32 flags, + TSExecuteCallback chkcond, + ExecPhraseData *data) +{ + ExecPhraseData Ldata, + Rdata; + TSTernaryValue lmatch, + rmatch; + int Loffset, + Roffset, + maxwidth; + + /* since this function recurses, it could be driven to stack overflow */ + check_stack_depth(); + + /* ... and let's check for query cancel while we're at it */ + CHECK_FOR_INTERRUPTS(); + + if (curitem->type == QI_VAL) + return chkcond(arg, (QueryOperand *) curitem, data); + + switch (curitem->qoperator.oper) + { + case OP_NOT: + + /* + * We need not touch data->width, since a NOT operation does not + * change the match width. + */ + if (flags & TS_EXEC_SKIP_NOT) + { + /* with SKIP_NOT, report NOT as "match everywhere" */ + Assert(data->npos == 0 && !data->negate); + data->negate = true; + return TS_YES; + } + switch (TS_phrase_execute(curitem + 1, arg, flags, chkcond, data)) + { + case TS_NO: + /* change "match nowhere" to "match everywhere" */ + Assert(data->npos == 0 && !data->negate); + data->negate = true; + return TS_YES; + case TS_YES: + if (data->npos > 0) + { + /* we have some positions, invert negate flag */ + data->negate = !data->negate; + return TS_YES; + } + else if (data->negate) + { + /* change "match everywhere" to "match nowhere" */ + data->negate = false; + return TS_NO; + } + /* Should not get here if result was TS_YES */ + Assert(false); + break; + case TS_MAYBE: + /* match positions are, and remain, uncertain */ + return TS_MAYBE; + } + break; + + case OP_PHRASE: + case OP_AND: + memset(&Ldata, 0, sizeof(Ldata)); + memset(&Rdata, 0, sizeof(Rdata)); + + lmatch = TS_phrase_execute(curitem + curitem->qoperator.left, + arg, flags, chkcond, &Ldata); + if (lmatch == TS_NO) + return TS_NO; + + rmatch = TS_phrase_execute(curitem + 1, + arg, flags, chkcond, &Rdata); + if (rmatch == TS_NO) + return TS_NO; + + /* + * If either operand has no position information, then we can't + * return reliable position data, only a MAYBE result. + */ + if (lmatch == TS_MAYBE || rmatch == TS_MAYBE) + return TS_MAYBE; + + if (curitem->qoperator.oper == OP_PHRASE) + { + /* + * Compute Loffset and Roffset suitable for phrase match, and + * compute overall width of whole phrase match. + */ + Loffset = curitem->qoperator.distance + Rdata.width; + Roffset = 0; + if (data) + data->width = curitem->qoperator.distance + + Ldata.width + Rdata.width; + } + else + { + /* + * For OP_AND, set output width and alignment like OP_OR (see + * comment below) + */ + maxwidth = Max(Ldata.width, Rdata.width); + Loffset = maxwidth - Ldata.width; + Roffset = maxwidth - Rdata.width; + if (data) + data->width = maxwidth; + } + + if (Ldata.negate && Rdata.negate) + { + /* !L & !R: treat as !(L | R) */ + (void) TS_phrase_output(data, &Ldata, &Rdata, + TSPO_BOTH | TSPO_L_ONLY | TSPO_R_ONLY, + Loffset, Roffset, + Ldata.npos + Rdata.npos); + if (data) + data->negate = true; + return TS_YES; + } + else if (Ldata.negate) + { + /* !L & R */ + return TS_phrase_output(data, &Ldata, &Rdata, + TSPO_R_ONLY, + Loffset, Roffset, + Rdata.npos); + } + else if (Rdata.negate) + { + /* L & !R */ + return TS_phrase_output(data, &Ldata, &Rdata, + TSPO_L_ONLY, + Loffset, Roffset, + Ldata.npos); + } + else + { + /* straight AND */ + return TS_phrase_output(data, &Ldata, &Rdata, + TSPO_BOTH, + Loffset, Roffset, + Min(Ldata.npos, Rdata.npos)); + } + + case OP_OR: + memset(&Ldata, 0, sizeof(Ldata)); + memset(&Rdata, 0, sizeof(Rdata)); + + lmatch = TS_phrase_execute(curitem + curitem->qoperator.left, + arg, flags, chkcond, &Ldata); + rmatch = TS_phrase_execute(curitem + 1, + arg, flags, chkcond, &Rdata); + + if (lmatch == TS_NO && rmatch == TS_NO) + return TS_NO; + + /* + * If either operand has no position information, then we can't + * return reliable position data, only a MAYBE result. + */ + if (lmatch == TS_MAYBE || rmatch == TS_MAYBE) + return TS_MAYBE; + + /* + * Cope with undefined output width from failed submatch. (This + * takes less code than trying to ensure that all failure returns + * set data->width to zero.) + */ + if (lmatch == TS_NO) + Ldata.width = 0; + if (rmatch == TS_NO) + Rdata.width = 0; + + /* + * For OP_AND and OP_OR, report the width of the wider of the two + * inputs, and align the narrower input's positions to the right + * end of that width. This rule deals at least somewhat + * reasonably with cases like "x <-> (y | z <-> q)". + */ + maxwidth = Max(Ldata.width, Rdata.width); + Loffset = maxwidth - Ldata.width; + Roffset = maxwidth - Rdata.width; + data->width = maxwidth; + + if (Ldata.negate && Rdata.negate) + { + /* !L | !R: treat as !(L & R) */ + (void) TS_phrase_output(data, &Ldata, &Rdata, + TSPO_BOTH, + Loffset, Roffset, + Min(Ldata.npos, Rdata.npos)); + data->negate = true; + return TS_YES; + } + else if (Ldata.negate) + { + /* !L | R: treat as !(L & !R) */ + (void) TS_phrase_output(data, &Ldata, &Rdata, + TSPO_L_ONLY, + Loffset, Roffset, + Ldata.npos); + data->negate = true; + return TS_YES; + } + else if (Rdata.negate) + { + /* L | !R: treat as !(!L & R) */ + (void) TS_phrase_output(data, &Ldata, &Rdata, + TSPO_R_ONLY, + Loffset, Roffset, + Rdata.npos); + data->negate = true; + return TS_YES; + } + else + { + /* straight OR */ + return TS_phrase_output(data, &Ldata, &Rdata, + TSPO_BOTH | TSPO_L_ONLY | TSPO_R_ONLY, + Loffset, Roffset, + Ldata.npos + Rdata.npos); + } + + default: + elog(ERROR, "unrecognized operator: %d", curitem->qoperator.oper); + } + + /* not reachable, but keep compiler quiet */ + return TS_NO; +} + + +/* + * Evaluate tsquery boolean expression. + * + * curitem: current tsquery item (initially, the first one) + * arg: opaque value to pass through to callback function + * flags: bitmask of flag bits shown in ts_utils.h + * chkcond: callback function to check whether a primitive value is present + */ +bool +TS_execute(QueryItem *curitem, void *arg, uint32 flags, + TSExecuteCallback chkcond) +{ + /* + * If we get TS_MAYBE from the recursion, return true. We could only see + * that result if the caller passed TS_EXEC_PHRASE_NO_POS, so there's no + * need to check again. + */ + return TS_execute_recurse(curitem, arg, flags, chkcond) != TS_NO; +} + +/* + * Evaluate tsquery boolean expression. + * + * This is the same as TS_execute except that TS_MAYBE is returned as-is. + */ +TSTernaryValue +TS_execute_ternary(QueryItem *curitem, void *arg, uint32 flags, + TSExecuteCallback chkcond) +{ + return TS_execute_recurse(curitem, arg, flags, chkcond); +} + +/* + * TS_execute recursion for operators above any phrase operator. Here we do + * not need to worry about lexeme positions. As soon as we hit an OP_PHRASE + * operator, we pass it off to TS_phrase_execute which does worry. + */ +static TSTernaryValue +TS_execute_recurse(QueryItem *curitem, void *arg, uint32 flags, + TSExecuteCallback chkcond) +{ + TSTernaryValue lmatch; + + /* since this function recurses, it could be driven to stack overflow */ + check_stack_depth(); + + /* ... and let's check for query cancel while we're at it */ + CHECK_FOR_INTERRUPTS(); + + if (curitem->type == QI_VAL) + return chkcond(arg, (QueryOperand *) curitem, + NULL /* don't need position info */ ); + + switch (curitem->qoperator.oper) + { + case OP_NOT: + if (flags & TS_EXEC_SKIP_NOT) + return TS_YES; + switch (TS_execute_recurse(curitem + 1, arg, flags, chkcond)) + { + case TS_NO: + return TS_YES; + case TS_YES: + return TS_NO; + case TS_MAYBE: + return TS_MAYBE; + } + break; + + case OP_AND: + lmatch = TS_execute_recurse(curitem + curitem->qoperator.left, arg, + flags, chkcond); + if (lmatch == TS_NO) + return TS_NO; + switch (TS_execute_recurse(curitem + 1, arg, flags, chkcond)) + { + case TS_NO: + return TS_NO; + case TS_YES: + return lmatch; + case TS_MAYBE: + return TS_MAYBE; + } + break; + + case OP_OR: + lmatch = TS_execute_recurse(curitem + curitem->qoperator.left, arg, + flags, chkcond); + if (lmatch == TS_YES) + return TS_YES; + switch (TS_execute_recurse(curitem + 1, arg, flags, chkcond)) + { + case TS_NO: + return lmatch; + case TS_YES: + return TS_YES; + case TS_MAYBE: + return TS_MAYBE; + } + break; + + case OP_PHRASE: + + /* + * If we get a MAYBE result, and the caller doesn't want that, + * convert it to NO. It would be more consistent, perhaps, to + * return the result of TS_phrase_execute() verbatim and then + * convert MAYBE results at the top of the recursion. But + * converting at the topmost phrase operator gives results that + * are bug-compatible with the old implementation, so do it like + * this for now. + */ + switch (TS_phrase_execute(curitem, arg, flags, chkcond, NULL)) + { + case TS_NO: + return TS_NO; + case TS_YES: + return TS_YES; + case TS_MAYBE: + return (flags & TS_EXEC_PHRASE_NO_POS) ? TS_MAYBE : TS_NO; + } + break; + + default: + elog(ERROR, "unrecognized operator: %d", curitem->qoperator.oper); + } + + /* not reachable, but keep compiler quiet */ + return TS_NO; +} + +/* + * Evaluate tsquery and report locations of matching terms. + * + * This is like TS_execute except that it returns match locations not just + * success/failure status. The callback function is required to provide + * position data (we report failure if it doesn't). + * + * On successful match, the result is a List of ExecPhraseData structs, one + * for each AND'ed term or phrase operator in the query. Each struct includes + * a sorted array of lexeme positions matching that term. (Recall that for + * phrase operators, the match includes width+1 lexemes, and the recorded + * position is that of the rightmost lexeme.) + * + * OR subexpressions are handled by union'ing their match locations into a + * single List element, which is valid since any of those locations contains + * a match. However, when some of the OR'ed terms are phrase operators, we + * report the maximum width of any of the OR'ed terms, making such cases + * slightly imprecise in the conservative direction. (For example, if the + * tsquery is "(A <-> B) | C", an occurrence of C in the data would be + * reported as though it includes the lexeme to the left of C.) + * + * Locations of NOT subexpressions are not reported. (Obviously, there can + * be no successful NOT matches at top level, or the match would have failed. + * So this amounts to ignoring NOTs underneath ORs.) + * + * The result is NIL if no match, or if position data was not returned. + * + * Arguments are the same as for TS_execute, although flags is currently + * vestigial since none of the defined bits are sensible here. + */ +List * +TS_execute_locations(QueryItem *curitem, void *arg, + uint32 flags, + TSExecuteCallback chkcond) +{ + List *result; + + /* No flags supported, as yet */ + Assert(flags == TS_EXEC_EMPTY); + if (TS_execute_locations_recurse(curitem, arg, chkcond, &result)) + return result; + return NIL; +} + +/* + * TS_execute_locations recursion for operators above any phrase operator. + * OP_PHRASE subexpressions can be passed off to TS_phrase_execute. + */ +static bool +TS_execute_locations_recurse(QueryItem *curitem, void *arg, + TSExecuteCallback chkcond, + List **locations) +{ + bool lmatch, + rmatch; + List *llocations, + *rlocations; + ExecPhraseData *data; + + /* since this function recurses, it could be driven to stack overflow */ + check_stack_depth(); + + /* ... and let's check for query cancel while we're at it */ + CHECK_FOR_INTERRUPTS(); + + /* Default locations result is empty */ + *locations = NIL; + + if (curitem->type == QI_VAL) + { + data = palloc0_object(ExecPhraseData); + if (chkcond(arg, (QueryOperand *) curitem, data) == TS_YES) + { + *locations = list_make1(data); + return true; + } + pfree(data); + return false; + } + + switch (curitem->qoperator.oper) + { + case OP_NOT: + if (!TS_execute_locations_recurse(curitem + 1, arg, chkcond, + &llocations)) + return true; /* we don't pass back any locations */ + return false; + + case OP_AND: + if (!TS_execute_locations_recurse(curitem + curitem->qoperator.left, + arg, chkcond, + &llocations)) + return false; + if (!TS_execute_locations_recurse(curitem + 1, + arg, chkcond, + &rlocations)) + return false; + *locations = list_concat(llocations, rlocations); + return true; + + case OP_OR: + lmatch = TS_execute_locations_recurse(curitem + curitem->qoperator.left, + arg, chkcond, + &llocations); + rmatch = TS_execute_locations_recurse(curitem + 1, + arg, chkcond, + &rlocations); + if (lmatch || rmatch) + { + /* + * We generate an AND'able location struct from each + * combination of sub-matches, following the disjunctive law + * (A & B) | (C & D) = (A | C) & (A | D) & (B | C) & (B | D). + * + * However, if either input didn't produce locations (i.e., it + * failed or was a NOT), we must just return the other list. + */ + if (llocations == NIL) + *locations = rlocations; + else if (rlocations == NIL) + *locations = llocations; + else + { + ListCell *ll; + + foreach(ll, llocations) + { + ExecPhraseData *ldata = (ExecPhraseData *) lfirst(ll); + ListCell *lr; + + foreach(lr, rlocations) + { + ExecPhraseData *rdata = (ExecPhraseData *) lfirst(lr); + + data = palloc0_object(ExecPhraseData); + (void) TS_phrase_output(data, ldata, rdata, + TSPO_BOTH | TSPO_L_ONLY | TSPO_R_ONLY, + 0, 0, + ldata->npos + rdata->npos); + /* Report the larger width, as explained above. */ + data->width = Max(ldata->width, rdata->width); + *locations = lappend(*locations, data); + } + } + } + + return true; + } + return false; + + case OP_PHRASE: + /* We can hand this off to TS_phrase_execute */ + data = palloc0_object(ExecPhraseData); + if (TS_phrase_execute(curitem, arg, TS_EXEC_EMPTY, chkcond, + data) == TS_YES) + { + if (!data->negate) + *locations = list_make1(data); + return true; + } + pfree(data); + return false; + + default: + elog(ERROR, "unrecognized operator: %d", curitem->qoperator.oper); + } + + /* not reachable, but keep compiler quiet */ + return false; +} + +/* + * Detect whether a tsquery boolean expression requires any positive matches + * to values shown in the tsquery. + * + * This is needed to know whether a GIN index search requires full index scan. + * For example, 'x & !y' requires a match of x, so it's sufficient to scan + * entries for x; but 'x | !y' could match rows containing neither x nor y. + */ +bool +tsquery_requires_match(QueryItem *curitem) +{ + /* since this function recurses, it could be driven to stack overflow */ + check_stack_depth(); + + if (curitem->type == QI_VAL) + return true; + + switch (curitem->qoperator.oper) + { + case OP_NOT: + + /* + * Assume there are no required matches underneath a NOT. For + * some cases with nested NOTs, we could prove there's a required + * match, but it seems unlikely to be worth the trouble. + */ + return false; + + case OP_PHRASE: + + /* + * Treat OP_PHRASE as OP_AND here + */ + case OP_AND: + /* If either side requires a match, we're good */ + if (tsquery_requires_match(curitem + curitem->qoperator.left)) + return true; + else + return tsquery_requires_match(curitem + 1); + + case OP_OR: + /* Both sides must require a match */ + if (tsquery_requires_match(curitem + curitem->qoperator.left)) + return tsquery_requires_match(curitem + 1); + else + return false; + + default: + elog(ERROR, "unrecognized operator: %d", curitem->qoperator.oper); + } + + /* not reachable, but keep compiler quiet */ + return false; +} + +/* + * boolean operations + */ +Datum +ts_match_qv(PG_FUNCTION_ARGS) +{ + PG_RETURN_DATUM(DirectFunctionCall2(ts_match_vq, + PG_GETARG_DATUM(1), + PG_GETARG_DATUM(0))); +} + +Datum +ts_match_vq(PG_FUNCTION_ARGS) +{ + TSVector val = PG_GETARG_TSVECTOR(0); + TSQuery query = PG_GETARG_TSQUERY(1); + CHKVAL chkval; + bool result; + + /* empty query matches nothing */ + if (!query->size) + { + PG_FREE_IF_COPY(val, 0); + PG_FREE_IF_COPY(query, 1); + PG_RETURN_BOOL(false); + } + + chkval.arrb = ARRPTR(val); + chkval.arre = chkval.arrb + val->size; + chkval.values = STRPTR(val); + chkval.operand = GETOPERAND(query); + result = TS_execute(GETQUERY(query), + &chkval, + TS_EXEC_EMPTY, + checkcondition_str); + + PG_FREE_IF_COPY(val, 0); + PG_FREE_IF_COPY(query, 1); + PG_RETURN_BOOL(result); +} + +Datum +ts_match_tt(PG_FUNCTION_ARGS) +{ + TSVector vector; + TSQuery query; + bool res; + + vector = DatumGetTSVector(DirectFunctionCall1(to_tsvector, + PG_GETARG_DATUM(0))); + query = DatumGetTSQuery(DirectFunctionCall1(plainto_tsquery, + PG_GETARG_DATUM(1))); + + res = DatumGetBool(DirectFunctionCall2(ts_match_vq, + TSVectorGetDatum(vector), + TSQueryGetDatum(query))); + + pfree(vector); + pfree(query); + + PG_RETURN_BOOL(res); +} + +Datum +ts_match_tq(PG_FUNCTION_ARGS) +{ + TSVector vector; + TSQuery query = PG_GETARG_TSQUERY(1); + bool res; + + vector = DatumGetTSVector(DirectFunctionCall1(to_tsvector, + PG_GETARG_DATUM(0))); + + res = DatumGetBool(DirectFunctionCall2(ts_match_vq, + TSVectorGetDatum(vector), + TSQueryGetDatum(query))); + + pfree(vector); + PG_FREE_IF_COPY(query, 1); + + PG_RETURN_BOOL(res); +} + +/* + * ts_stat statistic function support + */ + + +/* + * Returns the number of positions in value 'wptr' within tsvector 'txt', + * that have a weight equal to one of the weights in 'weight' bitmask. + */ +static int +check_weight(TSVector txt, WordEntry *wptr, int8 weight) +{ + int len = POSDATALEN(txt, wptr); + int num = 0; + WordEntryPos *ptr = POSDATAPTR(txt, wptr); + + while (len--) + { + if (weight & (1 << WEP_GETWEIGHT(*ptr))) + num++; + ptr++; + } + return num; +} + +#define compareStatWord(a,e,t) \ + tsCompareString((a)->lexeme, (a)->lenlexeme, \ + STRPTR(t) + (e)->pos, (e)->len, \ + false) + +static void +insertStatEntry(MemoryContext persistentContext, TSVectorStat *stat, TSVector txt, uint32 off) +{ + WordEntry *we = ARRPTR(txt) + off; + StatEntry *node = stat->root, + *pnode = NULL; + int n, + res = 0; + uint32 depth = 1; + + if (stat->weight == 0) + n = (we->haspos) ? POSDATALEN(txt, we) : 1; + else + n = (we->haspos) ? check_weight(txt, we, stat->weight) : 0; + + if (n == 0) + return; /* nothing to insert */ + + while (node) + { + res = compareStatWord(node, we, txt); + + if (res == 0) + { + break; + } + else + { + pnode = node; + node = (res < 0) ? node->left : node->right; + } + depth++; + } + + if (depth > stat->maxdepth) + stat->maxdepth = depth; + + if (node == NULL) + { + node = MemoryContextAlloc(persistentContext, STATENTRYHDRSZ + we->len); + node->left = node->right = NULL; + node->ndoc = 1; + node->nentry = n; + node->lenlexeme = we->len; + memcpy(node->lexeme, STRPTR(txt) + we->pos, node->lenlexeme); + + if (pnode == NULL) + { + stat->root = node; + } + else + { + if (res < 0) + pnode->left = node; + else + pnode->right = node; + } + } + else + { + node->ndoc++; + node->nentry += n; + } +} + +static void +chooseNextStatEntry(MemoryContext persistentContext, TSVectorStat *stat, TSVector txt, + uint32 low, uint32 high, uint32 offset) +{ + uint32 pos; + uint32 middle = (low + high) >> 1; + + pos = (low + middle) >> 1; + if (low != middle && pos >= offset && pos - offset < txt->size) + insertStatEntry(persistentContext, stat, txt, pos - offset); + pos = (high + middle + 1) >> 1; + if (middle + 1 != high && pos >= offset && pos - offset < txt->size) + insertStatEntry(persistentContext, stat, txt, pos - offset); + + if (low != middle) + chooseNextStatEntry(persistentContext, stat, txt, low, middle, offset); + if (high != middle + 1) + chooseNextStatEntry(persistentContext, stat, txt, middle + 1, high, offset); +} + +/* + * This is written like a custom aggregate function, because the + * original plan was to do just that. Unfortunately, an aggregate function + * can't return a set, so that plan was abandoned. If that limitation is + * lifted in the future, ts_stat could be a real aggregate function so that + * you could use it like this: + * + * SELECT ts_stat(vector_column) FROM vector_table; + * + * where vector_column is a tsvector-type column in vector_table. + */ + +static TSVectorStat * +ts_accum(MemoryContext persistentContext, TSVectorStat *stat, Datum data) +{ + TSVector txt = DatumGetTSVector(data); + uint32 i, + nbit = 0, + offset; + + if (stat == NULL) + { /* Init in first */ + stat = MemoryContextAllocZero(persistentContext, sizeof(TSVectorStat)); + stat->maxdepth = 1; + } + + /* simple check of correctness */ + if (txt == NULL || txt->size == 0) + { + if (txt && txt != (TSVector) DatumGetPointer(data)) + pfree(txt); + return stat; + } + + i = txt->size - 1; + for (; i > 0; i >>= 1) + nbit++; + + nbit = 1 << nbit; + offset = (nbit - txt->size) / 2; + + insertStatEntry(persistentContext, stat, txt, (nbit >> 1) - offset); + chooseNextStatEntry(persistentContext, stat, txt, 0, nbit, offset); + + return stat; +} + +static void +ts_setup_firstcall(FunctionCallInfo fcinfo, FuncCallContext *funcctx, + TSVectorStat *stat) +{ + TupleDesc tupdesc; + MemoryContext oldcontext; + StatEntry *node; + + funcctx->user_fctx = (void *) stat; + + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + stat->stack = palloc0(sizeof(StatEntry *) * (stat->maxdepth + 1)); + stat->stackpos = 0; + + node = stat->root; + /* find leftmost value */ + if (node == NULL) + stat->stack[stat->stackpos] = NULL; + else + for (;;) + { + stat->stack[stat->stackpos] = node; + if (node->left) + { + stat->stackpos++; + node = node->left; + } + else + break; + } + Assert(stat->stackpos <= stat->maxdepth); + + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + funcctx->tuple_desc = tupdesc; + funcctx->attinmeta = TupleDescGetAttInMetadata(tupdesc); + + MemoryContextSwitchTo(oldcontext); +} + +static StatEntry * +walkStatEntryTree(TSVectorStat *stat) +{ + StatEntry *node = stat->stack[stat->stackpos]; + + if (node == NULL) + return NULL; + + if (node->ndoc != 0) + { + /* return entry itself: we already was at left sublink */ + return node; + } + else if (node->right && node->right != stat->stack[stat->stackpos + 1]) + { + /* go on right sublink */ + stat->stackpos++; + node = node->right; + + /* find most-left value */ + for (;;) + { + stat->stack[stat->stackpos] = node; + if (node->left) + { + stat->stackpos++; + node = node->left; + } + else + break; + } + Assert(stat->stackpos <= stat->maxdepth); + } + else + { + /* we already return all left subtree, itself and right subtree */ + if (stat->stackpos == 0) + return NULL; + + stat->stackpos--; + return walkStatEntryTree(stat); + } + + return node; +} + +static Datum +ts_process_call(FuncCallContext *funcctx) +{ + TSVectorStat *st; + StatEntry *entry; + + st = (TSVectorStat *) funcctx->user_fctx; + + entry = walkStatEntryTree(st); + + if (entry != NULL) + { + Datum result; + char *values[3]; + char ndoc[16]; + char nentry[16]; + HeapTuple tuple; + + values[0] = palloc(entry->lenlexeme + 1); + memcpy(values[0], entry->lexeme, entry->lenlexeme); + (values[0])[entry->lenlexeme] = '\0'; + sprintf(ndoc, "%d", entry->ndoc); + values[1] = ndoc; + sprintf(nentry, "%d", entry->nentry); + values[2] = nentry; + + tuple = BuildTupleFromCStrings(funcctx->attinmeta, values); + result = HeapTupleGetDatum(tuple); + + pfree(values[0]); + + /* mark entry as already visited */ + entry->ndoc = 0; + + return result; + } + + return (Datum) 0; +} + +static TSVectorStat * +ts_stat_sql(MemoryContext persistentContext, text *txt, text *ws) +{ + char *query = text_to_cstring(txt); + TSVectorStat *stat; + bool isnull; + Portal portal; + SPIPlanPtr plan; + + if ((plan = SPI_prepare(query, 0, NULL)) == NULL) + /* internal error */ + elog(ERROR, "SPI_prepare(\"%s\") failed", query); + + if ((portal = SPI_cursor_open(NULL, plan, NULL, NULL, true)) == NULL) + /* internal error */ + elog(ERROR, "SPI_cursor_open(\"%s\") failed", query); + + SPI_cursor_fetch(portal, true, 100); + + if (SPI_tuptable == NULL || + SPI_tuptable->tupdesc->natts != 1 || + !IsBinaryCoercible(SPI_gettypeid(SPI_tuptable->tupdesc, 1), + TSVECTOROID)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("ts_stat query must return one tsvector column"))); + + stat = MemoryContextAllocZero(persistentContext, sizeof(TSVectorStat)); + stat->maxdepth = 1; + + if (ws) + { + char *buf; + + buf = VARDATA_ANY(ws); + while (buf - VARDATA_ANY(ws) < VARSIZE_ANY_EXHDR(ws)) + { + if (pg_mblen(buf) == 1) + { + switch (*buf) + { + case 'A': + case 'a': + stat->weight |= 1 << 3; + break; + case 'B': + case 'b': + stat->weight |= 1 << 2; + break; + case 'C': + case 'c': + stat->weight |= 1 << 1; + break; + case 'D': + case 'd': + stat->weight |= 1; + break; + default: + stat->weight |= 0; + } + } + buf += pg_mblen(buf); + } + } + + while (SPI_processed > 0) + { + uint64 i; + + for (i = 0; i < SPI_processed; i++) + { + Datum data = SPI_getbinval(SPI_tuptable->vals[i], SPI_tuptable->tupdesc, 1, &isnull); + + if (!isnull) + stat = ts_accum(persistentContext, stat, data); + } + + SPI_freetuptable(SPI_tuptable); + SPI_cursor_fetch(portal, true, 100); + } + + SPI_freetuptable(SPI_tuptable); + SPI_cursor_close(portal); + SPI_freeplan(plan); + pfree(query); + + return stat; +} + +Datum +ts_stat1(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + Datum result; + + if (SRF_IS_FIRSTCALL()) + { + TSVectorStat *stat; + text *txt = PG_GETARG_TEXT_PP(0); + + funcctx = SRF_FIRSTCALL_INIT(); + SPI_connect(); + stat = ts_stat_sql(funcctx->multi_call_memory_ctx, txt, NULL); + PG_FREE_IF_COPY(txt, 0); + ts_setup_firstcall(fcinfo, funcctx, stat); + SPI_finish(); + } + + funcctx = SRF_PERCALL_SETUP(); + if ((result = ts_process_call(funcctx)) != (Datum) 0) + SRF_RETURN_NEXT(funcctx, result); + SRF_RETURN_DONE(funcctx); +} + +Datum +ts_stat2(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + Datum result; + + if (SRF_IS_FIRSTCALL()) + { + TSVectorStat *stat; + text *txt = PG_GETARG_TEXT_PP(0); + text *ws = PG_GETARG_TEXT_PP(1); + + funcctx = SRF_FIRSTCALL_INIT(); + SPI_connect(); + stat = ts_stat_sql(funcctx->multi_call_memory_ctx, txt, ws); + PG_FREE_IF_COPY(txt, 0); + PG_FREE_IF_COPY(ws, 1); + ts_setup_firstcall(fcinfo, funcctx, stat); + SPI_finish(); + } + + funcctx = SRF_PERCALL_SETUP(); + if ((result = ts_process_call(funcctx)) != (Datum) 0) + SRF_RETURN_NEXT(funcctx, result); + SRF_RETURN_DONE(funcctx); +} + + +/* + * Triggers for automatic update of a tsvector column from text column(s) + * + * Trigger arguments are either + * name of tsvector col, name of tsconfig to use, name(s) of text col(s) + * name of tsvector col, name of regconfig col, name(s) of text col(s) + * ie, tsconfig can either be specified by name, or indirectly as the + * contents of a regconfig field in the row. If the name is used, it must + * be explicitly schema-qualified. + */ +Datum +tsvector_update_trigger_byid(PG_FUNCTION_ARGS) +{ + return tsvector_update_trigger(fcinfo, false); +} + +Datum +tsvector_update_trigger_bycolumn(PG_FUNCTION_ARGS) +{ + return tsvector_update_trigger(fcinfo, true); +} + +static Datum +tsvector_update_trigger(PG_FUNCTION_ARGS, bool config_column) +{ + TriggerData *trigdata; + Trigger *trigger; + Relation rel; + HeapTuple rettuple = NULL; + int tsvector_attr_num, + i; + ParsedText prs; + Datum datum; + bool isnull; + text *txt; + Oid cfgId; + bool update_needed; + + /* Check call context */ + if (!CALLED_AS_TRIGGER(fcinfo)) /* internal error */ + elog(ERROR, "tsvector_update_trigger: not fired by trigger manager"); + + trigdata = (TriggerData *) fcinfo->context; + if (!TRIGGER_FIRED_FOR_ROW(trigdata->tg_event)) + elog(ERROR, "tsvector_update_trigger: must be fired for row"); + if (!TRIGGER_FIRED_BEFORE(trigdata->tg_event)) + elog(ERROR, "tsvector_update_trigger: must be fired BEFORE event"); + + if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event)) + { + rettuple = trigdata->tg_trigtuple; + update_needed = true; + } + else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event)) + { + rettuple = trigdata->tg_newtuple; + update_needed = false; /* computed below */ + } + else + elog(ERROR, "tsvector_update_trigger: must be fired for INSERT or UPDATE"); + + trigger = trigdata->tg_trigger; + rel = trigdata->tg_relation; + + if (trigger->tgnargs < 3) + elog(ERROR, "tsvector_update_trigger: arguments must be tsvector_field, ts_config, text_field1, ...)"); + + /* Find the target tsvector column */ + tsvector_attr_num = SPI_fnumber(rel->rd_att, trigger->tgargs[0]); + if (tsvector_attr_num == SPI_ERROR_NOATTRIBUTE) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("tsvector column \"%s\" does not exist", + trigger->tgargs[0]))); + /* This will effectively reject system columns, so no separate test: */ + if (!IsBinaryCoercible(SPI_gettypeid(rel->rd_att, tsvector_attr_num), + TSVECTOROID)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("column \"%s\" is not of tsvector type", + trigger->tgargs[0]))); + + /* Find the configuration to use */ + if (config_column) + { + int config_attr_num; + + config_attr_num = SPI_fnumber(rel->rd_att, trigger->tgargs[1]); + if (config_attr_num == SPI_ERROR_NOATTRIBUTE) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("configuration column \"%s\" does not exist", + trigger->tgargs[1]))); + if (!IsBinaryCoercible(SPI_gettypeid(rel->rd_att, config_attr_num), + REGCONFIGOID)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("column \"%s\" is not of regconfig type", + trigger->tgargs[1]))); + + datum = SPI_getbinval(rettuple, rel->rd_att, config_attr_num, &isnull); + if (isnull) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("configuration column \"%s\" must not be null", + trigger->tgargs[1]))); + cfgId = DatumGetObjectId(datum); + } + else + { + List *names; + + names = stringToQualifiedNameList(trigger->tgargs[1], NULL); + /* require a schema so that results are not search path dependent */ + if (list_length(names) < 2) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("text search configuration name \"%s\" must be schema-qualified", + trigger->tgargs[1]))); + cfgId = get_ts_config_oid(names, false); + } + + /* initialize parse state */ + prs.lenwords = 32; + prs.curwords = 0; + prs.pos = 0; + prs.words = (ParsedWord *) palloc(sizeof(ParsedWord) * prs.lenwords); + + /* find all words in indexable column(s) */ + for (i = 2; i < trigger->tgnargs; i++) + { + int numattr; + + numattr = SPI_fnumber(rel->rd_att, trigger->tgargs[i]); + if (numattr == SPI_ERROR_NOATTRIBUTE) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column \"%s\" does not exist", + trigger->tgargs[i]))); + if (!IsBinaryCoercible(SPI_gettypeid(rel->rd_att, numattr), TEXTOID)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("column \"%s\" is not of a character type", + trigger->tgargs[i]))); + + if (bms_is_member(numattr - FirstLowInvalidHeapAttributeNumber, trigdata->tg_updatedcols)) + update_needed = true; + + datum = SPI_getbinval(rettuple, rel->rd_att, numattr, &isnull); + if (isnull) + continue; + + txt = DatumGetTextPP(datum); + + parsetext(cfgId, &prs, VARDATA_ANY(txt), VARSIZE_ANY_EXHDR(txt)); + + if (txt != (text *) DatumGetPointer(datum)) + pfree(txt); + } + + if (update_needed) + { + /* make tsvector value */ + datum = TSVectorGetDatum(make_tsvector(&prs)); + isnull = false; + + /* and insert it into tuple */ + rettuple = heap_modify_tuple_by_cols(rettuple, rel->rd_att, + 1, &tsvector_attr_num, + &datum, &isnull); + + pfree(DatumGetPointer(datum)); + } + + return PointerGetDatum(rettuple); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/tsvector_parser.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/tsvector_parser.c new file mode 100644 index 00000000000..13e075831fe --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/tsvector_parser.c @@ -0,0 +1,388 @@ +/*------------------------------------------------------------------------- + * + * tsvector_parser.c + * Parser for tsvector + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/utils/adt/tsvector_parser.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "tsearch/ts_locale.h" +#include "tsearch/ts_utils.h" + + +/* + * Private state of tsvector parser. Note that tsquery also uses this code to + * parse its input, hence the boolean flags. The oprisdelim and is_tsquery + * flags are both true or both false in current usage, but we keep them + * separate for clarity. + * + * If oprisdelim is set, the following characters are treated as delimiters + * (in addition to whitespace): ! | & ( ) + * + * is_tsquery affects *only* the content of error messages. + * + * is_web can be true to further modify tsquery parsing. + * + * If escontext is an ErrorSaveContext node, then soft errors can be + * captured there rather than being thrown. + */ +struct TSVectorParseStateData +{ + char *prsbuf; /* next input character */ + char *bufstart; /* whole string (used only for errors) */ + char *word; /* buffer to hold the current word */ + int len; /* size in bytes allocated for 'word' */ + int eml; /* max bytes per character */ + bool oprisdelim; /* treat ! | * ( ) as delimiters? */ + bool is_tsquery; /* say "tsquery" not "tsvector" in errors? */ + bool is_web; /* we're in websearch_to_tsquery() */ + Node *escontext; /* for soft error reporting */ +}; + + +/* + * Initializes a parser state object for the given input string. + * A bitmask of flags (see ts_utils.h) and an error context object + * can be provided as well. + */ +TSVectorParseState +init_tsvector_parser(char *input, int flags, Node *escontext) +{ + TSVectorParseState state; + + state = (TSVectorParseState) palloc(sizeof(struct TSVectorParseStateData)); + state->prsbuf = input; + state->bufstart = input; + state->len = 32; + state->word = (char *) palloc(state->len); + state->eml = pg_database_encoding_max_length(); + state->oprisdelim = (flags & P_TSV_OPR_IS_DELIM) != 0; + state->is_tsquery = (flags & P_TSV_IS_TSQUERY) != 0; + state->is_web = (flags & P_TSV_IS_WEB) != 0; + state->escontext = escontext; + + return state; +} + +/* + * Reinitializes parser to parse 'input', instead of previous input. + * + * Note that bufstart (the string reported in errors) is not changed. + */ +void +reset_tsvector_parser(TSVectorParseState state, char *input) +{ + state->prsbuf = input; +} + +/* + * Shuts down a tsvector parser. + */ +void +close_tsvector_parser(TSVectorParseState state) +{ + pfree(state->word); + pfree(state); +} + +/* increase the size of 'word' if needed to hold one more character */ +#define RESIZEPRSBUF \ +do { \ + int clen = curpos - state->word; \ + if ( clen + state->eml >= state->len ) \ + { \ + state->len *= 2; \ + state->word = (char *) repalloc(state->word, state->len); \ + curpos = state->word + clen; \ + } \ +} while (0) + +/* Fills gettoken_tsvector's output parameters, and returns true */ +#define RETURN_TOKEN \ +do { \ + if (pos_ptr != NULL) \ + { \ + *pos_ptr = pos; \ + *poslen = npos; \ + } \ + else if (pos != NULL) \ + pfree(pos); \ + \ + if (strval != NULL) \ + *strval = state->word; \ + if (lenval != NULL) \ + *lenval = curpos - state->word; \ + if (endptr != NULL) \ + *endptr = state->prsbuf; \ + return true; \ +} while(0) + + +/* State codes used in gettoken_tsvector */ +#define WAITWORD 1 +#define WAITENDWORD 2 +#define WAITNEXTCHAR 3 +#define WAITENDCMPLX 4 +#define WAITPOSINFO 5 +#define INPOSINFO 6 +#define WAITPOSDELIM 7 +#define WAITCHARCMPLX 8 + +#define PRSSYNTAXERROR return prssyntaxerror(state) + +static bool +prssyntaxerror(TSVectorParseState state) +{ + errsave(state->escontext, + (errcode(ERRCODE_SYNTAX_ERROR), + state->is_tsquery ? + errmsg("syntax error in tsquery: \"%s\"", state->bufstart) : + errmsg("syntax error in tsvector: \"%s\"", state->bufstart))); + /* In soft error situation, return false as convenience for caller */ + return false; +} + + +/* + * Get next token from string being parsed. Returns true if successful, + * false if end of input string is reached or soft error. + * + * On success, these output parameters are filled in: + * + * *strval pointer to token + * *lenval length of *strval + * *pos_ptr pointer to a palloc'd array of positions and weights + * associated with the token. If the caller is not interested + * in the information, NULL can be supplied. Otherwise + * the caller is responsible for pfreeing the array. + * *poslen number of elements in *pos_ptr + * *endptr scan resumption point + * + * Pass NULL for any unwanted output parameters. + * + * If state->escontext is an ErrorSaveContext, then caller must check + * SOFT_ERROR_OCCURRED() to determine whether a "false" result means + * error or normal end-of-string. + */ +bool +gettoken_tsvector(TSVectorParseState state, + char **strval, int *lenval, + WordEntryPos **pos_ptr, int *poslen, + char **endptr) +{ + int oldstate = 0; + char *curpos = state->word; + int statecode = WAITWORD; + + /* + * pos is for collecting the comma delimited list of positions followed by + * the actual token. + */ + WordEntryPos *pos = NULL; + int npos = 0; /* elements of pos used */ + int posalen = 0; /* allocated size of pos */ + + while (1) + { + if (statecode == WAITWORD) + { + if (*(state->prsbuf) == '\0') + return false; + else if (!state->is_web && t_iseq(state->prsbuf, '\'')) + statecode = WAITENDCMPLX; + else if (!state->is_web && t_iseq(state->prsbuf, '\\')) + { + statecode = WAITNEXTCHAR; + oldstate = WAITENDWORD; + } + else if ((state->oprisdelim && ISOPERATOR(state->prsbuf)) || + (state->is_web && t_iseq(state->prsbuf, '"'))) + PRSSYNTAXERROR; + else if (!t_isspace(state->prsbuf)) + { + COPYCHAR(curpos, state->prsbuf); + curpos += pg_mblen(state->prsbuf); + statecode = WAITENDWORD; + } + } + else if (statecode == WAITNEXTCHAR) + { + if (*(state->prsbuf) == '\0') + ereturn(state->escontext, false, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("there is no escaped character: \"%s\"", + state->bufstart))); + else + { + RESIZEPRSBUF; + COPYCHAR(curpos, state->prsbuf); + curpos += pg_mblen(state->prsbuf); + Assert(oldstate != 0); + statecode = oldstate; + } + } + else if (statecode == WAITENDWORD) + { + if (!state->is_web && t_iseq(state->prsbuf, '\\')) + { + statecode = WAITNEXTCHAR; + oldstate = WAITENDWORD; + } + else if (t_isspace(state->prsbuf) || *(state->prsbuf) == '\0' || + (state->oprisdelim && ISOPERATOR(state->prsbuf)) || + (state->is_web && t_iseq(state->prsbuf, '"'))) + { + RESIZEPRSBUF; + if (curpos == state->word) + PRSSYNTAXERROR; + *(curpos) = '\0'; + RETURN_TOKEN; + } + else if (t_iseq(state->prsbuf, ':')) + { + if (curpos == state->word) + PRSSYNTAXERROR; + *(curpos) = '\0'; + if (state->oprisdelim) + RETURN_TOKEN; + else + statecode = INPOSINFO; + } + else + { + RESIZEPRSBUF; + COPYCHAR(curpos, state->prsbuf); + curpos += pg_mblen(state->prsbuf); + } + } + else if (statecode == WAITENDCMPLX) + { + if (!state->is_web && t_iseq(state->prsbuf, '\'')) + { + statecode = WAITCHARCMPLX; + } + else if (!state->is_web && t_iseq(state->prsbuf, '\\')) + { + statecode = WAITNEXTCHAR; + oldstate = WAITENDCMPLX; + } + else if (*(state->prsbuf) == '\0') + PRSSYNTAXERROR; + else + { + RESIZEPRSBUF; + COPYCHAR(curpos, state->prsbuf); + curpos += pg_mblen(state->prsbuf); + } + } + else if (statecode == WAITCHARCMPLX) + { + if (!state->is_web && t_iseq(state->prsbuf, '\'')) + { + RESIZEPRSBUF; + COPYCHAR(curpos, state->prsbuf); + curpos += pg_mblen(state->prsbuf); + statecode = WAITENDCMPLX; + } + else + { + RESIZEPRSBUF; + *(curpos) = '\0'; + if (curpos == state->word) + PRSSYNTAXERROR; + if (state->oprisdelim) + { + /* state->prsbuf+=pg_mblen(state->prsbuf); */ + RETURN_TOKEN; + } + else + statecode = WAITPOSINFO; + continue; /* recheck current character */ + } + } + else if (statecode == WAITPOSINFO) + { + if (t_iseq(state->prsbuf, ':')) + statecode = INPOSINFO; + else + RETURN_TOKEN; + } + else if (statecode == INPOSINFO) + { + if (t_isdigit(state->prsbuf)) + { + if (posalen == 0) + { + posalen = 4; + pos = (WordEntryPos *) palloc(sizeof(WordEntryPos) * posalen); + npos = 0; + } + else if (npos + 1 >= posalen) + { + posalen *= 2; + pos = (WordEntryPos *) repalloc(pos, sizeof(WordEntryPos) * posalen); + } + npos++; + WEP_SETPOS(pos[npos - 1], LIMITPOS(atoi(state->prsbuf))); + /* we cannot get here in tsquery, so no need for 2 errmsgs */ + if (WEP_GETPOS(pos[npos - 1]) == 0) + ereturn(state->escontext, false, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("wrong position info in tsvector: \"%s\"", + state->bufstart))); + WEP_SETWEIGHT(pos[npos - 1], 0); + statecode = WAITPOSDELIM; + } + else + PRSSYNTAXERROR; + } + else if (statecode == WAITPOSDELIM) + { + if (t_iseq(state->prsbuf, ',')) + statecode = INPOSINFO; + else if (t_iseq(state->prsbuf, 'a') || t_iseq(state->prsbuf, 'A') || t_iseq(state->prsbuf, '*')) + { + if (WEP_GETWEIGHT(pos[npos - 1])) + PRSSYNTAXERROR; + WEP_SETWEIGHT(pos[npos - 1], 3); + } + else if (t_iseq(state->prsbuf, 'b') || t_iseq(state->prsbuf, 'B')) + { + if (WEP_GETWEIGHT(pos[npos - 1])) + PRSSYNTAXERROR; + WEP_SETWEIGHT(pos[npos - 1], 2); + } + else if (t_iseq(state->prsbuf, 'c') || t_iseq(state->prsbuf, 'C')) + { + if (WEP_GETWEIGHT(pos[npos - 1])) + PRSSYNTAXERROR; + WEP_SETWEIGHT(pos[npos - 1], 1); + } + else if (t_iseq(state->prsbuf, 'd') || t_iseq(state->prsbuf, 'D')) + { + if (WEP_GETWEIGHT(pos[npos - 1])) + PRSSYNTAXERROR; + WEP_SETWEIGHT(pos[npos - 1], 0); + } + else if (t_isspace(state->prsbuf) || + *(state->prsbuf) == '\0') + RETURN_TOKEN; + else if (!t_isdigit(state->prsbuf)) + PRSSYNTAXERROR; + } + else /* internal error */ + elog(ERROR, "unrecognized state in gettoken_tsvector: %d", + statecode); + + /* get next char */ + state->prsbuf += pg_mblen(state->prsbuf); + } +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/uuid.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/uuid.c new file mode 100644 index 00000000000..4f7aa768fda --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/uuid.c @@ -0,0 +1,423 @@ +/*------------------------------------------------------------------------- + * + * uuid.c + * Functions for the built-in type "uuid". + * + * Copyright (c) 2007-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/utils/adt/uuid.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "common/hashfn.h" +#include "lib/hyperloglog.h" +#include "libpq/pqformat.h" +#include "port/pg_bswap.h" +#include "utils/builtins.h" +#include "utils/guc.h" +#include "utils/sortsupport.h" +#include "utils/uuid.h" + +/* sortsupport for uuid */ +typedef struct +{ + int64 input_count; /* number of non-null values seen */ + bool estimating; /* true if estimating cardinality */ + + hyperLogLogState abbr_card; /* cardinality estimator */ +} uuid_sortsupport_state; + +static void string_to_uuid(const char *source, pg_uuid_t *uuid, Node *escontext); +static int uuid_internal_cmp(const pg_uuid_t *arg1, const pg_uuid_t *arg2); +static int uuid_fast_cmp(Datum x, Datum y, SortSupport ssup); +static bool uuid_abbrev_abort(int memtupcount, SortSupport ssup); +static Datum uuid_abbrev_convert(Datum original, SortSupport ssup); + +Datum +uuid_in(PG_FUNCTION_ARGS) +{ + char *uuid_str = PG_GETARG_CSTRING(0); + pg_uuid_t *uuid; + + uuid = (pg_uuid_t *) palloc(sizeof(*uuid)); + string_to_uuid(uuid_str, uuid, fcinfo->context); + PG_RETURN_UUID_P(uuid); +} + +Datum +uuid_out(PG_FUNCTION_ARGS) +{ + pg_uuid_t *uuid = PG_GETARG_UUID_P(0); + static const char hex_chars[] = "0123456789abcdef"; + StringInfoData buf; + int i; + + initStringInfo(&buf); + for (i = 0; i < UUID_LEN; i++) + { + int hi; + int lo; + + /* + * We print uuid values as a string of 8, 4, 4, 4, and then 12 + * hexadecimal characters, with each group is separated by a hyphen + * ("-"). Therefore, add the hyphens at the appropriate places here. + */ + if (i == 4 || i == 6 || i == 8 || i == 10) + appendStringInfoChar(&buf, '-'); + + hi = uuid->data[i] >> 4; + lo = uuid->data[i] & 0x0F; + + appendStringInfoChar(&buf, hex_chars[hi]); + appendStringInfoChar(&buf, hex_chars[lo]); + } + + PG_RETURN_CSTRING(buf.data); +} + +/* + * We allow UUIDs as a series of 32 hexadecimal digits with an optional dash + * after each group of 4 hexadecimal digits, and optionally surrounded by {}. + * (The canonical format 8x-4x-4x-4x-12x, where "nx" means n hexadecimal + * digits, is the only one used for output.) + */ +static void +string_to_uuid(const char *source, pg_uuid_t *uuid, Node *escontext) +{ + const char *src = source; + bool braces = false; + int i; + + if (src[0] == '{') + { + src++; + braces = true; + } + + for (i = 0; i < UUID_LEN; i++) + { + char str_buf[3]; + + if (src[0] == '\0' || src[1] == '\0') + goto syntax_error; + memcpy(str_buf, src, 2); + if (!isxdigit((unsigned char) str_buf[0]) || + !isxdigit((unsigned char) str_buf[1])) + goto syntax_error; + + str_buf[2] = '\0'; + uuid->data[i] = (unsigned char) strtoul(str_buf, NULL, 16); + src += 2; + if (src[0] == '-' && (i % 2) == 1 && i < UUID_LEN - 1) + src++; + } + + if (braces) + { + if (*src != '}') + goto syntax_error; + src++; + } + + if (*src != '\0') + goto syntax_error; + + return; + +syntax_error: + ereturn(escontext,, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s: \"%s\"", + "uuid", source))); +} + +Datum +uuid_recv(PG_FUNCTION_ARGS) +{ + StringInfo buffer = (StringInfo) PG_GETARG_POINTER(0); + pg_uuid_t *uuid; + + uuid = (pg_uuid_t *) palloc(UUID_LEN); + memcpy(uuid->data, pq_getmsgbytes(buffer, UUID_LEN), UUID_LEN); + PG_RETURN_POINTER(uuid); +} + +Datum +uuid_send(PG_FUNCTION_ARGS) +{ + pg_uuid_t *uuid = PG_GETARG_UUID_P(0); + StringInfoData buffer; + + pq_begintypsend(&buffer); + pq_sendbytes(&buffer, uuid->data, UUID_LEN); + PG_RETURN_BYTEA_P(pq_endtypsend(&buffer)); +} + +/* internal uuid compare function */ +static int +uuid_internal_cmp(const pg_uuid_t *arg1, const pg_uuid_t *arg2) +{ + return memcmp(arg1->data, arg2->data, UUID_LEN); +} + +Datum +uuid_lt(PG_FUNCTION_ARGS) +{ + pg_uuid_t *arg1 = PG_GETARG_UUID_P(0); + pg_uuid_t *arg2 = PG_GETARG_UUID_P(1); + + PG_RETURN_BOOL(uuid_internal_cmp(arg1, arg2) < 0); +} + +Datum +uuid_le(PG_FUNCTION_ARGS) +{ + pg_uuid_t *arg1 = PG_GETARG_UUID_P(0); + pg_uuid_t *arg2 = PG_GETARG_UUID_P(1); + + PG_RETURN_BOOL(uuid_internal_cmp(arg1, arg2) <= 0); +} + +Datum +uuid_eq(PG_FUNCTION_ARGS) +{ + pg_uuid_t *arg1 = PG_GETARG_UUID_P(0); + pg_uuid_t *arg2 = PG_GETARG_UUID_P(1); + + PG_RETURN_BOOL(uuid_internal_cmp(arg1, arg2) == 0); +} + +Datum +uuid_ge(PG_FUNCTION_ARGS) +{ + pg_uuid_t *arg1 = PG_GETARG_UUID_P(0); + pg_uuid_t *arg2 = PG_GETARG_UUID_P(1); + + PG_RETURN_BOOL(uuid_internal_cmp(arg1, arg2) >= 0); +} + +Datum +uuid_gt(PG_FUNCTION_ARGS) +{ + pg_uuid_t *arg1 = PG_GETARG_UUID_P(0); + pg_uuid_t *arg2 = PG_GETARG_UUID_P(1); + + PG_RETURN_BOOL(uuid_internal_cmp(arg1, arg2) > 0); +} + +Datum +uuid_ne(PG_FUNCTION_ARGS) +{ + pg_uuid_t *arg1 = PG_GETARG_UUID_P(0); + pg_uuid_t *arg2 = PG_GETARG_UUID_P(1); + + PG_RETURN_BOOL(uuid_internal_cmp(arg1, arg2) != 0); +} + +/* handler for btree index operator */ +Datum +uuid_cmp(PG_FUNCTION_ARGS) +{ + pg_uuid_t *arg1 = PG_GETARG_UUID_P(0); + pg_uuid_t *arg2 = PG_GETARG_UUID_P(1); + + PG_RETURN_INT32(uuid_internal_cmp(arg1, arg2)); +} + +/* + * Sort support strategy routine + */ +Datum +uuid_sortsupport(PG_FUNCTION_ARGS) +{ + SortSupport ssup = (SortSupport) PG_GETARG_POINTER(0); + + ssup->comparator = uuid_fast_cmp; + ssup->ssup_extra = NULL; + + if (ssup->abbreviate) + { + uuid_sortsupport_state *uss; + MemoryContext oldcontext; + + oldcontext = MemoryContextSwitchTo(ssup->ssup_cxt); + + uss = palloc(sizeof(uuid_sortsupport_state)); + uss->input_count = 0; + uss->estimating = true; + initHyperLogLog(&uss->abbr_card, 10); + + ssup->ssup_extra = uss; + + ssup->comparator = ssup_datum_unsigned_cmp; + ssup->abbrev_converter = uuid_abbrev_convert; + ssup->abbrev_abort = uuid_abbrev_abort; + ssup->abbrev_full_comparator = uuid_fast_cmp; + + MemoryContextSwitchTo(oldcontext); + } + + PG_RETURN_VOID(); +} + +/* + * SortSupport comparison func + */ +static int +uuid_fast_cmp(Datum x, Datum y, SortSupport ssup) +{ + pg_uuid_t *arg1 = DatumGetUUIDP(x); + pg_uuid_t *arg2 = DatumGetUUIDP(y); + + return uuid_internal_cmp(arg1, arg2); +} + +/* + * Callback for estimating effectiveness of abbreviated key optimization. + * + * We pay no attention to the cardinality of the non-abbreviated data, because + * there is no equality fast-path within authoritative uuid comparator. + */ +static bool +uuid_abbrev_abort(int memtupcount, SortSupport ssup) +{ + uuid_sortsupport_state *uss = ssup->ssup_extra; + double abbr_card; + + if (memtupcount < 10000 || uss->input_count < 10000 || !uss->estimating) + return false; + + abbr_card = estimateHyperLogLog(&uss->abbr_card); + + /* + * If we have >100k distinct values, then even if we were sorting many + * billion rows we'd likely still break even, and the penalty of undoing + * that many rows of abbrevs would probably not be worth it. Stop even + * counting at that point. + */ + if (abbr_card > 100000.0) + { +#ifdef TRACE_SORT + if (trace_sort) + elog(LOG, + "uuid_abbrev: estimation ends at cardinality %f" + " after " INT64_FORMAT " values (%d rows)", + abbr_card, uss->input_count, memtupcount); +#endif + uss->estimating = false; + return false; + } + + /* + * Target minimum cardinality is 1 per ~2k of non-null inputs. 0.5 row + * fudge factor allows us to abort earlier on genuinely pathological data + * where we've had exactly one abbreviated value in the first 2k + * (non-null) rows. + */ + if (abbr_card < uss->input_count / 2000.0 + 0.5) + { +#ifdef TRACE_SORT + if (trace_sort) + elog(LOG, + "uuid_abbrev: aborting abbreviation at cardinality %f" + " below threshold %f after " INT64_FORMAT " values (%d rows)", + abbr_card, uss->input_count / 2000.0 + 0.5, uss->input_count, + memtupcount); +#endif + return true; + } + +#ifdef TRACE_SORT + if (trace_sort) + elog(LOG, + "uuid_abbrev: cardinality %f after " INT64_FORMAT + " values (%d rows)", abbr_card, uss->input_count, memtupcount); +#endif + + return false; +} + +/* + * Conversion routine for sortsupport. Converts original uuid representation + * to abbreviated key representation. Our encoding strategy is simple -- pack + * the first `sizeof(Datum)` bytes of uuid data into a Datum (on little-endian + * machines, the bytes are stored in reverse order), and treat it as an + * unsigned integer. + */ +static Datum +uuid_abbrev_convert(Datum original, SortSupport ssup) +{ + uuid_sortsupport_state *uss = ssup->ssup_extra; + pg_uuid_t *authoritative = DatumGetUUIDP(original); + Datum res; + + memcpy(&res, authoritative->data, sizeof(Datum)); + uss->input_count += 1; + + if (uss->estimating) + { + uint32 tmp; + +#if SIZEOF_DATUM == 8 + tmp = (uint32) res ^ (uint32) ((uint64) res >> 32); +#else /* SIZEOF_DATUM != 8 */ + tmp = (uint32) res; +#endif + + addHyperLogLog(&uss->abbr_card, DatumGetUInt32(hash_uint32(tmp))); + } + + /* + * Byteswap on little-endian machines. + * + * This is needed so that ssup_datum_unsigned_cmp() (an unsigned integer + * 3-way comparator) works correctly on all platforms. If we didn't do + * this, the comparator would have to call memcmp() with a pair of + * pointers to the first byte of each abbreviated key, which is slower. + */ + res = DatumBigEndianToNative(res); + + return res; +} + +/* hash index support */ +Datum +uuid_hash(PG_FUNCTION_ARGS) +{ + pg_uuid_t *key = PG_GETARG_UUID_P(0); + + return hash_any(key->data, UUID_LEN); +} + +Datum +uuid_hash_extended(PG_FUNCTION_ARGS) +{ + pg_uuid_t *key = PG_GETARG_UUID_P(0); + + return hash_any_extended(key->data, UUID_LEN, PG_GETARG_INT64(1)); +} + +Datum +gen_random_uuid(PG_FUNCTION_ARGS) +{ + pg_uuid_t *uuid = palloc(UUID_LEN); + + if (!pg_strong_random(uuid, UUID_LEN)) + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("could not generate random values"))); + + /* + * Set magic numbers for a "version 4" (pseudorandom) UUID, see + * http://tools.ietf.org/html/rfc4122#section-4.4 + */ + uuid->data[6] = (uuid->data[6] & 0x0f) | 0x40; /* time_hi_and_version */ + uuid->data[8] = (uuid->data[8] & 0x3f) | 0x80; /* clock_seq_hi_and_reserved */ + + PG_RETURN_UUID_P(uuid); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/varbit.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/varbit.c new file mode 100644 index 00000000000..3dbbd1207f9 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/varbit.c @@ -0,0 +1,1894 @@ +/*------------------------------------------------------------------------- + * + * varbit.c + * Functions for the SQL datatypes BIT() and BIT VARYING(). + * + * The data structure contains the following elements: + * header -- length of the whole data structure (incl header) + * in bytes (as with all varying length datatypes) + * data section -- private data section for the bits data structures + * bitlength -- length of the bit string in bits + * bitdata -- bit string, most significant byte first + * + * The length of the bitdata vector should always be exactly as many + * bytes as are needed for the given bitlength. If the bitlength is + * not a multiple of 8, the extra low-order padding bits of the last + * byte must be zeroes. + * + * attypmod is defined as the length of the bit string in bits, or for + * varying bits the maximum length. + * + * Code originally contributed by Adriaan Joubert. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/utils/adt/varbit.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/htup_details.h" +#include "common/int.h" +#include "libpq/pqformat.h" +#include "nodes/nodeFuncs.h" +#include "nodes/supportnodes.h" +#include "port/pg_bitutils.h" +#include "utils/array.h" +#include "utils/builtins.h" +#include "utils/varbit.h" + +#define HEXDIG(z) ((z)<10 ? ((z)+'0') : ((z)-10+'A')) + +/* Mask off any bits that should be zero in the last byte of a bitstring */ +#define VARBIT_PAD(vb) \ + do { \ + int32 pad_ = VARBITPAD(vb); \ + Assert(pad_ >= 0 && pad_ < BITS_PER_BYTE); \ + if (pad_ > 0) \ + *(VARBITS(vb) + VARBITBYTES(vb) - 1) &= BITMASK << pad_; \ + } while (0) + +/* + * Many functions work byte-by-byte, so they have a pointer handy to the + * last-plus-one byte, which saves a cycle or two. + */ +#define VARBIT_PAD_LAST(vb, ptr) \ + do { \ + int32 pad_ = VARBITPAD(vb); \ + Assert(pad_ >= 0 && pad_ < BITS_PER_BYTE); \ + if (pad_ > 0) \ + *((ptr) - 1) &= BITMASK << pad_; \ + } while (0) + +/* Assert proper padding of a bitstring */ +#ifdef USE_ASSERT_CHECKING +#define VARBIT_CORRECTLY_PADDED(vb) \ + do { \ + int32 pad_ = VARBITPAD(vb); \ + Assert(pad_ >= 0 && pad_ < BITS_PER_BYTE); \ + Assert(pad_ == 0 || \ + (*(VARBITS(vb) + VARBITBYTES(vb) - 1) & ~(BITMASK << pad_)) == 0); \ + } while (0) +#else +#define VARBIT_CORRECTLY_PADDED(vb) ((void) 0) +#endif + +static VarBit *bit_catenate(VarBit *arg1, VarBit *arg2); +static VarBit *bitsubstring(VarBit *arg, int32 s, int32 l, + bool length_not_specified); +static VarBit *bit_overlay(VarBit *t1, VarBit *t2, int sp, int sl); + + +/* + * common code for bittypmodin and varbittypmodin + */ +static int32 +anybit_typmodin(ArrayType *ta, const char *typename) +{ + int32 typmod; + int32 *tl; + int n; + + tl = ArrayGetIntegerTypmods(ta, &n); + + /* + * we're not too tense about good error message here because grammar + * shouldn't allow wrong number of modifiers for BIT + */ + if (n != 1) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid type modifier"))); + + if (*tl < 1) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("length for type %s must be at least 1", + typename))); + if (*tl > (MaxAttrSize * BITS_PER_BYTE)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("length for type %s cannot exceed %d", + typename, MaxAttrSize * BITS_PER_BYTE))); + + typmod = *tl; + + return typmod; +} + +/* + * common code for bittypmodout and varbittypmodout + */ +static char * +anybit_typmodout(int32 typmod) +{ + char *res = (char *) palloc(64); + + if (typmod >= 0) + snprintf(res, 64, "(%d)", typmod); + else + *res = '\0'; + + return res; +} + + +/* + * bit_in - + * converts a char string to the internal representation of a bitstring. + * The length is determined by the number of bits required plus + * VARHDRSZ bytes or from atttypmod. + */ +Datum +bit_in(PG_FUNCTION_ARGS) +{ + char *input_string = PG_GETARG_CSTRING(0); +#ifdef NOT_USED + Oid typelem = PG_GETARG_OID(1); +#endif + int32 atttypmod = PG_GETARG_INT32(2); + Node *escontext = fcinfo->context; + VarBit *result; /* The resulting bit string */ + char *sp; /* pointer into the character string */ + bits8 *r; /* pointer into the result */ + int len, /* Length of the whole data structure */ + bitlen, /* Number of bits in the bit string */ + slen; /* Length of the input string */ + bool bit_not_hex; /* false = hex string true = bit string */ + int bc; + bits8 x = 0; + + /* Check that the first character is a b or an x */ + if (input_string[0] == 'b' || input_string[0] == 'B') + { + bit_not_hex = true; + sp = input_string + 1; + } + else if (input_string[0] == 'x' || input_string[0] == 'X') + { + bit_not_hex = false; + sp = input_string + 1; + } + else + { + /* + * Otherwise it's binary. This allows things like cast('1001' as bit) + * to work transparently. + */ + bit_not_hex = true; + sp = input_string; + } + + /* + * Determine bitlength from input string. MaxAllocSize ensures a regular + * input is small enough, but we must check hex input. + */ + slen = strlen(sp); + if (bit_not_hex) + bitlen = slen; + else + { + if (slen > VARBITMAXLEN / 4) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("bit string length exceeds the maximum allowed (%d)", + VARBITMAXLEN))); + bitlen = slen * 4; + } + + /* + * Sometimes atttypmod is not supplied. If it is supplied we need to make + * sure that the bitstring fits. + */ + if (atttypmod <= 0) + atttypmod = bitlen; + else if (bitlen != atttypmod) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_STRING_DATA_LENGTH_MISMATCH), + errmsg("bit string length %d does not match type bit(%d)", + bitlen, atttypmod))); + + len = VARBITTOTALLEN(atttypmod); + /* set to 0 so that *r is always initialised and string is zero-padded */ + result = (VarBit *) palloc0(len); + SET_VARSIZE(result, len); + VARBITLEN(result) = atttypmod; + + r = VARBITS(result); + if (bit_not_hex) + { + /* Parse the bit representation of the string */ + /* We know it fits, as bitlen was compared to atttypmod */ + x = HIGHBIT; + for (; *sp; sp++) + { + if (*sp == '1') + *r |= x; + else if (*sp != '0') + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("\"%.*s\" is not a valid binary digit", + pg_mblen(sp), sp))); + + x >>= 1; + if (x == 0) + { + x = HIGHBIT; + r++; + } + } + } + else + { + /* Parse the hex representation of the string */ + for (bc = 0; *sp; sp++) + { + if (*sp >= '0' && *sp <= '9') + x = (bits8) (*sp - '0'); + else if (*sp >= 'A' && *sp <= 'F') + x = (bits8) (*sp - 'A') + 10; + else if (*sp >= 'a' && *sp <= 'f') + x = (bits8) (*sp - 'a') + 10; + else + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("\"%.*s\" is not a valid hexadecimal digit", + pg_mblen(sp), sp))); + + if (bc) + { + *r++ |= x; + bc = 0; + } + else + { + *r = x << 4; + bc = 1; + } + } + } + + PG_RETURN_VARBIT_P(result); +} + + +Datum +bit_out(PG_FUNCTION_ARGS) +{ +#if 1 + /* same as varbit output */ + return varbit_out(fcinfo); +#else + + /* + * This is how one would print a hex string, in case someone wants to + * write a formatting function. + */ + VarBit *s = PG_GETARG_VARBIT_P(0); + char *result, + *r; + bits8 *sp; + int i, + len, + bitlen; + + /* Assertion to help catch any bit functions that don't pad correctly */ + VARBIT_CORRECTLY_PADDED(s); + + bitlen = VARBITLEN(s); + len = (bitlen + 3) / 4; + result = (char *) palloc(len + 2); + sp = VARBITS(s); + r = result; + *r++ = 'X'; + /* we cheat by knowing that we store full bytes zero padded */ + for (i = 0; i < len; i += 2, sp++) + { + *r++ = HEXDIG((*sp) >> 4); + *r++ = HEXDIG((*sp) & 0xF); + } + + /* + * Go back one step if we printed a hex number that was not part of the + * bitstring anymore + */ + if (i > len) + r--; + *r = '\0'; + + PG_RETURN_CSTRING(result); +#endif +} + +/* + * bit_recv - converts external binary format to bit + */ +Datum +bit_recv(PG_FUNCTION_ARGS) +{ + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + +#ifdef NOT_USED + Oid typelem = PG_GETARG_OID(1); +#endif + int32 atttypmod = PG_GETARG_INT32(2); + VarBit *result; + int len, + bitlen; + + bitlen = pq_getmsgint(buf, sizeof(int32)); + if (bitlen < 0 || bitlen > VARBITMAXLEN) + ereport(ERROR, + (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION), + errmsg("invalid length in external bit string"))); + + /* + * Sometimes atttypmod is not supplied. If it is supplied we need to make + * sure that the bitstring fits. + */ + if (atttypmod > 0 && bitlen != atttypmod) + ereport(ERROR, + (errcode(ERRCODE_STRING_DATA_LENGTH_MISMATCH), + errmsg("bit string length %d does not match type bit(%d)", + bitlen, atttypmod))); + + len = VARBITTOTALLEN(bitlen); + result = (VarBit *) palloc(len); + SET_VARSIZE(result, len); + VARBITLEN(result) = bitlen; + + pq_copymsgbytes(buf, (char *) VARBITS(result), VARBITBYTES(result)); + + /* Make sure last byte is correctly zero-padded */ + VARBIT_PAD(result); + + PG_RETURN_VARBIT_P(result); +} + +/* + * bit_send - converts bit to binary format + */ +Datum +bit_send(PG_FUNCTION_ARGS) +{ + /* Exactly the same as varbit_send, so share code */ + return varbit_send(fcinfo); +} + +/* + * bit() + * Converts a bit() type to a specific internal length. + * len is the bitlength specified in the column definition. + * + * If doing implicit cast, raise error when source data is wrong length. + * If doing explicit cast, silently truncate or zero-pad to specified length. + */ +Datum +bit(PG_FUNCTION_ARGS) +{ + VarBit *arg = PG_GETARG_VARBIT_P(0); + int32 len = PG_GETARG_INT32(1); + bool isExplicit = PG_GETARG_BOOL(2); + VarBit *result; + int rlen; + + /* No work if typmod is invalid or supplied data matches it already */ + if (len <= 0 || len > VARBITMAXLEN || len == VARBITLEN(arg)) + PG_RETURN_VARBIT_P(arg); + + if (!isExplicit) + ereport(ERROR, + (errcode(ERRCODE_STRING_DATA_LENGTH_MISMATCH), + errmsg("bit string length %d does not match type bit(%d)", + VARBITLEN(arg), len))); + + rlen = VARBITTOTALLEN(len); + /* set to 0 so that string is zero-padded */ + result = (VarBit *) palloc0(rlen); + SET_VARSIZE(result, rlen); + VARBITLEN(result) = len; + + memcpy(VARBITS(result), VARBITS(arg), + Min(VARBITBYTES(result), VARBITBYTES(arg))); + + /* + * Make sure last byte is zero-padded if needed. This is useless but safe + * if source data was shorter than target length (we assume the last byte + * of the source data was itself correctly zero-padded). + */ + VARBIT_PAD(result); + + PG_RETURN_VARBIT_P(result); +} + +Datum +bittypmodin(PG_FUNCTION_ARGS) +{ + ArrayType *ta = PG_GETARG_ARRAYTYPE_P(0); + + PG_RETURN_INT32(anybit_typmodin(ta, "bit")); +} + +Datum +bittypmodout(PG_FUNCTION_ARGS) +{ + int32 typmod = PG_GETARG_INT32(0); + + PG_RETURN_CSTRING(anybit_typmodout(typmod)); +} + + +/* + * varbit_in - + * converts a string to the internal representation of a bitstring. + * This is the same as bit_in except that atttypmod is taken as + * the maximum length, not the exact length to force the bitstring to. + */ +Datum +varbit_in(PG_FUNCTION_ARGS) +{ + char *input_string = PG_GETARG_CSTRING(0); +#ifdef NOT_USED + Oid typelem = PG_GETARG_OID(1); +#endif + int32 atttypmod = PG_GETARG_INT32(2); + Node *escontext = fcinfo->context; + VarBit *result; /* The resulting bit string */ + char *sp; /* pointer into the character string */ + bits8 *r; /* pointer into the result */ + int len, /* Length of the whole data structure */ + bitlen, /* Number of bits in the bit string */ + slen; /* Length of the input string */ + bool bit_not_hex; /* false = hex string true = bit string */ + int bc; + bits8 x = 0; + + /* Check that the first character is a b or an x */ + if (input_string[0] == 'b' || input_string[0] == 'B') + { + bit_not_hex = true; + sp = input_string + 1; + } + else if (input_string[0] == 'x' || input_string[0] == 'X') + { + bit_not_hex = false; + sp = input_string + 1; + } + else + { + bit_not_hex = true; + sp = input_string; + } + + /* + * Determine bitlength from input string. MaxAllocSize ensures a regular + * input is small enough, but we must check hex input. + */ + slen = strlen(sp); + if (bit_not_hex) + bitlen = slen; + else + { + if (slen > VARBITMAXLEN / 4) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("bit string length exceeds the maximum allowed (%d)", + VARBITMAXLEN))); + bitlen = slen * 4; + } + + /* + * Sometimes atttypmod is not supplied. If it is supplied we need to make + * sure that the bitstring fits. + */ + if (atttypmod <= 0) + atttypmod = bitlen; + else if (bitlen > atttypmod) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION), + errmsg("bit string too long for type bit varying(%d)", + atttypmod))); + + len = VARBITTOTALLEN(bitlen); + /* set to 0 so that *r is always initialised and string is zero-padded */ + result = (VarBit *) palloc0(len); + SET_VARSIZE(result, len); + VARBITLEN(result) = Min(bitlen, atttypmod); + + r = VARBITS(result); + if (bit_not_hex) + { + /* Parse the bit representation of the string */ + /* We know it fits, as bitlen was compared to atttypmod */ + x = HIGHBIT; + for (; *sp; sp++) + { + if (*sp == '1') + *r |= x; + else if (*sp != '0') + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("\"%.*s\" is not a valid binary digit", + pg_mblen(sp), sp))); + + x >>= 1; + if (x == 0) + { + x = HIGHBIT; + r++; + } + } + } + else + { + /* Parse the hex representation of the string */ + for (bc = 0; *sp; sp++) + { + if (*sp >= '0' && *sp <= '9') + x = (bits8) (*sp - '0'); + else if (*sp >= 'A' && *sp <= 'F') + x = (bits8) (*sp - 'A') + 10; + else if (*sp >= 'a' && *sp <= 'f') + x = (bits8) (*sp - 'a') + 10; + else + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("\"%.*s\" is not a valid hexadecimal digit", + pg_mblen(sp), sp))); + + if (bc) + { + *r++ |= x; + bc = 0; + } + else + { + *r = x << 4; + bc = 1; + } + } + } + + PG_RETURN_VARBIT_P(result); +} + +/* + * varbit_out - + * Prints the string as bits to preserve length accurately + * + * XXX varbit_recv() and hex input to varbit_in() can load a value that this + * cannot emit. Consider using hex output for such values. + */ +Datum +varbit_out(PG_FUNCTION_ARGS) +{ + VarBit *s = PG_GETARG_VARBIT_P(0); + char *result, + *r; + bits8 *sp; + bits8 x; + int i, + k, + len; + + /* Assertion to help catch any bit functions that don't pad correctly */ + VARBIT_CORRECTLY_PADDED(s); + + len = VARBITLEN(s); + result = (char *) palloc(len + 1); + sp = VARBITS(s); + r = result; + for (i = 0; i <= len - BITS_PER_BYTE; i += BITS_PER_BYTE, sp++) + { + /* print full bytes */ + x = *sp; + for (k = 0; k < BITS_PER_BYTE; k++) + { + *r++ = IS_HIGHBIT_SET(x) ? '1' : '0'; + x <<= 1; + } + } + if (i < len) + { + /* print the last partial byte */ + x = *sp; + for (k = i; k < len; k++) + { + *r++ = IS_HIGHBIT_SET(x) ? '1' : '0'; + x <<= 1; + } + } + *r = '\0'; + + PG_RETURN_CSTRING(result); +} + +/* + * varbit_recv - converts external binary format to varbit + * + * External format is the bitlen as an int32, then the byte array. + */ +Datum +varbit_recv(PG_FUNCTION_ARGS) +{ + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + +#ifdef NOT_USED + Oid typelem = PG_GETARG_OID(1); +#endif + int32 atttypmod = PG_GETARG_INT32(2); + VarBit *result; + int len, + bitlen; + + bitlen = pq_getmsgint(buf, sizeof(int32)); + if (bitlen < 0 || bitlen > VARBITMAXLEN) + ereport(ERROR, + (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION), + errmsg("invalid length in external bit string"))); + + /* + * Sometimes atttypmod is not supplied. If it is supplied we need to make + * sure that the bitstring fits. + */ + if (atttypmod > 0 && bitlen > atttypmod) + ereport(ERROR, + (errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION), + errmsg("bit string too long for type bit varying(%d)", + atttypmod))); + + len = VARBITTOTALLEN(bitlen); + result = (VarBit *) palloc(len); + SET_VARSIZE(result, len); + VARBITLEN(result) = bitlen; + + pq_copymsgbytes(buf, (char *) VARBITS(result), VARBITBYTES(result)); + + /* Make sure last byte is correctly zero-padded */ + VARBIT_PAD(result); + + PG_RETURN_VARBIT_P(result); +} + +/* + * varbit_send - converts varbit to binary format + */ +Datum +varbit_send(PG_FUNCTION_ARGS) +{ + VarBit *s = PG_GETARG_VARBIT_P(0); + StringInfoData buf; + + pq_begintypsend(&buf); + pq_sendint32(&buf, VARBITLEN(s)); + pq_sendbytes(&buf, VARBITS(s), VARBITBYTES(s)); + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} + +/* + * varbit_support() + * + * Planner support function for the varbit() length coercion function. + * + * Currently, the only interesting thing we can do is flatten calls that set + * the new maximum length >= the previous maximum length. We can ignore the + * isExplicit argument, since that only affects truncation cases. + */ +Datum +varbit_support(PG_FUNCTION_ARGS) +{ + Node *rawreq = (Node *) PG_GETARG_POINTER(0); + Node *ret = NULL; + + if (IsA(rawreq, SupportRequestSimplify)) + { + SupportRequestSimplify *req = (SupportRequestSimplify *) rawreq; + FuncExpr *expr = req->fcall; + Node *typmod; + + Assert(list_length(expr->args) >= 2); + + typmod = (Node *) lsecond(expr->args); + + if (IsA(typmod, Const) && !((Const *) typmod)->constisnull) + { + Node *source = (Node *) linitial(expr->args); + int32 new_typmod = DatumGetInt32(((Const *) typmod)->constvalue); + int32 old_max = exprTypmod(source); + int32 new_max = new_typmod; + + /* Note: varbit() treats typmod 0 as invalid, so we do too */ + if (new_max <= 0 || (old_max > 0 && old_max <= new_max)) + ret = relabel_to_typmod(source, new_typmod); + } + } + + PG_RETURN_POINTER(ret); +} + +/* + * varbit() + * Converts a varbit() type to a specific internal length. + * len is the maximum bitlength specified in the column definition. + * + * If doing implicit cast, raise error when source data is too long. + * If doing explicit cast, silently truncate to max length. + */ +Datum +varbit(PG_FUNCTION_ARGS) +{ + VarBit *arg = PG_GETARG_VARBIT_P(0); + int32 len = PG_GETARG_INT32(1); + bool isExplicit = PG_GETARG_BOOL(2); + VarBit *result; + int rlen; + + /* No work if typmod is invalid or supplied data matches it already */ + if (len <= 0 || len >= VARBITLEN(arg)) + PG_RETURN_VARBIT_P(arg); + + if (!isExplicit) + ereport(ERROR, + (errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION), + errmsg("bit string too long for type bit varying(%d)", + len))); + + rlen = VARBITTOTALLEN(len); + result = (VarBit *) palloc(rlen); + SET_VARSIZE(result, rlen); + VARBITLEN(result) = len; + + memcpy(VARBITS(result), VARBITS(arg), VARBITBYTES(result)); + + /* Make sure last byte is correctly zero-padded */ + VARBIT_PAD(result); + + PG_RETURN_VARBIT_P(result); +} + +Datum +varbittypmodin(PG_FUNCTION_ARGS) +{ + ArrayType *ta = PG_GETARG_ARRAYTYPE_P(0); + + PG_RETURN_INT32(anybit_typmodin(ta, "varbit")); +} + +Datum +varbittypmodout(PG_FUNCTION_ARGS) +{ + int32 typmod = PG_GETARG_INT32(0); + + PG_RETURN_CSTRING(anybit_typmodout(typmod)); +} + + +/* + * Comparison operators + * + * We only need one set of comparison operators for bitstrings, as the lengths + * are stored in the same way for zero-padded and varying bit strings. + * + * Note that the standard is not unambiguous about the comparison between + * zero-padded bit strings and varying bitstrings. If the same value is written + * into a zero padded bitstring as into a varying bitstring, but the zero + * padded bitstring has greater length, it will be bigger. + * + * Zeros from the beginning of a bitstring cannot simply be ignored, as they + * may be part of a bit string and may be significant. + * + * Note: btree indexes need these routines not to leak memory; therefore, + * be careful to free working copies of toasted datums. Most places don't + * need to be so careful. + */ + +/* + * bit_cmp + * + * Compares two bitstrings and returns <0, 0, >0 depending on whether the first + * string is smaller, equal, or bigger than the second. All bits are considered + * and additional zero bits may make one string smaller/larger than the other, + * even if their zero-padded values would be the same. + */ +static int32 +bit_cmp(VarBit *arg1, VarBit *arg2) +{ + int bitlen1, + bytelen1, + bitlen2, + bytelen2; + int32 cmp; + + bytelen1 = VARBITBYTES(arg1); + bytelen2 = VARBITBYTES(arg2); + + cmp = memcmp(VARBITS(arg1), VARBITS(arg2), Min(bytelen1, bytelen2)); + if (cmp == 0) + { + bitlen1 = VARBITLEN(arg1); + bitlen2 = VARBITLEN(arg2); + if (bitlen1 != bitlen2) + cmp = (bitlen1 < bitlen2) ? -1 : 1; + } + return cmp; +} + +Datum +biteq(PG_FUNCTION_ARGS) +{ + VarBit *arg1 = PG_GETARG_VARBIT_P(0); + VarBit *arg2 = PG_GETARG_VARBIT_P(1); + bool result; + int bitlen1, + bitlen2; + + bitlen1 = VARBITLEN(arg1); + bitlen2 = VARBITLEN(arg2); + + /* fast path for different-length inputs */ + if (bitlen1 != bitlen2) + result = false; + else + result = (bit_cmp(arg1, arg2) == 0); + + PG_FREE_IF_COPY(arg1, 0); + PG_FREE_IF_COPY(arg2, 1); + + PG_RETURN_BOOL(result); +} + +Datum +bitne(PG_FUNCTION_ARGS) +{ + VarBit *arg1 = PG_GETARG_VARBIT_P(0); + VarBit *arg2 = PG_GETARG_VARBIT_P(1); + bool result; + int bitlen1, + bitlen2; + + bitlen1 = VARBITLEN(arg1); + bitlen2 = VARBITLEN(arg2); + + /* fast path for different-length inputs */ + if (bitlen1 != bitlen2) + result = true; + else + result = (bit_cmp(arg1, arg2) != 0); + + PG_FREE_IF_COPY(arg1, 0); + PG_FREE_IF_COPY(arg2, 1); + + PG_RETURN_BOOL(result); +} + +Datum +bitlt(PG_FUNCTION_ARGS) +{ + VarBit *arg1 = PG_GETARG_VARBIT_P(0); + VarBit *arg2 = PG_GETARG_VARBIT_P(1); + bool result; + + result = (bit_cmp(arg1, arg2) < 0); + + PG_FREE_IF_COPY(arg1, 0); + PG_FREE_IF_COPY(arg2, 1); + + PG_RETURN_BOOL(result); +} + +Datum +bitle(PG_FUNCTION_ARGS) +{ + VarBit *arg1 = PG_GETARG_VARBIT_P(0); + VarBit *arg2 = PG_GETARG_VARBIT_P(1); + bool result; + + result = (bit_cmp(arg1, arg2) <= 0); + + PG_FREE_IF_COPY(arg1, 0); + PG_FREE_IF_COPY(arg2, 1); + + PG_RETURN_BOOL(result); +} + +Datum +bitgt(PG_FUNCTION_ARGS) +{ + VarBit *arg1 = PG_GETARG_VARBIT_P(0); + VarBit *arg2 = PG_GETARG_VARBIT_P(1); + bool result; + + result = (bit_cmp(arg1, arg2) > 0); + + PG_FREE_IF_COPY(arg1, 0); + PG_FREE_IF_COPY(arg2, 1); + + PG_RETURN_BOOL(result); +} + +Datum +bitge(PG_FUNCTION_ARGS) +{ + VarBit *arg1 = PG_GETARG_VARBIT_P(0); + VarBit *arg2 = PG_GETARG_VARBIT_P(1); + bool result; + + result = (bit_cmp(arg1, arg2) >= 0); + + PG_FREE_IF_COPY(arg1, 0); + PG_FREE_IF_COPY(arg2, 1); + + PG_RETURN_BOOL(result); +} + +Datum +bitcmp(PG_FUNCTION_ARGS) +{ + VarBit *arg1 = PG_GETARG_VARBIT_P(0); + VarBit *arg2 = PG_GETARG_VARBIT_P(1); + int32 result; + + result = bit_cmp(arg1, arg2); + + PG_FREE_IF_COPY(arg1, 0); + PG_FREE_IF_COPY(arg2, 1); + + PG_RETURN_INT32(result); +} + +/* + * bitcat + * Concatenation of bit strings + */ +Datum +bitcat(PG_FUNCTION_ARGS) +{ + VarBit *arg1 = PG_GETARG_VARBIT_P(0); + VarBit *arg2 = PG_GETARG_VARBIT_P(1); + + PG_RETURN_VARBIT_P(bit_catenate(arg1, arg2)); +} + +static VarBit * +bit_catenate(VarBit *arg1, VarBit *arg2) +{ + VarBit *result; + int bitlen1, + bitlen2, + bytelen, + bit1pad, + bit2shift; + bits8 *pr, + *pa; + + bitlen1 = VARBITLEN(arg1); + bitlen2 = VARBITLEN(arg2); + + if (bitlen1 > VARBITMAXLEN - bitlen2) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("bit string length exceeds the maximum allowed (%d)", + VARBITMAXLEN))); + bytelen = VARBITTOTALLEN(bitlen1 + bitlen2); + + result = (VarBit *) palloc(bytelen); + SET_VARSIZE(result, bytelen); + VARBITLEN(result) = bitlen1 + bitlen2; + + /* Copy the first bitstring in */ + memcpy(VARBITS(result), VARBITS(arg1), VARBITBYTES(arg1)); + + /* Copy the second bit string */ + bit1pad = VARBITPAD(arg1); + if (bit1pad == 0) + { + memcpy(VARBITS(result) + VARBITBYTES(arg1), VARBITS(arg2), + VARBITBYTES(arg2)); + } + else if (bitlen2 > 0) + { + /* We need to shift all the bits to fit */ + bit2shift = BITS_PER_BYTE - bit1pad; + pr = VARBITS(result) + VARBITBYTES(arg1) - 1; + for (pa = VARBITS(arg2); pa < VARBITEND(arg2); pa++) + { + *pr |= ((*pa >> bit2shift) & BITMASK); + pr++; + if (pr < VARBITEND(result)) + *pr = (*pa << bit1pad) & BITMASK; + } + } + + /* The pad bits should be already zero at this point */ + + return result; +} + +/* + * bitsubstr + * retrieve a substring from the bit string. + * Note, s is 1-based. + * SQL draft 6.10 9) + */ +Datum +bitsubstr(PG_FUNCTION_ARGS) +{ + PG_RETURN_VARBIT_P(bitsubstring(PG_GETARG_VARBIT_P(0), + PG_GETARG_INT32(1), + PG_GETARG_INT32(2), + false)); +} + +Datum +bitsubstr_no_len(PG_FUNCTION_ARGS) +{ + PG_RETURN_VARBIT_P(bitsubstring(PG_GETARG_VARBIT_P(0), + PG_GETARG_INT32(1), + -1, true)); +} + +static VarBit * +bitsubstring(VarBit *arg, int32 s, int32 l, bool length_not_specified) +{ + VarBit *result; + int bitlen, + rbitlen, + len, + ishift, + i; + int32 e, + s1, + e1; + bits8 *r, + *ps; + + bitlen = VARBITLEN(arg); + s1 = Max(s, 1); + /* If we do not have an upper bound, use end of string */ + if (length_not_specified) + { + e1 = bitlen + 1; + } + else if (l < 0) + { + /* SQL99 says to throw an error for E < S, i.e., negative length */ + ereport(ERROR, + (errcode(ERRCODE_SUBSTRING_ERROR), + errmsg("negative substring length not allowed"))); + e1 = -1; /* silence stupider compilers */ + } + else if (pg_add_s32_overflow(s, l, &e)) + { + /* + * L could be large enough for S + L to overflow, in which case the + * substring must run to end of string. + */ + e1 = bitlen + 1; + } + else + { + e1 = Min(e, bitlen + 1); + } + if (s1 > bitlen || e1 <= s1) + { + /* Need to return a zero-length bitstring */ + len = VARBITTOTALLEN(0); + result = (VarBit *) palloc(len); + SET_VARSIZE(result, len); + VARBITLEN(result) = 0; + } + else + { + /* + * OK, we've got a true substring starting at position s1-1 and ending + * at position e1-1 + */ + rbitlen = e1 - s1; + len = VARBITTOTALLEN(rbitlen); + result = (VarBit *) palloc(len); + SET_VARSIZE(result, len); + VARBITLEN(result) = rbitlen; + len -= VARHDRSZ + VARBITHDRSZ; + /* Are we copying from a byte boundary? */ + if ((s1 - 1) % BITS_PER_BYTE == 0) + { + /* Yep, we are copying bytes */ + memcpy(VARBITS(result), VARBITS(arg) + (s1 - 1) / BITS_PER_BYTE, + len); + } + else + { + /* Figure out how much we need to shift the sequence by */ + ishift = (s1 - 1) % BITS_PER_BYTE; + r = VARBITS(result); + ps = VARBITS(arg) + (s1 - 1) / BITS_PER_BYTE; + for (i = 0; i < len; i++) + { + *r = (*ps << ishift) & BITMASK; + if ((++ps) < VARBITEND(arg)) + *r |= *ps >> (BITS_PER_BYTE - ishift); + r++; + } + } + + /* Make sure last byte is correctly zero-padded */ + VARBIT_PAD(result); + } + + return result; +} + +/* + * bitoverlay + * Replace specified substring of first string with second + * + * The SQL standard defines OVERLAY() in terms of substring and concatenation. + * This code is a direct implementation of what the standard says. + */ +Datum +bitoverlay(PG_FUNCTION_ARGS) +{ + VarBit *t1 = PG_GETARG_VARBIT_P(0); + VarBit *t2 = PG_GETARG_VARBIT_P(1); + int sp = PG_GETARG_INT32(2); /* substring start position */ + int sl = PG_GETARG_INT32(3); /* substring length */ + + PG_RETURN_VARBIT_P(bit_overlay(t1, t2, sp, sl)); +} + +Datum +bitoverlay_no_len(PG_FUNCTION_ARGS) +{ + VarBit *t1 = PG_GETARG_VARBIT_P(0); + VarBit *t2 = PG_GETARG_VARBIT_P(1); + int sp = PG_GETARG_INT32(2); /* substring start position */ + int sl; + + sl = VARBITLEN(t2); /* defaults to length(t2) */ + PG_RETURN_VARBIT_P(bit_overlay(t1, t2, sp, sl)); +} + +static VarBit * +bit_overlay(VarBit *t1, VarBit *t2, int sp, int sl) +{ + VarBit *result; + VarBit *s1; + VarBit *s2; + int sp_pl_sl; + + /* + * Check for possible integer-overflow cases. For negative sp, throw a + * "substring length" error because that's what should be expected + * according to the spec's definition of OVERLAY(). + */ + if (sp <= 0) + ereport(ERROR, + (errcode(ERRCODE_SUBSTRING_ERROR), + errmsg("negative substring length not allowed"))); + if (pg_add_s32_overflow(sp, sl, &sp_pl_sl)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("integer out of range"))); + + s1 = bitsubstring(t1, 1, sp - 1, false); + s2 = bitsubstring(t1, sp_pl_sl, -1, true); + result = bit_catenate(s1, t2); + result = bit_catenate(result, s2); + + return result; +} + +/* + * bit_count + * + * Returns the number of bits set in a bit string. + */ +Datum +bit_bit_count(PG_FUNCTION_ARGS) +{ + VarBit *arg = PG_GETARG_VARBIT_P(0); + + PG_RETURN_INT64(pg_popcount((char *) VARBITS(arg), VARBITBYTES(arg))); +} + +/* + * bitlength, bitoctetlength + * Return the length of a bit string + */ +Datum +bitlength(PG_FUNCTION_ARGS) +{ + VarBit *arg = PG_GETARG_VARBIT_P(0); + + PG_RETURN_INT32(VARBITLEN(arg)); +} + +Datum +bitoctetlength(PG_FUNCTION_ARGS) +{ + VarBit *arg = PG_GETARG_VARBIT_P(0); + + PG_RETURN_INT32(VARBITBYTES(arg)); +} + +/* + * bit_and + * perform a logical AND on two bit strings. + */ +Datum +bit_and(PG_FUNCTION_ARGS) +{ + VarBit *arg1 = PG_GETARG_VARBIT_P(0); + VarBit *arg2 = PG_GETARG_VARBIT_P(1); + VarBit *result; + int len, + bitlen1, + bitlen2, + i; + bits8 *p1, + *p2, + *r; + + bitlen1 = VARBITLEN(arg1); + bitlen2 = VARBITLEN(arg2); + if (bitlen1 != bitlen2) + ereport(ERROR, + (errcode(ERRCODE_STRING_DATA_LENGTH_MISMATCH), + errmsg("cannot AND bit strings of different sizes"))); + + len = VARSIZE(arg1); + result = (VarBit *) palloc(len); + SET_VARSIZE(result, len); + VARBITLEN(result) = bitlen1; + + p1 = VARBITS(arg1); + p2 = VARBITS(arg2); + r = VARBITS(result); + for (i = 0; i < VARBITBYTES(arg1); i++) + *r++ = *p1++ & *p2++; + + /* Padding is not needed as & of 0 pads is 0 */ + + PG_RETURN_VARBIT_P(result); +} + +/* + * bit_or + * perform a logical OR on two bit strings. + */ +Datum +bit_or(PG_FUNCTION_ARGS) +{ + VarBit *arg1 = PG_GETARG_VARBIT_P(0); + VarBit *arg2 = PG_GETARG_VARBIT_P(1); + VarBit *result; + int len, + bitlen1, + bitlen2, + i; + bits8 *p1, + *p2, + *r; + + bitlen1 = VARBITLEN(arg1); + bitlen2 = VARBITLEN(arg2); + if (bitlen1 != bitlen2) + ereport(ERROR, + (errcode(ERRCODE_STRING_DATA_LENGTH_MISMATCH), + errmsg("cannot OR bit strings of different sizes"))); + len = VARSIZE(arg1); + result = (VarBit *) palloc(len); + SET_VARSIZE(result, len); + VARBITLEN(result) = bitlen1; + + p1 = VARBITS(arg1); + p2 = VARBITS(arg2); + r = VARBITS(result); + for (i = 0; i < VARBITBYTES(arg1); i++) + *r++ = *p1++ | *p2++; + + /* Padding is not needed as | of 0 pads is 0 */ + + PG_RETURN_VARBIT_P(result); +} + +/* + * bitxor + * perform a logical XOR on two bit strings. + */ +Datum +bitxor(PG_FUNCTION_ARGS) +{ + VarBit *arg1 = PG_GETARG_VARBIT_P(0); + VarBit *arg2 = PG_GETARG_VARBIT_P(1); + VarBit *result; + int len, + bitlen1, + bitlen2, + i; + bits8 *p1, + *p2, + *r; + + bitlen1 = VARBITLEN(arg1); + bitlen2 = VARBITLEN(arg2); + if (bitlen1 != bitlen2) + ereport(ERROR, + (errcode(ERRCODE_STRING_DATA_LENGTH_MISMATCH), + errmsg("cannot XOR bit strings of different sizes"))); + + len = VARSIZE(arg1); + result = (VarBit *) palloc(len); + SET_VARSIZE(result, len); + VARBITLEN(result) = bitlen1; + + p1 = VARBITS(arg1); + p2 = VARBITS(arg2); + r = VARBITS(result); + for (i = 0; i < VARBITBYTES(arg1); i++) + *r++ = *p1++ ^ *p2++; + + /* Padding is not needed as ^ of 0 pads is 0 */ + + PG_RETURN_VARBIT_P(result); +} + +/* + * bitnot + * perform a logical NOT on a bit string. + */ +Datum +bitnot(PG_FUNCTION_ARGS) +{ + VarBit *arg = PG_GETARG_VARBIT_P(0); + VarBit *result; + bits8 *p, + *r; + + result = (VarBit *) palloc(VARSIZE(arg)); + SET_VARSIZE(result, VARSIZE(arg)); + VARBITLEN(result) = VARBITLEN(arg); + + p = VARBITS(arg); + r = VARBITS(result); + for (; p < VARBITEND(arg); p++) + *r++ = ~*p; + + /* Must zero-pad the result, because extra bits are surely 1's here */ + VARBIT_PAD_LAST(result, r); + + PG_RETURN_VARBIT_P(result); +} + +/* + * bitshiftleft + * do a left shift (i.e. towards the beginning of the string) + */ +Datum +bitshiftleft(PG_FUNCTION_ARGS) +{ + VarBit *arg = PG_GETARG_VARBIT_P(0); + int32 shft = PG_GETARG_INT32(1); + VarBit *result; + int byte_shift, + ishift, + len; + bits8 *p, + *r; + + /* Negative shift is a shift to the right */ + if (shft < 0) + { + /* Prevent integer overflow in negation */ + if (shft < -VARBITMAXLEN) + shft = -VARBITMAXLEN; + PG_RETURN_DATUM(DirectFunctionCall2(bitshiftright, + VarBitPGetDatum(arg), + Int32GetDatum(-shft))); + } + + result = (VarBit *) palloc(VARSIZE(arg)); + SET_VARSIZE(result, VARSIZE(arg)); + VARBITLEN(result) = VARBITLEN(arg); + r = VARBITS(result); + + /* If we shifted all the bits out, return an all-zero string */ + if (shft >= VARBITLEN(arg)) + { + MemSet(r, 0, VARBITBYTES(arg)); + PG_RETURN_VARBIT_P(result); + } + + byte_shift = shft / BITS_PER_BYTE; + ishift = shft % BITS_PER_BYTE; + p = VARBITS(arg) + byte_shift; + + if (ishift == 0) + { + /* Special case: we can do a memcpy */ + len = VARBITBYTES(arg) - byte_shift; + memcpy(r, p, len); + MemSet(r + len, 0, byte_shift); + } + else + { + for (; p < VARBITEND(arg); r++) + { + *r = *p << ishift; + if ((++p) < VARBITEND(arg)) + *r |= *p >> (BITS_PER_BYTE - ishift); + } + for (; r < VARBITEND(result); r++) + *r = 0; + } + + /* The pad bits should be already zero at this point */ + + PG_RETURN_VARBIT_P(result); +} + +/* + * bitshiftright + * do a right shift (i.e. towards the end of the string) + */ +Datum +bitshiftright(PG_FUNCTION_ARGS) +{ + VarBit *arg = PG_GETARG_VARBIT_P(0); + int32 shft = PG_GETARG_INT32(1); + VarBit *result; + int byte_shift, + ishift, + len; + bits8 *p, + *r; + + /* Negative shift is a shift to the left */ + if (shft < 0) + { + /* Prevent integer overflow in negation */ + if (shft < -VARBITMAXLEN) + shft = -VARBITMAXLEN; + PG_RETURN_DATUM(DirectFunctionCall2(bitshiftleft, + VarBitPGetDatum(arg), + Int32GetDatum(-shft))); + } + + result = (VarBit *) palloc(VARSIZE(arg)); + SET_VARSIZE(result, VARSIZE(arg)); + VARBITLEN(result) = VARBITLEN(arg); + r = VARBITS(result); + + /* If we shifted all the bits out, return an all-zero string */ + if (shft >= VARBITLEN(arg)) + { + MemSet(r, 0, VARBITBYTES(arg)); + PG_RETURN_VARBIT_P(result); + } + + byte_shift = shft / BITS_PER_BYTE; + ishift = shft % BITS_PER_BYTE; + p = VARBITS(arg); + + /* Set the first part of the result to 0 */ + MemSet(r, 0, byte_shift); + r += byte_shift; + + if (ishift == 0) + { + /* Special case: we can do a memcpy */ + len = VARBITBYTES(arg) - byte_shift; + memcpy(r, p, len); + r += len; + } + else + { + if (r < VARBITEND(result)) + *r = 0; /* initialize first byte */ + for (; r < VARBITEND(result); p++) + { + *r |= *p >> ishift; + if ((++r) < VARBITEND(result)) + *r = (*p << (BITS_PER_BYTE - ishift)) & BITMASK; + } + } + + /* We may have shifted 1's into the pad bits, so fix that */ + VARBIT_PAD_LAST(result, r); + + PG_RETURN_VARBIT_P(result); +} + +/* + * This is not defined in any standard. We retain the natural ordering of + * bits here, as it just seems more intuitive. + */ +Datum +bitfromint4(PG_FUNCTION_ARGS) +{ + int32 a = PG_GETARG_INT32(0); + int32 typmod = PG_GETARG_INT32(1); + VarBit *result; + bits8 *r; + int rlen; + int destbitsleft, + srcbitsleft; + + if (typmod <= 0 || typmod > VARBITMAXLEN) + typmod = 1; /* default bit length */ + + rlen = VARBITTOTALLEN(typmod); + result = (VarBit *) palloc(rlen); + SET_VARSIZE(result, rlen); + VARBITLEN(result) = typmod; + + r = VARBITS(result); + destbitsleft = typmod; + srcbitsleft = 32; + /* drop any input bits that don't fit */ + srcbitsleft = Min(srcbitsleft, destbitsleft); + /* sign-fill any excess bytes in output */ + while (destbitsleft >= srcbitsleft + 8) + { + *r++ = (bits8) ((a < 0) ? BITMASK : 0); + destbitsleft -= 8; + } + /* store first fractional byte */ + if (destbitsleft > srcbitsleft) + { + unsigned int val = (unsigned int) (a >> (destbitsleft - 8)); + + /* Force sign-fill in case the compiler implements >> as zero-fill */ + if (a < 0) + val |= ((unsigned int) -1) << (srcbitsleft + 8 - destbitsleft); + *r++ = (bits8) (val & BITMASK); + destbitsleft -= 8; + } + /* Now srcbitsleft and destbitsleft are the same, need not track both */ + /* store whole bytes */ + while (destbitsleft >= 8) + { + *r++ = (bits8) ((a >> (destbitsleft - 8)) & BITMASK); + destbitsleft -= 8; + } + /* store last fractional byte */ + if (destbitsleft > 0) + *r = (bits8) ((a << (8 - destbitsleft)) & BITMASK); + + PG_RETURN_VARBIT_P(result); +} + +Datum +bittoint4(PG_FUNCTION_ARGS) +{ + VarBit *arg = PG_GETARG_VARBIT_P(0); + uint32 result; + bits8 *r; + + /* Check that the bit string is not too long */ + if (VARBITLEN(arg) > sizeof(result) * BITS_PER_BYTE) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("integer out of range"))); + + result = 0; + for (r = VARBITS(arg); r < VARBITEND(arg); r++) + { + result <<= BITS_PER_BYTE; + result |= *r; + } + /* Now shift the result to take account of the padding at the end */ + result >>= VARBITPAD(arg); + + PG_RETURN_INT32(result); +} + +Datum +bitfromint8(PG_FUNCTION_ARGS) +{ + int64 a = PG_GETARG_INT64(0); + int32 typmod = PG_GETARG_INT32(1); + VarBit *result; + bits8 *r; + int rlen; + int destbitsleft, + srcbitsleft; + + if (typmod <= 0 || typmod > VARBITMAXLEN) + typmod = 1; /* default bit length */ + + rlen = VARBITTOTALLEN(typmod); + result = (VarBit *) palloc(rlen); + SET_VARSIZE(result, rlen); + VARBITLEN(result) = typmod; + + r = VARBITS(result); + destbitsleft = typmod; + srcbitsleft = 64; + /* drop any input bits that don't fit */ + srcbitsleft = Min(srcbitsleft, destbitsleft); + /* sign-fill any excess bytes in output */ + while (destbitsleft >= srcbitsleft + 8) + { + *r++ = (bits8) ((a < 0) ? BITMASK : 0); + destbitsleft -= 8; + } + /* store first fractional byte */ + if (destbitsleft > srcbitsleft) + { + unsigned int val = (unsigned int) (a >> (destbitsleft - 8)); + + /* Force sign-fill in case the compiler implements >> as zero-fill */ + if (a < 0) + val |= ((unsigned int) -1) << (srcbitsleft + 8 - destbitsleft); + *r++ = (bits8) (val & BITMASK); + destbitsleft -= 8; + } + /* Now srcbitsleft and destbitsleft are the same, need not track both */ + /* store whole bytes */ + while (destbitsleft >= 8) + { + *r++ = (bits8) ((a >> (destbitsleft - 8)) & BITMASK); + destbitsleft -= 8; + } + /* store last fractional byte */ + if (destbitsleft > 0) + *r = (bits8) ((a << (8 - destbitsleft)) & BITMASK); + + PG_RETURN_VARBIT_P(result); +} + +Datum +bittoint8(PG_FUNCTION_ARGS) +{ + VarBit *arg = PG_GETARG_VARBIT_P(0); + uint64 result; + bits8 *r; + + /* Check that the bit string is not too long */ + if (VARBITLEN(arg) > sizeof(result) * BITS_PER_BYTE) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("bigint out of range"))); + + result = 0; + for (r = VARBITS(arg); r < VARBITEND(arg); r++) + { + result <<= BITS_PER_BYTE; + result |= *r; + } + /* Now shift the result to take account of the padding at the end */ + result >>= VARBITPAD(arg); + + PG_RETURN_INT64(result); +} + + +/* + * Determines the position of S2 in the bitstring S1 (1-based string). + * If S2 does not appear in S1 this function returns 0. + * If S2 is of length 0 this function returns 1. + * Compatible in usage with POSITION() functions for other data types. + */ +Datum +bitposition(PG_FUNCTION_ARGS) +{ + VarBit *str = PG_GETARG_VARBIT_P(0); + VarBit *substr = PG_GETARG_VARBIT_P(1); + int substr_length, + str_length, + i, + is; + bits8 *s, /* pointer into substring */ + *p; /* pointer into str */ + bits8 cmp, /* shifted substring byte to compare */ + mask1, /* mask for substring byte shifted right */ + mask2, /* mask for substring byte shifted left */ + end_mask, /* pad mask for last substring byte */ + str_mask; /* pad mask for last string byte */ + bool is_match; + + /* Get the substring length */ + substr_length = VARBITLEN(substr); + str_length = VARBITLEN(str); + + /* String has zero length or substring longer than string, return 0 */ + if ((str_length == 0) || (substr_length > str_length)) + PG_RETURN_INT32(0); + + /* zero-length substring means return 1 */ + if (substr_length == 0) + PG_RETURN_INT32(1); + + /* Initialise the padding masks */ + end_mask = BITMASK << VARBITPAD(substr); + str_mask = BITMASK << VARBITPAD(str); + for (i = 0; i < VARBITBYTES(str) - VARBITBYTES(substr) + 1; i++) + { + for (is = 0; is < BITS_PER_BYTE; is++) + { + is_match = true; + p = VARBITS(str) + i; + mask1 = BITMASK >> is; + mask2 = ~mask1; + for (s = VARBITS(substr); + is_match && s < VARBITEND(substr); s++) + { + cmp = *s >> is; + if (s == VARBITEND(substr) - 1) + { + mask1 &= end_mask >> is; + if (p == VARBITEND(str) - 1) + { + /* Check that there is enough of str left */ + if (mask1 & ~str_mask) + { + is_match = false; + break; + } + mask1 &= str_mask; + } + } + is_match = ((cmp ^ *p) & mask1) == 0; + if (!is_match) + break; + /* Move on to the next byte */ + p++; + if (p == VARBITEND(str)) + { + mask2 = end_mask << (BITS_PER_BYTE - is); + is_match = mask2 == 0; +#if 0 + elog(DEBUG4, "S. %d %d em=%2x sm=%2x r=%d", + i, is, end_mask, mask2, is_match); +#endif + break; + } + cmp = *s << (BITS_PER_BYTE - is); + if (s == VARBITEND(substr) - 1) + { + mask2 &= end_mask << (BITS_PER_BYTE - is); + if (p == VARBITEND(str) - 1) + { + if (mask2 & ~str_mask) + { + is_match = false; + break; + } + mask2 &= str_mask; + } + } + is_match = ((cmp ^ *p) & mask2) == 0; + } + /* Have we found a match? */ + if (is_match) + PG_RETURN_INT32(i * BITS_PER_BYTE + is + 1); + } + } + PG_RETURN_INT32(0); +} + + +/* + * bitsetbit + * + * Given an instance of type 'bit' creates a new one with + * the Nth bit set to the given value. + * + * The bit location is specified left-to-right in a zero-based fashion + * consistent with the other get_bit and set_bit functions, but + * inconsistent with the standard substring, position, overlay functions + */ +Datum +bitsetbit(PG_FUNCTION_ARGS) +{ + VarBit *arg1 = PG_GETARG_VARBIT_P(0); + int32 n = PG_GETARG_INT32(1); + int32 newBit = PG_GETARG_INT32(2); + VarBit *result; + int len, + bitlen; + bits8 *r, + *p; + int byteNo, + bitNo; + + bitlen = VARBITLEN(arg1); + if (n < 0 || n >= bitlen) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("bit index %d out of valid range (0..%d)", + n, bitlen - 1))); + + /* + * sanity check! + */ + if (newBit != 0 && newBit != 1) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("new bit must be 0 or 1"))); + + len = VARSIZE(arg1); + result = (VarBit *) palloc(len); + SET_VARSIZE(result, len); + VARBITLEN(result) = bitlen; + + p = VARBITS(arg1); + r = VARBITS(result); + + memcpy(r, p, VARBITBYTES(arg1)); + + byteNo = n / BITS_PER_BYTE; + bitNo = BITS_PER_BYTE - 1 - (n % BITS_PER_BYTE); + + /* + * Update the byte. + */ + if (newBit == 0) + r[byteNo] &= (~(1 << bitNo)); + else + r[byteNo] |= (1 << bitNo); + + PG_RETURN_VARBIT_P(result); +} + +/* + * bitgetbit + * + * returns the value of the Nth bit of a bit array (0 or 1). + * + * The bit location is specified left-to-right in a zero-based fashion + * consistent with the other get_bit and set_bit functions, but + * inconsistent with the standard substring, position, overlay functions + */ +Datum +bitgetbit(PG_FUNCTION_ARGS) +{ + VarBit *arg1 = PG_GETARG_VARBIT_P(0); + int32 n = PG_GETARG_INT32(1); + int bitlen; + bits8 *p; + int byteNo, + bitNo; + + bitlen = VARBITLEN(arg1); + if (n < 0 || n >= bitlen) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("bit index %d out of valid range (0..%d)", + n, bitlen - 1))); + + p = VARBITS(arg1); + + byteNo = n / BITS_PER_BYTE; + bitNo = BITS_PER_BYTE - 1 - (n % BITS_PER_BYTE); + + if (p[byteNo] & (1 << bitNo)) + PG_RETURN_INT32(1); + else + PG_RETURN_INT32(0); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/varchar.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/varchar.c new file mode 100644 index 00000000000..b92ff4d266e --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/varchar.c @@ -0,0 +1,1231 @@ +/*------------------------------------------------------------------------- + * + * varchar.c + * Functions for the built-in types char(n) and varchar(n). + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/adt/varchar.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/detoast.h" +#include "access/htup_details.h" +#include "catalog/pg_collation.h" +#include "catalog/pg_type.h" +#include "common/hashfn.h" +#include "libpq/pqformat.h" +#include "mb/pg_wchar.h" +#include "nodes/nodeFuncs.h" +#include "nodes/supportnodes.h" +#include "utils/array.h" +#include "utils/builtins.h" +#include "utils/lsyscache.h" +#include "utils/pg_locale.h" +#include "utils/varlena.h" + +/* common code for bpchartypmodin and varchartypmodin */ +static int32 +anychar_typmodin(ArrayType *ta, const char *typename) +{ + int32 typmod; + int32 *tl; + int n; + + tl = ArrayGetIntegerTypmods(ta, &n); + + /* + * we're not too tense about good error message here because grammar + * shouldn't allow wrong number of modifiers for CHAR + */ + if (n != 1) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid type modifier"))); + + if (*tl < 1) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("length for type %s must be at least 1", typename))); + if (*tl > MaxAttrSize) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("length for type %s cannot exceed %d", + typename, MaxAttrSize))); + + /* + * For largely historical reasons, the typmod is VARHDRSZ plus the number + * of characters; there is enough client-side code that knows about that + * that we'd better not change it. + */ + typmod = VARHDRSZ + *tl; + + return typmod; +} + +/* common code for bpchartypmodout and varchartypmodout */ +static char * +anychar_typmodout(int32 typmod) +{ + char *res = (char *) palloc(64); + + if (typmod > VARHDRSZ) + snprintf(res, 64, "(%d)", (int) (typmod - VARHDRSZ)); + else + *res = '\0'; + + return res; +} + + +/* + * CHAR() and VARCHAR() types are part of the SQL standard. CHAR() + * is for blank-padded string whose length is specified in CREATE TABLE. + * VARCHAR is for storing string whose length is at most the length specified + * at CREATE TABLE time. + * + * It's hard to implement these types because we cannot figure out + * the length of the type from the type itself. I changed (hopefully all) the + * fmgr calls that invoke input functions of a data type to supply the + * length also. (eg. in INSERTs, we have the tupleDescriptor which contains + * the length of the attributes and hence the exact length of the char() or + * varchar(). We pass this to bpcharin() or varcharin().) In the case where + * we cannot determine the length, we pass in -1 instead and the input + * converter does not enforce any length check. + * + * We actually implement this as a varlena so that we don't have to pass in + * the length for the comparison functions. (The difference between these + * types and "text" is that we truncate and possibly blank-pad the string + * at insertion time.) + * + * - ay 6/95 + */ + + +/***************************************************************************** + * bpchar - char() * + *****************************************************************************/ + +/* + * bpchar_input -- common guts of bpcharin and bpcharrecv + * + * s is the input text of length len (may not be null-terminated) + * atttypmod is the typmod value to apply + * + * Note that atttypmod is measured in characters, which + * is not necessarily the same as the number of bytes. + * + * If the input string is too long, raise an error, unless the extra + * characters are spaces, in which case they're truncated. (per SQL) + * + * If escontext points to an ErrorSaveContext node, that is filled instead + * of throwing an error; the caller must check SOFT_ERROR_OCCURRED() + * to detect errors. + */ +static BpChar * +bpchar_input(const char *s, size_t len, int32 atttypmod, Node *escontext) +{ + BpChar *result; + char *r; + size_t maxlen; + + /* If typmod is -1 (or invalid), use the actual string length */ + if (atttypmod < (int32) VARHDRSZ) + maxlen = len; + else + { + size_t charlen; /* number of CHARACTERS in the input */ + + maxlen = atttypmod - VARHDRSZ; + charlen = pg_mbstrlen_with_len(s, len); + if (charlen > maxlen) + { + /* Verify that extra characters are spaces, and clip them off */ + size_t mbmaxlen = pg_mbcharcliplen(s, len, maxlen); + size_t j; + + /* + * at this point, len is the actual BYTE length of the input + * string, maxlen is the max number of CHARACTERS allowed for this + * bpchar type, mbmaxlen is the length in BYTES of those chars. + */ + for (j = mbmaxlen; j < len; j++) + { + if (s[j] != ' ') + ereturn(escontext, NULL, + (errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION), + errmsg("value too long for type character(%d)", + (int) maxlen))); + } + + /* + * Now we set maxlen to the necessary byte length, not the number + * of CHARACTERS! + */ + maxlen = len = mbmaxlen; + } + else + { + /* + * Now we set maxlen to the necessary byte length, not the number + * of CHARACTERS! + */ + maxlen = len + (maxlen - charlen); + } + } + + result = (BpChar *) palloc(maxlen + VARHDRSZ); + SET_VARSIZE(result, maxlen + VARHDRSZ); + r = VARDATA(result); + memcpy(r, s, len); + + /* blank pad the string if necessary */ + if (maxlen > len) + memset(r + len, ' ', maxlen - len); + + return result; +} + +/* + * Convert a C string to CHARACTER internal representation. atttypmod + * is the declared length of the type plus VARHDRSZ. + */ +Datum +bpcharin(PG_FUNCTION_ARGS) +{ + char *s = PG_GETARG_CSTRING(0); +#ifdef NOT_USED + Oid typelem = PG_GETARG_OID(1); +#endif + int32 atttypmod = PG_GETARG_INT32(2); + BpChar *result; + + result = bpchar_input(s, strlen(s), atttypmod, fcinfo->context); + PG_RETURN_BPCHAR_P(result); +} + + +/* + * Convert a CHARACTER value to a C string. + * + * Uses the text conversion functions, which is only appropriate if BpChar + * and text are equivalent types. + */ +Datum +bpcharout(PG_FUNCTION_ARGS) +{ + Datum txt = PG_GETARG_DATUM(0); + + PG_RETURN_CSTRING(TextDatumGetCString(txt)); +} + +/* + * bpcharrecv - converts external binary format to bpchar + */ +Datum +bpcharrecv(PG_FUNCTION_ARGS) +{ + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); +#ifdef NOT_USED + Oid typelem = PG_GETARG_OID(1); +#endif + int32 atttypmod = PG_GETARG_INT32(2); + BpChar *result; + char *str; + int nbytes; + + str = pq_getmsgtext(buf, buf->len - buf->cursor, &nbytes); + result = bpchar_input(str, nbytes, atttypmod, NULL); + pfree(str); + PG_RETURN_BPCHAR_P(result); +} + +/* + * bpcharsend - converts bpchar to binary format + */ +Datum +bpcharsend(PG_FUNCTION_ARGS) +{ + /* Exactly the same as textsend, so share code */ + return textsend(fcinfo); +} + + +/* + * Converts a CHARACTER type to the specified size. + * + * maxlen is the typmod, ie, declared length plus VARHDRSZ bytes. + * isExplicit is true if this is for an explicit cast to char(N). + * + * Truncation rules: for an explicit cast, silently truncate to the given + * length; for an implicit cast, raise error unless extra characters are + * all spaces. (This is sort-of per SQL: the spec would actually have us + * raise a "completion condition" for the explicit cast case, but Postgres + * hasn't got such a concept.) + */ +Datum +bpchar(PG_FUNCTION_ARGS) +{ + BpChar *source = PG_GETARG_BPCHAR_PP(0); + int32 maxlen = PG_GETARG_INT32(1); + bool isExplicit = PG_GETARG_BOOL(2); + BpChar *result; + int32 len; + char *r; + char *s; + int i; + int charlen; /* number of characters in the input string + + * VARHDRSZ */ + + /* No work if typmod is invalid */ + if (maxlen < (int32) VARHDRSZ) + PG_RETURN_BPCHAR_P(source); + + maxlen -= VARHDRSZ; + + len = VARSIZE_ANY_EXHDR(source); + s = VARDATA_ANY(source); + + charlen = pg_mbstrlen_with_len(s, len); + + /* No work if supplied data matches typmod already */ + if (charlen == maxlen) + PG_RETURN_BPCHAR_P(source); + + if (charlen > maxlen) + { + /* Verify that extra characters are spaces, and clip them off */ + size_t maxmblen; + + maxmblen = pg_mbcharcliplen(s, len, maxlen); + + if (!isExplicit) + { + for (i = maxmblen; i < len; i++) + if (s[i] != ' ') + ereport(ERROR, + (errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION), + errmsg("value too long for type character(%d)", + maxlen))); + } + + len = maxmblen; + + /* + * At this point, maxlen is the necessary byte length, not the number + * of CHARACTERS! + */ + maxlen = len; + } + else + { + /* + * At this point, maxlen is the necessary byte length, not the number + * of CHARACTERS! + */ + maxlen = len + (maxlen - charlen); + } + + Assert(maxlen >= len); + + result = palloc(maxlen + VARHDRSZ); + SET_VARSIZE(result, maxlen + VARHDRSZ); + r = VARDATA(result); + + memcpy(r, s, len); + + /* blank pad the string if necessary */ + if (maxlen > len) + memset(r + len, ' ', maxlen - len); + + PG_RETURN_BPCHAR_P(result); +} + + +/* char_bpchar() + * Convert char to bpchar(1). + */ +Datum +char_bpchar(PG_FUNCTION_ARGS) +{ + char c = PG_GETARG_CHAR(0); + BpChar *result; + + result = (BpChar *) palloc(VARHDRSZ + 1); + + SET_VARSIZE(result, VARHDRSZ + 1); + *(VARDATA(result)) = c; + + PG_RETURN_BPCHAR_P(result); +} + + +/* bpchar_name() + * Converts a bpchar() type to a NameData type. + */ +Datum +bpchar_name(PG_FUNCTION_ARGS) +{ + BpChar *s = PG_GETARG_BPCHAR_PP(0); + char *s_data; + Name result; + int len; + + len = VARSIZE_ANY_EXHDR(s); + s_data = VARDATA_ANY(s); + + /* Truncate oversize input */ + if (len >= NAMEDATALEN) + len = pg_mbcliplen(s_data, len, NAMEDATALEN - 1); + + /* Remove trailing blanks */ + while (len > 0) + { + if (s_data[len - 1] != ' ') + break; + len--; + } + + /* We use palloc0 here to ensure result is zero-padded */ + result = (Name) palloc0(NAMEDATALEN); + memcpy(NameStr(*result), s_data, len); + + PG_RETURN_NAME(result); +} + +/* name_bpchar() + * Converts a NameData type to a bpchar type. + * + * Uses the text conversion functions, which is only appropriate if BpChar + * and text are equivalent types. + */ +Datum +name_bpchar(PG_FUNCTION_ARGS) +{ + Name s = PG_GETARG_NAME(0); + BpChar *result; + + result = (BpChar *) cstring_to_text(NameStr(*s)); + PG_RETURN_BPCHAR_P(result); +} + +Datum +bpchartypmodin(PG_FUNCTION_ARGS) +{ + ArrayType *ta = PG_GETARG_ARRAYTYPE_P(0); + + PG_RETURN_INT32(anychar_typmodin(ta, "char")); +} + +Datum +bpchartypmodout(PG_FUNCTION_ARGS) +{ + int32 typmod = PG_GETARG_INT32(0); + + PG_RETURN_CSTRING(anychar_typmodout(typmod)); +} + + +/***************************************************************************** + * varchar - varchar(n) + * + * Note: varchar piggybacks on type text for most operations, and so has no + * C-coded functions except for I/O and typmod checking. + *****************************************************************************/ + +/* + * varchar_input -- common guts of varcharin and varcharrecv + * + * s is the input text of length len (may not be null-terminated) + * atttypmod is the typmod value to apply + * + * Note that atttypmod is measured in characters, which + * is not necessarily the same as the number of bytes. + * + * If the input string is too long, raise an error, unless the extra + * characters are spaces, in which case they're truncated. (per SQL) + * + * If escontext points to an ErrorSaveContext node, that is filled instead + * of throwing an error; the caller must check SOFT_ERROR_OCCURRED() + * to detect errors. + */ +static VarChar * +varchar_input(const char *s, size_t len, int32 atttypmod, Node *escontext) +{ + VarChar *result; + size_t maxlen; + + maxlen = atttypmod - VARHDRSZ; + + if (atttypmod >= (int32) VARHDRSZ && len > maxlen) + { + /* Verify that extra characters are spaces, and clip them off */ + size_t mbmaxlen = pg_mbcharcliplen(s, len, maxlen); + size_t j; + + for (j = mbmaxlen; j < len; j++) + { + if (s[j] != ' ') + ereturn(escontext, NULL, + (errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION), + errmsg("value too long for type character varying(%d)", + (int) maxlen))); + } + + len = mbmaxlen; + } + + /* + * We can use cstring_to_text_with_len because VarChar and text are + * binary-compatible types. + */ + result = (VarChar *) cstring_to_text_with_len(s, len); + return result; +} + +/* + * Convert a C string to VARCHAR internal representation. atttypmod + * is the declared length of the type plus VARHDRSZ. + */ +Datum +varcharin(PG_FUNCTION_ARGS) +{ + char *s = PG_GETARG_CSTRING(0); +#ifdef NOT_USED + Oid typelem = PG_GETARG_OID(1); +#endif + int32 atttypmod = PG_GETARG_INT32(2); + VarChar *result; + + result = varchar_input(s, strlen(s), atttypmod, fcinfo->context); + PG_RETURN_VARCHAR_P(result); +} + + +/* + * Convert a VARCHAR value to a C string. + * + * Uses the text to C string conversion function, which is only appropriate + * if VarChar and text are equivalent types. + */ +Datum +varcharout(PG_FUNCTION_ARGS) +{ + Datum txt = PG_GETARG_DATUM(0); + + PG_RETURN_CSTRING(TextDatumGetCString(txt)); +} + +/* + * varcharrecv - converts external binary format to varchar + */ +Datum +varcharrecv(PG_FUNCTION_ARGS) +{ + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); +#ifdef NOT_USED + Oid typelem = PG_GETARG_OID(1); +#endif + int32 atttypmod = PG_GETARG_INT32(2); + VarChar *result; + char *str; + int nbytes; + + str = pq_getmsgtext(buf, buf->len - buf->cursor, &nbytes); + result = varchar_input(str, nbytes, atttypmod, NULL); + pfree(str); + PG_RETURN_VARCHAR_P(result); +} + +/* + * varcharsend - converts varchar to binary format + */ +Datum +varcharsend(PG_FUNCTION_ARGS) +{ + /* Exactly the same as textsend, so share code */ + return textsend(fcinfo); +} + + +/* + * varchar_support() + * + * Planner support function for the varchar() length coercion function. + * + * Currently, the only interesting thing we can do is flatten calls that set + * the new maximum length >= the previous maximum length. We can ignore the + * isExplicit argument, since that only affects truncation cases. + */ +Datum +varchar_support(PG_FUNCTION_ARGS) +{ + Node *rawreq = (Node *) PG_GETARG_POINTER(0); + Node *ret = NULL; + + if (IsA(rawreq, SupportRequestSimplify)) + { + SupportRequestSimplify *req = (SupportRequestSimplify *) rawreq; + FuncExpr *expr = req->fcall; + Node *typmod; + + Assert(list_length(expr->args) >= 2); + + typmod = (Node *) lsecond(expr->args); + + if (IsA(typmod, Const) && !((Const *) typmod)->constisnull) + { + Node *source = (Node *) linitial(expr->args); + int32 old_typmod = exprTypmod(source); + int32 new_typmod = DatumGetInt32(((Const *) typmod)->constvalue); + int32 old_max = old_typmod - VARHDRSZ; + int32 new_max = new_typmod - VARHDRSZ; + + if (new_typmod < 0 || (old_typmod >= 0 && old_max <= new_max)) + ret = relabel_to_typmod(source, new_typmod); + } + } + + PG_RETURN_POINTER(ret); +} + +/* + * Converts a VARCHAR type to the specified size. + * + * maxlen is the typmod, ie, declared length plus VARHDRSZ bytes. + * isExplicit is true if this is for an explicit cast to varchar(N). + * + * Truncation rules: for an explicit cast, silently truncate to the given + * length; for an implicit cast, raise error unless extra characters are + * all spaces. (This is sort-of per SQL: the spec would actually have us + * raise a "completion condition" for the explicit cast case, but Postgres + * hasn't got such a concept.) + */ +Datum +varchar(PG_FUNCTION_ARGS) +{ + VarChar *source = PG_GETARG_VARCHAR_PP(0); + int32 typmod = PG_GETARG_INT32(1); + bool isExplicit = PG_GETARG_BOOL(2); + int32 len, + maxlen; + size_t maxmblen; + int i; + char *s_data; + + len = VARSIZE_ANY_EXHDR(source); + s_data = VARDATA_ANY(source); + maxlen = typmod - VARHDRSZ; + + /* No work if typmod is invalid or supplied data fits it already */ + if (maxlen < 0 || len <= maxlen) + PG_RETURN_VARCHAR_P(source); + + /* only reach here if string is too long... */ + + /* truncate multibyte string preserving multibyte boundary */ + maxmblen = pg_mbcharcliplen(s_data, len, maxlen); + + if (!isExplicit) + { + for (i = maxmblen; i < len; i++) + if (s_data[i] != ' ') + ereport(ERROR, + (errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION), + errmsg("value too long for type character varying(%d)", + maxlen))); + } + + PG_RETURN_VARCHAR_P((VarChar *) cstring_to_text_with_len(s_data, + maxmblen)); +} + +Datum +varchartypmodin(PG_FUNCTION_ARGS) +{ + ArrayType *ta = PG_GETARG_ARRAYTYPE_P(0); + + PG_RETURN_INT32(anychar_typmodin(ta, "varchar")); +} + +Datum +varchartypmodout(PG_FUNCTION_ARGS) +{ + int32 typmod = PG_GETARG_INT32(0); + + PG_RETURN_CSTRING(anychar_typmodout(typmod)); +} + + +/***************************************************************************** + * Exported functions + *****************************************************************************/ + +/* "True" length (not counting trailing blanks) of a BpChar */ +static inline int +bcTruelen(BpChar *arg) +{ + return bpchartruelen(VARDATA_ANY(arg), VARSIZE_ANY_EXHDR(arg)); +} + +int +bpchartruelen(char *s, int len) +{ + int i; + + /* + * Note that we rely on the assumption that ' ' is a singleton unit on + * every supported multibyte server encoding. + */ + for (i = len - 1; i >= 0; i--) + { + if (s[i] != ' ') + break; + } + return i + 1; +} + +Datum +bpcharlen(PG_FUNCTION_ARGS) +{ + BpChar *arg = PG_GETARG_BPCHAR_PP(0); + int len; + + /* get number of bytes, ignoring trailing spaces */ + len = bcTruelen(arg); + + /* in multibyte encoding, convert to number of characters */ + if (pg_database_encoding_max_length() != 1) + len = pg_mbstrlen_with_len(VARDATA_ANY(arg), len); + + PG_RETURN_INT32(len); +} + +Datum +bpcharoctetlen(PG_FUNCTION_ARGS) +{ + Datum arg = PG_GETARG_DATUM(0); + + /* We need not detoast the input at all */ + PG_RETURN_INT32(toast_raw_datum_size(arg) - VARHDRSZ); +} + + +/***************************************************************************** + * Comparison Functions used for bpchar + * + * Note: btree indexes need these routines not to leak memory; therefore, + * be careful to free working copies of toasted datums. Most places don't + * need to be so careful. + *****************************************************************************/ + +static void +check_collation_set(Oid collid) +{ + if (!OidIsValid(collid)) + { + /* + * This typically means that the parser could not resolve a conflict + * of implicit collations, so report it that way. + */ + ereport(ERROR, + (errcode(ERRCODE_INDETERMINATE_COLLATION), + errmsg("could not determine which collation to use for string comparison"), + errhint("Use the COLLATE clause to set the collation explicitly."))); + } +} + +Datum +bpchareq(PG_FUNCTION_ARGS) +{ + BpChar *arg1 = PG_GETARG_BPCHAR_PP(0); + BpChar *arg2 = PG_GETARG_BPCHAR_PP(1); + int len1, + len2; + bool result; + Oid collid = PG_GET_COLLATION(); + bool locale_is_c = false; + pg_locale_t mylocale = 0; + + check_collation_set(collid); + + len1 = bcTruelen(arg1); + len2 = bcTruelen(arg2); + + if (lc_collate_is_c(collid)) + locale_is_c = true; + else + mylocale = pg_newlocale_from_collation(collid); + + if (locale_is_c || pg_locale_deterministic(mylocale)) + { + /* + * Since we only care about equality or not-equality, we can avoid all + * the expense of strcoll() here, and just do bitwise comparison. + */ + if (len1 != len2) + result = false; + else + result = (memcmp(VARDATA_ANY(arg1), VARDATA_ANY(arg2), len1) == 0); + } + else + { + result = (varstr_cmp(VARDATA_ANY(arg1), len1, VARDATA_ANY(arg2), len2, + collid) == 0); + } + + PG_FREE_IF_COPY(arg1, 0); + PG_FREE_IF_COPY(arg2, 1); + + PG_RETURN_BOOL(result); +} + +Datum +bpcharne(PG_FUNCTION_ARGS) +{ + BpChar *arg1 = PG_GETARG_BPCHAR_PP(0); + BpChar *arg2 = PG_GETARG_BPCHAR_PP(1); + int len1, + len2; + bool result; + Oid collid = PG_GET_COLLATION(); + bool locale_is_c = false; + pg_locale_t mylocale = 0; + + check_collation_set(collid); + + len1 = bcTruelen(arg1); + len2 = bcTruelen(arg2); + + if (lc_collate_is_c(collid)) + locale_is_c = true; + else + mylocale = pg_newlocale_from_collation(collid); + + if (locale_is_c || pg_locale_deterministic(mylocale)) + { + /* + * Since we only care about equality or not-equality, we can avoid all + * the expense of strcoll() here, and just do bitwise comparison. + */ + if (len1 != len2) + result = true; + else + result = (memcmp(VARDATA_ANY(arg1), VARDATA_ANY(arg2), len1) != 0); + } + else + { + result = (varstr_cmp(VARDATA_ANY(arg1), len1, VARDATA_ANY(arg2), len2, + collid) != 0); + } + + PG_FREE_IF_COPY(arg1, 0); + PG_FREE_IF_COPY(arg2, 1); + + PG_RETURN_BOOL(result); +} + +Datum +bpcharlt(PG_FUNCTION_ARGS) +{ + BpChar *arg1 = PG_GETARG_BPCHAR_PP(0); + BpChar *arg2 = PG_GETARG_BPCHAR_PP(1); + int len1, + len2; + int cmp; + + len1 = bcTruelen(arg1); + len2 = bcTruelen(arg2); + + cmp = varstr_cmp(VARDATA_ANY(arg1), len1, VARDATA_ANY(arg2), len2, + PG_GET_COLLATION()); + + PG_FREE_IF_COPY(arg1, 0); + PG_FREE_IF_COPY(arg2, 1); + + PG_RETURN_BOOL(cmp < 0); +} + +Datum +bpcharle(PG_FUNCTION_ARGS) +{ + BpChar *arg1 = PG_GETARG_BPCHAR_PP(0); + BpChar *arg2 = PG_GETARG_BPCHAR_PP(1); + int len1, + len2; + int cmp; + + len1 = bcTruelen(arg1); + len2 = bcTruelen(arg2); + + cmp = varstr_cmp(VARDATA_ANY(arg1), len1, VARDATA_ANY(arg2), len2, + PG_GET_COLLATION()); + + PG_FREE_IF_COPY(arg1, 0); + PG_FREE_IF_COPY(arg2, 1); + + PG_RETURN_BOOL(cmp <= 0); +} + +Datum +bpchargt(PG_FUNCTION_ARGS) +{ + BpChar *arg1 = PG_GETARG_BPCHAR_PP(0); + BpChar *arg2 = PG_GETARG_BPCHAR_PP(1); + int len1, + len2; + int cmp; + + len1 = bcTruelen(arg1); + len2 = bcTruelen(arg2); + + cmp = varstr_cmp(VARDATA_ANY(arg1), len1, VARDATA_ANY(arg2), len2, + PG_GET_COLLATION()); + + PG_FREE_IF_COPY(arg1, 0); + PG_FREE_IF_COPY(arg2, 1); + + PG_RETURN_BOOL(cmp > 0); +} + +Datum +bpcharge(PG_FUNCTION_ARGS) +{ + BpChar *arg1 = PG_GETARG_BPCHAR_PP(0); + BpChar *arg2 = PG_GETARG_BPCHAR_PP(1); + int len1, + len2; + int cmp; + + len1 = bcTruelen(arg1); + len2 = bcTruelen(arg2); + + cmp = varstr_cmp(VARDATA_ANY(arg1), len1, VARDATA_ANY(arg2), len2, + PG_GET_COLLATION()); + + PG_FREE_IF_COPY(arg1, 0); + PG_FREE_IF_COPY(arg2, 1); + + PG_RETURN_BOOL(cmp >= 0); +} + +Datum +bpcharcmp(PG_FUNCTION_ARGS) +{ + BpChar *arg1 = PG_GETARG_BPCHAR_PP(0); + BpChar *arg2 = PG_GETARG_BPCHAR_PP(1); + int len1, + len2; + int cmp; + + len1 = bcTruelen(arg1); + len2 = bcTruelen(arg2); + + cmp = varstr_cmp(VARDATA_ANY(arg1), len1, VARDATA_ANY(arg2), len2, + PG_GET_COLLATION()); + + PG_FREE_IF_COPY(arg1, 0); + PG_FREE_IF_COPY(arg2, 1); + + PG_RETURN_INT32(cmp); +} + +Datum +bpchar_sortsupport(PG_FUNCTION_ARGS) +{ + SortSupport ssup = (SortSupport) PG_GETARG_POINTER(0); + Oid collid = ssup->ssup_collation; + MemoryContext oldcontext; + + oldcontext = MemoryContextSwitchTo(ssup->ssup_cxt); + + /* Use generic string SortSupport */ + varstr_sortsupport(ssup, BPCHAROID, collid); + + MemoryContextSwitchTo(oldcontext); + + PG_RETURN_VOID(); +} + +Datum +bpchar_larger(PG_FUNCTION_ARGS) +{ + BpChar *arg1 = PG_GETARG_BPCHAR_PP(0); + BpChar *arg2 = PG_GETARG_BPCHAR_PP(1); + int len1, + len2; + int cmp; + + len1 = bcTruelen(arg1); + len2 = bcTruelen(arg2); + + cmp = varstr_cmp(VARDATA_ANY(arg1), len1, VARDATA_ANY(arg2), len2, + PG_GET_COLLATION()); + + PG_RETURN_BPCHAR_P((cmp >= 0) ? arg1 : arg2); +} + +Datum +bpchar_smaller(PG_FUNCTION_ARGS) +{ + BpChar *arg1 = PG_GETARG_BPCHAR_PP(0); + BpChar *arg2 = PG_GETARG_BPCHAR_PP(1); + int len1, + len2; + int cmp; + + len1 = bcTruelen(arg1); + len2 = bcTruelen(arg2); + + cmp = varstr_cmp(VARDATA_ANY(arg1), len1, VARDATA_ANY(arg2), len2, + PG_GET_COLLATION()); + + PG_RETURN_BPCHAR_P((cmp <= 0) ? arg1 : arg2); +} + + +/* + * bpchar needs a specialized hash function because we want to ignore + * trailing blanks in comparisons. + */ +Datum +hashbpchar(PG_FUNCTION_ARGS) +{ + BpChar *key = PG_GETARG_BPCHAR_PP(0); + Oid collid = PG_GET_COLLATION(); + char *keydata; + int keylen; + pg_locale_t mylocale = 0; + Datum result; + + if (!collid) + ereport(ERROR, + (errcode(ERRCODE_INDETERMINATE_COLLATION), + errmsg("could not determine which collation to use for string hashing"), + errhint("Use the COLLATE clause to set the collation explicitly."))); + + keydata = VARDATA_ANY(key); + keylen = bcTruelen(key); + + if (!lc_collate_is_c(collid)) + mylocale = pg_newlocale_from_collation(collid); + + if (pg_locale_deterministic(mylocale)) + { + result = hash_any((unsigned char *) keydata, keylen); + } + else + { + Size bsize, + rsize; + char *buf; + + bsize = pg_strnxfrm(NULL, 0, keydata, keylen, mylocale); + buf = palloc(bsize + 1); + + rsize = pg_strnxfrm(buf, bsize + 1, keydata, keylen, mylocale); + if (rsize != bsize) + elog(ERROR, "pg_strnxfrm() returned unexpected result"); + + /* + * In principle, there's no reason to include the terminating NUL + * character in the hash, but it was done before and the behavior must + * be preserved. + */ + result = hash_any((uint8_t *) buf, bsize + 1); + + pfree(buf); + } + + /* Avoid leaking memory for toasted inputs */ + PG_FREE_IF_COPY(key, 0); + + return result; +} + +Datum +hashbpcharextended(PG_FUNCTION_ARGS) +{ + BpChar *key = PG_GETARG_BPCHAR_PP(0); + Oid collid = PG_GET_COLLATION(); + char *keydata; + int keylen; + pg_locale_t mylocale = 0; + Datum result; + + if (!collid) + ereport(ERROR, + (errcode(ERRCODE_INDETERMINATE_COLLATION), + errmsg("could not determine which collation to use for string hashing"), + errhint("Use the COLLATE clause to set the collation explicitly."))); + + keydata = VARDATA_ANY(key); + keylen = bcTruelen(key); + + if (!lc_collate_is_c(collid)) + mylocale = pg_newlocale_from_collation(collid); + + if (pg_locale_deterministic(mylocale)) + { + result = hash_any_extended((unsigned char *) keydata, keylen, + PG_GETARG_INT64(1)); + } + else + { + Size bsize, + rsize; + char *buf; + + bsize = pg_strnxfrm(NULL, 0, keydata, keylen, mylocale); + buf = palloc(bsize + 1); + + rsize = pg_strnxfrm(buf, bsize + 1, keydata, keylen, mylocale); + if (rsize != bsize) + elog(ERROR, "pg_strnxfrm() returned unexpected result"); + + /* + * In principle, there's no reason to include the terminating NUL + * character in the hash, but it was done before and the behavior must + * be preserved. + */ + result = hash_any_extended((uint8_t *) buf, bsize + 1, + PG_GETARG_INT64(1)); + + pfree(buf); + } + + PG_FREE_IF_COPY(key, 0); + + return result; +} + +/* + * The following operators support character-by-character comparison + * of bpchar datums, to allow building indexes suitable for LIKE clauses. + * Note that the regular bpchareq/bpcharne comparison operators, and + * regular support functions 1 and 2 with "C" collation are assumed to be + * compatible with these! + */ + +static int +internal_bpchar_pattern_compare(BpChar *arg1, BpChar *arg2) +{ + int result; + int len1, + len2; + + len1 = bcTruelen(arg1); + len2 = bcTruelen(arg2); + + result = memcmp(VARDATA_ANY(arg1), VARDATA_ANY(arg2), Min(len1, len2)); + if (result != 0) + return result; + else if (len1 < len2) + return -1; + else if (len1 > len2) + return 1; + else + return 0; +} + + +Datum +bpchar_pattern_lt(PG_FUNCTION_ARGS) +{ + BpChar *arg1 = PG_GETARG_BPCHAR_PP(0); + BpChar *arg2 = PG_GETARG_BPCHAR_PP(1); + int result; + + result = internal_bpchar_pattern_compare(arg1, arg2); + + PG_FREE_IF_COPY(arg1, 0); + PG_FREE_IF_COPY(arg2, 1); + + PG_RETURN_BOOL(result < 0); +} + + +Datum +bpchar_pattern_le(PG_FUNCTION_ARGS) +{ + BpChar *arg1 = PG_GETARG_BPCHAR_PP(0); + BpChar *arg2 = PG_GETARG_BPCHAR_PP(1); + int result; + + result = internal_bpchar_pattern_compare(arg1, arg2); + + PG_FREE_IF_COPY(arg1, 0); + PG_FREE_IF_COPY(arg2, 1); + + PG_RETURN_BOOL(result <= 0); +} + + +Datum +bpchar_pattern_ge(PG_FUNCTION_ARGS) +{ + BpChar *arg1 = PG_GETARG_BPCHAR_PP(0); + BpChar *arg2 = PG_GETARG_BPCHAR_PP(1); + int result; + + result = internal_bpchar_pattern_compare(arg1, arg2); + + PG_FREE_IF_COPY(arg1, 0); + PG_FREE_IF_COPY(arg2, 1); + + PG_RETURN_BOOL(result >= 0); +} + + +Datum +bpchar_pattern_gt(PG_FUNCTION_ARGS) +{ + BpChar *arg1 = PG_GETARG_BPCHAR_PP(0); + BpChar *arg2 = PG_GETARG_BPCHAR_PP(1); + int result; + + result = internal_bpchar_pattern_compare(arg1, arg2); + + PG_FREE_IF_COPY(arg1, 0); + PG_FREE_IF_COPY(arg2, 1); + + PG_RETURN_BOOL(result > 0); +} + + +Datum +btbpchar_pattern_cmp(PG_FUNCTION_ARGS) +{ + BpChar *arg1 = PG_GETARG_BPCHAR_PP(0); + BpChar *arg2 = PG_GETARG_BPCHAR_PP(1); + int result; + + result = internal_bpchar_pattern_compare(arg1, arg2); + + PG_FREE_IF_COPY(arg1, 0); + PG_FREE_IF_COPY(arg2, 1); + + PG_RETURN_INT32(result); +} + + +Datum +btbpchar_pattern_sortsupport(PG_FUNCTION_ARGS) +{ + SortSupport ssup = (SortSupport) PG_GETARG_POINTER(0); + MemoryContext oldcontext; + + oldcontext = MemoryContextSwitchTo(ssup->ssup_cxt); + + /* Use generic string SortSupport, forcing "C" collation */ + varstr_sortsupport(ssup, BPCHAROID, C_COLLATION_OID); + + MemoryContextSwitchTo(oldcontext); + + PG_RETURN_VOID(); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/varlena.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/varlena.c new file mode 100644 index 00000000000..06cc9fdd41a --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/varlena.c @@ -0,0 +1,6532 @@ +/*------------------------------------------------------------------------- + * + * varlena.c + * Functions for the variable-length built-in types. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/adt/varlena.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <ctype.h> +#include <limits.h> + +#include "access/detoast.h" +#include "access/toast_compression.h" +#include "catalog/pg_collation.h" +#include "catalog/pg_type.h" +#include "common/hashfn.h" +#include "common/int.h" +#include "common/unicode_norm.h" +#include "funcapi.h" +#include "lib/hyperloglog.h" +#include "libpq/pqformat.h" +#include "miscadmin.h" +#include "nodes/execnodes.h" +#include "parser/scansup.h" +#include "port/pg_bswap.h" +#include "regex/regex.h" +#include "utils/builtins.h" +#include "utils/bytea.h" +#include "utils/guc.h" +#include "utils/lsyscache.h" +#include "utils/memutils.h" +#include "utils/pg_locale.h" +#include "utils/sortsupport.h" +#include "utils/varlena.h" + + +/* GUC variable */ +__thread int bytea_output = BYTEA_OUTPUT_HEX; + +typedef struct varlena VarString; + +/* + * State for text_position_* functions. + */ +typedef struct +{ + bool is_multibyte_char_in_char; /* need to check char boundaries? */ + + char *str1; /* haystack string */ + char *str2; /* needle string */ + int len1; /* string lengths in bytes */ + int len2; + + /* Skip table for Boyer-Moore-Horspool search algorithm: */ + int skiptablemask; /* mask for ANDing with skiptable subscripts */ + int skiptable[256]; /* skip distance for given mismatched char */ + + char *last_match; /* pointer to last match in 'str1' */ + + /* + * Sometimes we need to convert the byte position of a match to a + * character position. These store the last position that was converted, + * so that on the next call, we can continue from that point, rather than + * count characters from the very beginning. + */ + char *refpoint; /* pointer within original haystack string */ + int refpos; /* 0-based character offset of the same point */ +} TextPositionState; + +typedef struct +{ + char *buf1; /* 1st string, or abbreviation original string + * buf */ + char *buf2; /* 2nd string, or abbreviation strxfrm() buf */ + int buflen1; /* Allocated length of buf1 */ + int buflen2; /* Allocated length of buf2 */ + int last_len1; /* Length of last buf1 string/strxfrm() input */ + int last_len2; /* Length of last buf2 string/strxfrm() blob */ + int last_returned; /* Last comparison result (cache) */ + bool cache_blob; /* Does buf2 contain strxfrm() blob, etc? */ + bool collate_c; + Oid typid; /* Actual datatype (text/bpchar/bytea/name) */ + hyperLogLogState abbr_card; /* Abbreviated key cardinality state */ + hyperLogLogState full_card; /* Full key cardinality state */ + double prop_card; /* Required cardinality proportion */ + pg_locale_t locale; +} VarStringSortSupport; + +/* + * Output data for split_text(): we output either to an array or a table. + * tupstore and tupdesc must be set up in advance to output to a table. + */ +typedef struct +{ + ArrayBuildState *astate; + Tuplestorestate *tupstore; + TupleDesc tupdesc; +} SplitTextOutputData; + +/* + * This should be large enough that most strings will fit, but small enough + * that we feel comfortable putting it on the stack + */ +#define TEXTBUFLEN 1024 + +#define DatumGetVarStringP(X) ((VarString *) PG_DETOAST_DATUM(X)) +#define DatumGetVarStringPP(X) ((VarString *) PG_DETOAST_DATUM_PACKED(X)) + +static int varstrfastcmp_c(Datum x, Datum y, SortSupport ssup); +static int bpcharfastcmp_c(Datum x, Datum y, SortSupport ssup); +static int namefastcmp_c(Datum x, Datum y, SortSupport ssup); +static int varlenafastcmp_locale(Datum x, Datum y, SortSupport ssup); +static int namefastcmp_locale(Datum x, Datum y, SortSupport ssup); +static int varstrfastcmp_locale(char *a1p, int len1, char *a2p, int len2, SortSupport ssup); +static Datum varstr_abbrev_convert(Datum original, SortSupport ssup); +static bool varstr_abbrev_abort(int memtupcount, SortSupport ssup); +static int32 text_length(Datum str); +static text *text_catenate(text *t1, text *t2); +static text *text_substring(Datum str, + int32 start, + int32 length, + bool length_not_specified); +static text *text_overlay(text *t1, text *t2, int sp, int sl); +static int text_position(text *t1, text *t2, Oid collid); +static void text_position_setup(text *t1, text *t2, Oid collid, TextPositionState *state); +static bool text_position_next(TextPositionState *state); +static char *text_position_next_internal(char *start_ptr, TextPositionState *state); +static char *text_position_get_match_ptr(TextPositionState *state); +static int text_position_get_match_pos(TextPositionState *state); +static void text_position_cleanup(TextPositionState *state); +static void check_collation_set(Oid collid); +static int text_cmp(text *arg1, text *arg2, Oid collid); +static bytea *bytea_catenate(bytea *t1, bytea *t2); +static bytea *bytea_substring(Datum str, + int S, + int L, + bool length_not_specified); +static bytea *bytea_overlay(bytea *t1, bytea *t2, int sp, int sl); +static void appendStringInfoText(StringInfo str, const text *t); +static bool split_text(FunctionCallInfo fcinfo, SplitTextOutputData *tstate); +static void split_text_accum_result(SplitTextOutputData *tstate, + text *field_value, + text *null_string, + Oid collation); +static text *array_to_text_internal(FunctionCallInfo fcinfo, ArrayType *v, + const char *fldsep, const char *null_string); +static StringInfo makeStringAggState(FunctionCallInfo fcinfo); +static bool text_format_parse_digits(const char **ptr, const char *end_ptr, + int *value); +static const char *text_format_parse_format(const char *start_ptr, + const char *end_ptr, + int *argpos, int *widthpos, + int *flags, int *width); +static void text_format_string_conversion(StringInfo buf, char conversion, + FmgrInfo *typOutputInfo, + Datum value, bool isNull, + int flags, int width); +static void text_format_append_string(StringInfo buf, const char *str, + int flags, int width); + + +/***************************************************************************** + * CONVERSION ROUTINES EXPORTED FOR USE BY C CODE * + *****************************************************************************/ + +/* + * cstring_to_text + * + * Create a text value from a null-terminated C string. + * + * The new text value is freshly palloc'd with a full-size VARHDR. + */ +text * +cstring_to_text(const char *s) +{ + return cstring_to_text_with_len(s, strlen(s)); +} + +/* + * cstring_to_text_with_len + * + * Same as cstring_to_text except the caller specifies the string length; + * the string need not be null_terminated. + */ +text * +cstring_to_text_with_len(const char *s, int len) +{ + text *result = (text *) palloc(len + VARHDRSZ); + + SET_VARSIZE(result, len + VARHDRSZ); + memcpy(VARDATA(result), s, len); + + return result; +} + +/* + * text_to_cstring + * + * Create a palloc'd, null-terminated C string from a text value. + * + * We support being passed a compressed or toasted text value. + * This is a bit bogus since such values shouldn't really be referred to as + * "text *", but it seems useful for robustness. If we didn't handle that + * case here, we'd need another routine that did, anyway. + */ +char * +text_to_cstring(const text *t) +{ + /* must cast away the const, unfortunately */ + text *tunpacked = pg_detoast_datum_packed(unconstify(text *, t)); + int len = VARSIZE_ANY_EXHDR(tunpacked); + char *result; + + result = (char *) palloc(len + 1); + memcpy(result, VARDATA_ANY(tunpacked), len); + result[len] = '\0'; + + if (tunpacked != t) + pfree(tunpacked); + + return result; +} + +/* + * text_to_cstring_buffer + * + * Copy a text value into a caller-supplied buffer of size dst_len. + * + * The text string is truncated if necessary to fit. The result is + * guaranteed null-terminated (unless dst_len == 0). + * + * We support being passed a compressed or toasted text value. + * This is a bit bogus since such values shouldn't really be referred to as + * "text *", but it seems useful for robustness. If we didn't handle that + * case here, we'd need another routine that did, anyway. + */ +void +text_to_cstring_buffer(const text *src, char *dst, size_t dst_len) +{ + /* must cast away the const, unfortunately */ + text *srcunpacked = pg_detoast_datum_packed(unconstify(text *, src)); + size_t src_len = VARSIZE_ANY_EXHDR(srcunpacked); + + if (dst_len > 0) + { + dst_len--; + if (dst_len >= src_len) + dst_len = src_len; + else /* ensure truncation is encoding-safe */ + dst_len = pg_mbcliplen(VARDATA_ANY(srcunpacked), src_len, dst_len); + memcpy(dst, VARDATA_ANY(srcunpacked), dst_len); + dst[dst_len] = '\0'; + } + + if (srcunpacked != src) + pfree(srcunpacked); +} + + +/***************************************************************************** + * USER I/O ROUTINES * + *****************************************************************************/ + + +#define VAL(CH) ((CH) - '0') +#define DIG(VAL) ((VAL) + '0') + +/* + * byteain - converts from printable representation of byte array + * + * Non-printable characters must be passed as '\nnn' (octal) and are + * converted to internal form. '\' must be passed as '\\'. + * ereport(ERROR, ...) if bad form. + * + * BUGS: + * The input is scanned twice. + * The error checking of input is minimal. + */ +Datum +byteain(PG_FUNCTION_ARGS) +{ + char *inputText = PG_GETARG_CSTRING(0); + Node *escontext = fcinfo->context; + char *tp; + char *rp; + int bc; + bytea *result; + + /* Recognize hex input */ + if (inputText[0] == '\\' && inputText[1] == 'x') + { + size_t len = strlen(inputText); + + bc = (len - 2) / 2 + VARHDRSZ; /* maximum possible length */ + result = palloc(bc); + bc = hex_decode_safe(inputText + 2, len - 2, VARDATA(result), + escontext); + SET_VARSIZE(result, bc + VARHDRSZ); /* actual length */ + + PG_RETURN_BYTEA_P(result); + } + + /* Else, it's the traditional escaped style */ + for (bc = 0, tp = inputText; *tp != '\0'; bc++) + { + if (tp[0] != '\\') + tp++; + else if ((tp[0] == '\\') && + (tp[1] >= '0' && tp[1] <= '3') && + (tp[2] >= '0' && tp[2] <= '7') && + (tp[3] >= '0' && tp[3] <= '7')) + tp += 4; + else if ((tp[0] == '\\') && + (tp[1] == '\\')) + tp += 2; + else + { + /* + * one backslash, not followed by another or ### valid octal + */ + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s", "bytea"))); + } + } + + bc += VARHDRSZ; + + result = (bytea *) palloc(bc); + SET_VARSIZE(result, bc); + + tp = inputText; + rp = VARDATA(result); + while (*tp != '\0') + { + if (tp[0] != '\\') + *rp++ = *tp++; + else if ((tp[0] == '\\') && + (tp[1] >= '0' && tp[1] <= '3') && + (tp[2] >= '0' && tp[2] <= '7') && + (tp[3] >= '0' && tp[3] <= '7')) + { + bc = VAL(tp[1]); + bc <<= 3; + bc += VAL(tp[2]); + bc <<= 3; + *rp++ = bc + VAL(tp[3]); + + tp += 4; + } + else if ((tp[0] == '\\') && + (tp[1] == '\\')) + { + *rp++ = '\\'; + tp += 2; + } + else + { + /* + * We should never get here. The first pass should not allow it. + */ + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s", "bytea"))); + } + } + + PG_RETURN_BYTEA_P(result); +} + +/* + * byteaout - converts to printable representation of byte array + * + * In the traditional escaped format, non-printable characters are + * printed as '\nnn' (octal) and '\' as '\\'. + */ +Datum +byteaout(PG_FUNCTION_ARGS) +{ + bytea *vlena = PG_GETARG_BYTEA_PP(0); + char *result; + char *rp; + + if (bytea_output == BYTEA_OUTPUT_HEX) + { + /* Print hex format */ + rp = result = palloc(VARSIZE_ANY_EXHDR(vlena) * 2 + 2 + 1); + *rp++ = '\\'; + *rp++ = 'x'; + rp += hex_encode(VARDATA_ANY(vlena), VARSIZE_ANY_EXHDR(vlena), rp); + } + else if (bytea_output == BYTEA_OUTPUT_ESCAPE) + { + /* Print traditional escaped format */ + char *vp; + uint64 len; + int i; + + len = 1; /* empty string has 1 char */ + vp = VARDATA_ANY(vlena); + for (i = VARSIZE_ANY_EXHDR(vlena); i != 0; i--, vp++) + { + if (*vp == '\\') + len += 2; + else if ((unsigned char) *vp < 0x20 || (unsigned char) *vp > 0x7e) + len += 4; + else + len++; + } + + /* + * In principle len can't overflow uint32 if the input fit in 1GB, but + * for safety let's check rather than relying on palloc's internal + * check. + */ + if (len > MaxAllocSize) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg_internal("result of bytea output conversion is too large"))); + rp = result = (char *) palloc(len); + + vp = VARDATA_ANY(vlena); + for (i = VARSIZE_ANY_EXHDR(vlena); i != 0; i--, vp++) + { + if (*vp == '\\') + { + *rp++ = '\\'; + *rp++ = '\\'; + } + else if ((unsigned char) *vp < 0x20 || (unsigned char) *vp > 0x7e) + { + int val; /* holds unprintable chars */ + + val = *vp; + rp[0] = '\\'; + rp[3] = DIG(val & 07); + val >>= 3; + rp[2] = DIG(val & 07); + val >>= 3; + rp[1] = DIG(val & 03); + rp += 4; + } + else + *rp++ = *vp; + } + } + else + { + elog(ERROR, "unrecognized bytea_output setting: %d", + bytea_output); + rp = result = NULL; /* keep compiler quiet */ + } + *rp = '\0'; + PG_RETURN_CSTRING(result); +} + +/* + * bytearecv - converts external binary format to bytea + */ +Datum +bytearecv(PG_FUNCTION_ARGS) +{ + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + bytea *result; + int nbytes; + + nbytes = buf->len - buf->cursor; + result = (bytea *) palloc(nbytes + VARHDRSZ); + SET_VARSIZE(result, nbytes + VARHDRSZ); + pq_copymsgbytes(buf, VARDATA(result), nbytes); + PG_RETURN_BYTEA_P(result); +} + +/* + * byteasend - converts bytea to binary format + * + * This is a special case: just copy the input... + */ +Datum +byteasend(PG_FUNCTION_ARGS) +{ + bytea *vlena = PG_GETARG_BYTEA_P_COPY(0); + + PG_RETURN_BYTEA_P(vlena); +} + +Datum +bytea_string_agg_transfn(PG_FUNCTION_ARGS) +{ + StringInfo state; + + state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0); + + /* Append the value unless null, preceding it with the delimiter. */ + if (!PG_ARGISNULL(1)) + { + bytea *value = PG_GETARG_BYTEA_PP(1); + bool isfirst = false; + + /* + * You might think we can just throw away the first delimiter, however + * we must keep it as we may be a parallel worker doing partial + * aggregation building a state to send to the main process. We need + * to keep the delimiter of every aggregation so that the combine + * function can properly join up the strings of two separately + * partially aggregated results. The first delimiter is only stripped + * off in the final function. To know how much to strip off the front + * of the string, we store the length of the first delimiter in the + * StringInfo's cursor field, which we don't otherwise need here. + */ + if (state == NULL) + { + state = makeStringAggState(fcinfo); + isfirst = true; + } + + if (!PG_ARGISNULL(2)) + { + bytea *delim = PG_GETARG_BYTEA_PP(2); + + appendBinaryStringInfo(state, VARDATA_ANY(delim), + VARSIZE_ANY_EXHDR(delim)); + if (isfirst) + state->cursor = VARSIZE_ANY_EXHDR(delim); + } + + appendBinaryStringInfo(state, VARDATA_ANY(value), + VARSIZE_ANY_EXHDR(value)); + } + + /* + * The transition type for string_agg() is declared to be "internal", + * which is a pass-by-value type the same size as a pointer. + */ + if (state) + PG_RETURN_POINTER(state); + PG_RETURN_NULL(); +} + +Datum +bytea_string_agg_finalfn(PG_FUNCTION_ARGS) +{ + StringInfo state; + + /* cannot be called directly because of internal-type argument */ + Assert(AggCheckCallContext(fcinfo, NULL)); + + state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0); + + if (state != NULL) + { + /* As per comment in transfn, strip data before the cursor position */ + bytea *result; + int strippedlen = state->len - state->cursor; + + result = (bytea *) palloc(strippedlen + VARHDRSZ); + SET_VARSIZE(result, strippedlen + VARHDRSZ); + memcpy(VARDATA(result), &state->data[state->cursor], strippedlen); + PG_RETURN_BYTEA_P(result); + } + else + PG_RETURN_NULL(); +} + +/* + * textin - converts cstring to internal representation + */ +Datum +textin(PG_FUNCTION_ARGS) +{ + char *inputText = PG_GETARG_CSTRING(0); + + PG_RETURN_TEXT_P(cstring_to_text(inputText)); +} + +/* + * textout - converts internal representation to cstring + */ +Datum +textout(PG_FUNCTION_ARGS) +{ + Datum txt = PG_GETARG_DATUM(0); + + PG_RETURN_CSTRING(TextDatumGetCString(txt)); +} + +/* + * textrecv - converts external binary format to text + */ +Datum +textrecv(PG_FUNCTION_ARGS) +{ + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + text *result; + char *str; + int nbytes; + + str = pq_getmsgtext(buf, buf->len - buf->cursor, &nbytes); + + result = cstring_to_text_with_len(str, nbytes); + pfree(str); + PG_RETURN_TEXT_P(result); +} + +/* + * textsend - converts text to binary format + */ +Datum +textsend(PG_FUNCTION_ARGS) +{ + text *t = PG_GETARG_TEXT_PP(0); + StringInfoData buf; + + pq_begintypsend(&buf); + pq_sendtext(&buf, VARDATA_ANY(t), VARSIZE_ANY_EXHDR(t)); + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} + + +/* + * unknownin - converts cstring to internal representation + */ +Datum +unknownin(PG_FUNCTION_ARGS) +{ + char *str = PG_GETARG_CSTRING(0); + + /* representation is same as cstring */ + PG_RETURN_CSTRING(pstrdup(str)); +} + +/* + * unknownout - converts internal representation to cstring + */ +Datum +unknownout(PG_FUNCTION_ARGS) +{ + /* representation is same as cstring */ + char *str = PG_GETARG_CSTRING(0); + + PG_RETURN_CSTRING(pstrdup(str)); +} + +/* + * unknownrecv - converts external binary format to unknown + */ +Datum +unknownrecv(PG_FUNCTION_ARGS) +{ + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + char *str; + int nbytes; + + str = pq_getmsgtext(buf, buf->len - buf->cursor, &nbytes); + /* representation is same as cstring */ + PG_RETURN_CSTRING(str); +} + +/* + * unknownsend - converts unknown to binary format + */ +Datum +unknownsend(PG_FUNCTION_ARGS) +{ + /* representation is same as cstring */ + char *str = PG_GETARG_CSTRING(0); + StringInfoData buf; + + pq_begintypsend(&buf); + pq_sendtext(&buf, str, strlen(str)); + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} + + +/* ========== PUBLIC ROUTINES ========== */ + +/* + * textlen - + * returns the logical length of a text* + * (which is less than the VARSIZE of the text*) + */ +Datum +textlen(PG_FUNCTION_ARGS) +{ + Datum str = PG_GETARG_DATUM(0); + + /* try to avoid decompressing argument */ + PG_RETURN_INT32(text_length(str)); +} + +/* + * text_length - + * Does the real work for textlen() + * + * This is broken out so it can be called directly by other string processing + * functions. Note that the argument is passed as a Datum, to indicate that + * it may still be in compressed form. We can avoid decompressing it at all + * in some cases. + */ +static int32 +text_length(Datum str) +{ + /* fastpath when max encoding length is one */ + if (pg_database_encoding_max_length() == 1) + PG_RETURN_INT32(toast_raw_datum_size(str) - VARHDRSZ); + else + { + text *t = DatumGetTextPP(str); + + PG_RETURN_INT32(pg_mbstrlen_with_len(VARDATA_ANY(t), + VARSIZE_ANY_EXHDR(t))); + } +} + +/* + * textoctetlen - + * returns the physical length of a text* + * (which is less than the VARSIZE of the text*) + */ +Datum +textoctetlen(PG_FUNCTION_ARGS) +{ + Datum str = PG_GETARG_DATUM(0); + + /* We need not detoast the input at all */ + PG_RETURN_INT32(toast_raw_datum_size(str) - VARHDRSZ); +} + +/* + * textcat - + * takes two text* and returns a text* that is the concatenation of + * the two. + * + * Rewritten by Sapa, sapa@hq.icb.chel.su. 8-Jul-96. + * Updated by Thomas, Thomas.Lockhart@jpl.nasa.gov 1997-07-10. + * Allocate space for output in all cases. + * XXX - thomas 1997-07-10 + */ +Datum +textcat(PG_FUNCTION_ARGS) +{ + text *t1 = PG_GETARG_TEXT_PP(0); + text *t2 = PG_GETARG_TEXT_PP(1); + + PG_RETURN_TEXT_P(text_catenate(t1, t2)); +} + +/* + * text_catenate + * Guts of textcat(), broken out so it can be used by other functions + * + * Arguments can be in short-header form, but not compressed or out-of-line + */ +static text * +text_catenate(text *t1, text *t2) +{ + text *result; + int len1, + len2, + len; + char *ptr; + + len1 = VARSIZE_ANY_EXHDR(t1); + len2 = VARSIZE_ANY_EXHDR(t2); + + /* paranoia ... probably should throw error instead? */ + if (len1 < 0) + len1 = 0; + if (len2 < 0) + len2 = 0; + + len = len1 + len2 + VARHDRSZ; + result = (text *) palloc(len); + + /* Set size of result string... */ + SET_VARSIZE(result, len); + + /* Fill data field of result string... */ + ptr = VARDATA(result); + if (len1 > 0) + memcpy(ptr, VARDATA_ANY(t1), len1); + if (len2 > 0) + memcpy(ptr + len1, VARDATA_ANY(t2), len2); + + return result; +} + +/* + * charlen_to_bytelen() + * Compute the number of bytes occupied by n characters starting at *p + * + * It is caller's responsibility that there actually are n characters; + * the string need not be null-terminated. + */ +static int +charlen_to_bytelen(const char *p, int n) +{ + if (pg_database_encoding_max_length() == 1) + { + /* Optimization for single-byte encodings */ + return n; + } + else + { + const char *s; + + for (s = p; n > 0; n--) + s += pg_mblen(s); + + return s - p; + } +} + +/* + * text_substr() + * Return a substring starting at the specified position. + * - thomas 1997-12-31 + * + * Input: + * - string + * - starting position (is one-based) + * - string length + * + * If the starting position is zero or less, then return from the start of the string + * adjusting the length to be consistent with the "negative start" per SQL. + * If the length is less than zero, return the remaining string. + * + * Added multibyte support. + * - Tatsuo Ishii 1998-4-21 + * Changed behavior if starting position is less than one to conform to SQL behavior. + * Formerly returned the entire string; now returns a portion. + * - Thomas Lockhart 1998-12-10 + * Now uses faster TOAST-slicing interface + * - John Gray 2002-02-22 + * Remove "#ifdef MULTIBYTE" and test for encoding_max_length instead. Change + * behaviors conflicting with SQL to meet SQL (if E = S + L < S throw + * error; if E < 1, return '', not entire string). Fixed MB related bug when + * S > LC and < LC + 4 sometimes garbage characters are returned. + * - Joe Conway 2002-08-10 + */ +Datum +text_substr(PG_FUNCTION_ARGS) +{ + PG_RETURN_TEXT_P(text_substring(PG_GETARG_DATUM(0), + PG_GETARG_INT32(1), + PG_GETARG_INT32(2), + false)); +} + +/* + * text_substr_no_len - + * Wrapper to avoid opr_sanity failure due to + * one function accepting a different number of args. + */ +Datum +text_substr_no_len(PG_FUNCTION_ARGS) +{ + PG_RETURN_TEXT_P(text_substring(PG_GETARG_DATUM(0), + PG_GETARG_INT32(1), + -1, true)); +} + +/* + * text_substring - + * Does the real work for text_substr() and text_substr_no_len() + * + * This is broken out so it can be called directly by other string processing + * functions. Note that the argument is passed as a Datum, to indicate that + * it may still be in compressed/toasted form. We can avoid detoasting all + * of it in some cases. + * + * The result is always a freshly palloc'd datum. + */ +static text * +text_substring(Datum str, int32 start, int32 length, bool length_not_specified) +{ + int32 eml = pg_database_encoding_max_length(); + int32 S = start; /* start position */ + int32 S1; /* adjusted start position */ + int32 L1; /* adjusted substring length */ + int32 E; /* end position */ + + /* + * SQL99 says S can be zero or negative, but we still must fetch from the + * start of the string. + */ + S1 = Max(S, 1); + + /* life is easy if the encoding max length is 1 */ + if (eml == 1) + { + if (length_not_specified) /* special case - get length to end of + * string */ + L1 = -1; + else if (length < 0) + { + /* SQL99 says to throw an error for E < S, i.e., negative length */ + ereport(ERROR, + (errcode(ERRCODE_SUBSTRING_ERROR), + errmsg("negative substring length not allowed"))); + L1 = -1; /* silence stupider compilers */ + } + else if (pg_add_s32_overflow(S, length, &E)) + { + /* + * L could be large enough for S + L to overflow, in which case + * the substring must run to end of string. + */ + L1 = -1; + } + else + { + /* + * A zero or negative value for the end position can happen if the + * start was negative or one. SQL99 says to return a zero-length + * string. + */ + if (E < 1) + return cstring_to_text(""); + + L1 = E - S1; + } + + /* + * If the start position is past the end of the string, SQL99 says to + * return a zero-length string -- DatumGetTextPSlice() will do that + * for us. We need only convert S1 to zero-based starting position. + */ + return DatumGetTextPSlice(str, S1 - 1, L1); + } + else if (eml > 1) + { + /* + * When encoding max length is > 1, we can't get LC without + * detoasting, so we'll grab a conservatively large slice now and go + * back later to do the right thing + */ + int32 slice_start; + int32 slice_size; + int32 slice_strlen; + text *slice; + int32 E1; + int32 i; + char *p; + char *s; + text *ret; + + /* + * We need to start at position zero because there is no way to know + * in advance which byte offset corresponds to the supplied start + * position. + */ + slice_start = 0; + + if (length_not_specified) /* special case - get length to end of + * string */ + slice_size = L1 = -1; + else if (length < 0) + { + /* SQL99 says to throw an error for E < S, i.e., negative length */ + ereport(ERROR, + (errcode(ERRCODE_SUBSTRING_ERROR), + errmsg("negative substring length not allowed"))); + slice_size = L1 = -1; /* silence stupider compilers */ + } + else if (pg_add_s32_overflow(S, length, &E)) + { + /* + * L could be large enough for S + L to overflow, in which case + * the substring must run to end of string. + */ + slice_size = L1 = -1; + } + else + { + /* + * A zero or negative value for the end position can happen if the + * start was negative or one. SQL99 says to return a zero-length + * string. + */ + if (E < 1) + return cstring_to_text(""); + + /* + * if E is past the end of the string, the tuple toaster will + * truncate the length for us + */ + L1 = E - S1; + + /* + * Total slice size in bytes can't be any longer than the start + * position plus substring length times the encoding max length. + * If that overflows, we can just use -1. + */ + if (pg_mul_s32_overflow(E, eml, &slice_size)) + slice_size = -1; + } + + /* + * If we're working with an untoasted source, no need to do an extra + * copying step. + */ + if (VARATT_IS_COMPRESSED(DatumGetPointer(str)) || + VARATT_IS_EXTERNAL(DatumGetPointer(str))) + slice = DatumGetTextPSlice(str, slice_start, slice_size); + else + slice = (text *) DatumGetPointer(str); + + /* see if we got back an empty string */ + if (VARSIZE_ANY_EXHDR(slice) == 0) + { + if (slice != (text *) DatumGetPointer(str)) + pfree(slice); + return cstring_to_text(""); + } + + /* Now we can get the actual length of the slice in MB characters */ + slice_strlen = pg_mbstrlen_with_len(VARDATA_ANY(slice), + VARSIZE_ANY_EXHDR(slice)); + + /* + * Check that the start position wasn't > slice_strlen. If so, SQL99 + * says to return a zero-length string. + */ + if (S1 > slice_strlen) + { + if (slice != (text *) DatumGetPointer(str)) + pfree(slice); + return cstring_to_text(""); + } + + /* + * Adjust L1 and E1 now that we know the slice string length. Again + * remember that S1 is one based, and slice_start is zero based. + */ + if (L1 > -1) + E1 = Min(S1 + L1, slice_start + 1 + slice_strlen); + else + E1 = slice_start + 1 + slice_strlen; + + /* + * Find the start position in the slice; remember S1 is not zero based + */ + p = VARDATA_ANY(slice); + for (i = 0; i < S1 - 1; i++) + p += pg_mblen(p); + + /* hang onto a pointer to our start position */ + s = p; + + /* + * Count the actual bytes used by the substring of the requested + * length. + */ + for (i = S1; i < E1; i++) + p += pg_mblen(p); + + ret = (text *) palloc(VARHDRSZ + (p - s)); + SET_VARSIZE(ret, VARHDRSZ + (p - s)); + memcpy(VARDATA(ret), s, (p - s)); + + if (slice != (text *) DatumGetPointer(str)) + pfree(slice); + + return ret; + } + else + elog(ERROR, "invalid backend encoding: encoding max length < 1"); + + /* not reached: suppress compiler warning */ + return NULL; +} + +/* + * textoverlay + * Replace specified substring of first string with second + * + * The SQL standard defines OVERLAY() in terms of substring and concatenation. + * This code is a direct implementation of what the standard says. + */ +Datum +textoverlay(PG_FUNCTION_ARGS) +{ + text *t1 = PG_GETARG_TEXT_PP(0); + text *t2 = PG_GETARG_TEXT_PP(1); + int sp = PG_GETARG_INT32(2); /* substring start position */ + int sl = PG_GETARG_INT32(3); /* substring length */ + + PG_RETURN_TEXT_P(text_overlay(t1, t2, sp, sl)); +} + +Datum +textoverlay_no_len(PG_FUNCTION_ARGS) +{ + text *t1 = PG_GETARG_TEXT_PP(0); + text *t2 = PG_GETARG_TEXT_PP(1); + int sp = PG_GETARG_INT32(2); /* substring start position */ + int sl; + + sl = text_length(PointerGetDatum(t2)); /* defaults to length(t2) */ + PG_RETURN_TEXT_P(text_overlay(t1, t2, sp, sl)); +} + +static text * +text_overlay(text *t1, text *t2, int sp, int sl) +{ + text *result; + text *s1; + text *s2; + int sp_pl_sl; + + /* + * Check for possible integer-overflow cases. For negative sp, throw a + * "substring length" error because that's what should be expected + * according to the spec's definition of OVERLAY(). + */ + if (sp <= 0) + ereport(ERROR, + (errcode(ERRCODE_SUBSTRING_ERROR), + errmsg("negative substring length not allowed"))); + if (pg_add_s32_overflow(sp, sl, &sp_pl_sl)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("integer out of range"))); + + s1 = text_substring(PointerGetDatum(t1), 1, sp - 1, false); + s2 = text_substring(PointerGetDatum(t1), sp_pl_sl, -1, true); + result = text_catenate(s1, t2); + result = text_catenate(result, s2); + + return result; +} + +/* + * textpos - + * Return the position of the specified substring. + * Implements the SQL POSITION() function. + * Ref: A Guide To The SQL Standard, Date & Darwen, 1997 + * - thomas 1997-07-27 + */ +Datum +textpos(PG_FUNCTION_ARGS) +{ + text *str = PG_GETARG_TEXT_PP(0); + text *search_str = PG_GETARG_TEXT_PP(1); + + PG_RETURN_INT32((int32) text_position(str, search_str, PG_GET_COLLATION())); +} + +/* + * text_position - + * Does the real work for textpos() + * + * Inputs: + * t1 - string to be searched + * t2 - pattern to match within t1 + * Result: + * Character index of the first matched char, starting from 1, + * or 0 if no match. + * + * This is broken out so it can be called directly by other string processing + * functions. + */ +static int +text_position(text *t1, text *t2, Oid collid) +{ + TextPositionState state; + int result; + + /* Empty needle always matches at position 1 */ + if (VARSIZE_ANY_EXHDR(t2) < 1) + return 1; + + /* Otherwise, can't match if haystack is shorter than needle */ + if (VARSIZE_ANY_EXHDR(t1) < VARSIZE_ANY_EXHDR(t2)) + return 0; + + text_position_setup(t1, t2, collid, &state); + if (!text_position_next(&state)) + result = 0; + else + result = text_position_get_match_pos(&state); + text_position_cleanup(&state); + return result; +} + + +/* + * text_position_setup, text_position_next, text_position_cleanup - + * Component steps of text_position() + * + * These are broken out so that a string can be efficiently searched for + * multiple occurrences of the same pattern. text_position_next may be + * called multiple times, and it advances to the next match on each call. + * text_position_get_match_ptr() and text_position_get_match_pos() return + * a pointer or 1-based character position of the last match, respectively. + * + * The "state" variable is normally just a local variable in the caller. + * + * NOTE: text_position_next skips over the matched portion. For example, + * searching for "xx" in "xxx" returns only one match, not two. + */ + +static void +text_position_setup(text *t1, text *t2, Oid collid, TextPositionState *state) +{ + int len1 = VARSIZE_ANY_EXHDR(t1); + int len2 = VARSIZE_ANY_EXHDR(t2); + pg_locale_t mylocale = 0; + + check_collation_set(collid); + + if (!lc_collate_is_c(collid)) + mylocale = pg_newlocale_from_collation(collid); + + if (!pg_locale_deterministic(mylocale)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("nondeterministic collations are not supported for substring searches"))); + + Assert(len1 > 0); + Assert(len2 > 0); + + /* + * Even with a multi-byte encoding, we perform the search using the raw + * byte sequence, ignoring multibyte issues. For UTF-8, that works fine, + * because in UTF-8 the byte sequence of one character cannot contain + * another character. For other multi-byte encodings, we do the search + * initially as a simple byte search, ignoring multibyte issues, but + * verify afterwards that the match we found is at a character boundary, + * and continue the search if it was a false match. + */ + if (pg_database_encoding_max_length() == 1) + state->is_multibyte_char_in_char = false; + else if (GetDatabaseEncoding() == PG_UTF8) + state->is_multibyte_char_in_char = false; + else + state->is_multibyte_char_in_char = true; + + state->str1 = VARDATA_ANY(t1); + state->str2 = VARDATA_ANY(t2); + state->len1 = len1; + state->len2 = len2; + state->last_match = NULL; + state->refpoint = state->str1; + state->refpos = 0; + + /* + * Prepare the skip table for Boyer-Moore-Horspool searching. In these + * notes we use the terminology that the "haystack" is the string to be + * searched (t1) and the "needle" is the pattern being sought (t2). + * + * If the needle is empty or bigger than the haystack then there is no + * point in wasting cycles initializing the table. We also choose not to + * use B-M-H for needles of length 1, since the skip table can't possibly + * save anything in that case. + */ + if (len1 >= len2 && len2 > 1) + { + int searchlength = len1 - len2; + int skiptablemask; + int last; + int i; + const char *str2 = state->str2; + + /* + * First we must determine how much of the skip table to use. The + * declaration of TextPositionState allows up to 256 elements, but for + * short search problems we don't really want to have to initialize so + * many elements --- it would take too long in comparison to the + * actual search time. So we choose a useful skip table size based on + * the haystack length minus the needle length. The closer the needle + * length is to the haystack length the less useful skipping becomes. + * + * Note: since we use bit-masking to select table elements, the skip + * table size MUST be a power of 2, and so the mask must be 2^N-1. + */ + if (searchlength < 16) + skiptablemask = 3; + else if (searchlength < 64) + skiptablemask = 7; + else if (searchlength < 128) + skiptablemask = 15; + else if (searchlength < 512) + skiptablemask = 31; + else if (searchlength < 2048) + skiptablemask = 63; + else if (searchlength < 4096) + skiptablemask = 127; + else + skiptablemask = 255; + state->skiptablemask = skiptablemask; + + /* + * Initialize the skip table. We set all elements to the needle + * length, since this is the correct skip distance for any character + * not found in the needle. + */ + for (i = 0; i <= skiptablemask; i++) + state->skiptable[i] = len2; + + /* + * Now examine the needle. For each character except the last one, + * set the corresponding table element to the appropriate skip + * distance. Note that when two characters share the same skip table + * entry, the one later in the needle must determine the skip + * distance. + */ + last = len2 - 1; + + for (i = 0; i < last; i++) + state->skiptable[(unsigned char) str2[i] & skiptablemask] = last - i; + } +} + +/* + * Advance to the next match, starting from the end of the previous match + * (or the beginning of the string, on first call). Returns true if a match + * is found. + * + * Note that this refuses to match an empty-string needle. Most callers + * will have handled that case specially and we'll never see it here. + */ +static bool +text_position_next(TextPositionState *state) +{ + int needle_len = state->len2; + char *start_ptr; + char *matchptr; + + if (needle_len <= 0) + return false; /* result for empty pattern */ + + /* Start from the point right after the previous match. */ + if (state->last_match) + start_ptr = state->last_match + needle_len; + else + start_ptr = state->str1; + +retry: + matchptr = text_position_next_internal(start_ptr, state); + + if (!matchptr) + return false; + + /* + * Found a match for the byte sequence. If this is a multibyte encoding, + * where one character's byte sequence can appear inside a longer + * multi-byte character, we need to verify that the match was at a + * character boundary, not in the middle of a multi-byte character. + */ + if (state->is_multibyte_char_in_char) + { + /* Walk one character at a time, until we reach the match. */ + + /* the search should never move backwards. */ + Assert(state->refpoint <= matchptr); + + while (state->refpoint < matchptr) + { + /* step to next character. */ + state->refpoint += pg_mblen(state->refpoint); + state->refpos++; + + /* + * If we stepped over the match's start position, then it was a + * false positive, where the byte sequence appeared in the middle + * of a multi-byte character. Skip it, and continue the search at + * the next character boundary. + */ + if (state->refpoint > matchptr) + { + start_ptr = state->refpoint; + goto retry; + } + } + } + + state->last_match = matchptr; + return true; +} + +/* + * Subroutine of text_position_next(). This searches for the raw byte + * sequence, ignoring any multi-byte encoding issues. Returns the first + * match starting at 'start_ptr', or NULL if no match is found. + */ +static char * +text_position_next_internal(char *start_ptr, TextPositionState *state) +{ + int haystack_len = state->len1; + int needle_len = state->len2; + int skiptablemask = state->skiptablemask; + const char *haystack = state->str1; + const char *needle = state->str2; + const char *haystack_end = &haystack[haystack_len]; + const char *hptr; + + Assert(start_ptr >= haystack && start_ptr <= haystack_end); + + if (needle_len == 1) + { + /* No point in using B-M-H for a one-character needle */ + char nchar = *needle; + + hptr = start_ptr; + while (hptr < haystack_end) + { + if (*hptr == nchar) + return (char *) hptr; + hptr++; + } + } + else + { + const char *needle_last = &needle[needle_len - 1]; + + /* Start at startpos plus the length of the needle */ + hptr = start_ptr + needle_len - 1; + while (hptr < haystack_end) + { + /* Match the needle scanning *backward* */ + const char *nptr; + const char *p; + + nptr = needle_last; + p = hptr; + while (*nptr == *p) + { + /* Matched it all? If so, return 1-based position */ + if (nptr == needle) + return (char *) p; + nptr--, p--; + } + + /* + * No match, so use the haystack char at hptr to decide how far to + * advance. If the needle had any occurrence of that character + * (or more precisely, one sharing the same skiptable entry) + * before its last character, then we advance far enough to align + * the last such needle character with that haystack position. + * Otherwise we can advance by the whole needle length. + */ + hptr += state->skiptable[(unsigned char) *hptr & skiptablemask]; + } + } + + return 0; /* not found */ +} + +/* + * Return a pointer to the current match. + * + * The returned pointer points into the original haystack string. + */ +static char * +text_position_get_match_ptr(TextPositionState *state) +{ + return state->last_match; +} + +/* + * Return the offset of the current match. + * + * The offset is in characters, 1-based. + */ +static int +text_position_get_match_pos(TextPositionState *state) +{ + /* Convert the byte position to char position. */ + state->refpos += pg_mbstrlen_with_len(state->refpoint, + state->last_match - state->refpoint); + state->refpoint = state->last_match; + return state->refpos + 1; +} + +/* + * Reset search state to the initial state installed by text_position_setup. + * + * The next call to text_position_next will search from the beginning + * of the string. + */ +static void +text_position_reset(TextPositionState *state) +{ + state->last_match = NULL; + state->refpoint = state->str1; + state->refpos = 0; +} + +static void +text_position_cleanup(TextPositionState *state) +{ + /* no cleanup needed */ +} + + +static void +check_collation_set(Oid collid) +{ + if (!OidIsValid(collid)) + { + /* + * This typically means that the parser could not resolve a conflict + * of implicit collations, so report it that way. + */ + ereport(ERROR, + (errcode(ERRCODE_INDETERMINATE_COLLATION), + errmsg("could not determine which collation to use for string comparison"), + errhint("Use the COLLATE clause to set the collation explicitly."))); + } +} + +/* varstr_cmp() + * Comparison function for text strings with given lengths. + * Includes locale support, but must copy strings to temporary memory + * to allow null-termination for inputs to strcoll(). + * Returns an integer less than, equal to, or greater than zero, indicating + * whether arg1 is less than, equal to, or greater than arg2. + * + * Note: many functions that depend on this are marked leakproof; therefore, + * avoid reporting the actual contents of the input when throwing errors. + * All errors herein should be things that can't happen except on corrupt + * data, anyway; otherwise we will have trouble with indexing strings that + * would cause them. + */ +int +varstr_cmp(const char *arg1, int len1, const char *arg2, int len2, Oid collid) +{ + int result; + + check_collation_set(collid); + + /* + * Unfortunately, there is no strncoll(), so in the non-C locale case we + * have to do some memory copying. This turns out to be significantly + * slower, so we optimize the case where LC_COLLATE is C. We also try to + * optimize relatively-short strings by avoiding palloc/pfree overhead. + */ + if (lc_collate_is_c(collid)) + { + result = memcmp(arg1, arg2, Min(len1, len2)); + if ((result == 0) && (len1 != len2)) + result = (len1 < len2) ? -1 : 1; + } + else + { + pg_locale_t mylocale; + + mylocale = pg_newlocale_from_collation(collid); + + /* + * memcmp() can't tell us which of two unequal strings sorts first, + * but it's a cheap way to tell if they're equal. Testing shows that + * memcmp() followed by strcoll() is only trivially slower than + * strcoll() by itself, so we don't lose much if this doesn't work out + * very often, and if it does - for example, because there are many + * equal strings in the input - then we win big by avoiding expensive + * collation-aware comparisons. + */ + if (len1 == len2 && memcmp(arg1, arg2, len1) == 0) + return 0; + + result = pg_strncoll(arg1, len1, arg2, len2, mylocale); + + /* Break tie if necessary. */ + if (result == 0 && pg_locale_deterministic(mylocale)) + { + result = memcmp(arg1, arg2, Min(len1, len2)); + if ((result == 0) && (len1 != len2)) + result = (len1 < len2) ? -1 : 1; + } + } + + return result; +} + +/* text_cmp() + * Internal comparison function for text strings. + * Returns -1, 0 or 1 + */ +static int +text_cmp(text *arg1, text *arg2, Oid collid) +{ + char *a1p, + *a2p; + int len1, + len2; + + a1p = VARDATA_ANY(arg1); + a2p = VARDATA_ANY(arg2); + + len1 = VARSIZE_ANY_EXHDR(arg1); + len2 = VARSIZE_ANY_EXHDR(arg2); + + return varstr_cmp(a1p, len1, a2p, len2, collid); +} + +/* + * Comparison functions for text strings. + * + * Note: btree indexes need these routines not to leak memory; therefore, + * be careful to free working copies of toasted datums. Most places don't + * need to be so careful. + */ + +Datum +texteq(PG_FUNCTION_ARGS) +{ + Oid collid = PG_GET_COLLATION(); + bool locale_is_c = false; + pg_locale_t mylocale = 0; + bool result; + + check_collation_set(collid); + + if (lc_collate_is_c(collid)) + locale_is_c = true; + else + mylocale = pg_newlocale_from_collation(collid); + + if (locale_is_c || pg_locale_deterministic(mylocale)) + { + Datum arg1 = PG_GETARG_DATUM(0); + Datum arg2 = PG_GETARG_DATUM(1); + Size len1, + len2; + + /* + * Since we only care about equality or not-equality, we can avoid all + * the expense of strcoll() here, and just do bitwise comparison. In + * fact, we don't even have to do a bitwise comparison if we can show + * the lengths of the strings are unequal; which might save us from + * having to detoast one or both values. + */ + len1 = toast_raw_datum_size(arg1); + len2 = toast_raw_datum_size(arg2); + if (len1 != len2) + result = false; + else + { + text *targ1 = DatumGetTextPP(arg1); + text *targ2 = DatumGetTextPP(arg2); + + result = (memcmp(VARDATA_ANY(targ1), VARDATA_ANY(targ2), + len1 - VARHDRSZ) == 0); + + PG_FREE_IF_COPY(targ1, 0); + PG_FREE_IF_COPY(targ2, 1); + } + } + else + { + text *arg1 = PG_GETARG_TEXT_PP(0); + text *arg2 = PG_GETARG_TEXT_PP(1); + + result = (text_cmp(arg1, arg2, collid) == 0); + + PG_FREE_IF_COPY(arg1, 0); + PG_FREE_IF_COPY(arg2, 1); + } + + PG_RETURN_BOOL(result); +} + +Datum +textne(PG_FUNCTION_ARGS) +{ + Oid collid = PG_GET_COLLATION(); + bool locale_is_c = false; + pg_locale_t mylocale = 0; + bool result; + + check_collation_set(collid); + + if (lc_collate_is_c(collid)) + locale_is_c = true; + else + mylocale = pg_newlocale_from_collation(collid); + + if (locale_is_c || pg_locale_deterministic(mylocale)) + { + Datum arg1 = PG_GETARG_DATUM(0); + Datum arg2 = PG_GETARG_DATUM(1); + Size len1, + len2; + + /* See comment in texteq() */ + len1 = toast_raw_datum_size(arg1); + len2 = toast_raw_datum_size(arg2); + if (len1 != len2) + result = true; + else + { + text *targ1 = DatumGetTextPP(arg1); + text *targ2 = DatumGetTextPP(arg2); + + result = (memcmp(VARDATA_ANY(targ1), VARDATA_ANY(targ2), + len1 - VARHDRSZ) != 0); + + PG_FREE_IF_COPY(targ1, 0); + PG_FREE_IF_COPY(targ2, 1); + } + } + else + { + text *arg1 = PG_GETARG_TEXT_PP(0); + text *arg2 = PG_GETARG_TEXT_PP(1); + + result = (text_cmp(arg1, arg2, collid) != 0); + + PG_FREE_IF_COPY(arg1, 0); + PG_FREE_IF_COPY(arg2, 1); + } + + PG_RETURN_BOOL(result); +} + +Datum +text_lt(PG_FUNCTION_ARGS) +{ + text *arg1 = PG_GETARG_TEXT_PP(0); + text *arg2 = PG_GETARG_TEXT_PP(1); + bool result; + + result = (text_cmp(arg1, arg2, PG_GET_COLLATION()) < 0); + + PG_FREE_IF_COPY(arg1, 0); + PG_FREE_IF_COPY(arg2, 1); + + PG_RETURN_BOOL(result); +} + +Datum +text_le(PG_FUNCTION_ARGS) +{ + text *arg1 = PG_GETARG_TEXT_PP(0); + text *arg2 = PG_GETARG_TEXT_PP(1); + bool result; + + result = (text_cmp(arg1, arg2, PG_GET_COLLATION()) <= 0); + + PG_FREE_IF_COPY(arg1, 0); + PG_FREE_IF_COPY(arg2, 1); + + PG_RETURN_BOOL(result); +} + +Datum +text_gt(PG_FUNCTION_ARGS) +{ + text *arg1 = PG_GETARG_TEXT_PP(0); + text *arg2 = PG_GETARG_TEXT_PP(1); + bool result; + + result = (text_cmp(arg1, arg2, PG_GET_COLLATION()) > 0); + + PG_FREE_IF_COPY(arg1, 0); + PG_FREE_IF_COPY(arg2, 1); + + PG_RETURN_BOOL(result); +} + +Datum +text_ge(PG_FUNCTION_ARGS) +{ + text *arg1 = PG_GETARG_TEXT_PP(0); + text *arg2 = PG_GETARG_TEXT_PP(1); + bool result; + + result = (text_cmp(arg1, arg2, PG_GET_COLLATION()) >= 0); + + PG_FREE_IF_COPY(arg1, 0); + PG_FREE_IF_COPY(arg2, 1); + + PG_RETURN_BOOL(result); +} + +Datum +text_starts_with(PG_FUNCTION_ARGS) +{ + Datum arg1 = PG_GETARG_DATUM(0); + Datum arg2 = PG_GETARG_DATUM(1); + Oid collid = PG_GET_COLLATION(); + pg_locale_t mylocale = 0; + bool result; + Size len1, + len2; + + check_collation_set(collid); + + if (!lc_collate_is_c(collid)) + mylocale = pg_newlocale_from_collation(collid); + + if (!pg_locale_deterministic(mylocale)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("nondeterministic collations are not supported for substring searches"))); + + len1 = toast_raw_datum_size(arg1); + len2 = toast_raw_datum_size(arg2); + if (len2 > len1) + result = false; + else + { + text *targ1 = text_substring(arg1, 1, len2, false); + text *targ2 = DatumGetTextPP(arg2); + + result = (memcmp(VARDATA_ANY(targ1), VARDATA_ANY(targ2), + VARSIZE_ANY_EXHDR(targ2)) == 0); + + PG_FREE_IF_COPY(targ1, 0); + PG_FREE_IF_COPY(targ2, 1); + } + + PG_RETURN_BOOL(result); +} + +Datum +bttextcmp(PG_FUNCTION_ARGS) +{ + text *arg1 = PG_GETARG_TEXT_PP(0); + text *arg2 = PG_GETARG_TEXT_PP(1); + int32 result; + + result = text_cmp(arg1, arg2, PG_GET_COLLATION()); + + PG_FREE_IF_COPY(arg1, 0); + PG_FREE_IF_COPY(arg2, 1); + + PG_RETURN_INT32(result); +} + +Datum +bttextsortsupport(PG_FUNCTION_ARGS) +{ + SortSupport ssup = (SortSupport) PG_GETARG_POINTER(0); + Oid collid = ssup->ssup_collation; + MemoryContext oldcontext; + + oldcontext = MemoryContextSwitchTo(ssup->ssup_cxt); + + /* Use generic string SortSupport */ + varstr_sortsupport(ssup, TEXTOID, collid); + + MemoryContextSwitchTo(oldcontext); + + PG_RETURN_VOID(); +} + +/* + * Generic sortsupport interface for character type's operator classes. + * Includes locale support, and support for BpChar semantics (i.e. removing + * trailing spaces before comparison). + * + * Relies on the assumption that text, VarChar, BpChar, and bytea all have the + * same representation. Callers that always use the C collation (e.g. + * non-collatable type callers like bytea) may have NUL bytes in their strings; + * this will not work with any other collation, though. + */ +void +varstr_sortsupport(SortSupport ssup, Oid typid, Oid collid) +{ + bool abbreviate = ssup->abbreviate; + bool collate_c = false; + VarStringSortSupport *sss; + pg_locale_t locale = 0; + + check_collation_set(collid); + + /* + * If possible, set ssup->comparator to a function which can be used to + * directly compare two datums. If we can do this, we'll avoid the + * overhead of a trip through the fmgr layer for every comparison, which + * can be substantial. + * + * Most typically, we'll set the comparator to varlenafastcmp_locale, + * which uses strcoll() to perform comparisons. We use that for the + * BpChar case too, but type NAME uses namefastcmp_locale. However, if + * LC_COLLATE = C, we can make things quite a bit faster with + * varstrfastcmp_c, bpcharfastcmp_c, or namefastcmp_c, all of which use + * memcmp() rather than strcoll(). + */ + if (lc_collate_is_c(collid)) + { + if (typid == BPCHAROID) + ssup->comparator = bpcharfastcmp_c; + else if (typid == NAMEOID) + { + ssup->comparator = namefastcmp_c; + /* Not supporting abbreviation with type NAME, for now */ + abbreviate = false; + } + else + ssup->comparator = varstrfastcmp_c; + + collate_c = true; + } + else + { + /* + * We need a collation-sensitive comparison. To make things faster, + * we'll figure out the collation based on the locale id and cache the + * result. + */ + locale = pg_newlocale_from_collation(collid); + + /* + * We use varlenafastcmp_locale except for type NAME. + */ + if (typid == NAMEOID) + { + ssup->comparator = namefastcmp_locale; + /* Not supporting abbreviation with type NAME, for now */ + abbreviate = false; + } + else + ssup->comparator = varlenafastcmp_locale; + } + + /* + * Unfortunately, it seems that abbreviation for non-C collations is + * broken on many common platforms; see pg_strxfrm_enabled(). + * + * Even apart from the risk of broken locales, it's possible that there + * are platforms where the use of abbreviated keys should be disabled at + * compile time. Having only 4 byte datums could make worst-case + * performance drastically more likely, for example. Moreover, macOS's + * strxfrm() implementation is known to not effectively concentrate a + * significant amount of entropy from the original string in earlier + * transformed blobs. It's possible that other supported platforms are + * similarly encumbered. So, if we ever get past disabling this + * categorically, we may still want or need to disable it for particular + * platforms. + */ + if (!collate_c && !pg_strxfrm_enabled(locale)) + abbreviate = false; + + /* + * If we're using abbreviated keys, or if we're using a locale-aware + * comparison, we need to initialize a VarStringSortSupport object. Both + * cases will make use of the temporary buffers we initialize here for + * scratch space (and to detect requirement for BpChar semantics from + * caller), and the abbreviation case requires additional state. + */ + if (abbreviate || !collate_c) + { + sss = palloc(sizeof(VarStringSortSupport)); + sss->buf1 = palloc(TEXTBUFLEN); + sss->buflen1 = TEXTBUFLEN; + sss->buf2 = palloc(TEXTBUFLEN); + sss->buflen2 = TEXTBUFLEN; + /* Start with invalid values */ + sss->last_len1 = -1; + sss->last_len2 = -1; + /* Initialize */ + sss->last_returned = 0; + sss->locale = locale; + + /* + * To avoid somehow confusing a strxfrm() blob and an original string, + * constantly keep track of the variety of data that buf1 and buf2 + * currently contain. + * + * Comparisons may be interleaved with conversion calls. Frequently, + * conversions and comparisons are batched into two distinct phases, + * but the correctness of caching cannot hinge upon this. For + * comparison caching, buffer state is only trusted if cache_blob is + * found set to false, whereas strxfrm() caching only trusts the state + * when cache_blob is found set to true. + * + * Arbitrarily initialize cache_blob to true. + */ + sss->cache_blob = true; + sss->collate_c = collate_c; + sss->typid = typid; + ssup->ssup_extra = sss; + + /* + * If possible, plan to use the abbreviated keys optimization. The + * core code may switch back to authoritative comparator should + * abbreviation be aborted. + */ + if (abbreviate) + { + sss->prop_card = 0.20; + initHyperLogLog(&sss->abbr_card, 10); + initHyperLogLog(&sss->full_card, 10); + ssup->abbrev_full_comparator = ssup->comparator; + ssup->comparator = ssup_datum_unsigned_cmp; + ssup->abbrev_converter = varstr_abbrev_convert; + ssup->abbrev_abort = varstr_abbrev_abort; + } + } +} + +/* + * sortsupport comparison func (for C locale case) + */ +static int +varstrfastcmp_c(Datum x, Datum y, SortSupport ssup) +{ + VarString *arg1 = DatumGetVarStringPP(x); + VarString *arg2 = DatumGetVarStringPP(y); + char *a1p, + *a2p; + int len1, + len2, + result; + + a1p = VARDATA_ANY(arg1); + a2p = VARDATA_ANY(arg2); + + len1 = VARSIZE_ANY_EXHDR(arg1); + len2 = VARSIZE_ANY_EXHDR(arg2); + + result = memcmp(a1p, a2p, Min(len1, len2)); + if ((result == 0) && (len1 != len2)) + result = (len1 < len2) ? -1 : 1; + + /* We can't afford to leak memory here. */ + if (PointerGetDatum(arg1) != x) + pfree(arg1); + if (PointerGetDatum(arg2) != y) + pfree(arg2); + + return result; +} + +/* + * sortsupport comparison func (for BpChar C locale case) + * + * BpChar outsources its sortsupport to this module. Specialization for the + * varstr_sortsupport BpChar case, modeled on + * internal_bpchar_pattern_compare(). + */ +static int +bpcharfastcmp_c(Datum x, Datum y, SortSupport ssup) +{ + BpChar *arg1 = DatumGetBpCharPP(x); + BpChar *arg2 = DatumGetBpCharPP(y); + char *a1p, + *a2p; + int len1, + len2, + result; + + a1p = VARDATA_ANY(arg1); + a2p = VARDATA_ANY(arg2); + + len1 = bpchartruelen(a1p, VARSIZE_ANY_EXHDR(arg1)); + len2 = bpchartruelen(a2p, VARSIZE_ANY_EXHDR(arg2)); + + result = memcmp(a1p, a2p, Min(len1, len2)); + if ((result == 0) && (len1 != len2)) + result = (len1 < len2) ? -1 : 1; + + /* We can't afford to leak memory here. */ + if (PointerGetDatum(arg1) != x) + pfree(arg1); + if (PointerGetDatum(arg2) != y) + pfree(arg2); + + return result; +} + +/* + * sortsupport comparison func (for NAME C locale case) + */ +static int +namefastcmp_c(Datum x, Datum y, SortSupport ssup) +{ + Name arg1 = DatumGetName(x); + Name arg2 = DatumGetName(y); + + return strncmp(NameStr(*arg1), NameStr(*arg2), NAMEDATALEN); +} + +/* + * sortsupport comparison func (for locale case with all varlena types) + */ +static int +varlenafastcmp_locale(Datum x, Datum y, SortSupport ssup) +{ + VarString *arg1 = DatumGetVarStringPP(x); + VarString *arg2 = DatumGetVarStringPP(y); + char *a1p, + *a2p; + int len1, + len2, + result; + + a1p = VARDATA_ANY(arg1); + a2p = VARDATA_ANY(arg2); + + len1 = VARSIZE_ANY_EXHDR(arg1); + len2 = VARSIZE_ANY_EXHDR(arg2); + + result = varstrfastcmp_locale(a1p, len1, a2p, len2, ssup); + + /* We can't afford to leak memory here. */ + if (PointerGetDatum(arg1) != x) + pfree(arg1); + if (PointerGetDatum(arg2) != y) + pfree(arg2); + + return result; +} + +/* + * sortsupport comparison func (for locale case with NAME type) + */ +static int +namefastcmp_locale(Datum x, Datum y, SortSupport ssup) +{ + Name arg1 = DatumGetName(x); + Name arg2 = DatumGetName(y); + + return varstrfastcmp_locale(NameStr(*arg1), strlen(NameStr(*arg1)), + NameStr(*arg2), strlen(NameStr(*arg2)), + ssup); +} + +/* + * sortsupport comparison func for locale cases + */ +static int +varstrfastcmp_locale(char *a1p, int len1, char *a2p, int len2, SortSupport ssup) +{ + VarStringSortSupport *sss = (VarStringSortSupport *) ssup->ssup_extra; + int result; + bool arg1_match; + + /* Fast pre-check for equality, as discussed in varstr_cmp() */ + if (len1 == len2 && memcmp(a1p, a2p, len1) == 0) + { + /* + * No change in buf1 or buf2 contents, so avoid changing last_len1 or + * last_len2. Existing contents of buffers might still be used by + * next call. + * + * It's fine to allow the comparison of BpChar padding bytes here, + * even though that implies that the memcmp() will usually be + * performed for BpChar callers (though multibyte characters could + * still prevent that from occurring). The memcmp() is still very + * cheap, and BpChar's funny semantics have us remove trailing spaces + * (not limited to padding), so we need make no distinction between + * padding space characters and "real" space characters. + */ + return 0; + } + + if (sss->typid == BPCHAROID) + { + /* Get true number of bytes, ignoring trailing spaces */ + len1 = bpchartruelen(a1p, len1); + len2 = bpchartruelen(a2p, len2); + } + + if (len1 >= sss->buflen1) + { + sss->buflen1 = Max(len1 + 1, Min(sss->buflen1 * 2, MaxAllocSize)); + sss->buf1 = repalloc(sss->buf1, sss->buflen1); + } + if (len2 >= sss->buflen2) + { + sss->buflen2 = Max(len2 + 1, Min(sss->buflen2 * 2, MaxAllocSize)); + sss->buf2 = repalloc(sss->buf2, sss->buflen2); + } + + /* + * We're likely to be asked to compare the same strings repeatedly, and + * memcmp() is so much cheaper than strcoll() that it pays to try to cache + * comparisons, even though in general there is no reason to think that + * that will work out (every string datum may be unique). Caching does + * not slow things down measurably when it doesn't work out, and can speed + * things up by rather a lot when it does. In part, this is because the + * memcmp() compares data from cachelines that are needed in L1 cache even + * when the last comparison's result cannot be reused. + */ + arg1_match = true; + if (len1 != sss->last_len1 || memcmp(sss->buf1, a1p, len1) != 0) + { + arg1_match = false; + memcpy(sss->buf1, a1p, len1); + sss->buf1[len1] = '\0'; + sss->last_len1 = len1; + } + + /* + * If we're comparing the same two strings as last time, we can return the + * same answer without calling strcoll() again. This is more likely than + * it seems (at least with moderate to low cardinality sets), because + * quicksort compares the same pivot against many values. + */ + if (len2 != sss->last_len2 || memcmp(sss->buf2, a2p, len2) != 0) + { + memcpy(sss->buf2, a2p, len2); + sss->buf2[len2] = '\0'; + sss->last_len2 = len2; + } + else if (arg1_match && !sss->cache_blob) + { + /* Use result cached following last actual strcoll() call */ + return sss->last_returned; + } + + result = pg_strcoll(sss->buf1, sss->buf2, sss->locale); + + /* Break tie if necessary. */ + if (result == 0 && pg_locale_deterministic(sss->locale)) + result = strcmp(sss->buf1, sss->buf2); + + /* Cache result, perhaps saving an expensive strcoll() call next time */ + sss->cache_blob = false; + sss->last_returned = result; + return result; +} + +/* + * Conversion routine for sortsupport. Converts original to abbreviated key + * representation. Our encoding strategy is simple -- pack the first 8 bytes + * of a strxfrm() blob into a Datum (on little-endian machines, the 8 bytes are + * stored in reverse order), and treat it as an unsigned integer. When the "C" + * locale is used, or in case of bytea, just memcpy() from original instead. + */ +static Datum +varstr_abbrev_convert(Datum original, SortSupport ssup) +{ + const size_t max_prefix_bytes = sizeof(Datum); + VarStringSortSupport *sss = (VarStringSortSupport *) ssup->ssup_extra; + VarString *authoritative = DatumGetVarStringPP(original); + char *authoritative_data = VARDATA_ANY(authoritative); + + /* working state */ + Datum res; + char *pres; + int len; + uint32 hash; + + pres = (char *) &res; + /* memset(), so any non-overwritten bytes are NUL */ + memset(pres, 0, max_prefix_bytes); + len = VARSIZE_ANY_EXHDR(authoritative); + + /* Get number of bytes, ignoring trailing spaces */ + if (sss->typid == BPCHAROID) + len = bpchartruelen(authoritative_data, len); + + /* + * If we're using the C collation, use memcpy(), rather than strxfrm(), to + * abbreviate keys. The full comparator for the C locale is always + * memcmp(). It would be incorrect to allow bytea callers (callers that + * always force the C collation -- bytea isn't a collatable type, but this + * approach is convenient) to use strxfrm(). This is because bytea + * strings may contain NUL bytes. Besides, this should be faster, too. + * + * More generally, it's okay that bytea callers can have NUL bytes in + * strings because abbreviated cmp need not make a distinction between + * terminating NUL bytes, and NUL bytes representing actual NULs in the + * authoritative representation. Hopefully a comparison at or past one + * abbreviated key's terminating NUL byte will resolve the comparison + * without consulting the authoritative representation; specifically, some + * later non-NUL byte in the longer string can resolve the comparison + * against a subsequent terminating NUL in the shorter string. There will + * usually be what is effectively a "length-wise" resolution there and + * then. + * + * If that doesn't work out -- if all bytes in the longer string + * positioned at or past the offset of the smaller string's (first) + * terminating NUL are actually representative of NUL bytes in the + * authoritative binary string (perhaps with some *terminating* NUL bytes + * towards the end of the longer string iff it happens to still be small) + * -- then an authoritative tie-breaker will happen, and do the right + * thing: explicitly consider string length. + */ + if (sss->collate_c) + memcpy(pres, authoritative_data, Min(len, max_prefix_bytes)); + else + { + Size bsize; + + /* + * We're not using the C collation, so fall back on strxfrm or ICU + * analogs. + */ + + /* By convention, we use buffer 1 to store and NUL-terminate */ + if (len >= sss->buflen1) + { + sss->buflen1 = Max(len + 1, Min(sss->buflen1 * 2, MaxAllocSize)); + sss->buf1 = repalloc(sss->buf1, sss->buflen1); + } + + /* Might be able to reuse strxfrm() blob from last call */ + if (sss->last_len1 == len && sss->cache_blob && + memcmp(sss->buf1, authoritative_data, len) == 0) + { + memcpy(pres, sss->buf2, Min(max_prefix_bytes, sss->last_len2)); + /* No change affecting cardinality, so no hashing required */ + goto done; + } + + memcpy(sss->buf1, authoritative_data, len); + + /* + * pg_strxfrm() and pg_strxfrm_prefix expect NUL-terminated strings. + */ + sss->buf1[len] = '\0'; + sss->last_len1 = len; + + if (pg_strxfrm_prefix_enabled(sss->locale)) + { + if (sss->buflen2 < max_prefix_bytes) + { + sss->buflen2 = Max(max_prefix_bytes, + Min(sss->buflen2 * 2, MaxAllocSize)); + sss->buf2 = repalloc(sss->buf2, sss->buflen2); + } + + bsize = pg_strxfrm_prefix(sss->buf2, sss->buf1, + max_prefix_bytes, sss->locale); + sss->last_len2 = bsize; + } + else + { + /* + * Loop: Call pg_strxfrm(), possibly enlarge buffer, and try + * again. The pg_strxfrm() function leaves the result buffer + * content undefined if the result did not fit, so we need to + * retry until everything fits, even though we only need the first + * few bytes in the end. + */ + for (;;) + { + bsize = pg_strxfrm(sss->buf2, sss->buf1, sss->buflen2, + sss->locale); + + sss->last_len2 = bsize; + if (bsize < sss->buflen2) + break; + + /* + * Grow buffer and retry. + */ + sss->buflen2 = Max(bsize + 1, + Min(sss->buflen2 * 2, MaxAllocSize)); + sss->buf2 = repalloc(sss->buf2, sss->buflen2); + } + } + + /* + * Every Datum byte is always compared. This is safe because the + * strxfrm() blob is itself NUL terminated, leaving no danger of + * misinterpreting any NUL bytes not intended to be interpreted as + * logically representing termination. + * + * (Actually, even if there were NUL bytes in the blob it would be + * okay. See remarks on bytea case above.) + */ + memcpy(pres, sss->buf2, Min(max_prefix_bytes, bsize)); + } + + /* + * Maintain approximate cardinality of both abbreviated keys and original, + * authoritative keys using HyperLogLog. Used as cheap insurance against + * the worst case, where we do many string transformations for no saving + * in full strcoll()-based comparisons. These statistics are used by + * varstr_abbrev_abort(). + * + * First, Hash key proper, or a significant fraction of it. Mix in length + * in order to compensate for cases where differences are past + * PG_CACHE_LINE_SIZE bytes, so as to limit the overhead of hashing. + */ + hash = DatumGetUInt32(hash_any((unsigned char *) authoritative_data, + Min(len, PG_CACHE_LINE_SIZE))); + + if (len > PG_CACHE_LINE_SIZE) + hash ^= DatumGetUInt32(hash_uint32((uint32) len)); + + addHyperLogLog(&sss->full_card, hash); + + /* Hash abbreviated key */ +#if SIZEOF_DATUM == 8 + { + uint32 lohalf, + hihalf; + + lohalf = (uint32) res; + hihalf = (uint32) (res >> 32); + hash = DatumGetUInt32(hash_uint32(lohalf ^ hihalf)); + } +#else /* SIZEOF_DATUM != 8 */ + hash = DatumGetUInt32(hash_uint32((uint32) res)); +#endif + + addHyperLogLog(&sss->abbr_card, hash); + + /* Cache result, perhaps saving an expensive strxfrm() call next time */ + sss->cache_blob = true; +done: + + /* + * Byteswap on little-endian machines. + * + * This is needed so that ssup_datum_unsigned_cmp() (an unsigned integer + * 3-way comparator) works correctly on all platforms. If we didn't do + * this, the comparator would have to call memcmp() with a pair of + * pointers to the first byte of each abbreviated key, which is slower. + */ + res = DatumBigEndianToNative(res); + + /* Don't leak memory here */ + if (PointerGetDatum(authoritative) != original) + pfree(authoritative); + + return res; +} + +/* + * Callback for estimating effectiveness of abbreviated key optimization, using + * heuristic rules. Returns value indicating if the abbreviation optimization + * should be aborted, based on its projected effectiveness. + */ +static bool +varstr_abbrev_abort(int memtupcount, SortSupport ssup) +{ + VarStringSortSupport *sss = (VarStringSortSupport *) ssup->ssup_extra; + double abbrev_distinct, + key_distinct; + + Assert(ssup->abbreviate); + + /* Have a little patience */ + if (memtupcount < 100) + return false; + + abbrev_distinct = estimateHyperLogLog(&sss->abbr_card); + key_distinct = estimateHyperLogLog(&sss->full_card); + + /* + * Clamp cardinality estimates to at least one distinct value. While + * NULLs are generally disregarded, if only NULL values were seen so far, + * that might misrepresent costs if we failed to clamp. + */ + if (abbrev_distinct <= 1.0) + abbrev_distinct = 1.0; + + if (key_distinct <= 1.0) + key_distinct = 1.0; + + /* + * In the worst case all abbreviated keys are identical, while at the same + * time there are differences within full key strings not captured in + * abbreviations. + */ +#ifdef TRACE_SORT + if (trace_sort) + { + double norm_abbrev_card = abbrev_distinct / (double) memtupcount; + + elog(LOG, "varstr_abbrev: abbrev_distinct after %d: %f " + "(key_distinct: %f, norm_abbrev_card: %f, prop_card: %f)", + memtupcount, abbrev_distinct, key_distinct, norm_abbrev_card, + sss->prop_card); + } +#endif + + /* + * If the number of distinct abbreviated keys approximately matches the + * number of distinct authoritative original keys, that's reason enough to + * proceed. We can win even with a very low cardinality set if most + * tie-breakers only memcmp(). This is by far the most important + * consideration. + * + * While comparisons that are resolved at the abbreviated key level are + * considerably cheaper than tie-breakers resolved with memcmp(), both of + * those two outcomes are so much cheaper than a full strcoll() once + * sorting is underway that it doesn't seem worth it to weigh abbreviated + * cardinality against the overall size of the set in order to more + * accurately model costs. Assume that an abbreviated comparison, and an + * abbreviated comparison with a cheap memcmp()-based authoritative + * resolution are equivalent. + */ + if (abbrev_distinct > key_distinct * sss->prop_card) + { + /* + * When we have exceeded 10,000 tuples, decay required cardinality + * aggressively for next call. + * + * This is useful because the number of comparisons required on + * average increases at a linearithmic rate, and at roughly 10,000 + * tuples that factor will start to dominate over the linear costs of + * string transformation (this is a conservative estimate). The decay + * rate is chosen to be a little less aggressive than halving -- which + * (since we're called at points at which memtupcount has doubled) + * would never see the cost model actually abort past the first call + * following a decay. This decay rate is mostly a precaution against + * a sudden, violent swing in how well abbreviated cardinality tracks + * full key cardinality. The decay also serves to prevent a marginal + * case from being aborted too late, when too much has already been + * invested in string transformation. + * + * It's possible for sets of several million distinct strings with + * mere tens of thousands of distinct abbreviated keys to still + * benefit very significantly. This will generally occur provided + * each abbreviated key is a proxy for a roughly uniform number of the + * set's full keys. If it isn't so, we hope to catch that early and + * abort. If it isn't caught early, by the time the problem is + * apparent it's probably not worth aborting. + */ + if (memtupcount > 10000) + sss->prop_card *= 0.65; + + return false; + } + + /* + * Abort abbreviation strategy. + * + * The worst case, where all abbreviated keys are identical while all + * original strings differ will typically only see a regression of about + * 10% in execution time for small to medium sized lists of strings. + * Whereas on modern CPUs where cache stalls are the dominant cost, we can + * often expect very large improvements, particularly with sets of strings + * of moderately high to high abbreviated cardinality. There is little to + * lose but much to gain, which our strategy reflects. + */ +#ifdef TRACE_SORT + if (trace_sort) + elog(LOG, "varstr_abbrev: aborted abbreviation at %d " + "(abbrev_distinct: %f, key_distinct: %f, prop_card: %f)", + memtupcount, abbrev_distinct, key_distinct, sss->prop_card); +#endif + + return true; +} + +/* + * Generic equalimage support function for character type's operator classes. + * Disables the use of deduplication with nondeterministic collations. + */ +Datum +btvarstrequalimage(PG_FUNCTION_ARGS) +{ + /* Oid opcintype = PG_GETARG_OID(0); */ + Oid collid = PG_GET_COLLATION(); + + check_collation_set(collid); + + if (lc_collate_is_c(collid) || + collid == DEFAULT_COLLATION_OID || + get_collation_isdeterministic(collid)) + PG_RETURN_BOOL(true); + else + PG_RETURN_BOOL(false); +} + +Datum +text_larger(PG_FUNCTION_ARGS) +{ + text *arg1 = PG_GETARG_TEXT_PP(0); + text *arg2 = PG_GETARG_TEXT_PP(1); + text *result; + + result = ((text_cmp(arg1, arg2, PG_GET_COLLATION()) > 0) ? arg1 : arg2); + + PG_RETURN_TEXT_P(result); +} + +Datum +text_smaller(PG_FUNCTION_ARGS) +{ + text *arg1 = PG_GETARG_TEXT_PP(0); + text *arg2 = PG_GETARG_TEXT_PP(1); + text *result; + + result = ((text_cmp(arg1, arg2, PG_GET_COLLATION()) < 0) ? arg1 : arg2); + + PG_RETURN_TEXT_P(result); +} + + +/* + * Cross-type comparison functions for types text and name. + */ + +Datum +nameeqtext(PG_FUNCTION_ARGS) +{ + Name arg1 = PG_GETARG_NAME(0); + text *arg2 = PG_GETARG_TEXT_PP(1); + size_t len1 = strlen(NameStr(*arg1)); + size_t len2 = VARSIZE_ANY_EXHDR(arg2); + Oid collid = PG_GET_COLLATION(); + bool result; + + check_collation_set(collid); + + if (collid == C_COLLATION_OID) + result = (len1 == len2 && + memcmp(NameStr(*arg1), VARDATA_ANY(arg2), len1) == 0); + else + result = (varstr_cmp(NameStr(*arg1), len1, + VARDATA_ANY(arg2), len2, + collid) == 0); + + PG_FREE_IF_COPY(arg2, 1); + + PG_RETURN_BOOL(result); +} + +Datum +texteqname(PG_FUNCTION_ARGS) +{ + text *arg1 = PG_GETARG_TEXT_PP(0); + Name arg2 = PG_GETARG_NAME(1); + size_t len1 = VARSIZE_ANY_EXHDR(arg1); + size_t len2 = strlen(NameStr(*arg2)); + Oid collid = PG_GET_COLLATION(); + bool result; + + check_collation_set(collid); + + if (collid == C_COLLATION_OID) + result = (len1 == len2 && + memcmp(VARDATA_ANY(arg1), NameStr(*arg2), len1) == 0); + else + result = (varstr_cmp(VARDATA_ANY(arg1), len1, + NameStr(*arg2), len2, + collid) == 0); + + PG_FREE_IF_COPY(arg1, 0); + + PG_RETURN_BOOL(result); +} + +Datum +namenetext(PG_FUNCTION_ARGS) +{ + Name arg1 = PG_GETARG_NAME(0); + text *arg2 = PG_GETARG_TEXT_PP(1); + size_t len1 = strlen(NameStr(*arg1)); + size_t len2 = VARSIZE_ANY_EXHDR(arg2); + Oid collid = PG_GET_COLLATION(); + bool result; + + check_collation_set(collid); + + if (collid == C_COLLATION_OID) + result = !(len1 == len2 && + memcmp(NameStr(*arg1), VARDATA_ANY(arg2), len1) == 0); + else + result = !(varstr_cmp(NameStr(*arg1), len1, + VARDATA_ANY(arg2), len2, + collid) == 0); + + PG_FREE_IF_COPY(arg2, 1); + + PG_RETURN_BOOL(result); +} + +Datum +textnename(PG_FUNCTION_ARGS) +{ + text *arg1 = PG_GETARG_TEXT_PP(0); + Name arg2 = PG_GETARG_NAME(1); + size_t len1 = VARSIZE_ANY_EXHDR(arg1); + size_t len2 = strlen(NameStr(*arg2)); + Oid collid = PG_GET_COLLATION(); + bool result; + + check_collation_set(collid); + + if (collid == C_COLLATION_OID) + result = !(len1 == len2 && + memcmp(VARDATA_ANY(arg1), NameStr(*arg2), len1) == 0); + else + result = !(varstr_cmp(VARDATA_ANY(arg1), len1, + NameStr(*arg2), len2, + collid) == 0); + + PG_FREE_IF_COPY(arg1, 0); + + PG_RETURN_BOOL(result); +} + +Datum +btnametextcmp(PG_FUNCTION_ARGS) +{ + Name arg1 = PG_GETARG_NAME(0); + text *arg2 = PG_GETARG_TEXT_PP(1); + int32 result; + + result = varstr_cmp(NameStr(*arg1), strlen(NameStr(*arg1)), + VARDATA_ANY(arg2), VARSIZE_ANY_EXHDR(arg2), + PG_GET_COLLATION()); + + PG_FREE_IF_COPY(arg2, 1); + + PG_RETURN_INT32(result); +} + +Datum +bttextnamecmp(PG_FUNCTION_ARGS) +{ + text *arg1 = PG_GETARG_TEXT_PP(0); + Name arg2 = PG_GETARG_NAME(1); + int32 result; + + result = varstr_cmp(VARDATA_ANY(arg1), VARSIZE_ANY_EXHDR(arg1), + NameStr(*arg2), strlen(NameStr(*arg2)), + PG_GET_COLLATION()); + + PG_FREE_IF_COPY(arg1, 0); + + PG_RETURN_INT32(result); +} + +#define CmpCall(cmpfunc) \ + DatumGetInt32(DirectFunctionCall2Coll(cmpfunc, \ + PG_GET_COLLATION(), \ + PG_GETARG_DATUM(0), \ + PG_GETARG_DATUM(1))) + +Datum +namelttext(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(CmpCall(btnametextcmp) < 0); +} + +Datum +nameletext(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(CmpCall(btnametextcmp) <= 0); +} + +Datum +namegttext(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(CmpCall(btnametextcmp) > 0); +} + +Datum +namegetext(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(CmpCall(btnametextcmp) >= 0); +} + +Datum +textltname(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(CmpCall(bttextnamecmp) < 0); +} + +Datum +textlename(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(CmpCall(bttextnamecmp) <= 0); +} + +Datum +textgtname(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(CmpCall(bttextnamecmp) > 0); +} + +Datum +textgename(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(CmpCall(bttextnamecmp) >= 0); +} + +#undef CmpCall + + +/* + * The following operators support character-by-character comparison + * of text datums, to allow building indexes suitable for LIKE clauses. + * Note that the regular texteq/textne comparison operators, and regular + * support functions 1 and 2 with "C" collation are assumed to be + * compatible with these! + */ + +static int +internal_text_pattern_compare(text *arg1, text *arg2) +{ + int result; + int len1, + len2; + + len1 = VARSIZE_ANY_EXHDR(arg1); + len2 = VARSIZE_ANY_EXHDR(arg2); + + result = memcmp(VARDATA_ANY(arg1), VARDATA_ANY(arg2), Min(len1, len2)); + if (result != 0) + return result; + else if (len1 < len2) + return -1; + else if (len1 > len2) + return 1; + else + return 0; +} + + +Datum +text_pattern_lt(PG_FUNCTION_ARGS) +{ + text *arg1 = PG_GETARG_TEXT_PP(0); + text *arg2 = PG_GETARG_TEXT_PP(1); + int result; + + result = internal_text_pattern_compare(arg1, arg2); + + PG_FREE_IF_COPY(arg1, 0); + PG_FREE_IF_COPY(arg2, 1); + + PG_RETURN_BOOL(result < 0); +} + + +Datum +text_pattern_le(PG_FUNCTION_ARGS) +{ + text *arg1 = PG_GETARG_TEXT_PP(0); + text *arg2 = PG_GETARG_TEXT_PP(1); + int result; + + result = internal_text_pattern_compare(arg1, arg2); + + PG_FREE_IF_COPY(arg1, 0); + PG_FREE_IF_COPY(arg2, 1); + + PG_RETURN_BOOL(result <= 0); +} + + +Datum +text_pattern_ge(PG_FUNCTION_ARGS) +{ + text *arg1 = PG_GETARG_TEXT_PP(0); + text *arg2 = PG_GETARG_TEXT_PP(1); + int result; + + result = internal_text_pattern_compare(arg1, arg2); + + PG_FREE_IF_COPY(arg1, 0); + PG_FREE_IF_COPY(arg2, 1); + + PG_RETURN_BOOL(result >= 0); +} + + +Datum +text_pattern_gt(PG_FUNCTION_ARGS) +{ + text *arg1 = PG_GETARG_TEXT_PP(0); + text *arg2 = PG_GETARG_TEXT_PP(1); + int result; + + result = internal_text_pattern_compare(arg1, arg2); + + PG_FREE_IF_COPY(arg1, 0); + PG_FREE_IF_COPY(arg2, 1); + + PG_RETURN_BOOL(result > 0); +} + + +Datum +bttext_pattern_cmp(PG_FUNCTION_ARGS) +{ + text *arg1 = PG_GETARG_TEXT_PP(0); + text *arg2 = PG_GETARG_TEXT_PP(1); + int result; + + result = internal_text_pattern_compare(arg1, arg2); + + PG_FREE_IF_COPY(arg1, 0); + PG_FREE_IF_COPY(arg2, 1); + + PG_RETURN_INT32(result); +} + + +Datum +bttext_pattern_sortsupport(PG_FUNCTION_ARGS) +{ + SortSupport ssup = (SortSupport) PG_GETARG_POINTER(0); + MemoryContext oldcontext; + + oldcontext = MemoryContextSwitchTo(ssup->ssup_cxt); + + /* Use generic string SortSupport, forcing "C" collation */ + varstr_sortsupport(ssup, TEXTOID, C_COLLATION_OID); + + MemoryContextSwitchTo(oldcontext); + + PG_RETURN_VOID(); +} + + +/*------------------------------------------------------------- + * byteaoctetlen + * + * get the number of bytes contained in an instance of type 'bytea' + *------------------------------------------------------------- + */ +Datum +byteaoctetlen(PG_FUNCTION_ARGS) +{ + Datum str = PG_GETARG_DATUM(0); + + /* We need not detoast the input at all */ + PG_RETURN_INT32(toast_raw_datum_size(str) - VARHDRSZ); +} + +/* + * byteacat - + * takes two bytea* and returns a bytea* that is the concatenation of + * the two. + * + * Cloned from textcat and modified as required. + */ +Datum +byteacat(PG_FUNCTION_ARGS) +{ + bytea *t1 = PG_GETARG_BYTEA_PP(0); + bytea *t2 = PG_GETARG_BYTEA_PP(1); + + PG_RETURN_BYTEA_P(bytea_catenate(t1, t2)); +} + +/* + * bytea_catenate + * Guts of byteacat(), broken out so it can be used by other functions + * + * Arguments can be in short-header form, but not compressed or out-of-line + */ +static bytea * +bytea_catenate(bytea *t1, bytea *t2) +{ + bytea *result; + int len1, + len2, + len; + char *ptr; + + len1 = VARSIZE_ANY_EXHDR(t1); + len2 = VARSIZE_ANY_EXHDR(t2); + + /* paranoia ... probably should throw error instead? */ + if (len1 < 0) + len1 = 0; + if (len2 < 0) + len2 = 0; + + len = len1 + len2 + VARHDRSZ; + result = (bytea *) palloc(len); + + /* Set size of result string... */ + SET_VARSIZE(result, len); + + /* Fill data field of result string... */ + ptr = VARDATA(result); + if (len1 > 0) + memcpy(ptr, VARDATA_ANY(t1), len1); + if (len2 > 0) + memcpy(ptr + len1, VARDATA_ANY(t2), len2); + + return result; +} + +#define PG_STR_GET_BYTEA(str_) \ + DatumGetByteaPP(DirectFunctionCall1(byteain, CStringGetDatum(str_))) + +/* + * bytea_substr() + * Return a substring starting at the specified position. + * Cloned from text_substr and modified as required. + * + * Input: + * - string + * - starting position (is one-based) + * - string length (optional) + * + * If the starting position is zero or less, then return from the start of the string + * adjusting the length to be consistent with the "negative start" per SQL. + * If the length is less than zero, an ERROR is thrown. If no third argument + * (length) is provided, the length to the end of the string is assumed. + */ +Datum +bytea_substr(PG_FUNCTION_ARGS) +{ + PG_RETURN_BYTEA_P(bytea_substring(PG_GETARG_DATUM(0), + PG_GETARG_INT32(1), + PG_GETARG_INT32(2), + false)); +} + +/* + * bytea_substr_no_len - + * Wrapper to avoid opr_sanity failure due to + * one function accepting a different number of args. + */ +Datum +bytea_substr_no_len(PG_FUNCTION_ARGS) +{ + PG_RETURN_BYTEA_P(bytea_substring(PG_GETARG_DATUM(0), + PG_GETARG_INT32(1), + -1, + true)); +} + +static bytea * +bytea_substring(Datum str, + int S, + int L, + bool length_not_specified) +{ + int32 S1; /* adjusted start position */ + int32 L1; /* adjusted substring length */ + int32 E; /* end position */ + + /* + * The logic here should generally match text_substring(). + */ + S1 = Max(S, 1); + + if (length_not_specified) + { + /* + * Not passed a length - DatumGetByteaPSlice() grabs everything to the + * end of the string if we pass it a negative value for length. + */ + L1 = -1; + } + else if (L < 0) + { + /* SQL99 says to throw an error for E < S, i.e., negative length */ + ereport(ERROR, + (errcode(ERRCODE_SUBSTRING_ERROR), + errmsg("negative substring length not allowed"))); + L1 = -1; /* silence stupider compilers */ + } + else if (pg_add_s32_overflow(S, L, &E)) + { + /* + * L could be large enough for S + L to overflow, in which case the + * substring must run to end of string. + */ + L1 = -1; + } + else + { + /* + * A zero or negative value for the end position can happen if the + * start was negative or one. SQL99 says to return a zero-length + * string. + */ + if (E < 1) + return PG_STR_GET_BYTEA(""); + + L1 = E - S1; + } + + /* + * If the start position is past the end of the string, SQL99 says to + * return a zero-length string -- DatumGetByteaPSlice() will do that for + * us. We need only convert S1 to zero-based starting position. + */ + return DatumGetByteaPSlice(str, S1 - 1, L1); +} + +/* + * byteaoverlay + * Replace specified substring of first string with second + * + * The SQL standard defines OVERLAY() in terms of substring and concatenation. + * This code is a direct implementation of what the standard says. + */ +Datum +byteaoverlay(PG_FUNCTION_ARGS) +{ + bytea *t1 = PG_GETARG_BYTEA_PP(0); + bytea *t2 = PG_GETARG_BYTEA_PP(1); + int sp = PG_GETARG_INT32(2); /* substring start position */ + int sl = PG_GETARG_INT32(3); /* substring length */ + + PG_RETURN_BYTEA_P(bytea_overlay(t1, t2, sp, sl)); +} + +Datum +byteaoverlay_no_len(PG_FUNCTION_ARGS) +{ + bytea *t1 = PG_GETARG_BYTEA_PP(0); + bytea *t2 = PG_GETARG_BYTEA_PP(1); + int sp = PG_GETARG_INT32(2); /* substring start position */ + int sl; + + sl = VARSIZE_ANY_EXHDR(t2); /* defaults to length(t2) */ + PG_RETURN_BYTEA_P(bytea_overlay(t1, t2, sp, sl)); +} + +static bytea * +bytea_overlay(bytea *t1, bytea *t2, int sp, int sl) +{ + bytea *result; + bytea *s1; + bytea *s2; + int sp_pl_sl; + + /* + * Check for possible integer-overflow cases. For negative sp, throw a + * "substring length" error because that's what should be expected + * according to the spec's definition of OVERLAY(). + */ + if (sp <= 0) + ereport(ERROR, + (errcode(ERRCODE_SUBSTRING_ERROR), + errmsg("negative substring length not allowed"))); + if (pg_add_s32_overflow(sp, sl, &sp_pl_sl)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("integer out of range"))); + + s1 = bytea_substring(PointerGetDatum(t1), 1, sp - 1, false); + s2 = bytea_substring(PointerGetDatum(t1), sp_pl_sl, -1, true); + result = bytea_catenate(s1, t2); + result = bytea_catenate(result, s2); + + return result; +} + +/* + * bit_count + */ +Datum +bytea_bit_count(PG_FUNCTION_ARGS) +{ + bytea *t1 = PG_GETARG_BYTEA_PP(0); + + PG_RETURN_INT64(pg_popcount(VARDATA_ANY(t1), VARSIZE_ANY_EXHDR(t1))); +} + +/* + * byteapos - + * Return the position of the specified substring. + * Implements the SQL POSITION() function. + * Cloned from textpos and modified as required. + */ +Datum +byteapos(PG_FUNCTION_ARGS) +{ + bytea *t1 = PG_GETARG_BYTEA_PP(0); + bytea *t2 = PG_GETARG_BYTEA_PP(1); + int pos; + int px, + p; + int len1, + len2; + char *p1, + *p2; + + len1 = VARSIZE_ANY_EXHDR(t1); + len2 = VARSIZE_ANY_EXHDR(t2); + + if (len2 <= 0) + PG_RETURN_INT32(1); /* result for empty pattern */ + + p1 = VARDATA_ANY(t1); + p2 = VARDATA_ANY(t2); + + pos = 0; + px = (len1 - len2); + for (p = 0; p <= px; p++) + { + if ((*p2 == *p1) && (memcmp(p1, p2, len2) == 0)) + { + pos = p + 1; + break; + }; + p1++; + }; + + PG_RETURN_INT32(pos); +} + +/*------------------------------------------------------------- + * byteaGetByte + * + * this routine treats "bytea" as an array of bytes. + * It returns the Nth byte (a number between 0 and 255). + *------------------------------------------------------------- + */ +Datum +byteaGetByte(PG_FUNCTION_ARGS) +{ + bytea *v = PG_GETARG_BYTEA_PP(0); + int32 n = PG_GETARG_INT32(1); + int len; + int byte; + + len = VARSIZE_ANY_EXHDR(v); + + if (n < 0 || n >= len) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("index %d out of valid range, 0..%d", + n, len - 1))); + + byte = ((unsigned char *) VARDATA_ANY(v))[n]; + + PG_RETURN_INT32(byte); +} + +/*------------------------------------------------------------- + * byteaGetBit + * + * This routine treats a "bytea" type like an array of bits. + * It returns the value of the Nth bit (0 or 1). + * + *------------------------------------------------------------- + */ +Datum +byteaGetBit(PG_FUNCTION_ARGS) +{ + bytea *v = PG_GETARG_BYTEA_PP(0); + int64 n = PG_GETARG_INT64(1); + int byteNo, + bitNo; + int len; + int byte; + + len = VARSIZE_ANY_EXHDR(v); + + if (n < 0 || n >= (int64) len * 8) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("index %lld out of valid range, 0..%lld", + (long long) n, (long long) len * 8 - 1))); + + /* n/8 is now known < len, so safe to cast to int */ + byteNo = (int) (n / 8); + bitNo = (int) (n % 8); + + byte = ((unsigned char *) VARDATA_ANY(v))[byteNo]; + + if (byte & (1 << bitNo)) + PG_RETURN_INT32(1); + else + PG_RETURN_INT32(0); +} + +/*------------------------------------------------------------- + * byteaSetByte + * + * Given an instance of type 'bytea' creates a new one with + * the Nth byte set to the given value. + * + *------------------------------------------------------------- + */ +Datum +byteaSetByte(PG_FUNCTION_ARGS) +{ + bytea *res = PG_GETARG_BYTEA_P_COPY(0); + int32 n = PG_GETARG_INT32(1); + int32 newByte = PG_GETARG_INT32(2); + int len; + + len = VARSIZE(res) - VARHDRSZ; + + if (n < 0 || n >= len) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("index %d out of valid range, 0..%d", + n, len - 1))); + + /* + * Now set the byte. + */ + ((unsigned char *) VARDATA(res))[n] = newByte; + + PG_RETURN_BYTEA_P(res); +} + +/*------------------------------------------------------------- + * byteaSetBit + * + * Given an instance of type 'bytea' creates a new one with + * the Nth bit set to the given value. + * + *------------------------------------------------------------- + */ +Datum +byteaSetBit(PG_FUNCTION_ARGS) +{ + bytea *res = PG_GETARG_BYTEA_P_COPY(0); + int64 n = PG_GETARG_INT64(1); + int32 newBit = PG_GETARG_INT32(2); + int len; + int oldByte, + newByte; + int byteNo, + bitNo; + + len = VARSIZE(res) - VARHDRSZ; + + if (n < 0 || n >= (int64) len * 8) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("index %lld out of valid range, 0..%lld", + (long long) n, (long long) len * 8 - 1))); + + /* n/8 is now known < len, so safe to cast to int */ + byteNo = (int) (n / 8); + bitNo = (int) (n % 8); + + /* + * sanity check! + */ + if (newBit != 0 && newBit != 1) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("new bit must be 0 or 1"))); + + /* + * Update the byte. + */ + oldByte = ((unsigned char *) VARDATA(res))[byteNo]; + + if (newBit == 0) + newByte = oldByte & (~(1 << bitNo)); + else + newByte = oldByte | (1 << bitNo); + + ((unsigned char *) VARDATA(res))[byteNo] = newByte; + + PG_RETURN_BYTEA_P(res); +} + + +/* text_name() + * Converts a text type to a Name type. + */ +Datum +text_name(PG_FUNCTION_ARGS) +{ + text *s = PG_GETARG_TEXT_PP(0); + Name result; + int len; + + len = VARSIZE_ANY_EXHDR(s); + + /* Truncate oversize input */ + if (len >= NAMEDATALEN) + len = pg_mbcliplen(VARDATA_ANY(s), len, NAMEDATALEN - 1); + + /* We use palloc0 here to ensure result is zero-padded */ + result = (Name) palloc0(NAMEDATALEN); + memcpy(NameStr(*result), VARDATA_ANY(s), len); + + PG_RETURN_NAME(result); +} + +/* name_text() + * Converts a Name type to a text type. + */ +Datum +name_text(PG_FUNCTION_ARGS) +{ + Name s = PG_GETARG_NAME(0); + + PG_RETURN_TEXT_P(cstring_to_text(NameStr(*s))); +} + + +/* + * textToQualifiedNameList - convert a text object to list of names + * + * This implements the input parsing needed by nextval() and other + * functions that take a text parameter representing a qualified name. + * We split the name at dots, downcase if not double-quoted, and + * truncate names if they're too long. + */ +List * +textToQualifiedNameList(text *textval) +{ + char *rawname; + List *result = NIL; + List *namelist; + ListCell *l; + + /* Convert to C string (handles possible detoasting). */ + /* Note we rely on being able to modify rawname below. */ + rawname = text_to_cstring(textval); + + if (!SplitIdentifierString(rawname, '.', &namelist)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_NAME), + errmsg("invalid name syntax"))); + + if (namelist == NIL) + ereport(ERROR, + (errcode(ERRCODE_INVALID_NAME), + errmsg("invalid name syntax"))); + + foreach(l, namelist) + { + char *curname = (char *) lfirst(l); + + result = lappend(result, makeString(pstrdup(curname))); + } + + pfree(rawname); + list_free(namelist); + + return result; +} + +/* + * SplitIdentifierString --- parse a string containing identifiers + * + * This is the guts of textToQualifiedNameList, and is exported for use in + * other situations such as parsing GUC variables. In the GUC case, it's + * important to avoid memory leaks, so the API is designed to minimize the + * amount of stuff that needs to be allocated and freed. + * + * Inputs: + * rawstring: the input string; must be overwritable! On return, it's + * been modified to contain the separated identifiers. + * separator: the separator punctuation expected between identifiers + * (typically '.' or ','). Whitespace may also appear around + * identifiers. + * Outputs: + * namelist: filled with a palloc'd list of pointers to identifiers within + * rawstring. Caller should list_free() this even on error return. + * + * Returns true if okay, false if there is a syntax error in the string. + * + * Note that an empty string is considered okay here, though not in + * textToQualifiedNameList. + */ +bool +SplitIdentifierString(char *rawstring, char separator, + List **namelist) +{ + char *nextp = rawstring; + bool done = false; + + *namelist = NIL; + + while (scanner_isspace(*nextp)) + nextp++; /* skip leading whitespace */ + + if (*nextp == '\0') + return true; /* allow empty string */ + + /* At the top of the loop, we are at start of a new identifier. */ + do + { + char *curname; + char *endp; + + if (*nextp == '"') + { + /* Quoted name --- collapse quote-quote pairs, no downcasing */ + curname = nextp + 1; + for (;;) + { + endp = strchr(nextp + 1, '"'); + if (endp == NULL) + return false; /* mismatched quotes */ + if (endp[1] != '"') + break; /* found end of quoted name */ + /* Collapse adjacent quotes into one quote, and look again */ + memmove(endp, endp + 1, strlen(endp)); + nextp = endp; + } + /* endp now points at the terminating quote */ + nextp = endp + 1; + } + else + { + /* Unquoted name --- extends to separator or whitespace */ + char *downname; + int len; + + curname = nextp; + while (*nextp && *nextp != separator && + !scanner_isspace(*nextp)) + nextp++; + endp = nextp; + if (curname == nextp) + return false; /* empty unquoted name not allowed */ + + /* + * Downcase the identifier, using same code as main lexer does. + * + * XXX because we want to overwrite the input in-place, we cannot + * support a downcasing transformation that increases the string + * length. This is not a problem given the current implementation + * of downcase_truncate_identifier, but we'll probably have to do + * something about this someday. + */ + len = endp - curname; + downname = downcase_truncate_identifier(curname, len, false); + Assert(strlen(downname) <= len); + strncpy(curname, downname, len); /* strncpy is required here */ + pfree(downname); + } + + while (scanner_isspace(*nextp)) + nextp++; /* skip trailing whitespace */ + + if (*nextp == separator) + { + nextp++; + while (scanner_isspace(*nextp)) + nextp++; /* skip leading whitespace for next */ + /* we expect another name, so done remains false */ + } + else if (*nextp == '\0') + done = true; + else + return false; /* invalid syntax */ + + /* Now safe to overwrite separator with a null */ + *endp = '\0'; + + /* Truncate name if it's overlength */ + truncate_identifier(curname, strlen(curname), false); + + /* + * Finished isolating current name --- add it to list + */ + *namelist = lappend(*namelist, curname); + + /* Loop back if we didn't reach end of string */ + } while (!done); + + return true; +} + + +/* + * SplitDirectoriesString --- parse a string containing file/directory names + * + * This works fine on file names too; the function name is historical. + * + * This is similar to SplitIdentifierString, except that the parsing + * rules are meant to handle pathnames instead of identifiers: there is + * no downcasing, embedded spaces are allowed, the max length is MAXPGPATH-1, + * and we apply canonicalize_path() to each extracted string. Because of the + * last, the returned strings are separately palloc'd rather than being + * pointers into rawstring --- but we still scribble on rawstring. + * + * Inputs: + * rawstring: the input string; must be modifiable! + * separator: the separator punctuation expected between directories + * (typically ',' or ';'). Whitespace may also appear around + * directories. + * Outputs: + * namelist: filled with a palloc'd list of directory names. + * Caller should list_free_deep() this even on error return. + * + * Returns true if okay, false if there is a syntax error in the string. + * + * Note that an empty string is considered okay here. + */ +bool +SplitDirectoriesString(char *rawstring, char separator, + List **namelist) +{ + char *nextp = rawstring; + bool done = false; + + *namelist = NIL; + + while (scanner_isspace(*nextp)) + nextp++; /* skip leading whitespace */ + + if (*nextp == '\0') + return true; /* allow empty string */ + + /* At the top of the loop, we are at start of a new directory. */ + do + { + char *curname; + char *endp; + + if (*nextp == '"') + { + /* Quoted name --- collapse quote-quote pairs */ + curname = nextp + 1; + for (;;) + { + endp = strchr(nextp + 1, '"'); + if (endp == NULL) + return false; /* mismatched quotes */ + if (endp[1] != '"') + break; /* found end of quoted name */ + /* Collapse adjacent quotes into one quote, and look again */ + memmove(endp, endp + 1, strlen(endp)); + nextp = endp; + } + /* endp now points at the terminating quote */ + nextp = endp + 1; + } + else + { + /* Unquoted name --- extends to separator or end of string */ + curname = endp = nextp; + while (*nextp && *nextp != separator) + { + /* trailing whitespace should not be included in name */ + if (!scanner_isspace(*nextp)) + endp = nextp + 1; + nextp++; + } + if (curname == endp) + return false; /* empty unquoted name not allowed */ + } + + while (scanner_isspace(*nextp)) + nextp++; /* skip trailing whitespace */ + + if (*nextp == separator) + { + nextp++; + while (scanner_isspace(*nextp)) + nextp++; /* skip leading whitespace for next */ + /* we expect another name, so done remains false */ + } + else if (*nextp == '\0') + done = true; + else + return false; /* invalid syntax */ + + /* Now safe to overwrite separator with a null */ + *endp = '\0'; + + /* Truncate path if it's overlength */ + if (strlen(curname) >= MAXPGPATH) + curname[MAXPGPATH - 1] = '\0'; + + /* + * Finished isolating current name --- add it to list + */ + curname = pstrdup(curname); + canonicalize_path(curname); + *namelist = lappend(*namelist, curname); + + /* Loop back if we didn't reach end of string */ + } while (!done); + + return true; +} + + +/* + * SplitGUCList --- parse a string containing identifiers or file names + * + * This is used to split the value of a GUC_LIST_QUOTE GUC variable, without + * presuming whether the elements will be taken as identifiers or file names. + * We assume the input has already been through flatten_set_variable_args(), + * so that we need never downcase (if appropriate, that was done already). + * Nor do we ever truncate, since we don't know the correct max length. + * We disallow embedded whitespace for simplicity (it shouldn't matter, + * because any embedded whitespace should have led to double-quoting). + * Otherwise the API is identical to SplitIdentifierString. + * + * XXX it's annoying to have so many copies of this string-splitting logic. + * However, it's not clear that having one function with a bunch of option + * flags would be much better. + * + * XXX there is a version of this function in src/bin/pg_dump/dumputils.c. + * Be sure to update that if you have to change this. + * + * Inputs: + * rawstring: the input string; must be overwritable! On return, it's + * been modified to contain the separated identifiers. + * separator: the separator punctuation expected between identifiers + * (typically '.' or ','). Whitespace may also appear around + * identifiers. + * Outputs: + * namelist: filled with a palloc'd list of pointers to identifiers within + * rawstring. Caller should list_free() this even on error return. + * + * Returns true if okay, false if there is a syntax error in the string. + */ +bool +SplitGUCList(char *rawstring, char separator, + List **namelist) +{ + char *nextp = rawstring; + bool done = false; + + *namelist = NIL; + + while (scanner_isspace(*nextp)) + nextp++; /* skip leading whitespace */ + + if (*nextp == '\0') + return true; /* allow empty string */ + + /* At the top of the loop, we are at start of a new identifier. */ + do + { + char *curname; + char *endp; + + if (*nextp == '"') + { + /* Quoted name --- collapse quote-quote pairs */ + curname = nextp + 1; + for (;;) + { + endp = strchr(nextp + 1, '"'); + if (endp == NULL) + return false; /* mismatched quotes */ + if (endp[1] != '"') + break; /* found end of quoted name */ + /* Collapse adjacent quotes into one quote, and look again */ + memmove(endp, endp + 1, strlen(endp)); + nextp = endp; + } + /* endp now points at the terminating quote */ + nextp = endp + 1; + } + else + { + /* Unquoted name --- extends to separator or whitespace */ + curname = nextp; + while (*nextp && *nextp != separator && + !scanner_isspace(*nextp)) + nextp++; + endp = nextp; + if (curname == nextp) + return false; /* empty unquoted name not allowed */ + } + + while (scanner_isspace(*nextp)) + nextp++; /* skip trailing whitespace */ + + if (*nextp == separator) + { + nextp++; + while (scanner_isspace(*nextp)) + nextp++; /* skip leading whitespace for next */ + /* we expect another name, so done remains false */ + } + else if (*nextp == '\0') + done = true; + else + return false; /* invalid syntax */ + + /* Now safe to overwrite separator with a null */ + *endp = '\0'; + + /* + * Finished isolating current name --- add it to list + */ + *namelist = lappend(*namelist, curname); + + /* Loop back if we didn't reach end of string */ + } while (!done); + + return true; +} + + +/***************************************************************************** + * Comparison Functions used for bytea + * + * Note: btree indexes need these routines not to leak memory; therefore, + * be careful to free working copies of toasted datums. Most places don't + * need to be so careful. + *****************************************************************************/ + +Datum +byteaeq(PG_FUNCTION_ARGS) +{ + Datum arg1 = PG_GETARG_DATUM(0); + Datum arg2 = PG_GETARG_DATUM(1); + bool result; + Size len1, + len2; + + /* + * We can use a fast path for unequal lengths, which might save us from + * having to detoast one or both values. + */ + len1 = toast_raw_datum_size(arg1); + len2 = toast_raw_datum_size(arg2); + if (len1 != len2) + result = false; + else + { + bytea *barg1 = DatumGetByteaPP(arg1); + bytea *barg2 = DatumGetByteaPP(arg2); + + result = (memcmp(VARDATA_ANY(barg1), VARDATA_ANY(barg2), + len1 - VARHDRSZ) == 0); + + PG_FREE_IF_COPY(barg1, 0); + PG_FREE_IF_COPY(barg2, 1); + } + + PG_RETURN_BOOL(result); +} + +Datum +byteane(PG_FUNCTION_ARGS) +{ + Datum arg1 = PG_GETARG_DATUM(0); + Datum arg2 = PG_GETARG_DATUM(1); + bool result; + Size len1, + len2; + + /* + * We can use a fast path for unequal lengths, which might save us from + * having to detoast one or both values. + */ + len1 = toast_raw_datum_size(arg1); + len2 = toast_raw_datum_size(arg2); + if (len1 != len2) + result = true; + else + { + bytea *barg1 = DatumGetByteaPP(arg1); + bytea *barg2 = DatumGetByteaPP(arg2); + + result = (memcmp(VARDATA_ANY(barg1), VARDATA_ANY(barg2), + len1 - VARHDRSZ) != 0); + + PG_FREE_IF_COPY(barg1, 0); + PG_FREE_IF_COPY(barg2, 1); + } + + PG_RETURN_BOOL(result); +} + +Datum +bytealt(PG_FUNCTION_ARGS) +{ + bytea *arg1 = PG_GETARG_BYTEA_PP(0); + bytea *arg2 = PG_GETARG_BYTEA_PP(1); + int len1, + len2; + int cmp; + + len1 = VARSIZE_ANY_EXHDR(arg1); + len2 = VARSIZE_ANY_EXHDR(arg2); + + cmp = memcmp(VARDATA_ANY(arg1), VARDATA_ANY(arg2), Min(len1, len2)); + + PG_FREE_IF_COPY(arg1, 0); + PG_FREE_IF_COPY(arg2, 1); + + PG_RETURN_BOOL((cmp < 0) || ((cmp == 0) && (len1 < len2))); +} + +Datum +byteale(PG_FUNCTION_ARGS) +{ + bytea *arg1 = PG_GETARG_BYTEA_PP(0); + bytea *arg2 = PG_GETARG_BYTEA_PP(1); + int len1, + len2; + int cmp; + + len1 = VARSIZE_ANY_EXHDR(arg1); + len2 = VARSIZE_ANY_EXHDR(arg2); + + cmp = memcmp(VARDATA_ANY(arg1), VARDATA_ANY(arg2), Min(len1, len2)); + + PG_FREE_IF_COPY(arg1, 0); + PG_FREE_IF_COPY(arg2, 1); + + PG_RETURN_BOOL((cmp < 0) || ((cmp == 0) && (len1 <= len2))); +} + +Datum +byteagt(PG_FUNCTION_ARGS) +{ + bytea *arg1 = PG_GETARG_BYTEA_PP(0); + bytea *arg2 = PG_GETARG_BYTEA_PP(1); + int len1, + len2; + int cmp; + + len1 = VARSIZE_ANY_EXHDR(arg1); + len2 = VARSIZE_ANY_EXHDR(arg2); + + cmp = memcmp(VARDATA_ANY(arg1), VARDATA_ANY(arg2), Min(len1, len2)); + + PG_FREE_IF_COPY(arg1, 0); + PG_FREE_IF_COPY(arg2, 1); + + PG_RETURN_BOOL((cmp > 0) || ((cmp == 0) && (len1 > len2))); +} + +Datum +byteage(PG_FUNCTION_ARGS) +{ + bytea *arg1 = PG_GETARG_BYTEA_PP(0); + bytea *arg2 = PG_GETARG_BYTEA_PP(1); + int len1, + len2; + int cmp; + + len1 = VARSIZE_ANY_EXHDR(arg1); + len2 = VARSIZE_ANY_EXHDR(arg2); + + cmp = memcmp(VARDATA_ANY(arg1), VARDATA_ANY(arg2), Min(len1, len2)); + + PG_FREE_IF_COPY(arg1, 0); + PG_FREE_IF_COPY(arg2, 1); + + PG_RETURN_BOOL((cmp > 0) || ((cmp == 0) && (len1 >= len2))); +} + +Datum +byteacmp(PG_FUNCTION_ARGS) +{ + bytea *arg1 = PG_GETARG_BYTEA_PP(0); + bytea *arg2 = PG_GETARG_BYTEA_PP(1); + int len1, + len2; + int cmp; + + len1 = VARSIZE_ANY_EXHDR(arg1); + len2 = VARSIZE_ANY_EXHDR(arg2); + + cmp = memcmp(VARDATA_ANY(arg1), VARDATA_ANY(arg2), Min(len1, len2)); + if ((cmp == 0) && (len1 != len2)) + cmp = (len1 < len2) ? -1 : 1; + + PG_FREE_IF_COPY(arg1, 0); + PG_FREE_IF_COPY(arg2, 1); + + PG_RETURN_INT32(cmp); +} + +Datum +bytea_sortsupport(PG_FUNCTION_ARGS) +{ + SortSupport ssup = (SortSupport) PG_GETARG_POINTER(0); + MemoryContext oldcontext; + + oldcontext = MemoryContextSwitchTo(ssup->ssup_cxt); + + /* Use generic string SortSupport, forcing "C" collation */ + varstr_sortsupport(ssup, BYTEAOID, C_COLLATION_OID); + + MemoryContextSwitchTo(oldcontext); + + PG_RETURN_VOID(); +} + +/* + * appendStringInfoText + * + * Append a text to str. + * Like appendStringInfoString(str, text_to_cstring(t)) but faster. + */ +static void +appendStringInfoText(StringInfo str, const text *t) +{ + appendBinaryStringInfo(str, VARDATA_ANY(t), VARSIZE_ANY_EXHDR(t)); +} + +/* + * replace_text + * replace all occurrences of 'old_sub_str' in 'orig_str' + * with 'new_sub_str' to form 'new_str' + * + * returns 'orig_str' if 'old_sub_str' == '' or 'orig_str' == '' + * otherwise returns 'new_str' + */ +Datum +replace_text(PG_FUNCTION_ARGS) +{ + text *src_text = PG_GETARG_TEXT_PP(0); + text *from_sub_text = PG_GETARG_TEXT_PP(1); + text *to_sub_text = PG_GETARG_TEXT_PP(2); + int src_text_len; + int from_sub_text_len; + TextPositionState state; + text *ret_text; + int chunk_len; + char *curr_ptr; + char *start_ptr; + StringInfoData str; + bool found; + + src_text_len = VARSIZE_ANY_EXHDR(src_text); + from_sub_text_len = VARSIZE_ANY_EXHDR(from_sub_text); + + /* Return unmodified source string if empty source or pattern */ + if (src_text_len < 1 || from_sub_text_len < 1) + { + PG_RETURN_TEXT_P(src_text); + } + + text_position_setup(src_text, from_sub_text, PG_GET_COLLATION(), &state); + + found = text_position_next(&state); + + /* When the from_sub_text is not found, there is nothing to do. */ + if (!found) + { + text_position_cleanup(&state); + PG_RETURN_TEXT_P(src_text); + } + curr_ptr = text_position_get_match_ptr(&state); + start_ptr = VARDATA_ANY(src_text); + + initStringInfo(&str); + + do + { + CHECK_FOR_INTERRUPTS(); + + /* copy the data skipped over by last text_position_next() */ + chunk_len = curr_ptr - start_ptr; + appendBinaryStringInfo(&str, start_ptr, chunk_len); + + appendStringInfoText(&str, to_sub_text); + + start_ptr = curr_ptr + from_sub_text_len; + + found = text_position_next(&state); + if (found) + curr_ptr = text_position_get_match_ptr(&state); + } + while (found); + + /* copy trailing data */ + chunk_len = ((char *) src_text + VARSIZE_ANY(src_text)) - start_ptr; + appendBinaryStringInfo(&str, start_ptr, chunk_len); + + text_position_cleanup(&state); + + ret_text = cstring_to_text_with_len(str.data, str.len); + pfree(str.data); + + PG_RETURN_TEXT_P(ret_text); +} + +/* + * check_replace_text_has_escape + * + * Returns 0 if text contains no backslashes that need processing. + * Returns 1 if text contains backslashes, but not regexp submatch specifiers. + * Returns 2 if text contains regexp submatch specifiers (\1 .. \9). + */ +static int +check_replace_text_has_escape(const text *replace_text) +{ + int result = 0; + const char *p = VARDATA_ANY(replace_text); + const char *p_end = p + VARSIZE_ANY_EXHDR(replace_text); + + while (p < p_end) + { + /* Find next escape char, if any. */ + p = memchr(p, '\\', p_end - p); + if (p == NULL) + break; + p++; + /* Note: a backslash at the end doesn't require extra processing. */ + if (p < p_end) + { + if (*p >= '1' && *p <= '9') + return 2; /* Found a submatch specifier, so done */ + result = 1; /* Found some other sequence, keep looking */ + p++; + } + } + return result; +} + +/* + * appendStringInfoRegexpSubstr + * + * Append replace_text to str, substituting regexp back references for + * \n escapes. start_ptr is the start of the match in the source string, + * at logical character position data_pos. + */ +static void +appendStringInfoRegexpSubstr(StringInfo str, text *replace_text, + regmatch_t *pmatch, + char *start_ptr, int data_pos) +{ + const char *p = VARDATA_ANY(replace_text); + const char *p_end = p + VARSIZE_ANY_EXHDR(replace_text); + + while (p < p_end) + { + const char *chunk_start = p; + int so; + int eo; + + /* Find next escape char, if any. */ + p = memchr(p, '\\', p_end - p); + if (p == NULL) + p = p_end; + + /* Copy the text we just scanned over, if any. */ + if (p > chunk_start) + appendBinaryStringInfo(str, chunk_start, p - chunk_start); + + /* Done if at end of string, else advance over escape char. */ + if (p >= p_end) + break; + p++; + + if (p >= p_end) + { + /* Escape at very end of input. Treat same as unexpected char */ + appendStringInfoChar(str, '\\'); + break; + } + + if (*p >= '1' && *p <= '9') + { + /* Use the back reference of regexp. */ + int idx = *p - '0'; + + so = pmatch[idx].rm_so; + eo = pmatch[idx].rm_eo; + p++; + } + else if (*p == '&') + { + /* Use the entire matched string. */ + so = pmatch[0].rm_so; + eo = pmatch[0].rm_eo; + p++; + } + else if (*p == '\\') + { + /* \\ means transfer one \ to output. */ + appendStringInfoChar(str, '\\'); + p++; + continue; + } + else + { + /* + * If escape char is not followed by any expected char, just treat + * it as ordinary data to copy. (XXX would it be better to throw + * an error?) + */ + appendStringInfoChar(str, '\\'); + continue; + } + + if (so >= 0 && eo >= 0) + { + /* + * Copy the text that is back reference of regexp. Note so and eo + * are counted in characters not bytes. + */ + char *chunk_start; + int chunk_len; + + Assert(so >= data_pos); + chunk_start = start_ptr; + chunk_start += charlen_to_bytelen(chunk_start, so - data_pos); + chunk_len = charlen_to_bytelen(chunk_start, eo - so); + appendBinaryStringInfo(str, chunk_start, chunk_len); + } + } +} + +/* + * replace_text_regexp + * + * replace substring(s) in src_text that match pattern with replace_text. + * The replace_text can contain backslash markers to substitute + * (parts of) the matched text. + * + * cflags: regexp compile flags. + * collation: collation to use. + * search_start: the character (not byte) offset in src_text at which to + * begin searching. + * n: if 0, replace all matches; if > 0, replace only the N'th match. + */ +text * +replace_text_regexp(text *src_text, text *pattern_text, + text *replace_text, + int cflags, Oid collation, + int search_start, int n) +{ + text *ret_text; + regex_t *re; + int src_text_len = VARSIZE_ANY_EXHDR(src_text); + int nmatches = 0; + StringInfoData buf; + regmatch_t pmatch[10]; /* main match, plus \1 to \9 */ + int nmatch = lengthof(pmatch); + pg_wchar *data; + size_t data_len; + int data_pos; + char *start_ptr; + int escape_status; + + initStringInfo(&buf); + + /* Convert data string to wide characters. */ + data = (pg_wchar *) palloc((src_text_len + 1) * sizeof(pg_wchar)); + data_len = pg_mb2wchar_with_len(VARDATA_ANY(src_text), data, src_text_len); + + /* Check whether replace_text has escapes, especially regexp submatches. */ + escape_status = check_replace_text_has_escape(replace_text); + + /* If no regexp submatches, we can use REG_NOSUB. */ + if (escape_status < 2) + { + cflags |= REG_NOSUB; + /* Also tell pg_regexec we only want the whole-match location. */ + nmatch = 1; + } + + /* Prepare the regexp. */ + re = RE_compile_and_cache(pattern_text, cflags, collation); + + /* start_ptr points to the data_pos'th character of src_text */ + start_ptr = (char *) VARDATA_ANY(src_text); + data_pos = 0; + + while (search_start <= data_len) + { + int regexec_result; + + CHECK_FOR_INTERRUPTS(); + + regexec_result = pg_regexec(re, + data, + data_len, + search_start, + NULL, /* no details */ + nmatch, + pmatch, + 0); + + if (regexec_result == REG_NOMATCH) + break; + + if (regexec_result != REG_OKAY) + { + char errMsg[100]; + + pg_regerror(regexec_result, re, errMsg, sizeof(errMsg)); + ereport(ERROR, + (errcode(ERRCODE_INVALID_REGULAR_EXPRESSION), + errmsg("regular expression failed: %s", errMsg))); + } + + /* + * Count matches, and decide whether to replace this match. + */ + nmatches++; + if (n > 0 && nmatches != n) + { + /* + * No, so advance search_start, but not start_ptr/data_pos. (Thus, + * we treat the matched text as if it weren't matched, and copy it + * to the output later.) + */ + search_start = pmatch[0].rm_eo; + if (pmatch[0].rm_so == pmatch[0].rm_eo) + search_start++; + continue; + } + + /* + * Copy the text to the left of the match position. Note we are given + * character not byte indexes. + */ + if (pmatch[0].rm_so - data_pos > 0) + { + int chunk_len; + + chunk_len = charlen_to_bytelen(start_ptr, + pmatch[0].rm_so - data_pos); + appendBinaryStringInfo(&buf, start_ptr, chunk_len); + + /* + * Advance start_ptr over that text, to avoid multiple rescans of + * it if the replace_text contains multiple back-references. + */ + start_ptr += chunk_len; + data_pos = pmatch[0].rm_so; + } + + /* + * Copy the replace_text, processing escapes if any are present. + */ + if (escape_status > 0) + appendStringInfoRegexpSubstr(&buf, replace_text, pmatch, + start_ptr, data_pos); + else + appendStringInfoText(&buf, replace_text); + + /* Advance start_ptr and data_pos over the matched text. */ + start_ptr += charlen_to_bytelen(start_ptr, + pmatch[0].rm_eo - data_pos); + data_pos = pmatch[0].rm_eo; + + /* + * If we only want to replace one occurrence, we're done. + */ + if (n > 0) + break; + + /* + * Advance search position. Normally we start the next search at the + * end of the previous match; but if the match was of zero length, we + * have to advance by one character, or we'd just find the same match + * again. + */ + search_start = data_pos; + if (pmatch[0].rm_so == pmatch[0].rm_eo) + search_start++; + } + + /* + * Copy the text to the right of the last match. + */ + if (data_pos < data_len) + { + int chunk_len; + + chunk_len = ((char *) src_text + VARSIZE_ANY(src_text)) - start_ptr; + appendBinaryStringInfo(&buf, start_ptr, chunk_len); + } + + ret_text = cstring_to_text_with_len(buf.data, buf.len); + pfree(buf.data); + pfree(data); + + return ret_text; +} + +/* + * split_part + * parse input string based on provided field separator + * return N'th item (1 based, negative counts from end) + */ +Datum +split_part(PG_FUNCTION_ARGS) +{ + text *inputstring = PG_GETARG_TEXT_PP(0); + text *fldsep = PG_GETARG_TEXT_PP(1); + int fldnum = PG_GETARG_INT32(2); + int inputstring_len; + int fldsep_len; + TextPositionState state; + char *start_ptr; + char *end_ptr; + text *result_text; + bool found; + + /* field number is 1 based */ + if (fldnum == 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("field position must not be zero"))); + + inputstring_len = VARSIZE_ANY_EXHDR(inputstring); + fldsep_len = VARSIZE_ANY_EXHDR(fldsep); + + /* return empty string for empty input string */ + if (inputstring_len < 1) + PG_RETURN_TEXT_P(cstring_to_text("")); + + /* handle empty field separator */ + if (fldsep_len < 1) + { + /* if first or last field, return input string, else empty string */ + if (fldnum == 1 || fldnum == -1) + PG_RETURN_TEXT_P(inputstring); + else + PG_RETURN_TEXT_P(cstring_to_text("")); + } + + /* find the first field separator */ + text_position_setup(inputstring, fldsep, PG_GET_COLLATION(), &state); + + found = text_position_next(&state); + + /* special case if fldsep not found at all */ + if (!found) + { + text_position_cleanup(&state); + /* if first or last field, return input string, else empty string */ + if (fldnum == 1 || fldnum == -1) + PG_RETURN_TEXT_P(inputstring); + else + PG_RETURN_TEXT_P(cstring_to_text("")); + } + + /* + * take care of a negative field number (i.e. count from the right) by + * converting to a positive field number; we need total number of fields + */ + if (fldnum < 0) + { + /* we found a fldsep, so there are at least two fields */ + int numfields = 2; + + while (text_position_next(&state)) + numfields++; + + /* special case of last field does not require an extra pass */ + if (fldnum == -1) + { + start_ptr = text_position_get_match_ptr(&state) + fldsep_len; + end_ptr = VARDATA_ANY(inputstring) + inputstring_len; + text_position_cleanup(&state); + PG_RETURN_TEXT_P(cstring_to_text_with_len(start_ptr, + end_ptr - start_ptr)); + } + + /* else, convert fldnum to positive notation */ + fldnum += numfields + 1; + + /* if nonexistent field, return empty string */ + if (fldnum <= 0) + { + text_position_cleanup(&state); + PG_RETURN_TEXT_P(cstring_to_text("")); + } + + /* reset to pointing at first match, but now with positive fldnum */ + text_position_reset(&state); + found = text_position_next(&state); + Assert(found); + } + + /* identify bounds of first field */ + start_ptr = VARDATA_ANY(inputstring); + end_ptr = text_position_get_match_ptr(&state); + + while (found && --fldnum > 0) + { + /* identify bounds of next field */ + start_ptr = end_ptr + fldsep_len; + found = text_position_next(&state); + if (found) + end_ptr = text_position_get_match_ptr(&state); + } + + text_position_cleanup(&state); + + if (fldnum > 0) + { + /* N'th field separator not found */ + /* if last field requested, return it, else empty string */ + if (fldnum == 1) + { + int last_len = start_ptr - VARDATA_ANY(inputstring); + + result_text = cstring_to_text_with_len(start_ptr, + inputstring_len - last_len); + } + else + result_text = cstring_to_text(""); + } + else + { + /* non-last field requested */ + result_text = cstring_to_text_with_len(start_ptr, end_ptr - start_ptr); + } + + PG_RETURN_TEXT_P(result_text); +} + +/* + * Convenience function to return true when two text params are equal. + */ +static bool +text_isequal(text *txt1, text *txt2, Oid collid) +{ + return DatumGetBool(DirectFunctionCall2Coll(texteq, + collid, + PointerGetDatum(txt1), + PointerGetDatum(txt2))); +} + +/* + * text_to_array + * parse input string and return text array of elements, + * based on provided field separator + */ +Datum +text_to_array(PG_FUNCTION_ARGS) +{ + SplitTextOutputData tstate; + + /* For array output, tstate should start as all zeroes */ + memset(&tstate, 0, sizeof(tstate)); + + if (!split_text(fcinfo, &tstate)) + PG_RETURN_NULL(); + + if (tstate.astate == NULL) + PG_RETURN_ARRAYTYPE_P(construct_empty_array(TEXTOID)); + + PG_RETURN_DATUM(makeArrayResult(tstate.astate, + CurrentMemoryContext)); +} + +/* + * text_to_array_null + * parse input string and return text array of elements, + * based on provided field separator and null string + * + * This is a separate entry point only to prevent the regression tests from + * complaining about different argument sets for the same internal function. + */ +Datum +text_to_array_null(PG_FUNCTION_ARGS) +{ + return text_to_array(fcinfo); +} + +/* + * text_to_table + * parse input string and return table of elements, + * based on provided field separator + */ +Datum +text_to_table(PG_FUNCTION_ARGS) +{ + ReturnSetInfo *rsi = (ReturnSetInfo *) fcinfo->resultinfo; + SplitTextOutputData tstate; + + tstate.astate = NULL; + InitMaterializedSRF(fcinfo, MAT_SRF_USE_EXPECTED_DESC); + tstate.tupstore = rsi->setResult; + tstate.tupdesc = rsi->setDesc; + + (void) split_text(fcinfo, &tstate); + + return (Datum) 0; +} + +/* + * text_to_table_null + * parse input string and return table of elements, + * based on provided field separator and null string + * + * This is a separate entry point only to prevent the regression tests from + * complaining about different argument sets for the same internal function. + */ +Datum +text_to_table_null(PG_FUNCTION_ARGS) +{ + return text_to_table(fcinfo); +} + +/* + * Common code for text_to_array, text_to_array_null, text_to_table + * and text_to_table_null functions. + * + * These are not strict so we have to test for null inputs explicitly. + * Returns false if result is to be null, else returns true. + * + * Note that if the result is valid but empty (zero elements), we return + * without changing *tstate --- caller must handle that case, too. + */ +static bool +split_text(FunctionCallInfo fcinfo, SplitTextOutputData *tstate) +{ + text *inputstring; + text *fldsep; + text *null_string; + Oid collation = PG_GET_COLLATION(); + int inputstring_len; + int fldsep_len; + char *start_ptr; + text *result_text; + + /* when input string is NULL, then result is NULL too */ + if (PG_ARGISNULL(0)) + return false; + + inputstring = PG_GETARG_TEXT_PP(0); + + /* fldsep can be NULL */ + if (!PG_ARGISNULL(1)) + fldsep = PG_GETARG_TEXT_PP(1); + else + fldsep = NULL; + + /* null_string can be NULL or omitted */ + if (PG_NARGS() > 2 && !PG_ARGISNULL(2)) + null_string = PG_GETARG_TEXT_PP(2); + else + null_string = NULL; + + if (fldsep != NULL) + { + /* + * Normal case with non-null fldsep. Use the text_position machinery + * to search for occurrences of fldsep. + */ + TextPositionState state; + + inputstring_len = VARSIZE_ANY_EXHDR(inputstring); + fldsep_len = VARSIZE_ANY_EXHDR(fldsep); + + /* return empty set for empty input string */ + if (inputstring_len < 1) + return true; + + /* empty field separator: return input string as a one-element set */ + if (fldsep_len < 1) + { + split_text_accum_result(tstate, inputstring, + null_string, collation); + return true; + } + + text_position_setup(inputstring, fldsep, collation, &state); + + start_ptr = VARDATA_ANY(inputstring); + + for (;;) + { + bool found; + char *end_ptr; + int chunk_len; + + CHECK_FOR_INTERRUPTS(); + + found = text_position_next(&state); + if (!found) + { + /* fetch last field */ + chunk_len = ((char *) inputstring + VARSIZE_ANY(inputstring)) - start_ptr; + end_ptr = NULL; /* not used, but some compilers complain */ + } + else + { + /* fetch non-last field */ + end_ptr = text_position_get_match_ptr(&state); + chunk_len = end_ptr - start_ptr; + } + + /* build a temp text datum to pass to split_text_accum_result */ + result_text = cstring_to_text_with_len(start_ptr, chunk_len); + + /* stash away this field */ + split_text_accum_result(tstate, result_text, + null_string, collation); + + pfree(result_text); + + if (!found) + break; + + start_ptr = end_ptr + fldsep_len; + } + + text_position_cleanup(&state); + } + else + { + /* + * When fldsep is NULL, each character in the input string becomes a + * separate element in the result set. The separator is effectively + * the space between characters. + */ + inputstring_len = VARSIZE_ANY_EXHDR(inputstring); + + start_ptr = VARDATA_ANY(inputstring); + + while (inputstring_len > 0) + { + int chunk_len = pg_mblen(start_ptr); + + CHECK_FOR_INTERRUPTS(); + + /* build a temp text datum to pass to split_text_accum_result */ + result_text = cstring_to_text_with_len(start_ptr, chunk_len); + + /* stash away this field */ + split_text_accum_result(tstate, result_text, + null_string, collation); + + pfree(result_text); + + start_ptr += chunk_len; + inputstring_len -= chunk_len; + } + } + + return true; +} + +/* + * Add text item to result set (table or array). + * + * This is also responsible for checking to see if the item matches + * the null_string, in which case we should emit NULL instead. + */ +static void +split_text_accum_result(SplitTextOutputData *tstate, + text *field_value, + text *null_string, + Oid collation) +{ + bool is_null = false; + + if (null_string && text_isequal(field_value, null_string, collation)) + is_null = true; + + if (tstate->tupstore) + { + Datum values[1]; + bool nulls[1]; + + values[0] = PointerGetDatum(field_value); + nulls[0] = is_null; + + tuplestore_putvalues(tstate->tupstore, + tstate->tupdesc, + values, + nulls); + } + else + { + tstate->astate = accumArrayResult(tstate->astate, + PointerGetDatum(field_value), + is_null, + TEXTOID, + CurrentMemoryContext); + } +} + +/* + * array_to_text + * concatenate Cstring representation of input array elements + * using provided field separator + */ +Datum +array_to_text(PG_FUNCTION_ARGS) +{ + ArrayType *v = PG_GETARG_ARRAYTYPE_P(0); + char *fldsep = text_to_cstring(PG_GETARG_TEXT_PP(1)); + + PG_RETURN_TEXT_P(array_to_text_internal(fcinfo, v, fldsep, NULL)); +} + +/* + * array_to_text_null + * concatenate Cstring representation of input array elements + * using provided field separator and null string + * + * This version is not strict so we have to test for null inputs explicitly. + */ +Datum +array_to_text_null(PG_FUNCTION_ARGS) +{ + ArrayType *v; + char *fldsep; + char *null_string; + + /* returns NULL when first or second parameter is NULL */ + if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) + PG_RETURN_NULL(); + + v = PG_GETARG_ARRAYTYPE_P(0); + fldsep = text_to_cstring(PG_GETARG_TEXT_PP(1)); + + /* NULL null string is passed through as a null pointer */ + if (!PG_ARGISNULL(2)) + null_string = text_to_cstring(PG_GETARG_TEXT_PP(2)); + else + null_string = NULL; + + PG_RETURN_TEXT_P(array_to_text_internal(fcinfo, v, fldsep, null_string)); +} + +/* + * common code for array_to_text and array_to_text_null functions + */ +static text * +array_to_text_internal(FunctionCallInfo fcinfo, ArrayType *v, + const char *fldsep, const char *null_string) +{ + text *result; + int nitems, + *dims, + ndims; + Oid element_type; + int typlen; + bool typbyval; + char typalign; + StringInfoData buf; + bool printed = false; + char *p; + bits8 *bitmap; + int bitmask; + int i; + ArrayMetaState *my_extra; + + ndims = ARR_NDIM(v); + dims = ARR_DIMS(v); + nitems = ArrayGetNItems(ndims, dims); + + /* if there are no elements, return an empty string */ + if (nitems == 0) + return cstring_to_text_with_len("", 0); + + element_type = ARR_ELEMTYPE(v); + initStringInfo(&buf); + + /* + * We arrange to look up info about element type, including its output + * conversion proc, only once per series of calls, assuming the element + * type doesn't change underneath us. + */ + my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra; + if (my_extra == NULL) + { + fcinfo->flinfo->fn_extra = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, + sizeof(ArrayMetaState)); + my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra; + my_extra->element_type = ~element_type; + } + + if (my_extra->element_type != element_type) + { + /* + * Get info about element type, including its output conversion proc + */ + get_type_io_data(element_type, IOFunc_output, + &my_extra->typlen, &my_extra->typbyval, + &my_extra->typalign, &my_extra->typdelim, + &my_extra->typioparam, &my_extra->typiofunc); + fmgr_info_cxt(my_extra->typiofunc, &my_extra->proc, + fcinfo->flinfo->fn_mcxt); + my_extra->element_type = element_type; + } + typlen = my_extra->typlen; + typbyval = my_extra->typbyval; + typalign = my_extra->typalign; + + p = ARR_DATA_PTR(v); + bitmap = ARR_NULLBITMAP(v); + bitmask = 1; + + for (i = 0; i < nitems; i++) + { + Datum itemvalue; + char *value; + + /* Get source element, checking for NULL */ + if (bitmap && (*bitmap & bitmask) == 0) + { + /* if null_string is NULL, we just ignore null elements */ + if (null_string != NULL) + { + if (printed) + appendStringInfo(&buf, "%s%s", fldsep, null_string); + else + appendStringInfoString(&buf, null_string); + printed = true; + } + } + else + { + itemvalue = fetch_att(p, typbyval, typlen); + + value = OutputFunctionCall(&my_extra->proc, itemvalue); + + if (printed) + appendStringInfo(&buf, "%s%s", fldsep, value); + else + appendStringInfoString(&buf, value); + printed = true; + + p = att_addlength_pointer(p, typlen, p); + p = (char *) att_align_nominal(p, typalign); + } + + /* advance bitmap pointer if any */ + if (bitmap) + { + bitmask <<= 1; + if (bitmask == 0x100) + { + bitmap++; + bitmask = 1; + } + } + } + + result = cstring_to_text_with_len(buf.data, buf.len); + pfree(buf.data); + + return result; +} + +#define HEXBASE 16 +/* + * Convert an int32 to a string containing a base 16 (hex) representation of + * the number. + */ +Datum +to_hex32(PG_FUNCTION_ARGS) +{ + uint32 value = (uint32) PG_GETARG_INT32(0); + char *ptr; + const char *digits = "0123456789abcdef"; + char buf[32]; /* bigger than needed, but reasonable */ + + ptr = buf + sizeof(buf) - 1; + *ptr = '\0'; + + do + { + *--ptr = digits[value % HEXBASE]; + value /= HEXBASE; + } while (ptr > buf && value); + + PG_RETURN_TEXT_P(cstring_to_text(ptr)); +} + +/* + * Convert an int64 to a string containing a base 16 (hex) representation of + * the number. + */ +Datum +to_hex64(PG_FUNCTION_ARGS) +{ + uint64 value = (uint64) PG_GETARG_INT64(0); + char *ptr; + const char *digits = "0123456789abcdef"; + char buf[32]; /* bigger than needed, but reasonable */ + + ptr = buf + sizeof(buf) - 1; + *ptr = '\0'; + + do + { + *--ptr = digits[value % HEXBASE]; + value /= HEXBASE; + } while (ptr > buf && value); + + PG_RETURN_TEXT_P(cstring_to_text(ptr)); +} + +/* + * Return the size of a datum, possibly compressed + * + * Works on any data type + */ +Datum +pg_column_size(PG_FUNCTION_ARGS) +{ + Datum value = PG_GETARG_DATUM(0); + int32 result; + int typlen; + + /* On first call, get the input type's typlen, and save at *fn_extra */ + if (fcinfo->flinfo->fn_extra == NULL) + { + /* Lookup the datatype of the supplied argument */ + Oid argtypeid = get_fn_expr_argtype(fcinfo->flinfo, 0); + + typlen = get_typlen(argtypeid); + if (typlen == 0) /* should not happen */ + elog(ERROR, "cache lookup failed for type %u", argtypeid); + + fcinfo->flinfo->fn_extra = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, + sizeof(int)); + *((int *) fcinfo->flinfo->fn_extra) = typlen; + } + else + typlen = *((int *) fcinfo->flinfo->fn_extra); + + if (typlen == -1) + { + /* varlena type, possibly toasted */ + result = toast_datum_size(value); + } + else if (typlen == -2) + { + /* cstring */ + result = strlen(DatumGetCString(value)) + 1; + } + else + { + /* ordinary fixed-width type */ + result = typlen; + } + + PG_RETURN_INT32(result); +} + +/* + * Return the compression method stored in the compressed attribute. Return + * NULL for non varlena type or uncompressed data. + */ +Datum +pg_column_compression(PG_FUNCTION_ARGS) +{ + int typlen; + char *result; + ToastCompressionId cmid; + + /* On first call, get the input type's typlen, and save at *fn_extra */ + if (fcinfo->flinfo->fn_extra == NULL) + { + /* Lookup the datatype of the supplied argument */ + Oid argtypeid = get_fn_expr_argtype(fcinfo->flinfo, 0); + + typlen = get_typlen(argtypeid); + if (typlen == 0) /* should not happen */ + elog(ERROR, "cache lookup failed for type %u", argtypeid); + + fcinfo->flinfo->fn_extra = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, + sizeof(int)); + *((int *) fcinfo->flinfo->fn_extra) = typlen; + } + else + typlen = *((int *) fcinfo->flinfo->fn_extra); + + if (typlen != -1) + PG_RETURN_NULL(); + + /* get the compression method id stored in the compressed varlena */ + cmid = toast_get_compression_id((struct varlena *) + DatumGetPointer(PG_GETARG_DATUM(0))); + if (cmid == TOAST_INVALID_COMPRESSION_ID) + PG_RETURN_NULL(); + + /* convert compression method id to compression method name */ + switch (cmid) + { + case TOAST_PGLZ_COMPRESSION_ID: + result = "pglz"; + break; + case TOAST_LZ4_COMPRESSION_ID: + result = "lz4"; + break; + default: + elog(ERROR, "invalid compression method id %d", cmid); + } + + PG_RETURN_TEXT_P(cstring_to_text(result)); +} + +/* + * string_agg - Concatenates values and returns string. + * + * Syntax: string_agg(value text, delimiter text) RETURNS text + * + * Note: Any NULL values are ignored. The first-call delimiter isn't + * actually used at all, and on subsequent calls the delimiter precedes + * the associated value. + */ + +/* subroutine to initialize state */ +static StringInfo +makeStringAggState(FunctionCallInfo fcinfo) +{ + StringInfo state; + MemoryContext aggcontext; + MemoryContext oldcontext; + + if (!AggCheckCallContext(fcinfo, &aggcontext)) + { + /* cannot be called directly because of internal-type argument */ + elog(ERROR, "string_agg_transfn called in non-aggregate context"); + } + + /* + * Create state in aggregate context. It'll stay there across subsequent + * calls. + */ + oldcontext = MemoryContextSwitchTo(aggcontext); + state = makeStringInfo(); + MemoryContextSwitchTo(oldcontext); + + return state; +} + +Datum +string_agg_transfn(PG_FUNCTION_ARGS) +{ + StringInfo state; + + state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0); + + /* Append the value unless null, preceding it with the delimiter. */ + if (!PG_ARGISNULL(1)) + { + text *value = PG_GETARG_TEXT_PP(1); + bool isfirst = false; + + /* + * You might think we can just throw away the first delimiter, however + * we must keep it as we may be a parallel worker doing partial + * aggregation building a state to send to the main process. We need + * to keep the delimiter of every aggregation so that the combine + * function can properly join up the strings of two separately + * partially aggregated results. The first delimiter is only stripped + * off in the final function. To know how much to strip off the front + * of the string, we store the length of the first delimiter in the + * StringInfo's cursor field, which we don't otherwise need here. + */ + if (state == NULL) + { + state = makeStringAggState(fcinfo); + isfirst = true; + } + + if (!PG_ARGISNULL(2)) + { + text *delim = PG_GETARG_TEXT_PP(2); + + appendStringInfoText(state, delim); + if (isfirst) + state->cursor = VARSIZE_ANY_EXHDR(delim); + } + + appendStringInfoText(state, value); + } + + /* + * The transition type for string_agg() is declared to be "internal", + * which is a pass-by-value type the same size as a pointer. + */ + if (state) + PG_RETURN_POINTER(state); + PG_RETURN_NULL(); +} + +/* + * string_agg_combine + * Aggregate combine function for string_agg(text) and string_agg(bytea) + */ +Datum +string_agg_combine(PG_FUNCTION_ARGS) +{ + StringInfo state1; + StringInfo state2; + MemoryContext agg_context; + + if (!AggCheckCallContext(fcinfo, &agg_context)) + elog(ERROR, "aggregate function called in non-aggregate context"); + + state1 = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0); + state2 = PG_ARGISNULL(1) ? NULL : (StringInfo) PG_GETARG_POINTER(1); + + if (state2 == NULL) + { + /* + * NULL state2 is easy, just return state1, which we know is already + * in the agg_context + */ + if (state1 == NULL) + PG_RETURN_NULL(); + PG_RETURN_POINTER(state1); + } + + if (state1 == NULL) + { + /* We must copy state2's data into the agg_context */ + MemoryContext old_context; + + old_context = MemoryContextSwitchTo(agg_context); + state1 = makeStringAggState(fcinfo); + appendBinaryStringInfo(state1, state2->data, state2->len); + state1->cursor = state2->cursor; + MemoryContextSwitchTo(old_context); + } + else if (state2->len > 0) + { + /* Combine ... state1->cursor does not change in this case */ + appendBinaryStringInfo(state1, state2->data, state2->len); + } + + PG_RETURN_POINTER(state1); +} + +/* + * string_agg_serialize + * Aggregate serialize function for string_agg(text) and string_agg(bytea) + * + * This is strict, so we need not handle NULL input + */ +Datum +string_agg_serialize(PG_FUNCTION_ARGS) +{ + StringInfo state; + StringInfoData buf; + bytea *result; + + /* cannot be called directly because of internal-type argument */ + Assert(AggCheckCallContext(fcinfo, NULL)); + + state = (StringInfo) PG_GETARG_POINTER(0); + + pq_begintypsend(&buf); + + /* cursor */ + pq_sendint(&buf, state->cursor, 4); + + /* data */ + pq_sendbytes(&buf, state->data, state->len); + + result = pq_endtypsend(&buf); + + PG_RETURN_BYTEA_P(result); +} + +/* + * string_agg_deserialize + * Aggregate deserial function for string_agg(text) and string_agg(bytea) + * + * This is strict, so we need not handle NULL input + */ +Datum +string_agg_deserialize(PG_FUNCTION_ARGS) +{ + bytea *sstate; + StringInfo result; + StringInfoData buf; + char *data; + int datalen; + + /* cannot be called directly because of internal-type argument */ + Assert(AggCheckCallContext(fcinfo, NULL)); + + sstate = PG_GETARG_BYTEA_PP(0); + + /* + * Copy the bytea into a StringInfo so that we can "receive" it using the + * standard recv-function infrastructure. + */ + initStringInfo(&buf); + appendBinaryStringInfo(&buf, + VARDATA_ANY(sstate), VARSIZE_ANY_EXHDR(sstate)); + + result = makeStringAggState(fcinfo); + + /* cursor */ + result->cursor = pq_getmsgint(&buf, 4); + + /* data */ + datalen = VARSIZE_ANY_EXHDR(sstate) - 4; + data = (char *) pq_getmsgbytes(&buf, datalen); + appendBinaryStringInfo(result, data, datalen); + + pq_getmsgend(&buf); + pfree(buf.data); + + PG_RETURN_POINTER(result); +} + +Datum +string_agg_finalfn(PG_FUNCTION_ARGS) +{ + StringInfo state; + + /* cannot be called directly because of internal-type argument */ + Assert(AggCheckCallContext(fcinfo, NULL)); + + state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0); + + if (state != NULL) + { + /* As per comment in transfn, strip data before the cursor position */ + PG_RETURN_TEXT_P(cstring_to_text_with_len(&state->data[state->cursor], + state->len - state->cursor)); + } + else + PG_RETURN_NULL(); +} + +/* + * Prepare cache with fmgr info for the output functions of the datatypes of + * the arguments of a concat-like function, beginning with argument "argidx". + * (Arguments before that will have corresponding slots in the resulting + * FmgrInfo array, but we don't fill those slots.) + */ +static FmgrInfo * +build_concat_foutcache(FunctionCallInfo fcinfo, int argidx) +{ + FmgrInfo *foutcache; + int i; + + /* We keep the info in fn_mcxt so it survives across calls */ + foutcache = (FmgrInfo *) MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, + PG_NARGS() * sizeof(FmgrInfo)); + + for (i = argidx; i < PG_NARGS(); i++) + { + Oid valtype; + Oid typOutput; + bool typIsVarlena; + + valtype = get_fn_expr_argtype(fcinfo->flinfo, i); + if (!OidIsValid(valtype)) + elog(ERROR, "could not determine data type of concat() input"); + + getTypeOutputInfo(valtype, &typOutput, &typIsVarlena); + fmgr_info_cxt(typOutput, &foutcache[i], fcinfo->flinfo->fn_mcxt); + } + + fcinfo->flinfo->fn_extra = foutcache; + + return foutcache; +} + +/* + * Implementation of both concat() and concat_ws(). + * + * sepstr is the separator string to place between values. + * argidx identifies the first argument to concatenate (counting from zero); + * note that this must be constant across any one series of calls. + * + * Returns NULL if result should be NULL, else text value. + */ +static text * +concat_internal(const char *sepstr, int argidx, + FunctionCallInfo fcinfo) +{ + text *result; + StringInfoData str; + FmgrInfo *foutcache; + bool first_arg = true; + int i; + + /* + * concat(VARIADIC some-array) is essentially equivalent to + * array_to_text(), ie concat the array elements with the given separator. + * So we just pass the case off to that code. + */ + if (get_fn_expr_variadic(fcinfo->flinfo)) + { + ArrayType *arr; + + /* Should have just the one argument */ + Assert(argidx == PG_NARGS() - 1); + + /* concat(VARIADIC NULL) is defined as NULL */ + if (PG_ARGISNULL(argidx)) + return NULL; + + /* + * Non-null argument had better be an array. We assume that any call + * context that could let get_fn_expr_variadic return true will have + * checked that a VARIADIC-labeled parameter actually is an array. So + * it should be okay to just Assert that it's an array rather than + * doing a full-fledged error check. + */ + Assert(OidIsValid(get_base_element_type(get_fn_expr_argtype(fcinfo->flinfo, argidx)))); + + /* OK, safe to fetch the array value */ + arr = PG_GETARG_ARRAYTYPE_P(argidx); + + /* + * And serialize the array. We tell array_to_text to ignore null + * elements, which matches the behavior of the loop below. + */ + return array_to_text_internal(fcinfo, arr, sepstr, NULL); + } + + /* Normal case without explicit VARIADIC marker */ + initStringInfo(&str); + + /* Get output function info, building it if first time through */ + foutcache = (FmgrInfo *) fcinfo->flinfo->fn_extra; + if (foutcache == NULL) + foutcache = build_concat_foutcache(fcinfo, argidx); + + for (i = argidx; i < PG_NARGS(); i++) + { + if (!PG_ARGISNULL(i)) + { + Datum value = PG_GETARG_DATUM(i); + + /* add separator if appropriate */ + if (first_arg) + first_arg = false; + else + appendStringInfoString(&str, sepstr); + + /* call the appropriate type output function, append the result */ + appendStringInfoString(&str, + OutputFunctionCall(&foutcache[i], value)); + } + } + + result = cstring_to_text_with_len(str.data, str.len); + pfree(str.data); + + return result; +} + +/* + * Concatenate all arguments. NULL arguments are ignored. + */ +Datum +text_concat(PG_FUNCTION_ARGS) +{ + text *result; + + result = concat_internal("", 0, fcinfo); + if (result == NULL) + PG_RETURN_NULL(); + PG_RETURN_TEXT_P(result); +} + +/* + * Concatenate all but first argument value with separators. The first + * parameter is used as the separator. NULL arguments are ignored. + */ +Datum +text_concat_ws(PG_FUNCTION_ARGS) +{ + char *sep; + text *result; + + /* return NULL when separator is NULL */ + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + sep = text_to_cstring(PG_GETARG_TEXT_PP(0)); + + result = concat_internal(sep, 1, fcinfo); + if (result == NULL) + PG_RETURN_NULL(); + PG_RETURN_TEXT_P(result); +} + +/* + * Return first n characters in the string. When n is negative, + * return all but last |n| characters. + */ +Datum +text_left(PG_FUNCTION_ARGS) +{ + int n = PG_GETARG_INT32(1); + + if (n < 0) + { + text *str = PG_GETARG_TEXT_PP(0); + const char *p = VARDATA_ANY(str); + int len = VARSIZE_ANY_EXHDR(str); + int rlen; + + n = pg_mbstrlen_with_len(p, len) + n; + rlen = pg_mbcharcliplen(p, len, n); + PG_RETURN_TEXT_P(cstring_to_text_with_len(p, rlen)); + } + else + PG_RETURN_TEXT_P(text_substring(PG_GETARG_DATUM(0), 1, n, false)); +} + +/* + * Return last n characters in the string. When n is negative, + * return all but first |n| characters. + */ +Datum +text_right(PG_FUNCTION_ARGS) +{ + text *str = PG_GETARG_TEXT_PP(0); + const char *p = VARDATA_ANY(str); + int len = VARSIZE_ANY_EXHDR(str); + int n = PG_GETARG_INT32(1); + int off; + + if (n < 0) + n = -n; + else + n = pg_mbstrlen_with_len(p, len) - n; + off = pg_mbcharcliplen(p, len, n); + + PG_RETURN_TEXT_P(cstring_to_text_with_len(p + off, len - off)); +} + +/* + * Return reversed string + */ +Datum +text_reverse(PG_FUNCTION_ARGS) +{ + text *str = PG_GETARG_TEXT_PP(0); + const char *p = VARDATA_ANY(str); + int len = VARSIZE_ANY_EXHDR(str); + const char *endp = p + len; + text *result; + char *dst; + + result = palloc(len + VARHDRSZ); + dst = (char *) VARDATA(result) + len; + SET_VARSIZE(result, len + VARHDRSZ); + + if (pg_database_encoding_max_length() > 1) + { + /* multibyte version */ + while (p < endp) + { + int sz; + + sz = pg_mblen(p); + dst -= sz; + memcpy(dst, p, sz); + p += sz; + } + } + else + { + /* single byte version */ + while (p < endp) + *(--dst) = *p++; + } + + PG_RETURN_TEXT_P(result); +} + + +/* + * Support macros for text_format() + */ +#define TEXT_FORMAT_FLAG_MINUS 0x0001 /* is minus flag present? */ + +#define ADVANCE_PARSE_POINTER(ptr,end_ptr) \ + do { \ + if (++(ptr) >= (end_ptr)) \ + ereport(ERROR, \ + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), \ + errmsg("unterminated format() type specifier"), \ + errhint("For a single \"%%\" use \"%%%%\"."))); \ + } while (0) + +/* + * Returns a formatted string + */ +Datum +text_format(PG_FUNCTION_ARGS) +{ + text *fmt; + StringInfoData str; + const char *cp; + const char *start_ptr; + const char *end_ptr; + text *result; + int arg; + bool funcvariadic; + int nargs; + Datum *elements = NULL; + bool *nulls = NULL; + Oid element_type = InvalidOid; + Oid prev_type = InvalidOid; + Oid prev_width_type = InvalidOid; + FmgrInfo typoutputfinfo; + FmgrInfo typoutputinfo_width; + + /* When format string is null, immediately return null */ + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + + /* If argument is marked VARIADIC, expand array into elements */ + if (get_fn_expr_variadic(fcinfo->flinfo)) + { + ArrayType *arr; + int16 elmlen; + bool elmbyval; + char elmalign; + int nitems; + + /* Should have just the one argument */ + Assert(PG_NARGS() == 2); + + /* If argument is NULL, we treat it as zero-length array */ + if (PG_ARGISNULL(1)) + nitems = 0; + else + { + /* + * Non-null argument had better be an array. We assume that any + * call context that could let get_fn_expr_variadic return true + * will have checked that a VARIADIC-labeled parameter actually is + * an array. So it should be okay to just Assert that it's an + * array rather than doing a full-fledged error check. + */ + Assert(OidIsValid(get_base_element_type(get_fn_expr_argtype(fcinfo->flinfo, 1)))); + + /* OK, safe to fetch the array value */ + arr = PG_GETARG_ARRAYTYPE_P(1); + + /* Get info about array element type */ + element_type = ARR_ELEMTYPE(arr); + get_typlenbyvalalign(element_type, + &elmlen, &elmbyval, &elmalign); + + /* Extract all array elements */ + deconstruct_array(arr, element_type, elmlen, elmbyval, elmalign, + &elements, &nulls, &nitems); + } + + nargs = nitems + 1; + funcvariadic = true; + } + else + { + /* Non-variadic case, we'll process the arguments individually */ + nargs = PG_NARGS(); + funcvariadic = false; + } + + /* Setup for main loop. */ + fmt = PG_GETARG_TEXT_PP(0); + start_ptr = VARDATA_ANY(fmt); + end_ptr = start_ptr + VARSIZE_ANY_EXHDR(fmt); + initStringInfo(&str); + arg = 1; /* next argument position to print */ + + /* Scan format string, looking for conversion specifiers. */ + for (cp = start_ptr; cp < end_ptr; cp++) + { + int argpos; + int widthpos; + int flags; + int width; + Datum value; + bool isNull; + Oid typid; + + /* + * If it's not the start of a conversion specifier, just copy it to + * the output buffer. + */ + if (*cp != '%') + { + appendStringInfoCharMacro(&str, *cp); + continue; + } + + ADVANCE_PARSE_POINTER(cp, end_ptr); + + /* Easy case: %% outputs a single % */ + if (*cp == '%') + { + appendStringInfoCharMacro(&str, *cp); + continue; + } + + /* Parse the optional portions of the format specifier */ + cp = text_format_parse_format(cp, end_ptr, + &argpos, &widthpos, + &flags, &width); + + /* + * Next we should see the main conversion specifier. Whether or not + * an argument position was present, it's known that at least one + * character remains in the string at this point. Experience suggests + * that it's worth checking that that character is one of the expected + * ones before we try to fetch arguments, so as to produce the least + * confusing response to a mis-formatted specifier. + */ + if (strchr("sIL", *cp) == NULL) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unrecognized format() type specifier \"%.*s\"", + pg_mblen(cp), cp), + errhint("For a single \"%%\" use \"%%%%\"."))); + + /* If indirect width was specified, get its value */ + if (widthpos >= 0) + { + /* Collect the specified or next argument position */ + if (widthpos > 0) + arg = widthpos; + if (arg >= nargs) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("too few arguments for format()"))); + + /* Get the value and type of the selected argument */ + if (!funcvariadic) + { + value = PG_GETARG_DATUM(arg); + isNull = PG_ARGISNULL(arg); + typid = get_fn_expr_argtype(fcinfo->flinfo, arg); + } + else + { + value = elements[arg - 1]; + isNull = nulls[arg - 1]; + typid = element_type; + } + if (!OidIsValid(typid)) + elog(ERROR, "could not determine data type of format() input"); + + arg++; + + /* We can treat NULL width the same as zero */ + if (isNull) + width = 0; + else if (typid == INT4OID) + width = DatumGetInt32(value); + else if (typid == INT2OID) + width = DatumGetInt16(value); + else + { + /* For less-usual datatypes, convert to text then to int */ + char *str; + + if (typid != prev_width_type) + { + Oid typoutputfunc; + bool typIsVarlena; + + getTypeOutputInfo(typid, &typoutputfunc, &typIsVarlena); + fmgr_info(typoutputfunc, &typoutputinfo_width); + prev_width_type = typid; + } + + str = OutputFunctionCall(&typoutputinfo_width, value); + + /* pg_strtoint32 will complain about bad data or overflow */ + width = pg_strtoint32(str); + + pfree(str); + } + } + + /* Collect the specified or next argument position */ + if (argpos > 0) + arg = argpos; + if (arg >= nargs) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("too few arguments for format()"))); + + /* Get the value and type of the selected argument */ + if (!funcvariadic) + { + value = PG_GETARG_DATUM(arg); + isNull = PG_ARGISNULL(arg); + typid = get_fn_expr_argtype(fcinfo->flinfo, arg); + } + else + { + value = elements[arg - 1]; + isNull = nulls[arg - 1]; + typid = element_type; + } + if (!OidIsValid(typid)) + elog(ERROR, "could not determine data type of format() input"); + + arg++; + + /* + * Get the appropriate typOutput function, reusing previous one if + * same type as previous argument. That's particularly useful in the + * variadic-array case, but often saves work even for ordinary calls. + */ + if (typid != prev_type) + { + Oid typoutputfunc; + bool typIsVarlena; + + getTypeOutputInfo(typid, &typoutputfunc, &typIsVarlena); + fmgr_info(typoutputfunc, &typoutputfinfo); + prev_type = typid; + } + + /* + * And now we can format the value. + */ + switch (*cp) + { + case 's': + case 'I': + case 'L': + text_format_string_conversion(&str, *cp, &typoutputfinfo, + value, isNull, + flags, width); + break; + default: + /* should not get here, because of previous check */ + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unrecognized format() type specifier \"%.*s\"", + pg_mblen(cp), cp), + errhint("For a single \"%%\" use \"%%%%\"."))); + break; + } + } + + /* Don't need deconstruct_array results anymore. */ + if (elements != NULL) + pfree(elements); + if (nulls != NULL) + pfree(nulls); + + /* Generate results. */ + result = cstring_to_text_with_len(str.data, str.len); + pfree(str.data); + + PG_RETURN_TEXT_P(result); +} + +/* + * Parse contiguous digits as a decimal number. + * + * Returns true if some digits could be parsed. + * The value is returned into *value, and *ptr is advanced to the next + * character to be parsed. + * + * Note parsing invariant: at least one character is known available before + * string end (end_ptr) at entry, and this is still true at exit. + */ +static bool +text_format_parse_digits(const char **ptr, const char *end_ptr, int *value) +{ + bool found = false; + const char *cp = *ptr; + int val = 0; + + while (*cp >= '0' && *cp <= '9') + { + int8 digit = (*cp - '0'); + + if (unlikely(pg_mul_s32_overflow(val, 10, &val)) || + unlikely(pg_add_s32_overflow(val, digit, &val))) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("number is out of range"))); + ADVANCE_PARSE_POINTER(cp, end_ptr); + found = true; + } + + *ptr = cp; + *value = val; + + return found; +} + +/* + * Parse a format specifier (generally following the SUS printf spec). + * + * We have already advanced over the initial '%', and we are looking for + * [argpos][flags][width]type (but the type character is not consumed here). + * + * Inputs are start_ptr (the position after '%') and end_ptr (string end + 1). + * Output parameters: + * argpos: argument position for value to be printed. -1 means unspecified. + * widthpos: argument position for width. Zero means the argument position + * was unspecified (ie, take the next arg) and -1 means no width + * argument (width was omitted or specified as a constant). + * flags: bitmask of flags. + * width: directly-specified width value. Zero means the width was omitted + * (note it's not necessary to distinguish this case from an explicit + * zero width value). + * + * The function result is the next character position to be parsed, ie, the + * location where the type character is/should be. + * + * Note parsing invariant: at least one character is known available before + * string end (end_ptr) at entry, and this is still true at exit. + */ +static const char * +text_format_parse_format(const char *start_ptr, const char *end_ptr, + int *argpos, int *widthpos, + int *flags, int *width) +{ + const char *cp = start_ptr; + int n; + + /* set defaults for output parameters */ + *argpos = -1; + *widthpos = -1; + *flags = 0; + *width = 0; + + /* try to identify first number */ + if (text_format_parse_digits(&cp, end_ptr, &n)) + { + if (*cp != '$') + { + /* Must be just a width and a type, so we're done */ + *width = n; + return cp; + } + /* The number was argument position */ + *argpos = n; + /* Explicit 0 for argument index is immediately refused */ + if (n == 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("format specifies argument 0, but arguments are numbered from 1"))); + ADVANCE_PARSE_POINTER(cp, end_ptr); + } + + /* Handle flags (only minus is supported now) */ + while (*cp == '-') + { + *flags |= TEXT_FORMAT_FLAG_MINUS; + ADVANCE_PARSE_POINTER(cp, end_ptr); + } + + if (*cp == '*') + { + /* Handle indirect width */ + ADVANCE_PARSE_POINTER(cp, end_ptr); + if (text_format_parse_digits(&cp, end_ptr, &n)) + { + /* number in this position must be closed by $ */ + if (*cp != '$') + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("width argument position must be ended by \"$\""))); + /* The number was width argument position */ + *widthpos = n; + /* Explicit 0 for argument index is immediately refused */ + if (n == 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("format specifies argument 0, but arguments are numbered from 1"))); + ADVANCE_PARSE_POINTER(cp, end_ptr); + } + else + *widthpos = 0; /* width's argument position is unspecified */ + } + else + { + /* Check for direct width specification */ + if (text_format_parse_digits(&cp, end_ptr, &n)) + *width = n; + } + + /* cp should now be pointing at type character */ + return cp; +} + +/* + * Format a %s, %I, or %L conversion + */ +static void +text_format_string_conversion(StringInfo buf, char conversion, + FmgrInfo *typOutputInfo, + Datum value, bool isNull, + int flags, int width) +{ + char *str; + + /* Handle NULL arguments before trying to stringify the value. */ + if (isNull) + { + if (conversion == 's') + text_format_append_string(buf, "", flags, width); + else if (conversion == 'L') + text_format_append_string(buf, "NULL", flags, width); + else if (conversion == 'I') + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("null values cannot be formatted as an SQL identifier"))); + return; + } + + /* Stringify. */ + str = OutputFunctionCall(typOutputInfo, value); + + /* Escape. */ + if (conversion == 'I') + { + /* quote_identifier may or may not allocate a new string. */ + text_format_append_string(buf, quote_identifier(str), flags, width); + } + else if (conversion == 'L') + { + char *qstr = quote_literal_cstr(str); + + text_format_append_string(buf, qstr, flags, width); + /* quote_literal_cstr() always allocates a new string */ + pfree(qstr); + } + else + text_format_append_string(buf, str, flags, width); + + /* Cleanup. */ + pfree(str); +} + +/* + * Append str to buf, padding as directed by flags/width + */ +static void +text_format_append_string(StringInfo buf, const char *str, + int flags, int width) +{ + bool align_to_left = false; + int len; + + /* fast path for typical easy case */ + if (width == 0) + { + appendStringInfoString(buf, str); + return; + } + + if (width < 0) + { + /* Negative width: implicit '-' flag, then take absolute value */ + align_to_left = true; + /* -INT_MIN is undefined */ + if (width <= INT_MIN) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("number is out of range"))); + width = -width; + } + else if (flags & TEXT_FORMAT_FLAG_MINUS) + align_to_left = true; + + len = pg_mbstrlen(str); + if (align_to_left) + { + /* left justify */ + appendStringInfoString(buf, str); + if (len < width) + appendStringInfoSpaces(buf, width - len); + } + else + { + /* right justify */ + if (len < width) + appendStringInfoSpaces(buf, width - len); + appendStringInfoString(buf, str); + } +} + +/* + * text_format_nv - nonvariadic wrapper for text_format function. + * + * note: this wrapper is necessary to pass the sanity check in opr_sanity, + * which checks that all built-in functions that share the implementing C + * function take the same number of arguments. + */ +Datum +text_format_nv(PG_FUNCTION_ARGS) +{ + return text_format(fcinfo); +} + +/* + * Helper function for Levenshtein distance functions. Faster than memcmp(), + * for this use case. + */ +static inline bool +rest_of_char_same(const char *s1, const char *s2, int len) +{ + while (len > 0) + { + len--; + if (s1[len] != s2[len]) + return false; + } + return true; +} + +/* Expand each Levenshtein distance variant */ +#include "levenshtein.c" +#define LEVENSHTEIN_LESS_EQUAL +#include "levenshtein.c" + + +/* + * The following *ClosestMatch() functions can be used to determine whether a + * user-provided string resembles any known valid values, which is useful for + * providing hints in log messages, among other things. Use these functions + * like so: + * + * initClosestMatch(&state, source_string, max_distance); + * + * for (int i = 0; i < num_valid_strings; i++) + * updateClosestMatch(&state, valid_strings[i]); + * + * closestMatch = getClosestMatch(&state); + */ + +/* + * Initialize the given state with the source string and maximum Levenshtein + * distance to consider. + */ +void +initClosestMatch(ClosestMatchState *state, const char *source, int max_d) +{ + Assert(state); + Assert(max_d >= 0); + + state->source = source; + state->min_d = -1; + state->max_d = max_d; + state->match = NULL; +} + +/* + * If the candidate string is a closer match than the current one saved (or + * there is no match saved), save it as the closest match. + * + * If the source or candidate string is NULL, empty, or too long, this function + * takes no action. Likewise, if the Levenshtein distance exceeds the maximum + * allowed or more than half the characters are different, no action is taken. + */ +void +updateClosestMatch(ClosestMatchState *state, const char *candidate) +{ + int dist; + + Assert(state); + + if (state->source == NULL || state->source[0] == '\0' || + candidate == NULL || candidate[0] == '\0') + return; + + /* + * To avoid ERROR-ing, we check the lengths here instead of setting + * 'trusted' to false in the call to varstr_levenshtein_less_equal(). + */ + if (strlen(state->source) > MAX_LEVENSHTEIN_STRLEN || + strlen(candidate) > MAX_LEVENSHTEIN_STRLEN) + return; + + dist = varstr_levenshtein_less_equal(state->source, strlen(state->source), + candidate, strlen(candidate), 1, 1, 1, + state->max_d, true); + if (dist <= state->max_d && + dist <= strlen(state->source) / 2 && + (state->min_d == -1 || dist < state->min_d)) + { + state->min_d = dist; + state->match = candidate; + } +} + +/* + * Return the closest match. If no suitable candidates were provided via + * updateClosestMatch(), return NULL. + */ +const char * +getClosestMatch(ClosestMatchState *state) +{ + Assert(state); + + return state->match; +} + + +/* + * Unicode support + */ + +static UnicodeNormalizationForm +unicode_norm_form_from_string(const char *formstr) +{ + UnicodeNormalizationForm form = -1; + + /* + * Might as well check this while we're here. + */ + if (GetDatabaseEncoding() != PG_UTF8) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("Unicode normalization can only be performed if server encoding is UTF8"))); + + if (pg_strcasecmp(formstr, "NFC") == 0) + form = UNICODE_NFC; + else if (pg_strcasecmp(formstr, "NFD") == 0) + form = UNICODE_NFD; + else if (pg_strcasecmp(formstr, "NFKC") == 0) + form = UNICODE_NFKC; + else if (pg_strcasecmp(formstr, "NFKD") == 0) + form = UNICODE_NFKD; + else + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid normalization form: %s", formstr))); + + return form; +} + +Datum +unicode_normalize_func(PG_FUNCTION_ARGS) +{ + text *input = PG_GETARG_TEXT_PP(0); + char *formstr = text_to_cstring(PG_GETARG_TEXT_PP(1)); + UnicodeNormalizationForm form; + int size; + pg_wchar *input_chars; + pg_wchar *output_chars; + unsigned char *p; + text *result; + int i; + + form = unicode_norm_form_from_string(formstr); + + /* convert to pg_wchar */ + size = pg_mbstrlen_with_len(VARDATA_ANY(input), VARSIZE_ANY_EXHDR(input)); + input_chars = palloc((size + 1) * sizeof(pg_wchar)); + p = (unsigned char *) VARDATA_ANY(input); + for (i = 0; i < size; i++) + { + input_chars[i] = utf8_to_unicode(p); + p += pg_utf_mblen(p); + } + input_chars[i] = (pg_wchar) '\0'; + Assert((char *) p == VARDATA_ANY(input) + VARSIZE_ANY_EXHDR(input)); + + /* action */ + output_chars = unicode_normalize(form, input_chars); + + /* convert back to UTF-8 string */ + size = 0; + for (pg_wchar *wp = output_chars; *wp; wp++) + { + unsigned char buf[4]; + + unicode_to_utf8(*wp, buf); + size += pg_utf_mblen(buf); + } + + result = palloc(size + VARHDRSZ); + SET_VARSIZE(result, size + VARHDRSZ); + + p = (unsigned char *) VARDATA_ANY(result); + for (pg_wchar *wp = output_chars; *wp; wp++) + { + unicode_to_utf8(*wp, p); + p += pg_utf_mblen(p); + } + Assert((char *) p == (char *) result + size + VARHDRSZ); + + PG_RETURN_TEXT_P(result); +} + +/* + * Check whether the string is in the specified Unicode normalization form. + * + * This is done by converting the string to the specified normal form and then + * comparing that to the original string. To speed that up, we also apply the + * "quick check" algorithm specified in UAX #15, which can give a yes or no + * answer for many strings by just scanning the string once. + * + * This function should generally be optimized for the case where the string + * is in fact normalized. In that case, we'll end up looking at the entire + * string, so it's probably not worth doing any incremental conversion etc. + */ +Datum +unicode_is_normalized(PG_FUNCTION_ARGS) +{ + text *input = PG_GETARG_TEXT_PP(0); + char *formstr = text_to_cstring(PG_GETARG_TEXT_PP(1)); + UnicodeNormalizationForm form; + int size; + pg_wchar *input_chars; + pg_wchar *output_chars; + unsigned char *p; + int i; + UnicodeNormalizationQC quickcheck; + int output_size; + bool result; + + form = unicode_norm_form_from_string(formstr); + + /* convert to pg_wchar */ + size = pg_mbstrlen_with_len(VARDATA_ANY(input), VARSIZE_ANY_EXHDR(input)); + input_chars = palloc((size + 1) * sizeof(pg_wchar)); + p = (unsigned char *) VARDATA_ANY(input); + for (i = 0; i < size; i++) + { + input_chars[i] = utf8_to_unicode(p); + p += pg_utf_mblen(p); + } + input_chars[i] = (pg_wchar) '\0'; + Assert((char *) p == VARDATA_ANY(input) + VARSIZE_ANY_EXHDR(input)); + + /* quick check (see UAX #15) */ + quickcheck = unicode_is_normalized_quickcheck(form, input_chars); + if (quickcheck == UNICODE_NORM_QC_YES) + PG_RETURN_BOOL(true); + else if (quickcheck == UNICODE_NORM_QC_NO) + PG_RETURN_BOOL(false); + + /* normalize and compare with original */ + output_chars = unicode_normalize(form, input_chars); + + output_size = 0; + for (pg_wchar *wp = output_chars; *wp; wp++) + output_size++; + + result = (size == output_size) && + (memcmp(input_chars, output_chars, size * sizeof(pg_wchar)) == 0); + + PG_RETURN_BOOL(result); +} + +/* + * Check if first n chars are hexadecimal digits + */ +static bool +isxdigits_n(const char *instr, size_t n) +{ + for (size_t i = 0; i < n; i++) + if (!isxdigit((unsigned char) instr[i])) + return false; + + return true; +} + +static unsigned int +hexval(unsigned char c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + if (c >= 'a' && c <= 'f') + return c - 'a' + 0xA; + if (c >= 'A' && c <= 'F') + return c - 'A' + 0xA; + elog(ERROR, "invalid hexadecimal digit"); + return 0; /* not reached */ +} + +/* + * Translate string with hexadecimal digits to number + */ +static unsigned int +hexval_n(const char *instr, size_t n) +{ + unsigned int result = 0; + + for (size_t i = 0; i < n; i++) + result += hexval(instr[i]) << (4 * (n - i - 1)); + + return result; +} + +/* + * Replaces Unicode escape sequences by Unicode characters + */ +Datum +unistr(PG_FUNCTION_ARGS) +{ + text *input_text = PG_GETARG_TEXT_PP(0); + char *instr; + int len; + StringInfoData str; + text *result; + pg_wchar pair_first = 0; + char cbuf[MAX_UNICODE_EQUIVALENT_STRING + 1]; + + instr = VARDATA_ANY(input_text); + len = VARSIZE_ANY_EXHDR(input_text); + + initStringInfo(&str); + + while (len > 0) + { + if (instr[0] == '\\') + { + if (len >= 2 && + instr[1] == '\\') + { + if (pair_first) + goto invalid_pair; + appendStringInfoChar(&str, '\\'); + instr += 2; + len -= 2; + } + else if ((len >= 5 && isxdigits_n(instr + 1, 4)) || + (len >= 6 && instr[1] == 'u' && isxdigits_n(instr + 2, 4))) + { + pg_wchar unicode; + int offset = instr[1] == 'u' ? 2 : 1; + + unicode = hexval_n(instr + offset, 4); + + if (!is_valid_unicode_codepoint(unicode)) + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid Unicode code point: %04X", unicode)); + + if (pair_first) + { + if (is_utf16_surrogate_second(unicode)) + { + unicode = surrogate_pair_to_codepoint(pair_first, unicode); + pair_first = 0; + } + else + goto invalid_pair; + } + else if (is_utf16_surrogate_second(unicode)) + goto invalid_pair; + + if (is_utf16_surrogate_first(unicode)) + pair_first = unicode; + else + { + pg_unicode_to_server(unicode, (unsigned char *) cbuf); + appendStringInfoString(&str, cbuf); + } + + instr += 4 + offset; + len -= 4 + offset; + } + else if (len >= 8 && instr[1] == '+' && isxdigits_n(instr + 2, 6)) + { + pg_wchar unicode; + + unicode = hexval_n(instr + 2, 6); + + if (!is_valid_unicode_codepoint(unicode)) + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid Unicode code point: %04X", unicode)); + + if (pair_first) + { + if (is_utf16_surrogate_second(unicode)) + { + unicode = surrogate_pair_to_codepoint(pair_first, unicode); + pair_first = 0; + } + else + goto invalid_pair; + } + else if (is_utf16_surrogate_second(unicode)) + goto invalid_pair; + + if (is_utf16_surrogate_first(unicode)) + pair_first = unicode; + else + { + pg_unicode_to_server(unicode, (unsigned char *) cbuf); + appendStringInfoString(&str, cbuf); + } + + instr += 8; + len -= 8; + } + else if (len >= 10 && instr[1] == 'U' && isxdigits_n(instr + 2, 8)) + { + pg_wchar unicode; + + unicode = hexval_n(instr + 2, 8); + + if (!is_valid_unicode_codepoint(unicode)) + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid Unicode code point: %04X", unicode)); + + if (pair_first) + { + if (is_utf16_surrogate_second(unicode)) + { + unicode = surrogate_pair_to_codepoint(pair_first, unicode); + pair_first = 0; + } + else + goto invalid_pair; + } + else if (is_utf16_surrogate_second(unicode)) + goto invalid_pair; + + if (is_utf16_surrogate_first(unicode)) + pair_first = unicode; + else + { + pg_unicode_to_server(unicode, (unsigned char *) cbuf); + appendStringInfoString(&str, cbuf); + } + + instr += 10; + len -= 10; + } + else + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid Unicode escape"), + errhint("Unicode escapes must be \\XXXX, \\+XXXXXX, \\uXXXX, or \\UXXXXXXXX."))); + } + else + { + if (pair_first) + goto invalid_pair; + + appendStringInfoChar(&str, *instr++); + len--; + } + } + + /* unfinished surrogate pair? */ + if (pair_first) + goto invalid_pair; + + result = cstring_to_text_with_len(str.data, str.len); + pfree(str.data); + + PG_RETURN_TEXT_P(result); + +invalid_pair: + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid Unicode surrogate pair"))); + PG_RETURN_NULL(); /* keep compiler quiet */ +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/version.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/version.c new file mode 100644 index 00000000000..30edac59302 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/version.c @@ -0,0 +1,24 @@ +/*------------------------------------------------------------------------- + * + * version.c + * Returns the PostgreSQL version string + * + * Copyright (c) 1998-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * + * src/backend/utils/adt/version.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "utils/builtins.h" + + +Datum +pgsql_version(PG_FUNCTION_ARGS) +{ + PG_RETURN_TEXT_P(cstring_to_text(PG_VERSION_STR)); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/windowfuncs.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/windowfuncs.c new file mode 100644 index 00000000000..0c7cc55845a --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/windowfuncs.c @@ -0,0 +1,732 @@ +/*------------------------------------------------------------------------- + * + * windowfuncs.c + * Standard window functions defined in SQL spec. + * + * Portions Copyright (c) 2000-2023, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/utils/adt/windowfuncs.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "nodes/supportnodes.h" +#include "optimizer/optimizer.h" +#include "utils/builtins.h" +#include "windowapi.h" + +/* + * ranking process information + */ +typedef struct rank_context +{ + int64 rank; /* current rank */ +} rank_context; + +/* + * ntile process information + */ +typedef struct +{ + int32 ntile; /* current result */ + int64 rows_per_bucket; /* row number of current bucket */ + int64 boundary; /* how many rows should be in the bucket */ + int64 remainder; /* (total rows) % (bucket num) */ +} ntile_context; + +static bool rank_up(WindowObject winobj); +static Datum leadlag_common(FunctionCallInfo fcinfo, + bool forward, bool withoffset, bool withdefault); + + +/* + * utility routine for *_rank functions. + */ +static bool +rank_up(WindowObject winobj) +{ + bool up = false; /* should rank increase? */ + int64 curpos = WinGetCurrentPosition(winobj); + rank_context *context; + + context = (rank_context *) + WinGetPartitionLocalMemory(winobj, sizeof(rank_context)); + + if (context->rank == 0) + { + /* first call: rank of first row is always 1 */ + Assert(curpos == 0); + context->rank = 1; + } + else + { + Assert(curpos > 0); + /* do current and prior tuples match by ORDER BY clause? */ + if (!WinRowsArePeers(winobj, curpos - 1, curpos)) + up = true; + } + + /* We can advance the mark, but only *after* access to prior row */ + WinSetMarkPosition(winobj, curpos); + + return up; +} + + +/* + * row_number + * just increment up from 1 until current partition finishes. + */ +Datum +window_row_number(PG_FUNCTION_ARGS) +{ + WindowObject winobj = PG_WINDOW_OBJECT(); + int64 curpos = WinGetCurrentPosition(winobj); + + WinSetMarkPosition(winobj, curpos); + PG_RETURN_INT64(curpos + 1); +} + +/* + * window_row_number_support + * prosupport function for window_row_number() + */ +Datum +window_row_number_support(PG_FUNCTION_ARGS) +{ + Node *rawreq = (Node *) PG_GETARG_POINTER(0); + + if (IsA(rawreq, SupportRequestWFuncMonotonic)) + { + SupportRequestWFuncMonotonic *req = (SupportRequestWFuncMonotonic *) rawreq; + + /* row_number() is monotonically increasing */ + req->monotonic = MONOTONICFUNC_INCREASING; + PG_RETURN_POINTER(req); + } + + if (IsA(rawreq, SupportRequestOptimizeWindowClause)) + { + SupportRequestOptimizeWindowClause *req = (SupportRequestOptimizeWindowClause *) rawreq; + + /* + * The frame options can always become "ROWS BETWEEN UNBOUNDED + * PRECEDING AND CURRENT ROW". row_number() always just increments by + * 1 with each row in the partition. Using ROWS instead of RANGE + * saves effort checking peer rows during execution. + */ + req->frameOptions = (FRAMEOPTION_NONDEFAULT | + FRAMEOPTION_ROWS | + FRAMEOPTION_START_UNBOUNDED_PRECEDING | + FRAMEOPTION_END_CURRENT_ROW); + + PG_RETURN_POINTER(req); + } + + PG_RETURN_POINTER(NULL); +} + +/* + * rank + * Rank changes when key columns change. + * The new rank number is the current row number. + */ +Datum +window_rank(PG_FUNCTION_ARGS) +{ + WindowObject winobj = PG_WINDOW_OBJECT(); + rank_context *context; + bool up; + + up = rank_up(winobj); + context = (rank_context *) + WinGetPartitionLocalMemory(winobj, sizeof(rank_context)); + if (up) + context->rank = WinGetCurrentPosition(winobj) + 1; + + PG_RETURN_INT64(context->rank); +} + +/* + * window_rank_support + * prosupport function for window_rank() + */ +Datum +window_rank_support(PG_FUNCTION_ARGS) +{ + Node *rawreq = (Node *) PG_GETARG_POINTER(0); + + if (IsA(rawreq, SupportRequestWFuncMonotonic)) + { + SupportRequestWFuncMonotonic *req = (SupportRequestWFuncMonotonic *) rawreq; + + /* rank() is monotonically increasing */ + req->monotonic = MONOTONICFUNC_INCREASING; + PG_RETURN_POINTER(req); + } + + if (IsA(rawreq, SupportRequestOptimizeWindowClause)) + { + SupportRequestOptimizeWindowClause *req = (SupportRequestOptimizeWindowClause *) rawreq; + + /* + * rank() is coded in such a way that it returns "(COUNT (*) OVER + * (<opt> RANGE UNBOUNDED PRECEDING) - COUNT (*) OVER (<opt> RANGE + * CURRENT ROW) + 1)" regardless of the frame options. We'll set the + * frame options to "ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW" + * so they agree with what window_row_number_support() optimized the + * frame options to be. Using ROWS instead of RANGE saves from doing + * peer row checks during execution. + */ + req->frameOptions = (FRAMEOPTION_NONDEFAULT | + FRAMEOPTION_ROWS | + FRAMEOPTION_START_UNBOUNDED_PRECEDING | + FRAMEOPTION_END_CURRENT_ROW); + + PG_RETURN_POINTER(req); + } + + PG_RETURN_POINTER(NULL); +} + +/* + * dense_rank + * Rank increases by 1 when key columns change. + */ +Datum +window_dense_rank(PG_FUNCTION_ARGS) +{ + WindowObject winobj = PG_WINDOW_OBJECT(); + rank_context *context; + bool up; + + up = rank_up(winobj); + context = (rank_context *) + WinGetPartitionLocalMemory(winobj, sizeof(rank_context)); + if (up) + context->rank++; + + PG_RETURN_INT64(context->rank); +} + +/* + * window_dense_rank_support + * prosupport function for window_dense_rank() + */ +Datum +window_dense_rank_support(PG_FUNCTION_ARGS) +{ + Node *rawreq = (Node *) PG_GETARG_POINTER(0); + + if (IsA(rawreq, SupportRequestWFuncMonotonic)) + { + SupportRequestWFuncMonotonic *req = (SupportRequestWFuncMonotonic *) rawreq; + + /* dense_rank() is monotonically increasing */ + req->monotonic = MONOTONICFUNC_INCREASING; + PG_RETURN_POINTER(req); + } + + if (IsA(rawreq, SupportRequestOptimizeWindowClause)) + { + SupportRequestOptimizeWindowClause *req = (SupportRequestOptimizeWindowClause *) rawreq; + + /* + * dense_rank() is unaffected by the frame options. Here we set the + * frame options to match what's done in row_number's support + * function. Using ROWS instead of RANGE (the default) saves the + * executor from having to check for peer rows. + */ + req->frameOptions = (FRAMEOPTION_NONDEFAULT | + FRAMEOPTION_ROWS | + FRAMEOPTION_START_UNBOUNDED_PRECEDING | + FRAMEOPTION_END_CURRENT_ROW); + + PG_RETURN_POINTER(req); + } + + PG_RETURN_POINTER(NULL); +} + +/* + * percent_rank + * return fraction between 0 and 1 inclusive, + * which is described as (RK - 1) / (NR - 1), where RK is the current row's + * rank and NR is the total number of rows, per spec. + */ +Datum +window_percent_rank(PG_FUNCTION_ARGS) +{ + WindowObject winobj = PG_WINDOW_OBJECT(); + rank_context *context; + bool up; + int64 totalrows = WinGetPartitionRowCount(winobj); + + Assert(totalrows > 0); + + up = rank_up(winobj); + context = (rank_context *) + WinGetPartitionLocalMemory(winobj, sizeof(rank_context)); + if (up) + context->rank = WinGetCurrentPosition(winobj) + 1; + + /* return zero if there's only one row, per spec */ + if (totalrows <= 1) + PG_RETURN_FLOAT8(0.0); + + PG_RETURN_FLOAT8((float8) (context->rank - 1) / (float8) (totalrows - 1)); +} + +/* + * window_percent_rank_support + * prosupport function for window_percent_rank() + */ +Datum +window_percent_rank_support(PG_FUNCTION_ARGS) +{ + Node *rawreq = (Node *) PG_GETARG_POINTER(0); + + if (IsA(rawreq, SupportRequestWFuncMonotonic)) + { + SupportRequestWFuncMonotonic *req = (SupportRequestWFuncMonotonic *) rawreq; + + /* percent_rank() is monotonically increasing */ + req->monotonic = MONOTONICFUNC_INCREASING; + PG_RETURN_POINTER(req); + } + + if (IsA(rawreq, SupportRequestOptimizeWindowClause)) + { + SupportRequestOptimizeWindowClause *req = (SupportRequestOptimizeWindowClause *) rawreq; + + /* + * percent_rank() is unaffected by the frame options. Here we set the + * frame options to match what's done in row_number's support + * function. Using ROWS instead of RANGE (the default) saves the + * executor from having to check for peer rows. + */ + req->frameOptions = (FRAMEOPTION_NONDEFAULT | + FRAMEOPTION_ROWS | + FRAMEOPTION_START_UNBOUNDED_PRECEDING | + FRAMEOPTION_END_CURRENT_ROW); + + PG_RETURN_POINTER(req); + } + + PG_RETURN_POINTER(NULL); +} + + +/* + * cume_dist + * return fraction between 0 and 1 inclusive, + * which is described as NP / NR, where NP is the number of rows preceding or + * peers to the current row, and NR is the total number of rows, per spec. + */ +Datum +window_cume_dist(PG_FUNCTION_ARGS) +{ + WindowObject winobj = PG_WINDOW_OBJECT(); + rank_context *context; + bool up; + int64 totalrows = WinGetPartitionRowCount(winobj); + + Assert(totalrows > 0); + + up = rank_up(winobj); + context = (rank_context *) + WinGetPartitionLocalMemory(winobj, sizeof(rank_context)); + if (up || context->rank == 1) + { + /* + * The current row is not peer to prior row or is just the first, so + * count up the number of rows that are peer to the current. + */ + int64 row; + + context->rank = WinGetCurrentPosition(winobj) + 1; + + /* + * start from current + 1 + */ + for (row = context->rank; row < totalrows; row++) + { + if (!WinRowsArePeers(winobj, row - 1, row)) + break; + context->rank++; + } + } + + PG_RETURN_FLOAT8((float8) context->rank / (float8) totalrows); +} + +/* + * window_cume_dist_support + * prosupport function for window_cume_dist() + */ +Datum +window_cume_dist_support(PG_FUNCTION_ARGS) +{ + Node *rawreq = (Node *) PG_GETARG_POINTER(0); + + if (IsA(rawreq, SupportRequestWFuncMonotonic)) + { + SupportRequestWFuncMonotonic *req = (SupportRequestWFuncMonotonic *) rawreq; + + /* cume_dist() is monotonically increasing */ + req->monotonic = MONOTONICFUNC_INCREASING; + PG_RETURN_POINTER(req); + } + + if (IsA(rawreq, SupportRequestOptimizeWindowClause)) + { + SupportRequestOptimizeWindowClause *req = (SupportRequestOptimizeWindowClause *) rawreq; + + /* + * cume_dist() is unaffected by the frame options. Here we set the + * frame options to match what's done in row_number's support + * function. Using ROWS instead of RANGE (the default) saves the + * executor from having to check for peer rows. + */ + req->frameOptions = (FRAMEOPTION_NONDEFAULT | + FRAMEOPTION_ROWS | + FRAMEOPTION_START_UNBOUNDED_PRECEDING | + FRAMEOPTION_END_CURRENT_ROW); + + PG_RETURN_POINTER(req); + } + + PG_RETURN_POINTER(NULL); +} + +/* + * ntile + * compute an exact numeric value with scale 0 (zero), + * ranging from 1 (one) to n, per spec. + */ +Datum +window_ntile(PG_FUNCTION_ARGS) +{ + WindowObject winobj = PG_WINDOW_OBJECT(); + ntile_context *context; + + context = (ntile_context *) + WinGetPartitionLocalMemory(winobj, sizeof(ntile_context)); + + if (context->ntile == 0) + { + /* first call */ + int64 total; + int32 nbuckets; + bool isnull; + + total = WinGetPartitionRowCount(winobj); + nbuckets = DatumGetInt32(WinGetFuncArgCurrent(winobj, 0, &isnull)); + + /* + * per spec: If NT is the null value, then the result is the null + * value. + */ + if (isnull) + PG_RETURN_NULL(); + + /* + * per spec: If NT is less than or equal to 0 (zero), then an + * exception condition is raised. + */ + if (nbuckets <= 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_ARGUMENT_FOR_NTILE), + errmsg("argument of ntile must be greater than zero"))); + + context->ntile = 1; + context->rows_per_bucket = 0; + context->boundary = total / nbuckets; + if (context->boundary <= 0) + context->boundary = 1; + else + { + /* + * If the total number is not divisible, add 1 row to leading + * buckets. + */ + context->remainder = total % nbuckets; + if (context->remainder != 0) + context->boundary++; + } + } + + context->rows_per_bucket++; + if (context->boundary < context->rows_per_bucket) + { + /* ntile up */ + if (context->remainder != 0 && context->ntile == context->remainder) + { + context->remainder = 0; + context->boundary -= 1; + } + context->ntile += 1; + context->rows_per_bucket = 1; + } + + PG_RETURN_INT32(context->ntile); +} + +/* + * window_ntile_support + * prosupport function for window_ntile() + */ +Datum +window_ntile_support(PG_FUNCTION_ARGS) +{ + Node *rawreq = (Node *) PG_GETARG_POINTER(0); + + if (IsA(rawreq, SupportRequestWFuncMonotonic)) + { + SupportRequestWFuncMonotonic *req = (SupportRequestWFuncMonotonic *) rawreq; + WindowFunc *wfunc = req->window_func; + + if (list_length(wfunc->args) == 1) + { + Node *expr = eval_const_expressions(NULL, linitial(wfunc->args)); + + /* + * Due to the Node representation of WindowClause runConditions in + * version prior to v17, we need to insist that ntile arg is Const + * to allow safe application of the runCondition optimization. + */ + if (IsA(expr, Const)) + { + /* + * ntile() is monotonically increasing as the number of + * buckets cannot change after the first call + */ + req->monotonic = MONOTONICFUNC_INCREASING; + PG_RETURN_POINTER(req); + } + } + + PG_RETURN_POINTER(NULL); + } + + if (IsA(rawreq, SupportRequestOptimizeWindowClause)) + { + SupportRequestOptimizeWindowClause *req = (SupportRequestOptimizeWindowClause *) rawreq; + + /* + * ntile() is unaffected by the frame options. Here we set the frame + * options to match what's done in row_number's support function. + * Using ROWS instead of RANGE (the default) saves the executor from + * having to check for peer rows. + */ + req->frameOptions = (FRAMEOPTION_NONDEFAULT | + FRAMEOPTION_ROWS | + FRAMEOPTION_START_UNBOUNDED_PRECEDING | + FRAMEOPTION_END_CURRENT_ROW); + + PG_RETURN_POINTER(req); + } + + PG_RETURN_POINTER(NULL); +} + +/* + * leadlag_common + * common operation of lead() and lag() + * For lead() forward is true, whereas for lag() it is false. + * withoffset indicates we have an offset second argument. + * withdefault indicates we have a default third argument. + */ +static Datum +leadlag_common(FunctionCallInfo fcinfo, + bool forward, bool withoffset, bool withdefault) +{ + WindowObject winobj = PG_WINDOW_OBJECT(); + int32 offset; + bool const_offset; + Datum result; + bool isnull; + bool isout; + + if (withoffset) + { + offset = DatumGetInt32(WinGetFuncArgCurrent(winobj, 1, &isnull)); + if (isnull) + PG_RETURN_NULL(); + const_offset = get_fn_expr_arg_stable(fcinfo->flinfo, 1); + } + else + { + offset = 1; + const_offset = true; + } + + result = WinGetFuncArgInPartition(winobj, 0, + (forward ? offset : -offset), + WINDOW_SEEK_CURRENT, + const_offset, + &isnull, &isout); + + if (isout) + { + /* + * target row is out of the partition; supply default value if + * provided. otherwise it'll stay NULL + */ + if (withdefault) + result = WinGetFuncArgCurrent(winobj, 2, &isnull); + } + + if (isnull) + PG_RETURN_NULL(); + + PG_RETURN_DATUM(result); +} + +/* + * lag + * returns the value of VE evaluated on a row that is 1 + * row before the current row within a partition, + * per spec. + */ +Datum +window_lag(PG_FUNCTION_ARGS) +{ + return leadlag_common(fcinfo, false, false, false); +} + +/* + * lag_with_offset + * returns the value of VE evaluated on a row that is OFFSET + * rows before the current row within a partition, + * per spec. + */ +Datum +window_lag_with_offset(PG_FUNCTION_ARGS) +{ + return leadlag_common(fcinfo, false, true, false); +} + +/* + * lag_with_offset_and_default + * same as lag_with_offset but accepts default value + * as its third argument. + */ +Datum +window_lag_with_offset_and_default(PG_FUNCTION_ARGS) +{ + return leadlag_common(fcinfo, false, true, true); +} + +/* + * lead + * returns the value of VE evaluated on a row that is 1 + * row after the current row within a partition, + * per spec. + */ +Datum +window_lead(PG_FUNCTION_ARGS) +{ + return leadlag_common(fcinfo, true, false, false); +} + +/* + * lead_with_offset + * returns the value of VE evaluated on a row that is OFFSET + * number of rows after the current row within a partition, + * per spec. + */ +Datum +window_lead_with_offset(PG_FUNCTION_ARGS) +{ + return leadlag_common(fcinfo, true, true, false); +} + +/* + * lead_with_offset_and_default + * same as lead_with_offset but accepts default value + * as its third argument. + */ +Datum +window_lead_with_offset_and_default(PG_FUNCTION_ARGS) +{ + return leadlag_common(fcinfo, true, true, true); +} + +/* + * first_value + * return the value of VE evaluated on the first row of the + * window frame, per spec. + */ +Datum +window_first_value(PG_FUNCTION_ARGS) +{ + WindowObject winobj = PG_WINDOW_OBJECT(); + Datum result; + bool isnull; + + result = WinGetFuncArgInFrame(winobj, 0, + 0, WINDOW_SEEK_HEAD, true, + &isnull, NULL); + if (isnull) + PG_RETURN_NULL(); + + PG_RETURN_DATUM(result); +} + +/* + * last_value + * return the value of VE evaluated on the last row of the + * window frame, per spec. + */ +Datum +window_last_value(PG_FUNCTION_ARGS) +{ + WindowObject winobj = PG_WINDOW_OBJECT(); + Datum result; + bool isnull; + + result = WinGetFuncArgInFrame(winobj, 0, + 0, WINDOW_SEEK_TAIL, true, + &isnull, NULL); + if (isnull) + PG_RETURN_NULL(); + + PG_RETURN_DATUM(result); +} + +/* + * nth_value + * return the value of VE evaluated on the n-th row from the first + * row of the window frame, per spec. + */ +Datum +window_nth_value(PG_FUNCTION_ARGS) +{ + WindowObject winobj = PG_WINDOW_OBJECT(); + bool const_offset; + Datum result; + bool isnull; + int32 nth; + + nth = DatumGetInt32(WinGetFuncArgCurrent(winobj, 1, &isnull)); + if (isnull) + PG_RETURN_NULL(); + const_offset = get_fn_expr_arg_stable(fcinfo->flinfo, 1); + + if (nth <= 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_ARGUMENT_FOR_NTH_VALUE), + errmsg("argument of nth_value must be greater than zero"))); + + result = WinGetFuncArgInFrame(winobj, 0, + nth - 1, WINDOW_SEEK_HEAD, const_offset, + &isnull, NULL); + if (isnull) + PG_RETURN_NULL(); + + PG_RETURN_DATUM(result); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/xid.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/xid.c new file mode 100644 index 00000000000..8ac1679c381 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/xid.c @@ -0,0 +1,379 @@ +/*------------------------------------------------------------------------- + * + * xid.c + * POSTGRES transaction identifier and command identifier datatypes. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/adt/xid.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <limits.h> + +#include "access/multixact.h" +#include "access/transam.h" +#include "access/xact.h" +#include "libpq/pqformat.h" +#include "utils/builtins.h" +#include "utils/xid8.h" + +#define PG_GETARG_COMMANDID(n) DatumGetCommandId(PG_GETARG_DATUM(n)) +#define PG_RETURN_COMMANDID(x) return CommandIdGetDatum(x) + + +Datum +xidin(PG_FUNCTION_ARGS) +{ + char *str = PG_GETARG_CSTRING(0); + TransactionId result; + + result = uint32in_subr(str, NULL, "xid", fcinfo->context); + PG_RETURN_TRANSACTIONID(result); +} + +Datum +xidout(PG_FUNCTION_ARGS) +{ + TransactionId transactionId = PG_GETARG_TRANSACTIONID(0); + char *result = (char *) palloc(16); + + snprintf(result, 16, "%lu", (unsigned long) transactionId); + PG_RETURN_CSTRING(result); +} + +/* + * xidrecv - converts external binary format to xid + */ +Datum +xidrecv(PG_FUNCTION_ARGS) +{ + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + + PG_RETURN_TRANSACTIONID((TransactionId) pq_getmsgint(buf, sizeof(TransactionId))); +} + +/* + * xidsend - converts xid to binary format + */ +Datum +xidsend(PG_FUNCTION_ARGS) +{ + TransactionId arg1 = PG_GETARG_TRANSACTIONID(0); + StringInfoData buf; + + pq_begintypsend(&buf); + pq_sendint32(&buf, arg1); + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} + +/* + * xideq - are two xids equal? + */ +Datum +xideq(PG_FUNCTION_ARGS) +{ + TransactionId xid1 = PG_GETARG_TRANSACTIONID(0); + TransactionId xid2 = PG_GETARG_TRANSACTIONID(1); + + PG_RETURN_BOOL(TransactionIdEquals(xid1, xid2)); +} + +/* + * xidneq - are two xids different? + */ +Datum +xidneq(PG_FUNCTION_ARGS) +{ + TransactionId xid1 = PG_GETARG_TRANSACTIONID(0); + TransactionId xid2 = PG_GETARG_TRANSACTIONID(1); + + PG_RETURN_BOOL(!TransactionIdEquals(xid1, xid2)); +} + +/* + * xid_age - compute age of an XID (relative to latest stable xid) + */ +Datum +xid_age(PG_FUNCTION_ARGS) +{ + TransactionId xid = PG_GETARG_TRANSACTIONID(0); + TransactionId now = GetStableLatestTransactionId(); + + /* Permanent XIDs are always infinitely old */ + if (!TransactionIdIsNormal(xid)) + PG_RETURN_INT32(INT_MAX); + + PG_RETURN_INT32((int32) (now - xid)); +} + +/* + * mxid_age - compute age of a multi XID (relative to latest stable mxid) + */ +Datum +mxid_age(PG_FUNCTION_ARGS) +{ + TransactionId xid = PG_GETARG_TRANSACTIONID(0); + MultiXactId now = ReadNextMultiXactId(); + + if (!MultiXactIdIsValid(xid)) + PG_RETURN_INT32(INT_MAX); + + PG_RETURN_INT32((int32) (now - xid)); +} + +/* + * xidComparator + * qsort comparison function for XIDs + * + * We can't use wraparound comparison for XIDs because that does not respect + * the triangle inequality! Any old sort order will do. + */ +int +xidComparator(const void *arg1, const void *arg2) +{ + TransactionId xid1 = *(const TransactionId *) arg1; + TransactionId xid2 = *(const TransactionId *) arg2; + + if (xid1 > xid2) + return 1; + if (xid1 < xid2) + return -1; + return 0; +} + +/* + * xidLogicalComparator + * qsort comparison function for XIDs + * + * This is used to compare only XIDs from the same epoch (e.g. for backends + * running at the same time). So there must be only normal XIDs, so there's + * no issue with triangle inequality. + */ +int +xidLogicalComparator(const void *arg1, const void *arg2) +{ + TransactionId xid1 = *(const TransactionId *) arg1; + TransactionId xid2 = *(const TransactionId *) arg2; + + Assert(TransactionIdIsNormal(xid1)); + Assert(TransactionIdIsNormal(xid2)); + + if (TransactionIdPrecedes(xid1, xid2)) + return -1; + + if (TransactionIdPrecedes(xid2, xid1)) + return 1; + + return 0; +} + +Datum +xid8toxid(PG_FUNCTION_ARGS) +{ + FullTransactionId fxid = PG_GETARG_FULLTRANSACTIONID(0); + + PG_RETURN_TRANSACTIONID(XidFromFullTransactionId(fxid)); +} + +Datum +xid8in(PG_FUNCTION_ARGS) +{ + char *str = PG_GETARG_CSTRING(0); + uint64 result; + + result = uint64in_subr(str, NULL, "xid8", fcinfo->context); + PG_RETURN_FULLTRANSACTIONID(FullTransactionIdFromU64(result)); +} + +Datum +xid8out(PG_FUNCTION_ARGS) +{ + FullTransactionId fxid = PG_GETARG_FULLTRANSACTIONID(0); + char *result = (char *) palloc(21); + + snprintf(result, 21, UINT64_FORMAT, U64FromFullTransactionId(fxid)); + PG_RETURN_CSTRING(result); +} + +Datum +xid8recv(PG_FUNCTION_ARGS) +{ + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + uint64 value; + + value = (uint64) pq_getmsgint64(buf); + PG_RETURN_FULLTRANSACTIONID(FullTransactionIdFromU64(value)); +} + +Datum +xid8send(PG_FUNCTION_ARGS) +{ + FullTransactionId arg1 = PG_GETARG_FULLTRANSACTIONID(0); + StringInfoData buf; + + pq_begintypsend(&buf); + pq_sendint64(&buf, (uint64) U64FromFullTransactionId(arg1)); + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} + +Datum +xid8eq(PG_FUNCTION_ARGS) +{ + FullTransactionId fxid1 = PG_GETARG_FULLTRANSACTIONID(0); + FullTransactionId fxid2 = PG_GETARG_FULLTRANSACTIONID(1); + + PG_RETURN_BOOL(FullTransactionIdEquals(fxid1, fxid2)); +} + +Datum +xid8ne(PG_FUNCTION_ARGS) +{ + FullTransactionId fxid1 = PG_GETARG_FULLTRANSACTIONID(0); + FullTransactionId fxid2 = PG_GETARG_FULLTRANSACTIONID(1); + + PG_RETURN_BOOL(!FullTransactionIdEquals(fxid1, fxid2)); +} + +Datum +xid8lt(PG_FUNCTION_ARGS) +{ + FullTransactionId fxid1 = PG_GETARG_FULLTRANSACTIONID(0); + FullTransactionId fxid2 = PG_GETARG_FULLTRANSACTIONID(1); + + PG_RETURN_BOOL(FullTransactionIdPrecedes(fxid1, fxid2)); +} + +Datum +xid8gt(PG_FUNCTION_ARGS) +{ + FullTransactionId fxid1 = PG_GETARG_FULLTRANSACTIONID(0); + FullTransactionId fxid2 = PG_GETARG_FULLTRANSACTIONID(1); + + PG_RETURN_BOOL(FullTransactionIdFollows(fxid1, fxid2)); +} + +Datum +xid8le(PG_FUNCTION_ARGS) +{ + FullTransactionId fxid1 = PG_GETARG_FULLTRANSACTIONID(0); + FullTransactionId fxid2 = PG_GETARG_FULLTRANSACTIONID(1); + + PG_RETURN_BOOL(FullTransactionIdPrecedesOrEquals(fxid1, fxid2)); +} + +Datum +xid8ge(PG_FUNCTION_ARGS) +{ + FullTransactionId fxid1 = PG_GETARG_FULLTRANSACTIONID(0); + FullTransactionId fxid2 = PG_GETARG_FULLTRANSACTIONID(1); + + PG_RETURN_BOOL(FullTransactionIdFollowsOrEquals(fxid1, fxid2)); +} + +Datum +xid8cmp(PG_FUNCTION_ARGS) +{ + FullTransactionId fxid1 = PG_GETARG_FULLTRANSACTIONID(0); + FullTransactionId fxid2 = PG_GETARG_FULLTRANSACTIONID(1); + + if (FullTransactionIdFollows(fxid1, fxid2)) + PG_RETURN_INT32(1); + else if (FullTransactionIdEquals(fxid1, fxid2)) + PG_RETURN_INT32(0); + else + PG_RETURN_INT32(-1); +} + +Datum +xid8_larger(PG_FUNCTION_ARGS) +{ + FullTransactionId fxid1 = PG_GETARG_FULLTRANSACTIONID(0); + FullTransactionId fxid2 = PG_GETARG_FULLTRANSACTIONID(1); + + if (FullTransactionIdFollows(fxid1, fxid2)) + PG_RETURN_FULLTRANSACTIONID(fxid1); + else + PG_RETURN_FULLTRANSACTIONID(fxid2); +} + +Datum +xid8_smaller(PG_FUNCTION_ARGS) +{ + FullTransactionId fxid1 = PG_GETARG_FULLTRANSACTIONID(0); + FullTransactionId fxid2 = PG_GETARG_FULLTRANSACTIONID(1); + + if (FullTransactionIdPrecedes(fxid1, fxid2)) + PG_RETURN_FULLTRANSACTIONID(fxid1); + else + PG_RETURN_FULLTRANSACTIONID(fxid2); +} + +/***************************************************************************** + * COMMAND IDENTIFIER ROUTINES * + *****************************************************************************/ + +/* + * cidin - converts CommandId to internal representation. + */ +Datum +cidin(PG_FUNCTION_ARGS) +{ + char *str = PG_GETARG_CSTRING(0); + CommandId result; + + result = uint32in_subr(str, NULL, "cid", fcinfo->context); + PG_RETURN_COMMANDID(result); +} + +/* + * cidout - converts a cid to external representation. + */ +Datum +cidout(PG_FUNCTION_ARGS) +{ + CommandId c = PG_GETARG_COMMANDID(0); + char *result = (char *) palloc(16); + + snprintf(result, 16, "%lu", (unsigned long) c); + PG_RETURN_CSTRING(result); +} + +/* + * cidrecv - converts external binary format to cid + */ +Datum +cidrecv(PG_FUNCTION_ARGS) +{ + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + + PG_RETURN_COMMANDID((CommandId) pq_getmsgint(buf, sizeof(CommandId))); +} + +/* + * cidsend - converts cid to binary format + */ +Datum +cidsend(PG_FUNCTION_ARGS) +{ + CommandId arg1 = PG_GETARG_COMMANDID(0); + StringInfoData buf; + + pq_begintypsend(&buf); + pq_sendint32(&buf, arg1); + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} + +Datum +cideq(PG_FUNCTION_ARGS) +{ + CommandId arg1 = PG_GETARG_COMMANDID(0); + CommandId arg2 = PG_GETARG_COMMANDID(1); + + PG_RETURN_BOOL(arg1 == arg2); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/xid8funcs.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/xid8funcs.c new file mode 100644 index 00000000000..6fbfb3a1cc2 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/xid8funcs.c @@ -0,0 +1,716 @@ +/*------------------------------------------------------------------------- + * xid8funcs.c + * + * Export internal transaction IDs to user level. + * + * Note that only top-level transaction IDs are exposed to user sessions. + * This is important because xid8s frequently persist beyond the global + * xmin horizon, or may even be shipped to other machines, so we cannot + * rely on being able to correlate subtransaction IDs with their parents + * via functions such as SubTransGetTopmostTransaction(). + * + * These functions are used to support the txid_XXX functions and the newer + * pg_current_xact_id, pg_current_snapshot and related fmgr functions, since + * the only difference between them is whether they expose xid8 or int8 values + * to users. The txid_XXX variants should eventually be dropped. + * + * + * Copyright (c) 2003-2023, PostgreSQL Global Development Group + * Author: Jan Wieck, Afilias USA INC. + * 64-bit txids: Marko Kreen, Skype Technologies + * + * src/backend/utils/adt/xid8funcs.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/clog.h" +#include "access/transam.h" +#include "access/xact.h" +#include "access/xlog.h" +#include "funcapi.h" +#include "lib/qunique.h" +#include "libpq/pqformat.h" +#include "miscadmin.h" +#include "postmaster/postmaster.h" +#include "storage/lwlock.h" +#include "storage/procarray.h" +#include "utils/builtins.h" +#include "utils/memutils.h" +#include "utils/snapmgr.h" +#include "utils/xid8.h" + + +/* + * If defined, use bsearch() function for searching for xid8s in snapshots + * that have more than the specified number of values. + */ +#define USE_BSEARCH_IF_NXIP_GREATER 30 + + +/* + * Snapshot containing FullTransactionIds. + */ +typedef struct +{ + /* + * 4-byte length hdr, should not be touched directly. + * + * Explicit embedding is ok as we want always correct alignment anyway. + */ + int32 __varsz; + + uint32 nxip; /* number of fxids in xip array */ + FullTransactionId xmin; + FullTransactionId xmax; + /* in-progress fxids, xmin <= xip[i] < xmax: */ + FullTransactionId xip[FLEXIBLE_ARRAY_MEMBER]; +} pg_snapshot; + +#define PG_SNAPSHOT_SIZE(nxip) \ + (offsetof(pg_snapshot, xip) + sizeof(FullTransactionId) * (nxip)) +#define PG_SNAPSHOT_MAX_NXIP \ + ((MaxAllocSize - offsetof(pg_snapshot, xip)) / sizeof(FullTransactionId)) + +/* + * Compile-time limits on the procarray (MAX_BACKENDS processes plus + * MAX_BACKENDS prepared transactions) guarantee nxip won't be too large. + */ +StaticAssertDecl(MAX_BACKENDS * 2 <= PG_SNAPSHOT_MAX_NXIP, + "possible overflow in pg_current_snapshot()"); + + +/* + * Helper to get a TransactionId from a 64-bit xid with wraparound detection. + * + * It is an ERROR if the xid is in the future. Otherwise, returns true if + * the transaction is still new enough that we can determine whether it + * committed and false otherwise. If *extracted_xid is not NULL, it is set + * to the low 32 bits of the transaction ID (i.e. the actual XID, without the + * epoch). + * + * The caller must hold XactTruncationLock since it's dealing with arbitrary + * XIDs, and must continue to hold it until it's done with any clog lookups + * relating to those XIDs. + */ +static bool +TransactionIdInRecentPast(FullTransactionId fxid, TransactionId *extracted_xid) +{ + TransactionId xid = XidFromFullTransactionId(fxid); + uint32 now_epoch; + TransactionId now_epoch_next_xid; + FullTransactionId now_fullxid; + TransactionId oldest_xid; + FullTransactionId oldest_fxid; + + now_fullxid = ReadNextFullTransactionId(); + now_epoch_next_xid = XidFromFullTransactionId(now_fullxid); + now_epoch = EpochFromFullTransactionId(now_fullxid); + + if (extracted_xid != NULL) + *extracted_xid = xid; + + if (!TransactionIdIsValid(xid)) + return false; + + /* For non-normal transaction IDs, we can ignore the epoch. */ + if (!TransactionIdIsNormal(xid)) + return true; + + /* If the transaction ID is in the future, throw an error. */ + if (!FullTransactionIdPrecedes(fxid, now_fullxid)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("transaction ID %llu is in the future", + (unsigned long long) U64FromFullTransactionId(fxid)))); + + /* + * ShmemVariableCache->oldestClogXid is protected by XactTruncationLock, + * but we don't acquire that lock here. Instead, we require the caller to + * acquire it, because the caller is presumably going to look up the + * returned XID. If we took and released the lock within this function, a + * CLOG truncation could occur before the caller finished with the XID. + */ + Assert(LWLockHeldByMe(XactTruncationLock)); + + /* + * If fxid is not older than ShmemVariableCache->oldestClogXid, the + * relevant CLOG entry is guaranteed to still exist. Convert + * ShmemVariableCache->oldestClogXid into a FullTransactionId to compare + * it with fxid. Determine the right epoch knowing that oldest_fxid + * shouldn't be more than 2^31 older than now_fullxid. + */ + oldest_xid = ShmemVariableCache->oldestClogXid; + Assert(TransactionIdPrecedesOrEquals(oldest_xid, now_epoch_next_xid)); + if (oldest_xid <= now_epoch_next_xid) + { + oldest_fxid = FullTransactionIdFromEpochAndXid(now_epoch, oldest_xid); + } + else + { + Assert(now_epoch > 0); + oldest_fxid = FullTransactionIdFromEpochAndXid(now_epoch - 1, oldest_xid); + } + return !FullTransactionIdPrecedes(fxid, oldest_fxid); +} + +/* + * Convert a TransactionId obtained from a snapshot held by the caller to a + * FullTransactionId. Use next_fxid as a reference FullTransactionId, so that + * we can compute the high order bits. It must have been obtained by the + * caller with ReadNextFullTransactionId() after the snapshot was created. + */ +static FullTransactionId +widen_snapshot_xid(TransactionId xid, FullTransactionId next_fxid) +{ + TransactionId next_xid = XidFromFullTransactionId(next_fxid); + uint32 epoch = EpochFromFullTransactionId(next_fxid); + + /* Special transaction ID. */ + if (!TransactionIdIsNormal(xid)) + return FullTransactionIdFromEpochAndXid(0, xid); + + /* + * The 64 bit result must be <= next_fxid, since next_fxid hadn't been + * issued yet when the snapshot was created. Every TransactionId in the + * snapshot must therefore be from the same epoch as next_fxid, or the + * epoch before. We know this because next_fxid is never allow to get + * more than one epoch ahead of the TransactionIds in any snapshot. + */ + if (xid > next_xid) + epoch--; + + return FullTransactionIdFromEpochAndXid(epoch, xid); +} + +/* + * txid comparator for qsort/bsearch + */ +static int +cmp_fxid(const void *aa, const void *bb) +{ + FullTransactionId a = *(const FullTransactionId *) aa; + FullTransactionId b = *(const FullTransactionId *) bb; + + if (FullTransactionIdPrecedes(a, b)) + return -1; + if (FullTransactionIdPrecedes(b, a)) + return 1; + return 0; +} + +/* + * Sort a snapshot's txids, so we can use bsearch() later. Also remove + * any duplicates. + * + * For consistency of on-disk representation, we always sort even if bsearch + * will not be used. + */ +static void +sort_snapshot(pg_snapshot *snap) +{ + if (snap->nxip > 1) + { + qsort(snap->xip, snap->nxip, sizeof(FullTransactionId), cmp_fxid); + snap->nxip = qunique(snap->xip, snap->nxip, sizeof(FullTransactionId), + cmp_fxid); + } +} + +/* + * check fxid visibility. + */ +static bool +is_visible_fxid(FullTransactionId value, const pg_snapshot *snap) +{ + if (FullTransactionIdPrecedes(value, snap->xmin)) + return true; + else if (!FullTransactionIdPrecedes(value, snap->xmax)) + return false; +#ifdef USE_BSEARCH_IF_NXIP_GREATER + else if (snap->nxip > USE_BSEARCH_IF_NXIP_GREATER) + { + void *res; + + res = bsearch(&value, snap->xip, snap->nxip, sizeof(FullTransactionId), + cmp_fxid); + /* if found, transaction is still in progress */ + return (res) ? false : true; + } +#endif + else + { + uint32 i; + + for (i = 0; i < snap->nxip; i++) + { + if (FullTransactionIdEquals(value, snap->xip[i])) + return false; + } + return true; + } +} + +/* + * helper functions to use StringInfo for pg_snapshot creation. + */ + +static StringInfo +buf_init(FullTransactionId xmin, FullTransactionId xmax) +{ + pg_snapshot snap; + StringInfo buf; + + snap.xmin = xmin; + snap.xmax = xmax; + snap.nxip = 0; + + buf = makeStringInfo(); + appendBinaryStringInfo(buf, &snap, PG_SNAPSHOT_SIZE(0)); + return buf; +} + +static void +buf_add_txid(StringInfo buf, FullTransactionId fxid) +{ + pg_snapshot *snap = (pg_snapshot *) buf->data; + + /* do this before possible realloc */ + snap->nxip++; + + appendBinaryStringInfo(buf, &fxid, sizeof(fxid)); +} + +static pg_snapshot * +buf_finalize(StringInfo buf) +{ + pg_snapshot *snap = (pg_snapshot *) buf->data; + + SET_VARSIZE(snap, buf->len); + + /* buf is not needed anymore */ + buf->data = NULL; + pfree(buf); + + return snap; +} + +/* + * parse snapshot from cstring + */ +static pg_snapshot * +parse_snapshot(const char *str, Node *escontext) +{ + FullTransactionId xmin; + FullTransactionId xmax; + FullTransactionId last_val = InvalidFullTransactionId; + FullTransactionId val; + const char *str_start = str; + char *endp; + StringInfo buf; + + xmin = FullTransactionIdFromU64(strtou64(str, &endp, 10)); + if (*endp != ':') + goto bad_format; + str = endp + 1; + + xmax = FullTransactionIdFromU64(strtou64(str, &endp, 10)); + if (*endp != ':') + goto bad_format; + str = endp + 1; + + /* it should look sane */ + if (!FullTransactionIdIsValid(xmin) || + !FullTransactionIdIsValid(xmax) || + FullTransactionIdPrecedes(xmax, xmin)) + goto bad_format; + + /* allocate buffer */ + buf = buf_init(xmin, xmax); + + /* loop over values */ + while (*str != '\0') + { + /* read next value */ + val = FullTransactionIdFromU64(strtou64(str, &endp, 10)); + str = endp; + + /* require the input to be in order */ + if (FullTransactionIdPrecedes(val, xmin) || + FullTransactionIdFollowsOrEquals(val, xmax) || + FullTransactionIdPrecedes(val, last_val)) + goto bad_format; + + /* skip duplicates */ + if (!FullTransactionIdEquals(val, last_val)) + buf_add_txid(buf, val); + last_val = val; + + if (*str == ',') + str++; + else if (*str != '\0') + goto bad_format; + } + + return buf_finalize(buf); + +bad_format: + ereturn(escontext, NULL, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s: \"%s\"", + "pg_snapshot", str_start))); +} + +/* + * pg_current_xact_id() returns xid8 + * + * Return the current toplevel full transaction ID. + * If the current transaction does not have one, one is assigned. + */ +Datum +pg_current_xact_id(PG_FUNCTION_ARGS) +{ + /* + * Must prevent during recovery because if an xid is not assigned we try + * to assign one, which would fail. Programs already rely on this function + * to always return a valid current xid, so we should not change this to + * return NULL or similar invalid xid. + */ + PreventCommandDuringRecovery("pg_current_xact_id()"); + + PG_RETURN_FULLTRANSACTIONID(GetTopFullTransactionId()); +} + +/* + * Same as pg_current_xact_id() but doesn't assign a new xid if there + * isn't one yet. + */ +Datum +pg_current_xact_id_if_assigned(PG_FUNCTION_ARGS) +{ + FullTransactionId topfxid = GetTopFullTransactionIdIfAny(); + + if (!FullTransactionIdIsValid(topfxid)) + PG_RETURN_NULL(); + + PG_RETURN_FULLTRANSACTIONID(topfxid); +} + +/* + * pg_current_snapshot() returns pg_snapshot + * + * Return current snapshot + * + * Note that only top-transaction XIDs are included in the snapshot. + */ +Datum +pg_current_snapshot(PG_FUNCTION_ARGS) +{ + pg_snapshot *snap; + uint32 nxip, + i; + Snapshot cur; + FullTransactionId next_fxid = ReadNextFullTransactionId(); + + cur = GetActiveSnapshot(); + if (cur == NULL) + elog(ERROR, "no active snapshot set"); + + /* allocate */ + nxip = cur->xcnt; + snap = palloc(PG_SNAPSHOT_SIZE(nxip)); + + /* fill */ + snap->xmin = widen_snapshot_xid(cur->xmin, next_fxid); + snap->xmax = widen_snapshot_xid(cur->xmax, next_fxid); + snap->nxip = nxip; + for (i = 0; i < nxip; i++) + snap->xip[i] = widen_snapshot_xid(cur->xip[i], next_fxid); + + /* + * We want them guaranteed to be in ascending order. This also removes + * any duplicate xids. Normally, an XID can only be assigned to one + * backend, but when preparing a transaction for two-phase commit, there + * is a transient state when both the original backend and the dummy + * PGPROC entry reserved for the prepared transaction hold the same XID. + */ + sort_snapshot(snap); + + /* set size after sorting, because it may have removed duplicate xips */ + SET_VARSIZE(snap, PG_SNAPSHOT_SIZE(snap->nxip)); + + PG_RETURN_POINTER(snap); +} + +/* + * pg_snapshot_in(cstring) returns pg_snapshot + * + * input function for type pg_snapshot + */ +Datum +pg_snapshot_in(PG_FUNCTION_ARGS) +{ + char *str = PG_GETARG_CSTRING(0); + pg_snapshot *snap; + + snap = parse_snapshot(str, fcinfo->context); + + PG_RETURN_POINTER(snap); +} + +/* + * pg_snapshot_out(pg_snapshot) returns cstring + * + * output function for type pg_snapshot + */ +Datum +pg_snapshot_out(PG_FUNCTION_ARGS) +{ + pg_snapshot *snap = (pg_snapshot *) PG_GETARG_VARLENA_P(0); + StringInfoData str; + uint32 i; + + initStringInfo(&str); + + appendStringInfo(&str, UINT64_FORMAT ":", + U64FromFullTransactionId(snap->xmin)); + appendStringInfo(&str, UINT64_FORMAT ":", + U64FromFullTransactionId(snap->xmax)); + + for (i = 0; i < snap->nxip; i++) + { + if (i > 0) + appendStringInfoChar(&str, ','); + appendStringInfo(&str, UINT64_FORMAT, + U64FromFullTransactionId(snap->xip[i])); + } + + PG_RETURN_CSTRING(str.data); +} + +/* + * pg_snapshot_recv(internal) returns pg_snapshot + * + * binary input function for type pg_snapshot + * + * format: int4 nxip, int8 xmin, int8 xmax, int8 xip + */ +Datum +pg_snapshot_recv(PG_FUNCTION_ARGS) +{ + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + pg_snapshot *snap; + FullTransactionId last = InvalidFullTransactionId; + int nxip; + int i; + FullTransactionId xmin; + FullTransactionId xmax; + + /* load and validate nxip */ + nxip = pq_getmsgint(buf, 4); + if (nxip < 0 || nxip > PG_SNAPSHOT_MAX_NXIP) + goto bad_format; + + xmin = FullTransactionIdFromU64((uint64) pq_getmsgint64(buf)); + xmax = FullTransactionIdFromU64((uint64) pq_getmsgint64(buf)); + if (!FullTransactionIdIsValid(xmin) || + !FullTransactionIdIsValid(xmax) || + FullTransactionIdPrecedes(xmax, xmin)) + goto bad_format; + + snap = palloc(PG_SNAPSHOT_SIZE(nxip)); + snap->xmin = xmin; + snap->xmax = xmax; + + for (i = 0; i < nxip; i++) + { + FullTransactionId cur = + FullTransactionIdFromU64((uint64) pq_getmsgint64(buf)); + + if (FullTransactionIdPrecedes(cur, last) || + FullTransactionIdPrecedes(cur, xmin) || + FullTransactionIdPrecedes(xmax, cur)) + goto bad_format; + + /* skip duplicate xips */ + if (FullTransactionIdEquals(cur, last)) + { + i--; + nxip--; + continue; + } + + snap->xip[i] = cur; + last = cur; + } + snap->nxip = nxip; + SET_VARSIZE(snap, PG_SNAPSHOT_SIZE(nxip)); + PG_RETURN_POINTER(snap); + +bad_format: + ereport(ERROR, + (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION), + errmsg("invalid external pg_snapshot data"))); + PG_RETURN_POINTER(NULL); /* keep compiler quiet */ +} + +/* + * pg_snapshot_send(pg_snapshot) returns bytea + * + * binary output function for type pg_snapshot + * + * format: int4 nxip, u64 xmin, u64 xmax, u64 xip... + */ +Datum +pg_snapshot_send(PG_FUNCTION_ARGS) +{ + pg_snapshot *snap = (pg_snapshot *) PG_GETARG_VARLENA_P(0); + StringInfoData buf; + uint32 i; + + pq_begintypsend(&buf); + pq_sendint32(&buf, snap->nxip); + pq_sendint64(&buf, (int64) U64FromFullTransactionId(snap->xmin)); + pq_sendint64(&buf, (int64) U64FromFullTransactionId(snap->xmax)); + for (i = 0; i < snap->nxip; i++) + pq_sendint64(&buf, (int64) U64FromFullTransactionId(snap->xip[i])); + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} + +/* + * pg_visible_in_snapshot(xid8, pg_snapshot) returns bool + * + * is txid visible in snapshot ? + */ +Datum +pg_visible_in_snapshot(PG_FUNCTION_ARGS) +{ + FullTransactionId value = PG_GETARG_FULLTRANSACTIONID(0); + pg_snapshot *snap = (pg_snapshot *) PG_GETARG_VARLENA_P(1); + + PG_RETURN_BOOL(is_visible_fxid(value, snap)); +} + +/* + * pg_snapshot_xmin(pg_snapshot) returns xid8 + * + * return snapshot's xmin + */ +Datum +pg_snapshot_xmin(PG_FUNCTION_ARGS) +{ + pg_snapshot *snap = (pg_snapshot *) PG_GETARG_VARLENA_P(0); + + PG_RETURN_FULLTRANSACTIONID(snap->xmin); +} + +/* + * pg_snapshot_xmax(pg_snapshot) returns xid8 + * + * return snapshot's xmax + */ +Datum +pg_snapshot_xmax(PG_FUNCTION_ARGS) +{ + pg_snapshot *snap = (pg_snapshot *) PG_GETARG_VARLENA_P(0); + + PG_RETURN_FULLTRANSACTIONID(snap->xmax); +} + +/* + * pg_snapshot_xip(pg_snapshot) returns setof xid8 + * + * return in-progress xid8s in snapshot. + */ +Datum +pg_snapshot_xip(PG_FUNCTION_ARGS) +{ + FuncCallContext *fctx; + pg_snapshot *snap; + FullTransactionId value; + + /* on first call initialize fctx and get copy of snapshot */ + if (SRF_IS_FIRSTCALL()) + { + pg_snapshot *arg = (pg_snapshot *) PG_GETARG_VARLENA_P(0); + + fctx = SRF_FIRSTCALL_INIT(); + + /* make a copy of user snapshot */ + snap = MemoryContextAlloc(fctx->multi_call_memory_ctx, VARSIZE(arg)); + memcpy(snap, arg, VARSIZE(arg)); + + fctx->user_fctx = snap; + } + + /* return values one-by-one */ + fctx = SRF_PERCALL_SETUP(); + snap = fctx->user_fctx; + if (fctx->call_cntr < snap->nxip) + { + value = snap->xip[fctx->call_cntr]; + SRF_RETURN_NEXT(fctx, FullTransactionIdGetDatum(value)); + } + else + { + SRF_RETURN_DONE(fctx); + } +} + +/* + * Report the status of a recent transaction ID, or null for wrapped, + * truncated away or otherwise too old XIDs. + * + * The passed epoch-qualified xid is treated as a normal xid, not a + * multixact id. + * + * If it points to a committed subxact the result is the subxact status even + * though the parent xact may still be in progress or may have aborted. + */ +Datum +pg_xact_status(PG_FUNCTION_ARGS) +{ + const char *status; + FullTransactionId fxid = PG_GETARG_FULLTRANSACTIONID(0); + TransactionId xid; + + /* + * We must protect against concurrent truncation of clog entries to avoid + * an I/O error on SLRU lookup. + */ + LWLockAcquire(XactTruncationLock, LW_SHARED); + if (TransactionIdInRecentPast(fxid, &xid)) + { + Assert(TransactionIdIsValid(xid)); + + /* + * Like when doing visibility checks on a row, check whether the + * transaction is still in progress before looking into the CLOG. + * Otherwise we would incorrectly return "committed" for a transaction + * that is committing and has already updated the CLOG, but hasn't + * removed its XID from the proc array yet. (See comment on that race + * condition at the top of heapam_visibility.c) + */ + if (TransactionIdIsInProgress(xid)) + status = "in progress"; + else if (TransactionIdDidCommit(xid)) + status = "committed"; + else + { + /* it must have aborted or crashed */ + status = "aborted"; + } + } + else + { + status = NULL; + } + LWLockRelease(XactTruncationLock); + + if (status == NULL) + PG_RETURN_NULL(); + else + PG_RETURN_TEXT_P(cstring_to_text(status)); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/xml.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/xml.c new file mode 100644 index 00000000000..64c632c07f6 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/adt/xml.c @@ -0,0 +1,5022 @@ +/*------------------------------------------------------------------------- + * + * xml.c + * XML data type support. + * + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/backend/utils/adt/xml.c + * + *------------------------------------------------------------------------- + */ + +/* + * Generally, XML type support is only available when libxml use was + * configured during the build. But even if that is not done, the + * type and all the functions are available, but most of them will + * fail. For one thing, this avoids having to manage variant catalog + * installations. But it also has nice effects such as that you can + * dump a database containing XML type data even if the server is not + * linked with libxml. Thus, make sure xml_out() works even if nothing + * else does. + */ + +/* + * Notes on memory management: + * + * Sometimes libxml allocates global structures in the hope that it can reuse + * them later on. This makes it impractical to change the xmlMemSetup + * functions on-the-fly; that is likely to lead to trying to pfree() chunks + * allocated with malloc() or vice versa. Since libxml might be used by + * loadable modules, eg libperl, our only safe choices are to change the + * functions at postmaster/backend launch or not at all. Since we'd rather + * not activate libxml in sessions that might never use it, the latter choice + * is the preferred one. However, for debugging purposes it can be awfully + * handy to constrain libxml's allocations to be done in a specific palloc + * context, where they're easy to track. Therefore there is code here that + * can be enabled in debug builds to redirect libxml's allocations into a + * special context LibxmlContext. It's not recommended to turn this on in + * a production build because of the possibility of bad interactions with + * external modules. + */ +/* #define USE_LIBXMLCONTEXT */ + +#include "postgres.h" + +#ifdef USE_LIBXML +#include <libxml/chvalid.h> +#include <libxml/parser.h> +#include <libxml/parserInternals.h> +#include <libxml/tree.h> +#include <libxml/uri.h> +#include <libxml/xmlerror.h> +#include <libxml/xmlsave.h> +#include <libxml/xmlversion.h> +#include <libxml/xmlwriter.h> +#include <libxml/xpath.h> +#include <libxml/xpathInternals.h> + +/* + * We used to check for xmlStructuredErrorContext via a configure test; but + * that doesn't work on Windows, so instead use this grottier method of + * testing the library version number. + */ +#if LIBXML_VERSION >= 20704 +#define HAVE_XMLSTRUCTUREDERRORCONTEXT 1 +#endif + +/* + * libxml2 2.12 decided to insert "const" into the error handler API. + */ +#if LIBXML_VERSION >= 21200 +#define PgXmlErrorPtr const xmlError * +#else +#define PgXmlErrorPtr xmlErrorPtr +#endif + +#endif /* USE_LIBXML */ + +#include "access/htup_details.h" +#include "access/table.h" +#include "catalog/namespace.h" +#include "catalog/pg_class.h" +#include "catalog/pg_type.h" +#include "commands/dbcommands.h" +#include "executor/spi.h" +#include "executor/tablefunc.h" +#include "fmgr.h" +#include "lib/stringinfo.h" +#include "libpq/pqformat.h" +#include "mb/pg_wchar.h" +#include "miscadmin.h" +#include "nodes/execnodes.h" +#include "nodes/miscnodes.h" +#include "nodes/nodeFuncs.h" +#include "utils/array.h" +#include "utils/builtins.h" +#include "utils/date.h" +#include "utils/datetime.h" +#include "utils/lsyscache.h" +#include "utils/memutils.h" +#include "utils/rel.h" +#include "utils/syscache.h" +#include "utils/xml.h" + + +/* GUC variables */ +__thread int xmlbinary = XMLBINARY_BASE64; +__thread int xmloption = XMLOPTION_CONTENT; + +#ifdef USE_LIBXML + +/* random number to identify PgXmlErrorContext */ +#define ERRCXT_MAGIC 68275028 + +struct PgXmlErrorContext +{ + int magic; + /* strictness argument passed to pg_xml_init */ + PgXmlStrictness strictness; + /* current error status and accumulated message, if any */ + bool err_occurred; + StringInfoData err_buf; + /* previous libxml error handling state (saved by pg_xml_init) */ + xmlStructuredErrorFunc saved_errfunc; + void *saved_errcxt; + /* previous libxml entity handler (saved by pg_xml_init) */ + xmlExternalEntityLoader saved_entityfunc; +}; + +static xmlParserInputPtr xmlPgEntityLoader(const char *URL, const char *ID, + xmlParserCtxtPtr ctxt); +static void xml_errsave(Node *escontext, PgXmlErrorContext *errcxt, + int sqlcode, const char *msg); +static void xml_errorHandler(void *data, PgXmlErrorPtr error); +static int errdetail_for_xml_code(int code); +static void chopStringInfoNewlines(StringInfo str); +static void appendStringInfoLineSeparator(StringInfo str); + +#ifdef USE_LIBXMLCONTEXT + +static MemoryContext LibxmlContext = NULL; + +static void xml_memory_init(void); +static void *xml_palloc(size_t size); +static void *xml_repalloc(void *ptr, size_t size); +static void xml_pfree(void *ptr); +static char *xml_pstrdup(const char *string); +#endif /* USE_LIBXMLCONTEXT */ + +static xmlChar *xml_text2xmlChar(text *in); +static int parse_xml_decl(const xmlChar *str, size_t *lenp, + xmlChar **version, xmlChar **encoding, int *standalone); +static bool print_xml_decl(StringInfo buf, const xmlChar *version, + pg_enc encoding, int standalone); +static bool xml_doctype_in_content(const xmlChar *str); +static xmlDocPtr xml_parse(text *data, XmlOptionType xmloption_arg, + bool preserve_whitespace, int encoding, + XmlOptionType *parsed_xmloptiontype, + xmlNodePtr *parsed_nodes, + Node *escontext); +static text *xml_xmlnodetoxmltype(xmlNodePtr cur, PgXmlErrorContext *xmlerrcxt); +static int xml_xpathobjtoxmlarray(xmlXPathObjectPtr xpathobj, + ArrayBuildState *astate, + PgXmlErrorContext *xmlerrcxt); +static xmlChar *pg_xmlCharStrndup(const char *str, size_t len); +#endif /* USE_LIBXML */ + +static void xmldata_root_element_start(StringInfo result, const char *eltname, + const char *xmlschema, const char *targetns, + bool top_level); +static void xmldata_root_element_end(StringInfo result, const char *eltname); +static StringInfo query_to_xml_internal(const char *query, char *tablename, + const char *xmlschema, bool nulls, bool tableforest, + const char *targetns, bool top_level); +static const char *map_sql_table_to_xmlschema(TupleDesc tupdesc, Oid relid, + bool nulls, bool tableforest, const char *targetns); +static const char *map_sql_schema_to_xmlschema_types(Oid nspid, + List *relid_list, bool nulls, + bool tableforest, const char *targetns); +static const char *map_sql_catalog_to_xmlschema_types(List *nspid_list, + bool nulls, bool tableforest, + const char *targetns); +static const char *map_sql_type_to_xml_name(Oid typeoid, int typmod); +static const char *map_sql_typecoll_to_xmlschema_types(List *tupdesc_list); +static const char *map_sql_type_to_xmlschema_type(Oid typeoid, int typmod); +static void SPI_sql_row_to_xmlelement(uint64 rownum, StringInfo result, + char *tablename, bool nulls, bool tableforest, + const char *targetns, bool top_level); + +/* XMLTABLE support */ +#ifdef USE_LIBXML +/* random number to identify XmlTableContext */ +#define XMLTABLE_CONTEXT_MAGIC 46922182 +typedef struct XmlTableBuilderData +{ + int magic; + int natts; + long int row_count; + PgXmlErrorContext *xmlerrcxt; + xmlParserCtxtPtr ctxt; + xmlDocPtr doc; + xmlXPathContextPtr xpathcxt; + xmlXPathCompExprPtr xpathcomp; + xmlXPathObjectPtr xpathobj; + xmlXPathCompExprPtr *xpathscomp; +} XmlTableBuilderData; +#endif + +static void XmlTableInitOpaque(struct TableFuncScanState *state, int natts); +static void XmlTableSetDocument(struct TableFuncScanState *state, Datum value); +static void XmlTableSetNamespace(struct TableFuncScanState *state, const char *name, + const char *uri); +static void XmlTableSetRowFilter(struct TableFuncScanState *state, const char *path); +static void XmlTableSetColumnFilter(struct TableFuncScanState *state, + const char *path, int colnum); +static bool XmlTableFetchRow(struct TableFuncScanState *state); +static Datum XmlTableGetValue(struct TableFuncScanState *state, int colnum, + Oid typid, int32 typmod, bool *isnull); +static void XmlTableDestroyOpaque(struct TableFuncScanState *state); + +const TableFuncRoutine XmlTableRoutine = +{ + XmlTableInitOpaque, + XmlTableSetDocument, + XmlTableSetNamespace, + XmlTableSetRowFilter, + XmlTableSetColumnFilter, + XmlTableFetchRow, + XmlTableGetValue, + XmlTableDestroyOpaque +}; + +#define NO_XML_SUPPORT() \ + ereport(ERROR, \ + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), \ + errmsg("unsupported XML feature"), \ + errdetail("This functionality requires the server to be built with libxml support."))) + + +/* from SQL/XML:2008 section 4.9 */ +#define NAMESPACE_XSD "http://www.w3.org/2001/XMLSchema" +#define NAMESPACE_XSI "http://www.w3.org/2001/XMLSchema-instance" +#define NAMESPACE_SQLXML "http://standards.iso.org/iso/9075/2003/sqlxml" + + +#ifdef USE_LIBXML + +static int +xmlChar_to_encoding(const xmlChar *encoding_name) +{ + int encoding = pg_char_to_encoding((const char *) encoding_name); + + if (encoding < 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid encoding name \"%s\"", + (const char *) encoding_name))); + return encoding; +} +#endif + + +/* + * xml_in uses a plain C string to VARDATA conversion, so for the time being + * we use the conversion function for the text datatype. + * + * This is only acceptable so long as xmltype and text use the same + * representation. + */ +Datum +xml_in(PG_FUNCTION_ARGS) +{ +#ifdef USE_LIBXML + char *s = PG_GETARG_CSTRING(0); + xmltype *vardata; + xmlDocPtr doc; + + /* Build the result object. */ + vardata = (xmltype *) cstring_to_text(s); + + /* + * Parse the data to check if it is well-formed XML data. + * + * Note: we don't need to worry about whether a soft error is detected. + */ + doc = xml_parse(vardata, xmloption, true, GetDatabaseEncoding(), + NULL, NULL, fcinfo->context); + if (doc != NULL) + xmlFreeDoc(doc); + + PG_RETURN_XML_P(vardata); +#else + NO_XML_SUPPORT(); + return 0; +#endif +} + + +#define PG_XML_DEFAULT_VERSION "1.0" + + +/* + * xml_out_internal uses a plain VARDATA to C string conversion, so for the + * time being we use the conversion function for the text datatype. + * + * This is only acceptable so long as xmltype and text use the same + * representation. + */ +static char * +xml_out_internal(xmltype *x, pg_enc target_encoding) +{ + char *str = text_to_cstring((text *) x); + +#ifdef USE_LIBXML + size_t len = strlen(str); + xmlChar *version; + int standalone; + int res_code; + + if ((res_code = parse_xml_decl((xmlChar *) str, + &len, &version, NULL, &standalone)) == 0) + { + StringInfoData buf; + + initStringInfo(&buf); + + if (!print_xml_decl(&buf, version, target_encoding, standalone)) + { + /* + * If we are not going to produce an XML declaration, eat a single + * newline in the original string to prevent empty first lines in + * the output. + */ + if (*(str + len) == '\n') + len += 1; + } + appendStringInfoString(&buf, str + len); + + pfree(str); + + return buf.data; + } + + ereport(WARNING, + errcode(ERRCODE_INTERNAL_ERROR), + errmsg_internal("could not parse XML declaration in stored value"), + errdetail_for_xml_code(res_code)); +#endif + return str; +} + + +Datum +xml_out(PG_FUNCTION_ARGS) +{ + xmltype *x = PG_GETARG_XML_P(0); + + /* + * xml_out removes the encoding property in all cases. This is because we + * cannot control from here whether the datum will be converted to a + * different client encoding, so we'd do more harm than good by including + * it. + */ + PG_RETURN_CSTRING(xml_out_internal(x, 0)); +} + + +Datum +xml_recv(PG_FUNCTION_ARGS) +{ +#ifdef USE_LIBXML + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + xmltype *result; + char *str; + char *newstr; + int nbytes; + xmlDocPtr doc; + xmlChar *encodingStr = NULL; + int encoding; + + /* + * Read the data in raw format. We don't know yet what the encoding is, as + * that information is embedded in the xml declaration; so we have to + * parse that before converting to server encoding. + */ + nbytes = buf->len - buf->cursor; + str = (char *) pq_getmsgbytes(buf, nbytes); + + /* + * We need a null-terminated string to pass to parse_xml_decl(). Rather + * than make a separate copy, make the temporary result one byte bigger + * than it needs to be. + */ + result = palloc(nbytes + 1 + VARHDRSZ); + SET_VARSIZE(result, nbytes + VARHDRSZ); + memcpy(VARDATA(result), str, nbytes); + str = VARDATA(result); + str[nbytes] = '\0'; + + parse_xml_decl((const xmlChar *) str, NULL, NULL, &encodingStr, NULL); + + /* + * If encoding wasn't explicitly specified in the XML header, treat it as + * UTF-8, as that's the default in XML. This is different from xml_in(), + * where the input has to go through the normal client to server encoding + * conversion. + */ + encoding = encodingStr ? xmlChar_to_encoding(encodingStr) : PG_UTF8; + + /* + * Parse the data to check if it is well-formed XML data. Assume that + * xml_parse will throw ERROR if not. + */ + doc = xml_parse(result, xmloption, true, encoding, NULL, NULL, NULL); + xmlFreeDoc(doc); + + /* Now that we know what we're dealing with, convert to server encoding */ + newstr = pg_any_to_server(str, nbytes, encoding); + + if (newstr != str) + { + pfree(result); + result = (xmltype *) cstring_to_text(newstr); + pfree(newstr); + } + + PG_RETURN_XML_P(result); +#else + NO_XML_SUPPORT(); + return 0; +#endif +} + + +Datum +xml_send(PG_FUNCTION_ARGS) +{ + xmltype *x = PG_GETARG_XML_P(0); + char *outval; + StringInfoData buf; + + /* + * xml_out_internal doesn't convert the encoding, it just prints the right + * declaration. pq_sendtext will do the conversion. + */ + outval = xml_out_internal(x, pg_get_client_encoding()); + + pq_begintypsend(&buf); + pq_sendtext(&buf, outval, strlen(outval)); + pfree(outval); + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} + + +#ifdef USE_LIBXML +static void +appendStringInfoText(StringInfo str, const text *t) +{ + appendBinaryStringInfo(str, VARDATA_ANY(t), VARSIZE_ANY_EXHDR(t)); +} +#endif + + +static xmltype * +stringinfo_to_xmltype(StringInfo buf) +{ + return (xmltype *) cstring_to_text_with_len(buf->data, buf->len); +} + + +static xmltype * +cstring_to_xmltype(const char *string) +{ + return (xmltype *) cstring_to_text(string); +} + + +#ifdef USE_LIBXML +static xmltype * +xmlBuffer_to_xmltype(xmlBufferPtr buf) +{ + return (xmltype *) cstring_to_text_with_len((const char *) xmlBufferContent(buf), + xmlBufferLength(buf)); +} +#endif + + +Datum +xmlcomment(PG_FUNCTION_ARGS) +{ +#ifdef USE_LIBXML + text *arg = PG_GETARG_TEXT_PP(0); + char *argdata = VARDATA_ANY(arg); + int len = VARSIZE_ANY_EXHDR(arg); + StringInfoData buf; + int i; + + /* check for "--" in string or "-" at the end */ + for (i = 1; i < len; i++) + { + if (argdata[i] == '-' && argdata[i - 1] == '-') + ereport(ERROR, + (errcode(ERRCODE_INVALID_XML_COMMENT), + errmsg("invalid XML comment"))); + } + if (len > 0 && argdata[len - 1] == '-') + ereport(ERROR, + (errcode(ERRCODE_INVALID_XML_COMMENT), + errmsg("invalid XML comment"))); + + initStringInfo(&buf); + appendStringInfoString(&buf, "<!--"); + appendStringInfoText(&buf, arg); + appendStringInfoString(&buf, "-->"); + + PG_RETURN_XML_P(stringinfo_to_xmltype(&buf)); +#else + NO_XML_SUPPORT(); + return 0; +#endif +} + + + +/* + * TODO: xmlconcat needs to merge the notations and unparsed entities + * of the argument values. Not very important in practice, though. + */ +xmltype * +xmlconcat(List *args) +{ +#ifdef USE_LIBXML + int global_standalone = 1; + xmlChar *global_version = NULL; + bool global_version_no_value = false; + StringInfoData buf; + ListCell *v; + + initStringInfo(&buf); + foreach(v, args) + { + xmltype *x = DatumGetXmlP(PointerGetDatum(lfirst(v))); + size_t len; + xmlChar *version; + int standalone; + char *str; + + len = VARSIZE(x) - VARHDRSZ; + str = text_to_cstring((text *) x); + + parse_xml_decl((xmlChar *) str, &len, &version, NULL, &standalone); + + if (standalone == 0 && global_standalone == 1) + global_standalone = 0; + if (standalone < 0) + global_standalone = -1; + + if (!version) + global_version_no_value = true; + else if (!global_version) + global_version = version; + else if (xmlStrcmp(version, global_version) != 0) + global_version_no_value = true; + + appendStringInfoString(&buf, str + len); + pfree(str); + } + + if (!global_version_no_value || global_standalone >= 0) + { + StringInfoData buf2; + + initStringInfo(&buf2); + + print_xml_decl(&buf2, + (!global_version_no_value) ? global_version : NULL, + 0, + global_standalone); + + appendBinaryStringInfo(&buf2, buf.data, buf.len); + buf = buf2; + } + + return stringinfo_to_xmltype(&buf); +#else + NO_XML_SUPPORT(); + return NULL; +#endif +} + + +/* + * XMLAGG support + */ +Datum +xmlconcat2(PG_FUNCTION_ARGS) +{ + if (PG_ARGISNULL(0)) + { + if (PG_ARGISNULL(1)) + PG_RETURN_NULL(); + else + PG_RETURN_XML_P(PG_GETARG_XML_P(1)); + } + else if (PG_ARGISNULL(1)) + PG_RETURN_XML_P(PG_GETARG_XML_P(0)); + else + PG_RETURN_XML_P(xmlconcat(list_make2(PG_GETARG_XML_P(0), + PG_GETARG_XML_P(1)))); +} + + +Datum +texttoxml(PG_FUNCTION_ARGS) +{ + text *data = PG_GETARG_TEXT_PP(0); + + PG_RETURN_XML_P(xmlparse(data, xmloption, true)); +} + + +Datum +xmltotext(PG_FUNCTION_ARGS) +{ + xmltype *data = PG_GETARG_XML_P(0); + + /* It's actually binary compatible. */ + PG_RETURN_TEXT_P((text *) data); +} + + +text * +xmltotext_with_options(xmltype *data, XmlOptionType xmloption_arg, bool indent) +{ +#ifdef USE_LIBXML + text *volatile result; + xmlDocPtr doc; + XmlOptionType parsed_xmloptiontype; + xmlNodePtr content_nodes; + volatile xmlBufferPtr buf = NULL; + volatile xmlSaveCtxtPtr ctxt = NULL; + ErrorSaveContext escontext = {T_ErrorSaveContext}; + PgXmlErrorContext *xmlerrcxt; +#endif + + if (xmloption_arg != XMLOPTION_DOCUMENT && !indent) + { + /* + * We don't actually need to do anything, so just return the + * binary-compatible input. For backwards-compatibility reasons, + * allow such cases to succeed even without USE_LIBXML. + */ + return (text *) data; + } + +#ifdef USE_LIBXML + /* Parse the input according to the xmloption */ + doc = xml_parse(data, xmloption_arg, true, GetDatabaseEncoding(), + &parsed_xmloptiontype, &content_nodes, + (Node *) &escontext); + if (doc == NULL || escontext.error_occurred) + { + if (doc) + xmlFreeDoc(doc); + /* A soft error must be failure to conform to XMLOPTION_DOCUMENT */ + ereport(ERROR, + (errcode(ERRCODE_NOT_AN_XML_DOCUMENT), + errmsg("not an XML document"))); + } + + /* If we weren't asked to indent, we're done. */ + if (!indent) + { + xmlFreeDoc(doc); + return (text *) data; + } + + /* Otherwise, we gotta spin up some error handling. */ + xmlerrcxt = pg_xml_init(PG_XML_STRICTNESS_ALL); + + PG_TRY(); + { + size_t decl_len = 0; + + /* The serialized data will go into this buffer. */ + buf = xmlBufferCreate(); + + if (buf == NULL || xmlerrcxt->err_occurred) + xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY, + "could not allocate xmlBuffer"); + + /* Detect whether there's an XML declaration */ + parse_xml_decl(xml_text2xmlChar(data), &decl_len, NULL, NULL, NULL); + + /* + * Emit declaration only if the input had one. Note: some versions of + * xmlSaveToBuffer leak memory if a non-null encoding argument is + * passed, so don't do that. We don't want any encoding conversion + * anyway. + */ + if (decl_len == 0) + ctxt = xmlSaveToBuffer(buf, NULL, + XML_SAVE_NO_DECL | XML_SAVE_FORMAT); + else + ctxt = xmlSaveToBuffer(buf, NULL, + XML_SAVE_FORMAT); + + if (ctxt == NULL || xmlerrcxt->err_occurred) + xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY, + "could not allocate xmlSaveCtxt"); + + if (parsed_xmloptiontype == XMLOPTION_DOCUMENT) + { + /* If it's a document, saving is easy. */ + if (xmlSaveDoc(ctxt, doc) == -1 || xmlerrcxt->err_occurred) + xml_ereport(xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR, + "could not save document to xmlBuffer"); + } + else if (content_nodes != NULL) + { + /* + * Deal with the case where we have non-singly-rooted XML. + * libxml's dump functions don't work well for that without help. + * We build a fake root node that serves as a container for the + * content nodes, and then iterate over the nodes. + */ + xmlNodePtr root; + xmlNodePtr newline; + + root = xmlNewNode(NULL, (const xmlChar *) "content-root"); + if (root == NULL || xmlerrcxt->err_occurred) + xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY, + "could not allocate xml node"); + + /* This attaches root to doc, so we need not free it separately. */ + xmlDocSetRootElement(doc, root); + xmlAddChild(root, content_nodes); + + /* + * We use this node to insert newlines in the dump. Note: in at + * least some libxml versions, xmlNewDocText would not attach the + * node to the document even if we passed it. Therefore, manage + * freeing of this node manually, and pass NULL here to make sure + * there's not a dangling link. + */ + newline = xmlNewDocText(NULL, (const xmlChar *) "\n"); + if (newline == NULL || xmlerrcxt->err_occurred) + xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY, + "could not allocate xml node"); + + for (xmlNodePtr node = root->children; node; node = node->next) + { + /* insert newlines between nodes */ + if (node->type != XML_TEXT_NODE && node->prev != NULL) + { + if (xmlSaveTree(ctxt, newline) == -1 || xmlerrcxt->err_occurred) + { + xmlFreeNode(newline); + xml_ereport(xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR, + "could not save newline to xmlBuffer"); + } + } + + if (xmlSaveTree(ctxt, node) == -1 || xmlerrcxt->err_occurred) + { + xmlFreeNode(newline); + xml_ereport(xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR, + "could not save content to xmlBuffer"); + } + } + + xmlFreeNode(newline); + } + + if (xmlSaveClose(ctxt) == -1 || xmlerrcxt->err_occurred) + { + ctxt = NULL; /* don't try to close it again */ + xml_ereport(xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR, + "could not close xmlSaveCtxtPtr"); + } + + result = (text *) xmlBuffer_to_xmltype(buf); + } + PG_CATCH(); + { + if (ctxt) + xmlSaveClose(ctxt); + if (buf) + xmlBufferFree(buf); + if (doc) + xmlFreeDoc(doc); + + pg_xml_done(xmlerrcxt, true); + + PG_RE_THROW(); + } + PG_END_TRY(); + + xmlBufferFree(buf); + xmlFreeDoc(doc); + + pg_xml_done(xmlerrcxt, false); + + return result; +#else + NO_XML_SUPPORT(); + return NULL; +#endif +} + + +xmltype * +xmlelement(XmlExpr *xexpr, + Datum *named_argvalue, bool *named_argnull, + Datum *argvalue, bool *argnull) +{ +#ifdef USE_LIBXML + xmltype *result; + List *named_arg_strings; + List *arg_strings; + int i; + ListCell *arg; + ListCell *narg; + PgXmlErrorContext *xmlerrcxt; + volatile xmlBufferPtr buf = NULL; + volatile xmlTextWriterPtr writer = NULL; + + /* + * All arguments are already evaluated, and their values are passed in the + * named_argvalue/named_argnull or argvalue/argnull arrays. This avoids + * issues if one of the arguments involves a call to some other function + * or subsystem that wants to use libxml on its own terms. We examine the + * original XmlExpr to identify the numbers and types of the arguments. + */ + named_arg_strings = NIL; + i = 0; + foreach(arg, xexpr->named_args) + { + Expr *e = (Expr *) lfirst(arg); + char *str; + + if (named_argnull[i]) + str = NULL; + else + str = map_sql_value_to_xml_value(named_argvalue[i], + exprType((Node *) e), + false); + named_arg_strings = lappend(named_arg_strings, str); + i++; + } + + arg_strings = NIL; + i = 0; + foreach(arg, xexpr->args) + { + Expr *e = (Expr *) lfirst(arg); + char *str; + + /* here we can just forget NULL elements immediately */ + if (!argnull[i]) + { + str = map_sql_value_to_xml_value(argvalue[i], + exprType((Node *) e), + true); + arg_strings = lappend(arg_strings, str); + } + i++; + } + + xmlerrcxt = pg_xml_init(PG_XML_STRICTNESS_ALL); + + PG_TRY(); + { + buf = xmlBufferCreate(); + if (buf == NULL || xmlerrcxt->err_occurred) + xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY, + "could not allocate xmlBuffer"); + writer = xmlNewTextWriterMemory(buf, 0); + if (writer == NULL || xmlerrcxt->err_occurred) + xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY, + "could not allocate xmlTextWriter"); + + xmlTextWriterStartElement(writer, (xmlChar *) xexpr->name); + + forboth(arg, named_arg_strings, narg, xexpr->arg_names) + { + char *str = (char *) lfirst(arg); + char *argname = strVal(lfirst(narg)); + + if (str) + xmlTextWriterWriteAttribute(writer, + (xmlChar *) argname, + (xmlChar *) str); + } + + foreach(arg, arg_strings) + { + char *str = (char *) lfirst(arg); + + xmlTextWriterWriteRaw(writer, (xmlChar *) str); + } + + xmlTextWriterEndElement(writer); + + /* we MUST do this now to flush data out to the buffer ... */ + xmlFreeTextWriter(writer); + writer = NULL; + + result = xmlBuffer_to_xmltype(buf); + } + PG_CATCH(); + { + if (writer) + xmlFreeTextWriter(writer); + if (buf) + xmlBufferFree(buf); + + pg_xml_done(xmlerrcxt, true); + + PG_RE_THROW(); + } + PG_END_TRY(); + + xmlBufferFree(buf); + + pg_xml_done(xmlerrcxt, false); + + return result; +#else + NO_XML_SUPPORT(); + return NULL; +#endif +} + + +xmltype * +xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace) +{ +#ifdef USE_LIBXML + xmlDocPtr doc; + + doc = xml_parse(data, xmloption_arg, preserve_whitespace, + GetDatabaseEncoding(), NULL, NULL, NULL); + xmlFreeDoc(doc); + + return (xmltype *) data; +#else + NO_XML_SUPPORT(); + return NULL; +#endif +} + + +xmltype * +xmlpi(const char *target, text *arg, bool arg_is_null, bool *result_is_null) +{ +#ifdef USE_LIBXML + xmltype *result; + StringInfoData buf; + + if (pg_strcasecmp(target, "xml") == 0) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), /* really */ + errmsg("invalid XML processing instruction"), + errdetail("XML processing instruction target name cannot be \"%s\".", target))); + + /* + * Following the SQL standard, the null check comes after the syntax check + * above. + */ + *result_is_null = arg_is_null; + if (*result_is_null) + return NULL; + + initStringInfo(&buf); + + appendStringInfo(&buf, "<?%s", target); + + if (arg != NULL) + { + char *string; + + string = text_to_cstring(arg); + if (strstr(string, "?>") != NULL) + ereport(ERROR, + (errcode(ERRCODE_INVALID_XML_PROCESSING_INSTRUCTION), + errmsg("invalid XML processing instruction"), + errdetail("XML processing instruction cannot contain \"?>\"."))); + + appendStringInfoChar(&buf, ' '); + appendStringInfoString(&buf, string + strspn(string, " ")); + pfree(string); + } + appendStringInfoString(&buf, "?>"); + + result = stringinfo_to_xmltype(&buf); + pfree(buf.data); + return result; +#else + NO_XML_SUPPORT(); + return NULL; +#endif +} + + +xmltype * +xmlroot(xmltype *data, text *version, int standalone) +{ +#ifdef USE_LIBXML + char *str; + size_t len; + xmlChar *orig_version; + int orig_standalone; + StringInfoData buf; + + len = VARSIZE(data) - VARHDRSZ; + str = text_to_cstring((text *) data); + + parse_xml_decl((xmlChar *) str, &len, &orig_version, NULL, &orig_standalone); + + if (version) + orig_version = xml_text2xmlChar(version); + else + orig_version = NULL; + + switch (standalone) + { + case XML_STANDALONE_YES: + orig_standalone = 1; + break; + case XML_STANDALONE_NO: + orig_standalone = 0; + break; + case XML_STANDALONE_NO_VALUE: + orig_standalone = -1; + break; + case XML_STANDALONE_OMITTED: + /* leave original value */ + break; + } + + initStringInfo(&buf); + print_xml_decl(&buf, orig_version, 0, orig_standalone); + appendStringInfoString(&buf, str + len); + + return stringinfo_to_xmltype(&buf); +#else + NO_XML_SUPPORT(); + return NULL; +#endif +} + + +/* + * Validate document (given as string) against DTD (given as external link) + * + * This has been removed because it is a security hole: unprivileged users + * should not be able to use Postgres to fetch arbitrary external files, + * which unfortunately is exactly what libxml is willing to do with the DTD + * parameter. + */ +Datum +xmlvalidate(PG_FUNCTION_ARGS) +{ + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("xmlvalidate is not implemented"))); + return 0; +} + + +bool +xml_is_document(xmltype *arg) +{ +#ifdef USE_LIBXML + xmlDocPtr doc; + ErrorSaveContext escontext = {T_ErrorSaveContext}; + + /* + * We'll report "true" if no soft error is reported by xml_parse(). + */ + doc = xml_parse((text *) arg, XMLOPTION_DOCUMENT, true, + GetDatabaseEncoding(), NULL, NULL, (Node *) &escontext); + if (doc) + xmlFreeDoc(doc); + + return !escontext.error_occurred; +#else /* not USE_LIBXML */ + NO_XML_SUPPORT(); + return false; +#endif /* not USE_LIBXML */ +} + + +#ifdef USE_LIBXML + +/* + * pg_xml_init_library --- set up for use of libxml + * + * This should be called by each function that is about to use libxml + * facilities but doesn't require error handling. It initializes libxml + * and verifies compatibility with the loaded libxml version. These are + * once-per-session activities. + * + * TODO: xmlChar is utf8-char, make proper tuning (initdb with enc!=utf8 and + * check) + */ +void +pg_xml_init_library(void) +{ + static __thread bool first_time = true; + + if (first_time) + { + /* Stuff we need do only once per session */ + + /* + * Currently, we have no pure UTF-8 support for internals -- check if + * we can work. + */ + if (sizeof(char) != sizeof(xmlChar)) + ereport(ERROR, + (errmsg("could not initialize XML library"), + errdetail("libxml2 has incompatible char type: sizeof(char)=%zu, sizeof(xmlChar)=%zu.", + sizeof(char), sizeof(xmlChar)))); + +#ifdef USE_LIBXMLCONTEXT + /* Set up libxml's memory allocation our way */ + xml_memory_init(); +#endif + + /* Check library compatibility */ + LIBXML_TEST_VERSION; + + first_time = false; + } +} + +/* + * pg_xml_init --- set up for use of libxml and register an error handler + * + * This should be called by each function that is about to use libxml + * facilities and requires error handling. It initializes libxml with + * pg_xml_init_library() and establishes our libxml error handler. + * + * strictness determines which errors are reported and which are ignored. + * + * Calls to this function MUST be followed by a PG_TRY block that guarantees + * that pg_xml_done() is called during either normal or error exit. + * + * This is exported for use by contrib/xml2, as well as other code that might + * wish to share use of this module's libxml error handler. + */ +PgXmlErrorContext * +pg_xml_init(PgXmlStrictness strictness) +{ + PgXmlErrorContext *errcxt; + void *new_errcxt; + + /* Do one-time setup if needed */ + pg_xml_init_library(); + + /* Create error handling context structure */ + errcxt = (PgXmlErrorContext *) palloc(sizeof(PgXmlErrorContext)); + errcxt->magic = ERRCXT_MAGIC; + errcxt->strictness = strictness; + errcxt->err_occurred = false; + initStringInfo(&errcxt->err_buf); + + /* + * Save original error handler and install ours. libxml originally didn't + * distinguish between the contexts for generic and for structured error + * handlers. If we're using an old libxml version, we must thus save the + * generic error context, even though we're using a structured error + * handler. + */ + errcxt->saved_errfunc = xmlStructuredError; + +#ifdef HAVE_XMLSTRUCTUREDERRORCONTEXT + errcxt->saved_errcxt = xmlStructuredErrorContext; +#else + errcxt->saved_errcxt = xmlGenericErrorContext; +#endif + + xmlSetStructuredErrorFunc((void *) errcxt, xml_errorHandler); + + /* + * Verify that xmlSetStructuredErrorFunc set the context variable we + * expected it to. If not, the error context pointer we just saved is not + * the correct thing to restore, and since that leaves us without a way to + * restore the context in pg_xml_done, we must fail. + * + * The only known situation in which this test fails is if we compile with + * headers from a libxml2 that doesn't track the structured error context + * separately (< 2.7.4), but at runtime use a version that does, or vice + * versa. The libxml2 authors did not treat that change as constituting + * an ABI break, so the LIBXML_TEST_VERSION test in pg_xml_init_library + * fails to protect us from this. + */ + +#ifdef HAVE_XMLSTRUCTUREDERRORCONTEXT + new_errcxt = xmlStructuredErrorContext; +#else + new_errcxt = xmlGenericErrorContext; +#endif + + if (new_errcxt != (void *) errcxt) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("could not set up XML error handler"), + errhint("This probably indicates that the version of libxml2" + " being used is not compatible with the libxml2" + " header files that PostgreSQL was built with."))); + + /* + * Also, install an entity loader to prevent unwanted fetches of external + * files and URLs. + */ + errcxt->saved_entityfunc = xmlGetExternalEntityLoader(); + xmlSetExternalEntityLoader(xmlPgEntityLoader); + + return errcxt; +} + + +/* + * pg_xml_done --- restore previous libxml error handling + * + * Resets libxml's global error-handling state to what it was before + * pg_xml_init() was called. + * + * This routine verifies that all pending errors have been dealt with + * (in assert-enabled builds, anyway). + */ +void +pg_xml_done(PgXmlErrorContext *errcxt, bool isError) +{ + void *cur_errcxt; + + /* An assert seems like enough protection here */ + Assert(errcxt->magic == ERRCXT_MAGIC); + + /* + * In a normal exit, there should be no un-handled libxml errors. But we + * shouldn't try to enforce this during error recovery, since the longjmp + * could have been thrown before xml_ereport had a chance to run. + */ + Assert(!errcxt->err_occurred || isError); + + /* + * Check that libxml's global state is correct, warn if not. This is a + * real test and not an Assert because it has a higher probability of + * happening. + */ +#ifdef HAVE_XMLSTRUCTUREDERRORCONTEXT + cur_errcxt = xmlStructuredErrorContext; +#else + cur_errcxt = xmlGenericErrorContext; +#endif + + if (cur_errcxt != (void *) errcxt) + elog(WARNING, "libxml error handling state is out of sync with xml.c"); + + /* Restore the saved handlers */ + xmlSetStructuredErrorFunc(errcxt->saved_errcxt, errcxt->saved_errfunc); + xmlSetExternalEntityLoader(errcxt->saved_entityfunc); + + /* + * Mark the struct as invalid, just in case somebody somehow manages to + * call xml_errorHandler or xml_ereport with it. + */ + errcxt->magic = 0; + + /* Release memory */ + pfree(errcxt->err_buf.data); + pfree(errcxt); +} + + +/* + * pg_xml_error_occurred() --- test the error flag + */ +bool +pg_xml_error_occurred(PgXmlErrorContext *errcxt) +{ + return errcxt->err_occurred; +} + + +/* + * SQL/XML allows storing "XML documents" or "XML content". "XML + * documents" are specified by the XML specification and are parsed + * easily by libxml. "XML content" is specified by SQL/XML as the + * production "XMLDecl? content". But libxml can only parse the + * "content" part, so we have to parse the XML declaration ourselves + * to complete this. + */ + +#define CHECK_XML_SPACE(p) \ + do { \ + if (!xmlIsBlank_ch(*(p))) \ + return XML_ERR_SPACE_REQUIRED; \ + } while (0) + +#define SKIP_XML_SPACE(p) \ + while (xmlIsBlank_ch(*(p))) (p)++ + +/* Letter | Digit | '.' | '-' | '_' | ':' | CombiningChar | Extender */ +/* Beware of multiple evaluations of argument! */ +#define PG_XMLISNAMECHAR(c) \ + (xmlIsBaseChar_ch(c) || xmlIsIdeographicQ(c) \ + || xmlIsDigit_ch(c) \ + || c == '.' || c == '-' || c == '_' || c == ':' \ + || xmlIsCombiningQ(c) \ + || xmlIsExtender_ch(c)) + +/* pnstrdup, but deal with xmlChar not char; len is measured in xmlChars */ +static xmlChar * +xml_pnstrdup(const xmlChar *str, size_t len) +{ + xmlChar *result; + + result = (xmlChar *) palloc((len + 1) * sizeof(xmlChar)); + memcpy(result, str, len * sizeof(xmlChar)); + result[len] = 0; + return result; +} + +/* Ditto, except input is char* */ +static xmlChar * +pg_xmlCharStrndup(const char *str, size_t len) +{ + xmlChar *result; + + result = (xmlChar *) palloc((len + 1) * sizeof(xmlChar)); + memcpy(result, str, len); + result[len] = '\0'; + + return result; +} + +/* + * Copy xmlChar string to PostgreSQL-owned memory, freeing the input. + * + * The input xmlChar is freed regardless of success of the copy. + */ +static char * +xml_pstrdup_and_free(xmlChar *str) +{ + char *result; + + if (str) + { + PG_TRY(); + { + result = pstrdup((char *) str); + } + PG_FINALLY(); + { + xmlFree(str); + } + PG_END_TRY(); + } + else + result = NULL; + + return result; +} + +/* + * str is the null-terminated input string. Remaining arguments are + * output arguments; each can be NULL if value is not wanted. + * version and encoding are returned as locally-palloc'd strings. + * Result is 0 if OK, an error code if not. + */ +static int +parse_xml_decl(const xmlChar *str, size_t *lenp, + xmlChar **version, xmlChar **encoding, int *standalone) +{ + const xmlChar *p; + const xmlChar *save_p; + size_t len; + int utf8char; + int utf8len; + + /* + * Only initialize libxml. We don't need error handling here, but we do + * need to make sure libxml is initialized before calling any of its + * functions. Note that this is safe (and a no-op) if caller has already + * done pg_xml_init(). + */ + pg_xml_init_library(); + + /* Initialize output arguments to "not present" */ + if (version) + *version = NULL; + if (encoding) + *encoding = NULL; + if (standalone) + *standalone = -1; + + p = str; + + if (xmlStrncmp(p, (xmlChar *) "<?xml", 5) != 0) + goto finished; + + /* + * If next char is a name char, it's a PI like <?xml-stylesheet ...?> + * rather than an XMLDecl, so we have done what we came to do and found no + * XMLDecl. + * + * We need an input length value for xmlGetUTF8Char, but there's no need + * to count the whole document size, so use strnlen not strlen. + */ + utf8len = strnlen((const char *) (p + 5), MAX_MULTIBYTE_CHAR_LEN); + utf8char = xmlGetUTF8Char(p + 5, &utf8len); + if (PG_XMLISNAMECHAR(utf8char)) + goto finished; + + p += 5; + + /* version */ + CHECK_XML_SPACE(p); + SKIP_XML_SPACE(p); + if (xmlStrncmp(p, (xmlChar *) "version", 7) != 0) + return XML_ERR_VERSION_MISSING; + p += 7; + SKIP_XML_SPACE(p); + if (*p != '=') + return XML_ERR_VERSION_MISSING; + p += 1; + SKIP_XML_SPACE(p); + + if (*p == '\'' || *p == '"') + { + const xmlChar *q; + + q = xmlStrchr(p + 1, *p); + if (!q) + return XML_ERR_VERSION_MISSING; + + if (version) + *version = xml_pnstrdup(p + 1, q - p - 1); + p = q + 1; + } + else + return XML_ERR_VERSION_MISSING; + + /* encoding */ + save_p = p; + SKIP_XML_SPACE(p); + if (xmlStrncmp(p, (xmlChar *) "encoding", 8) == 0) + { + CHECK_XML_SPACE(save_p); + p += 8; + SKIP_XML_SPACE(p); + if (*p != '=') + return XML_ERR_MISSING_ENCODING; + p += 1; + SKIP_XML_SPACE(p); + + if (*p == '\'' || *p == '"') + { + const xmlChar *q; + + q = xmlStrchr(p + 1, *p); + if (!q) + return XML_ERR_MISSING_ENCODING; + + if (encoding) + *encoding = xml_pnstrdup(p + 1, q - p - 1); + p = q + 1; + } + else + return XML_ERR_MISSING_ENCODING; + } + else + { + p = save_p; + } + + /* standalone */ + save_p = p; + SKIP_XML_SPACE(p); + if (xmlStrncmp(p, (xmlChar *) "standalone", 10) == 0) + { + CHECK_XML_SPACE(save_p); + p += 10; + SKIP_XML_SPACE(p); + if (*p != '=') + return XML_ERR_STANDALONE_VALUE; + p += 1; + SKIP_XML_SPACE(p); + if (xmlStrncmp(p, (xmlChar *) "'yes'", 5) == 0 || + xmlStrncmp(p, (xmlChar *) "\"yes\"", 5) == 0) + { + if (standalone) + *standalone = 1; + p += 5; + } + else if (xmlStrncmp(p, (xmlChar *) "'no'", 4) == 0 || + xmlStrncmp(p, (xmlChar *) "\"no\"", 4) == 0) + { + if (standalone) + *standalone = 0; + p += 4; + } + else + return XML_ERR_STANDALONE_VALUE; + } + else + { + p = save_p; + } + + SKIP_XML_SPACE(p); + if (xmlStrncmp(p, (xmlChar *) "?>", 2) != 0) + return XML_ERR_XMLDECL_NOT_FINISHED; + p += 2; + +finished: + len = p - str; + + for (p = str; p < str + len; p++) + if (*p > 127) + return XML_ERR_INVALID_CHAR; + + if (lenp) + *lenp = len; + + return XML_ERR_OK; +} + + +/* + * Write an XML declaration. On output, we adjust the XML declaration + * as follows. (These rules are the moral equivalent of the clause + * "Serialization of an XML value" in the SQL standard.) + * + * We try to avoid generating an XML declaration if possible. This is + * so that you don't get trivial things like xml '<foo/>' resulting in + * '<?xml version="1.0"?><foo/>', which would surely be annoying. We + * must provide a declaration if the standalone property is specified + * or if we include an encoding declaration. If we have a + * declaration, we must specify a version (XML requires this). + * Otherwise we only make a declaration if the version is not "1.0", + * which is the default version specified in SQL:2003. + */ +static bool +print_xml_decl(StringInfo buf, const xmlChar *version, + pg_enc encoding, int standalone) +{ + if ((version && strcmp((const char *) version, PG_XML_DEFAULT_VERSION) != 0) + || (encoding && encoding != PG_UTF8) + || standalone != -1) + { + appendStringInfoString(buf, "<?xml"); + + if (version) + appendStringInfo(buf, " version=\"%s\"", version); + else + appendStringInfo(buf, " version=\"%s\"", PG_XML_DEFAULT_VERSION); + + if (encoding && encoding != PG_UTF8) + { + /* + * XXX might be useful to convert this to IANA names (ISO-8859-1 + * instead of LATIN1 etc.); needs field experience + */ + appendStringInfo(buf, " encoding=\"%s\"", + pg_encoding_to_char(encoding)); + } + + if (standalone == 1) + appendStringInfoString(buf, " standalone=\"yes\""); + else if (standalone == 0) + appendStringInfoString(buf, " standalone=\"no\""); + appendStringInfoString(buf, "?>"); + + return true; + } + else + return false; +} + +/* + * Test whether an input that is to be parsed as CONTENT contains a DTD. + * + * The SQL/XML:2003 definition of CONTENT ("XMLDecl? content") is not + * satisfied by a document with a DTD, which is a bit of a wart, as it means + * the CONTENT type is not a proper superset of DOCUMENT. SQL/XML:2006 and + * later fix that, by redefining content with reference to the "more + * permissive" Document Node of the XQuery/XPath Data Model, such that any + * DOCUMENT value is indeed also a CONTENT value. That definition is more + * useful, as CONTENT becomes usable for parsing input of unknown form (think + * pg_restore). + * + * As used below in parse_xml when parsing for CONTENT, libxml does not give + * us the 2006+ behavior, but only the 2003; it will choke if the input has + * a DTD. But we can provide the 2006+ definition of CONTENT easily enough, + * by detecting this case first and simply doing the parse as DOCUMENT. + * + * A DTD can be found arbitrarily far in, but that would be a contrived case; + * it will ordinarily start within a few dozen characters. The only things + * that can precede it are an XMLDecl (here, the caller will have called + * parse_xml_decl already), whitespace, comments, and processing instructions. + * This function need only return true if it sees a valid sequence of such + * things leading to <!DOCTYPE. It can simply return false in any other + * cases, including malformed input; that will mean the input gets parsed as + * CONTENT as originally planned, with libxml reporting any errors. + * + * This is only to be called from xml_parse, when pg_xml_init has already + * been called. The input is already in UTF8 encoding. + */ +static bool +xml_doctype_in_content(const xmlChar *str) +{ + const xmlChar *p = str; + + for (;;) + { + const xmlChar *e; + + SKIP_XML_SPACE(p); + if (*p != '<') + return false; + p++; + + if (*p == '!') + { + p++; + + /* if we see <!DOCTYPE, we can return true */ + if (xmlStrncmp(p, (xmlChar *) "DOCTYPE", 7) == 0) + return true; + + /* otherwise, if it's not a comment, fail */ + if (xmlStrncmp(p, (xmlChar *) "--", 2) != 0) + return false; + /* find end of comment: find -- and a > must follow */ + p = xmlStrstr(p + 2, (xmlChar *) "--"); + if (!p || p[2] != '>') + return false; + /* advance over comment, and keep scanning */ + p += 3; + continue; + } + + /* otherwise, if it's not a PI <?target something?>, fail */ + if (*p != '?') + return false; + p++; + + /* find end of PI (the string ?> is forbidden within a PI) */ + e = xmlStrstr(p, (xmlChar *) "?>"); + if (!e) + return false; + + /* advance over PI, keep scanning */ + p = e + 2; + } +} + + +/* + * Convert a text object to XML internal representation + * + * data is the source data (must not be toasted!), encoding is its encoding, + * and xmloption_arg and preserve_whitespace are options for the + * transformation. + * + * If parsed_xmloptiontype isn't NULL, *parsed_xmloptiontype is set to the + * XmlOptionType actually used to parse the input (typically the same as + * xmloption_arg, but a DOCTYPE node in the input can force DOCUMENT mode). + * + * If parsed_nodes isn't NULL and the input is not an XML document, the list + * of parsed nodes from the xmlParseBalancedChunkMemory call will be returned + * to *parsed_nodes. + * + * Errors normally result in ereport(ERROR), but if escontext is an + * ErrorSaveContext, then "safe" errors are reported there instead, and the + * caller must check SOFT_ERROR_OCCURRED() to see whether that happened. + * + * Note: it is caller's responsibility to xmlFreeDoc() the result, + * else a permanent memory leak will ensue! But note the result could + * be NULL after a soft error. + * + * TODO maybe libxml2's xmlreader is better? (do not construct DOM, + * yet do not use SAX - see xmlreader.c) + */ +static xmlDocPtr +xml_parse(text *data, XmlOptionType xmloption_arg, + bool preserve_whitespace, int encoding, + XmlOptionType *parsed_xmloptiontype, xmlNodePtr *parsed_nodes, + Node *escontext) +{ + int32 len; + xmlChar *string; + xmlChar *utf8string; + PgXmlErrorContext *xmlerrcxt; + volatile xmlParserCtxtPtr ctxt = NULL; + volatile xmlDocPtr doc = NULL; + + /* + * This step looks annoyingly redundant, but we must do it to have a + * null-terminated string in case encoding conversion isn't required. + */ + len = VARSIZE_ANY_EXHDR(data); /* will be useful later */ + string = xml_text2xmlChar(data); + + /* + * If the data isn't UTF8, we must translate before giving it to libxml. + * + * XXX ideally, we'd catch any encoding conversion failure and return a + * soft error. However, failure to convert to UTF8 should be pretty darn + * rare, so for now this is left undone. + */ + utf8string = pg_do_encoding_conversion(string, + len, + encoding, + PG_UTF8); + + /* Start up libxml and its parser */ + xmlerrcxt = pg_xml_init(PG_XML_STRICTNESS_WELLFORMED); + + /* Use a TRY block to ensure we clean up correctly */ + PG_TRY(); + { + bool parse_as_document = false; + int res_code; + size_t count = 0; + xmlChar *version = NULL; + int standalone = 0; + + /* Any errors here are reported as hard ereport's */ + xmlInitParser(); + + ctxt = xmlNewParserCtxt(); + if (ctxt == NULL || xmlerrcxt->err_occurred) + xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY, + "could not allocate parser context"); + + /* Decide whether to parse as document or content */ + if (xmloption_arg == XMLOPTION_DOCUMENT) + parse_as_document = true; + else + { + /* Parse and skip over the XML declaration, if any */ + res_code = parse_xml_decl(utf8string, + &count, &version, NULL, &standalone); + if (res_code != 0) + { + errsave(escontext, + errcode(ERRCODE_INVALID_XML_CONTENT), + errmsg_internal("invalid XML content: invalid XML declaration"), + errdetail_for_xml_code(res_code)); + goto fail; + } + + /* Is there a DOCTYPE element? */ + if (xml_doctype_in_content(utf8string + count)) + parse_as_document = true; + } + + /* initialize output parameters */ + if (parsed_xmloptiontype != NULL) + *parsed_xmloptiontype = parse_as_document ? XMLOPTION_DOCUMENT : + XMLOPTION_CONTENT; + if (parsed_nodes != NULL) + *parsed_nodes = NULL; + + if (parse_as_document) + { + /* + * Note, that here we try to apply DTD defaults + * (XML_PARSE_DTDATTR) according to SQL/XML:2008 GR 10.16.7.d: + * 'Default values defined by internal DTD are applied'. As for + * external DTDs, we try to support them too, (see SQL/XML:2008 GR + * 10.16.7.e) + */ + doc = xmlCtxtReadDoc(ctxt, utf8string, + NULL, + "UTF-8", + XML_PARSE_DTDATTR // XML_PARSE_NOENT removed to make coverity happy + | (preserve_whitespace ? 0 : XML_PARSE_NOBLANKS)); + if (doc == NULL || xmlerrcxt->err_occurred) + { + /* Use original option to decide which error code to report */ + if (xmloption_arg == XMLOPTION_DOCUMENT) + xml_errsave(escontext, xmlerrcxt, + ERRCODE_INVALID_XML_DOCUMENT, + "invalid XML document"); + else + xml_errsave(escontext, xmlerrcxt, + ERRCODE_INVALID_XML_CONTENT, + "invalid XML content"); + goto fail; + } + } + else + { + doc = xmlNewDoc(version); + if (doc == NULL || xmlerrcxt->err_occurred) + xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY, + "could not allocate XML document"); + + Assert(doc->encoding == NULL); + doc->encoding = xmlStrdup((const xmlChar *) "UTF-8"); + if (doc->encoding == NULL || xmlerrcxt->err_occurred) + xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY, + "could not allocate XML document"); + doc->standalone = standalone; + + /* allow empty content */ + if (*(utf8string + count)) + { + res_code = xmlParseBalancedChunkMemory(doc, NULL, NULL, 0, + utf8string + count, + parsed_nodes); + if (res_code != 0 || xmlerrcxt->err_occurred) + { + xml_errsave(escontext, xmlerrcxt, + ERRCODE_INVALID_XML_CONTENT, + "invalid XML content"); + goto fail; + } + } + } + +fail: + ; + } + PG_CATCH(); + { + if (doc != NULL) + xmlFreeDoc(doc); + if (ctxt != NULL) + xmlFreeParserCtxt(ctxt); + + pg_xml_done(xmlerrcxt, true); + + PG_RE_THROW(); + } + PG_END_TRY(); + + xmlFreeParserCtxt(ctxt); + + pg_xml_done(xmlerrcxt, false); + + return doc; +} + + +/* + * xmlChar<->text conversions + */ +static xmlChar * +xml_text2xmlChar(text *in) +{ + return (xmlChar *) text_to_cstring(in); +} + + +#ifdef USE_LIBXMLCONTEXT + +/* + * Manage the special context used for all libxml allocations (but only + * in special debug builds; see notes at top of file) + */ +static void +xml_memory_init(void) +{ + /* Create memory context if not there already */ + if (LibxmlContext == NULL) + LibxmlContext = AllocSetContextCreate(TopMemoryContext, + "Libxml context", + ALLOCSET_DEFAULT_SIZES); + + /* Re-establish the callbacks even if already set */ + xmlMemSetup(xml_pfree, xml_palloc, xml_repalloc, xml_pstrdup); +} + +/* + * Wrappers for memory management functions + */ +static void * +xml_palloc(size_t size) +{ + return MemoryContextAlloc(LibxmlContext, size); +} + + +static void * +xml_repalloc(void *ptr, size_t size) +{ + return repalloc(ptr, size); +} + + +static void +xml_pfree(void *ptr) +{ + /* At least some parts of libxml assume xmlFree(NULL) is allowed */ + if (ptr) + pfree(ptr); +} + + +static char * +xml_pstrdup(const char *string) +{ + return MemoryContextStrdup(LibxmlContext, string); +} +#endif /* USE_LIBXMLCONTEXT */ + + +/* + * xmlPgEntityLoader --- entity loader callback function + * + * Silently prevent any external entity URL from being loaded. We don't want + * to throw an error, so instead make the entity appear to expand to an empty + * string. + * + * We would prefer to allow loading entities that exist in the system's + * global XML catalog; but the available libxml2 APIs make that a complex + * and fragile task. For now, just shut down all external access. + */ +static xmlParserInputPtr +xmlPgEntityLoader(const char *URL, const char *ID, + xmlParserCtxtPtr ctxt) +{ + return xmlNewStringInputStream(ctxt, (const xmlChar *) ""); +} + + +/* + * xml_ereport --- report an XML-related error + * + * The "msg" is the SQL-level message; some can be adopted from the SQL/XML + * standard. This function adds libxml's native error message, if any, as + * detail. + * + * This is exported for modules that want to share the core libxml error + * handler. Note that pg_xml_init() *must* have been called previously. + */ +void +xml_ereport(PgXmlErrorContext *errcxt, int level, int sqlcode, const char *msg) +{ + char *detail; + + /* Defend against someone passing us a bogus context struct */ + if (errcxt->magic != ERRCXT_MAGIC) + elog(ERROR, "xml_ereport called with invalid PgXmlErrorContext"); + + /* Flag that the current libxml error has been reported */ + errcxt->err_occurred = false; + + /* Include detail only if we have some text from libxml */ + if (errcxt->err_buf.len > 0) + detail = errcxt->err_buf.data; + else + detail = NULL; + + ereport(level, + (errcode(sqlcode), + errmsg_internal("%s", msg), + detail ? errdetail_internal("%s", detail) : 0)); +} + + +/* + * xml_errsave --- save an XML-related error + * + * If escontext is an ErrorSaveContext, error details are saved into it, + * and control returns normally. + * + * Otherwise, the error is thrown, so that this is equivalent to + * xml_ereport() with level == ERROR. + * + * This should be used only for errors that we're sure we do not need + * a transaction abort to clean up after. + */ +static void +xml_errsave(Node *escontext, PgXmlErrorContext *errcxt, + int sqlcode, const char *msg) +{ + char *detail; + + /* Defend against someone passing us a bogus context struct */ + if (errcxt->magic != ERRCXT_MAGIC) + elog(ERROR, "xml_errsave called with invalid PgXmlErrorContext"); + + /* Flag that the current libxml error has been reported */ + errcxt->err_occurred = false; + + /* Include detail only if we have some text from libxml */ + if (errcxt->err_buf.len > 0) + detail = errcxt->err_buf.data; + else + detail = NULL; + + errsave(escontext, + (errcode(sqlcode), + errmsg_internal("%s", msg), + detail ? errdetail_internal("%s", detail) : 0)); +} + + +/* + * Error handler for libxml errors and warnings + */ +static void +xml_errorHandler(void *data, PgXmlErrorPtr error) +{ + PgXmlErrorContext *xmlerrcxt = (PgXmlErrorContext *) data; + xmlParserCtxtPtr ctxt = (xmlParserCtxtPtr) error->ctxt; + xmlParserInputPtr input = (ctxt != NULL) ? ctxt->input : NULL; + xmlNodePtr node = error->node; + const xmlChar *name = (node != NULL && + node->type == XML_ELEMENT_NODE) ? node->name : NULL; + int domain = error->domain; + int level = error->level; + StringInfo errorBuf; + + /* + * Defend against someone passing us a bogus context struct. + * + * We force a backend exit if this check fails because longjmp'ing out of + * libxml would likely render it unsafe to use further. + */ + if (xmlerrcxt->magic != ERRCXT_MAGIC) + elog(FATAL, "xml_errorHandler called with invalid PgXmlErrorContext"); + + /*---------- + * Older libxml versions report some errors differently. + * First, some errors were previously reported as coming from the parser + * domain but are now reported as coming from the namespace domain. + * Second, some warnings were upgraded to errors. + * We attempt to compensate for that here. + *---------- + */ + switch (error->code) + { + case XML_WAR_NS_URI: + level = XML_ERR_ERROR; + domain = XML_FROM_NAMESPACE; + break; + + case XML_ERR_NS_DECL_ERROR: + case XML_WAR_NS_URI_RELATIVE: + case XML_WAR_NS_COLUMN: + case XML_NS_ERR_XML_NAMESPACE: + case XML_NS_ERR_UNDEFINED_NAMESPACE: + case XML_NS_ERR_QNAME: + case XML_NS_ERR_ATTRIBUTE_REDEFINED: + case XML_NS_ERR_EMPTY: + domain = XML_FROM_NAMESPACE; + break; + } + + /* Decide whether to act on the error or not */ + switch (domain) + { + case XML_FROM_PARSER: + case XML_FROM_NONE: + case XML_FROM_MEMORY: + case XML_FROM_IO: + + /* + * Suppress warnings about undeclared entities. We need to do + * this to avoid problems due to not loading DTD definitions. + */ + if (error->code == XML_WAR_UNDECLARED_ENTITY) + return; + + /* Otherwise, accept error regardless of the parsing purpose */ + break; + + default: + /* Ignore error if only doing well-formedness check */ + if (xmlerrcxt->strictness == PG_XML_STRICTNESS_WELLFORMED) + return; + break; + } + + /* Prepare error message in errorBuf */ + errorBuf = makeStringInfo(); + + if (error->line > 0) + appendStringInfo(errorBuf, "line %d: ", error->line); + if (name != NULL) + appendStringInfo(errorBuf, "element %s: ", name); + if (error->message != NULL) + appendStringInfoString(errorBuf, error->message); + else + appendStringInfoString(errorBuf, "(no message provided)"); + + /* + * Append context information to errorBuf. + * + * xmlParserPrintFileContext() uses libxml's "generic" error handler to + * write the context. Since we don't want to duplicate libxml + * functionality here, we set up a generic error handler temporarily. + * + * We use appendStringInfo() directly as libxml's generic error handler. + * This should work because it has essentially the same signature as + * libxml expects, namely (void *ptr, const char *msg, ...). + */ + if (input != NULL) + { + xmlGenericErrorFunc errFuncSaved = xmlGenericError; + void *errCtxSaved = xmlGenericErrorContext; + + xmlSetGenericErrorFunc((void *) errorBuf, + (xmlGenericErrorFunc) appendStringInfo); + + /* Add context information to errorBuf */ + appendStringInfoLineSeparator(errorBuf); + + xmlParserPrintFileContext(input); + + /* Restore generic error func */ + xmlSetGenericErrorFunc(errCtxSaved, errFuncSaved); + } + + /* Get rid of any trailing newlines in errorBuf */ + chopStringInfoNewlines(errorBuf); + + /* + * Legacy error handling mode. err_occurred is never set, we just add the + * message to err_buf. This mode exists because the xml2 contrib module + * uses our error-handling infrastructure, but we don't want to change its + * behaviour since it's deprecated anyway. This is also why we don't + * distinguish between notices, warnings and errors here --- the old-style + * generic error handler wouldn't have done that either. + */ + if (xmlerrcxt->strictness == PG_XML_STRICTNESS_LEGACY) + { + appendStringInfoLineSeparator(&xmlerrcxt->err_buf); + appendBinaryStringInfo(&xmlerrcxt->err_buf, errorBuf->data, + errorBuf->len); + + pfree(errorBuf->data); + pfree(errorBuf); + return; + } + + /* + * We don't want to ereport() here because that'd probably leave libxml in + * an inconsistent state. Instead, we remember the error and ereport() + * from xml_ereport(). + * + * Warnings and notices can be reported immediately since they won't cause + * a longjmp() out of libxml. + */ + if (level >= XML_ERR_ERROR) + { + appendStringInfoLineSeparator(&xmlerrcxt->err_buf); + appendBinaryStringInfo(&xmlerrcxt->err_buf, errorBuf->data, + errorBuf->len); + + xmlerrcxt->err_occurred = true; + } + else if (level >= XML_ERR_WARNING) + { + ereport(WARNING, + (errmsg_internal("%s", errorBuf->data))); + } + else + { + ereport(NOTICE, + (errmsg_internal("%s", errorBuf->data))); + } + + pfree(errorBuf->data); + pfree(errorBuf); +} + + +/* + * Convert libxml error codes into textual errdetail messages. + * + * This should be called within an ereport or errsave invocation, + * just as errdetail would be. + * + * At the moment, we only need to cover those codes that we + * may raise in this file. + */ +static int +errdetail_for_xml_code(int code) +{ + const char *det; + + switch (code) + { + case XML_ERR_INVALID_CHAR: + det = gettext_noop("Invalid character value."); + break; + case XML_ERR_SPACE_REQUIRED: + det = gettext_noop("Space required."); + break; + case XML_ERR_STANDALONE_VALUE: + det = gettext_noop("standalone accepts only 'yes' or 'no'."); + break; + case XML_ERR_VERSION_MISSING: + det = gettext_noop("Malformed declaration: missing version."); + break; + case XML_ERR_MISSING_ENCODING: + det = gettext_noop("Missing encoding in text declaration."); + break; + case XML_ERR_XMLDECL_NOT_FINISHED: + det = gettext_noop("Parsing XML declaration: '?>' expected."); + break; + default: + det = gettext_noop("Unrecognized libxml error code: %d."); + break; + } + + return errdetail(det, code); +} + + +/* + * Remove all trailing newlines from a StringInfo string + */ +static void +chopStringInfoNewlines(StringInfo str) +{ + while (str->len > 0 && str->data[str->len - 1] == '\n') + str->data[--str->len] = '\0'; +} + + +/* + * Append a newline after removing any existing trailing newlines + */ +static void +appendStringInfoLineSeparator(StringInfo str) +{ + chopStringInfoNewlines(str); + if (str->len > 0) + appendStringInfoChar(str, '\n'); +} + + +/* + * Convert one char in the current server encoding to a Unicode codepoint. + */ +static pg_wchar +sqlchar_to_unicode(const char *s) +{ + char *utf8string; + pg_wchar ret[2]; /* need space for trailing zero */ + + /* note we're not assuming s is null-terminated */ + utf8string = pg_server_to_any(s, pg_mblen(s), PG_UTF8); + + pg_encoding_mb2wchar_with_len(PG_UTF8, utf8string, ret, + pg_encoding_mblen(PG_UTF8, utf8string)); + + if (utf8string != s) + pfree(utf8string); + + return ret[0]; +} + + +static bool +is_valid_xml_namefirst(pg_wchar c) +{ + /* (Letter | '_' | ':') */ + return (xmlIsBaseCharQ(c) || xmlIsIdeographicQ(c) + || c == '_' || c == ':'); +} + + +static bool +is_valid_xml_namechar(pg_wchar c) +{ + /* Letter | Digit | '.' | '-' | '_' | ':' | CombiningChar | Extender */ + return (xmlIsBaseCharQ(c) || xmlIsIdeographicQ(c) + || xmlIsDigitQ(c) + || c == '.' || c == '-' || c == '_' || c == ':' + || xmlIsCombiningQ(c) + || xmlIsExtenderQ(c)); +} +#endif /* USE_LIBXML */ + + +/* + * Map SQL identifier to XML name; see SQL/XML:2008 section 9.1. + */ +char * +map_sql_identifier_to_xml_name(const char *ident, bool fully_escaped, + bool escape_period) +{ +#ifdef USE_LIBXML + StringInfoData buf; + const char *p; + + /* + * SQL/XML doesn't make use of this case anywhere, so it's probably a + * mistake. + */ + Assert(fully_escaped || !escape_period); + + initStringInfo(&buf); + + for (p = ident; *p; p += pg_mblen(p)) + { + if (*p == ':' && (p == ident || fully_escaped)) + appendStringInfoString(&buf, "_x003A_"); + else if (*p == '_' && *(p + 1) == 'x') + appendStringInfoString(&buf, "_x005F_"); + else if (fully_escaped && p == ident && + pg_strncasecmp(p, "xml", 3) == 0) + { + if (*p == 'x') + appendStringInfoString(&buf, "_x0078_"); + else + appendStringInfoString(&buf, "_x0058_"); + } + else if (escape_period && *p == '.') + appendStringInfoString(&buf, "_x002E_"); + else + { + pg_wchar u = sqlchar_to_unicode(p); + + if ((p == ident) + ? !is_valid_xml_namefirst(u) + : !is_valid_xml_namechar(u)) + appendStringInfo(&buf, "_x%04X_", (unsigned int) u); + else + appendBinaryStringInfo(&buf, p, pg_mblen(p)); + } + } + + return buf.data; +#else /* not USE_LIBXML */ + NO_XML_SUPPORT(); + return NULL; +#endif /* not USE_LIBXML */ +} + + +/* + * Map XML name to SQL identifier; see SQL/XML:2008 section 9.3. + */ +char * +map_xml_name_to_sql_identifier(const char *name) +{ + StringInfoData buf; + const char *p; + + initStringInfo(&buf); + + for (p = name; *p; p += pg_mblen(p)) + { + if (*p == '_' && *(p + 1) == 'x' + && isxdigit((unsigned char) *(p + 2)) + && isxdigit((unsigned char) *(p + 3)) + && isxdigit((unsigned char) *(p + 4)) + && isxdigit((unsigned char) *(p + 5)) + && *(p + 6) == '_') + { + char cbuf[MAX_UNICODE_EQUIVALENT_STRING + 1]; + unsigned int u; + + sscanf(p + 2, "%X", &u); + pg_unicode_to_server(u, (unsigned char *) cbuf); + appendStringInfoString(&buf, cbuf); + p += 6; + } + else + appendBinaryStringInfo(&buf, p, pg_mblen(p)); + } + + return buf.data; +} + +/* + * Map SQL value to XML value; see SQL/XML:2008 section 9.8. + * + * When xml_escape_strings is true, then certain characters in string + * values are replaced by entity references (< etc.), as specified + * in SQL/XML:2008 section 9.8 GR 9) a) iii). This is normally what is + * wanted. The false case is mainly useful when the resulting value + * is used with xmlTextWriterWriteAttribute() to write out an + * attribute, because that function does the escaping itself. + */ +char * +map_sql_value_to_xml_value(Datum value, Oid type, bool xml_escape_strings) +{ + if (type_is_array_domain(type)) + { + ArrayType *array; + Oid elmtype; + int16 elmlen; + bool elmbyval; + char elmalign; + int num_elems; + Datum *elem_values; + bool *elem_nulls; + StringInfoData buf; + int i; + + array = DatumGetArrayTypeP(value); + elmtype = ARR_ELEMTYPE(array); + get_typlenbyvalalign(elmtype, &elmlen, &elmbyval, &elmalign); + + deconstruct_array(array, elmtype, + elmlen, elmbyval, elmalign, + &elem_values, &elem_nulls, + &num_elems); + + initStringInfo(&buf); + + for (i = 0; i < num_elems; i++) + { + if (elem_nulls[i]) + continue; + appendStringInfoString(&buf, "<element>"); + appendStringInfoString(&buf, + map_sql_value_to_xml_value(elem_values[i], + elmtype, true)); + appendStringInfoString(&buf, "</element>"); + } + + pfree(elem_values); + pfree(elem_nulls); + + return buf.data; + } + else + { + Oid typeOut; + bool isvarlena; + char *str; + + /* + * Flatten domains; the special-case treatments below should apply to, + * eg, domains over boolean not just boolean. + */ + type = getBaseType(type); + + /* + * Special XSD formatting for some data types + */ + switch (type) + { + case BOOLOID: + if (DatumGetBool(value)) + return "true"; + else + return "false"; + + case DATEOID: + { + DateADT date; + struct pg_tm tm; + char buf[MAXDATELEN + 1]; + + date = DatumGetDateADT(value); + /* XSD doesn't support infinite values */ + if (DATE_NOT_FINITE(date)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("date out of range"), + errdetail("XML does not support infinite date values."))); + j2date(date + POSTGRES_EPOCH_JDATE, + &(tm.tm_year), &(tm.tm_mon), &(tm.tm_mday)); + EncodeDateOnly(&tm, USE_XSD_DATES, buf); + + return pstrdup(buf); + } + + case TIMESTAMPOID: + { + Timestamp timestamp; + struct pg_tm tm; + fsec_t fsec; + char buf[MAXDATELEN + 1]; + + timestamp = DatumGetTimestamp(value); + + /* XSD doesn't support infinite values */ + if (TIMESTAMP_NOT_FINITE(timestamp)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"), + errdetail("XML does not support infinite timestamp values."))); + else if (timestamp2tm(timestamp, NULL, &tm, &fsec, NULL, NULL) == 0) + EncodeDateTime(&tm, fsec, false, 0, NULL, USE_XSD_DATES, buf); + else + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + + return pstrdup(buf); + } + + case TIMESTAMPTZOID: + { + TimestampTz timestamp; + struct pg_tm tm; + int tz; + fsec_t fsec; + const char *tzn = NULL; + char buf[MAXDATELEN + 1]; + + timestamp = DatumGetTimestamp(value); + + /* XSD doesn't support infinite values */ + if (TIMESTAMP_NOT_FINITE(timestamp)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"), + errdetail("XML does not support infinite timestamp values."))); + else if (timestamp2tm(timestamp, &tz, &tm, &fsec, &tzn, NULL) == 0) + EncodeDateTime(&tm, fsec, true, tz, tzn, USE_XSD_DATES, buf); + else + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + + return pstrdup(buf); + } + +#ifdef USE_LIBXML + case BYTEAOID: + { + bytea *bstr = DatumGetByteaPP(value); + PgXmlErrorContext *xmlerrcxt; + volatile xmlBufferPtr buf = NULL; + volatile xmlTextWriterPtr writer = NULL; + char *result; + + xmlerrcxt = pg_xml_init(PG_XML_STRICTNESS_ALL); + + PG_TRY(); + { + buf = xmlBufferCreate(); + if (buf == NULL || xmlerrcxt->err_occurred) + xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY, + "could not allocate xmlBuffer"); + writer = xmlNewTextWriterMemory(buf, 0); + if (writer == NULL || xmlerrcxt->err_occurred) + xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY, + "could not allocate xmlTextWriter"); + + if (xmlbinary == XMLBINARY_BASE64) + xmlTextWriterWriteBase64(writer, VARDATA_ANY(bstr), + 0, VARSIZE_ANY_EXHDR(bstr)); + else + xmlTextWriterWriteBinHex(writer, VARDATA_ANY(bstr), + 0, VARSIZE_ANY_EXHDR(bstr)); + + /* we MUST do this now to flush data out to the buffer */ + xmlFreeTextWriter(writer); + writer = NULL; + + result = pstrdup((const char *) xmlBufferContent(buf)); + } + PG_CATCH(); + { + if (writer) + xmlFreeTextWriter(writer); + if (buf) + xmlBufferFree(buf); + + pg_xml_done(xmlerrcxt, true); + + PG_RE_THROW(); + } + PG_END_TRY(); + + xmlBufferFree(buf); + + pg_xml_done(xmlerrcxt, false); + + return result; + } +#endif /* USE_LIBXML */ + + } + + /* + * otherwise, just use the type's native text representation + */ + getTypeOutputInfo(type, &typeOut, &isvarlena); + str = OidOutputFunctionCall(typeOut, value); + + /* ... exactly as-is for XML, and when escaping is not wanted */ + if (type == XMLOID || !xml_escape_strings) + return str; + + /* otherwise, translate special characters as needed */ + return escape_xml(str); + } +} + + +/* + * Escape characters in text that have special meanings in XML. + * + * Returns a palloc'd string. + * + * NB: this is intentionally not dependent on libxml. + */ +char * +escape_xml(const char *str) +{ + StringInfoData buf; + const char *p; + + initStringInfo(&buf); + for (p = str; *p; p++) + { + switch (*p) + { + case '&': + appendStringInfoString(&buf, "&"); + break; + case '<': + appendStringInfoString(&buf, "<"); + break; + case '>': + appendStringInfoString(&buf, ">"); + break; + case '\r': + appendStringInfoString(&buf, "
"); + break; + default: + appendStringInfoCharMacro(&buf, *p); + break; + } + } + return buf.data; +} + + +static char * +_SPI_strdup(const char *s) +{ + size_t len = strlen(s) + 1; + char *ret = SPI_palloc(len); + + memcpy(ret, s, len); + return ret; +} + + +/* + * SQL to XML mapping functions + * + * What follows below was at one point intentionally organized so that + * you can read along in the SQL/XML standard. The functions are + * mostly split up the way the clauses lay out in the standards + * document, and the identifiers are also aligned with the standard + * text. Unfortunately, SQL/XML:2006 reordered the clauses + * differently than SQL/XML:2003, so the order below doesn't make much + * sense anymore. + * + * There are many things going on there: + * + * There are two kinds of mappings: Mapping SQL data (table contents) + * to XML documents, and mapping SQL structure (the "schema") to XML + * Schema. And there are functions that do both at the same time. + * + * Then you can map a database, a schema, or a table, each in both + * ways. This breaks down recursively: Mapping a database invokes + * mapping schemas, which invokes mapping tables, which invokes + * mapping rows, which invokes mapping columns, although you can't + * call the last two from the outside. Because of this, there are a + * number of xyz_internal() functions which are to be called both from + * the function manager wrapper and from some upper layer in a + * recursive call. + * + * See the documentation about what the common function arguments + * nulls, tableforest, and targetns mean. + * + * Some style guidelines for XML output: Use double quotes for quoting + * XML attributes. Indent XML elements by two spaces, but remember + * that a lot of code is called recursively at different levels, so + * it's better not to indent rather than create output that indents + * and outdents weirdly. Add newlines to make the output look nice. + */ + + +/* + * Visibility of objects for XML mappings; see SQL/XML:2008 section + * 4.10.8. + */ + +/* + * Given a query, which must return type oid as first column, produce + * a list of Oids with the query results. + */ +static List * +query_to_oid_list(const char *query) +{ + uint64 i; + List *list = NIL; + int spi_result; + + spi_result = SPI_execute(query, true, 0); + if (spi_result != SPI_OK_SELECT) + elog(ERROR, "SPI_execute returned %s for %s", + SPI_result_code_string(spi_result), query); + + for (i = 0; i < SPI_processed; i++) + { + Datum oid; + bool isnull; + + oid = SPI_getbinval(SPI_tuptable->vals[i], + SPI_tuptable->tupdesc, + 1, + &isnull); + if (!isnull) + list = lappend_oid(list, DatumGetObjectId(oid)); + } + + return list; +} + + +static List * +schema_get_xml_visible_tables(Oid nspid) +{ + StringInfoData query; + + initStringInfo(&query); + appendStringInfo(&query, "SELECT oid FROM pg_catalog.pg_class" + " WHERE relnamespace = %u AND relkind IN (" + CppAsString2(RELKIND_RELATION) "," + CppAsString2(RELKIND_MATVIEW) "," + CppAsString2(RELKIND_VIEW) ")" + " AND pg_catalog.has_table_privilege (oid, 'SELECT')" + " ORDER BY relname;", nspid); + + return query_to_oid_list(query.data); +} + + +/* + * Including the system schemas is probably not useful for a database + * mapping. + */ +#define XML_VISIBLE_SCHEMAS_EXCLUDE "(nspname ~ '^pg_' OR nspname = 'information_schema')" + +#define XML_VISIBLE_SCHEMAS "SELECT oid FROM pg_catalog.pg_namespace WHERE pg_catalog.has_schema_privilege (oid, 'USAGE') AND NOT " XML_VISIBLE_SCHEMAS_EXCLUDE + + +static List * +database_get_xml_visible_schemas(void) +{ + return query_to_oid_list(XML_VISIBLE_SCHEMAS " ORDER BY nspname;"); +} + + +static List * +database_get_xml_visible_tables(void) +{ + /* At the moment there is no order required here. */ + return query_to_oid_list("SELECT oid FROM pg_catalog.pg_class" + " WHERE relkind IN (" + CppAsString2(RELKIND_RELATION) "," + CppAsString2(RELKIND_MATVIEW) "," + CppAsString2(RELKIND_VIEW) ")" + " AND pg_catalog.has_table_privilege(pg_class.oid, 'SELECT')" + " AND relnamespace IN (" XML_VISIBLE_SCHEMAS ");"); +} + + +/* + * Map SQL table to XML and/or XML Schema document; see SQL/XML:2008 + * section 9.11. + */ + +static StringInfo +table_to_xml_internal(Oid relid, + const char *xmlschema, bool nulls, bool tableforest, + const char *targetns, bool top_level) +{ + StringInfoData query; + + initStringInfo(&query); + appendStringInfo(&query, "SELECT * FROM %s", + DatumGetCString(DirectFunctionCall1(regclassout, + ObjectIdGetDatum(relid)))); + return query_to_xml_internal(query.data, get_rel_name(relid), + xmlschema, nulls, tableforest, + targetns, top_level); +} + + +Datum +table_to_xml(PG_FUNCTION_ARGS) +{ + Oid relid = PG_GETARG_OID(0); + bool nulls = PG_GETARG_BOOL(1); + bool tableforest = PG_GETARG_BOOL(2); + const char *targetns = text_to_cstring(PG_GETARG_TEXT_PP(3)); + + PG_RETURN_XML_P(stringinfo_to_xmltype(table_to_xml_internal(relid, NULL, + nulls, tableforest, + targetns, true))); +} + + +Datum +query_to_xml(PG_FUNCTION_ARGS) +{ + char *query = text_to_cstring(PG_GETARG_TEXT_PP(0)); + bool nulls = PG_GETARG_BOOL(1); + bool tableforest = PG_GETARG_BOOL(2); + const char *targetns = text_to_cstring(PG_GETARG_TEXT_PP(3)); + + PG_RETURN_XML_P(stringinfo_to_xmltype(query_to_xml_internal(query, NULL, + NULL, nulls, tableforest, + targetns, true))); +} + + +Datum +cursor_to_xml(PG_FUNCTION_ARGS) +{ + char *name = text_to_cstring(PG_GETARG_TEXT_PP(0)); + int32 count = PG_GETARG_INT32(1); + bool nulls = PG_GETARG_BOOL(2); + bool tableforest = PG_GETARG_BOOL(3); + const char *targetns = text_to_cstring(PG_GETARG_TEXT_PP(4)); + + StringInfoData result; + Portal portal; + uint64 i; + + initStringInfo(&result); + + if (!tableforest) + { + xmldata_root_element_start(&result, "table", NULL, targetns, true); + appendStringInfoChar(&result, '\n'); + } + + SPI_connect(); + portal = SPI_cursor_find(name); + if (portal == NULL) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_CURSOR), + errmsg("cursor \"%s\" does not exist", name))); + + SPI_cursor_fetch(portal, true, count); + for (i = 0; i < SPI_processed; i++) + SPI_sql_row_to_xmlelement(i, &result, NULL, nulls, + tableforest, targetns, true); + + SPI_finish(); + + if (!tableforest) + xmldata_root_element_end(&result, "table"); + + PG_RETURN_XML_P(stringinfo_to_xmltype(&result)); +} + + +/* + * Write the start tag of the root element of a data mapping. + * + * top_level means that this is the very top level of the eventual + * output. For example, when the user calls table_to_xml, then a call + * with a table name to this function is the top level. When the user + * calls database_to_xml, then a call with a schema name to this + * function is not the top level. If top_level is false, then the XML + * namespace declarations are omitted, because they supposedly already + * appeared earlier in the output. Repeating them is not wrong, but + * it looks ugly. + */ +static void +xmldata_root_element_start(StringInfo result, const char *eltname, + const char *xmlschema, const char *targetns, + bool top_level) +{ + /* This isn't really wrong but currently makes no sense. */ + Assert(top_level || !xmlschema); + + appendStringInfo(result, "<%s", eltname); + if (top_level) + { + appendStringInfoString(result, " xmlns:xsi=\"" NAMESPACE_XSI "\""); + if (strlen(targetns) > 0) + appendStringInfo(result, " xmlns=\"%s\"", targetns); + } + if (xmlschema) + { + /* FIXME: better targets */ + if (strlen(targetns) > 0) + appendStringInfo(result, " xsi:schemaLocation=\"%s #\"", targetns); + else + appendStringInfoString(result, " xsi:noNamespaceSchemaLocation=\"#\""); + } + appendStringInfoString(result, ">\n"); +} + + +static void +xmldata_root_element_end(StringInfo result, const char *eltname) +{ + appendStringInfo(result, "</%s>\n", eltname); +} + + +static StringInfo +query_to_xml_internal(const char *query, char *tablename, + const char *xmlschema, bool nulls, bool tableforest, + const char *targetns, bool top_level) +{ + StringInfo result; + char *xmltn; + uint64 i; + + if (tablename) + xmltn = map_sql_identifier_to_xml_name(tablename, true, false); + else + xmltn = "table"; + + result = makeStringInfo(); + + SPI_connect(); + if (SPI_execute(query, true, 0) != SPI_OK_SELECT) + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("invalid query"))); + + if (!tableforest) + { + xmldata_root_element_start(result, xmltn, xmlschema, + targetns, top_level); + appendStringInfoChar(result, '\n'); + } + + if (xmlschema) + appendStringInfo(result, "%s\n\n", xmlschema); + + for (i = 0; i < SPI_processed; i++) + SPI_sql_row_to_xmlelement(i, result, tablename, nulls, + tableforest, targetns, top_level); + + if (!tableforest) + xmldata_root_element_end(result, xmltn); + + SPI_finish(); + + return result; +} + + +Datum +table_to_xmlschema(PG_FUNCTION_ARGS) +{ + Oid relid = PG_GETARG_OID(0); + bool nulls = PG_GETARG_BOOL(1); + bool tableforest = PG_GETARG_BOOL(2); + const char *targetns = text_to_cstring(PG_GETARG_TEXT_PP(3)); + const char *result; + Relation rel; + + rel = table_open(relid, AccessShareLock); + result = map_sql_table_to_xmlschema(rel->rd_att, relid, nulls, + tableforest, targetns); + table_close(rel, NoLock); + + PG_RETURN_XML_P(cstring_to_xmltype(result)); +} + + +Datum +query_to_xmlschema(PG_FUNCTION_ARGS) +{ + char *query = text_to_cstring(PG_GETARG_TEXT_PP(0)); + bool nulls = PG_GETARG_BOOL(1); + bool tableforest = PG_GETARG_BOOL(2); + const char *targetns = text_to_cstring(PG_GETARG_TEXT_PP(3)); + const char *result; + SPIPlanPtr plan; + Portal portal; + + SPI_connect(); + + if ((plan = SPI_prepare(query, 0, NULL)) == NULL) + elog(ERROR, "SPI_prepare(\"%s\") failed", query); + + if ((portal = SPI_cursor_open(NULL, plan, NULL, NULL, true)) == NULL) + elog(ERROR, "SPI_cursor_open(\"%s\") failed", query); + + result = _SPI_strdup(map_sql_table_to_xmlschema(portal->tupDesc, + InvalidOid, nulls, + tableforest, targetns)); + SPI_cursor_close(portal); + SPI_finish(); + + PG_RETURN_XML_P(cstring_to_xmltype(result)); +} + + +Datum +cursor_to_xmlschema(PG_FUNCTION_ARGS) +{ + char *name = text_to_cstring(PG_GETARG_TEXT_PP(0)); + bool nulls = PG_GETARG_BOOL(1); + bool tableforest = PG_GETARG_BOOL(2); + const char *targetns = text_to_cstring(PG_GETARG_TEXT_PP(3)); + const char *xmlschema; + Portal portal; + + SPI_connect(); + portal = SPI_cursor_find(name); + if (portal == NULL) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_CURSOR), + errmsg("cursor \"%s\" does not exist", name))); + if (portal->tupDesc == NULL) + ereport(ERROR, + (errcode(ERRCODE_INVALID_CURSOR_STATE), + errmsg("portal \"%s\" does not return tuples", name))); + + xmlschema = _SPI_strdup(map_sql_table_to_xmlschema(portal->tupDesc, + InvalidOid, nulls, + tableforest, targetns)); + SPI_finish(); + + PG_RETURN_XML_P(cstring_to_xmltype(xmlschema)); +} + + +Datum +table_to_xml_and_xmlschema(PG_FUNCTION_ARGS) +{ + Oid relid = PG_GETARG_OID(0); + bool nulls = PG_GETARG_BOOL(1); + bool tableforest = PG_GETARG_BOOL(2); + const char *targetns = text_to_cstring(PG_GETARG_TEXT_PP(3)); + Relation rel; + const char *xmlschema; + + rel = table_open(relid, AccessShareLock); + xmlschema = map_sql_table_to_xmlschema(rel->rd_att, relid, nulls, + tableforest, targetns); + table_close(rel, NoLock); + + PG_RETURN_XML_P(stringinfo_to_xmltype(table_to_xml_internal(relid, + xmlschema, nulls, tableforest, + targetns, true))); +} + + +Datum +query_to_xml_and_xmlschema(PG_FUNCTION_ARGS) +{ + char *query = text_to_cstring(PG_GETARG_TEXT_PP(0)); + bool nulls = PG_GETARG_BOOL(1); + bool tableforest = PG_GETARG_BOOL(2); + const char *targetns = text_to_cstring(PG_GETARG_TEXT_PP(3)); + + const char *xmlschema; + SPIPlanPtr plan; + Portal portal; + + SPI_connect(); + + if ((plan = SPI_prepare(query, 0, NULL)) == NULL) + elog(ERROR, "SPI_prepare(\"%s\") failed", query); + + if ((portal = SPI_cursor_open(NULL, plan, NULL, NULL, true)) == NULL) + elog(ERROR, "SPI_cursor_open(\"%s\") failed", query); + + xmlschema = _SPI_strdup(map_sql_table_to_xmlschema(portal->tupDesc, + InvalidOid, nulls, tableforest, targetns)); + SPI_cursor_close(portal); + SPI_finish(); + + PG_RETURN_XML_P(stringinfo_to_xmltype(query_to_xml_internal(query, NULL, + xmlschema, nulls, tableforest, + targetns, true))); +} + + +/* + * Map SQL schema to XML and/or XML Schema document; see SQL/XML:2008 + * sections 9.13, 9.14. + */ + +static StringInfo +schema_to_xml_internal(Oid nspid, const char *xmlschema, bool nulls, + bool tableforest, const char *targetns, bool top_level) +{ + StringInfo result; + char *xmlsn; + List *relid_list; + ListCell *cell; + + xmlsn = map_sql_identifier_to_xml_name(get_namespace_name(nspid), + true, false); + result = makeStringInfo(); + + xmldata_root_element_start(result, xmlsn, xmlschema, targetns, top_level); + appendStringInfoChar(result, '\n'); + + if (xmlschema) + appendStringInfo(result, "%s\n\n", xmlschema); + + SPI_connect(); + + relid_list = schema_get_xml_visible_tables(nspid); + + foreach(cell, relid_list) + { + Oid relid = lfirst_oid(cell); + StringInfo subres; + + subres = table_to_xml_internal(relid, NULL, nulls, tableforest, + targetns, false); + + appendBinaryStringInfo(result, subres->data, subres->len); + appendStringInfoChar(result, '\n'); + } + + SPI_finish(); + + xmldata_root_element_end(result, xmlsn); + + return result; +} + + +Datum +schema_to_xml(PG_FUNCTION_ARGS) +{ + Name name = PG_GETARG_NAME(0); + bool nulls = PG_GETARG_BOOL(1); + bool tableforest = PG_GETARG_BOOL(2); + const char *targetns = text_to_cstring(PG_GETARG_TEXT_PP(3)); + + char *schemaname; + Oid nspid; + + schemaname = NameStr(*name); + nspid = LookupExplicitNamespace(schemaname, false); + + PG_RETURN_XML_P(stringinfo_to_xmltype(schema_to_xml_internal(nspid, NULL, + nulls, tableforest, targetns, true))); +} + + +/* + * Write the start element of the root element of an XML Schema mapping. + */ +static void +xsd_schema_element_start(StringInfo result, const char *targetns) +{ + appendStringInfoString(result, + "<xsd:schema\n" + " xmlns:xsd=\"" NAMESPACE_XSD "\""); + if (strlen(targetns) > 0) + appendStringInfo(result, + "\n" + " targetNamespace=\"%s\"\n" + " elementFormDefault=\"qualified\"", + targetns); + appendStringInfoString(result, + ">\n\n"); +} + + +static void +xsd_schema_element_end(StringInfo result) +{ + appendStringInfoString(result, "</xsd:schema>"); +} + + +static StringInfo +schema_to_xmlschema_internal(const char *schemaname, bool nulls, + bool tableforest, const char *targetns) +{ + Oid nspid; + List *relid_list; + List *tupdesc_list; + ListCell *cell; + StringInfo result; + + result = makeStringInfo(); + + nspid = LookupExplicitNamespace(schemaname, false); + + xsd_schema_element_start(result, targetns); + + SPI_connect(); + + relid_list = schema_get_xml_visible_tables(nspid); + + tupdesc_list = NIL; + foreach(cell, relid_list) + { + Relation rel; + + rel = table_open(lfirst_oid(cell), AccessShareLock); + tupdesc_list = lappend(tupdesc_list, CreateTupleDescCopy(rel->rd_att)); + table_close(rel, NoLock); + } + + appendStringInfoString(result, + map_sql_typecoll_to_xmlschema_types(tupdesc_list)); + + appendStringInfoString(result, + map_sql_schema_to_xmlschema_types(nspid, relid_list, + nulls, tableforest, targetns)); + + xsd_schema_element_end(result); + + SPI_finish(); + + return result; +} + + +Datum +schema_to_xmlschema(PG_FUNCTION_ARGS) +{ + Name name = PG_GETARG_NAME(0); + bool nulls = PG_GETARG_BOOL(1); + bool tableforest = PG_GETARG_BOOL(2); + const char *targetns = text_to_cstring(PG_GETARG_TEXT_PP(3)); + + PG_RETURN_XML_P(stringinfo_to_xmltype(schema_to_xmlschema_internal(NameStr(*name), + nulls, tableforest, targetns))); +} + + +Datum +schema_to_xml_and_xmlschema(PG_FUNCTION_ARGS) +{ + Name name = PG_GETARG_NAME(0); + bool nulls = PG_GETARG_BOOL(1); + bool tableforest = PG_GETARG_BOOL(2); + const char *targetns = text_to_cstring(PG_GETARG_TEXT_PP(3)); + char *schemaname; + Oid nspid; + StringInfo xmlschema; + + schemaname = NameStr(*name); + nspid = LookupExplicitNamespace(schemaname, false); + + xmlschema = schema_to_xmlschema_internal(schemaname, nulls, + tableforest, targetns); + + PG_RETURN_XML_P(stringinfo_to_xmltype(schema_to_xml_internal(nspid, + xmlschema->data, nulls, + tableforest, targetns, true))); +} + + +/* + * Map SQL database to XML and/or XML Schema document; see SQL/XML:2008 + * sections 9.16, 9.17. + */ + +static StringInfo +database_to_xml_internal(const char *xmlschema, bool nulls, + bool tableforest, const char *targetns) +{ + StringInfo result; + List *nspid_list; + ListCell *cell; + char *xmlcn; + + xmlcn = map_sql_identifier_to_xml_name(get_database_name(MyDatabaseId), + true, false); + result = makeStringInfo(); + + xmldata_root_element_start(result, xmlcn, xmlschema, targetns, true); + appendStringInfoChar(result, '\n'); + + if (xmlschema) + appendStringInfo(result, "%s\n\n", xmlschema); + + SPI_connect(); + + nspid_list = database_get_xml_visible_schemas(); + + foreach(cell, nspid_list) + { + Oid nspid = lfirst_oid(cell); + StringInfo subres; + + subres = schema_to_xml_internal(nspid, NULL, nulls, + tableforest, targetns, false); + + appendBinaryStringInfo(result, subres->data, subres->len); + appendStringInfoChar(result, '\n'); + } + + SPI_finish(); + + xmldata_root_element_end(result, xmlcn); + + return result; +} + + +Datum +database_to_xml(PG_FUNCTION_ARGS) +{ + bool nulls = PG_GETARG_BOOL(0); + bool tableforest = PG_GETARG_BOOL(1); + const char *targetns = text_to_cstring(PG_GETARG_TEXT_PP(2)); + + PG_RETURN_XML_P(stringinfo_to_xmltype(database_to_xml_internal(NULL, nulls, + tableforest, targetns))); +} + + +static StringInfo +database_to_xmlschema_internal(bool nulls, bool tableforest, + const char *targetns) +{ + List *relid_list; + List *nspid_list; + List *tupdesc_list; + ListCell *cell; + StringInfo result; + + result = makeStringInfo(); + + xsd_schema_element_start(result, targetns); + + SPI_connect(); + + relid_list = database_get_xml_visible_tables(); + nspid_list = database_get_xml_visible_schemas(); + + tupdesc_list = NIL; + foreach(cell, relid_list) + { + Relation rel; + + rel = table_open(lfirst_oid(cell), AccessShareLock); + tupdesc_list = lappend(tupdesc_list, CreateTupleDescCopy(rel->rd_att)); + table_close(rel, NoLock); + } + + appendStringInfoString(result, + map_sql_typecoll_to_xmlschema_types(tupdesc_list)); + + appendStringInfoString(result, + map_sql_catalog_to_xmlschema_types(nspid_list, nulls, tableforest, targetns)); + + xsd_schema_element_end(result); + + SPI_finish(); + + return result; +} + + +Datum +database_to_xmlschema(PG_FUNCTION_ARGS) +{ + bool nulls = PG_GETARG_BOOL(0); + bool tableforest = PG_GETARG_BOOL(1); + const char *targetns = text_to_cstring(PG_GETARG_TEXT_PP(2)); + + PG_RETURN_XML_P(stringinfo_to_xmltype(database_to_xmlschema_internal(nulls, + tableforest, targetns))); +} + + +Datum +database_to_xml_and_xmlschema(PG_FUNCTION_ARGS) +{ + bool nulls = PG_GETARG_BOOL(0); + bool tableforest = PG_GETARG_BOOL(1); + const char *targetns = text_to_cstring(PG_GETARG_TEXT_PP(2)); + StringInfo xmlschema; + + xmlschema = database_to_xmlschema_internal(nulls, tableforest, targetns); + + PG_RETURN_XML_P(stringinfo_to_xmltype(database_to_xml_internal(xmlschema->data, + nulls, tableforest, targetns))); +} + + +/* + * Map a multi-part SQL name to an XML name; see SQL/XML:2008 section + * 9.2. + */ +static char * +map_multipart_sql_identifier_to_xml_name(const char *a, const char *b, const char *c, const char *d) +{ + StringInfoData result; + + initStringInfo(&result); + + if (a) + appendStringInfoString(&result, + map_sql_identifier_to_xml_name(a, true, true)); + if (b) + appendStringInfo(&result, ".%s", + map_sql_identifier_to_xml_name(b, true, true)); + if (c) + appendStringInfo(&result, ".%s", + map_sql_identifier_to_xml_name(c, true, true)); + if (d) + appendStringInfo(&result, ".%s", + map_sql_identifier_to_xml_name(d, true, true)); + + return result.data; +} + + +/* + * Map an SQL table to an XML Schema document; see SQL/XML:2008 + * section 9.11. + * + * Map an SQL table to XML Schema data types; see SQL/XML:2008 section + * 9.9. + */ +static const char * +map_sql_table_to_xmlschema(TupleDesc tupdesc, Oid relid, bool nulls, + bool tableforest, const char *targetns) +{ + int i; + char *xmltn; + char *tabletypename; + char *rowtypename; + StringInfoData result; + + initStringInfo(&result); + + if (OidIsValid(relid)) + { + HeapTuple tuple; + Form_pg_class reltuple; + + tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for relation %u", relid); + reltuple = (Form_pg_class) GETSTRUCT(tuple); + + xmltn = map_sql_identifier_to_xml_name(NameStr(reltuple->relname), + true, false); + + tabletypename = map_multipart_sql_identifier_to_xml_name("TableType", + get_database_name(MyDatabaseId), + get_namespace_name(reltuple->relnamespace), + NameStr(reltuple->relname)); + + rowtypename = map_multipart_sql_identifier_to_xml_name("RowType", + get_database_name(MyDatabaseId), + get_namespace_name(reltuple->relnamespace), + NameStr(reltuple->relname)); + + ReleaseSysCache(tuple); + } + else + { + if (tableforest) + xmltn = "row"; + else + xmltn = "table"; + + tabletypename = "TableType"; + rowtypename = "RowType"; + } + + xsd_schema_element_start(&result, targetns); + + appendStringInfoString(&result, + map_sql_typecoll_to_xmlschema_types(list_make1(tupdesc))); + + appendStringInfo(&result, + "<xsd:complexType name=\"%s\">\n" + " <xsd:sequence>\n", + rowtypename); + + for (i = 0; i < tupdesc->natts; i++) + { + Form_pg_attribute att = TupleDescAttr(tupdesc, i); + + if (att->attisdropped) + continue; + appendStringInfo(&result, + " <xsd:element name=\"%s\" type=\"%s\"%s></xsd:element>\n", + map_sql_identifier_to_xml_name(NameStr(att->attname), + true, false), + map_sql_type_to_xml_name(att->atttypid, -1), + nulls ? " nillable=\"true\"" : " minOccurs=\"0\""); + } + + appendStringInfoString(&result, + " </xsd:sequence>\n" + "</xsd:complexType>\n\n"); + + if (!tableforest) + { + appendStringInfo(&result, + "<xsd:complexType name=\"%s\">\n" + " <xsd:sequence>\n" + " <xsd:element name=\"row\" type=\"%s\" minOccurs=\"0\" maxOccurs=\"unbounded\"/>\n" + " </xsd:sequence>\n" + "</xsd:complexType>\n\n", + tabletypename, rowtypename); + + appendStringInfo(&result, + "<xsd:element name=\"%s\" type=\"%s\"/>\n\n", + xmltn, tabletypename); + } + else + appendStringInfo(&result, + "<xsd:element name=\"%s\" type=\"%s\"/>\n\n", + xmltn, rowtypename); + + xsd_schema_element_end(&result); + + return result.data; +} + + +/* + * Map an SQL schema to XML Schema data types; see SQL/XML:2008 + * section 9.12. + */ +static const char * +map_sql_schema_to_xmlschema_types(Oid nspid, List *relid_list, bool nulls, + bool tableforest, const char *targetns) +{ + char *dbname; + char *nspname; + char *xmlsn; + char *schematypename; + StringInfoData result; + ListCell *cell; + + dbname = get_database_name(MyDatabaseId); + nspname = get_namespace_name(nspid); + + initStringInfo(&result); + + xmlsn = map_sql_identifier_to_xml_name(nspname, true, false); + + schematypename = map_multipart_sql_identifier_to_xml_name("SchemaType", + dbname, + nspname, + NULL); + + appendStringInfo(&result, + "<xsd:complexType name=\"%s\">\n", schematypename); + if (!tableforest) + appendStringInfoString(&result, + " <xsd:all>\n"); + else + appendStringInfoString(&result, + " <xsd:sequence>\n"); + + foreach(cell, relid_list) + { + Oid relid = lfirst_oid(cell); + char *relname = get_rel_name(relid); + char *xmltn = map_sql_identifier_to_xml_name(relname, true, false); + char *tabletypename = map_multipart_sql_identifier_to_xml_name(tableforest ? "RowType" : "TableType", + dbname, + nspname, + relname); + + if (!tableforest) + appendStringInfo(&result, + " <xsd:element name=\"%s\" type=\"%s\"/>\n", + xmltn, tabletypename); + else + appendStringInfo(&result, + " <xsd:element name=\"%s\" type=\"%s\" minOccurs=\"0\" maxOccurs=\"unbounded\"/>\n", + xmltn, tabletypename); + } + + if (!tableforest) + appendStringInfoString(&result, + " </xsd:all>\n"); + else + appendStringInfoString(&result, + " </xsd:sequence>\n"); + appendStringInfoString(&result, + "</xsd:complexType>\n\n"); + + appendStringInfo(&result, + "<xsd:element name=\"%s\" type=\"%s\"/>\n\n", + xmlsn, schematypename); + + return result.data; +} + + +/* + * Map an SQL catalog to XML Schema data types; see SQL/XML:2008 + * section 9.15. + */ +static const char * +map_sql_catalog_to_xmlschema_types(List *nspid_list, bool nulls, + bool tableforest, const char *targetns) +{ + char *dbname; + char *xmlcn; + char *catalogtypename; + StringInfoData result; + ListCell *cell; + + dbname = get_database_name(MyDatabaseId); + + initStringInfo(&result); + + xmlcn = map_sql_identifier_to_xml_name(dbname, true, false); + + catalogtypename = map_multipart_sql_identifier_to_xml_name("CatalogType", + dbname, + NULL, + NULL); + + appendStringInfo(&result, + "<xsd:complexType name=\"%s\">\n", catalogtypename); + appendStringInfoString(&result, + " <xsd:all>\n"); + + foreach(cell, nspid_list) + { + Oid nspid = lfirst_oid(cell); + char *nspname = get_namespace_name(nspid); + char *xmlsn = map_sql_identifier_to_xml_name(nspname, true, false); + char *schematypename = map_multipart_sql_identifier_to_xml_name("SchemaType", + dbname, + nspname, + NULL); + + appendStringInfo(&result, + " <xsd:element name=\"%s\" type=\"%s\"/>\n", + xmlsn, schematypename); + } + + appendStringInfoString(&result, + " </xsd:all>\n"); + appendStringInfoString(&result, + "</xsd:complexType>\n\n"); + + appendStringInfo(&result, + "<xsd:element name=\"%s\" type=\"%s\"/>\n\n", + xmlcn, catalogtypename); + + return result.data; +} + + +/* + * Map an SQL data type to an XML name; see SQL/XML:2008 section 9.4. + */ +static const char * +map_sql_type_to_xml_name(Oid typeoid, int typmod) +{ + StringInfoData result; + + initStringInfo(&result); + + switch (typeoid) + { + case BPCHAROID: + if (typmod == -1) + appendStringInfoString(&result, "CHAR"); + else + appendStringInfo(&result, "CHAR_%d", typmod - VARHDRSZ); + break; + case VARCHAROID: + if (typmod == -1) + appendStringInfoString(&result, "VARCHAR"); + else + appendStringInfo(&result, "VARCHAR_%d", typmod - VARHDRSZ); + break; + case NUMERICOID: + if (typmod == -1) + appendStringInfoString(&result, "NUMERIC"); + else + appendStringInfo(&result, "NUMERIC_%d_%d", + ((typmod - VARHDRSZ) >> 16) & 0xffff, + (typmod - VARHDRSZ) & 0xffff); + break; + case INT4OID: + appendStringInfoString(&result, "INTEGER"); + break; + case INT2OID: + appendStringInfoString(&result, "SMALLINT"); + break; + case INT8OID: + appendStringInfoString(&result, "BIGINT"); + break; + case FLOAT4OID: + appendStringInfoString(&result, "REAL"); + break; + case FLOAT8OID: + appendStringInfoString(&result, "DOUBLE"); + break; + case BOOLOID: + appendStringInfoString(&result, "BOOLEAN"); + break; + case TIMEOID: + if (typmod == -1) + appendStringInfoString(&result, "TIME"); + else + appendStringInfo(&result, "TIME_%d", typmod); + break; + case TIMETZOID: + if (typmod == -1) + appendStringInfoString(&result, "TIME_WTZ"); + else + appendStringInfo(&result, "TIME_WTZ_%d", typmod); + break; + case TIMESTAMPOID: + if (typmod == -1) + appendStringInfoString(&result, "TIMESTAMP"); + else + appendStringInfo(&result, "TIMESTAMP_%d", typmod); + break; + case TIMESTAMPTZOID: + if (typmod == -1) + appendStringInfoString(&result, "TIMESTAMP_WTZ"); + else + appendStringInfo(&result, "TIMESTAMP_WTZ_%d", typmod); + break; + case DATEOID: + appendStringInfoString(&result, "DATE"); + break; + case XMLOID: + appendStringInfoString(&result, "XML"); + break; + default: + { + HeapTuple tuple; + Form_pg_type typtuple; + + tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typeoid)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for type %u", typeoid); + typtuple = (Form_pg_type) GETSTRUCT(tuple); + + appendStringInfoString(&result, + map_multipart_sql_identifier_to_xml_name((typtuple->typtype == TYPTYPE_DOMAIN) ? "Domain" : "UDT", + get_database_name(MyDatabaseId), + get_namespace_name(typtuple->typnamespace), + NameStr(typtuple->typname))); + + ReleaseSysCache(tuple); + } + } + + return result.data; +} + + +/* + * Map a collection of SQL data types to XML Schema data types; see + * SQL/XML:2008 section 9.7. + */ +static const char * +map_sql_typecoll_to_xmlschema_types(List *tupdesc_list) +{ + List *uniquetypes = NIL; + int i; + StringInfoData result; + ListCell *cell0; + + /* extract all column types used in the set of TupleDescs */ + foreach(cell0, tupdesc_list) + { + TupleDesc tupdesc = (TupleDesc) lfirst(cell0); + + for (i = 0; i < tupdesc->natts; i++) + { + Form_pg_attribute att = TupleDescAttr(tupdesc, i); + + if (att->attisdropped) + continue; + uniquetypes = list_append_unique_oid(uniquetypes, att->atttypid); + } + } + + /* add base types of domains */ + foreach(cell0, uniquetypes) + { + Oid typid = lfirst_oid(cell0); + Oid basetypid = getBaseType(typid); + + if (basetypid != typid) + uniquetypes = list_append_unique_oid(uniquetypes, basetypid); + } + + /* Convert to textual form */ + initStringInfo(&result); + + foreach(cell0, uniquetypes) + { + appendStringInfo(&result, "%s\n", + map_sql_type_to_xmlschema_type(lfirst_oid(cell0), + -1)); + } + + return result.data; +} + + +/* + * Map an SQL data type to a named XML Schema data type; see + * SQL/XML:2008 sections 9.5 and 9.6. + * + * (The distinction between 9.5 and 9.6 is basically that 9.6 adds + * a name attribute, which this function does. The name-less version + * 9.5 doesn't appear to be required anywhere.) + */ +static const char * +map_sql_type_to_xmlschema_type(Oid typeoid, int typmod) +{ + StringInfoData result; + const char *typename = map_sql_type_to_xml_name(typeoid, typmod); + + initStringInfo(&result); + + if (typeoid == XMLOID) + { + appendStringInfoString(&result, + "<xsd:complexType mixed=\"true\">\n" + " <xsd:sequence>\n" + " <xsd:any name=\"element\" minOccurs=\"0\" maxOccurs=\"unbounded\" processContents=\"skip\"/>\n" + " </xsd:sequence>\n" + "</xsd:complexType>\n"); + } + else + { + appendStringInfo(&result, + "<xsd:simpleType name=\"%s\">\n", typename); + + switch (typeoid) + { + case BPCHAROID: + case VARCHAROID: + case TEXTOID: + appendStringInfoString(&result, + " <xsd:restriction base=\"xsd:string\">\n"); + if (typmod != -1) + appendStringInfo(&result, + " <xsd:maxLength value=\"%d\"/>\n", + typmod - VARHDRSZ); + appendStringInfoString(&result, " </xsd:restriction>\n"); + break; + + case BYTEAOID: + appendStringInfo(&result, + " <xsd:restriction base=\"xsd:%s\">\n" + " </xsd:restriction>\n", + xmlbinary == XMLBINARY_BASE64 ? "base64Binary" : "hexBinary"); + break; + + case NUMERICOID: + if (typmod != -1) + appendStringInfo(&result, + " <xsd:restriction base=\"xsd:decimal\">\n" + " <xsd:totalDigits value=\"%d\"/>\n" + " <xsd:fractionDigits value=\"%d\"/>\n" + " </xsd:restriction>\n", + ((typmod - VARHDRSZ) >> 16) & 0xffff, + (typmod - VARHDRSZ) & 0xffff); + break; + + case INT2OID: + appendStringInfo(&result, + " <xsd:restriction base=\"xsd:short\">\n" + " <xsd:maxInclusive value=\"%d\"/>\n" + " <xsd:minInclusive value=\"%d\"/>\n" + " </xsd:restriction>\n", + SHRT_MAX, SHRT_MIN); + break; + + case INT4OID: + appendStringInfo(&result, + " <xsd:restriction base=\"xsd:int\">\n" + " <xsd:maxInclusive value=\"%d\"/>\n" + " <xsd:minInclusive value=\"%d\"/>\n" + " </xsd:restriction>\n", + INT_MAX, INT_MIN); + break; + + case INT8OID: + appendStringInfo(&result, + " <xsd:restriction base=\"xsd:long\">\n" + " <xsd:maxInclusive value=\"" INT64_FORMAT "\"/>\n" + " <xsd:minInclusive value=\"" INT64_FORMAT "\"/>\n" + " </xsd:restriction>\n", + PG_INT64_MAX, + PG_INT64_MIN); + break; + + case FLOAT4OID: + appendStringInfoString(&result, + " <xsd:restriction base=\"xsd:float\"></xsd:restriction>\n"); + break; + + case FLOAT8OID: + appendStringInfoString(&result, + " <xsd:restriction base=\"xsd:double\"></xsd:restriction>\n"); + break; + + case BOOLOID: + appendStringInfoString(&result, + " <xsd:restriction base=\"xsd:boolean\"></xsd:restriction>\n"); + break; + + case TIMEOID: + case TIMETZOID: + { + const char *tz = (typeoid == TIMETZOID ? "(\\+|-)\\p{Nd}{2}:\\p{Nd}{2}" : ""); + + if (typmod == -1) + appendStringInfo(&result, + " <xsd:restriction base=\"xsd:time\">\n" + " <xsd:pattern value=\"\\p{Nd}{2}:\\p{Nd}{2}:\\p{Nd}{2}(.\\p{Nd}+)?%s\"/>\n" + " </xsd:restriction>\n", tz); + else if (typmod == 0) + appendStringInfo(&result, + " <xsd:restriction base=\"xsd:time\">\n" + " <xsd:pattern value=\"\\p{Nd}{2}:\\p{Nd}{2}:\\p{Nd}{2}%s\"/>\n" + " </xsd:restriction>\n", tz); + else + appendStringInfo(&result, + " <xsd:restriction base=\"xsd:time\">\n" + " <xsd:pattern value=\"\\p{Nd}{2}:\\p{Nd}{2}:\\p{Nd}{2}.\\p{Nd}{%d}%s\"/>\n" + " </xsd:restriction>\n", typmod - VARHDRSZ, tz); + break; + } + + case TIMESTAMPOID: + case TIMESTAMPTZOID: + { + const char *tz = (typeoid == TIMESTAMPTZOID ? "(\\+|-)\\p{Nd}{2}:\\p{Nd}{2}" : ""); + + if (typmod == -1) + appendStringInfo(&result, + " <xsd:restriction base=\"xsd:dateTime\">\n" + " <xsd:pattern value=\"\\p{Nd}{4}-\\p{Nd}{2}-\\p{Nd}{2}T\\p{Nd}{2}:\\p{Nd}{2}:\\p{Nd}{2}(.\\p{Nd}+)?%s\"/>\n" + " </xsd:restriction>\n", tz); + else if (typmod == 0) + appendStringInfo(&result, + " <xsd:restriction base=\"xsd:dateTime\">\n" + " <xsd:pattern value=\"\\p{Nd}{4}-\\p{Nd}{2}-\\p{Nd}{2}T\\p{Nd}{2}:\\p{Nd}{2}:\\p{Nd}{2}%s\"/>\n" + " </xsd:restriction>\n", tz); + else + appendStringInfo(&result, + " <xsd:restriction base=\"xsd:dateTime\">\n" + " <xsd:pattern value=\"\\p{Nd}{4}-\\p{Nd}{2}-\\p{Nd}{2}T\\p{Nd}{2}:\\p{Nd}{2}:\\p{Nd}{2}.\\p{Nd}{%d}%s\"/>\n" + " </xsd:restriction>\n", typmod - VARHDRSZ, tz); + break; + } + + case DATEOID: + appendStringInfoString(&result, + " <xsd:restriction base=\"xsd:date\">\n" + " <xsd:pattern value=\"\\p{Nd}{4}-\\p{Nd}{2}-\\p{Nd}{2}\"/>\n" + " </xsd:restriction>\n"); + break; + + default: + if (get_typtype(typeoid) == TYPTYPE_DOMAIN) + { + Oid base_typeoid; + int32 base_typmod = -1; + + base_typeoid = getBaseTypeAndTypmod(typeoid, &base_typmod); + + appendStringInfo(&result, + " <xsd:restriction base=\"%s\"/>\n", + map_sql_type_to_xml_name(base_typeoid, base_typmod)); + } + break; + } + appendStringInfoString(&result, "</xsd:simpleType>\n"); + } + + return result.data; +} + + +/* + * Map an SQL row to an XML element, taking the row from the active + * SPI cursor. See also SQL/XML:2008 section 9.10. + */ +static void +SPI_sql_row_to_xmlelement(uint64 rownum, StringInfo result, char *tablename, + bool nulls, bool tableforest, + const char *targetns, bool top_level) +{ + int i; + char *xmltn; + + if (tablename) + xmltn = map_sql_identifier_to_xml_name(tablename, true, false); + else + { + if (tableforest) + xmltn = "row"; + else + xmltn = "table"; + } + + if (tableforest) + xmldata_root_element_start(result, xmltn, NULL, targetns, top_level); + else + appendStringInfoString(result, "<row>\n"); + + for (i = 1; i <= SPI_tuptable->tupdesc->natts; i++) + { + char *colname; + Datum colval; + bool isnull; + + colname = map_sql_identifier_to_xml_name(SPI_fname(SPI_tuptable->tupdesc, i), + true, false); + colval = SPI_getbinval(SPI_tuptable->vals[rownum], + SPI_tuptable->tupdesc, + i, + &isnull); + if (isnull) + { + if (nulls) + appendStringInfo(result, " <%s xsi:nil=\"true\"/>\n", colname); + } + else + appendStringInfo(result, " <%s>%s</%s>\n", + colname, + map_sql_value_to_xml_value(colval, + SPI_gettypeid(SPI_tuptable->tupdesc, i), true), + colname); + } + + if (tableforest) + { + xmldata_root_element_end(result, xmltn); + appendStringInfoChar(result, '\n'); + } + else + appendStringInfoString(result, "</row>\n\n"); +} + + +/* + * XPath related functions + */ + +#ifdef USE_LIBXML + +/* + * Convert XML node to text. + * + * For attribute and text nodes, return the escaped text. For anything else, + * dump the whole subtree. + */ +static text * +xml_xmlnodetoxmltype(xmlNodePtr cur, PgXmlErrorContext *xmlerrcxt) +{ + xmltype *result = NULL; + + if (cur->type != XML_ATTRIBUTE_NODE && cur->type != XML_TEXT_NODE) + { + void (*volatile nodefree) (xmlNodePtr) = NULL; + volatile xmlBufferPtr buf = NULL; + volatile xmlNodePtr cur_copy = NULL; + + PG_TRY(); + { + int bytes; + + buf = xmlBufferCreate(); + if (buf == NULL || xmlerrcxt->err_occurred) + xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY, + "could not allocate xmlBuffer"); + + /* + * Produce a dump of the node that we can serialize. xmlNodeDump + * does that, but the result of that function won't contain + * namespace definitions from ancestor nodes, so we first do a + * xmlCopyNode() which duplicates the node along with its required + * namespace definitions. + * + * Some old libxml2 versions such as 2.7.6 produce partially + * broken XML_DOCUMENT_NODE nodes (unset content field) when + * copying them. xmlNodeDump of such a node works fine, but + * xmlFreeNode crashes; set us up to call xmlFreeDoc instead. + */ + cur_copy = xmlCopyNode(cur, 1); + if (cur_copy == NULL || xmlerrcxt->err_occurred) + xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY, + "could not copy node"); + nodefree = (cur_copy->type == XML_DOCUMENT_NODE) ? + (void (*) (xmlNodePtr)) xmlFreeDoc : xmlFreeNode; + + bytes = xmlNodeDump(buf, NULL, cur_copy, 0, 0); + if (bytes == -1 || xmlerrcxt->err_occurred) + xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY, + "could not dump node"); + + result = xmlBuffer_to_xmltype(buf); + } + PG_FINALLY(); + { + if (nodefree) + nodefree(cur_copy); + if (buf) + xmlBufferFree(buf); + } + PG_END_TRY(); + } + else + { + xmlChar *str; + + str = xmlXPathCastNodeToString(cur); + PG_TRY(); + { + /* Here we rely on XML having the same representation as TEXT */ + char *escaped = escape_xml((char *) str); + + result = (xmltype *) cstring_to_text(escaped); + pfree(escaped); + } + PG_FINALLY(); + { + xmlFree(str); + } + PG_END_TRY(); + } + + return result; +} + +/* + * Convert an XML XPath object (the result of evaluating an XPath expression) + * to an array of xml values, which are appended to astate. The function + * result value is the number of elements in the array. + * + * If "astate" is NULL then we don't generate the array value, but we still + * return the number of elements it would have had. + * + * Nodesets are converted to an array containing the nodes' textual + * representations. Primitive values (float, double, string) are converted + * to a single-element array containing the value's string representation. + */ +static int +xml_xpathobjtoxmlarray(xmlXPathObjectPtr xpathobj, + ArrayBuildState *astate, + PgXmlErrorContext *xmlerrcxt) +{ + int result = 0; + Datum datum; + Oid datumtype; + char *result_str; + + switch (xpathobj->type) + { + case XPATH_NODESET: + if (xpathobj->nodesetval != NULL) + { + result = xpathobj->nodesetval->nodeNr; + if (astate != NULL) + { + int i; + + for (i = 0; i < result; i++) + { + datum = PointerGetDatum(xml_xmlnodetoxmltype(xpathobj->nodesetval->nodeTab[i], + xmlerrcxt)); + (void) accumArrayResult(astate, datum, false, + XMLOID, CurrentMemoryContext); + } + } + } + return result; + + case XPATH_BOOLEAN: + if (astate == NULL) + return 1; + datum = BoolGetDatum(xpathobj->boolval); + datumtype = BOOLOID; + break; + + case XPATH_NUMBER: + if (astate == NULL) + return 1; + datum = Float8GetDatum(xpathobj->floatval); + datumtype = FLOAT8OID; + break; + + case XPATH_STRING: + if (astate == NULL) + return 1; + datum = CStringGetDatum((char *) xpathobj->stringval); + datumtype = CSTRINGOID; + break; + + default: + elog(ERROR, "xpath expression result type %d is unsupported", + xpathobj->type); + return 0; /* keep compiler quiet */ + } + + /* Common code for scalar-value cases */ + result_str = map_sql_value_to_xml_value(datum, datumtype, true); + datum = PointerGetDatum(cstring_to_xmltype(result_str)); + (void) accumArrayResult(astate, datum, false, + XMLOID, CurrentMemoryContext); + return 1; +} + + +/* + * Common code for xpath() and xmlexists() + * + * Evaluate XPath expression and return number of nodes in res_nitems + * and array of XML values in astate. Either of those pointers can be + * NULL if the corresponding result isn't wanted. + * + * It is up to the user to ensure that the XML passed is in fact + * an XML document - XPath doesn't work easily on fragments without + * a context node being known. + */ +static void +xpath_internal(text *xpath_expr_text, xmltype *data, ArrayType *namespaces, + int *res_nitems, ArrayBuildState *astate) +{ + PgXmlErrorContext *xmlerrcxt; + volatile xmlParserCtxtPtr ctxt = NULL; + volatile xmlDocPtr doc = NULL; + volatile xmlXPathContextPtr xpathctx = NULL; + volatile xmlXPathCompExprPtr xpathcomp = NULL; + volatile xmlXPathObjectPtr xpathobj = NULL; + char *datastr; + int32 len; + int32 xpath_len; + xmlChar *string; + xmlChar *xpath_expr; + size_t xmldecl_len = 0; + int i; + int ndim; + Datum *ns_names_uris; + bool *ns_names_uris_nulls; + int ns_count; + + /* + * Namespace mappings are passed as text[]. If an empty array is passed + * (ndim = 0, "0-dimensional"), then there are no namespace mappings. + * Else, a 2-dimensional array with length of the second axis being equal + * to 2 should be passed, i.e., every subarray contains 2 elements, the + * first element defining the name, the second one the URI. Example: + * ARRAY[ARRAY['myns', 'http://example.com'], ARRAY['myns2', + * 'http://example2.com']]. + */ + ndim = namespaces ? ARR_NDIM(namespaces) : 0; + if (ndim != 0) + { + int *dims; + + dims = ARR_DIMS(namespaces); + + if (ndim != 2 || dims[1] != 2) + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("invalid array for XML namespace mapping"), + errdetail("The array must be two-dimensional with length of the second axis equal to 2."))); + + Assert(ARR_ELEMTYPE(namespaces) == TEXTOID); + + deconstruct_array_builtin(namespaces, TEXTOID, + &ns_names_uris, &ns_names_uris_nulls, + &ns_count); + + Assert((ns_count % 2) == 0); /* checked above */ + ns_count /= 2; /* count pairs only */ + } + else + { + ns_names_uris = NULL; + ns_names_uris_nulls = NULL; + ns_count = 0; + } + + datastr = VARDATA(data); + len = VARSIZE(data) - VARHDRSZ; + xpath_len = VARSIZE_ANY_EXHDR(xpath_expr_text); + if (xpath_len == 0) + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("empty XPath expression"))); + + string = pg_xmlCharStrndup(datastr, len); + xpath_expr = pg_xmlCharStrndup(VARDATA_ANY(xpath_expr_text), xpath_len); + + /* + * In a UTF8 database, skip any xml declaration, which might assert + * another encoding. Ignore parse_xml_decl() failure, letting + * xmlCtxtReadMemory() report parse errors. Documentation disclaims + * xpath() support for non-ASCII data in non-UTF8 databases, so leave + * those scenarios bug-compatible with historical behavior. + */ + if (GetDatabaseEncoding() == PG_UTF8) + parse_xml_decl(string, &xmldecl_len, NULL, NULL, NULL); + + xmlerrcxt = pg_xml_init(PG_XML_STRICTNESS_ALL); + + PG_TRY(); + { + xmlInitParser(); + + /* + * redundant XML parsing (two parsings for the same value during one + * command execution are possible) + */ + ctxt = xmlNewParserCtxt(); + if (ctxt == NULL || xmlerrcxt->err_occurred) + xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY, + "could not allocate parser context"); + doc = xmlCtxtReadMemory(ctxt, (char *) string + xmldecl_len, + len - xmldecl_len, NULL, NULL, 0); + if (doc == NULL || xmlerrcxt->err_occurred) + xml_ereport(xmlerrcxt, ERROR, ERRCODE_INVALID_XML_DOCUMENT, + "could not parse XML document"); + xpathctx = xmlXPathNewContext(doc); + if (xpathctx == NULL || xmlerrcxt->err_occurred) + xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY, + "could not allocate XPath context"); + xpathctx->node = (xmlNodePtr) doc; + + /* register namespaces, if any */ + if (ns_count > 0) + { + for (i = 0; i < ns_count; i++) + { + char *ns_name; + char *ns_uri; + + if (ns_names_uris_nulls[i * 2] || + ns_names_uris_nulls[i * 2 + 1]) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("neither namespace name nor URI may be null"))); + ns_name = TextDatumGetCString(ns_names_uris[i * 2]); + ns_uri = TextDatumGetCString(ns_names_uris[i * 2 + 1]); + if (xmlXPathRegisterNs(xpathctx, + (xmlChar *) ns_name, + (xmlChar *) ns_uri) != 0) + ereport(ERROR, /* is this an internal error??? */ + (errmsg("could not register XML namespace with name \"%s\" and URI \"%s\"", + ns_name, ns_uri))); + } + } + + xpathcomp = xmlXPathCompile(xpath_expr); + if (xpathcomp == NULL || xmlerrcxt->err_occurred) + xml_ereport(xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR, + "invalid XPath expression"); + + /* + * Version 2.6.27 introduces a function named + * xmlXPathCompiledEvalToBoolean, which would be enough for xmlexists, + * but we can derive the existence by whether any nodes are returned, + * thereby preventing a library version upgrade and keeping the code + * the same. + */ + xpathobj = xmlXPathCompiledEval(xpathcomp, xpathctx); + if (xpathobj == NULL || xmlerrcxt->err_occurred) + xml_ereport(xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR, + "could not create XPath object"); + + /* + * Extract the results as requested. + */ + if (res_nitems != NULL) + *res_nitems = xml_xpathobjtoxmlarray(xpathobj, astate, xmlerrcxt); + else + (void) xml_xpathobjtoxmlarray(xpathobj, astate, xmlerrcxt); + } + PG_CATCH(); + { + if (xpathobj) + xmlXPathFreeObject(xpathobj); + if (xpathcomp) + xmlXPathFreeCompExpr(xpathcomp); + if (xpathctx) + xmlXPathFreeContext(xpathctx); + if (doc) + xmlFreeDoc(doc); + if (ctxt) + xmlFreeParserCtxt(ctxt); + + pg_xml_done(xmlerrcxt, true); + + PG_RE_THROW(); + } + PG_END_TRY(); + + xmlXPathFreeObject(xpathobj); + xmlXPathFreeCompExpr(xpathcomp); + xmlXPathFreeContext(xpathctx); + xmlFreeDoc(doc); + xmlFreeParserCtxt(ctxt); + + pg_xml_done(xmlerrcxt, false); +} +#endif /* USE_LIBXML */ + +/* + * Evaluate XPath expression and return array of XML values. + * + * As we have no support of XQuery sequences yet, this function seems + * to be the most useful one (array of XML functions plays a role of + * some kind of substitution for XQuery sequences). + */ +Datum +xpath(PG_FUNCTION_ARGS) +{ +#ifdef USE_LIBXML + text *xpath_expr_text = PG_GETARG_TEXT_PP(0); + xmltype *data = PG_GETARG_XML_P(1); + ArrayType *namespaces = PG_GETARG_ARRAYTYPE_P(2); + ArrayBuildState *astate; + + astate = initArrayResult(XMLOID, CurrentMemoryContext, true); + xpath_internal(xpath_expr_text, data, namespaces, + NULL, astate); + PG_RETURN_DATUM(makeArrayResult(astate, CurrentMemoryContext)); +#else + NO_XML_SUPPORT(); + return 0; +#endif +} + +/* + * Determines if the node specified by the supplied XPath exists + * in a given XML document, returning a boolean. + */ +Datum +xmlexists(PG_FUNCTION_ARGS) +{ +#ifdef USE_LIBXML + text *xpath_expr_text = PG_GETARG_TEXT_PP(0); + xmltype *data = PG_GETARG_XML_P(1); + int res_nitems; + + xpath_internal(xpath_expr_text, data, NULL, + &res_nitems, NULL); + + PG_RETURN_BOOL(res_nitems > 0); +#else + NO_XML_SUPPORT(); + return 0; +#endif +} + +/* + * Determines if the node specified by the supplied XPath exists + * in a given XML document, returning a boolean. Differs from + * xmlexists as it supports namespaces and is not defined in SQL/XML. + */ +Datum +xpath_exists(PG_FUNCTION_ARGS) +{ +#ifdef USE_LIBXML + text *xpath_expr_text = PG_GETARG_TEXT_PP(0); + xmltype *data = PG_GETARG_XML_P(1); + ArrayType *namespaces = PG_GETARG_ARRAYTYPE_P(2); + int res_nitems; + + xpath_internal(xpath_expr_text, data, namespaces, + &res_nitems, NULL); + + PG_RETURN_BOOL(res_nitems > 0); +#else + NO_XML_SUPPORT(); + return 0; +#endif +} + +/* + * Functions for checking well-formed-ness + */ + +#ifdef USE_LIBXML +static bool +wellformed_xml(text *data, XmlOptionType xmloption_arg) +{ + xmlDocPtr doc; + ErrorSaveContext escontext = {T_ErrorSaveContext}; + + /* + * We'll report "true" if no soft error is reported by xml_parse(). + */ + doc = xml_parse(data, xmloption_arg, true, + GetDatabaseEncoding(), NULL, NULL, (Node *) &escontext); + if (doc) + xmlFreeDoc(doc); + + return !escontext.error_occurred; +} +#endif + +Datum +xml_is_well_formed(PG_FUNCTION_ARGS) +{ +#ifdef USE_LIBXML + text *data = PG_GETARG_TEXT_PP(0); + + PG_RETURN_BOOL(wellformed_xml(data, xmloption)); +#else + NO_XML_SUPPORT(); + return 0; +#endif /* not USE_LIBXML */ +} + +Datum +xml_is_well_formed_document(PG_FUNCTION_ARGS) +{ +#ifdef USE_LIBXML + text *data = PG_GETARG_TEXT_PP(0); + + PG_RETURN_BOOL(wellformed_xml(data, XMLOPTION_DOCUMENT)); +#else + NO_XML_SUPPORT(); + return 0; +#endif /* not USE_LIBXML */ +} + +Datum +xml_is_well_formed_content(PG_FUNCTION_ARGS) +{ +#ifdef USE_LIBXML + text *data = PG_GETARG_TEXT_PP(0); + + PG_RETURN_BOOL(wellformed_xml(data, XMLOPTION_CONTENT)); +#else + NO_XML_SUPPORT(); + return 0; +#endif /* not USE_LIBXML */ +} + +/* + * support functions for XMLTABLE + * + */ +#ifdef USE_LIBXML + +/* + * Returns private data from executor state. Ensure validity by check with + * MAGIC number. + */ +static inline XmlTableBuilderData * +GetXmlTableBuilderPrivateData(TableFuncScanState *state, const char *fname) +{ + XmlTableBuilderData *result; + + if (!IsA(state, TableFuncScanState)) + elog(ERROR, "%s called with invalid TableFuncScanState", fname); + result = (XmlTableBuilderData *) state->opaque; + if (result->magic != XMLTABLE_CONTEXT_MAGIC) + elog(ERROR, "%s called with invalid TableFuncScanState", fname); + + return result; +} +#endif + +/* + * XmlTableInitOpaque + * Fill in TableFuncScanState->opaque for XmlTable processor; initialize + * the XML parser. + * + * Note: Because we call pg_xml_init() here and pg_xml_done() in + * XmlTableDestroyOpaque, it is critical for robustness that no other + * executor nodes run until this node is processed to completion. Caller + * must execute this to completion (probably filling a tuplestore to exhaust + * this node in a single pass) instead of using row-per-call mode. + */ +static void +XmlTableInitOpaque(TableFuncScanState *state, int natts) +{ +#ifdef USE_LIBXML + volatile xmlParserCtxtPtr ctxt = NULL; + XmlTableBuilderData *xtCxt; + PgXmlErrorContext *xmlerrcxt; + + xtCxt = palloc0(sizeof(XmlTableBuilderData)); + xtCxt->magic = XMLTABLE_CONTEXT_MAGIC; + xtCxt->natts = natts; + xtCxt->xpathscomp = palloc0(sizeof(xmlXPathCompExprPtr) * natts); + + xmlerrcxt = pg_xml_init(PG_XML_STRICTNESS_ALL); + + PG_TRY(); + { + xmlInitParser(); + + ctxt = xmlNewParserCtxt(); + if (ctxt == NULL || xmlerrcxt->err_occurred) + xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY, + "could not allocate parser context"); + } + PG_CATCH(); + { + if (ctxt != NULL) + xmlFreeParserCtxt(ctxt); + + pg_xml_done(xmlerrcxt, true); + + PG_RE_THROW(); + } + PG_END_TRY(); + + xtCxt->xmlerrcxt = xmlerrcxt; + xtCxt->ctxt = ctxt; + + state->opaque = xtCxt; +#else + NO_XML_SUPPORT(); +#endif /* not USE_LIBXML */ +} + +/* + * XmlTableSetDocument + * Install the input document + */ +static void +XmlTableSetDocument(TableFuncScanState *state, Datum value) +{ +#ifdef USE_LIBXML + XmlTableBuilderData *xtCxt; + xmltype *xmlval = DatumGetXmlP(value); + char *str; + xmlChar *xstr; + int length; + volatile xmlDocPtr doc = NULL; + volatile xmlXPathContextPtr xpathcxt = NULL; + + xtCxt = GetXmlTableBuilderPrivateData(state, "XmlTableSetDocument"); + + /* + * Use out function for casting to string (remove encoding property). See + * comment in xml_out. + */ + str = xml_out_internal(xmlval, 0); + + length = strlen(str); + xstr = pg_xmlCharStrndup(str, length); + + PG_TRY(); + { + doc = xmlCtxtReadMemory(xtCxt->ctxt, (char *) xstr, length, NULL, NULL, 0); + if (doc == NULL || xtCxt->xmlerrcxt->err_occurred) + xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_INVALID_XML_DOCUMENT, + "could not parse XML document"); + xpathcxt = xmlXPathNewContext(doc); + if (xpathcxt == NULL || xtCxt->xmlerrcxt->err_occurred) + xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY, + "could not allocate XPath context"); + xpathcxt->node = (xmlNodePtr) doc; + } + PG_CATCH(); + { + if (xpathcxt != NULL) + xmlXPathFreeContext(xpathcxt); + if (doc != NULL) + xmlFreeDoc(doc); + + PG_RE_THROW(); + } + PG_END_TRY(); + + xtCxt->doc = doc; + xtCxt->xpathcxt = xpathcxt; +#else + NO_XML_SUPPORT(); +#endif /* not USE_LIBXML */ +} + +/* + * XmlTableSetNamespace + * Add a namespace declaration + */ +static void +XmlTableSetNamespace(TableFuncScanState *state, const char *name, const char *uri) +{ +#ifdef USE_LIBXML + XmlTableBuilderData *xtCxt; + + if (name == NULL) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("DEFAULT namespace is not supported"))); + xtCxt = GetXmlTableBuilderPrivateData(state, "XmlTableSetNamespace"); + + if (xmlXPathRegisterNs(xtCxt->xpathcxt, + pg_xmlCharStrndup(name, strlen(name)), + pg_xmlCharStrndup(uri, strlen(uri)))) + xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_DATA_EXCEPTION, + "could not set XML namespace"); +#else + NO_XML_SUPPORT(); +#endif /* not USE_LIBXML */ +} + +/* + * XmlTableSetRowFilter + * Install the row-filter Xpath expression. + */ +static void +XmlTableSetRowFilter(TableFuncScanState *state, const char *path) +{ +#ifdef USE_LIBXML + XmlTableBuilderData *xtCxt; + xmlChar *xstr; + + xtCxt = GetXmlTableBuilderPrivateData(state, "XmlTableSetRowFilter"); + + if (*path == '\0') + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("row path filter must not be empty string"))); + + xstr = pg_xmlCharStrndup(path, strlen(path)); + + xtCxt->xpathcomp = xmlXPathCompile(xstr); + if (xtCxt->xpathcomp == NULL || xtCxt->xmlerrcxt->err_occurred) + xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_SYNTAX_ERROR, + "invalid XPath expression"); +#else + NO_XML_SUPPORT(); +#endif /* not USE_LIBXML */ +} + +/* + * XmlTableSetColumnFilter + * Install the column-filter Xpath expression, for the given column. + */ +static void +XmlTableSetColumnFilter(TableFuncScanState *state, const char *path, int colnum) +{ +#ifdef USE_LIBXML + XmlTableBuilderData *xtCxt; + xmlChar *xstr; + + Assert(PointerIsValid(path)); + + xtCxt = GetXmlTableBuilderPrivateData(state, "XmlTableSetColumnFilter"); + + if (*path == '\0') + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("column path filter must not be empty string"))); + + xstr = pg_xmlCharStrndup(path, strlen(path)); + + xtCxt->xpathscomp[colnum] = xmlXPathCompile(xstr); + if (xtCxt->xpathscomp[colnum] == NULL || xtCxt->xmlerrcxt->err_occurred) + xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_DATA_EXCEPTION, + "invalid XPath expression"); +#else + NO_XML_SUPPORT(); +#endif /* not USE_LIBXML */ +} + +/* + * XmlTableFetchRow + * Prepare the next "current" tuple for upcoming GetValue calls. + * Returns false if the row-filter expression returned no more rows. + */ +static bool +XmlTableFetchRow(TableFuncScanState *state) +{ +#ifdef USE_LIBXML + XmlTableBuilderData *xtCxt; + + xtCxt = GetXmlTableBuilderPrivateData(state, "XmlTableFetchRow"); + + /* Propagate our own error context to libxml2 */ + xmlSetStructuredErrorFunc((void *) xtCxt->xmlerrcxt, xml_errorHandler); + + if (xtCxt->xpathobj == NULL) + { + xtCxt->xpathobj = xmlXPathCompiledEval(xtCxt->xpathcomp, xtCxt->xpathcxt); + if (xtCxt->xpathobj == NULL || xtCxt->xmlerrcxt->err_occurred) + xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR, + "could not create XPath object"); + + xtCxt->row_count = 0; + } + + if (xtCxt->xpathobj->type == XPATH_NODESET) + { + if (xtCxt->xpathobj->nodesetval != NULL) + { + if (xtCxt->row_count++ < xtCxt->xpathobj->nodesetval->nodeNr) + return true; + } + } + + return false; +#else + NO_XML_SUPPORT(); + return false; +#endif /* not USE_LIBXML */ +} + +/* + * XmlTableGetValue + * Return the value for column number 'colnum' for the current row. If + * column -1 is requested, return representation of the whole row. + * + * This leaks memory, so be sure to reset often the context in which it's + * called. + */ +static Datum +XmlTableGetValue(TableFuncScanState *state, int colnum, + Oid typid, int32 typmod, bool *isnull) +{ +#ifdef USE_LIBXML + XmlTableBuilderData *xtCxt; + Datum result = (Datum) 0; + xmlNodePtr cur; + char *cstr = NULL; + volatile xmlXPathObjectPtr xpathobj = NULL; + + xtCxt = GetXmlTableBuilderPrivateData(state, "XmlTableGetValue"); + + Assert(xtCxt->xpathobj && + xtCxt->xpathobj->type == XPATH_NODESET && + xtCxt->xpathobj->nodesetval != NULL); + + /* Propagate our own error context to libxml2 */ + xmlSetStructuredErrorFunc((void *) xtCxt->xmlerrcxt, xml_errorHandler); + + *isnull = false; + + cur = xtCxt->xpathobj->nodesetval->nodeTab[xtCxt->row_count - 1]; + + Assert(xtCxt->xpathscomp[colnum] != NULL); + + PG_TRY(); + { + /* Set current node as entry point for XPath evaluation */ + xtCxt->xpathcxt->node = cur; + + /* Evaluate column path */ + xpathobj = xmlXPathCompiledEval(xtCxt->xpathscomp[colnum], xtCxt->xpathcxt); + if (xpathobj == NULL || xtCxt->xmlerrcxt->err_occurred) + xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR, + "could not create XPath object"); + + /* + * There are four possible cases, depending on the number of nodes + * returned by the XPath expression and the type of the target column: + * a) XPath returns no nodes. b) The target type is XML (return all + * as XML). For non-XML return types: c) One node (return content). + * d) Multiple nodes (error). + */ + if (xpathobj->type == XPATH_NODESET) + { + int count = 0; + + if (xpathobj->nodesetval != NULL) + count = xpathobj->nodesetval->nodeNr; + + if (xpathobj->nodesetval == NULL || count == 0) + { + *isnull = true; + } + else + { + if (typid == XMLOID) + { + text *textstr; + StringInfoData str; + + /* Concatenate serialized values */ + initStringInfo(&str); + for (int i = 0; i < count; i++) + { + textstr = + xml_xmlnodetoxmltype(xpathobj->nodesetval->nodeTab[i], + xtCxt->xmlerrcxt); + + appendStringInfoText(&str, textstr); + } + cstr = str.data; + } + else + { + xmlChar *str; + + if (count > 1) + ereport(ERROR, + (errcode(ERRCODE_CARDINALITY_VIOLATION), + errmsg("more than one value returned by column XPath expression"))); + + str = xmlXPathCastNodeSetToString(xpathobj->nodesetval); + cstr = str ? xml_pstrdup_and_free(str) : ""; + } + } + } + else if (xpathobj->type == XPATH_STRING) + { + /* Content should be escaped when target will be XML */ + if (typid == XMLOID) + cstr = escape_xml((char *) xpathobj->stringval); + else + cstr = (char *) xpathobj->stringval; + } + else if (xpathobj->type == XPATH_BOOLEAN) + { + char typcategory; + bool typispreferred; + xmlChar *str; + + /* Allow implicit casting from boolean to numbers */ + get_type_category_preferred(typid, &typcategory, &typispreferred); + + if (typcategory != TYPCATEGORY_NUMERIC) + str = xmlXPathCastBooleanToString(xpathobj->boolval); + else + str = xmlXPathCastNumberToString(xmlXPathCastBooleanToNumber(xpathobj->boolval)); + + cstr = xml_pstrdup_and_free(str); + } + else if (xpathobj->type == XPATH_NUMBER) + { + xmlChar *str; + + str = xmlXPathCastNumberToString(xpathobj->floatval); + cstr = xml_pstrdup_and_free(str); + } + else + elog(ERROR, "unexpected XPath object type %u", xpathobj->type); + + /* + * By here, either cstr contains the result value, or the isnull flag + * has been set. + */ + Assert(cstr || *isnull); + + if (!*isnull) + result = InputFunctionCall(&state->in_functions[colnum], + cstr, + state->typioparams[colnum], + typmod); + } + PG_FINALLY(); + { + if (xpathobj != NULL) + xmlXPathFreeObject(xpathobj); + } + PG_END_TRY(); + + return result; +#else + NO_XML_SUPPORT(); + return 0; +#endif /* not USE_LIBXML */ +} + +/* + * XmlTableDestroyOpaque + * Release all libxml2 resources + */ +static void +XmlTableDestroyOpaque(TableFuncScanState *state) +{ +#ifdef USE_LIBXML + XmlTableBuilderData *xtCxt; + + xtCxt = GetXmlTableBuilderPrivateData(state, "XmlTableDestroyOpaque"); + + /* Propagate our own error context to libxml2 */ + xmlSetStructuredErrorFunc((void *) xtCxt->xmlerrcxt, xml_errorHandler); + + if (xtCxt->xpathscomp != NULL) + { + int i; + + for (i = 0; i < xtCxt->natts; i++) + if (xtCxt->xpathscomp[i] != NULL) + xmlXPathFreeCompExpr(xtCxt->xpathscomp[i]); + } + + if (xtCxt->xpathobj != NULL) + xmlXPathFreeObject(xtCxt->xpathobj); + if (xtCxt->xpathcomp != NULL) + xmlXPathFreeCompExpr(xtCxt->xpathcomp); + if (xtCxt->xpathcxt != NULL) + xmlXPathFreeContext(xtCxt->xpathcxt); + if (xtCxt->doc != NULL) + xmlFreeDoc(xtCxt->doc); + if (xtCxt->ctxt != NULL) + xmlFreeParserCtxt(xtCxt->ctxt); + + pg_xml_done(xtCxt->xmlerrcxt, true); + + /* not valid anymore */ + xtCxt->magic = 0; + state->opaque = NULL; + +#else + NO_XML_SUPPORT(); +#endif /* not USE_LIBXML */ +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/cache/attoptcache.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/cache/attoptcache.c new file mode 100644 index 00000000000..0618917eb19 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/cache/attoptcache.c @@ -0,0 +1,178 @@ +/*------------------------------------------------------------------------- + * + * attoptcache.c + * Attribute options cache management. + * + * Attribute options are cached separately from the fixed-size portion of + * pg_attribute entries, which are handled by the relcache. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/utils/cache/attoptcache.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/reloptions.h" +#include "utils/attoptcache.h" +#include "utils/catcache.h" +#include "utils/hsearch.h" +#include "utils/inval.h" +#include "utils/syscache.h" +#include "varatt.h" + + +/* Hash table for information about each attribute's options */ +static __thread HTAB *AttoptCacheHash = NULL; + +/* attrelid and attnum form the lookup key, and must appear first */ +typedef struct +{ + Oid attrelid; + int attnum; +} AttoptCacheKey; + +typedef struct +{ + AttoptCacheKey key; /* lookup key - must be first */ + AttributeOpts *opts; /* options, or NULL if none */ +} AttoptCacheEntry; + + +/* + * InvalidateAttoptCacheCallback + * Flush all cache entries when pg_attribute is updated. + * + * When pg_attribute is updated, we must flush the cache entry at least + * for that attribute. Currently, we just flush them all. Since attribute + * options are not currently used in performance-critical paths (such as + * query execution), this seems OK. + */ +static void +InvalidateAttoptCacheCallback(Datum arg, int cacheid, uint32 hashvalue) +{ + HASH_SEQ_STATUS status; + AttoptCacheEntry *attopt; + + hash_seq_init(&status, AttoptCacheHash); + while ((attopt = (AttoptCacheEntry *) hash_seq_search(&status)) != NULL) + { + if (attopt->opts) + pfree(attopt->opts); + if (hash_search(AttoptCacheHash, + &attopt->key, + HASH_REMOVE, + NULL) == NULL) + elog(ERROR, "hash table corrupted"); + } +} + +/* + * InitializeAttoptCache + * Initialize the attribute options cache. + */ +static void +InitializeAttoptCache(void) +{ + HASHCTL ctl; + + /* Initialize the hash table. */ + ctl.keysize = sizeof(AttoptCacheKey); + ctl.entrysize = sizeof(AttoptCacheEntry); + AttoptCacheHash = + hash_create("Attopt cache", 256, &ctl, + HASH_ELEM | HASH_BLOBS); + + /* Make sure we've initialized CacheMemoryContext. */ + if (!CacheMemoryContext) + CreateCacheMemoryContext(); + + /* Watch for invalidation events. */ + CacheRegisterSyscacheCallback(ATTNUM, + InvalidateAttoptCacheCallback, + (Datum) 0); +} + +/* + * get_attribute_options + * Fetch attribute options for a specified table OID. + */ +AttributeOpts * +get_attribute_options(Oid attrelid, int attnum) +{ + AttoptCacheKey key; + AttoptCacheEntry *attopt; + AttributeOpts *result; + HeapTuple tp; + + /* Find existing cache entry, if any. */ + if (!AttoptCacheHash) + InitializeAttoptCache(); + memset(&key, 0, sizeof(key)); /* make sure any padding bits are unset */ + key.attrelid = attrelid; + key.attnum = attnum; + attopt = + (AttoptCacheEntry *) hash_search(AttoptCacheHash, + &key, + HASH_FIND, + NULL); + + /* Not found in Attopt cache. Construct new cache entry. */ + if (!attopt) + { + AttributeOpts *opts; + + tp = SearchSysCache2(ATTNUM, + ObjectIdGetDatum(attrelid), + Int16GetDatum(attnum)); + + /* + * If we don't find a valid HeapTuple, it must mean someone has + * managed to request attribute details for a non-existent attribute. + * We treat that case as if no options were specified. + */ + if (!HeapTupleIsValid(tp)) + opts = NULL; + else + { + Datum datum; + bool isNull; + + datum = SysCacheGetAttr(ATTNUM, + tp, + Anum_pg_attribute_attoptions, + &isNull); + if (isNull) + opts = NULL; + else + { + bytea *bytea_opts = attribute_reloptions(datum, false); + + opts = MemoryContextAlloc(CacheMemoryContext, + VARSIZE(bytea_opts)); + memcpy(opts, bytea_opts, VARSIZE(bytea_opts)); + } + ReleaseSysCache(tp); + } + + /* + * It's important to create the actual cache entry only after reading + * pg_attribute, since the read could cause a cache flush. + */ + attopt = (AttoptCacheEntry *) hash_search(AttoptCacheHash, + &key, + HASH_ENTER, + NULL); + attopt->opts = opts; + } + + /* Return results in caller's memory context. */ + if (attopt->opts == NULL) + return NULL; + result = palloc(VARSIZE(attopt->opts)); + memcpy(result, attopt->opts, VARSIZE(attopt->opts)); + return result; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/cache/catcache.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/cache/catcache.c new file mode 100644 index 00000000000..63fdd755e70 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/cache/catcache.c @@ -0,0 +1,2267 @@ +/*------------------------------------------------------------------------- + * + * catcache.c + * System catalog cache for tuples matching a key. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/cache/catcache.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/genam.h" +#include "access/heaptoast.h" +#include "access/relscan.h" +#include "access/sysattr.h" +#include "access/table.h" +#include "access/xact.h" +#include "catalog/pg_collation.h" +#include "catalog/pg_operator.h" +#include "catalog/pg_type.h" +#include "common/hashfn.h" +#include "common/pg_prng.h" +#include "miscadmin.h" +#include "port/pg_bitutils.h" +#ifdef CATCACHE_STATS +#include "storage/ipc.h" /* for on_proc_exit */ +#endif +#include "storage/lmgr.h" +#include "utils/builtins.h" +#include "utils/datum.h" +#include "utils/fmgroids.h" +#include "utils/inval.h" +#include "utils/memutils.h" +#include "utils/rel.h" +#include "utils/resowner_private.h" +#include "utils/syscache.h" + + + /* #define CACHEDEBUG */ /* turns DEBUG elogs on */ + +/* + * Given a hash value and the size of the hash table, find the bucket + * in which the hash value belongs. Since the hash table must contain + * a power-of-2 number of elements, this is a simple bitmask. + */ +#define HASH_INDEX(h, sz) ((Index) ((h) & ((sz) - 1))) + + +/* + * variables, macros and other stuff + */ + +#ifdef CACHEDEBUG +#define CACHE_elog(...) elog(__VA_ARGS__) +#else +#define CACHE_elog(...) +#endif + +/* Cache management header --- pointer is NULL until created */ +static __thread CatCacheHeader *CacheHdr = NULL; + +static inline HeapTuple SearchCatCacheInternal(CatCache *cache, + int nkeys, + Datum v1, Datum v2, + Datum v3, Datum v4); + +static pg_noinline HeapTuple SearchCatCacheMiss(CatCache *cache, + int nkeys, + uint32 hashValue, + Index hashIndex, + Datum v1, Datum v2, + Datum v3, Datum v4); + +static uint32 CatalogCacheComputeHashValue(CatCache *cache, int nkeys, + Datum v1, Datum v2, Datum v3, Datum v4); +static uint32 CatalogCacheComputeTupleHashValue(CatCache *cache, int nkeys, + HeapTuple tuple); +static inline bool CatalogCacheCompareTuple(const CatCache *cache, int nkeys, + const Datum *cachekeys, + const Datum *searchkeys); + +#ifdef CATCACHE_STATS +static void CatCachePrintStats(int code, Datum arg); +#endif +static void CatCacheRemoveCTup(CatCache *cache, CatCTup *ct); +static void CatCacheRemoveCList(CatCache *cache, CatCList *cl); +static void RehashCatCache(CatCache *cp); +static void RehashCatCacheLists(CatCache *cp); +static void CatalogCacheInitializeCache(CatCache *cache); +static CatCTup *CatalogCacheCreateEntry(CatCache *cache, + HeapTuple ntp, SysScanDesc scandesc, + Datum *arguments, + uint32 hashValue, Index hashIndex); + +static void CatCacheFreeKeys(TupleDesc tupdesc, int nkeys, int *attnos, + Datum *keys); +static void CatCacheCopyKeys(TupleDesc tupdesc, int nkeys, int *attnos, + Datum *srckeys, Datum *dstkeys); + + +/* + * internal support functions + */ + +/* + * Hash and equality functions for system types that are used as cache key + * fields. In some cases, we just call the regular SQL-callable functions for + * the appropriate data type, but that tends to be a little slow, and the + * speed of these functions is performance-critical. Therefore, for data + * types that frequently occur as catcache keys, we hard-code the logic here. + * Avoiding the overhead of DirectFunctionCallN(...) is a substantial win, and + * in certain cases (like int4) we can adopt a faster hash algorithm as well. + */ + +static bool +chareqfast(Datum a, Datum b) +{ + return DatumGetChar(a) == DatumGetChar(b); +} + +static uint32 +charhashfast(Datum datum) +{ + return murmurhash32((int32) DatumGetChar(datum)); +} + +static bool +nameeqfast(Datum a, Datum b) +{ + char *ca = NameStr(*DatumGetName(a)); + char *cb = NameStr(*DatumGetName(b)); + + return strncmp(ca, cb, NAMEDATALEN) == 0; +} + +static uint32 +namehashfast(Datum datum) +{ + char *key = NameStr(*DatumGetName(datum)); + + return hash_any((unsigned char *) key, strlen(key)); +} + +static bool +int2eqfast(Datum a, Datum b) +{ + return DatumGetInt16(a) == DatumGetInt16(b); +} + +static uint32 +int2hashfast(Datum datum) +{ + return murmurhash32((int32) DatumGetInt16(datum)); +} + +static bool +int4eqfast(Datum a, Datum b) +{ + return DatumGetInt32(a) == DatumGetInt32(b); +} + +static uint32 +int4hashfast(Datum datum) +{ + return murmurhash32((int32) DatumGetInt32(datum)); +} + +static bool +texteqfast(Datum a, Datum b) +{ + /* + * The use of DEFAULT_COLLATION_OID is fairly arbitrary here. We just + * want to take the fast "deterministic" path in texteq(). + */ + return DatumGetBool(DirectFunctionCall2Coll(texteq, DEFAULT_COLLATION_OID, a, b)); +} + +static uint32 +texthashfast(Datum datum) +{ + /* analogously here as in texteqfast() */ + return DatumGetInt32(DirectFunctionCall1Coll(hashtext, DEFAULT_COLLATION_OID, datum)); +} + +static bool +oidvectoreqfast(Datum a, Datum b) +{ + return DatumGetBool(DirectFunctionCall2(oidvectoreq, a, b)); +} + +static uint32 +oidvectorhashfast(Datum datum) +{ + return DatumGetInt32(DirectFunctionCall1(hashoidvector, datum)); +} + +/* Lookup support functions for a type. */ +static void +GetCCHashEqFuncs(Oid keytype, CCHashFN *hashfunc, RegProcedure *eqfunc, CCFastEqualFN *fasteqfunc) +{ + switch (keytype) + { + case BOOLOID: + *hashfunc = charhashfast; + *fasteqfunc = chareqfast; + *eqfunc = F_BOOLEQ; + break; + case CHAROID: + *hashfunc = charhashfast; + *fasteqfunc = chareqfast; + *eqfunc = F_CHAREQ; + break; + case NAMEOID: + *hashfunc = namehashfast; + *fasteqfunc = nameeqfast; + *eqfunc = F_NAMEEQ; + break; + case INT2OID: + *hashfunc = int2hashfast; + *fasteqfunc = int2eqfast; + *eqfunc = F_INT2EQ; + break; + case INT4OID: + *hashfunc = int4hashfast; + *fasteqfunc = int4eqfast; + *eqfunc = F_INT4EQ; + break; + case TEXTOID: + *hashfunc = texthashfast; + *fasteqfunc = texteqfast; + *eqfunc = F_TEXTEQ; + break; + case OIDOID: + case REGPROCOID: + case REGPROCEDUREOID: + case REGOPEROID: + case REGOPERATOROID: + case REGCLASSOID: + case REGTYPEOID: + case REGCOLLATIONOID: + case REGCONFIGOID: + case REGDICTIONARYOID: + case REGROLEOID: + case REGNAMESPACEOID: + *hashfunc = int4hashfast; + *fasteqfunc = int4eqfast; + *eqfunc = F_OIDEQ; + break; + case OIDVECTOROID: + *hashfunc = oidvectorhashfast; + *fasteqfunc = oidvectoreqfast; + *eqfunc = F_OIDVECTOREQ; + break; + default: + elog(FATAL, "type %u not supported as catcache key", keytype); + *hashfunc = NULL; /* keep compiler quiet */ + + *eqfunc = InvalidOid; + break; + } +} + +/* + * CatalogCacheComputeHashValue + * + * Compute the hash value associated with a given set of lookup keys + */ +static uint32 +CatalogCacheComputeHashValue(CatCache *cache, int nkeys, + Datum v1, Datum v2, Datum v3, Datum v4) +{ + uint32 hashValue = 0; + uint32 oneHash; + CCHashFN *cc_hashfunc = cache->cc_hashfunc; + + CACHE_elog(DEBUG2, "CatalogCacheComputeHashValue %s %d %p", + cache->cc_relname, nkeys, cache); + + switch (nkeys) + { + case 4: + oneHash = (cc_hashfunc[3]) (v4); + hashValue ^= pg_rotate_left32(oneHash, 24); + /* FALLTHROUGH */ + case 3: + oneHash = (cc_hashfunc[2]) (v3); + hashValue ^= pg_rotate_left32(oneHash, 16); + /* FALLTHROUGH */ + case 2: + oneHash = (cc_hashfunc[1]) (v2); + hashValue ^= pg_rotate_left32(oneHash, 8); + /* FALLTHROUGH */ + case 1: + oneHash = (cc_hashfunc[0]) (v1); + hashValue ^= oneHash; + break; + default: + elog(FATAL, "wrong number of hash keys: %d", nkeys); + break; + } + + return hashValue; +} + +/* + * CatalogCacheComputeTupleHashValue + * + * Compute the hash value associated with a given tuple to be cached + */ +static uint32 +CatalogCacheComputeTupleHashValue(CatCache *cache, int nkeys, HeapTuple tuple) +{ + Datum v1 = 0, + v2 = 0, + v3 = 0, + v4 = 0; + bool isNull = false; + int *cc_keyno = cache->cc_keyno; + TupleDesc cc_tupdesc = cache->cc_tupdesc; + + /* Now extract key fields from tuple, insert into scankey */ + switch (nkeys) + { + case 4: + v4 = fastgetattr(tuple, + cc_keyno[3], + cc_tupdesc, + &isNull); + Assert(!isNull); + /* FALLTHROUGH */ + case 3: + v3 = fastgetattr(tuple, + cc_keyno[2], + cc_tupdesc, + &isNull); + Assert(!isNull); + /* FALLTHROUGH */ + case 2: + v2 = fastgetattr(tuple, + cc_keyno[1], + cc_tupdesc, + &isNull); + Assert(!isNull); + /* FALLTHROUGH */ + case 1: + v1 = fastgetattr(tuple, + cc_keyno[0], + cc_tupdesc, + &isNull); + Assert(!isNull); + break; + default: + elog(FATAL, "wrong number of hash keys: %d", nkeys); + break; + } + + return CatalogCacheComputeHashValue(cache, nkeys, v1, v2, v3, v4); +} + +/* + * CatalogCacheCompareTuple + * + * Compare a tuple to the passed arguments. + */ +static inline bool +CatalogCacheCompareTuple(const CatCache *cache, int nkeys, + const Datum *cachekeys, + const Datum *searchkeys) +{ + const CCFastEqualFN *cc_fastequal = cache->cc_fastequal; + int i; + + for (i = 0; i < nkeys; i++) + { + if (!(cc_fastequal[i]) (cachekeys[i], searchkeys[i])) + return false; + } + return true; +} + + +#ifdef CATCACHE_STATS + +static void +CatCachePrintStats(int code, Datum arg) +{ + slist_iter iter; + long cc_searches = 0; + long cc_hits = 0; + long cc_neg_hits = 0; + long cc_newloads = 0; + long cc_invals = 0; + long cc_nlists = 0; + long cc_lsearches = 0; + long cc_lhits = 0; + + slist_foreach(iter, &CacheHdr->ch_caches) + { + CatCache *cache = slist_container(CatCache, cc_next, iter.cur); + + if (cache->cc_ntup == 0 && cache->cc_searches == 0) + continue; /* don't print unused caches */ + elog(DEBUG2, "catcache %s/%u: %d tup, %ld srch, %ld+%ld=%ld hits, %ld+%ld=%ld loads, %ld invals, %d lists, %ld lsrch, %ld lhits", + cache->cc_relname, + cache->cc_indexoid, + cache->cc_ntup, + cache->cc_searches, + cache->cc_hits, + cache->cc_neg_hits, + cache->cc_hits + cache->cc_neg_hits, + cache->cc_newloads, + cache->cc_searches - cache->cc_hits - cache->cc_neg_hits - cache->cc_newloads, + cache->cc_searches - cache->cc_hits - cache->cc_neg_hits, + cache->cc_invals, + cache->cc_nlist, + cache->cc_lsearches, + cache->cc_lhits); + cc_searches += cache->cc_searches; + cc_hits += cache->cc_hits; + cc_neg_hits += cache->cc_neg_hits; + cc_newloads += cache->cc_newloads; + cc_invals += cache->cc_invals; + cc_nlists += cache->cc_nlist; + cc_lsearches += cache->cc_lsearches; + cc_lhits += cache->cc_lhits; + } + elog(DEBUG2, "catcache totals: %d tup, %ld srch, %ld+%ld=%ld hits, %ld+%ld=%ld loads, %ld invals, %ld lists, %ld lsrch, %ld lhits", + CacheHdr->ch_ntup, + cc_searches, + cc_hits, + cc_neg_hits, + cc_hits + cc_neg_hits, + cc_newloads, + cc_searches - cc_hits - cc_neg_hits - cc_newloads, + cc_searches - cc_hits - cc_neg_hits, + cc_invals, + cc_nlists, + cc_lsearches, + cc_lhits); +} +#endif /* CATCACHE_STATS */ + + +/* + * CatCacheRemoveCTup + * + * Unlink and delete the given cache entry + * + * NB: if it is a member of a CatCList, the CatCList is deleted too. + * Both the cache entry and the list had better have zero refcount. + */ +static void +CatCacheRemoveCTup(CatCache *cache, CatCTup *ct) +{ + Assert(ct->refcount == 0); + Assert(ct->my_cache == cache); + + if (ct->c_list) + { + /* + * The cleanest way to handle this is to call CatCacheRemoveCList, + * which will recurse back to me, and the recursive call will do the + * work. Set the "dead" flag to make sure it does recurse. + */ + ct->dead = true; + CatCacheRemoveCList(cache, ct->c_list); + return; /* nothing left to do */ + } + + /* delink from linked list */ + dlist_delete(&ct->cache_elem); + + /* + * Free keys when we're dealing with a negative entry, normal entries just + * point into tuple, allocated together with the CatCTup. + */ + if (ct->negative) + CatCacheFreeKeys(cache->cc_tupdesc, cache->cc_nkeys, + cache->cc_keyno, ct->keys); + + pfree(ct); + + --cache->cc_ntup; + --CacheHdr->ch_ntup; +} + +/* + * CatCacheRemoveCList + * + * Unlink and delete the given cache list entry + * + * NB: any dead member entries that become unreferenced are deleted too. + */ +static void +CatCacheRemoveCList(CatCache *cache, CatCList *cl) +{ + int i; + + Assert(cl->refcount == 0); + Assert(cl->my_cache == cache); + + /* delink from member tuples */ + for (i = cl->n_members; --i >= 0;) + { + CatCTup *ct = cl->members[i]; + + Assert(ct->c_list == cl); + ct->c_list = NULL; + /* if the member is dead and now has no references, remove it */ + if ( +#ifndef CATCACHE_FORCE_RELEASE + ct->dead && +#endif + ct->refcount == 0) + CatCacheRemoveCTup(cache, ct); + } + + /* delink from linked list */ + dlist_delete(&cl->cache_elem); + + /* free associated column data */ + CatCacheFreeKeys(cache->cc_tupdesc, cl->nkeys, + cache->cc_keyno, cl->keys); + + pfree(cl); + + --cache->cc_nlist; +} + + +/* + * CatCacheInvalidate + * + * Invalidate entries in the specified cache, given a hash value. + * + * We delete cache entries that match the hash value, whether positive + * or negative. We don't care whether the invalidation is the result + * of a tuple insertion or a deletion. + * + * We used to try to match positive cache entries by TID, but that is + * unsafe after a VACUUM FULL on a system catalog: an inval event could + * be queued before VACUUM FULL, and then processed afterwards, when the + * target tuple that has to be invalidated has a different TID than it + * did when the event was created. So now we just compare hash values and + * accept the small risk of unnecessary invalidations due to false matches. + * + * This routine is only quasi-public: it should only be used by inval.c. + */ +void +CatCacheInvalidate(CatCache *cache, uint32 hashValue) +{ + Index hashIndex; + dlist_mutable_iter iter; + + CACHE_elog(DEBUG2, "CatCacheInvalidate: called"); + + /* + * We don't bother to check whether the cache has finished initialization + * yet; if not, there will be no entries in it so no problem. + */ + + /* + * Invalidate *all* CatCLists in this cache; it's too hard to tell which + * searches might still be correct, so just zap 'em all. + */ + for (int i = 0; i < cache->cc_nlbuckets; i++) + { + dlist_head *bucket = &cache->cc_lbucket[i]; + + dlist_foreach_modify(iter, bucket) + { + CatCList *cl = dlist_container(CatCList, cache_elem, iter.cur); + + if (cl->refcount > 0) + cl->dead = true; + else + CatCacheRemoveCList(cache, cl); + } + } + + /* + * inspect the proper hash bucket for tuple matches + */ + hashIndex = HASH_INDEX(hashValue, cache->cc_nbuckets); + dlist_foreach_modify(iter, &cache->cc_bucket[hashIndex]) + { + CatCTup *ct = dlist_container(CatCTup, cache_elem, iter.cur); + + if (hashValue == ct->hash_value) + { + if (ct->refcount > 0 || + (ct->c_list && ct->c_list->refcount > 0)) + { + ct->dead = true; + /* list, if any, was marked dead above */ + Assert(ct->c_list == NULL || ct->c_list->dead); + } + else + CatCacheRemoveCTup(cache, ct); + CACHE_elog(DEBUG2, "CatCacheInvalidate: invalidated"); +#ifdef CATCACHE_STATS + cache->cc_invals++; +#endif + /* could be multiple matches, so keep looking! */ + } + } +} + +/* ---------------------------------------------------------------- + * public functions + * ---------------------------------------------------------------- + */ + + +/* + * Standard routine for creating cache context if it doesn't exist yet + * + * There are a lot of places (probably far more than necessary) that check + * whether CacheMemoryContext exists yet and want to create it if not. + * We centralize knowledge of exactly how to create it here. + */ +void +CreateCacheMemoryContext(void) +{ + /* + * Purely for paranoia, check that context doesn't exist; caller probably + * did so already. + */ + if (!CacheMemoryContext) + CacheMemoryContext = AllocSetContextCreate(TopMemoryContext, + "CacheMemoryContext", + ALLOCSET_DEFAULT_SIZES); +} + + +/* + * ResetCatalogCache + * + * Reset one catalog cache to empty. + * + * This is not very efficient if the target cache is nearly empty. + * However, it shouldn't need to be efficient; we don't invoke it often. + */ +static void +ResetCatalogCache(CatCache *cache) +{ + dlist_mutable_iter iter; + int i; + + /* Remove each list in this cache, or at least mark it dead */ + for (i = 0; i < cache->cc_nlbuckets; i++) + { + dlist_head *bucket = &cache->cc_lbucket[i]; + + dlist_foreach_modify(iter, bucket) + { + CatCList *cl = dlist_container(CatCList, cache_elem, iter.cur); + + if (cl->refcount > 0) + cl->dead = true; + else + CatCacheRemoveCList(cache, cl); + } + } + + /* Remove each tuple in this cache, or at least mark it dead */ + for (i = 0; i < cache->cc_nbuckets; i++) + { + dlist_head *bucket = &cache->cc_bucket[i]; + + dlist_foreach_modify(iter, bucket) + { + CatCTup *ct = dlist_container(CatCTup, cache_elem, iter.cur); + + if (ct->refcount > 0 || + (ct->c_list && ct->c_list->refcount > 0)) + { + ct->dead = true; + /* list, if any, was marked dead above */ + Assert(ct->c_list == NULL || ct->c_list->dead); + } + else + CatCacheRemoveCTup(cache, ct); +#ifdef CATCACHE_STATS + cache->cc_invals++; +#endif + } + } +} + +/* + * ResetCatalogCaches + * + * Reset all caches when a shared cache inval event forces it + */ +void +ResetCatalogCaches(void) +{ + slist_iter iter; + + CACHE_elog(DEBUG2, "ResetCatalogCaches called"); + + slist_foreach(iter, &CacheHdr->ch_caches) + { + CatCache *cache = slist_container(CatCache, cc_next, iter.cur); + + ResetCatalogCache(cache); + } + + CACHE_elog(DEBUG2, "end of ResetCatalogCaches call"); +} + +/* + * CatalogCacheFlushCatalog + * + * Flush all catcache entries that came from the specified system catalog. + * This is needed after VACUUM FULL/CLUSTER on the catalog, since the + * tuples very likely now have different TIDs than before. (At one point + * we also tried to force re-execution of CatalogCacheInitializeCache for + * the cache(s) on that catalog. This is a bad idea since it leads to all + * kinds of trouble if a cache flush occurs while loading cache entries. + * We now avoid the need to do it by copying cc_tupdesc out of the relcache, + * rather than relying on the relcache to keep a tupdesc for us. Of course + * this assumes the tupdesc of a cachable system table will not change...) + */ +void +CatalogCacheFlushCatalog(Oid catId) +{ + slist_iter iter; + + CACHE_elog(DEBUG2, "CatalogCacheFlushCatalog called for %u", catId); + + slist_foreach(iter, &CacheHdr->ch_caches) + { + CatCache *cache = slist_container(CatCache, cc_next, iter.cur); + + /* Does this cache store tuples of the target catalog? */ + if (cache->cc_reloid == catId) + { + /* Yes, so flush all its contents */ + ResetCatalogCache(cache); + + /* Tell inval.c to call syscache callbacks for this cache */ + CallSyscacheCallbacks(cache->id, 0); + } + } + + CACHE_elog(DEBUG2, "end of CatalogCacheFlushCatalog call"); +} + +/* + * InitCatCache + * + * This allocates and initializes a cache for a system catalog relation. + * Actually, the cache is only partially initialized to avoid opening the + * relation. The relation will be opened and the rest of the cache + * structure initialized on the first access. + */ +#ifdef CACHEDEBUG +#define InitCatCache_DEBUG2 \ +do { \ + elog(DEBUG2, "InitCatCache: rel=%u ind=%u id=%d nkeys=%d size=%d", \ + cp->cc_reloid, cp->cc_indexoid, cp->id, \ + cp->cc_nkeys, cp->cc_nbuckets); \ +} while(0) +#else +#define InitCatCache_DEBUG2 +#endif + +CatCache * +InitCatCache(int id, + Oid reloid, + Oid indexoid, + int nkeys, + const int *key, + int nbuckets) +{ + CatCache *cp; + MemoryContext oldcxt; + int i; + + /* + * nbuckets is the initial number of hash buckets to use in this catcache. + * It will be enlarged later if it becomes too full. + * + * nbuckets must be a power of two. We check this via Assert rather than + * a full runtime check because the values will be coming from constant + * tables. + * + * If you're confused by the power-of-two check, see comments in + * bitmapset.c for an explanation. + */ + Assert(nbuckets > 0 && (nbuckets & -nbuckets) == nbuckets); + + /* + * first switch to the cache context so our allocations do not vanish at + * the end of a transaction + */ + if (!CacheMemoryContext) + CreateCacheMemoryContext(); + + oldcxt = MemoryContextSwitchTo(CacheMemoryContext); + + /* + * if first time through, initialize the cache group header + */ + if (CacheHdr == NULL) + { + CacheHdr = (CatCacheHeader *) palloc(sizeof(CatCacheHeader)); + slist_init(&CacheHdr->ch_caches); + CacheHdr->ch_ntup = 0; +#ifdef CATCACHE_STATS + /* set up to dump stats at backend exit */ + on_proc_exit(CatCachePrintStats, 0); +#endif + } + + /* + * Allocate a new cache structure, aligning to a cacheline boundary + * + * Note: we rely on zeroing to initialize all the dlist headers correctly + */ + cp = (CatCache *) palloc_aligned(sizeof(CatCache), PG_CACHE_LINE_SIZE, + MCXT_ALLOC_ZERO); + cp->cc_bucket = palloc0(nbuckets * sizeof(dlist_head)); + + /* + * Many catcaches never receive any list searches. Therefore, we don't + * allocate the cc_lbuckets till we get a list search. + */ + cp->cc_lbucket = NULL; + + /* + * initialize the cache's relation information for the relation + * corresponding to this cache, and initialize some of the new cache's + * other internal fields. But don't open the relation yet. + */ + cp->id = id; + cp->cc_relname = "(not known yet)"; + cp->cc_reloid = reloid; + cp->cc_indexoid = indexoid; + cp->cc_relisshared = false; /* temporary */ + cp->cc_tupdesc = (TupleDesc) NULL; + cp->cc_ntup = 0; + cp->cc_nlist = 0; + cp->cc_nbuckets = nbuckets; + cp->cc_nlbuckets = 0; + cp->cc_nkeys = nkeys; + for (i = 0; i < nkeys; ++i) + cp->cc_keyno[i] = key[i]; + + /* + * new cache is initialized as far as we can go for now. print some + * debugging information, if appropriate. + */ + InitCatCache_DEBUG2; + + /* + * add completed cache to top of group header's list + */ + slist_push_head(&CacheHdr->ch_caches, &cp->cc_next); + + /* + * back to the old context before we return... + */ + MemoryContextSwitchTo(oldcxt); + + return cp; +} + +/* + * Enlarge a catcache, doubling the number of buckets. + */ +static void +RehashCatCache(CatCache *cp) +{ + dlist_head *newbucket; + int newnbuckets; + int i; + + elog(DEBUG1, "rehashing catalog cache id %d for %s; %d tups, %d buckets", + cp->id, cp->cc_relname, cp->cc_ntup, cp->cc_nbuckets); + + /* Allocate a new, larger, hash table. */ + newnbuckets = cp->cc_nbuckets * 2; + newbucket = (dlist_head *) MemoryContextAllocZero(CacheMemoryContext, newnbuckets * sizeof(dlist_head)); + + /* Move all entries from old hash table to new. */ + for (i = 0; i < cp->cc_nbuckets; i++) + { + dlist_mutable_iter iter; + + dlist_foreach_modify(iter, &cp->cc_bucket[i]) + { + CatCTup *ct = dlist_container(CatCTup, cache_elem, iter.cur); + int hashIndex = HASH_INDEX(ct->hash_value, newnbuckets); + + dlist_delete(iter.cur); + dlist_push_head(&newbucket[hashIndex], &ct->cache_elem); + } + } + + /* Switch to the new array. */ + pfree(cp->cc_bucket); + cp->cc_nbuckets = newnbuckets; + cp->cc_bucket = newbucket; +} + +/* + * Enlarge a catcache's list storage, doubling the number of buckets. + */ +static void +RehashCatCacheLists(CatCache *cp) +{ + dlist_head *newbucket; + int newnbuckets; + int i; + + elog(DEBUG1, "rehashing catalog cache id %d for %s; %d lists, %d buckets", + cp->id, cp->cc_relname, cp->cc_nlist, cp->cc_nlbuckets); + + /* Allocate a new, larger, hash table. */ + newnbuckets = cp->cc_nlbuckets * 2; + newbucket = (dlist_head *) MemoryContextAllocZero(CacheMemoryContext, newnbuckets * sizeof(dlist_head)); + + /* Move all entries from old hash table to new. */ + for (i = 0; i < cp->cc_nlbuckets; i++) + { + dlist_mutable_iter iter; + + dlist_foreach_modify(iter, &cp->cc_lbucket[i]) + { + CatCList *cl = dlist_container(CatCList, cache_elem, iter.cur); + int hashIndex = HASH_INDEX(cl->hash_value, newnbuckets); + + dlist_delete(iter.cur); + dlist_push_head(&newbucket[hashIndex], &cl->cache_elem); + } + } + + /* Switch to the new array. */ + pfree(cp->cc_lbucket); + cp->cc_nlbuckets = newnbuckets; + cp->cc_lbucket = newbucket; +} + +/* + * CatalogCacheInitializeCache + * + * This function does final initialization of a catcache: obtain the tuple + * descriptor and set up the hash and equality function links. We assume + * that the relcache entry can be opened at this point! + */ +#ifdef CACHEDEBUG +#define CatalogCacheInitializeCache_DEBUG1 \ + elog(DEBUG2, "CatalogCacheInitializeCache: cache @%p rel=%u", cache, \ + cache->cc_reloid) + +#define CatalogCacheInitializeCache_DEBUG2 \ +do { \ + if (cache->cc_keyno[i] > 0) { \ + elog(DEBUG2, "CatalogCacheInitializeCache: load %d/%d w/%d, %u", \ + i+1, cache->cc_nkeys, cache->cc_keyno[i], \ + TupleDescAttr(tupdesc, cache->cc_keyno[i] - 1)->atttypid); \ + } else { \ + elog(DEBUG2, "CatalogCacheInitializeCache: load %d/%d w/%d", \ + i+1, cache->cc_nkeys, cache->cc_keyno[i]); \ + } \ +} while(0) +#else +#define CatalogCacheInitializeCache_DEBUG1 +#define CatalogCacheInitializeCache_DEBUG2 +#endif + +static void +CatalogCacheInitializeCache(CatCache *cache) +{ + Relation relation; + MemoryContext oldcxt; + TupleDesc tupdesc; + int i; + + CatalogCacheInitializeCache_DEBUG1; + + relation = table_open(cache->cc_reloid, AccessShareLock); + + /* + * switch to the cache context so our allocations do not vanish at the end + * of a transaction + */ + Assert(CacheMemoryContext != NULL); + + oldcxt = MemoryContextSwitchTo(CacheMemoryContext); + + /* + * copy the relcache's tuple descriptor to permanent cache storage + */ + tupdesc = CreateTupleDescCopyConstr(RelationGetDescr(relation)); + + /* + * save the relation's name and relisshared flag, too (cc_relname is used + * only for debugging purposes) + */ + cache->cc_relname = pstrdup(RelationGetRelationName(relation)); + cache->cc_relisshared = RelationGetForm(relation)->relisshared; + + /* + * return to the caller's memory context and close the rel + */ + MemoryContextSwitchTo(oldcxt); + + table_close(relation, AccessShareLock); + + CACHE_elog(DEBUG2, "CatalogCacheInitializeCache: %s, %d keys", + cache->cc_relname, cache->cc_nkeys); + + /* + * initialize cache's key information + */ + for (i = 0; i < cache->cc_nkeys; ++i) + { + Oid keytype; + RegProcedure eqfunc; + + CatalogCacheInitializeCache_DEBUG2; + + if (cache->cc_keyno[i] > 0) + { + Form_pg_attribute attr = TupleDescAttr(tupdesc, + cache->cc_keyno[i] - 1); + + keytype = attr->atttypid; + /* cache key columns should always be NOT NULL */ + Assert(attr->attnotnull); + } + else + { + if (cache->cc_keyno[i] < 0) + elog(FATAL, "sys attributes are not supported in caches"); + keytype = OIDOID; + } + + GetCCHashEqFuncs(keytype, + &cache->cc_hashfunc[i], + &eqfunc, + &cache->cc_fastequal[i]); + + /* + * Do equality-function lookup (we assume this won't need a catalog + * lookup for any supported type) + */ + fmgr_info_cxt(eqfunc, + &cache->cc_skey[i].sk_func, + CacheMemoryContext); + + /* Initialize sk_attno suitably for HeapKeyTest() and heap scans */ + cache->cc_skey[i].sk_attno = cache->cc_keyno[i]; + + /* Fill in sk_strategy as well --- always standard equality */ + cache->cc_skey[i].sk_strategy = BTEqualStrategyNumber; + cache->cc_skey[i].sk_subtype = InvalidOid; + /* If a catcache key requires a collation, it must be C collation */ + cache->cc_skey[i].sk_collation = C_COLLATION_OID; + + CACHE_elog(DEBUG2, "CatalogCacheInitializeCache %s %d %p", + cache->cc_relname, i, cache); + } + + /* + * mark this cache fully initialized + */ + cache->cc_tupdesc = tupdesc; +} + +/* + * InitCatCachePhase2 -- external interface for CatalogCacheInitializeCache + * + * One reason to call this routine is to ensure that the relcache has + * created entries for all the catalogs and indexes referenced by catcaches. + * Therefore, provide an option to open the index as well as fixing the + * cache itself. An exception is the indexes on pg_am, which we don't use + * (cf. IndexScanOK). + */ +void +InitCatCachePhase2(CatCache *cache, bool touch_index) +{ + if (cache->cc_tupdesc == NULL) + CatalogCacheInitializeCache(cache); + + if (touch_index && + cache->id != AMOID && + cache->id != AMNAME) + { + Relation idesc; + + /* + * We must lock the underlying catalog before opening the index to + * avoid deadlock, since index_open could possibly result in reading + * this same catalog, and if anyone else is exclusive-locking this + * catalog and index they'll be doing it in that order. + */ + LockRelationOid(cache->cc_reloid, AccessShareLock); + idesc = index_open(cache->cc_indexoid, AccessShareLock); + + /* + * While we've got the index open, let's check that it's unique (and + * not just deferrable-unique, thank you very much). This is just to + * catch thinkos in definitions of new catcaches, so we don't worry + * about the pg_am indexes not getting tested. + */ + Assert(idesc->rd_index->indisunique && + idesc->rd_index->indimmediate); + + index_close(idesc, AccessShareLock); + UnlockRelationOid(cache->cc_reloid, AccessShareLock); + } +} + + +/* + * IndexScanOK + * + * This function checks for tuples that will be fetched by + * IndexSupportInitialize() during relcache initialization for + * certain system indexes that support critical syscaches. + * We can't use an indexscan to fetch these, else we'll get into + * infinite recursion. A plain heap scan will work, however. + * Once we have completed relcache initialization (signaled by + * criticalRelcachesBuilt), we don't have to worry anymore. + * + * Similarly, during backend startup we have to be able to use the + * pg_authid, pg_auth_members and pg_database syscaches for + * authentication even if we don't yet have relcache entries for those + * catalogs' indexes. + */ +static bool +IndexScanOK(CatCache *cache, ScanKey cur_skey) +{ + switch (cache->id) + { + case INDEXRELID: + + /* + * Rather than tracking exactly which indexes have to be loaded + * before we can use indexscans (which changes from time to time), + * just force all pg_index searches to be heap scans until we've + * built the critical relcaches. + */ + if (!criticalRelcachesBuilt) + return false; + break; + + case AMOID: + case AMNAME: + + /* + * Always do heap scans in pg_am, because it's so small there's + * not much point in an indexscan anyway. We *must* do this when + * initially building critical relcache entries, but we might as + * well just always do it. + */ + return false; + + case AUTHNAME: + case AUTHOID: + case AUTHMEMMEMROLE: + case DATABASEOID: + + /* + * Protect authentication lookups occurring before relcache has + * collected entries for shared indexes. + */ + if (!criticalSharedRelcachesBuilt) + return false; + break; + + default: + break; + } + + /* Normal case, allow index scan */ + return true; +} + +/* + * SearchCatCache + * + * This call searches a system cache for a tuple, opening the relation + * if necessary (on the first access to a particular cache). + * + * The result is NULL if not found, or a pointer to a HeapTuple in + * the cache. The caller must not modify the tuple, and must call + * ReleaseCatCache() when done with it. + * + * The search key values should be expressed as Datums of the key columns' + * datatype(s). (Pass zeroes for any unused parameters.) As a special + * exception, the passed-in key for a NAME column can be just a C string; + * the caller need not go to the trouble of converting it to a fully + * null-padded NAME. + */ +HeapTuple +SearchCatCache(CatCache *cache, + Datum v1, + Datum v2, + Datum v3, + Datum v4) +{ + return SearchCatCacheInternal(cache, cache->cc_nkeys, v1, v2, v3, v4); +} + + +/* + * SearchCatCacheN() are SearchCatCache() versions for a specific number of + * arguments. The compiler can inline the body and unroll loops, making them a + * bit faster than SearchCatCache(). + */ + +HeapTuple +SearchCatCache1(CatCache *cache, + Datum v1) +{ + return SearchCatCacheInternal(cache, 1, v1, 0, 0, 0); +} + + +HeapTuple +SearchCatCache2(CatCache *cache, + Datum v1, Datum v2) +{ + return SearchCatCacheInternal(cache, 2, v1, v2, 0, 0); +} + + +HeapTuple +SearchCatCache3(CatCache *cache, + Datum v1, Datum v2, Datum v3) +{ + return SearchCatCacheInternal(cache, 3, v1, v2, v3, 0); +} + + +HeapTuple +SearchCatCache4(CatCache *cache, + Datum v1, Datum v2, Datum v3, Datum v4) +{ + return SearchCatCacheInternal(cache, 4, v1, v2, v3, v4); +} + +/* + * Work-horse for SearchCatCache/SearchCatCacheN. + */ +static inline HeapTuple +SearchCatCacheInternal(CatCache *cache, + int nkeys, + Datum v1, + Datum v2, + Datum v3, + Datum v4) +{ + Datum arguments[CATCACHE_MAXKEYS]; + uint32 hashValue; + Index hashIndex; + dlist_iter iter; + dlist_head *bucket; + CatCTup *ct; + + /* Make sure we're in an xact, even if this ends up being a cache hit */ + Assert(IsTransactionState()); + + Assert(cache->cc_nkeys == nkeys); + + /* + * one-time startup overhead for each cache + */ + if (unlikely(cache->cc_tupdesc == NULL)) + CatalogCacheInitializeCache(cache); + +#ifdef CATCACHE_STATS + cache->cc_searches++; +#endif + + /* Initialize local parameter array */ + arguments[0] = v1; + arguments[1] = v2; + arguments[2] = v3; + arguments[3] = v4; + + /* + * find the hash bucket in which to look for the tuple + */ + hashValue = CatalogCacheComputeHashValue(cache, nkeys, v1, v2, v3, v4); + hashIndex = HASH_INDEX(hashValue, cache->cc_nbuckets); + + /* + * scan the hash bucket until we find a match or exhaust our tuples + * + * Note: it's okay to use dlist_foreach here, even though we modify the + * dlist within the loop, because we don't continue the loop afterwards. + */ + bucket = &cache->cc_bucket[hashIndex]; + dlist_foreach(iter, bucket) + { + ct = dlist_container(CatCTup, cache_elem, iter.cur); + + if (ct->dead) + continue; /* ignore dead entries */ + + if (ct->hash_value != hashValue) + continue; /* quickly skip entry if wrong hash val */ + + if (!CatalogCacheCompareTuple(cache, nkeys, ct->keys, arguments)) + continue; + + /* + * We found a match in the cache. Move it to the front of the list + * for its hashbucket, in order to speed subsequent searches. (The + * most frequently accessed elements in any hashbucket will tend to be + * near the front of the hashbucket's list.) + */ + dlist_move_head(bucket, &ct->cache_elem); + + /* + * If it's a positive entry, bump its refcount and return it. If it's + * negative, we can report failure to the caller. + */ + if (!ct->negative) + { + ResourceOwnerEnlargeCatCacheRefs(CurrentResourceOwner); + ct->refcount++; + ResourceOwnerRememberCatCacheRef(CurrentResourceOwner, &ct->tuple); + + CACHE_elog(DEBUG2, "SearchCatCache(%s): found in bucket %d", + cache->cc_relname, hashIndex); + +#ifdef CATCACHE_STATS + cache->cc_hits++; +#endif + + return &ct->tuple; + } + else + { + CACHE_elog(DEBUG2, "SearchCatCache(%s): found neg entry in bucket %d", + cache->cc_relname, hashIndex); + +#ifdef CATCACHE_STATS + cache->cc_neg_hits++; +#endif + + return NULL; + } + } + + return SearchCatCacheMiss(cache, nkeys, hashValue, hashIndex, v1, v2, v3, v4); +} + +/* + * Search the actual catalogs, rather than the cache. + * + * This is kept separate from SearchCatCacheInternal() to keep the fast-path + * as small as possible. To avoid that effort being undone by a helpful + * compiler, try to explicitly forbid inlining. + */ +static pg_noinline HeapTuple +SearchCatCacheMiss(CatCache *cache, + int nkeys, + uint32 hashValue, + Index hashIndex, + Datum v1, + Datum v2, + Datum v3, + Datum v4) +{ + ScanKeyData cur_skey[CATCACHE_MAXKEYS]; + Relation relation; + SysScanDesc scandesc; + HeapTuple ntp; + CatCTup *ct; + bool stale; + Datum arguments[CATCACHE_MAXKEYS]; + + /* Initialize local parameter array */ + arguments[0] = v1; + arguments[1] = v2; + arguments[2] = v3; + arguments[3] = v4; + + /* + * Tuple was not found in cache, so we have to try to retrieve it directly + * from the relation. If found, we will add it to the cache; if not + * found, we will add a negative cache entry instead. + * + * NOTE: it is possible for recursive cache lookups to occur while reading + * the relation --- for example, due to shared-cache-inval messages being + * processed during table_open(). This is OK. It's even possible for one + * of those lookups to find and enter the very same tuple we are trying to + * fetch here. If that happens, we will enter a second copy of the tuple + * into the cache. The first copy will never be referenced again, and + * will eventually age out of the cache, so there's no functional problem. + * This case is rare enough that it's not worth expending extra cycles to + * detect. + * + * Another case, which we *must* handle, is that the tuple could become + * outdated during CatalogCacheCreateEntry's attempt to detoast it (since + * AcceptInvalidationMessages can run during TOAST table access). We do + * not want to return already-stale catcache entries, so we loop around + * and do the table scan again if that happens. + */ + relation = table_open(cache->cc_reloid, AccessShareLock); + + do + { + /* + * Ok, need to make a lookup in the relation, copy the scankey and + * fill out any per-call fields. (We must re-do this when retrying, + * because systable_beginscan scribbles on the scankey.) + */ + memcpy(cur_skey, cache->cc_skey, sizeof(ScanKeyData) * nkeys); + cur_skey[0].sk_argument = v1; + cur_skey[1].sk_argument = v2; + cur_skey[2].sk_argument = v3; + cur_skey[3].sk_argument = v4; + + scandesc = systable_beginscan(relation, + cache->cc_indexoid, + IndexScanOK(cache, cur_skey), + NULL, + nkeys, + cur_skey); + + ct = NULL; + stale = false; + + while (HeapTupleIsValid(ntp = systable_getnext(scandesc))) + { + ct = CatalogCacheCreateEntry(cache, ntp, scandesc, NULL, + hashValue, hashIndex); + /* upon failure, we must start the scan over */ + if (ct == NULL) + { + stale = true; + break; + } + /* immediately set the refcount to 1 */ + ResourceOwnerEnlargeCatCacheRefs(CurrentResourceOwner); + ct->refcount++; + ResourceOwnerRememberCatCacheRef(CurrentResourceOwner, &ct->tuple); + break; /* assume only one match */ + } + + systable_endscan(scandesc); + } while (stale); + + table_close(relation, AccessShareLock); + + /* + * If tuple was not found, we need to build a negative cache entry + * containing a fake tuple. The fake tuple has the correct key columns, + * but nulls everywhere else. + * + * In bootstrap mode, we don't build negative entries, because the cache + * invalidation mechanism isn't alive and can't clear them if the tuple + * gets created later. (Bootstrap doesn't do UPDATEs, so it doesn't need + * cache inval for that.) + */ + if (ct == NULL) + { + if (IsBootstrapProcessingMode()) + return NULL; + + ct = CatalogCacheCreateEntry(cache, NULL, NULL, arguments, + hashValue, hashIndex); + + /* Creating a negative cache entry shouldn't fail */ + Assert(ct != NULL); + + CACHE_elog(DEBUG2, "SearchCatCache(%s): Contains %d/%d tuples", + cache->cc_relname, cache->cc_ntup, CacheHdr->ch_ntup); + CACHE_elog(DEBUG2, "SearchCatCache(%s): put neg entry in bucket %d", + cache->cc_relname, hashIndex); + + /* + * We are not returning the negative entry to the caller, so leave its + * refcount zero. + */ + + return NULL; + } + + CACHE_elog(DEBUG2, "SearchCatCache(%s): Contains %d/%d tuples", + cache->cc_relname, cache->cc_ntup, CacheHdr->ch_ntup); + CACHE_elog(DEBUG2, "SearchCatCache(%s): put in bucket %d", + cache->cc_relname, hashIndex); + +#ifdef CATCACHE_STATS + cache->cc_newloads++; +#endif + + return &ct->tuple; +} + +/* + * ReleaseCatCache + * + * Decrement the reference count of a catcache entry (releasing the + * hold grabbed by a successful SearchCatCache). + * + * NOTE: if compiled with -DCATCACHE_FORCE_RELEASE then catcache entries + * will be freed as soon as their refcount goes to zero. In combination + * with aset.c's CLOBBER_FREED_MEMORY option, this provides a good test + * to catch references to already-released catcache entries. + */ +void +ReleaseCatCache(HeapTuple tuple) +{ + CatCTup *ct = (CatCTup *) (((char *) tuple) - + offsetof(CatCTup, tuple)); + + /* Safety checks to ensure we were handed a cache entry */ + Assert(ct->ct_magic == CT_MAGIC); + Assert(ct->refcount > 0); + + ct->refcount--; + ResourceOwnerForgetCatCacheRef(CurrentResourceOwner, &ct->tuple); + + if ( +#ifndef CATCACHE_FORCE_RELEASE + ct->dead && +#endif + ct->refcount == 0 && + (ct->c_list == NULL || ct->c_list->refcount == 0)) + CatCacheRemoveCTup(ct->my_cache, ct); +} + + +/* + * GetCatCacheHashValue + * + * Compute the hash value for a given set of search keys. + * + * The reason for exposing this as part of the API is that the hash value is + * exposed in cache invalidation operations, so there are places outside the + * catcache code that need to be able to compute the hash values. + */ +uint32 +GetCatCacheHashValue(CatCache *cache, + Datum v1, + Datum v2, + Datum v3, + Datum v4) +{ + /* + * one-time startup overhead for each cache + */ + if (cache->cc_tupdesc == NULL) + CatalogCacheInitializeCache(cache); + + /* + * calculate the hash value + */ + return CatalogCacheComputeHashValue(cache, cache->cc_nkeys, v1, v2, v3, v4); +} + + +/* + * SearchCatCacheList + * + * Generate a list of all tuples matching a partial key (that is, + * a key specifying just the first K of the cache's N key columns). + * + * It doesn't make any sense to specify all of the cache's key columns + * here: since the key is unique, there could be at most one match, so + * you ought to use SearchCatCache() instead. Hence this function takes + * one fewer Datum argument than SearchCatCache() does. + * + * The caller must not modify the list object or the pointed-to tuples, + * and must call ReleaseCatCacheList() when done with the list. + */ +CatCList * +SearchCatCacheList(CatCache *cache, + int nkeys, + Datum v1, + Datum v2, + Datum v3) +{ + Datum v4 = 0; /* dummy last-column value */ + Datum arguments[CATCACHE_MAXKEYS]; + uint32 lHashValue; + Index lHashIndex; + dlist_iter iter; + dlist_head *lbucket; + CatCList *cl; + CatCTup *ct; + List *volatile ctlist; + ListCell *ctlist_item; + int nmembers; + bool ordered; + HeapTuple ntp; + MemoryContext oldcxt; + int i; + + /* + * one-time startup overhead for each cache + */ + if (unlikely(cache->cc_tupdesc == NULL)) + CatalogCacheInitializeCache(cache); + + Assert(nkeys > 0 && nkeys < cache->cc_nkeys); + +#ifdef CATCACHE_STATS + cache->cc_lsearches++; +#endif + + /* Initialize local parameter array */ + arguments[0] = v1; + arguments[1] = v2; + arguments[2] = v3; + arguments[3] = v4; + + /* + * If we haven't previously done a list search in this cache, create the + * bucket header array; otherwise, consider whether it's time to enlarge + * it. + */ + if (cache->cc_lbucket == NULL) + { + /* Arbitrary initial size --- must be a power of 2 */ + int nbuckets = 16; + + cache->cc_lbucket = (dlist_head *) + MemoryContextAllocZero(CacheMemoryContext, + nbuckets * sizeof(dlist_head)); + /* Don't set cc_nlbuckets if we get OOM allocating cc_lbucket */ + cache->cc_nlbuckets = nbuckets; + } + else + { + /* + * If the hash table has become too full, enlarge the buckets array. + * Quite arbitrarily, we enlarge when fill factor > 2. + */ + if (cache->cc_nlist > cache->cc_nlbuckets * 2) + RehashCatCacheLists(cache); + } + + /* + * Find the hash bucket in which to look for the CatCList. + */ + lHashValue = CatalogCacheComputeHashValue(cache, nkeys, v1, v2, v3, v4); + lHashIndex = HASH_INDEX(lHashValue, cache->cc_nlbuckets); + + /* + * scan the items until we find a match or exhaust our list + * + * Note: it's okay to use dlist_foreach here, even though we modify the + * dlist within the loop, because we don't continue the loop afterwards. + */ + lbucket = &cache->cc_lbucket[lHashIndex]; + dlist_foreach(iter, lbucket) + { + cl = dlist_container(CatCList, cache_elem, iter.cur); + + if (cl->dead) + continue; /* ignore dead entries */ + + if (cl->hash_value != lHashValue) + continue; /* quickly skip entry if wrong hash val */ + + /* + * see if the cached list matches our key. + */ + if (cl->nkeys != nkeys) + continue; + + if (!CatalogCacheCompareTuple(cache, nkeys, cl->keys, arguments)) + continue; + + /* + * We found a matching list. Move the list to the front of the list + * for its hashbucket, so as to speed subsequent searches. (We do not + * move the members to the fronts of their hashbucket lists, however, + * since there's no point in that unless they are searched for + * individually.) + */ + dlist_move_head(lbucket, &cl->cache_elem); + + /* Bump the list's refcount and return it */ + ResourceOwnerEnlargeCatCacheListRefs(CurrentResourceOwner); + cl->refcount++; + ResourceOwnerRememberCatCacheListRef(CurrentResourceOwner, cl); + + CACHE_elog(DEBUG2, "SearchCatCacheList(%s): found list", + cache->cc_relname); + +#ifdef CATCACHE_STATS + cache->cc_lhits++; +#endif + + return cl; + } + + /* + * List was not found in cache, so we have to build it by reading the + * relation. For each matching tuple found in the relation, use an + * existing cache entry if possible, else build a new one. + * + * We have to bump the member refcounts temporarily to ensure they won't + * get dropped from the cache while loading other members. We use a PG_TRY + * block to ensure we can undo those refcounts if we get an error before + * we finish constructing the CatCList. ctlist must be valid throughout + * the PG_TRY block. + */ + ResourceOwnerEnlargeCatCacheListRefs(CurrentResourceOwner); + + ctlist = NIL; + + PG_TRY(); + { + ScanKeyData cur_skey[CATCACHE_MAXKEYS]; + Relation relation; + SysScanDesc scandesc; + bool stale; + + relation = table_open(cache->cc_reloid, AccessShareLock); + + do + { + /* + * Ok, need to make a lookup in the relation, copy the scankey and + * fill out any per-call fields. (We must re-do this when + * retrying, because systable_beginscan scribbles on the scankey.) + */ + memcpy(cur_skey, cache->cc_skey, sizeof(ScanKeyData) * cache->cc_nkeys); + cur_skey[0].sk_argument = v1; + cur_skey[1].sk_argument = v2; + cur_skey[2].sk_argument = v3; + cur_skey[3].sk_argument = v4; + + scandesc = systable_beginscan(relation, + cache->cc_indexoid, + IndexScanOK(cache, cur_skey), + NULL, + nkeys, + cur_skey); + + /* The list will be ordered iff we are doing an index scan */ + ordered = (scandesc->irel != NULL); + + stale = false; + + while (HeapTupleIsValid(ntp = systable_getnext(scandesc))) + { + uint32 hashValue; + Index hashIndex; + bool found = false; + dlist_head *bucket; + + /* + * See if there's an entry for this tuple already. + */ + ct = NULL; + hashValue = CatalogCacheComputeTupleHashValue(cache, cache->cc_nkeys, ntp); + hashIndex = HASH_INDEX(hashValue, cache->cc_nbuckets); + + bucket = &cache->cc_bucket[hashIndex]; + dlist_foreach(iter, bucket) + { + ct = dlist_container(CatCTup, cache_elem, iter.cur); + + if (ct->dead || ct->negative) + continue; /* ignore dead and negative entries */ + + if (ct->hash_value != hashValue) + continue; /* quickly skip entry if wrong hash val */ + + if (!ItemPointerEquals(&(ct->tuple.t_self), &(ntp->t_self))) + continue; /* not same tuple */ + + /* + * Found a match, but can't use it if it belongs to + * another list already + */ + if (ct->c_list) + continue; + + found = true; + break; /* A-OK */ + } + + if (!found) + { + /* We didn't find a usable entry, so make a new one */ + ct = CatalogCacheCreateEntry(cache, ntp, scandesc, NULL, + hashValue, hashIndex); + /* upon failure, we must start the scan over */ + if (ct == NULL) + { + /* + * Release refcounts on any items we already had. We + * dare not try to free them if they're now + * unreferenced, since an error while doing that would + * result in the PG_CATCH below doing extra refcount + * decrements. Besides, we'll likely re-adopt those + * items in the next iteration, so it's not worth + * complicating matters to try to get rid of them. + */ + foreach(ctlist_item, ctlist) + { + ct = (CatCTup *) lfirst(ctlist_item); + Assert(ct->c_list == NULL); + Assert(ct->refcount > 0); + ct->refcount--; + } + /* Reset ctlist in preparation for new try */ + ctlist = NIL; + stale = true; + break; + } + } + + /* Careful here: add entry to ctlist, then bump its refcount */ + /* This way leaves state correct if lappend runs out of memory */ + ctlist = lappend(ctlist, ct); + ct->refcount++; + } + + systable_endscan(scandesc); + } while (stale); + + table_close(relation, AccessShareLock); + + /* Now we can build the CatCList entry. */ + oldcxt = MemoryContextSwitchTo(CacheMemoryContext); + nmembers = list_length(ctlist); + cl = (CatCList *) + palloc(offsetof(CatCList, members) + nmembers * sizeof(CatCTup *)); + + /* Extract key values */ + CatCacheCopyKeys(cache->cc_tupdesc, nkeys, cache->cc_keyno, + arguments, cl->keys); + MemoryContextSwitchTo(oldcxt); + + /* + * We are now past the last thing that could trigger an elog before we + * have finished building the CatCList and remembering it in the + * resource owner. So it's OK to fall out of the PG_TRY, and indeed + * we'd better do so before we start marking the members as belonging + * to the list. + */ + } + PG_CATCH(); + { + foreach(ctlist_item, ctlist) + { + ct = (CatCTup *) lfirst(ctlist_item); + Assert(ct->c_list == NULL); + Assert(ct->refcount > 0); + ct->refcount--; + if ( +#ifndef CATCACHE_FORCE_RELEASE + ct->dead && +#endif + ct->refcount == 0 && + (ct->c_list == NULL || ct->c_list->refcount == 0)) + CatCacheRemoveCTup(cache, ct); + } + + PG_RE_THROW(); + } + PG_END_TRY(); + + cl->cl_magic = CL_MAGIC; + cl->my_cache = cache; + cl->refcount = 0; /* for the moment */ + cl->dead = false; + cl->ordered = ordered; + cl->nkeys = nkeys; + cl->hash_value = lHashValue; + cl->n_members = nmembers; + + i = 0; + foreach(ctlist_item, ctlist) + { + cl->members[i++] = ct = (CatCTup *) lfirst(ctlist_item); + Assert(ct->c_list == NULL); + ct->c_list = cl; + /* release the temporary refcount on the member */ + Assert(ct->refcount > 0); + ct->refcount--; + /* mark list dead if any members already dead */ + if (ct->dead) + cl->dead = true; + } + Assert(i == nmembers); + + /* + * Add the CatCList to the appropriate bucket, and count it. + */ + dlist_push_head(lbucket, &cl->cache_elem); + + cache->cc_nlist++; + + /* Finally, bump the list's refcount and return it */ + cl->refcount++; + ResourceOwnerRememberCatCacheListRef(CurrentResourceOwner, cl); + + CACHE_elog(DEBUG2, "SearchCatCacheList(%s): made list of %d members", + cache->cc_relname, nmembers); + + return cl; +} + +/* + * ReleaseCatCacheList + * + * Decrement the reference count of a catcache list. + */ +void +ReleaseCatCacheList(CatCList *list) +{ + return; + /* Safety checks to ensure we were handed a cache entry */ + Assert(list->cl_magic == CL_MAGIC); + Assert(list->refcount > 0); + list->refcount--; + ResourceOwnerForgetCatCacheListRef(CurrentResourceOwner, list); + + if ( +#ifndef CATCACHE_FORCE_RELEASE + list->dead && +#endif + list->refcount == 0) + CatCacheRemoveCList(list->my_cache, list); +} + + +/* + * CatalogCacheCreateEntry + * Create a new CatCTup entry, copying the given HeapTuple and other + * supplied data into it. The new entry initially has refcount 0. + * + * To create a normal cache entry, ntp must be the HeapTuple just fetched + * from scandesc, and "arguments" is not used. To create a negative cache + * entry, pass NULL for ntp and scandesc; then "arguments" is the cache + * keys to use. In either case, hashValue/hashIndex are the hash values + * computed from the cache keys. + * + * Returns NULL if we attempt to detoast the tuple and observe that it + * became stale. (This cannot happen for a negative entry.) Caller must + * retry the tuple lookup in that case. + */ +static CatCTup * +CatalogCacheCreateEntry(CatCache *cache, HeapTuple ntp, SysScanDesc scandesc, + Datum *arguments, + uint32 hashValue, Index hashIndex) +{ + CatCTup *ct; + HeapTuple dtp; + MemoryContext oldcxt; + + if (ntp) + { + int i; + + /* + * The visibility recheck below essentially never fails during our + * regression tests, and there's no easy way to force it to fail for + * testing purposes. To ensure we have test coverage for the retry + * paths in our callers, make debug builds randomly fail about 0.1% of + * the times through this code path, even when there's no toasted + * fields. + */ +#ifdef USE_ASSERT_CHECKING + if (pg_prng_uint32(&pg_global_prng_state) <= (PG_UINT32_MAX / 1000)) + return NULL; +#endif + + /* + * If there are any out-of-line toasted fields in the tuple, expand + * them in-line. This saves cycles during later use of the catcache + * entry, and also protects us against the possibility of the toast + * tuples being freed before we attempt to fetch them, in case of + * something using a slightly stale catcache entry. + */ + if (HeapTupleHasExternal(ntp)) + { + dtp = toast_flatten_tuple(ntp, cache->cc_tupdesc); + + /* + * The tuple could become stale while we are doing toast table + * access (since AcceptInvalidationMessages can run then), so we + * must recheck its visibility afterwards. + */ + if (!systable_recheck_tuple(scandesc, ntp)) + { + heap_freetuple(dtp); + return NULL; + } + } + else + dtp = ntp; + + /* Allocate memory for CatCTup and the cached tuple in one go */ + oldcxt = MemoryContextSwitchTo(CacheMemoryContext); + + ct = (CatCTup *) palloc(sizeof(CatCTup) + + MAXIMUM_ALIGNOF + dtp->t_len); + ct->tuple.t_len = dtp->t_len; + ct->tuple.t_self = dtp->t_self; + ct->tuple.t_tableOid = dtp->t_tableOid; + ct->tuple.t_data = (HeapTupleHeader) + MAXALIGN(((char *) ct) + sizeof(CatCTup)); + /* copy tuple contents */ + memcpy((char *) ct->tuple.t_data, + (const char *) dtp->t_data, + dtp->t_len); + MemoryContextSwitchTo(oldcxt); + + if (dtp != ntp) + heap_freetuple(dtp); + + /* extract keys - they'll point into the tuple if not by-value */ + for (i = 0; i < cache->cc_nkeys; i++) + { + Datum atp; + bool isnull; + + atp = heap_getattr(&ct->tuple, + cache->cc_keyno[i], + cache->cc_tupdesc, + &isnull); + Assert(!isnull); + ct->keys[i] = atp; + } + } + else + { + /* Set up keys for a negative cache entry */ + oldcxt = MemoryContextSwitchTo(CacheMemoryContext); + ct = (CatCTup *) palloc(sizeof(CatCTup)); + + /* + * Store keys - they'll point into separately allocated memory if not + * by-value. + */ + CatCacheCopyKeys(cache->cc_tupdesc, cache->cc_nkeys, cache->cc_keyno, + arguments, ct->keys); + MemoryContextSwitchTo(oldcxt); + } + + /* + * Finish initializing the CatCTup header, and add it to the cache's + * linked list and counts. + */ + ct->ct_magic = CT_MAGIC; + ct->my_cache = cache; + ct->c_list = NULL; + ct->refcount = 0; /* for the moment */ + ct->dead = false; + ct->negative = (ntp == NULL); + ct->hash_value = hashValue; + + dlist_push_head(&cache->cc_bucket[hashIndex], &ct->cache_elem); + + cache->cc_ntup++; + CacheHdr->ch_ntup++; + + /* + * If the hash table has become too full, enlarge the buckets array. Quite + * arbitrarily, we enlarge when fill factor > 2. + */ + if (cache->cc_ntup > cache->cc_nbuckets * 2) + RehashCatCache(cache); + + return ct; +} + +/* + * Helper routine that frees keys stored in the keys array. + */ +static void +CatCacheFreeKeys(TupleDesc tupdesc, int nkeys, int *attnos, Datum *keys) +{ + int i; + + for (i = 0; i < nkeys; i++) + { + int attnum = attnos[i]; + Form_pg_attribute att; + + /* system attribute are not supported in caches */ + Assert(attnum > 0); + + att = TupleDescAttr(tupdesc, attnum - 1); + + if (!att->attbyval) + pfree(DatumGetPointer(keys[i])); + } +} + +/* + * Helper routine that copies the keys in the srckeys array into the dstkeys + * one, guaranteeing that the datums are fully allocated in the current memory + * context. + */ +static void +CatCacheCopyKeys(TupleDesc tupdesc, int nkeys, int *attnos, + Datum *srckeys, Datum *dstkeys) +{ + int i; + + /* + * XXX: memory and lookup performance could possibly be improved by + * storing all keys in one allocation. + */ + + for (i = 0; i < nkeys; i++) + { + int attnum = attnos[i]; + Form_pg_attribute att = TupleDescAttr(tupdesc, attnum - 1); + Datum src = srckeys[i]; + NameData srcname; + + /* + * Must be careful in case the caller passed a C string where a NAME + * is wanted: convert the given argument to a correctly padded NAME. + * Otherwise the memcpy() done by datumCopy() could fall off the end + * of memory. + */ + if (att->atttypid == NAMEOID) + { + namestrcpy(&srcname, DatumGetCString(src)); + src = NameGetDatum(&srcname); + } + + dstkeys[i] = datumCopy(src, + att->attbyval, + att->attlen); + } +} + +/* + * PrepareToInvalidateCacheTuple() + * + * This is part of a rather subtle chain of events, so pay attention: + * + * When a tuple is inserted or deleted, it cannot be flushed from the + * catcaches immediately, for reasons explained at the top of cache/inval.c. + * Instead we have to add entry(s) for the tuple to a list of pending tuple + * invalidations that will be done at the end of the command or transaction. + * + * The lists of tuples that need to be flushed are kept by inval.c. This + * routine is a helper routine for inval.c. Given a tuple belonging to + * the specified relation, find all catcaches it could be in, compute the + * correct hash value for each such catcache, and call the specified + * function to record the cache id and hash value in inval.c's lists. + * SysCacheInvalidate will be called later, if appropriate, + * using the recorded information. + * + * For an insert or delete, tuple is the target tuple and newtuple is NULL. + * For an update, we are called just once, with tuple being the old tuple + * version and newtuple the new version. We should make two list entries + * if the tuple's hash value changed, but only one if it didn't. + * + * Note that it is irrelevant whether the given tuple is actually loaded + * into the catcache at the moment. Even if it's not there now, it might + * be by the end of the command, or there might be a matching negative entry + * to flush --- or other backends' caches might have such entries --- so + * we have to make list entries to flush it later. + * + * Also note that it's not an error if there are no catcaches for the + * specified relation. inval.c doesn't know exactly which rels have + * catcaches --- it will call this routine for any tuple that's in a + * system relation. + */ +void +PrepareToInvalidateCacheTuple(Relation relation, + HeapTuple tuple, + HeapTuple newtuple, + void (*function) (int, uint32, Oid)) +{ + slist_iter iter; + Oid reloid; + + CACHE_elog(DEBUG2, "PrepareToInvalidateCacheTuple: called"); + + /* + * sanity checks + */ + Assert(RelationIsValid(relation)); + Assert(HeapTupleIsValid(tuple)); + Assert(PointerIsValid(function)); + Assert(CacheHdr != NULL); + + reloid = RelationGetRelid(relation); + + /* ---------------- + * for each cache + * if the cache contains tuples from the specified relation + * compute the tuple's hash value(s) in this cache, + * and call the passed function to register the information. + * ---------------- + */ + + slist_foreach(iter, &CacheHdr->ch_caches) + { + CatCache *ccp = slist_container(CatCache, cc_next, iter.cur); + uint32 hashvalue; + Oid dbid; + + if (ccp->cc_reloid != reloid) + continue; + + /* Just in case cache hasn't finished initialization yet... */ + if (ccp->cc_tupdesc == NULL) + CatalogCacheInitializeCache(ccp); + + hashvalue = CatalogCacheComputeTupleHashValue(ccp, ccp->cc_nkeys, tuple); + dbid = ccp->cc_relisshared ? (Oid) 0 : MyDatabaseId; + + (*function) (ccp->id, hashvalue, dbid); + + if (newtuple) + { + uint32 newhashvalue; + + newhashvalue = CatalogCacheComputeTupleHashValue(ccp, ccp->cc_nkeys, newtuple); + + if (newhashvalue != hashvalue) + (*function) (ccp->id, newhashvalue, dbid); + } + } +} + + +/* + * Subroutines for warning about reference leaks. These are exported so + * that resowner.c can call them. + */ +void +PrintCatCacheLeakWarning(HeapTuple tuple) +{ + CatCTup *ct = (CatCTup *) (((char *) tuple) - + offsetof(CatCTup, tuple)); + + /* Safety check to ensure we were handed a cache entry */ + Assert(ct->ct_magic == CT_MAGIC); + + elog(WARNING, "cache reference leak: cache %s (%d), tuple %u/%u has count %d", + ct->my_cache->cc_relname, ct->my_cache->id, + ItemPointerGetBlockNumber(&(tuple->t_self)), + ItemPointerGetOffsetNumber(&(tuple->t_self)), + ct->refcount); +} + +void +PrintCatCacheListLeakWarning(CatCList *list) +{ + elog(WARNING, "cache reference leak: cache %s (%d), list %p has count %d", + list->my_cache->cc_relname, list->my_cache->id, + list, list->refcount); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/cache/evtcache.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/cache/evtcache.c new file mode 100644 index 00000000000..b00faf1baec --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/cache/evtcache.c @@ -0,0 +1,269 @@ +/*------------------------------------------------------------------------- + * + * evtcache.c + * Special-purpose cache for event trigger data. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/utils/cache/evtcache.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/genam.h" +#include "access/htup_details.h" +#include "access/relation.h" +#include "catalog/pg_event_trigger.h" +#include "catalog/pg_type.h" +#include "commands/trigger.h" +#include "tcop/cmdtag.h" +#include "utils/array.h" +#include "utils/builtins.h" +#include "utils/catcache.h" +#include "utils/evtcache.h" +#include "utils/hsearch.h" +#include "utils/inval.h" +#include "utils/memutils.h" +#include "utils/rel.h" +#include "utils/snapmgr.h" +#include "utils/syscache.h" + +typedef enum +{ + ETCS_NEEDS_REBUILD, + ETCS_REBUILD_STARTED, + ETCS_VALID +} EventTriggerCacheStateType; + +typedef struct +{ + EventTriggerEvent event; + List *triggerlist; +} EventTriggerCacheEntry; + +static __thread HTAB *EventTriggerCache; +static __thread MemoryContext EventTriggerCacheContext; +static __thread EventTriggerCacheStateType EventTriggerCacheState = ETCS_NEEDS_REBUILD; + +static void BuildEventTriggerCache(void); +static void InvalidateEventCacheCallback(Datum arg, + int cacheid, uint32 hashvalue); +static Bitmapset *DecodeTextArrayToBitmapset(Datum array); + +/* + * Search the event cache by trigger event. + * + * Note that the caller had better copy any data it wants to keep around + * across any operation that might touch a system catalog into some other + * memory context, since a cache reset could blow the return value away. + */ +List * +EventCacheLookup(EventTriggerEvent event) +{ + EventTriggerCacheEntry *entry; + + if (EventTriggerCacheState != ETCS_VALID) + BuildEventTriggerCache(); + entry = hash_search(EventTriggerCache, &event, HASH_FIND, NULL); + return entry != NULL ? entry->triggerlist : NIL; +} + +/* + * Rebuild the event trigger cache. + */ +static void +BuildEventTriggerCache(void) +{ + HASHCTL ctl; + HTAB *cache; + MemoryContext oldcontext; + Relation rel; + Relation irel; + SysScanDesc scan; + + if (EventTriggerCacheContext != NULL) + { + /* + * Free up any memory already allocated in EventTriggerCacheContext. + * This can happen either because a previous rebuild failed, or + * because an invalidation happened before the rebuild was complete. + */ + MemoryContextResetAndDeleteChildren(EventTriggerCacheContext); + } + else + { + /* + * This is our first time attempting to build the cache, so we need to + * set up the memory context and register a syscache callback to + * capture future invalidation events. + */ + if (CacheMemoryContext == NULL) + CreateCacheMemoryContext(); + EventTriggerCacheContext = + AllocSetContextCreate(CacheMemoryContext, + "EventTriggerCache", + ALLOCSET_DEFAULT_SIZES); + CacheRegisterSyscacheCallback(EVENTTRIGGEROID, + InvalidateEventCacheCallback, + (Datum) 0); + } + + /* Switch to correct memory context. */ + oldcontext = MemoryContextSwitchTo(EventTriggerCacheContext); + + /* Prevent the memory context from being nuked while we're rebuilding. */ + EventTriggerCacheState = ETCS_REBUILD_STARTED; + + /* Create new hash table. */ + ctl.keysize = sizeof(EventTriggerEvent); + ctl.entrysize = sizeof(EventTriggerCacheEntry); + ctl.hcxt = EventTriggerCacheContext; + cache = hash_create("Event Trigger Cache", 32, &ctl, + HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); + + /* + * Prepare to scan pg_event_trigger in name order. + */ + rel = relation_open(EventTriggerRelationId, AccessShareLock); + irel = index_open(EventTriggerNameIndexId, AccessShareLock); + scan = systable_beginscan_ordered(rel, irel, NULL, 0, NULL); + + /* + * Build a cache item for each pg_event_trigger tuple, and append each one + * to the appropriate cache entry. + */ + for (;;) + { + HeapTuple tup; + Form_pg_event_trigger form; + char *evtevent; + EventTriggerEvent event; + EventTriggerCacheItem *item; + Datum evttags; + bool evttags_isnull; + EventTriggerCacheEntry *entry; + bool found; + + /* Get next tuple. */ + tup = systable_getnext_ordered(scan, ForwardScanDirection); + if (!HeapTupleIsValid(tup)) + break; + + /* Skip trigger if disabled. */ + form = (Form_pg_event_trigger) GETSTRUCT(tup); + if (form->evtenabled == TRIGGER_DISABLED) + continue; + + /* Decode event name. */ + evtevent = NameStr(form->evtevent); + if (strcmp(evtevent, "ddl_command_start") == 0) + event = EVT_DDLCommandStart; + else if (strcmp(evtevent, "ddl_command_end") == 0) + event = EVT_DDLCommandEnd; + else if (strcmp(evtevent, "sql_drop") == 0) + event = EVT_SQLDrop; + else if (strcmp(evtevent, "table_rewrite") == 0) + event = EVT_TableRewrite; + else + continue; + + /* Allocate new cache item. */ + item = palloc0(sizeof(EventTriggerCacheItem)); + item->fnoid = form->evtfoid; + item->enabled = form->evtenabled; + + /* Decode and sort tags array. */ + evttags = heap_getattr(tup, Anum_pg_event_trigger_evttags, + RelationGetDescr(rel), &evttags_isnull); + if (!evttags_isnull) + item->tagset = DecodeTextArrayToBitmapset(evttags); + + /* Add to cache entry. */ + entry = hash_search(cache, &event, HASH_ENTER, &found); + if (found) + entry->triggerlist = lappend(entry->triggerlist, item); + else + entry->triggerlist = list_make1(item); + } + + /* Done with pg_event_trigger scan. */ + systable_endscan_ordered(scan); + index_close(irel, AccessShareLock); + relation_close(rel, AccessShareLock); + + /* Restore previous memory context. */ + MemoryContextSwitchTo(oldcontext); + + /* Install new cache. */ + EventTriggerCache = cache; + + /* + * If the cache has been invalidated since we entered this routine, we + * still use and return the cache we just finished constructing, to avoid + * infinite loops, but we leave the cache marked stale so that we'll + * rebuild it again on next access. Otherwise, we mark the cache valid. + */ + if (EventTriggerCacheState == ETCS_REBUILD_STARTED) + EventTriggerCacheState = ETCS_VALID; +} + +/* + * Decode text[] to a Bitmapset of CommandTags. + * + * We could avoid a bit of overhead here if we were willing to duplicate some + * of the logic from deconstruct_array, but it doesn't seem worth the code + * complexity. + */ +static Bitmapset * +DecodeTextArrayToBitmapset(Datum array) +{ + ArrayType *arr = DatumGetArrayTypeP(array); + Datum *elems; + Bitmapset *bms; + int i; + int nelems; + + if (ARR_NDIM(arr) != 1 || ARR_HASNULL(arr) || ARR_ELEMTYPE(arr) != TEXTOID) + elog(ERROR, "expected 1-D text array"); + deconstruct_array_builtin(arr, TEXTOID, &elems, NULL, &nelems); + + for (bms = NULL, i = 0; i < nelems; ++i) + { + char *str = TextDatumGetCString(elems[i]); + + bms = bms_add_member(bms, GetCommandTagEnum(str)); + pfree(str); + } + + pfree(elems); + + return bms; +} + +/* + * Flush all cache entries when pg_event_trigger is updated. + * + * This should be rare enough that we don't need to be very granular about + * it, so we just blow away everything, which also avoids the possibility of + * memory leaks. + */ +static void +InvalidateEventCacheCallback(Datum arg, int cacheid, uint32 hashvalue) +{ + /* + * If the cache isn't valid, then there might be a rebuild in progress, so + * we can't immediately blow it away. But it's advantageous to do this + * when possible, so as to immediately free memory. + */ + if (EventTriggerCacheState == ETCS_VALID) + { + MemoryContextResetAndDeleteChildren(EventTriggerCacheContext); + EventTriggerCache = NULL; + } + + /* Mark cache for rebuild. */ + EventTriggerCacheState = ETCS_NEEDS_REBUILD; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/cache/inval.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/cache/inval.c new file mode 100644 index 00000000000..7d3ebccf84c --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/cache/inval.c @@ -0,0 +1,1637 @@ +/*------------------------------------------------------------------------- + * + * inval.c + * POSTGRES cache invalidation dispatcher code. + * + * This is subtle stuff, so pay attention: + * + * When a tuple is updated or deleted, our standard visibility rules + * consider that it is *still valid* so long as we are in the same command, + * ie, until the next CommandCounterIncrement() or transaction commit. + * (See access/heap/heapam_visibility.c, and note that system catalogs are + * generally scanned under the most current snapshot available, rather than + * the transaction snapshot.) At the command boundary, the old tuple stops + * being valid and the new version, if any, becomes valid. Therefore, + * we cannot simply flush a tuple from the system caches during heap_update() + * or heap_delete(). The tuple is still good at that point; what's more, + * even if we did flush it, it might be reloaded into the caches by a later + * request in the same command. So the correct behavior is to keep a list + * of outdated (updated/deleted) tuples and then do the required cache + * flushes at the next command boundary. We must also keep track of + * inserted tuples so that we can flush "negative" cache entries that match + * the new tuples; again, that mustn't happen until end of command. + * + * Once we have finished the command, we still need to remember inserted + * tuples (including new versions of updated tuples), so that we can flush + * them from the caches if we abort the transaction. Similarly, we'd better + * be able to flush "negative" cache entries that may have been loaded in + * place of deleted tuples, so we still need the deleted ones too. + * + * If we successfully complete the transaction, we have to broadcast all + * these invalidation events to other backends (via the SI message queue) + * so that they can flush obsolete entries from their caches. Note we have + * to record the transaction commit before sending SI messages, otherwise + * the other backends won't see our updated tuples as good. + * + * When a subtransaction aborts, we can process and discard any events + * it has queued. When a subtransaction commits, we just add its events + * to the pending lists of the parent transaction. + * + * In short, we need to remember until xact end every insert or delete + * of a tuple that might be in the system caches. Updates are treated as + * two events, delete + insert, for simplicity. (If the update doesn't + * change the tuple hash value, catcache.c optimizes this into one event.) + * + * We do not need to register EVERY tuple operation in this way, just those + * on tuples in relations that have associated catcaches. We do, however, + * have to register every operation on every tuple that *could* be in a + * catcache, whether or not it currently is in our cache. Also, if the + * tuple is in a relation that has multiple catcaches, we need to register + * an invalidation message for each such catcache. catcache.c's + * PrepareToInvalidateCacheTuple() routine provides the knowledge of which + * catcaches may need invalidation for a given tuple. + * + * Also, whenever we see an operation on a pg_class, pg_attribute, or + * pg_index tuple, we register a relcache flush operation for the relation + * described by that tuple (as specified in CacheInvalidateHeapTuple()). + * Likewise for pg_constraint tuples for foreign keys on relations. + * + * We keep the relcache flush requests in lists separate from the catcache + * tuple flush requests. This allows us to issue all the pending catcache + * flushes before we issue relcache flushes, which saves us from loading + * a catcache tuple during relcache load only to flush it again right away. + * Also, we avoid queuing multiple relcache flush requests for the same + * relation, since a relcache flush is relatively expensive to do. + * (XXX is it worth testing likewise for duplicate catcache flush entries? + * Probably not.) + * + * Many subsystems own higher-level caches that depend on relcache and/or + * catcache, and they register callbacks here to invalidate their caches. + * While building a higher-level cache entry, a backend may receive a + * callback for the being-built entry or one of its dependencies. This + * implies the new higher-level entry would be born stale, and it might + * remain stale for the life of the backend. Many caches do not prevent + * that. They rely on DDL for can't-miss catalog changes taking + * AccessExclusiveLock on suitable objects. (For a change made with less + * locking, backends might never read the change.) The relation cache, + * however, needs to reflect changes from CREATE INDEX CONCURRENTLY no later + * than the beginning of the next transaction. Hence, when a relevant + * invalidation callback arrives during a build, relcache.c reattempts that + * build. Caches with similar needs could do likewise. + * + * If a relcache flush is issued for a system relation that we preload + * from the relcache init file, we must also delete the init file so that + * it will be rebuilt during the next backend restart. The actual work of + * manipulating the init file is in relcache.c, but we keep track of the + * need for it here. + * + * Currently, inval messages are sent without regard for the possibility + * that the object described by the catalog tuple might be a session-local + * object such as a temporary table. This is because (1) this code has + * no practical way to tell the difference, and (2) it is not certain that + * other backends don't have catalog cache or even relcache entries for + * such tables, anyway; there is nothing that prevents that. It might be + * worth trying to avoid sending such inval traffic in the future, if those + * problems can be overcome cheaply. + * + * When wal_level=logical, write invalidations into WAL at each command end to + * support the decoding of the in-progress transactions. See + * CommandEndInvalidationMessages. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/utils/cache/inval.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <limits.h> + +#include "access/htup_details.h" +#include "access/xact.h" +#include "access/xloginsert.h" +#include "catalog/catalog.h" +#include "catalog/pg_constraint.h" +#include "miscadmin.h" +#include "storage/sinval.h" +#include "storage/smgr.h" +#include "utils/catcache.h" +#include "utils/guc.h" +#include "utils/inval.h" +#include "utils/memdebug.h" +#include "utils/memutils.h" +#include "utils/rel.h" +#include "utils/relmapper.h" +#include "utils/snapmgr.h" +#include "utils/syscache.h" + + +/* + * Pending requests are stored as ready-to-send SharedInvalidationMessages. + * We keep the messages themselves in arrays in TopTransactionContext + * (there are separate arrays for catcache and relcache messages). Control + * information is kept in a chain of TransInvalidationInfo structs, also + * allocated in TopTransactionContext. (We could keep a subtransaction's + * TransInvalidationInfo in its CurTransactionContext; but that's more + * wasteful not less so, since in very many scenarios it'd be the only + * allocation in the subtransaction's CurTransactionContext.) + * + * We can store the message arrays densely, and yet avoid moving data around + * within an array, because within any one subtransaction we need only + * distinguish between messages emitted by prior commands and those emitted + * by the current command. Once a command completes and we've done local + * processing on its messages, we can fold those into the prior-commands + * messages just by changing array indexes in the TransInvalidationInfo + * struct. Similarly, we need distinguish messages of prior subtransactions + * from those of the current subtransaction only until the subtransaction + * completes, after which we adjust the array indexes in the parent's + * TransInvalidationInfo to include the subtransaction's messages. + * + * The ordering of the individual messages within a command's or + * subtransaction's output is not considered significant, although this + * implementation happens to preserve the order in which they were queued. + * (Previous versions of this code did not preserve it.) + * + * For notational convenience, control information is kept in two-element + * arrays, the first for catcache messages and the second for relcache + * messages. + */ +#define CatCacheMsgs 0 +#define RelCacheMsgs 1 + +/* Pointers to main arrays in TopTransactionContext */ +typedef struct InvalMessageArray +{ + SharedInvalidationMessage *msgs; /* palloc'd array (can be expanded) */ + int maxmsgs; /* current allocated size of array */ +} InvalMessageArray; + +static __thread InvalMessageArray InvalMessageArrays[2]; + +/* Control information for one logical group of messages */ +typedef struct InvalidationMsgsGroup +{ + int firstmsg[2]; /* first index in relevant array */ + int nextmsg[2]; /* last+1 index */ +} InvalidationMsgsGroup; + +/* Macros to help preserve InvalidationMsgsGroup abstraction */ +#define SetSubGroupToFollow(targetgroup, priorgroup, subgroup) \ + do { \ + (targetgroup)->firstmsg[subgroup] = \ + (targetgroup)->nextmsg[subgroup] = \ + (priorgroup)->nextmsg[subgroup]; \ + } while (0) + +#define SetGroupToFollow(targetgroup, priorgroup) \ + do { \ + SetSubGroupToFollow(targetgroup, priorgroup, CatCacheMsgs); \ + SetSubGroupToFollow(targetgroup, priorgroup, RelCacheMsgs); \ + } while (0) + +#define NumMessagesInSubGroup(group, subgroup) \ + ((group)->nextmsg[subgroup] - (group)->firstmsg[subgroup]) + +#define NumMessagesInGroup(group) \ + (NumMessagesInSubGroup(group, CatCacheMsgs) + \ + NumMessagesInSubGroup(group, RelCacheMsgs)) + + +/*---------------- + * Invalidation messages are divided into two groups: + * 1) events so far in current command, not yet reflected to caches. + * 2) events in previous commands of current transaction; these have + * been reflected to local caches, and must be either broadcast to + * other backends or rolled back from local cache when we commit + * or abort the transaction. + * Actually, we need such groups for each level of nested transaction, + * so that we can discard events from an aborted subtransaction. When + * a subtransaction commits, we append its events to the parent's groups. + * + * The relcache-file-invalidated flag can just be a simple boolean, + * since we only act on it at transaction commit; we don't care which + * command of the transaction set it. + *---------------- + */ + +typedef struct TransInvalidationInfo +{ + /* Back link to parent transaction's info */ + struct TransInvalidationInfo *parent; + + /* Subtransaction nesting depth */ + int my_level; + + /* Events emitted by current command */ + InvalidationMsgsGroup CurrentCmdInvalidMsgs; + + /* Events emitted by previous commands of this (sub)transaction */ + InvalidationMsgsGroup PriorCmdInvalidMsgs; + + /* init file must be invalidated? */ + bool RelcacheInitFileInval; +} TransInvalidationInfo; + +static __thread TransInvalidationInfo *transInvalInfo = NULL; + +/* GUC storage */ +__thread int debug_discard_caches = 0; + +/* + * Dynamically-registered callback functions. Current implementation + * assumes there won't be enough of these to justify a dynamically resizable + * array; it'd be easy to improve that if needed. + * + * To avoid searching in CallSyscacheCallbacks, all callbacks for a given + * syscache are linked into a list pointed to by syscache_callback_links[id]. + * The link values are syscache_callback_list[] index plus 1, or 0 for none. + */ + +#define MAX_SYSCACHE_CALLBACKS 64 +#define MAX_RELCACHE_CALLBACKS 10 + +typedef struct SYSCACHECALLBACK +{ + int16 id; /* cache number */ + int16 link; /* next callback index+1 for same cache */ + SyscacheCallbackFunction function; + Datum arg; +} SYSCACHECALLBACK; static __thread SYSCACHECALLBACK syscache_callback_list[MAX_SYSCACHE_CALLBACKS]; + +static __thread int16 syscache_callback_links[SysCacheSize]; + +static __thread int syscache_callback_count = 0; + +typedef struct RELCACHECALLBACK +{ + RelcacheCallbackFunction function; + Datum arg; +} RELCACHECALLBACK; static __thread RELCACHECALLBACK relcache_callback_list[MAX_RELCACHE_CALLBACKS]; + +static __thread int relcache_callback_count = 0; + +/* ---------------------------------------------------------------- + * Invalidation subgroup support functions + * ---------------------------------------------------------------- + */ + +/* + * AddInvalidationMessage + * Add an invalidation message to a (sub)group. + * + * The group must be the last active one, since we assume we can add to the + * end of the relevant InvalMessageArray. + * + * subgroup must be CatCacheMsgs or RelCacheMsgs. + */ +static void +AddInvalidationMessage(InvalidationMsgsGroup *group, int subgroup, + const SharedInvalidationMessage *msg) +{ + InvalMessageArray *ima = &InvalMessageArrays[subgroup]; + int nextindex = group->nextmsg[subgroup]; + + if (nextindex >= ima->maxmsgs) + { + if (ima->msgs == NULL) + { + /* Create new storage array in TopTransactionContext */ + int reqsize = 32; /* arbitrary */ + + ima->msgs = (SharedInvalidationMessage *) + MemoryContextAlloc(TopTransactionContext, + reqsize * sizeof(SharedInvalidationMessage)); + ima->maxmsgs = reqsize; + Assert(nextindex == 0); + } + else + { + /* Enlarge storage array */ + int reqsize = 2 * ima->maxmsgs; + + ima->msgs = (SharedInvalidationMessage *) + repalloc(ima->msgs, + reqsize * sizeof(SharedInvalidationMessage)); + ima->maxmsgs = reqsize; + } + } + /* Okay, add message to current group */ + ima->msgs[nextindex] = *msg; + group->nextmsg[subgroup]++; +} + +/* + * Append one subgroup of invalidation messages to another, resetting + * the source subgroup to empty. + */ +static void +AppendInvalidationMessageSubGroup(InvalidationMsgsGroup *dest, + InvalidationMsgsGroup *src, + int subgroup) +{ + /* Messages must be adjacent in main array */ + Assert(dest->nextmsg[subgroup] == src->firstmsg[subgroup]); + + /* ... which makes this easy: */ + dest->nextmsg[subgroup] = src->nextmsg[subgroup]; + + /* + * This is handy for some callers and irrelevant for others. But we do it + * always, reasoning that it's bad to leave different groups pointing at + * the same fragment of the message array. + */ + SetSubGroupToFollow(src, dest, subgroup); +} + +/* + * Process a subgroup of invalidation messages. + * + * This is a macro that executes the given code fragment for each message in + * a message subgroup. The fragment should refer to the message as *msg. + */ +#define ProcessMessageSubGroup(group, subgroup, codeFragment) \ + do { \ + int _msgindex = (group)->firstmsg[subgroup]; \ + int _endmsg = (group)->nextmsg[subgroup]; \ + for (; _msgindex < _endmsg; _msgindex++) \ + { \ + SharedInvalidationMessage *msg = \ + &InvalMessageArrays[subgroup].msgs[_msgindex]; \ + codeFragment; \ + } \ + } while (0) + +/* + * Process a subgroup of invalidation messages as an array. + * + * As above, but the code fragment can handle an array of messages. + * The fragment should refer to the messages as msgs[], with n entries. + */ +#define ProcessMessageSubGroupMulti(group, subgroup, codeFragment) \ + do { \ + int n = NumMessagesInSubGroup(group, subgroup); \ + if (n > 0) { \ + SharedInvalidationMessage *msgs = \ + &InvalMessageArrays[subgroup].msgs[(group)->firstmsg[subgroup]]; \ + codeFragment; \ + } \ + } while (0) + + +/* ---------------------------------------------------------------- + * Invalidation group support functions + * + * These routines understand about the division of a logical invalidation + * group into separate physical arrays for catcache and relcache entries. + * ---------------------------------------------------------------- + */ + +/* + * Add a catcache inval entry + */ +static void +AddCatcacheInvalidationMessage(InvalidationMsgsGroup *group, + int id, uint32 hashValue, Oid dbId) +{ + SharedInvalidationMessage msg; + + Assert(id < CHAR_MAX); + msg.cc.id = (int8) id; + msg.cc.dbId = dbId; + msg.cc.hashValue = hashValue; + + /* + * Define padding bytes in SharedInvalidationMessage structs to be + * defined. Otherwise the sinvaladt.c ringbuffer, which is accessed by + * multiple processes, will cause spurious valgrind warnings about + * undefined memory being used. That's because valgrind remembers the + * undefined bytes from the last local process's store, not realizing that + * another process has written since, filling the previously uninitialized + * bytes + */ + VALGRIND_MAKE_MEM_DEFINED(&msg, sizeof(msg)); + + AddInvalidationMessage(group, CatCacheMsgs, &msg); +} + +/* + * Add a whole-catalog inval entry + */ +static void +AddCatalogInvalidationMessage(InvalidationMsgsGroup *group, + Oid dbId, Oid catId) +{ + SharedInvalidationMessage msg; + + msg.cat.id = SHAREDINVALCATALOG_ID; + msg.cat.dbId = dbId; + msg.cat.catId = catId; + /* check AddCatcacheInvalidationMessage() for an explanation */ + VALGRIND_MAKE_MEM_DEFINED(&msg, sizeof(msg)); + + AddInvalidationMessage(group, CatCacheMsgs, &msg); +} + +/* + * Add a relcache inval entry + */ +static void +AddRelcacheInvalidationMessage(InvalidationMsgsGroup *group, + Oid dbId, Oid relId) +{ + SharedInvalidationMessage msg; + + /* + * Don't add a duplicate item. We assume dbId need not be checked because + * it will never change. InvalidOid for relId means all relations so we + * don't need to add individual ones when it is present. + */ + ProcessMessageSubGroup(group, RelCacheMsgs, + if (msg->rc.id == SHAREDINVALRELCACHE_ID && + (msg->rc.relId == relId || + msg->rc.relId == InvalidOid)) + return); + + /* OK, add the item */ + msg.rc.id = SHAREDINVALRELCACHE_ID; + msg.rc.dbId = dbId; + msg.rc.relId = relId; + /* check AddCatcacheInvalidationMessage() for an explanation */ + VALGRIND_MAKE_MEM_DEFINED(&msg, sizeof(msg)); + + AddInvalidationMessage(group, RelCacheMsgs, &msg); +} + +/* + * Add a snapshot inval entry + * + * We put these into the relcache subgroup for simplicity. + */ +static void +AddSnapshotInvalidationMessage(InvalidationMsgsGroup *group, + Oid dbId, Oid relId) +{ + SharedInvalidationMessage msg; + + /* Don't add a duplicate item */ + /* We assume dbId need not be checked because it will never change */ + ProcessMessageSubGroup(group, RelCacheMsgs, + if (msg->sn.id == SHAREDINVALSNAPSHOT_ID && + msg->sn.relId == relId) + return); + + /* OK, add the item */ + msg.sn.id = SHAREDINVALSNAPSHOT_ID; + msg.sn.dbId = dbId; + msg.sn.relId = relId; + /* check AddCatcacheInvalidationMessage() for an explanation */ + VALGRIND_MAKE_MEM_DEFINED(&msg, sizeof(msg)); + + AddInvalidationMessage(group, RelCacheMsgs, &msg); +} + +/* + * Append one group of invalidation messages to another, resetting + * the source group to empty. + */ +static void +AppendInvalidationMessages(InvalidationMsgsGroup *dest, + InvalidationMsgsGroup *src) +{ + AppendInvalidationMessageSubGroup(dest, src, CatCacheMsgs); + AppendInvalidationMessageSubGroup(dest, src, RelCacheMsgs); +} + +/* + * Execute the given function for all the messages in an invalidation group. + * The group is not altered. + * + * catcache entries are processed first, for reasons mentioned above. + */ +static void +ProcessInvalidationMessages(InvalidationMsgsGroup *group, + void (*func) (SharedInvalidationMessage *msg)) +{ + ProcessMessageSubGroup(group, CatCacheMsgs, func(msg)); + ProcessMessageSubGroup(group, RelCacheMsgs, func(msg)); +} + +/* + * As above, but the function is able to process an array of messages + * rather than just one at a time. + */ +static void +ProcessInvalidationMessagesMulti(InvalidationMsgsGroup *group, + void (*func) (const SharedInvalidationMessage *msgs, int n)) +{ + ProcessMessageSubGroupMulti(group, CatCacheMsgs, func(msgs, n)); + ProcessMessageSubGroupMulti(group, RelCacheMsgs, func(msgs, n)); +} + +/* ---------------------------------------------------------------- + * private support functions + * ---------------------------------------------------------------- + */ + +/* + * RegisterCatcacheInvalidation + * + * Register an invalidation event for a catcache tuple entry. + */ +static void +RegisterCatcacheInvalidation(int cacheId, + uint32 hashValue, + Oid dbId) +{ + AddCatcacheInvalidationMessage(&transInvalInfo->CurrentCmdInvalidMsgs, + cacheId, hashValue, dbId); +} + +/* + * RegisterCatalogInvalidation + * + * Register an invalidation event for all catcache entries from a catalog. + */ +static void +RegisterCatalogInvalidation(Oid dbId, Oid catId) +{ + AddCatalogInvalidationMessage(&transInvalInfo->CurrentCmdInvalidMsgs, + dbId, catId); +} + +/* + * RegisterRelcacheInvalidation + * + * As above, but register a relcache invalidation event. + */ +static void +RegisterRelcacheInvalidation(Oid dbId, Oid relId) +{ + AddRelcacheInvalidationMessage(&transInvalInfo->CurrentCmdInvalidMsgs, + dbId, relId); + + /* + * Most of the time, relcache invalidation is associated with system + * catalog updates, but there are a few cases where it isn't. Quick hack + * to ensure that the next CommandCounterIncrement() will think that we + * need to do CommandEndInvalidationMessages(). + */ + (void) GetCurrentCommandId(true); + + /* + * If the relation being invalidated is one of those cached in a relcache + * init file, mark that we need to zap that file at commit. For simplicity + * invalidations for a specific database always invalidate the shared file + * as well. Also zap when we are invalidating whole relcache. + */ + if (relId == InvalidOid || RelationIdIsInInitFile(relId)) + transInvalInfo->RelcacheInitFileInval = true; +} + +/* + * RegisterSnapshotInvalidation + * + * Register an invalidation event for MVCC scans against a given catalog. + * Only needed for catalogs that don't have catcaches. + */ +static void +RegisterSnapshotInvalidation(Oid dbId, Oid relId) +{ + AddSnapshotInvalidationMessage(&transInvalInfo->CurrentCmdInvalidMsgs, + dbId, relId); +} + +/* + * LocalExecuteInvalidationMessage + * + * Process a single invalidation message (which could be of any type). + * Only the local caches are flushed; this does not transmit the message + * to other backends. + */ +void +LocalExecuteInvalidationMessage(SharedInvalidationMessage *msg) +{ + if (msg->id >= 0) + { + if (msg->cc.dbId == MyDatabaseId || msg->cc.dbId == InvalidOid) + { + InvalidateCatalogSnapshot(); + + SysCacheInvalidate(msg->cc.id, msg->cc.hashValue); + + CallSyscacheCallbacks(msg->cc.id, msg->cc.hashValue); + } + } + else if (msg->id == SHAREDINVALCATALOG_ID) + { + if (msg->cat.dbId == MyDatabaseId || msg->cat.dbId == InvalidOid) + { + InvalidateCatalogSnapshot(); + + CatalogCacheFlushCatalog(msg->cat.catId); + + /* CatalogCacheFlushCatalog calls CallSyscacheCallbacks as needed */ + } + } + else if (msg->id == SHAREDINVALRELCACHE_ID) + { + if (msg->rc.dbId == MyDatabaseId || msg->rc.dbId == InvalidOid) + { + int i; + + if (msg->rc.relId == InvalidOid) + RelationCacheInvalidate(false); + else + RelationCacheInvalidateEntry(msg->rc.relId); + + for (i = 0; i < relcache_callback_count; i++) + { + struct RELCACHECALLBACK *ccitem = relcache_callback_list + i; + + ccitem->function(ccitem->arg, msg->rc.relId); + } + } + } + else if (msg->id == SHAREDINVALSMGR_ID) + { + /* + * We could have smgr entries for relations of other databases, so no + * short-circuit test is possible here. + */ + RelFileLocatorBackend rlocator; + + rlocator.locator = msg->sm.rlocator; + rlocator.backend = (msg->sm.backend_hi << 16) | (int) msg->sm.backend_lo; + smgrcloserellocator(rlocator); + } + else if (msg->id == SHAREDINVALRELMAP_ID) + { + /* We only care about our own database and shared catalogs */ + if (msg->rm.dbId == InvalidOid) + RelationMapInvalidate(true); + else if (msg->rm.dbId == MyDatabaseId) + RelationMapInvalidate(false); + } + else if (msg->id == SHAREDINVALSNAPSHOT_ID) + { + /* We only care about our own database and shared catalogs */ + if (msg->sn.dbId == InvalidOid) + InvalidateCatalogSnapshot(); + else if (msg->sn.dbId == MyDatabaseId) + InvalidateCatalogSnapshot(); + } + else + elog(FATAL, "unrecognized SI message ID: %d", msg->id); +} + +/* + * InvalidateSystemCaches + * + * This blows away all tuples in the system catalog caches and + * all the cached relation descriptors and smgr cache entries. + * Relation descriptors that have positive refcounts are then rebuilt. + * + * We call this when we see a shared-inval-queue overflow signal, + * since that tells us we've lost some shared-inval messages and hence + * don't know what needs to be invalidated. + */ +void +InvalidateSystemCaches(void) +{ + InvalidateSystemCachesExtended(false); +} + +void +InvalidateSystemCachesExtended(bool debug_discard) +{ + int i; + + InvalidateCatalogSnapshot(); + ResetCatalogCaches(); + RelationCacheInvalidate(debug_discard); /* gets smgr and relmap too */ + + for (i = 0; i < syscache_callback_count; i++) + { + struct SYSCACHECALLBACK *ccitem = syscache_callback_list + i; + + ccitem->function(ccitem->arg, ccitem->id, 0); + } + + for (i = 0; i < relcache_callback_count; i++) + { + struct RELCACHECALLBACK *ccitem = relcache_callback_list + i; + + ccitem->function(ccitem->arg, InvalidOid); + } +} + + +/* ---------------------------------------------------------------- + * public functions + * ---------------------------------------------------------------- + */ + +/* + * AcceptInvalidationMessages + * Read and process invalidation messages from the shared invalidation + * message queue. + * + * Note: + * This should be called as the first step in processing a transaction. + */ +void +AcceptInvalidationMessages_original(void) +{ + ReceiveSharedInvalidMessages(LocalExecuteInvalidationMessage, + InvalidateSystemCaches); + + /*---------- + * Test code to force cache flushes anytime a flush could happen. + * + * This helps detect intermittent faults caused by code that reads a cache + * entry and then performs an action that could invalidate the entry, but + * rarely actually does so. This can spot issues that would otherwise + * only arise with badly timed concurrent DDL, for example. + * + * The default debug_discard_caches = 0 does no forced cache flushes. + * + * If used with CLOBBER_FREED_MEMORY, + * debug_discard_caches = 1 (formerly known as CLOBBER_CACHE_ALWAYS) + * provides a fairly thorough test that the system contains no cache-flush + * hazards. However, it also makes the system unbelievably slow --- the + * regression tests take about 100 times longer than normal. + * + * If you're a glutton for punishment, try + * debug_discard_caches = 3 (formerly known as CLOBBER_CACHE_RECURSIVELY). + * This slows things by at least a factor of 10000, so I wouldn't suggest + * trying to run the entire regression tests that way. It's useful to try + * a few simple tests, to make sure that cache reload isn't subject to + * internal cache-flush hazards, but after you've done a few thousand + * recursive reloads it's unlikely you'll learn more. + *---------- + */ +#ifdef DISCARD_CACHES_ENABLED + { + static int recursion_depth = 0; + + if (recursion_depth < debug_discard_caches) + { + recursion_depth++; + InvalidateSystemCachesExtended(true); + recursion_depth--; + } + } +#endif +} + +/* + * PrepareInvalidationState + * Initialize inval data for the current (sub)transaction. + */ +static void +PrepareInvalidationState(void) +{ + TransInvalidationInfo *myInfo; + + if (transInvalInfo != NULL && + transInvalInfo->my_level == GetCurrentTransactionNestLevel()) + return; + + myInfo = (TransInvalidationInfo *) + MemoryContextAllocZero(TopTransactionContext, + sizeof(TransInvalidationInfo)); + myInfo->parent = transInvalInfo; + myInfo->my_level = GetCurrentTransactionNestLevel(); + + /* Now, do we have a previous stack entry? */ + if (transInvalInfo != NULL) + { + /* Yes; this one should be for a deeper nesting level. */ + Assert(myInfo->my_level > transInvalInfo->my_level); + + /* + * The parent (sub)transaction must not have any current (i.e., + * not-yet-locally-processed) messages. If it did, we'd have a + * semantic problem: the new subtransaction presumably ought not be + * able to see those events yet, but since the CommandCounter is + * linear, that can't work once the subtransaction advances the + * counter. This is a convenient place to check for that, as well as + * being important to keep management of the message arrays simple. + */ + if (NumMessagesInGroup(&transInvalInfo->CurrentCmdInvalidMsgs) != 0) + elog(ERROR, "cannot start a subtransaction when there are unprocessed inval messages"); + + /* + * MemoryContextAllocZero set firstmsg = nextmsg = 0 in each group, + * which is fine for the first (sub)transaction, but otherwise we need + * to update them to follow whatever is already in the arrays. + */ + SetGroupToFollow(&myInfo->PriorCmdInvalidMsgs, + &transInvalInfo->CurrentCmdInvalidMsgs); + SetGroupToFollow(&myInfo->CurrentCmdInvalidMsgs, + &myInfo->PriorCmdInvalidMsgs); + } + else + { + /* + * Here, we need only clear any array pointers left over from a prior + * transaction. + */ + InvalMessageArrays[CatCacheMsgs].msgs = NULL; + InvalMessageArrays[CatCacheMsgs].maxmsgs = 0; + InvalMessageArrays[RelCacheMsgs].msgs = NULL; + InvalMessageArrays[RelCacheMsgs].maxmsgs = 0; + } + + transInvalInfo = myInfo; +} + +/* + * PostPrepare_Inval + * Clean up after successful PREPARE. + * + * Here, we want to act as though the transaction aborted, so that we will + * undo any syscache changes it made, thereby bringing us into sync with the + * outside world, which doesn't believe the transaction committed yet. + * + * If the prepared transaction is later aborted, there is nothing more to + * do; if it commits, we will receive the consequent inval messages just + * like everyone else. + */ +void +PostPrepare_Inval(void) +{ + AtEOXact_Inval(false); +} + +/* + * xactGetCommittedInvalidationMessages() is called by + * RecordTransactionCommit() to collect invalidation messages to add to the + * commit record. This applies only to commit message types, never to + * abort records. Must always run before AtEOXact_Inval(), since that + * removes the data we need to see. + * + * Remember that this runs before we have officially committed, so we + * must not do anything here to change what might occur *if* we should + * fail between here and the actual commit. + * + * see also xact_redo_commit() and xact_desc_commit() + */ +int +xactGetCommittedInvalidationMessages(SharedInvalidationMessage **msgs, + bool *RelcacheInitFileInval) +{ + SharedInvalidationMessage *msgarray; + int nummsgs; + int nmsgs; + + /* Quick exit if we haven't done anything with invalidation messages. */ + if (transInvalInfo == NULL) + { + *RelcacheInitFileInval = false; + *msgs = NULL; + return 0; + } + + /* Must be at top of stack */ + Assert(transInvalInfo->my_level == 1 && transInvalInfo->parent == NULL); + + /* + * Relcache init file invalidation requires processing both before and + * after we send the SI messages. However, we need not do anything unless + * we committed. + */ + *RelcacheInitFileInval = transInvalInfo->RelcacheInitFileInval; + + /* + * Collect all the pending messages into a single contiguous array of + * invalidation messages, to simplify what needs to happen while building + * the commit WAL message. Maintain the order that they would be + * processed in by AtEOXact_Inval(), to ensure emulated behaviour in redo + * is as similar as possible to original. We want the same bugs, if any, + * not new ones. + */ + nummsgs = NumMessagesInGroup(&transInvalInfo->PriorCmdInvalidMsgs) + + NumMessagesInGroup(&transInvalInfo->CurrentCmdInvalidMsgs); + + *msgs = msgarray = (SharedInvalidationMessage *) + MemoryContextAlloc(CurTransactionContext, + nummsgs * sizeof(SharedInvalidationMessage)); + + nmsgs = 0; + ProcessMessageSubGroupMulti(&transInvalInfo->PriorCmdInvalidMsgs, + CatCacheMsgs, + (memcpy(msgarray + nmsgs, + msgs, + n * sizeof(SharedInvalidationMessage)), + nmsgs += n)); + ProcessMessageSubGroupMulti(&transInvalInfo->CurrentCmdInvalidMsgs, + CatCacheMsgs, + (memcpy(msgarray + nmsgs, + msgs, + n * sizeof(SharedInvalidationMessage)), + nmsgs += n)); + ProcessMessageSubGroupMulti(&transInvalInfo->PriorCmdInvalidMsgs, + RelCacheMsgs, + (memcpy(msgarray + nmsgs, + msgs, + n * sizeof(SharedInvalidationMessage)), + nmsgs += n)); + ProcessMessageSubGroupMulti(&transInvalInfo->CurrentCmdInvalidMsgs, + RelCacheMsgs, + (memcpy(msgarray + nmsgs, + msgs, + n * sizeof(SharedInvalidationMessage)), + nmsgs += n)); + Assert(nmsgs == nummsgs); + + return nmsgs; +} + +/* + * ProcessCommittedInvalidationMessages is executed by xact_redo_commit() or + * standby_redo() to process invalidation messages. Currently that happens + * only at end-of-xact. + * + * Relcache init file invalidation requires processing both + * before and after we send the SI messages. See AtEOXact_Inval() + */ +void +ProcessCommittedInvalidationMessages(SharedInvalidationMessage *msgs, + int nmsgs, bool RelcacheInitFileInval, + Oid dbid, Oid tsid) +{ + if (nmsgs <= 0) + return; + + elog(trace_recovery(DEBUG4), "replaying commit with %d messages%s", nmsgs, + (RelcacheInitFileInval ? " and relcache file invalidation" : "")); + + if (RelcacheInitFileInval) + { + elog(trace_recovery(DEBUG4), "removing relcache init files for database %u", + dbid); + + /* + * RelationCacheInitFilePreInvalidate, when the invalidation message + * is for a specific database, requires DatabasePath to be set, but we + * should not use SetDatabasePath during recovery, since it is + * intended to be used only once by normal backends. Hence, a quick + * hack: set DatabasePath directly then unset after use. + */ + if (OidIsValid(dbid)) + DatabasePath = GetDatabasePath(dbid, tsid); + + RelationCacheInitFilePreInvalidate(); + + if (OidIsValid(dbid)) + { + pfree(DatabasePath); + DatabasePath = NULL; + } + } + + SendSharedInvalidMessages(msgs, nmsgs); + + if (RelcacheInitFileInval) + RelationCacheInitFilePostInvalidate(); +} + +/* + * AtEOXact_Inval + * Process queued-up invalidation messages at end of main transaction. + * + * If isCommit, we must send out the messages in our PriorCmdInvalidMsgs list + * to the shared invalidation message queue. Note that these will be read + * not only by other backends, but also by our own backend at the next + * transaction start (via AcceptInvalidationMessages). This means that + * we can skip immediate local processing of anything that's still in + * CurrentCmdInvalidMsgs, and just send that list out too. + * + * If not isCommit, we are aborting, and must locally process the messages + * in PriorCmdInvalidMsgs. No messages need be sent to other backends, + * since they'll not have seen our changed tuples anyway. We can forget + * about CurrentCmdInvalidMsgs too, since those changes haven't touched + * the caches yet. + * + * In any case, reset our state to empty. We need not physically + * free memory here, since TopTransactionContext is about to be emptied + * anyway. + * + * Note: + * This should be called as the last step in processing a transaction. + */ +void +AtEOXact_Inval(bool isCommit) +{ + /* Quick exit if no messages */ + if (transInvalInfo == NULL) + return; + + /* Must be at top of stack */ + Assert(transInvalInfo->my_level == 1 && transInvalInfo->parent == NULL); + + if (isCommit) + { + /* + * Relcache init file invalidation requires processing both before and + * after we send the SI messages. However, we need not do anything + * unless we committed. + */ + if (transInvalInfo->RelcacheInitFileInval) + RelationCacheInitFilePreInvalidate(); + + AppendInvalidationMessages(&transInvalInfo->PriorCmdInvalidMsgs, + &transInvalInfo->CurrentCmdInvalidMsgs); + + ProcessInvalidationMessagesMulti(&transInvalInfo->PriorCmdInvalidMsgs, + SendSharedInvalidMessages); + + if (transInvalInfo->RelcacheInitFileInval) + RelationCacheInitFilePostInvalidate(); + } + else + { + ProcessInvalidationMessages(&transInvalInfo->PriorCmdInvalidMsgs, + LocalExecuteInvalidationMessage); + } + + /* Need not free anything explicitly */ + transInvalInfo = NULL; +} + +/* + * AtEOSubXact_Inval + * Process queued-up invalidation messages at end of subtransaction. + * + * If isCommit, process CurrentCmdInvalidMsgs if any (there probably aren't), + * and then attach both CurrentCmdInvalidMsgs and PriorCmdInvalidMsgs to the + * parent's PriorCmdInvalidMsgs list. + * + * If not isCommit, we are aborting, and must locally process the messages + * in PriorCmdInvalidMsgs. No messages need be sent to other backends. + * We can forget about CurrentCmdInvalidMsgs too, since those changes haven't + * touched the caches yet. + * + * In any case, pop the transaction stack. We need not physically free memory + * here, since CurTransactionContext is about to be emptied anyway + * (if aborting). Beware of the possibility of aborting the same nesting + * level twice, though. + */ +void +AtEOSubXact_Inval(bool isCommit) +{ + int my_level; + TransInvalidationInfo *myInfo = transInvalInfo; + + /* Quick exit if no messages. */ + if (myInfo == NULL) + return; + + /* Also bail out quickly if messages are not for this level. */ + my_level = GetCurrentTransactionNestLevel(); + if (myInfo->my_level != my_level) + { + Assert(myInfo->my_level < my_level); + return; + } + + if (isCommit) + { + /* If CurrentCmdInvalidMsgs still has anything, fix it */ + CommandEndInvalidationMessages(); + + /* + * We create invalidation stack entries lazily, so the parent might + * not have one. Instead of creating one, moving all the data over, + * and then freeing our own, we can just adjust the level of our own + * entry. + */ + if (myInfo->parent == NULL || myInfo->parent->my_level < my_level - 1) + { + myInfo->my_level--; + return; + } + + /* + * Pass up my inval messages to parent. Notice that we stick them in + * PriorCmdInvalidMsgs, not CurrentCmdInvalidMsgs, since they've + * already been locally processed. (This would trigger the Assert in + * AppendInvalidationMessageSubGroup if the parent's + * CurrentCmdInvalidMsgs isn't empty; but we already checked that in + * PrepareInvalidationState.) + */ + AppendInvalidationMessages(&myInfo->parent->PriorCmdInvalidMsgs, + &myInfo->PriorCmdInvalidMsgs); + + /* Must readjust parent's CurrentCmdInvalidMsgs indexes now */ + SetGroupToFollow(&myInfo->parent->CurrentCmdInvalidMsgs, + &myInfo->parent->PriorCmdInvalidMsgs); + + /* Pending relcache inval becomes parent's problem too */ + if (myInfo->RelcacheInitFileInval) + myInfo->parent->RelcacheInitFileInval = true; + + /* Pop the transaction state stack */ + transInvalInfo = myInfo->parent; + + /* Need not free anything else explicitly */ + pfree(myInfo); + } + else + { + ProcessInvalidationMessages(&myInfo->PriorCmdInvalidMsgs, + LocalExecuteInvalidationMessage); + + /* Pop the transaction state stack */ + transInvalInfo = myInfo->parent; + + /* Need not free anything else explicitly */ + pfree(myInfo); + } +} + +/* + * CommandEndInvalidationMessages + * Process queued-up invalidation messages at end of one command + * in a transaction. + * + * Here, we send no messages to the shared queue, since we don't know yet if + * we will commit. We do need to locally process the CurrentCmdInvalidMsgs + * list, so as to flush our caches of any entries we have outdated in the + * current command. We then move the current-cmd list over to become part + * of the prior-cmds list. + * + * Note: + * This should be called during CommandCounterIncrement(), + * after we have advanced the command ID. + */ +void +CommandEndInvalidationMessages(void) +{ + /* + * You might think this shouldn't be called outside any transaction, but + * bootstrap does it, and also ABORT issued when not in a transaction. So + * just quietly return if no state to work on. + */ + if (transInvalInfo == NULL) + return; + + ProcessInvalidationMessages(&transInvalInfo->CurrentCmdInvalidMsgs, + LocalExecuteInvalidationMessage); + + /* WAL Log per-command invalidation messages for wal_level=logical */ + if (XLogLogicalInfoActive()) + LogLogicalInvalidations(); + + AppendInvalidationMessages(&transInvalInfo->PriorCmdInvalidMsgs, + &transInvalInfo->CurrentCmdInvalidMsgs); +} + + +/* + * CacheInvalidateHeapTuple + * Register the given tuple for invalidation at end of command + * (ie, current command is creating or outdating this tuple). + * Also, detect whether a relcache invalidation is implied. + * + * For an insert or delete, tuple is the target tuple and newtuple is NULL. + * For an update, we are called just once, with tuple being the old tuple + * version and newtuple the new version. This allows avoidance of duplicate + * effort during an update. + */ +void +CacheInvalidateHeapTuple(Relation relation, + HeapTuple tuple, + HeapTuple newtuple) +{ + Oid tupleRelId; + Oid databaseId; + Oid relationId; + + /* Do nothing during bootstrap */ + if (IsBootstrapProcessingMode()) + return; + + /* + * We only need to worry about invalidation for tuples that are in system + * catalogs; user-relation tuples are never in catcaches and can't affect + * the relcache either. + */ + if (!IsCatalogRelation(relation)) + return; + + /* + * IsCatalogRelation() will return true for TOAST tables of system + * catalogs, but we don't care about those, either. + */ + if (IsToastRelation(relation)) + return; + + /* + * If we're not prepared to queue invalidation messages for this + * subtransaction level, get ready now. + */ + PrepareInvalidationState(); + + /* + * First let the catcache do its thing + */ + tupleRelId = RelationGetRelid(relation); + if (RelationInvalidatesSnapshotsOnly(tupleRelId)) + { + databaseId = IsSharedRelation(tupleRelId) ? InvalidOid : MyDatabaseId; + RegisterSnapshotInvalidation(databaseId, tupleRelId); + } + else + PrepareToInvalidateCacheTuple(relation, tuple, newtuple, + RegisterCatcacheInvalidation); + + /* + * Now, is this tuple one of the primary definers of a relcache entry? See + * comments in file header for deeper explanation. + * + * Note we ignore newtuple here; we assume an update cannot move a tuple + * from being part of one relcache entry to being part of another. + */ + if (tupleRelId == RelationRelationId) + { + Form_pg_class classtup = (Form_pg_class) GETSTRUCT(tuple); + + relationId = classtup->oid; + if (classtup->relisshared) + databaseId = InvalidOid; + else + databaseId = MyDatabaseId; + } + else if (tupleRelId == AttributeRelationId) + { + Form_pg_attribute atttup = (Form_pg_attribute) GETSTRUCT(tuple); + + relationId = atttup->attrelid; + + /* + * KLUGE ALERT: we always send the relcache event with MyDatabaseId, + * even if the rel in question is shared (which we can't easily tell). + * This essentially means that only backends in this same database + * will react to the relcache flush request. This is in fact + * appropriate, since only those backends could see our pg_attribute + * change anyway. It looks a bit ugly though. (In practice, shared + * relations can't have schema changes after bootstrap, so we should + * never come here for a shared rel anyway.) + */ + databaseId = MyDatabaseId; + } + else if (tupleRelId == IndexRelationId) + { + Form_pg_index indextup = (Form_pg_index) GETSTRUCT(tuple); + + /* + * When a pg_index row is updated, we should send out a relcache inval + * for the index relation. As above, we don't know the shared status + * of the index, but in practice it doesn't matter since indexes of + * shared catalogs can't have such updates. + */ + relationId = indextup->indexrelid; + databaseId = MyDatabaseId; + } + else if (tupleRelId == ConstraintRelationId) + { + Form_pg_constraint constrtup = (Form_pg_constraint) GETSTRUCT(tuple); + + /* + * Foreign keys are part of relcache entries, too, so send out an + * inval for the table that the FK applies to. + */ + if (constrtup->contype == CONSTRAINT_FOREIGN && + OidIsValid(constrtup->conrelid)) + { + relationId = constrtup->conrelid; + databaseId = MyDatabaseId; + } + else + return; + } + else + return; + + /* + * Yes. We need to register a relcache invalidation event. + */ + RegisterRelcacheInvalidation(databaseId, relationId); +} + +/* + * CacheInvalidateCatalog + * Register invalidation of the whole content of a system catalog. + * + * This is normally used in VACUUM FULL/CLUSTER, where we haven't so much + * changed any tuples as moved them around. Some uses of catcache entries + * expect their TIDs to be correct, so we have to blow away the entries. + * + * Note: we expect caller to verify that the rel actually is a system + * catalog. If it isn't, no great harm is done, just a wasted sinval message. + */ +void +CacheInvalidateCatalog(Oid catalogId) +{ + Oid databaseId; + + PrepareInvalidationState(); + + if (IsSharedRelation(catalogId)) + databaseId = InvalidOid; + else + databaseId = MyDatabaseId; + + RegisterCatalogInvalidation(databaseId, catalogId); +} + +/* + * CacheInvalidateRelcache + * Register invalidation of the specified relation's relcache entry + * at end of command. + * + * This is used in places that need to force relcache rebuild but aren't + * changing any of the tuples recognized as contributors to the relcache + * entry by CacheInvalidateHeapTuple. (An example is dropping an index.) + */ +void +CacheInvalidateRelcache(Relation relation) +{ + Oid databaseId; + Oid relationId; + + PrepareInvalidationState(); + + relationId = RelationGetRelid(relation); + if (relation->rd_rel->relisshared) + databaseId = InvalidOid; + else + databaseId = MyDatabaseId; + + RegisterRelcacheInvalidation(databaseId, relationId); +} + +/* + * CacheInvalidateRelcacheAll + * Register invalidation of the whole relcache at the end of command. + * + * This is used by alter publication as changes in publications may affect + * large number of tables. + */ +void +CacheInvalidateRelcacheAll(void) +{ + PrepareInvalidationState(); + + RegisterRelcacheInvalidation(InvalidOid, InvalidOid); +} + +/* + * CacheInvalidateRelcacheByTuple + * As above, but relation is identified by passing its pg_class tuple. + */ +void +CacheInvalidateRelcacheByTuple(HeapTuple classTuple) +{ + Form_pg_class classtup = (Form_pg_class) GETSTRUCT(classTuple); + Oid databaseId; + Oid relationId; + + PrepareInvalidationState(); + + relationId = classtup->oid; + if (classtup->relisshared) + databaseId = InvalidOid; + else + databaseId = MyDatabaseId; + RegisterRelcacheInvalidation(databaseId, relationId); +} + +/* + * CacheInvalidateRelcacheByRelid + * As above, but relation is identified by passing its OID. + * This is the least efficient of the three options; use one of + * the above routines if you have a Relation or pg_class tuple. + */ +void +CacheInvalidateRelcacheByRelid(Oid relid) +{ + HeapTuple tup; + + PrepareInvalidationState(); + + tup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for relation %u", relid); + CacheInvalidateRelcacheByTuple(tup); + ReleaseSysCache(tup); +} + + +/* + * CacheInvalidateSmgr + * Register invalidation of smgr references to a physical relation. + * + * Sending this type of invalidation msg forces other backends to close open + * smgr entries for the rel. This should be done to flush dangling open-file + * references when the physical rel is being dropped or truncated. Because + * these are nontransactional (i.e., not-rollback-able) operations, we just + * send the inval message immediately without any queuing. + * + * Note: in most cases there will have been a relcache flush issued against + * the rel at the logical level. We need a separate smgr-level flush because + * it is possible for backends to have open smgr entries for rels they don't + * have a relcache entry for, e.g. because the only thing they ever did with + * the rel is write out dirty shared buffers. + * + * Note: because these messages are nontransactional, they won't be captured + * in commit/abort WAL entries. Instead, calls to CacheInvalidateSmgr() + * should happen in low-level smgr.c routines, which are executed while + * replaying WAL as well as when creating it. + * + * Note: In order to avoid bloating SharedInvalidationMessage, we store only + * three bytes of the backend ID using what would otherwise be padding space. + * Thus, the maximum possible backend ID is 2^23-1. + */ +void +CacheInvalidateSmgr(RelFileLocatorBackend rlocator) +{ + SharedInvalidationMessage msg; + + msg.sm.id = SHAREDINVALSMGR_ID; + msg.sm.backend_hi = rlocator.backend >> 16; + msg.sm.backend_lo = rlocator.backend & 0xffff; + msg.sm.rlocator = rlocator.locator; + /* check AddCatcacheInvalidationMessage() for an explanation */ + VALGRIND_MAKE_MEM_DEFINED(&msg, sizeof(msg)); + + SendSharedInvalidMessages(&msg, 1); +} + +/* + * CacheInvalidateRelmap + * Register invalidation of the relation mapping for a database, + * or for the shared catalogs if databaseId is zero. + * + * Sending this type of invalidation msg forces other backends to re-read + * the indicated relation mapping file. It is also necessary to send a + * relcache inval for the specific relations whose mapping has been altered, + * else the relcache won't get updated with the new filenode data. + * + * Note: because these messages are nontransactional, they won't be captured + * in commit/abort WAL entries. Instead, calls to CacheInvalidateRelmap() + * should happen in low-level relmapper.c routines, which are executed while + * replaying WAL as well as when creating it. + */ +void +CacheInvalidateRelmap(Oid databaseId) +{ + SharedInvalidationMessage msg; + + msg.rm.id = SHAREDINVALRELMAP_ID; + msg.rm.dbId = databaseId; + /* check AddCatcacheInvalidationMessage() for an explanation */ + VALGRIND_MAKE_MEM_DEFINED(&msg, sizeof(msg)); + + SendSharedInvalidMessages(&msg, 1); +} + + +/* + * CacheRegisterSyscacheCallback + * Register the specified function to be called for all future + * invalidation events in the specified cache. The cache ID and the + * hash value of the tuple being invalidated will be passed to the + * function. + * + * NOTE: Hash value zero will be passed if a cache reset request is received. + * In this case the called routines should flush all cached state. + * Yes, there's a possibility of a false match to zero, but it doesn't seem + * worth troubling over, especially since most of the current callees just + * flush all cached state anyway. + */ +void +CacheRegisterSyscacheCallback(int cacheid, + SyscacheCallbackFunction func, + Datum arg) +{ + if (cacheid < 0 || cacheid >= SysCacheSize) + elog(FATAL, "invalid cache ID: %d", cacheid); + if (syscache_callback_count >= MAX_SYSCACHE_CALLBACKS) + elog(FATAL, "out of syscache_callback_list slots"); + + if (syscache_callback_links[cacheid] == 0) + { + /* first callback for this cache */ + syscache_callback_links[cacheid] = syscache_callback_count + 1; + } + else + { + /* add to end of chain, so that older callbacks are called first */ + int i = syscache_callback_links[cacheid] - 1; + + while (syscache_callback_list[i].link > 0) + i = syscache_callback_list[i].link - 1; + syscache_callback_list[i].link = syscache_callback_count + 1; + } + + syscache_callback_list[syscache_callback_count].id = cacheid; + syscache_callback_list[syscache_callback_count].link = 0; + syscache_callback_list[syscache_callback_count].function = func; + syscache_callback_list[syscache_callback_count].arg = arg; + + ++syscache_callback_count; +} + +/* + * CacheRegisterRelcacheCallback + * Register the specified function to be called for all future + * relcache invalidation events. The OID of the relation being + * invalidated will be passed to the function. + * + * NOTE: InvalidOid will be passed if a cache reset request is received. + * In this case the called routines should flush all cached state. + */ +void +CacheRegisterRelcacheCallback(RelcacheCallbackFunction func, + Datum arg) +{ + if (relcache_callback_count >= MAX_RELCACHE_CALLBACKS) + elog(FATAL, "out of relcache_callback_list slots"); + + relcache_callback_list[relcache_callback_count].function = func; + relcache_callback_list[relcache_callback_count].arg = arg; + + ++relcache_callback_count; +} + +/* + * CallSyscacheCallbacks + * + * This is exported so that CatalogCacheFlushCatalog can call it, saving + * this module from knowing which catcache IDs correspond to which catalogs. + */ +void +CallSyscacheCallbacks(int cacheid, uint32 hashvalue) +{ + int i; + + if (cacheid < 0 || cacheid >= SysCacheSize) + elog(ERROR, "invalid cache ID: %d", cacheid); + + i = syscache_callback_links[cacheid] - 1; + while (i >= 0) + { + struct SYSCACHECALLBACK *ccitem = syscache_callback_list + i; + + Assert(ccitem->id == cacheid); + ccitem->function(ccitem->arg, cacheid, hashvalue); + i = ccitem->link - 1; + } +} + +/* + * LogLogicalInvalidations + * + * Emit WAL for invalidations caused by the current command. + * + * This is currently only used for logging invalidations at the command end + * or at commit time if any invalidations are pending. + */ +void +LogLogicalInvalidations(void) +{ + xl_xact_invals xlrec; + InvalidationMsgsGroup *group; + int nmsgs; + + /* Quick exit if we haven't done anything with invalidation messages. */ + if (transInvalInfo == NULL) + return; + + group = &transInvalInfo->CurrentCmdInvalidMsgs; + nmsgs = NumMessagesInGroup(group); + + if (nmsgs > 0) + { + /* prepare record */ + memset(&xlrec, 0, MinSizeOfXactInvals); + xlrec.nmsgs = nmsgs; + + /* perform insertion */ + XLogBeginInsert(); + XLogRegisterData((char *) (&xlrec), MinSizeOfXactInvals); + ProcessMessageSubGroupMulti(group, CatCacheMsgs, + XLogRegisterData((char *) msgs, + n * sizeof(SharedInvalidationMessage))); + ProcessMessageSubGroupMulti(group, RelCacheMsgs, + XLogRegisterData((char *) msgs, + n * sizeof(SharedInvalidationMessage))); + XLogInsert(RM_XACT_ID, XLOG_XACT_INVALIDATIONS); + } +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/cache/lsyscache.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/cache/lsyscache.c new file mode 100644 index 00000000000..aee07f817c5 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/cache/lsyscache.c @@ -0,0 +1,3676 @@ +/*------------------------------------------------------------------------- + * + * lsyscache.c + * Convenience routines for common queries in the system catalog cache. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/utils/cache/lsyscache.c + * + * NOTES + * Eventually, the index information should go through here, too. + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/hash.h" +#include "access/htup_details.h" +#include "access/nbtree.h" +#include "bootstrap/bootstrap.h" +#include "catalog/namespace.h" +#include "catalog/pg_am.h" +#include "catalog/pg_amop.h" +#include "catalog/pg_amproc.h" +#include "catalog/pg_cast.h" +#include "catalog/pg_collation.h" +#include "catalog/pg_constraint.h" +#include "catalog/pg_language.h" +#include "catalog/pg_namespace.h" +#include "catalog/pg_opclass.h" +#include "catalog/pg_operator.h" +#include "catalog/pg_proc.h" +#include "catalog/pg_range.h" +#include "catalog/pg_statistic.h" +#include "catalog/pg_subscription.h" +#include "catalog/pg_transform.h" +#include "catalog/pg_type.h" +#include "miscadmin.h" +#include "nodes/makefuncs.h" +#include "utils/array.h" +#include "utils/builtins.h" +#include "utils/catcache.h" +#include "utils/datum.h" +#include "utils/fmgroids.h" +#include "utils/lsyscache.h" +#include "utils/rel.h" +#include "utils/syscache.h" +#include "utils/typcache.h" + +/* Hook for plugins to get control in get_attavgwidth() */ +__thread get_attavgwidth_hook_type get_attavgwidth_hook = NULL; + + +/* ---------- AMOP CACHES ---------- */ + +/* + * op_in_opfamily + * + * Return t iff operator 'opno' is in operator family 'opfamily'. + * + * This function only considers search operators, not ordering operators. + */ +bool +op_in_opfamily(Oid opno, Oid opfamily) +{ + return SearchSysCacheExists3(AMOPOPID, + ObjectIdGetDatum(opno), + CharGetDatum(AMOP_SEARCH), + ObjectIdGetDatum(opfamily)); +} + +/* + * get_op_opfamily_strategy + * + * Get the operator's strategy number within the specified opfamily, + * or 0 if it's not a member of the opfamily. + * + * This function only considers search operators, not ordering operators. + */ +int +get_op_opfamily_strategy(Oid opno, Oid opfamily) +{ + HeapTuple tp; + Form_pg_amop amop_tup; + int result; + + tp = SearchSysCache3(AMOPOPID, + ObjectIdGetDatum(opno), + CharGetDatum(AMOP_SEARCH), + ObjectIdGetDatum(opfamily)); + if (!HeapTupleIsValid(tp)) + return 0; + amop_tup = (Form_pg_amop) GETSTRUCT(tp); + result = amop_tup->amopstrategy; + ReleaseSysCache(tp); + return result; +} + +/* + * get_op_opfamily_sortfamily + * + * If the operator is an ordering operator within the specified opfamily, + * return its amopsortfamily OID; else return InvalidOid. + */ +Oid +get_op_opfamily_sortfamily(Oid opno, Oid opfamily) +{ + HeapTuple tp; + Form_pg_amop amop_tup; + Oid result; + + tp = SearchSysCache3(AMOPOPID, + ObjectIdGetDatum(opno), + CharGetDatum(AMOP_ORDER), + ObjectIdGetDatum(opfamily)); + if (!HeapTupleIsValid(tp)) + return InvalidOid; + amop_tup = (Form_pg_amop) GETSTRUCT(tp); + result = amop_tup->amopsortfamily; + ReleaseSysCache(tp); + return result; +} + +/* + * get_op_opfamily_properties + * + * Get the operator's strategy number and declared input data types + * within the specified opfamily. + * + * Caller should already have verified that opno is a member of opfamily, + * therefore we raise an error if the tuple is not found. + */ +void +get_op_opfamily_properties(Oid opno, Oid opfamily, bool ordering_op, + int *strategy, + Oid *lefttype, + Oid *righttype) +{ + HeapTuple tp; + Form_pg_amop amop_tup; + + tp = SearchSysCache3(AMOPOPID, + ObjectIdGetDatum(opno), + CharGetDatum(ordering_op ? AMOP_ORDER : AMOP_SEARCH), + ObjectIdGetDatum(opfamily)); + if (!HeapTupleIsValid(tp)) + elog(ERROR, "operator %u is not a member of opfamily %u", + opno, opfamily); + amop_tup = (Form_pg_amop) GETSTRUCT(tp); + *strategy = amop_tup->amopstrategy; + *lefttype = amop_tup->amoplefttype; + *righttype = amop_tup->amoprighttype; + ReleaseSysCache(tp); +} + +/* + * get_opfamily_member + * Get the OID of the operator that implements the specified strategy + * with the specified datatypes for the specified opfamily. + * + * Returns InvalidOid if there is no pg_amop entry for the given keys. + */ +Oid +get_opfamily_member_original(Oid opfamily, Oid lefttype, Oid righttype, + int16 strategy) +{ + HeapTuple tp; + Form_pg_amop amop_tup; + Oid result; + + tp = SearchSysCache4(AMOPSTRATEGY, + ObjectIdGetDatum(opfamily), + ObjectIdGetDatum(lefttype), + ObjectIdGetDatum(righttype), + Int16GetDatum(strategy)); + if (!HeapTupleIsValid(tp)) + return InvalidOid; + amop_tup = (Form_pg_amop) GETSTRUCT(tp); + result = amop_tup->amopopr; + ReleaseSysCache(tp); + return result; +} + +/* + * get_ordering_op_properties + * Given the OID of an ordering operator (a btree "<" or ">" operator), + * determine its opfamily, its declared input datatype, and its + * strategy number (BTLessStrategyNumber or BTGreaterStrategyNumber). + * + * Returns true if successful, false if no matching pg_amop entry exists. + * (This indicates that the operator is not a valid ordering operator.) + * + * Note: the operator could be registered in multiple families, for example + * if someone were to build a "reverse sort" opfamily. This would result in + * uncertainty as to whether "ORDER BY USING op" would default to NULLS FIRST + * or NULLS LAST, as well as inefficient planning due to failure to match up + * pathkeys that should be the same. So we want a determinate result here. + * Because of the way the syscache search works, we'll use the interpretation + * associated with the opfamily with smallest OID, which is probably + * determinate enough. Since there is no longer any particularly good reason + * to build reverse-sort opfamilies, it doesn't seem worth expending any + * additional effort on ensuring consistency. + */ +bool +get_ordering_op_properties(Oid opno, + Oid *opfamily, Oid *opcintype, int16 *strategy) +{ + bool result = false; + CatCList *catlist; + int i; + + /* ensure outputs are initialized on failure */ + *opfamily = InvalidOid; + *opcintype = InvalidOid; + *strategy = 0; + + /* + * Search pg_amop to see if the target operator is registered as the "<" + * or ">" operator of any btree opfamily. + */ + catlist = SearchSysCacheList1(AMOPOPID, ObjectIdGetDatum(opno)); + + for (i = 0; i < catlist->n_members; i++) + { + HeapTuple tuple = &catlist->members[i]->tuple; + Form_pg_amop aform = (Form_pg_amop) GETSTRUCT(tuple); + + /* must be btree */ + if (aform->amopmethod != BTREE_AM_OID) + continue; + + if (aform->amopstrategy == BTLessStrategyNumber || + aform->amopstrategy == BTGreaterStrategyNumber) + { + /* Found it ... should have consistent input types */ + if (aform->amoplefttype == aform->amoprighttype) + { + /* Found a suitable opfamily, return info */ + *opfamily = aform->amopfamily; + *opcintype = aform->amoplefttype; + *strategy = aform->amopstrategy; + result = true; + break; + } + } + } + + ReleaseSysCacheList(catlist); + + return result; +} + +/* + * get_equality_op_for_ordering_op + * Get the OID of the datatype-specific btree equality operator + * associated with an ordering operator (a "<" or ">" operator). + * + * If "reverse" isn't NULL, also set *reverse to false if the operator is "<", + * true if it's ">" + * + * Returns InvalidOid if no matching equality operator can be found. + * (This indicates that the operator is not a valid ordering operator.) + */ +Oid +get_equality_op_for_ordering_op(Oid opno, bool *reverse) +{ + Oid result = InvalidOid; + Oid opfamily; + Oid opcintype; + int16 strategy; + + /* Find the operator in pg_amop */ + if (get_ordering_op_properties(opno, + &opfamily, &opcintype, &strategy)) + { + /* Found a suitable opfamily, get matching equality operator */ + result = get_opfamily_member(opfamily, + opcintype, + opcintype, + BTEqualStrategyNumber); + if (reverse) + *reverse = (strategy == BTGreaterStrategyNumber); + } + + return result; +} + +/* + * get_ordering_op_for_equality_op + * Get the OID of a datatype-specific btree ordering operator + * associated with an equality operator. (If there are multiple + * possibilities, assume any one will do.) + * + * This function is used when we have to sort data before unique-ifying, + * and don't much care which sorting op is used as long as it's compatible + * with the intended equality operator. Since we need a sorting operator, + * it should be single-data-type even if the given operator is cross-type. + * The caller specifies whether to find an op for the LHS or RHS data type. + * + * Returns InvalidOid if no matching ordering operator can be found. + */ +Oid +get_ordering_op_for_equality_op(Oid opno, bool use_lhs_type) +{ + Oid result = InvalidOid; + CatCList *catlist; + int i; + + /* + * Search pg_amop to see if the target operator is registered as the "=" + * operator of any btree opfamily. + */ + catlist = SearchSysCacheList1(AMOPOPID, ObjectIdGetDatum(opno)); + + for (i = 0; i < catlist->n_members; i++) + { + HeapTuple tuple = &catlist->members[i]->tuple; + Form_pg_amop aform = (Form_pg_amop) GETSTRUCT(tuple); + + /* must be btree */ + if (aform->amopmethod != BTREE_AM_OID) + continue; + + if (aform->amopstrategy == BTEqualStrategyNumber) + { + /* Found a suitable opfamily, get matching ordering operator */ + Oid typid; + + typid = use_lhs_type ? aform->amoplefttype : aform->amoprighttype; + result = get_opfamily_member(aform->amopfamily, + typid, typid, + BTLessStrategyNumber); + if (OidIsValid(result)) + break; + /* failure probably shouldn't happen, but keep looking if so */ + } + } + + ReleaseSysCacheList(catlist); + + return result; +} + +/* + * get_mergejoin_opfamilies + * Given a putatively mergejoinable operator, return a list of the OIDs + * of the btree opfamilies in which it represents equality. + * + * It is possible (though at present unusual) for an operator to be equality + * in more than one opfamily, hence the result is a list. This also lets us + * return NIL if the operator is not found in any opfamilies. + * + * The planner currently uses simple equal() tests to compare the lists + * returned by this function, which makes the list order relevant, though + * strictly speaking it should not be. Because of the way syscache list + * searches are handled, in normal operation the result will be sorted by OID + * so everything works fine. If running with system index usage disabled, + * the result ordering is unspecified and hence the planner might fail to + * recognize optimization opportunities ... but that's hardly a scenario in + * which performance is good anyway, so there's no point in expending code + * or cycles here to guarantee the ordering in that case. + */ +List * +get_mergejoin_opfamilies(Oid opno) +{ + List *result = NIL; + CatCList *catlist; + int i; + + /* + * Search pg_amop to see if the target operator is registered as the "=" + * operator of any btree opfamily. + */ + catlist = SearchSysCacheList1(AMOPOPID, ObjectIdGetDatum(opno)); + + for (i = 0; i < catlist->n_members; i++) + { + HeapTuple tuple = &catlist->members[i]->tuple; + Form_pg_amop aform = (Form_pg_amop) GETSTRUCT(tuple); + + /* must be btree equality */ + if (aform->amopmethod == BTREE_AM_OID && + aform->amopstrategy == BTEqualStrategyNumber) + result = lappend_oid(result, aform->amopfamily); + } + + ReleaseSysCacheList(catlist); + + return result; +} + +/* + * get_compatible_hash_operators + * Get the OID(s) of hash equality operator(s) compatible with the given + * operator, but operating on its LHS and/or RHS datatype. + * + * An operator for the LHS type is sought and returned into *lhs_opno if + * lhs_opno isn't NULL. Similarly, an operator for the RHS type is sought + * and returned into *rhs_opno if rhs_opno isn't NULL. + * + * If the given operator is not cross-type, the results should be the same + * operator, but in cross-type situations they will be different. + * + * Returns true if able to find the requested operator(s), false if not. + * (This indicates that the operator should not have been marked oprcanhash.) + */ +bool +get_compatible_hash_operators(Oid opno, + Oid *lhs_opno, Oid *rhs_opno) +{ + bool result = false; + CatCList *catlist; + int i; + + /* Ensure output args are initialized on failure */ + if (lhs_opno) + *lhs_opno = InvalidOid; + if (rhs_opno) + *rhs_opno = InvalidOid; + + /* + * Search pg_amop to see if the target operator is registered as the "=" + * operator of any hash opfamily. If the operator is registered in + * multiple opfamilies, assume we can use any one. + */ + catlist = SearchSysCacheList1(AMOPOPID, ObjectIdGetDatum(opno)); + + for (i = 0; i < catlist->n_members; i++) + { + HeapTuple tuple = &catlist->members[i]->tuple; + Form_pg_amop aform = (Form_pg_amop) GETSTRUCT(tuple); + + if (aform->amopmethod == HASH_AM_OID && + aform->amopstrategy == HTEqualStrategyNumber) + { + /* No extra lookup needed if given operator is single-type */ + if (aform->amoplefttype == aform->amoprighttype) + { + if (lhs_opno) + *lhs_opno = opno; + if (rhs_opno) + *rhs_opno = opno; + result = true; + break; + } + + /* + * Get the matching single-type operator(s). Failure probably + * shouldn't happen --- it implies a bogus opfamily --- but + * continue looking if so. + */ + if (lhs_opno) + { + *lhs_opno = get_opfamily_member(aform->amopfamily, + aform->amoplefttype, + aform->amoplefttype, + HTEqualStrategyNumber); + if (!OidIsValid(*lhs_opno)) + continue; + /* Matching LHS found, done if caller doesn't want RHS */ + if (!rhs_opno) + { + result = true; + break; + } + } + if (rhs_opno) + { + *rhs_opno = get_opfamily_member(aform->amopfamily, + aform->amoprighttype, + aform->amoprighttype, + HTEqualStrategyNumber); + if (!OidIsValid(*rhs_opno)) + { + /* Forget any LHS operator from this opfamily */ + if (lhs_opno) + *lhs_opno = InvalidOid; + continue; + } + /* Matching RHS found, so done */ + result = true; + break; + } + } + } + + ReleaseSysCacheList(catlist); + + return result; +} + +/* + * get_op_hash_functions + * Get the OID(s) of the standard hash support function(s) compatible with + * the given operator, operating on its LHS and/or RHS datatype as required. + * + * A function for the LHS type is sought and returned into *lhs_procno if + * lhs_procno isn't NULL. Similarly, a function for the RHS type is sought + * and returned into *rhs_procno if rhs_procno isn't NULL. + * + * If the given operator is not cross-type, the results should be the same + * function, but in cross-type situations they will be different. + * + * Returns true if able to find the requested function(s), false if not. + * (This indicates that the operator should not have been marked oprcanhash.) + */ +bool +get_op_hash_functions(Oid opno, + RegProcedure *lhs_procno, RegProcedure *rhs_procno) +{ + bool result = false; + CatCList *catlist; + int i; + + /* Ensure output args are initialized on failure */ + if (lhs_procno) + *lhs_procno = InvalidOid; + if (rhs_procno) + *rhs_procno = InvalidOid; + + /* + * Search pg_amop to see if the target operator is registered as the "=" + * operator of any hash opfamily. If the operator is registered in + * multiple opfamilies, assume we can use any one. + */ + catlist = SearchSysCacheList1(AMOPOPID, ObjectIdGetDatum(opno)); + + for (i = 0; i < catlist->n_members; i++) + { + HeapTuple tuple = &catlist->members[i]->tuple; + Form_pg_amop aform = (Form_pg_amop) GETSTRUCT(tuple); + + if (aform->amopmethod == HASH_AM_OID && + aform->amopstrategy == HTEqualStrategyNumber) + { + /* + * Get the matching support function(s). Failure probably + * shouldn't happen --- it implies a bogus opfamily --- but + * continue looking if so. + */ + if (lhs_procno) + { + *lhs_procno = get_opfamily_proc(aform->amopfamily, + aform->amoplefttype, + aform->amoplefttype, + HASHSTANDARD_PROC); + if (!OidIsValid(*lhs_procno)) + continue; + /* Matching LHS found, done if caller doesn't want RHS */ + if (!rhs_procno) + { + result = true; + break; + } + /* Only one lookup needed if given operator is single-type */ + if (aform->amoplefttype == aform->amoprighttype) + { + *rhs_procno = *lhs_procno; + result = true; + break; + } + } + if (rhs_procno) + { + *rhs_procno = get_opfamily_proc(aform->amopfamily, + aform->amoprighttype, + aform->amoprighttype, + HASHSTANDARD_PROC); + if (!OidIsValid(*rhs_procno)) + { + /* Forget any LHS function from this opfamily */ + if (lhs_procno) + *lhs_procno = InvalidOid; + continue; + } + /* Matching RHS found, so done */ + result = true; + break; + } + } + } + + ReleaseSysCacheList(catlist); + + return result; +} + +/* + * get_op_btree_interpretation + * Given an operator's OID, find out which btree opfamilies it belongs to, + * and what properties it has within each one. The results are returned + * as a palloc'd list of OpBtreeInterpretation structs. + * + * In addition to the normal btree operators, we consider a <> operator to be + * a "member" of an opfamily if its negator is an equality operator of the + * opfamily. ROWCOMPARE_NE is returned as the strategy number for this case. + */ +List * +get_op_btree_interpretation(Oid opno) +{ + List *result = NIL; + OpBtreeInterpretation *thisresult; + CatCList *catlist; + int i; + + /* + * Find all the pg_amop entries containing the operator. + */ + catlist = SearchSysCacheList1(AMOPOPID, ObjectIdGetDatum(opno)); + + for (i = 0; i < catlist->n_members; i++) + { + HeapTuple op_tuple = &catlist->members[i]->tuple; + Form_pg_amop op_form = (Form_pg_amop) GETSTRUCT(op_tuple); + StrategyNumber op_strategy; + + /* must be btree */ + if (op_form->amopmethod != BTREE_AM_OID) + continue; + + /* Get the operator's btree strategy number */ + op_strategy = (StrategyNumber) op_form->amopstrategy; + Assert(op_strategy >= 1 && op_strategy <= 5); + + thisresult = (OpBtreeInterpretation *) + palloc(sizeof(OpBtreeInterpretation)); + thisresult->opfamily_id = op_form->amopfamily; + thisresult->strategy = op_strategy; + thisresult->oplefttype = op_form->amoplefttype; + thisresult->oprighttype = op_form->amoprighttype; + result = lappend(result, thisresult); + } + + ReleaseSysCacheList(catlist); + + /* + * If we didn't find any btree opfamily containing the operator, perhaps + * it is a <> operator. See if it has a negator that is in an opfamily. + */ + if (result == NIL) + { + Oid op_negator = get_negator(opno); + + if (OidIsValid(op_negator)) + { + catlist = SearchSysCacheList1(AMOPOPID, + ObjectIdGetDatum(op_negator)); + + for (i = 0; i < catlist->n_members; i++) + { + HeapTuple op_tuple = &catlist->members[i]->tuple; + Form_pg_amop op_form = (Form_pg_amop) GETSTRUCT(op_tuple); + StrategyNumber op_strategy; + + /* must be btree */ + if (op_form->amopmethod != BTREE_AM_OID) + continue; + + /* Get the operator's btree strategy number */ + op_strategy = (StrategyNumber) op_form->amopstrategy; + Assert(op_strategy >= 1 && op_strategy <= 5); + + /* Only consider negators that are = */ + if (op_strategy != BTEqualStrategyNumber) + continue; + + /* OK, report it with "strategy" ROWCOMPARE_NE */ + thisresult = (OpBtreeInterpretation *) + palloc(sizeof(OpBtreeInterpretation)); + thisresult->opfamily_id = op_form->amopfamily; + thisresult->strategy = ROWCOMPARE_NE; + thisresult->oplefttype = op_form->amoplefttype; + thisresult->oprighttype = op_form->amoprighttype; + result = lappend(result, thisresult); + } + + ReleaseSysCacheList(catlist); + } + } + + return result; +} + +/* + * equality_ops_are_compatible + * Return true if the two given equality operators have compatible + * semantics. + * + * This is trivially true if they are the same operator. Otherwise, + * we look to see if they can be found in the same btree or hash opfamily. + * Either finding allows us to assume that they have compatible notions + * of equality. (The reason we need to do these pushups is that one might + * be a cross-type operator; for instance int24eq vs int4eq.) + */ +bool +equality_ops_are_compatible(Oid opno1, Oid opno2) +{ + bool result; + CatCList *catlist; + int i; + + /* Easy if they're the same operator */ + if (opno1 == opno2) + return true; + + /* + * We search through all the pg_amop entries for opno1. + */ + catlist = SearchSysCacheList1(AMOPOPID, ObjectIdGetDatum(opno1)); + + result = false; + for (i = 0; i < catlist->n_members; i++) + { + HeapTuple op_tuple = &catlist->members[i]->tuple; + Form_pg_amop op_form = (Form_pg_amop) GETSTRUCT(op_tuple); + + /* must be btree or hash */ + if (op_form->amopmethod == BTREE_AM_OID || + op_form->amopmethod == HASH_AM_OID) + { + if (op_in_opfamily(opno2, op_form->amopfamily)) + { + result = true; + break; + } + } + } + + ReleaseSysCacheList(catlist); + + return result; +} + +/* + * comparison_ops_are_compatible + * Return true if the two given comparison operators have compatible + * semantics. + * + * This is trivially true if they are the same operator. Otherwise, + * we look to see if they can be found in the same btree opfamily. + * For example, '<' and '>=' ops match if they belong to the same family. + * + * (This is identical to equality_ops_are_compatible(), except that we + * don't bother to examine hash opclasses.) + */ +bool +comparison_ops_are_compatible(Oid opno1, Oid opno2) +{ + bool result; + CatCList *catlist; + int i; + + /* Easy if they're the same operator */ + if (opno1 == opno2) + return true; + + /* + * We search through all the pg_amop entries for opno1. + */ + catlist = SearchSysCacheList1(AMOPOPID, ObjectIdGetDatum(opno1)); + + result = false; + for (i = 0; i < catlist->n_members; i++) + { + HeapTuple op_tuple = &catlist->members[i]->tuple; + Form_pg_amop op_form = (Form_pg_amop) GETSTRUCT(op_tuple); + + if (op_form->amopmethod == BTREE_AM_OID) + { + if (op_in_opfamily(opno2, op_form->amopfamily)) + { + result = true; + break; + } + } + } + + ReleaseSysCacheList(catlist); + + return result; +} + + +/* ---------- AMPROC CACHES ---------- */ + +/* + * get_opfamily_proc + * Get the OID of the specified support function + * for the specified opfamily and datatypes. + * + * Returns InvalidOid if there is no pg_amproc entry for the given keys. + */ +Oid +get_opfamily_proc_original(Oid opfamily, Oid lefttype, Oid righttype, int16 procnum) +{ + HeapTuple tp; + Form_pg_amproc amproc_tup; + RegProcedure result; + + tp = SearchSysCache4(AMPROCNUM, + ObjectIdGetDatum(opfamily), + ObjectIdGetDatum(lefttype), + ObjectIdGetDatum(righttype), + Int16GetDatum(procnum)); + if (!HeapTupleIsValid(tp)) + return InvalidOid; + amproc_tup = (Form_pg_amproc) GETSTRUCT(tp); + result = amproc_tup->amproc; + ReleaseSysCache(tp); + return result; +} + + +/* ---------- ATTRIBUTE CACHES ---------- */ + +/* + * get_attname + * Given the relation id and the attribute number, return the "attname" + * field from the attribute relation as a palloc'ed string. + * + * If no such attribute exists and missing_ok is true, NULL is returned; + * otherwise a not-intended-for-user-consumption error is thrown. + */ +char * +get_attname(Oid relid, AttrNumber attnum, bool missing_ok) +{ + HeapTuple tp; + + tp = SearchSysCache2(ATTNUM, + ObjectIdGetDatum(relid), Int16GetDatum(attnum)); + if (HeapTupleIsValid(tp)) + { + Form_pg_attribute att_tup = (Form_pg_attribute) GETSTRUCT(tp); + char *result; + + result = pstrdup(NameStr(att_tup->attname)); + ReleaseSysCache(tp); + return result; + } + + if (!missing_ok) + elog(ERROR, "cache lookup failed for attribute %d of relation %u", + attnum, relid); + return NULL; +} + +/* + * get_attnum + * + * Given the relation id and the attribute name, + * return the "attnum" field from the attribute relation. + * + * Returns InvalidAttrNumber if the attr doesn't exist (or is dropped). + */ +AttrNumber +get_attnum(Oid relid, const char *attname) +{ + HeapTuple tp; + + tp = SearchSysCacheAttName(relid, attname); + if (HeapTupleIsValid(tp)) + { + Form_pg_attribute att_tup = (Form_pg_attribute) GETSTRUCT(tp); + AttrNumber result; + + result = att_tup->attnum; + ReleaseSysCache(tp); + return result; + } + else + return InvalidAttrNumber; +} + +/* + * get_attstattarget + * + * Given the relation id and the attribute number, + * return the "attstattarget" field from the attribute relation. + * + * Errors if not found. + */ +int +get_attstattarget(Oid relid, AttrNumber attnum) +{ + HeapTuple tp; + Form_pg_attribute att_tup; + int result; + + tp = SearchSysCache2(ATTNUM, + ObjectIdGetDatum(relid), + Int16GetDatum(attnum)); + if (!HeapTupleIsValid(tp)) + elog(ERROR, "cache lookup failed for attribute %d of relation %u", + attnum, relid); + att_tup = (Form_pg_attribute) GETSTRUCT(tp); + result = att_tup->attstattarget; + ReleaseSysCache(tp); + return result; +} + +/* + * get_attgenerated + * + * Given the relation id and the attribute number, + * return the "attgenerated" field from the attribute relation. + * + * Errors if not found. + * + * Since not generated is represented by '\0', this can also be used as a + * Boolean test. + */ +char +get_attgenerated(Oid relid, AttrNumber attnum) +{ + HeapTuple tp; + Form_pg_attribute att_tup; + char result; + + tp = SearchSysCache2(ATTNUM, + ObjectIdGetDatum(relid), + Int16GetDatum(attnum)); + if (!HeapTupleIsValid(tp)) + elog(ERROR, "cache lookup failed for attribute %d of relation %u", + attnum, relid); + att_tup = (Form_pg_attribute) GETSTRUCT(tp); + result = att_tup->attgenerated; + ReleaseSysCache(tp); + return result; +} + +/* + * get_atttype + * + * Given the relation OID and the attribute number with the relation, + * return the attribute type OID. + */ +Oid +get_atttype(Oid relid, AttrNumber attnum) +{ + HeapTuple tp; + + tp = SearchSysCache2(ATTNUM, + ObjectIdGetDatum(relid), + Int16GetDatum(attnum)); + if (HeapTupleIsValid(tp)) + { + Form_pg_attribute att_tup = (Form_pg_attribute) GETSTRUCT(tp); + Oid result; + + result = att_tup->atttypid; + ReleaseSysCache(tp); + return result; + } + else + return InvalidOid; +} + +/* + * get_atttypetypmodcoll + * + * A three-fer: given the relation id and the attribute number, + * fetch atttypid, atttypmod, and attcollation in a single cache lookup. + * + * Unlike the otherwise-similar get_atttype, this routine + * raises an error if it can't obtain the information. + */ +void +get_atttypetypmodcoll(Oid relid, AttrNumber attnum, + Oid *typid, int32 *typmod, Oid *collid) +{ + HeapTuple tp; + Form_pg_attribute att_tup; + + tp = SearchSysCache2(ATTNUM, + ObjectIdGetDatum(relid), + Int16GetDatum(attnum)); + if (!HeapTupleIsValid(tp)) + elog(ERROR, "cache lookup failed for attribute %d of relation %u", + attnum, relid); + att_tup = (Form_pg_attribute) GETSTRUCT(tp); + + *typid = att_tup->atttypid; + *typmod = att_tup->atttypmod; + *collid = att_tup->attcollation; + ReleaseSysCache(tp); +} + +/* + * get_attoptions + * + * Given the relation id and the attribute number, + * return the attribute options text[] datum, if any. + */ +Datum +get_attoptions(Oid relid, int16 attnum) +{ + HeapTuple tuple; + Datum attopts; + Datum result; + bool isnull; + + tuple = SearchSysCache2(ATTNUM, + ObjectIdGetDatum(relid), + Int16GetDatum(attnum)); + + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for attribute %d of relation %u", + attnum, relid); + + attopts = SysCacheGetAttr(ATTNAME, tuple, Anum_pg_attribute_attoptions, + &isnull); + + if (isnull) + result = (Datum) 0; + else + result = datumCopy(attopts, false, -1); /* text[] */ + + ReleaseSysCache(tuple); + + return result; +} + +/* ---------- PG_CAST CACHE ---------- */ + +/* + * get_cast_oid - given two type OIDs, look up a cast OID + * + * If missing_ok is false, throw an error if the cast is not found. If + * true, just return InvalidOid. + */ +Oid +get_cast_oid(Oid sourcetypeid, Oid targettypeid, bool missing_ok) +{ + Oid oid; + + oid = GetSysCacheOid2(CASTSOURCETARGET, Anum_pg_cast_oid, + ObjectIdGetDatum(sourcetypeid), + ObjectIdGetDatum(targettypeid)); + if (!OidIsValid(oid) && !missing_ok) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("cast from type %s to type %s does not exist", + format_type_be(sourcetypeid), + format_type_be(targettypeid)))); + return oid; +} + +/* ---------- COLLATION CACHE ---------- */ + +/* + * get_collation_name + * Returns the name of a given pg_collation entry. + * + * Returns a palloc'd copy of the string, or NULL if no such collation. + * + * NOTE: since collation name is not unique, be wary of code that uses this + * for anything except preparing error messages. + */ +char * +get_collation_name(Oid colloid) +{ + HeapTuple tp; + + tp = SearchSysCache1(COLLOID, ObjectIdGetDatum(colloid)); + if (HeapTupleIsValid(tp)) + { + Form_pg_collation colltup = (Form_pg_collation) GETSTRUCT(tp); + char *result; + + result = pstrdup(NameStr(colltup->collname)); + ReleaseSysCache(tp); + return result; + } + else + return NULL; +} + +bool +get_collation_isdeterministic(Oid colloid) +{ + HeapTuple tp; + Form_pg_collation colltup; + bool result; + + tp = SearchSysCache1(COLLOID, ObjectIdGetDatum(colloid)); + if (!HeapTupleIsValid(tp)) + elog(ERROR, "cache lookup failed for collation %u", colloid); + colltup = (Form_pg_collation) GETSTRUCT(tp); + result = colltup->collisdeterministic; + ReleaseSysCache(tp); + return result; +} + +/* ---------- CONSTRAINT CACHE ---------- */ + +/* + * get_constraint_name + * Returns the name of a given pg_constraint entry. + * + * Returns a palloc'd copy of the string, or NULL if no such constraint. + * + * NOTE: since constraint name is not unique, be wary of code that uses this + * for anything except preparing error messages. + */ +char * +get_constraint_name(Oid conoid) +{ + HeapTuple tp; + + tp = SearchSysCache1(CONSTROID, ObjectIdGetDatum(conoid)); + if (HeapTupleIsValid(tp)) + { + Form_pg_constraint contup = (Form_pg_constraint) GETSTRUCT(tp); + char *result; + + result = pstrdup(NameStr(contup->conname)); + ReleaseSysCache(tp); + return result; + } + else + return NULL; +} + +/* + * get_constraint_index + * Given the OID of a unique, primary-key, or exclusion constraint, + * return the OID of the underlying index. + * + * Returns InvalidOid if the constraint could not be found or is of + * the wrong type. + * + * The intent of this function is to return the index "owned" by the + * specified constraint. Therefore we must check contype, since some + * pg_constraint entries (e.g. for foreign-key constraints) store the + * OID of an index that is referenced but not owned by the constraint. + */ +Oid +get_constraint_index(Oid conoid) +{ + HeapTuple tp; + + tp = SearchSysCache1(CONSTROID, ObjectIdGetDatum(conoid)); + if (HeapTupleIsValid(tp)) + { + Form_pg_constraint contup = (Form_pg_constraint) GETSTRUCT(tp); + Oid result; + + if (contup->contype == CONSTRAINT_UNIQUE || + contup->contype == CONSTRAINT_PRIMARY || + contup->contype == CONSTRAINT_EXCLUSION) + result = contup->conindid; + else + result = InvalidOid; + ReleaseSysCache(tp); + return result; + } + else + return InvalidOid; +} + +/* ---------- LANGUAGE CACHE ---------- */ + +char * +get_language_name(Oid langoid, bool missing_ok) +{ + HeapTuple tp; + + tp = SearchSysCache1(LANGOID, ObjectIdGetDatum(langoid)); + if (HeapTupleIsValid(tp)) + { + Form_pg_language lantup = (Form_pg_language) GETSTRUCT(tp); + char *result; + + result = pstrdup(NameStr(lantup->lanname)); + ReleaseSysCache(tp); + return result; + } + + if (!missing_ok) + elog(ERROR, "cache lookup failed for language %u", + langoid); + return NULL; +} + +/* ---------- OPCLASS CACHE ---------- */ + +/* + * get_opclass_family + * + * Returns the OID of the operator family the opclass belongs to. + */ +Oid +get_opclass_family(Oid opclass) +{ + HeapTuple tp; + Form_pg_opclass cla_tup; + Oid result; + + tp = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass)); + if (!HeapTupleIsValid(tp)) + elog(ERROR, "cache lookup failed for opclass %u", opclass); + cla_tup = (Form_pg_opclass) GETSTRUCT(tp); + + result = cla_tup->opcfamily; + ReleaseSysCache(tp); + return result; +} + +/* + * get_opclass_input_type + * + * Returns the OID of the datatype the opclass indexes. + */ +Oid +get_opclass_input_type(Oid opclass) +{ + HeapTuple tp; + Form_pg_opclass cla_tup; + Oid result; + + tp = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass)); + if (!HeapTupleIsValid(tp)) + elog(ERROR, "cache lookup failed for opclass %u", opclass); + cla_tup = (Form_pg_opclass) GETSTRUCT(tp); + + result = cla_tup->opcintype; + ReleaseSysCache(tp); + return result; +} + +/* + * get_opclass_opfamily_and_input_type + * + * Returns the OID of the operator family the opclass belongs to, + * the OID of the datatype the opclass indexes + */ +bool +get_opclass_opfamily_and_input_type(Oid opclass, Oid *opfamily, Oid *opcintype) +{ + HeapTuple tp; + Form_pg_opclass cla_tup; + + tp = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass)); + if (!HeapTupleIsValid(tp)) + return false; + + cla_tup = (Form_pg_opclass) GETSTRUCT(tp); + + *opfamily = cla_tup->opcfamily; + *opcintype = cla_tup->opcintype; + + ReleaseSysCache(tp); + + return true; +} + +/* ---------- OPERATOR CACHE ---------- */ + +/* + * get_opcode + * + * Returns the regproc id of the routine used to implement an + * operator given the operator oid. + */ +RegProcedure +get_opcode_original(Oid opno) +{ + HeapTuple tp; + + tp = SearchSysCache1(OPEROID, ObjectIdGetDatum(opno)); + if (HeapTupleIsValid(tp)) + { + Form_pg_operator optup = (Form_pg_operator) GETSTRUCT(tp); + RegProcedure result; + + result = optup->oprcode; + ReleaseSysCache(tp); + return result; + } + else + return (RegProcedure) InvalidOid; +} + +/* + * get_opname + * returns the name of the operator with the given opno + * + * Note: returns a palloc'd copy of the string, or NULL if no such operator. + */ +char * +get_opname(Oid opno) +{ + HeapTuple tp; + + tp = SearchSysCache1(OPEROID, ObjectIdGetDatum(opno)); + if (HeapTupleIsValid(tp)) + { + Form_pg_operator optup = (Form_pg_operator) GETSTRUCT(tp); + char *result; + + result = pstrdup(NameStr(optup->oprname)); + ReleaseSysCache(tp); + return result; + } + else + return NULL; +} + +/* + * get_op_rettype + * Given operator oid, return the operator's result type. + */ +Oid +get_op_rettype(Oid opno) +{ + HeapTuple tp; + + tp = SearchSysCache1(OPEROID, ObjectIdGetDatum(opno)); + if (HeapTupleIsValid(tp)) + { + Form_pg_operator optup = (Form_pg_operator) GETSTRUCT(tp); + Oid result; + + result = optup->oprresult; + ReleaseSysCache(tp); + return result; + } + else + return InvalidOid; +} + +/* + * op_input_types + * + * Returns the left and right input datatypes for an operator + * (InvalidOid if not relevant). + */ +void +op_input_types(Oid opno, Oid *lefttype, Oid *righttype) +{ + HeapTuple tp; + Form_pg_operator optup; + + tp = SearchSysCache1(OPEROID, ObjectIdGetDatum(opno)); + if (!HeapTupleIsValid(tp)) /* shouldn't happen */ + elog(ERROR, "cache lookup failed for operator %u", opno); + optup = (Form_pg_operator) GETSTRUCT(tp); + *lefttype = optup->oprleft; + *righttype = optup->oprright; + ReleaseSysCache(tp); +} + +/* + * op_mergejoinable + * + * Returns true if the operator is potentially mergejoinable. (The planner + * will fail to find any mergejoin plans unless there are suitable btree + * opfamily entries for this operator and associated sortops. The pg_operator + * flag is just a hint to tell the planner whether to bother looking.) + * + * In some cases (currently only array_eq and record_eq), mergejoinability + * depends on the specific input data type the operator is invoked for, so + * that must be passed as well. We currently assume that only one input's type + * is needed to check this --- by convention, pass the left input's data type. + */ +bool +op_mergejoinable_original(Oid opno, Oid inputtype) +{ + bool result = false; + HeapTuple tp; + TypeCacheEntry *typentry; + + /* + * For array_eq or record_eq, we can sort if the element or field types + * are all sortable. We could implement all the checks for that here, but + * the typcache already does that and caches the results too, so let's + * rely on the typcache. + */ + if (opno == ARRAY_EQ_OP) + { + typentry = lookup_type_cache(inputtype, TYPECACHE_CMP_PROC); + if (typentry->cmp_proc == F_BTARRAYCMP) + result = true; + } + else if (opno == RECORD_EQ_OP) + { + typentry = lookup_type_cache(inputtype, TYPECACHE_CMP_PROC); + if (typentry->cmp_proc == F_BTRECORDCMP) + result = true; + } + else + { + /* For all other operators, rely on pg_operator.oprcanmerge */ + tp = SearchSysCache1(OPEROID, ObjectIdGetDatum(opno)); + if (HeapTupleIsValid(tp)) + { + Form_pg_operator optup = (Form_pg_operator) GETSTRUCT(tp); + + result = optup->oprcanmerge; + ReleaseSysCache(tp); + } + } + return result; +} + +/* + * op_hashjoinable + * + * Returns true if the operator is hashjoinable. (There must be a suitable + * hash opfamily entry for this operator if it is so marked.) + * + * In some cases (currently only array_eq), hashjoinability depends on the + * specific input data type the operator is invoked for, so that must be + * passed as well. We currently assume that only one input's type is needed + * to check this --- by convention, pass the left input's data type. + */ +bool +op_hashjoinable_original(Oid opno, Oid inputtype) +{ + bool result = false; + HeapTuple tp; + TypeCacheEntry *typentry; + + /* As in op_mergejoinable, let the typcache handle the hard cases */ + if (opno == ARRAY_EQ_OP) + { + typentry = lookup_type_cache(inputtype, TYPECACHE_HASH_PROC); + if (typentry->hash_proc == F_HASH_ARRAY) + result = true; + } + else if (opno == RECORD_EQ_OP) + { + typentry = lookup_type_cache(inputtype, TYPECACHE_HASH_PROC); + if (typentry->hash_proc == F_HASH_RECORD) + result = true; + } + else + { + /* For all other operators, rely on pg_operator.oprcanhash */ + tp = SearchSysCache1(OPEROID, ObjectIdGetDatum(opno)); + if (HeapTupleIsValid(tp)) + { + Form_pg_operator optup = (Form_pg_operator) GETSTRUCT(tp); + + result = optup->oprcanhash; + ReleaseSysCache(tp); + } + } + return result; +} + +/* + * op_strict + * + * Get the proisstrict flag for the operator's underlying function. + */ +bool +op_strict(Oid opno) +{ + RegProcedure funcid = get_opcode(opno); + + if (funcid == (RegProcedure) InvalidOid) + elog(ERROR, "operator %u does not exist", opno); + + return func_strict((Oid) funcid); +} + +/* + * op_volatile + * + * Get the provolatile flag for the operator's underlying function. + */ +char +op_volatile(Oid opno) +{ + RegProcedure funcid = get_opcode(opno); + + if (funcid == (RegProcedure) InvalidOid) + elog(ERROR, "operator %u does not exist", opno); + + return func_volatile((Oid) funcid); +} + +/* + * get_commutator + * + * Returns the corresponding commutator of an operator. + */ +Oid +get_commutator(Oid opno) +{ + HeapTuple tp; + + tp = SearchSysCache1(OPEROID, ObjectIdGetDatum(opno)); + if (HeapTupleIsValid(tp)) + { + Form_pg_operator optup = (Form_pg_operator) GETSTRUCT(tp); + Oid result; + + result = optup->oprcom; + ReleaseSysCache(tp); + return result; + } + else + return InvalidOid; +} + +/* + * get_negator + * + * Returns the corresponding negator of an operator. + */ +Oid +get_negator(Oid opno) +{ + HeapTuple tp; + + tp = SearchSysCache1(OPEROID, ObjectIdGetDatum(opno)); + if (HeapTupleIsValid(tp)) + { + Form_pg_operator optup = (Form_pg_operator) GETSTRUCT(tp); + Oid result; + + result = optup->oprnegate; + ReleaseSysCache(tp); + return result; + } + else + return InvalidOid; +} + +/* + * get_oprrest + * + * Returns procedure id for computing selectivity of an operator. + */ +RegProcedure +get_oprrest(Oid opno) +{ + HeapTuple tp; + + tp = SearchSysCache1(OPEROID, ObjectIdGetDatum(opno)); + if (HeapTupleIsValid(tp)) + { + Form_pg_operator optup = (Form_pg_operator) GETSTRUCT(tp); + RegProcedure result; + + result = optup->oprrest; + ReleaseSysCache(tp); + return result; + } + else + return (RegProcedure) InvalidOid; +} + +/* + * get_oprjoin + * + * Returns procedure id for computing selectivity of a join. + */ +RegProcedure +get_oprjoin_original(Oid opno) +{ + HeapTuple tp; + + tp = SearchSysCache1(OPEROID, ObjectIdGetDatum(opno)); + if (HeapTupleIsValid(tp)) + { + Form_pg_operator optup = (Form_pg_operator) GETSTRUCT(tp); + RegProcedure result; + + result = optup->oprjoin; + ReleaseSysCache(tp); + return result; + } + else + return (RegProcedure) InvalidOid; +} + +/* ---------- FUNCTION CACHE ---------- */ + +/* + * get_func_name + * returns the name of the function with the given funcid + * + * Note: returns a palloc'd copy of the string, or NULL if no such function. + */ +char * +get_func_name(Oid funcid) +{ + HeapTuple tp; + + tp = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid)); + if (HeapTupleIsValid(tp)) + { + Form_pg_proc functup = (Form_pg_proc) GETSTRUCT(tp); + char *result; + + result = pstrdup(NameStr(functup->proname)); + ReleaseSysCache(tp); + return result; + } + else + return NULL; +} + +/* + * get_func_namespace + * + * Returns the pg_namespace OID associated with a given function. + */ +Oid +get_func_namespace(Oid funcid) +{ + HeapTuple tp; + + tp = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid)); + if (HeapTupleIsValid(tp)) + { + Form_pg_proc functup = (Form_pg_proc) GETSTRUCT(tp); + Oid result; + + result = functup->pronamespace; + ReleaseSysCache(tp); + return result; + } + else + return InvalidOid; +} + +/* + * get_func_rettype + * Given procedure id, return the function's result type. + */ +Oid +get_func_rettype(Oid funcid) +{ + HeapTuple tp; + Oid result; + + tp = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid)); + if (!HeapTupleIsValid(tp)) + elog(ERROR, "cache lookup failed for function %u", funcid); + + result = ((Form_pg_proc) GETSTRUCT(tp))->prorettype; + ReleaseSysCache(tp); + return result; +} + +/* + * get_func_nargs + * Given procedure id, return the number of arguments. + */ +int +get_func_nargs(Oid funcid) +{ + HeapTuple tp; + int result; + + tp = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid)); + if (!HeapTupleIsValid(tp)) + elog(ERROR, "cache lookup failed for function %u", funcid); + + result = ((Form_pg_proc) GETSTRUCT(tp))->pronargs; + ReleaseSysCache(tp); + return result; +} + +/* + * get_func_signature + * Given procedure id, return the function's argument and result types. + * (The return value is the result type.) + * + * The arguments are returned as a palloc'd array. + */ +Oid +get_func_signature(Oid funcid, Oid **argtypes, int *nargs) +{ + HeapTuple tp; + Form_pg_proc procstruct; + Oid result; + + tp = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid)); + if (!HeapTupleIsValid(tp)) + elog(ERROR, "cache lookup failed for function %u", funcid); + + procstruct = (Form_pg_proc) GETSTRUCT(tp); + + result = procstruct->prorettype; + *nargs = (int) procstruct->pronargs; + Assert(*nargs == procstruct->proargtypes.dim1); + *argtypes = (Oid *) palloc(*nargs * sizeof(Oid)); + memcpy(*argtypes, procstruct->proargtypes.values, *nargs * sizeof(Oid)); + + ReleaseSysCache(tp); + return result; +} + +/* + * get_func_variadictype + * Given procedure id, return the function's provariadic field. + */ +Oid +get_func_variadictype(Oid funcid) +{ + HeapTuple tp; + Oid result; + + tp = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid)); + if (!HeapTupleIsValid(tp)) + elog(ERROR, "cache lookup failed for function %u", funcid); + + result = ((Form_pg_proc) GETSTRUCT(tp))->provariadic; + ReleaseSysCache(tp); + return result; +} + +/* + * get_func_retset + * Given procedure id, return the function's proretset flag. + */ +bool +get_func_retset(Oid funcid) +{ + HeapTuple tp; + bool result; + + tp = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid)); + if (!HeapTupleIsValid(tp)) + elog(ERROR, "cache lookup failed for function %u", funcid); + + result = ((Form_pg_proc) GETSTRUCT(tp))->proretset; + ReleaseSysCache(tp); + return result; +} + +/* + * func_strict + * Given procedure id, return the function's proisstrict flag. + */ +bool +func_strict(Oid funcid) +{ + HeapTuple tp; + bool result; + + tp = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid)); + if (!HeapTupleIsValid(tp)) + elog(ERROR, "cache lookup failed for function %u", funcid); + + result = ((Form_pg_proc) GETSTRUCT(tp))->proisstrict; + ReleaseSysCache(tp); + return result; +} + +/* + * func_volatile + * Given procedure id, return the function's provolatile flag. + */ +char +func_volatile_original(Oid funcid) +{ + HeapTuple tp; + char result; + + tp = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid)); + if (!HeapTupleIsValid(tp)) + elog(ERROR, "cache lookup failed for function %u", funcid); + + result = ((Form_pg_proc) GETSTRUCT(tp))->provolatile; + ReleaseSysCache(tp); + return result; +} + +/* + * func_parallel + * Given procedure id, return the function's proparallel flag. + */ +char +func_parallel(Oid funcid) +{ + HeapTuple tp; + char result; + + tp = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid)); + if (!HeapTupleIsValid(tp)) + elog(ERROR, "cache lookup failed for function %u", funcid); + + result = ((Form_pg_proc) GETSTRUCT(tp))->proparallel; + ReleaseSysCache(tp); + return result; +} + +/* + * get_func_prokind + * Given procedure id, return the routine kind. + */ +char +get_func_prokind(Oid funcid) +{ + HeapTuple tp; + char result; + + tp = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid)); + if (!HeapTupleIsValid(tp)) + elog(ERROR, "cache lookup failed for function %u", funcid); + + result = ((Form_pg_proc) GETSTRUCT(tp))->prokind; + ReleaseSysCache(tp); + return result; +} + +/* + * get_func_leakproof + * Given procedure id, return the function's leakproof field. + */ +bool +get_func_leakproof(Oid funcid) +{ + HeapTuple tp; + bool result; + + tp = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid)); + if (!HeapTupleIsValid(tp)) + elog(ERROR, "cache lookup failed for function %u", funcid); + + result = ((Form_pg_proc) GETSTRUCT(tp))->proleakproof; + ReleaseSysCache(tp); + return result; +} + +/* + * get_func_support + * + * Returns the support function OID associated with a given function, + * or InvalidOid if there is none. + */ +RegProcedure +get_func_support(Oid funcid) +{ + HeapTuple tp; + + tp = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid)); + if (HeapTupleIsValid(tp)) + { + Form_pg_proc functup = (Form_pg_proc) GETSTRUCT(tp); + RegProcedure result; + + result = functup->prosupport; + ReleaseSysCache(tp); + return result; + } + else + return (RegProcedure) InvalidOid; +} + +/* ---------- RELATION CACHE ---------- */ + +/* + * get_relname_relid + * Given name and namespace of a relation, look up the OID. + * + * Returns InvalidOid if there is no such relation. + */ +Oid +get_relname_relid(const char *relname, Oid relnamespace) +{ + return GetSysCacheOid2(RELNAMENSP, Anum_pg_class_oid, + PointerGetDatum(relname), + ObjectIdGetDatum(relnamespace)); +} + +#ifdef NOT_USED +/* + * get_relnatts + * + * Returns the number of attributes for a given relation. + */ +int +get_relnatts(Oid relid) +{ + HeapTuple tp; + + tp = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (HeapTupleIsValid(tp)) + { + Form_pg_class reltup = (Form_pg_class) GETSTRUCT(tp); + int result; + + result = reltup->relnatts; + ReleaseSysCache(tp); + return result; + } + else + return InvalidAttrNumber; +} +#endif + +/* + * get_rel_name + * Returns the name of a given relation. + * + * Returns a palloc'd copy of the string, or NULL if no such relation. + * + * NOTE: since relation name is not unique, be wary of code that uses this + * for anything except preparing error messages. + */ +char * +get_rel_name(Oid relid) +{ + HeapTuple tp; + + tp = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (HeapTupleIsValid(tp)) + { + Form_pg_class reltup = (Form_pg_class) GETSTRUCT(tp); + char *result; + + result = pstrdup(NameStr(reltup->relname)); + ReleaseSysCache(tp); + return result; + } + else + return NULL; +} + +/* + * get_rel_namespace + * + * Returns the pg_namespace OID associated with a given relation. + */ +Oid +get_rel_namespace(Oid relid) +{ + HeapTuple tp; + + tp = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (HeapTupleIsValid(tp)) + { + Form_pg_class reltup = (Form_pg_class) GETSTRUCT(tp); + Oid result; + + result = reltup->relnamespace; + ReleaseSysCache(tp); + return result; + } + else + return InvalidOid; +} + +/* + * get_rel_type_id + * + * Returns the pg_type OID associated with a given relation. + * + * Note: not all pg_class entries have associated pg_type OIDs; so be + * careful to check for InvalidOid result. + */ +Oid +get_rel_type_id(Oid relid) +{ + HeapTuple tp; + + tp = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (HeapTupleIsValid(tp)) + { + Form_pg_class reltup = (Form_pg_class) GETSTRUCT(tp); + Oid result; + + result = reltup->reltype; + ReleaseSysCache(tp); + return result; + } + else + return InvalidOid; +} + +/* + * get_rel_relkind + * + * Returns the relkind associated with a given relation. + */ +char +get_rel_relkind(Oid relid) +{ + HeapTuple tp; + + tp = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (HeapTupleIsValid(tp)) + { + Form_pg_class reltup = (Form_pg_class) GETSTRUCT(tp); + char result; + + result = reltup->relkind; + ReleaseSysCache(tp); + return result; + } + else + return '\0'; +} + +/* + * get_rel_relispartition + * + * Returns the relispartition flag associated with a given relation. + */ +bool +get_rel_relispartition(Oid relid) +{ + HeapTuple tp; + + tp = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (HeapTupleIsValid(tp)) + { + Form_pg_class reltup = (Form_pg_class) GETSTRUCT(tp); + bool result; + + result = reltup->relispartition; + ReleaseSysCache(tp); + return result; + } + else + return false; +} + +/* + * get_rel_tablespace + * + * Returns the pg_tablespace OID associated with a given relation. + * + * Note: InvalidOid might mean either that we couldn't find the relation, + * or that it is in the database's default tablespace. + */ +Oid +get_rel_tablespace(Oid relid) +{ + HeapTuple tp; + + tp = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (HeapTupleIsValid(tp)) + { + Form_pg_class reltup = (Form_pg_class) GETSTRUCT(tp); + Oid result; + + result = reltup->reltablespace; + ReleaseSysCache(tp); + return result; + } + else + return InvalidOid; +} + +/* + * get_rel_persistence + * + * Returns the relpersistence associated with a given relation. + */ +char +get_rel_persistence(Oid relid) +{ + HeapTuple tp; + Form_pg_class reltup; + char result; + + tp = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(tp)) + elog(ERROR, "cache lookup failed for relation %u", relid); + reltup = (Form_pg_class) GETSTRUCT(tp); + result = reltup->relpersistence; + ReleaseSysCache(tp); + + return result; +} + + +/* ---------- TRANSFORM CACHE ---------- */ + +Oid +get_transform_fromsql(Oid typid, Oid langid, List *trftypes) +{ + HeapTuple tup; + + if (!list_member_oid(trftypes, typid)) + return InvalidOid; + + tup = SearchSysCache2(TRFTYPELANG, typid, langid); + if (HeapTupleIsValid(tup)) + { + Oid funcid; + + funcid = ((Form_pg_transform) GETSTRUCT(tup))->trffromsql; + ReleaseSysCache(tup); + return funcid; + } + else + return InvalidOid; +} + +Oid +get_transform_tosql(Oid typid, Oid langid, List *trftypes) +{ + HeapTuple tup; + + if (!list_member_oid(trftypes, typid)) + return InvalidOid; + + tup = SearchSysCache2(TRFTYPELANG, typid, langid); + if (HeapTupleIsValid(tup)) + { + Oid funcid; + + funcid = ((Form_pg_transform) GETSTRUCT(tup))->trftosql; + ReleaseSysCache(tup); + return funcid; + } + else + return InvalidOid; +} + + +/* ---------- TYPE CACHE ---------- */ + +/* + * get_typisdefined + * + * Given the type OID, determine whether the type is defined + * (if not, it's only a shell). + */ +bool +get_typisdefined(Oid typid) +{ + HeapTuple tp; + + tp = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid)); + if (HeapTupleIsValid(tp)) + { + Form_pg_type typtup = (Form_pg_type) GETSTRUCT(tp); + bool result; + + result = typtup->typisdefined; + ReleaseSysCache(tp); + return result; + } + else + return false; +} + +/* + * get_typlen + * + * Given the type OID, return the length of the type. + */ +int16 +get_typlen(Oid typid) +{ + HeapTuple tp; + + tp = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid)); + if (HeapTupleIsValid(tp)) + { + Form_pg_type typtup = (Form_pg_type) GETSTRUCT(tp); + int16 result; + + result = typtup->typlen; + ReleaseSysCache(tp); + return result; + } + else + return 0; +} + +/* + * get_typbyval + * + * Given the type OID, determine whether the type is returned by value or + * not. Returns true if by value, false if by reference. + */ +bool +get_typbyval(Oid typid) +{ + HeapTuple tp; + + tp = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid)); + if (HeapTupleIsValid(tp)) + { + Form_pg_type typtup = (Form_pg_type) GETSTRUCT(tp); + bool result; + + result = typtup->typbyval; + ReleaseSysCache(tp); + return result; + } + else + return false; +} + +/* + * get_typlenbyval + * + * A two-fer: given the type OID, return both typlen and typbyval. + * + * Since both pieces of info are needed to know how to copy a Datum, + * many places need both. Might as well get them with one cache lookup + * instead of two. Also, this routine raises an error instead of + * returning a bogus value when given a bad type OID. + */ +void +get_typlenbyval(Oid typid, int16 *typlen, bool *typbyval) +{ + HeapTuple tp; + Form_pg_type typtup; + + tp = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid)); + if (!HeapTupleIsValid(tp)) + elog(ERROR, "cache lookup failed for type %u", typid); + typtup = (Form_pg_type) GETSTRUCT(tp); + *typlen = typtup->typlen; + *typbyval = typtup->typbyval; + ReleaseSysCache(tp); +} + +/* + * get_typlenbyvalalign + * + * A three-fer: given the type OID, return typlen, typbyval, typalign. + */ +void +get_typlenbyvalalign(Oid typid, int16 *typlen, bool *typbyval, + char *typalign) +{ + HeapTuple tp; + Form_pg_type typtup; + + tp = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid)); + if (!HeapTupleIsValid(tp)) + elog(ERROR, "cache lookup failed for type %u", typid); + typtup = (Form_pg_type) GETSTRUCT(tp); + *typlen = typtup->typlen; + *typbyval = typtup->typbyval; + *typalign = typtup->typalign; + ReleaseSysCache(tp); +} + +/* + * getTypeIOParam + * Given a pg_type row, select the type OID to pass to I/O functions + * + * Formerly, all I/O functions were passed pg_type.typelem as their second + * parameter, but we now have a more complex rule about what to pass. + * This knowledge is intended to be centralized here --- direct references + * to typelem elsewhere in the code are wrong, if they are associated with + * I/O calls and not with actual subscripting operations! (But see + * bootstrap.c's boot_get_type_io_data() if you need to change this.) + * + * As of PostgreSQL 8.1, output functions receive only the value itself + * and not any auxiliary parameters, so the name of this routine is now + * a bit of a misnomer ... it should be getTypeInputParam. + */ +Oid +getTypeIOParam(HeapTuple typeTuple) +{ + Form_pg_type typeStruct = (Form_pg_type) GETSTRUCT(typeTuple); + + /* + * Array types get their typelem as parameter; everybody else gets their + * own type OID as parameter. + */ + if (OidIsValid(typeStruct->typelem)) + return typeStruct->typelem; + else + return typeStruct->oid; +} + +/* + * get_type_io_data + * + * A six-fer: given the type OID, return typlen, typbyval, typalign, + * typdelim, typioparam, and IO function OID. The IO function + * returned is controlled by IOFuncSelector + */ +void +get_type_io_data_original(Oid typid, + IOFuncSelector which_func, + int16 *typlen, + bool *typbyval, + char *typalign, + char *typdelim, + Oid *typioparam, + Oid *func) +{ + HeapTuple typeTuple; + Form_pg_type typeStruct; + + /* + * In bootstrap mode, pass it off to bootstrap.c. This hack allows us to + * use array_in and array_out during bootstrap. + */ + if (IsBootstrapProcessingMode()) + { + Oid typinput; + Oid typoutput; + + boot_get_type_io_data(typid, + typlen, + typbyval, + typalign, + typdelim, + typioparam, + &typinput, + &typoutput); + switch (which_func) + { + case IOFunc_input: + *func = typinput; + break; + case IOFunc_output: + *func = typoutput; + break; + default: + elog(ERROR, "binary I/O not supported during bootstrap"); + break; + } + return; + } + + typeTuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid)); + if (!HeapTupleIsValid(typeTuple)) + elog(ERROR, "cache lookup failed for type %u", typid); + typeStruct = (Form_pg_type) GETSTRUCT(typeTuple); + + *typlen = typeStruct->typlen; + *typbyval = typeStruct->typbyval; + *typalign = typeStruct->typalign; + *typdelim = typeStruct->typdelim; + *typioparam = getTypeIOParam(typeTuple); + switch (which_func) + { + case IOFunc_input: + *func = typeStruct->typinput; + break; + case IOFunc_output: + *func = typeStruct->typoutput; + break; + case IOFunc_receive: + *func = typeStruct->typreceive; + break; + case IOFunc_send: + *func = typeStruct->typsend; + break; + } + ReleaseSysCache(typeTuple); +} + +#ifdef NOT_USED +char +get_typalign(Oid typid) +{ + HeapTuple tp; + + tp = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid)); + if (HeapTupleIsValid(tp)) + { + Form_pg_type typtup = (Form_pg_type) GETSTRUCT(tp); + char result; + + result = typtup->typalign; + ReleaseSysCache(tp); + return result; + } + else + return TYPALIGN_INT; +} +#endif + +char +get_typstorage(Oid typid) +{ + HeapTuple tp; + + tp = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid)); + if (HeapTupleIsValid(tp)) + { + Form_pg_type typtup = (Form_pg_type) GETSTRUCT(tp); + char result; + + result = typtup->typstorage; + ReleaseSysCache(tp); + return result; + } + else + return TYPSTORAGE_PLAIN; +} + +/* + * get_typdefault + * Given a type OID, return the type's default value, if any. + * + * The result is a palloc'd expression node tree, or NULL if there + * is no defined default for the datatype. + * + * NB: caller should be prepared to coerce result to correct datatype; + * the returned expression tree might produce something of the wrong type. + */ +Node * +get_typdefault(Oid typid) +{ + HeapTuple typeTuple; + Form_pg_type type; + Datum datum; + bool isNull; + Node *expr; + + typeTuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid)); + if (!HeapTupleIsValid(typeTuple)) + elog(ERROR, "cache lookup failed for type %u", typid); + type = (Form_pg_type) GETSTRUCT(typeTuple); + + /* + * typdefault and typdefaultbin are potentially null, so don't try to + * access 'em as struct fields. Must do it the hard way with + * SysCacheGetAttr. + */ + datum = SysCacheGetAttr(TYPEOID, + typeTuple, + Anum_pg_type_typdefaultbin, + &isNull); + + if (!isNull) + { + /* We have an expression default */ + expr = stringToNode(TextDatumGetCString(datum)); + } + else + { + /* Perhaps we have a plain literal default */ + datum = SysCacheGetAttr(TYPEOID, + typeTuple, + Anum_pg_type_typdefault, + &isNull); + + if (!isNull) + { + char *strDefaultVal; + + /* Convert text datum to C string */ + strDefaultVal = TextDatumGetCString(datum); + /* Convert C string to a value of the given type */ + datum = OidInputFunctionCall(type->typinput, strDefaultVal, + getTypeIOParam(typeTuple), -1); + /* Build a Const node containing the value */ + expr = (Node *) makeConst(typid, + -1, + type->typcollation, + type->typlen, + datum, + false, + type->typbyval); + pfree(strDefaultVal); + } + else + { + /* No default */ + expr = NULL; + } + } + + ReleaseSysCache(typeTuple); + + return expr; +} + +/* + * getBaseType + * If the given type is a domain, return its base type; + * otherwise return the type's own OID. + */ +Oid +getBaseType(Oid typid) +{ + int32 typmod = -1; + + return getBaseTypeAndTypmod(typid, &typmod); +} + +/* + * getBaseTypeAndTypmod + * If the given type is a domain, return its base type and typmod; + * otherwise return the type's own OID, and leave *typmod unchanged. + * + * Note that the "applied typmod" should be -1 for every domain level + * above the bottommost; therefore, if the passed-in typid is indeed + * a domain, *typmod should be -1. + */ +Oid +getBaseTypeAndTypmod(Oid typid, int32 *typmod) +{ + /* + * We loop to find the bottom base type in a stack of domains. + */ + for (;;) + { + HeapTuple tup; + Form_pg_type typTup; + + tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid)); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for type %u", typid); + typTup = (Form_pg_type) GETSTRUCT(tup); + if (typTup->typtype != TYPTYPE_DOMAIN) + { + /* Not a domain, so done */ + ReleaseSysCache(tup); + break; + } + + Assert(*typmod == -1); + typid = typTup->typbasetype; + *typmod = typTup->typtypmod; + + ReleaseSysCache(tup); + } + + return typid; +} + +/* + * get_typavgwidth + * + * Given a type OID and a typmod value (pass -1 if typmod is unknown), + * estimate the average width of values of the type. This is used by + * the planner, which doesn't require absolutely correct results; + * it's OK (and expected) to guess if we don't know for sure. + */ +int32 +get_typavgwidth(Oid typid, int32 typmod) +{ + int typlen = get_typlen(typid); + int32 maxwidth; + + /* + * Easy if it's a fixed-width type + */ + if (typlen > 0) + return typlen; + + /* + * type_maximum_size knows the encoding of typmod for some datatypes; + * don't duplicate that knowledge here. + */ + maxwidth = type_maximum_size(typid, typmod); + if (maxwidth > 0) + { + /* + * For BPCHAR, the max width is also the only width. Otherwise we + * need to guess about the typical data width given the max. A sliding + * scale for percentage of max width seems reasonable. + */ + if (typid == BPCHAROID) + return maxwidth; + if (maxwidth <= 32) + return maxwidth; /* assume full width */ + if (maxwidth < 1000) + return 32 + (maxwidth - 32) / 2; /* assume 50% */ + + /* + * Beyond 1000, assume we're looking at something like + * "varchar(10000)" where the limit isn't actually reached often, and + * use a fixed estimate. + */ + return 32 + (1000 - 32) / 2; + } + + /* + * Oops, we have no idea ... wild guess time. + */ + return 32; +} + +/* + * get_typtype + * + * Given the type OID, find if it is a basic type, a complex type, etc. + * It returns the null char if the cache lookup fails... + */ +char +get_typtype(Oid typid) +{ + HeapTuple tp; + + tp = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid)); + if (HeapTupleIsValid(tp)) + { + Form_pg_type typtup = (Form_pg_type) GETSTRUCT(tp); + char result; + + result = typtup->typtype; + ReleaseSysCache(tp); + return result; + } + else + return '\0'; +} + +/* + * type_is_rowtype + * + * Convenience function to determine whether a type OID represents + * a "rowtype" type --- either RECORD or a named composite type + * (including a domain over a named composite type). + */ +bool +type_is_rowtype(Oid typid) +{ + if (typid == RECORDOID) + return true; /* easy case */ + switch (get_typtype(typid)) + { + case TYPTYPE_COMPOSITE: + return true; + case TYPTYPE_DOMAIN: + if (get_typtype(getBaseType(typid)) == TYPTYPE_COMPOSITE) + return true; + break; + default: + break; + } + return false; +} + +/* + * type_is_enum + * Returns true if the given type is an enum type. + */ +bool +type_is_enum(Oid typid) +{ + return (get_typtype(typid) == TYPTYPE_ENUM); +} + +/* + * type_is_range + * Returns true if the given type is a range type. + */ +bool +type_is_range(Oid typid) +{ + return (get_typtype(typid) == TYPTYPE_RANGE); +} + +/* + * type_is_multirange + * Returns true if the given type is a multirange type. + */ +bool +type_is_multirange(Oid typid) +{ + return (get_typtype(typid) == TYPTYPE_MULTIRANGE); +} + +/* + * get_type_category_preferred + * + * Given the type OID, fetch its category and preferred-type status. + * Throws error on failure. + */ +void +get_type_category_preferred(Oid typid, char *typcategory, bool *typispreferred) +{ + HeapTuple tp; + Form_pg_type typtup; + + tp = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid)); + if (!HeapTupleIsValid(tp)) + elog(ERROR, "cache lookup failed for type %u", typid); + typtup = (Form_pg_type) GETSTRUCT(tp); + *typcategory = typtup->typcategory; + *typispreferred = typtup->typispreferred; + ReleaseSysCache(tp); +} + +/* + * get_typ_typrelid + * + * Given the type OID, get the typrelid (InvalidOid if not a complex + * type). + */ +Oid +get_typ_typrelid(Oid typid) +{ + HeapTuple tp; + + tp = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid)); + if (HeapTupleIsValid(tp)) + { + Form_pg_type typtup = (Form_pg_type) GETSTRUCT(tp); + Oid result; + + result = typtup->typrelid; + ReleaseSysCache(tp); + return result; + } + else + return InvalidOid; +} + +/* + * get_element_type + * + * Given the type OID, get the typelem (InvalidOid if not an array type). + * + * NB: this only succeeds for "true" arrays having array_subscript_handler + * as typsubscript. For other types, InvalidOid is returned independently + * of whether they have typelem or typsubscript set. + */ +Oid +get_element_type(Oid typid) +{ + HeapTuple tp; + + tp = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid)); + if (HeapTupleIsValid(tp)) + { + Form_pg_type typtup = (Form_pg_type) GETSTRUCT(tp); + Oid result; + + if (IsTrueArrayType(typtup)) + result = typtup->typelem; + else + result = InvalidOid; + ReleaseSysCache(tp); + return result; + } + else + return InvalidOid; +} + +/* + * get_array_type + * + * Given the type OID, get the corresponding "true" array type. + * Returns InvalidOid if no array type can be found. + */ +Oid +get_array_type(Oid typid) +{ + HeapTuple tp; + Oid result = InvalidOid; + + tp = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid)); + if (HeapTupleIsValid(tp)) + { + result = ((Form_pg_type) GETSTRUCT(tp))->typarray; + ReleaseSysCache(tp); + } + return result; +} + +/* + * get_promoted_array_type + * + * The "promoted" type is what you'd get from an ARRAY(SELECT ...) + * construct, that is, either the corresponding "true" array type + * if the input is a scalar type that has such an array type, + * or the same type if the input is already a "true" array type. + * Returns InvalidOid if neither rule is satisfied. + */ +Oid +get_promoted_array_type(Oid typid) +{ + Oid array_type = get_array_type(typid); + + if (OidIsValid(array_type)) + return array_type; + if (OidIsValid(get_element_type(typid))) + return typid; + return InvalidOid; +} + +extern Oid get_base_element_type(Oid typid); + +/* + * get_base_element_type + * Given the type OID, get the typelem, looking "through" any domain + * to its underlying array type. + * + * This is equivalent to get_element_type(getBaseType(typid)), but avoids + * an extra cache lookup. Note that it fails to provide any information + * about the typmod of the array. + */ +Oid +get_base_element_type_original(Oid typid) +{ + /* + * We loop to find the bottom base type in a stack of domains. + */ + for (;;) + { + HeapTuple tup; + Form_pg_type typTup; + + tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid)); + if (!HeapTupleIsValid(tup)) + break; + typTup = (Form_pg_type) GETSTRUCT(tup); + if (typTup->typtype != TYPTYPE_DOMAIN) + { + /* Not a domain, so stop descending */ + Oid result; + + /* This test must match get_element_type */ + if (IsTrueArrayType(typTup)) + result = typTup->typelem; + else + result = InvalidOid; + ReleaseSysCache(tup); + return result; + } + + typid = typTup->typbasetype; + ReleaseSysCache(tup); + } + + /* Like get_element_type, silently return InvalidOid for bogus input */ + return InvalidOid; +} + +/* + * getTypeInputInfo + * + * Get info needed for converting values of a type to internal form + */ +void +getTypeInputInfo(Oid type, Oid *typInput, Oid *typIOParam) +{ + HeapTuple typeTuple; + Form_pg_type pt; + + typeTuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type)); + if (!HeapTupleIsValid(typeTuple)) + elog(ERROR, "cache lookup failed for type %u", type); + pt = (Form_pg_type) GETSTRUCT(typeTuple); + + if (!pt->typisdefined) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("type %s is only a shell", + format_type_be(type)))); + if (!OidIsValid(pt->typinput)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("no input function available for type %s", + format_type_be(type)))); + + *typInput = pt->typinput; + *typIOParam = getTypeIOParam(typeTuple); + + ReleaseSysCache(typeTuple); +} + +/* + * getTypeOutputInfo + * + * Get info needed for printing values of a type + */ +void +getTypeOutputInfo(Oid type, Oid *typOutput, bool *typIsVarlena) +{ + HeapTuple typeTuple; + Form_pg_type pt; + + typeTuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type)); + if (!HeapTupleIsValid(typeTuple)) + elog(ERROR, "cache lookup failed for type %u", type); + pt = (Form_pg_type) GETSTRUCT(typeTuple); + + if (!pt->typisdefined) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("type %s is only a shell", + format_type_be(type)))); + if (!OidIsValid(pt->typoutput)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("no output function available for type %s", + format_type_be(type)))); + + *typOutput = pt->typoutput; + *typIsVarlena = (!pt->typbyval) && (pt->typlen == -1); + + ReleaseSysCache(typeTuple); +} + +/* + * getTypeBinaryInputInfo + * + * Get info needed for binary input of values of a type + */ +void +getTypeBinaryInputInfo(Oid type, Oid *typReceive, Oid *typIOParam) +{ + HeapTuple typeTuple; + Form_pg_type pt; + + typeTuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type)); + if (!HeapTupleIsValid(typeTuple)) + elog(ERROR, "cache lookup failed for type %u", type); + pt = (Form_pg_type) GETSTRUCT(typeTuple); + + if (!pt->typisdefined) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("type %s is only a shell", + format_type_be(type)))); + if (!OidIsValid(pt->typreceive)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("no binary input function available for type %s", + format_type_be(type)))); + + *typReceive = pt->typreceive; + *typIOParam = getTypeIOParam(typeTuple); + + ReleaseSysCache(typeTuple); +} + +/* + * getTypeBinaryOutputInfo + * + * Get info needed for binary output of values of a type + */ +void +getTypeBinaryOutputInfo(Oid type, Oid *typSend, bool *typIsVarlena) +{ + HeapTuple typeTuple; + Form_pg_type pt; + + typeTuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type)); + if (!HeapTupleIsValid(typeTuple)) + elog(ERROR, "cache lookup failed for type %u", type); + pt = (Form_pg_type) GETSTRUCT(typeTuple); + + if (!pt->typisdefined) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("type %s is only a shell", + format_type_be(type)))); + if (!OidIsValid(pt->typsend)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("no binary output function available for type %s", + format_type_be(type)))); + + *typSend = pt->typsend; + *typIsVarlena = (!pt->typbyval) && (pt->typlen == -1); + + ReleaseSysCache(typeTuple); +} + +/* + * get_typmodin + * + * Given the type OID, return the type's typmodin procedure, if any. + */ +Oid +get_typmodin(Oid typid) +{ + HeapTuple tp; + + tp = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid)); + if (HeapTupleIsValid(tp)) + { + Form_pg_type typtup = (Form_pg_type) GETSTRUCT(tp); + Oid result; + + result = typtup->typmodin; + ReleaseSysCache(tp); + return result; + } + else + return InvalidOid; +} + +#ifdef NOT_USED +/* + * get_typmodout + * + * Given the type OID, return the type's typmodout procedure, if any. + */ +Oid +get_typmodout(Oid typid) +{ + HeapTuple tp; + + tp = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid)); + if (HeapTupleIsValid(tp)) + { + Form_pg_type typtup = (Form_pg_type) GETSTRUCT(tp); + Oid result; + + result = typtup->typmodout; + ReleaseSysCache(tp); + return result; + } + else + return InvalidOid; +} +#endif /* NOT_USED */ + +/* + * get_typcollation + * + * Given the type OID, return the type's typcollation attribute. + */ +Oid +get_typcollation(Oid typid) +{ + HeapTuple tp; + + tp = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid)); + if (HeapTupleIsValid(tp)) + { + Form_pg_type typtup = (Form_pg_type) GETSTRUCT(tp); + Oid result; + + result = typtup->typcollation; + ReleaseSysCache(tp); + return result; + } + else + return InvalidOid; +} + + +/* + * type_is_collatable + * + * Return whether the type cares about collations + */ +bool +type_is_collatable(Oid typid) +{ + return OidIsValid(get_typcollation(typid)); +} + + +/* + * get_typsubscript + * + * Given the type OID, return the type's subscripting handler's OID, + * if it has one. + * + * If typelemp isn't NULL, we also store the type's typelem value there. + * This saves some callers an extra catalog lookup. + */ +RegProcedure +get_typsubscript(Oid typid, Oid *typelemp) +{ + HeapTuple tp; + + tp = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid)); + if (HeapTupleIsValid(tp)) + { + Form_pg_type typform = (Form_pg_type) GETSTRUCT(tp); + RegProcedure handler = typform->typsubscript; + + if (typelemp) + *typelemp = typform->typelem; + ReleaseSysCache(tp); + return handler; + } + else + { + if (typelemp) + *typelemp = InvalidOid; + return InvalidOid; + } +} + +/* + * getSubscriptingRoutines + * + * Given the type OID, fetch the type's subscripting methods struct. + * Return NULL if type is not subscriptable. + * + * If typelemp isn't NULL, we also store the type's typelem value there. + * This saves some callers an extra catalog lookup. + */ +const struct SubscriptRoutines * +getSubscriptingRoutines(Oid typid, Oid *typelemp) +{ + RegProcedure typsubscript = get_typsubscript(typid, typelemp); + + if (!OidIsValid(typsubscript)) + return NULL; + + return (const struct SubscriptRoutines *) + DatumGetPointer(OidFunctionCall0(typsubscript)); +} + + +/* ---------- STATISTICS CACHE ---------- */ + +/* + * get_attavgwidth + * + * Given the table and attribute number of a column, get the average + * width of entries in the column. Return zero if no data available. + * + * Currently this is only consulted for individual tables, not for inheritance + * trees, so we don't need an "inh" parameter. + * + * Calling a hook at this point looks somewhat strange, but is required + * because the optimizer calls this function without any other way for + * plug-ins to control the result. + */ +int32 +get_attavgwidth(Oid relid, AttrNumber attnum) +{ + HeapTuple tp; + int32 stawidth; + + if (get_attavgwidth_hook) + { + stawidth = (*get_attavgwidth_hook) (relid, attnum); + if (stawidth > 0) + return stawidth; + } + tp = SearchSysCache3(STATRELATTINH, + ObjectIdGetDatum(relid), + Int16GetDatum(attnum), + BoolGetDatum(false)); + if (HeapTupleIsValid(tp)) + { + stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth; + ReleaseSysCache(tp); + if (stawidth > 0) + return stawidth; + } + return 0; +} + +/* + * get_attstatsslot + * + * Extract the contents of a "slot" of a pg_statistic tuple. + * Returns true if requested slot type was found, else false. + * + * Unlike other routines in this file, this takes a pointer to an + * already-looked-up tuple in the pg_statistic cache. We do this since + * most callers will want to extract more than one value from the cache + * entry, and we don't want to repeat the cache lookup unnecessarily. + * Also, this API allows this routine to be used with statistics tuples + * that have been provided by a stats hook and didn't really come from + * pg_statistic. + * + * sslot: pointer to output area (typically, a local variable in the caller). + * statstuple: pg_statistic tuple to be examined. + * reqkind: STAKIND code for desired statistics slot kind. + * reqop: STAOP value wanted, or InvalidOid if don't care. + * flags: bitmask of ATTSTATSSLOT_VALUES and/or ATTSTATSSLOT_NUMBERS. + * + * If a matching slot is found, true is returned, and *sslot is filled thus: + * staop: receives the actual STAOP value. + * stacoll: receives the actual STACOLL value. + * valuetype: receives actual datatype of the elements of stavalues. + * values: receives pointer to an array of the slot's stavalues. + * nvalues: receives number of stavalues. + * numbers: receives pointer to an array of the slot's stanumbers (as float4). + * nnumbers: receives number of stanumbers. + * + * valuetype/values/nvalues are InvalidOid/NULL/0 if ATTSTATSSLOT_VALUES + * wasn't specified. Likewise, numbers/nnumbers are NULL/0 if + * ATTSTATSSLOT_NUMBERS wasn't specified. + * + * If no matching slot is found, false is returned, and *sslot is zeroed. + * + * Note that the current API doesn't allow for searching for a slot with + * a particular collation. If we ever actually support recording more than + * one collation, we'll have to extend the API, but for now simple is good. + * + * The data referred to by the fields of sslot is locally palloc'd and + * is independent of the original pg_statistic tuple. When the caller + * is done with it, call free_attstatsslot to release the palloc'd data. + * + * If it's desirable to call free_attstatsslot when get_attstatsslot might + * not have been called, memset'ing sslot to zeroes will allow that. + * + * Passing flags=0 can be useful to quickly check if the requested slot type + * exists. In this case no arrays are extracted, so free_attstatsslot need + * not be called. + */ +bool +get_attstatsslot(AttStatsSlot *sslot, HeapTuple statstuple, + int reqkind, Oid reqop, int flags) +{ + Form_pg_statistic stats = (Form_pg_statistic) GETSTRUCT(statstuple); + int i; + Datum val; + ArrayType *statarray; + Oid arrayelemtype; + int narrayelem; + HeapTuple typeTuple; + Form_pg_type typeForm; + + /* initialize *sslot properly */ + memset(sslot, 0, sizeof(AttStatsSlot)); + + for (i = 0; i < STATISTIC_NUM_SLOTS; i++) + { + if ((&stats->stakind1)[i] == reqkind && + (reqop == InvalidOid || (&stats->staop1)[i] == reqop)) + break; + } + if (i >= STATISTIC_NUM_SLOTS) + return false; /* not there */ + + sslot->staop = (&stats->staop1)[i]; + sslot->stacoll = (&stats->stacoll1)[i]; + + if (flags & ATTSTATSSLOT_VALUES) + { + val = SysCacheGetAttrNotNull(STATRELATTINH, statstuple, + Anum_pg_statistic_stavalues1 + i); + + /* + * Detoast the array if needed, and in any case make a copy that's + * under control of this AttStatsSlot. + */ + statarray = DatumGetArrayTypePCopy(val); + + /* + * Extract the actual array element type, and pass it back in case the + * caller needs it. + */ + sslot->valuetype = arrayelemtype = ARR_ELEMTYPE(statarray); + + /* Need info about element type */ + typeTuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(arrayelemtype)); + if (!HeapTupleIsValid(typeTuple)) + elog(ERROR, "cache lookup failed for type %u", arrayelemtype); + typeForm = (Form_pg_type) GETSTRUCT(typeTuple); + + /* Deconstruct array into Datum elements; NULLs not expected */ + deconstruct_array(statarray, + arrayelemtype, + typeForm->typlen, + typeForm->typbyval, + typeForm->typalign, + &sslot->values, NULL, &sslot->nvalues); + + /* + * If the element type is pass-by-reference, we now have a bunch of + * Datums that are pointers into the statarray, so we need to keep + * that until free_attstatsslot. Otherwise, all the useful info is in + * sslot->values[], so we can free the array object immediately. + */ + if (!typeForm->typbyval) + sslot->values_arr = statarray; + else + pfree(statarray); + + ReleaseSysCache(typeTuple); + } + + if (flags & ATTSTATSSLOT_NUMBERS) + { + val = SysCacheGetAttrNotNull(STATRELATTINH, statstuple, + Anum_pg_statistic_stanumbers1 + i); + + /* + * Detoast the array if needed, and in any case make a copy that's + * under control of this AttStatsSlot. + */ + statarray = DatumGetArrayTypePCopy(val); + + /* + * We expect the array to be a 1-D float4 array; verify that. We don't + * need to use deconstruct_array() since the array data is just going + * to look like a C array of float4 values. + */ + narrayelem = ARR_DIMS(statarray)[0]; + if (ARR_NDIM(statarray) != 1 || narrayelem <= 0 || + ARR_HASNULL(statarray) || + ARR_ELEMTYPE(statarray) != FLOAT4OID) + elog(ERROR, "stanumbers is not a 1-D float4 array"); + + /* Give caller a pointer directly into the statarray */ + sslot->numbers = (float4 *) ARR_DATA_PTR(statarray); + sslot->nnumbers = narrayelem; + + /* We'll free the statarray in free_attstatsslot */ + sslot->numbers_arr = statarray; + } + + return true; +} + +/* + * free_attstatsslot + * Free data allocated by get_attstatsslot + */ +void +free_attstatsslot(AttStatsSlot *sslot) +{ + /* The values[] array was separately palloc'd by deconstruct_array */ + if (sslot->values) + pfree(sslot->values); + /* The numbers[] array points into numbers_arr, do not pfree it */ + /* Free the detoasted array objects, if any */ + if (sslot->values_arr) + pfree(sslot->values_arr); + if (sslot->numbers_arr) + pfree(sslot->numbers_arr); +} + +/* ---------- PG_NAMESPACE CACHE ---------- */ + +/* + * get_namespace_name + * Returns the name of a given namespace + * + * Returns a palloc'd copy of the string, or NULL if no such namespace. + */ +char * +get_namespace_name(Oid nspid) +{ + HeapTuple tp; + + tp = SearchSysCache1(NAMESPACEOID, ObjectIdGetDatum(nspid)); + if (HeapTupleIsValid(tp)) + { + Form_pg_namespace nsptup = (Form_pg_namespace) GETSTRUCT(tp); + char *result; + + result = pstrdup(NameStr(nsptup->nspname)); + ReleaseSysCache(tp); + return result; + } + else + return NULL; +} + +/* + * get_namespace_name_or_temp + * As above, but if it is this backend's temporary namespace, return + * "pg_temp" instead. + */ +char * +get_namespace_name_or_temp(Oid nspid) +{ + if (isTempNamespace(nspid)) + return pstrdup("pg_temp"); + else + return get_namespace_name(nspid); +} + +/* ---------- PG_RANGE CACHES ---------- */ + +/* + * get_range_subtype + * Returns the subtype of a given range type + * + * Returns InvalidOid if the type is not a range type. + */ +Oid +get_range_subtype(Oid rangeOid) +{ + HeapTuple tp; + + tp = SearchSysCache1(RANGETYPE, ObjectIdGetDatum(rangeOid)); + if (HeapTupleIsValid(tp)) + { + Form_pg_range rngtup = (Form_pg_range) GETSTRUCT(tp); + Oid result; + + result = rngtup->rngsubtype; + ReleaseSysCache(tp); + return result; + } + else + return InvalidOid; +} + +/* + * get_range_collation + * Returns the collation of a given range type + * + * Returns InvalidOid if the type is not a range type, + * or if its subtype is not collatable. + */ +Oid +get_range_collation(Oid rangeOid) +{ + HeapTuple tp; + + tp = SearchSysCache1(RANGETYPE, ObjectIdGetDatum(rangeOid)); + if (HeapTupleIsValid(tp)) + { + Form_pg_range rngtup = (Form_pg_range) GETSTRUCT(tp); + Oid result; + + result = rngtup->rngcollation; + ReleaseSysCache(tp); + return result; + } + else + return InvalidOid; +} + +/* + * get_range_multirange + * Returns the multirange type of a given range type + * + * Returns InvalidOid if the type is not a range type. + */ +Oid +get_range_multirange(Oid rangeOid) +{ + HeapTuple tp; + + tp = SearchSysCache1(RANGETYPE, ObjectIdGetDatum(rangeOid)); + if (HeapTupleIsValid(tp)) + { + Form_pg_range rngtup = (Form_pg_range) GETSTRUCT(tp); + Oid result; + + result = rngtup->rngmultitypid; + ReleaseSysCache(tp); + return result; + } + else + return InvalidOid; +} + +/* + * get_multirange_range + * Returns the range type of a given multirange + * + * Returns InvalidOid if the type is not a multirange. + */ +Oid +get_multirange_range(Oid multirangeOid) +{ + HeapTuple tp; + + tp = SearchSysCache1(RANGEMULTIRANGE, ObjectIdGetDatum(multirangeOid)); + if (HeapTupleIsValid(tp)) + { + Form_pg_range rngtup = (Form_pg_range) GETSTRUCT(tp); + Oid result; + + result = rngtup->rngtypid; + ReleaseSysCache(tp); + return result; + } + else + return InvalidOid; +} + +/* ---------- PG_INDEX CACHE ---------- */ + +/* + * get_index_column_opclass + * + * Given the index OID and column number, + * return opclass of the index column + * or InvalidOid if the index was not found + * or column is non-key one. + */ +Oid +get_index_column_opclass(Oid index_oid, int attno) +{ + HeapTuple tuple; + Form_pg_index rd_index; + Datum datum; + oidvector *indclass; + Oid opclass; + + /* First we need to know the column's opclass. */ + + tuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(index_oid)); + if (!HeapTupleIsValid(tuple)) + return InvalidOid; + + rd_index = (Form_pg_index) GETSTRUCT(tuple); + + /* caller is supposed to guarantee this */ + Assert(attno > 0 && attno <= rd_index->indnatts); + + /* Non-key attributes don't have an opclass */ + if (attno > rd_index->indnkeyatts) + { + ReleaseSysCache(tuple); + return InvalidOid; + } + + datum = SysCacheGetAttrNotNull(INDEXRELID, tuple, Anum_pg_index_indclass); + indclass = ((oidvector *) DatumGetPointer(datum)); + + Assert(attno <= indclass->dim1); + opclass = indclass->values[attno - 1]; + + ReleaseSysCache(tuple); + + return opclass; +} + +/* + * get_index_isreplident + * + * Given the index OID, return pg_index.indisreplident. + */ +bool +get_index_isreplident(Oid index_oid) +{ + HeapTuple tuple; + Form_pg_index rd_index; + bool result; + + tuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(index_oid)); + if (!HeapTupleIsValid(tuple)) + return false; + + rd_index = (Form_pg_index) GETSTRUCT(tuple); + result = rd_index->indisreplident; + ReleaseSysCache(tuple); + + return result; +} + +/* + * get_index_isvalid + * + * Given the index OID, return pg_index.indisvalid. + */ +bool +get_index_isvalid(Oid index_oid) +{ + bool isvalid; + HeapTuple tuple; + Form_pg_index rd_index; + + tuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(index_oid)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for index %u", index_oid); + + rd_index = (Form_pg_index) GETSTRUCT(tuple); + isvalid = rd_index->indisvalid; + ReleaseSysCache(tuple); + + return isvalid; +} + +/* + * get_index_isclustered + * + * Given the index OID, return pg_index.indisclustered. + */ +bool +get_index_isclustered(Oid index_oid) +{ + bool isclustered; + HeapTuple tuple; + Form_pg_index rd_index; + + tuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(index_oid)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for index %u", index_oid); + + rd_index = (Form_pg_index) GETSTRUCT(tuple); + isclustered = rd_index->indisclustered; + ReleaseSysCache(tuple); + + return isclustered; +} + +/* + * get_publication_oid - given a publication name, look up the OID + * + * If missing_ok is false, throw an error if name not found. If true, just + * return InvalidOid. + */ +Oid +get_publication_oid(const char *pubname, bool missing_ok) +{ + Oid oid; + + oid = GetSysCacheOid1(PUBLICATIONNAME, Anum_pg_publication_oid, + CStringGetDatum(pubname)); + if (!OidIsValid(oid) && !missing_ok) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("publication \"%s\" does not exist", pubname))); + return oid; +} + +/* + * get_publication_name - given a publication Oid, look up the name + * + * If missing_ok is false, throw an error if name not found. If true, just + * return NULL. + */ +char * +get_publication_name(Oid pubid, bool missing_ok) +{ + HeapTuple tup; + char *pubname; + Form_pg_publication pubform; + + tup = SearchSysCache1(PUBLICATIONOID, ObjectIdGetDatum(pubid)); + + if (!HeapTupleIsValid(tup)) + { + if (!missing_ok) + elog(ERROR, "cache lookup failed for publication %u", pubid); + return NULL; + } + + pubform = (Form_pg_publication) GETSTRUCT(tup); + pubname = pstrdup(NameStr(pubform->pubname)); + + ReleaseSysCache(tup); + + return pubname; +} + +/* + * get_subscription_oid - given a subscription name, look up the OID + * + * If missing_ok is false, throw an error if name not found. If true, just + * return InvalidOid. + */ +Oid +get_subscription_oid(const char *subname, bool missing_ok) +{ + Oid oid; + + oid = GetSysCacheOid2(SUBSCRIPTIONNAME, Anum_pg_subscription_oid, + MyDatabaseId, CStringGetDatum(subname)); + if (!OidIsValid(oid) && !missing_ok) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("subscription \"%s\" does not exist", subname))); + return oid; +} + +/* + * get_subscription_name - given a subscription OID, look up the name + * + * If missing_ok is false, throw an error if name not found. If true, just + * return NULL. + */ +char * +get_subscription_name(Oid subid, bool missing_ok) +{ + HeapTuple tup; + char *subname; + Form_pg_subscription subform; + + tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid)); + + if (!HeapTupleIsValid(tup)) + { + if (!missing_ok) + elog(ERROR, "cache lookup failed for subscription %u", subid); + return NULL; + } + + subform = (Form_pg_subscription) GETSTRUCT(tup); + subname = pstrdup(NameStr(subform->subname)); + + ReleaseSysCache(tup); + + return subname; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/cache/partcache.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/cache/partcache.c new file mode 100644 index 00000000000..5f3516ad0c2 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/cache/partcache.c @@ -0,0 +1,434 @@ +/*------------------------------------------------------------------------- + * + * partcache.c + * Support routines for manipulating partition information cached in + * relcache + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/utils/cache/partcache.c + * + *------------------------------------------------------------------------- +*/ +#include "postgres.h" + +#include "access/hash.h" +#include "access/htup_details.h" +#include "access/nbtree.h" +#include "access/relation.h" +#include "catalog/partition.h" +#include "catalog/pg_inherits.h" +#include "catalog/pg_opclass.h" +#include "catalog/pg_partitioned_table.h" +#include "miscadmin.h" +#include "nodes/makefuncs.h" +#include "nodes/nodeFuncs.h" +#include "optimizer/optimizer.h" +#include "partitioning/partbounds.h" +#include "rewrite/rewriteHandler.h" +#include "utils/builtins.h" +#include "utils/datum.h" +#include "utils/lsyscache.h" +#include "utils/memutils.h" +#include "utils/partcache.h" +#include "utils/rel.h" +#include "utils/syscache.h" + + +static void RelationBuildPartitionKey(Relation relation); +static List *generate_partition_qual(Relation rel); + +/* + * RelationGetPartitionKey -- get partition key, if relation is partitioned + * + * Note: partition keys are not allowed to change after the partitioned rel + * is created. RelationClearRelation knows this and preserves rd_partkey + * across relcache rebuilds, as long as the relation is open. Therefore, + * even though we hand back a direct pointer into the relcache entry, it's + * safe for callers to continue to use that pointer as long as they hold + * the relation open. + */ +PartitionKey +RelationGetPartitionKey(Relation rel) +{ + if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE) + return NULL; + + if (unlikely(rel->rd_partkey == NULL)) + RelationBuildPartitionKey(rel); + + return rel->rd_partkey; +} + +/* + * RelationBuildPartitionKey + * Build partition key data of relation, and attach to relcache + * + * Partitioning key data is a complex structure; to avoid complicated logic to + * free individual elements whenever the relcache entry is flushed, we give it + * its own memory context, a child of CacheMemoryContext, which can easily be + * deleted on its own. To avoid leaking memory in that context in case of an + * error partway through this function, the context is initially created as a + * child of CurTransactionContext and only re-parented to CacheMemoryContext + * at the end, when no further errors are possible. Also, we don't make this + * context the current context except in very brief code sections, out of fear + * that some of our callees allocate memory on their own which would be leaked + * permanently. + */ +static void +RelationBuildPartitionKey(Relation relation) +{ + Form_pg_partitioned_table form; + HeapTuple tuple; + bool isnull; + int i; + PartitionKey key; + AttrNumber *attrs; + oidvector *opclass; + oidvector *collation; + ListCell *partexprs_item; + Datum datum; + MemoryContext partkeycxt, + oldcxt; + int16 procnum; + + tuple = SearchSysCache1(PARTRELID, + ObjectIdGetDatum(RelationGetRelid(relation))); + + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for partition key of relation %u", + RelationGetRelid(relation)); + + partkeycxt = AllocSetContextCreate(CurTransactionContext, + "partition key", + ALLOCSET_SMALL_SIZES); + MemoryContextCopyAndSetIdentifier(partkeycxt, + RelationGetRelationName(relation)); + + key = (PartitionKey) MemoryContextAllocZero(partkeycxt, + sizeof(PartitionKeyData)); + + /* Fixed-length attributes */ + form = (Form_pg_partitioned_table) GETSTRUCT(tuple); + key->strategy = form->partstrat; + key->partnatts = form->partnatts; + + /* Validate partition strategy code */ + if (key->strategy != PARTITION_STRATEGY_LIST && + key->strategy != PARTITION_STRATEGY_RANGE && + key->strategy != PARTITION_STRATEGY_HASH) + elog(ERROR, "invalid partition strategy \"%c\"", key->strategy); + + /* + * We can rely on the first variable-length attribute being mapped to the + * relevant field of the catalog's C struct, because all previous + * attributes are non-nullable and fixed-length. + */ + attrs = form->partattrs.values; + + /* But use the hard way to retrieve further variable-length attributes */ + /* Operator class */ + datum = SysCacheGetAttrNotNull(PARTRELID, tuple, + Anum_pg_partitioned_table_partclass); + opclass = (oidvector *) DatumGetPointer(datum); + + /* Collation */ + datum = SysCacheGetAttrNotNull(PARTRELID, tuple, + Anum_pg_partitioned_table_partcollation); + collation = (oidvector *) DatumGetPointer(datum); + + /* Expressions */ + datum = SysCacheGetAttr(PARTRELID, tuple, + Anum_pg_partitioned_table_partexprs, &isnull); + if (!isnull) + { + char *exprString; + Node *expr; + + exprString = TextDatumGetCString(datum); + expr = stringToNode(exprString); + pfree(exprString); + + /* + * Run the expressions through const-simplification since the planner + * will be comparing them to similarly-processed qual clause operands, + * and may fail to detect valid matches without this step; fix + * opfuncids while at it. We don't need to bother with + * canonicalize_qual() though, because partition expressions should be + * in canonical form already (ie, no need for OR-merging or constant + * elimination). + */ + expr = eval_const_expressions(NULL, expr); + fix_opfuncids(expr); + + oldcxt = MemoryContextSwitchTo(partkeycxt); + key->partexprs = (List *) copyObject(expr); + MemoryContextSwitchTo(oldcxt); + } + + /* Allocate assorted arrays in the partkeycxt, which we'll fill below */ + oldcxt = MemoryContextSwitchTo(partkeycxt); + key->partattrs = (AttrNumber *) palloc0(key->partnatts * sizeof(AttrNumber)); + key->partopfamily = (Oid *) palloc0(key->partnatts * sizeof(Oid)); + key->partopcintype = (Oid *) palloc0(key->partnatts * sizeof(Oid)); + key->partsupfunc = (FmgrInfo *) palloc0(key->partnatts * sizeof(FmgrInfo)); + + key->partcollation = (Oid *) palloc0(key->partnatts * sizeof(Oid)); + key->parttypid = (Oid *) palloc0(key->partnatts * sizeof(Oid)); + key->parttypmod = (int32 *) palloc0(key->partnatts * sizeof(int32)); + key->parttyplen = (int16 *) palloc0(key->partnatts * sizeof(int16)); + key->parttypbyval = (bool *) palloc0(key->partnatts * sizeof(bool)); + key->parttypalign = (char *) palloc0(key->partnatts * sizeof(char)); + key->parttypcoll = (Oid *) palloc0(key->partnatts * sizeof(Oid)); + MemoryContextSwitchTo(oldcxt); + + /* determine support function number to search for */ + procnum = (key->strategy == PARTITION_STRATEGY_HASH) ? + HASHEXTENDED_PROC : BTORDER_PROC; + + /* Copy partattrs and fill other per-attribute info */ + memcpy(key->partattrs, attrs, key->partnatts * sizeof(int16)); + partexprs_item = list_head(key->partexprs); + for (i = 0; i < key->partnatts; i++) + { + AttrNumber attno = key->partattrs[i]; + HeapTuple opclasstup; + Form_pg_opclass opclassform; + Oid funcid; + + /* Collect opfamily information */ + opclasstup = SearchSysCache1(CLAOID, + ObjectIdGetDatum(opclass->values[i])); + if (!HeapTupleIsValid(opclasstup)) + elog(ERROR, "cache lookup failed for opclass %u", opclass->values[i]); + + opclassform = (Form_pg_opclass) GETSTRUCT(opclasstup); + key->partopfamily[i] = opclassform->opcfamily; + key->partopcintype[i] = opclassform->opcintype; + + /* Get a support function for the specified opfamily and datatypes */ + funcid = get_opfamily_proc(opclassform->opcfamily, + opclassform->opcintype, + opclassform->opcintype, + procnum); + if (!OidIsValid(funcid)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("operator class \"%s\" of access method %s is missing support function %d for type %s", + NameStr(opclassform->opcname), + (key->strategy == PARTITION_STRATEGY_HASH) ? + "hash" : "btree", + procnum, + format_type_be(opclassform->opcintype)))); + + fmgr_info_cxt(funcid, &key->partsupfunc[i], partkeycxt); + + /* Collation */ + key->partcollation[i] = collation->values[i]; + + /* Collect type information */ + if (attno != 0) + { + Form_pg_attribute att = TupleDescAttr(relation->rd_att, attno - 1); + + key->parttypid[i] = att->atttypid; + key->parttypmod[i] = att->atttypmod; + key->parttypcoll[i] = att->attcollation; + } + else + { + if (partexprs_item == NULL) + elog(ERROR, "wrong number of partition key expressions"); + + key->parttypid[i] = exprType(lfirst(partexprs_item)); + key->parttypmod[i] = exprTypmod(lfirst(partexprs_item)); + key->parttypcoll[i] = exprCollation(lfirst(partexprs_item)); + + partexprs_item = lnext(key->partexprs, partexprs_item); + } + get_typlenbyvalalign(key->parttypid[i], + &key->parttyplen[i], + &key->parttypbyval[i], + &key->parttypalign[i]); + + ReleaseSysCache(opclasstup); + } + + ReleaseSysCache(tuple); + + /* Assert that we're not leaking any old data during assignments below */ + Assert(relation->rd_partkeycxt == NULL); + Assert(relation->rd_partkey == NULL); + + /* + * Success --- reparent our context and make the relcache point to the + * newly constructed key + */ + MemoryContextSetParent(partkeycxt, CacheMemoryContext); + relation->rd_partkeycxt = partkeycxt; + relation->rd_partkey = key; +} + +/* + * RelationGetPartitionQual + * + * Returns a list of partition quals + */ +List * +RelationGetPartitionQual(Relation rel) +{ + /* Quick exit */ + if (!rel->rd_rel->relispartition) + return NIL; + + return generate_partition_qual(rel); +} + +/* + * get_partition_qual_relid + * + * Returns an expression tree describing the passed-in relation's partition + * constraint. + * + * If the relation is not found, or is not a partition, or there is no + * partition constraint, return NULL. We must guard against the first two + * cases because this supports a SQL function that could be passed any OID. + * The last case can happen even if relispartition is true, when a default + * partition is the only partition. + */ +Expr * +get_partition_qual_relid(Oid relid) +{ + Expr *result = NULL; + + /* Do the work only if this relation exists and is a partition. */ + if (get_rel_relispartition(relid)) + { + Relation rel = relation_open(relid, AccessShareLock); + List *and_args; + + and_args = generate_partition_qual(rel); + + /* Convert implicit-AND list format to boolean expression */ + if (and_args == NIL) + result = NULL; + else if (list_length(and_args) > 1) + result = makeBoolExpr(AND_EXPR, and_args, -1); + else + result = linitial(and_args); + + /* Keep the lock, to allow safe deparsing against the rel by caller. */ + relation_close(rel, NoLock); + } + + return result; +} + +/* + * generate_partition_qual + * + * Generate partition predicate from rel's partition bound expression. The + * function returns a NIL list if there is no predicate. + * + * We cache a copy of the result in the relcache entry, after constructing + * it using the caller's context. This approach avoids leaking any data + * into long-lived cache contexts, especially if we fail partway through. + */ +static List * +generate_partition_qual(Relation rel) +{ + HeapTuple tuple; + MemoryContext oldcxt; + Datum boundDatum; + bool isnull; + List *my_qual = NIL, + *result = NIL; + Oid parentrelid; + Relation parent; + + /* Guard against stack overflow due to overly deep partition tree */ + check_stack_depth(); + + /* If we already cached the result, just return a copy */ + if (rel->rd_partcheckvalid) + return copyObject(rel->rd_partcheck); + + /* + * Grab at least an AccessShareLock on the parent table. Must do this + * even if the partition has been partially detached, because transactions + * concurrent with the detach might still be trying to use a partition + * descriptor that includes it. + */ + parentrelid = get_partition_parent(RelationGetRelid(rel), true); + parent = relation_open(parentrelid, AccessShareLock); + + /* Get pg_class.relpartbound */ + tuple = SearchSysCache1(RELOID, RelationGetRelid(rel)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for relation %u", + RelationGetRelid(rel)); + + boundDatum = SysCacheGetAttr(RELOID, tuple, + Anum_pg_class_relpartbound, + &isnull); + if (!isnull) + { + PartitionBoundSpec *bound; + + bound = castNode(PartitionBoundSpec, + stringToNode(TextDatumGetCString(boundDatum))); + + my_qual = get_qual_from_partbound(parent, bound); + } + + ReleaseSysCache(tuple); + + /* Add the parent's quals to the list (if any) */ + if (parent->rd_rel->relispartition) + result = list_concat(generate_partition_qual(parent), my_qual); + else + result = my_qual; + + /* + * Change Vars to have partition's attnos instead of the parent's. We do + * this after we concatenate the parent's quals, because we want every Var + * in it to bear this relation's attnos. It's safe to assume varno = 1 + * here. + */ + result = map_partition_varattnos(result, 1, rel, parent); + + /* Assert that we're not leaking any old data during assignments below */ + Assert(rel->rd_partcheckcxt == NULL); + Assert(rel->rd_partcheck == NIL); + + /* + * Save a copy in the relcache. The order of these operations is fairly + * critical to avoid memory leaks and ensure that we don't leave a corrupt + * relcache entry if we fail partway through copyObject. + * + * If, as is definitely possible, the partcheck list is NIL, then we do + * not need to make a context to hold it. + */ + if (result != NIL) + { + rel->rd_partcheckcxt = AllocSetContextCreate(CacheMemoryContext, + "partition constraint", + ALLOCSET_SMALL_SIZES); + MemoryContextCopyAndSetIdentifier(rel->rd_partcheckcxt, + RelationGetRelationName(rel)); + oldcxt = MemoryContextSwitchTo(rel->rd_partcheckcxt); + rel->rd_partcheck = copyObject(result); + MemoryContextSwitchTo(oldcxt); + } + else + rel->rd_partcheck = NIL; + rel->rd_partcheckvalid = true; + + /* Keep the parent locked until commit */ + relation_close(parent, NoLock); + + /* Return the working copy to the caller */ + return result; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/cache/plancache.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/cache/plancache.c new file mode 100644 index 00000000000..5041490853a --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/cache/plancache.c @@ -0,0 +1,2205 @@ +/*------------------------------------------------------------------------- + * + * plancache.c + * Plan cache management. + * + * The plan cache manager has two principal responsibilities: deciding when + * to use a generic plan versus a custom (parameter-value-specific) plan, + * and tracking whether cached plans need to be invalidated because of schema + * changes in the objects they depend on. + * + * The logic for choosing generic or custom plans is in choose_custom_plan, + * which see for comments. + * + * Cache invalidation is driven off sinval events. Any CachedPlanSource + * that matches the event is marked invalid, as is its generic CachedPlan + * if it has one. When (and if) the next demand for a cached plan occurs, + * parse analysis and rewrite is repeated to build a new valid query tree, + * and then planning is performed as normal. We also force re-analysis and + * re-planning if the active search_path is different from the previous time + * or, if RLS is involved, if the user changes or the RLS environment changes. + * + * Note that if the sinval was a result of user DDL actions, parse analysis + * could throw an error, for example if a column referenced by the query is + * no longer present. Another possibility is for the query's output tupdesc + * to change (for instance "SELECT *" might expand differently than before). + * The creator of a cached plan can specify whether it is allowable for the + * query to change output tupdesc on replan --- if so, it's up to the + * caller to notice changes and cope with them. + * + * Currently, we track exactly the dependencies of plans on relations, + * user-defined functions, and domains. On relcache invalidation events or + * pg_proc or pg_type syscache invalidation events, we invalidate just those + * plans that depend on the particular object being modified. (Note: this + * scheme assumes that any table modification that requires replanning will + * generate a relcache inval event.) We also watch for inval events on + * certain other system catalogs, such as pg_namespace; but for them, our + * response is just to invalidate all plans. We expect updates on those + * catalogs to be infrequent enough that more-detailed tracking is not worth + * the effort. + * + * In addition to full-fledged query plans, we provide a facility for + * detecting invalidations of simple scalar expressions. This is fairly + * bare-bones; it's the caller's responsibility to build a new expression + * if the old one gets invalidated. + * + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/utils/cache/plancache.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <limits.h> + +#include "access/transam.h" +#include "catalog/namespace.h" +#include "executor/executor.h" +#include "miscadmin.h" +#include "nodes/nodeFuncs.h" +#include "optimizer/optimizer.h" +#include "parser/analyze.h" +#include "parser/parsetree.h" +#include "storage/lmgr.h" +#include "tcop/pquery.h" +#include "tcop/utility.h" +#include "utils/inval.h" +#include "utils/memutils.h" +#include "utils/resowner_private.h" +#include "utils/rls.h" +#include "utils/snapmgr.h" +#include "utils/syscache.h" + + +/* + * We must skip "overhead" operations that involve database access when the + * cached plan's subject statement is a transaction control command or one + * that requires a snapshot not to be set yet (such as SET or LOCK). More + * generally, statements that do not require parse analysis/rewrite/plan + * activity never need to be revalidated, so we can treat them all like that. + * For the convenience of postgres.c, treat empty statements that way too. + */ +#define StmtPlanRequiresRevalidation(plansource) \ + ((plansource)->raw_parse_tree != NULL && \ + stmt_requires_parse_analysis((plansource)->raw_parse_tree)) + +/* + * This is the head of the backend's list of "saved" CachedPlanSources (i.e., + * those that are in long-lived storage and are examined for sinval events). + * We use a dlist instead of separate List cells so that we can guarantee + * to save a CachedPlanSource without error. + */ +static __thread dlist_head saved_plan_list ;void saved_plan_list_init(void) { dlist_init(&saved_plan_list); } + +/* + * This is the head of the backend's list of CachedExpressions. + */ +static __thread dlist_head cached_expression_list ;void cached_expression_list_init(void) { dlist_init(&cached_expression_list); } + +static void ReleaseGenericPlan(CachedPlanSource *plansource); +static List *RevalidateCachedQuery(CachedPlanSource *plansource, + QueryEnvironment *queryEnv); +static bool CheckCachedPlan(CachedPlanSource *plansource); +static CachedPlan *BuildCachedPlan(CachedPlanSource *plansource, List *qlist, + ParamListInfo boundParams, QueryEnvironment *queryEnv); +static bool choose_custom_plan(CachedPlanSource *plansource, + ParamListInfo boundParams); +static double cached_plan_cost(CachedPlan *plan, bool include_planner); +static Query *QueryListGetPrimaryStmt(List *stmts); +static void AcquireExecutorLocks(List *stmt_list, bool acquire); +static void AcquirePlannerLocks(List *stmt_list, bool acquire); +static void ScanQueryForLocks(Query *parsetree, bool acquire); +static bool ScanQueryWalker(Node *node, bool *acquire); +static TupleDesc PlanCacheComputeResultDesc(List *stmt_list); +static void PlanCacheRelCallback(Datum arg, Oid relid); +static void PlanCacheObjectCallback(Datum arg, int cacheid, uint32 hashvalue); +static void PlanCacheSysCallback(Datum arg, int cacheid, uint32 hashvalue); + +/* GUC parameter */ +__thread int plan_cache_mode = PLAN_CACHE_MODE_AUTO; + +/* + * InitPlanCache: initialize module during InitPostgres. + * + * All we need to do is hook into inval.c's callback lists. + */ +void +InitPlanCache(void) +{ + CacheRegisterRelcacheCallback(PlanCacheRelCallback, (Datum) 0); + CacheRegisterSyscacheCallback(PROCOID, PlanCacheObjectCallback, (Datum) 0); + CacheRegisterSyscacheCallback(TYPEOID, PlanCacheObjectCallback, (Datum) 0); + CacheRegisterSyscacheCallback(NAMESPACEOID, PlanCacheSysCallback, (Datum) 0); + CacheRegisterSyscacheCallback(OPEROID, PlanCacheSysCallback, (Datum) 0); + CacheRegisterSyscacheCallback(AMOPOPID, PlanCacheSysCallback, (Datum) 0); + CacheRegisterSyscacheCallback(FOREIGNSERVEROID, PlanCacheSysCallback, (Datum) 0); + CacheRegisterSyscacheCallback(FOREIGNDATAWRAPPEROID, PlanCacheSysCallback, (Datum) 0); +} + +/* + * CreateCachedPlan: initially create a plan cache entry. + * + * Creation of a cached plan is divided into two steps, CreateCachedPlan and + * CompleteCachedPlan. CreateCachedPlan should be called after running the + * query through raw_parser, but before doing parse analysis and rewrite; + * CompleteCachedPlan is called after that. The reason for this arrangement + * is that it can save one round of copying of the raw parse tree, since + * the parser will normally scribble on the raw parse tree. Callers would + * otherwise need to make an extra copy of the parse tree to ensure they + * still had a clean copy to present at plan cache creation time. + * + * All arguments presented to CreateCachedPlan are copied into a memory + * context created as a child of the call-time CurrentMemoryContext, which + * should be a reasonably short-lived working context that will go away in + * event of an error. This ensures that the cached plan data structure will + * likewise disappear if an error occurs before we have fully constructed it. + * Once constructed, the cached plan can be made longer-lived, if needed, + * by calling SaveCachedPlan. + * + * raw_parse_tree: output of raw_parser(), or NULL if empty query + * query_string: original query text + * commandTag: command tag for query, or UNKNOWN if empty query + */ +CachedPlanSource * +CreateCachedPlan(RawStmt *raw_parse_tree, + const char *query_string, + CommandTag commandTag) +{ + CachedPlanSource *plansource; + MemoryContext source_context; + MemoryContext oldcxt; + + Assert(query_string != NULL); /* required as of 8.4 */ + + /* + * Make a dedicated memory context for the CachedPlanSource and its + * permanent subsidiary data. It's probably not going to be large, but + * just in case, allow it to grow large. Initially it's a child of the + * caller's context (which we assume to be transient), so that it will be + * cleaned up on error. + */ + source_context = AllocSetContextCreate(CurrentMemoryContext, + "CachedPlanSource", + ALLOCSET_START_SMALL_SIZES); + + /* + * Create and fill the CachedPlanSource struct within the new context. + * Most fields are just left empty for the moment. + */ + oldcxt = MemoryContextSwitchTo(source_context); + + plansource = (CachedPlanSource *) palloc0(sizeof(CachedPlanSource)); + plansource->magic = CACHEDPLANSOURCE_MAGIC; + plansource->raw_parse_tree = copyObject(raw_parse_tree); + plansource->query_string = pstrdup(query_string); + MemoryContextSetIdentifier(source_context, plansource->query_string); + plansource->commandTag = commandTag; + plansource->param_types = NULL; + plansource->num_params = 0; + plansource->parserSetup = NULL; + plansource->parserSetupArg = NULL; + plansource->cursor_options = 0; + plansource->fixed_result = false; + plansource->resultDesc = NULL; + plansource->context = source_context; + plansource->query_list = NIL; + plansource->relationOids = NIL; + plansource->invalItems = NIL; + plansource->search_path = NULL; + plansource->query_context = NULL; + plansource->rewriteRoleId = InvalidOid; + plansource->rewriteRowSecurity = false; + plansource->dependsOnRLS = false; + plansource->gplan = NULL; + plansource->is_oneshot = false; + plansource->is_complete = false; + plansource->is_saved = false; + plansource->is_valid = false; + plansource->generation = 0; + plansource->generic_cost = -1; + plansource->total_custom_cost = 0; + plansource->num_generic_plans = 0; + plansource->num_custom_plans = 0; + + MemoryContextSwitchTo(oldcxt); + + return plansource; +} + +/* + * CreateOneShotCachedPlan: initially create a one-shot plan cache entry. + * + * This variant of CreateCachedPlan creates a plan cache entry that is meant + * to be used only once. No data copying occurs: all data structures remain + * in the caller's memory context (which typically should get cleared after + * completing execution). The CachedPlanSource struct itself is also created + * in that context. + * + * A one-shot plan cannot be saved or copied, since we make no effort to + * preserve the raw parse tree unmodified. There is also no support for + * invalidation, so plan use must be completed in the current transaction, + * and DDL that might invalidate the querytree_list must be avoided as well. + * + * raw_parse_tree: output of raw_parser(), or NULL if empty query + * query_string: original query text + * commandTag: command tag for query, or NULL if empty query + */ +CachedPlanSource * +CreateOneShotCachedPlan(RawStmt *raw_parse_tree, + const char *query_string, + CommandTag commandTag) +{ + CachedPlanSource *plansource; + + Assert(query_string != NULL); /* required as of 8.4 */ + + /* + * Create and fill the CachedPlanSource struct within the caller's memory + * context. Most fields are just left empty for the moment. + */ + plansource = (CachedPlanSource *) palloc0(sizeof(CachedPlanSource)); + plansource->magic = CACHEDPLANSOURCE_MAGIC; + plansource->raw_parse_tree = raw_parse_tree; + plansource->query_string = query_string; + plansource->commandTag = commandTag; + plansource->param_types = NULL; + plansource->num_params = 0; + plansource->parserSetup = NULL; + plansource->parserSetupArg = NULL; + plansource->cursor_options = 0; + plansource->fixed_result = false; + plansource->resultDesc = NULL; + plansource->context = CurrentMemoryContext; + plansource->query_list = NIL; + plansource->relationOids = NIL; + plansource->invalItems = NIL; + plansource->search_path = NULL; + plansource->query_context = NULL; + plansource->rewriteRoleId = InvalidOid; + plansource->rewriteRowSecurity = false; + plansource->dependsOnRLS = false; + plansource->gplan = NULL; + plansource->is_oneshot = true; + plansource->is_complete = false; + plansource->is_saved = false; + plansource->is_valid = false; + plansource->generation = 0; + plansource->generic_cost = -1; + plansource->total_custom_cost = 0; + plansource->num_generic_plans = 0; + plansource->num_custom_plans = 0; + + return plansource; +} + +/* + * CompleteCachedPlan: second step of creating a plan cache entry. + * + * Pass in the analyzed-and-rewritten form of the query, as well as the + * required subsidiary data about parameters and such. All passed values will + * be copied into the CachedPlanSource's memory, except as specified below. + * After this is called, GetCachedPlan can be called to obtain a plan, and + * optionally the CachedPlanSource can be saved using SaveCachedPlan. + * + * If querytree_context is not NULL, the querytree_list must be stored in that + * context (but the other parameters need not be). The querytree_list is not + * copied, rather the given context is kept as the initial query_context of + * the CachedPlanSource. (It should have been created as a child of the + * caller's working memory context, but it will now be reparented to belong + * to the CachedPlanSource.) The querytree_context is normally the context in + * which the caller did raw parsing and parse analysis. This approach saves + * one tree copying step compared to passing NULL, but leaves lots of extra + * cruft in the query_context, namely whatever extraneous stuff parse analysis + * created, as well as whatever went unused from the raw parse tree. Using + * this option is a space-for-time tradeoff that is appropriate if the + * CachedPlanSource is not expected to survive long. + * + * plancache.c cannot know how to copy the data referenced by parserSetupArg, + * and it would often be inappropriate to do so anyway. When using that + * option, it is caller's responsibility that the referenced data remains + * valid for as long as the CachedPlanSource exists. + * + * If the CachedPlanSource is a "oneshot" plan, then no querytree copying + * occurs at all, and querytree_context is ignored; it is caller's + * responsibility that the passed querytree_list is sufficiently long-lived. + * + * plansource: structure returned by CreateCachedPlan + * querytree_list: analyzed-and-rewritten form of query (list of Query nodes) + * querytree_context: memory context containing querytree_list, + * or NULL to copy querytree_list into a fresh context + * param_types: array of fixed parameter type OIDs, or NULL if none + * num_params: number of fixed parameters + * parserSetup: alternate method for handling query parameters + * parserSetupArg: data to pass to parserSetup + * cursor_options: options bitmask to pass to planner + * fixed_result: true to disallow future changes in query's result tupdesc + */ +void +CompleteCachedPlan(CachedPlanSource *plansource, + List *querytree_list, + MemoryContext querytree_context, + Oid *param_types, + int num_params, + ParserSetupHook parserSetup, + void *parserSetupArg, + int cursor_options, + bool fixed_result) +{ + MemoryContext source_context = plansource->context; + MemoryContext oldcxt = CurrentMemoryContext; + + /* Assert caller is doing things in a sane order */ + Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC); + Assert(!plansource->is_complete); + + /* + * If caller supplied a querytree_context, reparent it underneath the + * CachedPlanSource's context; otherwise, create a suitable context and + * copy the querytree_list into it. But no data copying should be done + * for one-shot plans; for those, assume the passed querytree_list is + * sufficiently long-lived. + */ + if (plansource->is_oneshot) + { + querytree_context = CurrentMemoryContext; + } + else if (querytree_context != NULL) + { + MemoryContextSetParent(querytree_context, source_context); + MemoryContextSwitchTo(querytree_context); + } + else + { + /* Again, it's a good bet the querytree_context can be small */ + querytree_context = AllocSetContextCreate(source_context, + "CachedPlanQuery", + ALLOCSET_START_SMALL_SIZES); + MemoryContextSwitchTo(querytree_context); + querytree_list = copyObject(querytree_list); + } + + plansource->query_context = querytree_context; + plansource->query_list = querytree_list; + + if (!plansource->is_oneshot && StmtPlanRequiresRevalidation(plansource)) + { + /* + * Use the planner machinery to extract dependencies. Data is saved + * in query_context. (We assume that not a lot of extra cruft is + * created by this call.) We can skip this for one-shot plans, and + * plans not needing revalidation have no such dependencies anyway. + */ + extract_query_dependencies((Node *) querytree_list, + &plansource->relationOids, + &plansource->invalItems, + &plansource->dependsOnRLS); + + /* Update RLS info as well. */ + plansource->rewriteRoleId = GetUserId(); + plansource->rewriteRowSecurity = row_security; + + /* + * Also save the current search_path in the query_context. (This + * should not generate much extra cruft either, since almost certainly + * the path is already valid.) Again, we don't really need this for + * one-shot plans; and we *must* skip this for transaction control + * commands, because this could result in catalog accesses. + */ + plansource->search_path = GetOverrideSearchPath(querytree_context); + } + + /* + * Save the final parameter types (or other parameter specification data) + * into the source_context, as well as our other parameters. Also save + * the result tuple descriptor. + */ + MemoryContextSwitchTo(source_context); + + if (num_params > 0) + { + plansource->param_types = (Oid *) palloc(num_params * sizeof(Oid)); + memcpy(plansource->param_types, param_types, num_params * sizeof(Oid)); + } + else + plansource->param_types = NULL; + plansource->num_params = num_params; + plansource->parserSetup = parserSetup; + plansource->parserSetupArg = parserSetupArg; + plansource->cursor_options = cursor_options; + plansource->fixed_result = fixed_result; + plansource->resultDesc = PlanCacheComputeResultDesc(querytree_list); + + MemoryContextSwitchTo(oldcxt); + + plansource->is_complete = true; + plansource->is_valid = true; +} + +/* + * SaveCachedPlan: save a cached plan permanently + * + * This function moves the cached plan underneath CacheMemoryContext (making + * it live for the life of the backend, unless explicitly dropped), and adds + * it to the list of cached plans that are checked for invalidation when an + * sinval event occurs. + * + * This is guaranteed not to throw error, except for the caller-error case + * of trying to save a one-shot plan. Callers typically depend on that + * since this is called just before or just after adding a pointer to the + * CachedPlanSource to some permanent data structure of their own. Up until + * this is done, a CachedPlanSource is just transient data that will go away + * automatically on transaction abort. + */ +void +SaveCachedPlan(CachedPlanSource *plansource) +{ + /* Assert caller is doing things in a sane order */ + Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC); + Assert(plansource->is_complete); + Assert(!plansource->is_saved); + + /* This seems worth a real test, though */ + if (plansource->is_oneshot) + elog(ERROR, "cannot save one-shot cached plan"); + + /* + * In typical use, this function would be called before generating any + * plans from the CachedPlanSource. If there is a generic plan, moving it + * into CacheMemoryContext would be pretty risky since it's unclear + * whether the caller has taken suitable care with making references + * long-lived. Best thing to do seems to be to discard the plan. + */ + ReleaseGenericPlan(plansource); + + /* + * Reparent the source memory context under CacheMemoryContext so that it + * will live indefinitely. The query_context follows along since it's + * already a child of the other one. + */ + MemoryContextSetParent(plansource->context, CacheMemoryContext); + + /* + * Add the entry to the global list of cached plans. + */ + dlist_push_tail(&saved_plan_list, &plansource->node); + + plansource->is_saved = true; +} + +/* + * DropCachedPlan: destroy a cached plan. + * + * Actually this only destroys the CachedPlanSource: any referenced CachedPlan + * is released, but not destroyed until its refcount goes to zero. That + * handles the situation where DropCachedPlan is called while the plan is + * still in use. + */ +void +DropCachedPlan(CachedPlanSource *plansource) +{ + Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC); + + /* If it's been saved, remove it from the list */ + if (plansource->is_saved) + { + dlist_delete(&plansource->node); + plansource->is_saved = false; + } + + /* Decrement generic CachedPlan's refcount and drop if no longer needed */ + ReleaseGenericPlan(plansource); + + /* Mark it no longer valid */ + plansource->magic = 0; + + /* + * Remove the CachedPlanSource and all subsidiary data (including the + * query_context if any). But if it's a one-shot we can't free anything. + */ + if (!plansource->is_oneshot) + MemoryContextDelete(plansource->context); +} + +/* + * ReleaseGenericPlan: release a CachedPlanSource's generic plan, if any. + */ +static void +ReleaseGenericPlan(CachedPlanSource *plansource) +{ + /* Be paranoid about the possibility that ReleaseCachedPlan fails */ + if (plansource->gplan) + { + CachedPlan *plan = plansource->gplan; + + Assert(plan->magic == CACHEDPLAN_MAGIC); + plansource->gplan = NULL; + ReleaseCachedPlan(plan, NULL); + } +} + +/* + * RevalidateCachedQuery: ensure validity of analyzed-and-rewritten query tree. + * + * What we do here is re-acquire locks and redo parse analysis if necessary. + * On return, the query_list is valid and we have sufficient locks to begin + * planning. + * + * If any parse analysis activity is required, the caller's memory context is + * used for that work. + * + * The result value is the transient analyzed-and-rewritten query tree if we + * had to do re-analysis, and NIL otherwise. (This is returned just to save + * a tree copying step in a subsequent BuildCachedPlan call.) + */ +static List * +RevalidateCachedQuery(CachedPlanSource *plansource, + QueryEnvironment *queryEnv) +{ + bool snapshot_set; + RawStmt *rawtree; + List *tlist; /* transient query-tree list */ + List *qlist; /* permanent query-tree list */ + TupleDesc resultDesc; + MemoryContext querytree_context; + MemoryContext oldcxt; + + /* + * For one-shot plans, we do not support revalidation checking; it's + * assumed the query is parsed, planned, and executed in one transaction, + * so that no lock re-acquisition is necessary. Also, if the statement + * type can't require revalidation, we needn't do anything (and we mustn't + * risk catalog accesses when handling, eg, transaction control commands). + */ + if (plansource->is_oneshot || !StmtPlanRequiresRevalidation(plansource)) + { + Assert(plansource->is_valid); + return NIL; + } + + /* + * If the query is currently valid, we should have a saved search_path --- + * check to see if that matches the current environment. If not, we want + * to force replan. + */ + if (plansource->is_valid) + { + Assert(plansource->search_path != NULL); + if (!OverrideSearchPathMatchesCurrent(plansource->search_path)) + { + /* Invalidate the querytree and generic plan */ + plansource->is_valid = false; + if (plansource->gplan) + plansource->gplan->is_valid = false; + } + } + + /* + * If the query rewrite phase had a possible RLS dependency, we must redo + * it if either the role or the row_security setting has changed. + */ + if (plansource->is_valid && plansource->dependsOnRLS && + (plansource->rewriteRoleId != GetUserId() || + plansource->rewriteRowSecurity != row_security)) + plansource->is_valid = false; + + /* + * If the query is currently valid, acquire locks on the referenced + * objects; then check again. We need to do it this way to cover the race + * condition that an invalidation message arrives before we get the locks. + */ + if (plansource->is_valid) + { + AcquirePlannerLocks(plansource->query_list, true); + + /* + * By now, if any invalidation has happened, the inval callback + * functions will have marked the query invalid. + */ + if (plansource->is_valid) + { + /* Successfully revalidated and locked the query. */ + return NIL; + } + + /* Oops, the race case happened. Release useless locks. */ + AcquirePlannerLocks(plansource->query_list, false); + } + + /* + * Discard the no-longer-useful query tree. (Note: we don't want to do + * this any earlier, else we'd not have been able to release locks + * correctly in the race condition case.) + */ + plansource->is_valid = false; + plansource->query_list = NIL; + plansource->relationOids = NIL; + plansource->invalItems = NIL; + plansource->search_path = NULL; + + /* + * Free the query_context. We don't really expect MemoryContextDelete to + * fail, but just in case, make sure the CachedPlanSource is left in a + * reasonably sane state. (The generic plan won't get unlinked yet, but + * that's acceptable.) + */ + if (plansource->query_context) + { + MemoryContext qcxt = plansource->query_context; + + plansource->query_context = NULL; + MemoryContextDelete(qcxt); + } + + /* Drop the generic plan reference if any */ + ReleaseGenericPlan(plansource); + + /* + * Now re-do parse analysis and rewrite. This not incidentally acquires + * the locks we need to do planning safely. + */ + Assert(plansource->is_complete); + + /* + * If a snapshot is already set (the normal case), we can just use that + * for parsing/planning. But if it isn't, install one. Note: no point in + * checking whether parse analysis requires a snapshot; utility commands + * don't have invalidatable plans, so we'd not get here for such a + * command. + */ + snapshot_set = false; + if (!ActiveSnapshotSet()) + { + PushActiveSnapshot(GetTransactionSnapshot()); + snapshot_set = true; + } + + /* + * Run parse analysis and rule rewriting. The parser tends to scribble on + * its input, so we must copy the raw parse tree to prevent corruption of + * the cache. + */ + rawtree = copyObject(plansource->raw_parse_tree); + if (rawtree == NULL) + tlist = NIL; + else if (plansource->parserSetup != NULL) + tlist = pg_analyze_and_rewrite_withcb(rawtree, + plansource->query_string, + plansource->parserSetup, + plansource->parserSetupArg, + queryEnv); + else + tlist = pg_analyze_and_rewrite_fixedparams(rawtree, + plansource->query_string, + plansource->param_types, + plansource->num_params, + queryEnv); + + /* Release snapshot if we got one */ + if (snapshot_set) + PopActiveSnapshot(); + + /* + * Check or update the result tupdesc. XXX should we use a weaker + * condition than equalTupleDescs() here? + * + * We assume the parameter types didn't change from the first time, so no + * need to update that. + */ + resultDesc = PlanCacheComputeResultDesc(tlist); + if (resultDesc == NULL && plansource->resultDesc == NULL) + { + /* OK, doesn't return tuples */ + } + else if (resultDesc == NULL || plansource->resultDesc == NULL || + !equalTupleDescs(resultDesc, plansource->resultDesc)) + { + /* can we give a better error message? */ + if (plansource->fixed_result) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cached plan must not change result type"))); + oldcxt = MemoryContextSwitchTo(plansource->context); + if (resultDesc) + resultDesc = CreateTupleDescCopy(resultDesc); + if (plansource->resultDesc) + FreeTupleDesc(plansource->resultDesc); + plansource->resultDesc = resultDesc; + MemoryContextSwitchTo(oldcxt); + } + + /* + * Allocate new query_context and copy the completed querytree into it. + * It's transient until we complete the copying and dependency extraction. + */ + querytree_context = AllocSetContextCreate(CurrentMemoryContext, + "CachedPlanQuery", + ALLOCSET_START_SMALL_SIZES); + oldcxt = MemoryContextSwitchTo(querytree_context); + + qlist = copyObject(tlist); + + /* + * Use the planner machinery to extract dependencies. Data is saved in + * query_context. (We assume that not a lot of extra cruft is created by + * this call.) + */ + extract_query_dependencies((Node *) qlist, + &plansource->relationOids, + &plansource->invalItems, + &plansource->dependsOnRLS); + + /* Update RLS info as well. */ + plansource->rewriteRoleId = GetUserId(); + plansource->rewriteRowSecurity = row_security; + + /* + * Also save the current search_path in the query_context. (This should + * not generate much extra cruft either, since almost certainly the path + * is already valid.) + */ + plansource->search_path = GetOverrideSearchPath(querytree_context); + + MemoryContextSwitchTo(oldcxt); + + /* Now reparent the finished query_context and save the links */ + MemoryContextSetParent(querytree_context, plansource->context); + + plansource->query_context = querytree_context; + plansource->query_list = qlist; + + /* + * Note: we do not reset generic_cost or total_custom_cost, although we + * could choose to do so. If the DDL or statistics change that prompted + * the invalidation meant a significant change in the cost estimates, it + * would be better to reset those variables and start fresh; but often it + * doesn't, and we're better retaining our hard-won knowledge about the + * relative costs. + */ + + plansource->is_valid = true; + + /* Return transient copy of querytrees for possible use in planning */ + return tlist; +} + +/* + * CheckCachedPlan: see if the CachedPlanSource's generic plan is valid. + * + * Caller must have already called RevalidateCachedQuery to verify that the + * querytree is up to date. + * + * On a "true" return, we have acquired the locks needed to run the plan. + * (We must do this for the "true" result to be race-condition-free.) + */ +static bool +CheckCachedPlan(CachedPlanSource *plansource) +{ + CachedPlan *plan = plansource->gplan; + + /* Assert that caller checked the querytree */ + Assert(plansource->is_valid); + + /* If there's no generic plan, just say "false" */ + if (!plan) + return false; + + Assert(plan->magic == CACHEDPLAN_MAGIC); + /* Generic plans are never one-shot */ + Assert(!plan->is_oneshot); + + /* + * If plan isn't valid for current role, we can't use it. + */ + if (plan->is_valid && plan->dependsOnRole && + plan->planRoleId != GetUserId()) + plan->is_valid = false; + + /* + * If it appears valid, acquire locks and recheck; this is much the same + * logic as in RevalidateCachedQuery, but for a plan. + */ + if (plan->is_valid) + { + /* + * Plan must have positive refcount because it is referenced by + * plansource; so no need to fear it disappears under us here. + */ + Assert(plan->refcount > 0); + + AcquireExecutorLocks(plan->stmt_list, true); + + /* + * If plan was transient, check to see if TransactionXmin has + * advanced, and if so invalidate it. + */ + if (plan->is_valid && + TransactionIdIsValid(plan->saved_xmin) && + !TransactionIdEquals(plan->saved_xmin, TransactionXmin)) + plan->is_valid = false; + + /* + * By now, if any invalidation has happened, the inval callback + * functions will have marked the plan invalid. + */ + if (plan->is_valid) + { + /* Successfully revalidated and locked the query. */ + return true; + } + + /* Oops, the race case happened. Release useless locks. */ + AcquireExecutorLocks(plan->stmt_list, false); + } + + /* + * Plan has been invalidated, so unlink it from the parent and release it. + */ + ReleaseGenericPlan(plansource); + + return false; +} + +/* + * BuildCachedPlan: construct a new CachedPlan from a CachedPlanSource. + * + * qlist should be the result value from a previous RevalidateCachedQuery, + * or it can be set to NIL if we need to re-copy the plansource's query_list. + * + * To build a generic, parameter-value-independent plan, pass NULL for + * boundParams. To build a custom plan, pass the actual parameter values via + * boundParams. For best effect, the PARAM_FLAG_CONST flag should be set on + * each parameter value; otherwise the planner will treat the value as a + * hint rather than a hard constant. + * + * Planning work is done in the caller's memory context. The finished plan + * is in a child memory context, which typically should get reparented + * (unless this is a one-shot plan, in which case we don't copy the plan). + */ +static CachedPlan * +BuildCachedPlan(CachedPlanSource *plansource, List *qlist, + ParamListInfo boundParams, QueryEnvironment *queryEnv) +{ + CachedPlan *plan; + List *plist; + bool snapshot_set; + bool is_transient; + MemoryContext plan_context; + MemoryContext oldcxt = CurrentMemoryContext; + ListCell *lc; + + /* + * Normally the querytree should be valid already, but if it's not, + * rebuild it. + * + * NOTE: GetCachedPlan should have called RevalidateCachedQuery first, so + * we ought to be holding sufficient locks to prevent any invalidation. + * However, if we're building a custom plan after having built and + * rejected a generic plan, it's possible to reach here with is_valid + * false due to an invalidation while making the generic plan. In theory + * the invalidation must be a false positive, perhaps a consequence of an + * sinval reset event or the debug_discard_caches code. But for safety, + * let's treat it as real and redo the RevalidateCachedQuery call. + */ + if (!plansource->is_valid) + qlist = RevalidateCachedQuery(plansource, queryEnv); + + /* + * If we don't already have a copy of the querytree list that can be + * scribbled on by the planner, make one. For a one-shot plan, we assume + * it's okay to scribble on the original query_list. + */ + if (qlist == NIL) + { + if (!plansource->is_oneshot) + qlist = copyObject(plansource->query_list); + else + qlist = plansource->query_list; + } + + /* + * If a snapshot is already set (the normal case), we can just use that + * for planning. But if it isn't, and we need one, install one. + */ + snapshot_set = false; + if (!ActiveSnapshotSet() && + plansource->raw_parse_tree && + analyze_requires_snapshot(plansource->raw_parse_tree)) + { + PushActiveSnapshot(GetTransactionSnapshot()); + snapshot_set = true; + } + + /* + * Generate the plan. + */ + plist = pg_plan_queries(qlist, plansource->query_string, + plansource->cursor_options, boundParams); + + /* Release snapshot if we got one */ + if (snapshot_set) + PopActiveSnapshot(); + + /* + * Normally we make a dedicated memory context for the CachedPlan and its + * subsidiary data. (It's probably not going to be large, but just in + * case, allow it to grow large. It's transient for the moment.) But for + * a one-shot plan, we just leave it in the caller's memory context. + */ + if (!plansource->is_oneshot) + { + plan_context = AllocSetContextCreate(CurrentMemoryContext, + "CachedPlan", + ALLOCSET_START_SMALL_SIZES); + MemoryContextCopyAndSetIdentifier(plan_context, plansource->query_string); + + /* + * Copy plan into the new context. + */ + MemoryContextSwitchTo(plan_context); + + plist = copyObject(plist); + } + else + plan_context = CurrentMemoryContext; + + /* + * Create and fill the CachedPlan struct within the new context. + */ + plan = (CachedPlan *) palloc(sizeof(CachedPlan)); + plan->magic = CACHEDPLAN_MAGIC; + plan->stmt_list = plist; + + /* + * CachedPlan is dependent on role either if RLS affected the rewrite + * phase or if a role dependency was injected during planning. And it's + * transient if any plan is marked so. + */ + plan->planRoleId = GetUserId(); + plan->dependsOnRole = plansource->dependsOnRLS; + is_transient = false; + foreach(lc, plist) + { + PlannedStmt *plannedstmt = lfirst_node(PlannedStmt, lc); + + if (plannedstmt->commandType == CMD_UTILITY) + continue; /* Ignore utility statements */ + + if (plannedstmt->transientPlan) + is_transient = true; + if (plannedstmt->dependsOnRole) + plan->dependsOnRole = true; + } + if (is_transient) + { + Assert(TransactionIdIsNormal(TransactionXmin)); + plan->saved_xmin = TransactionXmin; + } + else + plan->saved_xmin = InvalidTransactionId; + plan->refcount = 0; + plan->context = plan_context; + plan->is_oneshot = plansource->is_oneshot; + plan->is_saved = false; + plan->is_valid = true; + + /* assign generation number to new plan */ + plan->generation = ++(plansource->generation); + + MemoryContextSwitchTo(oldcxt); + + return plan; +} + +/* + * choose_custom_plan: choose whether to use custom or generic plan + * + * This defines the policy followed by GetCachedPlan. + */ +static bool +choose_custom_plan(CachedPlanSource *plansource, ParamListInfo boundParams) +{ + double avg_custom_cost; + + /* One-shot plans will always be considered custom */ + if (plansource->is_oneshot) + return true; + + /* Otherwise, never any point in a custom plan if there's no parameters */ + if (boundParams == NULL) + return false; + /* ... nor when planning would be a no-op */ + if (!StmtPlanRequiresRevalidation(plansource)) + return false; + + /* Let settings force the decision */ + if (plan_cache_mode == PLAN_CACHE_MODE_FORCE_GENERIC_PLAN) + return false; + if (plan_cache_mode == PLAN_CACHE_MODE_FORCE_CUSTOM_PLAN) + return true; + + /* See if caller wants to force the decision */ + if (plansource->cursor_options & CURSOR_OPT_GENERIC_PLAN) + return false; + if (plansource->cursor_options & CURSOR_OPT_CUSTOM_PLAN) + return true; + + /* Generate custom plans until we have done at least 5 (arbitrary) */ + if (plansource->num_custom_plans < 5) + return true; + + avg_custom_cost = plansource->total_custom_cost / plansource->num_custom_plans; + + /* + * Prefer generic plan if it's less expensive than the average custom + * plan. (Because we include a charge for cost of planning in the + * custom-plan costs, this means the generic plan only has to be less + * expensive than the execution cost plus replan cost of the custom + * plans.) + * + * Note that if generic_cost is -1 (indicating we've not yet determined + * the generic plan cost), we'll always prefer generic at this point. + */ + if (plansource->generic_cost < avg_custom_cost) + return false; + + return true; +} + +/* + * cached_plan_cost: calculate estimated cost of a plan + * + * If include_planner is true, also include the estimated cost of constructing + * the plan. (We must factor that into the cost of using a custom plan, but + * we don't count it for a generic plan.) + */ +static double +cached_plan_cost(CachedPlan *plan, bool include_planner) +{ + double result = 0; + ListCell *lc; + + foreach(lc, plan->stmt_list) + { + PlannedStmt *plannedstmt = lfirst_node(PlannedStmt, lc); + + if (plannedstmt->commandType == CMD_UTILITY) + continue; /* Ignore utility statements */ + + result += plannedstmt->planTree->total_cost; + + if (include_planner) + { + /* + * Currently we use a very crude estimate of planning effort based + * on the number of relations in the finished plan's rangetable. + * Join planning effort actually scales much worse than linearly + * in the number of relations --- but only until the join collapse + * limits kick in. Also, while inheritance child relations surely + * add to planning effort, they don't make the join situation + * worse. So the actual shape of the planning cost curve versus + * number of relations isn't all that obvious. It will take + * considerable work to arrive at a less crude estimate, and for + * now it's not clear that's worth doing. + * + * The other big difficulty here is that we don't have any very + * good model of how planning cost compares to execution costs. + * The current multiplier of 1000 * cpu_operator_cost is probably + * on the low side, but we'll try this for awhile before making a + * more aggressive correction. + * + * If we ever do write a more complicated estimator, it should + * probably live in src/backend/optimizer/ not here. + */ + int nrelations = list_length(plannedstmt->rtable); + + result += 1000.0 * cpu_operator_cost * (nrelations + 1); + } + } + + return result; +} + +/* + * GetCachedPlan: get a cached plan from a CachedPlanSource. + * + * This function hides the logic that decides whether to use a generic + * plan or a custom plan for the given parameters: the caller does not know + * which it will get. + * + * On return, the plan is valid and we have sufficient locks to begin + * execution. + * + * On return, the refcount of the plan has been incremented; a later + * ReleaseCachedPlan() call is expected. If "owner" is not NULL then + * the refcount has been reported to that ResourceOwner (note that this + * is only supported for "saved" CachedPlanSources). + * + * Note: if any replanning activity is required, the caller's memory context + * is used for that work. + */ +CachedPlan * +GetCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams, + ResourceOwner owner, QueryEnvironment *queryEnv) +{ + CachedPlan *plan = NULL; + List *qlist; + bool customplan; + + /* Assert caller is doing things in a sane order */ + Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC); + Assert(plansource->is_complete); + /* This seems worth a real test, though */ + if (owner && !plansource->is_saved) + elog(ERROR, "cannot apply ResourceOwner to non-saved cached plan"); + + /* Make sure the querytree list is valid and we have parse-time locks */ + qlist = RevalidateCachedQuery(plansource, queryEnv); + + /* Decide whether to use a custom plan */ + customplan = choose_custom_plan(plansource, boundParams); + + if (!customplan) + { + if (CheckCachedPlan(plansource)) + { + /* We want a generic plan, and we already have a valid one */ + plan = plansource->gplan; + Assert(plan->magic == CACHEDPLAN_MAGIC); + } + else + { + /* Build a new generic plan */ + plan = BuildCachedPlan(plansource, qlist, NULL, queryEnv); + /* Just make real sure plansource->gplan is clear */ + ReleaseGenericPlan(plansource); + /* Link the new generic plan into the plansource */ + plansource->gplan = plan; + plan->refcount++; + /* Immediately reparent into appropriate context */ + if (plansource->is_saved) + { + /* saved plans all live under CacheMemoryContext */ + MemoryContextSetParent(plan->context, CacheMemoryContext); + plan->is_saved = true; + } + else + { + /* otherwise, it should be a sibling of the plansource */ + MemoryContextSetParent(plan->context, + MemoryContextGetParent(plansource->context)); + } + /* Update generic_cost whenever we make a new generic plan */ + plansource->generic_cost = cached_plan_cost(plan, false); + + /* + * If, based on the now-known value of generic_cost, we'd not have + * chosen to use a generic plan, then forget it and make a custom + * plan. This is a bit of a wart but is necessary to avoid a + * glitch in behavior when the custom plans are consistently big + * winners; at some point we'll experiment with a generic plan and + * find it's a loser, but we don't want to actually execute that + * plan. + */ + customplan = choose_custom_plan(plansource, boundParams); + + /* + * If we choose to plan again, we need to re-copy the query_list, + * since the planner probably scribbled on it. We can force + * BuildCachedPlan to do that by passing NIL. + */ + qlist = NIL; + } + } + + if (customplan) + { + /* Build a custom plan */ + plan = BuildCachedPlan(plansource, qlist, boundParams, queryEnv); + /* Accumulate total costs of custom plans */ + plansource->total_custom_cost += cached_plan_cost(plan, true); + + plansource->num_custom_plans++; + } + else + { + plansource->num_generic_plans++; + } + + Assert(plan != NULL); + + /* Flag the plan as in use by caller */ + if (owner) + ResourceOwnerEnlargePlanCacheRefs(owner); + plan->refcount++; + if (owner) + ResourceOwnerRememberPlanCacheRef(owner, plan); + + /* + * Saved plans should be under CacheMemoryContext so they will not go away + * until their reference count goes to zero. In the generic-plan cases we + * already took care of that, but for a custom plan, do it as soon as we + * have created a reference-counted link. + */ + if (customplan && plansource->is_saved) + { + MemoryContextSetParent(plan->context, CacheMemoryContext); + plan->is_saved = true; + } + + return plan; +} + +/* + * ReleaseCachedPlan: release active use of a cached plan. + * + * This decrements the reference count, and frees the plan if the count + * has thereby gone to zero. If "owner" is not NULL, it is assumed that + * the reference count is managed by that ResourceOwner. + * + * Note: owner == NULL is used for releasing references that are in + * persistent data structures, such as the parent CachedPlanSource or a + * Portal. Transient references should be protected by a resource owner. + */ +void +ReleaseCachedPlan(CachedPlan *plan, ResourceOwner owner) +{ + Assert(plan->magic == CACHEDPLAN_MAGIC); + if (owner) + { + Assert(plan->is_saved); + ResourceOwnerForgetPlanCacheRef(owner, plan); + } + Assert(plan->refcount > 0); + plan->refcount--; + if (plan->refcount == 0) + { + /* Mark it no longer valid */ + plan->magic = 0; + + /* One-shot plans do not own their context, so we can't free them */ + if (!plan->is_oneshot) + MemoryContextDelete(plan->context); + } +} + +/* + * CachedPlanAllowsSimpleValidityCheck: can we use CachedPlanIsSimplyValid? + * + * This function, together with CachedPlanIsSimplyValid, provides a fast path + * for revalidating "simple" generic plans. The core requirement to be simple + * is that the plan must not require taking any locks, which translates to + * not touching any tables; this happens to match up well with an important + * use-case in PL/pgSQL. This function tests whether that's true, along + * with checking some other corner cases that we'd rather not bother with + * handling in the fast path. (Note that it's still possible for such a plan + * to be invalidated, for example due to a change in a function that was + * inlined into the plan.) + * + * If the plan is simply valid, and "owner" is not NULL, record a refcount on + * the plan in that resowner before returning. It is caller's responsibility + * to be sure that a refcount is held on any plan that's being actively used. + * + * This must only be called on known-valid generic plans (eg, ones just + * returned by GetCachedPlan). If it returns true, the caller may re-use + * the cached plan as long as CachedPlanIsSimplyValid returns true; that + * check is much cheaper than the full revalidation done by GetCachedPlan. + * Nonetheless, no required checks are omitted. + */ +bool +CachedPlanAllowsSimpleValidityCheck(CachedPlanSource *plansource, + CachedPlan *plan, ResourceOwner owner) +{ + ListCell *lc; + + /* + * Sanity-check that the caller gave us a validated generic plan. Notice + * that we *don't* assert plansource->is_valid as you might expect; that's + * because it's possible that that's already false when GetCachedPlan + * returns, e.g. because ResetPlanCache happened partway through. We + * should accept the plan as long as plan->is_valid is true, and expect to + * replan after the next CachedPlanIsSimplyValid call. + */ + Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC); + Assert(plan->magic == CACHEDPLAN_MAGIC); + Assert(plan->is_valid); + Assert(plan == plansource->gplan); + Assert(plansource->search_path != NULL); + Assert(OverrideSearchPathMatchesCurrent(plansource->search_path)); + + /* We don't support oneshot plans here. */ + if (plansource->is_oneshot) + return false; + Assert(!plan->is_oneshot); + + /* + * If the plan is dependent on RLS considerations, or it's transient, + * reject. These things probably can't ever happen for table-free + * queries, but for safety's sake let's check. + */ + if (plansource->dependsOnRLS) + return false; + if (plan->dependsOnRole) + return false; + if (TransactionIdIsValid(plan->saved_xmin)) + return false; + + /* + * Reject if AcquirePlannerLocks would have anything to do. This is + * simplistic, but there's no need to inquire any more carefully; indeed, + * for current callers it shouldn't even be possible to hit any of these + * checks. + */ + foreach(lc, plansource->query_list) + { + Query *query = lfirst_node(Query, lc); + + if (query->commandType == CMD_UTILITY) + return false; + if (query->rtable || query->cteList || query->hasSubLinks) + return false; + } + + /* + * Reject if AcquireExecutorLocks would have anything to do. This is + * probably unnecessary given the previous check, but let's be safe. + */ + foreach(lc, plan->stmt_list) + { + PlannedStmt *plannedstmt = lfirst_node(PlannedStmt, lc); + ListCell *lc2; + + if (plannedstmt->commandType == CMD_UTILITY) + return false; + + /* + * We have to grovel through the rtable because it's likely to contain + * an RTE_RESULT relation, rather than being totally empty. + */ + foreach(lc2, plannedstmt->rtable) + { + RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc2); + + if (rte->rtekind == RTE_RELATION) + return false; + } + } + + /* + * Okay, it's simple. Note that what we've primarily established here is + * that no locks need be taken before checking the plan's is_valid flag. + */ + + /* Bump refcount if requested. */ + if (owner) + { + ResourceOwnerEnlargePlanCacheRefs(owner); + plan->refcount++; + ResourceOwnerRememberPlanCacheRef(owner, plan); + } + + return true; +} + +/* + * CachedPlanIsSimplyValid: quick check for plan still being valid + * + * This function must not be used unless CachedPlanAllowsSimpleValidityCheck + * previously said it was OK. + * + * If the plan is valid, and "owner" is not NULL, record a refcount on + * the plan in that resowner before returning. It is caller's responsibility + * to be sure that a refcount is held on any plan that's being actively used. + * + * The code here is unconditionally safe as long as the only use of this + * CachedPlanSource is in connection with the particular CachedPlan pointer + * that's passed in. If the plansource were being used for other purposes, + * it's possible that its generic plan could be invalidated and regenerated + * while the current caller wasn't looking, and then there could be a chance + * collision of address between this caller's now-stale plan pointer and the + * actual address of the new generic plan. For current uses, that scenario + * can't happen; but with a plansource shared across multiple uses, it'd be + * advisable to also save plan->generation and verify that that still matches. + */ +bool +CachedPlanIsSimplyValid(CachedPlanSource *plansource, CachedPlan *plan, + ResourceOwner owner) +{ + /* + * Careful here: since the caller doesn't necessarily hold a refcount on + * the plan to start with, it's possible that "plan" is a dangling + * pointer. Don't dereference it until we've verified that it still + * matches the plansource's gplan (which is either valid or NULL). + */ + Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC); + + /* + * Has cache invalidation fired on this plan? We can check this right + * away since there are no locks that we'd need to acquire first. Note + * that here we *do* check plansource->is_valid, so as to force plan + * rebuild if that's become false. + */ + if (!plansource->is_valid || + plan == NULL || plan != plansource->gplan || + !plan->is_valid) + return false; + + Assert(plan->magic == CACHEDPLAN_MAGIC); + + /* Is the search_path still the same as when we made it? */ + Assert(plansource->search_path != NULL); + if (!OverrideSearchPathMatchesCurrent(plansource->search_path)) + return false; + + /* It's still good. Bump refcount if requested. */ + if (owner) + { + ResourceOwnerEnlargePlanCacheRefs(owner); + plan->refcount++; + ResourceOwnerRememberPlanCacheRef(owner, plan); + } + + return true; +} + +/* + * CachedPlanSetParentContext: move a CachedPlanSource to a new memory context + * + * This can only be applied to unsaved plans; once saved, a plan always + * lives underneath CacheMemoryContext. + */ +void +CachedPlanSetParentContext(CachedPlanSource *plansource, + MemoryContext newcontext) +{ + /* Assert caller is doing things in a sane order */ + Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC); + Assert(plansource->is_complete); + + /* These seem worth real tests, though */ + if (plansource->is_saved) + elog(ERROR, "cannot move a saved cached plan to another context"); + if (plansource->is_oneshot) + elog(ERROR, "cannot move a one-shot cached plan to another context"); + + /* OK, let the caller keep the plan where he wishes */ + MemoryContextSetParent(plansource->context, newcontext); + + /* + * The query_context needs no special handling, since it's a child of + * plansource->context. But if there's a generic plan, it should be + * maintained as a sibling of plansource->context. + */ + if (plansource->gplan) + { + Assert(plansource->gplan->magic == CACHEDPLAN_MAGIC); + MemoryContextSetParent(plansource->gplan->context, newcontext); + } +} + +/* + * CopyCachedPlan: make a copy of a CachedPlanSource + * + * This is a convenience routine that does the equivalent of + * CreateCachedPlan + CompleteCachedPlan, using the data stored in the + * input CachedPlanSource. The result is therefore "unsaved" (regardless + * of the state of the source), and we don't copy any generic plan either. + * The result will be currently valid, or not, the same as the source. + */ +CachedPlanSource * +CopyCachedPlan(CachedPlanSource *plansource) +{ + CachedPlanSource *newsource; + MemoryContext source_context; + MemoryContext querytree_context; + MemoryContext oldcxt; + + Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC); + Assert(plansource->is_complete); + + /* + * One-shot plans can't be copied, because we haven't taken care that + * parsing/planning didn't scribble on the raw parse tree or querytrees. + */ + if (plansource->is_oneshot) + elog(ERROR, "cannot copy a one-shot cached plan"); + + source_context = AllocSetContextCreate(CurrentMemoryContext, + "CachedPlanSource", + ALLOCSET_START_SMALL_SIZES); + + oldcxt = MemoryContextSwitchTo(source_context); + + newsource = (CachedPlanSource *) palloc0(sizeof(CachedPlanSource)); + newsource->magic = CACHEDPLANSOURCE_MAGIC; + newsource->raw_parse_tree = copyObject(plansource->raw_parse_tree); + newsource->query_string = pstrdup(plansource->query_string); + MemoryContextSetIdentifier(source_context, newsource->query_string); + newsource->commandTag = plansource->commandTag; + if (plansource->num_params > 0) + { + newsource->param_types = (Oid *) + palloc(plansource->num_params * sizeof(Oid)); + memcpy(newsource->param_types, plansource->param_types, + plansource->num_params * sizeof(Oid)); + } + else + newsource->param_types = NULL; + newsource->num_params = plansource->num_params; + newsource->parserSetup = plansource->parserSetup; + newsource->parserSetupArg = plansource->parserSetupArg; + newsource->cursor_options = plansource->cursor_options; + newsource->fixed_result = plansource->fixed_result; + if (plansource->resultDesc) + newsource->resultDesc = CreateTupleDescCopy(plansource->resultDesc); + else + newsource->resultDesc = NULL; + newsource->context = source_context; + + querytree_context = AllocSetContextCreate(source_context, + "CachedPlanQuery", + ALLOCSET_START_SMALL_SIZES); + MemoryContextSwitchTo(querytree_context); + newsource->query_list = copyObject(plansource->query_list); + newsource->relationOids = copyObject(plansource->relationOids); + newsource->invalItems = copyObject(plansource->invalItems); + if (plansource->search_path) + newsource->search_path = CopyOverrideSearchPath(plansource->search_path); + newsource->query_context = querytree_context; + newsource->rewriteRoleId = plansource->rewriteRoleId; + newsource->rewriteRowSecurity = plansource->rewriteRowSecurity; + newsource->dependsOnRLS = plansource->dependsOnRLS; + + newsource->gplan = NULL; + + newsource->is_oneshot = false; + newsource->is_complete = true; + newsource->is_saved = false; + newsource->is_valid = plansource->is_valid; + newsource->generation = plansource->generation; + + /* We may as well copy any acquired cost knowledge */ + newsource->generic_cost = plansource->generic_cost; + newsource->total_custom_cost = plansource->total_custom_cost; + newsource->num_generic_plans = plansource->num_generic_plans; + newsource->num_custom_plans = plansource->num_custom_plans; + + MemoryContextSwitchTo(oldcxt); + + return newsource; +} + +/* + * CachedPlanIsValid: test whether the rewritten querytree within a + * CachedPlanSource is currently valid (that is, not marked as being in need + * of revalidation). + * + * This result is only trustworthy (ie, free from race conditions) if + * the caller has acquired locks on all the relations used in the plan. + */ +bool +CachedPlanIsValid(CachedPlanSource *plansource) +{ + Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC); + return plansource->is_valid; +} + +/* + * CachedPlanGetTargetList: return tlist, if any, describing plan's output + * + * The result is guaranteed up-to-date. However, it is local storage + * within the cached plan, and may disappear next time the plan is updated. + */ +List * +CachedPlanGetTargetList(CachedPlanSource *plansource, + QueryEnvironment *queryEnv) +{ + Query *pstmt; + + /* Assert caller is doing things in a sane order */ + Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC); + Assert(plansource->is_complete); + + /* + * No work needed if statement doesn't return tuples (we assume this + * feature cannot be changed by an invalidation) + */ + if (plansource->resultDesc == NULL) + return NIL; + + /* Make sure the querytree list is valid and we have parse-time locks */ + RevalidateCachedQuery(plansource, queryEnv); + + /* Get the primary statement and find out what it returns */ + pstmt = QueryListGetPrimaryStmt(plansource->query_list); + + return FetchStatementTargetList((Node *) pstmt); +} + +/* + * GetCachedExpression: construct a CachedExpression for an expression. + * + * This performs the same transformations on the expression as + * expression_planner(), ie, convert an expression as emitted by parse + * analysis to be ready to pass to the executor. + * + * The result is stashed in a private, long-lived memory context. + * (Note that this might leak a good deal of memory in the caller's + * context before that.) The passed-in expr tree is not modified. + */ +CachedExpression * +GetCachedExpression(Node *expr) +{ + CachedExpression *cexpr; + List *relationOids; + List *invalItems; + MemoryContext cexpr_context; + MemoryContext oldcxt; + + /* + * Pass the expression through the planner, and collect dependencies. + * Everything built here is leaked in the caller's context; that's + * intentional to minimize the size of the permanent data structure. + */ + expr = (Node *) expression_planner_with_deps((Expr *) expr, + &relationOids, + &invalItems); + + /* + * Make a private memory context, and copy what we need into that. To + * avoid leaking a long-lived context if we fail while copying data, we + * initially make the context under the caller's context. + */ + cexpr_context = AllocSetContextCreate(CurrentMemoryContext, + "CachedExpression", + ALLOCSET_SMALL_SIZES); + + oldcxt = MemoryContextSwitchTo(cexpr_context); + + cexpr = (CachedExpression *) palloc(sizeof(CachedExpression)); + cexpr->magic = CACHEDEXPR_MAGIC; + cexpr->expr = copyObject(expr); + cexpr->is_valid = true; + cexpr->relationOids = copyObject(relationOids); + cexpr->invalItems = copyObject(invalItems); + cexpr->context = cexpr_context; + + MemoryContextSwitchTo(oldcxt); + + /* + * Reparent the expr's memory context under CacheMemoryContext so that it + * will live indefinitely. + */ + MemoryContextSetParent(cexpr_context, CacheMemoryContext); + + /* + * Add the entry to the global list of cached expressions. + */ + dlist_push_tail(&cached_expression_list, &cexpr->node); + + return cexpr; +} + +/* + * FreeCachedExpression + * Delete a CachedExpression. + */ +void +FreeCachedExpression(CachedExpression *cexpr) +{ + /* Sanity check */ + Assert(cexpr->magic == CACHEDEXPR_MAGIC); + /* Unlink from global list */ + dlist_delete(&cexpr->node); + /* Free all storage associated with CachedExpression */ + MemoryContextDelete(cexpr->context); +} + +/* + * QueryListGetPrimaryStmt + * Get the "primary" stmt within a list, ie, the one marked canSetTag. + * + * Returns NULL if no such stmt. If multiple queries within the list are + * marked canSetTag, returns the first one. Neither of these cases should + * occur in present usages of this function. + */ +static Query * +QueryListGetPrimaryStmt(List *stmts) +{ + ListCell *lc; + + foreach(lc, stmts) + { + Query *stmt = lfirst_node(Query, lc); + + if (stmt->canSetTag) + return stmt; + } + return NULL; +} + +/* + * AcquireExecutorLocks: acquire locks needed for execution of a cached plan; + * or release them if acquire is false. + */ +static void +AcquireExecutorLocks(List *stmt_list, bool acquire) +{ + ListCell *lc1; + + foreach(lc1, stmt_list) + { + PlannedStmt *plannedstmt = lfirst_node(PlannedStmt, lc1); + ListCell *lc2; + + if (plannedstmt->commandType == CMD_UTILITY) + { + /* + * Ignore utility statements, except those (such as EXPLAIN) that + * contain a parsed-but-not-planned query. Note: it's okay to use + * ScanQueryForLocks, even though the query hasn't been through + * rule rewriting, because rewriting doesn't change the query + * representation. + */ + Query *query = UtilityContainsQuery(plannedstmt->utilityStmt); + + if (query) + ScanQueryForLocks(query, acquire); + continue; + } + + foreach(lc2, plannedstmt->rtable) + { + RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc2); + + if (!(rte->rtekind == RTE_RELATION || + (rte->rtekind == RTE_SUBQUERY && OidIsValid(rte->relid)))) + continue; + + /* + * Acquire the appropriate type of lock on each relation OID. Note + * that we don't actually try to open the rel, and hence will not + * fail if it's been dropped entirely --- we'll just transiently + * acquire a non-conflicting lock. + */ + if (acquire) + LockRelationOid(rte->relid, rte->rellockmode); + else + UnlockRelationOid(rte->relid, rte->rellockmode); + } + } +} + +/* + * AcquirePlannerLocks: acquire locks needed for planning of a querytree list; + * or release them if acquire is false. + * + * Note that we don't actually try to open the relations, and hence will not + * fail if one has been dropped entirely --- we'll just transiently acquire + * a non-conflicting lock. + */ +static void +AcquirePlannerLocks(List *stmt_list, bool acquire) +{ + ListCell *lc; + + foreach(lc, stmt_list) + { + Query *query = lfirst_node(Query, lc); + + if (query->commandType == CMD_UTILITY) + { + /* Ignore utility statements, unless they contain a Query */ + query = UtilityContainsQuery(query->utilityStmt); + if (query) + ScanQueryForLocks(query, acquire); + continue; + } + + ScanQueryForLocks(query, acquire); + } +} + +/* + * ScanQueryForLocks: recursively scan one Query for AcquirePlannerLocks. + */ +static void +ScanQueryForLocks(Query *parsetree, bool acquire) +{ + ListCell *lc; + + /* Shouldn't get called on utility commands */ + Assert(parsetree->commandType != CMD_UTILITY); + + /* + * First, process RTEs of the current query level. + */ + foreach(lc, parsetree->rtable) + { + RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc); + + switch (rte->rtekind) + { + case RTE_RELATION: + /* Acquire or release the appropriate type of lock */ + if (acquire) + LockRelationOid(rte->relid, rte->rellockmode); + else + UnlockRelationOid(rte->relid, rte->rellockmode); + break; + + case RTE_SUBQUERY: + /* If this was a view, must lock/unlock the view */ + if (OidIsValid(rte->relid)) + { + if (acquire) + LockRelationOid(rte->relid, rte->rellockmode); + else + UnlockRelationOid(rte->relid, rte->rellockmode); + } + /* Recurse into subquery-in-FROM */ + ScanQueryForLocks(rte->subquery, acquire); + break; + + default: + /* ignore other types of RTEs */ + break; + } + } + + /* Recurse into subquery-in-WITH */ + foreach(lc, parsetree->cteList) + { + CommonTableExpr *cte = lfirst_node(CommonTableExpr, lc); + + ScanQueryForLocks(castNode(Query, cte->ctequery), acquire); + } + + /* + * Recurse into sublink subqueries, too. But we already did the ones in + * the rtable and cteList. + */ + if (parsetree->hasSubLinks) + { + query_tree_walker(parsetree, ScanQueryWalker, + (void *) &acquire, + QTW_IGNORE_RC_SUBQUERIES); + } +} + +/* + * Walker to find sublink subqueries for ScanQueryForLocks + */ +static bool +ScanQueryWalker(Node *node, bool *acquire) +{ + if (node == NULL) + return false; + if (IsA(node, SubLink)) + { + SubLink *sub = (SubLink *) node; + + /* Do what we came for */ + ScanQueryForLocks(castNode(Query, sub->subselect), *acquire); + /* Fall through to process lefthand args of SubLink */ + } + + /* + * Do NOT recurse into Query nodes, because ScanQueryForLocks already + * processed subselects of subselects for us. + */ + return expression_tree_walker(node, ScanQueryWalker, + (void *) acquire); +} + +/* + * PlanCacheComputeResultDesc: given a list of analyzed-and-rewritten Queries, + * determine the result tupledesc it will produce. Returns NULL if the + * execution will not return tuples. + * + * Note: the result is created or copied into current memory context. + */ +static TupleDesc +PlanCacheComputeResultDesc(List *stmt_list) +{ + Query *query; + + switch (ChoosePortalStrategy(stmt_list)) + { + case PORTAL_ONE_SELECT: + case PORTAL_ONE_MOD_WITH: + query = linitial_node(Query, stmt_list); + return ExecCleanTypeFromTL(query->targetList); + + case PORTAL_ONE_RETURNING: + query = QueryListGetPrimaryStmt(stmt_list); + Assert(query->returningList); + return ExecCleanTypeFromTL(query->returningList); + + case PORTAL_UTIL_SELECT: + query = linitial_node(Query, stmt_list); + Assert(query->utilityStmt); + return UtilityTupleDescriptor(query->utilityStmt); + + case PORTAL_MULTI_QUERY: + /* will not return tuples */ + break; + } + return NULL; +} + +/* + * PlanCacheRelCallback + * Relcache inval callback function + * + * Invalidate all plans mentioning the given rel, or all plans mentioning + * any rel at all if relid == InvalidOid. + */ +static void +PlanCacheRelCallback(Datum arg, Oid relid) +{ + dlist_iter iter; + + dlist_foreach(iter, &saved_plan_list) + { + CachedPlanSource *plansource = dlist_container(CachedPlanSource, + node, iter.cur); + + Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC); + + /* No work if it's already invalidated */ + if (!plansource->is_valid) + continue; + + /* Never invalidate if parse/plan would be a no-op anyway */ + if (!StmtPlanRequiresRevalidation(plansource)) + continue; + + /* + * Check the dependency list for the rewritten querytree. + */ + if ((relid == InvalidOid) ? plansource->relationOids != NIL : + list_member_oid(plansource->relationOids, relid)) + { + /* Invalidate the querytree and generic plan */ + plansource->is_valid = false; + if (plansource->gplan) + plansource->gplan->is_valid = false; + } + + /* + * The generic plan, if any, could have more dependencies than the + * querytree does, so we have to check it too. + */ + if (plansource->gplan && plansource->gplan->is_valid) + { + ListCell *lc; + + foreach(lc, plansource->gplan->stmt_list) + { + PlannedStmt *plannedstmt = lfirst_node(PlannedStmt, lc); + + if (plannedstmt->commandType == CMD_UTILITY) + continue; /* Ignore utility statements */ + if ((relid == InvalidOid) ? plannedstmt->relationOids != NIL : + list_member_oid(plannedstmt->relationOids, relid)) + { + /* Invalidate the generic plan only */ + plansource->gplan->is_valid = false; + break; /* out of stmt_list scan */ + } + } + } + } + + /* Likewise check cached expressions */ + dlist_foreach(iter, &cached_expression_list) + { + CachedExpression *cexpr = dlist_container(CachedExpression, + node, iter.cur); + + Assert(cexpr->magic == CACHEDEXPR_MAGIC); + + /* No work if it's already invalidated */ + if (!cexpr->is_valid) + continue; + + if ((relid == InvalidOid) ? cexpr->relationOids != NIL : + list_member_oid(cexpr->relationOids, relid)) + { + cexpr->is_valid = false; + } + } +} + +/* + * PlanCacheObjectCallback + * Syscache inval callback function for PROCOID and TYPEOID caches + * + * Invalidate all plans mentioning the object with the specified hash value, + * or all plans mentioning any member of this cache if hashvalue == 0. + */ +static void +PlanCacheObjectCallback(Datum arg, int cacheid, uint32 hashvalue) +{ + dlist_iter iter; + + dlist_foreach(iter, &saved_plan_list) + { + CachedPlanSource *plansource = dlist_container(CachedPlanSource, + node, iter.cur); + ListCell *lc; + + Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC); + + /* No work if it's already invalidated */ + if (!plansource->is_valid) + continue; + + /* Never invalidate if parse/plan would be a no-op anyway */ + if (!StmtPlanRequiresRevalidation(plansource)) + continue; + + /* + * Check the dependency list for the rewritten querytree. + */ + foreach(lc, plansource->invalItems) + { + PlanInvalItem *item = (PlanInvalItem *) lfirst(lc); + + if (item->cacheId != cacheid) + continue; + if (hashvalue == 0 || + item->hashValue == hashvalue) + { + /* Invalidate the querytree and generic plan */ + plansource->is_valid = false; + if (plansource->gplan) + plansource->gplan->is_valid = false; + break; + } + } + + /* + * The generic plan, if any, could have more dependencies than the + * querytree does, so we have to check it too. + */ + if (plansource->gplan && plansource->gplan->is_valid) + { + foreach(lc, plansource->gplan->stmt_list) + { + PlannedStmt *plannedstmt = lfirst_node(PlannedStmt, lc); + ListCell *lc3; + + if (plannedstmt->commandType == CMD_UTILITY) + continue; /* Ignore utility statements */ + foreach(lc3, plannedstmt->invalItems) + { + PlanInvalItem *item = (PlanInvalItem *) lfirst(lc3); + + if (item->cacheId != cacheid) + continue; + if (hashvalue == 0 || + item->hashValue == hashvalue) + { + /* Invalidate the generic plan only */ + plansource->gplan->is_valid = false; + break; /* out of invalItems scan */ + } + } + if (!plansource->gplan->is_valid) + break; /* out of stmt_list scan */ + } + } + } + + /* Likewise check cached expressions */ + dlist_foreach(iter, &cached_expression_list) + { + CachedExpression *cexpr = dlist_container(CachedExpression, + node, iter.cur); + ListCell *lc; + + Assert(cexpr->magic == CACHEDEXPR_MAGIC); + + /* No work if it's already invalidated */ + if (!cexpr->is_valid) + continue; + + foreach(lc, cexpr->invalItems) + { + PlanInvalItem *item = (PlanInvalItem *) lfirst(lc); + + if (item->cacheId != cacheid) + continue; + if (hashvalue == 0 || + item->hashValue == hashvalue) + { + cexpr->is_valid = false; + break; + } + } + } +} + +/* + * PlanCacheSysCallback + * Syscache inval callback function for other caches + * + * Just invalidate everything... + */ +static void +PlanCacheSysCallback(Datum arg, int cacheid, uint32 hashvalue) +{ + ResetPlanCache(); +} + +/* + * ResetPlanCache: invalidate all cached plans. + */ +void +ResetPlanCache(void) +{ + dlist_iter iter; + + dlist_foreach(iter, &saved_plan_list) + { + CachedPlanSource *plansource = dlist_container(CachedPlanSource, + node, iter.cur); + + Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC); + + /* No work if it's already invalidated */ + if (!plansource->is_valid) + continue; + + /* + * We *must not* mark transaction control statements as invalid, + * particularly not ROLLBACK, because they may need to be executed in + * aborted transactions when we can't revalidate them (cf bug #5269). + * In general there's no point in invalidating statements for which a + * new parse analysis/rewrite/plan cycle would certainly give the same + * results. + */ + if (!StmtPlanRequiresRevalidation(plansource)) + continue; + + plansource->is_valid = false; + if (plansource->gplan) + plansource->gplan->is_valid = false; + } + + /* Likewise invalidate cached expressions */ + dlist_foreach(iter, &cached_expression_list) + { + CachedExpression *cexpr = dlist_container(CachedExpression, + node, iter.cur); + + Assert(cexpr->magic == CACHEDEXPR_MAGIC); + + cexpr->is_valid = false; + } +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/cache/relcache.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/cache/relcache.c new file mode 100644 index 00000000000..4c773202d9c --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/cache/relcache.c @@ -0,0 +1,6822 @@ +/*------------------------------------------------------------------------- + * + * relcache.c + * POSTGRES relation descriptor cache code + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/cache/relcache.c + * + *------------------------------------------------------------------------- + */ +/* + * INTERFACE ROUTINES + * RelationCacheInitialize - initialize relcache (to empty) + * RelationCacheInitializePhase2 - initialize shared-catalog entries + * RelationCacheInitializePhase3 - finish initializing relcache + * RelationIdGetRelation - get a reldesc by relation id + * RelationClose - close an open relation + * + * NOTES + * The following code contains many undocumented hacks. Please be + * careful.... + */ +#include "postgres.h" + +#include <sys/file.h> +#include <fcntl.h> +#include <unistd.h> + +#include "access/htup_details.h" +#include "access/multixact.h" +#include "access/nbtree.h" +#include "access/parallel.h" +#include "access/reloptions.h" +#include "access/sysattr.h" +#include "access/table.h" +#include "access/tableam.h" +#include "access/tupdesc_details.h" +#include "access/xact.h" +#include "access/xlog.h" +#include "catalog/binary_upgrade.h" +#include "catalog/catalog.h" +#include "catalog/indexing.h" +#include "catalog/namespace.h" +#include "catalog/partition.h" +#include "catalog/pg_am.h" +#include "catalog/pg_amproc.h" +#include "catalog/pg_attrdef.h" +#include "catalog/pg_auth_members.h" +#include "catalog/pg_authid.h" +#include "catalog/pg_constraint.h" +#include "catalog/pg_database.h" +#include "catalog/pg_namespace.h" +#include "catalog/pg_opclass.h" +#include "catalog/pg_proc.h" +#include "catalog/pg_publication.h" +#include "catalog/pg_rewrite.h" +#include "catalog/pg_shseclabel.h" +#include "catalog/pg_statistic_ext.h" +#include "catalog/pg_subscription.h" +#include "catalog/pg_tablespace.h" +#include "catalog/pg_trigger.h" +#include "catalog/pg_type.h" +#include "catalog/schemapg.h" +#include "catalog/storage.h" +#include "commands/policy.h" +#include "commands/publicationcmds.h" +#include "commands/trigger.h" +#include "miscadmin.h" +#include "nodes/makefuncs.h" +#include "nodes/nodeFuncs.h" +#include "optimizer/optimizer.h" +#include "pgstat.h" +#include "rewrite/rewriteDefine.h" +#include "rewrite/rowsecurity.h" +#include "storage/lmgr.h" +#include "storage/smgr.h" +#include "utils/array.h" +#include "utils/builtins.h" +#include "utils/datum.h" +#include "utils/fmgroids.h" +#include "utils/inval.h" +#include "utils/lsyscache.h" +#include "utils/memutils.h" +#include "utils/relmapper.h" +#include "utils/resowner_private.h" +#include "utils/snapmgr.h" +#include "utils/syscache.h" + +#define RELCACHE_INIT_FILEMAGIC 0x573266 /* version ID value */ + +/* + * Whether to bother checking if relation cache memory needs to be freed + * eagerly. See also RelationBuildDesc() and pg_config_manual.h. + */ +#if defined(RECOVER_RELATION_BUILD_MEMORY) && (RECOVER_RELATION_BUILD_MEMORY != 0) +#define MAYBE_RECOVER_RELATION_BUILD_MEMORY 1 +#else +#define RECOVER_RELATION_BUILD_MEMORY 0 +#ifdef DISCARD_CACHES_ENABLED +#define MAYBE_RECOVER_RELATION_BUILD_MEMORY 1 +#endif +#endif + +/* + * hardcoded tuple descriptors, contents generated by genbki.pl + */ +static const FormData_pg_attribute Desc_pg_class[Natts_pg_class] = {Schema_pg_class}; +static const FormData_pg_attribute Desc_pg_attribute[Natts_pg_attribute] = {Schema_pg_attribute}; +static const FormData_pg_attribute Desc_pg_proc[Natts_pg_proc] = {Schema_pg_proc}; +static const FormData_pg_attribute Desc_pg_type[Natts_pg_type] = {Schema_pg_type}; +static const FormData_pg_attribute Desc_pg_database[Natts_pg_database] = {Schema_pg_database}; +static const FormData_pg_attribute Desc_pg_authid[Natts_pg_authid] = {Schema_pg_authid}; +static const FormData_pg_attribute Desc_pg_auth_members[Natts_pg_auth_members] = {Schema_pg_auth_members}; +static const FormData_pg_attribute Desc_pg_index[Natts_pg_index] = {Schema_pg_index}; +static const FormData_pg_attribute Desc_pg_shseclabel[Natts_pg_shseclabel] = {Schema_pg_shseclabel}; +static const FormData_pg_attribute Desc_pg_subscription[Natts_pg_subscription] = {Schema_pg_subscription}; + +/* + * Hash tables that index the relation cache + * + * We used to index the cache by both name and OID, but now there + * is only an index by OID. + */ +typedef struct relidcacheent +{ + Oid reloid; + Relation reldesc; +} RelIdCacheEnt; + +static __thread HTAB *RelationIdCache; + +/* + * This flag is false until we have prepared the critical relcache entries + * that are needed to do indexscans on the tables read by relcache building. + */ +__thread bool criticalRelcachesBuilt = false; + +/* + * This flag is false until we have prepared the critical relcache entries + * for shared catalogs (which are the tables needed for login). + */ +__thread bool criticalSharedRelcachesBuilt = false; + +/* + * This counter counts relcache inval events received since backend startup + * (but only for rels that are actually in cache). Presently, we use it only + * to detect whether data about to be written by write_relcache_init_file() + * might already be obsolete. + */ +static __thread long relcacheInvalsReceived = 0L; + +/* + * in_progress_list is a stack of ongoing RelationBuildDesc() calls. CREATE + * INDEX CONCURRENTLY makes catalog changes under ShareUpdateExclusiveLock. + * It critically relies on each backend absorbing those changes no later than + * next transaction start. Hence, RelationBuildDesc() loops until it finishes + * without accepting a relevant invalidation. (Most invalidation consumers + * don't do this.) + */ +typedef struct inprogressent +{ + Oid reloid; /* OID of relation being built */ + bool invalidated; /* whether an invalidation arrived for it */ +} InProgressEnt; + +static __thread InProgressEnt *in_progress_list; +static __thread int in_progress_list_len; +static __thread int in_progress_list_maxlen; + +/* + * eoxact_list[] stores the OIDs of relations that (might) need AtEOXact + * cleanup work. This list intentionally has limited size; if it overflows, + * we fall back to scanning the whole hashtable. There is no value in a very + * large list because (1) at some point, a hash_seq_search scan is faster than + * retail lookups, and (2) the value of this is to reduce EOXact work for + * short transactions, which can't have dirtied all that many tables anyway. + * EOXactListAdd() does not bother to prevent duplicate list entries, so the + * cleanup processing must be idempotent. + */ +#define MAX_EOXACT_LIST 32 +static __thread Oid eoxact_list[MAX_EOXACT_LIST]; +static __thread int eoxact_list_len = 0; +static __thread bool eoxact_list_overflowed = false; + +#define EOXactListAdd(rel) \ + do { \ + if (eoxact_list_len < MAX_EOXACT_LIST) \ + eoxact_list[eoxact_list_len++] = (rel)->rd_id; \ + else \ + eoxact_list_overflowed = true; \ + } while (0) + +/* + * EOXactTupleDescArray stores TupleDescs that (might) need AtEOXact + * cleanup work. The array expands as needed; there is no hashtable because + * we don't need to access individual items except at EOXact. + */ +static __thread TupleDesc *EOXactTupleDescArray; +static __thread int NextEOXactTupleDescNum = 0; +static __thread int EOXactTupleDescArrayLen = 0; + +/* + * macros to manipulate the lookup hashtable + */ +#define RelationCacheInsert(RELATION, replace_allowed) \ +do { \ + RelIdCacheEnt *hentry; bool found; \ + hentry = (RelIdCacheEnt *) hash_search(RelationIdCache, \ + &((RELATION)->rd_id), \ + HASH_ENTER, &found); \ + if (found) \ + { \ + /* see comments in RelationBuildDesc and RelationBuildLocalRelation */ \ + Relation _old_rel = hentry->reldesc; \ + Assert(replace_allowed); \ + hentry->reldesc = (RELATION); \ + if (RelationHasReferenceCountZero(_old_rel)) \ + RelationDestroyRelation(_old_rel, false); \ + else if (!IsBootstrapProcessingMode()) \ + elog(WARNING, "leaking still-referenced relcache entry for \"%s\"", \ + RelationGetRelationName(_old_rel)); \ + } \ + else \ + hentry->reldesc = (RELATION); \ +} while(0) + +#define RelationIdCacheLookup(ID, RELATION) \ +do { \ + RelIdCacheEnt *hentry; \ + hentry = (RelIdCacheEnt *) hash_search(RelationIdCache, \ + &(ID), \ + HASH_FIND, NULL); \ + if (hentry) \ + RELATION = hentry->reldesc; \ + else \ + RELATION = NULL; \ +} while(0) + +#define RelationCacheDelete(RELATION) \ +do { \ + RelIdCacheEnt *hentry; \ + hentry = (RelIdCacheEnt *) hash_search(RelationIdCache, \ + &((RELATION)->rd_id), \ + HASH_REMOVE, NULL); \ + if (hentry == NULL) \ + elog(WARNING, "failed to delete relcache entry for OID %u", \ + (RELATION)->rd_id); \ +} while(0) + + +/* + * Special cache for opclass-related information + * + * Note: only default support procs get cached, ie, those with + * lefttype = righttype = opcintype. + */ +typedef struct opclasscacheent +{ + Oid opclassoid; /* lookup key: OID of opclass */ + bool valid; /* set true after successful fill-in */ + StrategyNumber numSupport; /* max # of support procs (from pg_am) */ + Oid opcfamily; /* OID of opclass's family */ + Oid opcintype; /* OID of opclass's declared input type */ + RegProcedure *supportProcs; /* OIDs of support procedures */ +} OpClassCacheEnt; + +static __thread HTAB *OpClassCache = NULL; + + +/* non-export function prototypes */ + +static void RelationDestroyRelation(Relation relation, bool remember_tupdesc); +static void RelationClearRelation(Relation relation, bool rebuild); + +static void RelationReloadIndexInfo(Relation relation); +static void RelationReloadNailed(Relation relation); +static void RelationFlushRelation(Relation relation); +static void RememberToFreeTupleDescAtEOX(TupleDesc td); +#ifdef USE_ASSERT_CHECKING +static void AssertPendingSyncConsistency(Relation relation); +#endif +static void AtEOXact_cleanup(Relation relation, bool isCommit); +static void AtEOSubXact_cleanup(Relation relation, bool isCommit, + SubTransactionId mySubid, SubTransactionId parentSubid); +static bool load_relcache_init_file(bool shared); +static void write_relcache_init_file(bool shared); +static void write_item(const void *data, Size len, FILE *fp); + +static void formrdesc(const char *relationName, Oid relationReltype, + bool isshared, int natts, const FormData_pg_attribute *attrs); + +static HeapTuple ScanPgRelation(Oid targetRelId, bool indexOK, bool force_non_historic); +static Relation AllocateRelationDesc(Form_pg_class relp); +static void RelationParseRelOptions(Relation relation, HeapTuple tuple); +static void RelationBuildTupleDesc(Relation relation); +static Relation RelationBuildDesc(Oid targetRelId, bool insertIt); +static void RelationInitPhysicalAddr(Relation relation); +static void load_critical_index(Oid indexoid, Oid heapoid); +static TupleDesc GetPgClassDescriptor(void); +static TupleDesc GetPgIndexDescriptor(void); +static void AttrDefaultFetch(Relation relation, int ndef); +static int AttrDefaultCmp(const void *a, const void *b); +static void CheckConstraintFetch(Relation relation); +static int CheckConstraintCmp(const void *a, const void *b); +static void InitIndexAmRoutine(Relation relation); +static void IndexSupportInitialize(oidvector *indclass, + RegProcedure *indexSupport, + Oid *opFamily, + Oid *opcInType, + StrategyNumber maxSupportNumber, + AttrNumber maxAttributeNumber); +static OpClassCacheEnt *LookupOpclassInfo(Oid operatorClassOid, + StrategyNumber numSupport); +static void RelationCacheInitFileRemoveInDir(const char *tblspcpath); +static void unlink_initfile(const char *initfilename, int elevel); + + +/* + * ScanPgRelation + * + * This is used by RelationBuildDesc to find a pg_class + * tuple matching targetRelId. The caller must hold at least + * AccessShareLock on the target relid to prevent concurrent-update + * scenarios; it isn't guaranteed that all scans used to build the + * relcache entry will use the same snapshot. If, for example, + * an attribute were to be added after scanning pg_class and before + * scanning pg_attribute, relnatts wouldn't match. + * + * NB: the returned tuple has been copied into palloc'd storage + * and must eventually be freed with heap_freetuple. + */ +static HeapTuple +ScanPgRelation(Oid targetRelId, bool indexOK, bool force_non_historic) +{ + HeapTuple pg_class_tuple; + Relation pg_class_desc; + SysScanDesc pg_class_scan; + ScanKeyData key[1]; + Snapshot snapshot = NULL; + + /* + * If something goes wrong during backend startup, we might find ourselves + * trying to read pg_class before we've selected a database. That ain't + * gonna work, so bail out with a useful error message. If this happens, + * it probably means a relcache entry that needs to be nailed isn't. + */ + if (!OidIsValid(MyDatabaseId)) + elog(FATAL, "cannot read pg_class without having selected a database"); + + /* + * form a scan key + */ + ScanKeyInit(&key[0], + Anum_pg_class_oid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(targetRelId)); + + /* + * Open pg_class and fetch a tuple. Force heap scan if we haven't yet + * built the critical relcache entries (this includes initdb and startup + * without a pg_internal.init file). The caller can also force a heap + * scan by setting indexOK == false. + */ + pg_class_desc = table_open(RelationRelationId, AccessShareLock); + + /* + * The caller might need a tuple that's newer than the one the historic + * snapshot; currently the only case requiring to do so is looking up the + * relfilenumber of non mapped system relations during decoding. That + * snapshot can't change in the midst of a relcache build, so there's no + * need to register the snapshot. + */ + if (force_non_historic) + snapshot = GetNonHistoricCatalogSnapshot(RelationRelationId); + + pg_class_scan = systable_beginscan(pg_class_desc, ClassOidIndexId, + indexOK && criticalRelcachesBuilt, + snapshot, + 1, key); + + pg_class_tuple = systable_getnext(pg_class_scan); + + /* + * Must copy tuple before releasing buffer. + */ + if (HeapTupleIsValid(pg_class_tuple)) + pg_class_tuple = heap_copytuple(pg_class_tuple); + + /* all done */ + systable_endscan(pg_class_scan); + table_close(pg_class_desc, AccessShareLock); + + return pg_class_tuple; +} + +/* + * AllocateRelationDesc + * + * This is used to allocate memory for a new relation descriptor + * and initialize the rd_rel field from the given pg_class tuple. + */ +static Relation +AllocateRelationDesc(Form_pg_class relp) +{ + Relation relation; + MemoryContext oldcxt; + Form_pg_class relationForm; + + /* Relcache entries must live in CacheMemoryContext */ + oldcxt = MemoryContextSwitchTo(CacheMemoryContext); + + /* + * allocate and zero space for new relation descriptor + */ + relation = (Relation) palloc0(sizeof(RelationData)); + + /* make sure relation is marked as having no open file yet */ + relation->rd_smgr = NULL; + + /* + * Copy the relation tuple form + * + * We only allocate space for the fixed fields, ie, CLASS_TUPLE_SIZE. The + * variable-length fields (relacl, reloptions) are NOT stored in the + * relcache --- there'd be little point in it, since we don't copy the + * tuple's nulls bitmap and hence wouldn't know if the values are valid. + * Bottom line is that relacl *cannot* be retrieved from the relcache. Get + * it from the syscache if you need it. The same goes for the original + * form of reloptions (however, we do store the parsed form of reloptions + * in rd_options). + */ + relationForm = (Form_pg_class) palloc(CLASS_TUPLE_SIZE); + + memcpy(relationForm, relp, CLASS_TUPLE_SIZE); + + /* initialize relation tuple form */ + relation->rd_rel = relationForm; + + /* and allocate attribute tuple form storage */ + relation->rd_att = CreateTemplateTupleDesc(relationForm->relnatts); + /* which we mark as a reference-counted tupdesc */ + relation->rd_att->tdrefcount = 1; + + MemoryContextSwitchTo(oldcxt); + + return relation; +} + +/* + * RelationParseRelOptions + * Convert pg_class.reloptions into pre-parsed rd_options + * + * tuple is the real pg_class tuple (not rd_rel!) for relation + * + * Note: rd_rel and (if an index) rd_indam must be valid already + */ +static void +RelationParseRelOptions(Relation relation, HeapTuple tuple) +{ + bytea *options; + amoptions_function amoptsfn; + + relation->rd_options = NULL; + + /* + * Look up any AM-specific parse function; fall out if relkind should not + * have options. + */ + switch (relation->rd_rel->relkind) + { + case RELKIND_RELATION: + case RELKIND_TOASTVALUE: + case RELKIND_VIEW: + case RELKIND_MATVIEW: + case RELKIND_PARTITIONED_TABLE: + amoptsfn = NULL; + break; + case RELKIND_INDEX: + case RELKIND_PARTITIONED_INDEX: + amoptsfn = relation->rd_indam->amoptions; + break; + default: + return; + } + + /* + * Fetch reloptions from tuple; have to use a hardwired descriptor because + * we might not have any other for pg_class yet (consider executing this + * code for pg_class itself) + */ + options = extractRelOptions(tuple, GetPgClassDescriptor(), amoptsfn); + + /* + * Copy parsed data into CacheMemoryContext. To guard against the + * possibility of leaks in the reloptions code, we want to do the actual + * parsing in the caller's memory context and copy the results into + * CacheMemoryContext after the fact. + */ + if (options) + { + relation->rd_options = MemoryContextAlloc(CacheMemoryContext, + VARSIZE(options)); + memcpy(relation->rd_options, options, VARSIZE(options)); + pfree(options); + } +} + +/* + * RelationBuildTupleDesc + * + * Form the relation's tuple descriptor from information in + * the pg_attribute, pg_attrdef & pg_constraint system catalogs. + */ +static void +RelationBuildTupleDesc(Relation relation) +{ + HeapTuple pg_attribute_tuple; + Relation pg_attribute_desc; + SysScanDesc pg_attribute_scan; + ScanKeyData skey[2]; + int need; + TupleConstr *constr; + AttrMissing *attrmiss = NULL; + int ndef = 0; + + /* fill rd_att's type ID fields (compare heap.c's AddNewRelationTuple) */ + relation->rd_att->tdtypeid = + relation->rd_rel->reltype ? relation->rd_rel->reltype : RECORDOID; + relation->rd_att->tdtypmod = -1; /* just to be sure */ + + constr = (TupleConstr *) MemoryContextAllocZero(CacheMemoryContext, + sizeof(TupleConstr)); + constr->has_not_null = false; + constr->has_generated_stored = false; + + /* + * Form a scan key that selects only user attributes (attnum > 0). + * (Eliminating system attribute rows at the index level is lots faster + * than fetching them.) + */ + ScanKeyInit(&skey[0], + Anum_pg_attribute_attrelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(RelationGetRelid(relation))); + ScanKeyInit(&skey[1], + Anum_pg_attribute_attnum, + BTGreaterStrategyNumber, F_INT2GT, + Int16GetDatum(0)); + + /* + * Open pg_attribute and begin a scan. Force heap scan if we haven't yet + * built the critical relcache entries (this includes initdb and startup + * without a pg_internal.init file). + */ + pg_attribute_desc = table_open(AttributeRelationId, AccessShareLock); + pg_attribute_scan = systable_beginscan(pg_attribute_desc, + AttributeRelidNumIndexId, + criticalRelcachesBuilt, + NULL, + 2, skey); + + /* + * add attribute data to relation->rd_att + */ + need = RelationGetNumberOfAttributes(relation); + + while (HeapTupleIsValid(pg_attribute_tuple = systable_getnext(pg_attribute_scan))) + { + Form_pg_attribute attp; + int attnum; + + attp = (Form_pg_attribute) GETSTRUCT(pg_attribute_tuple); + + attnum = attp->attnum; + if (attnum <= 0 || attnum > RelationGetNumberOfAttributes(relation)) + elog(ERROR, "invalid attribute number %d for relation \"%s\"", + attp->attnum, RelationGetRelationName(relation)); + + memcpy(TupleDescAttr(relation->rd_att, attnum - 1), + attp, + ATTRIBUTE_FIXED_PART_SIZE); + + /* Update constraint/default info */ + if (attp->attnotnull) + constr->has_not_null = true; + if (attp->attgenerated == ATTRIBUTE_GENERATED_STORED) + constr->has_generated_stored = true; + if (attp->atthasdef) + ndef++; + + /* If the column has a "missing" value, put it in the attrmiss array */ + if (attp->atthasmissing) + { + Datum missingval; + bool missingNull; + + /* Do we have a missing value? */ + missingval = heap_getattr(pg_attribute_tuple, + Anum_pg_attribute_attmissingval, + pg_attribute_desc->rd_att, + &missingNull); + if (!missingNull) + { + /* Yes, fetch from the array */ + MemoryContext oldcxt; + bool is_null; + int one = 1; + Datum missval; + + if (attrmiss == NULL) + attrmiss = (AttrMissing *) + MemoryContextAllocZero(CacheMemoryContext, + relation->rd_rel->relnatts * + sizeof(AttrMissing)); + + missval = array_get_element(missingval, + 1, + &one, + -1, + attp->attlen, + attp->attbyval, + attp->attalign, + &is_null); + Assert(!is_null); + if (attp->attbyval) + { + /* for copy by val just copy the datum direct */ + attrmiss[attnum - 1].am_value = missval; + } + else + { + /* otherwise copy in the correct context */ + oldcxt = MemoryContextSwitchTo(CacheMemoryContext); + attrmiss[attnum - 1].am_value = datumCopy(missval, + attp->attbyval, + attp->attlen); + MemoryContextSwitchTo(oldcxt); + } + attrmiss[attnum - 1].am_present = true; + } + } + need--; + if (need == 0) + break; + } + + /* + * end the scan and close the attribute relation + */ + systable_endscan(pg_attribute_scan); + table_close(pg_attribute_desc, AccessShareLock); + + if (need != 0) + elog(ERROR, "pg_attribute catalog is missing %d attribute(s) for relation OID %u", + need, RelationGetRelid(relation)); + + /* + * The attcacheoff values we read from pg_attribute should all be -1 + * ("unknown"). Verify this if assert checking is on. They will be + * computed when and if needed during tuple access. + */ +#ifdef USE_ASSERT_CHECKING + { + int i; + + for (i = 0; i < RelationGetNumberOfAttributes(relation); i++) + Assert(TupleDescAttr(relation->rd_att, i)->attcacheoff == -1); + } +#endif + + /* + * However, we can easily set the attcacheoff value for the first + * attribute: it must be zero. This eliminates the need for special cases + * for attnum=1 that used to exist in fastgetattr() and index_getattr(). + */ + if (RelationGetNumberOfAttributes(relation) > 0) + TupleDescAttr(relation->rd_att, 0)->attcacheoff = 0; + + /* + * Set up constraint/default info + */ + if (constr->has_not_null || + constr->has_generated_stored || + ndef > 0 || + attrmiss || + relation->rd_rel->relchecks > 0) + { + relation->rd_att->constr = constr; + + if (ndef > 0) /* DEFAULTs */ + AttrDefaultFetch(relation, ndef); + else + constr->num_defval = 0; + + constr->missing = attrmiss; + + if (relation->rd_rel->relchecks > 0) /* CHECKs */ + CheckConstraintFetch(relation); + else + constr->num_check = 0; + } + else + { + pfree(constr); + relation->rd_att->constr = NULL; + } +} + +/* + * RelationBuildRuleLock + * + * Form the relation's rewrite rules from information in + * the pg_rewrite system catalog. + * + * Note: The rule parsetrees are potentially very complex node structures. + * To allow these trees to be freed when the relcache entry is flushed, + * we make a private memory context to hold the RuleLock information for + * each relcache entry that has associated rules. The context is used + * just for rule info, not for any other subsidiary data of the relcache + * entry, because that keeps the update logic in RelationClearRelation() + * manageable. The other subsidiary data structures are simple enough + * to be easy to free explicitly, anyway. + * + * Note: The relation's reloptions must have been extracted first. + */ +static void +RelationBuildRuleLock(Relation relation) +{ + MemoryContext rulescxt; + MemoryContext oldcxt; + HeapTuple rewrite_tuple; + Relation rewrite_desc; + TupleDesc rewrite_tupdesc; + SysScanDesc rewrite_scan; + ScanKeyData key; + RuleLock *rulelock; + int numlocks; + RewriteRule **rules; + int maxlocks; + + /* + * Make the private context. Assume it'll not contain much data. + */ + rulescxt = AllocSetContextCreate(CacheMemoryContext, + "relation rules", + ALLOCSET_SMALL_SIZES); + relation->rd_rulescxt = rulescxt; + MemoryContextCopyAndSetIdentifier(rulescxt, + RelationGetRelationName(relation)); + + /* + * allocate an array to hold the rewrite rules (the array is extended if + * necessary) + */ + maxlocks = 4; + rules = (RewriteRule **) + MemoryContextAlloc(rulescxt, sizeof(RewriteRule *) * maxlocks); + numlocks = 0; + + /* + * form a scan key + */ + ScanKeyInit(&key, + Anum_pg_rewrite_ev_class, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(RelationGetRelid(relation))); + + /* + * open pg_rewrite and begin a scan + * + * Note: since we scan the rules using RewriteRelRulenameIndexId, we will + * be reading the rules in name order, except possibly during + * emergency-recovery operations (ie, IgnoreSystemIndexes). This in turn + * ensures that rules will be fired in name order. + */ + rewrite_desc = table_open(RewriteRelationId, AccessShareLock); + rewrite_tupdesc = RelationGetDescr(rewrite_desc); + rewrite_scan = systable_beginscan(rewrite_desc, + RewriteRelRulenameIndexId, + true, NULL, + 1, &key); + + while (HeapTupleIsValid(rewrite_tuple = systable_getnext(rewrite_scan))) + { + Form_pg_rewrite rewrite_form = (Form_pg_rewrite) GETSTRUCT(rewrite_tuple); + bool isnull; + Datum rule_datum; + char *rule_str; + RewriteRule *rule; + Oid check_as_user; + + rule = (RewriteRule *) MemoryContextAlloc(rulescxt, + sizeof(RewriteRule)); + + rule->ruleId = rewrite_form->oid; + + rule->event = rewrite_form->ev_type - '0'; + rule->enabled = rewrite_form->ev_enabled; + rule->isInstead = rewrite_form->is_instead; + + /* + * Must use heap_getattr to fetch ev_action and ev_qual. Also, the + * rule strings are often large enough to be toasted. To avoid + * leaking memory in the caller's context, do the detoasting here so + * we can free the detoasted version. + */ + rule_datum = heap_getattr(rewrite_tuple, + Anum_pg_rewrite_ev_action, + rewrite_tupdesc, + &isnull); + Assert(!isnull); + rule_str = TextDatumGetCString(rule_datum); + oldcxt = MemoryContextSwitchTo(rulescxt); + rule->actions = (List *) stringToNode(rule_str); + MemoryContextSwitchTo(oldcxt); + pfree(rule_str); + + rule_datum = heap_getattr(rewrite_tuple, + Anum_pg_rewrite_ev_qual, + rewrite_tupdesc, + &isnull); + Assert(!isnull); + rule_str = TextDatumGetCString(rule_datum); + oldcxt = MemoryContextSwitchTo(rulescxt); + rule->qual = (Node *) stringToNode(rule_str); + MemoryContextSwitchTo(oldcxt); + pfree(rule_str); + + /* + * If this is a SELECT rule defining a view, and the view has + * "security_invoker" set, we must perform all permissions checks on + * relations referred to by the rule as the invoking user. + * + * In all other cases (including non-SELECT rules on security invoker + * views), perform the permissions checks as the relation owner. + */ + if (rule->event == CMD_SELECT && + relation->rd_rel->relkind == RELKIND_VIEW && + RelationHasSecurityInvoker(relation)) + check_as_user = InvalidOid; + else + check_as_user = relation->rd_rel->relowner; + + /* + * Scan through the rule's actions and set the checkAsUser field on + * all RTEPermissionInfos. We have to look at the qual as well, in + * case it contains sublinks. + * + * The reason for doing this when the rule is loaded, rather than when + * it is stored, is that otherwise ALTER TABLE OWNER would have to + * grovel through stored rules to update checkAsUser fields. Scanning + * the rule tree during load is relatively cheap (compared to + * constructing it in the first place), so we do it here. + */ + setRuleCheckAsUser((Node *) rule->actions, check_as_user); + setRuleCheckAsUser(rule->qual, check_as_user); + + if (numlocks >= maxlocks) + { + maxlocks *= 2; + rules = (RewriteRule **) + repalloc(rules, sizeof(RewriteRule *) * maxlocks); + } + rules[numlocks++] = rule; + } + + /* + * end the scan and close the attribute relation + */ + systable_endscan(rewrite_scan); + table_close(rewrite_desc, AccessShareLock); + + /* + * there might not be any rules (if relhasrules is out-of-date) + */ + if (numlocks == 0) + { + relation->rd_rules = NULL; + relation->rd_rulescxt = NULL; + MemoryContextDelete(rulescxt); + return; + } + + /* + * form a RuleLock and insert into relation + */ + rulelock = (RuleLock *) MemoryContextAlloc(rulescxt, sizeof(RuleLock)); + rulelock->numLocks = numlocks; + rulelock->rules = rules; + + relation->rd_rules = rulelock; +} + +/* + * equalRuleLocks + * + * Determine whether two RuleLocks are equivalent + * + * Probably this should be in the rules code someplace... + */ +static bool +equalRuleLocks(RuleLock *rlock1, RuleLock *rlock2) +{ + int i; + + /* + * As of 7.3 we assume the rule ordering is repeatable, because + * RelationBuildRuleLock should read 'em in a consistent order. So just + * compare corresponding slots. + */ + if (rlock1 != NULL) + { + if (rlock2 == NULL) + return false; + if (rlock1->numLocks != rlock2->numLocks) + return false; + for (i = 0; i < rlock1->numLocks; i++) + { + RewriteRule *rule1 = rlock1->rules[i]; + RewriteRule *rule2 = rlock2->rules[i]; + + if (rule1->ruleId != rule2->ruleId) + return false; + if (rule1->event != rule2->event) + return false; + if (rule1->enabled != rule2->enabled) + return false; + if (rule1->isInstead != rule2->isInstead) + return false; + if (!equal(rule1->qual, rule2->qual)) + return false; + if (!equal(rule1->actions, rule2->actions)) + return false; + } + } + else if (rlock2 != NULL) + return false; + return true; +} + +/* + * equalPolicy + * + * Determine whether two policies are equivalent + */ +static bool +equalPolicy(RowSecurityPolicy *policy1, RowSecurityPolicy *policy2) +{ + int i; + Oid *r1, + *r2; + + if (policy1 != NULL) + { + if (policy2 == NULL) + return false; + + if (policy1->polcmd != policy2->polcmd) + return false; + if (policy1->hassublinks != policy2->hassublinks) + return false; + if (strcmp(policy1->policy_name, policy2->policy_name) != 0) + return false; + if (ARR_DIMS(policy1->roles)[0] != ARR_DIMS(policy2->roles)[0]) + return false; + + r1 = (Oid *) ARR_DATA_PTR(policy1->roles); + r2 = (Oid *) ARR_DATA_PTR(policy2->roles); + + for (i = 0; i < ARR_DIMS(policy1->roles)[0]; i++) + { + if (r1[i] != r2[i]) + return false; + } + + if (!equal(policy1->qual, policy2->qual)) + return false; + if (!equal(policy1->with_check_qual, policy2->with_check_qual)) + return false; + } + else if (policy2 != NULL) + return false; + + return true; +} + +/* + * equalRSDesc + * + * Determine whether two RowSecurityDesc's are equivalent + */ +static bool +equalRSDesc(RowSecurityDesc *rsdesc1, RowSecurityDesc *rsdesc2) +{ + ListCell *lc, + *rc; + + if (rsdesc1 == NULL && rsdesc2 == NULL) + return true; + + if ((rsdesc1 != NULL && rsdesc2 == NULL) || + (rsdesc1 == NULL && rsdesc2 != NULL)) + return false; + + if (list_length(rsdesc1->policies) != list_length(rsdesc2->policies)) + return false; + + /* RelationBuildRowSecurity should build policies in order */ + forboth(lc, rsdesc1->policies, rc, rsdesc2->policies) + { + RowSecurityPolicy *l = (RowSecurityPolicy *) lfirst(lc); + RowSecurityPolicy *r = (RowSecurityPolicy *) lfirst(rc); + + if (!equalPolicy(l, r)) + return false; + } + + return true; +} + +/* + * RelationBuildDesc + * + * Build a relation descriptor. The caller must hold at least + * AccessShareLock on the target relid. + * + * The new descriptor is inserted into the hash table if insertIt is true. + * + * Returns NULL if no pg_class row could be found for the given relid + * (suggesting we are trying to access a just-deleted relation). + * Any other error is reported via elog. + */ +static Relation +RelationBuildDesc(Oid targetRelId, bool insertIt) +{ + int in_progress_offset; + Relation relation; + Oid relid; + HeapTuple pg_class_tuple; + Form_pg_class relp; + + /* + * This function and its subroutines can allocate a good deal of transient + * data in CurrentMemoryContext. Traditionally we've just leaked that + * data, reasoning that the caller's context is at worst of transaction + * scope, and relcache loads shouldn't happen so often that it's essential + * to recover transient data before end of statement/transaction. However + * that's definitely not true when debug_discard_caches is active, and + * perhaps it's not true in other cases. + * + * When debug_discard_caches is active or when forced to by + * RECOVER_RELATION_BUILD_MEMORY=1, arrange to allocate the junk in a + * temporary context that we'll free before returning. Make it a child of + * caller's context so that it will get cleaned up appropriately if we + * error out partway through. + */ +#ifdef MAYBE_RECOVER_RELATION_BUILD_MEMORY + MemoryContext tmpcxt = NULL; + MemoryContext oldcxt = NULL; + + if (RECOVER_RELATION_BUILD_MEMORY || debug_discard_caches > 0) + { + tmpcxt = AllocSetContextCreate(CurrentMemoryContext, + "RelationBuildDesc workspace", + ALLOCSET_DEFAULT_SIZES); + oldcxt = MemoryContextSwitchTo(tmpcxt); + } +#endif + + /* Register to catch invalidation messages */ + if (in_progress_list_len >= in_progress_list_maxlen) + { + int allocsize; + + allocsize = in_progress_list_maxlen * 2; + in_progress_list = repalloc(in_progress_list, + allocsize * sizeof(*in_progress_list)); + in_progress_list_maxlen = allocsize; + } + in_progress_offset = in_progress_list_len++; + in_progress_list[in_progress_offset].reloid = targetRelId; +retry: + in_progress_list[in_progress_offset].invalidated = false; + + /* + * find the tuple in pg_class corresponding to the given relation id + */ + pg_class_tuple = ScanPgRelation(targetRelId, true, false); + + /* + * if no such tuple exists, return NULL + */ + if (!HeapTupleIsValid(pg_class_tuple)) + { +#ifdef MAYBE_RECOVER_RELATION_BUILD_MEMORY + if (tmpcxt) + { + /* Return to caller's context, and blow away the temporary context */ + MemoryContextSwitchTo(oldcxt); + MemoryContextDelete(tmpcxt); + } +#endif + Assert(in_progress_offset + 1 == in_progress_list_len); + in_progress_list_len--; + return NULL; + } + + /* + * get information from the pg_class_tuple + */ + relp = (Form_pg_class) GETSTRUCT(pg_class_tuple); + relid = relp->oid; + Assert(relid == targetRelId); + + /* + * allocate storage for the relation descriptor, and copy pg_class_tuple + * to relation->rd_rel. + */ + relation = AllocateRelationDesc(relp); + + /* + * initialize the relation's relation id (relation->rd_id) + */ + RelationGetRelid(relation) = relid; + + /* + * Normal relations are not nailed into the cache. Since we don't flush + * new relations, it won't be new. It could be temp though. + */ + relation->rd_refcnt = 0; + relation->rd_isnailed = false; + relation->rd_createSubid = InvalidSubTransactionId; + relation->rd_newRelfilelocatorSubid = InvalidSubTransactionId; + relation->rd_firstRelfilelocatorSubid = InvalidSubTransactionId; + relation->rd_droppedSubid = InvalidSubTransactionId; + switch (relation->rd_rel->relpersistence) + { + case RELPERSISTENCE_UNLOGGED: + case RELPERSISTENCE_PERMANENT: + relation->rd_backend = InvalidBackendId; + relation->rd_islocaltemp = false; + break; + case RELPERSISTENCE_TEMP: + if (isTempOrTempToastNamespace(relation->rd_rel->relnamespace)) + { + relation->rd_backend = BackendIdForTempRelations(); + relation->rd_islocaltemp = true; + } + else + { + /* + * If it's a temp table, but not one of ours, we have to use + * the slow, grotty method to figure out the owning backend. + * + * Note: it's possible that rd_backend gets set to MyBackendId + * here, in case we are looking at a pg_class entry left over + * from a crashed backend that coincidentally had the same + * BackendId we're using. We should *not* consider such a + * table to be "ours"; this is why we need the separate + * rd_islocaltemp flag. The pg_class entry will get flushed + * if/when we clean out the corresponding temp table namespace + * in preparation for using it. + */ + relation->rd_backend = + GetTempNamespaceBackendId(relation->rd_rel->relnamespace); + Assert(relation->rd_backend != InvalidBackendId); + relation->rd_islocaltemp = false; + } + break; + default: + elog(ERROR, "invalid relpersistence: %c", + relation->rd_rel->relpersistence); + break; + } + + /* + * initialize the tuple descriptor (relation->rd_att). + */ + RelationBuildTupleDesc(relation); + + /* foreign key data is not loaded till asked for */ + relation->rd_fkeylist = NIL; + relation->rd_fkeyvalid = false; + + /* partitioning data is not loaded till asked for */ + relation->rd_partkey = NULL; + relation->rd_partkeycxt = NULL; + relation->rd_partdesc = NULL; + relation->rd_partdesc_nodetached = NULL; + relation->rd_partdesc_nodetached_xmin = InvalidTransactionId; + relation->rd_pdcxt = NULL; + relation->rd_pddcxt = NULL; + relation->rd_partcheck = NIL; + relation->rd_partcheckvalid = false; + relation->rd_partcheckcxt = NULL; + + /* + * initialize access method information + */ + if (relation->rd_rel->relkind == RELKIND_INDEX || + relation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX) + RelationInitIndexAccessInfo(relation); + else if (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind) || + relation->rd_rel->relkind == RELKIND_SEQUENCE) + RelationInitTableAccessMethod(relation); + else + Assert(relation->rd_rel->relam == InvalidOid); + + /* extract reloptions if any */ + RelationParseRelOptions(relation, pg_class_tuple); + + /* + * Fetch rules and triggers that affect this relation. + * + * Note that RelationBuildRuleLock() relies on this being done after + * extracting the relation's reloptions. + */ + if (relation->rd_rel->relhasrules) + RelationBuildRuleLock(relation); + else + { + relation->rd_rules = NULL; + relation->rd_rulescxt = NULL; + } + + if (relation->rd_rel->relhastriggers) + RelationBuildTriggers(relation); + else + relation->trigdesc = NULL; + + if (relation->rd_rel->relrowsecurity) + RelationBuildRowSecurity(relation); + else + relation->rd_rsdesc = NULL; + + /* + * initialize the relation lock manager information + */ + RelationInitLockInfo(relation); /* see lmgr.c */ + + /* + * initialize physical addressing information for the relation + */ + RelationInitPhysicalAddr(relation); + + /* make sure relation is marked as having no open file yet */ + relation->rd_smgr = NULL; + + /* + * now we can free the memory allocated for pg_class_tuple + */ + heap_freetuple(pg_class_tuple); + + /* + * If an invalidation arrived mid-build, start over. Between here and the + * end of this function, don't add code that does or reasonably could read + * system catalogs. That range must be free from invalidation processing + * for the !insertIt case. For the insertIt case, RelationCacheInsert() + * will enroll this relation in ordinary relcache invalidation processing, + */ + if (in_progress_list[in_progress_offset].invalidated) + { + RelationDestroyRelation(relation, false); + goto retry; + } + Assert(in_progress_offset + 1 == in_progress_list_len); + in_progress_list_len--; + + /* + * Insert newly created relation into relcache hash table, if requested. + * + * There is one scenario in which we might find a hashtable entry already + * present, even though our caller failed to find it: if the relation is a + * system catalog or index that's used during relcache load, we might have + * recursively created the same relcache entry during the preceding steps. + * So allow RelationCacheInsert to delete any already-present relcache + * entry for the same OID. The already-present entry should have refcount + * zero (else somebody forgot to close it); in the event that it doesn't, + * we'll elog a WARNING and leak the already-present entry. + */ + if (insertIt) + RelationCacheInsert(relation, true); + + /* It's fully valid */ + relation->rd_isvalid = true; + +#ifdef MAYBE_RECOVER_RELATION_BUILD_MEMORY + if (tmpcxt) + { + /* Return to caller's context, and blow away the temporary context */ + MemoryContextSwitchTo(oldcxt); + MemoryContextDelete(tmpcxt); + } +#endif + + return relation; +} + +/* + * Initialize the physical addressing info (RelFileLocator) for a relcache entry + * + * Note: at the physical level, relations in the pg_global tablespace must + * be treated as shared, even if relisshared isn't set. Hence we do not + * look at relisshared here. + */ +static void +RelationInitPhysicalAddr(Relation relation) +{ + RelFileNumber oldnumber = relation->rd_locator.relNumber; + + /* these relations kinds never have storage */ + if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind)) + return; + + if (relation->rd_rel->reltablespace) + relation->rd_locator.spcOid = relation->rd_rel->reltablespace; + else + relation->rd_locator.spcOid = MyDatabaseTableSpace; + if (relation->rd_locator.spcOid == GLOBALTABLESPACE_OID) + relation->rd_locator.dbOid = InvalidOid; + else + relation->rd_locator.dbOid = MyDatabaseId; + + if (relation->rd_rel->relfilenode) + { + /* + * Even if we are using a decoding snapshot that doesn't represent the + * current state of the catalog we need to make sure the filenode + * points to the current file since the older file will be gone (or + * truncated). The new file will still contain older rows so lookups + * in them will work correctly. This wouldn't work correctly if + * rewrites were allowed to change the schema in an incompatible way, + * but those are prevented both on catalog tables and on user tables + * declared as additional catalog tables. + */ + if (HistoricSnapshotActive() + && RelationIsAccessibleInLogicalDecoding(relation) + && IsTransactionState()) + { + HeapTuple phys_tuple; + Form_pg_class physrel; + + phys_tuple = ScanPgRelation(RelationGetRelid(relation), + RelationGetRelid(relation) != ClassOidIndexId, + true); + if (!HeapTupleIsValid(phys_tuple)) + elog(ERROR, "could not find pg_class entry for %u", + RelationGetRelid(relation)); + physrel = (Form_pg_class) GETSTRUCT(phys_tuple); + + relation->rd_rel->reltablespace = physrel->reltablespace; + relation->rd_rel->relfilenode = physrel->relfilenode; + heap_freetuple(phys_tuple); + } + + relation->rd_locator.relNumber = relation->rd_rel->relfilenode; + } + else + { + /* Consult the relation mapper */ + relation->rd_locator.relNumber = + RelationMapOidToFilenumber(relation->rd_id, + relation->rd_rel->relisshared); + if (!RelFileNumberIsValid(relation->rd_locator.relNumber)) + elog(ERROR, "could not find relation mapping for relation \"%s\", OID %u", + RelationGetRelationName(relation), relation->rd_id); + } + + /* + * For RelationNeedsWAL() to answer correctly on parallel workers, restore + * rd_firstRelfilelocatorSubid. No subtransactions start or end while in + * parallel mode, so the specific SubTransactionId does not matter. + */ + if (IsParallelWorker() && oldnumber != relation->rd_locator.relNumber) + { + if (RelFileLocatorSkippingWAL(relation->rd_locator)) + relation->rd_firstRelfilelocatorSubid = TopSubTransactionId; + else + relation->rd_firstRelfilelocatorSubid = InvalidSubTransactionId; + } +} + +/* + * Fill in the IndexAmRoutine for an index relation. + * + * relation's rd_amhandler and rd_indexcxt must be valid already. + */ +static void +InitIndexAmRoutine(Relation relation) +{ + IndexAmRoutine *cached, + *tmp; + + /* + * Call the amhandler in current, short-lived memory context, just in case + * it leaks anything (it probably won't, but let's be paranoid). + */ + tmp = GetIndexAmRoutine(relation->rd_amhandler); + + /* OK, now transfer the data into relation's rd_indexcxt. */ + cached = (IndexAmRoutine *) MemoryContextAlloc(relation->rd_indexcxt, + sizeof(IndexAmRoutine)); + memcpy(cached, tmp, sizeof(IndexAmRoutine)); + relation->rd_indam = cached; + + pfree(tmp); +} + +/* + * Initialize index-access-method support data for an index relation + */ +void +RelationInitIndexAccessInfo(Relation relation) +{ + HeapTuple tuple; + Form_pg_am aform; + Datum indcollDatum; + Datum indclassDatum; + Datum indoptionDatum; + bool isnull; + oidvector *indcoll; + oidvector *indclass; + int2vector *indoption; + MemoryContext indexcxt; + MemoryContext oldcontext; + int indnatts; + int indnkeyatts; + uint16 amsupport; + + /* + * Make a copy of the pg_index entry for the index. Since pg_index + * contains variable-length and possibly-null fields, we have to do this + * honestly rather than just treating it as a Form_pg_index struct. + */ + tuple = SearchSysCache1(INDEXRELID, + ObjectIdGetDatum(RelationGetRelid(relation))); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for index %u", + RelationGetRelid(relation)); + oldcontext = MemoryContextSwitchTo(CacheMemoryContext); + relation->rd_indextuple = heap_copytuple(tuple); + relation->rd_index = (Form_pg_index) GETSTRUCT(relation->rd_indextuple); + MemoryContextSwitchTo(oldcontext); + ReleaseSysCache(tuple); + + /* + * Look up the index's access method, save the OID of its handler function + */ + Assert(relation->rd_rel->relam != InvalidOid); + tuple = SearchSysCache1(AMOID, ObjectIdGetDatum(relation->rd_rel->relam)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for access method %u", + relation->rd_rel->relam); + aform = (Form_pg_am) GETSTRUCT(tuple); + relation->rd_amhandler = aform->amhandler; + ReleaseSysCache(tuple); + + indnatts = RelationGetNumberOfAttributes(relation); + if (indnatts != IndexRelationGetNumberOfAttributes(relation)) + elog(ERROR, "relnatts disagrees with indnatts for index %u", + RelationGetRelid(relation)); + indnkeyatts = IndexRelationGetNumberOfKeyAttributes(relation); + + /* + * Make the private context to hold index access info. The reason we need + * a context, and not just a couple of pallocs, is so that we won't leak + * any subsidiary info attached to fmgr lookup records. + */ + indexcxt = AllocSetContextCreate(CacheMemoryContext, + "index info", + ALLOCSET_SMALL_SIZES); + relation->rd_indexcxt = indexcxt; + MemoryContextCopyAndSetIdentifier(indexcxt, + RelationGetRelationName(relation)); + + /* + * Now we can fetch the index AM's API struct + */ + InitIndexAmRoutine(relation); + + /* + * Allocate arrays to hold data. Opclasses are not used for included + * columns, so allocate them for indnkeyatts only. + */ + relation->rd_opfamily = (Oid *) + MemoryContextAllocZero(indexcxt, indnkeyatts * sizeof(Oid)); + relation->rd_opcintype = (Oid *) + MemoryContextAllocZero(indexcxt, indnkeyatts * sizeof(Oid)); + + amsupport = relation->rd_indam->amsupport; + if (amsupport > 0) + { + int nsupport = indnatts * amsupport; + + relation->rd_support = (RegProcedure *) + MemoryContextAllocZero(indexcxt, nsupport * sizeof(RegProcedure)); + relation->rd_supportinfo = (FmgrInfo *) + MemoryContextAllocZero(indexcxt, nsupport * sizeof(FmgrInfo)); + } + else + { + relation->rd_support = NULL; + relation->rd_supportinfo = NULL; + } + + relation->rd_indcollation = (Oid *) + MemoryContextAllocZero(indexcxt, indnkeyatts * sizeof(Oid)); + + relation->rd_indoption = (int16 *) + MemoryContextAllocZero(indexcxt, indnkeyatts * sizeof(int16)); + + /* + * indcollation cannot be referenced directly through the C struct, + * because it comes after the variable-width indkey field. Must extract + * the datum the hard way... + */ + indcollDatum = fastgetattr(relation->rd_indextuple, + Anum_pg_index_indcollation, + GetPgIndexDescriptor(), + &isnull); + Assert(!isnull); + indcoll = (oidvector *) DatumGetPointer(indcollDatum); + memcpy(relation->rd_indcollation, indcoll->values, indnkeyatts * sizeof(Oid)); + + /* + * indclass cannot be referenced directly through the C struct, because it + * comes after the variable-width indkey field. Must extract the datum + * the hard way... + */ + indclassDatum = fastgetattr(relation->rd_indextuple, + Anum_pg_index_indclass, + GetPgIndexDescriptor(), + &isnull); + Assert(!isnull); + indclass = (oidvector *) DatumGetPointer(indclassDatum); + + /* + * Fill the support procedure OID array, as well as the info about + * opfamilies and opclass input types. (aminfo and supportinfo are left + * as zeroes, and are filled on-the-fly when used) + */ + IndexSupportInitialize(indclass, relation->rd_support, + relation->rd_opfamily, relation->rd_opcintype, + amsupport, indnkeyatts); + + /* + * Similarly extract indoption and copy it to the cache entry + */ + indoptionDatum = fastgetattr(relation->rd_indextuple, + Anum_pg_index_indoption, + GetPgIndexDescriptor(), + &isnull); + Assert(!isnull); + indoption = (int2vector *) DatumGetPointer(indoptionDatum); + memcpy(relation->rd_indoption, indoption->values, indnkeyatts * sizeof(int16)); + + (void) RelationGetIndexAttOptions(relation, false); + + /* + * expressions, predicate, exclusion caches will be filled later + */ + relation->rd_indexprs = NIL; + relation->rd_indpred = NIL; + relation->rd_exclops = NULL; + relation->rd_exclprocs = NULL; + relation->rd_exclstrats = NULL; + relation->rd_amcache = NULL; +} + +/* + * IndexSupportInitialize + * Initializes an index's cached opclass information, + * given the index's pg_index.indclass entry. + * + * Data is returned into *indexSupport, *opFamily, and *opcInType, + * which are arrays allocated by the caller. + * + * The caller also passes maxSupportNumber and maxAttributeNumber, since these + * indicate the size of the arrays it has allocated --- but in practice these + * numbers must always match those obtainable from the system catalog entries + * for the index and access method. + */ +static void +IndexSupportInitialize(oidvector *indclass, + RegProcedure *indexSupport, + Oid *opFamily, + Oid *opcInType, + StrategyNumber maxSupportNumber, + AttrNumber maxAttributeNumber) +{ + int attIndex; + + for (attIndex = 0; attIndex < maxAttributeNumber; attIndex++) + { + OpClassCacheEnt *opcentry; + + if (!OidIsValid(indclass->values[attIndex])) + elog(ERROR, "bogus pg_index tuple"); + + /* look up the info for this opclass, using a cache */ + opcentry = LookupOpclassInfo(indclass->values[attIndex], + maxSupportNumber); + + /* copy cached data into relcache entry */ + opFamily[attIndex] = opcentry->opcfamily; + opcInType[attIndex] = opcentry->opcintype; + if (maxSupportNumber > 0) + memcpy(&indexSupport[attIndex * maxSupportNumber], + opcentry->supportProcs, + maxSupportNumber * sizeof(RegProcedure)); + } +} + +/* + * LookupOpclassInfo + * + * This routine maintains a per-opclass cache of the information needed + * by IndexSupportInitialize(). This is more efficient than relying on + * the catalog cache, because we can load all the info about a particular + * opclass in a single indexscan of pg_amproc. + * + * The information from pg_am about expected range of support function + * numbers is passed in, rather than being looked up, mainly because the + * caller will have it already. + * + * Note there is no provision for flushing the cache. This is OK at the + * moment because there is no way to ALTER any interesting properties of an + * existing opclass --- all you can do is drop it, which will result in + * a useless but harmless dead entry in the cache. To support altering + * opclass membership (not the same as opfamily membership!), we'd need to + * be able to flush this cache as well as the contents of relcache entries + * for indexes. + */ +static OpClassCacheEnt * +LookupOpclassInfo(Oid operatorClassOid, + StrategyNumber numSupport) +{ + OpClassCacheEnt *opcentry; + bool found; + Relation rel; + SysScanDesc scan; + ScanKeyData skey[3]; + HeapTuple htup; + bool indexOK; + + if (OpClassCache == NULL) + { + /* First time through: initialize the opclass cache */ + HASHCTL ctl; + + /* Also make sure CacheMemoryContext exists */ + if (!CacheMemoryContext) + CreateCacheMemoryContext(); + + ctl.keysize = sizeof(Oid); + ctl.entrysize = sizeof(OpClassCacheEnt); + OpClassCache = hash_create("Operator class cache", 64, + &ctl, HASH_ELEM | HASH_BLOBS); + } + + opcentry = (OpClassCacheEnt *) hash_search(OpClassCache, + &operatorClassOid, + HASH_ENTER, &found); + + if (!found) + { + /* Initialize new entry */ + opcentry->valid = false; /* until known OK */ + opcentry->numSupport = numSupport; + opcentry->supportProcs = NULL; /* filled below */ + } + else + { + Assert(numSupport == opcentry->numSupport); + } + + /* + * When aggressively testing cache-flush hazards, we disable the operator + * class cache and force reloading of the info on each call. This models + * no real-world behavior, since the cache entries are never invalidated + * otherwise. However it can be helpful for detecting bugs in the cache + * loading logic itself, such as reliance on a non-nailed index. Given + * the limited use-case and the fact that this adds a great deal of + * expense, we enable it only for high values of debug_discard_caches. + */ +#ifdef DISCARD_CACHES_ENABLED + if (debug_discard_caches > 2) + opcentry->valid = false; +#endif + + if (opcentry->valid) + return opcentry; + + /* + * Need to fill in new entry. First allocate space, unless we already did + * so in some previous attempt. + */ + if (opcentry->supportProcs == NULL && numSupport > 0) + opcentry->supportProcs = (RegProcedure *) + MemoryContextAllocZero(CacheMemoryContext, + numSupport * sizeof(RegProcedure)); + + /* + * To avoid infinite recursion during startup, force heap scans if we're + * looking up info for the opclasses used by the indexes we would like to + * reference here. + */ + indexOK = criticalRelcachesBuilt || + (operatorClassOid != OID_BTREE_OPS_OID && + operatorClassOid != INT2_BTREE_OPS_OID); + + /* + * We have to fetch the pg_opclass row to determine its opfamily and + * opcintype, which are needed to look up related operators and functions. + * It'd be convenient to use the syscache here, but that probably doesn't + * work while bootstrapping. + */ + ScanKeyInit(&skey[0], + Anum_pg_opclass_oid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(operatorClassOid)); + rel = table_open(OperatorClassRelationId, AccessShareLock); + scan = systable_beginscan(rel, OpclassOidIndexId, indexOK, + NULL, 1, skey); + + if (HeapTupleIsValid(htup = systable_getnext(scan))) + { + Form_pg_opclass opclassform = (Form_pg_opclass) GETSTRUCT(htup); + + opcentry->opcfamily = opclassform->opcfamily; + opcentry->opcintype = opclassform->opcintype; + } + else + elog(ERROR, "could not find tuple for opclass %u", operatorClassOid); + + systable_endscan(scan); + table_close(rel, AccessShareLock); + + /* + * Scan pg_amproc to obtain support procs for the opclass. We only fetch + * the default ones (those with lefttype = righttype = opcintype). + */ + if (numSupport > 0) + { + ScanKeyInit(&skey[0], + Anum_pg_amproc_amprocfamily, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(opcentry->opcfamily)); + ScanKeyInit(&skey[1], + Anum_pg_amproc_amproclefttype, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(opcentry->opcintype)); + ScanKeyInit(&skey[2], + Anum_pg_amproc_amprocrighttype, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(opcentry->opcintype)); + rel = table_open(AccessMethodProcedureRelationId, AccessShareLock); + scan = systable_beginscan(rel, AccessMethodProcedureIndexId, indexOK, + NULL, 3, skey); + + while (HeapTupleIsValid(htup = systable_getnext(scan))) + { + Form_pg_amproc amprocform = (Form_pg_amproc) GETSTRUCT(htup); + + if (amprocform->amprocnum <= 0 || + (StrategyNumber) amprocform->amprocnum > numSupport) + elog(ERROR, "invalid amproc number %d for opclass %u", + amprocform->amprocnum, operatorClassOid); + + opcentry->supportProcs[amprocform->amprocnum - 1] = + amprocform->amproc; + } + + systable_endscan(scan); + table_close(rel, AccessShareLock); + } + + opcentry->valid = true; + return opcentry; +} + +/* + * Fill in the TableAmRoutine for a relation + * + * relation's rd_amhandler must be valid already. + */ +static void +InitTableAmRoutine(Relation relation) +{ + relation->rd_tableam = GetTableAmRoutine(relation->rd_amhandler); +} + +/* + * Initialize table access method support for a table like relation + */ +void +RelationInitTableAccessMethod(Relation relation) +{ + HeapTuple tuple; + Form_pg_am aform; + + if (relation->rd_rel->relkind == RELKIND_SEQUENCE) + { + /* + * Sequences are currently accessed like heap tables, but it doesn't + * seem prudent to show that in the catalog. So just overwrite it + * here. + */ + Assert(relation->rd_rel->relam == InvalidOid); + relation->rd_amhandler = F_HEAP_TABLEAM_HANDLER; + } + else if (IsCatalogRelation(relation)) + { + /* + * Avoid doing a syscache lookup for catalog tables. + */ + Assert(relation->rd_rel->relam == HEAP_TABLE_AM_OID); + relation->rd_amhandler = F_HEAP_TABLEAM_HANDLER; + } + else + { + /* + * Look up the table access method, save the OID of its handler + * function. + */ + Assert(relation->rd_rel->relam != InvalidOid); + tuple = SearchSysCache1(AMOID, + ObjectIdGetDatum(relation->rd_rel->relam)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for access method %u", + relation->rd_rel->relam); + aform = (Form_pg_am) GETSTRUCT(tuple); + relation->rd_amhandler = aform->amhandler; + ReleaseSysCache(tuple); + } + + /* + * Now we can fetch the table AM's API struct + */ + InitTableAmRoutine(relation); +} + +/* + * formrdesc + * + * This is a special cut-down version of RelationBuildDesc(), + * used while initializing the relcache. + * The relation descriptor is built just from the supplied parameters, + * without actually looking at any system table entries. We cheat + * quite a lot since we only need to work for a few basic system + * catalogs. + * + * The catalogs this is used for can't have constraints (except attnotnull), + * default values, rules, or triggers, since we don't cope with any of that. + * (Well, actually, this only matters for properties that need to be valid + * during bootstrap or before RelationCacheInitializePhase3 runs, and none of + * these properties matter then...) + * + * NOTE: we assume we are already switched into CacheMemoryContext. + */ +static void +formrdesc(const char *relationName, Oid relationReltype, + bool isshared, + int natts, const FormData_pg_attribute *attrs) +{ + Relation relation; + int i; + bool has_not_null; + + /* + * allocate new relation desc, clear all fields of reldesc + */ + relation = (Relation) palloc0(sizeof(RelationData)); + + /* make sure relation is marked as having no open file yet */ + relation->rd_smgr = NULL; + + /* + * initialize reference count: 1 because it is nailed in cache + */ + relation->rd_refcnt = 1; + + /* + * all entries built with this routine are nailed-in-cache; none are for + * new or temp relations. + */ + relation->rd_isnailed = true; + relation->rd_createSubid = InvalidSubTransactionId; + relation->rd_newRelfilelocatorSubid = InvalidSubTransactionId; + relation->rd_firstRelfilelocatorSubid = InvalidSubTransactionId; + relation->rd_droppedSubid = InvalidSubTransactionId; + relation->rd_backend = InvalidBackendId; + relation->rd_islocaltemp = false; + + /* + * initialize relation tuple form + * + * The data we insert here is pretty incomplete/bogus, but it'll serve to + * get us launched. RelationCacheInitializePhase3() will read the real + * data from pg_class and replace what we've done here. Note in + * particular that relowner is left as zero; this cues + * RelationCacheInitializePhase3 that the real data isn't there yet. + */ + relation->rd_rel = (Form_pg_class) palloc0(CLASS_TUPLE_SIZE); + + namestrcpy(&relation->rd_rel->relname, relationName); + relation->rd_rel->relnamespace = PG_CATALOG_NAMESPACE; + relation->rd_rel->reltype = relationReltype; + + /* + * It's important to distinguish between shared and non-shared relations, + * even at bootstrap time, to make sure we know where they are stored. + */ + relation->rd_rel->relisshared = isshared; + if (isshared) + relation->rd_rel->reltablespace = GLOBALTABLESPACE_OID; + + /* formrdesc is used only for permanent relations */ + relation->rd_rel->relpersistence = RELPERSISTENCE_PERMANENT; + + /* ... and they're always populated, too */ + relation->rd_rel->relispopulated = true; + + relation->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING; + relation->rd_rel->relpages = 0; + relation->rd_rel->reltuples = -1; + relation->rd_rel->relallvisible = 0; + relation->rd_rel->relkind = RELKIND_RELATION; + relation->rd_rel->relnatts = (int16) natts; + relation->rd_rel->relam = HEAP_TABLE_AM_OID; + + /* + * initialize attribute tuple form + * + * Unlike the case with the relation tuple, this data had better be right + * because it will never be replaced. The data comes from + * src/include/catalog/ headers via genbki.pl. + */ + relation->rd_att = CreateTemplateTupleDesc(natts); + relation->rd_att->tdrefcount = 1; /* mark as refcounted */ + + relation->rd_att->tdtypeid = relationReltype; + relation->rd_att->tdtypmod = -1; /* just to be sure */ + + /* + * initialize tuple desc info + */ + has_not_null = false; + for (i = 0; i < natts; i++) + { + memcpy(TupleDescAttr(relation->rd_att, i), + &attrs[i], + ATTRIBUTE_FIXED_PART_SIZE); + has_not_null |= attrs[i].attnotnull; + /* make sure attcacheoff is valid */ + TupleDescAttr(relation->rd_att, i)->attcacheoff = -1; + } + + /* initialize first attribute's attcacheoff, cf RelationBuildTupleDesc */ + TupleDescAttr(relation->rd_att, 0)->attcacheoff = 0; + + /* mark not-null status */ + if (has_not_null) + { + TupleConstr *constr = (TupleConstr *) palloc0(sizeof(TupleConstr)); + + constr->has_not_null = true; + relation->rd_att->constr = constr; + } + + /* + * initialize relation id from info in att array (my, this is ugly) + */ + RelationGetRelid(relation) = TupleDescAttr(relation->rd_att, 0)->attrelid; + + /* + * All relations made with formrdesc are mapped. This is necessarily so + * because there is no other way to know what filenumber they currently + * have. In bootstrap mode, add them to the initial relation mapper data, + * specifying that the initial filenumber is the same as the OID. + */ + relation->rd_rel->relfilenode = InvalidRelFileNumber; + if (IsBootstrapProcessingMode()) + RelationMapUpdateMap(RelationGetRelid(relation), + RelationGetRelid(relation), + isshared, true); + + /* + * initialize the relation lock manager information + */ + RelationInitLockInfo(relation); /* see lmgr.c */ + + /* + * initialize physical addressing information for the relation + */ + RelationInitPhysicalAddr(relation); + + /* + * initialize the table am handler + */ + relation->rd_rel->relam = HEAP_TABLE_AM_OID; + relation->rd_tableam = GetHeapamTableAmRoutine(); + + /* + * initialize the rel-has-index flag, using hardwired knowledge + */ + if (IsBootstrapProcessingMode()) + { + /* In bootstrap mode, we have no indexes */ + relation->rd_rel->relhasindex = false; + } + else + { + /* Otherwise, all the rels formrdesc is used for have indexes */ + relation->rd_rel->relhasindex = true; + } + + /* + * add new reldesc to relcache + */ + RelationCacheInsert(relation, false); + + /* It's fully valid */ + relation->rd_isvalid = true; +} + + +/* ---------------------------------------------------------------- + * Relation Descriptor Lookup Interface + * ---------------------------------------------------------------- + */ + +/* + * RelationIdGetRelation + * + * Lookup a reldesc by OID; make one if not already in cache. + * + * Returns NULL if no pg_class row could be found for the given relid + * (suggesting we are trying to access a just-deleted relation). + * Any other error is reported via elog. + * + * NB: caller should already have at least AccessShareLock on the + * relation ID, else there are nasty race conditions. + * + * NB: relation ref count is incremented, or set to 1 if new entry. + * Caller should eventually decrement count. (Usually, + * that happens by calling RelationClose().) + */ +Relation +RelationIdGetRelation(Oid relationId) +{ + Relation rd; + + /* Make sure we're in an xact, even if this ends up being a cache hit */ + Assert(IsTransactionState()); + + /* + * first try to find reldesc in the cache + */ + RelationIdCacheLookup(relationId, rd); + + if (RelationIsValid(rd)) + { + /* return NULL for dropped relations */ + if (rd->rd_droppedSubid != InvalidSubTransactionId) + { + Assert(!rd->rd_isvalid); + return NULL; + } + + RelationIncrementReferenceCount(rd); + /* revalidate cache entry if necessary */ + if (!rd->rd_isvalid) + { + /* + * Indexes only have a limited number of possible schema changes, + * and we don't want to use the full-blown procedure because it's + * a headache for indexes that reload itself depends on. + */ + if (rd->rd_rel->relkind == RELKIND_INDEX || + rd->rd_rel->relkind == RELKIND_PARTITIONED_INDEX) + RelationReloadIndexInfo(rd); + else + RelationClearRelation(rd, true); + + /* + * Normally entries need to be valid here, but before the relcache + * has been initialized, not enough infrastructure exists to + * perform pg_class lookups. The structure of such entries doesn't + * change, but we still want to update the rd_rel entry. So + * rd_isvalid = false is left in place for a later lookup. + */ + Assert(rd->rd_isvalid || + (rd->rd_isnailed && !criticalRelcachesBuilt)); + } + return rd; + } + + /* + * no reldesc in the cache, so have RelationBuildDesc() build one and add + * it. + */ + rd = RelationBuildDesc(relationId, true); + if (RelationIsValid(rd)) + RelationIncrementReferenceCount(rd); + return rd; +} + +/* ---------------------------------------------------------------- + * cache invalidation support routines + * ---------------------------------------------------------------- + */ + +/* + * RelationIncrementReferenceCount + * Increments relation reference count. + * + * Note: bootstrap mode has its own weird ideas about relation refcount + * behavior; we ought to fix it someday, but for now, just disable + * reference count ownership tracking in bootstrap mode. + */ +void +RelationIncrementReferenceCount(Relation rel) +{ + ResourceOwnerEnlargeRelationRefs(CurrentResourceOwner); + rel->rd_refcnt += 1; + if (!IsBootstrapProcessingMode()) + ResourceOwnerRememberRelationRef(CurrentResourceOwner, rel); +} + +/* + * RelationDecrementReferenceCount + * Decrements relation reference count. + */ +void +RelationDecrementReferenceCount(Relation rel) +{ + Assert(rel->rd_refcnt > 0); + rel->rd_refcnt -= 1; + if (!IsBootstrapProcessingMode()) + ResourceOwnerForgetRelationRef(CurrentResourceOwner, rel); +} + +/* + * RelationClose - close an open relation + * + * Actually, we just decrement the refcount. + * + * NOTE: if compiled with -DRELCACHE_FORCE_RELEASE then relcache entries + * will be freed as soon as their refcount goes to zero. In combination + * with aset.c's CLOBBER_FREED_MEMORY option, this provides a good test + * to catch references to already-released relcache entries. It slows + * things down quite a bit, however. + */ +void +RelationClose(Relation relation) +{ + /* Note: no locking manipulations needed */ + RelationDecrementReferenceCount(relation); + + /* + * If the relation is no longer open in this session, we can clean up any + * stale partition descriptors it has. This is unlikely, so check to see + * if there are child contexts before expending a call to mcxt.c. + */ + if (RelationHasReferenceCountZero(relation)) + { + if (relation->rd_pdcxt != NULL && + relation->rd_pdcxt->firstchild != NULL) + MemoryContextDeleteChildren(relation->rd_pdcxt); + + if (relation->rd_pddcxt != NULL && + relation->rd_pddcxt->firstchild != NULL) + MemoryContextDeleteChildren(relation->rd_pddcxt); + } + +#ifdef RELCACHE_FORCE_RELEASE + if (RelationHasReferenceCountZero(relation) && + relation->rd_createSubid == InvalidSubTransactionId && + relation->rd_firstRelfilelocatorSubid == InvalidSubTransactionId) + RelationClearRelation(relation, false); +#endif +} + +/* + * RelationReloadIndexInfo - reload minimal information for an open index + * + * This function is used only for indexes. A relcache inval on an index + * can mean that its pg_class or pg_index row changed. There are only + * very limited changes that are allowed to an existing index's schema, + * so we can update the relcache entry without a complete rebuild; which + * is fortunate because we can't rebuild an index entry that is "nailed" + * and/or in active use. We support full replacement of the pg_class row, + * as well as updates of a few simple fields of the pg_index row. + * + * We can't necessarily reread the catalog rows right away; we might be + * in a failed transaction when we receive the SI notification. If so, + * RelationClearRelation just marks the entry as invalid by setting + * rd_isvalid to false. This routine is called to fix the entry when it + * is next needed. + * + * We assume that at the time we are called, we have at least AccessShareLock + * on the target index. (Note: in the calls from RelationClearRelation, + * this is legitimate because we know the rel has positive refcount.) + * + * If the target index is an index on pg_class or pg_index, we'd better have + * previously gotten at least AccessShareLock on its underlying catalog, + * else we are at risk of deadlock against someone trying to exclusive-lock + * the heap and index in that order. This is ensured in current usage by + * only applying this to indexes being opened or having positive refcount. + */ +static void +RelationReloadIndexInfo(Relation relation) +{ + bool indexOK; + HeapTuple pg_class_tuple; + Form_pg_class relp; + + /* Should be called only for invalidated, live indexes */ + Assert((relation->rd_rel->relkind == RELKIND_INDEX || + relation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX) && + !relation->rd_isvalid && + relation->rd_droppedSubid == InvalidSubTransactionId); + + /* Ensure it's closed at smgr level */ + RelationCloseSmgr(relation); + + /* Must free any AM cached data upon relcache flush */ + if (relation->rd_amcache) + pfree(relation->rd_amcache); + relation->rd_amcache = NULL; + + /* + * If it's a shared index, we might be called before backend startup has + * finished selecting a database, in which case we have no way to read + * pg_class yet. However, a shared index can never have any significant + * schema updates, so it's okay to ignore the invalidation signal. Just + * mark it valid and return without doing anything more. + */ + if (relation->rd_rel->relisshared && !criticalRelcachesBuilt) + { + relation->rd_isvalid = true; + return; + } + + /* + * Read the pg_class row + * + * Don't try to use an indexscan of pg_class_oid_index to reload the info + * for pg_class_oid_index ... + */ + indexOK = (RelationGetRelid(relation) != ClassOidIndexId); + pg_class_tuple = ScanPgRelation(RelationGetRelid(relation), indexOK, false); + if (!HeapTupleIsValid(pg_class_tuple)) + elog(ERROR, "could not find pg_class tuple for index %u", + RelationGetRelid(relation)); + relp = (Form_pg_class) GETSTRUCT(pg_class_tuple); + memcpy(relation->rd_rel, relp, CLASS_TUPLE_SIZE); + /* Reload reloptions in case they changed */ + if (relation->rd_options) + pfree(relation->rd_options); + RelationParseRelOptions(relation, pg_class_tuple); + /* done with pg_class tuple */ + heap_freetuple(pg_class_tuple); + /* We must recalculate physical address in case it changed */ + RelationInitPhysicalAddr(relation); + + /* + * For a non-system index, there are fields of the pg_index row that are + * allowed to change, so re-read that row and update the relcache entry. + * Most of the info derived from pg_index (such as support function lookup + * info) cannot change, and indeed the whole point of this routine is to + * update the relcache entry without clobbering that data; so wholesale + * replacement is not appropriate. + */ + if (!IsSystemRelation(relation)) + { + HeapTuple tuple; + Form_pg_index index; + + tuple = SearchSysCache1(INDEXRELID, + ObjectIdGetDatum(RelationGetRelid(relation))); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for index %u", + RelationGetRelid(relation)); + index = (Form_pg_index) GETSTRUCT(tuple); + + /* + * Basically, let's just copy all the bool fields. There are one or + * two of these that can't actually change in the current code, but + * it's not worth it to track exactly which ones they are. None of + * the array fields are allowed to change, though. + */ + relation->rd_index->indisunique = index->indisunique; + relation->rd_index->indnullsnotdistinct = index->indnullsnotdistinct; + relation->rd_index->indisprimary = index->indisprimary; + relation->rd_index->indisexclusion = index->indisexclusion; + relation->rd_index->indimmediate = index->indimmediate; + relation->rd_index->indisclustered = index->indisclustered; + relation->rd_index->indisvalid = index->indisvalid; + relation->rd_index->indcheckxmin = index->indcheckxmin; + relation->rd_index->indisready = index->indisready; + relation->rd_index->indislive = index->indislive; + relation->rd_index->indisreplident = index->indisreplident; + + /* Copy xmin too, as that is needed to make sense of indcheckxmin */ + HeapTupleHeaderSetXmin(relation->rd_indextuple->t_data, + HeapTupleHeaderGetXmin(tuple->t_data)); + + ReleaseSysCache(tuple); + } + + /* Okay, now it's valid again */ + relation->rd_isvalid = true; +} + +/* + * RelationReloadNailed - reload minimal information for nailed relations. + * + * The structure of a nailed relation can never change (which is good, because + * we rely on knowing their structure to be able to read catalog content). But + * some parts, e.g. pg_class.relfrozenxid, are still important to have + * accurate content for. Therefore those need to be reloaded after the arrival + * of invalidations. + */ +static void +RelationReloadNailed(Relation relation) +{ + Assert(relation->rd_isnailed); + + /* + * Redo RelationInitPhysicalAddr in case it is a mapped relation whose + * mapping changed. + */ + RelationInitPhysicalAddr(relation); + + /* flag as needing to be revalidated */ + relation->rd_isvalid = false; + + /* + * Can only reread catalog contents if in a transaction. If the relation + * is currently open (not counting the nailed refcount), do so + * immediately. Otherwise we've already marked the entry as possibly + * invalid, and it'll be fixed when next opened. + */ + if (!IsTransactionState() || relation->rd_refcnt <= 1) + return; + + if (relation->rd_rel->relkind == RELKIND_INDEX) + { + /* + * If it's a nailed-but-not-mapped index, then we need to re-read the + * pg_class row to see if its relfilenumber changed. + */ + RelationReloadIndexInfo(relation); + } + else + { + /* + * Reload a non-index entry. We can't easily do so if relcaches + * aren't yet built, but that's fine because at that stage the + * attributes that need to be current (like relfrozenxid) aren't yet + * accessed. To ensure the entry will later be revalidated, we leave + * it in invalid state, but allow use (cf. RelationIdGetRelation()). + */ + if (criticalRelcachesBuilt) + { + HeapTuple pg_class_tuple; + Form_pg_class relp; + + /* + * NB: Mark the entry as valid before starting to scan, to avoid + * self-recursion when re-building pg_class. + */ + relation->rd_isvalid = true; + + pg_class_tuple = ScanPgRelation(RelationGetRelid(relation), + true, false); + relp = (Form_pg_class) GETSTRUCT(pg_class_tuple); + memcpy(relation->rd_rel, relp, CLASS_TUPLE_SIZE); + heap_freetuple(pg_class_tuple); + + /* + * Again mark as valid, to protect against concurrently arriving + * invalidations. + */ + relation->rd_isvalid = true; + } + } +} + +/* + * RelationDestroyRelation + * + * Physically delete a relation cache entry and all subsidiary data. + * Caller must already have unhooked the entry from the hash table. + */ +static void +RelationDestroyRelation(Relation relation, bool remember_tupdesc) +{ + Assert(RelationHasReferenceCountZero(relation)); + + /* + * Make sure smgr and lower levels close the relation's files, if they + * weren't closed already. (This was probably done by caller, but let's + * just be real sure.) + */ + RelationCloseSmgr(relation); + + /* break mutual link with stats entry */ + pgstat_unlink_relation(relation); + + /* + * Free all the subsidiary data structures of the relcache entry, then the + * entry itself. + */ + if (relation->rd_rel) + pfree(relation->rd_rel); + /* can't use DecrTupleDescRefCount here */ + Assert(relation->rd_att->tdrefcount > 0); + if (--relation->rd_att->tdrefcount == 0) + { + /* + * If we Rebuilt a relcache entry during a transaction then its + * possible we did that because the TupDesc changed as the result of + * an ALTER TABLE that ran at less than AccessExclusiveLock. It's + * possible someone copied that TupDesc, in which case the copy would + * point to free'd memory. So if we rebuild an entry we keep the + * TupDesc around until end of transaction, to be safe. + */ + if (remember_tupdesc) + RememberToFreeTupleDescAtEOX(relation->rd_att); + else + FreeTupleDesc(relation->rd_att); + } + FreeTriggerDesc(relation->trigdesc); + list_free_deep(relation->rd_fkeylist); + list_free(relation->rd_indexlist); + list_free(relation->rd_statlist); + bms_free(relation->rd_keyattr); + bms_free(relation->rd_pkattr); + bms_free(relation->rd_idattr); + bms_free(relation->rd_hotblockingattr); + bms_free(relation->rd_summarizedattr); + if (relation->rd_pubdesc) + pfree(relation->rd_pubdesc); + if (relation->rd_options) + pfree(relation->rd_options); + if (relation->rd_indextuple) + pfree(relation->rd_indextuple); + if (relation->rd_amcache) + pfree(relation->rd_amcache); + if (relation->rd_fdwroutine) + pfree(relation->rd_fdwroutine); + if (relation->rd_indexcxt) + MemoryContextDelete(relation->rd_indexcxt); + if (relation->rd_rulescxt) + MemoryContextDelete(relation->rd_rulescxt); + if (relation->rd_rsdesc) + MemoryContextDelete(relation->rd_rsdesc->rscxt); + if (relation->rd_partkeycxt) + MemoryContextDelete(relation->rd_partkeycxt); + if (relation->rd_pdcxt) + MemoryContextDelete(relation->rd_pdcxt); + if (relation->rd_pddcxt) + MemoryContextDelete(relation->rd_pddcxt); + if (relation->rd_partcheckcxt) + MemoryContextDelete(relation->rd_partcheckcxt); + pfree(relation); +} + +/* + * RelationClearRelation + * + * Physically blow away a relation cache entry, or reset it and rebuild + * it from scratch (that is, from catalog entries). The latter path is + * used when we are notified of a change to an open relation (one with + * refcount > 0). + * + * NB: when rebuilding, we'd better hold some lock on the relation, + * else the catalog data we need to read could be changing under us. + * Also, a rel to be rebuilt had better have refcnt > 0. This is because + * a sinval reset could happen while we're accessing the catalogs, and + * the rel would get blown away underneath us by RelationCacheInvalidate + * if it has zero refcnt. + * + * The "rebuild" parameter is redundant in current usage because it has + * to match the relation's refcnt status, but we keep it as a crosscheck + * that we're doing what the caller expects. + */ +static void +RelationClearRelation(Relation relation, bool rebuild) +{ + /* + * As per notes above, a rel to be rebuilt MUST have refcnt > 0; while of + * course it would be an equally bad idea to blow away one with nonzero + * refcnt, since that would leave someone somewhere with a dangling + * pointer. All callers are expected to have verified that this holds. + */ + Assert(rebuild ? + !RelationHasReferenceCountZero(relation) : + RelationHasReferenceCountZero(relation)); + + /* + * Make sure smgr and lower levels close the relation's files, if they + * weren't closed already. If the relation is not getting deleted, the + * next smgr access should reopen the files automatically. This ensures + * that the low-level file access state is updated after, say, a vacuum + * truncation. + */ + RelationCloseSmgr(relation); + + /* Free AM cached data, if any */ + if (relation->rd_amcache) + pfree(relation->rd_amcache); + relation->rd_amcache = NULL; + + /* + * Treat nailed-in system relations separately, they always need to be + * accessible, so we can't blow them away. + */ + if (relation->rd_isnailed) + { + RelationReloadNailed(relation); + return; + } + + /* Mark it invalid until we've finished rebuild */ + relation->rd_isvalid = false; + + /* See RelationForgetRelation(). */ + if (relation->rd_droppedSubid != InvalidSubTransactionId) + return; + + /* + * Even non-system indexes should not be blown away if they are open and + * have valid index support information. This avoids problems with active + * use of the index support information. As with nailed indexes, we + * re-read the pg_class row to handle possible physical relocation of the + * index, and we check for pg_index updates too. + */ + if ((relation->rd_rel->relkind == RELKIND_INDEX || + relation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX) && + relation->rd_refcnt > 0 && + relation->rd_indexcxt != NULL) + { + if (IsTransactionState()) + RelationReloadIndexInfo(relation); + return; + } + + /* + * If we're really done with the relcache entry, blow it away. But if + * someone is still using it, reconstruct the whole deal without moving + * the physical RelationData record (so that the someone's pointer is + * still valid). + */ + if (!rebuild) + { + /* Remove it from the hash table */ + RelationCacheDelete(relation); + + /* And release storage */ + RelationDestroyRelation(relation, false); + } + else if (!IsTransactionState()) + { + /* + * If we're not inside a valid transaction, we can't do any catalog + * access so it's not possible to rebuild yet. Just exit, leaving + * rd_isvalid = false so that the rebuild will occur when the entry is + * next opened. + * + * Note: it's possible that we come here during subtransaction abort, + * and the reason for wanting to rebuild is that the rel is open in + * the outer transaction. In that case it might seem unsafe to not + * rebuild immediately, since whatever code has the rel already open + * will keep on using the relcache entry as-is. However, in such a + * case the outer transaction should be holding a lock that's + * sufficient to prevent any significant change in the rel's schema, + * so the existing entry contents should be good enough for its + * purposes; at worst we might be behind on statistics updates or the + * like. (See also CheckTableNotInUse() and its callers.) These same + * remarks also apply to the cases above where we exit without having + * done RelationReloadIndexInfo() yet. + */ + return; + } + else + { + /* + * Our strategy for rebuilding an open relcache entry is to build a + * new entry from scratch, swap its contents with the old entry, and + * finally delete the new entry (along with any infrastructure swapped + * over from the old entry). This is to avoid trouble in case an + * error causes us to lose control partway through. The old entry + * will still be marked !rd_isvalid, so we'll try to rebuild it again + * on next access. Meanwhile it's not any less valid than it was + * before, so any code that might expect to continue accessing it + * isn't hurt by the rebuild failure. (Consider for example a + * subtransaction that ALTERs a table and then gets canceled partway + * through the cache entry rebuild. The outer transaction should + * still see the not-modified cache entry as valid.) The worst + * consequence of an error is leaking the necessarily-unreferenced new + * entry, and this shouldn't happen often enough for that to be a big + * problem. + * + * When rebuilding an open relcache entry, we must preserve ref count, + * rd_*Subid, and rd_toastoid state. Also attempt to preserve the + * pg_class entry (rd_rel), tupledesc, rewrite-rule, partition key, + * and partition descriptor substructures in place, because various + * places assume that these structures won't move while they are + * working with an open relcache entry. (Note: the refcount + * mechanism for tupledescs might someday allow us to remove this hack + * for the tupledesc.) + * + * Note that this process does not touch CurrentResourceOwner; which + * is good because whatever ref counts the entry may have do not + * necessarily belong to that resource owner. + */ + Relation newrel; + Oid save_relid = RelationGetRelid(relation); + bool keep_tupdesc; + bool keep_rules; + bool keep_policies; + bool keep_partkey; + + /* Build temporary entry, but don't link it into hashtable */ + newrel = RelationBuildDesc(save_relid, false); + + /* + * Between here and the end of the swap, don't add code that does or + * reasonably could read system catalogs. That range must be free + * from invalidation processing. See RelationBuildDesc() manipulation + * of in_progress_list. + */ + + if (newrel == NULL) + { + /* + * We can validly get here, if we're using a historic snapshot in + * which a relation, accessed from outside logical decoding, is + * still invisible. In that case it's fine to just mark the + * relation as invalid and return - it'll fully get reloaded by + * the cache reset at the end of logical decoding (or at the next + * access). During normal processing we don't want to ignore this + * case as it shouldn't happen there, as explained below. + */ + if (HistoricSnapshotActive()) + return; + + /* + * This shouldn't happen as dropping a relation is intended to be + * impossible if still referenced (cf. CheckTableNotInUse()). But + * if we get here anyway, we can't just delete the relcache entry, + * as it possibly could get accessed later (as e.g. the error + * might get trapped and handled via a subtransaction rollback). + */ + elog(ERROR, "relation %u deleted while still in use", save_relid); + } + + /* + * If we were to, again, have cases of the relkind of a relcache entry + * changing, we would need to ensure that pgstats does not get + * confused. + */ + Assert(relation->rd_rel->relkind == newrel->rd_rel->relkind); + + keep_tupdesc = equalTupleDescs(relation->rd_att, newrel->rd_att); + keep_rules = equalRuleLocks(relation->rd_rules, newrel->rd_rules); + keep_policies = equalRSDesc(relation->rd_rsdesc, newrel->rd_rsdesc); + /* partkey is immutable once set up, so we can always keep it */ + keep_partkey = (relation->rd_partkey != NULL); + + /* + * Perform swapping of the relcache entry contents. Within this + * process the old entry is momentarily invalid, so there *must* be no + * possibility of CHECK_FOR_INTERRUPTS within this sequence. Do it in + * all-in-line code for safety. + * + * Since the vast majority of fields should be swapped, our method is + * to swap the whole structures and then re-swap those few fields we + * didn't want swapped. + */ +#define SWAPFIELD(fldtype, fldname) \ + do { \ + fldtype _tmp = newrel->fldname; \ + newrel->fldname = relation->fldname; \ + relation->fldname = _tmp; \ + } while (0) + + /* swap all Relation struct fields */ + { + RelationData tmpstruct; + + memcpy(&tmpstruct, newrel, sizeof(RelationData)); + memcpy(newrel, relation, sizeof(RelationData)); + memcpy(relation, &tmpstruct, sizeof(RelationData)); + } + + /* rd_smgr must not be swapped, due to back-links from smgr level */ + SWAPFIELD(SMgrRelation, rd_smgr); + /* rd_refcnt must be preserved */ + SWAPFIELD(int, rd_refcnt); + /* isnailed shouldn't change */ + Assert(newrel->rd_isnailed == relation->rd_isnailed); + /* creation sub-XIDs must be preserved */ + SWAPFIELD(SubTransactionId, rd_createSubid); + SWAPFIELD(SubTransactionId, rd_newRelfilelocatorSubid); + SWAPFIELD(SubTransactionId, rd_firstRelfilelocatorSubid); + SWAPFIELD(SubTransactionId, rd_droppedSubid); + /* un-swap rd_rel pointers, swap contents instead */ + SWAPFIELD(Form_pg_class, rd_rel); + /* ... but actually, we don't have to update newrel->rd_rel */ + memcpy(relation->rd_rel, newrel->rd_rel, CLASS_TUPLE_SIZE); + /* preserve old tupledesc, rules, policies if no logical change */ + if (keep_tupdesc) + SWAPFIELD(TupleDesc, rd_att); + if (keep_rules) + { + SWAPFIELD(RuleLock *, rd_rules); + SWAPFIELD(MemoryContext, rd_rulescxt); + } + if (keep_policies) + SWAPFIELD(RowSecurityDesc *, rd_rsdesc); + /* toast OID override must be preserved */ + SWAPFIELD(Oid, rd_toastoid); + /* pgstat_info / enabled must be preserved */ + SWAPFIELD(struct PgStat_TableStatus *, pgstat_info); + SWAPFIELD(bool, pgstat_enabled); + /* preserve old partition key if we have one */ + if (keep_partkey) + { + SWAPFIELD(PartitionKey, rd_partkey); + SWAPFIELD(MemoryContext, rd_partkeycxt); + } + if (newrel->rd_pdcxt != NULL || newrel->rd_pddcxt != NULL) + { + /* + * We are rebuilding a partitioned relation with a non-zero + * reference count, so we must keep the old partition descriptor + * around, in case there's a PartitionDirectory with a pointer to + * it. This means we can't free the old rd_pdcxt yet. (This is + * necessary because RelationGetPartitionDesc hands out direct + * pointers to the relcache's data structure, unlike our usual + * practice which is to hand out copies. We'd have the same + * problem with rd_partkey, except that we always preserve that + * once created.) + * + * To ensure that it's not leaked completely, re-attach it to the + * new reldesc, or make it a child of the new reldesc's rd_pdcxt + * in the unlikely event that there is one already. (Compare hack + * in RelationBuildPartitionDesc.) RelationClose will clean up + * any such contexts once the reference count reaches zero. + * + * In the case where the reference count is zero, this code is not + * reached, which should be OK because in that case there should + * be no PartitionDirectory with a pointer to the old entry. + * + * Note that newrel and relation have already been swapped, so the + * "old" partition descriptor is actually the one hanging off of + * newrel. + */ + relation->rd_partdesc = NULL; /* ensure rd_partdesc is invalid */ + relation->rd_partdesc_nodetached = NULL; + relation->rd_partdesc_nodetached_xmin = InvalidTransactionId; + if (relation->rd_pdcxt != NULL) /* probably never happens */ + MemoryContextSetParent(newrel->rd_pdcxt, relation->rd_pdcxt); + else + relation->rd_pdcxt = newrel->rd_pdcxt; + if (relation->rd_pddcxt != NULL) + MemoryContextSetParent(newrel->rd_pddcxt, relation->rd_pddcxt); + else + relation->rd_pddcxt = newrel->rd_pddcxt; + /* drop newrel's pointers so we don't destroy it below */ + newrel->rd_partdesc = NULL; + newrel->rd_partdesc_nodetached = NULL; + newrel->rd_partdesc_nodetached_xmin = InvalidTransactionId; + newrel->rd_pdcxt = NULL; + newrel->rd_pddcxt = NULL; + } + +#undef SWAPFIELD + + /* And now we can throw away the temporary entry */ + RelationDestroyRelation(newrel, !keep_tupdesc); + } +} + +/* + * RelationFlushRelation + * + * Rebuild the relation if it is open (refcount > 0), else blow it away. + * This is used when we receive a cache invalidation event for the rel. + */ +static void +RelationFlushRelation(Relation relation) +{ + if (relation->rd_createSubid != InvalidSubTransactionId || + relation->rd_firstRelfilelocatorSubid != InvalidSubTransactionId) + { + /* + * New relcache entries are always rebuilt, not flushed; else we'd + * forget the "new" status of the relation. Ditto for the + * new-relfilenumber status. + * + * The rel could have zero refcnt here, so temporarily increment the + * refcnt to ensure it's safe to rebuild it. We can assume that the + * current transaction has some lock on the rel already. + */ + RelationIncrementReferenceCount(relation); + RelationClearRelation(relation, true); + RelationDecrementReferenceCount(relation); + } + else + { + /* + * Pre-existing rels can be dropped from the relcache if not open. + */ + bool rebuild = !RelationHasReferenceCountZero(relation); + + RelationClearRelation(relation, rebuild); + } +} + +/* + * RelationForgetRelation - caller reports that it dropped the relation + */ +void +RelationForgetRelation(Oid rid) +{ + Relation relation; + + RelationIdCacheLookup(rid, relation); + + if (!PointerIsValid(relation)) + return; /* not in cache, nothing to do */ + + if (!RelationHasReferenceCountZero(relation)) + elog(ERROR, "relation %u is still open", rid); + + Assert(relation->rd_droppedSubid == InvalidSubTransactionId); + if (relation->rd_createSubid != InvalidSubTransactionId || + relation->rd_firstRelfilelocatorSubid != InvalidSubTransactionId) + { + /* + * In the event of subtransaction rollback, we must not forget + * rd_*Subid. Mark the entry "dropped" so RelationClearRelation() + * invalidates it in lieu of destroying it. (If we're in a top + * transaction, we could opt to destroy the entry.) + */ + relation->rd_droppedSubid = GetCurrentSubTransactionId(); + } + + RelationClearRelation(relation, false); +} + +/* + * RelationCacheInvalidateEntry + * + * This routine is invoked for SI cache flush messages. + * + * Any relcache entry matching the relid must be flushed. (Note: caller has + * already determined that the relid belongs to our database or is a shared + * relation.) + * + * We used to skip local relations, on the grounds that they could + * not be targets of cross-backend SI update messages; but it seems + * safer to process them, so that our *own* SI update messages will + * have the same effects during CommandCounterIncrement for both + * local and nonlocal relations. + */ +void +RelationCacheInvalidateEntry(Oid relationId) +{ + Relation relation; + + RelationIdCacheLookup(relationId, relation); + + if (PointerIsValid(relation)) + { + relcacheInvalsReceived++; + RelationFlushRelation(relation); + } + else + { + int i; + + for (i = 0; i < in_progress_list_len; i++) + if (in_progress_list[i].reloid == relationId) + in_progress_list[i].invalidated = true; + } +} + +/* + * RelationCacheInvalidate + * Blow away cached relation descriptors that have zero reference counts, + * and rebuild those with positive reference counts. Also reset the smgr + * relation cache and re-read relation mapping data. + * + * Apart from debug_discard_caches, this is currently used only to recover + * from SI message buffer overflow, so we do not touch relations having + * new-in-transaction relfilenumbers; they cannot be targets of cross-backend + * SI updates (and our own updates now go through a separate linked list + * that isn't limited by the SI message buffer size). + * + * We do this in two phases: the first pass deletes deletable items, and + * the second one rebuilds the rebuildable items. This is essential for + * safety, because hash_seq_search only copes with concurrent deletion of + * the element it is currently visiting. If a second SI overflow were to + * occur while we are walking the table, resulting in recursive entry to + * this routine, we could crash because the inner invocation blows away + * the entry next to be visited by the outer scan. But this way is OK, + * because (a) during the first pass we won't process any more SI messages, + * so hash_seq_search will complete safely; (b) during the second pass we + * only hold onto pointers to nondeletable entries. + * + * The two-phase approach also makes it easy to update relfilenumbers for + * mapped relations before we do anything else, and to ensure that the + * second pass processes nailed-in-cache items before other nondeletable + * items. This should ensure that system catalogs are up to date before + * we attempt to use them to reload information about other open relations. + * + * After those two phases of work having immediate effects, we normally + * signal any RelationBuildDesc() on the stack to start over. However, we + * don't do this if called as part of debug_discard_caches. Otherwise, + * RelationBuildDesc() would become an infinite loop. + */ +void +RelationCacheInvalidate(bool debug_discard) +{ + HASH_SEQ_STATUS status; + RelIdCacheEnt *idhentry; + Relation relation; + List *rebuildFirstList = NIL; + List *rebuildList = NIL; + ListCell *l; + int i; + + /* + * Reload relation mapping data before starting to reconstruct cache. + */ + RelationMapInvalidateAll(); + + /* Phase 1 */ + hash_seq_init(&status, RelationIdCache); + + while ((idhentry = (RelIdCacheEnt *) hash_seq_search(&status)) != NULL) + { + relation = idhentry->reldesc; + + /* Must close all smgr references to avoid leaving dangling ptrs */ + RelationCloseSmgr(relation); + + /* + * Ignore new relations; no other backend will manipulate them before + * we commit. Likewise, before replacing a relation's relfilelocator, + * we shall have acquired AccessExclusiveLock and drained any + * applicable pending invalidations. + */ + if (relation->rd_createSubid != InvalidSubTransactionId || + relation->rd_firstRelfilelocatorSubid != InvalidSubTransactionId) + continue; + + relcacheInvalsReceived++; + + if (RelationHasReferenceCountZero(relation)) + { + /* Delete this entry immediately */ + Assert(!relation->rd_isnailed); + RelationClearRelation(relation, false); + } + else + { + /* + * If it's a mapped relation, immediately update its rd_locator in + * case its relfilenumber changed. We must do this during phase 1 + * in case the relation is consulted during rebuild of other + * relcache entries in phase 2. It's safe since consulting the + * map doesn't involve any access to relcache entries. + */ + if (RelationIsMapped(relation)) + RelationInitPhysicalAddr(relation); + + /* + * Add this entry to list of stuff to rebuild in second pass. + * pg_class goes to the front of rebuildFirstList while + * pg_class_oid_index goes to the back of rebuildFirstList, so + * they are done first and second respectively. Other nailed + * relations go to the front of rebuildList, so they'll be done + * next in no particular order; and everything else goes to the + * back of rebuildList. + */ + if (RelationGetRelid(relation) == RelationRelationId) + rebuildFirstList = lcons(relation, rebuildFirstList); + else if (RelationGetRelid(relation) == ClassOidIndexId) + rebuildFirstList = lappend(rebuildFirstList, relation); + else if (relation->rd_isnailed) + rebuildList = lcons(relation, rebuildList); + else + rebuildList = lappend(rebuildList, relation); + } + } + + /* + * Now zap any remaining smgr cache entries. This must happen before we + * start to rebuild entries, since that may involve catalog fetches which + * will re-open catalog files. + */ + smgrcloseall(); + + /* Phase 2: rebuild the items found to need rebuild in phase 1 */ + foreach(l, rebuildFirstList) + { + relation = (Relation) lfirst(l); + RelationClearRelation(relation, true); + } + list_free(rebuildFirstList); + foreach(l, rebuildList) + { + relation = (Relation) lfirst(l); + RelationClearRelation(relation, true); + } + list_free(rebuildList); + + if (!debug_discard) + /* Any RelationBuildDesc() on the stack must start over. */ + for (i = 0; i < in_progress_list_len; i++) + in_progress_list[i].invalidated = true; +} + +/* + * RelationCloseSmgrByOid - close a relcache entry's smgr link + * + * Needed in some cases where we are changing a relation's physical mapping. + * The link will be automatically reopened on next use. + */ +void +RelationCloseSmgrByOid(Oid relationId) +{ + Relation relation; + + RelationIdCacheLookup(relationId, relation); + + if (!PointerIsValid(relation)) + return; /* not in cache, nothing to do */ + + RelationCloseSmgr(relation); +} + +static void +RememberToFreeTupleDescAtEOX(TupleDesc td) +{ + if (EOXactTupleDescArray == NULL) + { + MemoryContext oldcxt; + + oldcxt = MemoryContextSwitchTo(CacheMemoryContext); + + EOXactTupleDescArray = (TupleDesc *) palloc(16 * sizeof(TupleDesc)); + EOXactTupleDescArrayLen = 16; + NextEOXactTupleDescNum = 0; + MemoryContextSwitchTo(oldcxt); + } + else if (NextEOXactTupleDescNum >= EOXactTupleDescArrayLen) + { + int32 newlen = EOXactTupleDescArrayLen * 2; + + Assert(EOXactTupleDescArrayLen > 0); + + EOXactTupleDescArray = (TupleDesc *) repalloc(EOXactTupleDescArray, + newlen * sizeof(TupleDesc)); + EOXactTupleDescArrayLen = newlen; + } + + EOXactTupleDescArray[NextEOXactTupleDescNum++] = td; +} + +#ifdef USE_ASSERT_CHECKING +static void +AssertPendingSyncConsistency(Relation relation) +{ + bool relcache_verdict = + RelationIsPermanent(relation) && + ((relation->rd_createSubid != InvalidSubTransactionId && + RELKIND_HAS_STORAGE(relation->rd_rel->relkind)) || + relation->rd_firstRelfilelocatorSubid != InvalidSubTransactionId); + + Assert(relcache_verdict == RelFileLocatorSkippingWAL(relation->rd_locator)); + + if (relation->rd_droppedSubid != InvalidSubTransactionId) + Assert(!relation->rd_isvalid && + (relation->rd_createSubid != InvalidSubTransactionId || + relation->rd_firstRelfilelocatorSubid != InvalidSubTransactionId)); +} + +/* + * AssertPendingSyncs_RelationCache + * + * Assert that relcache.c and storage.c agree on whether to skip WAL. + */ +void +AssertPendingSyncs_RelationCache(void) +{ + HASH_SEQ_STATUS status; + LOCALLOCK *locallock; + Relation *rels; + int maxrels; + int nrels; + RelIdCacheEnt *idhentry; + int i; + + /* + * Open every relation that this transaction has locked. If, for some + * relation, storage.c is skipping WAL and relcache.c is not skipping WAL, + * a CommandCounterIncrement() typically yields a local invalidation + * message that destroys the relcache entry. By recreating such entries + * here, we detect the problem. + */ + PushActiveSnapshot(GetTransactionSnapshot()); + maxrels = 1; + rels = palloc(maxrels * sizeof(*rels)); + nrels = 0; + hash_seq_init(&status, GetLockMethodLocalHash()); + while ((locallock = (LOCALLOCK *) hash_seq_search(&status)) != NULL) + { + Oid relid; + Relation r; + + if (locallock->nLocks <= 0) + continue; + if ((LockTagType) locallock->tag.lock.locktag_type != + LOCKTAG_RELATION) + continue; + relid = ObjectIdGetDatum(locallock->tag.lock.locktag_field2); + r = RelationIdGetRelation(relid); + if (!RelationIsValid(r)) + continue; + if (nrels >= maxrels) + { + maxrels *= 2; + rels = repalloc(rels, maxrels * sizeof(*rels)); + } + rels[nrels++] = r; + } + + hash_seq_init(&status, RelationIdCache); + while ((idhentry = (RelIdCacheEnt *) hash_seq_search(&status)) != NULL) + AssertPendingSyncConsistency(idhentry->reldesc); + + for (i = 0; i < nrels; i++) + RelationClose(rels[i]); + PopActiveSnapshot(); +} +#endif + +/* + * AtEOXact_RelationCache + * + * Clean up the relcache at main-transaction commit or abort. + * + * Note: this must be called *before* processing invalidation messages. + * In the case of abort, we don't want to try to rebuild any invalidated + * cache entries (since we can't safely do database accesses). Therefore + * we must reset refcnts before handling pending invalidations. + * + * As of PostgreSQL 8.1, relcache refcnts should get released by the + * ResourceOwner mechanism. This routine just does a debugging + * cross-check that no pins remain. However, we also need to do special + * cleanup when the current transaction created any relations or made use + * of forced index lists. + */ +void +AtEOXact_RelationCache(bool isCommit) +{ + HASH_SEQ_STATUS status; + RelIdCacheEnt *idhentry; + int i; + + /* + * Forget in_progress_list. This is relevant when we're aborting due to + * an error during RelationBuildDesc(). + */ + Assert(in_progress_list_len == 0 || !isCommit); + in_progress_list_len = 0; + + /* + * Unless the eoxact_list[] overflowed, we only need to examine the rels + * listed in it. Otherwise fall back on a hash_seq_search scan. + * + * For simplicity, eoxact_list[] entries are not deleted till end of + * top-level transaction, even though we could remove them at + * subtransaction end in some cases, or remove relations from the list if + * they are cleared for other reasons. Therefore we should expect the + * case that list entries are not found in the hashtable; if not, there's + * nothing to do for them. + */ + if (eoxact_list_overflowed) + { + hash_seq_init(&status, RelationIdCache); + while ((idhentry = (RelIdCacheEnt *) hash_seq_search(&status)) != NULL) + { + AtEOXact_cleanup(idhentry->reldesc, isCommit); + } + } + else + { + for (i = 0; i < eoxact_list_len; i++) + { + idhentry = (RelIdCacheEnt *) hash_search(RelationIdCache, + &eoxact_list[i], + HASH_FIND, + NULL); + if (idhentry != NULL) + AtEOXact_cleanup(idhentry->reldesc, isCommit); + } + } + + if (EOXactTupleDescArrayLen > 0) + { + Assert(EOXactTupleDescArray != NULL); + for (i = 0; i < NextEOXactTupleDescNum; i++) + FreeTupleDesc(EOXactTupleDescArray[i]); + pfree(EOXactTupleDescArray); + EOXactTupleDescArray = NULL; + } + + /* Now we're out of the transaction and can clear the lists */ + eoxact_list_len = 0; + eoxact_list_overflowed = false; + NextEOXactTupleDescNum = 0; + EOXactTupleDescArrayLen = 0; +} + +/* + * AtEOXact_cleanup + * + * Clean up a single rel at main-transaction commit or abort + * + * NB: this processing must be idempotent, because EOXactListAdd() doesn't + * bother to prevent duplicate entries in eoxact_list[]. + */ +static void +AtEOXact_cleanup(Relation relation, bool isCommit) +{ + bool clear_relcache = false; + + /* + * The relcache entry's ref count should be back to its normal + * not-in-a-transaction state: 0 unless it's nailed in cache. + * + * In bootstrap mode, this is NOT true, so don't check it --- the + * bootstrap code expects relations to stay open across start/commit + * transaction calls. (That seems bogus, but it's not worth fixing.) + * + * Note: ideally this check would be applied to every relcache entry, not + * just those that have eoxact work to do. But it's not worth forcing a + * scan of the whole relcache just for this. (Moreover, doing so would + * mean that assert-enabled testing never tests the hash_search code path + * above, which seems a bad idea.) + */ +#ifdef USE_ASSERT_CHECKING + if (!IsBootstrapProcessingMode()) + { + int expected_refcnt; + + expected_refcnt = relation->rd_isnailed ? 1 : 0; + Assert(relation->rd_refcnt == expected_refcnt); + } +#endif + + /* + * Is the relation live after this transaction ends? + * + * During commit, clear the relcache entry if it is preserved after + * relation drop, in order not to orphan the entry. During rollback, + * clear the relcache entry if the relation is created in the current + * transaction since it isn't interesting any longer once we are out of + * the transaction. + */ + clear_relcache = + (isCommit ? + relation->rd_droppedSubid != InvalidSubTransactionId : + relation->rd_createSubid != InvalidSubTransactionId); + + /* + * Since we are now out of the transaction, reset the subids to zero. That + * also lets RelationClearRelation() drop the relcache entry. + */ + relation->rd_createSubid = InvalidSubTransactionId; + relation->rd_newRelfilelocatorSubid = InvalidSubTransactionId; + relation->rd_firstRelfilelocatorSubid = InvalidSubTransactionId; + relation->rd_droppedSubid = InvalidSubTransactionId; + + if (clear_relcache) + { + if (RelationHasReferenceCountZero(relation)) + { + RelationClearRelation(relation, false); + return; + } + else + { + /* + * Hmm, somewhere there's a (leaked?) reference to the relation. + * We daren't remove the entry for fear of dereferencing a + * dangling pointer later. Bleat, and mark it as not belonging to + * the current transaction. Hopefully it'll get cleaned up + * eventually. This must be just a WARNING to avoid + * error-during-error-recovery loops. + */ + elog(WARNING, "cannot remove relcache entry for \"%s\" because it has nonzero refcount", + RelationGetRelationName(relation)); + } + } +} + +/* + * AtEOSubXact_RelationCache + * + * Clean up the relcache at sub-transaction commit or abort. + * + * Note: this must be called *before* processing invalidation messages. + */ +void +AtEOSubXact_RelationCache(bool isCommit, SubTransactionId mySubid, + SubTransactionId parentSubid) +{ + HASH_SEQ_STATUS status; + RelIdCacheEnt *idhentry; + int i; + + /* + * Forget in_progress_list. This is relevant when we're aborting due to + * an error during RelationBuildDesc(). We don't commit subtransactions + * during RelationBuildDesc(). + */ + Assert(in_progress_list_len == 0 || !isCommit); + in_progress_list_len = 0; + + /* + * Unless the eoxact_list[] overflowed, we only need to examine the rels + * listed in it. Otherwise fall back on a hash_seq_search scan. Same + * logic as in AtEOXact_RelationCache. + */ + if (eoxact_list_overflowed) + { + hash_seq_init(&status, RelationIdCache); + while ((idhentry = (RelIdCacheEnt *) hash_seq_search(&status)) != NULL) + { + AtEOSubXact_cleanup(idhentry->reldesc, isCommit, + mySubid, parentSubid); + } + } + else + { + for (i = 0; i < eoxact_list_len; i++) + { + idhentry = (RelIdCacheEnt *) hash_search(RelationIdCache, + &eoxact_list[i], + HASH_FIND, + NULL); + if (idhentry != NULL) + AtEOSubXact_cleanup(idhentry->reldesc, isCommit, + mySubid, parentSubid); + } + } + + /* Don't reset the list; we still need more cleanup later */ +} + +/* + * AtEOSubXact_cleanup + * + * Clean up a single rel at subtransaction commit or abort + * + * NB: this processing must be idempotent, because EOXactListAdd() doesn't + * bother to prevent duplicate entries in eoxact_list[]. + */ +static void +AtEOSubXact_cleanup(Relation relation, bool isCommit, + SubTransactionId mySubid, SubTransactionId parentSubid) +{ + /* + * Is it a relation created in the current subtransaction? + * + * During subcommit, mark it as belonging to the parent, instead, as long + * as it has not been dropped. Otherwise simply delete the relcache entry. + * --- it isn't interesting any longer. + */ + if (relation->rd_createSubid == mySubid) + { + /* + * Valid rd_droppedSubid means the corresponding relation is dropped + * but the relcache entry is preserved for at-commit pending sync. We + * need to drop it explicitly here not to make the entry orphan. + */ + Assert(relation->rd_droppedSubid == mySubid || + relation->rd_droppedSubid == InvalidSubTransactionId); + if (isCommit && relation->rd_droppedSubid == InvalidSubTransactionId) + relation->rd_createSubid = parentSubid; + else if (RelationHasReferenceCountZero(relation)) + { + /* allow the entry to be removed */ + relation->rd_createSubid = InvalidSubTransactionId; + relation->rd_newRelfilelocatorSubid = InvalidSubTransactionId; + relation->rd_firstRelfilelocatorSubid = InvalidSubTransactionId; + relation->rd_droppedSubid = InvalidSubTransactionId; + RelationClearRelation(relation, false); + return; + } + else + { + /* + * Hmm, somewhere there's a (leaked?) reference to the relation. + * We daren't remove the entry for fear of dereferencing a + * dangling pointer later. Bleat, and transfer it to the parent + * subtransaction so we can try again later. This must be just a + * WARNING to avoid error-during-error-recovery loops. + */ + relation->rd_createSubid = parentSubid; + elog(WARNING, "cannot remove relcache entry for \"%s\" because it has nonzero refcount", + RelationGetRelationName(relation)); + } + } + + /* + * Likewise, update or drop any new-relfilenumber-in-subtransaction record + * or drop record. + */ + if (relation->rd_newRelfilelocatorSubid == mySubid) + { + if (isCommit) + relation->rd_newRelfilelocatorSubid = parentSubid; + else + relation->rd_newRelfilelocatorSubid = InvalidSubTransactionId; + } + + if (relation->rd_firstRelfilelocatorSubid == mySubid) + { + if (isCommit) + relation->rd_firstRelfilelocatorSubid = parentSubid; + else + relation->rd_firstRelfilelocatorSubid = InvalidSubTransactionId; + } + + if (relation->rd_droppedSubid == mySubid) + { + if (isCommit) + relation->rd_droppedSubid = parentSubid; + else + relation->rd_droppedSubid = InvalidSubTransactionId; + } +} + + +/* + * RelationBuildLocalRelation + * Build a relcache entry for an about-to-be-created relation, + * and enter it into the relcache. + */ +Relation +RelationBuildLocalRelation(const char *relname, + Oid relnamespace, + TupleDesc tupDesc, + Oid relid, + Oid accessmtd, + RelFileNumber relfilenumber, + Oid reltablespace, + bool shared_relation, + bool mapped_relation, + char relpersistence, + char relkind) +{ + Relation rel; + MemoryContext oldcxt; + int natts = tupDesc->natts; + int i; + bool has_not_null; + bool nailit; + + Assert(natts >= 0); + + /* + * check for creation of a rel that must be nailed in cache. + * + * XXX this list had better match the relations specially handled in + * RelationCacheInitializePhase2/3. + */ + switch (relid) + { + case DatabaseRelationId: + case AuthIdRelationId: + case AuthMemRelationId: + case RelationRelationId: + case AttributeRelationId: + case ProcedureRelationId: + case TypeRelationId: + nailit = true; + break; + default: + nailit = false; + break; + } + + /* + * check that hardwired list of shared rels matches what's in the + * bootstrap .bki file. If you get a failure here during initdb, you + * probably need to fix IsSharedRelation() to match whatever you've done + * to the set of shared relations. + */ + if (shared_relation != IsSharedRelation(relid)) + elog(ERROR, "shared_relation flag for \"%s\" does not match IsSharedRelation(%u)", + relname, relid); + + /* Shared relations had better be mapped, too */ + Assert(mapped_relation || !shared_relation); + + /* + * switch to the cache context to create the relcache entry. + */ + if (!CacheMemoryContext) + CreateCacheMemoryContext(); + + oldcxt = MemoryContextSwitchTo(CacheMemoryContext); + + /* + * allocate a new relation descriptor and fill in basic state fields. + */ + rel = (Relation) palloc0(sizeof(RelationData)); + + /* make sure relation is marked as having no open file yet */ + rel->rd_smgr = NULL; + + /* mark it nailed if appropriate */ + rel->rd_isnailed = nailit; + + rel->rd_refcnt = nailit ? 1 : 0; + + /* it's being created in this transaction */ + rel->rd_createSubid = GetCurrentSubTransactionId(); + rel->rd_newRelfilelocatorSubid = InvalidSubTransactionId; + rel->rd_firstRelfilelocatorSubid = InvalidSubTransactionId; + rel->rd_droppedSubid = InvalidSubTransactionId; + + /* + * create a new tuple descriptor from the one passed in. We do this + * partly to copy it into the cache context, and partly because the new + * relation can't have any defaults or constraints yet; they have to be + * added in later steps, because they require additions to multiple system + * catalogs. We can copy attnotnull constraints here, however. + */ + rel->rd_att = CreateTupleDescCopy(tupDesc); + rel->rd_att->tdrefcount = 1; /* mark as refcounted */ + has_not_null = false; + for (i = 0; i < natts; i++) + { + Form_pg_attribute satt = TupleDescAttr(tupDesc, i); + Form_pg_attribute datt = TupleDescAttr(rel->rd_att, i); + + datt->attidentity = satt->attidentity; + datt->attgenerated = satt->attgenerated; + datt->attnotnull = satt->attnotnull; + has_not_null |= satt->attnotnull; + } + + if (has_not_null) + { + TupleConstr *constr = (TupleConstr *) palloc0(sizeof(TupleConstr)); + + constr->has_not_null = true; + rel->rd_att->constr = constr; + } + + /* + * initialize relation tuple form (caller may add/override data later) + */ + rel->rd_rel = (Form_pg_class) palloc0(CLASS_TUPLE_SIZE); + + namestrcpy(&rel->rd_rel->relname, relname); + rel->rd_rel->relnamespace = relnamespace; + + rel->rd_rel->relkind = relkind; + rel->rd_rel->relnatts = natts; + rel->rd_rel->reltype = InvalidOid; + /* needed when bootstrapping: */ + rel->rd_rel->relowner = BOOTSTRAP_SUPERUSERID; + + /* set up persistence and relcache fields dependent on it */ + rel->rd_rel->relpersistence = relpersistence; + switch (relpersistence) + { + case RELPERSISTENCE_UNLOGGED: + case RELPERSISTENCE_PERMANENT: + rel->rd_backend = InvalidBackendId; + rel->rd_islocaltemp = false; + break; + case RELPERSISTENCE_TEMP: + Assert(isTempOrTempToastNamespace(relnamespace)); + rel->rd_backend = BackendIdForTempRelations(); + rel->rd_islocaltemp = true; + break; + default: + elog(ERROR, "invalid relpersistence: %c", relpersistence); + break; + } + + /* if it's a materialized view, it's not populated initially */ + if (relkind == RELKIND_MATVIEW) + rel->rd_rel->relispopulated = false; + else + rel->rd_rel->relispopulated = true; + + /* set replica identity -- system catalogs and non-tables don't have one */ + if (!IsCatalogNamespace(relnamespace) && + (relkind == RELKIND_RELATION || + relkind == RELKIND_MATVIEW || + relkind == RELKIND_PARTITIONED_TABLE)) + rel->rd_rel->relreplident = REPLICA_IDENTITY_DEFAULT; + else + rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING; + + /* + * Insert relation physical and logical identifiers (OIDs) into the right + * places. For a mapped relation, we set relfilenumber to zero and rely + * on RelationInitPhysicalAddr to consult the map. + */ + rel->rd_rel->relisshared = shared_relation; + + RelationGetRelid(rel) = relid; + + for (i = 0; i < natts; i++) + TupleDescAttr(rel->rd_att, i)->attrelid = relid; + + rel->rd_rel->reltablespace = reltablespace; + + if (mapped_relation) + { + rel->rd_rel->relfilenode = InvalidRelFileNumber; + /* Add it to the active mapping information */ + RelationMapUpdateMap(relid, relfilenumber, shared_relation, true); + } + else + rel->rd_rel->relfilenode = relfilenumber; + + RelationInitLockInfo(rel); /* see lmgr.c */ + + RelationInitPhysicalAddr(rel); + + rel->rd_rel->relam = accessmtd; + + /* + * RelationInitTableAccessMethod will do syscache lookups, so we mustn't + * run it in CacheMemoryContext. Fortunately, the remaining steps don't + * require a long-lived current context. + */ + MemoryContextSwitchTo(oldcxt); + + if (RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_SEQUENCE) + RelationInitTableAccessMethod(rel); + + /* + * Okay to insert into the relcache hash table. + * + * Ordinarily, there should certainly not be an existing hash entry for + * the same OID; but during bootstrap, when we create a "real" relcache + * entry for one of the bootstrap relations, we'll be overwriting the + * phony one created with formrdesc. So allow that to happen for nailed + * rels. + */ + RelationCacheInsert(rel, nailit); + + /* + * Flag relation as needing eoxact cleanup (to clear rd_createSubid). We + * can't do this before storing relid in it. + */ + EOXactListAdd(rel); + + /* It's fully valid */ + rel->rd_isvalid = true; + + /* + * Caller expects us to pin the returned entry. + */ + RelationIncrementReferenceCount(rel); + + return rel; +} + + +/* + * RelationSetNewRelfilenumber + * + * Assign a new relfilenumber (physical file name), and possibly a new + * persistence setting, to the relation. + * + * This allows a full rewrite of the relation to be done with transactional + * safety (since the filenumber assignment can be rolled back). Note however + * that there is no simple way to access the relation's old data for the + * remainder of the current transaction. This limits the usefulness to cases + * such as TRUNCATE or rebuilding an index from scratch. + * + * Caller must already hold exclusive lock on the relation. + */ +void +RelationSetNewRelfilenumber(Relation relation, char persistence) +{ + RelFileNumber newrelfilenumber; + Relation pg_class; + HeapTuple tuple; + Form_pg_class classform; + MultiXactId minmulti = InvalidMultiXactId; + TransactionId freezeXid = InvalidTransactionId; + RelFileLocator newrlocator; + + if (!IsBinaryUpgrade) + { + /* Allocate a new relfilenumber */ + newrelfilenumber = GetNewRelFileNumber(relation->rd_rel->reltablespace, + NULL, persistence); + } + else if (relation->rd_rel->relkind == RELKIND_INDEX) + { + if (!OidIsValid(binary_upgrade_next_index_pg_class_relfilenumber)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("index relfilenumber value not set when in binary upgrade mode"))); + + newrelfilenumber = binary_upgrade_next_index_pg_class_relfilenumber; + binary_upgrade_next_index_pg_class_relfilenumber = InvalidOid; + } + else if (relation->rd_rel->relkind == RELKIND_RELATION) + { + if (!OidIsValid(binary_upgrade_next_heap_pg_class_relfilenumber)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("heap relfilenumber value not set when in binary upgrade mode"))); + + newrelfilenumber = binary_upgrade_next_heap_pg_class_relfilenumber; + binary_upgrade_next_heap_pg_class_relfilenumber = InvalidOid; + } + else + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unexpected request for new relfilenumber in binary upgrade mode"))); + + /* + * Get a writable copy of the pg_class tuple for the given relation. + */ + pg_class = table_open(RelationRelationId, RowExclusiveLock); + + tuple = SearchSysCacheCopy1(RELOID, + ObjectIdGetDatum(RelationGetRelid(relation))); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "could not find tuple for relation %u", + RelationGetRelid(relation)); + classform = (Form_pg_class) GETSTRUCT(tuple); + + /* + * Schedule unlinking of the old storage at transaction commit, except + * when performing a binary upgrade, when we must do it immediately. + */ + if (IsBinaryUpgrade) + { + SMgrRelation srel; + + /* + * During a binary upgrade, we use this code path to ensure that + * pg_largeobject and its index have the same relfilenumbers as in the + * old cluster. This is necessary because pg_upgrade treats + * pg_largeobject like a user table, not a system table. It is however + * possible that a table or index may need to end up with the same + * relfilenumber in the new cluster as what it had in the old cluster. + * Hence, we can't wait until commit time to remove the old storage. + * + * In general, this function needs to have transactional semantics, + * and removing the old storage before commit time surely isn't. + * However, it doesn't really matter, because if a binary upgrade + * fails at this stage, the new cluster will need to be recreated + * anyway. + */ + srel = smgropen(relation->rd_locator, relation->rd_backend); + smgrdounlinkall(&srel, 1, false); + smgrclose(srel); + } + else + { + /* Not a binary upgrade, so just schedule it to happen later. */ + RelationDropStorage(relation); + } + + /* + * Create storage for the main fork of the new relfilenumber. If it's a + * table-like object, call into the table AM to do so, which'll also + * create the table's init fork if needed. + * + * NOTE: If relevant for the AM, any conflict in relfilenumber value will + * be caught here, if GetNewRelFileNumber messes up for any reason. + */ + newrlocator = relation->rd_locator; + newrlocator.relNumber = newrelfilenumber; + + if (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind)) + { + table_relation_set_new_filelocator(relation, &newrlocator, + persistence, + &freezeXid, &minmulti); + } + else if (RELKIND_HAS_STORAGE(relation->rd_rel->relkind)) + { + /* handle these directly, at least for now */ + SMgrRelation srel; + + srel = RelationCreateStorage(newrlocator, persistence, true); + smgrclose(srel); + } + else + { + /* we shouldn't be called for anything else */ + elog(ERROR, "relation \"%s\" does not have storage", + RelationGetRelationName(relation)); + } + + /* + * If we're dealing with a mapped index, pg_class.relfilenode doesn't + * change; instead we have to send the update to the relation mapper. + * + * For mapped indexes, we don't actually change the pg_class entry at all; + * this is essential when reindexing pg_class itself. That leaves us with + * possibly-inaccurate values of relpages etc, but those will be fixed up + * later. + */ + if (RelationIsMapped(relation)) + { + /* This case is only supported for indexes */ + Assert(relation->rd_rel->relkind == RELKIND_INDEX); + + /* Since we're not updating pg_class, these had better not change */ + Assert(classform->relfrozenxid == freezeXid); + Assert(classform->relminmxid == minmulti); + Assert(classform->relpersistence == persistence); + + /* + * In some code paths it's possible that the tuple update we'd + * otherwise do here is the only thing that would assign an XID for + * the current transaction. However, we must have an XID to delete + * files, so make sure one is assigned. + */ + (void) GetCurrentTransactionId(); + + /* Do the deed */ + RelationMapUpdateMap(RelationGetRelid(relation), + newrelfilenumber, + relation->rd_rel->relisshared, + false); + + /* Since we're not updating pg_class, must trigger inval manually */ + CacheInvalidateRelcache(relation); + } + else + { + /* Normal case, update the pg_class entry */ + classform->relfilenode = newrelfilenumber; + + /* relpages etc. never change for sequences */ + if (relation->rd_rel->relkind != RELKIND_SEQUENCE) + { + classform->relpages = 0; /* it's empty until further notice */ + classform->reltuples = -1; + classform->relallvisible = 0; + } + classform->relfrozenxid = freezeXid; + classform->relminmxid = minmulti; + classform->relpersistence = persistence; + + CatalogTupleUpdate(pg_class, &tuple->t_self, tuple); + } + + heap_freetuple(tuple); + + table_close(pg_class, RowExclusiveLock); + + /* + * Make the pg_class row change or relation map change visible. This will + * cause the relcache entry to get updated, too. + */ + CommandCounterIncrement(); + + RelationAssumeNewRelfilelocator(relation); +} + +/* + * RelationAssumeNewRelfilelocator + * + * Code that modifies pg_class.reltablespace or pg_class.relfilenode must call + * this. The call shall precede any code that might insert WAL records whose + * replay would modify bytes in the new RelFileLocator, and the call shall follow + * any WAL modifying bytes in the prior RelFileLocator. See struct RelationData. + * Ideally, call this as near as possible to the CommandCounterIncrement() + * that makes the pg_class change visible (before it or after it); that + * minimizes the chance of future development adding a forbidden WAL insertion + * between RelationAssumeNewRelfilelocator() and CommandCounterIncrement(). + */ +void +RelationAssumeNewRelfilelocator(Relation relation) +{ + relation->rd_newRelfilelocatorSubid = GetCurrentSubTransactionId(); + if (relation->rd_firstRelfilelocatorSubid == InvalidSubTransactionId) + relation->rd_firstRelfilelocatorSubid = relation->rd_newRelfilelocatorSubid; + + /* Flag relation as needing eoxact cleanup (to clear these fields) */ + EOXactListAdd(relation); +} + + +/* + * RelationCacheInitialize + * + * This initializes the relation descriptor cache. At the time + * that this is invoked, we can't do database access yet (mainly + * because the transaction subsystem is not up); all we are doing + * is making an empty cache hashtable. This must be done before + * starting the initialization transaction, because otherwise + * AtEOXact_RelationCache would crash if that transaction aborts + * before we can get the relcache set up. + */ + +#define INITRELCACHESIZE 400 + +void +RelationCacheInitialize(void) +{ + HASHCTL ctl; + int allocsize; + + /* + * make sure cache memory context exists + */ + if (!CacheMemoryContext) + CreateCacheMemoryContext(); + + /* + * create hashtable that indexes the relcache + */ + ctl.keysize = sizeof(Oid); + ctl.entrysize = sizeof(RelIdCacheEnt); + RelationIdCache = hash_create("Relcache by OID", INITRELCACHESIZE, + &ctl, HASH_ELEM | HASH_BLOBS); + + /* + * reserve enough in_progress_list slots for many cases + */ + allocsize = 4; + in_progress_list = + MemoryContextAlloc(CacheMemoryContext, + allocsize * sizeof(*in_progress_list)); + in_progress_list_maxlen = allocsize; + + /* + * relation mapper needs to be initialized too + */ + RelationMapInitialize(); +} + +/* + * RelationCacheInitializePhase2 + * + * This is called to prepare for access to shared catalogs during startup. + * We must at least set up nailed reldescs for pg_database, pg_authid, + * pg_auth_members, and pg_shseclabel. Ideally we'd like to have reldescs + * for their indexes, too. We attempt to load this information from the + * shared relcache init file. If that's missing or broken, just make + * phony entries for the catalogs themselves. + * RelationCacheInitializePhase3 will clean up as needed. + */ +void +RelationCacheInitializePhase2(void) +{ + MemoryContext oldcxt; + + /* + * relation mapper needs initialized too + */ + RelationMapInitializePhase2(); + + /* + * In bootstrap mode, the shared catalogs aren't there yet anyway, so do + * nothing. + */ + if (IsBootstrapProcessingMode()) + return; + + /* + * switch to cache memory context + */ + oldcxt = MemoryContextSwitchTo(CacheMemoryContext); + + /* + * Try to load the shared relcache cache file. If unsuccessful, bootstrap + * the cache with pre-made descriptors for the critical shared catalogs. + */ + if (!load_relcache_init_file(true)) + { + formrdesc("pg_database", DatabaseRelation_Rowtype_Id, true, + Natts_pg_database, Desc_pg_database); + formrdesc("pg_authid", AuthIdRelation_Rowtype_Id, true, + Natts_pg_authid, Desc_pg_authid); + formrdesc("pg_auth_members", AuthMemRelation_Rowtype_Id, true, + Natts_pg_auth_members, Desc_pg_auth_members); + formrdesc("pg_shseclabel", SharedSecLabelRelation_Rowtype_Id, true, + Natts_pg_shseclabel, Desc_pg_shseclabel); + formrdesc("pg_subscription", SubscriptionRelation_Rowtype_Id, true, + Natts_pg_subscription, Desc_pg_subscription); + +#define NUM_CRITICAL_SHARED_RELS 5 /* fix if you change list above */ + } + + MemoryContextSwitchTo(oldcxt); +} + +/* + * RelationCacheInitializePhase3 + * + * This is called as soon as the catcache and transaction system + * are functional and we have determined MyDatabaseId. At this point + * we can actually read data from the database's system catalogs. + * We first try to read pre-computed relcache entries from the local + * relcache init file. If that's missing or broken, make phony entries + * for the minimum set of nailed-in-cache relations. Then (unless + * bootstrapping) make sure we have entries for the critical system + * indexes. Once we've done all this, we have enough infrastructure to + * open any system catalog or use any catcache. The last step is to + * rewrite the cache files if needed. + */ +void +RelationCacheInitializePhase3(void) +{ + HASH_SEQ_STATUS status; + RelIdCacheEnt *idhentry; + MemoryContext oldcxt; + bool needNewCacheFile = !criticalSharedRelcachesBuilt; + + /* + * relation mapper needs initialized too + */ + RelationMapInitializePhase3(); + + /* + * switch to cache memory context + */ + oldcxt = MemoryContextSwitchTo(CacheMemoryContext); + + /* + * Try to load the local relcache cache file. If unsuccessful, bootstrap + * the cache with pre-made descriptors for the critical "nailed-in" system + * catalogs. + */ + if (IsBootstrapProcessingMode() || + !load_relcache_init_file(false)) + { + needNewCacheFile = true; + + formrdesc("pg_class", RelationRelation_Rowtype_Id, false, + Natts_pg_class, Desc_pg_class); + formrdesc("pg_attribute", AttributeRelation_Rowtype_Id, false, + Natts_pg_attribute, Desc_pg_attribute); + formrdesc("pg_proc", ProcedureRelation_Rowtype_Id, false, + Natts_pg_proc, Desc_pg_proc); + formrdesc("pg_type", TypeRelation_Rowtype_Id, false, + Natts_pg_type, Desc_pg_type); + +#define NUM_CRITICAL_LOCAL_RELS 4 /* fix if you change list above */ + } + + MemoryContextSwitchTo(oldcxt); + + /* In bootstrap mode, the faked-up formrdesc info is all we'll have */ + if (IsBootstrapProcessingMode()) + return; + + /* + * If we didn't get the critical system indexes loaded into relcache, do + * so now. These are critical because the catcache and/or opclass cache + * depend on them for fetches done during relcache load. Thus, we have an + * infinite-recursion problem. We can break the recursion by doing + * heapscans instead of indexscans at certain key spots. To avoid hobbling + * performance, we only want to do that until we have the critical indexes + * loaded into relcache. Thus, the flag criticalRelcachesBuilt is used to + * decide whether to do heapscan or indexscan at the key spots, and we set + * it true after we've loaded the critical indexes. + * + * The critical indexes are marked as "nailed in cache", partly to make it + * easy for load_relcache_init_file to count them, but mainly because we + * cannot flush and rebuild them once we've set criticalRelcachesBuilt to + * true. (NOTE: perhaps it would be possible to reload them by + * temporarily setting criticalRelcachesBuilt to false again. For now, + * though, we just nail 'em in.) + * + * RewriteRelRulenameIndexId and TriggerRelidNameIndexId are not critical + * in the same way as the others, because the critical catalogs don't + * (currently) have any rules or triggers, and so these indexes can be + * rebuilt without inducing recursion. However they are used during + * relcache load when a rel does have rules or triggers, so we choose to + * nail them for performance reasons. + */ + if (!criticalRelcachesBuilt) + { + load_critical_index(ClassOidIndexId, + RelationRelationId); + load_critical_index(AttributeRelidNumIndexId, + AttributeRelationId); + load_critical_index(IndexRelidIndexId, + IndexRelationId); + load_critical_index(OpclassOidIndexId, + OperatorClassRelationId); + load_critical_index(AccessMethodProcedureIndexId, + AccessMethodProcedureRelationId); + load_critical_index(RewriteRelRulenameIndexId, + RewriteRelationId); + load_critical_index(TriggerRelidNameIndexId, + TriggerRelationId); + +#define NUM_CRITICAL_LOCAL_INDEXES 7 /* fix if you change list above */ + + criticalRelcachesBuilt = true; + } + + /* + * Process critical shared indexes too. + * + * DatabaseNameIndexId isn't critical for relcache loading, but rather for + * initial lookup of MyDatabaseId, without which we'll never find any + * non-shared catalogs at all. Autovacuum calls InitPostgres with a + * database OID, so it instead depends on DatabaseOidIndexId. We also + * need to nail up some indexes on pg_authid and pg_auth_members for use + * during client authentication. SharedSecLabelObjectIndexId isn't + * critical for the core system, but authentication hooks might be + * interested in it. + */ + if (!criticalSharedRelcachesBuilt) + { + load_critical_index(DatabaseNameIndexId, + DatabaseRelationId); + load_critical_index(DatabaseOidIndexId, + DatabaseRelationId); + load_critical_index(AuthIdRolnameIndexId, + AuthIdRelationId); + load_critical_index(AuthIdOidIndexId, + AuthIdRelationId); + load_critical_index(AuthMemMemRoleIndexId, + AuthMemRelationId); + load_critical_index(SharedSecLabelObjectIndexId, + SharedSecLabelRelationId); + +#define NUM_CRITICAL_SHARED_INDEXES 6 /* fix if you change list above */ + + criticalSharedRelcachesBuilt = true; + } + + /* + * Now, scan all the relcache entries and update anything that might be + * wrong in the results from formrdesc or the relcache cache file. If we + * faked up relcache entries using formrdesc, then read the real pg_class + * rows and replace the fake entries with them. Also, if any of the + * relcache entries have rules, triggers, or security policies, load that + * info the hard way since it isn't recorded in the cache file. + * + * Whenever we access the catalogs to read data, there is a possibility of + * a shared-inval cache flush causing relcache entries to be removed. + * Since hash_seq_search only guarantees to still work after the *current* + * entry is removed, it's unsafe to continue the hashtable scan afterward. + * We handle this by restarting the scan from scratch after each access. + * This is theoretically O(N^2), but the number of entries that actually + * need to be fixed is small enough that it doesn't matter. + */ + hash_seq_init(&status, RelationIdCache); + + while ((idhentry = (RelIdCacheEnt *) hash_seq_search(&status)) != NULL) + { + Relation relation = idhentry->reldesc; + bool restart = false; + + /* + * Make sure *this* entry doesn't get flushed while we work with it. + */ + RelationIncrementReferenceCount(relation); + + /* + * If it's a faked-up entry, read the real pg_class tuple. + */ + if (relation->rd_rel->relowner == InvalidOid) + { + HeapTuple htup; + Form_pg_class relp; + + htup = SearchSysCache1(RELOID, + ObjectIdGetDatum(RelationGetRelid(relation))); + if (!HeapTupleIsValid(htup)) + elog(FATAL, "cache lookup failed for relation %u", + RelationGetRelid(relation)); + relp = (Form_pg_class) GETSTRUCT(htup); + + /* + * Copy tuple to relation->rd_rel. (See notes in + * AllocateRelationDesc()) + */ + memcpy((char *) relation->rd_rel, (char *) relp, CLASS_TUPLE_SIZE); + + /* Update rd_options while we have the tuple */ + if (relation->rd_options) + pfree(relation->rd_options); + RelationParseRelOptions(relation, htup); + + /* + * Check the values in rd_att were set up correctly. (We cannot + * just copy them over now: formrdesc must have set up the rd_att + * data correctly to start with, because it may already have been + * copied into one or more catcache entries.) + */ + Assert(relation->rd_att->tdtypeid == relp->reltype); + Assert(relation->rd_att->tdtypmod == -1); + + ReleaseSysCache(htup); + + /* relowner had better be OK now, else we'll loop forever */ + if (relation->rd_rel->relowner == InvalidOid) + elog(ERROR, "invalid relowner in pg_class entry for \"%s\"", + RelationGetRelationName(relation)); + + restart = true; + } + + /* + * Fix data that isn't saved in relcache cache file. + * + * relhasrules or relhastriggers could possibly be wrong or out of + * date. If we don't actually find any rules or triggers, clear the + * local copy of the flag so that we don't get into an infinite loop + * here. We don't make any attempt to fix the pg_class entry, though. + */ + if (relation->rd_rel->relhasrules && relation->rd_rules == NULL) + { + RelationBuildRuleLock(relation); + if (relation->rd_rules == NULL) + relation->rd_rel->relhasrules = false; + restart = true; + } + if (relation->rd_rel->relhastriggers && relation->trigdesc == NULL) + { + RelationBuildTriggers(relation); + if (relation->trigdesc == NULL) + relation->rd_rel->relhastriggers = false; + restart = true; + } + + /* + * Re-load the row security policies if the relation has them, since + * they are not preserved in the cache. Note that we can never NOT + * have a policy while relrowsecurity is true, + * RelationBuildRowSecurity will create a single default-deny policy + * if there is no policy defined in pg_policy. + */ + if (relation->rd_rel->relrowsecurity && relation->rd_rsdesc == NULL) + { + RelationBuildRowSecurity(relation); + + Assert(relation->rd_rsdesc != NULL); + restart = true; + } + + /* Reload tableam data if needed */ + if (relation->rd_tableam == NULL && + (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind) || relation->rd_rel->relkind == RELKIND_SEQUENCE)) + { + RelationInitTableAccessMethod(relation); + Assert(relation->rd_tableam != NULL); + + restart = true; + } + + /* Release hold on the relation */ + RelationDecrementReferenceCount(relation); + + /* Now, restart the hashtable scan if needed */ + if (restart) + { + hash_seq_term(&status); + hash_seq_init(&status, RelationIdCache); + } + } + + /* + * Lastly, write out new relcache cache files if needed. We don't bother + * to distinguish cases where only one of the two needs an update. + */ + if (needNewCacheFile) + { + /* + * Force all the catcaches to finish initializing and thereby open the + * catalogs and indexes they use. This will preload the relcache with + * entries for all the most important system catalogs and indexes, so + * that the init files will be most useful for future backends. + */ + InitCatalogCachePhase2(); + + /* now write the files */ + write_relcache_init_file(true); + write_relcache_init_file(false); + } +} + +/* + * Load one critical system index into the relcache + * + * indexoid is the OID of the target index, heapoid is the OID of the catalog + * it belongs to. + */ +static void +load_critical_index(Oid indexoid, Oid heapoid) +{ + Relation ird; + + /* + * We must lock the underlying catalog before locking the index to avoid + * deadlock, since RelationBuildDesc might well need to read the catalog, + * and if anyone else is exclusive-locking this catalog and index they'll + * be doing it in that order. + */ + LockRelationOid(heapoid, AccessShareLock); + LockRelationOid(indexoid, AccessShareLock); + ird = RelationBuildDesc(indexoid, true); + if (ird == NULL) + elog(PANIC, "could not open critical system index %u", indexoid); + ird->rd_isnailed = true; + ird->rd_refcnt = 1; + UnlockRelationOid(indexoid, AccessShareLock); + UnlockRelationOid(heapoid, AccessShareLock); + + (void) RelationGetIndexAttOptions(ird, false); +} + +/* + * GetPgClassDescriptor -- get a predefined tuple descriptor for pg_class + * GetPgIndexDescriptor -- get a predefined tuple descriptor for pg_index + * + * We need this kluge because we have to be able to access non-fixed-width + * fields of pg_class and pg_index before we have the standard catalog caches + * available. We use predefined data that's set up in just the same way as + * the bootstrapped reldescs used by formrdesc(). The resulting tupdesc is + * not 100% kosher: it does not have the correct rowtype OID in tdtypeid, nor + * does it have a TupleConstr field. But it's good enough for the purpose of + * extracting fields. + */ +static TupleDesc +BuildHardcodedDescriptor(int natts, const FormData_pg_attribute *attrs) +{ + TupleDesc result; + MemoryContext oldcxt; + int i; + + oldcxt = MemoryContextSwitchTo(CacheMemoryContext); + + result = CreateTemplateTupleDesc(natts); + result->tdtypeid = RECORDOID; /* not right, but we don't care */ + result->tdtypmod = -1; + + for (i = 0; i < natts; i++) + { + memcpy(TupleDescAttr(result, i), &attrs[i], ATTRIBUTE_FIXED_PART_SIZE); + /* make sure attcacheoff is valid */ + TupleDescAttr(result, i)->attcacheoff = -1; + } + + /* initialize first attribute's attcacheoff, cf RelationBuildTupleDesc */ + TupleDescAttr(result, 0)->attcacheoff = 0; + + /* Note: we don't bother to set up a TupleConstr entry */ + + MemoryContextSwitchTo(oldcxt); + + return result; +} + +static TupleDesc +GetPgClassDescriptor(void) +{ + static __thread TupleDesc pgclassdesc = NULL; + + /* Already done? */ + if (pgclassdesc == NULL) + pgclassdesc = BuildHardcodedDescriptor(Natts_pg_class, + Desc_pg_class); + + return pgclassdesc; +} + +static TupleDesc +GetPgIndexDescriptor(void) +{ + static __thread TupleDesc pgindexdesc = NULL; + + /* Already done? */ + if (pgindexdesc == NULL) + pgindexdesc = BuildHardcodedDescriptor(Natts_pg_index, + Desc_pg_index); + + return pgindexdesc; +} + +/* + * Load any default attribute value definitions for the relation. + * + * ndef is the number of attributes that were marked atthasdef. + * + * Note: we don't make it a hard error to be missing some pg_attrdef records. + * We can limp along as long as nothing needs to use the default value. Code + * that fails to find an expected AttrDefault record should throw an error. + */ +static void +AttrDefaultFetch(Relation relation, int ndef) +{ + AttrDefault *attrdef; + Relation adrel; + SysScanDesc adscan; + ScanKeyData skey; + HeapTuple htup; + int found = 0; + + /* Allocate array with room for as many entries as expected */ + attrdef = (AttrDefault *) + MemoryContextAllocZero(CacheMemoryContext, + ndef * sizeof(AttrDefault)); + + /* Search pg_attrdef for relevant entries */ + ScanKeyInit(&skey, + Anum_pg_attrdef_adrelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(RelationGetRelid(relation))); + + adrel = table_open(AttrDefaultRelationId, AccessShareLock); + adscan = systable_beginscan(adrel, AttrDefaultIndexId, true, + NULL, 1, &skey); + + while (HeapTupleIsValid(htup = systable_getnext(adscan))) + { + Form_pg_attrdef adform = (Form_pg_attrdef) GETSTRUCT(htup); + Datum val; + bool isnull; + + /* protect limited size of array */ + if (found >= ndef) + { + elog(WARNING, "unexpected pg_attrdef record found for attribute %d of relation \"%s\"", + adform->adnum, RelationGetRelationName(relation)); + break; + } + + val = fastgetattr(htup, + Anum_pg_attrdef_adbin, + adrel->rd_att, &isnull); + if (isnull) + elog(WARNING, "null adbin for attribute %d of relation \"%s\"", + adform->adnum, RelationGetRelationName(relation)); + else + { + /* detoast and convert to cstring in caller's context */ + char *s = TextDatumGetCString(val); + + attrdef[found].adnum = adform->adnum; + attrdef[found].adbin = MemoryContextStrdup(CacheMemoryContext, s); + pfree(s); + found++; + } + } + + systable_endscan(adscan); + table_close(adrel, AccessShareLock); + + if (found != ndef) + elog(WARNING, "%d pg_attrdef record(s) missing for relation \"%s\"", + ndef - found, RelationGetRelationName(relation)); + + /* + * Sort the AttrDefault entries by adnum, for the convenience of + * equalTupleDescs(). (Usually, they already will be in order, but this + * might not be so if systable_getnext isn't using an index.) + */ + if (found > 1) + qsort(attrdef, found, sizeof(AttrDefault), AttrDefaultCmp); + + /* Install array only after it's fully valid */ + relation->rd_att->constr->defval = attrdef; + relation->rd_att->constr->num_defval = found; +} + +/* + * qsort comparator to sort AttrDefault entries by adnum + */ +static int +AttrDefaultCmp(const void *a, const void *b) +{ + const AttrDefault *ada = (const AttrDefault *) a; + const AttrDefault *adb = (const AttrDefault *) b; + + return ada->adnum - adb->adnum; +} + +/* + * Load any check constraints for the relation. + * + * As with defaults, if we don't find the expected number of them, just warn + * here. The executor should throw an error if an INSERT/UPDATE is attempted. + */ +static void +CheckConstraintFetch(Relation relation) +{ + ConstrCheck *check; + int ncheck = relation->rd_rel->relchecks; + Relation conrel; + SysScanDesc conscan; + ScanKeyData skey[1]; + HeapTuple htup; + int found = 0; + + /* Allocate array with room for as many entries as expected */ + check = (ConstrCheck *) + MemoryContextAllocZero(CacheMemoryContext, + ncheck * sizeof(ConstrCheck)); + + /* Search pg_constraint for relevant entries */ + ScanKeyInit(&skey[0], + Anum_pg_constraint_conrelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(RelationGetRelid(relation))); + + conrel = table_open(ConstraintRelationId, AccessShareLock); + conscan = systable_beginscan(conrel, ConstraintRelidTypidNameIndexId, true, + NULL, 1, skey); + + while (HeapTupleIsValid(htup = systable_getnext(conscan))) + { + Form_pg_constraint conform = (Form_pg_constraint) GETSTRUCT(htup); + Datum val; + bool isnull; + + /* We want check constraints only */ + if (conform->contype != CONSTRAINT_CHECK) + continue; + + /* protect limited size of array */ + if (found >= ncheck) + { + elog(WARNING, "unexpected pg_constraint record found for relation \"%s\"", + RelationGetRelationName(relation)); + break; + } + + check[found].ccvalid = conform->convalidated; + check[found].ccnoinherit = conform->connoinherit; + check[found].ccname = MemoryContextStrdup(CacheMemoryContext, + NameStr(conform->conname)); + + /* Grab and test conbin is actually set */ + val = fastgetattr(htup, + Anum_pg_constraint_conbin, + conrel->rd_att, &isnull); + if (isnull) + elog(WARNING, "null conbin for relation \"%s\"", + RelationGetRelationName(relation)); + else + { + /* detoast and convert to cstring in caller's context */ + char *s = TextDatumGetCString(val); + + check[found].ccbin = MemoryContextStrdup(CacheMemoryContext, s); + pfree(s); + found++; + } + } + + systable_endscan(conscan); + table_close(conrel, AccessShareLock); + + if (found != ncheck) + elog(WARNING, "%d pg_constraint record(s) missing for relation \"%s\"", + ncheck - found, RelationGetRelationName(relation)); + + /* + * Sort the records by name. This ensures that CHECKs are applied in a + * deterministic order, and it also makes equalTupleDescs() faster. + */ + if (found > 1) + qsort(check, found, sizeof(ConstrCheck), CheckConstraintCmp); + + /* Install array only after it's fully valid */ + relation->rd_att->constr->check = check; + relation->rd_att->constr->num_check = found; +} + +/* + * qsort comparator to sort ConstrCheck entries by name + */ +static int +CheckConstraintCmp(const void *a, const void *b) +{ + const ConstrCheck *ca = (const ConstrCheck *) a; + const ConstrCheck *cb = (const ConstrCheck *) b; + + return strcmp(ca->ccname, cb->ccname); +} + +/* + * RelationGetFKeyList -- get a list of foreign key info for the relation + * + * Returns a list of ForeignKeyCacheInfo structs, one per FK constraining + * the given relation. This data is a direct copy of relevant fields from + * pg_constraint. The list items are in no particular order. + * + * CAUTION: the returned list is part of the relcache's data, and could + * vanish in a relcache entry reset. Callers must inspect or copy it + * before doing anything that might trigger a cache flush, such as + * system catalog accesses. copyObject() can be used if desired. + * (We define it this way because current callers want to filter and + * modify the list entries anyway, so copying would be a waste of time.) + */ +List * +RelationGetFKeyList(Relation relation) +{ + List *result; + Relation conrel; + SysScanDesc conscan; + ScanKeyData skey; + HeapTuple htup; + List *oldlist; + MemoryContext oldcxt; + + /* Quick exit if we already computed the list. */ + if (relation->rd_fkeyvalid) + return relation->rd_fkeylist; + + /* Fast path: non-partitioned tables without triggers can't have FKs */ + if (!relation->rd_rel->relhastriggers && + relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE) + return NIL; + + /* + * We build the list we intend to return (in the caller's context) while + * doing the scan. After successfully completing the scan, we copy that + * list into the relcache entry. This avoids cache-context memory leakage + * if we get some sort of error partway through. + */ + result = NIL; + + /* Prepare to scan pg_constraint for entries having conrelid = this rel. */ + ScanKeyInit(&skey, + Anum_pg_constraint_conrelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(RelationGetRelid(relation))); + + conrel = table_open(ConstraintRelationId, AccessShareLock); + conscan = systable_beginscan(conrel, ConstraintRelidTypidNameIndexId, true, + NULL, 1, &skey); + + while (HeapTupleIsValid(htup = systable_getnext(conscan))) + { + Form_pg_constraint constraint = (Form_pg_constraint) GETSTRUCT(htup); + ForeignKeyCacheInfo *info; + + /* consider only foreign keys */ + if (constraint->contype != CONSTRAINT_FOREIGN) + continue; + + info = makeNode(ForeignKeyCacheInfo); + info->conoid = constraint->oid; + info->conrelid = constraint->conrelid; + info->confrelid = constraint->confrelid; + + DeconstructFkConstraintRow(htup, &info->nkeys, + info->conkey, + info->confkey, + info->conpfeqop, + NULL, NULL, NULL, NULL); + + /* Add FK's node to the result list */ + result = lappend(result, info); + } + + systable_endscan(conscan); + table_close(conrel, AccessShareLock); + + /* Now save a copy of the completed list in the relcache entry. */ + oldcxt = MemoryContextSwitchTo(CacheMemoryContext); + oldlist = relation->rd_fkeylist; + relation->rd_fkeylist = copyObject(result); + relation->rd_fkeyvalid = true; + MemoryContextSwitchTo(oldcxt); + + /* Don't leak the old list, if there is one */ + list_free_deep(oldlist); + + return result; +} + +/* + * RelationGetIndexList -- get a list of OIDs of indexes on this relation + * + * The index list is created only if someone requests it. We scan pg_index + * to find relevant indexes, and add the list to the relcache entry so that + * we won't have to compute it again. Note that shared cache inval of a + * relcache entry will delete the old list and set rd_indexvalid to false, + * so that we must recompute the index list on next request. This handles + * creation or deletion of an index. + * + * Indexes that are marked not indislive are omitted from the returned list. + * Such indexes are expected to be dropped momentarily, and should not be + * touched at all by any caller of this function. + * + * The returned list is guaranteed to be sorted in order by OID. This is + * needed by the executor, since for index types that we obtain exclusive + * locks on when updating the index, all backends must lock the indexes in + * the same order or we will get deadlocks (see ExecOpenIndices()). Any + * consistent ordering would do, but ordering by OID is easy. + * + * Since shared cache inval causes the relcache's copy of the list to go away, + * we return a copy of the list palloc'd in the caller's context. The caller + * may list_free() the returned list after scanning it. This is necessary + * since the caller will typically be doing syscache lookups on the relevant + * indexes, and syscache lookup could cause SI messages to be processed! + * + * In exactly the same way, we update rd_pkindex, which is the OID of the + * relation's primary key index if any, else InvalidOid; and rd_replidindex, + * which is the pg_class OID of an index to be used as the relation's + * replication identity index, or InvalidOid if there is no such index. + */ +List * +RelationGetIndexList(Relation relation) +{ + Relation indrel; + SysScanDesc indscan; + ScanKeyData skey; + HeapTuple htup; + List *result; + List *oldlist; + char replident = relation->rd_rel->relreplident; + Oid pkeyIndex = InvalidOid; + Oid candidateIndex = InvalidOid; + MemoryContext oldcxt; + + /* Quick exit if we already computed the list. */ + if (relation->rd_indexvalid) + return list_copy(relation->rd_indexlist); + + /* + * We build the list we intend to return (in the caller's context) while + * doing the scan. After successfully completing the scan, we copy that + * list into the relcache entry. This avoids cache-context memory leakage + * if we get some sort of error partway through. + */ + result = NIL; + + /* Prepare to scan pg_index for entries having indrelid = this rel. */ + ScanKeyInit(&skey, + Anum_pg_index_indrelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(RelationGetRelid(relation))); + + indrel = table_open(IndexRelationId, AccessShareLock); + indscan = systable_beginscan(indrel, IndexIndrelidIndexId, true, + NULL, 1, &skey); + + while (HeapTupleIsValid(htup = systable_getnext(indscan))) + { + Form_pg_index index = (Form_pg_index) GETSTRUCT(htup); + + /* + * Ignore any indexes that are currently being dropped. This will + * prevent them from being searched, inserted into, or considered in + * HOT-safety decisions. It's unsafe to touch such an index at all + * since its catalog entries could disappear at any instant. + */ + if (!index->indislive) + continue; + + /* add index's OID to result list */ + result = lappend_oid(result, index->indexrelid); + + /* + * Invalid, non-unique, non-immediate or predicate indexes aren't + * interesting for either oid indexes or replication identity indexes, + * so don't check them. + */ + if (!index->indisvalid || !index->indisunique || + !index->indimmediate || + !heap_attisnull(htup, Anum_pg_index_indpred, NULL)) + continue; + + /* remember primary key index if any */ + if (index->indisprimary) + pkeyIndex = index->indexrelid; + + /* remember explicitly chosen replica index */ + if (index->indisreplident) + candidateIndex = index->indexrelid; + } + + systable_endscan(indscan); + + table_close(indrel, AccessShareLock); + + /* Sort the result list into OID order, per API spec. */ + list_sort(result, list_oid_cmp); + + /* Now save a copy of the completed list in the relcache entry. */ + oldcxt = MemoryContextSwitchTo(CacheMemoryContext); + oldlist = relation->rd_indexlist; + relation->rd_indexlist = list_copy(result); + relation->rd_pkindex = pkeyIndex; + if (replident == REPLICA_IDENTITY_DEFAULT && OidIsValid(pkeyIndex)) + relation->rd_replidindex = pkeyIndex; + else if (replident == REPLICA_IDENTITY_INDEX && OidIsValid(candidateIndex)) + relation->rd_replidindex = candidateIndex; + else + relation->rd_replidindex = InvalidOid; + relation->rd_indexvalid = true; + MemoryContextSwitchTo(oldcxt); + + /* Don't leak the old list, if there is one */ + list_free(oldlist); + + return result; +} + +/* + * RelationGetStatExtList + * get a list of OIDs of statistics objects on this relation + * + * The statistics list is created only if someone requests it, in a way + * similar to RelationGetIndexList(). We scan pg_statistic_ext to find + * relevant statistics, and add the list to the relcache entry so that we + * won't have to compute it again. Note that shared cache inval of a + * relcache entry will delete the old list and set rd_statvalid to 0, + * so that we must recompute the statistics list on next request. This + * handles creation or deletion of a statistics object. + * + * The returned list is guaranteed to be sorted in order by OID, although + * this is not currently needed. + * + * Since shared cache inval causes the relcache's copy of the list to go away, + * we return a copy of the list palloc'd in the caller's context. The caller + * may list_free() the returned list after scanning it. This is necessary + * since the caller will typically be doing syscache lookups on the relevant + * statistics, and syscache lookup could cause SI messages to be processed! + */ +List * +RelationGetStatExtList(Relation relation) +{ + Relation indrel; + SysScanDesc indscan; + ScanKeyData skey; + HeapTuple htup; + List *result; + List *oldlist; + MemoryContext oldcxt; + + /* Quick exit if we already computed the list. */ + if (relation->rd_statvalid != 0) + return list_copy(relation->rd_statlist); + + /* + * We build the list we intend to return (in the caller's context) while + * doing the scan. After successfully completing the scan, we copy that + * list into the relcache entry. This avoids cache-context memory leakage + * if we get some sort of error partway through. + */ + result = NIL; + + /* + * Prepare to scan pg_statistic_ext for entries having stxrelid = this + * rel. + */ + ScanKeyInit(&skey, + Anum_pg_statistic_ext_stxrelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(RelationGetRelid(relation))); + + indrel = table_open(StatisticExtRelationId, AccessShareLock); + indscan = systable_beginscan(indrel, StatisticExtRelidIndexId, true, + NULL, 1, &skey); + + while (HeapTupleIsValid(htup = systable_getnext(indscan))) + { + Oid oid = ((Form_pg_statistic_ext) GETSTRUCT(htup))->oid; + + result = lappend_oid(result, oid); + } + + systable_endscan(indscan); + + table_close(indrel, AccessShareLock); + + /* Sort the result list into OID order, per API spec. */ + list_sort(result, list_oid_cmp); + + /* Now save a copy of the completed list in the relcache entry. */ + oldcxt = MemoryContextSwitchTo(CacheMemoryContext); + oldlist = relation->rd_statlist; + relation->rd_statlist = list_copy(result); + + relation->rd_statvalid = true; + MemoryContextSwitchTo(oldcxt); + + /* Don't leak the old list, if there is one */ + list_free(oldlist); + + return result; +} + +/* + * RelationGetPrimaryKeyIndex -- get OID of the relation's primary key index + * + * Returns InvalidOid if there is no such index. + */ +Oid +RelationGetPrimaryKeyIndex(Relation relation) +{ + List *ilist; + + if (!relation->rd_indexvalid) + { + /* RelationGetIndexList does the heavy lifting. */ + ilist = RelationGetIndexList(relation); + list_free(ilist); + Assert(relation->rd_indexvalid); + } + + return relation->rd_pkindex; +} + +/* + * RelationGetReplicaIndex -- get OID of the relation's replica identity index + * + * Returns InvalidOid if there is no such index. + */ +Oid +RelationGetReplicaIndex(Relation relation) +{ + List *ilist; + + if (!relation->rd_indexvalid) + { + /* RelationGetIndexList does the heavy lifting. */ + ilist = RelationGetIndexList(relation); + list_free(ilist); + Assert(relation->rd_indexvalid); + } + + return relation->rd_replidindex; +} + +/* + * RelationGetIndexExpressions -- get the index expressions for an index + * + * We cache the result of transforming pg_index.indexprs into a node tree. + * If the rel is not an index or has no expressional columns, we return NIL. + * Otherwise, the returned tree is copied into the caller's memory context. + * (We don't want to return a pointer to the relcache copy, since it could + * disappear due to relcache invalidation.) + */ +List * +RelationGetIndexExpressions(Relation relation) +{ + List *result; + Datum exprsDatum; + bool isnull; + char *exprsString; + MemoryContext oldcxt; + + /* Quick exit if we already computed the result. */ + if (relation->rd_indexprs) + return copyObject(relation->rd_indexprs); + + /* Quick exit if there is nothing to do. */ + if (relation->rd_indextuple == NULL || + heap_attisnull(relation->rd_indextuple, Anum_pg_index_indexprs, NULL)) + return NIL; + + /* + * We build the tree we intend to return in the caller's context. After + * successfully completing the work, we copy it into the relcache entry. + * This avoids problems if we get some sort of error partway through. + */ + exprsDatum = heap_getattr(relation->rd_indextuple, + Anum_pg_index_indexprs, + GetPgIndexDescriptor(), + &isnull); + Assert(!isnull); + exprsString = TextDatumGetCString(exprsDatum); + result = (List *) stringToNode(exprsString); + pfree(exprsString); + + /* + * Run the expressions through eval_const_expressions. This is not just an + * optimization, but is necessary, because the planner will be comparing + * them to similarly-processed qual clauses, and may fail to detect valid + * matches without this. We must not use canonicalize_qual, however, + * since these aren't qual expressions. + */ + result = (List *) eval_const_expressions(NULL, (Node *) result); + + /* May as well fix opfuncids too */ + fix_opfuncids((Node *) result); + + /* Now save a copy of the completed tree in the relcache entry. */ + oldcxt = MemoryContextSwitchTo(relation->rd_indexcxt); + relation->rd_indexprs = copyObject(result); + MemoryContextSwitchTo(oldcxt); + + return result; +} + +/* + * RelationGetDummyIndexExpressions -- get dummy expressions for an index + * + * Return a list of dummy expressions (just Const nodes) with the same + * types/typmods/collations as the index's real expressions. This is + * useful in situations where we don't want to run any user-defined code. + */ +List * +RelationGetDummyIndexExpressions(Relation relation) +{ + List *result; + Datum exprsDatum; + bool isnull; + char *exprsString; + List *rawExprs; + ListCell *lc; + + /* Quick exit if there is nothing to do. */ + if (relation->rd_indextuple == NULL || + heap_attisnull(relation->rd_indextuple, Anum_pg_index_indexprs, NULL)) + return NIL; + + /* Extract raw node tree(s) from index tuple. */ + exprsDatum = heap_getattr(relation->rd_indextuple, + Anum_pg_index_indexprs, + GetPgIndexDescriptor(), + &isnull); + Assert(!isnull); + exprsString = TextDatumGetCString(exprsDatum); + rawExprs = (List *) stringToNode(exprsString); + pfree(exprsString); + + /* Construct null Consts; the typlen and typbyval are arbitrary. */ + result = NIL; + foreach(lc, rawExprs) + { + Node *rawExpr = (Node *) lfirst(lc); + + result = lappend(result, + makeConst(exprType(rawExpr), + exprTypmod(rawExpr), + exprCollation(rawExpr), + 1, + (Datum) 0, + true, + true)); + } + + return result; +} + +/* + * RelationGetIndexPredicate -- get the index predicate for an index + * + * We cache the result of transforming pg_index.indpred into an implicit-AND + * node tree (suitable for use in planning). + * If the rel is not an index or has no predicate, we return NIL. + * Otherwise, the returned tree is copied into the caller's memory context. + * (We don't want to return a pointer to the relcache copy, since it could + * disappear due to relcache invalidation.) + */ +List * +RelationGetIndexPredicate(Relation relation) +{ + List *result; + Datum predDatum; + bool isnull; + char *predString; + MemoryContext oldcxt; + + /* Quick exit if we already computed the result. */ + if (relation->rd_indpred) + return copyObject(relation->rd_indpred); + + /* Quick exit if there is nothing to do. */ + if (relation->rd_indextuple == NULL || + heap_attisnull(relation->rd_indextuple, Anum_pg_index_indpred, NULL)) + return NIL; + + /* + * We build the tree we intend to return in the caller's context. After + * successfully completing the work, we copy it into the relcache entry. + * This avoids problems if we get some sort of error partway through. + */ + predDatum = heap_getattr(relation->rd_indextuple, + Anum_pg_index_indpred, + GetPgIndexDescriptor(), + &isnull); + Assert(!isnull); + predString = TextDatumGetCString(predDatum); + result = (List *) stringToNode(predString); + pfree(predString); + + /* + * Run the expression through const-simplification and canonicalization. + * This is not just an optimization, but is necessary, because the planner + * will be comparing it to similarly-processed qual clauses, and may fail + * to detect valid matches without this. This must match the processing + * done to qual clauses in preprocess_expression()! (We can skip the + * stuff involving subqueries, however, since we don't allow any in index + * predicates.) + */ + result = (List *) eval_const_expressions(NULL, (Node *) result); + + result = (List *) canonicalize_qual((Expr *) result, false); + + /* Also convert to implicit-AND format */ + result = make_ands_implicit((Expr *) result); + + /* May as well fix opfuncids too */ + fix_opfuncids((Node *) result); + + /* Now save a copy of the completed tree in the relcache entry. */ + oldcxt = MemoryContextSwitchTo(relation->rd_indexcxt); + relation->rd_indpred = copyObject(result); + MemoryContextSwitchTo(oldcxt); + + return result; +} + +/* + * RelationGetIndexAttrBitmap -- get a bitmap of index attribute numbers + * + * The result has a bit set for each attribute used anywhere in the index + * definitions of all the indexes on this relation. (This includes not only + * simple index keys, but attributes used in expressions and partial-index + * predicates.) + * + * Depending on attrKind, a bitmap covering attnums for certain columns is + * returned: + * INDEX_ATTR_BITMAP_KEY Columns in non-partial unique indexes not + * in expressions (i.e., usable for FKs) + * INDEX_ATTR_BITMAP_PRIMARY_KEY Columns in the table's primary key + * INDEX_ATTR_BITMAP_IDENTITY_KEY Columns in the table's replica identity + * index (empty if FULL) + * INDEX_ATTR_BITMAP_HOT_BLOCKING Columns that block updates from being HOT + * INDEX_ATTR_BITMAP_SUMMARIZED Columns included in summarizing indexes + * + * Attribute numbers are offset by FirstLowInvalidHeapAttributeNumber so that + * we can include system attributes (e.g., OID) in the bitmap representation. + * + * Caller had better hold at least RowExclusiveLock on the target relation + * to ensure it is safe (deadlock-free) for us to take locks on the relation's + * indexes. Note that since the introduction of CREATE INDEX CONCURRENTLY, + * that lock level doesn't guarantee a stable set of indexes, so we have to + * be prepared to retry here in case of a change in the set of indexes. + * + * The returned result is palloc'd in the caller's memory context and should + * be bms_free'd when not needed anymore. + */ +Bitmapset * +RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind) +{ + Bitmapset *uindexattrs; /* columns in unique indexes */ + Bitmapset *pkindexattrs; /* columns in the primary index */ + Bitmapset *idindexattrs; /* columns in the replica identity */ + Bitmapset *hotblockingattrs; /* columns with HOT blocking indexes */ + Bitmapset *summarizedattrs; /* columns with summarizing indexes */ + List *indexoidlist; + List *newindexoidlist; + Oid relpkindex; + Oid relreplindex; + ListCell *l; + MemoryContext oldcxt; + + /* Quick exit if we already computed the result. */ + if (relation->rd_attrsvalid) + { + switch (attrKind) + { + case INDEX_ATTR_BITMAP_KEY: + return bms_copy(relation->rd_keyattr); + case INDEX_ATTR_BITMAP_PRIMARY_KEY: + return bms_copy(relation->rd_pkattr); + case INDEX_ATTR_BITMAP_IDENTITY_KEY: + return bms_copy(relation->rd_idattr); + case INDEX_ATTR_BITMAP_HOT_BLOCKING: + return bms_copy(relation->rd_hotblockingattr); + case INDEX_ATTR_BITMAP_SUMMARIZED: + return bms_copy(relation->rd_summarizedattr); + default: + elog(ERROR, "unknown attrKind %u", attrKind); + } + } + + /* Fast path if definitely no indexes */ + if (!RelationGetForm(relation)->relhasindex) + return NULL; + + /* + * Get cached list of index OIDs. If we have to start over, we do so here. + */ +restart: + indexoidlist = RelationGetIndexList(relation); + + /* Fall out if no indexes (but relhasindex was set) */ + if (indexoidlist == NIL) + return NULL; + + /* + * Copy the rd_pkindex and rd_replidindex values computed by + * RelationGetIndexList before proceeding. This is needed because a + * relcache flush could occur inside index_open below, resetting the + * fields managed by RelationGetIndexList. We need to do the work with + * stable values of these fields. + */ + relpkindex = relation->rd_pkindex; + relreplindex = relation->rd_replidindex; + + /* + * For each index, add referenced attributes to indexattrs. + * + * Note: we consider all indexes returned by RelationGetIndexList, even if + * they are not indisready or indisvalid. This is important because an + * index for which CREATE INDEX CONCURRENTLY has just started must be + * included in HOT-safety decisions (see README.HOT). If a DROP INDEX + * CONCURRENTLY is far enough along that we should ignore the index, it + * won't be returned at all by RelationGetIndexList. + */ + uindexattrs = NULL; + pkindexattrs = NULL; + idindexattrs = NULL; + hotblockingattrs = NULL; + summarizedattrs = NULL; + foreach(l, indexoidlist) + { + Oid indexOid = lfirst_oid(l); + Relation indexDesc; + Datum datum; + bool isnull; + Node *indexExpressions; + Node *indexPredicate; + int i; + bool isKey; /* candidate key */ + bool isPK; /* primary key */ + bool isIDKey; /* replica identity index */ + Bitmapset **attrs; + + indexDesc = index_open(indexOid, AccessShareLock); + + /* + * Extract index expressions and index predicate. Note: Don't use + * RelationGetIndexExpressions()/RelationGetIndexPredicate(), because + * those might run constant expressions evaluation, which needs a + * snapshot, which we might not have here. (Also, it's probably more + * sound to collect the bitmaps before any transformations that might + * eliminate columns, but the practical impact of this is limited.) + */ + + datum = heap_getattr(indexDesc->rd_indextuple, Anum_pg_index_indexprs, + GetPgIndexDescriptor(), &isnull); + if (!isnull) + indexExpressions = stringToNode(TextDatumGetCString(datum)); + else + indexExpressions = NULL; + + datum = heap_getattr(indexDesc->rd_indextuple, Anum_pg_index_indpred, + GetPgIndexDescriptor(), &isnull); + if (!isnull) + indexPredicate = stringToNode(TextDatumGetCString(datum)); + else + indexPredicate = NULL; + + /* Can this index be referenced by a foreign key? */ + isKey = indexDesc->rd_index->indisunique && + indexExpressions == NULL && + indexPredicate == NULL; + + /* Is this a primary key? */ + isPK = (indexOid == relpkindex); + + /* Is this index the configured (or default) replica identity? */ + isIDKey = (indexOid == relreplindex); + + /* + * If the index is summarizing, it doesn't block HOT updates, but we + * may still need to update it (if the attributes were modified). So + * decide which bitmap we'll update in the following loop. + */ + if (indexDesc->rd_indam->amsummarizing) + attrs = &summarizedattrs; + else + attrs = &hotblockingattrs; + + /* Collect simple attribute references */ + for (i = 0; i < indexDesc->rd_index->indnatts; i++) + { + int attrnum = indexDesc->rd_index->indkey.values[i]; + + /* + * Since we have covering indexes with non-key columns, we must + * handle them accurately here. non-key columns must be added into + * hotblockingattrs or summarizedattrs, since they are in index, + * and update shouldn't miss them. + * + * Summarizing indexes do not block HOT, but do need to be updated + * when the column value changes, thus require a separate + * attribute bitmapset. + * + * Obviously, non-key columns couldn't be referenced by foreign + * key or identity key. Hence we do not include them into + * uindexattrs, pkindexattrs and idindexattrs bitmaps. + */ + if (attrnum != 0) + { + *attrs = bms_add_member(*attrs, + attrnum - FirstLowInvalidHeapAttributeNumber); + + if (isKey && i < indexDesc->rd_index->indnkeyatts) + uindexattrs = bms_add_member(uindexattrs, + attrnum - FirstLowInvalidHeapAttributeNumber); + + if (isPK && i < indexDesc->rd_index->indnkeyatts) + pkindexattrs = bms_add_member(pkindexattrs, + attrnum - FirstLowInvalidHeapAttributeNumber); + + if (isIDKey && i < indexDesc->rd_index->indnkeyatts) + idindexattrs = bms_add_member(idindexattrs, + attrnum - FirstLowInvalidHeapAttributeNumber); + } + } + + /* Collect all attributes used in expressions, too */ + pull_varattnos(indexExpressions, 1, attrs); + + /* Collect all attributes in the index predicate, too */ + pull_varattnos(indexPredicate, 1, attrs); + + index_close(indexDesc, AccessShareLock); + } + + /* + * During one of the index_opens in the above loop, we might have received + * a relcache flush event on this relcache entry, which might have been + * signaling a change in the rel's index list. If so, we'd better start + * over to ensure we deliver up-to-date attribute bitmaps. + */ + newindexoidlist = RelationGetIndexList(relation); + if (equal(indexoidlist, newindexoidlist) && + relpkindex == relation->rd_pkindex && + relreplindex == relation->rd_replidindex) + { + /* Still the same index set, so proceed */ + list_free(newindexoidlist); + list_free(indexoidlist); + } + else + { + /* Gotta do it over ... might as well not leak memory */ + list_free(newindexoidlist); + list_free(indexoidlist); + bms_free(uindexattrs); + bms_free(pkindexattrs); + bms_free(idindexattrs); + bms_free(hotblockingattrs); + bms_free(summarizedattrs); + + goto restart; + } + + /* Don't leak the old values of these bitmaps, if any */ + relation->rd_attrsvalid = false; + bms_free(relation->rd_keyattr); + relation->rd_keyattr = NULL; + bms_free(relation->rd_pkattr); + relation->rd_pkattr = NULL; + bms_free(relation->rd_idattr); + relation->rd_idattr = NULL; + bms_free(relation->rd_hotblockingattr); + relation->rd_hotblockingattr = NULL; + bms_free(relation->rd_summarizedattr); + relation->rd_summarizedattr = NULL; + + /* + * Now save copies of the bitmaps in the relcache entry. We intentionally + * set rd_attrsvalid last, because that's the one that signals validity of + * the values; if we run out of memory before making that copy, we won't + * leave the relcache entry looking like the other ones are valid but + * empty. + */ + oldcxt = MemoryContextSwitchTo(CacheMemoryContext); + relation->rd_keyattr = bms_copy(uindexattrs); + relation->rd_pkattr = bms_copy(pkindexattrs); + relation->rd_idattr = bms_copy(idindexattrs); + relation->rd_hotblockingattr = bms_copy(hotblockingattrs); + relation->rd_summarizedattr = bms_copy(summarizedattrs); + relation->rd_attrsvalid = true; + MemoryContextSwitchTo(oldcxt); + + /* We return our original working copy for caller to play with */ + switch (attrKind) + { + case INDEX_ATTR_BITMAP_KEY: + return uindexattrs; + case INDEX_ATTR_BITMAP_PRIMARY_KEY: + return pkindexattrs; + case INDEX_ATTR_BITMAP_IDENTITY_KEY: + return idindexattrs; + case INDEX_ATTR_BITMAP_HOT_BLOCKING: + return hotblockingattrs; + case INDEX_ATTR_BITMAP_SUMMARIZED: + return summarizedattrs; + default: + elog(ERROR, "unknown attrKind %u", attrKind); + return NULL; + } +} + +/* + * RelationGetIdentityKeyBitmap -- get a bitmap of replica identity attribute + * numbers + * + * A bitmap of index attribute numbers for the configured replica identity + * index is returned. + * + * See also comments of RelationGetIndexAttrBitmap(). + * + * This is a special purpose function used during logical replication. Here, + * unlike RelationGetIndexAttrBitmap(), we don't acquire a lock on the required + * index as we build the cache entry using a historic snapshot and all the + * later changes are absorbed while decoding WAL. Due to this reason, we don't + * need to retry here in case of a change in the set of indexes. + */ +Bitmapset * +RelationGetIdentityKeyBitmap(Relation relation) +{ + Bitmapset *idindexattrs = NULL; /* columns in the replica identity */ + Relation indexDesc; + int i; + Oid replidindex; + MemoryContext oldcxt; + + /* Quick exit if we already computed the result */ + if (relation->rd_idattr != NULL) + return bms_copy(relation->rd_idattr); + + /* Fast path if definitely no indexes */ + if (!RelationGetForm(relation)->relhasindex) + return NULL; + + /* Historic snapshot must be set. */ + Assert(HistoricSnapshotActive()); + + replidindex = RelationGetReplicaIndex(relation); + + /* Fall out if there is no replica identity index */ + if (!OidIsValid(replidindex)) + return NULL; + + /* Look up the description for the replica identity index */ + indexDesc = RelationIdGetRelation(replidindex); + + if (!RelationIsValid(indexDesc)) + elog(ERROR, "could not open relation with OID %u", + relation->rd_replidindex); + + /* Add referenced attributes to idindexattrs */ + for (i = 0; i < indexDesc->rd_index->indnatts; i++) + { + int attrnum = indexDesc->rd_index->indkey.values[i]; + + /* + * We don't include non-key columns into idindexattrs bitmaps. See + * RelationGetIndexAttrBitmap. + */ + if (attrnum != 0) + { + if (i < indexDesc->rd_index->indnkeyatts) + idindexattrs = bms_add_member(idindexattrs, + attrnum - FirstLowInvalidHeapAttributeNumber); + } + } + + RelationClose(indexDesc); + + /* Don't leak the old values of these bitmaps, if any */ + bms_free(relation->rd_idattr); + relation->rd_idattr = NULL; + + /* Now save copy of the bitmap in the relcache entry */ + oldcxt = MemoryContextSwitchTo(CacheMemoryContext); + relation->rd_idattr = bms_copy(idindexattrs); + MemoryContextSwitchTo(oldcxt); + + /* We return our original working copy for caller to play with */ + return idindexattrs; +} + +/* + * RelationGetExclusionInfo -- get info about index's exclusion constraint + * + * This should be called only for an index that is known to have an + * associated exclusion constraint. It returns arrays (palloc'd in caller's + * context) of the exclusion operator OIDs, their underlying functions' + * OIDs, and their strategy numbers in the index's opclasses. We cache + * all this information since it requires a fair amount of work to get. + */ +void +RelationGetExclusionInfo(Relation indexRelation, + Oid **operators, + Oid **procs, + uint16 **strategies) +{ + int indnkeyatts; + Oid *ops; + Oid *funcs; + uint16 *strats; + Relation conrel; + SysScanDesc conscan; + ScanKeyData skey[1]; + HeapTuple htup; + bool found; + MemoryContext oldcxt; + int i; + + indnkeyatts = IndexRelationGetNumberOfKeyAttributes(indexRelation); + + /* Allocate result space in caller context */ + *operators = ops = (Oid *) palloc(sizeof(Oid) * indnkeyatts); + *procs = funcs = (Oid *) palloc(sizeof(Oid) * indnkeyatts); + *strategies = strats = (uint16 *) palloc(sizeof(uint16) * indnkeyatts); + + /* Quick exit if we have the data cached already */ + if (indexRelation->rd_exclstrats != NULL) + { + memcpy(ops, indexRelation->rd_exclops, sizeof(Oid) * indnkeyatts); + memcpy(funcs, indexRelation->rd_exclprocs, sizeof(Oid) * indnkeyatts); + memcpy(strats, indexRelation->rd_exclstrats, sizeof(uint16) * indnkeyatts); + return; + } + + /* + * Search pg_constraint for the constraint associated with the index. To + * make this not too painfully slow, we use the index on conrelid; that + * will hold the parent relation's OID not the index's own OID. + * + * Note: if we wanted to rely on the constraint name matching the index's + * name, we could just do a direct lookup using pg_constraint's unique + * index. For the moment it doesn't seem worth requiring that. + */ + ScanKeyInit(&skey[0], + Anum_pg_constraint_conrelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(indexRelation->rd_index->indrelid)); + + conrel = table_open(ConstraintRelationId, AccessShareLock); + conscan = systable_beginscan(conrel, ConstraintRelidTypidNameIndexId, true, + NULL, 1, skey); + found = false; + + while (HeapTupleIsValid(htup = systable_getnext(conscan))) + { + Form_pg_constraint conform = (Form_pg_constraint) GETSTRUCT(htup); + Datum val; + bool isnull; + ArrayType *arr; + int nelem; + + /* We want the exclusion constraint owning the index */ + if (conform->contype != CONSTRAINT_EXCLUSION || + conform->conindid != RelationGetRelid(indexRelation)) + continue; + + /* There should be only one */ + if (found) + elog(ERROR, "unexpected exclusion constraint record found for rel %s", + RelationGetRelationName(indexRelation)); + found = true; + + /* Extract the operator OIDS from conexclop */ + val = fastgetattr(htup, + Anum_pg_constraint_conexclop, + conrel->rd_att, &isnull); + if (isnull) + elog(ERROR, "null conexclop for rel %s", + RelationGetRelationName(indexRelation)); + + arr = DatumGetArrayTypeP(val); /* ensure not toasted */ + nelem = ARR_DIMS(arr)[0]; + if (ARR_NDIM(arr) != 1 || + nelem != indnkeyatts || + ARR_HASNULL(arr) || + ARR_ELEMTYPE(arr) != OIDOID) + elog(ERROR, "conexclop is not a 1-D Oid array"); + + memcpy(ops, ARR_DATA_PTR(arr), sizeof(Oid) * indnkeyatts); + } + + systable_endscan(conscan); + table_close(conrel, AccessShareLock); + + if (!found) + elog(ERROR, "exclusion constraint record missing for rel %s", + RelationGetRelationName(indexRelation)); + + /* We need the func OIDs and strategy numbers too */ + for (i = 0; i < indnkeyatts; i++) + { + funcs[i] = get_opcode(ops[i]); + strats[i] = get_op_opfamily_strategy(ops[i], + indexRelation->rd_opfamily[i]); + /* shouldn't fail, since it was checked at index creation */ + if (strats[i] == InvalidStrategy) + elog(ERROR, "could not find strategy for operator %u in family %u", + ops[i], indexRelation->rd_opfamily[i]); + } + + /* Save a copy of the results in the relcache entry. */ + oldcxt = MemoryContextSwitchTo(indexRelation->rd_indexcxt); + indexRelation->rd_exclops = (Oid *) palloc(sizeof(Oid) * indnkeyatts); + indexRelation->rd_exclprocs = (Oid *) palloc(sizeof(Oid) * indnkeyatts); + indexRelation->rd_exclstrats = (uint16 *) palloc(sizeof(uint16) * indnkeyatts); + memcpy(indexRelation->rd_exclops, ops, sizeof(Oid) * indnkeyatts); + memcpy(indexRelation->rd_exclprocs, funcs, sizeof(Oid) * indnkeyatts); + memcpy(indexRelation->rd_exclstrats, strats, sizeof(uint16) * indnkeyatts); + MemoryContextSwitchTo(oldcxt); +} + +/* + * Get the publication information for the given relation. + * + * Traverse all the publications which the relation is in to get the + * publication actions and validate the row filter expressions for such + * publications if any. We consider the row filter expression as invalid if it + * references any column which is not part of REPLICA IDENTITY. + * + * To avoid fetching the publication information repeatedly, we cache the + * publication actions and row filter validation information. + */ +void +RelationBuildPublicationDesc(Relation relation, PublicationDesc *pubdesc) +{ + List *puboids; + ListCell *lc; + MemoryContext oldcxt; + Oid schemaid; + List *ancestors = NIL; + Oid relid = RelationGetRelid(relation); + + /* + * If not publishable, it publishes no actions. (pgoutput_change() will + * ignore it.) + */ + if (!is_publishable_relation(relation)) + { + memset(pubdesc, 0, sizeof(PublicationDesc)); + pubdesc->rf_valid_for_update = true; + pubdesc->rf_valid_for_delete = true; + pubdesc->cols_valid_for_update = true; + pubdesc->cols_valid_for_delete = true; + return; + } + + if (relation->rd_pubdesc) + { + memcpy(pubdesc, relation->rd_pubdesc, sizeof(PublicationDesc)); + return; + } + + memset(pubdesc, 0, sizeof(PublicationDesc)); + pubdesc->rf_valid_for_update = true; + pubdesc->rf_valid_for_delete = true; + pubdesc->cols_valid_for_update = true; + pubdesc->cols_valid_for_delete = true; + + /* Fetch the publication membership info. */ + puboids = GetRelationPublications(relid); + schemaid = RelationGetNamespace(relation); + puboids = list_concat_unique_oid(puboids, GetSchemaPublications(schemaid)); + + if (relation->rd_rel->relispartition) + { + /* Add publications that the ancestors are in too. */ + ancestors = get_partition_ancestors(relid); + + foreach(lc, ancestors) + { + Oid ancestor = lfirst_oid(lc); + + puboids = list_concat_unique_oid(puboids, + GetRelationPublications(ancestor)); + schemaid = get_rel_namespace(ancestor); + puboids = list_concat_unique_oid(puboids, + GetSchemaPublications(schemaid)); + } + } + puboids = list_concat_unique_oid(puboids, GetAllTablesPublications()); + + foreach(lc, puboids) + { + Oid pubid = lfirst_oid(lc); + HeapTuple tup; + Form_pg_publication pubform; + + tup = SearchSysCache1(PUBLICATIONOID, ObjectIdGetDatum(pubid)); + + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for publication %u", pubid); + + pubform = (Form_pg_publication) GETSTRUCT(tup); + + pubdesc->pubactions.pubinsert |= pubform->pubinsert; + pubdesc->pubactions.pubupdate |= pubform->pubupdate; + pubdesc->pubactions.pubdelete |= pubform->pubdelete; + pubdesc->pubactions.pubtruncate |= pubform->pubtruncate; + + /* + * Check if all columns referenced in the filter expression are part + * of the REPLICA IDENTITY index or not. + * + * If the publication is FOR ALL TABLES then it means the table has no + * row filters and we can skip the validation. + */ + if (!pubform->puballtables && + (pubform->pubupdate || pubform->pubdelete) && + pub_rf_contains_invalid_column(pubid, relation, ancestors, + pubform->pubviaroot)) + { + if (pubform->pubupdate) + pubdesc->rf_valid_for_update = false; + if (pubform->pubdelete) + pubdesc->rf_valid_for_delete = false; + } + + /* + * Check if all columns are part of the REPLICA IDENTITY index or not. + * + * If the publication is FOR ALL TABLES then it means the table has no + * column list and we can skip the validation. + */ + if (!pubform->puballtables && + (pubform->pubupdate || pubform->pubdelete) && + pub_collist_contains_invalid_column(pubid, relation, ancestors, + pubform->pubviaroot)) + { + if (pubform->pubupdate) + pubdesc->cols_valid_for_update = false; + if (pubform->pubdelete) + pubdesc->cols_valid_for_delete = false; + } + + ReleaseSysCache(tup); + + /* + * If we know everything is replicated and the row filter is invalid + * for update and delete, there is no point to check for other + * publications. + */ + if (pubdesc->pubactions.pubinsert && pubdesc->pubactions.pubupdate && + pubdesc->pubactions.pubdelete && pubdesc->pubactions.pubtruncate && + !pubdesc->rf_valid_for_update && !pubdesc->rf_valid_for_delete) + break; + + /* + * If we know everything is replicated and the column list is invalid + * for update and delete, there is no point to check for other + * publications. + */ + if (pubdesc->pubactions.pubinsert && pubdesc->pubactions.pubupdate && + pubdesc->pubactions.pubdelete && pubdesc->pubactions.pubtruncate && + !pubdesc->cols_valid_for_update && !pubdesc->cols_valid_for_delete) + break; + } + + if (relation->rd_pubdesc) + { + pfree(relation->rd_pubdesc); + relation->rd_pubdesc = NULL; + } + + /* Now save copy of the descriptor in the relcache entry. */ + oldcxt = MemoryContextSwitchTo(CacheMemoryContext); + relation->rd_pubdesc = palloc(sizeof(PublicationDesc)); + memcpy(relation->rd_pubdesc, pubdesc, sizeof(PublicationDesc)); + MemoryContextSwitchTo(oldcxt); +} + +/* + * RelationGetIndexRawAttOptions -- get AM/opclass-specific options for the index + */ +Datum * +RelationGetIndexRawAttOptions(Relation indexrel) +{ + Oid indexrelid = RelationGetRelid(indexrel); + int16 natts = RelationGetNumberOfAttributes(indexrel); + Datum *options = NULL; + int16 attnum; + + for (attnum = 1; attnum <= natts; attnum++) + { + if (indexrel->rd_indam->amoptsprocnum == 0) + continue; + + if (!OidIsValid(index_getprocid(indexrel, attnum, + indexrel->rd_indam->amoptsprocnum))) + continue; + + if (!options) + options = palloc0(sizeof(Datum) * natts); + + options[attnum - 1] = get_attoptions(indexrelid, attnum); + } + + return options; +} + +static bytea ** +CopyIndexAttOptions(bytea **srcopts, int natts) +{ + bytea **opts = palloc(sizeof(*opts) * natts); + + for (int i = 0; i < natts; i++) + { + bytea *opt = srcopts[i]; + + opts[i] = !opt ? NULL : (bytea *) + DatumGetPointer(datumCopy(PointerGetDatum(opt), false, -1)); + } + + return opts; +} + +/* + * RelationGetIndexAttOptions + * get AM/opclass-specific options for an index parsed into a binary form + */ +bytea ** +RelationGetIndexAttOptions(Relation relation, bool copy) +{ + MemoryContext oldcxt; + bytea **opts = relation->rd_opcoptions; + Oid relid = RelationGetRelid(relation); + int natts = RelationGetNumberOfAttributes(relation); /* XXX + * IndexRelationGetNumberOfKeyAttributes */ + int i; + + /* Try to copy cached options. */ + if (opts) + return copy ? CopyIndexAttOptions(opts, natts) : opts; + + /* Get and parse opclass options. */ + opts = palloc0(sizeof(*opts) * natts); + + for (i = 0; i < natts; i++) + { + if (criticalRelcachesBuilt && relid != AttributeRelidNumIndexId) + { + Datum attoptions = get_attoptions(relid, i + 1); + + opts[i] = index_opclass_options(relation, i + 1, attoptions, false); + + if (attoptions != (Datum) 0) + pfree(DatumGetPointer(attoptions)); + } + } + + /* Copy parsed options to the cache. */ + oldcxt = MemoryContextSwitchTo(relation->rd_indexcxt); + relation->rd_opcoptions = CopyIndexAttOptions(opts, natts); + MemoryContextSwitchTo(oldcxt); + + if (copy) + return opts; + + for (i = 0; i < natts; i++) + { + if (opts[i]) + pfree(opts[i]); + } + + pfree(opts); + + return relation->rd_opcoptions; +} + +/* + * Routines to support ereport() reports of relation-related errors + * + * These could have been put into elog.c, but it seems like a module layering + * violation to have elog.c calling relcache or syscache stuff --- and we + * definitely don't want elog.h including rel.h. So we put them here. + */ + +/* + * errtable --- stores schema_name and table_name of a table + * within the current errordata. + */ +int +errtable(Relation rel) +{ + err_generic_string(PG_DIAG_SCHEMA_NAME, + get_namespace_name(RelationGetNamespace(rel))); + err_generic_string(PG_DIAG_TABLE_NAME, RelationGetRelationName(rel)); + + return 0; /* return value does not matter */ +} + +/* + * errtablecol --- stores schema_name, table_name and column_name + * of a table column within the current errordata. + * + * The column is specified by attribute number --- for most callers, this is + * easier and less error-prone than getting the column name for themselves. + */ +int +errtablecol(Relation rel, int attnum) +{ + TupleDesc reldesc = RelationGetDescr(rel); + const char *colname; + + /* Use reldesc if it's a user attribute, else consult the catalogs */ + if (attnum > 0 && attnum <= reldesc->natts) + colname = NameStr(TupleDescAttr(reldesc, attnum - 1)->attname); + else + colname = get_attname(RelationGetRelid(rel), attnum, false); + + return errtablecolname(rel, colname); +} + +/* + * errtablecolname --- stores schema_name, table_name and column_name + * of a table column within the current errordata, where the column name is + * given directly rather than extracted from the relation's catalog data. + * + * Don't use this directly unless errtablecol() is inconvenient for some + * reason. This might possibly be needed during intermediate states in ALTER + * TABLE, for instance. + */ +int +errtablecolname(Relation rel, const char *colname) +{ + errtable(rel); + err_generic_string(PG_DIAG_COLUMN_NAME, colname); + + return 0; /* return value does not matter */ +} + +/* + * errtableconstraint --- stores schema_name, table_name and constraint_name + * of a table-related constraint within the current errordata. + */ +int +errtableconstraint(Relation rel, const char *conname) +{ + errtable(rel); + err_generic_string(PG_DIAG_CONSTRAINT_NAME, conname); + + return 0; /* return value does not matter */ +} + + +/* + * load_relcache_init_file, write_relcache_init_file + * + * In late 1992, we started regularly having databases with more than + * a thousand classes in them. With this number of classes, it became + * critical to do indexed lookups on the system catalogs. + * + * Bootstrapping these lookups is very hard. We want to be able to + * use an index on pg_attribute, for example, but in order to do so, + * we must have read pg_attribute for the attributes in the index, + * which implies that we need to use the index. + * + * In order to get around the problem, we do the following: + * + * + When the database system is initialized (at initdb time), we + * don't use indexes. We do sequential scans. + * + * + When the backend is started up in normal mode, we load an image + * of the appropriate relation descriptors, in internal format, + * from an initialization file in the data/base/... directory. + * + * + If the initialization file isn't there, then we create the + * relation descriptors using sequential scans and write 'em to + * the initialization file for use by subsequent backends. + * + * As of Postgres 9.0, there is one local initialization file in each + * database, plus one shared initialization file for shared catalogs. + * + * We could dispense with the initialization files and just build the + * critical reldescs the hard way on every backend startup, but that + * slows down backend startup noticeably. + * + * We can in fact go further, and save more relcache entries than + * just the ones that are absolutely critical; this allows us to speed + * up backend startup by not having to build such entries the hard way. + * Presently, all the catalog and index entries that are referred to + * by catcaches are stored in the initialization files. + * + * The same mechanism that detects when catcache and relcache entries + * need to be invalidated (due to catalog updates) also arranges to + * unlink the initialization files when the contents may be out of date. + * The files will then be rebuilt during the next backend startup. + */ + +/* + * load_relcache_init_file -- attempt to load cache from the shared + * or local cache init file + * + * If successful, return true and set criticalRelcachesBuilt or + * criticalSharedRelcachesBuilt to true. + * If not successful, return false. + * + * NOTE: we assume we are already switched into CacheMemoryContext. + */ +static bool +load_relcache_init_file(bool shared) +{ + FILE *fp; + char initfilename[MAXPGPATH]; + Relation *rels; + int relno, + num_rels, + max_rels, + nailed_rels, + nailed_indexes, + magic; + int i; + + if (shared) + snprintf(initfilename, sizeof(initfilename), "global/%s", + RELCACHE_INIT_FILENAME); + else + snprintf(initfilename, sizeof(initfilename), "%s/%s", + DatabasePath, RELCACHE_INIT_FILENAME); + + fp = AllocateFile(initfilename, PG_BINARY_R); + if (fp == NULL) + return false; + + /* + * Read the index relcache entries from the file. Note we will not enter + * any of them into the cache if the read fails partway through; this + * helps to guard against broken init files. + */ + max_rels = 100; + rels = (Relation *) palloc(max_rels * sizeof(Relation)); + num_rels = 0; + nailed_rels = nailed_indexes = 0; + + /* check for correct magic number (compatible version) */ + if (fread(&magic, 1, sizeof(magic), fp) != sizeof(magic)) + goto read_failed; + if (magic != RELCACHE_INIT_FILEMAGIC) + goto read_failed; + + for (relno = 0;; relno++) + { + Size len; + size_t nread; + Relation rel; + Form_pg_class relform; + bool has_not_null; + + /* first read the relation descriptor length */ + nread = fread(&len, 1, sizeof(len), fp); + if (nread != sizeof(len)) + { + if (nread == 0) + break; /* end of file */ + goto read_failed; + } + + /* safety check for incompatible relcache layout */ + if (len != sizeof(RelationData)) + goto read_failed; + + /* allocate another relcache header */ + if (num_rels >= max_rels) + { + max_rels *= 2; + rels = (Relation *) repalloc(rels, max_rels * sizeof(Relation)); + } + + rel = rels[num_rels++] = (Relation) palloc(len); + + /* then, read the Relation structure */ + if (fread(rel, 1, len, fp) != len) + goto read_failed; + + /* next read the relation tuple form */ + if (fread(&len, 1, sizeof(len), fp) != sizeof(len)) + goto read_failed; + + relform = (Form_pg_class) palloc(len); + if (fread(relform, 1, len, fp) != len) + goto read_failed; + + rel->rd_rel = relform; + + /* initialize attribute tuple forms */ + rel->rd_att = CreateTemplateTupleDesc(relform->relnatts); + rel->rd_att->tdrefcount = 1; /* mark as refcounted */ + + rel->rd_att->tdtypeid = relform->reltype ? relform->reltype : RECORDOID; + rel->rd_att->tdtypmod = -1; /* just to be sure */ + + /* next read all the attribute tuple form data entries */ + has_not_null = false; + for (i = 0; i < relform->relnatts; i++) + { + Form_pg_attribute attr = TupleDescAttr(rel->rd_att, i); + + if (fread(&len, 1, sizeof(len), fp) != sizeof(len)) + goto read_failed; + if (len != ATTRIBUTE_FIXED_PART_SIZE) + goto read_failed; + if (fread(attr, 1, len, fp) != len) + goto read_failed; + + has_not_null |= attr->attnotnull; + } + + /* next read the access method specific field */ + if (fread(&len, 1, sizeof(len), fp) != sizeof(len)) + goto read_failed; + if (len > 0) + { + rel->rd_options = palloc(len); + if (fread(rel->rd_options, 1, len, fp) != len) + goto read_failed; + if (len != VARSIZE(rel->rd_options)) + goto read_failed; /* sanity check */ + } + else + { + rel->rd_options = NULL; + } + + /* mark not-null status */ + if (has_not_null) + { + TupleConstr *constr = (TupleConstr *) palloc0(sizeof(TupleConstr)); + + constr->has_not_null = true; + rel->rd_att->constr = constr; + } + + /* + * If it's an index, there's more to do. Note we explicitly ignore + * partitioned indexes here. + */ + if (rel->rd_rel->relkind == RELKIND_INDEX) + { + MemoryContext indexcxt; + Oid *opfamily; + Oid *opcintype; + RegProcedure *support; + int nsupport; + int16 *indoption; + Oid *indcollation; + + /* Count nailed indexes to ensure we have 'em all */ + if (rel->rd_isnailed) + nailed_indexes++; + + /* read the pg_index tuple */ + if (fread(&len, 1, sizeof(len), fp) != sizeof(len)) + goto read_failed; + + rel->rd_indextuple = (HeapTuple) palloc(len); + if (fread(rel->rd_indextuple, 1, len, fp) != len) + goto read_failed; + + /* Fix up internal pointers in the tuple -- see heap_copytuple */ + rel->rd_indextuple->t_data = (HeapTupleHeader) ((char *) rel->rd_indextuple + HEAPTUPLESIZE); + rel->rd_index = (Form_pg_index) GETSTRUCT(rel->rd_indextuple); + + /* + * prepare index info context --- parameters should match + * RelationInitIndexAccessInfo + */ + indexcxt = AllocSetContextCreate(CacheMemoryContext, + "index info", + ALLOCSET_SMALL_SIZES); + rel->rd_indexcxt = indexcxt; + MemoryContextCopyAndSetIdentifier(indexcxt, + RelationGetRelationName(rel)); + + /* + * Now we can fetch the index AM's API struct. (We can't store + * that in the init file, since it contains function pointers that + * might vary across server executions. Fortunately, it should be + * safe to call the amhandler even while bootstrapping indexes.) + */ + InitIndexAmRoutine(rel); + + /* read the vector of opfamily OIDs */ + if (fread(&len, 1, sizeof(len), fp) != sizeof(len)) + goto read_failed; + + opfamily = (Oid *) MemoryContextAlloc(indexcxt, len); + if (fread(opfamily, 1, len, fp) != len) + goto read_failed; + + rel->rd_opfamily = opfamily; + + /* read the vector of opcintype OIDs */ + if (fread(&len, 1, sizeof(len), fp) != sizeof(len)) + goto read_failed; + + opcintype = (Oid *) MemoryContextAlloc(indexcxt, len); + if (fread(opcintype, 1, len, fp) != len) + goto read_failed; + + rel->rd_opcintype = opcintype; + + /* read the vector of support procedure OIDs */ + if (fread(&len, 1, sizeof(len), fp) != sizeof(len)) + goto read_failed; + support = (RegProcedure *) MemoryContextAlloc(indexcxt, len); + if (fread(support, 1, len, fp) != len) + goto read_failed; + + rel->rd_support = support; + + /* read the vector of collation OIDs */ + if (fread(&len, 1, sizeof(len), fp) != sizeof(len)) + goto read_failed; + + indcollation = (Oid *) MemoryContextAlloc(indexcxt, len); + if (fread(indcollation, 1, len, fp) != len) + goto read_failed; + + rel->rd_indcollation = indcollation; + + /* read the vector of indoption values */ + if (fread(&len, 1, sizeof(len), fp) != sizeof(len)) + goto read_failed; + + indoption = (int16 *) MemoryContextAlloc(indexcxt, len); + if (fread(indoption, 1, len, fp) != len) + goto read_failed; + + rel->rd_indoption = indoption; + + /* read the vector of opcoptions values */ + rel->rd_opcoptions = (bytea **) + MemoryContextAllocZero(indexcxt, sizeof(*rel->rd_opcoptions) * relform->relnatts); + + for (i = 0; i < relform->relnatts; i++) + { + if (fread(&len, 1, sizeof(len), fp) != sizeof(len)) + goto read_failed; + + if (len > 0) + { + rel->rd_opcoptions[i] = (bytea *) MemoryContextAlloc(indexcxt, len); + if (fread(rel->rd_opcoptions[i], 1, len, fp) != len) + goto read_failed; + } + } + + /* set up zeroed fmgr-info vector */ + nsupport = relform->relnatts * rel->rd_indam->amsupport; + rel->rd_supportinfo = (FmgrInfo *) + MemoryContextAllocZero(indexcxt, nsupport * sizeof(FmgrInfo)); + } + else + { + /* Count nailed rels to ensure we have 'em all */ + if (rel->rd_isnailed) + nailed_rels++; + + /* Load table AM data */ + if (RELKIND_HAS_TABLE_AM(rel->rd_rel->relkind) || rel->rd_rel->relkind == RELKIND_SEQUENCE) + RelationInitTableAccessMethod(rel); + + Assert(rel->rd_index == NULL); + Assert(rel->rd_indextuple == NULL); + Assert(rel->rd_indexcxt == NULL); + Assert(rel->rd_indam == NULL); + Assert(rel->rd_opfamily == NULL); + Assert(rel->rd_opcintype == NULL); + Assert(rel->rd_support == NULL); + Assert(rel->rd_supportinfo == NULL); + Assert(rel->rd_indoption == NULL); + Assert(rel->rd_indcollation == NULL); + Assert(rel->rd_opcoptions == NULL); + } + + /* + * Rules and triggers are not saved (mainly because the internal + * format is complex and subject to change). They must be rebuilt if + * needed by RelationCacheInitializePhase3. This is not expected to + * be a big performance hit since few system catalogs have such. Ditto + * for RLS policy data, partition info, index expressions, predicates, + * exclusion info, and FDW info. + */ + rel->rd_rules = NULL; + rel->rd_rulescxt = NULL; + rel->trigdesc = NULL; + rel->rd_rsdesc = NULL; + rel->rd_partkey = NULL; + rel->rd_partkeycxt = NULL; + rel->rd_partdesc = NULL; + rel->rd_partdesc_nodetached = NULL; + rel->rd_partdesc_nodetached_xmin = InvalidTransactionId; + rel->rd_pdcxt = NULL; + rel->rd_pddcxt = NULL; + rel->rd_partcheck = NIL; + rel->rd_partcheckvalid = false; + rel->rd_partcheckcxt = NULL; + rel->rd_indexprs = NIL; + rel->rd_indpred = NIL; + rel->rd_exclops = NULL; + rel->rd_exclprocs = NULL; + rel->rd_exclstrats = NULL; + rel->rd_fdwroutine = NULL; + + /* + * Reset transient-state fields in the relcache entry + */ + rel->rd_smgr = NULL; + if (rel->rd_isnailed) + rel->rd_refcnt = 1; + else + rel->rd_refcnt = 0; + rel->rd_indexvalid = false; + rel->rd_indexlist = NIL; + rel->rd_pkindex = InvalidOid; + rel->rd_replidindex = InvalidOid; + rel->rd_attrsvalid = false; + rel->rd_keyattr = NULL; + rel->rd_pkattr = NULL; + rel->rd_idattr = NULL; + rel->rd_pubdesc = NULL; + rel->rd_statvalid = false; + rel->rd_statlist = NIL; + rel->rd_fkeyvalid = false; + rel->rd_fkeylist = NIL; + rel->rd_createSubid = InvalidSubTransactionId; + rel->rd_newRelfilelocatorSubid = InvalidSubTransactionId; + rel->rd_firstRelfilelocatorSubid = InvalidSubTransactionId; + rel->rd_droppedSubid = InvalidSubTransactionId; + rel->rd_amcache = NULL; + rel->pgstat_info = NULL; + + /* + * Recompute lock and physical addressing info. This is needed in + * case the pg_internal.init file was copied from some other database + * by CREATE DATABASE. + */ + RelationInitLockInfo(rel); + RelationInitPhysicalAddr(rel); + } + + /* + * We reached the end of the init file without apparent problem. Did we + * get the right number of nailed items? This is a useful crosscheck in + * case the set of critical rels or indexes changes. However, that should + * not happen in a normally-running system, so let's bleat if it does. + * + * For the shared init file, we're called before client authentication is + * done, which means that elog(WARNING) will go only to the postmaster + * log, where it's easily missed. To ensure that developers notice bad + * values of NUM_CRITICAL_SHARED_RELS/NUM_CRITICAL_SHARED_INDEXES, we put + * an Assert(false) there. + */ + if (shared) + { + if (nailed_rels != NUM_CRITICAL_SHARED_RELS || + nailed_indexes != NUM_CRITICAL_SHARED_INDEXES) + { + elog(WARNING, "found %d nailed shared rels and %d nailed shared indexes in init file, but expected %d and %d respectively", + nailed_rels, nailed_indexes, + NUM_CRITICAL_SHARED_RELS, NUM_CRITICAL_SHARED_INDEXES); + /* Make sure we get developers' attention about this */ + Assert(false); + /* In production builds, recover by bootstrapping the relcache */ + goto read_failed; + } + } + else + { + if (nailed_rels != NUM_CRITICAL_LOCAL_RELS || + nailed_indexes != NUM_CRITICAL_LOCAL_INDEXES) + { + elog(WARNING, "found %d nailed rels and %d nailed indexes in init file, but expected %d and %d respectively", + nailed_rels, nailed_indexes, + NUM_CRITICAL_LOCAL_RELS, NUM_CRITICAL_LOCAL_INDEXES); + /* We don't need an Assert() in this case */ + goto read_failed; + } + } + + /* + * OK, all appears well. + * + * Now insert all the new relcache entries into the cache. + */ + for (relno = 0; relno < num_rels; relno++) + { + RelationCacheInsert(rels[relno], false); + } + + pfree(rels); + FreeFile(fp); + + if (shared) + criticalSharedRelcachesBuilt = true; + else + criticalRelcachesBuilt = true; + return true; + + /* + * init file is broken, so do it the hard way. We don't bother trying to + * free the clutter we just allocated; it's not in the relcache so it + * won't hurt. + */ +read_failed: + pfree(rels); + FreeFile(fp); + + return false; +} + +/* + * Write out a new initialization file with the current contents + * of the relcache (either shared rels or local rels, as indicated). + */ +static void +write_relcache_init_file(bool shared) +{ + FILE *fp; + char tempfilename[MAXPGPATH]; + char finalfilename[MAXPGPATH]; + int magic; + HASH_SEQ_STATUS status; + RelIdCacheEnt *idhentry; + int i; + + /* + * If we have already received any relcache inval events, there's no + * chance of succeeding so we may as well skip the whole thing. + */ + if (relcacheInvalsReceived != 0L) + return; + + /* + * We must write a temporary file and rename it into place. Otherwise, + * another backend starting at about the same time might crash trying to + * read the partially-complete file. + */ + if (shared) + { + snprintf(tempfilename, sizeof(tempfilename), "global/%s.%d", + RELCACHE_INIT_FILENAME, MyProcPid); + snprintf(finalfilename, sizeof(finalfilename), "global/%s", + RELCACHE_INIT_FILENAME); + } + else + { + snprintf(tempfilename, sizeof(tempfilename), "%s/%s.%d", + DatabasePath, RELCACHE_INIT_FILENAME, MyProcPid); + snprintf(finalfilename, sizeof(finalfilename), "%s/%s", + DatabasePath, RELCACHE_INIT_FILENAME); + } + + unlink(tempfilename); /* in case it exists w/wrong permissions */ + + fp = AllocateFile(tempfilename, PG_BINARY_W); + if (fp == NULL) + { + /* + * We used to consider this a fatal error, but we might as well + * continue with backend startup ... + */ + ereport(WARNING, + (errcode_for_file_access(), + errmsg("could not create relation-cache initialization file \"%s\": %m", + tempfilename), + errdetail("Continuing anyway, but there's something wrong."))); + return; + } + + /* + * Write a magic number to serve as a file version identifier. We can + * change the magic number whenever the relcache layout changes. + */ + magic = RELCACHE_INIT_FILEMAGIC; + if (fwrite(&magic, 1, sizeof(magic), fp) != sizeof(magic)) + elog(FATAL, "could not write init file"); + + /* + * Write all the appropriate reldescs (in no particular order). + */ + hash_seq_init(&status, RelationIdCache); + + while ((idhentry = (RelIdCacheEnt *) hash_seq_search(&status)) != NULL) + { + Relation rel = idhentry->reldesc; + Form_pg_class relform = rel->rd_rel; + + /* ignore if not correct group */ + if (relform->relisshared != shared) + continue; + + /* + * Ignore if not supposed to be in init file. We can allow any shared + * relation that's been loaded so far to be in the shared init file, + * but unshared relations must be ones that should be in the local + * file per RelationIdIsInInitFile. (Note: if you want to change the + * criterion for rels to be kept in the init file, see also inval.c. + * The reason for filtering here is to be sure that we don't put + * anything into the local init file for which a relcache inval would + * not cause invalidation of that init file.) + */ + if (!shared && !RelationIdIsInInitFile(RelationGetRelid(rel))) + { + /* Nailed rels had better get stored. */ + Assert(!rel->rd_isnailed); + continue; + } + + /* first write the relcache entry proper */ + write_item(rel, sizeof(RelationData), fp); + + /* next write the relation tuple form */ + write_item(relform, CLASS_TUPLE_SIZE, fp); + + /* next, do all the attribute tuple form data entries */ + for (i = 0; i < relform->relnatts; i++) + { + write_item(TupleDescAttr(rel->rd_att, i), + ATTRIBUTE_FIXED_PART_SIZE, fp); + } + + /* next, do the access method specific field */ + write_item(rel->rd_options, + (rel->rd_options ? VARSIZE(rel->rd_options) : 0), + fp); + + /* + * If it's an index, there's more to do. Note we explicitly ignore + * partitioned indexes here. + */ + if (rel->rd_rel->relkind == RELKIND_INDEX) + { + /* write the pg_index tuple */ + /* we assume this was created by heap_copytuple! */ + write_item(rel->rd_indextuple, + HEAPTUPLESIZE + rel->rd_indextuple->t_len, + fp); + + /* write the vector of opfamily OIDs */ + write_item(rel->rd_opfamily, + relform->relnatts * sizeof(Oid), + fp); + + /* write the vector of opcintype OIDs */ + write_item(rel->rd_opcintype, + relform->relnatts * sizeof(Oid), + fp); + + /* write the vector of support procedure OIDs */ + write_item(rel->rd_support, + relform->relnatts * (rel->rd_indam->amsupport * sizeof(RegProcedure)), + fp); + + /* write the vector of collation OIDs */ + write_item(rel->rd_indcollation, + relform->relnatts * sizeof(Oid), + fp); + + /* write the vector of indoption values */ + write_item(rel->rd_indoption, + relform->relnatts * sizeof(int16), + fp); + + Assert(rel->rd_opcoptions); + + /* write the vector of opcoptions values */ + for (i = 0; i < relform->relnatts; i++) + { + bytea *opt = rel->rd_opcoptions[i]; + + write_item(opt, opt ? VARSIZE(opt) : 0, fp); + } + } + } + + if (FreeFile(fp)) + elog(FATAL, "could not write init file"); + + /* + * Now we have to check whether the data we've so painstakingly + * accumulated is already obsolete due to someone else's just-committed + * catalog changes. If so, we just delete the temp file and leave it to + * the next backend to try again. (Our own relcache entries will be + * updated by SI message processing, but we can't be sure whether what we + * wrote out was up-to-date.) + * + * This mustn't run concurrently with the code that unlinks an init file + * and sends SI messages, so grab a serialization lock for the duration. + */ + LWLockAcquire(RelCacheInitLock, LW_EXCLUSIVE); + + /* Make sure we have seen all incoming SI messages */ + AcceptInvalidationMessages(); + + /* + * If we have received any SI relcache invals since backend start, assume + * we may have written out-of-date data. + */ + if (relcacheInvalsReceived == 0L) + { + /* + * OK, rename the temp file to its final name, deleting any + * previously-existing init file. + * + * Note: a failure here is possible under Cygwin, if some other + * backend is holding open an unlinked-but-not-yet-gone init file. So + * treat this as a noncritical failure; just remove the useless temp + * file on failure. + */ + if (rename(tempfilename, finalfilename) < 0) + unlink(tempfilename); + } + else + { + /* Delete the already-obsolete temp file */ + unlink(tempfilename); + } + + LWLockRelease(RelCacheInitLock); +} + +/* write a chunk of data preceded by its length */ +static void +write_item(const void *data, Size len, FILE *fp) +{ + if (fwrite(&len, 1, sizeof(len), fp) != sizeof(len)) + elog(FATAL, "could not write init file"); + if (len > 0 && fwrite(data, 1, len, fp) != len) + elog(FATAL, "could not write init file"); +} + +/* + * Determine whether a given relation (identified by OID) is one of the ones + * we should store in a relcache init file. + * + * We must cache all nailed rels, and for efficiency we should cache every rel + * that supports a syscache. The former set is almost but not quite a subset + * of the latter. The special cases are relations where + * RelationCacheInitializePhase2/3 chooses to nail for efficiency reasons, but + * which do not support any syscache. + */ +bool +RelationIdIsInInitFile(Oid relationId) +{ + if (relationId == SharedSecLabelRelationId || + relationId == TriggerRelidNameIndexId || + relationId == DatabaseNameIndexId || + relationId == SharedSecLabelObjectIndexId) + { + /* + * If this Assert fails, we don't need the applicable special case + * anymore. + */ + Assert(!RelationSupportsSysCache(relationId)); + return true; + } + return RelationSupportsSysCache(relationId); +} + +/* + * Invalidate (remove) the init file during commit of a transaction that + * changed one or more of the relation cache entries that are kept in the + * local init file. + * + * To be safe against concurrent inspection or rewriting of the init file, + * we must take RelCacheInitLock, then remove the old init file, then send + * the SI messages that include relcache inval for such relations, and then + * release RelCacheInitLock. This serializes the whole affair against + * write_relcache_init_file, so that we can be sure that any other process + * that's concurrently trying to create a new init file won't move an + * already-stale version into place after we unlink. Also, because we unlink + * before sending the SI messages, a backend that's currently starting cannot + * read the now-obsolete init file and then miss the SI messages that will + * force it to update its relcache entries. (This works because the backend + * startup sequence gets into the sinval array before trying to load the init + * file.) + * + * We take the lock and do the unlink in RelationCacheInitFilePreInvalidate, + * then release the lock in RelationCacheInitFilePostInvalidate. Caller must + * send any pending SI messages between those calls. + */ +void +RelationCacheInitFilePreInvalidate(void) +{ + char localinitfname[MAXPGPATH]; + char sharedinitfname[MAXPGPATH]; + + if (DatabasePath) + snprintf(localinitfname, sizeof(localinitfname), "%s/%s", + DatabasePath, RELCACHE_INIT_FILENAME); + snprintf(sharedinitfname, sizeof(sharedinitfname), "global/%s", + RELCACHE_INIT_FILENAME); + + LWLockAcquire(RelCacheInitLock, LW_EXCLUSIVE); + + /* + * The files might not be there if no backend has been started since the + * last removal. But complain about failures other than ENOENT with + * ERROR. Fortunately, it's not too late to abort the transaction if we + * can't get rid of the would-be-obsolete init file. + */ + if (DatabasePath) + unlink_initfile(localinitfname, ERROR); + unlink_initfile(sharedinitfname, ERROR); +} + +void +RelationCacheInitFilePostInvalidate(void) +{ + LWLockRelease(RelCacheInitLock); +} + +/* + * Remove the init files during postmaster startup. + * + * We used to keep the init files across restarts, but that is unsafe in PITR + * scenarios, and even in simple crash-recovery cases there are windows for + * the init files to become out-of-sync with the database. So now we just + * remove them during startup and expect the first backend launch to rebuild + * them. Of course, this has to happen in each database of the cluster. + */ +void +RelationCacheInitFileRemove(void) +{ + const char *tblspcdir = "pg_tblspc"; + DIR *dir; + struct dirent *de; + char path[MAXPGPATH + 10 + sizeof(TABLESPACE_VERSION_DIRECTORY)]; + + snprintf(path, sizeof(path), "global/%s", + RELCACHE_INIT_FILENAME); + unlink_initfile(path, LOG); + + /* Scan everything in the default tablespace */ + RelationCacheInitFileRemoveInDir("base"); + + /* Scan the tablespace link directory to find non-default tablespaces */ + dir = AllocateDir(tblspcdir); + + while ((de = ReadDirExtended(dir, tblspcdir, LOG)) != NULL) + { + if (strspn(de->d_name, "0123456789") == strlen(de->d_name)) + { + /* Scan the tablespace dir for per-database dirs */ + snprintf(path, sizeof(path), "%s/%s/%s", + tblspcdir, de->d_name, TABLESPACE_VERSION_DIRECTORY); + RelationCacheInitFileRemoveInDir(path); + } + } + + FreeDir(dir); +} + +/* Process one per-tablespace directory for RelationCacheInitFileRemove */ +static void +RelationCacheInitFileRemoveInDir(const char *tblspcpath) +{ + DIR *dir; + struct dirent *de; + char initfilename[MAXPGPATH * 2]; + + /* Scan the tablespace directory to find per-database directories */ + dir = AllocateDir(tblspcpath); + + while ((de = ReadDirExtended(dir, tblspcpath, LOG)) != NULL) + { + if (strspn(de->d_name, "0123456789") == strlen(de->d_name)) + { + /* Try to remove the init file in each database */ + snprintf(initfilename, sizeof(initfilename), "%s/%s/%s", + tblspcpath, de->d_name, RELCACHE_INIT_FILENAME); + unlink_initfile(initfilename, LOG); + } + } + + FreeDir(dir); +} + +static void +unlink_initfile(const char *initfilename, int elevel) +{ + if (unlink(initfilename) < 0) + { + /* It might not be there, but log any error other than ENOENT */ + if (errno != ENOENT) + ereport(elevel, + (errcode_for_file_access(), + errmsg("could not remove cache file \"%s\": %m", + initfilename))); + } +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/cache/relfilenodemap.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/cache/relfilenodemap.c new file mode 100644 index 00000000000..3abef32c9b8 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/cache/relfilenodemap.c @@ -0,0 +1,244 @@ +/*------------------------------------------------------------------------- + * + * relfilenodemap.c + * relfilenode to oid mapping cache. + * + * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/utils/cache/relfilenodemap.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/genam.h" +#include "access/htup_details.h" +#include "access/table.h" +#include "catalog/pg_class.h" +#include "catalog/pg_tablespace.h" +#include "miscadmin.h" +#include "utils/builtins.h" +#include "utils/catcache.h" +#include "utils/fmgroids.h" +#include "utils/hsearch.h" +#include "utils/inval.h" +#include "utils/rel.h" +#include "utils/relfilenodemap.h" +#include "utils/relmapper.h" + +/* Hash table for information about each relfilenode <-> oid pair */ +static __thread HTAB *RelfilenodeMapHash = NULL; + +/* built first time through in InitializeRelfilenodeMap */ +static __thread ScanKeyData relfilenode_skey[2]; + +typedef struct +{ + Oid reltablespace; + Oid relfilenode; +} RelfilenodeMapKey; + +typedef struct +{ + RelfilenodeMapKey key; /* lookup key - must be first */ + Oid relid; /* pg_class.oid */ +} RelfilenodeMapEntry; + +/* + * RelfilenodeMapInvalidateCallback + * Flush mapping entries when pg_class is updated in a relevant fashion. + */ +static void +RelfilenodeMapInvalidateCallback(Datum arg, Oid relid) +{ + HASH_SEQ_STATUS status; + RelfilenodeMapEntry *entry; + + /* callback only gets registered after creating the hash */ + Assert(RelfilenodeMapHash != NULL); + + hash_seq_init(&status, RelfilenodeMapHash); + while ((entry = (RelfilenodeMapEntry *) hash_seq_search(&status)) != NULL) + { + /* + * If relid is InvalidOid, signaling a complete reset, we must remove + * all entries, otherwise just remove the specific relation's entry. + * Always remove negative cache entries. + */ + if (relid == InvalidOid || /* complete reset */ + entry->relid == InvalidOid || /* negative cache entry */ + entry->relid == relid) /* individual flushed relation */ + { + if (hash_search(RelfilenodeMapHash, + (void *) &entry->key, + HASH_REMOVE, + NULL) == NULL) + elog(ERROR, "hash table corrupted"); + } + } +} + +/* + * InitializeRelfilenodeMap + * Initialize cache, either on first use or after a reset. + */ +static void +InitializeRelfilenodeMap(void) +{ + HASHCTL ctl; + int i; + + /* Make sure we've initialized CacheMemoryContext. */ + if (CacheMemoryContext == NULL) + CreateCacheMemoryContext(); + + /* build skey */ + MemSet(&relfilenode_skey, 0, sizeof(relfilenode_skey)); + + for (i = 0; i < 2; i++) + { + fmgr_info_cxt(F_OIDEQ, + &relfilenode_skey[i].sk_func, + CacheMemoryContext); + relfilenode_skey[i].sk_strategy = BTEqualStrategyNumber; + relfilenode_skey[i].sk_subtype = InvalidOid; + relfilenode_skey[i].sk_collation = InvalidOid; + } + + relfilenode_skey[0].sk_attno = Anum_pg_class_reltablespace; + relfilenode_skey[1].sk_attno = Anum_pg_class_relfilenode; + + /* + * Only create the RelfilenodeMapHash now, so we don't end up partially + * initialized when fmgr_info_cxt() above ERRORs out with an out of memory + * error. + */ + ctl.keysize = sizeof(RelfilenodeMapKey); + ctl.entrysize = sizeof(RelfilenodeMapEntry); + ctl.hcxt = CacheMemoryContext; + + RelfilenodeMapHash = + hash_create("RelfilenodeMap cache", 64, &ctl, + HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); + + /* Watch for invalidation events. */ + CacheRegisterRelcacheCallback(RelfilenodeMapInvalidateCallback, + (Datum) 0); +} + +/* + * Map a relation's (tablespace, filenode) to a relation's oid and cache the + * result. + * + * Returns InvalidOid if no relation matching the criteria could be found. + */ +Oid +RelidByRelfilenode(Oid reltablespace, Oid relfilenode) +{ + RelfilenodeMapKey key; + RelfilenodeMapEntry *entry; + bool found; + SysScanDesc scandesc; + Relation relation; + HeapTuple ntp; + ScanKeyData skey[2]; + Oid relid; + + if (RelfilenodeMapHash == NULL) + InitializeRelfilenodeMap(); + + /* pg_class will show 0 when the value is actually MyDatabaseTableSpace */ + if (reltablespace == MyDatabaseTableSpace) + reltablespace = 0; + + MemSet(&key, 0, sizeof(key)); + key.reltablespace = reltablespace; + key.relfilenode = relfilenode; + + /* + * Check cache and return entry if one is found. Even if no target + * relation can be found later on we store the negative match and return a + * InvalidOid from cache. That's not really necessary for performance + * since querying invalid values isn't supposed to be a frequent thing, + * but it's basically free. + */ + entry = hash_search(RelfilenodeMapHash, (void *) &key, HASH_FIND, &found); + + if (found) + return entry->relid; + + /* ok, no previous cache entry, do it the hard way */ + + /* initialize empty/negative cache entry before doing the actual lookups */ + relid = InvalidOid; + + if (reltablespace == GLOBALTABLESPACE_OID) + { + /* + * Ok, shared table, check relmapper. + */ + relid = RelationMapFilenodeToOid(relfilenode, true); + } + else + { + /* + * Not a shared table, could either be a plain relation or a + * non-shared, nailed one, like e.g. pg_class. + */ + + /* check for plain relations by looking in pg_class */ + relation = table_open(RelationRelationId, AccessShareLock); + + /* copy scankey to local copy, it will be modified during the scan */ + memcpy(skey, relfilenode_skey, sizeof(skey)); + + /* set scan arguments */ + skey[0].sk_argument = ObjectIdGetDatum(reltablespace); + skey[1].sk_argument = ObjectIdGetDatum(relfilenode); + + scandesc = systable_beginscan(relation, + ClassTblspcRelfilenodeIndexId, + true, + NULL, + 2, + skey); + + found = false; + + while (HeapTupleIsValid(ntp = systable_getnext(scandesc))) + { + Form_pg_class classform = (Form_pg_class) GETSTRUCT(ntp); + + if (found) + elog(ERROR, + "unexpected duplicate for tablespace %u, relfilenode %u", + reltablespace, relfilenode); + found = true; + + Assert(classform->reltablespace == reltablespace); + Assert(classform->relfilenode == relfilenode); + relid = classform->oid; + } + + systable_endscan(scandesc); + table_close(relation, AccessShareLock); + + /* check for tables that are mapped but not shared */ + if (!found) + relid = RelationMapFilenodeToOid(relfilenode, false); + } + + /* + * Only enter entry into cache now, our opening of pg_class could have + * caused cache invalidations to be executed which would have deleted a + * new entry if we had entered it above. + */ + entry = hash_search(RelfilenodeMapHash, (void *) &key, HASH_ENTER, &found); + if (found) + elog(ERROR, "corrupted hashtable"); + entry->relid = relid; + + return relid; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/cache/relfilenumbermap.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/cache/relfilenumbermap.c new file mode 100644 index 00000000000..85f01a18ac9 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/cache/relfilenumbermap.c @@ -0,0 +1,244 @@ +/*------------------------------------------------------------------------- + * + * relfilenumbermap.c + * relfilenumber to oid mapping cache. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/utils/cache/relfilenumbermap.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/genam.h" +#include "access/htup_details.h" +#include "access/table.h" +#include "catalog/pg_class.h" +#include "catalog/pg_tablespace.h" +#include "miscadmin.h" +#include "utils/builtins.h" +#include "utils/catcache.h" +#include "utils/fmgroids.h" +#include "utils/hsearch.h" +#include "utils/inval.h" +#include "utils/rel.h" +#include "utils/relfilenumbermap.h" +#include "utils/relmapper.h" + +/* Hash table for information about each relfilenumber <-> oid pair */ +static __thread HTAB *RelfilenumberMapHash = NULL; + +/* built first time through in InitializeRelfilenumberMap */ +static __thread ScanKeyData relfilenumber_skey[2]; + +typedef struct +{ + Oid reltablespace; + RelFileNumber relfilenumber; +} RelfilenumberMapKey; + +typedef struct +{ + RelfilenumberMapKey key; /* lookup key - must be first */ + Oid relid; /* pg_class.oid */ +} RelfilenumberMapEntry; + +/* + * RelfilenumberMapInvalidateCallback + * Flush mapping entries when pg_class is updated in a relevant fashion. + */ +static void +RelfilenumberMapInvalidateCallback(Datum arg, Oid relid) +{ + HASH_SEQ_STATUS status; + RelfilenumberMapEntry *entry; + + /* callback only gets registered after creating the hash */ + Assert(RelfilenumberMapHash != NULL); + + hash_seq_init(&status, RelfilenumberMapHash); + while ((entry = (RelfilenumberMapEntry *) hash_seq_search(&status)) != NULL) + { + /* + * If relid is InvalidOid, signaling a complete reset, we must remove + * all entries, otherwise just remove the specific relation's entry. + * Always remove negative cache entries. + */ + if (relid == InvalidOid || /* complete reset */ + entry->relid == InvalidOid || /* negative cache entry */ + entry->relid == relid) /* individual flushed relation */ + { + if (hash_search(RelfilenumberMapHash, + &entry->key, + HASH_REMOVE, + NULL) == NULL) + elog(ERROR, "hash table corrupted"); + } + } +} + +/* + * InitializeRelfilenumberMap + * Initialize cache, either on first use or after a reset. + */ +static void +InitializeRelfilenumberMap(void) +{ + HASHCTL ctl; + int i; + + /* Make sure we've initialized CacheMemoryContext. */ + if (CacheMemoryContext == NULL) + CreateCacheMemoryContext(); + + /* build skey */ + MemSet(&relfilenumber_skey, 0, sizeof(relfilenumber_skey)); + + for (i = 0; i < 2; i++) + { + fmgr_info_cxt(F_OIDEQ, + &relfilenumber_skey[i].sk_func, + CacheMemoryContext); + relfilenumber_skey[i].sk_strategy = BTEqualStrategyNumber; + relfilenumber_skey[i].sk_subtype = InvalidOid; + relfilenumber_skey[i].sk_collation = InvalidOid; + } + + relfilenumber_skey[0].sk_attno = Anum_pg_class_reltablespace; + relfilenumber_skey[1].sk_attno = Anum_pg_class_relfilenode; + + /* + * Only create the RelfilenumberMapHash now, so we don't end up partially + * initialized when fmgr_info_cxt() above ERRORs out with an out of memory + * error. + */ + ctl.keysize = sizeof(RelfilenumberMapKey); + ctl.entrysize = sizeof(RelfilenumberMapEntry); + ctl.hcxt = CacheMemoryContext; + + RelfilenumberMapHash = + hash_create("RelfilenumberMap cache", 64, &ctl, + HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); + + /* Watch for invalidation events. */ + CacheRegisterRelcacheCallback(RelfilenumberMapInvalidateCallback, + (Datum) 0); +} + +/* + * Map a relation's (tablespace, relfilenumber) to a relation's oid and cache + * the result. + * + * Returns InvalidOid if no relation matching the criteria could be found. + */ +Oid +RelidByRelfilenumber(Oid reltablespace, RelFileNumber relfilenumber) +{ + RelfilenumberMapKey key; + RelfilenumberMapEntry *entry; + bool found; + SysScanDesc scandesc; + Relation relation; + HeapTuple ntp; + ScanKeyData skey[2]; + Oid relid; + + if (RelfilenumberMapHash == NULL) + InitializeRelfilenumberMap(); + + /* pg_class will show 0 when the value is actually MyDatabaseTableSpace */ + if (reltablespace == MyDatabaseTableSpace) + reltablespace = 0; + + MemSet(&key, 0, sizeof(key)); + key.reltablespace = reltablespace; + key.relfilenumber = relfilenumber; + + /* + * Check cache and return entry if one is found. Even if no target + * relation can be found later on we store the negative match and return a + * InvalidOid from cache. That's not really necessary for performance + * since querying invalid values isn't supposed to be a frequent thing, + * but it's basically free. + */ + entry = hash_search(RelfilenumberMapHash, &key, HASH_FIND, &found); + + if (found) + return entry->relid; + + /* ok, no previous cache entry, do it the hard way */ + + /* initialize empty/negative cache entry before doing the actual lookups */ + relid = InvalidOid; + + if (reltablespace == GLOBALTABLESPACE_OID) + { + /* + * Ok, shared table, check relmapper. + */ + relid = RelationMapFilenumberToOid(relfilenumber, true); + } + else + { + /* + * Not a shared table, could either be a plain relation or a + * non-shared, nailed one, like e.g. pg_class. + */ + + /* check for plain relations by looking in pg_class */ + relation = table_open(RelationRelationId, AccessShareLock); + + /* copy scankey to local copy, it will be modified during the scan */ + memcpy(skey, relfilenumber_skey, sizeof(skey)); + + /* set scan arguments */ + skey[0].sk_argument = ObjectIdGetDatum(reltablespace); + skey[1].sk_argument = ObjectIdGetDatum(relfilenumber); + + scandesc = systable_beginscan(relation, + ClassTblspcRelfilenodeIndexId, + true, + NULL, + 2, + skey); + + found = false; + + while (HeapTupleIsValid(ntp = systable_getnext(scandesc))) + { + Form_pg_class classform = (Form_pg_class) GETSTRUCT(ntp); + + if (found) + elog(ERROR, + "unexpected duplicate for tablespace %u, relfilenumber %u", + reltablespace, relfilenumber); + found = true; + + Assert(classform->reltablespace == reltablespace); + Assert(classform->relfilenode == relfilenumber); + relid = classform->oid; + } + + systable_endscan(scandesc); + table_close(relation, AccessShareLock); + + /* check for tables that are mapped but not shared */ + if (!found) + relid = RelationMapFilenumberToOid(relfilenumber, false); + } + + /* + * Only enter entry into cache now, our opening of pg_class could have + * caused cache invalidations to be executed which would have deleted a + * new entry if we had entered it above. + */ + entry = hash_search(RelfilenumberMapHash, &key, HASH_ENTER, &found); + if (found) + elog(ERROR, "corrupted hashtable"); + entry->relid = relid; + + return relid; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/cache/relmapper.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/cache/relmapper.c new file mode 100644 index 00000000000..82706c1df7a --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/cache/relmapper.c @@ -0,0 +1,1142 @@ +/*------------------------------------------------------------------------- + * + * relmapper.c + * Catalog-to-filenumber mapping + * + * For most tables, the physical file underlying the table is specified by + * pg_class.relfilenode. However, that obviously won't work for pg_class + * itself, nor for the other "nailed" catalogs for which we have to be able + * to set up working Relation entries without access to pg_class. It also + * does not work for shared catalogs, since there is no practical way to + * update other databases' pg_class entries when relocating a shared catalog. + * Therefore, for these special catalogs (henceforth referred to as "mapped + * catalogs") we rely on a separately maintained file that shows the mapping + * from catalog OIDs to filenumbers. Each database has a map file for + * its local mapped catalogs, and there is a separate map file for shared + * catalogs. Mapped catalogs have zero in their pg_class.relfilenode entries. + * + * Relocation of a normal table is committed (ie, the new physical file becomes + * authoritative) when the pg_class row update commits. For mapped catalogs, + * the act of updating the map file is effectively commit of the relocation. + * We postpone the file update till just before commit of the transaction + * doing the rewrite, but there is necessarily a window between. Therefore + * mapped catalogs can only be relocated by operations such as VACUUM FULL + * and CLUSTER, which make no transactionally-significant changes: it must be + * safe for the new file to replace the old, even if the transaction itself + * aborts. An important factor here is that the indexes and toast table of + * a mapped catalog must also be mapped, so that the rewrites/relocations of + * all these files commit in a single map file update rather than being tied + * to transaction commit. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/cache/relmapper.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <fcntl.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "access/xact.h" +#include "access/xlog.h" +#include "access/xloginsert.h" +#include "catalog/catalog.h" +#include "catalog/pg_tablespace.h" +#include "catalog/storage.h" +#include "miscadmin.h" +#include "pgstat.h" +#include "storage/fd.h" +#include "storage/lwlock.h" +#include "utils/inval.h" +#include "utils/relmapper.h" + + +/* + * The map file is critical data: we have no automatic method for recovering + * from loss or corruption of it. We use a CRC so that we can detect + * corruption. Since the file might be more than one standard-size disk + * sector in size, we cannot rely on overwrite-in-place. Instead, we generate + * a new file and rename it into place, atomically replacing the original file. + * + * Entries in the mappings[] array are in no particular order. We could + * speed searching by insisting on OID order, but it really shouldn't be + * worth the trouble given the intended size of the mapping sets. + */ +#define RELMAPPER_FILENAME "pg_filenode.map" +#define RELMAPPER_TEMP_FILENAME "pg_filenode.map.tmp" + +#define RELMAPPER_FILEMAGIC 0x592717 /* version ID value */ + +/* + * There's no need for this constant to have any particular value, and we + * can raise it as necessary if we end up with more mapped relations. For + * now, we just pick a round number that is modestly larger than the expected + * number of mappings. + */ +#define MAX_MAPPINGS 64 + +typedef struct RelMapping +{ + Oid mapoid; /* OID of a catalog */ + RelFileNumber mapfilenumber; /* its rel file number */ +} RelMapping; + +typedef struct RelMapFile +{ + int32 magic; /* always RELMAPPER_FILEMAGIC */ + int32 num_mappings; /* number of valid RelMapping entries */ + RelMapping mappings[MAX_MAPPINGS]; + pg_crc32c crc; /* CRC of all above */ +} RelMapFile; + +/* + * State for serializing local and shared relmappings for parallel workers + * (active states only). See notes on active_* and pending_* updates state. + */ +typedef struct SerializedActiveRelMaps +{ + RelMapFile active_shared_updates; + RelMapFile active_local_updates; +} SerializedActiveRelMaps; + +/* + * The currently known contents of the shared map file and our database's + * local map file are stored here. These can be reloaded from disk + * immediately whenever we receive an update sinval message. + */ +static __thread RelMapFile shared_map; +static __thread RelMapFile local_map; + +/* + * We use the same RelMapFile data structure to track uncommitted local + * changes in the mappings (but note the magic and crc fields are not made + * valid in these variables). Currently, map updates are not allowed within + * subtransactions, so one set of transaction-level changes is sufficient. + * + * The active_xxx variables contain updates that are valid in our transaction + * and should be honored by RelationMapOidToFilenumber. The pending_xxx + * variables contain updates we have been told about that aren't active yet; + * they will become active at the next CommandCounterIncrement. This setup + * lets map updates act similarly to updates of pg_class rows, ie, they + * become visible only at the next CommandCounterIncrement boundary. + * + * Active shared and active local updates are serialized by the parallel + * infrastructure, and deserialized within parallel workers. + */ +static __thread RelMapFile active_shared_updates; +static __thread RelMapFile active_local_updates; +static __thread RelMapFile pending_shared_updates; +static __thread RelMapFile pending_local_updates; + + +/* non-export function prototypes */ +static void apply_map_update(RelMapFile *map, Oid relationId, + RelFileNumber fileNumber, bool add_okay); +static void merge_map_updates(RelMapFile *map, const RelMapFile *updates, + bool add_okay); +static void load_relmap_file(bool shared, bool lock_held); +static void read_relmap_file(RelMapFile *map, char *dbpath, bool lock_held, + int elevel); +static void write_relmap_file(RelMapFile *newmap, bool write_wal, + bool send_sinval, bool preserve_files, + Oid dbid, Oid tsid, const char *dbpath); +static void perform_relmap_update(bool shared, const RelMapFile *updates); + + +/* + * RelationMapOidToFilenumber + * + * The raison d' etre ... given a relation OID, look up its filenumber. + * + * Although shared and local relation OIDs should never overlap, the caller + * always knows which we need --- so pass that information to avoid useless + * searching. + * + * Returns InvalidRelFileNumber if the OID is not known (which should never + * happen, but the caller is in a better position to report a meaningful + * error). + */ +RelFileNumber +RelationMapOidToFilenumber(Oid relationId, bool shared) +{ + const RelMapFile *map; + int32 i; + + /* If there are active updates, believe those over the main maps */ + if (shared) + { + map = &active_shared_updates; + for (i = 0; i < map->num_mappings; i++) + { + if (relationId == map->mappings[i].mapoid) + return map->mappings[i].mapfilenumber; + } + map = &shared_map; + for (i = 0; i < map->num_mappings; i++) + { + if (relationId == map->mappings[i].mapoid) + return map->mappings[i].mapfilenumber; + } + } + else + { + map = &active_local_updates; + for (i = 0; i < map->num_mappings; i++) + { + if (relationId == map->mappings[i].mapoid) + return map->mappings[i].mapfilenumber; + } + map = &local_map; + for (i = 0; i < map->num_mappings; i++) + { + if (relationId == map->mappings[i].mapoid) + return map->mappings[i].mapfilenumber; + } + } + + return InvalidRelFileNumber; +} + +/* + * RelationMapFilenumberToOid + * + * Do the reverse of the normal direction of mapping done in + * RelationMapOidToFilenumber. + * + * This is not supposed to be used during normal running but rather for + * information purposes when looking at the filesystem or xlog. + * + * Returns InvalidOid if the OID is not known; this can easily happen if the + * relfilenumber doesn't pertain to a mapped relation. + */ +Oid +RelationMapFilenumberToOid(RelFileNumber filenumber, bool shared) +{ + const RelMapFile *map; + int32 i; + + /* If there are active updates, believe those over the main maps */ + if (shared) + { + map = &active_shared_updates; + for (i = 0; i < map->num_mappings; i++) + { + if (filenumber == map->mappings[i].mapfilenumber) + return map->mappings[i].mapoid; + } + map = &shared_map; + for (i = 0; i < map->num_mappings; i++) + { + if (filenumber == map->mappings[i].mapfilenumber) + return map->mappings[i].mapoid; + } + } + else + { + map = &active_local_updates; + for (i = 0; i < map->num_mappings; i++) + { + if (filenumber == map->mappings[i].mapfilenumber) + return map->mappings[i].mapoid; + } + map = &local_map; + for (i = 0; i < map->num_mappings; i++) + { + if (filenumber == map->mappings[i].mapfilenumber) + return map->mappings[i].mapoid; + } + } + + return InvalidOid; +} + +/* + * RelationMapOidToFilenumberForDatabase + * + * Like RelationMapOidToFilenumber, but reads the mapping from the indicated + * path instead of using the one for the current database. + */ +RelFileNumber +RelationMapOidToFilenumberForDatabase(char *dbpath, Oid relationId) +{ + RelMapFile map; + int i; + + /* Read the relmap file from the source database. */ + read_relmap_file(&map, dbpath, false, ERROR); + + /* Iterate over the relmap entries to find the input relation OID. */ + for (i = 0; i < map.num_mappings; i++) + { + if (relationId == map.mappings[i].mapoid) + return map.mappings[i].mapfilenumber; + } + + return InvalidRelFileNumber; +} + +/* + * RelationMapCopy + * + * Copy relmapfile from source db path to the destination db path and WAL log + * the operation. This is intended for use in creating a new relmap file + * for a database that doesn't have one yet, not for replacing an existing + * relmap file. + */ +void +RelationMapCopy(Oid dbid, Oid tsid, char *srcdbpath, char *dstdbpath) +{ + RelMapFile map; + + /* + * Read the relmap file from the source database. + */ + read_relmap_file(&map, srcdbpath, false, ERROR); + + /* + * Write the same data into the destination database's relmap file. + * + * No sinval is needed because no one can be connected to the destination + * database yet. + * + * There's no point in trying to preserve files here. The new database + * isn't usable yet anyway, and won't ever be if we can't install a relmap + * file. + */ + LWLockAcquire(RelationMappingLock, LW_EXCLUSIVE); + write_relmap_file(&map, true, false, false, dbid, tsid, dstdbpath); + LWLockRelease(RelationMappingLock); +} + +/* + * RelationMapUpdateMap + * + * Install a new relfilenumber mapping for the specified relation. + * + * If immediate is true (or we're bootstrapping), the mapping is activated + * immediately. Otherwise it is made pending until CommandCounterIncrement. + */ +void +RelationMapUpdateMap(Oid relationId, RelFileNumber fileNumber, bool shared, + bool immediate) +{ + RelMapFile *map; + + if (IsBootstrapProcessingMode()) + { + /* + * In bootstrap mode, the mapping gets installed in permanent map. + */ + if (shared) + map = &shared_map; + else + map = &local_map; + } + else + { + /* + * We don't currently support map changes within subtransactions, or + * when in parallel mode. This could be done with more bookkeeping + * infrastructure, but it doesn't presently seem worth it. + */ + if (GetCurrentTransactionNestLevel() > 1) + elog(ERROR, "cannot change relation mapping within subtransaction"); + + if (IsInParallelMode()) + elog(ERROR, "cannot change relation mapping in parallel mode"); + + if (immediate) + { + /* Make it active, but only locally */ + if (shared) + map = &active_shared_updates; + else + map = &active_local_updates; + } + else + { + /* Make it pending */ + if (shared) + map = &pending_shared_updates; + else + map = &pending_local_updates; + } + } + apply_map_update(map, relationId, fileNumber, true); +} + +/* + * apply_map_update + * + * Insert a new mapping into the given map variable, replacing any existing + * mapping for the same relation. + * + * In some cases the caller knows there must be an existing mapping; pass + * add_okay = false to draw an error if not. + */ +static void +apply_map_update(RelMapFile *map, Oid relationId, RelFileNumber fileNumber, + bool add_okay) +{ + int32 i; + + /* Replace any existing mapping */ + for (i = 0; i < map->num_mappings; i++) + { + if (relationId == map->mappings[i].mapoid) + { + map->mappings[i].mapfilenumber = fileNumber; + return; + } + } + + /* Nope, need to add a new mapping */ + if (!add_okay) + elog(ERROR, "attempt to apply a mapping to unmapped relation %u", + relationId); + if (map->num_mappings >= MAX_MAPPINGS) + elog(ERROR, "ran out of space in relation map"); + map->mappings[map->num_mappings].mapoid = relationId; + map->mappings[map->num_mappings].mapfilenumber = fileNumber; + map->num_mappings++; +} + +/* + * merge_map_updates + * + * Merge all the updates in the given pending-update map into the target map. + * This is just a bulk form of apply_map_update. + */ +static void +merge_map_updates(RelMapFile *map, const RelMapFile *updates, bool add_okay) +{ + int32 i; + + for (i = 0; i < updates->num_mappings; i++) + { + apply_map_update(map, + updates->mappings[i].mapoid, + updates->mappings[i].mapfilenumber, + add_okay); + } +} + +/* + * RelationMapRemoveMapping + * + * Remove a relation's entry in the map. This is only allowed for "active" + * (but not committed) local mappings. We need it so we can back out the + * entry for the transient target file when doing VACUUM FULL/CLUSTER on + * a mapped relation. + */ +void +RelationMapRemoveMapping(Oid relationId) +{ + RelMapFile *map = &active_local_updates; + int32 i; + + for (i = 0; i < map->num_mappings; i++) + { + if (relationId == map->mappings[i].mapoid) + { + /* Found it, collapse it out */ + map->mappings[i] = map->mappings[map->num_mappings - 1]; + map->num_mappings--; + return; + } + } + elog(ERROR, "could not find temporary mapping for relation %u", + relationId); +} + +/* + * RelationMapInvalidate + * + * This routine is invoked for SI cache flush messages. We must re-read + * the indicated map file. However, we might receive a SI message in a + * process that hasn't yet, and might never, load the mapping files; + * for example the autovacuum launcher, which *must not* try to read + * a local map since it is attached to no particular database. + * So, re-read only if the map is valid now. + */ +void +RelationMapInvalidate(bool shared) +{ + if (shared) + { + if (shared_map.magic == RELMAPPER_FILEMAGIC) + load_relmap_file(true, false); + } + else + { + if (local_map.magic == RELMAPPER_FILEMAGIC) + load_relmap_file(false, false); + } +} + +/* + * RelationMapInvalidateAll + * + * Reload all map files. This is used to recover from SI message buffer + * overflow: we can't be sure if we missed an inval message. + * Again, reload only currently-valid maps. + */ +void +RelationMapInvalidateAll(void) +{ + if (shared_map.magic == RELMAPPER_FILEMAGIC) + load_relmap_file(true, false); + if (local_map.magic == RELMAPPER_FILEMAGIC) + load_relmap_file(false, false); +} + +/* + * AtCCI_RelationMap + * + * Activate any "pending" relation map updates at CommandCounterIncrement time. + */ +void +AtCCI_RelationMap(void) +{ + if (pending_shared_updates.num_mappings != 0) + { + merge_map_updates(&active_shared_updates, + &pending_shared_updates, + true); + pending_shared_updates.num_mappings = 0; + } + if (pending_local_updates.num_mappings != 0) + { + merge_map_updates(&active_local_updates, + &pending_local_updates, + true); + pending_local_updates.num_mappings = 0; + } +} + +/* + * AtEOXact_RelationMap + * + * Handle relation mapping at main-transaction commit or abort. + * + * During commit, this must be called as late as possible before the actual + * transaction commit, so as to minimize the window where the transaction + * could still roll back after committing map changes. Although nothing + * critically bad happens in such a case, we still would prefer that it + * not happen, since we'd possibly be losing useful updates to the relations' + * pg_class row(s). + * + * During abort, we just have to throw away any pending map changes. + * Normal post-abort cleanup will take care of fixing relcache entries. + * Parallel worker commit/abort is handled by resetting active mappings + * that may have been received from the leader process. (There should be + * no pending updates in parallel workers.) + */ +void +AtEOXact_RelationMap(bool isCommit, bool isParallelWorker) +{ + if (isCommit && !isParallelWorker) + { + /* + * We should not get here with any "pending" updates. (We could + * logically choose to treat such as committed, but in the current + * code this should never happen.) + */ + Assert(pending_shared_updates.num_mappings == 0); + Assert(pending_local_updates.num_mappings == 0); + + /* + * Write any active updates to the actual map files, then reset them. + */ + if (active_shared_updates.num_mappings != 0) + { + perform_relmap_update(true, &active_shared_updates); + active_shared_updates.num_mappings = 0; + } + if (active_local_updates.num_mappings != 0) + { + perform_relmap_update(false, &active_local_updates); + active_local_updates.num_mappings = 0; + } + } + else + { + /* Abort or parallel worker --- drop all local and pending updates */ + Assert(!isParallelWorker || pending_shared_updates.num_mappings == 0); + Assert(!isParallelWorker || pending_local_updates.num_mappings == 0); + + active_shared_updates.num_mappings = 0; + active_local_updates.num_mappings = 0; + pending_shared_updates.num_mappings = 0; + pending_local_updates.num_mappings = 0; + } +} + +/* + * AtPrepare_RelationMap + * + * Handle relation mapping at PREPARE. + * + * Currently, we don't support preparing any transaction that changes the map. + */ +void +AtPrepare_RelationMap(void) +{ + if (active_shared_updates.num_mappings != 0 || + active_local_updates.num_mappings != 0 || + pending_shared_updates.num_mappings != 0 || + pending_local_updates.num_mappings != 0) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot PREPARE a transaction that modified relation mapping"))); +} + +/* + * CheckPointRelationMap + * + * This is called during a checkpoint. It must ensure that any relation map + * updates that were WAL-logged before the start of the checkpoint are + * securely flushed to disk and will not need to be replayed later. This + * seems unlikely to be a performance-critical issue, so we use a simple + * method: we just take and release the RelationMappingLock. This ensures + * that any already-logged map update is complete, because write_relmap_file + * will fsync the map file before the lock is released. + */ +void +CheckPointRelationMap(void) +{ + LWLockAcquire(RelationMappingLock, LW_SHARED); + LWLockRelease(RelationMappingLock); +} + +/* + * RelationMapFinishBootstrap + * + * Write out the initial relation mapping files at the completion of + * bootstrap. All the mapped files should have been made known to us + * via RelationMapUpdateMap calls. + */ +void +RelationMapFinishBootstrap(void) +{ + Assert(IsBootstrapProcessingMode()); + + /* Shouldn't be anything "pending" ... */ + Assert(active_shared_updates.num_mappings == 0); + Assert(active_local_updates.num_mappings == 0); + Assert(pending_shared_updates.num_mappings == 0); + Assert(pending_local_updates.num_mappings == 0); + + /* Write the files; no WAL or sinval needed */ + LWLockAcquire(RelationMappingLock, LW_EXCLUSIVE); + write_relmap_file(&shared_map, false, false, false, + InvalidOid, GLOBALTABLESPACE_OID, "global"); + write_relmap_file(&local_map, false, false, false, + MyDatabaseId, MyDatabaseTableSpace, DatabasePath); + LWLockRelease(RelationMappingLock); +} + +/* + * RelationMapInitialize + * + * This initializes the mapper module at process startup. We can't access the + * database yet, so just make sure the maps are empty. + */ +void +RelationMapInitialize(void) +{ + /* The static variables should initialize to zeroes, but let's be sure */ + shared_map.magic = 0; /* mark it not loaded */ + local_map.magic = 0; + shared_map.num_mappings = 0; + local_map.num_mappings = 0; + active_shared_updates.num_mappings = 0; + active_local_updates.num_mappings = 0; + pending_shared_updates.num_mappings = 0; + pending_local_updates.num_mappings = 0; +} + +/* + * RelationMapInitializePhase2 + * + * This is called to prepare for access to pg_database during startup. + * We should be able to read the shared map file now. + */ +void +RelationMapInitializePhase2(void) +{ + /* + * In bootstrap mode, the map file isn't there yet, so do nothing. + */ + if (IsBootstrapProcessingMode()) + return; + + /* + * Load the shared map file, die on error. + */ + load_relmap_file(true, false); +} + +/* + * RelationMapInitializePhase3 + * + * This is called as soon as we have determined MyDatabaseId and set up + * DatabasePath. At this point we should be able to read the local map file. + */ +void +RelationMapInitializePhase3(void) +{ + /* + * In bootstrap mode, the map file isn't there yet, so do nothing. + */ + if (IsBootstrapProcessingMode()) + return; + + /* + * Load the local map file, die on error. + */ + load_relmap_file(false, false); +} + +/* + * EstimateRelationMapSpace + * + * Estimate space needed to pass active shared and local relmaps to parallel + * workers. + */ +Size +EstimateRelationMapSpace(void) +{ + return sizeof(SerializedActiveRelMaps); +} + +/* + * SerializeRelationMap + * + * Serialize active shared and local relmap state for parallel workers. + */ +void +SerializeRelationMap(Size maxSize, char *startAddress) +{ + SerializedActiveRelMaps *relmaps; + + Assert(maxSize >= EstimateRelationMapSpace()); + + relmaps = (SerializedActiveRelMaps *) startAddress; + relmaps->active_shared_updates = active_shared_updates; + relmaps->active_local_updates = active_local_updates; +} + +/* + * RestoreRelationMap + * + * Restore active shared and local relmap state within a parallel worker. + */ +void +RestoreRelationMap(char *startAddress) +{ + SerializedActiveRelMaps *relmaps; + + if (active_shared_updates.num_mappings != 0 || + active_local_updates.num_mappings != 0 || + pending_shared_updates.num_mappings != 0 || + pending_local_updates.num_mappings != 0) + elog(ERROR, "parallel worker has existing mappings"); + + relmaps = (SerializedActiveRelMaps *) startAddress; + active_shared_updates = relmaps->active_shared_updates; + active_local_updates = relmaps->active_local_updates; +} + +/* + * load_relmap_file -- load the shared or local map file + * + * Because these files are essential for access to core system catalogs, + * failure to load either of them is a fatal error. + * + * Note that the local case requires DatabasePath to be set up. + */ +static void +load_relmap_file(bool shared, bool lock_held) +{ + if (shared) + read_relmap_file(&shared_map, "global", lock_held, FATAL); + else + read_relmap_file(&local_map, DatabasePath, lock_held, FATAL); +} + +/* + * read_relmap_file -- load data from any relation mapper file + * + * dbpath must be the relevant database path, or "global" for shared relations. + * + * RelationMappingLock will be acquired released unless lock_held = true. + * + * Errors will be reported at the indicated elevel, which should be at least + * ERROR. + */ +static void +read_relmap_file(RelMapFile *map, char *dbpath, bool lock_held, int elevel) +{ + char mapfilename[MAXPGPATH]; + pg_crc32c crc; + int fd; + int r; + + Assert(elevel >= ERROR); + + /* + * Grab the lock to prevent the file from being updated while we read it, + * unless the caller is already holding the lock. If the file is updated + * shortly after we look, the sinval signaling mechanism will make us + * re-read it before we are able to access any relation that's affected by + * the change. + */ + if (!lock_held) + LWLockAcquire(RelationMappingLock, LW_SHARED); + + /* + * Open the target file. + * + * Because Windows isn't happy about the idea of renaming over a file that + * someone has open, we only open this file after acquiring the lock, and + * for the same reason, we close it before releasing the lock. That way, + * by the time write_relmap_file() acquires an exclusive lock, no one else + * will have it open. + */ + snprintf(mapfilename, sizeof(mapfilename), "%s/%s", dbpath, + RELMAPPER_FILENAME); + fd = OpenTransientFile(mapfilename, O_RDONLY | PG_BINARY); + if (fd < 0) + ereport(elevel, + (errcode_for_file_access(), + errmsg("could not open file \"%s\": %m", + mapfilename))); + + /* Now read the data. */ + pgstat_report_wait_start(WAIT_EVENT_RELATION_MAP_READ); + r = read(fd, map, sizeof(RelMapFile)); + if (r != sizeof(RelMapFile)) + { + if (r < 0) + ereport(elevel, + (errcode_for_file_access(), + errmsg("could not read file \"%s\": %m", mapfilename))); + else + ereport(elevel, + (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("could not read file \"%s\": read %d of %zu", + mapfilename, r, sizeof(RelMapFile)))); + } + pgstat_report_wait_end(); + + if (CloseTransientFile(fd) != 0) + ereport(elevel, + (errcode_for_file_access(), + errmsg("could not close file \"%s\": %m", + mapfilename))); + + if (!lock_held) + LWLockRelease(RelationMappingLock); + + /* check for correct magic number, etc */ + if (map->magic != RELMAPPER_FILEMAGIC || + map->num_mappings < 0 || + map->num_mappings > MAX_MAPPINGS) + ereport(elevel, + (errmsg("relation mapping file \"%s\" contains invalid data", + mapfilename))); + + /* verify the CRC */ + INIT_CRC32C(crc); + COMP_CRC32C(crc, (char *) map, offsetof(RelMapFile, crc)); + FIN_CRC32C(crc); + + if (!EQ_CRC32C(crc, map->crc)) + ereport(elevel, + (errmsg("relation mapping file \"%s\" contains incorrect checksum", + mapfilename))); +} + +/* + * Write out a new shared or local map file with the given contents. + * + * The magic number and CRC are automatically updated in *newmap. On + * success, we copy the data to the appropriate permanent static variable. + * + * If write_wal is true then an appropriate WAL message is emitted. + * (It will be false for bootstrap and WAL replay cases.) + * + * If send_sinval is true then a SI invalidation message is sent. + * (This should be true except in bootstrap case.) + * + * If preserve_files is true then the storage manager is warned not to + * delete the files listed in the map. + * + * Because this may be called during WAL replay when MyDatabaseId, + * DatabasePath, etc aren't valid, we require the caller to pass in suitable + * values. Pass dbpath as "global" for the shared map. + * + * The caller is also responsible for being sure no concurrent map update + * could be happening. + */ +static void +write_relmap_file(RelMapFile *newmap, bool write_wal, bool send_sinval, + bool preserve_files, Oid dbid, Oid tsid, const char *dbpath) +{ + int fd; + char mapfilename[MAXPGPATH]; + char maptempfilename[MAXPGPATH]; + + /* + * Even without concurrent use of this map, CheckPointRelationMap() relies + * on this locking. Without it, a restore of a base backup taken after + * this function's XLogInsert() and before its durable_rename() would not + * have the changes. wal_level=minimal doesn't need the lock, but this + * isn't performance-critical enough for such a micro-optimization. + */ + Assert(LWLockHeldByMeInMode(RelationMappingLock, LW_EXCLUSIVE)); + + /* + * Fill in the overhead fields and update CRC. + */ + newmap->magic = RELMAPPER_FILEMAGIC; + if (newmap->num_mappings < 0 || newmap->num_mappings > MAX_MAPPINGS) + elog(ERROR, "attempt to write bogus relation mapping"); + + INIT_CRC32C(newmap->crc); + COMP_CRC32C(newmap->crc, (char *) newmap, offsetof(RelMapFile, crc)); + FIN_CRC32C(newmap->crc); + + /* + * Construct filenames -- a temporary file that we'll create to write the + * data initially, and then the permanent name to which we will rename it. + */ + snprintf(mapfilename, sizeof(mapfilename), "%s/%s", + dbpath, RELMAPPER_FILENAME); + snprintf(maptempfilename, sizeof(maptempfilename), "%s/%s", + dbpath, RELMAPPER_TEMP_FILENAME); + + /* + * Open a temporary file. If a file already exists with this name, it must + * be left over from a previous crash, so we can overwrite it. Concurrent + * calls to this function are not allowed. + */ + fd = OpenTransientFile(maptempfilename, + O_WRONLY | O_CREAT | O_TRUNC | PG_BINARY); + if (fd < 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not open file \"%s\": %m", + maptempfilename))); + + /* Write new data to the file. */ + pgstat_report_wait_start(WAIT_EVENT_RELATION_MAP_WRITE); + if (write(fd, newmap, sizeof(RelMapFile)) != sizeof(RelMapFile)) + { + /* if write didn't set errno, assume problem is no disk space */ + if (errno == 0) + errno = ENOSPC; + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not write file \"%s\": %m", + maptempfilename))); + } + pgstat_report_wait_end(); + + /* And close the file. */ + if (CloseTransientFile(fd) != 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not close file \"%s\": %m", + maptempfilename))); + + if (write_wal) + { + xl_relmap_update xlrec; + XLogRecPtr lsn; + + /* now errors are fatal ... */ + START_CRIT_SECTION(); + + xlrec.dbid = dbid; + xlrec.tsid = tsid; + xlrec.nbytes = sizeof(RelMapFile); + + XLogBeginInsert(); + XLogRegisterData((char *) (&xlrec), MinSizeOfRelmapUpdate); + XLogRegisterData((char *) newmap, sizeof(RelMapFile)); + + lsn = XLogInsert(RM_RELMAP_ID, XLOG_RELMAP_UPDATE); + + /* As always, WAL must hit the disk before the data update does */ + XLogFlush(lsn); + } + + /* + * durable_rename() does all the hard work of making sure that we rename + * the temporary file into place in a crash-safe manner. + * + * NB: Although we instruct durable_rename() to use ERROR, we will often + * be in a critical section at this point; if so, ERROR will become PANIC. + */ + pgstat_report_wait_start(WAIT_EVENT_RELATION_MAP_REPLACE); + durable_rename(maptempfilename, mapfilename, ERROR); + pgstat_report_wait_end(); + + /* + * Now that the file is safely on disk, send sinval message to let other + * backends know to re-read it. We must do this inside the critical + * section: if for some reason we fail to send the message, we have to + * force a database-wide PANIC. Otherwise other backends might continue + * execution with stale mapping information, which would be catastrophic + * as soon as others began to use the now-committed data. + */ + if (send_sinval) + CacheInvalidateRelmap(dbid); + + /* + * Make sure that the files listed in the map are not deleted if the outer + * transaction aborts. This had better be within the critical section + * too: it's not likely to fail, but if it did, we'd arrive at transaction + * abort with the files still vulnerable. PANICing will leave things in a + * good state on-disk. + * + * Note: we're cheating a little bit here by assuming that mapped files + * are either in pg_global or the database's default tablespace. + */ + if (preserve_files) + { + int32 i; + + for (i = 0; i < newmap->num_mappings; i++) + { + RelFileLocator rlocator; + + rlocator.spcOid = tsid; + rlocator.dbOid = dbid; + rlocator.relNumber = newmap->mappings[i].mapfilenumber; + RelationPreserveStorage(rlocator, false); + } + } + + /* Critical section done */ + if (write_wal) + END_CRIT_SECTION(); +} + +/* + * Merge the specified updates into the appropriate "real" map, + * and write out the changes. This function must be used for committing + * updates during normal multiuser operation. + */ +static void +perform_relmap_update(bool shared, const RelMapFile *updates) +{ + RelMapFile newmap; + + /* + * Anyone updating a relation's mapping info should take exclusive lock on + * that rel and hold it until commit. This ensures that there will not be + * concurrent updates on the same mapping value; but there could easily be + * concurrent updates on different values in the same file. We cover that + * by acquiring the RelationMappingLock, re-reading the target file to + * ensure it's up to date, applying the updates, and writing the data + * before releasing RelationMappingLock. + * + * There is only one RelationMappingLock. In principle we could try to + * have one per mapping file, but it seems unlikely to be worth the + * trouble. + */ + LWLockAcquire(RelationMappingLock, LW_EXCLUSIVE); + + /* Be certain we see any other updates just made */ + load_relmap_file(shared, true); + + /* Prepare updated data in a local variable */ + if (shared) + memcpy(&newmap, &shared_map, sizeof(RelMapFile)); + else + memcpy(&newmap, &local_map, sizeof(RelMapFile)); + + /* + * Apply the updates to newmap. No new mappings should appear, unless + * somebody is adding indexes to system catalogs. + */ + merge_map_updates(&newmap, updates, allowSystemTableMods); + + /* Write out the updated map and do other necessary tasks */ + write_relmap_file(&newmap, true, true, true, + (shared ? InvalidOid : MyDatabaseId), + (shared ? GLOBALTABLESPACE_OID : MyDatabaseTableSpace), + (shared ? "global" : DatabasePath)); + + /* + * We successfully wrote the updated file, so it's now safe to rely on the + * new values in this process, too. + */ + if (shared) + memcpy(&shared_map, &newmap, sizeof(RelMapFile)); + else + memcpy(&local_map, &newmap, sizeof(RelMapFile)); + + /* Now we can release the lock */ + LWLockRelease(RelationMappingLock); +} + +/* + * RELMAP resource manager's routines + */ +void +relmap_redo(XLogReaderState *record) +{ + uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK; + + /* Backup blocks are not used in relmap records */ + Assert(!XLogRecHasAnyBlockRefs(record)); + + if (info == XLOG_RELMAP_UPDATE) + { + xl_relmap_update *xlrec = (xl_relmap_update *) XLogRecGetData(record); + RelMapFile newmap; + char *dbpath; + + if (xlrec->nbytes != sizeof(RelMapFile)) + elog(PANIC, "relmap_redo: wrong size %u in relmap update record", + xlrec->nbytes); + memcpy(&newmap, xlrec->data, sizeof(newmap)); + + /* We need to construct the pathname for this database */ + dbpath = GetDatabasePath(xlrec->dbid, xlrec->tsid); + + /* + * Write out the new map and send sinval, but of course don't write a + * new WAL entry. There's no surrounding transaction to tell to + * preserve files, either. + * + * There shouldn't be anyone else updating relmaps during WAL replay, + * but grab the lock to interlock against load_relmap_file(). + * + * Note that we use the same WAL record for updating the relmap of an + * existing database as we do for creating a new database. In the + * latter case, taking the relmap log and sending sinval messages is + * unnecessary, but harmless. If we wanted to avoid it, we could add a + * flag to the WAL record to indicate which operation is being + * performed. + */ + LWLockAcquire(RelationMappingLock, LW_EXCLUSIVE); + write_relmap_file(&newmap, false, true, false, + xlrec->dbid, xlrec->tsid, dbpath); + LWLockRelease(RelationMappingLock); + + pfree(dbpath); + } + else + elog(PANIC, "relmap_redo: unknown op code %u", info); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/cache/spccache.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/cache/spccache.c new file mode 100644 index 00000000000..8fecff1c02e --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/cache/spccache.c @@ -0,0 +1,237 @@ +/*------------------------------------------------------------------------- + * + * spccache.c + * Tablespace cache management. + * + * We cache the parsed version of spcoptions for each tablespace to avoid + * needing to reparse on every lookup. Right now, there doesn't appear to + * be a measurable performance gain from doing this, but that might change + * in the future as we add more options. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/utils/cache/spccache.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/reloptions.h" +#include "catalog/pg_tablespace.h" +#include "commands/tablespace.h" +#include "miscadmin.h" +#include "optimizer/optimizer.h" +#include "storage/bufmgr.h" +#include "utils/catcache.h" +#include "utils/hsearch.h" +#include "utils/inval.h" +#include "utils/spccache.h" +#include "utils/syscache.h" +#include "varatt.h" + + +/* Hash table for information about each tablespace */ +static __thread HTAB *TableSpaceCacheHash = NULL; + +typedef struct +{ + Oid oid; /* lookup key - must be first */ + TableSpaceOpts *opts; /* options, or NULL if none */ +} TableSpaceCacheEntry; + + +/* + * InvalidateTableSpaceCacheCallback + * Flush all cache entries when pg_tablespace is updated. + * + * When pg_tablespace is updated, we must flush the cache entry at least + * for that tablespace. Currently, we just flush them all. This is quick + * and easy and doesn't cost much, since there shouldn't be terribly many + * tablespaces, nor do we expect them to be frequently modified. + */ +static void +InvalidateTableSpaceCacheCallback(Datum arg, int cacheid, uint32 hashvalue) +{ + HASH_SEQ_STATUS status; + TableSpaceCacheEntry *spc; + + hash_seq_init(&status, TableSpaceCacheHash); + while ((spc = (TableSpaceCacheEntry *) hash_seq_search(&status)) != NULL) + { + if (spc->opts) + pfree(spc->opts); + if (hash_search(TableSpaceCacheHash, + &spc->oid, + HASH_REMOVE, + NULL) == NULL) + elog(ERROR, "hash table corrupted"); + } +} + +/* + * InitializeTableSpaceCache + * Initialize the tablespace cache. + */ +static void +InitializeTableSpaceCache(void) +{ + HASHCTL ctl; + + /* Initialize the hash table. */ + ctl.keysize = sizeof(Oid); + ctl.entrysize = sizeof(TableSpaceCacheEntry); + TableSpaceCacheHash = + hash_create("TableSpace cache", 16, &ctl, + HASH_ELEM | HASH_BLOBS); + + /* Make sure we've initialized CacheMemoryContext. */ + if (!CacheMemoryContext) + CreateCacheMemoryContext(); + + /* Watch for invalidation events. */ + CacheRegisterSyscacheCallback(TABLESPACEOID, + InvalidateTableSpaceCacheCallback, + (Datum) 0); +} + +/* + * get_tablespace + * Fetch TableSpaceCacheEntry structure for a specified table OID. + * + * Pointers returned by this function should not be stored, since a cache + * flush will invalidate them. + */ +static TableSpaceCacheEntry * +get_tablespace(Oid spcid) +{ + TableSpaceCacheEntry *spc; + HeapTuple tp; + TableSpaceOpts *opts; + + /* + * Since spcid is always from a pg_class tuple, InvalidOid implies the + * default. + */ + if (spcid == InvalidOid) + spcid = MyDatabaseTableSpace; + + /* Find existing cache entry, if any. */ + if (!TableSpaceCacheHash) + InitializeTableSpaceCache(); + spc = (TableSpaceCacheEntry *) hash_search(TableSpaceCacheHash, + &spcid, + HASH_FIND, + NULL); + if (spc) + return spc; + + /* + * Not found in TableSpace cache. Check catcache. If we don't find a + * valid HeapTuple, it must mean someone has managed to request tablespace + * details for a non-existent tablespace. We'll just treat that case as + * if no options were specified. + */ + tp = SearchSysCache1(TABLESPACEOID, ObjectIdGetDatum(spcid)); + if (!HeapTupleIsValid(tp)) + opts = NULL; + else + { + Datum datum; + bool isNull; + + datum = SysCacheGetAttr(TABLESPACEOID, + tp, + Anum_pg_tablespace_spcoptions, + &isNull); + if (isNull) + opts = NULL; + else + { + bytea *bytea_opts = tablespace_reloptions(datum, false); + + opts = MemoryContextAlloc(CacheMemoryContext, VARSIZE(bytea_opts)); + memcpy(opts, bytea_opts, VARSIZE(bytea_opts)); + } + ReleaseSysCache(tp); + } + + /* + * Now create the cache entry. It's important to do this only after + * reading the pg_tablespace entry, since doing so could cause a cache + * flush. + */ + spc = (TableSpaceCacheEntry *) hash_search(TableSpaceCacheHash, + &spcid, + HASH_ENTER, + NULL); + spc->opts = opts; + return spc; +} + +/* + * get_tablespace_page_costs + * Return random and/or sequential page costs for a given tablespace. + * + * This value is not locked by the transaction, so this value may + * be changed while a SELECT that has used these values for planning + * is still executing. + */ +void +get_tablespace_page_costs(Oid spcid, + double *spc_random_page_cost, + double *spc_seq_page_cost) +{ + TableSpaceCacheEntry *spc = get_tablespace(spcid); + + Assert(spc != NULL); + + if (spc_random_page_cost) + { + if (!spc->opts || spc->opts->random_page_cost < 0) + *spc_random_page_cost = random_page_cost; + else + *spc_random_page_cost = spc->opts->random_page_cost; + } + + if (spc_seq_page_cost) + { + if (!spc->opts || spc->opts->seq_page_cost < 0) + *spc_seq_page_cost = seq_page_cost; + else + *spc_seq_page_cost = spc->opts->seq_page_cost; + } +} + +/* + * get_tablespace_io_concurrency + * + * This value is not locked by the transaction, so this value may + * be changed while a SELECT that has used these values for planning + * is still executing. + */ +int +get_tablespace_io_concurrency(Oid spcid) +{ + TableSpaceCacheEntry *spc = get_tablespace(spcid); + + if (!spc->opts || spc->opts->effective_io_concurrency < 0) + return effective_io_concurrency; + else + return spc->opts->effective_io_concurrency; +} + +/* + * get_tablespace_maintenance_io_concurrency + */ +int +get_tablespace_maintenance_io_concurrency(Oid spcid) +{ + TableSpaceCacheEntry *spc = get_tablespace(spcid); + + if (!spc->opts || spc->opts->maintenance_io_concurrency < 0) + return maintenance_io_concurrency; + else + return spc->opts->maintenance_io_concurrency; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/cache/syscache.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/cache/syscache.c new file mode 100644 index 00000000000..a53704de2aa --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/cache/syscache.c @@ -0,0 +1,1282 @@ +/*------------------------------------------------------------------------- + * + * syscache.c + * System cache management routines + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/cache/syscache.c + * + * NOTES + * These routines allow the parser/planner/executor to perform + * rapid lookups on the contents of the system catalogs. + * + * see utils/syscache.h for a list of the cache IDs + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/htup_details.h" +#include "access/sysattr.h" +#include "catalog/pg_aggregate.h" +#include "catalog/pg_am.h" +#include "catalog/pg_amop.h" +#include "catalog/pg_amproc.h" +#include "catalog/pg_auth_members.h" +#include "catalog/pg_authid.h" +#include "catalog/pg_cast.h" +#include "catalog/pg_collation.h" +#include "catalog/pg_constraint.h" +#include "catalog/pg_conversion.h" +#include "catalog/pg_database.h" +#include "catalog/pg_db_role_setting.h" +#include "catalog/pg_default_acl.h" +#include "catalog/pg_depend.h" +#include "catalog/pg_description.h" +#include "catalog/pg_enum.h" +#include "catalog/pg_event_trigger.h" +#include "catalog/pg_foreign_data_wrapper.h" +#include "catalog/pg_foreign_server.h" +#include "catalog/pg_foreign_table.h" +#include "catalog/pg_language.h" +#include "catalog/pg_namespace.h" +#include "catalog/pg_opclass.h" +#include "catalog/pg_operator.h" +#include "catalog/pg_opfamily.h" +#include "catalog/pg_parameter_acl.h" +#include "catalog/pg_partitioned_table.h" +#include "catalog/pg_proc.h" +#include "catalog/pg_publication.h" +#include "catalog/pg_publication_namespace.h" +#include "catalog/pg_publication_rel.h" +#include "catalog/pg_range.h" +#include "catalog/pg_replication_origin.h" +#include "catalog/pg_rewrite.h" +#include "catalog/pg_seclabel.h" +#include "catalog/pg_sequence.h" +#include "catalog/pg_shdepend.h" +#include "catalog/pg_shdescription.h" +#include "catalog/pg_shseclabel.h" +#include "catalog/pg_statistic.h" +#include "catalog/pg_statistic_ext.h" +#include "catalog/pg_statistic_ext_data.h" +#include "catalog/pg_subscription.h" +#include "catalog/pg_subscription_rel.h" +#include "catalog/pg_tablespace.h" +#include "catalog/pg_transform.h" +#include "catalog/pg_ts_config.h" +#include "catalog/pg_ts_config_map.h" +#include "catalog/pg_ts_dict.h" +#include "catalog/pg_ts_parser.h" +#include "catalog/pg_ts_template.h" +#include "catalog/pg_type.h" +#include "catalog/pg_user_mapping.h" +#include "lib/qunique.h" +#include "utils/catcache.h" +#include "utils/lsyscache.h" +#include "utils/rel.h" +#include "utils/syscache.h" + +/*--------------------------------------------------------------------------- + + Adding system caches: + + Add your new cache to the list in include/utils/syscache.h. + Keep the list sorted alphabetically. + + Add your entry to the cacheinfo[] array below. All cache lists are + alphabetical, so add it in the proper place. Specify the relation OID, + index OID, number of keys, key attribute numbers, and initial number of + hash buckets. + + The number of hash buckets must be a power of 2. It's reasonable to + set this to the number of entries that might be in the particular cache + in a medium-size database. + + There must be a unique index underlying each syscache (ie, an index + whose key is the same as that of the cache). If there is not one + already, add the definition for it to include/catalog/pg_*.h using + DECLARE_UNIQUE_INDEX. + (Adding an index requires a catversion.h update, while simply + adding/deleting caches only requires a recompile.) + + Finally, any place your relation gets heap_insert() or + heap_update() calls, use CatalogTupleInsert() or CatalogTupleUpdate() + instead, which also update indexes. The heap_* calls do not do that. + +*--------------------------------------------------------------------------- +*/ + +/* + * struct cachedesc: information defining a single syscache + */ +struct cachedesc +{ + Oid reloid; /* OID of the relation being cached */ + Oid indoid; /* OID of index relation for this cache */ + int nkeys; /* # of keys needed for cache lookup */ + int key[4]; /* attribute numbers of key attrs */ + int nbuckets; /* number of hash buckets for this cache */ +}; + +/* Macro to provide nkeys and key array with convenient syntax. */ +#define KEY(...) VA_ARGS_NARGS(__VA_ARGS__), { __VA_ARGS__ } + +static const struct cachedesc cacheinfo[] = { + [AGGFNOID] = { + AggregateRelationId, + AggregateFnoidIndexId, + KEY(Anum_pg_aggregate_aggfnoid), + 16 + }, + [AMNAME] = { + AccessMethodRelationId, + AmNameIndexId, + KEY(Anum_pg_am_amname), + 4 + }, + [AMOID] = { + AccessMethodRelationId, + AmOidIndexId, + KEY(Anum_pg_am_oid), + 4 + }, + [AMOPOPID] = { + AccessMethodOperatorRelationId, + AccessMethodOperatorIndexId, + KEY(Anum_pg_amop_amopopr, + Anum_pg_amop_amoppurpose, + Anum_pg_amop_amopfamily), + 64 + }, + [AMOPSTRATEGY] = { + AccessMethodOperatorRelationId, + AccessMethodStrategyIndexId, + KEY(Anum_pg_amop_amopfamily, + Anum_pg_amop_amoplefttype, + Anum_pg_amop_amoprighttype, + Anum_pg_amop_amopstrategy), + 64 + }, + [AMPROCNUM] = { + AccessMethodProcedureRelationId, + AccessMethodProcedureIndexId, + KEY(Anum_pg_amproc_amprocfamily, + Anum_pg_amproc_amproclefttype, + Anum_pg_amproc_amprocrighttype, + Anum_pg_amproc_amprocnum), + 16 + }, + [ATTNAME] = { + AttributeRelationId, + AttributeRelidNameIndexId, + KEY(Anum_pg_attribute_attrelid, + Anum_pg_attribute_attname), + 32 + }, + [ATTNUM] = { + AttributeRelationId, + AttributeRelidNumIndexId, + KEY(Anum_pg_attribute_attrelid, + Anum_pg_attribute_attnum), + 128 + }, + [AUTHMEMMEMROLE] = { + AuthMemRelationId, + AuthMemMemRoleIndexId, + KEY(Anum_pg_auth_members_member, + Anum_pg_auth_members_roleid, + Anum_pg_auth_members_grantor), + 8 + }, + [AUTHMEMROLEMEM] = { + AuthMemRelationId, + AuthMemRoleMemIndexId, + KEY(Anum_pg_auth_members_roleid, + Anum_pg_auth_members_member, + Anum_pg_auth_members_grantor), + 8 + }, + [AUTHNAME] = { + AuthIdRelationId, + AuthIdRolnameIndexId, + KEY(Anum_pg_authid_rolname), + 8 + }, + [AUTHOID] = { + AuthIdRelationId, + AuthIdOidIndexId, + KEY(Anum_pg_authid_oid), + 8 + }, + [CASTSOURCETARGET] = { + CastRelationId, + CastSourceTargetIndexId, + KEY(Anum_pg_cast_castsource, + Anum_pg_cast_casttarget), + 256 + }, + [CLAAMNAMENSP] = { + OperatorClassRelationId, + OpclassAmNameNspIndexId, + KEY(Anum_pg_opclass_opcmethod, + Anum_pg_opclass_opcname, + Anum_pg_opclass_opcnamespace), + 8 + }, + [CLAOID] = { + OperatorClassRelationId, + OpclassOidIndexId, + KEY(Anum_pg_opclass_oid), + 8 + }, + [COLLNAMEENCNSP] = { + CollationRelationId, + CollationNameEncNspIndexId, + KEY(Anum_pg_collation_collname, + Anum_pg_collation_collencoding, + Anum_pg_collation_collnamespace), + 8 + }, + [COLLOID] = { + CollationRelationId, + CollationOidIndexId, + KEY(Anum_pg_collation_oid), + 8 + }, + [CONDEFAULT] = { + ConversionRelationId, + ConversionDefaultIndexId, + KEY(Anum_pg_conversion_connamespace, + Anum_pg_conversion_conforencoding, + Anum_pg_conversion_contoencoding, + Anum_pg_conversion_oid), + 8 + }, + [CONNAMENSP] = { + ConversionRelationId, + ConversionNameNspIndexId, + KEY(Anum_pg_conversion_conname, + Anum_pg_conversion_connamespace), + 8 + }, + [CONSTROID] = { + ConstraintRelationId, + ConstraintOidIndexId, + KEY(Anum_pg_constraint_oid), + 16 + }, + [CONVOID] = { + ConversionRelationId, + ConversionOidIndexId, + KEY(Anum_pg_conversion_oid), + 8 + }, + [DATABASEOID] = { + DatabaseRelationId, + DatabaseOidIndexId, + KEY(Anum_pg_database_oid), + 4 + }, + [DEFACLROLENSPOBJ] = { + DefaultAclRelationId, + DefaultAclRoleNspObjIndexId, + KEY(Anum_pg_default_acl_defaclrole, + Anum_pg_default_acl_defaclnamespace, + Anum_pg_default_acl_defaclobjtype), + 8 + }, + [ENUMOID] = { + EnumRelationId, + EnumOidIndexId, + KEY(Anum_pg_enum_oid), + 8 + }, + [ENUMTYPOIDNAME] = { + EnumRelationId, + EnumTypIdLabelIndexId, + KEY(Anum_pg_enum_enumtypid, + Anum_pg_enum_enumlabel), + 8 + }, + [EVENTTRIGGERNAME] = { + EventTriggerRelationId, + EventTriggerNameIndexId, + KEY(Anum_pg_event_trigger_evtname), + 8 + }, + [EVENTTRIGGEROID] = { + EventTriggerRelationId, + EventTriggerOidIndexId, + KEY(Anum_pg_event_trigger_oid), + 8 + }, + [FOREIGNDATAWRAPPERNAME] = { + ForeignDataWrapperRelationId, + ForeignDataWrapperNameIndexId, + KEY(Anum_pg_foreign_data_wrapper_fdwname), + 2 + }, + [FOREIGNDATAWRAPPEROID] = { + ForeignDataWrapperRelationId, + ForeignDataWrapperOidIndexId, + KEY(Anum_pg_foreign_data_wrapper_oid), + 2 + }, + [FOREIGNSERVERNAME] = { + ForeignServerRelationId, + ForeignServerNameIndexId, + KEY(Anum_pg_foreign_server_srvname), + 2 + }, + [FOREIGNSERVEROID] = { + ForeignServerRelationId, + ForeignServerOidIndexId, + KEY(Anum_pg_foreign_server_oid), + 2 + }, + [FOREIGNTABLEREL] = { + ForeignTableRelationId, + ForeignTableRelidIndexId, + KEY(Anum_pg_foreign_table_ftrelid), + 4 + }, + [INDEXRELID] = { + IndexRelationId, + IndexRelidIndexId, + KEY(Anum_pg_index_indexrelid), + 64 + }, + [LANGNAME] = { + LanguageRelationId, + LanguageNameIndexId, + KEY(Anum_pg_language_lanname), + 4 + }, + [LANGOID] = { + LanguageRelationId, + LanguageOidIndexId, + KEY(Anum_pg_language_oid), + 4 + }, + [NAMESPACENAME] = { + NamespaceRelationId, + NamespaceNameIndexId, + KEY(Anum_pg_namespace_nspname), + 4 + }, + [NAMESPACEOID] = { + NamespaceRelationId, + NamespaceOidIndexId, + KEY(Anum_pg_namespace_oid), + 16 + }, + [OPERNAMENSP] = { + OperatorRelationId, + OperatorNameNspIndexId, + KEY(Anum_pg_operator_oprname, + Anum_pg_operator_oprleft, + Anum_pg_operator_oprright, + Anum_pg_operator_oprnamespace), + 256 + }, + [OPEROID] = { + OperatorRelationId, + OperatorOidIndexId, + KEY(Anum_pg_operator_oid), + 32 + }, + [OPFAMILYAMNAMENSP] = { + OperatorFamilyRelationId, + OpfamilyAmNameNspIndexId, + KEY(Anum_pg_opfamily_opfmethod, + Anum_pg_opfamily_opfname, + Anum_pg_opfamily_opfnamespace), + 8 + }, + [OPFAMILYOID] = { + OperatorFamilyRelationId, + OpfamilyOidIndexId, + KEY(Anum_pg_opfamily_oid), + 8 + }, + [PARAMETERACLNAME] = { + ParameterAclRelationId, + ParameterAclParnameIndexId, + KEY(Anum_pg_parameter_acl_parname), + 4 + }, + [PARAMETERACLOID] = { + ParameterAclRelationId, + ParameterAclOidIndexId, + KEY(Anum_pg_parameter_acl_oid), + 4 + }, + [PARTRELID] = { + PartitionedRelationId, + PartitionedRelidIndexId, + KEY(Anum_pg_partitioned_table_partrelid), + 32 + }, + [PROCNAMEARGSNSP] = { + ProcedureRelationId, + ProcedureNameArgsNspIndexId, + KEY(Anum_pg_proc_proname, + Anum_pg_proc_proargtypes, + Anum_pg_proc_pronamespace), + 128 + }, + [PROCOID] = { + ProcedureRelationId, + ProcedureOidIndexId, + KEY(Anum_pg_proc_oid), + 128 + }, + [PUBLICATIONNAME] = { + PublicationRelationId, + PublicationNameIndexId, + KEY(Anum_pg_publication_pubname), + 8 + }, + [PUBLICATIONNAMESPACE] = { + PublicationNamespaceRelationId, + PublicationNamespaceObjectIndexId, + KEY(Anum_pg_publication_namespace_oid), + 64 + }, + [PUBLICATIONNAMESPACEMAP] = { + PublicationNamespaceRelationId, + PublicationNamespacePnnspidPnpubidIndexId, + KEY(Anum_pg_publication_namespace_pnnspid, + Anum_pg_publication_namespace_pnpubid), + 64 + }, + [PUBLICATIONOID] = { + PublicationRelationId, + PublicationObjectIndexId, + KEY(Anum_pg_publication_oid), + 8 + }, + [PUBLICATIONREL] = { + PublicationRelRelationId, + PublicationRelObjectIndexId, + KEY(Anum_pg_publication_rel_oid), + 64 + }, + [PUBLICATIONRELMAP] = { + PublicationRelRelationId, + PublicationRelPrrelidPrpubidIndexId, + KEY(Anum_pg_publication_rel_prrelid, + Anum_pg_publication_rel_prpubid), + 64 + }, + [RANGEMULTIRANGE] = { + RangeRelationId, + RangeMultirangeTypidIndexId, + KEY(Anum_pg_range_rngmultitypid), + 4 + }, + [RANGETYPE] = { + RangeRelationId, + RangeTypidIndexId, + KEY(Anum_pg_range_rngtypid), + 4 + }, + [RELNAMENSP] = { + RelationRelationId, + ClassNameNspIndexId, + KEY(Anum_pg_class_relname, + Anum_pg_class_relnamespace), + 128 + }, + [RELOID] = { + RelationRelationId, + ClassOidIndexId, + KEY(Anum_pg_class_oid), + 128 + }, + [REPLORIGIDENT] = { + ReplicationOriginRelationId, + ReplicationOriginIdentIndex, + KEY(Anum_pg_replication_origin_roident), + 16 + }, + [REPLORIGNAME] = { + ReplicationOriginRelationId, + ReplicationOriginNameIndex, + KEY(Anum_pg_replication_origin_roname), + 16 + }, + [RULERELNAME] = { + RewriteRelationId, + RewriteRelRulenameIndexId, + KEY(Anum_pg_rewrite_ev_class, + Anum_pg_rewrite_rulename), + 8 + }, + [SEQRELID] = { + SequenceRelationId, + SequenceRelidIndexId, + KEY(Anum_pg_sequence_seqrelid), + 32 + }, + [STATEXTDATASTXOID] = { + StatisticExtDataRelationId, + StatisticExtDataStxoidInhIndexId, + KEY(Anum_pg_statistic_ext_data_stxoid, + Anum_pg_statistic_ext_data_stxdinherit), + 4 + }, + [STATEXTNAMENSP] = { + StatisticExtRelationId, + StatisticExtNameIndexId, + KEY(Anum_pg_statistic_ext_stxname, + Anum_pg_statistic_ext_stxnamespace), + 4 + }, + [STATEXTOID] = { + StatisticExtRelationId, + StatisticExtOidIndexId, + KEY(Anum_pg_statistic_ext_oid), + 4 + }, + [STATRELATTINH] = { + StatisticRelationId, + StatisticRelidAttnumInhIndexId, + KEY(Anum_pg_statistic_starelid, + Anum_pg_statistic_staattnum, + Anum_pg_statistic_stainherit), + 128 + }, + [SUBSCRIPTIONNAME] = { + SubscriptionRelationId, + SubscriptionNameIndexId, + KEY(Anum_pg_subscription_subdbid, + Anum_pg_subscription_subname), + 4 + }, + [SUBSCRIPTIONOID] = { + SubscriptionRelationId, + SubscriptionObjectIndexId, + KEY(Anum_pg_subscription_oid), + 4 + }, + [SUBSCRIPTIONRELMAP] = { + SubscriptionRelRelationId, + SubscriptionRelSrrelidSrsubidIndexId, + KEY(Anum_pg_subscription_rel_srrelid, + Anum_pg_subscription_rel_srsubid), + 64 + }, + [TABLESPACEOID] = { + TableSpaceRelationId, + TablespaceOidIndexId, + KEY(Anum_pg_tablespace_oid), + 4 + }, + [TRFOID] = { + TransformRelationId, + TransformOidIndexId, + KEY(Anum_pg_transform_oid), + 16 + }, + [TRFTYPELANG] = { + TransformRelationId, + TransformTypeLangIndexId, + KEY(Anum_pg_transform_trftype, + Anum_pg_transform_trflang), + 16 + }, + [TSCONFIGMAP] = { + TSConfigMapRelationId, + TSConfigMapIndexId, + KEY(Anum_pg_ts_config_map_mapcfg, + Anum_pg_ts_config_map_maptokentype, + Anum_pg_ts_config_map_mapseqno), + 2 + }, + [TSCONFIGNAMENSP] = { + TSConfigRelationId, + TSConfigNameNspIndexId, + KEY(Anum_pg_ts_config_cfgname, + Anum_pg_ts_config_cfgnamespace), + 2 + }, + [TSCONFIGOID] = { + TSConfigRelationId, + TSConfigOidIndexId, + KEY(Anum_pg_ts_config_oid), + 2 + }, + [TSDICTNAMENSP] = { + TSDictionaryRelationId, + TSDictionaryNameNspIndexId, + KEY(Anum_pg_ts_dict_dictname, + Anum_pg_ts_dict_dictnamespace), + 2 + }, + [TSDICTOID] = { + TSDictionaryRelationId, + TSDictionaryOidIndexId, + KEY(Anum_pg_ts_dict_oid), + 2 + }, + [TSPARSERNAMENSP] = { + TSParserRelationId, + TSParserNameNspIndexId, + KEY(Anum_pg_ts_parser_prsname, + Anum_pg_ts_parser_prsnamespace), + 2 + }, + [TSPARSEROID] = { + TSParserRelationId, + TSParserOidIndexId, + KEY(Anum_pg_ts_parser_oid), + 2 + }, + [TSTEMPLATENAMENSP] = { + TSTemplateRelationId, + TSTemplateNameNspIndexId, + KEY(Anum_pg_ts_template_tmplname, + Anum_pg_ts_template_tmplnamespace), + 2 + }, + [TSTEMPLATEOID] = { + TSTemplateRelationId, + TSTemplateOidIndexId, + KEY(Anum_pg_ts_template_oid), + 2 + }, + [TYPENAMENSP] = { + TypeRelationId, + TypeNameNspIndexId, + KEY(Anum_pg_type_typname, + Anum_pg_type_typnamespace), + 64 + }, + [TYPEOID] = { + TypeRelationId, + TypeOidIndexId, + KEY(Anum_pg_type_oid), + 64 + }, + [USERMAPPINGOID] = { + UserMappingRelationId, + UserMappingOidIndexId, + KEY(Anum_pg_user_mapping_oid), + 2 + }, + [USERMAPPINGUSERSERVER] = { + UserMappingRelationId, + UserMappingUserServerIndexId, + KEY(Anum_pg_user_mapping_umuser, + Anum_pg_user_mapping_umserver), + 2 + } +}; + +StaticAssertDecl(lengthof(cacheinfo) == SysCacheSize, + "SysCacheSize does not match syscache.c's array"); + +static __thread CatCache *SysCache[SysCacheSize]; + +static __thread bool CacheInitialized = false; + +/* Sorted array of OIDs of tables that have caches on them */ +static __thread Oid SysCacheRelationOid[SysCacheSize]; +static __thread int SysCacheRelationOidSize; + +/* Sorted array of OIDs of tables and indexes used by caches */ +static __thread Oid SysCacheSupportingRelOid[SysCacheSize * 2]; +static __thread int SysCacheSupportingRelOidSize; + +static int oid_compare(const void *a, const void *b); + + +/* + * InitCatalogCache - initialize the caches + * + * Note that no database access is done here; we only allocate memory + * and initialize the cache structure. Interrogation of the database + * to complete initialization of a cache happens upon first use + * of that cache. + */ +void +InitCatalogCache(void) +{ + int cacheId; + + Assert(!CacheInitialized); + + SysCacheRelationOidSize = SysCacheSupportingRelOidSize = 0; + + for (cacheId = 0; cacheId < SysCacheSize; cacheId++) + { + /* + * Assert that every enumeration value defined in syscache.h has been + * populated in the cacheinfo array. + */ + Assert(cacheinfo[cacheId].reloid != 0); + + SysCache[cacheId] = InitCatCache(cacheId, + cacheinfo[cacheId].reloid, + cacheinfo[cacheId].indoid, + cacheinfo[cacheId].nkeys, + cacheinfo[cacheId].key, + cacheinfo[cacheId].nbuckets); + if (!PointerIsValid(SysCache[cacheId])) + elog(ERROR, "could not initialize cache %u (%d)", + cacheinfo[cacheId].reloid, cacheId); + /* Accumulate data for OID lists, too */ + SysCacheRelationOid[SysCacheRelationOidSize++] = + cacheinfo[cacheId].reloid; + SysCacheSupportingRelOid[SysCacheSupportingRelOidSize++] = + cacheinfo[cacheId].reloid; + SysCacheSupportingRelOid[SysCacheSupportingRelOidSize++] = + cacheinfo[cacheId].indoid; + /* see comments for RelationInvalidatesSnapshotsOnly */ + Assert(!RelationInvalidatesSnapshotsOnly(cacheinfo[cacheId].reloid)); + } + + Assert(SysCacheRelationOidSize <= lengthof(SysCacheRelationOid)); + Assert(SysCacheSupportingRelOidSize <= lengthof(SysCacheSupportingRelOid)); + + /* Sort and de-dup OID arrays, so we can use binary search. */ + pg_qsort(SysCacheRelationOid, SysCacheRelationOidSize, + sizeof(Oid), oid_compare); + SysCacheRelationOidSize = + qunique(SysCacheRelationOid, SysCacheRelationOidSize, sizeof(Oid), + oid_compare); + + pg_qsort(SysCacheSupportingRelOid, SysCacheSupportingRelOidSize, + sizeof(Oid), oid_compare); + SysCacheSupportingRelOidSize = + qunique(SysCacheSupportingRelOid, SysCacheSupportingRelOidSize, + sizeof(Oid), oid_compare); + + CacheInitialized = true; +} + +/* + * InitCatalogCachePhase2 - finish initializing the caches + * + * Finish initializing all the caches, including necessary database + * access. + * + * This is *not* essential; normally we allow syscaches to be initialized + * on first use. However, it is useful as a mechanism to preload the + * relcache with entries for the most-commonly-used system catalogs. + * Therefore, we invoke this routine when we need to write a new relcache + * init file. + */ +void +InitCatalogCachePhase2(void) +{ + int cacheId; + + Assert(CacheInitialized); + + for (cacheId = 0; cacheId < SysCacheSize; cacheId++) + InitCatCachePhase2(SysCache[cacheId], true); +} + + +/* + * SearchSysCache + * + * A layer on top of SearchCatCache that does the initialization and + * key-setting for you. + * + * Returns the cache copy of the tuple if one is found, NULL if not. + * The tuple is the 'cache' copy and must NOT be modified! + * + * When the caller is done using the tuple, call ReleaseSysCache() + * to release the reference count grabbed by SearchSysCache(). If this + * is not done, the tuple will remain locked in cache until end of + * transaction, which is tolerable but not desirable. + * + * CAUTION: The tuple that is returned must NOT be freed by the caller! + */ +HeapTuple +SearchSysCache_original(int cacheId, + Datum key1, + Datum key2, + Datum key3, + Datum key4) +{ + Assert(cacheId >= 0 && cacheId < SysCacheSize && + PointerIsValid(SysCache[cacheId])); + + return SearchCatCache(SysCache[cacheId], key1, key2, key3, key4); +} + +HeapTuple +SearchSysCache1_original(int cacheId, + Datum key1) +{ + Assert(cacheId >= 0 && cacheId < SysCacheSize && + PointerIsValid(SysCache[cacheId])); + Assert(SysCache[cacheId]->cc_nkeys == 1); + + return SearchCatCache1(SysCache[cacheId], key1); +} + +HeapTuple +SearchSysCache2_original(int cacheId, + Datum key1, Datum key2) +{ + Assert(cacheId >= 0 && cacheId < SysCacheSize && + PointerIsValid(SysCache[cacheId])); + Assert(SysCache[cacheId]->cc_nkeys == 2); + + return SearchCatCache2(SysCache[cacheId], key1, key2); +} + +HeapTuple +SearchSysCache3_original(int cacheId, + Datum key1, Datum key2, Datum key3) +{ + Assert(cacheId >= 0 && cacheId < SysCacheSize && + PointerIsValid(SysCache[cacheId])); + Assert(SysCache[cacheId]->cc_nkeys == 3); + + return SearchCatCache3(SysCache[cacheId], key1, key2, key3); +} + +HeapTuple +SearchSysCache4_original(int cacheId, + Datum key1, Datum key2, Datum key3, Datum key4) +{ + Assert(cacheId >= 0 && cacheId < SysCacheSize && + PointerIsValid(SysCache[cacheId])); + Assert(SysCache[cacheId]->cc_nkeys == 4); + + return SearchCatCache4(SysCache[cacheId], key1, key2, key3, key4); +} + +/* + * ReleaseSysCache + * Release previously grabbed reference count on a tuple + */ +void +ReleaseSysCache_original(HeapTuple tuple) +{ + ReleaseCatCache(tuple); +} + +/* + * SearchSysCacheCopy + * + * A convenience routine that does SearchSysCache and (if successful) + * returns a modifiable copy of the syscache entry. The original + * syscache entry is released before returning. The caller should + * heap_freetuple() the result when done with it. + */ +HeapTuple +SearchSysCacheCopy(int cacheId, + Datum key1, + Datum key2, + Datum key3, + Datum key4) +{ + HeapTuple tuple, + newtuple; + + tuple = SearchSysCache(cacheId, key1, key2, key3, key4); + if (!HeapTupleIsValid(tuple)) + return tuple; + newtuple = heap_copytuple(tuple); + ReleaseSysCache(tuple); + return newtuple; +} + +/* + * SearchSysCacheExists + * + * A convenience routine that just probes to see if a tuple can be found. + * No lock is retained on the syscache entry. + */ +bool +SearchSysCacheExists(int cacheId, + Datum key1, + Datum key2, + Datum key3, + Datum key4) +{ + HeapTuple tuple; + + tuple = SearchSysCache(cacheId, key1, key2, key3, key4); + if (!HeapTupleIsValid(tuple)) + return false; + ReleaseSysCache(tuple); + return true; +} + +/* + * GetSysCacheOid + * + * A convenience routine that does SearchSysCache and returns the OID in the + * oidcol column of the found tuple, or InvalidOid if no tuple could be found. + * No lock is retained on the syscache entry. + */ +Oid +GetSysCacheOid_original(int cacheId, + AttrNumber oidcol, + Datum key1, + Datum key2, + Datum key3, + Datum key4) +{ + HeapTuple tuple; + bool isNull; + Oid result; + + tuple = SearchSysCache(cacheId, key1, key2, key3, key4); + if (!HeapTupleIsValid(tuple)) + return InvalidOid; + result = heap_getattr(tuple, oidcol, + SysCache[cacheId]->cc_tupdesc, + &isNull); + Assert(!isNull); /* columns used as oids should never be NULL */ + ReleaseSysCache(tuple); + return result; +} + + +/* + * SearchSysCacheAttName + * + * This routine is equivalent to SearchSysCache on the ATTNAME cache, + * except that it will return NULL if the found attribute is marked + * attisdropped. This is convenient for callers that want to act as + * though dropped attributes don't exist. + */ +HeapTuple +SearchSysCacheAttName(Oid relid, const char *attname) +{ + HeapTuple tuple; + + tuple = SearchSysCache2(ATTNAME, + ObjectIdGetDatum(relid), + CStringGetDatum(attname)); + if (!HeapTupleIsValid(tuple)) + return NULL; + if (((Form_pg_attribute) GETSTRUCT(tuple))->attisdropped) + { + ReleaseSysCache(tuple); + return NULL; + } + return tuple; +} + +/* + * SearchSysCacheCopyAttName + * + * As above, an attisdropped-aware version of SearchSysCacheCopy. + */ +HeapTuple +SearchSysCacheCopyAttName(Oid relid, const char *attname) +{ + HeapTuple tuple, + newtuple; + + tuple = SearchSysCacheAttName(relid, attname); + if (!HeapTupleIsValid(tuple)) + return tuple; + newtuple = heap_copytuple(tuple); + ReleaseSysCache(tuple); + return newtuple; +} + +/* + * SearchSysCacheExistsAttName + * + * As above, an attisdropped-aware version of SearchSysCacheExists. + */ +bool +SearchSysCacheExistsAttName(Oid relid, const char *attname) +{ + HeapTuple tuple; + + tuple = SearchSysCacheAttName(relid, attname); + if (!HeapTupleIsValid(tuple)) + return false; + ReleaseSysCache(tuple); + return true; +} + + +/* + * SearchSysCacheAttNum + * + * This routine is equivalent to SearchSysCache on the ATTNUM cache, + * except that it will return NULL if the found attribute is marked + * attisdropped. This is convenient for callers that want to act as + * though dropped attributes don't exist. + */ +HeapTuple +SearchSysCacheAttNum(Oid relid, int16 attnum) +{ + HeapTuple tuple; + + tuple = SearchSysCache2(ATTNUM, + ObjectIdGetDatum(relid), + Int16GetDatum(attnum)); + if (!HeapTupleIsValid(tuple)) + return NULL; + if (((Form_pg_attribute) GETSTRUCT(tuple))->attisdropped) + { + ReleaseSysCache(tuple); + return NULL; + } + return tuple; +} + +/* + * SearchSysCacheCopyAttNum + * + * As above, an attisdropped-aware version of SearchSysCacheCopy. + */ +HeapTuple +SearchSysCacheCopyAttNum(Oid relid, int16 attnum) +{ + HeapTuple tuple, + newtuple; + + tuple = SearchSysCacheAttNum(relid, attnum); + if (!HeapTupleIsValid(tuple)) + return NULL; + newtuple = heap_copytuple(tuple); + ReleaseSysCache(tuple); + return newtuple; +} + + +/* + * SysCacheGetAttr + * + * Given a tuple previously fetched by SearchSysCache(), + * extract a specific attribute. + * + * This is equivalent to using heap_getattr() on a tuple fetched + * from a non-cached relation. Usually, this is only used for attributes + * that could be NULL or variable length; the fixed-size attributes in + * a system table are accessed just by mapping the tuple onto the C struct + * declarations from include/catalog/. + * + * As with heap_getattr(), if the attribute is of a pass-by-reference type + * then a pointer into the tuple data area is returned --- the caller must + * not modify or pfree the datum! + * + * Note: it is legal to use SysCacheGetAttr() with a cacheId referencing + * a different cache for the same catalog the tuple was fetched from. + */ +Datum +SysCacheGetAttr_original(int cacheId, HeapTuple tup, + AttrNumber attributeNumber, + bool *isNull) +{ + /* + * We just need to get the TupleDesc out of the cache entry, and then we + * can apply heap_getattr(). Normally the cache control data is already + * valid (because the caller recently fetched the tuple via this same + * cache), but there are cases where we have to initialize the cache here. + */ + if (cacheId < 0 || cacheId >= SysCacheSize || + !PointerIsValid(SysCache[cacheId])) + elog(ERROR, "invalid cache ID: %d", cacheId); + if (!PointerIsValid(SysCache[cacheId]->cc_tupdesc)) + { + InitCatCachePhase2(SysCache[cacheId], false); + Assert(PointerIsValid(SysCache[cacheId]->cc_tupdesc)); + } + + return heap_getattr(tup, attributeNumber, + SysCache[cacheId]->cc_tupdesc, + isNull); +} + +/* + * SysCacheGetAttrNotNull + * + * As above, a version of SysCacheGetAttr which knows that the attr cannot + * be NULL. + */ +Datum +SysCacheGetAttrNotNull(int cacheId, HeapTuple tup, + AttrNumber attributeNumber) +{ + bool isnull; + Datum attr; + + attr = SysCacheGetAttr(cacheId, tup, attributeNumber, &isnull); + + if (isnull) + { + elog(ERROR, + "unexpected null value in cached tuple for catalog %s column %s", + get_rel_name(cacheinfo[cacheId].reloid), + NameStr(TupleDescAttr(SysCache[cacheId]->cc_tupdesc, attributeNumber - 1)->attname)); + } + + return attr; +} + +/* + * GetSysCacheHashValue + * + * Get the hash value that would be used for a tuple in the specified cache + * with the given search keys. + * + * The reason for exposing this as part of the API is that the hash value is + * exposed in cache invalidation operations, so there are places outside the + * catcache code that need to be able to compute the hash values. + */ +uint32 +GetSysCacheHashValue(int cacheId, + Datum key1, + Datum key2, + Datum key3, + Datum key4) +{ + if (cacheId < 0 || cacheId >= SysCacheSize || + !PointerIsValid(SysCache[cacheId])) + elog(ERROR, "invalid cache ID: %d", cacheId); + + return GetCatCacheHashValue(SysCache[cacheId], key1, key2, key3, key4); +} + +/* + * List-search interface + */ +struct catclist * +SearchSysCacheList_original(int cacheId, int nkeys, + Datum key1, Datum key2, Datum key3) +{ + if (cacheId < 0 || cacheId >= SysCacheSize || + !PointerIsValid(SysCache[cacheId])) + elog(ERROR, "invalid cache ID: %d", cacheId); + + return SearchCatCacheList(SysCache[cacheId], nkeys, + key1, key2, key3); +} + +/* + * SysCacheInvalidate + * + * Invalidate entries in the specified cache, given a hash value. + * See CatCacheInvalidate() for more info. + * + * This routine is only quasi-public: it should only be used by inval.c. + */ +void +SysCacheInvalidate(int cacheId, uint32 hashValue) +{ + if (cacheId < 0 || cacheId >= SysCacheSize) + elog(ERROR, "invalid cache ID: %d", cacheId); + + /* if this cache isn't initialized yet, no need to do anything */ + if (!PointerIsValid(SysCache[cacheId])) + return; + + CatCacheInvalidate(SysCache[cacheId], hashValue); +} + +/* + * Certain relations that do not have system caches send snapshot invalidation + * messages in lieu of catcache messages. This is for the benefit of + * GetCatalogSnapshot(), which can then reuse its existing MVCC snapshot + * for scanning one of those catalogs, rather than taking a new one, if no + * invalidation has been received. + * + * Relations that have syscaches need not (and must not) be listed here. The + * catcache invalidation messages will also flush the snapshot. If you add a + * syscache for one of these relations, remove it from this list. + */ +bool +RelationInvalidatesSnapshotsOnly(Oid relid) +{ + switch (relid) + { + case DbRoleSettingRelationId: + case DependRelationId: + case SharedDependRelationId: + case DescriptionRelationId: + case SharedDescriptionRelationId: + case SecLabelRelationId: + case SharedSecLabelRelationId: + return true; + default: + break; + } + + return false; +} + +/* + * Test whether a relation has a system cache. + */ +bool +RelationHasSysCache(Oid relid) +{ + int low = 0, + high = SysCacheRelationOidSize - 1; + + while (low <= high) + { + int middle = low + (high - low) / 2; + + if (SysCacheRelationOid[middle] == relid) + return true; + if (SysCacheRelationOid[middle] < relid) + low = middle + 1; + else + high = middle - 1; + } + + return false; +} + +/* + * Test whether a relation supports a system cache, ie it is either a + * cached table or the index used for a cache. + */ +bool +RelationSupportsSysCache(Oid relid) +{ + int low = 0, + high = SysCacheSupportingRelOidSize - 1; + + while (low <= high) + { + int middle = low + (high - low) / 2; + + if (SysCacheSupportingRelOid[middle] == relid) + return true; + if (SysCacheSupportingRelOid[middle] < relid) + low = middle + 1; + else + high = middle - 1; + } + + return false; +} + + +/* + * OID comparator for pg_qsort + */ +static int +oid_compare(const void *a, const void *b) +{ + Oid oa = *((const Oid *) a); + Oid ob = *((const Oid *) b); + + if (oa == ob) + return 0; + return (oa > ob) ? 1 : -1; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/cache/ts_cache.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/cache/ts_cache.c new file mode 100644 index 00000000000..ceb0aca329d --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/cache/ts_cache.c @@ -0,0 +1,674 @@ +/*------------------------------------------------------------------------- + * + * ts_cache.c + * Tsearch related object caches. + * + * Tsearch performance is very sensitive to performance of parsers, + * dictionaries and mapping, so lookups should be cached as much + * as possible. + * + * Once a backend has created a cache entry for a particular TS object OID, + * the cache entry will exist for the life of the backend; hence it is + * safe to hold onto a pointer to the cache entry while doing things that + * might result in recognizing a cache invalidation. Beware however that + * subsidiary information might be deleted and reallocated somewhere else + * if a cache inval and reval happens! This does not look like it will be + * a big problem as long as parser and dictionary methods do not attempt + * any database access. + * + * + * Copyright (c) 2006-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/utils/cache/ts_cache.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/genam.h" +#include "access/htup_details.h" +#include "access/table.h" +#include "access/xact.h" +#include "catalog/namespace.h" +#include "catalog/pg_ts_config.h" +#include "catalog/pg_ts_config_map.h" +#include "catalog/pg_ts_dict.h" +#include "catalog/pg_ts_parser.h" +#include "catalog/pg_ts_template.h" +#include "commands/defrem.h" +#include "miscadmin.h" +#include "nodes/miscnodes.h" +#include "tsearch/ts_cache.h" +#include "utils/builtins.h" +#include "utils/catcache.h" +#include "utils/fmgroids.h" +#include "utils/guc_hooks.h" +#include "utils/inval.h" +#include "utils/lsyscache.h" +#include "utils/memutils.h" +#include "utils/regproc.h" +#include "utils/syscache.h" + + +/* + * MAXTOKENTYPE/MAXDICTSPERTT are arbitrary limits on the workspace size + * used in lookup_ts_config_cache(). We could avoid hardwiring a limit + * by making the workspace dynamically enlargeable, but it seems unlikely + * to be worth the trouble. + */ +#define MAXTOKENTYPE 256 +#define MAXDICTSPERTT 100 + + +static __thread HTAB *TSParserCacheHash = NULL; +static __thread TSParserCacheEntry *lastUsedParser = NULL; + +static __thread HTAB *TSDictionaryCacheHash = NULL; +static __thread TSDictionaryCacheEntry *lastUsedDictionary = NULL; + +static __thread HTAB *TSConfigCacheHash = NULL; +static __thread TSConfigCacheEntry *lastUsedConfig = NULL; + +/* + * GUC default_text_search_config, and a cache of the current config's OID + */ +__thread char *TSCurrentConfig = NULL; + +static __thread Oid TSCurrentConfigCache = InvalidOid; + + +/* + * We use this syscache callback to detect when a visible change to a TS + * catalog entry has been made, by either our own backend or another one. + * + * In principle we could just flush the specific cache entry that changed, + * but given that TS configuration changes are probably infrequent, it + * doesn't seem worth the trouble to determine that; we just flush all the + * entries of the related hash table. + * + * We can use the same function for all TS caches by passing the hash + * table address as the "arg". + */ +static void +InvalidateTSCacheCallBack(Datum arg, int cacheid, uint32 hashvalue) +{ + HTAB *hash = (HTAB *) DatumGetPointer(arg); + HASH_SEQ_STATUS status; + TSAnyCacheEntry *entry; + + hash_seq_init(&status, hash); + while ((entry = (TSAnyCacheEntry *) hash_seq_search(&status)) != NULL) + entry->isvalid = false; + + /* Also invalidate the current-config cache if it's pg_ts_config */ + if (hash == TSConfigCacheHash) + TSCurrentConfigCache = InvalidOid; +} + +/* + * Fetch parser cache entry + */ +TSParserCacheEntry * +lookup_ts_parser_cache(Oid prsId) +{ + TSParserCacheEntry *entry; + + if (TSParserCacheHash == NULL) + { + /* First time through: initialize the hash table */ + HASHCTL ctl; + + ctl.keysize = sizeof(Oid); + ctl.entrysize = sizeof(TSParserCacheEntry); + TSParserCacheHash = hash_create("Tsearch parser cache", 4, + &ctl, HASH_ELEM | HASH_BLOBS); + /* Flush cache on pg_ts_parser changes */ + CacheRegisterSyscacheCallback(TSPARSEROID, InvalidateTSCacheCallBack, + PointerGetDatum(TSParserCacheHash)); + + /* Also make sure CacheMemoryContext exists */ + if (!CacheMemoryContext) + CreateCacheMemoryContext(); + } + + /* Check single-entry cache */ + if (lastUsedParser && lastUsedParser->prsId == prsId && + lastUsedParser->isvalid) + return lastUsedParser; + + /* Try to look up an existing entry */ + entry = (TSParserCacheEntry *) hash_search(TSParserCacheHash, + &prsId, + HASH_FIND, NULL); + if (entry == NULL || !entry->isvalid) + { + /* + * If we didn't find one, we want to make one. But first look up the + * object to be sure the OID is real. + */ + HeapTuple tp; + Form_pg_ts_parser prs; + + tp = SearchSysCache1(TSPARSEROID, ObjectIdGetDatum(prsId)); + if (!HeapTupleIsValid(tp)) + elog(ERROR, "cache lookup failed for text search parser %u", + prsId); + prs = (Form_pg_ts_parser) GETSTRUCT(tp); + + /* + * Sanity checks + */ + if (!OidIsValid(prs->prsstart)) + elog(ERROR, "text search parser %u has no prsstart method", prsId); + if (!OidIsValid(prs->prstoken)) + elog(ERROR, "text search parser %u has no prstoken method", prsId); + if (!OidIsValid(prs->prsend)) + elog(ERROR, "text search parser %u has no prsend method", prsId); + + if (entry == NULL) + { + bool found; + + /* Now make the cache entry */ + entry = (TSParserCacheEntry *) + hash_search(TSParserCacheHash, &prsId, HASH_ENTER, &found); + Assert(!found); /* it wasn't there a moment ago */ + } + + MemSet(entry, 0, sizeof(TSParserCacheEntry)); + entry->prsId = prsId; + entry->startOid = prs->prsstart; + entry->tokenOid = prs->prstoken; + entry->endOid = prs->prsend; + entry->headlineOid = prs->prsheadline; + entry->lextypeOid = prs->prslextype; + + ReleaseSysCache(tp); + + fmgr_info_cxt(entry->startOid, &entry->prsstart, CacheMemoryContext); + fmgr_info_cxt(entry->tokenOid, &entry->prstoken, CacheMemoryContext); + fmgr_info_cxt(entry->endOid, &entry->prsend, CacheMemoryContext); + if (OidIsValid(entry->headlineOid)) + fmgr_info_cxt(entry->headlineOid, &entry->prsheadline, + CacheMemoryContext); + + entry->isvalid = true; + } + + lastUsedParser = entry; + + return entry; +} + +/* + * Fetch dictionary cache entry + */ +TSDictionaryCacheEntry * +lookup_ts_dictionary_cache(Oid dictId) +{ + TSDictionaryCacheEntry *entry; + + if (TSDictionaryCacheHash == NULL) + { + /* First time through: initialize the hash table */ + HASHCTL ctl; + + ctl.keysize = sizeof(Oid); + ctl.entrysize = sizeof(TSDictionaryCacheEntry); + TSDictionaryCacheHash = hash_create("Tsearch dictionary cache", 8, + &ctl, HASH_ELEM | HASH_BLOBS); + /* Flush cache on pg_ts_dict and pg_ts_template changes */ + CacheRegisterSyscacheCallback(TSDICTOID, InvalidateTSCacheCallBack, + PointerGetDatum(TSDictionaryCacheHash)); + CacheRegisterSyscacheCallback(TSTEMPLATEOID, InvalidateTSCacheCallBack, + PointerGetDatum(TSDictionaryCacheHash)); + + /* Also make sure CacheMemoryContext exists */ + if (!CacheMemoryContext) + CreateCacheMemoryContext(); + } + + /* Check single-entry cache */ + if (lastUsedDictionary && lastUsedDictionary->dictId == dictId && + lastUsedDictionary->isvalid) + return lastUsedDictionary; + + /* Try to look up an existing entry */ + entry = (TSDictionaryCacheEntry *) hash_search(TSDictionaryCacheHash, + &dictId, + HASH_FIND, NULL); + if (entry == NULL || !entry->isvalid) + { + /* + * If we didn't find one, we want to make one. But first look up the + * object to be sure the OID is real. + */ + HeapTuple tpdict, + tptmpl; + Form_pg_ts_dict dict; + Form_pg_ts_template template; + MemoryContext saveCtx; + + tpdict = SearchSysCache1(TSDICTOID, ObjectIdGetDatum(dictId)); + if (!HeapTupleIsValid(tpdict)) + elog(ERROR, "cache lookup failed for text search dictionary %u", + dictId); + dict = (Form_pg_ts_dict) GETSTRUCT(tpdict); + + /* + * Sanity checks + */ + if (!OidIsValid(dict->dicttemplate)) + elog(ERROR, "text search dictionary %u has no template", dictId); + + /* + * Retrieve dictionary's template + */ + tptmpl = SearchSysCache1(TSTEMPLATEOID, + ObjectIdGetDatum(dict->dicttemplate)); + if (!HeapTupleIsValid(tptmpl)) + elog(ERROR, "cache lookup failed for text search template %u", + dict->dicttemplate); + template = (Form_pg_ts_template) GETSTRUCT(tptmpl); + + /* + * Sanity checks + */ + if (!OidIsValid(template->tmpllexize)) + elog(ERROR, "text search template %u has no lexize method", + template->tmpllexize); + + if (entry == NULL) + { + bool found; + + /* Now make the cache entry */ + entry = (TSDictionaryCacheEntry *) + hash_search(TSDictionaryCacheHash, + &dictId, + HASH_ENTER, &found); + Assert(!found); /* it wasn't there a moment ago */ + + /* Create private memory context the first time through */ + saveCtx = AllocSetContextCreate(CacheMemoryContext, + "TS dictionary", + ALLOCSET_SMALL_SIZES); + MemoryContextCopyAndSetIdentifier(saveCtx, NameStr(dict->dictname)); + } + else + { + /* Clear the existing entry's private context */ + saveCtx = entry->dictCtx; + /* Don't let context's ident pointer dangle while we reset it */ + MemoryContextSetIdentifier(saveCtx, NULL); + MemoryContextReset(saveCtx); + MemoryContextCopyAndSetIdentifier(saveCtx, NameStr(dict->dictname)); + } + + MemSet(entry, 0, sizeof(TSDictionaryCacheEntry)); + entry->dictId = dictId; + entry->dictCtx = saveCtx; + + entry->lexizeOid = template->tmpllexize; + + if (OidIsValid(template->tmplinit)) + { + List *dictoptions; + Datum opt; + bool isnull; + MemoryContext oldcontext; + + /* + * Init method runs in dictionary's private memory context, and we + * make sure the options are stored there too + */ + oldcontext = MemoryContextSwitchTo(entry->dictCtx); + + opt = SysCacheGetAttr(TSDICTOID, tpdict, + Anum_pg_ts_dict_dictinitoption, + &isnull); + if (isnull) + dictoptions = NIL; + else + dictoptions = deserialize_deflist(opt); + + entry->dictData = + DatumGetPointer(OidFunctionCall1(template->tmplinit, + PointerGetDatum(dictoptions))); + + MemoryContextSwitchTo(oldcontext); + } + + ReleaseSysCache(tptmpl); + ReleaseSysCache(tpdict); + + fmgr_info_cxt(entry->lexizeOid, &entry->lexize, entry->dictCtx); + + entry->isvalid = true; + } + + lastUsedDictionary = entry; + + return entry; +} + +/* + * Initialize config cache and prepare callbacks. This is split out of + * lookup_ts_config_cache because we need to activate the callback before + * caching TSCurrentConfigCache, too. + */ +static void +init_ts_config_cache(void) +{ + HASHCTL ctl; + + ctl.keysize = sizeof(Oid); + ctl.entrysize = sizeof(TSConfigCacheEntry); + TSConfigCacheHash = hash_create("Tsearch configuration cache", 16, + &ctl, HASH_ELEM | HASH_BLOBS); + /* Flush cache on pg_ts_config and pg_ts_config_map changes */ + CacheRegisterSyscacheCallback(TSCONFIGOID, InvalidateTSCacheCallBack, + PointerGetDatum(TSConfigCacheHash)); + CacheRegisterSyscacheCallback(TSCONFIGMAP, InvalidateTSCacheCallBack, + PointerGetDatum(TSConfigCacheHash)); + + /* Also make sure CacheMemoryContext exists */ + if (!CacheMemoryContext) + CreateCacheMemoryContext(); +} + +/* + * Fetch configuration cache entry + */ +TSConfigCacheEntry * +lookup_ts_config_cache(Oid cfgId) +{ + TSConfigCacheEntry *entry; + + if (TSConfigCacheHash == NULL) + { + /* First time through: initialize the hash table */ + init_ts_config_cache(); + } + + /* Check single-entry cache */ + if (lastUsedConfig && lastUsedConfig->cfgId == cfgId && + lastUsedConfig->isvalid) + return lastUsedConfig; + + /* Try to look up an existing entry */ + entry = (TSConfigCacheEntry *) hash_search(TSConfigCacheHash, + &cfgId, + HASH_FIND, NULL); + if (entry == NULL || !entry->isvalid) + { + /* + * If we didn't find one, we want to make one. But first look up the + * object to be sure the OID is real. + */ + HeapTuple tp; + Form_pg_ts_config cfg; + Relation maprel; + Relation mapidx; + ScanKeyData mapskey; + SysScanDesc mapscan; + HeapTuple maptup; + ListDictionary maplists[MAXTOKENTYPE + 1]; + Oid mapdicts[MAXDICTSPERTT]; + int maxtokentype; + int ndicts; + int i; + + tp = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(cfgId)); + if (!HeapTupleIsValid(tp)) + elog(ERROR, "cache lookup failed for text search configuration %u", + cfgId); + cfg = (Form_pg_ts_config) GETSTRUCT(tp); + + /* + * Sanity checks + */ + if (!OidIsValid(cfg->cfgparser)) + elog(ERROR, "text search configuration %u has no parser", cfgId); + + if (entry == NULL) + { + bool found; + + /* Now make the cache entry */ + entry = (TSConfigCacheEntry *) + hash_search(TSConfigCacheHash, + &cfgId, + HASH_ENTER, &found); + Assert(!found); /* it wasn't there a moment ago */ + } + else + { + /* Cleanup old contents */ + if (entry->map) + { + for (i = 0; i < entry->lenmap; i++) + if (entry->map[i].dictIds) + pfree(entry->map[i].dictIds); + pfree(entry->map); + } + } + + MemSet(entry, 0, sizeof(TSConfigCacheEntry)); + entry->cfgId = cfgId; + entry->prsId = cfg->cfgparser; + + ReleaseSysCache(tp); + + /* + * Scan pg_ts_config_map to gather dictionary list for each token type + * + * Because the index is on (mapcfg, maptokentype, mapseqno), we will + * see the entries in maptokentype order, and in mapseqno order for + * each token type, even though we didn't explicitly ask for that. + */ + MemSet(maplists, 0, sizeof(maplists)); + maxtokentype = 0; + ndicts = 0; + + ScanKeyInit(&mapskey, + Anum_pg_ts_config_map_mapcfg, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(cfgId)); + + maprel = table_open(TSConfigMapRelationId, AccessShareLock); + mapidx = index_open(TSConfigMapIndexId, AccessShareLock); + mapscan = systable_beginscan_ordered(maprel, mapidx, + NULL, 1, &mapskey); + + while ((maptup = systable_getnext_ordered(mapscan, ForwardScanDirection)) != NULL) + { + Form_pg_ts_config_map cfgmap = (Form_pg_ts_config_map) GETSTRUCT(maptup); + int toktype = cfgmap->maptokentype; + + if (toktype <= 0 || toktype > MAXTOKENTYPE) + elog(ERROR, "maptokentype value %d is out of range", toktype); + if (toktype < maxtokentype) + elog(ERROR, "maptokentype entries are out of order"); + if (toktype > maxtokentype) + { + /* starting a new token type, but first save the prior data */ + if (ndicts > 0) + { + maplists[maxtokentype].len = ndicts; + maplists[maxtokentype].dictIds = (Oid *) + MemoryContextAlloc(CacheMemoryContext, + sizeof(Oid) * ndicts); + memcpy(maplists[maxtokentype].dictIds, mapdicts, + sizeof(Oid) * ndicts); + } + maxtokentype = toktype; + mapdicts[0] = cfgmap->mapdict; + ndicts = 1; + } + else + { + /* continuing data for current token type */ + if (ndicts >= MAXDICTSPERTT) + elog(ERROR, "too many pg_ts_config_map entries for one token type"); + mapdicts[ndicts++] = cfgmap->mapdict; + } + } + + systable_endscan_ordered(mapscan); + index_close(mapidx, AccessShareLock); + table_close(maprel, AccessShareLock); + + if (ndicts > 0) + { + /* save the last token type's dictionaries */ + maplists[maxtokentype].len = ndicts; + maplists[maxtokentype].dictIds = (Oid *) + MemoryContextAlloc(CacheMemoryContext, + sizeof(Oid) * ndicts); + memcpy(maplists[maxtokentype].dictIds, mapdicts, + sizeof(Oid) * ndicts); + /* and save the overall map */ + entry->lenmap = maxtokentype + 1; + entry->map = (ListDictionary *) + MemoryContextAlloc(CacheMemoryContext, + sizeof(ListDictionary) * entry->lenmap); + memcpy(entry->map, maplists, + sizeof(ListDictionary) * entry->lenmap); + } + + entry->isvalid = true; + } + + lastUsedConfig = entry; + + return entry; +} + + +/*--------------------------------------------------- + * GUC variable "default_text_search_config" + *--------------------------------------------------- + */ + +Oid +getTSCurrentConfig(bool emitError) +{ + List *namelist; + + /* if we have a cached value, return it */ + if (OidIsValid(TSCurrentConfigCache)) + return TSCurrentConfigCache; + + /* fail if GUC hasn't been set up yet */ + if (TSCurrentConfig == NULL || *TSCurrentConfig == '\0') + { + if (emitError) + elog(ERROR, "text search configuration isn't set"); + else + return InvalidOid; + } + + if (TSConfigCacheHash == NULL) + { + /* First time through: initialize the tsconfig inval callback */ + init_ts_config_cache(); + } + + /* Look up the config */ + if (emitError) + { + namelist = stringToQualifiedNameList(TSCurrentConfig, NULL); + TSCurrentConfigCache = get_ts_config_oid(namelist, false); + } + else + { + ErrorSaveContext escontext = {T_ErrorSaveContext}; + + namelist = stringToQualifiedNameList(TSCurrentConfig, + (Node *) &escontext); + if (namelist != NIL) + TSCurrentConfigCache = get_ts_config_oid(namelist, true); + else + TSCurrentConfigCache = InvalidOid; /* bad name list syntax */ + } + + return TSCurrentConfigCache; +} + +/* GUC check_hook for default_text_search_config */ +bool +check_default_text_search_config(char **newval, void **extra, GucSource source) +{ + /* + * If we aren't inside a transaction, or connected to a database, we + * cannot do the catalog accesses necessary to verify the config name. + * Must accept it on faith. + */ + if (IsTransactionState() && MyDatabaseId != InvalidOid) + { + ErrorSaveContext escontext = {T_ErrorSaveContext}; + List *namelist; + Oid cfgId; + HeapTuple tuple; + Form_pg_ts_config cfg; + char *buf; + + namelist = stringToQualifiedNameList(*newval, + (Node *) &escontext); + if (namelist != NIL) + cfgId = get_ts_config_oid(namelist, true); + else + cfgId = InvalidOid; /* bad name list syntax */ + + /* + * When source == PGC_S_TEST, don't throw a hard error for a + * nonexistent configuration, only a NOTICE. See comments in guc.h. + */ + if (!OidIsValid(cfgId)) + { + if (source == PGC_S_TEST) + { + ereport(NOTICE, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("text search configuration \"%s\" does not exist", *newval))); + return true; + } + else + return false; + } + + /* + * Modify the actually stored value to be fully qualified, to ensure + * later changes of search_path don't affect it. + */ + tuple = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(cfgId)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for text search configuration %u", + cfgId); + cfg = (Form_pg_ts_config) GETSTRUCT(tuple); + + buf = quote_qualified_identifier(get_namespace_name(cfg->cfgnamespace), + NameStr(cfg->cfgname)); + + ReleaseSysCache(tuple); + + /* GUC wants it guc_malloc'd not palloc'd */ + guc_free(*newval); + *newval = guc_strdup(LOG, buf); + pfree(buf); + if (!*newval) + return false; + } + + return true; +} + +/* GUC assign_hook for default_text_search_config */ +void +assign_default_text_search_config(const char *newval, void *extra) +{ + /* Just reset the cache to force a lookup on first use */ + TSCurrentConfigCache = InvalidOid; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/cache/typcache.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/cache/typcache.c new file mode 100644 index 00000000000..07bc39c7e80 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/cache/typcache.c @@ -0,0 +1,2900 @@ +/*------------------------------------------------------------------------- + * + * typcache.c + * POSTGRES type cache code + * + * The type cache exists to speed lookup of certain information about data + * types that is not directly available from a type's pg_type row. For + * example, we use a type's default btree opclass, or the default hash + * opclass if no btree opclass exists, to determine which operators should + * be used for grouping and sorting the type (GROUP BY, ORDER BY ASC/DESC). + * + * Several seemingly-odd choices have been made to support use of the type + * cache by generic array and record handling routines, such as array_eq(), + * record_cmp(), and hash_array(). Because those routines are used as index + * support operations, they cannot leak memory. To allow them to execute + * efficiently, all information that they would like to re-use across calls + * is kept in the type cache. + * + * Once created, a type cache entry lives as long as the backend does, so + * there is no need for a call to release a cache entry. If the type is + * dropped, the cache entry simply becomes wasted storage. This is not + * expected to happen often, and assuming that typcache entries are good + * permanently allows caching pointers to them in long-lived places. + * + * We have some provisions for updating cache entries if the stored data + * becomes obsolete. Core data extracted from the pg_type row is updated + * when we detect updates to pg_type. Information dependent on opclasses is + * cleared if we detect updates to pg_opclass. We also support clearing the + * tuple descriptor and operator/function parts of a rowtype's cache entry, + * since those may need to change as a consequence of ALTER TABLE. Domain + * constraint changes are also tracked properly. + * + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/utils/cache/typcache.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <limits.h> + +#include "access/hash.h" +#include "access/htup_details.h" +#include "access/nbtree.h" +#include "access/parallel.h" +#include "access/relation.h" +#include "access/session.h" +#include "access/table.h" +#include "catalog/pg_am.h" +#include "catalog/pg_constraint.h" +#include "catalog/pg_enum.h" +#include "catalog/pg_operator.h" +#include "catalog/pg_range.h" +#include "catalog/pg_type.h" +#include "commands/defrem.h" +#include "executor/executor.h" +#include "lib/dshash.h" +#include "optimizer/optimizer.h" +#include "port/pg_bitutils.h" +#include "storage/lwlock.h" +#include "utils/builtins.h" +#include "utils/catcache.h" +#include "utils/fmgroids.h" +#include "utils/inval.h" +#include "utils/lsyscache.h" +#include "utils/memutils.h" +#include "utils/rel.h" +#include "utils/snapmgr.h" +#include "utils/syscache.h" +#include "utils/typcache.h" + + +/* The main type cache hashtable searched by lookup_type_cache */ +static __thread HTAB *TypeCacheHash = NULL; + +/* List of type cache entries for domain types */ +static __thread TypeCacheEntry *firstDomainTypeEntry = NULL; + +/* Private flag bits in the TypeCacheEntry.flags field */ +#define TCFLAGS_HAVE_PG_TYPE_DATA 0x000001 +#define TCFLAGS_CHECKED_BTREE_OPCLASS 0x000002 +#define TCFLAGS_CHECKED_HASH_OPCLASS 0x000004 +#define TCFLAGS_CHECKED_EQ_OPR 0x000008 +#define TCFLAGS_CHECKED_LT_OPR 0x000010 +#define TCFLAGS_CHECKED_GT_OPR 0x000020 +#define TCFLAGS_CHECKED_CMP_PROC 0x000040 +#define TCFLAGS_CHECKED_HASH_PROC 0x000080 +#define TCFLAGS_CHECKED_HASH_EXTENDED_PROC 0x000100 +#define TCFLAGS_CHECKED_ELEM_PROPERTIES 0x000200 +#define TCFLAGS_HAVE_ELEM_EQUALITY 0x000400 +#define TCFLAGS_HAVE_ELEM_COMPARE 0x000800 +#define TCFLAGS_HAVE_ELEM_HASHING 0x001000 +#define TCFLAGS_HAVE_ELEM_EXTENDED_HASHING 0x002000 +#define TCFLAGS_CHECKED_FIELD_PROPERTIES 0x004000 +#define TCFLAGS_HAVE_FIELD_EQUALITY 0x008000 +#define TCFLAGS_HAVE_FIELD_COMPARE 0x010000 +#define TCFLAGS_HAVE_FIELD_HASHING 0x020000 +#define TCFLAGS_HAVE_FIELD_EXTENDED_HASHING 0x040000 +#define TCFLAGS_CHECKED_DOMAIN_CONSTRAINTS 0x080000 +#define TCFLAGS_DOMAIN_BASE_IS_COMPOSITE 0x100000 + +/* The flags associated with equality/comparison/hashing are all but these: */ +#define TCFLAGS_OPERATOR_FLAGS \ + (~(TCFLAGS_HAVE_PG_TYPE_DATA | \ + TCFLAGS_CHECKED_DOMAIN_CONSTRAINTS | \ + TCFLAGS_DOMAIN_BASE_IS_COMPOSITE)) + +/* + * Data stored about a domain type's constraints. Note that we do not create + * this struct for the common case of a constraint-less domain; we just set + * domainData to NULL to indicate that. + * + * Within a DomainConstraintCache, we store expression plan trees, but the + * check_exprstate fields of the DomainConstraintState nodes are just NULL. + * When needed, expression evaluation nodes are built by flat-copying the + * DomainConstraintState nodes and applying ExecInitExpr to check_expr. + * Such a node tree is not part of the DomainConstraintCache, but is + * considered to belong to a DomainConstraintRef. + */ +struct DomainConstraintCache +{ + List *constraints; /* list of DomainConstraintState nodes */ + MemoryContext dccContext; /* memory context holding all associated data */ + long dccRefCount; /* number of references to this struct */ +}; + +/* Private information to support comparisons of enum values */ +typedef struct +{ + Oid enum_oid; /* OID of one enum value */ + float4 sort_order; /* its sort position */ +} EnumItem; + +typedef struct TypeCacheEnumData +{ + Oid bitmap_base; /* OID corresponding to bit 0 of bitmapset */ + Bitmapset *sorted_values; /* Set of OIDs known to be in order */ + int num_values; /* total number of values in enum */ + EnumItem enum_values[FLEXIBLE_ARRAY_MEMBER]; +} TypeCacheEnumData; + +/* + * We use a separate table for storing the definitions of non-anonymous + * record types. Once defined, a record type will be remembered for the + * life of the backend. Subsequent uses of the "same" record type (where + * sameness means equalTupleDescs) will refer to the existing table entry. + * + * Stored record types are remembered in a linear array of TupleDescs, + * which can be indexed quickly with the assigned typmod. There is also + * a hash table to speed searches for matching TupleDescs. + */ + +typedef struct RecordCacheEntry +{ + TupleDesc tupdesc; +} RecordCacheEntry; + +/* + * To deal with non-anonymous record types that are exchanged by backends + * involved in a parallel query, we also need a shared version of the above. + */ +struct SharedRecordTypmodRegistry +{ + /* A hash table for finding a matching TupleDesc. */ + dshash_table_handle record_table_handle; + /* A hash table for finding a TupleDesc by typmod. */ + dshash_table_handle typmod_table_handle; + /* A source of new record typmod numbers. */ + pg_atomic_uint32 next_typmod; +}; + +/* + * When using shared tuple descriptors as hash table keys we need a way to be + * able to search for an equal shared TupleDesc using a backend-local + * TupleDesc. So we use this type which can hold either, and hash and compare + * functions that know how to handle both. + */ +typedef struct SharedRecordTableKey +{ + union + { + TupleDesc local_tupdesc; + dsa_pointer shared_tupdesc; + } u; + bool shared; +} SharedRecordTableKey; + +/* + * The shared version of RecordCacheEntry. This lets us look up a typmod + * using a TupleDesc which may be in local or shared memory. + */ +typedef struct SharedRecordTableEntry +{ + SharedRecordTableKey key; +} SharedRecordTableEntry; + +/* + * An entry in SharedRecordTypmodRegistry's typmod table. This lets us look + * up a TupleDesc in shared memory using a typmod. + */ +typedef struct SharedTypmodTableEntry +{ + uint32 typmod; + dsa_pointer shared_tupdesc; +} SharedTypmodTableEntry; + +/* + * A comparator function for SharedRecordTableKey. + */ +static int +shared_record_table_compare(const void *a, const void *b, size_t size, + void *arg) +{ + dsa_area *area = (dsa_area *) arg; + SharedRecordTableKey *k1 = (SharedRecordTableKey *) a; + SharedRecordTableKey *k2 = (SharedRecordTableKey *) b; + TupleDesc t1; + TupleDesc t2; + + if (k1->shared) + t1 = (TupleDesc) dsa_get_address(area, k1->u.shared_tupdesc); + else + t1 = k1->u.local_tupdesc; + + if (k2->shared) + t2 = (TupleDesc) dsa_get_address(area, k2->u.shared_tupdesc); + else + t2 = k2->u.local_tupdesc; + + return equalTupleDescs(t1, t2) ? 0 : 1; +} + +/* + * A hash function for SharedRecordTableKey. + */ +static uint32 +shared_record_table_hash(const void *a, size_t size, void *arg) +{ + dsa_area *area = (dsa_area *) arg; + SharedRecordTableKey *k = (SharedRecordTableKey *) a; + TupleDesc t; + + if (k->shared) + t = (TupleDesc) dsa_get_address(area, k->u.shared_tupdesc); + else + t = k->u.local_tupdesc; + + return hashTupleDesc(t); +} + +/* Parameters for SharedRecordTypmodRegistry's TupleDesc table. */ +static const dshash_parameters srtr_record_table_params = { + sizeof(SharedRecordTableKey), /* unused */ + sizeof(SharedRecordTableEntry), + shared_record_table_compare, + shared_record_table_hash, + LWTRANCHE_PER_SESSION_RECORD_TYPE +}; + +/* Parameters for SharedRecordTypmodRegistry's typmod hash table. */ +static const dshash_parameters srtr_typmod_table_params = { + sizeof(uint32), + sizeof(SharedTypmodTableEntry), + dshash_memcmp, + dshash_memhash, + LWTRANCHE_PER_SESSION_RECORD_TYPMOD +}; + +/* hashtable for recognizing registered record types */ +static __thread HTAB *RecordCacheHash = NULL; + +typedef struct RecordCacheArrayEntry +{ + uint64 id; + TupleDesc tupdesc; +} RecordCacheArrayEntry; + +/* array of info about registered record types, indexed by assigned typmod */ +static __thread RecordCacheArrayEntry *RecordCacheArray = NULL; +static __thread int32 RecordCacheArrayLen = 0; /* allocated length of above array */ +static __thread int32 NextRecordTypmod = 0; /* number of entries used */ + +/* + * Process-wide counter for generating unique tupledesc identifiers. + * Zero and one (INVALID_TUPLEDESC_IDENTIFIER) aren't allowed to be chosen + * as identifiers, so we start the counter at INVALID_TUPLEDESC_IDENTIFIER. + */ +static __thread uint64 tupledesc_id_counter = INVALID_TUPLEDESC_IDENTIFIER; + +void LoadRecordCacheState(RecordCacheState* state) { + RecordCacheHash = state->RecordCacheHash; + RecordCacheArray = (RecordCacheArrayEntry*)state->RecordCacheArray; + RecordCacheArrayLen = state->RecordCacheArrayLen; + NextRecordTypmod = state->NextRecordTypmod; + tupledesc_id_counter = state->tupledesc_id_counter; +} + +extern void SaveRecordCacheState(RecordCacheState* state) { + state->RecordCacheHash = RecordCacheHash; + state->RecordCacheArray = RecordCacheArray; + state->RecordCacheArrayLen = RecordCacheArrayLen; + state->NextRecordTypmod = NextRecordTypmod; + state->tupledesc_id_counter = tupledesc_id_counter; +} + +static void load_typcache_tupdesc(TypeCacheEntry *typentry); +static void load_rangetype_info(TypeCacheEntry *typentry); +static void load_multirangetype_info(TypeCacheEntry *typentry); +static void load_domaintype_info(TypeCacheEntry *typentry); +static int dcs_cmp(const void *a, const void *b); +static void decr_dcc_refcount(DomainConstraintCache *dcc); +static void dccref_deletion_callback(void *arg); +static List *prep_domain_constraints(List *constraints, MemoryContext execctx); +bool array_element_has_equality(TypeCacheEntry *typentry); +bool array_element_has_compare(TypeCacheEntry *typentry); +bool array_element_has_hashing(TypeCacheEntry *typentry); +bool array_element_has_extended_hashing(TypeCacheEntry *typentry); +static void cache_array_element_properties(TypeCacheEntry *typentry); +static bool record_fields_have_equality(TypeCacheEntry *typentry); +static bool record_fields_have_compare(TypeCacheEntry *typentry); +static bool record_fields_have_hashing(TypeCacheEntry *typentry); +static bool record_fields_have_extended_hashing(TypeCacheEntry *typentry); +static void cache_record_field_properties(TypeCacheEntry *typentry); +static bool range_element_has_hashing(TypeCacheEntry *typentry); +static bool range_element_has_extended_hashing(TypeCacheEntry *typentry); +static void cache_range_element_properties(TypeCacheEntry *typentry); +static bool multirange_element_has_hashing(TypeCacheEntry *typentry); +static bool multirange_element_has_extended_hashing(TypeCacheEntry *typentry); +static void cache_multirange_element_properties(TypeCacheEntry *typentry); +static void TypeCacheRelCallback(Datum arg, Oid relid); +static void TypeCacheTypCallback(Datum arg, int cacheid, uint32 hashvalue); +static void TypeCacheOpcCallback(Datum arg, int cacheid, uint32 hashvalue); +static void TypeCacheConstrCallback(Datum arg, int cacheid, uint32 hashvalue); +static void load_enum_cache_data(TypeCacheEntry *tcache); +static EnumItem *find_enumitem(TypeCacheEnumData *enumdata, Oid arg); +static int enum_oid_cmp(const void *left, const void *right); +static void shared_record_typmod_registry_detach(dsm_segment *segment, + Datum datum); +static TupleDesc find_or_make_matching_shared_tupledesc(TupleDesc tupdesc); +static dsa_pointer share_tupledesc(dsa_area *area, TupleDesc tupdesc, + uint32 typmod); + + +/* + * lookup_type_cache + * + * Fetch the type cache entry for the specified datatype, and make sure that + * all the fields requested by bits in 'flags' are valid. + * + * The result is never NULL --- we will ereport() if the passed type OID is + * invalid. Note however that we may fail to find one or more of the + * values requested by 'flags'; the caller needs to check whether the fields + * are InvalidOid or not. + */ +TypeCacheEntry * +lookup_type_cache_original(Oid type_id, int flags) +{ + TypeCacheEntry *typentry; + bool found; + + if (TypeCacheHash == NULL) + { + /* First time through: initialize the hash table */ + HASHCTL ctl; + + ctl.keysize = sizeof(Oid); + ctl.entrysize = sizeof(TypeCacheEntry); + TypeCacheHash = hash_create("Type information cache", 64, + &ctl, HASH_ELEM | HASH_BLOBS); + + /* Also set up callbacks for SI invalidations */ + CacheRegisterRelcacheCallback(TypeCacheRelCallback, (Datum) 0); + CacheRegisterSyscacheCallback(TYPEOID, TypeCacheTypCallback, (Datum) 0); + CacheRegisterSyscacheCallback(CLAOID, TypeCacheOpcCallback, (Datum) 0); + CacheRegisterSyscacheCallback(CONSTROID, TypeCacheConstrCallback, (Datum) 0); + + /* Also make sure CacheMemoryContext exists */ + if (!CacheMemoryContext) + CreateCacheMemoryContext(); + } + + /* Try to look up an existing entry */ + typentry = (TypeCacheEntry *) hash_search(TypeCacheHash, + &type_id, + HASH_FIND, NULL); + if (typentry == NULL) + { + /* + * If we didn't find one, we want to make one. But first look up the + * pg_type row, just to make sure we don't make a cache entry for an + * invalid type OID. If the type OID is not valid, present a + * user-facing error, since some code paths such as domain_in() allow + * this function to be reached with a user-supplied OID. + */ + HeapTuple tp; + Form_pg_type typtup; + + tp = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type_id)); + if (!HeapTupleIsValid(tp)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("type with OID %u does not exist", type_id))); + typtup = (Form_pg_type) GETSTRUCT(tp); + if (!typtup->typisdefined) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("type \"%s\" is only a shell", + NameStr(typtup->typname)))); + + /* Now make the typcache entry */ + typentry = (TypeCacheEntry *) hash_search(TypeCacheHash, + &type_id, + HASH_ENTER, &found); + Assert(!found); /* it wasn't there a moment ago */ + + MemSet(typentry, 0, sizeof(TypeCacheEntry)); + + /* These fields can never change, by definition */ + typentry->type_id = type_id; + typentry->type_id_hash = GetSysCacheHashValue1(TYPEOID, + ObjectIdGetDatum(type_id)); + + /* Keep this part in sync with the code below */ + typentry->typlen = typtup->typlen; + typentry->typbyval = typtup->typbyval; + typentry->typalign = typtup->typalign; + typentry->typstorage = typtup->typstorage; + typentry->typtype = typtup->typtype; + typentry->typrelid = typtup->typrelid; + typentry->typsubscript = typtup->typsubscript; + typentry->typelem = typtup->typelem; + typentry->typcollation = typtup->typcollation; + typentry->flags |= TCFLAGS_HAVE_PG_TYPE_DATA; + + /* If it's a domain, immediately thread it into the domain cache list */ + if (typentry->typtype == TYPTYPE_DOMAIN) + { + typentry->nextDomain = firstDomainTypeEntry; + firstDomainTypeEntry = typentry; + } + + ReleaseSysCache(tp); + } + else if (!(typentry->flags & TCFLAGS_HAVE_PG_TYPE_DATA)) + { + /* + * We have an entry, but its pg_type row got changed, so reload the + * data obtained directly from pg_type. + */ + HeapTuple tp; + Form_pg_type typtup; + + tp = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type_id)); + if (!HeapTupleIsValid(tp)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("type with OID %u does not exist", type_id))); + typtup = (Form_pg_type) GETSTRUCT(tp); + if (!typtup->typisdefined) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("type \"%s\" is only a shell", + NameStr(typtup->typname)))); + + /* + * Keep this part in sync with the code above. Many of these fields + * shouldn't ever change, particularly typtype, but copy 'em anyway. + */ + typentry->typlen = typtup->typlen; + typentry->typbyval = typtup->typbyval; + typentry->typalign = typtup->typalign; + typentry->typstorage = typtup->typstorage; + typentry->typtype = typtup->typtype; + typentry->typrelid = typtup->typrelid; + typentry->typsubscript = typtup->typsubscript; + typentry->typelem = typtup->typelem; + typentry->typcollation = typtup->typcollation; + typentry->flags |= TCFLAGS_HAVE_PG_TYPE_DATA; + + ReleaseSysCache(tp); + } + + /* + * Look up opclasses if we haven't already and any dependent info is + * requested. + */ + if ((flags & (TYPECACHE_EQ_OPR | TYPECACHE_LT_OPR | TYPECACHE_GT_OPR | + TYPECACHE_CMP_PROC | + TYPECACHE_EQ_OPR_FINFO | TYPECACHE_CMP_PROC_FINFO | + TYPECACHE_BTREE_OPFAMILY)) && + !(typentry->flags & TCFLAGS_CHECKED_BTREE_OPCLASS)) + { + Oid opclass; + + opclass = GetDefaultOpClass(type_id, BTREE_AM_OID); + if (OidIsValid(opclass)) + { + typentry->btree_opf = get_opclass_family(opclass); + typentry->btree_opintype = get_opclass_input_type(opclass); + } + else + { + typentry->btree_opf = typentry->btree_opintype = InvalidOid; + } + + /* + * Reset information derived from btree opclass. Note in particular + * that we'll redetermine the eq_opr even if we previously found one; + * this matters in case a btree opclass has been added to a type that + * previously had only a hash opclass. + */ + typentry->flags &= ~(TCFLAGS_CHECKED_EQ_OPR | + TCFLAGS_CHECKED_LT_OPR | + TCFLAGS_CHECKED_GT_OPR | + TCFLAGS_CHECKED_CMP_PROC); + typentry->flags |= TCFLAGS_CHECKED_BTREE_OPCLASS; + } + + /* + * If we need to look up equality operator, and there's no btree opclass, + * force lookup of hash opclass. + */ + if ((flags & (TYPECACHE_EQ_OPR | TYPECACHE_EQ_OPR_FINFO)) && + !(typentry->flags & TCFLAGS_CHECKED_EQ_OPR) && + typentry->btree_opf == InvalidOid) + flags |= TYPECACHE_HASH_OPFAMILY; + + if ((flags & (TYPECACHE_HASH_PROC | TYPECACHE_HASH_PROC_FINFO | + TYPECACHE_HASH_EXTENDED_PROC | + TYPECACHE_HASH_EXTENDED_PROC_FINFO | + TYPECACHE_HASH_OPFAMILY)) && + !(typentry->flags & TCFLAGS_CHECKED_HASH_OPCLASS)) + { + Oid opclass; + + opclass = GetDefaultOpClass(type_id, HASH_AM_OID); + if (OidIsValid(opclass)) + { + typentry->hash_opf = get_opclass_family(opclass); + typentry->hash_opintype = get_opclass_input_type(opclass); + } + else + { + typentry->hash_opf = typentry->hash_opintype = InvalidOid; + } + + /* + * Reset information derived from hash opclass. We do *not* reset the + * eq_opr; if we already found one from the btree opclass, that + * decision is still good. + */ + typentry->flags &= ~(TCFLAGS_CHECKED_HASH_PROC | + TCFLAGS_CHECKED_HASH_EXTENDED_PROC); + typentry->flags |= TCFLAGS_CHECKED_HASH_OPCLASS; + } + + /* + * Look for requested operators and functions, if we haven't already. + */ + if ((flags & (TYPECACHE_EQ_OPR | TYPECACHE_EQ_OPR_FINFO)) && + !(typentry->flags & TCFLAGS_CHECKED_EQ_OPR)) + { + Oid eq_opr = InvalidOid; + + if (typentry->btree_opf != InvalidOid) + eq_opr = get_opfamily_member(typentry->btree_opf, + typentry->btree_opintype, + typentry->btree_opintype, + BTEqualStrategyNumber); + if (eq_opr == InvalidOid && + typentry->hash_opf != InvalidOid) + eq_opr = get_opfamily_member(typentry->hash_opf, + typentry->hash_opintype, + typentry->hash_opintype, + HTEqualStrategyNumber); + + /* + * If the proposed equality operator is array_eq or record_eq, check + * to see if the element type or column types support equality. If + * not, array_eq or record_eq would fail at runtime, so we don't want + * to report that the type has equality. (We can omit similar + * checking for ranges and multiranges because ranges can't be created + * in the first place unless their subtypes support equality.) + */ + if (eq_opr == ARRAY_EQ_OP && + !array_element_has_equality(typentry)) + eq_opr = InvalidOid; + else if (eq_opr == RECORD_EQ_OP && + !record_fields_have_equality(typentry)) + eq_opr = InvalidOid; + + /* Force update of eq_opr_finfo only if we're changing state */ + if (typentry->eq_opr != eq_opr) + typentry->eq_opr_finfo.fn_oid = InvalidOid; + + typentry->eq_opr = eq_opr; + + /* + * Reset info about hash functions whenever we pick up new info about + * equality operator. This is so we can ensure that the hash + * functions match the operator. + */ + typentry->flags &= ~(TCFLAGS_CHECKED_HASH_PROC | + TCFLAGS_CHECKED_HASH_EXTENDED_PROC); + typentry->flags |= TCFLAGS_CHECKED_EQ_OPR; + } + if ((flags & TYPECACHE_LT_OPR) && + !(typentry->flags & TCFLAGS_CHECKED_LT_OPR)) + { + Oid lt_opr = InvalidOid; + + if (typentry->btree_opf != InvalidOid) + lt_opr = get_opfamily_member(typentry->btree_opf, + typentry->btree_opintype, + typentry->btree_opintype, + BTLessStrategyNumber); + + /* + * As above, make sure array_cmp or record_cmp will succeed; but again + * we need no special check for ranges or multiranges. + */ + if (lt_opr == ARRAY_LT_OP && + !array_element_has_compare(typentry)) + lt_opr = InvalidOid; + else if (lt_opr == RECORD_LT_OP && + !record_fields_have_compare(typentry)) + lt_opr = InvalidOid; + + typentry->lt_opr = lt_opr; + typentry->flags |= TCFLAGS_CHECKED_LT_OPR; + } + if ((flags & TYPECACHE_GT_OPR) && + !(typentry->flags & TCFLAGS_CHECKED_GT_OPR)) + { + Oid gt_opr = InvalidOid; + + if (typentry->btree_opf != InvalidOid) + gt_opr = get_opfamily_member(typentry->btree_opf, + typentry->btree_opintype, + typentry->btree_opintype, + BTGreaterStrategyNumber); + + /* + * As above, make sure array_cmp or record_cmp will succeed; but again + * we need no special check for ranges or multiranges. + */ + if (gt_opr == ARRAY_GT_OP && + !array_element_has_compare(typentry)) + gt_opr = InvalidOid; + else if (gt_opr == RECORD_GT_OP && + !record_fields_have_compare(typentry)) + gt_opr = InvalidOid; + + typentry->gt_opr = gt_opr; + typentry->flags |= TCFLAGS_CHECKED_GT_OPR; + } + if ((flags & (TYPECACHE_CMP_PROC | TYPECACHE_CMP_PROC_FINFO)) && + !(typentry->flags & TCFLAGS_CHECKED_CMP_PROC)) + { + Oid cmp_proc = InvalidOid; + + if (typentry->btree_opf != InvalidOid) + cmp_proc = get_opfamily_proc(typentry->btree_opf, + typentry->btree_opintype, + typentry->btree_opintype, + BTORDER_PROC); + + /* + * As above, make sure array_cmp or record_cmp will succeed; but again + * we need no special check for ranges or multiranges. + */ + if (cmp_proc == F_BTARRAYCMP && + !array_element_has_compare(typentry)) + cmp_proc = InvalidOid; + else if (cmp_proc == F_BTRECORDCMP && + !record_fields_have_compare(typentry)) + cmp_proc = InvalidOid; + + /* Force update of cmp_proc_finfo only if we're changing state */ + if (typentry->cmp_proc != cmp_proc) + typentry->cmp_proc_finfo.fn_oid = InvalidOid; + + typentry->cmp_proc = cmp_proc; + typentry->flags |= TCFLAGS_CHECKED_CMP_PROC; + } + if ((flags & (TYPECACHE_HASH_PROC | TYPECACHE_HASH_PROC_FINFO)) && + !(typentry->flags & TCFLAGS_CHECKED_HASH_PROC)) + { + Oid hash_proc = InvalidOid; + + /* + * We insist that the eq_opr, if one has been determined, match the + * hash opclass; else report there is no hash function. + */ + if (typentry->hash_opf != InvalidOid && + (!OidIsValid(typentry->eq_opr) || + typentry->eq_opr == get_opfamily_member(typentry->hash_opf, + typentry->hash_opintype, + typentry->hash_opintype, + HTEqualStrategyNumber))) + hash_proc = get_opfamily_proc(typentry->hash_opf, + typentry->hash_opintype, + typentry->hash_opintype, + HASHSTANDARD_PROC); + + /* + * As above, make sure hash_array, hash_record, or hash_range will + * succeed. + */ + if (hash_proc == F_HASH_ARRAY && + !array_element_has_hashing(typentry)) + hash_proc = InvalidOid; + else if (hash_proc == F_HASH_RECORD && + !record_fields_have_hashing(typentry)) + hash_proc = InvalidOid; + else if (hash_proc == F_HASH_RANGE && + !range_element_has_hashing(typentry)) + hash_proc = InvalidOid; + + /* + * Likewise for hash_multirange. + */ + if (hash_proc == F_HASH_MULTIRANGE && + !multirange_element_has_hashing(typentry)) + hash_proc = InvalidOid; + + /* Force update of hash_proc_finfo only if we're changing state */ + if (typentry->hash_proc != hash_proc) + typentry->hash_proc_finfo.fn_oid = InvalidOid; + + typentry->hash_proc = hash_proc; + typentry->flags |= TCFLAGS_CHECKED_HASH_PROC; + } + if ((flags & (TYPECACHE_HASH_EXTENDED_PROC | + TYPECACHE_HASH_EXTENDED_PROC_FINFO)) && + !(typentry->flags & TCFLAGS_CHECKED_HASH_EXTENDED_PROC)) + { + Oid hash_extended_proc = InvalidOid; + + /* + * We insist that the eq_opr, if one has been determined, match the + * hash opclass; else report there is no hash function. + */ + if (typentry->hash_opf != InvalidOid && + (!OidIsValid(typentry->eq_opr) || + typentry->eq_opr == get_opfamily_member(typentry->hash_opf, + typentry->hash_opintype, + typentry->hash_opintype, + HTEqualStrategyNumber))) + hash_extended_proc = get_opfamily_proc(typentry->hash_opf, + typentry->hash_opintype, + typentry->hash_opintype, + HASHEXTENDED_PROC); + + /* + * As above, make sure hash_array_extended, hash_record_extended, or + * hash_range_extended will succeed. + */ + if (hash_extended_proc == F_HASH_ARRAY_EXTENDED && + !array_element_has_extended_hashing(typentry)) + hash_extended_proc = InvalidOid; + else if (hash_extended_proc == F_HASH_RECORD_EXTENDED && + !record_fields_have_extended_hashing(typentry)) + hash_extended_proc = InvalidOid; + else if (hash_extended_proc == F_HASH_RANGE_EXTENDED && + !range_element_has_extended_hashing(typentry)) + hash_extended_proc = InvalidOid; + + /* + * Likewise for hash_multirange_extended. + */ + if (hash_extended_proc == F_HASH_MULTIRANGE_EXTENDED && + !multirange_element_has_extended_hashing(typentry)) + hash_extended_proc = InvalidOid; + + /* Force update of proc finfo only if we're changing state */ + if (typentry->hash_extended_proc != hash_extended_proc) + typentry->hash_extended_proc_finfo.fn_oid = InvalidOid; + + typentry->hash_extended_proc = hash_extended_proc; + typentry->flags |= TCFLAGS_CHECKED_HASH_EXTENDED_PROC; + } + + /* + * Set up fmgr lookup info as requested + * + * Note: we tell fmgr the finfo structures live in CacheMemoryContext, + * which is not quite right (they're really in the hash table's private + * memory context) but this will do for our purposes. + * + * Note: the code above avoids invalidating the finfo structs unless the + * referenced operator/function OID actually changes. This is to prevent + * unnecessary leakage of any subsidiary data attached to an finfo, since + * that would cause session-lifespan memory leaks. + */ + if ((flags & TYPECACHE_EQ_OPR_FINFO) && + typentry->eq_opr_finfo.fn_oid == InvalidOid && + typentry->eq_opr != InvalidOid) + { + Oid eq_opr_func; + + eq_opr_func = get_opcode(typentry->eq_opr); + if (eq_opr_func != InvalidOid) + fmgr_info_cxt(eq_opr_func, &typentry->eq_opr_finfo, + CacheMemoryContext); + } + if ((flags & TYPECACHE_CMP_PROC_FINFO) && + typentry->cmp_proc_finfo.fn_oid == InvalidOid && + typentry->cmp_proc != InvalidOid) + { + fmgr_info_cxt(typentry->cmp_proc, &typentry->cmp_proc_finfo, + CacheMemoryContext); + } + if ((flags & TYPECACHE_HASH_PROC_FINFO) && + typentry->hash_proc_finfo.fn_oid == InvalidOid && + typentry->hash_proc != InvalidOid) + { + fmgr_info_cxt(typentry->hash_proc, &typentry->hash_proc_finfo, + CacheMemoryContext); + } + if ((flags & TYPECACHE_HASH_EXTENDED_PROC_FINFO) && + typentry->hash_extended_proc_finfo.fn_oid == InvalidOid && + typentry->hash_extended_proc != InvalidOid) + { + fmgr_info_cxt(typentry->hash_extended_proc, + &typentry->hash_extended_proc_finfo, + CacheMemoryContext); + } + + /* + * If it's a composite type (row type), get tupdesc if requested + */ + if ((flags & TYPECACHE_TUPDESC) && + typentry->tupDesc == NULL && + typentry->typtype == TYPTYPE_COMPOSITE) + { + load_typcache_tupdesc(typentry); + } + + /* + * If requested, get information about a range type + * + * This includes making sure that the basic info about the range element + * type is up-to-date. + */ + if ((flags & TYPECACHE_RANGE_INFO) && + typentry->typtype == TYPTYPE_RANGE) + { + if (typentry->rngelemtype == NULL) + load_rangetype_info(typentry); + else if (!(typentry->rngelemtype->flags & TCFLAGS_HAVE_PG_TYPE_DATA)) + (void) lookup_type_cache(typentry->rngelemtype->type_id, 0); + } + + /* + * If requested, get information about a multirange type + */ + if ((flags & TYPECACHE_MULTIRANGE_INFO) && + typentry->rngtype == NULL && + typentry->typtype == TYPTYPE_MULTIRANGE) + { + load_multirangetype_info(typentry); + } + + /* + * If requested, get information about a domain type + */ + if ((flags & TYPECACHE_DOMAIN_BASE_INFO) && + typentry->domainBaseType == InvalidOid && + typentry->typtype == TYPTYPE_DOMAIN) + { + typentry->domainBaseTypmod = -1; + typentry->domainBaseType = + getBaseTypeAndTypmod(type_id, &typentry->domainBaseTypmod); + } + if ((flags & TYPECACHE_DOMAIN_CONSTR_INFO) && + (typentry->flags & TCFLAGS_CHECKED_DOMAIN_CONSTRAINTS) == 0 && + typentry->typtype == TYPTYPE_DOMAIN) + { + load_domaintype_info(typentry); + } + + return typentry; +} + +/* + * load_typcache_tupdesc --- helper routine to set up composite type's tupDesc + */ +static void +load_typcache_tupdesc(TypeCacheEntry *typentry) +{ + Relation rel; + + if (!OidIsValid(typentry->typrelid)) /* should not happen */ + elog(ERROR, "invalid typrelid for composite type %u", + typentry->type_id); + rel = relation_open(typentry->typrelid, AccessShareLock); + Assert(rel->rd_rel->reltype == typentry->type_id); + + /* + * Link to the tupdesc and increment its refcount (we assert it's a + * refcounted descriptor). We don't use IncrTupleDescRefCount() for this, + * because the reference mustn't be entered in the current resource owner; + * it can outlive the current query. + */ + typentry->tupDesc = RelationGetDescr(rel); + + Assert(typentry->tupDesc->tdrefcount > 0); + typentry->tupDesc->tdrefcount++; + + /* + * In future, we could take some pains to not change tupDesc_identifier if + * the tupdesc didn't really change; but for now it's not worth it. + */ + typentry->tupDesc_identifier = ++tupledesc_id_counter; + + relation_close(rel, AccessShareLock); +} + +/* + * load_rangetype_info --- helper routine to set up range type information + */ +static void +load_rangetype_info(TypeCacheEntry *typentry) +{ + Form_pg_range pg_range; + HeapTuple tup; + Oid subtypeOid; + Oid opclassOid; + Oid canonicalOid; + Oid subdiffOid; + Oid opfamilyOid; + Oid opcintype; + Oid cmpFnOid; + + /* get information from pg_range */ + tup = SearchSysCache1(RANGETYPE, ObjectIdGetDatum(typentry->type_id)); + /* should not fail, since we already checked typtype ... */ + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for range type %u", + typentry->type_id); + pg_range = (Form_pg_range) GETSTRUCT(tup); + + subtypeOid = pg_range->rngsubtype; + typentry->rng_collation = pg_range->rngcollation; + opclassOid = pg_range->rngsubopc; + canonicalOid = pg_range->rngcanonical; + subdiffOid = pg_range->rngsubdiff; + + ReleaseSysCache(tup); + + /* get opclass properties and look up the comparison function */ + opfamilyOid = get_opclass_family(opclassOid); + opcintype = get_opclass_input_type(opclassOid); + + cmpFnOid = get_opfamily_proc(opfamilyOid, opcintype, opcintype, + BTORDER_PROC); + if (!RegProcedureIsValid(cmpFnOid)) + elog(ERROR, "missing support function %d(%u,%u) in opfamily %u", + BTORDER_PROC, opcintype, opcintype, opfamilyOid); + + /* set up cached fmgrinfo structs */ + fmgr_info_cxt(cmpFnOid, &typentry->rng_cmp_proc_finfo, + CacheMemoryContext); + if (OidIsValid(canonicalOid)) + fmgr_info_cxt(canonicalOid, &typentry->rng_canonical_finfo, + CacheMemoryContext); + if (OidIsValid(subdiffOid)) + fmgr_info_cxt(subdiffOid, &typentry->rng_subdiff_finfo, + CacheMemoryContext); + + /* Lastly, set up link to the element type --- this marks data valid */ + typentry->rngelemtype = lookup_type_cache(subtypeOid, 0); +} + +/* + * load_multirangetype_info --- helper routine to set up multirange type + * information + */ +static void +load_multirangetype_info(TypeCacheEntry *typentry) +{ + Oid rangetypeOid; + + rangetypeOid = get_multirange_range(typentry->type_id); + if (!OidIsValid(rangetypeOid)) + elog(ERROR, "cache lookup failed for multirange type %u", + typentry->type_id); + + typentry->rngtype = lookup_type_cache(rangetypeOid, TYPECACHE_RANGE_INFO); +} + +/* + * load_domaintype_info --- helper routine to set up domain constraint info + * + * Note: we assume we're called in a relatively short-lived context, so it's + * okay to leak data into the current context while scanning pg_constraint. + * We build the new DomainConstraintCache data in a context underneath + * CurrentMemoryContext, and reparent it under CacheMemoryContext when + * complete. + */ +static void +load_domaintype_info(TypeCacheEntry *typentry) +{ + Oid typeOid = typentry->type_id; + DomainConstraintCache *dcc; + bool notNull = false; + DomainConstraintState **ccons; + int cconslen; + Relation conRel; + MemoryContext oldcxt; + + /* + * If we're here, any existing constraint info is stale, so release it. + * For safety, be sure to null the link before trying to delete the data. + */ + if (typentry->domainData) + { + dcc = typentry->domainData; + typentry->domainData = NULL; + decr_dcc_refcount(dcc); + } + + /* + * We try to optimize the common case of no domain constraints, so don't + * create the dcc object and context until we find a constraint. Likewise + * for the temp sorting array. + */ + dcc = NULL; + ccons = NULL; + cconslen = 0; + + /* + * Scan pg_constraint for relevant constraints. We want to find + * constraints for not just this domain, but any ancestor domains, so the + * outer loop crawls up the domain stack. + */ + conRel = table_open(ConstraintRelationId, AccessShareLock); + + for (;;) + { + HeapTuple tup; + HeapTuple conTup; + Form_pg_type typTup; + int nccons = 0; + ScanKeyData key[1]; + SysScanDesc scan; + + tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typeOid)); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for type %u", typeOid); + typTup = (Form_pg_type) GETSTRUCT(tup); + + if (typTup->typtype != TYPTYPE_DOMAIN) + { + /* Not a domain, so done */ + ReleaseSysCache(tup); + break; + } + + /* Test for NOT NULL Constraint */ + if (typTup->typnotnull) + notNull = true; + + /* Look for CHECK Constraints on this domain */ + ScanKeyInit(&key[0], + Anum_pg_constraint_contypid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(typeOid)); + + scan = systable_beginscan(conRel, ConstraintTypidIndexId, true, + NULL, 1, key); + + while (HeapTupleIsValid(conTup = systable_getnext(scan))) + { + Form_pg_constraint c = (Form_pg_constraint) GETSTRUCT(conTup); + Datum val; + bool isNull; + char *constring; + Expr *check_expr; + DomainConstraintState *r; + + /* Ignore non-CHECK constraints (presently, shouldn't be any) */ + if (c->contype != CONSTRAINT_CHECK) + continue; + + /* Not expecting conbin to be NULL, but we'll test for it anyway */ + val = fastgetattr(conTup, Anum_pg_constraint_conbin, + conRel->rd_att, &isNull); + if (isNull) + elog(ERROR, "domain \"%s\" constraint \"%s\" has NULL conbin", + NameStr(typTup->typname), NameStr(c->conname)); + + /* Convert conbin to C string in caller context */ + constring = TextDatumGetCString(val); + + /* Create the DomainConstraintCache object and context if needed */ + if (dcc == NULL) + { + MemoryContext cxt; + + cxt = AllocSetContextCreate(CurrentMemoryContext, + "Domain constraints", + ALLOCSET_SMALL_SIZES); + dcc = (DomainConstraintCache *) + MemoryContextAlloc(cxt, sizeof(DomainConstraintCache)); + dcc->constraints = NIL; + dcc->dccContext = cxt; + dcc->dccRefCount = 0; + } + + /* Create node trees in DomainConstraintCache's context */ + oldcxt = MemoryContextSwitchTo(dcc->dccContext); + + check_expr = (Expr *) stringToNode(constring); + + /* + * Plan the expression, since ExecInitExpr will expect that. + * + * Note: caching the result of expression_planner() is not very + * good practice. Ideally we'd use a CachedExpression here so + * that we would react promptly to, eg, changes in inlined + * functions. However, because we don't support mutable domain + * CHECK constraints, it's not really clear that it's worth the + * extra overhead to do that. + */ + check_expr = expression_planner(check_expr); + + r = makeNode(DomainConstraintState); + r->constrainttype = DOM_CONSTRAINT_CHECK; + r->name = pstrdup(NameStr(c->conname)); + r->check_expr = check_expr; + r->check_exprstate = NULL; + + MemoryContextSwitchTo(oldcxt); + + /* Accumulate constraints in an array, for sorting below */ + if (ccons == NULL) + { + cconslen = 8; + ccons = (DomainConstraintState **) + palloc(cconslen * sizeof(DomainConstraintState *)); + } + else if (nccons >= cconslen) + { + cconslen *= 2; + ccons = (DomainConstraintState **) + repalloc(ccons, cconslen * sizeof(DomainConstraintState *)); + } + ccons[nccons++] = r; + } + + systable_endscan(scan); + + if (nccons > 0) + { + /* + * Sort the items for this domain, so that CHECKs are applied in a + * deterministic order. + */ + if (nccons > 1) + qsort(ccons, nccons, sizeof(DomainConstraintState *), dcs_cmp); + + /* + * Now attach them to the overall list. Use lcons() here because + * constraints of parent domains should be applied earlier. + */ + oldcxt = MemoryContextSwitchTo(dcc->dccContext); + while (nccons > 0) + dcc->constraints = lcons(ccons[--nccons], dcc->constraints); + MemoryContextSwitchTo(oldcxt); + } + + /* loop to next domain in stack */ + typeOid = typTup->typbasetype; + ReleaseSysCache(tup); + } + + table_close(conRel, AccessShareLock); + + /* + * Only need to add one NOT NULL check regardless of how many domains in + * the stack request it. + */ + if (notNull) + { + DomainConstraintState *r; + + /* Create the DomainConstraintCache object and context if needed */ + if (dcc == NULL) + { + MemoryContext cxt; + + cxt = AllocSetContextCreate(CurrentMemoryContext, + "Domain constraints", + ALLOCSET_SMALL_SIZES); + dcc = (DomainConstraintCache *) + MemoryContextAlloc(cxt, sizeof(DomainConstraintCache)); + dcc->constraints = NIL; + dcc->dccContext = cxt; + dcc->dccRefCount = 0; + } + + /* Create node trees in DomainConstraintCache's context */ + oldcxt = MemoryContextSwitchTo(dcc->dccContext); + + r = makeNode(DomainConstraintState); + + r->constrainttype = DOM_CONSTRAINT_NOTNULL; + r->name = pstrdup("NOT NULL"); + r->check_expr = NULL; + r->check_exprstate = NULL; + + /* lcons to apply the nullness check FIRST */ + dcc->constraints = lcons(r, dcc->constraints); + + MemoryContextSwitchTo(oldcxt); + } + + /* + * If we made a constraint object, move it into CacheMemoryContext and + * attach it to the typcache entry. + */ + if (dcc) + { + MemoryContextSetParent(dcc->dccContext, CacheMemoryContext); + typentry->domainData = dcc; + dcc->dccRefCount++; /* count the typcache's reference */ + } + + /* Either way, the typcache entry's domain data is now valid. */ + typentry->flags |= TCFLAGS_CHECKED_DOMAIN_CONSTRAINTS; +} + +/* + * qsort comparator to sort DomainConstraintState pointers by name + */ +static int +dcs_cmp(const void *a, const void *b) +{ + const DomainConstraintState *const *ca = (const DomainConstraintState *const *) a; + const DomainConstraintState *const *cb = (const DomainConstraintState *const *) b; + + return strcmp((*ca)->name, (*cb)->name); +} + +/* + * decr_dcc_refcount --- decrement a DomainConstraintCache's refcount, + * and free it if no references remain + */ +static void +decr_dcc_refcount(DomainConstraintCache *dcc) +{ + Assert(dcc->dccRefCount > 0); + if (--(dcc->dccRefCount) <= 0) + MemoryContextDelete(dcc->dccContext); +} + +/* + * Context reset/delete callback for a DomainConstraintRef + */ +static void +dccref_deletion_callback(void *arg) +{ + DomainConstraintRef *ref = (DomainConstraintRef *) arg; + DomainConstraintCache *dcc = ref->dcc; + + /* Paranoia --- be sure link is nulled before trying to release */ + if (dcc) + { + ref->constraints = NIL; + ref->dcc = NULL; + decr_dcc_refcount(dcc); + } +} + +/* + * prep_domain_constraints --- prepare domain constraints for execution + * + * The expression trees stored in the DomainConstraintCache's list are + * converted to executable expression state trees stored in execctx. + */ +static List * +prep_domain_constraints(List *constraints, MemoryContext execctx) +{ + List *result = NIL; + MemoryContext oldcxt; + ListCell *lc; + + oldcxt = MemoryContextSwitchTo(execctx); + + foreach(lc, constraints) + { + DomainConstraintState *r = (DomainConstraintState *) lfirst(lc); + DomainConstraintState *newr; + + newr = makeNode(DomainConstraintState); + newr->constrainttype = r->constrainttype; + newr->name = r->name; + newr->check_expr = r->check_expr; + newr->check_exprstate = ExecInitExpr(r->check_expr, NULL); + + result = lappend(result, newr); + } + + MemoryContextSwitchTo(oldcxt); + + return result; +} + +/* + * InitDomainConstraintRef --- initialize a DomainConstraintRef struct + * + * Caller must tell us the MemoryContext in which the DomainConstraintRef + * lives. The ref will be cleaned up when that context is reset/deleted. + * + * Caller must also tell us whether it wants check_exprstate fields to be + * computed in the DomainConstraintState nodes attached to this ref. + * If it doesn't, we need not make a copy of the DomainConstraintState list. + */ +void +InitDomainConstraintRef(Oid type_id, DomainConstraintRef *ref, + MemoryContext refctx, bool need_exprstate) +{ + /* Look up the typcache entry --- we assume it survives indefinitely */ + ref->tcache = lookup_type_cache(type_id, TYPECACHE_DOMAIN_CONSTR_INFO); + ref->need_exprstate = need_exprstate; + /* For safety, establish the callback before acquiring a refcount */ + ref->refctx = refctx; + ref->dcc = NULL; + ref->callback.func = dccref_deletion_callback; + ref->callback.arg = (void *) ref; + MemoryContextRegisterResetCallback(refctx, &ref->callback); + /* Acquire refcount if there are constraints, and set up exported list */ + if (ref->tcache->domainData) + { + ref->dcc = ref->tcache->domainData; + ref->dcc->dccRefCount++; + if (ref->need_exprstate) + ref->constraints = prep_domain_constraints(ref->dcc->constraints, + ref->refctx); + else + ref->constraints = ref->dcc->constraints; + } + else + ref->constraints = NIL; +} + +/* + * UpdateDomainConstraintRef --- recheck validity of domain constraint info + * + * If the domain's constraint set changed, ref->constraints is updated to + * point at a new list of cached constraints. + * + * In the normal case where nothing happened to the domain, this is cheap + * enough that it's reasonable (and expected) to check before *each* use + * of the constraint info. + */ +void +UpdateDomainConstraintRef(DomainConstraintRef *ref) +{ + TypeCacheEntry *typentry = ref->tcache; + + /* Make sure typcache entry's data is up to date */ + if ((typentry->flags & TCFLAGS_CHECKED_DOMAIN_CONSTRAINTS) == 0 && + typentry->typtype == TYPTYPE_DOMAIN) + load_domaintype_info(typentry); + + /* Transfer to ref object if there's new info, adjusting refcounts */ + if (ref->dcc != typentry->domainData) + { + /* Paranoia --- be sure link is nulled before trying to release */ + DomainConstraintCache *dcc = ref->dcc; + + if (dcc) + { + /* + * Note: we just leak the previous list of executable domain + * constraints. Alternatively, we could keep those in a child + * context of ref->refctx and free that context at this point. + * However, in practice this code path will be taken so seldom + * that the extra bookkeeping for a child context doesn't seem + * worthwhile; we'll just allow a leak for the lifespan of refctx. + */ + ref->constraints = NIL; + ref->dcc = NULL; + decr_dcc_refcount(dcc); + } + dcc = typentry->domainData; + if (dcc) + { + ref->dcc = dcc; + dcc->dccRefCount++; + if (ref->need_exprstate) + ref->constraints = prep_domain_constraints(dcc->constraints, + ref->refctx); + else + ref->constraints = dcc->constraints; + } + } +} + +/* + * DomainHasConstraints --- utility routine to check if a domain has constraints + * + * This is defined to return false, not fail, if type is not a domain. + */ +bool +DomainHasConstraints(Oid type_id) +{ + TypeCacheEntry *typentry; + + /* + * Note: a side effect is to cause the typcache's domain data to become + * valid. This is fine since we'll likely need it soon if there is any. + */ + typentry = lookup_type_cache(type_id, TYPECACHE_DOMAIN_CONSTR_INFO); + + return (typentry->domainData != NULL); +} + + +/* + * array_element_has_equality and friends are helper routines to check + * whether we should believe that array_eq and related functions will work + * on the given array type or composite type. + * + * The logic above may call these repeatedly on the same type entry, so we + * make use of the typentry->flags field to cache the results once known. + * Also, we assume that we'll probably want all these facts about the type + * if we want any, so we cache them all using only one lookup of the + * component datatype(s). + */ + +bool +array_element_has_equality(TypeCacheEntry *typentry) +{ + if (!(typentry->flags & TCFLAGS_CHECKED_ELEM_PROPERTIES)) + cache_array_element_properties(typentry); + return (typentry->flags & TCFLAGS_HAVE_ELEM_EQUALITY) != 0; +} + +bool +array_element_has_compare(TypeCacheEntry *typentry) +{ + if (!(typentry->flags & TCFLAGS_CHECKED_ELEM_PROPERTIES)) + cache_array_element_properties(typentry); + return (typentry->flags & TCFLAGS_HAVE_ELEM_COMPARE) != 0; +} + +bool +array_element_has_hashing(TypeCacheEntry *typentry) +{ + if (!(typentry->flags & TCFLAGS_CHECKED_ELEM_PROPERTIES)) + cache_array_element_properties(typentry); + return (typentry->flags & TCFLAGS_HAVE_ELEM_HASHING) != 0; +} + +bool +array_element_has_extended_hashing(TypeCacheEntry *typentry) +{ + if (!(typentry->flags & TCFLAGS_CHECKED_ELEM_PROPERTIES)) + cache_array_element_properties(typentry); + return (typentry->flags & TCFLAGS_HAVE_ELEM_EXTENDED_HASHING) != 0; +} + +static void +cache_array_element_properties(TypeCacheEntry *typentry) +{ + Oid elem_type = get_base_element_type(typentry->type_id); + + if (OidIsValid(elem_type)) + { + TypeCacheEntry *elementry; + + elementry = lookup_type_cache(elem_type, + TYPECACHE_EQ_OPR | + TYPECACHE_CMP_PROC | + TYPECACHE_HASH_PROC | + TYPECACHE_HASH_EXTENDED_PROC); + if (OidIsValid(elementry->eq_opr)) + typentry->flags |= TCFLAGS_HAVE_ELEM_EQUALITY; + if (OidIsValid(elementry->cmp_proc)) + typentry->flags |= TCFLAGS_HAVE_ELEM_COMPARE; + if (OidIsValid(elementry->hash_proc)) + typentry->flags |= TCFLAGS_HAVE_ELEM_HASHING; + if (OidIsValid(elementry->hash_extended_proc)) + typentry->flags |= TCFLAGS_HAVE_ELEM_EXTENDED_HASHING; + } + typentry->flags |= TCFLAGS_CHECKED_ELEM_PROPERTIES; +} + +/* + * Likewise, some helper functions for composite types. + */ + +static bool +record_fields_have_equality(TypeCacheEntry *typentry) +{ + if (!(typentry->flags & TCFLAGS_CHECKED_FIELD_PROPERTIES)) + cache_record_field_properties(typentry); + return (typentry->flags & TCFLAGS_HAVE_FIELD_EQUALITY) != 0; +} + +static bool +record_fields_have_compare(TypeCacheEntry *typentry) +{ + if (!(typentry->flags & TCFLAGS_CHECKED_FIELD_PROPERTIES)) + cache_record_field_properties(typentry); + return (typentry->flags & TCFLAGS_HAVE_FIELD_COMPARE) != 0; +} + +static bool +record_fields_have_hashing(TypeCacheEntry *typentry) +{ + if (!(typentry->flags & TCFLAGS_CHECKED_FIELD_PROPERTIES)) + cache_record_field_properties(typentry); + return (typentry->flags & TCFLAGS_HAVE_FIELD_HASHING) != 0; +} + +static bool +record_fields_have_extended_hashing(TypeCacheEntry *typentry) +{ + if (!(typentry->flags & TCFLAGS_CHECKED_FIELD_PROPERTIES)) + cache_record_field_properties(typentry); + return (typentry->flags & TCFLAGS_HAVE_FIELD_EXTENDED_HASHING) != 0; +} + +static void +cache_record_field_properties(TypeCacheEntry *typentry) +{ + /* + * For type RECORD, we can't really tell what will work, since we don't + * have access here to the specific anonymous type. Just assume that + * equality and comparison will (we may get a failure at runtime). We + * could also claim that hashing works, but then if code that has the + * option between a comparison-based (sort-based) and a hash-based plan + * chooses hashing, stuff could fail that would otherwise work if it chose + * a comparison-based plan. In practice more types support comparison + * than hashing. + */ + if (typentry->type_id == RECORDOID) + { + typentry->flags |= (TCFLAGS_HAVE_FIELD_EQUALITY | + TCFLAGS_HAVE_FIELD_COMPARE); + } + else if (typentry->typtype == TYPTYPE_COMPOSITE) + { + TupleDesc tupdesc; + int newflags; + int i; + + /* Fetch composite type's tupdesc if we don't have it already */ + if (typentry->tupDesc == NULL) + load_typcache_tupdesc(typentry); + tupdesc = typentry->tupDesc; + + /* Must bump the refcount while we do additional catalog lookups */ + IncrTupleDescRefCount(tupdesc); + + /* Have each property if all non-dropped fields have the property */ + newflags = (TCFLAGS_HAVE_FIELD_EQUALITY | + TCFLAGS_HAVE_FIELD_COMPARE | + TCFLAGS_HAVE_FIELD_HASHING | + TCFLAGS_HAVE_FIELD_EXTENDED_HASHING); + for (i = 0; i < tupdesc->natts; i++) + { + TypeCacheEntry *fieldentry; + Form_pg_attribute attr = TupleDescAttr(tupdesc, i); + + if (attr->attisdropped) + continue; + + fieldentry = lookup_type_cache(attr->atttypid, + TYPECACHE_EQ_OPR | + TYPECACHE_CMP_PROC | + TYPECACHE_HASH_PROC | + TYPECACHE_HASH_EXTENDED_PROC); + if (!OidIsValid(fieldentry->eq_opr)) + newflags &= ~TCFLAGS_HAVE_FIELD_EQUALITY; + if (!OidIsValid(fieldentry->cmp_proc)) + newflags &= ~TCFLAGS_HAVE_FIELD_COMPARE; + if (!OidIsValid(fieldentry->hash_proc)) + newflags &= ~TCFLAGS_HAVE_FIELD_HASHING; + if (!OidIsValid(fieldentry->hash_extended_proc)) + newflags &= ~TCFLAGS_HAVE_FIELD_EXTENDED_HASHING; + + /* We can drop out of the loop once we disprove all bits */ + if (newflags == 0) + break; + } + typentry->flags |= newflags; + + DecrTupleDescRefCount(tupdesc); + } + else if (typentry->typtype == TYPTYPE_DOMAIN) + { + /* If it's domain over composite, copy base type's properties */ + TypeCacheEntry *baseentry; + + /* load up basetype info if we didn't already */ + if (typentry->domainBaseType == InvalidOid) + { + typentry->domainBaseTypmod = -1; + typentry->domainBaseType = + getBaseTypeAndTypmod(typentry->type_id, + &typentry->domainBaseTypmod); + } + baseentry = lookup_type_cache(typentry->domainBaseType, + TYPECACHE_EQ_OPR | + TYPECACHE_CMP_PROC | + TYPECACHE_HASH_PROC | + TYPECACHE_HASH_EXTENDED_PROC); + if (baseentry->typtype == TYPTYPE_COMPOSITE) + { + typentry->flags |= TCFLAGS_DOMAIN_BASE_IS_COMPOSITE; + typentry->flags |= baseentry->flags & (TCFLAGS_HAVE_FIELD_EQUALITY | + TCFLAGS_HAVE_FIELD_COMPARE | + TCFLAGS_HAVE_FIELD_HASHING | + TCFLAGS_HAVE_FIELD_EXTENDED_HASHING); + } + } + typentry->flags |= TCFLAGS_CHECKED_FIELD_PROPERTIES; +} + +/* + * Likewise, some helper functions for range and multirange types. + * + * We can borrow the flag bits for array element properties to use for range + * element properties, since those flag bits otherwise have no use in a + * range or multirange type's typcache entry. + */ + +static bool +range_element_has_hashing(TypeCacheEntry *typentry) +{ + if (!(typentry->flags & TCFLAGS_CHECKED_ELEM_PROPERTIES)) + cache_range_element_properties(typentry); + return (typentry->flags & TCFLAGS_HAVE_ELEM_HASHING) != 0; +} + +static bool +range_element_has_extended_hashing(TypeCacheEntry *typentry) +{ + if (!(typentry->flags & TCFLAGS_CHECKED_ELEM_PROPERTIES)) + cache_range_element_properties(typentry); + return (typentry->flags & TCFLAGS_HAVE_ELEM_EXTENDED_HASHING) != 0; +} + +static void +cache_range_element_properties(TypeCacheEntry *typentry) +{ + /* load up subtype link if we didn't already */ + if (typentry->rngelemtype == NULL && + typentry->typtype == TYPTYPE_RANGE) + load_rangetype_info(typentry); + + if (typentry->rngelemtype != NULL) + { + TypeCacheEntry *elementry; + + /* might need to calculate subtype's hash function properties */ + elementry = lookup_type_cache(typentry->rngelemtype->type_id, + TYPECACHE_HASH_PROC | + TYPECACHE_HASH_EXTENDED_PROC); + if (OidIsValid(elementry->hash_proc)) + typentry->flags |= TCFLAGS_HAVE_ELEM_HASHING; + if (OidIsValid(elementry->hash_extended_proc)) + typentry->flags |= TCFLAGS_HAVE_ELEM_EXTENDED_HASHING; + } + typentry->flags |= TCFLAGS_CHECKED_ELEM_PROPERTIES; +} + +static bool +multirange_element_has_hashing(TypeCacheEntry *typentry) +{ + if (!(typentry->flags & TCFLAGS_CHECKED_ELEM_PROPERTIES)) + cache_multirange_element_properties(typentry); + return (typentry->flags & TCFLAGS_HAVE_ELEM_HASHING) != 0; +} + +static bool +multirange_element_has_extended_hashing(TypeCacheEntry *typentry) +{ + if (!(typentry->flags & TCFLAGS_CHECKED_ELEM_PROPERTIES)) + cache_multirange_element_properties(typentry); + return (typentry->flags & TCFLAGS_HAVE_ELEM_EXTENDED_HASHING) != 0; +} + +static void +cache_multirange_element_properties(TypeCacheEntry *typentry) +{ + /* load up range link if we didn't already */ + if (typentry->rngtype == NULL && + typentry->typtype == TYPTYPE_MULTIRANGE) + load_multirangetype_info(typentry); + + if (typentry->rngtype != NULL && typentry->rngtype->rngelemtype != NULL) + { + TypeCacheEntry *elementry; + + /* might need to calculate subtype's hash function properties */ + elementry = lookup_type_cache(typentry->rngtype->rngelemtype->type_id, + TYPECACHE_HASH_PROC | + TYPECACHE_HASH_EXTENDED_PROC); + if (OidIsValid(elementry->hash_proc)) + typentry->flags |= TCFLAGS_HAVE_ELEM_HASHING; + if (OidIsValid(elementry->hash_extended_proc)) + typentry->flags |= TCFLAGS_HAVE_ELEM_EXTENDED_HASHING; + } + typentry->flags |= TCFLAGS_CHECKED_ELEM_PROPERTIES; +} + +/* + * Make sure that RecordCacheArray and RecordIdentifierArray are large enough + * to store 'typmod'. + */ +static void +ensure_record_cache_typmod_slot_exists(int32 typmod) +{ + if (RecordCacheArray == NULL) + { + RecordCacheArray = (RecordCacheArrayEntry *) + MemoryContextAllocZero(CacheMemoryContext, + 64 * sizeof(RecordCacheArrayEntry)); + RecordCacheArrayLen = 64; + } + + if (typmod >= RecordCacheArrayLen) + { + int32 newlen = pg_nextpower2_32(typmod + 1); + + RecordCacheArray = repalloc0_array(RecordCacheArray, + RecordCacheArrayEntry, + RecordCacheArrayLen, + newlen); + RecordCacheArrayLen = newlen; + } +} + +/* + * lookup_rowtype_tupdesc_internal --- internal routine to lookup a rowtype + * + * Same API as lookup_rowtype_tupdesc_noerror, but the returned tupdesc + * hasn't had its refcount bumped. + */ +static TupleDesc +lookup_rowtype_tupdesc_internal(Oid type_id, int32 typmod, bool noError) +{ + if (type_id != RECORDOID) + { + /* + * It's a named composite type, so use the regular typcache. + */ + TypeCacheEntry *typentry; + + typentry = lookup_type_cache(type_id, TYPECACHE_TUPDESC); + if (typentry->tupDesc == NULL && !noError) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("type %s is not composite", + format_type_be(type_id)))); + return typentry->tupDesc; + } + else + { + /* + * It's a transient record type, so look in our record-type table. + */ + if (typmod >= 0) + { + /* It is already in our local cache? */ + if (typmod < RecordCacheArrayLen && + RecordCacheArray[typmod].tupdesc != NULL) + return RecordCacheArray[typmod].tupdesc; + + /* Are we attached to a shared record typmod registry? */ + if (CurrentSession->shared_typmod_registry != NULL) + { + SharedTypmodTableEntry *entry; + + /* Try to find it in the shared typmod index. */ + entry = dshash_find(CurrentSession->shared_typmod_table, + &typmod, false); + if (entry != NULL) + { + TupleDesc tupdesc; + + tupdesc = (TupleDesc) + dsa_get_address(CurrentSession->area, + entry->shared_tupdesc); + Assert(typmod == tupdesc->tdtypmod); + + /* We may need to extend the local RecordCacheArray. */ + ensure_record_cache_typmod_slot_exists(typmod); + + /* + * Our local array can now point directly to the TupleDesc + * in shared memory, which is non-reference-counted. + */ + RecordCacheArray[typmod].tupdesc = tupdesc; + Assert(tupdesc->tdrefcount == -1); + + /* + * We don't share tupdesc identifiers across processes, so + * assign one locally. + */ + RecordCacheArray[typmod].id = ++tupledesc_id_counter; + + dshash_release_lock(CurrentSession->shared_typmod_table, + entry); + + return RecordCacheArray[typmod].tupdesc; + } + } + } + + if (!noError) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("record type has not been registered"))); + return NULL; + } +} + +/* + * lookup_rowtype_tupdesc + * + * Given a typeid/typmod that should describe a known composite type, + * return the tuple descriptor for the type. Will ereport on failure. + * (Use ereport because this is reachable with user-specified OIDs, + * for example from record_in().) + * + * Note: on success, we increment the refcount of the returned TupleDesc, + * and log the reference in CurrentResourceOwner. Caller must call + * ReleaseTupleDesc when done using the tupdesc. (There are some + * cases in which the returned tupdesc is not refcounted, in which + * case PinTupleDesc/ReleaseTupleDesc are no-ops; but in these cases + * the tupdesc is guaranteed to live till process exit.) + */ +TupleDesc +lookup_rowtype_tupdesc(Oid type_id, int32 typmod) +{ + TupleDesc tupDesc; + + tupDesc = lookup_rowtype_tupdesc_internal(type_id, typmod, false); + PinTupleDesc(tupDesc); + return tupDesc; +} + +/* + * lookup_rowtype_tupdesc_noerror + * + * As above, but if the type is not a known composite type and noError + * is true, returns NULL instead of ereport'ing. (Note that if a bogus + * type_id is passed, you'll get an ereport anyway.) + */ +TupleDesc +lookup_rowtype_tupdesc_noerror(Oid type_id, int32 typmod, bool noError) +{ + TupleDesc tupDesc; + + tupDesc = lookup_rowtype_tupdesc_internal(type_id, typmod, noError); + if (tupDesc != NULL) + PinTupleDesc(tupDesc); + return tupDesc; +} + +/* + * lookup_rowtype_tupdesc_copy + * + * Like lookup_rowtype_tupdesc(), but the returned TupleDesc has been + * copied into the CurrentMemoryContext and is not reference-counted. + */ +TupleDesc +lookup_rowtype_tupdesc_copy(Oid type_id, int32 typmod) +{ + TupleDesc tmp; + + tmp = lookup_rowtype_tupdesc_internal(type_id, typmod, false); + return CreateTupleDescCopyConstr(tmp); +} + +/* + * lookup_rowtype_tupdesc_domain + * + * Same as lookup_rowtype_tupdesc_noerror(), except that the type can also be + * a domain over a named composite type; so this is effectively equivalent to + * lookup_rowtype_tupdesc_noerror(getBaseType(type_id), typmod, noError) + * except for being a tad faster. + * + * Note: the reason we don't fold the look-through-domain behavior into plain + * lookup_rowtype_tupdesc() is that we want callers to know they might be + * dealing with a domain. Otherwise they might construct a tuple that should + * be of the domain type, but not apply domain constraints. + */ +TupleDesc +lookup_rowtype_tupdesc_domain(Oid type_id, int32 typmod, bool noError) +{ + TupleDesc tupDesc; + + if (type_id != RECORDOID) + { + /* + * Check for domain or named composite type. We might as well load + * whichever data is needed. + */ + TypeCacheEntry *typentry; + + typentry = lookup_type_cache(type_id, + TYPECACHE_TUPDESC | + TYPECACHE_DOMAIN_BASE_INFO); + if (typentry->typtype == TYPTYPE_DOMAIN) + return lookup_rowtype_tupdesc_noerror(typentry->domainBaseType, + typentry->domainBaseTypmod, + noError); + if (typentry->tupDesc == NULL && !noError) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("type %s is not composite", + format_type_be(type_id)))); + tupDesc = typentry->tupDesc; + } + else + tupDesc = lookup_rowtype_tupdesc_internal(type_id, typmod, noError); + if (tupDesc != NULL) + PinTupleDesc(tupDesc); + return tupDesc; +} + +/* + * Hash function for the hash table of RecordCacheEntry. + */ +static uint32 +record_type_typmod_hash(const void *data, size_t size) +{ + RecordCacheEntry *entry = (RecordCacheEntry *) data; + + return hashTupleDesc(entry->tupdesc); +} + +/* + * Match function for the hash table of RecordCacheEntry. + */ +static int +record_type_typmod_compare(const void *a, const void *b, size_t size) +{ + RecordCacheEntry *left = (RecordCacheEntry *) a; + RecordCacheEntry *right = (RecordCacheEntry *) b; + + return equalTupleDescs(left->tupdesc, right->tupdesc) ? 0 : 1; +} + +/* + * assign_record_type_typmod + * + * Given a tuple descriptor for a RECORD type, find or create a cache entry + * for the type, and set the tupdesc's tdtypmod field to a value that will + * identify this cache entry to lookup_rowtype_tupdesc. + */ +void +assign_record_type_typmod(TupleDesc tupDesc) +{ + RecordCacheEntry *recentry; + TupleDesc entDesc; + bool found; + MemoryContext oldcxt; + + Assert(tupDesc->tdtypeid == RECORDOID); + + if (RecordCacheHash == NULL) + { + /* First time through: initialize the hash table */ + HASHCTL ctl; + + ctl.keysize = sizeof(TupleDesc); /* just the pointer */ + ctl.entrysize = sizeof(RecordCacheEntry); + ctl.hash = record_type_typmod_hash; + ctl.match = record_type_typmod_compare; + RecordCacheHash = hash_create("Record information cache", 64, + &ctl, + HASH_ELEM | HASH_FUNCTION | HASH_COMPARE); + + /* Also make sure CacheMemoryContext exists */ + if (!CacheMemoryContext) + CreateCacheMemoryContext(); + } + + /* + * Find a hashtable entry for this tuple descriptor. We don't use + * HASH_ENTER yet, because if it's missing, we need to make sure that all + * the allocations succeed before we create the new entry. + */ + recentry = (RecordCacheEntry *) hash_search(RecordCacheHash, + &tupDesc, + HASH_FIND, &found); + if (found && recentry->tupdesc != NULL) + { + tupDesc->tdtypmod = recentry->tupdesc->tdtypmod; + return; + } + + /* Not present, so need to manufacture an entry */ + oldcxt = MemoryContextSwitchTo(CacheMemoryContext); + + /* Look in the SharedRecordTypmodRegistry, if attached */ + entDesc = find_or_make_matching_shared_tupledesc(tupDesc); + if (entDesc == NULL) + { + /* + * Make sure we have room before we CreateTupleDescCopy() or advance + * NextRecordTypmod. + */ + ensure_record_cache_typmod_slot_exists(NextRecordTypmod); + + /* Reference-counted local cache only. */ + entDesc = CreateTupleDescCopy(tupDesc); + entDesc->tdrefcount = 1; + entDesc->tdtypmod = NextRecordTypmod++; + } + else + { + ensure_record_cache_typmod_slot_exists(entDesc->tdtypmod); + } + + RecordCacheArray[entDesc->tdtypmod].tupdesc = entDesc; + + /* Assign a unique tupdesc identifier, too. */ + RecordCacheArray[entDesc->tdtypmod].id = ++tupledesc_id_counter; + + /* Fully initialized; create the hash table entry */ + recentry = (RecordCacheEntry *) hash_search(RecordCacheHash, + &tupDesc, + HASH_ENTER, NULL); + recentry->tupdesc = entDesc; + + /* Update the caller's tuple descriptor. */ + tupDesc->tdtypmod = entDesc->tdtypmod; + + MemoryContextSwitchTo(oldcxt); +} + +/* + * assign_record_type_identifier + * + * Get an identifier, which will be unique over the lifespan of this backend + * process, for the current tuple descriptor of the specified composite type. + * For named composite types, the value is guaranteed to change if the type's + * definition does. For registered RECORD types, the value will not change + * once assigned, since the registered type won't either. If an anonymous + * RECORD type is specified, we return a new identifier on each call. + */ +uint64 +assign_record_type_identifier(Oid type_id, int32 typmod) +{ + if (type_id != RECORDOID) + { + /* + * It's a named composite type, so use the regular typcache. + */ + TypeCacheEntry *typentry; + + typentry = lookup_type_cache(type_id, TYPECACHE_TUPDESC); + if (typentry->tupDesc == NULL) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("type %s is not composite", + format_type_be(type_id)))); + Assert(typentry->tupDesc_identifier != 0); + return typentry->tupDesc_identifier; + } + else + { + /* + * It's a transient record type, so look in our record-type table. + */ + if (typmod >= 0 && typmod < RecordCacheArrayLen && + RecordCacheArray[typmod].tupdesc != NULL) + { + Assert(RecordCacheArray[typmod].id != 0); + return RecordCacheArray[typmod].id; + } + + /* For anonymous or unrecognized record type, generate a new ID */ + return ++tupledesc_id_counter; + } +} + +/* + * Return the amount of shmem required to hold a SharedRecordTypmodRegistry. + * This exists only to avoid exposing private innards of + * SharedRecordTypmodRegistry in a header. + */ +size_t +SharedRecordTypmodRegistryEstimate(void) +{ + return sizeof(SharedRecordTypmodRegistry); +} + +/* + * Initialize 'registry' in a pre-existing shared memory region, which must be + * maximally aligned and have space for SharedRecordTypmodRegistryEstimate() + * bytes. + * + * 'area' will be used to allocate shared memory space as required for the + * typemod registration. The current process, expected to be a leader process + * in a parallel query, will be attached automatically and its current record + * types will be loaded into *registry. While attached, all calls to + * assign_record_type_typmod will use the shared registry. Worker backends + * will need to attach explicitly. + * + * Note that this function takes 'area' and 'segment' as arguments rather than + * accessing them via CurrentSession, because they aren't installed there + * until after this function runs. + */ +void +SharedRecordTypmodRegistryInit(SharedRecordTypmodRegistry *registry, + dsm_segment *segment, + dsa_area *area) +{ + MemoryContext old_context; + dshash_table *record_table; + dshash_table *typmod_table; + int32 typmod; + + Assert(!IsParallelWorker()); + + /* We can't already be attached to a shared registry. */ + Assert(CurrentSession->shared_typmod_registry == NULL); + Assert(CurrentSession->shared_record_table == NULL); + Assert(CurrentSession->shared_typmod_table == NULL); + + old_context = MemoryContextSwitchTo(TopMemoryContext); + + /* Create the hash table of tuple descriptors indexed by themselves. */ + record_table = dshash_create(area, &srtr_record_table_params, area); + + /* Create the hash table of tuple descriptors indexed by typmod. */ + typmod_table = dshash_create(area, &srtr_typmod_table_params, NULL); + + MemoryContextSwitchTo(old_context); + + /* Initialize the SharedRecordTypmodRegistry. */ + registry->record_table_handle = dshash_get_hash_table_handle(record_table); + registry->typmod_table_handle = dshash_get_hash_table_handle(typmod_table); + pg_atomic_init_u32(®istry->next_typmod, NextRecordTypmod); + + /* + * Copy all entries from this backend's private registry into the shared + * registry. + */ + for (typmod = 0; typmod < NextRecordTypmod; ++typmod) + { + SharedTypmodTableEntry *typmod_table_entry; + SharedRecordTableEntry *record_table_entry; + SharedRecordTableKey record_table_key; + dsa_pointer shared_dp; + TupleDesc tupdesc; + bool found; + + tupdesc = RecordCacheArray[typmod].tupdesc; + if (tupdesc == NULL) + continue; + + /* Copy the TupleDesc into shared memory. */ + shared_dp = share_tupledesc(area, tupdesc, typmod); + + /* Insert into the typmod table. */ + typmod_table_entry = dshash_find_or_insert(typmod_table, + &tupdesc->tdtypmod, + &found); + if (found) + elog(ERROR, "cannot create duplicate shared record typmod"); + typmod_table_entry->typmod = tupdesc->tdtypmod; + typmod_table_entry->shared_tupdesc = shared_dp; + dshash_release_lock(typmod_table, typmod_table_entry); + + /* Insert into the record table. */ + record_table_key.shared = false; + record_table_key.u.local_tupdesc = tupdesc; + record_table_entry = dshash_find_or_insert(record_table, + &record_table_key, + &found); + if (!found) + { + record_table_entry->key.shared = true; + record_table_entry->key.u.shared_tupdesc = shared_dp; + } + dshash_release_lock(record_table, record_table_entry); + } + + /* + * Set up the global state that will tell assign_record_type_typmod and + * lookup_rowtype_tupdesc_internal about the shared registry. + */ + CurrentSession->shared_record_table = record_table; + CurrentSession->shared_typmod_table = typmod_table; + CurrentSession->shared_typmod_registry = registry; + + /* + * We install a detach hook in the leader, but only to handle cleanup on + * failure during GetSessionDsmHandle(). Once GetSessionDsmHandle() pins + * the memory, the leader process will use a shared registry until it + * exits. + */ + on_dsm_detach(segment, shared_record_typmod_registry_detach, (Datum) 0); +} + +/* + * Attach to 'registry', which must have been initialized already by another + * backend. Future calls to assign_record_type_typmod and + * lookup_rowtype_tupdesc_internal will use the shared registry until the + * current session is detached. + */ +void +SharedRecordTypmodRegistryAttach(SharedRecordTypmodRegistry *registry) +{ + MemoryContext old_context; + dshash_table *record_table; + dshash_table *typmod_table; + + Assert(IsParallelWorker()); + + /* We can't already be attached to a shared registry. */ + Assert(CurrentSession != NULL); + Assert(CurrentSession->segment != NULL); + Assert(CurrentSession->area != NULL); + Assert(CurrentSession->shared_typmod_registry == NULL); + Assert(CurrentSession->shared_record_table == NULL); + Assert(CurrentSession->shared_typmod_table == NULL); + + /* + * We can't already have typmods in our local cache, because they'd clash + * with those imported by SharedRecordTypmodRegistryInit. This should be + * a freshly started parallel worker. If we ever support worker + * recycling, a worker would need to zap its local cache in between + * servicing different queries, in order to be able to call this and + * synchronize typmods with a new leader; but that's problematic because + * we can't be very sure that record-typmod-related state hasn't escaped + * to anywhere else in the process. + */ + Assert(NextRecordTypmod == 0); + + old_context = MemoryContextSwitchTo(TopMemoryContext); + + /* Attach to the two hash tables. */ + record_table = dshash_attach(CurrentSession->area, + &srtr_record_table_params, + registry->record_table_handle, + CurrentSession->area); + typmod_table = dshash_attach(CurrentSession->area, + &srtr_typmod_table_params, + registry->typmod_table_handle, + NULL); + + MemoryContextSwitchTo(old_context); + + /* + * Set up detach hook to run at worker exit. Currently this is the same + * as the leader's detach hook, but in future they might need to be + * different. + */ + on_dsm_detach(CurrentSession->segment, + shared_record_typmod_registry_detach, + PointerGetDatum(registry)); + + /* + * Set up the session state that will tell assign_record_type_typmod and + * lookup_rowtype_tupdesc_internal about the shared registry. + */ + CurrentSession->shared_typmod_registry = registry; + CurrentSession->shared_record_table = record_table; + CurrentSession->shared_typmod_table = typmod_table; +} + +/* + * TypeCacheRelCallback + * Relcache inval callback function + * + * Delete the cached tuple descriptor (if any) for the given rel's composite + * type, or for all composite types if relid == InvalidOid. Also reset + * whatever info we have cached about the composite type's comparability. + * + * This is called when a relcache invalidation event occurs for the given + * relid. We must scan the whole typcache hash since we don't know the + * type OID corresponding to the relid. We could do a direct search if this + * were a syscache-flush callback on pg_type, but then we would need all + * ALTER-TABLE-like commands that could modify a rowtype to issue syscache + * invals against the rel's pg_type OID. The extra SI signaling could very + * well cost more than we'd save, since in most usages there are not very + * many entries in a backend's typcache. The risk of bugs-of-omission seems + * high, too. + * + * Another possibility, with only localized impact, is to maintain a second + * hashtable that indexes composite-type typcache entries by their typrelid. + * But it's still not clear it's worth the trouble. + */ +static void +TypeCacheRelCallback(Datum arg, Oid relid) +{ + HASH_SEQ_STATUS status; + TypeCacheEntry *typentry; + + /* TypeCacheHash must exist, else this callback wouldn't be registered */ + hash_seq_init(&status, TypeCacheHash); + while ((typentry = (TypeCacheEntry *) hash_seq_search(&status)) != NULL) + { + if (typentry->typtype == TYPTYPE_COMPOSITE) + { + /* Skip if no match, unless we're zapping all composite types */ + if (relid != typentry->typrelid && relid != InvalidOid) + continue; + + /* Delete tupdesc if we have it */ + if (typentry->tupDesc != NULL) + { + /* + * Release our refcount, and free the tupdesc if none remain. + * (Can't use DecrTupleDescRefCount because this reference is + * not logged in current resource owner.) + */ + Assert(typentry->tupDesc->tdrefcount > 0); + if (--typentry->tupDesc->tdrefcount == 0) + FreeTupleDesc(typentry->tupDesc); + typentry->tupDesc = NULL; + + /* + * Also clear tupDesc_identifier, so that anything watching + * that will realize that the tupdesc has possibly changed. + * (Alternatively, we could specify that to detect possible + * tupdesc change, one must check for tupDesc != NULL as well + * as tupDesc_identifier being the same as what was previously + * seen. That seems error-prone.) + */ + typentry->tupDesc_identifier = 0; + } + + /* Reset equality/comparison/hashing validity information */ + typentry->flags &= ~TCFLAGS_OPERATOR_FLAGS; + } + else if (typentry->typtype == TYPTYPE_DOMAIN) + { + /* + * If it's domain over composite, reset flags. (We don't bother + * trying to determine whether the specific base type needs a + * reset.) Note that if we haven't determined whether the base + * type is composite, we don't need to reset anything. + */ + if (typentry->flags & TCFLAGS_DOMAIN_BASE_IS_COMPOSITE) + typentry->flags &= ~TCFLAGS_OPERATOR_FLAGS; + } + } +} + +/* + * TypeCacheTypCallback + * Syscache inval callback function + * + * This is called when a syscache invalidation event occurs for any + * pg_type row. If we have information cached about that type, mark + * it as needing to be reloaded. + */ +static void +TypeCacheTypCallback(Datum arg, int cacheid, uint32 hashvalue) +{ + HASH_SEQ_STATUS status; + TypeCacheEntry *typentry; + + /* TypeCacheHash must exist, else this callback wouldn't be registered */ + hash_seq_init(&status, TypeCacheHash); + while ((typentry = (TypeCacheEntry *) hash_seq_search(&status)) != NULL) + { + /* Is this the targeted type row (or it's a total cache flush)? */ + if (hashvalue == 0 || typentry->type_id_hash == hashvalue) + { + /* + * Mark the data obtained directly from pg_type as invalid. Also, + * if it's a domain, typnotnull might've changed, so we'll need to + * recalculate its constraints. + */ + typentry->flags &= ~(TCFLAGS_HAVE_PG_TYPE_DATA | + TCFLAGS_CHECKED_DOMAIN_CONSTRAINTS); + } + } +} + +/* + * TypeCacheOpcCallback + * Syscache inval callback function + * + * This is called when a syscache invalidation event occurs for any pg_opclass + * row. In principle we could probably just invalidate data dependent on the + * particular opclass, but since updates on pg_opclass are rare in production + * it doesn't seem worth a lot of complication: we just mark all cached data + * invalid. + * + * Note that we don't bother watching for updates on pg_amop or pg_amproc. + * This should be safe because ALTER OPERATOR FAMILY ADD/DROP OPERATOR/FUNCTION + * is not allowed to be used to add/drop the primary operators and functions + * of an opclass, only cross-type members of a family; and the latter sorts + * of members are not going to get cached here. + */ +static void +TypeCacheOpcCallback(Datum arg, int cacheid, uint32 hashvalue) +{ + HASH_SEQ_STATUS status; + TypeCacheEntry *typentry; + + /* TypeCacheHash must exist, else this callback wouldn't be registered */ + hash_seq_init(&status, TypeCacheHash); + while ((typentry = (TypeCacheEntry *) hash_seq_search(&status)) != NULL) + { + /* Reset equality/comparison/hashing validity information */ + typentry->flags &= ~TCFLAGS_OPERATOR_FLAGS; + } +} + +/* + * TypeCacheConstrCallback + * Syscache inval callback function + * + * This is called when a syscache invalidation event occurs for any + * pg_constraint row. We flush information about domain constraints + * when this happens. + * + * It's slightly annoying that we can't tell whether the inval event was for + * a domain constraint record or not; there's usually more update traffic + * for table constraints than domain constraints, so we'll do a lot of + * useless flushes. Still, this is better than the old no-caching-at-all + * approach to domain constraints. + */ +static void +TypeCacheConstrCallback(Datum arg, int cacheid, uint32 hashvalue) +{ + TypeCacheEntry *typentry; + + /* + * Because this is called very frequently, and typically very few of the + * typcache entries are for domains, we don't use hash_seq_search here. + * Instead we thread all the domain-type entries together so that we can + * visit them cheaply. + */ + for (typentry = firstDomainTypeEntry; + typentry != NULL; + typentry = typentry->nextDomain) + { + /* Reset domain constraint validity information */ + typentry->flags &= ~TCFLAGS_CHECKED_DOMAIN_CONSTRAINTS; + } +} + + +/* + * Check if given OID is part of the subset that's sortable by comparisons + */ +static inline bool +enum_known_sorted(TypeCacheEnumData *enumdata, Oid arg) +{ + Oid offset; + + if (arg < enumdata->bitmap_base) + return false; + offset = arg - enumdata->bitmap_base; + if (offset > (Oid) INT_MAX) + return false; + return bms_is_member((int) offset, enumdata->sorted_values); +} + + +/* + * compare_values_of_enum + * Compare two members of an enum type. + * Return <0, 0, or >0 according as arg1 <, =, or > arg2. + * + * Note: currently, the enumData cache is refreshed only if we are asked + * to compare an enum value that is not already in the cache. This is okay + * because there is no support for re-ordering existing values, so comparisons + * of previously cached values will return the right answer even if other + * values have been added since we last loaded the cache. + * + * Note: the enum logic has a special-case rule about even-numbered versus + * odd-numbered OIDs, but we take no account of that rule here; this + * routine shouldn't even get called when that rule applies. + */ +int +compare_values_of_enum(TypeCacheEntry *tcache, Oid arg1, Oid arg2) +{ + TypeCacheEnumData *enumdata; + EnumItem *item1; + EnumItem *item2; + + /* + * Equal OIDs are certainly equal --- this case was probably handled by + * our caller, but we may as well check. + */ + if (arg1 == arg2) + return 0; + + /* Load up the cache if first time through */ + if (tcache->enumData == NULL) + load_enum_cache_data(tcache); + enumdata = tcache->enumData; + + /* + * If both OIDs are known-sorted, we can just compare them directly. + */ + if (enum_known_sorted(enumdata, arg1) && + enum_known_sorted(enumdata, arg2)) + { + if (arg1 < arg2) + return -1; + else + return 1; + } + + /* + * Slow path: we have to identify their actual sort-order positions. + */ + item1 = find_enumitem(enumdata, arg1); + item2 = find_enumitem(enumdata, arg2); + + if (item1 == NULL || item2 == NULL) + { + /* + * We couldn't find one or both values. That means the enum has + * changed under us, so re-initialize the cache and try again. We + * don't bother retrying the known-sorted case in this path. + */ + load_enum_cache_data(tcache); + enumdata = tcache->enumData; + + item1 = find_enumitem(enumdata, arg1); + item2 = find_enumitem(enumdata, arg2); + + /* + * If we still can't find the values, complain: we must have corrupt + * data. + */ + if (item1 == NULL) + elog(ERROR, "enum value %u not found in cache for enum %s", + arg1, format_type_be(tcache->type_id)); + if (item2 == NULL) + elog(ERROR, "enum value %u not found in cache for enum %s", + arg2, format_type_be(tcache->type_id)); + } + + if (item1->sort_order < item2->sort_order) + return -1; + else if (item1->sort_order > item2->sort_order) + return 1; + else + return 0; +} + +/* + * Load (or re-load) the enumData member of the typcache entry. + */ +static void +load_enum_cache_data(TypeCacheEntry *tcache) +{ + TypeCacheEnumData *enumdata; + Relation enum_rel; + SysScanDesc enum_scan; + HeapTuple enum_tuple; + ScanKeyData skey; + EnumItem *items; + int numitems; + int maxitems; + Oid bitmap_base; + Bitmapset *bitmap; + MemoryContext oldcxt; + int bm_size, + start_pos; + + /* Check that this is actually an enum */ + if (tcache->typtype != TYPTYPE_ENUM) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("%s is not an enum", + format_type_be(tcache->type_id)))); + + /* + * Read all the information for members of the enum type. We collect the + * info in working memory in the caller's context, and then transfer it to + * permanent memory in CacheMemoryContext. This minimizes the risk of + * leaking memory from CacheMemoryContext in the event of an error partway + * through. + */ + maxitems = 64; + items = (EnumItem *) palloc(sizeof(EnumItem) * maxitems); + numitems = 0; + + /* Scan pg_enum for the members of the target enum type. */ + ScanKeyInit(&skey, + Anum_pg_enum_enumtypid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(tcache->type_id)); + + enum_rel = table_open(EnumRelationId, AccessShareLock); + enum_scan = systable_beginscan(enum_rel, + EnumTypIdLabelIndexId, + true, NULL, + 1, &skey); + + while (HeapTupleIsValid(enum_tuple = systable_getnext(enum_scan))) + { + Form_pg_enum en = (Form_pg_enum) GETSTRUCT(enum_tuple); + + if (numitems >= maxitems) + { + maxitems *= 2; + items = (EnumItem *) repalloc(items, sizeof(EnumItem) * maxitems); + } + items[numitems].enum_oid = en->oid; + items[numitems].sort_order = en->enumsortorder; + numitems++; + } + + systable_endscan(enum_scan); + table_close(enum_rel, AccessShareLock); + + /* Sort the items into OID order */ + qsort(items, numitems, sizeof(EnumItem), enum_oid_cmp); + + /* + * Here, we create a bitmap listing a subset of the enum's OIDs that are + * known to be in order and can thus be compared with just OID comparison. + * + * The point of this is that the enum's initial OIDs were certainly in + * order, so there is some subset that can be compared via OID comparison; + * and we'd rather not do binary searches unnecessarily. + * + * This is somewhat heuristic, and might identify a subset of OIDs that + * isn't exactly what the type started with. That's okay as long as the + * subset is correctly sorted. + */ + bitmap_base = InvalidOid; + bitmap = NULL; + bm_size = 1; /* only save sets of at least 2 OIDs */ + + for (start_pos = 0; start_pos < numitems - 1; start_pos++) + { + /* + * Identify longest sorted subsequence starting at start_pos + */ + Bitmapset *this_bitmap = bms_make_singleton(0); + int this_bm_size = 1; + Oid start_oid = items[start_pos].enum_oid; + float4 prev_order = items[start_pos].sort_order; + int i; + + for (i = start_pos + 1; i < numitems; i++) + { + Oid offset; + + offset = items[i].enum_oid - start_oid; + /* quit if bitmap would be too large; cutoff is arbitrary */ + if (offset >= 8192) + break; + /* include the item if it's in-order */ + if (items[i].sort_order > prev_order) + { + prev_order = items[i].sort_order; + this_bitmap = bms_add_member(this_bitmap, (int) offset); + this_bm_size++; + } + } + + /* Remember it if larger than previous best */ + if (this_bm_size > bm_size) + { + bms_free(bitmap); + bitmap_base = start_oid; + bitmap = this_bitmap; + bm_size = this_bm_size; + } + else + bms_free(this_bitmap); + + /* + * Done if it's not possible to find a longer sequence in the rest of + * the list. In typical cases this will happen on the first + * iteration, which is why we create the bitmaps on the fly instead of + * doing a second pass over the list. + */ + if (bm_size >= (numitems - start_pos - 1)) + break; + } + + /* OK, copy the data into CacheMemoryContext */ + oldcxt = MemoryContextSwitchTo(CacheMemoryContext); + enumdata = (TypeCacheEnumData *) + palloc(offsetof(TypeCacheEnumData, enum_values) + + numitems * sizeof(EnumItem)); + enumdata->bitmap_base = bitmap_base; + enumdata->sorted_values = bms_copy(bitmap); + enumdata->num_values = numitems; + memcpy(enumdata->enum_values, items, numitems * sizeof(EnumItem)); + MemoryContextSwitchTo(oldcxt); + + pfree(items); + bms_free(bitmap); + + /* And link the finished cache struct into the typcache */ + if (tcache->enumData != NULL) + pfree(tcache->enumData); + tcache->enumData = enumdata; +} + +/* + * Locate the EnumItem with the given OID, if present + */ +static EnumItem * +find_enumitem(TypeCacheEnumData *enumdata, Oid arg) +{ + EnumItem srch; + + /* On some versions of Solaris, bsearch of zero items dumps core */ + if (enumdata->num_values <= 0) + return NULL; + + srch.enum_oid = arg; + return bsearch(&srch, enumdata->enum_values, enumdata->num_values, + sizeof(EnumItem), enum_oid_cmp); +} + +/* + * qsort comparison function for OID-ordered EnumItems + */ +static int +enum_oid_cmp(const void *left, const void *right) +{ + const EnumItem *l = (const EnumItem *) left; + const EnumItem *r = (const EnumItem *) right; + + if (l->enum_oid < r->enum_oid) + return -1; + else if (l->enum_oid > r->enum_oid) + return 1; + else + return 0; +} + +/* + * Copy 'tupdesc' into newly allocated shared memory in 'area', set its typmod + * to the given value and return a dsa_pointer. + */ +static dsa_pointer +share_tupledesc(dsa_area *area, TupleDesc tupdesc, uint32 typmod) +{ + dsa_pointer shared_dp; + TupleDesc shared; + + shared_dp = dsa_allocate(area, TupleDescSize(tupdesc)); + shared = (TupleDesc) dsa_get_address(area, shared_dp); + TupleDescCopy(shared, tupdesc); + shared->tdtypmod = typmod; + + return shared_dp; +} + +/* + * If we are attached to a SharedRecordTypmodRegistry, use it to find or + * create a shared TupleDesc that matches 'tupdesc'. Otherwise return NULL. + * Tuple descriptors returned by this function are not reference counted, and + * will exist at least as long as the current backend remained attached to the + * current session. + */ +static TupleDesc +find_or_make_matching_shared_tupledesc(TupleDesc tupdesc) +{ + TupleDesc result; + SharedRecordTableKey key; + SharedRecordTableEntry *record_table_entry; + SharedTypmodTableEntry *typmod_table_entry; + dsa_pointer shared_dp; + bool found; + uint32 typmod; + + /* If not even attached, nothing to do. */ + if (CurrentSession->shared_typmod_registry == NULL) + return NULL; + + /* Try to find a matching tuple descriptor in the record table. */ + key.shared = false; + key.u.local_tupdesc = tupdesc; + record_table_entry = (SharedRecordTableEntry *) + dshash_find(CurrentSession->shared_record_table, &key, false); + if (record_table_entry) + { + Assert(record_table_entry->key.shared); + dshash_release_lock(CurrentSession->shared_record_table, + record_table_entry); + result = (TupleDesc) + dsa_get_address(CurrentSession->area, + record_table_entry->key.u.shared_tupdesc); + Assert(result->tdrefcount == -1); + + return result; + } + + /* Allocate a new typmod number. This will be wasted if we error out. */ + typmod = (int) + pg_atomic_fetch_add_u32(&CurrentSession->shared_typmod_registry->next_typmod, + 1); + + /* Copy the TupleDesc into shared memory. */ + shared_dp = share_tupledesc(CurrentSession->area, tupdesc, typmod); + + /* + * Create an entry in the typmod table so that others will understand this + * typmod number. + */ + PG_TRY(); + { + typmod_table_entry = (SharedTypmodTableEntry *) + dshash_find_or_insert(CurrentSession->shared_typmod_table, + &typmod, &found); + if (found) + elog(ERROR, "cannot create duplicate shared record typmod"); + } + PG_CATCH(); + { + dsa_free(CurrentSession->area, shared_dp); + PG_RE_THROW(); + } + PG_END_TRY(); + typmod_table_entry->typmod = typmod; + typmod_table_entry->shared_tupdesc = shared_dp; + dshash_release_lock(CurrentSession->shared_typmod_table, + typmod_table_entry); + + /* + * Finally create an entry in the record table so others with matching + * tuple descriptors can reuse the typmod. + */ + record_table_entry = (SharedRecordTableEntry *) + dshash_find_or_insert(CurrentSession->shared_record_table, &key, + &found); + if (found) + { + /* + * Someone concurrently inserted a matching tuple descriptor since the + * first time we checked. Use that one instead. + */ + dshash_release_lock(CurrentSession->shared_record_table, + record_table_entry); + + /* Might as well free up the space used by the one we created. */ + found = dshash_delete_key(CurrentSession->shared_typmod_table, + &typmod); + Assert(found); + dsa_free(CurrentSession->area, shared_dp); + + /* Return the one we found. */ + Assert(record_table_entry->key.shared); + result = (TupleDesc) + dsa_get_address(CurrentSession->area, + record_table_entry->key.u.shared_tupdesc); + Assert(result->tdrefcount == -1); + + return result; + } + + /* Store it and return it. */ + record_table_entry->key.shared = true; + record_table_entry->key.u.shared_tupdesc = shared_dp; + dshash_release_lock(CurrentSession->shared_record_table, + record_table_entry); + result = (TupleDesc) + dsa_get_address(CurrentSession->area, shared_dp); + Assert(result->tdrefcount == -1); + + return result; +} + +/* + * On-DSM-detach hook to forget about the current shared record typmod + * infrastructure. This is currently used by both leader and workers. + */ +static void +shared_record_typmod_registry_detach(dsm_segment *segment, Datum datum) +{ + /* Be cautious here: maybe we didn't finish initializing. */ + if (CurrentSession->shared_record_table != NULL) + { + dshash_detach(CurrentSession->shared_record_table); + CurrentSession->shared_record_table = NULL; + } + if (CurrentSession->shared_typmod_table != NULL) + { + dshash_detach(CurrentSession->shared_typmod_table); + CurrentSession->shared_typmod_table = NULL; + } + CurrentSession->shared_typmod_registry = NULL; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/errcodes.h b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/errcodes.h new file mode 100644 index 00000000000..a2f604c2fa1 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/errcodes.h @@ -0,0 +1,354 @@ +/* autogenerated from src/backend/utils/errcodes.txt, do not edit */ +/* there is deliberately not an #ifndef ERRCODES_H here */ + +/* Class 00 - Successful Completion */ +#define ERRCODE_SUCCESSFUL_COMPLETION MAKE_SQLSTATE('0','0','0','0','0') + +/* Class 01 - Warning */ +#define ERRCODE_WARNING MAKE_SQLSTATE('0','1','0','0','0') +#define ERRCODE_WARNING_DYNAMIC_RESULT_SETS_RETURNED MAKE_SQLSTATE('0','1','0','0','C') +#define ERRCODE_WARNING_IMPLICIT_ZERO_BIT_PADDING MAKE_SQLSTATE('0','1','0','0','8') +#define ERRCODE_WARNING_NULL_VALUE_ELIMINATED_IN_SET_FUNCTION MAKE_SQLSTATE('0','1','0','0','3') +#define ERRCODE_WARNING_PRIVILEGE_NOT_GRANTED MAKE_SQLSTATE('0','1','0','0','7') +#define ERRCODE_WARNING_PRIVILEGE_NOT_REVOKED MAKE_SQLSTATE('0','1','0','0','6') +#define ERRCODE_WARNING_STRING_DATA_RIGHT_TRUNCATION MAKE_SQLSTATE('0','1','0','0','4') +#define ERRCODE_WARNING_DEPRECATED_FEATURE MAKE_SQLSTATE('0','1','P','0','1') + +/* Class 02 - No Data (this is also a warning class per the SQL standard) */ +#define ERRCODE_NO_DATA MAKE_SQLSTATE('0','2','0','0','0') +#define ERRCODE_NO_ADDITIONAL_DYNAMIC_RESULT_SETS_RETURNED MAKE_SQLSTATE('0','2','0','0','1') + +/* Class 03 - SQL Statement Not Yet Complete */ +#define ERRCODE_SQL_STATEMENT_NOT_YET_COMPLETE MAKE_SQLSTATE('0','3','0','0','0') + +/* Class 08 - Connection Exception */ +#define ERRCODE_CONNECTION_EXCEPTION MAKE_SQLSTATE('0','8','0','0','0') +#define ERRCODE_CONNECTION_DOES_NOT_EXIST MAKE_SQLSTATE('0','8','0','0','3') +#define ERRCODE_CONNECTION_FAILURE MAKE_SQLSTATE('0','8','0','0','6') +#define ERRCODE_SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION MAKE_SQLSTATE('0','8','0','0','1') +#define ERRCODE_SQLSERVER_REJECTED_ESTABLISHMENT_OF_SQLCONNECTION MAKE_SQLSTATE('0','8','0','0','4') +#define ERRCODE_TRANSACTION_RESOLUTION_UNKNOWN MAKE_SQLSTATE('0','8','0','0','7') +#define ERRCODE_PROTOCOL_VIOLATION MAKE_SQLSTATE('0','8','P','0','1') + +/* Class 09 - Triggered Action Exception */ +#define ERRCODE_TRIGGERED_ACTION_EXCEPTION MAKE_SQLSTATE('0','9','0','0','0') + +/* Class 0A - Feature Not Supported */ +#define ERRCODE_FEATURE_NOT_SUPPORTED MAKE_SQLSTATE('0','A','0','0','0') + +/* Class 0B - Invalid Transaction Initiation */ +#define ERRCODE_INVALID_TRANSACTION_INITIATION MAKE_SQLSTATE('0','B','0','0','0') + +/* Class 0F - Locator Exception */ +#define ERRCODE_LOCATOR_EXCEPTION MAKE_SQLSTATE('0','F','0','0','0') +#define ERRCODE_L_E_INVALID_SPECIFICATION MAKE_SQLSTATE('0','F','0','0','1') + +/* Class 0L - Invalid Grantor */ +#define ERRCODE_INVALID_GRANTOR MAKE_SQLSTATE('0','L','0','0','0') +#define ERRCODE_INVALID_GRANT_OPERATION MAKE_SQLSTATE('0','L','P','0','1') + +/* Class 0P - Invalid Role Specification */ +#define ERRCODE_INVALID_ROLE_SPECIFICATION MAKE_SQLSTATE('0','P','0','0','0') + +/* Class 0Z - Diagnostics Exception */ +#define ERRCODE_DIAGNOSTICS_EXCEPTION MAKE_SQLSTATE('0','Z','0','0','0') +#define ERRCODE_STACKED_DIAGNOSTICS_ACCESSED_WITHOUT_ACTIVE_HANDLER MAKE_SQLSTATE('0','Z','0','0','2') + +/* Class 20 - Case Not Found */ +#define ERRCODE_CASE_NOT_FOUND MAKE_SQLSTATE('2','0','0','0','0') + +/* Class 21 - Cardinality Violation */ +#define ERRCODE_CARDINALITY_VIOLATION MAKE_SQLSTATE('2','1','0','0','0') + +/* Class 22 - Data Exception */ +#define ERRCODE_DATA_EXCEPTION MAKE_SQLSTATE('2','2','0','0','0') +#define ERRCODE_ARRAY_ELEMENT_ERROR MAKE_SQLSTATE('2','2','0','2','E') +#define ERRCODE_ARRAY_SUBSCRIPT_ERROR MAKE_SQLSTATE('2','2','0','2','E') +#define ERRCODE_CHARACTER_NOT_IN_REPERTOIRE MAKE_SQLSTATE('2','2','0','2','1') +#define ERRCODE_DATETIME_FIELD_OVERFLOW MAKE_SQLSTATE('2','2','0','0','8') +#define ERRCODE_DATETIME_VALUE_OUT_OF_RANGE MAKE_SQLSTATE('2','2','0','0','8') +#define ERRCODE_DIVISION_BY_ZERO MAKE_SQLSTATE('2','2','0','1','2') +#define ERRCODE_ERROR_IN_ASSIGNMENT MAKE_SQLSTATE('2','2','0','0','5') +#define ERRCODE_ESCAPE_CHARACTER_CONFLICT MAKE_SQLSTATE('2','2','0','0','B') +#define ERRCODE_INDICATOR_OVERFLOW MAKE_SQLSTATE('2','2','0','2','2') +#define ERRCODE_INTERVAL_FIELD_OVERFLOW MAKE_SQLSTATE('2','2','0','1','5') +#define ERRCODE_INVALID_ARGUMENT_FOR_LOG MAKE_SQLSTATE('2','2','0','1','E') +#define ERRCODE_INVALID_ARGUMENT_FOR_NTILE MAKE_SQLSTATE('2','2','0','1','4') +#define ERRCODE_INVALID_ARGUMENT_FOR_NTH_VALUE MAKE_SQLSTATE('2','2','0','1','6') +#define ERRCODE_INVALID_ARGUMENT_FOR_POWER_FUNCTION MAKE_SQLSTATE('2','2','0','1','F') +#define ERRCODE_INVALID_ARGUMENT_FOR_WIDTH_BUCKET_FUNCTION MAKE_SQLSTATE('2','2','0','1','G') +#define ERRCODE_INVALID_CHARACTER_VALUE_FOR_CAST MAKE_SQLSTATE('2','2','0','1','8') +#define ERRCODE_INVALID_DATETIME_FORMAT MAKE_SQLSTATE('2','2','0','0','7') +#define ERRCODE_INVALID_ESCAPE_CHARACTER MAKE_SQLSTATE('2','2','0','1','9') +#define ERRCODE_INVALID_ESCAPE_OCTET MAKE_SQLSTATE('2','2','0','0','D') +#define ERRCODE_INVALID_ESCAPE_SEQUENCE MAKE_SQLSTATE('2','2','0','2','5') +#define ERRCODE_NONSTANDARD_USE_OF_ESCAPE_CHARACTER MAKE_SQLSTATE('2','2','P','0','6') +#define ERRCODE_INVALID_INDICATOR_PARAMETER_VALUE MAKE_SQLSTATE('2','2','0','1','0') +#define ERRCODE_INVALID_PARAMETER_VALUE MAKE_SQLSTATE('2','2','0','2','3') +#define ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE MAKE_SQLSTATE('2','2','0','1','3') +#define ERRCODE_INVALID_REGULAR_EXPRESSION MAKE_SQLSTATE('2','2','0','1','B') +#define ERRCODE_INVALID_ROW_COUNT_IN_LIMIT_CLAUSE MAKE_SQLSTATE('2','2','0','1','W') +#define ERRCODE_INVALID_ROW_COUNT_IN_RESULT_OFFSET_CLAUSE MAKE_SQLSTATE('2','2','0','1','X') +#define ERRCODE_INVALID_TABLESAMPLE_ARGUMENT MAKE_SQLSTATE('2','2','0','2','H') +#define ERRCODE_INVALID_TABLESAMPLE_REPEAT MAKE_SQLSTATE('2','2','0','2','G') +#define ERRCODE_INVALID_TIME_ZONE_DISPLACEMENT_VALUE MAKE_SQLSTATE('2','2','0','0','9') +#define ERRCODE_INVALID_USE_OF_ESCAPE_CHARACTER MAKE_SQLSTATE('2','2','0','0','C') +#define ERRCODE_MOST_SPECIFIC_TYPE_MISMATCH MAKE_SQLSTATE('2','2','0','0','G') +#define ERRCODE_NULL_VALUE_NOT_ALLOWED MAKE_SQLSTATE('2','2','0','0','4') +#define ERRCODE_NULL_VALUE_NO_INDICATOR_PARAMETER MAKE_SQLSTATE('2','2','0','0','2') +#define ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE MAKE_SQLSTATE('2','2','0','0','3') +#define ERRCODE_SEQUENCE_GENERATOR_LIMIT_EXCEEDED MAKE_SQLSTATE('2','2','0','0','H') +#define ERRCODE_STRING_DATA_LENGTH_MISMATCH MAKE_SQLSTATE('2','2','0','2','6') +#define ERRCODE_STRING_DATA_RIGHT_TRUNCATION MAKE_SQLSTATE('2','2','0','0','1') +#define ERRCODE_SUBSTRING_ERROR MAKE_SQLSTATE('2','2','0','1','1') +#define ERRCODE_TRIM_ERROR MAKE_SQLSTATE('2','2','0','2','7') +#define ERRCODE_UNTERMINATED_C_STRING MAKE_SQLSTATE('2','2','0','2','4') +#define ERRCODE_ZERO_LENGTH_CHARACTER_STRING MAKE_SQLSTATE('2','2','0','0','F') +#define ERRCODE_FLOATING_POINT_EXCEPTION MAKE_SQLSTATE('2','2','P','0','1') +#define ERRCODE_INVALID_TEXT_REPRESENTATION MAKE_SQLSTATE('2','2','P','0','2') +#define ERRCODE_INVALID_BINARY_REPRESENTATION MAKE_SQLSTATE('2','2','P','0','3') +#define ERRCODE_BAD_COPY_FILE_FORMAT MAKE_SQLSTATE('2','2','P','0','4') +#define ERRCODE_UNTRANSLATABLE_CHARACTER MAKE_SQLSTATE('2','2','P','0','5') +#define ERRCODE_NOT_AN_XML_DOCUMENT MAKE_SQLSTATE('2','2','0','0','L') +#define ERRCODE_INVALID_XML_DOCUMENT MAKE_SQLSTATE('2','2','0','0','M') +#define ERRCODE_INVALID_XML_CONTENT MAKE_SQLSTATE('2','2','0','0','N') +#define ERRCODE_INVALID_XML_COMMENT MAKE_SQLSTATE('2','2','0','0','S') +#define ERRCODE_INVALID_XML_PROCESSING_INSTRUCTION MAKE_SQLSTATE('2','2','0','0','T') +#define ERRCODE_DUPLICATE_JSON_OBJECT_KEY_VALUE MAKE_SQLSTATE('2','2','0','3','0') +#define ERRCODE_INVALID_ARGUMENT_FOR_SQL_JSON_DATETIME_FUNCTION MAKE_SQLSTATE('2','2','0','3','1') +#define ERRCODE_INVALID_JSON_TEXT MAKE_SQLSTATE('2','2','0','3','2') +#define ERRCODE_INVALID_SQL_JSON_SUBSCRIPT MAKE_SQLSTATE('2','2','0','3','3') +#define ERRCODE_MORE_THAN_ONE_SQL_JSON_ITEM MAKE_SQLSTATE('2','2','0','3','4') +#define ERRCODE_NO_SQL_JSON_ITEM MAKE_SQLSTATE('2','2','0','3','5') +#define ERRCODE_NON_NUMERIC_SQL_JSON_ITEM MAKE_SQLSTATE('2','2','0','3','6') +#define ERRCODE_NON_UNIQUE_KEYS_IN_A_JSON_OBJECT MAKE_SQLSTATE('2','2','0','3','7') +#define ERRCODE_SINGLETON_SQL_JSON_ITEM_REQUIRED MAKE_SQLSTATE('2','2','0','3','8') +#define ERRCODE_SQL_JSON_ARRAY_NOT_FOUND MAKE_SQLSTATE('2','2','0','3','9') +#define ERRCODE_SQL_JSON_MEMBER_NOT_FOUND MAKE_SQLSTATE('2','2','0','3','A') +#define ERRCODE_SQL_JSON_NUMBER_NOT_FOUND MAKE_SQLSTATE('2','2','0','3','B') +#define ERRCODE_SQL_JSON_OBJECT_NOT_FOUND MAKE_SQLSTATE('2','2','0','3','C') +#define ERRCODE_TOO_MANY_JSON_ARRAY_ELEMENTS MAKE_SQLSTATE('2','2','0','3','D') +#define ERRCODE_TOO_MANY_JSON_OBJECT_MEMBERS MAKE_SQLSTATE('2','2','0','3','E') +#define ERRCODE_SQL_JSON_SCALAR_REQUIRED MAKE_SQLSTATE('2','2','0','3','F') +#define ERRCODE_SQL_JSON_ITEM_CANNOT_BE_CAST_TO_TARGET_TYPE MAKE_SQLSTATE('2','2','0','3','G') + +/* Class 23 - Integrity Constraint Violation */ +#define ERRCODE_INTEGRITY_CONSTRAINT_VIOLATION MAKE_SQLSTATE('2','3','0','0','0') +#define ERRCODE_RESTRICT_VIOLATION MAKE_SQLSTATE('2','3','0','0','1') +#define ERRCODE_NOT_NULL_VIOLATION MAKE_SQLSTATE('2','3','5','0','2') +#define ERRCODE_FOREIGN_KEY_VIOLATION MAKE_SQLSTATE('2','3','5','0','3') +#define ERRCODE_UNIQUE_VIOLATION MAKE_SQLSTATE('2','3','5','0','5') +#define ERRCODE_CHECK_VIOLATION MAKE_SQLSTATE('2','3','5','1','4') +#define ERRCODE_EXCLUSION_VIOLATION MAKE_SQLSTATE('2','3','P','0','1') + +/* Class 24 - Invalid Cursor State */ +#define ERRCODE_INVALID_CURSOR_STATE MAKE_SQLSTATE('2','4','0','0','0') + +/* Class 25 - Invalid Transaction State */ +#define ERRCODE_INVALID_TRANSACTION_STATE MAKE_SQLSTATE('2','5','0','0','0') +#define ERRCODE_ACTIVE_SQL_TRANSACTION MAKE_SQLSTATE('2','5','0','0','1') +#define ERRCODE_BRANCH_TRANSACTION_ALREADY_ACTIVE MAKE_SQLSTATE('2','5','0','0','2') +#define ERRCODE_HELD_CURSOR_REQUIRES_SAME_ISOLATION_LEVEL MAKE_SQLSTATE('2','5','0','0','8') +#define ERRCODE_INAPPROPRIATE_ACCESS_MODE_FOR_BRANCH_TRANSACTION MAKE_SQLSTATE('2','5','0','0','3') +#define ERRCODE_INAPPROPRIATE_ISOLATION_LEVEL_FOR_BRANCH_TRANSACTION MAKE_SQLSTATE('2','5','0','0','4') +#define ERRCODE_NO_ACTIVE_SQL_TRANSACTION_FOR_BRANCH_TRANSACTION MAKE_SQLSTATE('2','5','0','0','5') +#define ERRCODE_READ_ONLY_SQL_TRANSACTION MAKE_SQLSTATE('2','5','0','0','6') +#define ERRCODE_SCHEMA_AND_DATA_STATEMENT_MIXING_NOT_SUPPORTED MAKE_SQLSTATE('2','5','0','0','7') +#define ERRCODE_NO_ACTIVE_SQL_TRANSACTION MAKE_SQLSTATE('2','5','P','0','1') +#define ERRCODE_IN_FAILED_SQL_TRANSACTION MAKE_SQLSTATE('2','5','P','0','2') +#define ERRCODE_IDLE_IN_TRANSACTION_SESSION_TIMEOUT MAKE_SQLSTATE('2','5','P','0','3') + +/* Class 26 - Invalid SQL Statement Name */ +#define ERRCODE_INVALID_SQL_STATEMENT_NAME MAKE_SQLSTATE('2','6','0','0','0') + +/* Class 27 - Triggered Data Change Violation */ +#define ERRCODE_TRIGGERED_DATA_CHANGE_VIOLATION MAKE_SQLSTATE('2','7','0','0','0') + +/* Class 28 - Invalid Authorization Specification */ +#define ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION MAKE_SQLSTATE('2','8','0','0','0') +#define ERRCODE_INVALID_PASSWORD MAKE_SQLSTATE('2','8','P','0','1') + +/* Class 2B - Dependent Privilege Descriptors Still Exist */ +#define ERRCODE_DEPENDENT_PRIVILEGE_DESCRIPTORS_STILL_EXIST MAKE_SQLSTATE('2','B','0','0','0') +#define ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST MAKE_SQLSTATE('2','B','P','0','1') + +/* Class 2D - Invalid Transaction Termination */ +#define ERRCODE_INVALID_TRANSACTION_TERMINATION MAKE_SQLSTATE('2','D','0','0','0') + +/* Class 2F - SQL Routine Exception */ +#define ERRCODE_SQL_ROUTINE_EXCEPTION MAKE_SQLSTATE('2','F','0','0','0') +#define ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT MAKE_SQLSTATE('2','F','0','0','5') +#define ERRCODE_S_R_E_MODIFYING_SQL_DATA_NOT_PERMITTED MAKE_SQLSTATE('2','F','0','0','2') +#define ERRCODE_S_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED MAKE_SQLSTATE('2','F','0','0','3') +#define ERRCODE_S_R_E_READING_SQL_DATA_NOT_PERMITTED MAKE_SQLSTATE('2','F','0','0','4') + +/* Class 34 - Invalid Cursor Name */ +#define ERRCODE_INVALID_CURSOR_NAME MAKE_SQLSTATE('3','4','0','0','0') + +/* Class 38 - External Routine Exception */ +#define ERRCODE_EXTERNAL_ROUTINE_EXCEPTION MAKE_SQLSTATE('3','8','0','0','0') +#define ERRCODE_E_R_E_CONTAINING_SQL_NOT_PERMITTED MAKE_SQLSTATE('3','8','0','0','1') +#define ERRCODE_E_R_E_MODIFYING_SQL_DATA_NOT_PERMITTED MAKE_SQLSTATE('3','8','0','0','2') +#define ERRCODE_E_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED MAKE_SQLSTATE('3','8','0','0','3') +#define ERRCODE_E_R_E_READING_SQL_DATA_NOT_PERMITTED MAKE_SQLSTATE('3','8','0','0','4') + +/* Class 39 - External Routine Invocation Exception */ +#define ERRCODE_EXTERNAL_ROUTINE_INVOCATION_EXCEPTION MAKE_SQLSTATE('3','9','0','0','0') +#define ERRCODE_E_R_I_E_INVALID_SQLSTATE_RETURNED MAKE_SQLSTATE('3','9','0','0','1') +#define ERRCODE_E_R_I_E_NULL_VALUE_NOT_ALLOWED MAKE_SQLSTATE('3','9','0','0','4') +#define ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED MAKE_SQLSTATE('3','9','P','0','1') +#define ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED MAKE_SQLSTATE('3','9','P','0','2') +#define ERRCODE_E_R_I_E_EVENT_TRIGGER_PROTOCOL_VIOLATED MAKE_SQLSTATE('3','9','P','0','3') + +/* Class 3B - Savepoint Exception */ +#define ERRCODE_SAVEPOINT_EXCEPTION MAKE_SQLSTATE('3','B','0','0','0') +#define ERRCODE_S_E_INVALID_SPECIFICATION MAKE_SQLSTATE('3','B','0','0','1') + +/* Class 3D - Invalid Catalog Name */ +#define ERRCODE_INVALID_CATALOG_NAME MAKE_SQLSTATE('3','D','0','0','0') + +/* Class 3F - Invalid Schema Name */ +#define ERRCODE_INVALID_SCHEMA_NAME MAKE_SQLSTATE('3','F','0','0','0') + +/* Class 40 - Transaction Rollback */ +#define ERRCODE_TRANSACTION_ROLLBACK MAKE_SQLSTATE('4','0','0','0','0') +#define ERRCODE_T_R_INTEGRITY_CONSTRAINT_VIOLATION MAKE_SQLSTATE('4','0','0','0','2') +#define ERRCODE_T_R_SERIALIZATION_FAILURE MAKE_SQLSTATE('4','0','0','0','1') +#define ERRCODE_T_R_STATEMENT_COMPLETION_UNKNOWN MAKE_SQLSTATE('4','0','0','0','3') +#define ERRCODE_T_R_DEADLOCK_DETECTED MAKE_SQLSTATE('4','0','P','0','1') + +/* Class 42 - Syntax Error or Access Rule Violation */ +#define ERRCODE_SYNTAX_ERROR_OR_ACCESS_RULE_VIOLATION MAKE_SQLSTATE('4','2','0','0','0') +#define ERRCODE_SYNTAX_ERROR MAKE_SQLSTATE('4','2','6','0','1') +#define ERRCODE_INSUFFICIENT_PRIVILEGE MAKE_SQLSTATE('4','2','5','0','1') +#define ERRCODE_CANNOT_COERCE MAKE_SQLSTATE('4','2','8','4','6') +#define ERRCODE_GROUPING_ERROR MAKE_SQLSTATE('4','2','8','0','3') +#define ERRCODE_WINDOWING_ERROR MAKE_SQLSTATE('4','2','P','2','0') +#define ERRCODE_INVALID_RECURSION MAKE_SQLSTATE('4','2','P','1','9') +#define ERRCODE_INVALID_FOREIGN_KEY MAKE_SQLSTATE('4','2','8','3','0') +#define ERRCODE_INVALID_NAME MAKE_SQLSTATE('4','2','6','0','2') +#define ERRCODE_NAME_TOO_LONG MAKE_SQLSTATE('4','2','6','2','2') +#define ERRCODE_RESERVED_NAME MAKE_SQLSTATE('4','2','9','3','9') +#define ERRCODE_DATATYPE_MISMATCH MAKE_SQLSTATE('4','2','8','0','4') +#define ERRCODE_INDETERMINATE_DATATYPE MAKE_SQLSTATE('4','2','P','1','8') +#define ERRCODE_COLLATION_MISMATCH MAKE_SQLSTATE('4','2','P','2','1') +#define ERRCODE_INDETERMINATE_COLLATION MAKE_SQLSTATE('4','2','P','2','2') +#define ERRCODE_WRONG_OBJECT_TYPE MAKE_SQLSTATE('4','2','8','0','9') +#define ERRCODE_GENERATED_ALWAYS MAKE_SQLSTATE('4','2','8','C','9') +#define ERRCODE_UNDEFINED_COLUMN MAKE_SQLSTATE('4','2','7','0','3') +#define ERRCODE_UNDEFINED_CURSOR MAKE_SQLSTATE('3','4','0','0','0') +#define ERRCODE_UNDEFINED_DATABASE MAKE_SQLSTATE('3','D','0','0','0') +#define ERRCODE_UNDEFINED_FUNCTION MAKE_SQLSTATE('4','2','8','8','3') +#define ERRCODE_UNDEFINED_PSTATEMENT MAKE_SQLSTATE('2','6','0','0','0') +#define ERRCODE_UNDEFINED_SCHEMA MAKE_SQLSTATE('3','F','0','0','0') +#define ERRCODE_UNDEFINED_TABLE MAKE_SQLSTATE('4','2','P','0','1') +#define ERRCODE_UNDEFINED_PARAMETER MAKE_SQLSTATE('4','2','P','0','2') +#define ERRCODE_UNDEFINED_OBJECT MAKE_SQLSTATE('4','2','7','0','4') +#define ERRCODE_DUPLICATE_COLUMN MAKE_SQLSTATE('4','2','7','0','1') +#define ERRCODE_DUPLICATE_CURSOR MAKE_SQLSTATE('4','2','P','0','3') +#define ERRCODE_DUPLICATE_DATABASE MAKE_SQLSTATE('4','2','P','0','4') +#define ERRCODE_DUPLICATE_FUNCTION MAKE_SQLSTATE('4','2','7','2','3') +#define ERRCODE_DUPLICATE_PSTATEMENT MAKE_SQLSTATE('4','2','P','0','5') +#define ERRCODE_DUPLICATE_SCHEMA MAKE_SQLSTATE('4','2','P','0','6') +#define ERRCODE_DUPLICATE_TABLE MAKE_SQLSTATE('4','2','P','0','7') +#define ERRCODE_DUPLICATE_ALIAS MAKE_SQLSTATE('4','2','7','1','2') +#define ERRCODE_DUPLICATE_OBJECT MAKE_SQLSTATE('4','2','7','1','0') +#define ERRCODE_AMBIGUOUS_COLUMN MAKE_SQLSTATE('4','2','7','0','2') +#define ERRCODE_AMBIGUOUS_FUNCTION MAKE_SQLSTATE('4','2','7','2','5') +#define ERRCODE_AMBIGUOUS_PARAMETER MAKE_SQLSTATE('4','2','P','0','8') +#define ERRCODE_AMBIGUOUS_ALIAS MAKE_SQLSTATE('4','2','P','0','9') +#define ERRCODE_INVALID_COLUMN_REFERENCE MAKE_SQLSTATE('4','2','P','1','0') +#define ERRCODE_INVALID_COLUMN_DEFINITION MAKE_SQLSTATE('4','2','6','1','1') +#define ERRCODE_INVALID_CURSOR_DEFINITION MAKE_SQLSTATE('4','2','P','1','1') +#define ERRCODE_INVALID_DATABASE_DEFINITION MAKE_SQLSTATE('4','2','P','1','2') +#define ERRCODE_INVALID_FUNCTION_DEFINITION MAKE_SQLSTATE('4','2','P','1','3') +#define ERRCODE_INVALID_PSTATEMENT_DEFINITION MAKE_SQLSTATE('4','2','P','1','4') +#define ERRCODE_INVALID_SCHEMA_DEFINITION MAKE_SQLSTATE('4','2','P','1','5') +#define ERRCODE_INVALID_TABLE_DEFINITION MAKE_SQLSTATE('4','2','P','1','6') +#define ERRCODE_INVALID_OBJECT_DEFINITION MAKE_SQLSTATE('4','2','P','1','7') + +/* Class 44 - WITH CHECK OPTION Violation */ +#define ERRCODE_WITH_CHECK_OPTION_VIOLATION MAKE_SQLSTATE('4','4','0','0','0') + +/* Class 53 - Insufficient Resources */ +#define ERRCODE_INSUFFICIENT_RESOURCES MAKE_SQLSTATE('5','3','0','0','0') +#define ERRCODE_DISK_FULL MAKE_SQLSTATE('5','3','1','0','0') +#define ERRCODE_OUT_OF_MEMORY MAKE_SQLSTATE('5','3','2','0','0') +#define ERRCODE_TOO_MANY_CONNECTIONS MAKE_SQLSTATE('5','3','3','0','0') +#define ERRCODE_CONFIGURATION_LIMIT_EXCEEDED MAKE_SQLSTATE('5','3','4','0','0') + +/* Class 54 - Program Limit Exceeded */ +#define ERRCODE_PROGRAM_LIMIT_EXCEEDED MAKE_SQLSTATE('5','4','0','0','0') +#define ERRCODE_STATEMENT_TOO_COMPLEX MAKE_SQLSTATE('5','4','0','0','1') +#define ERRCODE_TOO_MANY_COLUMNS MAKE_SQLSTATE('5','4','0','1','1') +#define ERRCODE_TOO_MANY_ARGUMENTS MAKE_SQLSTATE('5','4','0','2','3') + +/* Class 55 - Object Not In Prerequisite State */ +#define ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE MAKE_SQLSTATE('5','5','0','0','0') +#define ERRCODE_OBJECT_IN_USE MAKE_SQLSTATE('5','5','0','0','6') +#define ERRCODE_CANT_CHANGE_RUNTIME_PARAM MAKE_SQLSTATE('5','5','P','0','2') +#define ERRCODE_LOCK_NOT_AVAILABLE MAKE_SQLSTATE('5','5','P','0','3') +#define ERRCODE_UNSAFE_NEW_ENUM_VALUE_USAGE MAKE_SQLSTATE('5','5','P','0','4') + +/* Class 57 - Operator Intervention */ +#define ERRCODE_OPERATOR_INTERVENTION MAKE_SQLSTATE('5','7','0','0','0') +#define ERRCODE_QUERY_CANCELED MAKE_SQLSTATE('5','7','0','1','4') +#define ERRCODE_ADMIN_SHUTDOWN MAKE_SQLSTATE('5','7','P','0','1') +#define ERRCODE_CRASH_SHUTDOWN MAKE_SQLSTATE('5','7','P','0','2') +#define ERRCODE_CANNOT_CONNECT_NOW MAKE_SQLSTATE('5','7','P','0','3') +#define ERRCODE_DATABASE_DROPPED MAKE_SQLSTATE('5','7','P','0','4') +#define ERRCODE_IDLE_SESSION_TIMEOUT MAKE_SQLSTATE('5','7','P','0','5') + +/* Class 58 - System Error (errors external to PostgreSQL itself) */ +#define ERRCODE_SYSTEM_ERROR MAKE_SQLSTATE('5','8','0','0','0') +#define ERRCODE_IO_ERROR MAKE_SQLSTATE('5','8','0','3','0') +#define ERRCODE_UNDEFINED_FILE MAKE_SQLSTATE('5','8','P','0','1') +#define ERRCODE_DUPLICATE_FILE MAKE_SQLSTATE('5','8','P','0','2') + +/* Class 72 - Snapshot Failure */ +#define ERRCODE_SNAPSHOT_TOO_OLD MAKE_SQLSTATE('7','2','0','0','0') + +/* Class F0 - Configuration File Error */ +#define ERRCODE_CONFIG_FILE_ERROR MAKE_SQLSTATE('F','0','0','0','0') +#define ERRCODE_LOCK_FILE_EXISTS MAKE_SQLSTATE('F','0','0','0','1') + +/* Class HV - Foreign Data Wrapper Error (SQL/MED) */ +#define ERRCODE_FDW_ERROR MAKE_SQLSTATE('H','V','0','0','0') +#define ERRCODE_FDW_COLUMN_NAME_NOT_FOUND MAKE_SQLSTATE('H','V','0','0','5') +#define ERRCODE_FDW_DYNAMIC_PARAMETER_VALUE_NEEDED MAKE_SQLSTATE('H','V','0','0','2') +#define ERRCODE_FDW_FUNCTION_SEQUENCE_ERROR MAKE_SQLSTATE('H','V','0','1','0') +#define ERRCODE_FDW_INCONSISTENT_DESCRIPTOR_INFORMATION MAKE_SQLSTATE('H','V','0','2','1') +#define ERRCODE_FDW_INVALID_ATTRIBUTE_VALUE MAKE_SQLSTATE('H','V','0','2','4') +#define ERRCODE_FDW_INVALID_COLUMN_NAME MAKE_SQLSTATE('H','V','0','0','7') +#define ERRCODE_FDW_INVALID_COLUMN_NUMBER MAKE_SQLSTATE('H','V','0','0','8') +#define ERRCODE_FDW_INVALID_DATA_TYPE MAKE_SQLSTATE('H','V','0','0','4') +#define ERRCODE_FDW_INVALID_DATA_TYPE_DESCRIPTORS MAKE_SQLSTATE('H','V','0','0','6') +#define ERRCODE_FDW_INVALID_DESCRIPTOR_FIELD_IDENTIFIER MAKE_SQLSTATE('H','V','0','9','1') +#define ERRCODE_FDW_INVALID_HANDLE MAKE_SQLSTATE('H','V','0','0','B') +#define ERRCODE_FDW_INVALID_OPTION_INDEX MAKE_SQLSTATE('H','V','0','0','C') +#define ERRCODE_FDW_INVALID_OPTION_NAME MAKE_SQLSTATE('H','V','0','0','D') +#define ERRCODE_FDW_INVALID_STRING_LENGTH_OR_BUFFER_LENGTH MAKE_SQLSTATE('H','V','0','9','0') +#define ERRCODE_FDW_INVALID_STRING_FORMAT MAKE_SQLSTATE('H','V','0','0','A') +#define ERRCODE_FDW_INVALID_USE_OF_NULL_POINTER MAKE_SQLSTATE('H','V','0','0','9') +#define ERRCODE_FDW_TOO_MANY_HANDLES MAKE_SQLSTATE('H','V','0','1','4') +#define ERRCODE_FDW_OUT_OF_MEMORY MAKE_SQLSTATE('H','V','0','0','1') +#define ERRCODE_FDW_NO_SCHEMAS MAKE_SQLSTATE('H','V','0','0','P') +#define ERRCODE_FDW_OPTION_NAME_NOT_FOUND MAKE_SQLSTATE('H','V','0','0','J') +#define ERRCODE_FDW_REPLY_HANDLE MAKE_SQLSTATE('H','V','0','0','K') +#define ERRCODE_FDW_SCHEMA_NOT_FOUND MAKE_SQLSTATE('H','V','0','0','Q') +#define ERRCODE_FDW_TABLE_NOT_FOUND MAKE_SQLSTATE('H','V','0','0','R') +#define ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION MAKE_SQLSTATE('H','V','0','0','L') +#define ERRCODE_FDW_UNABLE_TO_CREATE_REPLY MAKE_SQLSTATE('H','V','0','0','M') +#define ERRCODE_FDW_UNABLE_TO_ESTABLISH_CONNECTION MAKE_SQLSTATE('H','V','0','0','N') + +/* Class P0 - PL/pgSQL Error */ +#define ERRCODE_PLPGSQL_ERROR MAKE_SQLSTATE('P','0','0','0','0') +#define ERRCODE_RAISE_EXCEPTION MAKE_SQLSTATE('P','0','0','0','1') +#define ERRCODE_NO_DATA_FOUND MAKE_SQLSTATE('P','0','0','0','2') +#define ERRCODE_TOO_MANY_ROWS MAKE_SQLSTATE('P','0','0','0','3') +#define ERRCODE_ASSERT_FAILURE MAKE_SQLSTATE('P','0','0','0','4') + +/* Class XX - Internal Error */ +#define ERRCODE_INTERNAL_ERROR MAKE_SQLSTATE('X','X','0','0','0') +#define ERRCODE_DATA_CORRUPTED MAKE_SQLSTATE('X','X','0','0','1') +#define ERRCODE_INDEX_CORRUPTED MAKE_SQLSTATE('X','X','0','0','2') diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/error/assert.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/error/assert.c new file mode 100644 index 00000000000..719dd7b3092 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/error/assert.c @@ -0,0 +1,67 @@ +/*------------------------------------------------------------------------- + * + * assert.c + * Assert support code. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/error/assert.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <unistd.h> +#ifdef HAVE_EXECINFO_H +#include <execinfo.h> +#endif + +/* + * ExceptionalCondition - Handles the failure of an Assert() + * + * We intentionally do not go through elog() here, on the grounds of + * wanting to minimize the amount of infrastructure that has to be + * working to report an assertion failure. + */ +void +ExceptionalCondition(const char *conditionName, + const char *fileName, + int lineNumber) +{ + /* Report the failure on stderr (or local equivalent) */ + if (!PointerIsValid(conditionName) + || !PointerIsValid(fileName)) + write_stderr("TRAP: ExceptionalCondition: bad arguments in PID %d\n", + (int) getpid()); + else + write_stderr("TRAP: failed Assert(\"%s\"), File: \"%s\", Line: %d, PID: %d\n", + conditionName, fileName, lineNumber, (int) getpid()); + + /* Usually this shouldn't be needed, but make sure the msg went out */ + fflush(stderr); + + /* If we have support for it, dump a simple backtrace */ +#ifdef HAVE_BACKTRACE_SYMBOLS + { + void *buf[100]; + int nframes; + + nframes = backtrace(buf, lengthof(buf)); + backtrace_symbols_fd(buf, nframes, fileno(stderr)); + } +#endif + + /* + * If configured to do so, sleep indefinitely to allow user to attach a + * debugger. It would be nice to use pg_usleep() here, but that can sleep + * at most 2G usec or ~33 minutes, which seems too short. + */ +#ifdef SLEEP_ON_ASSERT + sleep(1000000); +#endif + + abort(); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/error/csvlog.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/error/csvlog.c new file mode 100644 index 00000000000..9d11f14c066 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/error/csvlog.c @@ -0,0 +1,264 @@ +/*------------------------------------------------------------------------- + * + * csvlog.c + * CSV logging + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/error/csvlog.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/xact.h" +#include "libpq/libpq.h" +#include "lib/stringinfo.h" +#include "miscadmin.h" +#include "postmaster/bgworker.h" +#include "postmaster/syslogger.h" +#include "storage/lock.h" +#include "storage/proc.h" +#include "tcop/tcopprot.h" +#include "utils/backend_status.h" +#include "utils/elog.h" +#include "utils/guc.h" +#include "utils/ps_status.h" + + +/* + * append a CSV'd version of a string to a StringInfo + * We use the PostgreSQL defaults for CSV, i.e. quote = escape = '"' + * If it's NULL, append nothing. + */ +static inline void +appendCSVLiteral(StringInfo buf, const char *data) +{ + const char *p = data; + char c; + + /* avoid confusing an empty string with NULL */ + if (p == NULL) + return; + + appendStringInfoCharMacro(buf, '"'); + while ((c = *p++) != '\0') + { + if (c == '"') + appendStringInfoCharMacro(buf, '"'); + appendStringInfoCharMacro(buf, c); + } + appendStringInfoCharMacro(buf, '"'); +} + +/* + * write_csvlog -- Generate and write CSV log entry + * + * Constructs the error message, depending on the Errordata it gets, in a CSV + * format which is described in doc/src/sgml/config.sgml. + */ +void +write_csvlog(ErrorData *edata) +{ + StringInfoData buf; + bool print_stmt = false; + + /* static counter for line numbers */ + static __thread long log_line_number = 0; + + /* has counter been reset in current process? */ + static __thread int log_my_pid = 0; + + /* + * This is one of the few places where we'd rather not inherit a static + * variable's value from the postmaster. But since we will, reset it when + * MyProcPid changes. + */ + if (log_my_pid != MyProcPid) + { + log_line_number = 0; + log_my_pid = MyProcPid; + reset_formatted_start_time(); + } + log_line_number++; + + initStringInfo(&buf); + + /* timestamp with milliseconds */ + appendStringInfoString(&buf, get_formatted_log_time()); + appendStringInfoChar(&buf, ','); + + /* username */ + if (MyProcPort) + appendCSVLiteral(&buf, MyProcPort->user_name); + appendStringInfoChar(&buf, ','); + + /* database name */ + if (MyProcPort) + appendCSVLiteral(&buf, MyProcPort->database_name); + appendStringInfoChar(&buf, ','); + + /* Process id */ + if (MyProcPid != 0) + appendStringInfo(&buf, "%d", MyProcPid); + appendStringInfoChar(&buf, ','); + + /* Remote host and port */ + if (MyProcPort && MyProcPort->remote_host) + { + appendStringInfoChar(&buf, '"'); + appendStringInfoString(&buf, MyProcPort->remote_host); + if (MyProcPort->remote_port && MyProcPort->remote_port[0] != '\0') + { + appendStringInfoChar(&buf, ':'); + appendStringInfoString(&buf, MyProcPort->remote_port); + } + appendStringInfoChar(&buf, '"'); + } + appendStringInfoChar(&buf, ','); + + /* session id */ + appendStringInfo(&buf, "%lx.%x", (long) MyStartTime, MyProcPid); + appendStringInfoChar(&buf, ','); + + /* Line number */ + appendStringInfo(&buf, "%ld", log_line_number); + appendStringInfoChar(&buf, ','); + + /* PS display */ + if (MyProcPort) + { + StringInfoData msgbuf; + const char *psdisp; + int displen; + + initStringInfo(&msgbuf); + + psdisp = get_ps_display(&displen); + appendBinaryStringInfo(&msgbuf, psdisp, displen); + appendCSVLiteral(&buf, msgbuf.data); + + pfree(msgbuf.data); + } + appendStringInfoChar(&buf, ','); + + /* session start timestamp */ + appendStringInfoString(&buf, get_formatted_start_time()); + appendStringInfoChar(&buf, ','); + + /* Virtual transaction id */ + /* keep VXID format in sync with lockfuncs.c */ + if (MyProc != NULL && MyProc->backendId != InvalidBackendId) + appendStringInfo(&buf, "%d/%u", MyProc->backendId, MyProc->lxid); + appendStringInfoChar(&buf, ','); + + /* Transaction id */ + appendStringInfo(&buf, "%u", GetTopTransactionIdIfAny()); + appendStringInfoChar(&buf, ','); + + /* Error severity */ + appendStringInfoString(&buf, _(error_severity(edata->elevel))); + appendStringInfoChar(&buf, ','); + + /* SQL state code */ + appendStringInfoString(&buf, unpack_sql_state(edata->sqlerrcode)); + appendStringInfoChar(&buf, ','); + + /* errmessage */ + appendCSVLiteral(&buf, edata->message); + appendStringInfoChar(&buf, ','); + + /* errdetail or errdetail_log */ + if (edata->detail_log) + appendCSVLiteral(&buf, edata->detail_log); + else + appendCSVLiteral(&buf, edata->detail); + appendStringInfoChar(&buf, ','); + + /* errhint */ + appendCSVLiteral(&buf, edata->hint); + appendStringInfoChar(&buf, ','); + + /* internal query */ + appendCSVLiteral(&buf, edata->internalquery); + appendStringInfoChar(&buf, ','); + + /* if printed internal query, print internal pos too */ + if (edata->internalpos > 0 && edata->internalquery != NULL) + appendStringInfo(&buf, "%d", edata->internalpos); + appendStringInfoChar(&buf, ','); + + /* errcontext */ + if (!edata->hide_ctx) + appendCSVLiteral(&buf, edata->context); + appendStringInfoChar(&buf, ','); + + /* user query --- only reported if not disabled by the caller */ + print_stmt = check_log_of_query(edata); + if (print_stmt) + appendCSVLiteral(&buf, debug_query_string); + appendStringInfoChar(&buf, ','); + if (print_stmt && edata->cursorpos > 0) + appendStringInfo(&buf, "%d", edata->cursorpos); + appendStringInfoChar(&buf, ','); + + /* file error location */ + if (Log_error_verbosity >= PGERROR_VERBOSE) + { + StringInfoData msgbuf; + + initStringInfo(&msgbuf); + + if (edata->funcname && edata->filename) + appendStringInfo(&msgbuf, "%s, %s:%d", + edata->funcname, edata->filename, + edata->lineno); + else if (edata->filename) + appendStringInfo(&msgbuf, "%s:%d", + edata->filename, edata->lineno); + appendCSVLiteral(&buf, msgbuf.data); + pfree(msgbuf.data); + } + appendStringInfoChar(&buf, ','); + + /* application name */ + if (application_name) + appendCSVLiteral(&buf, application_name); + + appendStringInfoChar(&buf, ','); + + /* backend type */ + appendCSVLiteral(&buf, get_backend_type_for_log()); + appendStringInfoChar(&buf, ','); + + /* leader PID */ + if (MyProc) + { + PGPROC *leader = MyProc->lockGroupLeader; + + /* + * Show the leader only for active parallel workers. This leaves out + * the leader of a parallel group. + */ + if (leader && leader->pid != MyProcPid) + appendStringInfo(&buf, "%d", leader->pid); + } + appendStringInfoChar(&buf, ','); + + /* query id */ + appendStringInfo(&buf, "%lld", (long long) pgstat_get_my_query_id()); + + appendStringInfoChar(&buf, '\n'); + + /* If in the syslogger process, try to write messages direct to file */ + if (MyBackendType == B_LOGGER) + write_syslogger_file(buf.data, buf.len, LOG_DESTINATION_CSVLOG); + else + write_pipe_chunks(buf.data, buf.len, LOG_DESTINATION_CSVLOG); + + pfree(buf.data); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/error/elog.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/error/elog.c new file mode 100644 index 00000000000..18db7d7a74f --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/error/elog.c @@ -0,0 +1,3799 @@ +/*------------------------------------------------------------------------- + * + * elog.c + * error logging and reporting + * + * Because of the extremely high rate at which log messages can be generated, + * we need to be mindful of the performance cost of obtaining any information + * that may be logged. Also, it's important to keep in mind that this code may + * get called from within an aborted transaction, in which case operations + * such as syscache lookups are unsafe. + * + * Some notes about recursion and errors during error processing: + * + * We need to be robust about recursive-error scenarios --- for example, + * if we run out of memory, it's important to be able to report that fact. + * There are a number of considerations that go into this. + * + * First, distinguish between re-entrant use and actual recursion. It + * is possible for an error or warning message to be emitted while the + * parameters for an error message are being computed. In this case + * errstart has been called for the outer message, and some field values + * may have already been saved, but we are not actually recursing. We handle + * this by providing a (small) stack of ErrorData records. The inner message + * can be computed and sent without disturbing the state of the outer message. + * (If the inner message is actually an error, this isn't very interesting + * because control won't come back to the outer message generator ... but + * if the inner message is only debug or log data, this is critical.) + * + * Second, actual recursion will occur if an error is reported by one of + * the elog.c routines or something they call. By far the most probable + * scenario of this sort is "out of memory"; and it's also the nastiest + * to handle because we'd likely also run out of memory while trying to + * report this error! Our escape hatch for this case is to reset the + * ErrorContext to empty before trying to process the inner error. Since + * ErrorContext is guaranteed to have at least 8K of space in it (see mcxt.c), + * we should be able to process an "out of memory" message successfully. + * Since we lose the prior error state due to the reset, we won't be able + * to return to processing the original error, but we wouldn't have anyway. + * (NOTE: the escape hatch is not used for recursive situations where the + * inner message is of less than ERROR severity; in that case we just + * try to process it and return normally. Usually this will work, but if + * it ends up in infinite recursion, we will PANIC due to error stack + * overflow.) + * + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/error/elog.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <fcntl.h> +#include <time.h> +#include <unistd.h> +#include <signal.h> +#include <ctype.h> +#ifdef HAVE_SYSLOG +#include <syslog.h> +#endif +#ifdef HAVE_EXECINFO_H +#include <execinfo.h> +#endif + +#include "access/transam.h" +#include "access/xact.h" +#include "libpq/libpq.h" +#include "libpq/pqformat.h" +#include "mb/pg_wchar.h" +#include "nodes/miscnodes.h" +#include "miscadmin.h" +#include "pgstat.h" +#include "postmaster/bgworker.h" +#include "postmaster/postmaster.h" +#include "postmaster/syslogger.h" +#include "storage/ipc.h" +#include "storage/proc.h" +#include "tcop/tcopprot.h" +#include "utils/guc_hooks.h" +#include "utils/memutils.h" +#include "utils/ps_status.h" +#include "utils/varlena.h" + + +/* In this module, access gettext() via err_gettext() */ +#undef _ +#define _(x) err_gettext(x) + + +/* Global variables */ +__thread ErrorContextCallback *error_context_stack = NULL; + +__thread sigjmp_buf *PG_exception_stack = NULL; +__thread bool yql_error_report_active = false; + +extern __thread bool redirection_done; + +/* + * Hook for intercepting messages before they are sent to the server log. + * Note that the hook will not get called for messages that are suppressed + * by log_min_messages. Also note that logging hooks implemented in preload + * libraries will miss any log messages that are generated before the + * library is loaded. + */ +__thread emit_log_hook_type emit_log_hook = NULL; + +/* GUC parameters */ +__thread int Log_error_verbosity = PGERROR_DEFAULT; +__thread char *Log_line_prefix = NULL; /* format for extra log line info */ +__thread int Log_destination = LOG_DESTINATION_STDERR; +__thread char *Log_destination_string = NULL; +__thread bool syslog_sequence_numbers = true; +__thread bool syslog_split_messages = true; + +/* Processed form of backtrace_symbols GUC */ +static __thread char *backtrace_symbol_list; + +#ifdef HAVE_SYSLOG + +/* + * Max string length to send to syslog(). Note that this doesn't count the + * sequence-number prefix we add, and of course it doesn't count the prefix + * added by syslog itself. Solaris and sysklogd truncate the final message + * at 1024 bytes, so this value leaves 124 bytes for those prefixes. (Most + * other syslog implementations seem to have limits of 2KB or so.) + */ +#ifndef PG_SYSLOG_LIMIT +#define PG_SYSLOG_LIMIT 900 +#endif + +static __thread bool openlog_done = false; +static __thread char *syslog_ident = NULL; +static __thread int syslog_facility = LOG_LOCAL0; + +static void write_syslog(int level, const char *line); +#endif + +#ifdef WIN32 +extern __thread char *event_source; + +static void write_eventlog(int level, const char *line, int len); +#endif + +/* We provide a small stack of ErrorData records for re-entrant cases */ +#define ERRORDATA_STACK_SIZE 5 + +static __thread ErrorData errordata[ERRORDATA_STACK_SIZE]; + +static __thread int errordata_stack_depth = -1; /* index of topmost active frame */ + +static __thread int recursion_depth = 0; /* to detect actual recursion */ + +/* + * Saved timeval and buffers for formatted timestamps that might be used by + * both log_line_prefix and csv logs. + */ +static __thread struct timeval saved_timeval; +static __thread bool saved_timeval_set = false; + +#define FORMATTED_TS_LEN 128 +static __thread char formatted_start_time[FORMATTED_TS_LEN]; +static __thread char formatted_log_time[FORMATTED_TS_LEN]; + + +/* Macro for checking errordata_stack_depth is reasonable */ +#define CHECK_STACK_DEPTH() \ + do { \ + if (errordata_stack_depth < 0) \ + { \ + errordata_stack_depth = -1; \ + ereport(ERROR, (errmsg_internal("errstart was not called"))); \ + } \ + } while (0) + + +static const char *err_gettext(const char *str) pg_attribute_format_arg(1); +static ErrorData *get_error_stack_entry(void); +static void set_stack_entry_domain(ErrorData *edata, const char *domain); +static void set_stack_entry_location(ErrorData *edata, + const char *filename, int lineno, + const char *funcname); +static bool matches_backtrace_functions(const char *funcname); +static pg_noinline void set_backtrace(ErrorData *edata, int num_skip); +static void set_errdata_field(MemoryContextData *cxt, char **ptr, const char *str); +static void FreeErrorDataContents(ErrorData *edata); +static void write_console(const char *line, int len); +static const char *process_log_prefix_padding(const char *p, int *ppadding); +static void log_line_prefix(StringInfo buf, ErrorData *edata); +static void send_message_to_server_log(ErrorData *edata); +static void send_message_to_frontend(ErrorData *edata); +static void append_with_tabs(StringInfo buf, const char *str); + + +/* + * is_log_level_output -- is elevel logically >= log_min_level? + * + * We use this for tests that should consider LOG to sort out-of-order, + * between ERROR and FATAL. Generally this is the right thing for testing + * whether a message should go to the postmaster log, whereas a simple >= + * test is correct for testing whether the message should go to the client. + */ +static inline bool +is_log_level_output(int elevel, int log_min_level) +{ + if (elevel == LOG || elevel == LOG_SERVER_ONLY) + { + if (log_min_level == LOG || log_min_level <= ERROR) + return true; + } + else if (elevel == WARNING_CLIENT_ONLY) + { + /* never sent to log, regardless of log_min_level */ + return false; + } + else if (log_min_level == LOG) + { + /* elevel != LOG */ + if (elevel >= FATAL) + return true; + } + /* Neither is LOG */ + else if (elevel >= log_min_level) + return true; + + return false; +} + +/* + * Policy-setting subroutines. These are fairly simple, but it seems wise + * to have the code in just one place. + */ + +/* + * should_output_to_server --- should message of given elevel go to the log? + */ +static inline bool +should_output_to_server(int elevel) +{ + return is_log_level_output(elevel, log_min_messages); +} + +/* + * should_output_to_client --- should message of given elevel go to the client? + */ +static inline bool +should_output_to_client(int elevel) +{ + if (whereToSendOutput == DestRemote && elevel != LOG_SERVER_ONLY) + { + /* + * client_min_messages is honored only after we complete the + * authentication handshake. This is required both for security + * reasons and because many clients can't handle NOTICE messages + * during authentication. + */ + if (ClientAuthInProgress) + return (elevel >= ERROR); + else + return (elevel >= client_min_messages || elevel == INFO); + } + return false; +} + + +/* + * message_level_is_interesting --- would ereport/elog do anything? + * + * Returns true if ereport/elog with this elevel will not be a no-op. + * This is useful to short-circuit any expensive preparatory work that + * might be needed for a logging message. There is no point in + * prepending this to a bare ereport/elog call, however. + */ +bool +message_level_is_interesting(int elevel) +{ + /* + * Keep this in sync with the decision-making in errstart(). + */ + if (elevel >= ERROR || + should_output_to_server(elevel) || + should_output_to_client(elevel)) + return true; + return false; +} + + +/* + * in_error_recursion_trouble --- are we at risk of infinite error recursion? + * + * This function exists to provide common control of various fallback steps + * that we take if we think we are facing infinite error recursion. See the + * callers for details. + */ +bool +in_error_recursion_trouble(void) +{ + /* Pull the plug if recurse more than once */ + return (recursion_depth > 2); +} + +/* + * One of those fallback steps is to stop trying to localize the error + * message, since there's a significant probability that that's exactly + * what's causing the recursion. + */ +static inline const char * +err_gettext(const char *str) +{ +#ifdef ENABLE_NLS + if (in_error_recursion_trouble()) + return str; + else + return gettext(str); +#else + return str; +#endif +} + +/* + * errstart_cold + * A simple wrapper around errstart, but hinted to be "cold". Supporting + * compilers are more likely to move code for branches containing this + * function into an area away from the calling function's code. This can + * result in more commonly executed code being more compact and fitting + * on fewer cache lines. + */ +pg_attribute_cold bool +errstart_cold(int elevel, const char *domain) +{ + return errstart(elevel, domain); +} + +/* + * errstart --- begin an error-reporting cycle + * + * Create and initialize error stack entry. Subsequently, errmsg() and + * perhaps other routines will be called to further populate the stack entry. + * Finally, errfinish() will be called to actually process the error report. + * + * Returns true in normal case. Returns false to short-circuit the error + * report (if it's a warning or lower and not to be reported anywhere). + */ +bool +errstart(int elevel, const char *domain) +{ + ErrorData *edata; + bool output_to_server; + bool output_to_client = false; + int i; + + /* + * Check some cases in which we want to promote an error into a more + * severe error. None of this logic applies for non-error messages. + */ + if (elevel >= ERROR) + { + /* + * If we are inside a critical section, all errors become PANIC + * errors. See miscadmin.h. + */ + if (CritSectionCount > 0) + elevel = PANIC; + + /* + * Check reasons for treating ERROR as FATAL: + * + * 1. we have no handler to pass the error to (implies we are in the + * postmaster or in backend startup). + * + * 2. ExitOnAnyError mode switch is set (initdb uses this). + * + * 3. the error occurred after proc_exit has begun to run. (It's + * proc_exit's responsibility to see that this doesn't turn into + * infinite recursion!) + */ + if (elevel == ERROR) + { + if ((PG_exception_stack == NULL && !yql_error_report_active) || + ExitOnAnyError || + proc_exit_inprogress) + elevel = FATAL; + } + + /* + * If the error level is ERROR or more, errfinish is not going to + * return to caller; therefore, if there is any stacked error already + * in progress it will be lost. This is more or less okay, except we + * do not want to have a FATAL or PANIC error downgraded because the + * reporting process was interrupted by a lower-grade error. So check + * the stack and make sure we panic if panic is warranted. + */ + for (i = 0; i <= errordata_stack_depth; i++) + elevel = Max(elevel, errordata[i].elevel); + } + + /* + * Now decide whether we need to process this report at all; if it's + * warning or less and not enabled for logging, just return false without + * starting up any error logging machinery. + */ + output_to_server = should_output_to_server(elevel); + output_to_client = should_output_to_client(elevel); + if (elevel < ERROR && !output_to_server && !output_to_client) + return false; + + /* + * We need to do some actual work. Make sure that memory context + * initialization has finished, else we can't do anything useful. + */ + if (ErrorContext == NULL) + { + /* Oops, hard crash time; very little we can do safely here */ + write_stderr("error occurred before error message processing is available\n"); + exit(2); + } + + /* + * Okay, crank up a stack entry to store the info in. + */ + + if (recursion_depth++ > 0 && elevel >= ERROR) + { + /* + * Oops, error during error processing. Clear ErrorContext as + * discussed at top of file. We will not return to the original + * error's reporter or handler, so we don't need it. + */ + MemoryContextReset(ErrorContext); + + /* + * Infinite error recursion might be due to something broken in a + * context traceback routine. Abandon them too. We also abandon + * attempting to print the error statement (which, if long, could + * itself be the source of the recursive failure). + */ + if (in_error_recursion_trouble()) + { + error_context_stack = NULL; + debug_query_string = NULL; + } + } + + /* Initialize data for this error frame */ + edata = get_error_stack_entry(); + edata->elevel = elevel; + edata->output_to_server = output_to_server; + edata->output_to_client = output_to_client; + set_stack_entry_domain(edata, domain); + /* Select default errcode based on elevel */ + if (elevel >= ERROR) + edata->sqlerrcode = ERRCODE_INTERNAL_ERROR; + else if (elevel >= WARNING) + edata->sqlerrcode = ERRCODE_WARNING; + else + edata->sqlerrcode = ERRCODE_SUCCESSFUL_COMPLETION; + + /* + * Any allocations for this error state level should go into ErrorContext + */ + edata->assoc_context = ErrorContext; + + recursion_depth--; + return true; +} + +/* + * errfinish --- end an error-reporting cycle + * + * Produce the appropriate error report(s) and pop the error stack. + * + * If elevel, as passed to errstart(), is ERROR or worse, control does not + * return to the caller. See elog.h for the error level definitions. + */ +void +errfinish(const char *filename, int lineno, const char *funcname) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + int elevel; + MemoryContext oldcontext; + ErrorContextCallback *econtext; + + recursion_depth++; + CHECK_STACK_DEPTH(); + + /* Save the last few bits of error state into the stack entry */ + set_stack_entry_location(edata, filename, lineno, funcname); + + elevel = edata->elevel; + + /* + * Do processing in ErrorContext, which we hope has enough reserved space + * to report an error. + */ + oldcontext = MemoryContextSwitchTo(ErrorContext); + + /* Collect backtrace, if enabled and we didn't already */ + if (!edata->backtrace && + edata->funcname && + backtrace_functions && + matches_backtrace_functions(edata->funcname)) + set_backtrace(edata, 2); + + /* + * Call any context callback functions. Errors occurring in callback + * functions will be treated as recursive errors --- this ensures we will + * avoid infinite recursion (see errstart). + */ + for (econtext = error_context_stack; + econtext != NULL; + econtext = econtext->previous) + econtext->callback(econtext->arg); + + /* + * If ERROR (not more nor less) we pass it off to the current handler. + * Printing it and popping the stack is the responsibility of the handler. + */ + if (elevel == ERROR) + { + /* + * We do some minimal cleanup before longjmp'ing so that handlers can + * execute in a reasonably sane state. + * + * Reset InterruptHoldoffCount in case we ereport'd from inside an + * interrupt holdoff section. (We assume here that no handler will + * itself be inside a holdoff section. If necessary, such a handler + * could save and restore InterruptHoldoffCount for itself, but this + * should make life easier for most.) + */ + InterruptHoldoffCount = 0; + QueryCancelHoldoffCount = 0; + + CritSectionCount = 0; /* should be unnecessary, but... */ + + /* + * Note that we leave CurrentMemoryContext set to ErrorContext. The + * handler should reset it to something else soon. + */ + + recursion_depth--; + PG_RE_THROW(); + } + + /* Emit the message to the right places */ + EmitErrorReport(); + + /* Now free up subsidiary data attached to stack entry, and release it */ + FreeErrorDataContents(edata); + errordata_stack_depth--; + + /* Exit error-handling context */ + MemoryContextSwitchTo(oldcontext); + recursion_depth--; + + /* + * Perform error recovery action as specified by elevel. + */ + if (elevel == FATAL) + { + /* + * For a FATAL error, we let proc_exit clean up and exit. + * + * If we just reported a startup failure, the client will disconnect + * on receiving it, so don't send any more to the client. + */ + if (PG_exception_stack == NULL && whereToSendOutput == DestRemote) + whereToSendOutput = DestNone; + + /* + * fflush here is just to improve the odds that we get to see the + * error message, in case things are so hosed that proc_exit crashes. + * Any other code you might be tempted to add here should probably be + * in an on_proc_exit or on_shmem_exit callback instead. + */ + fflush(NULL); + + /* + * Let the cumulative stats system know. Only mark the session as + * terminated by fatal error if there is no other known cause. + */ + if (pgStatSessionEndCause == DISCONNECT_NORMAL) + pgStatSessionEndCause = DISCONNECT_FATAL; + + /* + * Do normal process-exit cleanup, then return exit code 1 to indicate + * FATAL termination. The postmaster may or may not consider this + * worthy of panic, depending on which subprocess returns it. + */ + proc_exit(1); + } + + if (elevel >= PANIC) + { + /* + * Serious crash time. Postmaster will observe SIGABRT process exit + * status and kill the other backends too. + * + * XXX: what if we are *in* the postmaster? abort() won't kill our + * children... + */ + fflush(NULL); + abort(); + } + + /* + * Check for cancel/die interrupt first --- this is so that the user can + * stop a query emitting tons of notice or warning messages, even if it's + * in a loop that otherwise fails to check for interrupts. + */ + CHECK_FOR_INTERRUPTS(); +} + + +/* + * errsave_start --- begin a "soft" error-reporting cycle + * + * If "context" isn't an ErrorSaveContext node, this behaves as + * errstart(ERROR, domain), and the errsave() macro ends up acting + * exactly like ereport(ERROR, ...). + * + * If "context" is an ErrorSaveContext node, but the node creator only wants + * notification of the fact of a soft error without any details, we just set + * the error_occurred flag in the ErrorSaveContext node and return false, + * which will cause us to skip the remaining error processing steps. + * + * Otherwise, create and initialize error stack entry and return true. + * Subsequently, errmsg() and perhaps other routines will be called to further + * populate the stack entry. Finally, errsave_finish() will be called to + * tidy up. + */ +bool +errsave_start(struct Node *context, const char *domain) +{ + ErrorSaveContext *escontext; + ErrorData *edata; + + /* + * Do we have a context for soft error reporting? If not, just punt to + * errstart(). + */ + if (context == NULL || !IsA(context, ErrorSaveContext)) + return errstart(ERROR, domain); + + /* Report that a soft error was detected */ + escontext = (ErrorSaveContext *) context; + escontext->error_occurred = true; + + /* Nothing else to do if caller wants no further details */ + if (!escontext->details_wanted) + return false; + + /* + * Okay, crank up a stack entry to store the info in. + */ + + recursion_depth++; + + /* Initialize data for this error frame */ + edata = get_error_stack_entry(); + edata->elevel = LOG; /* signal all is well to errsave_finish */ + set_stack_entry_domain(edata, domain); + /* Select default errcode based on the assumed elevel of ERROR */ + edata->sqlerrcode = ERRCODE_INTERNAL_ERROR; + + /* + * Any allocations for this error state level should go into the caller's + * context. We don't need to pollute ErrorContext, or even require it to + * exist, in this code path. + */ + edata->assoc_context = CurrentMemoryContext; + + recursion_depth--; + return true; +} + +/* + * errsave_finish --- end a "soft" error-reporting cycle + * + * If errsave_start() decided this was a regular error, behave as + * errfinish(). Otherwise, package up the error details and save + * them in the ErrorSaveContext node. + */ +void +errsave_finish(struct Node *context, const char *filename, int lineno, + const char *funcname) +{ + ErrorSaveContext *escontext = (ErrorSaveContext *) context; + ErrorData *edata = &errordata[errordata_stack_depth]; + + /* verify stack depth before accessing *edata */ + CHECK_STACK_DEPTH(); + + /* + * If errsave_start punted to errstart, then elevel will be ERROR or + * perhaps even PANIC. Punt likewise to errfinish. + */ + if (edata->elevel >= ERROR) + { + errfinish(filename, lineno, funcname); + pg_unreachable(); + } + + /* + * Else, we should package up the stack entry contents and deliver them to + * the caller. + */ + recursion_depth++; + + /* Save the last few bits of error state into the stack entry */ + set_stack_entry_location(edata, filename, lineno, funcname); + + /* Replace the LOG value that errsave_start inserted */ + edata->elevel = ERROR; + + /* + * We skip calling backtrace and context functions, which are more likely + * to cause trouble than provide useful context; they might act on the + * assumption that a transaction abort is about to occur. + */ + + /* + * Make a copy of the error info for the caller. All the subsidiary + * strings are already in the caller's context, so it's sufficient to + * flat-copy the stack entry. + */ + escontext->error_data = palloc_object(ErrorData); + memcpy(escontext->error_data, edata, sizeof(ErrorData)); + + /* Exit error-handling context */ + errordata_stack_depth--; + recursion_depth--; +} + + +/* + * get_error_stack_entry --- allocate and initialize a new stack entry + * + * The entry should be freed, when we're done with it, by calling + * FreeErrorDataContents() and then decrementing errordata_stack_depth. + * + * Returning the entry's address is just a notational convenience, + * since it had better be errordata[errordata_stack_depth]. + * + * Although the error stack is not large, we don't expect to run out of space. + * Using more than one entry implies a new error report during error recovery, + * which is possible but already suggests we're in trouble. If we exhaust the + * stack, almost certainly we are in an infinite loop of errors during error + * recovery, so we give up and PANIC. + * + * (Note that this is distinct from the recursion_depth checks, which + * guard against recursion while handling a single stack entry.) + */ +static ErrorData * +get_error_stack_entry(void) +{ + ErrorData *edata; + + /* Allocate error frame */ + errordata_stack_depth++; + if (unlikely(errordata_stack_depth >= ERRORDATA_STACK_SIZE)) + { + /* Wups, stack not big enough */ + errordata_stack_depth = -1; /* make room on stack */ + ereport(PANIC, (errmsg_internal("ERRORDATA_STACK_SIZE exceeded"))); + } + + /* Initialize error frame to all zeroes/NULLs */ + edata = &errordata[errordata_stack_depth]; + memset(edata, 0, sizeof(ErrorData)); + + /* Save errno immediately to ensure error parameter eval can't change it */ + edata->saved_errno = errno; + + return edata; +} + +/* + * set_stack_entry_domain --- fill in the internationalization domain + */ +static void +set_stack_entry_domain(ErrorData *edata, const char *domain) +{ + /* the default text domain is the backend's */ + edata->domain = domain ? domain : PG_TEXTDOMAIN("postgres"); + /* initialize context_domain the same way (see set_errcontext_domain()) */ + edata->context_domain = edata->domain; +} + +/* + * set_stack_entry_location --- fill in code-location details + * + * Store the values of __FILE__, __LINE__, and __func__ from the call site. + * We make an effort to normalize __FILE__, since compilers are inconsistent + * about how much of the path they'll include, and we'd prefer that the + * behavior not depend on that (especially, that it not vary with build path). + */ +static void +set_stack_entry_location(ErrorData *edata, + const char *filename, int lineno, + const char *funcname) +{ + if (filename) + { + const char *slash; + + /* keep only base name, useful especially for vpath builds */ + slash = strrchr(filename, '/'); + if (slash) + filename = slash + 1; + /* Some Windows compilers use backslashes in __FILE__ strings */ + slash = strrchr(filename, '\\'); + if (slash) + filename = slash + 1; + } + + edata->filename = filename; + edata->lineno = lineno; + edata->funcname = funcname; +} + +/* + * matches_backtrace_functions --- checks whether the given funcname matches + * backtrace_functions + * + * See check_backtrace_functions. + */ +static bool +matches_backtrace_functions(const char *funcname) +{ + const char *p; + + if (!backtrace_symbol_list || funcname == NULL || funcname[0] == '\0') + return false; + + p = backtrace_symbol_list; + for (;;) + { + if (*p == '\0') /* end of backtrace_symbol_list */ + break; + + if (strcmp(funcname, p) == 0) + return true; + p += strlen(p) + 1; + } + + return false; +} + + +/* + * errcode --- add SQLSTATE error code to the current error + * + * The code is expected to be represented as per MAKE_SQLSTATE(). + */ +int +errcode(int sqlerrcode) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + + /* we don't bother incrementing recursion_depth */ + CHECK_STACK_DEPTH(); + + edata->sqlerrcode = sqlerrcode; + + return 0; /* return value does not matter */ +} + + +/* + * errcode_for_file_access --- add SQLSTATE error code to the current error + * + * The SQLSTATE code is chosen based on the saved errno value. We assume + * that the failing operation was some type of disk file access. + * + * NOTE: the primary error message string should generally include %m + * when this is used. + */ +int +errcode_for_file_access(void) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + + /* we don't bother incrementing recursion_depth */ + CHECK_STACK_DEPTH(); + + switch (edata->saved_errno) + { + /* Permission-denied failures */ + case EPERM: /* Not super-user */ + case EACCES: /* Permission denied */ +#ifdef EROFS + case EROFS: /* Read only file system */ +#endif + edata->sqlerrcode = ERRCODE_INSUFFICIENT_PRIVILEGE; + break; + + /* File not found */ + case ENOENT: /* No such file or directory */ + edata->sqlerrcode = ERRCODE_UNDEFINED_FILE; + break; + + /* Duplicate file */ + case EEXIST: /* File exists */ + edata->sqlerrcode = ERRCODE_DUPLICATE_FILE; + break; + + /* Wrong object type or state */ + case ENOTDIR: /* Not a directory */ + case EISDIR: /* Is a directory */ +#if defined(ENOTEMPTY) && (ENOTEMPTY != EEXIST) /* same code on AIX */ + case ENOTEMPTY: /* Directory not empty */ +#endif + edata->sqlerrcode = ERRCODE_WRONG_OBJECT_TYPE; + break; + + /* Insufficient resources */ + case ENOSPC: /* No space left on device */ + edata->sqlerrcode = ERRCODE_DISK_FULL; + break; + + case ENOMEM: /* Out of memory */ + edata->sqlerrcode = ERRCODE_OUT_OF_MEMORY; + break; + + case ENFILE: /* File table overflow */ + case EMFILE: /* Too many open files */ + edata->sqlerrcode = ERRCODE_INSUFFICIENT_RESOURCES; + break; + + /* Hardware failure */ + case EIO: /* I/O error */ + edata->sqlerrcode = ERRCODE_IO_ERROR; + break; + + /* All else is classified as internal errors */ + default: + edata->sqlerrcode = ERRCODE_INTERNAL_ERROR; + break; + } + + return 0; /* return value does not matter */ +} + +/* + * errcode_for_socket_access --- add SQLSTATE error code to the current error + * + * The SQLSTATE code is chosen based on the saved errno value. We assume + * that the failing operation was some type of socket access. + * + * NOTE: the primary error message string should generally include %m + * when this is used. + */ +int +errcode_for_socket_access(void) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + + /* we don't bother incrementing recursion_depth */ + CHECK_STACK_DEPTH(); + + switch (edata->saved_errno) + { + /* Loss of connection */ + case ALL_CONNECTION_FAILURE_ERRNOS: + edata->sqlerrcode = ERRCODE_CONNECTION_FAILURE; + break; + + /* All else is classified as internal errors */ + default: + edata->sqlerrcode = ERRCODE_INTERNAL_ERROR; + break; + } + + return 0; /* return value does not matter */ +} + + +/* + * This macro handles expansion of a format string and associated parameters; + * it's common code for errmsg(), errdetail(), etc. Must be called inside + * a routine that is declared like "const char *fmt, ..." and has an edata + * pointer set up. The message is assigned to edata->targetfield, or + * appended to it if appendval is true. The message is subject to translation + * if translateit is true. + * + * Note: we pstrdup the buffer rather than just transferring its storage + * to the edata field because the buffer might be considerably larger than + * really necessary. + */ +#define EVALUATE_MESSAGE(domain, targetfield, appendval, translateit) \ + { \ + StringInfoData buf; \ + /* Internationalize the error format string */ \ + if ((translateit) && !in_error_recursion_trouble()) \ + fmt = dgettext((domain), fmt); \ + initStringInfo(&buf); \ + if ((appendval) && edata->targetfield) { \ + appendStringInfoString(&buf, edata->targetfield); \ + appendStringInfoChar(&buf, '\n'); \ + } \ + /* Generate actual output --- have to use appendStringInfoVA */ \ + for (;;) \ + { \ + va_list args; \ + int needed; \ + errno = edata->saved_errno; \ + va_start(args, fmt); \ + needed = appendStringInfoVA(&buf, fmt, args); \ + va_end(args); \ + if (needed == 0) \ + break; \ + enlargeStringInfo(&buf, needed); \ + } \ + /* Save the completed message into the stack item */ \ + if (edata->targetfield) \ + pfree(edata->targetfield); \ + edata->targetfield = pstrdup(buf.data); \ + pfree(buf.data); \ + } + +/* + * Same as above, except for pluralized error messages. The calling routine + * must be declared like "const char *fmt_singular, const char *fmt_plural, + * unsigned long n, ...". Translation is assumed always wanted. + */ +#define EVALUATE_MESSAGE_PLURAL(domain, targetfield, appendval) \ + { \ + const char *fmt; \ + StringInfoData buf; \ + /* Internationalize the error format string */ \ + if (!in_error_recursion_trouble()) \ + fmt = dngettext((domain), fmt_singular, fmt_plural, n); \ + else \ + fmt = (n == 1 ? fmt_singular : fmt_plural); \ + initStringInfo(&buf); \ + if ((appendval) && edata->targetfield) { \ + appendStringInfoString(&buf, edata->targetfield); \ + appendStringInfoChar(&buf, '\n'); \ + } \ + /* Generate actual output --- have to use appendStringInfoVA */ \ + for (;;) \ + { \ + va_list args; \ + int needed; \ + errno = edata->saved_errno; \ + va_start(args, n); \ + needed = appendStringInfoVA(&buf, fmt, args); \ + va_end(args); \ + if (needed == 0) \ + break; \ + enlargeStringInfo(&buf, needed); \ + } \ + /* Save the completed message into the stack item */ \ + if (edata->targetfield) \ + pfree(edata->targetfield); \ + edata->targetfield = pstrdup(buf.data); \ + pfree(buf.data); \ + } + + +/* + * errmsg --- add a primary error message text to the current error + * + * In addition to the usual %-escapes recognized by printf, "%m" in + * fmt is replaced by the error message for the caller's value of errno. + * + * Note: no newline is needed at the end of the fmt string, since + * ereport will provide one for the output methods that need it. + */ +int +errmsg(const char *fmt,...) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + MemoryContext oldcontext; + + recursion_depth++; + CHECK_STACK_DEPTH(); + oldcontext = MemoryContextSwitchTo(edata->assoc_context); + + edata->message_id = fmt; + EVALUATE_MESSAGE(edata->domain, message, false, true); + + MemoryContextSwitchTo(oldcontext); + recursion_depth--; + return 0; /* return value does not matter */ +} + +/* + * Add a backtrace to the containing ereport() call. This is intended to be + * added temporarily during debugging. + */ +int +errbacktrace(void) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + MemoryContext oldcontext; + + recursion_depth++; + CHECK_STACK_DEPTH(); + oldcontext = MemoryContextSwitchTo(edata->assoc_context); + + set_backtrace(edata, 1); + + MemoryContextSwitchTo(oldcontext); + recursion_depth--; + + return 0; +} + +/* + * Compute backtrace data and add it to the supplied ErrorData. num_skip + * specifies how many inner frames to skip. Use this to avoid showing the + * internal backtrace support functions in the backtrace. This requires that + * this and related functions are not inlined. + */ +static void +set_backtrace(ErrorData *edata, int num_skip) +{ + StringInfoData errtrace; + + initStringInfo(&errtrace); + +#ifdef HAVE_BACKTRACE_SYMBOLS + { + void *buf[100]; + int nframes; + char **strfrms; + + nframes = backtrace(buf, lengthof(buf)); + strfrms = backtrace_symbols(buf, nframes); + if (strfrms == NULL) + return; + + for (int i = num_skip; i < nframes; i++) + appendStringInfo(&errtrace, "\n%s", strfrms[i]); + free(strfrms); + } +#else + appendStringInfoString(&errtrace, + "backtrace generation is not supported by this installation"); +#endif + + edata->backtrace = errtrace.data; +} + +/* + * errmsg_internal --- add a primary error message text to the current error + * + * This is exactly like errmsg() except that strings passed to errmsg_internal + * are not translated, and are customarily left out of the + * internationalization message dictionary. This should be used for "can't + * happen" cases that are probably not worth spending translation effort on. + * We also use this for certain cases where we *must* not try to translate + * the message because the translation would fail and result in infinite + * error recursion. + */ +int +errmsg_internal(const char *fmt,...) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + MemoryContext oldcontext; + + recursion_depth++; + CHECK_STACK_DEPTH(); + oldcontext = MemoryContextSwitchTo(edata->assoc_context); + + edata->message_id = fmt; + EVALUATE_MESSAGE(edata->domain, message, false, false); + + MemoryContextSwitchTo(oldcontext); + recursion_depth--; + return 0; /* return value does not matter */ +} + + +/* + * errmsg_plural --- add a primary error message text to the current error, + * with support for pluralization of the message text + */ +int +errmsg_plural(const char *fmt_singular, const char *fmt_plural, + unsigned long n,...) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + MemoryContext oldcontext; + + recursion_depth++; + CHECK_STACK_DEPTH(); + oldcontext = MemoryContextSwitchTo(edata->assoc_context); + + edata->message_id = fmt_singular; + EVALUATE_MESSAGE_PLURAL(edata->domain, message, false); + + MemoryContextSwitchTo(oldcontext); + recursion_depth--; + return 0; /* return value does not matter */ +} + + +/* + * errdetail --- add a detail error message text to the current error + */ +int +errdetail(const char *fmt,...) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + MemoryContext oldcontext; + + recursion_depth++; + CHECK_STACK_DEPTH(); + oldcontext = MemoryContextSwitchTo(edata->assoc_context); + + EVALUATE_MESSAGE(edata->domain, detail, false, true); + + MemoryContextSwitchTo(oldcontext); + recursion_depth--; + return 0; /* return value does not matter */ +} + + +/* + * errdetail_internal --- add a detail error message text to the current error + * + * This is exactly like errdetail() except that strings passed to + * errdetail_internal are not translated, and are customarily left out of the + * internationalization message dictionary. This should be used for detail + * messages that seem not worth translating for one reason or another + * (typically, that they don't seem to be useful to average users). + */ +int +errdetail_internal(const char *fmt,...) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + MemoryContext oldcontext; + + recursion_depth++; + CHECK_STACK_DEPTH(); + oldcontext = MemoryContextSwitchTo(edata->assoc_context); + + EVALUATE_MESSAGE(edata->domain, detail, false, false); + + MemoryContextSwitchTo(oldcontext); + recursion_depth--; + return 0; /* return value does not matter */ +} + + +/* + * errdetail_log --- add a detail_log error message text to the current error + */ +int +errdetail_log(const char *fmt,...) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + MemoryContext oldcontext; + + recursion_depth++; + CHECK_STACK_DEPTH(); + oldcontext = MemoryContextSwitchTo(edata->assoc_context); + + EVALUATE_MESSAGE(edata->domain, detail_log, false, true); + + MemoryContextSwitchTo(oldcontext); + recursion_depth--; + return 0; /* return value does not matter */ +} + +/* + * errdetail_log_plural --- add a detail_log error message text to the current error + * with support for pluralization of the message text + */ +int +errdetail_log_plural(const char *fmt_singular, const char *fmt_plural, + unsigned long n,...) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + MemoryContext oldcontext; + + recursion_depth++; + CHECK_STACK_DEPTH(); + oldcontext = MemoryContextSwitchTo(edata->assoc_context); + + EVALUATE_MESSAGE_PLURAL(edata->domain, detail_log, false); + + MemoryContextSwitchTo(oldcontext); + recursion_depth--; + return 0; /* return value does not matter */ +} + + +/* + * errdetail_plural --- add a detail error message text to the current error, + * with support for pluralization of the message text + */ +int +errdetail_plural(const char *fmt_singular, const char *fmt_plural, + unsigned long n,...) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + MemoryContext oldcontext; + + recursion_depth++; + CHECK_STACK_DEPTH(); + oldcontext = MemoryContextSwitchTo(edata->assoc_context); + + EVALUATE_MESSAGE_PLURAL(edata->domain, detail, false); + + MemoryContextSwitchTo(oldcontext); + recursion_depth--; + return 0; /* return value does not matter */ +} + + +/* + * errhint --- add a hint error message text to the current error + */ +int +errhint(const char *fmt,...) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + MemoryContext oldcontext; + + recursion_depth++; + CHECK_STACK_DEPTH(); + oldcontext = MemoryContextSwitchTo(edata->assoc_context); + + EVALUATE_MESSAGE(edata->domain, hint, false, true); + + MemoryContextSwitchTo(oldcontext); + recursion_depth--; + return 0; /* return value does not matter */ +} + + +/* + * errhint_plural --- add a hint error message text to the current error, + * with support for pluralization of the message text + */ +int +errhint_plural(const char *fmt_singular, const char *fmt_plural, + unsigned long n,...) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + MemoryContext oldcontext; + + recursion_depth++; + CHECK_STACK_DEPTH(); + oldcontext = MemoryContextSwitchTo(edata->assoc_context); + + EVALUATE_MESSAGE_PLURAL(edata->domain, hint, false); + + MemoryContextSwitchTo(oldcontext); + recursion_depth--; + return 0; /* return value does not matter */ +} + + +/* + * errcontext_msg --- add a context error message text to the current error + * + * Unlike other cases, multiple calls are allowed to build up a stack of + * context information. We assume earlier calls represent more-closely-nested + * states. + */ +int +errcontext_msg(const char *fmt,...) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + MemoryContext oldcontext; + + recursion_depth++; + CHECK_STACK_DEPTH(); + oldcontext = MemoryContextSwitchTo(edata->assoc_context); + + EVALUATE_MESSAGE(edata->context_domain, context, true, true); + + MemoryContextSwitchTo(oldcontext); + recursion_depth--; + return 0; /* return value does not matter */ +} + +/* + * set_errcontext_domain --- set message domain to be used by errcontext() + * + * errcontext_msg() can be called from a different module than the original + * ereport(), so we cannot use the message domain passed in errstart() to + * translate it. Instead, each errcontext_msg() call should be preceded by + * a set_errcontext_domain() call to specify the domain. This is usually + * done transparently by the errcontext() macro. + */ +int +set_errcontext_domain(const char *domain) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + + /* we don't bother incrementing recursion_depth */ + CHECK_STACK_DEPTH(); + + /* the default text domain is the backend's */ + edata->context_domain = domain ? domain : PG_TEXTDOMAIN("postgres"); + + return 0; /* return value does not matter */ +} + + +/* + * errhidestmt --- optionally suppress STATEMENT: field of log entry + * + * This should be called if the message text already includes the statement. + */ +int +errhidestmt(bool hide_stmt) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + + /* we don't bother incrementing recursion_depth */ + CHECK_STACK_DEPTH(); + + edata->hide_stmt = hide_stmt; + + return 0; /* return value does not matter */ +} + +/* + * errhidecontext --- optionally suppress CONTEXT: field of log entry + * + * This should only be used for verbose debugging messages where the repeated + * inclusion of context would bloat the log volume too much. + */ +int +errhidecontext(bool hide_ctx) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + + /* we don't bother incrementing recursion_depth */ + CHECK_STACK_DEPTH(); + + edata->hide_ctx = hide_ctx; + + return 0; /* return value does not matter */ +} + +/* + * errposition --- add cursor position to the current error + */ +int +errposition(int cursorpos) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + + /* we don't bother incrementing recursion_depth */ + CHECK_STACK_DEPTH(); + + edata->cursorpos = cursorpos; + + return 0; /* return value does not matter */ +} + +/* + * internalerrposition --- add internal cursor position to the current error + */ +int +internalerrposition(int cursorpos) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + + /* we don't bother incrementing recursion_depth */ + CHECK_STACK_DEPTH(); + + edata->internalpos = cursorpos; + + return 0; /* return value does not matter */ +} + +/* + * internalerrquery --- add internal query text to the current error + * + * Can also pass NULL to drop the internal query text entry. This case + * is intended for use in error callback subroutines that are editorializing + * on the layout of the error report. + */ +int +internalerrquery(const char *query) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + + /* we don't bother incrementing recursion_depth */ + CHECK_STACK_DEPTH(); + + if (edata->internalquery) + { + pfree(edata->internalquery); + edata->internalquery = NULL; + } + + if (query) + edata->internalquery = MemoryContextStrdup(edata->assoc_context, query); + + return 0; /* return value does not matter */ +} + +/* + * err_generic_string -- used to set individual ErrorData string fields + * identified by PG_DIAG_xxx codes. + * + * This intentionally only supports fields that don't use localized strings, + * so that there are no translation considerations. + * + * Most potential callers should not use this directly, but instead prefer + * higher-level abstractions, such as errtablecol() (see relcache.c). + */ +int +err_generic_string(int field, const char *str) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + + /* we don't bother incrementing recursion_depth */ + CHECK_STACK_DEPTH(); + + switch (field) + { + case PG_DIAG_SCHEMA_NAME: + set_errdata_field(edata->assoc_context, &edata->schema_name, str); + break; + case PG_DIAG_TABLE_NAME: + set_errdata_field(edata->assoc_context, &edata->table_name, str); + break; + case PG_DIAG_COLUMN_NAME: + set_errdata_field(edata->assoc_context, &edata->column_name, str); + break; + case PG_DIAG_DATATYPE_NAME: + set_errdata_field(edata->assoc_context, &edata->datatype_name, str); + break; + case PG_DIAG_CONSTRAINT_NAME: + set_errdata_field(edata->assoc_context, &edata->constraint_name, str); + break; + default: + elog(ERROR, "unsupported ErrorData field id: %d", field); + break; + } + + return 0; /* return value does not matter */ +} + +/* + * set_errdata_field --- set an ErrorData string field + */ +static void +set_errdata_field(MemoryContextData *cxt, char **ptr, const char *str) +{ + Assert(*ptr == NULL); + *ptr = MemoryContextStrdup(cxt, str); +} + +/* + * geterrcode --- return the currently set SQLSTATE error code + * + * This is only intended for use in error callback subroutines, since there + * is no other place outside elog.c where the concept is meaningful. + */ +int +geterrcode(void) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + + /* we don't bother incrementing recursion_depth */ + CHECK_STACK_DEPTH(); + + return edata->sqlerrcode; +} + +/* + * geterrposition --- return the currently set error position (0 if none) + * + * This is only intended for use in error callback subroutines, since there + * is no other place outside elog.c where the concept is meaningful. + */ +int +geterrposition(void) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + + /* we don't bother incrementing recursion_depth */ + CHECK_STACK_DEPTH(); + + return edata->cursorpos; +} + +/* + * getinternalerrposition --- same for internal error position + * + * This is only intended for use in error callback subroutines, since there + * is no other place outside elog.c where the concept is meaningful. + */ +int +getinternalerrposition(void) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + + /* we don't bother incrementing recursion_depth */ + CHECK_STACK_DEPTH(); + + return edata->internalpos; +} + + +/* + * Functions to allow construction of error message strings separately from + * the ereport() call itself. + * + * The expected calling convention is + * + * pre_format_elog_string(errno, domain), var = format_elog_string(format,...) + * + * which can be hidden behind a macro such as GUC_check_errdetail(). We + * assume that any functions called in the arguments of format_elog_string() + * cannot result in re-entrant use of these functions --- otherwise the wrong + * text domain might be used, or the wrong errno substituted for %m. This is + * okay for the current usage with GUC check hooks, but might need further + * effort someday. + * + * The result of format_elog_string() is stored in ErrorContext, and will + * therefore survive until FlushErrorState() is called. + */ +static __thread int save_format_errnumber; +static __thread const char *save_format_domain; + +void +pre_format_elog_string(int errnumber, const char *domain) +{ + /* Save errno before evaluation of argument functions can change it */ + save_format_errnumber = errnumber; + /* Save caller's text domain */ + save_format_domain = domain; +} + +char * +format_elog_string(const char *fmt,...) +{ + ErrorData errdata; + ErrorData *edata; + MemoryContext oldcontext; + + /* Initialize a mostly-dummy error frame */ + edata = &errdata; + MemSet(edata, 0, sizeof(ErrorData)); + /* the default text domain is the backend's */ + edata->domain = save_format_domain ? save_format_domain : PG_TEXTDOMAIN("postgres"); + /* set the errno to be used to interpret %m */ + edata->saved_errno = save_format_errnumber; + + oldcontext = MemoryContextSwitchTo(ErrorContext); + + edata->message_id = fmt; + EVALUATE_MESSAGE(edata->domain, message, false, true); + + MemoryContextSwitchTo(oldcontext); + + return edata->message; +} + + +/* + * Actual output of the top-of-stack error message + * + * In the ereport(ERROR) case this is called from PostgresMain (or not at all, + * if the error is caught by somebody). For all other severity levels this + * is called by errfinish. + */ +void +EmitErrorReport(void) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + MemoryContext oldcontext; + + recursion_depth++; + CHECK_STACK_DEPTH(); + oldcontext = MemoryContextSwitchTo(edata->assoc_context); + + /* + * Call hook before sending message to log. The hook function is allowed + * to turn off edata->output_to_server, so we must recheck that afterward. + * Making any other change in the content of edata is not considered + * supported. + * + * Note: the reason why the hook can only turn off output_to_server, and + * not turn it on, is that it'd be unreliable: we will never get here at + * all if errstart() deems the message uninteresting. A hook that could + * make decisions in that direction would have to hook into errstart(), + * where it would have much less information available. emit_log_hook is + * intended for custom log filtering and custom log message transmission + * mechanisms. + * + * The log hook has access to both the translated and original English + * error message text, which is passed through to allow it to be used as a + * message identifier. Note that the original text is not available for + * detail, detail_log, hint and context text elements. + */ + if (edata->output_to_server && emit_log_hook) + (*emit_log_hook) (edata); + + /* Send to server log, if enabled */ + if (edata->output_to_server) + send_message_to_server_log(edata); + + /* Send to client, if enabled */ + if (edata->output_to_client) + send_message_to_frontend(edata); + + MemoryContextSwitchTo(oldcontext); + recursion_depth--; +} + +/* + * CopyErrorData --- obtain a copy of the topmost error stack entry + * + * This is only for use in error handler code. The data is copied into the + * current memory context, so callers should always switch away from + * ErrorContext first; otherwise it will be lost when FlushErrorState is done. + */ +ErrorData * +CopyErrorData(void) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + ErrorData *newedata; + + /* + * we don't increment recursion_depth because out-of-memory here does not + * indicate a problem within the error subsystem. + */ + CHECK_STACK_DEPTH(); + + Assert(CurrentMemoryContext != ErrorContext); + + /* Copy the struct itself */ + newedata = (ErrorData *) palloc(sizeof(ErrorData)); + memcpy(newedata, edata, sizeof(ErrorData)); + + /* Make copies of separately-allocated fields */ + if (newedata->message) + newedata->message = pstrdup(newedata->message); + if (newedata->detail) + newedata->detail = pstrdup(newedata->detail); + if (newedata->detail_log) + newedata->detail_log = pstrdup(newedata->detail_log); + if (newedata->hint) + newedata->hint = pstrdup(newedata->hint); + if (newedata->context) + newedata->context = pstrdup(newedata->context); + if (newedata->backtrace) + newedata->backtrace = pstrdup(newedata->backtrace); + if (newedata->schema_name) + newedata->schema_name = pstrdup(newedata->schema_name); + if (newedata->table_name) + newedata->table_name = pstrdup(newedata->table_name); + if (newedata->column_name) + newedata->column_name = pstrdup(newedata->column_name); + if (newedata->datatype_name) + newedata->datatype_name = pstrdup(newedata->datatype_name); + if (newedata->constraint_name) + newedata->constraint_name = pstrdup(newedata->constraint_name); + if (newedata->internalquery) + newedata->internalquery = pstrdup(newedata->internalquery); + + /* Use the calling context for string allocation */ + newedata->assoc_context = CurrentMemoryContext; + + return newedata; +} + +/* + * FreeErrorData --- free the structure returned by CopyErrorData. + * + * Error handlers should use this in preference to assuming they know all + * the separately-allocated fields. + */ +void +FreeErrorData(ErrorData *edata) +{ + FreeErrorDataContents(edata); + pfree(edata); +} + +/* + * FreeErrorDataContents --- free the subsidiary data of an ErrorData. + * + * This can be used on either an error stack entry or a copied ErrorData. + */ +static void +FreeErrorDataContents(ErrorData *edata) +{ + if (edata->message) + pfree(edata->message); + if (edata->detail) + pfree(edata->detail); + if (edata->detail_log) + pfree(edata->detail_log); + if (edata->hint) + pfree(edata->hint); + if (edata->context) + pfree(edata->context); + if (edata->backtrace) + pfree(edata->backtrace); + if (edata->schema_name) + pfree(edata->schema_name); + if (edata->table_name) + pfree(edata->table_name); + if (edata->column_name) + pfree(edata->column_name); + if (edata->datatype_name) + pfree(edata->datatype_name); + if (edata->constraint_name) + pfree(edata->constraint_name); + if (edata->internalquery) + pfree(edata->internalquery); +} + +/* + * FlushErrorState --- flush the error state after error recovery + * + * This should be called by an error handler after it's done processing + * the error; or as soon as it's done CopyErrorData, if it intends to + * do stuff that is likely to provoke another error. You are not "out" of + * the error subsystem until you have done this. + */ +void +FlushErrorState(void) +{ + /* + * Reset stack to empty. The only case where it would be more than one + * deep is if we serviced an error that interrupted construction of + * another message. We assume control escaped out of that message + * construction and won't ever go back. + */ + errordata_stack_depth = -1; + recursion_depth = 0; + /* Delete all data in ErrorContext */ + MemoryContextResetAndDeleteChildren(ErrorContext); +} + +/* + * ThrowErrorData --- report an error described by an ErrorData structure + * + * This is somewhat like ReThrowError, but it allows elevels besides ERROR, + * and the boolean flags such as output_to_server are computed via the + * default rules rather than being copied from the given ErrorData. + * This is primarily used to re-report errors originally reported by + * background worker processes and then propagated (with or without + * modification) to the backend responsible for them. + */ +void +ThrowErrorData(ErrorData *edata) +{ + ErrorData *newedata; + MemoryContext oldcontext; + + if (!errstart(edata->elevel, edata->domain)) + return; /* error is not to be reported at all */ + + newedata = &errordata[errordata_stack_depth]; + recursion_depth++; + oldcontext = MemoryContextSwitchTo(newedata->assoc_context); + + /* Copy the supplied fields to the error stack entry. */ + if (edata->sqlerrcode != 0) + newedata->sqlerrcode = edata->sqlerrcode; + if (edata->message) + newedata->message = pstrdup(edata->message); + if (edata->detail) + newedata->detail = pstrdup(edata->detail); + if (edata->detail_log) + newedata->detail_log = pstrdup(edata->detail_log); + if (edata->hint) + newedata->hint = pstrdup(edata->hint); + if (edata->context) + newedata->context = pstrdup(edata->context); + if (edata->backtrace) + newedata->backtrace = pstrdup(edata->backtrace); + /* assume message_id is not available */ + if (edata->schema_name) + newedata->schema_name = pstrdup(edata->schema_name); + if (edata->table_name) + newedata->table_name = pstrdup(edata->table_name); + if (edata->column_name) + newedata->column_name = pstrdup(edata->column_name); + if (edata->datatype_name) + newedata->datatype_name = pstrdup(edata->datatype_name); + if (edata->constraint_name) + newedata->constraint_name = pstrdup(edata->constraint_name); + newedata->cursorpos = edata->cursorpos; + newedata->internalpos = edata->internalpos; + if (edata->internalquery) + newedata->internalquery = pstrdup(edata->internalquery); + + MemoryContextSwitchTo(oldcontext); + recursion_depth--; + + /* Process the error. */ + errfinish(edata->filename, edata->lineno, edata->funcname); +} + +/* + * ReThrowError --- re-throw a previously copied error + * + * A handler can do CopyErrorData/FlushErrorState to get out of the error + * subsystem, then do some processing, and finally ReThrowError to re-throw + * the original error. This is slower than just PG_RE_THROW() but should + * be used if the "some processing" is likely to incur another error. + */ +void +ReThrowError(ErrorData *edata) +{ + ErrorData *newedata; + + Assert(edata->elevel == ERROR); + + /* Push the data back into the error context */ + recursion_depth++; + MemoryContextSwitchTo(ErrorContext); + + newedata = get_error_stack_entry(); + memcpy(newedata, edata, sizeof(ErrorData)); + + /* Make copies of separately-allocated fields */ + if (newedata->message) + newedata->message = pstrdup(newedata->message); + if (newedata->detail) + newedata->detail = pstrdup(newedata->detail); + if (newedata->detail_log) + newedata->detail_log = pstrdup(newedata->detail_log); + if (newedata->hint) + newedata->hint = pstrdup(newedata->hint); + if (newedata->context) + newedata->context = pstrdup(newedata->context); + if (newedata->backtrace) + newedata->backtrace = pstrdup(newedata->backtrace); + if (newedata->schema_name) + newedata->schema_name = pstrdup(newedata->schema_name); + if (newedata->table_name) + newedata->table_name = pstrdup(newedata->table_name); + if (newedata->column_name) + newedata->column_name = pstrdup(newedata->column_name); + if (newedata->datatype_name) + newedata->datatype_name = pstrdup(newedata->datatype_name); + if (newedata->constraint_name) + newedata->constraint_name = pstrdup(newedata->constraint_name); + if (newedata->internalquery) + newedata->internalquery = pstrdup(newedata->internalquery); + + /* Reset the assoc_context to be ErrorContext */ + newedata->assoc_context = ErrorContext; + + recursion_depth--; + PG_RE_THROW(); +} + +/* + * pg_re_throw --- out-of-line implementation of PG_RE_THROW() macro + */ +void +pg_re_throw(void) +{ + /* If possible, throw the error to the next outer setjmp handler */ + if (PG_exception_stack != NULL) + siglongjmp(*PG_exception_stack, 1); + else if (yql_error_report_active) { + ErrorData *edata = &errordata[errordata_stack_depth]; + send_message_to_server_log(edata); + FlushErrorState(); + yql_raise_error(); + } + { + /* + * If we get here, elog(ERROR) was thrown inside a PG_TRY block, which + * we have now exited only to discover that there is no outer setjmp + * handler to pass the error to. Had the error been thrown outside + * the block to begin with, we'd have promoted the error to FATAL, so + * the correct behavior is to make it FATAL now; that is, emit it and + * then call proc_exit. + */ + ErrorData *edata = &errordata[errordata_stack_depth]; + + Assert(errordata_stack_depth >= 0); + Assert(edata->elevel == ERROR); + edata->elevel = FATAL; + + /* + * At least in principle, the increase in severity could have changed + * where-to-output decisions, so recalculate. + */ + edata->output_to_server = should_output_to_server(FATAL); + edata->output_to_client = should_output_to_client(FATAL); + + /* + * We can use errfinish() for the rest, but we don't want it to call + * any error context routines a second time. Since we know we are + * about to exit, it should be OK to just clear the context stack. + */ + error_context_stack = NULL; + + errfinish(edata->filename, edata->lineno, edata->funcname); + } + + /* Doesn't return ... */ + ExceptionalCondition("pg_re_throw tried to return", __FILE__, __LINE__); +} + + +/* + * GetErrorContextStack - Return the context stack, for display/diags + * + * Returns a pstrdup'd string in the caller's context which includes the PG + * error call stack. It is the caller's responsibility to ensure this string + * is pfree'd (or its context cleaned up) when done. + * + * This information is collected by traversing the error contexts and calling + * each context's callback function, each of which is expected to call + * errcontext() to return a string which can be presented to the user. + */ +char * +GetErrorContextStack(void) +{ + ErrorData *edata; + ErrorContextCallback *econtext; + + /* + * Crank up a stack entry to store the info in. + */ + recursion_depth++; + + edata = get_error_stack_entry(); + + /* + * Set up assoc_context to be the caller's context, so any allocations + * done (which will include edata->context) will use their context. + */ + edata->assoc_context = CurrentMemoryContext; + + /* + * Call any context callback functions to collect the context information + * into edata->context. + * + * Errors occurring in callback functions should go through the regular + * error handling code which should handle any recursive errors, though we + * double-check above, just in case. + */ + for (econtext = error_context_stack; + econtext != NULL; + econtext = econtext->previous) + econtext->callback(econtext->arg); + + /* + * Clean ourselves off the stack, any allocations done should have been + * using edata->assoc_context, which we set up earlier to be the caller's + * context, so we're free to just remove our entry off the stack and + * decrement recursion depth and exit. + */ + errordata_stack_depth--; + recursion_depth--; + + /* + * Return a pointer to the string the caller asked for, which should have + * been allocated in their context. + */ + return edata->context; +} + + +/* + * Initialization of error output file + */ +void +DebugFileOpen(void) +{ + int fd, + istty; + + if (OutputFileName[0]) + { + /* + * A debug-output file name was given. + * + * Make sure we can write the file, and find out if it's a tty. + */ + if ((fd = open(OutputFileName, O_CREAT | O_APPEND | O_WRONLY, + 0666)) < 0) + ereport(FATAL, + (errcode_for_file_access(), + errmsg("could not open file \"%s\": %m", OutputFileName))); + istty = isatty(fd); + close(fd); + + /* + * Redirect our stderr to the debug output file. + */ + if (!freopen(OutputFileName, "a", stderr)) + ereport(FATAL, + (errcode_for_file_access(), + errmsg("could not reopen file \"%s\" as stderr: %m", + OutputFileName))); + + /* + * If the file is a tty and we're running under the postmaster, try to + * send stdout there as well (if it isn't a tty then stderr will block + * out stdout, so we may as well let stdout go wherever it was going + * before). + */ + if (istty && IsUnderPostmaster) + if (!freopen(OutputFileName, "a", stdout)) + ereport(FATAL, + (errcode_for_file_access(), + errmsg("could not reopen file \"%s\" as stdout: %m", + OutputFileName))); + } +} + + +/* + * GUC check_hook for backtrace_functions + * + * We split the input string, where commas separate function names + * and certain whitespace chars are ignored, into a \0-separated (and + * \0\0-terminated) list of function names. This formulation allows + * easy scanning when an error is thrown while avoiding the use of + * non-reentrant strtok(), as well as keeping the output data in a + * single palloc() chunk. + */ +bool +check_backtrace_functions(char **newval, void **extra, GucSource source) +{ + int newvallen = strlen(*newval); + char *someval; + int validlen; + int i; + int j; + + /* + * Allow characters that can be C identifiers and commas as separators, as + * well as some whitespace for readability. + */ + validlen = strspn(*newval, + "0123456789_" + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + ", \n\t"); + if (validlen != newvallen) + { + GUC_check_errdetail("invalid character"); + return false; + } + + if (*newval[0] == '\0') + { + *extra = NULL; + return true; + } + + /* + * Allocate space for the output and create the copy. We could discount + * whitespace chars to save some memory, but it doesn't seem worth the + * trouble. + */ + someval = guc_malloc(ERROR, newvallen + 1 + 1); + for (i = 0, j = 0; i < newvallen; i++) + { + if ((*newval)[i] == ',') + someval[j++] = '\0'; /* next item */ + else if ((*newval)[i] == ' ' || + (*newval)[i] == '\n' || + (*newval)[i] == '\t') + ; /* ignore these */ + else + someval[j++] = (*newval)[i]; /* copy anything else */ + } + + /* two \0s end the setting */ + someval[j] = '\0'; + someval[j + 1] = '\0'; + + *extra = someval; + return true; +} + +/* + * GUC assign_hook for backtrace_functions + */ +void +assign_backtrace_functions(const char *newval, void *extra) +{ + backtrace_symbol_list = (char *) extra; +} + +/* + * GUC check_hook for log_destination + */ +bool +check_log_destination(char **newval, void **extra, GucSource source) +{ + char *rawstring; + List *elemlist; + ListCell *l; + int newlogdest = 0; + int *myextra; + + /* Need a modifiable copy of string */ + rawstring = pstrdup(*newval); + + /* Parse string into list of identifiers */ + if (!SplitIdentifierString(rawstring, ',', &elemlist)) + { + /* syntax error in list */ + GUC_check_errdetail("List syntax is invalid."); + pfree(rawstring); + list_free(elemlist); + return false; + } + + foreach(l, elemlist) + { + char *tok = (char *) lfirst(l); + + if (pg_strcasecmp(tok, "stderr") == 0) + newlogdest |= LOG_DESTINATION_STDERR; + else if (pg_strcasecmp(tok, "csvlog") == 0) + newlogdest |= LOG_DESTINATION_CSVLOG; + else if (pg_strcasecmp(tok, "jsonlog") == 0) + newlogdest |= LOG_DESTINATION_JSONLOG; +#ifdef HAVE_SYSLOG + else if (pg_strcasecmp(tok, "syslog") == 0) + newlogdest |= LOG_DESTINATION_SYSLOG; +#endif +#ifdef WIN32 + else if (pg_strcasecmp(tok, "eventlog") == 0) + newlogdest |= LOG_DESTINATION_EVENTLOG; +#endif + else + { + GUC_check_errdetail("Unrecognized key word: \"%s\".", tok); + pfree(rawstring); + list_free(elemlist); + return false; + } + } + + pfree(rawstring); + list_free(elemlist); + + myextra = (int *) guc_malloc(ERROR, sizeof(int)); + *myextra = newlogdest; + *extra = (void *) myextra; + + return true; +} + +/* + * GUC assign_hook for log_destination + */ +void +assign_log_destination(const char *newval, void *extra) +{ + Log_destination = *((int *) extra); +} + +/* + * GUC assign_hook for syslog_ident + */ +void +assign_syslog_ident(const char *newval, void *extra) +{ +#ifdef HAVE_SYSLOG + /* + * guc.c is likely to call us repeatedly with same parameters, so don't + * thrash the syslog connection unnecessarily. Also, we do not re-open + * the connection until needed, since this routine will get called whether + * or not Log_destination actually mentions syslog. + * + * Note that we make our own copy of the ident string rather than relying + * on guc.c's. This may be overly paranoid, but it ensures that we cannot + * accidentally free a string that syslog is still using. + */ + if (syslog_ident == NULL || strcmp(syslog_ident, newval) != 0) + { + if (openlog_done) + { + closelog(); + openlog_done = false; + } + free(syslog_ident); + syslog_ident = strdup(newval); + /* if the strdup fails, we will cope in write_syslog() */ + } +#endif + /* Without syslog support, just ignore it */ +} + +/* + * GUC assign_hook for syslog_facility + */ +void +assign_syslog_facility(int newval, void *extra) +{ +#ifdef HAVE_SYSLOG + /* + * As above, don't thrash the syslog connection unnecessarily. + */ + if (syslog_facility != newval) + { + if (openlog_done) + { + closelog(); + openlog_done = false; + } + syslog_facility = newval; + } +#endif + /* Without syslog support, just ignore it */ +} + +#ifdef HAVE_SYSLOG + +/* + * Write a message line to syslog + */ +static void +write_syslog(int level, const char *line) +{ + static __thread unsigned long seq = 0; + + int len; + const char *nlpos; + + /* Open syslog connection if not done yet */ + if (!openlog_done) + { + openlog(syslog_ident ? syslog_ident : "postgres", + LOG_PID | LOG_NDELAY | LOG_NOWAIT, + syslog_facility); + openlog_done = true; + } + + /* + * We add a sequence number to each log message to suppress "same" + * messages. + */ + seq++; + + /* + * Our problem here is that many syslog implementations don't handle long + * messages in an acceptable manner. While this function doesn't help that + * fact, it does work around by splitting up messages into smaller pieces. + * + * We divide into multiple syslog() calls if message is too long or if the + * message contains embedded newline(s). + */ + len = strlen(line); + nlpos = strchr(line, '\n'); + if (syslog_split_messages && (len > PG_SYSLOG_LIMIT || nlpos != NULL)) + { + int chunk_nr = 0; + + while (len > 0) + { + char buf[PG_SYSLOG_LIMIT + 1]; + int buflen; + int i; + + /* if we start at a newline, move ahead one char */ + if (line[0] == '\n') + { + line++; + len--; + /* we need to recompute the next newline's position, too */ + nlpos = strchr(line, '\n'); + continue; + } + + /* copy one line, or as much as will fit, to buf */ + if (nlpos != NULL) + buflen = nlpos - line; + else + buflen = len; + buflen = Min(buflen, PG_SYSLOG_LIMIT); + memcpy(buf, line, buflen); + buf[buflen] = '\0'; + + /* trim to multibyte letter boundary */ + buflen = pg_mbcliplen(buf, buflen, buflen); + if (buflen <= 0) + return; + buf[buflen] = '\0'; + + /* already word boundary? */ + if (line[buflen] != '\0' && + !isspace((unsigned char) line[buflen])) + { + /* try to divide at word boundary */ + i = buflen - 1; + while (i > 0 && !isspace((unsigned char) buf[i])) + i--; + + if (i > 0) /* else couldn't divide word boundary */ + { + buflen = i; + buf[i] = '\0'; + } + } + + chunk_nr++; + + if (syslog_sequence_numbers) + syslog(level, "[%lu-%d] %s", seq, chunk_nr, buf); + else + syslog(level, "[%d] %s", chunk_nr, buf); + + line += buflen; + len -= buflen; + } + } + else + { + /* message short enough */ + if (syslog_sequence_numbers) + syslog(level, "[%lu] %s", seq, line); + else + syslog(level, "%s", line); + } +} +#endif /* HAVE_SYSLOG */ + +#ifdef WIN32 +/* + * Get the PostgreSQL equivalent of the Windows ANSI code page. "ANSI" system + * interfaces (e.g. CreateFileA()) expect string arguments in this encoding. + * Every process in a given system will find the same value at all times. + */ +static int +GetACPEncoding(void) +{ + static int encoding = -2; + + if (encoding == -2) + encoding = pg_codepage_to_encoding(GetACP()); + + return encoding; +} + +/* + * Write a message line to the windows event log + */ +static void +write_eventlog(int level, const char *line, int len) +{ + WCHAR *utf16; + int eventlevel = EVENTLOG_ERROR_TYPE; + static HANDLE evtHandle = INVALID_HANDLE_VALUE; + + if (evtHandle == INVALID_HANDLE_VALUE) + { + evtHandle = RegisterEventSource(NULL, + event_source ? event_source : DEFAULT_EVENT_SOURCE); + if (evtHandle == NULL) + { + evtHandle = INVALID_HANDLE_VALUE; + return; + } + } + + switch (level) + { + case DEBUG5: + case DEBUG4: + case DEBUG3: + case DEBUG2: + case DEBUG1: + case LOG: + case LOG_SERVER_ONLY: + case INFO: + case NOTICE: + eventlevel = EVENTLOG_INFORMATION_TYPE; + break; + case WARNING: + case WARNING_CLIENT_ONLY: + eventlevel = EVENTLOG_WARNING_TYPE; + break; + case ERROR: + case FATAL: + case PANIC: + default: + eventlevel = EVENTLOG_ERROR_TYPE; + break; + } + + /* + * If message character encoding matches the encoding expected by + * ReportEventA(), call it to avoid the hazards of conversion. Otherwise, + * try to convert the message to UTF16 and write it with ReportEventW(). + * Fall back on ReportEventA() if conversion failed. + * + * Since we palloc the structure required for conversion, also fall + * through to writing unconverted if we have not yet set up + * CurrentMemoryContext. + * + * Also verify that we are not on our way into error recursion trouble due + * to error messages thrown deep inside pgwin32_message_to_UTF16(). + */ + if (!in_error_recursion_trouble() && + CurrentMemoryContext != NULL && + GetMessageEncoding() != GetACPEncoding()) + { + utf16 = pgwin32_message_to_UTF16(line, len, NULL); + if (utf16) + { + ReportEventW(evtHandle, + eventlevel, + 0, + 0, /* All events are Id 0 */ + NULL, + 1, + 0, + (LPCWSTR *) &utf16, + NULL); + /* XXX Try ReportEventA() when ReportEventW() fails? */ + + pfree(utf16); + return; + } + } + ReportEventA(evtHandle, + eventlevel, + 0, + 0, /* All events are Id 0 */ + NULL, + 1, + 0, + &line, + NULL); +} +#endif /* WIN32 */ + +static void +write_console(const char *line, int len) +{ + int rc; + +#ifdef WIN32 + + /* + * Try to convert the message to UTF16 and write it with WriteConsoleW(). + * Fall back on write() if anything fails. + * + * In contrast to write_eventlog(), don't skip straight to write() based + * on the applicable encodings. Unlike WriteConsoleW(), write() depends + * on the suitability of the console output code page. Since we put + * stderr into binary mode in SubPostmasterMain(), write() skips the + * necessary translation anyway. + * + * WriteConsoleW() will fail if stderr is redirected, so just fall through + * to writing unconverted to the logfile in this case. + * + * Since we palloc the structure required for conversion, also fall + * through to writing unconverted if we have not yet set up + * CurrentMemoryContext. + */ + if (!in_error_recursion_trouble() && + !redirection_done && + CurrentMemoryContext != NULL) + { + WCHAR *utf16; + int utf16len; + + utf16 = pgwin32_message_to_UTF16(line, len, &utf16len); + if (utf16 != NULL) + { + HANDLE stdHandle; + DWORD written; + + stdHandle = GetStdHandle(STD_ERROR_HANDLE); + if (WriteConsoleW(stdHandle, utf16, utf16len, &written, NULL)) + { + pfree(utf16); + return; + } + + /* + * In case WriteConsoleW() failed, fall back to writing the + * message unconverted. + */ + pfree(utf16); + } + } +#else + + /* + * Conversion on non-win32 platforms is not implemented yet. It requires + * non-throw version of pg_do_encoding_conversion(), that converts + * unconvertible characters to '?' without errors. + * + * XXX: We have a no-throw version now. It doesn't convert to '?' though. + */ +#endif + + /* + * We ignore any error from write() here. We have no useful way to report + * it ... certainly whining on stderr isn't likely to be productive. + */ + rc = write(fileno(stderr), line, len); + (void) rc; +} + +/* + * get_formatted_log_time -- compute and get the log timestamp. + * + * The timestamp is computed if not set yet, so as it is kept consistent + * among all the log destinations that require it to be consistent. Note + * that the computed timestamp is returned in a static buffer, not + * palloc()'d. + */ +char * +get_formatted_log_time(void) +{ + pg_time_t stamp_time; + char msbuf[13]; + + /* leave if already computed */ + if (formatted_log_time[0] != '\0') + return formatted_log_time; + + if (!saved_timeval_set) + { + gettimeofday(&saved_timeval, NULL); + saved_timeval_set = true; + } + + stamp_time = (pg_time_t) saved_timeval.tv_sec; + + /* + * Note: we expect that guc.c will ensure that log_timezone is set up (at + * least with a minimal GMT value) before Log_line_prefix can become + * nonempty or CSV mode can be selected. + */ + pg_strftime(formatted_log_time, FORMATTED_TS_LEN, + /* leave room for milliseconds... */ + "%Y-%m-%d %H:%M:%S %Z", + pg_localtime(&stamp_time, log_timezone)); + + /* 'paste' milliseconds into place... */ + sprintf(msbuf, ".%03d", (int) (saved_timeval.tv_usec / 1000)); + memcpy(formatted_log_time + 19, msbuf, 4); + + return formatted_log_time; +} + +/* + * reset_formatted_start_time -- reset the start timestamp + */ +void +reset_formatted_start_time(void) +{ + formatted_start_time[0] = '\0'; +} + +/* + * get_formatted_start_time -- compute and get the start timestamp. + * + * The timestamp is computed if not set yet. Note that the computed + * timestamp is returned in a static buffer, not palloc()'d. + */ +char * +get_formatted_start_time(void) +{ + pg_time_t stamp_time = (pg_time_t) MyStartTime; + + /* leave if already computed */ + if (formatted_start_time[0] != '\0') + return formatted_start_time; + + /* + * Note: we expect that guc.c will ensure that log_timezone is set up (at + * least with a minimal GMT value) before Log_line_prefix can become + * nonempty or CSV mode can be selected. + */ + pg_strftime(formatted_start_time, FORMATTED_TS_LEN, + "%Y-%m-%d %H:%M:%S %Z", + pg_localtime(&stamp_time, log_timezone)); + + return formatted_start_time; +} + +/* + * check_log_of_query -- check if a query can be logged + */ +bool +check_log_of_query(ErrorData *edata) +{ + /* log required? */ + if (!is_log_level_output(edata->elevel, log_min_error_statement)) + return false; + + /* query log wanted? */ + if (edata->hide_stmt) + return false; + + /* query string available? */ + if (debug_query_string == NULL) + return false; + + return true; +} + +/* + * get_backend_type_for_log -- backend type for log entries + * + * Returns a pointer to a static buffer, not palloc()'d. + */ +const char * +get_backend_type_for_log(void) +{ + const char *backend_type_str; + + if (MyProcPid == PostmasterPid) + backend_type_str = "postmaster"; + else if (MyBackendType == B_BG_WORKER) + backend_type_str = MyBgworkerEntry->bgw_type; + else + backend_type_str = GetBackendTypeDesc(MyBackendType); + + return backend_type_str; +} + +/* + * process_log_prefix_padding --- helper function for processing the format + * string in log_line_prefix + * + * Note: This function returns NULL if it finds something which + * it deems invalid in the format string. + */ +static const char * +process_log_prefix_padding(const char *p, int *ppadding) +{ + int paddingsign = 1; + int padding = 0; + + if (*p == '-') + { + p++; + + if (*p == '\0') /* Did the buf end in %- ? */ + return NULL; + paddingsign = -1; + } + + /* generate an int version of the numerical string */ + while (*p >= '0' && *p <= '9') + padding = padding * 10 + (*p++ - '0'); + + /* format is invalid if it ends with the padding number */ + if (*p == '\0') + return NULL; + + padding *= paddingsign; + *ppadding = padding; + return p; +} + +/* + * Format log status information using Log_line_prefix. + */ +static void +log_line_prefix(StringInfo buf, ErrorData *edata) +{ + log_status_format(buf, Log_line_prefix, edata); +} + +/* + * Format log status info; append to the provided buffer. + */ +void +log_status_format(StringInfo buf, const char *format, ErrorData *edata) +{ + /* static counter for line numbers */ + static __thread long log_line_number = 0; + + /* has counter been reset in current process? */ + static __thread int log_my_pid = 0; + int padding; + const char *p; + + /* + * This is one of the few places where we'd rather not inherit a static + * variable's value from the postmaster. But since we will, reset it when + * MyProcPid changes. MyStartTime also changes when MyProcPid does, so + * reset the formatted start timestamp too. + */ + if (log_my_pid != MyProcPid) + { + log_line_number = 0; + log_my_pid = MyProcPid; + reset_formatted_start_time(); + } + log_line_number++; + + if (format == NULL) + return; /* in case guc hasn't run yet */ + + for (p = format; *p != '\0'; p++) + { + if (*p != '%') + { + /* literal char, just copy */ + appendStringInfoChar(buf, *p); + continue; + } + + /* must be a '%', so skip to the next char */ + p++; + if (*p == '\0') + break; /* format error - ignore it */ + else if (*p == '%') + { + /* string contains %% */ + appendStringInfoChar(buf, '%'); + continue; + } + + + /* + * Process any formatting which may exist after the '%'. Note that + * process_log_prefix_padding moves p past the padding number if it + * exists. + * + * Note: Since only '-', '0' to '9' are valid formatting characters we + * can do a quick check here to pre-check for formatting. If the char + * is not formatting then we can skip a useless function call. + * + * Further note: At least on some platforms, passing %*s rather than + * %s to appendStringInfo() is substantially slower, so many of the + * cases below avoid doing that unless non-zero padding is in fact + * specified. + */ + if (*p > '9') + padding = 0; + else if ((p = process_log_prefix_padding(p, &padding)) == NULL) + break; + + /* process the option */ + switch (*p) + { + case 'a': + if (MyProcPort) + { + const char *appname = application_name; + + if (appname == NULL || *appname == '\0') + appname = _("[unknown]"); + if (padding != 0) + appendStringInfo(buf, "%*s", padding, appname); + else + appendStringInfoString(buf, appname); + } + else if (padding != 0) + appendStringInfoSpaces(buf, + padding > 0 ? padding : -padding); + + break; + case 'b': + { + const char *backend_type_str = get_backend_type_for_log(); + + if (padding != 0) + appendStringInfo(buf, "%*s", padding, backend_type_str); + else + appendStringInfoString(buf, backend_type_str); + break; + } + case 'u': + if (MyProcPort) + { + const char *username = MyProcPort->user_name; + + if (username == NULL || *username == '\0') + username = _("[unknown]"); + if (padding != 0) + appendStringInfo(buf, "%*s", padding, username); + else + appendStringInfoString(buf, username); + } + else if (padding != 0) + appendStringInfoSpaces(buf, + padding > 0 ? padding : -padding); + break; + case 'd': + if (MyProcPort) + { + const char *dbname = MyProcPort->database_name; + + if (dbname == NULL || *dbname == '\0') + dbname = _("[unknown]"); + if (padding != 0) + appendStringInfo(buf, "%*s", padding, dbname); + else + appendStringInfoString(buf, dbname); + } + else if (padding != 0) + appendStringInfoSpaces(buf, + padding > 0 ? padding : -padding); + break; + case 'c': + if (padding != 0) + { + char strfbuf[128]; + + snprintf(strfbuf, sizeof(strfbuf) - 1, "%lx.%x", + (long) (MyStartTime), MyProcPid); + appendStringInfo(buf, "%*s", padding, strfbuf); + } + else + appendStringInfo(buf, "%lx.%x", (long) (MyStartTime), MyProcPid); + break; + case 'p': + if (padding != 0) + appendStringInfo(buf, "%*d", padding, MyProcPid); + else + appendStringInfo(buf, "%d", MyProcPid); + break; + + case 'P': + if (MyProc) + { + PGPROC *leader = MyProc->lockGroupLeader; + + /* + * Show the leader only for active parallel workers. This + * leaves out the leader of a parallel group. + */ + if (leader == NULL || leader->pid == MyProcPid) + appendStringInfoSpaces(buf, + padding > 0 ? padding : -padding); + else if (padding != 0) + appendStringInfo(buf, "%*d", padding, leader->pid); + else + appendStringInfo(buf, "%d", leader->pid); + } + else if (padding != 0) + appendStringInfoSpaces(buf, + padding > 0 ? padding : -padding); + break; + + case 'l': + if (padding != 0) + appendStringInfo(buf, "%*ld", padding, log_line_number); + else + appendStringInfo(buf, "%ld", log_line_number); + break; + case 'm': + /* force a log timestamp reset */ + formatted_log_time[0] = '\0'; + (void) get_formatted_log_time(); + + if (padding != 0) + appendStringInfo(buf, "%*s", padding, formatted_log_time); + else + appendStringInfoString(buf, formatted_log_time); + break; + case 't': + { + pg_time_t stamp_time = (pg_time_t) time(NULL); + char strfbuf[128]; + + pg_strftime(strfbuf, sizeof(strfbuf), + "%Y-%m-%d %H:%M:%S %Z", + pg_localtime(&stamp_time, log_timezone)); + if (padding != 0) + appendStringInfo(buf, "%*s", padding, strfbuf); + else + appendStringInfoString(buf, strfbuf); + } + break; + case 'n': + { + char strfbuf[128]; + + if (!saved_timeval_set) + { + gettimeofday(&saved_timeval, NULL); + saved_timeval_set = true; + } + + snprintf(strfbuf, sizeof(strfbuf), "%ld.%03d", + (long) saved_timeval.tv_sec, + (int) (saved_timeval.tv_usec / 1000)); + + if (padding != 0) + appendStringInfo(buf, "%*s", padding, strfbuf); + else + appendStringInfoString(buf, strfbuf); + } + break; + case 's': + { + char *start_time = get_formatted_start_time(); + + if (padding != 0) + appendStringInfo(buf, "%*s", padding, start_time); + else + appendStringInfoString(buf, start_time); + } + break; + case 'i': + if (MyProcPort) + { + const char *psdisp; + int displen; + + psdisp = get_ps_display(&displen); + if (padding != 0) + appendStringInfo(buf, "%*s", padding, psdisp); + else + appendBinaryStringInfo(buf, psdisp, displen); + } + else if (padding != 0) + appendStringInfoSpaces(buf, + padding > 0 ? padding : -padding); + break; + case 'r': + if (MyProcPort && MyProcPort->remote_host) + { + if (padding != 0) + { + if (MyProcPort->remote_port && MyProcPort->remote_port[0] != '\0') + { + /* + * This option is slightly special as the port + * number may be appended onto the end. Here we + * need to build 1 string which contains the + * remote_host and optionally the remote_port (if + * set) so we can properly align the string. + */ + + char *hostport; + + hostport = psprintf("%s(%s)", MyProcPort->remote_host, MyProcPort->remote_port); + appendStringInfo(buf, "%*s", padding, hostport); + pfree(hostport); + } + else + appendStringInfo(buf, "%*s", padding, MyProcPort->remote_host); + } + else + { + /* padding is 0, so we don't need a temp buffer */ + appendStringInfoString(buf, MyProcPort->remote_host); + if (MyProcPort->remote_port && + MyProcPort->remote_port[0] != '\0') + appendStringInfo(buf, "(%s)", + MyProcPort->remote_port); + } + } + else if (padding != 0) + appendStringInfoSpaces(buf, + padding > 0 ? padding : -padding); + break; + case 'h': + if (MyProcPort && MyProcPort->remote_host) + { + if (padding != 0) + appendStringInfo(buf, "%*s", padding, MyProcPort->remote_host); + else + appendStringInfoString(buf, MyProcPort->remote_host); + } + else if (padding != 0) + appendStringInfoSpaces(buf, + padding > 0 ? padding : -padding); + break; + case 'q': + /* in postmaster and friends, stop if %q is seen */ + /* in a backend, just ignore */ + if (MyProcPort == NULL) + return; + break; + case 'v': + /* keep VXID format in sync with lockfuncs.c */ + if (MyProc != NULL && MyProc->backendId != InvalidBackendId) + { + if (padding != 0) + { + char strfbuf[128]; + + snprintf(strfbuf, sizeof(strfbuf) - 1, "%d/%u", + MyProc->backendId, MyProc->lxid); + appendStringInfo(buf, "%*s", padding, strfbuf); + } + else + appendStringInfo(buf, "%d/%u", MyProc->backendId, MyProc->lxid); + } + else if (padding != 0) + appendStringInfoSpaces(buf, + padding > 0 ? padding : -padding); + break; + case 'x': + if (padding != 0) + appendStringInfo(buf, "%*u", padding, GetTopTransactionIdIfAny()); + else + appendStringInfo(buf, "%u", GetTopTransactionIdIfAny()); + break; + case 'e': + if (padding != 0) + appendStringInfo(buf, "%*s", padding, unpack_sql_state(edata->sqlerrcode)); + else + appendStringInfoString(buf, unpack_sql_state(edata->sqlerrcode)); + break; + case 'Q': + if (padding != 0) + appendStringInfo(buf, "%*lld", padding, + (long long) pgstat_get_my_query_id()); + else + appendStringInfo(buf, "%lld", + (long long) pgstat_get_my_query_id()); + break; + default: + /* format error - ignore it */ + break; + } + } +} + +/* + * Unpack MAKE_SQLSTATE code. Note that this returns a pointer to a + * static buffer. + */ +char * +unpack_sql_state(int sql_state) +{ + static __thread char buf[12]; + int i; + + for (i = 0; i < 5; i++) + { + buf[i] = PGUNSIXBIT(sql_state); + sql_state >>= 6; + } + + buf[i] = '\0'; + return buf; +} + + +/* + * Write error report to server's log + */ +static void +send_message_to_server_log(ErrorData *edata) +{ + StringInfoData buf; + bool fallback_to_stderr = false; + + initStringInfo(&buf); + + saved_timeval_set = false; + formatted_log_time[0] = '\0'; + + log_line_prefix(&buf, edata); + appendStringInfo(&buf, "%s: ", _(error_severity(edata->elevel))); + + if (Log_error_verbosity >= PGERROR_VERBOSE) + appendStringInfo(&buf, "%s: ", unpack_sql_state(edata->sqlerrcode)); + + if (edata->message) + append_with_tabs(&buf, edata->message); + else + append_with_tabs(&buf, _("missing error text")); + + if (edata->cursorpos > 0) + appendStringInfo(&buf, _(" at character %d"), + edata->cursorpos); + else if (edata->internalpos > 0) + appendStringInfo(&buf, _(" at character %d"), + edata->internalpos); + + appendStringInfoChar(&buf, '\n'); + + if (Log_error_verbosity >= PGERROR_DEFAULT) + { + if (edata->detail_log) + { + log_line_prefix(&buf, edata); + appendStringInfoString(&buf, _("DETAIL: ")); + append_with_tabs(&buf, edata->detail_log); + appendStringInfoChar(&buf, '\n'); + } + else if (edata->detail) + { + log_line_prefix(&buf, edata); + appendStringInfoString(&buf, _("DETAIL: ")); + append_with_tabs(&buf, edata->detail); + appendStringInfoChar(&buf, '\n'); + } + if (edata->hint) + { + log_line_prefix(&buf, edata); + appendStringInfoString(&buf, _("HINT: ")); + append_with_tabs(&buf, edata->hint); + appendStringInfoChar(&buf, '\n'); + } + if (edata->internalquery) + { + log_line_prefix(&buf, edata); + appendStringInfoString(&buf, _("QUERY: ")); + append_with_tabs(&buf, edata->internalquery); + appendStringInfoChar(&buf, '\n'); + } + if (edata->context && !edata->hide_ctx) + { + log_line_prefix(&buf, edata); + appendStringInfoString(&buf, _("CONTEXT: ")); + append_with_tabs(&buf, edata->context); + appendStringInfoChar(&buf, '\n'); + } + if (Log_error_verbosity >= PGERROR_VERBOSE) + { + /* assume no newlines in funcname or filename... */ + if (edata->funcname && edata->filename) + { + log_line_prefix(&buf, edata); + appendStringInfo(&buf, _("LOCATION: %s, %s:%d\n"), + edata->funcname, edata->filename, + edata->lineno); + } + else if (edata->filename) + { + log_line_prefix(&buf, edata); + appendStringInfo(&buf, _("LOCATION: %s:%d\n"), + edata->filename, edata->lineno); + } + } + if (edata->backtrace) + { + log_line_prefix(&buf, edata); + appendStringInfoString(&buf, _("BACKTRACE: ")); + append_with_tabs(&buf, edata->backtrace); + appendStringInfoChar(&buf, '\n'); + } + } + + /* + * If the user wants the query that generated this error logged, do it. + */ + if (check_log_of_query(edata)) + { + log_line_prefix(&buf, edata); + appendStringInfoString(&buf, _("STATEMENT: ")); + append_with_tabs(&buf, debug_query_string); + appendStringInfoChar(&buf, '\n'); + } + + if (yql_error_report_active) { + yql_prepare_error(buf.data); + return; + } + +#ifdef HAVE_SYSLOG + /* Write to syslog, if enabled */ + if (Log_destination & LOG_DESTINATION_SYSLOG) + { + int syslog_level; + + switch (edata->elevel) + { + case DEBUG5: + case DEBUG4: + case DEBUG3: + case DEBUG2: + case DEBUG1: + syslog_level = LOG_DEBUG; + break; + case LOG: + case LOG_SERVER_ONLY: + case INFO: + syslog_level = LOG_INFO; + break; + case NOTICE: + case WARNING: + case WARNING_CLIENT_ONLY: + syslog_level = LOG_NOTICE; + break; + case ERROR: + syslog_level = LOG_WARNING; + break; + case FATAL: + syslog_level = LOG_ERR; + break; + case PANIC: + default: + syslog_level = LOG_CRIT; + break; + } + + write_syslog(syslog_level, buf.data); + } +#endif /* HAVE_SYSLOG */ + +#ifdef WIN32 + /* Write to eventlog, if enabled */ + if (Log_destination & LOG_DESTINATION_EVENTLOG) + { + write_eventlog(edata->elevel, buf.data, buf.len); + } +#endif /* WIN32 */ + + /* Write to csvlog, if enabled */ + if (Log_destination & LOG_DESTINATION_CSVLOG) + { + /* + * Send CSV data if it's safe to do so (syslogger doesn't need the + * pipe). If this is not possible, fallback to an entry written to + * stderr. + */ + if (redirection_done || MyBackendType == B_LOGGER) + write_csvlog(edata); + else + fallback_to_stderr = true; + } + + /* Write to JSON log, if enabled */ + if (Log_destination & LOG_DESTINATION_JSONLOG) + { + /* + * Send JSON data if it's safe to do so (syslogger doesn't need the + * pipe). If this is not possible, fallback to an entry written to + * stderr. + */ + if (redirection_done || MyBackendType == B_LOGGER) + { + write_jsonlog(edata); + } + else + fallback_to_stderr = true; + } + + /* + * Write to stderr, if enabled or if required because of a previous + * limitation. + */ + if ((Log_destination & LOG_DESTINATION_STDERR) || + whereToSendOutput == DestDebug || + fallback_to_stderr) + { + /* + * Use the chunking protocol if we know the syslogger should be + * catching stderr output, and we are not ourselves the syslogger. + * Otherwise, just do a vanilla write to stderr. + */ + if (redirection_done && MyBackendType != B_LOGGER) + write_pipe_chunks(buf.data, buf.len, LOG_DESTINATION_STDERR); +#ifdef WIN32 + + /* + * In a win32 service environment, there is no usable stderr. Capture + * anything going there and write it to the eventlog instead. + * + * If stderr redirection is active, it was OK to write to stderr above + * because that's really a pipe to the syslogger process. + */ + else if (pgwin32_is_service()) + write_eventlog(edata->elevel, buf.data, buf.len); +#endif + else + write_console(buf.data, buf.len); + } + + /* If in the syslogger process, try to write messages direct to file */ + if (MyBackendType == B_LOGGER) + write_syslogger_file(buf.data, buf.len, LOG_DESTINATION_STDERR); + + /* No more need of the message formatted for stderr */ + pfree(buf.data); +} + +/* + * Send data to the syslogger using the chunked protocol + * + * Note: when there are multiple backends writing into the syslogger pipe, + * it's critical that each write go into the pipe indivisibly, and not + * get interleaved with data from other processes. Fortunately, the POSIX + * spec requires that writes to pipes be atomic so long as they are not + * more than PIPE_BUF bytes long. So we divide long messages into chunks + * that are no more than that length, and send one chunk per write() call. + * The collector process knows how to reassemble the chunks. + * + * Because of the atomic write requirement, there are only two possible + * results from write() here: -1 for failure, or the requested number of + * bytes. There is not really anything we can do about a failure; retry would + * probably be an infinite loop, and we can't even report the error usefully. + * (There is noplace else we could send it!) So we might as well just ignore + * the result from write(). However, on some platforms you get a compiler + * warning from ignoring write()'s result, so do a little dance with casting + * rc to void to shut up the compiler. + */ +void +write_pipe_chunks(char *data, int len, int dest) +{ + PipeProtoChunk p; + int fd = fileno(stderr); + int rc; + + Assert(len > 0); + + p.proto.nuls[0] = p.proto.nuls[1] = '\0'; + p.proto.pid = MyProcPid; + p.proto.flags = 0; + if (dest == LOG_DESTINATION_STDERR) + p.proto.flags |= PIPE_PROTO_DEST_STDERR; + else if (dest == LOG_DESTINATION_CSVLOG) + p.proto.flags |= PIPE_PROTO_DEST_CSVLOG; + else if (dest == LOG_DESTINATION_JSONLOG) + p.proto.flags |= PIPE_PROTO_DEST_JSONLOG; + + /* write all but the last chunk */ + while (len > PIPE_MAX_PAYLOAD) + { + /* no need to set PIPE_PROTO_IS_LAST yet */ + p.proto.len = PIPE_MAX_PAYLOAD; + memcpy(p.proto.data, data, PIPE_MAX_PAYLOAD); + rc = write(fd, &p, PIPE_HEADER_SIZE + PIPE_MAX_PAYLOAD); + (void) rc; + data += PIPE_MAX_PAYLOAD; + len -= PIPE_MAX_PAYLOAD; + } + + /* write the last chunk */ + p.proto.flags |= PIPE_PROTO_IS_LAST; + p.proto.len = len; + memcpy(p.proto.data, data, len); + rc = write(fd, &p, PIPE_HEADER_SIZE + len); + (void) rc; +} + + +/* + * Append a text string to the error report being built for the client. + * + * This is ordinarily identical to pq_sendstring(), but if we are in + * error recursion trouble we skip encoding conversion, because of the + * possibility that the problem is a failure in the encoding conversion + * subsystem itself. Code elsewhere should ensure that the passed-in + * strings will be plain 7-bit ASCII, and thus not in need of conversion, + * in such cases. (In particular, we disable localization of error messages + * to help ensure that's true.) + */ +static void +err_sendstring(StringInfo buf, const char *str) +{ + if (in_error_recursion_trouble()) + pq_send_ascii_string(buf, str); + else + pq_sendstring(buf, str); +} + +/* + * Write error report to client + */ +static void +send_message_to_frontend(ErrorData *edata) +{ + StringInfoData msgbuf; + + /* + * We no longer support pre-3.0 FE/BE protocol, except here. If a client + * tries to connect using an older protocol version, it's nice to send the + * "protocol version not supported" error in a format the client + * understands. If protocol hasn't been set yet, early in backend + * startup, assume modern protocol. + */ + if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3 || FrontendProtocol == 0) + { + /* New style with separate fields */ + const char *sev; + char tbuf[12]; + + /* 'N' (Notice) is for nonfatal conditions, 'E' is for errors */ + pq_beginmessage(&msgbuf, (edata->elevel < ERROR) ? 'N' : 'E'); + + sev = error_severity(edata->elevel); + pq_sendbyte(&msgbuf, PG_DIAG_SEVERITY); + err_sendstring(&msgbuf, _(sev)); + pq_sendbyte(&msgbuf, PG_DIAG_SEVERITY_NONLOCALIZED); + err_sendstring(&msgbuf, sev); + + pq_sendbyte(&msgbuf, PG_DIAG_SQLSTATE); + err_sendstring(&msgbuf, unpack_sql_state(edata->sqlerrcode)); + + /* M field is required per protocol, so always send something */ + pq_sendbyte(&msgbuf, PG_DIAG_MESSAGE_PRIMARY); + if (edata->message) + err_sendstring(&msgbuf, edata->message); + else + err_sendstring(&msgbuf, _("missing error text")); + + if (edata->detail) + { + pq_sendbyte(&msgbuf, PG_DIAG_MESSAGE_DETAIL); + err_sendstring(&msgbuf, edata->detail); + } + + /* detail_log is intentionally not used here */ + + if (edata->hint) + { + pq_sendbyte(&msgbuf, PG_DIAG_MESSAGE_HINT); + err_sendstring(&msgbuf, edata->hint); + } + + if (edata->context) + { + pq_sendbyte(&msgbuf, PG_DIAG_CONTEXT); + err_sendstring(&msgbuf, edata->context); + } + + if (edata->schema_name) + { + pq_sendbyte(&msgbuf, PG_DIAG_SCHEMA_NAME); + err_sendstring(&msgbuf, edata->schema_name); + } + + if (edata->table_name) + { + pq_sendbyte(&msgbuf, PG_DIAG_TABLE_NAME); + err_sendstring(&msgbuf, edata->table_name); + } + + if (edata->column_name) + { + pq_sendbyte(&msgbuf, PG_DIAG_COLUMN_NAME); + err_sendstring(&msgbuf, edata->column_name); + } + + if (edata->datatype_name) + { + pq_sendbyte(&msgbuf, PG_DIAG_DATATYPE_NAME); + err_sendstring(&msgbuf, edata->datatype_name); + } + + if (edata->constraint_name) + { + pq_sendbyte(&msgbuf, PG_DIAG_CONSTRAINT_NAME); + err_sendstring(&msgbuf, edata->constraint_name); + } + + if (edata->cursorpos > 0) + { + snprintf(tbuf, sizeof(tbuf), "%d", edata->cursorpos); + pq_sendbyte(&msgbuf, PG_DIAG_STATEMENT_POSITION); + err_sendstring(&msgbuf, tbuf); + } + + if (edata->internalpos > 0) + { + snprintf(tbuf, sizeof(tbuf), "%d", edata->internalpos); + pq_sendbyte(&msgbuf, PG_DIAG_INTERNAL_POSITION); + err_sendstring(&msgbuf, tbuf); + } + + if (edata->internalquery) + { + pq_sendbyte(&msgbuf, PG_DIAG_INTERNAL_QUERY); + err_sendstring(&msgbuf, edata->internalquery); + } + + if (edata->filename) + { + pq_sendbyte(&msgbuf, PG_DIAG_SOURCE_FILE); + err_sendstring(&msgbuf, edata->filename); + } + + if (edata->lineno > 0) + { + snprintf(tbuf, sizeof(tbuf), "%d", edata->lineno); + pq_sendbyte(&msgbuf, PG_DIAG_SOURCE_LINE); + err_sendstring(&msgbuf, tbuf); + } + + if (edata->funcname) + { + pq_sendbyte(&msgbuf, PG_DIAG_SOURCE_FUNCTION); + err_sendstring(&msgbuf, edata->funcname); + } + + pq_sendbyte(&msgbuf, '\0'); /* terminator */ + + pq_endmessage(&msgbuf); + } + else + { + /* Old style --- gin up a backwards-compatible message */ + StringInfoData buf; + + initStringInfo(&buf); + + appendStringInfo(&buf, "%s: ", _(error_severity(edata->elevel))); + + if (edata->message) + appendStringInfoString(&buf, edata->message); + else + appendStringInfoString(&buf, _("missing error text")); + + appendStringInfoChar(&buf, '\n'); + + /* 'N' (Notice) is for nonfatal conditions, 'E' is for errors */ + pq_putmessage_v2((edata->elevel < ERROR) ? 'N' : 'E', buf.data, buf.len + 1); + + pfree(buf.data); + } + + /* + * This flush is normally not necessary, since postgres.c will flush out + * waiting data when control returns to the main loop. But it seems best + * to leave it here, so that the client has some clue what happened if the + * backend dies before getting back to the main loop ... error/notice + * messages should not be a performance-critical path anyway, so an extra + * flush won't hurt much ... + */ + pq_flush(); +} + + +/* + * Support routines for formatting error messages. + */ + + +/* + * error_severity --- get string representing elevel + * + * The string is not localized here, but we mark the strings for translation + * so that callers can invoke _() on the result. + */ +const char * +error_severity(int elevel) +{ + const char *prefix; + + switch (elevel) + { + case DEBUG1: + case DEBUG2: + case DEBUG3: + case DEBUG4: + case DEBUG5: + prefix = gettext_noop("DEBUG"); + break; + case LOG: + case LOG_SERVER_ONLY: + prefix = gettext_noop("LOG"); + break; + case INFO: + prefix = gettext_noop("INFO"); + break; + case NOTICE: + prefix = gettext_noop("NOTICE"); + break; + case WARNING: + case WARNING_CLIENT_ONLY: + prefix = gettext_noop("WARNING"); + break; + case ERROR: + prefix = gettext_noop("ERROR"); + break; + case FATAL: + prefix = gettext_noop("FATAL"); + break; + case PANIC: + prefix = gettext_noop("PANIC"); + break; + default: + prefix = "???"; + break; + } + + return prefix; +} + + +/* + * append_with_tabs + * + * Append the string to the StringInfo buffer, inserting a tab after any + * newline. + */ +static void +append_with_tabs(StringInfo buf, const char *str) +{ + char ch; + + while ((ch = *str++) != '\0') + { + appendStringInfoCharMacro(buf, ch); + if (ch == '\n') + appendStringInfoCharMacro(buf, '\t'); + } +} + + +/* + * Write errors to stderr (or by equal means when stderr is + * not available). Used before ereport/elog can be used + * safely (memory context, GUC load etc) + */ +void +write_stderr(const char *fmt,...) +{ + va_list ap; + +#ifdef WIN32 + char errbuf[2048]; /* Arbitrary size? */ +#endif + + fmt = _(fmt); + + va_start(ap, fmt); +#ifndef WIN32 + /* On Unix, we just fprintf to stderr */ + vfprintf(stderr, fmt, ap); + fflush(stderr); +#else + vsnprintf(errbuf, sizeof(errbuf), fmt, ap); + + /* + * On Win32, we print to stderr if running on a console, or write to + * eventlog if running as a service + */ + if (pgwin32_is_service()) /* Running as a service */ + { + write_eventlog(ERROR, errbuf, strlen(errbuf)); + } + else + { + /* Not running as service, write to stderr */ + write_console(errbuf, strlen(errbuf)); + fflush(stderr); + } +#endif + va_end(ap); +} + + +/* + * Write a message to STDERR using only async-signal-safe functions. This can + * be used to safely emit a message from a signal handler. + * + * TODO: It is likely possible to safely do a limited amount of string + * interpolation (e.g., %s and %d), but that is not presently supported. + */ +void +write_stderr_signal_safe(const char *str) +{ + int nwritten = 0; + int ntotal = strlen(str); + + while (nwritten < ntotal) + { + int rc; + + rc = write(STDERR_FILENO, str + nwritten, ntotal - nwritten); + + /* Just give up on error. There isn't much else we can do. */ + if (rc == -1) + return; + + nwritten += rc; + } +} + + +/* + * Adjust the level of a recovery-related message per trace_recovery_messages. + * + * The argument is the default log level of the message, eg, DEBUG2. (This + * should only be applied to DEBUGn log messages, otherwise it's a no-op.) + * If the level is >= trace_recovery_messages, we return LOG, causing the + * message to be logged unconditionally (for most settings of + * log_min_messages). Otherwise, we return the argument unchanged. + * The message will then be shown based on the setting of log_min_messages. + * + * Intention is to keep this for at least the whole of the 9.0 production + * release, so we can more easily diagnose production problems in the field. + * It should go away eventually, though, because it's an ugly and + * hard-to-explain kluge. + */ +int +trace_recovery(int trace_level) +{ + if (trace_level < LOG && + trace_level >= trace_recovery_messages) + return LOG; + + return trace_level; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/error/jsonlog.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/error/jsonlog.c new file mode 100644 index 00000000000..bc624f63e22 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/error/jsonlog.c @@ -0,0 +1,303 @@ +/*------------------------------------------------------------------------- + * + * jsonlog.c + * JSON logging + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/error/jsonlog.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/xact.h" +#include "libpq/libpq.h" +#include "lib/stringinfo.h" +#include "miscadmin.h" +#include "postmaster/bgworker.h" +#include "postmaster/syslogger.h" +#include "storage/lock.h" +#include "storage/proc.h" +#include "tcop/tcopprot.h" +#include "utils/backend_status.h" +#include "utils/elog.h" +#include "utils/guc.h" +#include "utils/json.h" +#include "utils/ps_status.h" + +static void appendJSONKeyValueFmt(StringInfo buf, const char *key, + bool escape_key, + const char *fmt,...) pg_attribute_printf(4, 5); + +/* + * appendJSONKeyValue + * + * Append to a StringInfo a comma followed by a JSON key and a value. + * The key is always escaped. The value can be escaped optionally, that + * is dependent on the data type of the key. + */ +static void +appendJSONKeyValue(StringInfo buf, const char *key, const char *value, + bool escape_value) +{ + Assert(key != NULL); + + if (value == NULL) + return; + + appendStringInfoChar(buf, ','); + escape_json(buf, key); + appendStringInfoChar(buf, ':'); + + if (escape_value) + escape_json(buf, value); + else + appendStringInfoString(buf, value); +} + + +/* + * appendJSONKeyValueFmt + * + * Evaluate the fmt string and then invoke appendJSONKeyValue() as the + * value of the JSON property. Both the key and value will be escaped by + * appendJSONKeyValue(). + */ +static void +appendJSONKeyValueFmt(StringInfo buf, const char *key, + bool escape_key, const char *fmt,...) +{ + int save_errno = errno; + size_t len = 128; /* initial assumption about buffer size */ + char *value; + + for (;;) + { + va_list args; + size_t newlen; + + /* Allocate result buffer */ + value = (char *) palloc(len); + + /* Try to format the data. */ + errno = save_errno; + va_start(args, fmt); + newlen = pvsnprintf(value, len, fmt, args); + va_end(args); + + if (newlen < len) + break; /* success */ + + /* Release buffer and loop around to try again with larger len. */ + pfree(value); + len = newlen; + } + + appendJSONKeyValue(buf, key, value, escape_key); + + /* Clean up */ + pfree(value); +} + +/* + * Write logs in json format. + */ +void +write_jsonlog(ErrorData *edata) +{ + StringInfoData buf; + char *start_time; + char *log_time; + + /* static counter for line numbers */ + static __thread long log_line_number = 0; + + /* Has the counter been reset in the current process? */ + static __thread int log_my_pid = 0; + + /* + * This is one of the few places where we'd rather not inherit a static + * variable's value from the postmaster. But since we will, reset it when + * MyProcPid changes. + */ + if (log_my_pid != MyProcPid) + { + log_line_number = 0; + log_my_pid = MyProcPid; + reset_formatted_start_time(); + } + log_line_number++; + + initStringInfo(&buf); + + /* Initialize string */ + appendStringInfoChar(&buf, '{'); + + /* timestamp with milliseconds */ + log_time = get_formatted_log_time(); + + /* + * First property does not use appendJSONKeyValue as it does not have + * comma prefix. + */ + escape_json(&buf, "timestamp"); + appendStringInfoChar(&buf, ':'); + escape_json(&buf, log_time); + + /* username */ + if (MyProcPort) + appendJSONKeyValue(&buf, "user", MyProcPort->user_name, true); + + /* database name */ + if (MyProcPort) + appendJSONKeyValue(&buf, "dbname", MyProcPort->database_name, true); + + /* Process ID */ + if (MyProcPid != 0) + appendJSONKeyValueFmt(&buf, "pid", false, "%d", MyProcPid); + + /* Remote host and port */ + if (MyProcPort && MyProcPort->remote_host) + { + appendJSONKeyValue(&buf, "remote_host", MyProcPort->remote_host, true); + if (MyProcPort->remote_port && MyProcPort->remote_port[0] != '\0') + appendJSONKeyValue(&buf, "remote_port", MyProcPort->remote_port, false); + } + + /* Session id */ + appendJSONKeyValueFmt(&buf, "session_id", true, "%lx.%x", + (long) MyStartTime, MyProcPid); + + /* Line number */ + appendJSONKeyValueFmt(&buf, "line_num", false, "%ld", log_line_number); + + /* PS display */ + if (MyProcPort) + { + StringInfoData msgbuf; + const char *psdisp; + int displen; + + initStringInfo(&msgbuf); + psdisp = get_ps_display(&displen); + appendBinaryStringInfo(&msgbuf, psdisp, displen); + appendJSONKeyValue(&buf, "ps", msgbuf.data, true); + + pfree(msgbuf.data); + } + + /* session start timestamp */ + start_time = get_formatted_start_time(); + appendJSONKeyValue(&buf, "session_start", start_time, true); + + /* Virtual transaction id */ + /* keep VXID format in sync with lockfuncs.c */ + if (MyProc != NULL && MyProc->backendId != InvalidBackendId) + appendJSONKeyValueFmt(&buf, "vxid", true, "%d/%u", MyProc->backendId, + MyProc->lxid); + + /* Transaction id */ + appendJSONKeyValueFmt(&buf, "txid", false, "%u", + GetTopTransactionIdIfAny()); + + /* Error severity */ + if (edata->elevel) + appendJSONKeyValue(&buf, "error_severity", + (char *) error_severity(edata->elevel), true); + + /* SQL state code */ + if (edata->sqlerrcode) + appendJSONKeyValue(&buf, "state_code", + unpack_sql_state(edata->sqlerrcode), true); + + /* errmessage */ + appendJSONKeyValue(&buf, "message", edata->message, true); + + /* errdetail or error_detail log */ + if (edata->detail_log) + appendJSONKeyValue(&buf, "detail", edata->detail_log, true); + else + appendJSONKeyValue(&buf, "detail", edata->detail, true); + + /* errhint */ + if (edata->hint) + appendJSONKeyValue(&buf, "hint", edata->hint, true); + + /* internal query */ + if (edata->internalquery) + appendJSONKeyValue(&buf, "internal_query", edata->internalquery, + true); + + /* if printed internal query, print internal pos too */ + if (edata->internalpos > 0 && edata->internalquery != NULL) + appendJSONKeyValueFmt(&buf, "internal_position", false, "%d", + edata->internalpos); + + /* errcontext */ + if (edata->context && !edata->hide_ctx) + appendJSONKeyValue(&buf, "context", edata->context, true); + + /* user query --- only reported if not disabled by the caller */ + if (check_log_of_query(edata)) + { + appendJSONKeyValue(&buf, "statement", debug_query_string, true); + if (edata->cursorpos > 0) + appendJSONKeyValueFmt(&buf, "cursor_position", false, "%d", + edata->cursorpos); + } + + /* file error location */ + if (Log_error_verbosity >= PGERROR_VERBOSE) + { + if (edata->funcname) + appendJSONKeyValue(&buf, "func_name", edata->funcname, true); + if (edata->filename) + { + appendJSONKeyValue(&buf, "file_name", edata->filename, true); + appendJSONKeyValueFmt(&buf, "file_line_num", false, "%d", + edata->lineno); + } + } + + /* Application name */ + if (application_name && application_name[0] != '\0') + appendJSONKeyValue(&buf, "application_name", application_name, true); + + /* backend type */ + appendJSONKeyValue(&buf, "backend_type", get_backend_type_for_log(), true); + + /* leader PID */ + if (MyProc) + { + PGPROC *leader = MyProc->lockGroupLeader; + + /* + * Show the leader only for active parallel workers. This leaves out + * the leader of a parallel group. + */ + if (leader && leader->pid != MyProcPid) + appendJSONKeyValueFmt(&buf, "leader_pid", false, "%d", + leader->pid); + } + + /* query id */ + appendJSONKeyValueFmt(&buf, "query_id", false, "%lld", + (long long) pgstat_get_my_query_id()); + + /* Finish string */ + appendStringInfoChar(&buf, '}'); + appendStringInfoChar(&buf, '\n'); + + /* If in the syslogger process, try to write messages direct to file */ + if (MyBackendType == B_LOGGER) + write_syslogger_file(buf.data, buf.len, LOG_DESTINATION_JSONLOG); + else + write_pipe_chunks(buf.data, buf.len, LOG_DESTINATION_JSONLOG); + + pfree(buf.data); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/fmgr/dfmgr.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/fmgr/dfmgr.c new file mode 100644 index 00000000000..47288468499 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/fmgr/dfmgr.c @@ -0,0 +1,700 @@ +/*------------------------------------------------------------------------- + * + * dfmgr.c + * Dynamic function manager code. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/fmgr/dfmgr.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <sys/stat.h> + +#ifndef WIN32 +#include <dlfcn.h> + +/* + * On macOS, <dlfcn.h> insists on including <stdbool.h>. If we're not + * using stdbool, undef bool to undo the damage. + */ +#ifndef PG_USE_STDBOOL +#ifdef bool +#undef bool +#endif +#endif +#endif /* !WIN32 */ + +#include "fmgr.h" +#include "lib/stringinfo.h" +#include "miscadmin.h" +#include "storage/shmem.h" +#include "utils/hsearch.h" + + +/* signature for PostgreSQL-specific library init function */ +typedef void (*PG_init_t) (void); + +/* hashtable entry for rendezvous variables */ +typedef struct +{ + char varName[NAMEDATALEN]; /* hash key (must be first) */ + void *varValue; +} rendezvousHashEntry; + +/* + * List of dynamically loaded files (kept in malloc'd memory). + */ + +typedef struct df_files +{ + struct df_files *next; /* List link */ + dev_t device; /* Device file is on */ +#ifndef WIN32 /* ensures we never again depend on this under + * win32 */ + ino_t inode; /* Inode number of file */ +#endif + void *handle; /* a handle for pg_dl* functions */ + char filename[FLEXIBLE_ARRAY_MEMBER]; /* Full pathname of file */ +} DynamicFileList; + +static __thread DynamicFileList *file_list = NULL; +static __thread DynamicFileList *file_tail = NULL; + +/* stat() call under Win32 returns an st_ino field, but it has no meaning */ +#ifndef WIN32 +#define SAME_INODE(A,B) ((A).st_ino == (B).inode && (A).st_dev == (B).device) +#else +#define SAME_INODE(A,B) false +#endif + +__thread char *Dynamic_library_path; + +static void *internal_load_library(const char *libname); +static void incompatible_module_error(const char *libname, + const Pg_magic_struct *module_magic_data) pg_attribute_noreturn(); +static bool file_exists(const char *name); +static char *expand_dynamic_library_name(const char *name); +static void check_restricted_library_name(const char *name); +static char *substitute_libpath_macro(const char *name); +static char *find_in_dynamic_libpath(const char *basename); + +/* Magic structure that module needs to match to be accepted */ +static const Pg_magic_struct magic_data = PG_MODULE_MAGIC_DATA; + + +/* + * Load the specified dynamic-link library file, and look for a function + * named funcname in it. + * + * If the function is not found, we raise an error if signalNotFound is true, + * else return NULL. Note that errors in loading the library + * will provoke ereport() regardless of signalNotFound. + * + * If filehandle is not NULL, then *filehandle will be set to a handle + * identifying the library file. The filehandle can be used with + * lookup_external_function to lookup additional functions in the same file + * at less cost than repeating load_external_function. + */ +void * +load_external_function(const char *filename, const char *funcname, + bool signalNotFound, void **filehandle) +{ + char *fullname; + void *lib_handle; + void *retval; + + /* Expand the possibly-abbreviated filename to an exact path name */ + fullname = expand_dynamic_library_name(filename); + + /* Load the shared library, unless we already did */ + lib_handle = internal_load_library(fullname); + + /* Return handle if caller wants it */ + if (filehandle) + *filehandle = lib_handle; + + /* Look up the function within the library. */ + retval = dlsym(lib_handle, funcname); + + if (retval == NULL && signalNotFound) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not find function \"%s\" in file \"%s\"", + funcname, fullname))); + + pfree(fullname); + return retval; +} + +/* + * This function loads a shlib file without looking up any particular + * function in it. If the same shlib has previously been loaded, + * unload and reload it. + * + * When 'restricted' is true, only libraries in the presumed-secure + * directory $libdir/plugins may be referenced. + */ +void +load_file(const char *filename, bool restricted) +{ + char *fullname; + + /* Apply security restriction if requested */ + if (restricted) + check_restricted_library_name(filename); + + /* Expand the possibly-abbreviated filename to an exact path name */ + fullname = expand_dynamic_library_name(filename); + + /* Load the shared library */ + (void) internal_load_library(fullname); + + pfree(fullname); +} + +/* + * Lookup a function whose library file is already loaded. + * Return NULL if not found. + */ +void * +lookup_external_function(void *filehandle, const char *funcname) +{ + return dlsym(filehandle, funcname); +} + + +/* + * Load the specified dynamic-link library file, unless it already is + * loaded. Return the pg_dl* handle for the file. + * + * Note: libname is expected to be an exact name for the library file. + * + * NB: There is presently no way to unload a dynamically loaded file. We might + * add one someday if we can convince ourselves we have safe protocols for un- + * hooking from hook function pointers, releasing custom GUC variables, and + * perhaps other things that are definitely unsafe currently. + */ +static void * +internal_load_library(const char *libname) +{ + DynamicFileList *file_scanner; + PGModuleMagicFunction magic_func; + char *load_error; + struct stat stat_buf; + PG_init_t PG_init; + + /* + * Scan the list of loaded FILES to see if the file has been loaded. + */ + for (file_scanner = file_list; + file_scanner != NULL && + strcmp(libname, file_scanner->filename) != 0; + file_scanner = file_scanner->next) + ; + + if (file_scanner == NULL) + { + /* + * Check for same files - different paths (ie, symlink or link) + */ + if (stat(libname, &stat_buf) == -1) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not access file \"%s\": %m", + libname))); + + for (file_scanner = file_list; + file_scanner != NULL && + !SAME_INODE(stat_buf, *file_scanner); + file_scanner = file_scanner->next) + ; + } + + if (file_scanner == NULL) + { + /* + * File not loaded yet. + */ + file_scanner = (DynamicFileList *) + malloc(offsetof(DynamicFileList, filename) + strlen(libname) + 1); + if (file_scanner == NULL) + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"))); + + MemSet(file_scanner, 0, offsetof(DynamicFileList, filename)); + strcpy(file_scanner->filename, libname); + file_scanner->device = stat_buf.st_dev; +#ifndef WIN32 + file_scanner->inode = stat_buf.st_ino; +#endif + file_scanner->next = NULL; + + file_scanner->handle = dlopen(file_scanner->filename, RTLD_NOW | RTLD_GLOBAL); + if (file_scanner->handle == NULL) + { + load_error = dlerror(); + free(file_scanner); + /* errcode_for_file_access might not be appropriate here? */ + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not load library \"%s\": %s", + libname, load_error))); + } + + /* Check the magic function to determine compatibility */ + magic_func = (PGModuleMagicFunction) + dlsym(file_scanner->handle, PG_MAGIC_FUNCTION_NAME_STRING); + if (magic_func) + { + const Pg_magic_struct *magic_data_ptr = (*magic_func) (); + + if (magic_data_ptr->len != magic_data.len || + memcmp(magic_data_ptr, &magic_data, magic_data.len) != 0) + { + /* copy data block before unlinking library */ + Pg_magic_struct module_magic_data = *magic_data_ptr; + + /* try to close library */ + dlclose(file_scanner->handle); + free(file_scanner); + + /* issue suitable complaint */ + incompatible_module_error(libname, &module_magic_data); + } + } + else + { + /* try to close library */ + dlclose(file_scanner->handle); + free(file_scanner); + /* complain */ + ereport(ERROR, + (errmsg("incompatible library \"%s\": missing magic block", + libname), + errhint("Extension libraries are required to use the PG_MODULE_MAGIC macro."))); + } + + /* + * If the library has a _PG_init() function, call it. + */ + PG_init = (PG_init_t) dlsym(file_scanner->handle, "_PG_init"); + if (PG_init) + (*PG_init) (); + + /* OK to link it into list */ + if (file_list == NULL) + file_list = file_scanner; + else + file_tail->next = file_scanner; + file_tail = file_scanner; + } + + return file_scanner->handle; +} + +/* + * Report a suitable error for an incompatible magic block. + */ +static void +incompatible_module_error(const char *libname, + const Pg_magic_struct *module_magic_data) +{ + StringInfoData details; + + /* + * If the version doesn't match, just report that, because the rest of the + * block might not even have the fields we expect. + */ + if (magic_data.version != module_magic_data->version) + { + char library_version[32]; + + if (module_magic_data->version >= 1000) + snprintf(library_version, sizeof(library_version), "%d", + module_magic_data->version / 100); + else + snprintf(library_version, sizeof(library_version), "%d.%d", + module_magic_data->version / 100, + module_magic_data->version % 100); + ereport(ERROR, + (errmsg("incompatible library \"%s\": version mismatch", + libname), + errdetail("Server is version %d, library is version %s.", + magic_data.version / 100, library_version))); + } + + /* + * Similarly, if the ABI extra field doesn't match, error out. Other + * fields below might also mismatch, but that isn't useful information if + * you're using the wrong product altogether. + */ + if (strcmp(module_magic_data->abi_extra, magic_data.abi_extra) != 0) + { + ereport(ERROR, + (errmsg("incompatible library \"%s\": ABI mismatch", + libname), + errdetail("Server has ABI \"%s\", library has \"%s\".", + magic_data.abi_extra, + module_magic_data->abi_extra))); + } + + /* + * Otherwise, spell out which fields don't agree. + * + * XXX this code has to be adjusted any time the set of fields in a magic + * block change! + */ + initStringInfo(&details); + + if (module_magic_data->funcmaxargs != magic_data.funcmaxargs) + { + if (details.len) + appendStringInfoChar(&details, '\n'); + appendStringInfo(&details, + _("Server has FUNC_MAX_ARGS = %d, library has %d."), + magic_data.funcmaxargs, + module_magic_data->funcmaxargs); + } + if (module_magic_data->indexmaxkeys != magic_data.indexmaxkeys) + { + if (details.len) + appendStringInfoChar(&details, '\n'); + appendStringInfo(&details, + _("Server has INDEX_MAX_KEYS = %d, library has %d."), + magic_data.indexmaxkeys, + module_magic_data->indexmaxkeys); + } + if (module_magic_data->namedatalen != magic_data.namedatalen) + { + if (details.len) + appendStringInfoChar(&details, '\n'); + appendStringInfo(&details, + _("Server has NAMEDATALEN = %d, library has %d."), + magic_data.namedatalen, + module_magic_data->namedatalen); + } + if (module_magic_data->float8byval != magic_data.float8byval) + { + if (details.len) + appendStringInfoChar(&details, '\n'); + appendStringInfo(&details, + _("Server has FLOAT8PASSBYVAL = %s, library has %s."), + magic_data.float8byval ? "true" : "false", + module_magic_data->float8byval ? "true" : "false"); + } + + if (details.len == 0) + appendStringInfoString(&details, + _("Magic block has unexpected length or padding difference.")); + + ereport(ERROR, + (errmsg("incompatible library \"%s\": magic block mismatch", + libname), + errdetail_internal("%s", details.data))); +} + +static bool +file_exists(const char *name) +{ + struct stat st; + + Assert(name != NULL); + + if (stat(name, &st) == 0) + return !S_ISDIR(st.st_mode); + else if (!(errno == ENOENT || errno == ENOTDIR || errno == EACCES)) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not access file \"%s\": %m", name))); + + return false; +} + + +/* + * If name contains a slash, check if the file exists, if so return + * the name. Else (no slash) try to expand using search path (see + * find_in_dynamic_libpath below); if that works, return the fully + * expanded file name. If the previous failed, append DLSUFFIX and + * try again. If all fails, just return the original name. + * + * The result will always be freshly palloc'd. + */ +static char * +expand_dynamic_library_name(const char *name) +{ + bool have_slash; + char *new; + char *full; + + Assert(name); + + have_slash = (first_dir_separator(name) != NULL); + + if (!have_slash) + { + full = find_in_dynamic_libpath(name); + if (full) + return full; + } + else + { + full = substitute_libpath_macro(name); + if (file_exists(full)) + return full; + pfree(full); + } + + new = psprintf("%s%s", name, DLSUFFIX); + + if (!have_slash) + { + full = find_in_dynamic_libpath(new); + pfree(new); + if (full) + return full; + } + else + { + full = substitute_libpath_macro(new); + pfree(new); + if (file_exists(full)) + return full; + pfree(full); + } + + /* + * If we can't find the file, just return the string as-is. The ensuing + * load attempt will fail and report a suitable message. + */ + return pstrdup(name); +} + +/* + * Check a restricted library name. It must begin with "$libdir/plugins/" + * and there must not be any directory separators after that (this is + * sufficient to prevent ".." style attacks). + */ +static void +check_restricted_library_name(const char *name) +{ + if (strncmp(name, "$libdir/plugins/", 16) != 0 || + first_dir_separator(name + 16) != NULL) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("access to library \"%s\" is not allowed", + name))); +} + +/* + * Substitute for any macros appearing in the given string. + * Result is always freshly palloc'd. + */ +static char * +substitute_libpath_macro(const char *name) +{ + const char *sep_ptr; + + Assert(name != NULL); + + /* Currently, we only recognize $libdir at the start of the string */ + if (name[0] != '$') + return pstrdup(name); + + if ((sep_ptr = first_dir_separator(name)) == NULL) + sep_ptr = name + strlen(name); + + if (strlen("$libdir") != sep_ptr - name || + strncmp(name, "$libdir", strlen("$libdir")) != 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_NAME), + errmsg("invalid macro name in dynamic library path: %s", + name))); + + return psprintf("%s%s", pkglib_path, sep_ptr); +} + + +/* + * Search for a file called 'basename' in the colon-separated search + * path Dynamic_library_path. If the file is found, the full file name + * is returned in freshly palloc'd memory. If the file is not found, + * return NULL. + */ +static char * +find_in_dynamic_libpath(const char *basename) +{ + const char *p; + size_t baselen; + + Assert(basename != NULL); + Assert(first_dir_separator(basename) == NULL); + Assert(Dynamic_library_path != NULL); + + p = Dynamic_library_path; + if (strlen(p) == 0) + return NULL; + + baselen = strlen(basename); + + for (;;) + { + size_t len; + char *piece; + char *mangled; + char *full; + + piece = first_path_var_separator(p); + if (piece == p) + ereport(ERROR, + (errcode(ERRCODE_INVALID_NAME), + errmsg("zero-length component in parameter \"dynamic_library_path\""))); + + if (piece == NULL) + len = strlen(p); + else + len = piece - p; + + piece = palloc(len + 1); + strlcpy(piece, p, len + 1); + + mangled = substitute_libpath_macro(piece); + pfree(piece); + + canonicalize_path(mangled); + + /* only absolute paths */ + if (!is_absolute_path(mangled)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_NAME), + errmsg("component in parameter \"dynamic_library_path\" is not an absolute path"))); + + full = palloc(strlen(mangled) + 1 + baselen + 1); + sprintf(full, "%s/%s", mangled, basename); + pfree(mangled); + + elog(DEBUG3, "find_in_dynamic_libpath: trying \"%s\"", full); + + if (file_exists(full)) + return full; + + pfree(full); + + if (p[len] == '\0') + break; + else + p += len + 1; + } + + return NULL; +} + + +/* + * Find (or create) a rendezvous variable that one dynamically + * loaded library can use to meet up with another. + * + * On the first call of this function for a particular varName, + * a "rendezvous variable" is created with the given name. + * The value of the variable is a void pointer (initially set to NULL). + * Subsequent calls with the same varName just return the address of + * the existing variable. Once created, a rendezvous variable lasts + * for the life of the process. + * + * Dynamically loaded libraries can use rendezvous variables + * to find each other and share information: they just need to agree + * on the variable name and the data it will point to. + */ +void ** +find_rendezvous_variable(const char *varName) +{ + static __thread HTAB *rendezvousHash = NULL; + + rendezvousHashEntry *hentry; + bool found; + + /* Create a hashtable if we haven't already done so in this process */ + if (rendezvousHash == NULL) + { + HASHCTL ctl; + + ctl.keysize = NAMEDATALEN; + ctl.entrysize = sizeof(rendezvousHashEntry); + rendezvousHash = hash_create("Rendezvous variable hash", + 16, + &ctl, + HASH_ELEM | HASH_STRINGS); + } + + /* Find or create the hashtable entry for this varName */ + hentry = (rendezvousHashEntry *) hash_search(rendezvousHash, + varName, + HASH_ENTER, + &found); + + /* Initialize to NULL if first time */ + if (!found) + hentry->varValue = NULL; + + return &hentry->varValue; +} + +/* + * Estimate the amount of space needed to serialize the list of libraries + * we have loaded. + */ +Size +EstimateLibraryStateSpace(void) +{ + DynamicFileList *file_scanner; + Size size = 1; + + for (file_scanner = file_list; + file_scanner != NULL; + file_scanner = file_scanner->next) + size = add_size(size, strlen(file_scanner->filename) + 1); + + return size; +} + +/* + * Serialize the list of libraries we have loaded to a chunk of memory. + */ +void +SerializeLibraryState(Size maxsize, char *start_address) +{ + DynamicFileList *file_scanner; + + for (file_scanner = file_list; + file_scanner != NULL; + file_scanner = file_scanner->next) + { + Size len; + + len = strlcpy(start_address, file_scanner->filename, maxsize) + 1; + Assert(len < maxsize); + maxsize -= len; + start_address += len; + } + start_address[0] = '\0'; +} + +/* + * Load every library the serializing backend had loaded. + */ +void +RestoreLibraryState(char *start_address) +{ + while (*start_address != '\0') + { + internal_load_library(start_address); + start_address += strlen(start_address) + 1; + } +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/fmgr/fmgr.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/fmgr/fmgr.c new file mode 100644 index 00000000000..1c568c89783 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/fmgr/fmgr.c @@ -0,0 +1,2171 @@ +/*------------------------------------------------------------------------- + * + * fmgr.c + * The Postgres function manager. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/fmgr/fmgr.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/detoast.h" +#include "catalog/pg_language.h" +#include "catalog/pg_proc.h" +#include "catalog/pg_type.h" +#include "executor/functions.h" +#include "lib/stringinfo.h" +#include "miscadmin.h" +#include "nodes/makefuncs.h" +#include "nodes/miscnodes.h" +#include "nodes/nodeFuncs.h" +#include "pgstat.h" +#include "utils/acl.h" +#include "utils/builtins.h" +#include "utils/fmgrtab.h" +#include "utils/guc.h" +#include "utils/lsyscache.h" +#include "utils/syscache.h" + +/* + * Hooks for function calls + */ +__thread PGDLLIMPORT needs_fmgr_hook_type needs_fmgr_hook = NULL; +__thread PGDLLIMPORT fmgr_hook_type fmgr_hook = NULL; + +/* + * Hashtable for fast lookup of external C functions + */ +typedef struct +{ + /* fn_oid is the hash key and so must be first! */ + Oid fn_oid; /* OID of an external C function */ + TransactionId fn_xmin; /* for checking up-to-dateness */ + ItemPointerData fn_tid; + PGFunction user_fn; /* the function's address */ + const Pg_finfo_record *inforec; /* address of its info record */ +} CFuncHashTabEntry; + +static __thread HTAB *CFuncHash = NULL; + + +static void fmgr_info_cxt_security(Oid functionId, FmgrInfo *finfo, MemoryContext mcxt, + bool ignore_security); +static void fmgr_info_C_lang(Oid functionId, FmgrInfo *finfo, HeapTuple procedureTuple); +static void fmgr_info_other_lang(Oid functionId, FmgrInfo *finfo, HeapTuple procedureTuple); +static CFuncHashTabEntry *lookup_C_func(HeapTuple procedureTuple); +static void record_C_func(HeapTuple procedureTuple, + PGFunction user_fn, const Pg_finfo_record *inforec); + +/* extern so it's callable via JIT */ +extern Datum fmgr_security_definer(PG_FUNCTION_ARGS); + + +/* + * Lookup routines for builtin-function table. We can search by either Oid + * or name, but search by Oid is much faster. + */ + +static const FmgrBuiltin * +fmgr_isbuiltin(Oid id) +{ + uint16 index; + + /* fast lookup only possible if original oid still assigned */ + if (id > fmgr_last_builtin_oid) + return NULL; + + /* + * Lookup function data. If there's a miss in that range it's likely a + * nonexistent function, returning NULL here will trigger an ERROR later. + */ + index = fmgr_builtin_oid_index[id]; + if (index == InvalidOidBuiltinMapping) + return NULL; + + return &fmgr_builtins[index]; +} + +/* + * Lookup a builtin by name. Note there can be more than one entry in + * the array with the same name, but they should all point to the same + * routine. + */ +static const FmgrBuiltin * +fmgr_lookupByName(const char *name) +{ + int i; + + for (i = 0; i < fmgr_nbuiltins; i++) + { + if (strcmp(name, fmgr_builtins[i].funcName) == 0) + return fmgr_builtins + i; + } + return NULL; +} + +/* + * This routine fills a FmgrInfo struct, given the OID + * of the function to be called. + * + * The caller's CurrentMemoryContext is used as the fn_mcxt of the info + * struct; this means that any subsidiary data attached to the info struct + * (either by fmgr_info itself, or later on by a function call handler) + * will be allocated in that context. The caller must ensure that this + * context is at least as long-lived as the info struct itself. This is + * not a problem in typical cases where the info struct is on the stack or + * in freshly-palloc'd space. However, if one intends to store an info + * struct in a long-lived table, it's better to use fmgr_info_cxt. + */ +void +fmgr_info(Oid functionId, FmgrInfo *finfo) +{ + fmgr_info_cxt_security(functionId, finfo, CurrentMemoryContext, false); +} + +/* + * Fill a FmgrInfo struct, specifying a memory context in which its + * subsidiary data should go. + */ +void +fmgr_info_cxt(Oid functionId, FmgrInfo *finfo, MemoryContext mcxt) +{ + fmgr_info_cxt_security(functionId, finfo, mcxt, false); +} + +/* + * This one does the actual work. ignore_security is ordinarily false + * but is set to true when we need to avoid recursion. + */ +static void +fmgr_info_cxt_security(Oid functionId, FmgrInfo *finfo, MemoryContext mcxt, + bool ignore_security) +{ + const FmgrBuiltin *fbp; + HeapTuple procedureTuple; + Form_pg_proc procedureStruct; + Datum prosrcdatum; + char *prosrc; + + /* + * fn_oid *must* be filled in last. Some code assumes that if fn_oid is + * valid, the whole struct is valid. Some FmgrInfo struct's do survive + * elogs. + */ + finfo->fn_oid = InvalidOid; + finfo->fn_extra = NULL; + finfo->fn_mcxt = mcxt; + finfo->fn_expr = NULL; /* caller may set this later */ + + if ((fbp = fmgr_isbuiltin(functionId)) != NULL) + { + /* + * Fast path for builtin functions: don't bother consulting pg_proc + */ + finfo->fn_nargs = fbp->nargs; + finfo->fn_strict = fbp->strict; + finfo->fn_retset = fbp->retset; + finfo->fn_stats = TRACK_FUNC_ALL; /* ie, never track */ + finfo->fn_addr = fbp->func; + finfo->fn_oid = functionId; + return; + } + + /* Otherwise we need the pg_proc entry */ + procedureTuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(functionId)); + if (!HeapTupleIsValid(procedureTuple)) + elog(ERROR, "cache lookup failed for function %u", functionId); + procedureStruct = (Form_pg_proc) GETSTRUCT(procedureTuple); + + finfo->fn_nargs = procedureStruct->pronargs; + finfo->fn_strict = procedureStruct->proisstrict; + finfo->fn_retset = procedureStruct->proretset; + + /* + * If it has prosecdef set, non-null proconfig, or if a plugin wants to + * hook function entry/exit, use fmgr_security_definer call handler --- + * unless we are being called again by fmgr_security_definer or + * fmgr_info_other_lang. + * + * When using fmgr_security_definer, function stats tracking is always + * disabled at the outer level, and instead we set the flag properly in + * fmgr_security_definer's private flinfo and implement the tracking + * inside fmgr_security_definer. This loses the ability to charge the + * overhead of fmgr_security_definer to the function, but gains the + * ability to set the track_functions GUC as a local GUC parameter of an + * interesting function and have the right things happen. + */ + if (!ignore_security && + (procedureStruct->prosecdef || + !heap_attisnull(procedureTuple, Anum_pg_proc_proconfig, NULL) || + FmgrHookIsNeeded(functionId))) + { + finfo->fn_addr = fmgr_security_definer; + finfo->fn_stats = TRACK_FUNC_ALL; /* ie, never track */ + finfo->fn_oid = functionId; + ReleaseSysCache(procedureTuple); + return; + } + + switch (procedureStruct->prolang) + { + case INTERNALlanguageId: + + /* + * For an ordinary builtin function, we should never get here + * because the fmgr_isbuiltin() search above will have succeeded. + * However, if the user has done a CREATE FUNCTION to create an + * alias for a builtin function, we can end up here. In that case + * we have to look up the function by name. The name of the + * internal function is stored in prosrc (it doesn't have to be + * the same as the name of the alias!) + */ + prosrcdatum = SysCacheGetAttrNotNull(PROCOID, procedureTuple, + Anum_pg_proc_prosrc); + prosrc = TextDatumGetCString(prosrcdatum); + fbp = fmgr_lookupByName(prosrc); + if (fbp == NULL) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("internal function \"%s\" is not in internal lookup table", + prosrc))); + pfree(prosrc); + /* Should we check that nargs, strict, retset match the table? */ + finfo->fn_addr = fbp->func; + /* note this policy is also assumed in fast path above */ + finfo->fn_stats = TRACK_FUNC_ALL; /* ie, never track */ + break; + + case ClanguageId: + fmgr_info_C_lang(functionId, finfo, procedureTuple); + finfo->fn_stats = TRACK_FUNC_PL; /* ie, track if ALL */ + break; + + case SQLlanguageId: + finfo->fn_addr = fmgr_sql; + finfo->fn_stats = TRACK_FUNC_PL; /* ie, track if ALL */ + break; + + default: + fmgr_info_other_lang(functionId, finfo, procedureTuple); + finfo->fn_stats = TRACK_FUNC_OFF; /* ie, track if not OFF */ + break; + } + + finfo->fn_oid = functionId; + ReleaseSysCache(procedureTuple); +} + +/* + * Return module and C function name providing implementation of functionId. + * + * If *mod == NULL and *fn == NULL, no C symbol is known to implement + * function. + * + * If *mod == NULL and *fn != NULL, the function is implemented by a symbol in + * the main binary. + * + * If *mod != NULL and *fn != NULL the function is implemented in an extension + * shared object. + * + * The returned module and function names are pstrdup'ed into the current + * memory context. + */ +void +fmgr_symbol(Oid functionId, char **mod, char **fn) +{ + HeapTuple procedureTuple; + Form_pg_proc procedureStruct; + Datum prosrcattr; + Datum probinattr; + + procedureTuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(functionId)); + if (!HeapTupleIsValid(procedureTuple)) + elog(ERROR, "cache lookup failed for function %u", functionId); + procedureStruct = (Form_pg_proc) GETSTRUCT(procedureTuple); + + if (procedureStruct->prosecdef || + !heap_attisnull(procedureTuple, Anum_pg_proc_proconfig, NULL) || + FmgrHookIsNeeded(functionId)) + { + *mod = NULL; /* core binary */ + *fn = pstrdup("fmgr_security_definer"); + ReleaseSysCache(procedureTuple); + return; + } + + /* see fmgr_info_cxt_security for the individual cases */ + switch (procedureStruct->prolang) + { + case INTERNALlanguageId: + prosrcattr = SysCacheGetAttrNotNull(PROCOID, procedureTuple, + Anum_pg_proc_prosrc); + + *mod = NULL; /* core binary */ + *fn = TextDatumGetCString(prosrcattr); + break; + + case ClanguageId: + prosrcattr = SysCacheGetAttrNotNull(PROCOID, procedureTuple, + Anum_pg_proc_prosrc); + + probinattr = SysCacheGetAttrNotNull(PROCOID, procedureTuple, + Anum_pg_proc_probin); + + /* + * No need to check symbol presence / API version here, already + * checked in fmgr_info_cxt_security. + */ + *mod = TextDatumGetCString(probinattr); + *fn = TextDatumGetCString(prosrcattr); + break; + + case SQLlanguageId: + *mod = NULL; /* core binary */ + *fn = pstrdup("fmgr_sql"); + break; + + default: + *mod = NULL; + *fn = NULL; /* unknown, pass pointer */ + break; + } + + ReleaseSysCache(procedureTuple); +} + + +/* + * Special fmgr_info processing for C-language functions. Note that + * finfo->fn_oid is not valid yet. + */ +static void +fmgr_info_C_lang(Oid functionId, FmgrInfo *finfo, HeapTuple procedureTuple) +{ + CFuncHashTabEntry *hashentry; + PGFunction user_fn; + const Pg_finfo_record *inforec; + + /* + * See if we have the function address cached already + */ + hashentry = lookup_C_func(procedureTuple); + if (hashentry) + { + user_fn = hashentry->user_fn; + inforec = hashentry->inforec; + } + else + { + Datum prosrcattr, + probinattr; + char *prosrcstring, + *probinstring; + void *libraryhandle; + + /* + * Get prosrc and probin strings (link symbol and library filename). + * While in general these columns might be null, that's not allowed + * for C-language functions. + */ + prosrcattr = SysCacheGetAttrNotNull(PROCOID, procedureTuple, + Anum_pg_proc_prosrc); + prosrcstring = TextDatumGetCString(prosrcattr); + + probinattr = SysCacheGetAttrNotNull(PROCOID, procedureTuple, + Anum_pg_proc_probin); + probinstring = TextDatumGetCString(probinattr); + + /* Look up the function itself */ + user_fn = load_external_function(probinstring, prosrcstring, true, + &libraryhandle); + + /* Get the function information record (real or default) */ + inforec = fetch_finfo_record(libraryhandle, prosrcstring); + + /* Cache the addresses for later calls */ + record_C_func(procedureTuple, user_fn, inforec); + + pfree(prosrcstring); + pfree(probinstring); + } + + switch (inforec->api_version) + { + case 1: + /* New style: call directly */ + finfo->fn_addr = user_fn; + break; + default: + /* Shouldn't get here if fetch_finfo_record did its job */ + elog(ERROR, "unrecognized function API version: %d", + inforec->api_version); + break; + } +} + +/* + * Special fmgr_info processing for other-language functions. Note + * that finfo->fn_oid is not valid yet. + */ +static void +fmgr_info_other_lang(Oid functionId, FmgrInfo *finfo, HeapTuple procedureTuple) +{ + Form_pg_proc procedureStruct = (Form_pg_proc) GETSTRUCT(procedureTuple); + Oid language = procedureStruct->prolang; + HeapTuple languageTuple; + Form_pg_language languageStruct; + FmgrInfo plfinfo; + + languageTuple = SearchSysCache1(LANGOID, ObjectIdGetDatum(language)); + if (!HeapTupleIsValid(languageTuple)) + elog(ERROR, "cache lookup failed for language %u", language); + languageStruct = (Form_pg_language) GETSTRUCT(languageTuple); + + /* + * Look up the language's call handler function, ignoring any attributes + * that would normally cause insertion of fmgr_security_definer. We need + * to get back a bare pointer to the actual C-language function. + */ + fmgr_info_cxt_security(languageStruct->lanplcallfoid, &plfinfo, + CurrentMemoryContext, true); + finfo->fn_addr = plfinfo.fn_addr; + + ReleaseSysCache(languageTuple); +} + +/* + * Fetch and validate the information record for the given external function. + * The function is specified by a handle for the containing library + * (obtained from load_external_function) as well as the function name. + * + * If no info function exists for the given name an error is raised. + * + * This function is broken out of fmgr_info_C_lang so that fmgr_c_validator + * can validate the information record for a function not yet entered into + * pg_proc. + */ +const Pg_finfo_record * +fetch_finfo_record(void *filehandle, const char *funcname) +{ + char *infofuncname; + PGFInfoFunction infofunc; + const Pg_finfo_record *inforec; + + infofuncname = psprintf("pg_finfo_%s", funcname); + + /* Try to look up the info function */ + infofunc = (PGFInfoFunction) lookup_external_function(filehandle, + infofuncname); + if (infofunc == NULL) + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not find function information for function \"%s\"", + funcname), + errhint("SQL-callable functions need an accompanying PG_FUNCTION_INFO_V1(funcname)."))); + return NULL; /* silence compiler */ + } + + /* Found, so call it */ + inforec = (*infofunc) (); + + /* Validate result as best we can */ + if (inforec == NULL) + elog(ERROR, "null result from info function \"%s\"", infofuncname); + switch (inforec->api_version) + { + case 1: + /* OK, no additional fields to validate */ + break; + default: + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unrecognized API version %d reported by info function \"%s\"", + inforec->api_version, infofuncname))); + break; + } + + pfree(infofuncname); + return inforec; +} + + +/*------------------------------------------------------------------------- + * Routines for caching lookup information for external C functions. + * + * The routines in dfmgr.c are relatively slow, so we try to avoid running + * them more than once per external function per session. We use a hash table + * with the function OID as the lookup key. + *------------------------------------------------------------------------- + */ + +/* + * lookup_C_func: try to find a C function in the hash table + * + * If an entry exists and is up to date, return it; else return NULL + */ +static CFuncHashTabEntry * +lookup_C_func(HeapTuple procedureTuple) +{ + Oid fn_oid = ((Form_pg_proc) GETSTRUCT(procedureTuple))->oid; + CFuncHashTabEntry *entry; + + if (CFuncHash == NULL) + return NULL; /* no table yet */ + entry = (CFuncHashTabEntry *) + hash_search(CFuncHash, + &fn_oid, + HASH_FIND, + NULL); + if (entry == NULL) + return NULL; /* no such entry */ + if (entry->fn_xmin == HeapTupleHeaderGetRawXmin(procedureTuple->t_data) && + ItemPointerEquals(&entry->fn_tid, &procedureTuple->t_self)) + return entry; /* OK */ + return NULL; /* entry is out of date */ +} + +/* + * record_C_func: enter (or update) info about a C function in the hash table + */ +static void +record_C_func(HeapTuple procedureTuple, + PGFunction user_fn, const Pg_finfo_record *inforec) +{ + Oid fn_oid = ((Form_pg_proc) GETSTRUCT(procedureTuple))->oid; + CFuncHashTabEntry *entry; + bool found; + + /* Create the hash table if it doesn't exist yet */ + if (CFuncHash == NULL) + { + HASHCTL hash_ctl; + + hash_ctl.keysize = sizeof(Oid); + hash_ctl.entrysize = sizeof(CFuncHashTabEntry); + CFuncHash = hash_create("CFuncHash", + 100, + &hash_ctl, + HASH_ELEM | HASH_BLOBS); + } + + entry = (CFuncHashTabEntry *) + hash_search(CFuncHash, + &fn_oid, + HASH_ENTER, + &found); + /* OID is already filled in */ + entry->fn_xmin = HeapTupleHeaderGetRawXmin(procedureTuple->t_data); + entry->fn_tid = procedureTuple->t_self; + entry->user_fn = user_fn; + entry->inforec = inforec; +} + + +/* + * Copy an FmgrInfo struct + * + * This is inherently somewhat bogus since we can't reliably duplicate + * language-dependent subsidiary info. We cheat by zeroing fn_extra, + * instead, meaning that subsidiary info will have to be recomputed. + */ +void +fmgr_info_copy(FmgrInfo *dstinfo, FmgrInfo *srcinfo, + MemoryContext destcxt) +{ + memcpy(dstinfo, srcinfo, sizeof(FmgrInfo)); + dstinfo->fn_mcxt = destcxt; + dstinfo->fn_extra = NULL; +} + + +/* + * Specialized lookup routine for fmgr_internal_validator: given the alleged + * name of an internal function, return the OID of the function. + * If the name is not recognized, return InvalidOid. + */ +Oid +fmgr_internal_function(const char *proname) +{ + const FmgrBuiltin *fbp = fmgr_lookupByName(proname); + + if (fbp == NULL) + return InvalidOid; + return fbp->foid; +} + + +/* + * Support for security-definer and proconfig-using functions. We support + * both of these features using the same call handler, because they are + * often used together and it would be inefficient (as well as notationally + * messy) to have two levels of call handler involved. + */ +struct fmgr_security_definer_cache +{ + FmgrInfo flinfo; /* lookup info for target function */ + Oid userid; /* userid to set, or InvalidOid */ + ArrayType *proconfig; /* GUC values to set, or NULL */ + Datum arg; /* passthrough argument for plugin modules */ +}; + +/* + * Function handler for security-definer/proconfig/plugin-hooked functions. + * We extract the OID of the actual function and do a fmgr lookup again. + * Then we fetch the pg_proc row and copy the owner ID and proconfig fields. + * (All this info is cached for the duration of the current query.) + * To execute a call, we temporarily replace the flinfo with the cached + * and looked-up one, while keeping the outer fcinfo (which contains all + * the actual arguments, etc.) intact. This is not re-entrant, but then + * the fcinfo itself can't be used reentrantly anyway. + */ +extern Datum +fmgr_security_definer(PG_FUNCTION_ARGS) +{ + Datum result; + struct fmgr_security_definer_cache *volatile fcache; + FmgrInfo *save_flinfo; + Oid save_userid; + int save_sec_context; + volatile int save_nestlevel; + PgStat_FunctionCallUsage fcusage; + + if (!fcinfo->flinfo->fn_extra) + { + HeapTuple tuple; + Form_pg_proc procedureStruct; + Datum datum; + bool isnull; + MemoryContext oldcxt; + + fcache = MemoryContextAllocZero(fcinfo->flinfo->fn_mcxt, + sizeof(*fcache)); + + fmgr_info_cxt_security(fcinfo->flinfo->fn_oid, &fcache->flinfo, + fcinfo->flinfo->fn_mcxt, true); + fcache->flinfo.fn_expr = fcinfo->flinfo->fn_expr; + + tuple = SearchSysCache1(PROCOID, + ObjectIdGetDatum(fcinfo->flinfo->fn_oid)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for function %u", + fcinfo->flinfo->fn_oid); + procedureStruct = (Form_pg_proc) GETSTRUCT(tuple); + + if (procedureStruct->prosecdef) + fcache->userid = procedureStruct->proowner; + + datum = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_proconfig, + &isnull); + if (!isnull) + { + oldcxt = MemoryContextSwitchTo(fcinfo->flinfo->fn_mcxt); + fcache->proconfig = DatumGetArrayTypePCopy(datum); + MemoryContextSwitchTo(oldcxt); + } + + ReleaseSysCache(tuple); + + fcinfo->flinfo->fn_extra = fcache; + } + else + fcache = fcinfo->flinfo->fn_extra; + + /* GetUserIdAndSecContext is cheap enough that no harm in a wasted call */ + GetUserIdAndSecContext(&save_userid, &save_sec_context); + if (fcache->proconfig) /* Need a new GUC nesting level */ + save_nestlevel = NewGUCNestLevel(); + else + save_nestlevel = 0; /* keep compiler quiet */ + + if (OidIsValid(fcache->userid)) + SetUserIdAndSecContext(fcache->userid, + save_sec_context | SECURITY_LOCAL_USERID_CHANGE); + + if (fcache->proconfig) + { + ProcessGUCArray(fcache->proconfig, + (superuser() ? PGC_SUSET : PGC_USERSET), + PGC_S_SESSION, + GUC_ACTION_SAVE); + } + + /* function manager hook */ + if (fmgr_hook) + (*fmgr_hook) (FHET_START, &fcache->flinfo, &fcache->arg); + + /* + * We don't need to restore GUC or userid settings on error, because the + * ensuing xact or subxact abort will do that. The PG_TRY block is only + * needed to clean up the flinfo link. + */ + save_flinfo = fcinfo->flinfo; + + PG_TRY(); + { + fcinfo->flinfo = &fcache->flinfo; + + /* See notes in fmgr_info_cxt_security */ + pgstat_init_function_usage(fcinfo, &fcusage); + + result = FunctionCallInvoke(fcinfo); + + /* + * We could be calling either a regular or a set-returning function, + * so we have to test to see what finalize flag to use. + */ + pgstat_end_function_usage(&fcusage, + (fcinfo->resultinfo == NULL || + !IsA(fcinfo->resultinfo, ReturnSetInfo) || + ((ReturnSetInfo *) fcinfo->resultinfo)->isDone != ExprMultipleResult)); + } + PG_CATCH(); + { + fcinfo->flinfo = save_flinfo; + if (fmgr_hook) + (*fmgr_hook) (FHET_ABORT, &fcache->flinfo, &fcache->arg); + PG_RE_THROW(); + } + PG_END_TRY(); + + fcinfo->flinfo = save_flinfo; + + if (fcache->proconfig) + AtEOXact_GUC(true, save_nestlevel); + if (OidIsValid(fcache->userid)) + SetUserIdAndSecContext(save_userid, save_sec_context); + if (fmgr_hook) + (*fmgr_hook) (FHET_END, &fcache->flinfo, &fcache->arg); + + return result; +} + + +/*------------------------------------------------------------------------- + * Support routines for callers of fmgr-compatible functions + *------------------------------------------------------------------------- + */ + +/* + * These are for invocation of a specifically named function with a + * directly-computed parameter list. Note that neither arguments nor result + * are allowed to be NULL. Also, the function cannot be one that needs to + * look at FmgrInfo, since there won't be any. + */ +Datum +DirectFunctionCall1Coll(PGFunction func, Oid collation, Datum arg1) +{ + LOCAL_FCINFO(fcinfo, 1); + Datum result; + + InitFunctionCallInfoData(*fcinfo, NULL, 1, collation, NULL, NULL); + + fcinfo->args[0].value = arg1; + fcinfo->args[0].isnull = false; + + result = (*func) (fcinfo); + + /* Check for null result, since caller is clearly not expecting one */ + if (fcinfo->isnull) + elog(ERROR, "function %p returned NULL", (void *) func); + + return result; +} + +Datum +DirectFunctionCall2Coll(PGFunction func, Oid collation, Datum arg1, Datum arg2) +{ + LOCAL_FCINFO(fcinfo, 2); + Datum result; + + InitFunctionCallInfoData(*fcinfo, NULL, 2, collation, NULL, NULL); + + fcinfo->args[0].value = arg1; + fcinfo->args[0].isnull = false; + fcinfo->args[1].value = arg2; + fcinfo->args[1].isnull = false; + + result = (*func) (fcinfo); + + /* Check for null result, since caller is clearly not expecting one */ + if (fcinfo->isnull) + elog(ERROR, "function %p returned NULL", (void *) func); + + return result; +} + +Datum +DirectFunctionCall3Coll(PGFunction func, Oid collation, Datum arg1, Datum arg2, + Datum arg3) +{ + LOCAL_FCINFO(fcinfo, 3); + Datum result; + + InitFunctionCallInfoData(*fcinfo, NULL, 3, collation, NULL, NULL); + + fcinfo->args[0].value = arg1; + fcinfo->args[0].isnull = false; + fcinfo->args[1].value = arg2; + fcinfo->args[1].isnull = false; + fcinfo->args[2].value = arg3; + fcinfo->args[2].isnull = false; + + result = (*func) (fcinfo); + + /* Check for null result, since caller is clearly not expecting one */ + if (fcinfo->isnull) + elog(ERROR, "function %p returned NULL", (void *) func); + + return result; +} + +Datum +DirectFunctionCall4Coll(PGFunction func, Oid collation, Datum arg1, Datum arg2, + Datum arg3, Datum arg4) +{ + LOCAL_FCINFO(fcinfo, 4); + Datum result; + + InitFunctionCallInfoData(*fcinfo, NULL, 4, collation, NULL, NULL); + + fcinfo->args[0].value = arg1; + fcinfo->args[0].isnull = false; + fcinfo->args[1].value = arg2; + fcinfo->args[1].isnull = false; + fcinfo->args[2].value = arg3; + fcinfo->args[2].isnull = false; + fcinfo->args[3].value = arg4; + fcinfo->args[3].isnull = false; + + result = (*func) (fcinfo); + + /* Check for null result, since caller is clearly not expecting one */ + if (fcinfo->isnull) + elog(ERROR, "function %p returned NULL", (void *) func); + + return result; +} + +Datum +DirectFunctionCall5Coll(PGFunction func, Oid collation, Datum arg1, Datum arg2, + Datum arg3, Datum arg4, Datum arg5) +{ + LOCAL_FCINFO(fcinfo, 5); + Datum result; + + InitFunctionCallInfoData(*fcinfo, NULL, 5, collation, NULL, NULL); + + fcinfo->args[0].value = arg1; + fcinfo->args[0].isnull = false; + fcinfo->args[1].value = arg2; + fcinfo->args[1].isnull = false; + fcinfo->args[2].value = arg3; + fcinfo->args[2].isnull = false; + fcinfo->args[3].value = arg4; + fcinfo->args[3].isnull = false; + fcinfo->args[4].value = arg5; + fcinfo->args[4].isnull = false; + + result = (*func) (fcinfo); + + /* Check for null result, since caller is clearly not expecting one */ + if (fcinfo->isnull) + elog(ERROR, "function %p returned NULL", (void *) func); + + return result; +} + +Datum +DirectFunctionCall6Coll(PGFunction func, Oid collation, Datum arg1, Datum arg2, + Datum arg3, Datum arg4, Datum arg5, + Datum arg6) +{ + LOCAL_FCINFO(fcinfo, 6); + Datum result; + + InitFunctionCallInfoData(*fcinfo, NULL, 6, collation, NULL, NULL); + + fcinfo->args[0].value = arg1; + fcinfo->args[0].isnull = false; + fcinfo->args[1].value = arg2; + fcinfo->args[1].isnull = false; + fcinfo->args[2].value = arg3; + fcinfo->args[2].isnull = false; + fcinfo->args[3].value = arg4; + fcinfo->args[3].isnull = false; + fcinfo->args[4].value = arg5; + fcinfo->args[4].isnull = false; + fcinfo->args[5].value = arg6; + fcinfo->args[5].isnull = false; + + result = (*func) (fcinfo); + + /* Check for null result, since caller is clearly not expecting one */ + if (fcinfo->isnull) + elog(ERROR, "function %p returned NULL", (void *) func); + + return result; +} + +Datum +DirectFunctionCall7Coll(PGFunction func, Oid collation, Datum arg1, Datum arg2, + Datum arg3, Datum arg4, Datum arg5, + Datum arg6, Datum arg7) +{ + LOCAL_FCINFO(fcinfo, 7); + Datum result; + + InitFunctionCallInfoData(*fcinfo, NULL, 7, collation, NULL, NULL); + + fcinfo->args[0].value = arg1; + fcinfo->args[0].isnull = false; + fcinfo->args[1].value = arg2; + fcinfo->args[1].isnull = false; + fcinfo->args[2].value = arg3; + fcinfo->args[2].isnull = false; + fcinfo->args[3].value = arg4; + fcinfo->args[3].isnull = false; + fcinfo->args[4].value = arg5; + fcinfo->args[4].isnull = false; + fcinfo->args[5].value = arg6; + fcinfo->args[5].isnull = false; + fcinfo->args[6].value = arg7; + fcinfo->args[6].isnull = false; + + result = (*func) (fcinfo); + + /* Check for null result, since caller is clearly not expecting one */ + if (fcinfo->isnull) + elog(ERROR, "function %p returned NULL", (void *) func); + + return result; +} + +Datum +DirectFunctionCall8Coll(PGFunction func, Oid collation, Datum arg1, Datum arg2, + Datum arg3, Datum arg4, Datum arg5, + Datum arg6, Datum arg7, Datum arg8) +{ + LOCAL_FCINFO(fcinfo, 8); + Datum result; + + InitFunctionCallInfoData(*fcinfo, NULL, 8, collation, NULL, NULL); + + fcinfo->args[0].value = arg1; + fcinfo->args[0].isnull = false; + fcinfo->args[1].value = arg2; + fcinfo->args[1].isnull = false; + fcinfo->args[2].value = arg3; + fcinfo->args[2].isnull = false; + fcinfo->args[3].value = arg4; + fcinfo->args[3].isnull = false; + fcinfo->args[4].value = arg5; + fcinfo->args[4].isnull = false; + fcinfo->args[5].value = arg6; + fcinfo->args[5].isnull = false; + fcinfo->args[6].value = arg7; + fcinfo->args[6].isnull = false; + fcinfo->args[7].value = arg8; + fcinfo->args[7].isnull = false; + + result = (*func) (fcinfo); + + /* Check for null result, since caller is clearly not expecting one */ + if (fcinfo->isnull) + elog(ERROR, "function %p returned NULL", (void *) func); + + return result; +} + +Datum +DirectFunctionCall9Coll(PGFunction func, Oid collation, Datum arg1, Datum arg2, + Datum arg3, Datum arg4, Datum arg5, + Datum arg6, Datum arg7, Datum arg8, + Datum arg9) +{ + LOCAL_FCINFO(fcinfo, 9); + Datum result; + + InitFunctionCallInfoData(*fcinfo, NULL, 9, collation, NULL, NULL); + + fcinfo->args[0].value = arg1; + fcinfo->args[0].isnull = false; + fcinfo->args[1].value = arg2; + fcinfo->args[1].isnull = false; + fcinfo->args[2].value = arg3; + fcinfo->args[2].isnull = false; + fcinfo->args[3].value = arg4; + fcinfo->args[3].isnull = false; + fcinfo->args[4].value = arg5; + fcinfo->args[4].isnull = false; + fcinfo->args[5].value = arg6; + fcinfo->args[5].isnull = false; + fcinfo->args[6].value = arg7; + fcinfo->args[6].isnull = false; + fcinfo->args[7].value = arg8; + fcinfo->args[7].isnull = false; + fcinfo->args[8].value = arg9; + fcinfo->args[8].isnull = false; + + result = (*func) (fcinfo); + + /* Check for null result, since caller is clearly not expecting one */ + if (fcinfo->isnull) + elog(ERROR, "function %p returned NULL", (void *) func); + + return result; +} + +/* + * These functions work like the DirectFunctionCall functions except that + * they use the flinfo parameter to initialise the fcinfo for the call. + * It's recommended that the callee only use the fn_extra and fn_mcxt + * fields, as other fields will typically describe the calling function + * not the callee. Conversely, the calling function should not have + * used fn_extra, unless its use is known to be compatible with the callee's. + */ + +Datum +CallerFInfoFunctionCall1(PGFunction func, FmgrInfo *flinfo, Oid collation, Datum arg1) +{ + LOCAL_FCINFO(fcinfo, 1); + Datum result; + + InitFunctionCallInfoData(*fcinfo, flinfo, 1, collation, NULL, NULL); + + fcinfo->args[0].value = arg1; + fcinfo->args[0].isnull = false; + + result = (*func) (fcinfo); + + /* Check for null result, since caller is clearly not expecting one */ + if (fcinfo->isnull) + elog(ERROR, "function %p returned NULL", (void *) func); + + return result; +} + +Datum +CallerFInfoFunctionCall2(PGFunction func, FmgrInfo *flinfo, Oid collation, Datum arg1, Datum arg2) +{ + LOCAL_FCINFO(fcinfo, 2); + Datum result; + + InitFunctionCallInfoData(*fcinfo, flinfo, 2, collation, NULL, NULL); + + fcinfo->args[0].value = arg1; + fcinfo->args[0].isnull = false; + fcinfo->args[1].value = arg2; + fcinfo->args[1].isnull = false; + + result = (*func) (fcinfo); + + /* Check for null result, since caller is clearly not expecting one */ + if (fcinfo->isnull) + elog(ERROR, "function %p returned NULL", (void *) func); + + return result; +} + +/* + * These are for invocation of a previously-looked-up function with a + * directly-computed parameter list. Note that neither arguments nor result + * are allowed to be NULL. + */ +Datum +FunctionCall0Coll(FmgrInfo *flinfo, Oid collation) +{ + LOCAL_FCINFO(fcinfo, 0); + Datum result; + + InitFunctionCallInfoData(*fcinfo, flinfo, 0, collation, NULL, NULL); + + result = FunctionCallInvoke(fcinfo); + + /* Check for null result, since caller is clearly not expecting one */ + if (fcinfo->isnull) + elog(ERROR, "function %u returned NULL", flinfo->fn_oid); + + return result; +} + +Datum +FunctionCall1Coll(FmgrInfo *flinfo, Oid collation, Datum arg1) +{ + LOCAL_FCINFO(fcinfo, 1); + Datum result; + + InitFunctionCallInfoData(*fcinfo, flinfo, 1, collation, NULL, NULL); + + fcinfo->args[0].value = arg1; + fcinfo->args[0].isnull = false; + + result = FunctionCallInvoke(fcinfo); + + /* Check for null result, since caller is clearly not expecting one */ + if (fcinfo->isnull) + elog(ERROR, "function %u returned NULL", flinfo->fn_oid); + + return result; +} + +Datum +FunctionCall2Coll(FmgrInfo *flinfo, Oid collation, Datum arg1, Datum arg2) +{ + LOCAL_FCINFO(fcinfo, 2); + Datum result; + + InitFunctionCallInfoData(*fcinfo, flinfo, 2, collation, NULL, NULL); + + fcinfo->args[0].value = arg1; + fcinfo->args[0].isnull = false; + fcinfo->args[1].value = arg2; + fcinfo->args[1].isnull = false; + + result = FunctionCallInvoke(fcinfo); + + /* Check for null result, since caller is clearly not expecting one */ + if (fcinfo->isnull) + elog(ERROR, "function %u returned NULL", flinfo->fn_oid); + + return result; +} + +Datum +FunctionCall3Coll(FmgrInfo *flinfo, Oid collation, Datum arg1, Datum arg2, + Datum arg3) +{ + LOCAL_FCINFO(fcinfo, 3); + Datum result; + + InitFunctionCallInfoData(*fcinfo, flinfo, 3, collation, NULL, NULL); + + fcinfo->args[0].value = arg1; + fcinfo->args[0].isnull = false; + fcinfo->args[1].value = arg2; + fcinfo->args[1].isnull = false; + fcinfo->args[2].value = arg3; + fcinfo->args[2].isnull = false; + + result = FunctionCallInvoke(fcinfo); + + /* Check for null result, since caller is clearly not expecting one */ + if (fcinfo->isnull) + elog(ERROR, "function %u returned NULL", flinfo->fn_oid); + + return result; +} + +Datum +FunctionCall4Coll(FmgrInfo *flinfo, Oid collation, Datum arg1, Datum arg2, + Datum arg3, Datum arg4) +{ + LOCAL_FCINFO(fcinfo, 4); + Datum result; + + InitFunctionCallInfoData(*fcinfo, flinfo, 4, collation, NULL, NULL); + + fcinfo->args[0].value = arg1; + fcinfo->args[0].isnull = false; + fcinfo->args[1].value = arg2; + fcinfo->args[1].isnull = false; + fcinfo->args[2].value = arg3; + fcinfo->args[2].isnull = false; + fcinfo->args[3].value = arg4; + fcinfo->args[3].isnull = false; + + result = FunctionCallInvoke(fcinfo); + + /* Check for null result, since caller is clearly not expecting one */ + if (fcinfo->isnull) + elog(ERROR, "function %u returned NULL", flinfo->fn_oid); + + return result; +} + +Datum +FunctionCall5Coll(FmgrInfo *flinfo, Oid collation, Datum arg1, Datum arg2, + Datum arg3, Datum arg4, Datum arg5) +{ + LOCAL_FCINFO(fcinfo, 5); + Datum result; + + InitFunctionCallInfoData(*fcinfo, flinfo, 5, collation, NULL, NULL); + + fcinfo->args[0].value = arg1; + fcinfo->args[0].isnull = false; + fcinfo->args[1].value = arg2; + fcinfo->args[1].isnull = false; + fcinfo->args[2].value = arg3; + fcinfo->args[2].isnull = false; + fcinfo->args[3].value = arg4; + fcinfo->args[3].isnull = false; + fcinfo->args[4].value = arg5; + fcinfo->args[4].isnull = false; + + result = FunctionCallInvoke(fcinfo); + + /* Check for null result, since caller is clearly not expecting one */ + if (fcinfo->isnull) + elog(ERROR, "function %u returned NULL", flinfo->fn_oid); + + return result; +} + +Datum +FunctionCall6Coll(FmgrInfo *flinfo, Oid collation, Datum arg1, Datum arg2, + Datum arg3, Datum arg4, Datum arg5, + Datum arg6) +{ + LOCAL_FCINFO(fcinfo, 6); + Datum result; + + InitFunctionCallInfoData(*fcinfo, flinfo, 6, collation, NULL, NULL); + + fcinfo->args[0].value = arg1; + fcinfo->args[0].isnull = false; + fcinfo->args[1].value = arg2; + fcinfo->args[1].isnull = false; + fcinfo->args[2].value = arg3; + fcinfo->args[2].isnull = false; + fcinfo->args[3].value = arg4; + fcinfo->args[3].isnull = false; + fcinfo->args[4].value = arg5; + fcinfo->args[4].isnull = false; + fcinfo->args[5].value = arg6; + fcinfo->args[5].isnull = false; + + result = FunctionCallInvoke(fcinfo); + + /* Check for null result, since caller is clearly not expecting one */ + if (fcinfo->isnull) + elog(ERROR, "function %u returned NULL", flinfo->fn_oid); + + return result; +} + +Datum +FunctionCall7Coll(FmgrInfo *flinfo, Oid collation, Datum arg1, Datum arg2, + Datum arg3, Datum arg4, Datum arg5, + Datum arg6, Datum arg7) +{ + LOCAL_FCINFO(fcinfo, 7); + Datum result; + + InitFunctionCallInfoData(*fcinfo, flinfo, 7, collation, NULL, NULL); + + fcinfo->args[0].value = arg1; + fcinfo->args[0].isnull = false; + fcinfo->args[1].value = arg2; + fcinfo->args[1].isnull = false; + fcinfo->args[2].value = arg3; + fcinfo->args[2].isnull = false; + fcinfo->args[3].value = arg4; + fcinfo->args[3].isnull = false; + fcinfo->args[4].value = arg5; + fcinfo->args[4].isnull = false; + fcinfo->args[5].value = arg6; + fcinfo->args[5].isnull = false; + fcinfo->args[6].value = arg7; + fcinfo->args[6].isnull = false; + + result = FunctionCallInvoke(fcinfo); + + /* Check for null result, since caller is clearly not expecting one */ + if (fcinfo->isnull) + elog(ERROR, "function %u returned NULL", flinfo->fn_oid); + + return result; +} + +Datum +FunctionCall8Coll(FmgrInfo *flinfo, Oid collation, Datum arg1, Datum arg2, + Datum arg3, Datum arg4, Datum arg5, + Datum arg6, Datum arg7, Datum arg8) +{ + LOCAL_FCINFO(fcinfo, 8); + Datum result; + + InitFunctionCallInfoData(*fcinfo, flinfo, 8, collation, NULL, NULL); + + fcinfo->args[0].value = arg1; + fcinfo->args[0].isnull = false; + fcinfo->args[1].value = arg2; + fcinfo->args[1].isnull = false; + fcinfo->args[2].value = arg3; + fcinfo->args[2].isnull = false; + fcinfo->args[3].value = arg4; + fcinfo->args[3].isnull = false; + fcinfo->args[4].value = arg5; + fcinfo->args[4].isnull = false; + fcinfo->args[5].value = arg6; + fcinfo->args[5].isnull = false; + fcinfo->args[6].value = arg7; + fcinfo->args[6].isnull = false; + fcinfo->args[7].value = arg8; + fcinfo->args[7].isnull = false; + + result = FunctionCallInvoke(fcinfo); + + /* Check for null result, since caller is clearly not expecting one */ + if (fcinfo->isnull) + elog(ERROR, "function %u returned NULL", flinfo->fn_oid); + + return result; +} + +Datum +FunctionCall9Coll(FmgrInfo *flinfo, Oid collation, Datum arg1, Datum arg2, + Datum arg3, Datum arg4, Datum arg5, + Datum arg6, Datum arg7, Datum arg8, + Datum arg9) +{ + LOCAL_FCINFO(fcinfo, 9); + Datum result; + + InitFunctionCallInfoData(*fcinfo, flinfo, 9, collation, NULL, NULL); + + fcinfo->args[0].value = arg1; + fcinfo->args[0].isnull = false; + fcinfo->args[1].value = arg2; + fcinfo->args[1].isnull = false; + fcinfo->args[2].value = arg3; + fcinfo->args[2].isnull = false; + fcinfo->args[3].value = arg4; + fcinfo->args[3].isnull = false; + fcinfo->args[4].value = arg5; + fcinfo->args[4].isnull = false; + fcinfo->args[5].value = arg6; + fcinfo->args[5].isnull = false; + fcinfo->args[6].value = arg7; + fcinfo->args[6].isnull = false; + fcinfo->args[7].value = arg8; + fcinfo->args[7].isnull = false; + fcinfo->args[8].value = arg9; + fcinfo->args[8].isnull = false; + + result = FunctionCallInvoke(fcinfo); + + /* Check for null result, since caller is clearly not expecting one */ + if (fcinfo->isnull) + elog(ERROR, "function %u returned NULL", flinfo->fn_oid); + + return result; +} + + +/* + * These are for invocation of a function identified by OID with a + * directly-computed parameter list. Note that neither arguments nor result + * are allowed to be NULL. These are essentially fmgr_info() followed + * by FunctionCallN(). If the same function is to be invoked repeatedly, + * do the fmgr_info() once and then use FunctionCallN(). + */ +Datum +OidFunctionCall0Coll(Oid functionId, Oid collation) +{ + FmgrInfo flinfo; + + fmgr_info(functionId, &flinfo); + + return FunctionCall0Coll(&flinfo, collation); +} + +Datum +OidFunctionCall1Coll(Oid functionId, Oid collation, Datum arg1) +{ + FmgrInfo flinfo; + + fmgr_info(functionId, &flinfo); + + return FunctionCall1Coll(&flinfo, collation, arg1); +} + +Datum +OidFunctionCall2Coll(Oid functionId, Oid collation, Datum arg1, Datum arg2) +{ + FmgrInfo flinfo; + + fmgr_info(functionId, &flinfo); + + return FunctionCall2Coll(&flinfo, collation, arg1, arg2); +} + +Datum +OidFunctionCall3Coll(Oid functionId, Oid collation, Datum arg1, Datum arg2, + Datum arg3) +{ + FmgrInfo flinfo; + + fmgr_info(functionId, &flinfo); + + return FunctionCall3Coll(&flinfo, collation, arg1, arg2, arg3); +} + +Datum +OidFunctionCall4Coll(Oid functionId, Oid collation, Datum arg1, Datum arg2, + Datum arg3, Datum arg4) +{ + FmgrInfo flinfo; + + fmgr_info(functionId, &flinfo); + + return FunctionCall4Coll(&flinfo, collation, arg1, arg2, arg3, arg4); +} + +Datum +OidFunctionCall5Coll(Oid functionId, Oid collation, Datum arg1, Datum arg2, + Datum arg3, Datum arg4, Datum arg5) +{ + FmgrInfo flinfo; + + fmgr_info(functionId, &flinfo); + + return FunctionCall5Coll(&flinfo, collation, arg1, arg2, arg3, arg4, arg5); +} + +Datum +OidFunctionCall6Coll(Oid functionId, Oid collation, Datum arg1, Datum arg2, + Datum arg3, Datum arg4, Datum arg5, + Datum arg6) +{ + FmgrInfo flinfo; + + fmgr_info(functionId, &flinfo); + + return FunctionCall6Coll(&flinfo, collation, arg1, arg2, arg3, arg4, arg5, + arg6); +} + +Datum +OidFunctionCall7Coll(Oid functionId, Oid collation, Datum arg1, Datum arg2, + Datum arg3, Datum arg4, Datum arg5, + Datum arg6, Datum arg7) +{ + FmgrInfo flinfo; + + fmgr_info(functionId, &flinfo); + + return FunctionCall7Coll(&flinfo, collation, arg1, arg2, arg3, arg4, arg5, + arg6, arg7); +} + +Datum +OidFunctionCall8Coll(Oid functionId, Oid collation, Datum arg1, Datum arg2, + Datum arg3, Datum arg4, Datum arg5, + Datum arg6, Datum arg7, Datum arg8) +{ + FmgrInfo flinfo; + + fmgr_info(functionId, &flinfo); + + return FunctionCall8Coll(&flinfo, collation, arg1, arg2, arg3, arg4, arg5, + arg6, arg7, arg8); +} + +Datum +OidFunctionCall9Coll(Oid functionId, Oid collation, Datum arg1, Datum arg2, + Datum arg3, Datum arg4, Datum arg5, + Datum arg6, Datum arg7, Datum arg8, + Datum arg9) +{ + FmgrInfo flinfo; + + fmgr_info(functionId, &flinfo); + + return FunctionCall9Coll(&flinfo, collation, arg1, arg2, arg3, arg4, arg5, + arg6, arg7, arg8, arg9); +} + + +/* + * Special cases for convenient invocation of datatype I/O functions. + */ + +/* + * Call a previously-looked-up datatype input function. + * + * "str" may be NULL to indicate we are reading a NULL. In this case + * the caller should assume the result is NULL, but we'll call the input + * function anyway if it's not strict. So this is almost but not quite + * the same as FunctionCall3. + */ +Datum +InputFunctionCall(FmgrInfo *flinfo, char *str, Oid typioparam, int32 typmod) +{ + LOCAL_FCINFO(fcinfo, 3); + Datum result; + + if (str == NULL && flinfo->fn_strict) + return (Datum) 0; /* just return null result */ + + InitFunctionCallInfoData(*fcinfo, flinfo, 3, InvalidOid, NULL, NULL); + + fcinfo->args[0].value = CStringGetDatum(str); + fcinfo->args[0].isnull = false; + fcinfo->args[1].value = ObjectIdGetDatum(typioparam); + fcinfo->args[1].isnull = false; + fcinfo->args[2].value = Int32GetDatum(typmod); + fcinfo->args[2].isnull = false; + + result = FunctionCallInvoke(fcinfo); + + /* Should get null result if and only if str is NULL */ + if (str == NULL) + { + if (!fcinfo->isnull) + elog(ERROR, "input function %u returned non-NULL", + flinfo->fn_oid); + } + else + { + if (fcinfo->isnull) + elog(ERROR, "input function %u returned NULL", + flinfo->fn_oid); + } + + return result; +} + +/* + * Call a previously-looked-up datatype input function, with non-exception + * handling of "soft" errors. + * + * This is basically like InputFunctionCall, but the converted Datum is + * returned into *result while the function result is true for success or + * false for failure. Also, the caller may pass an ErrorSaveContext node. + * (We declare that as "fmNodePtr" to avoid including nodes.h in fmgr.h.) + * + * If escontext points to an ErrorSaveContext, any "soft" errors detected by + * the input function will be reported by filling the escontext struct and + * returning false. (The caller can choose to test SOFT_ERROR_OCCURRED(), + * but checking the function result instead is usually cheaper.) + * + * If escontext does not point to an ErrorSaveContext, errors are reported + * via ereport(ERROR), so that there is no functional difference from + * InputFunctionCall; the result will always be true if control returns. + */ +bool +InputFunctionCallSafe(FmgrInfo *flinfo, char *str, + Oid typioparam, int32 typmod, + fmNodePtr escontext, + Datum *result) +{ + LOCAL_FCINFO(fcinfo, 3); + + if (str == NULL && flinfo->fn_strict) + { + *result = (Datum) 0; /* just return null result */ + return true; + } + + InitFunctionCallInfoData(*fcinfo, flinfo, 3, InvalidOid, escontext, NULL); + + fcinfo->args[0].value = CStringGetDatum(str); + fcinfo->args[0].isnull = false; + fcinfo->args[1].value = ObjectIdGetDatum(typioparam); + fcinfo->args[1].isnull = false; + fcinfo->args[2].value = Int32GetDatum(typmod); + fcinfo->args[2].isnull = false; + + *result = FunctionCallInvoke(fcinfo); + + /* Result value is garbage, and could be null, if an error was reported */ + if (SOFT_ERROR_OCCURRED(escontext)) + return false; + + /* Otherwise, should get null result if and only if str is NULL */ + if (str == NULL) + { + if (!fcinfo->isnull) + elog(ERROR, "input function %u returned non-NULL", + flinfo->fn_oid); + } + else + { + if (fcinfo->isnull) + elog(ERROR, "input function %u returned NULL", + flinfo->fn_oid); + } + + return true; +} + +/* + * Call a directly-named datatype input function, with non-exception + * handling of "soft" errors. + * + * This is like InputFunctionCallSafe, except that it is given a direct + * pointer to the C function to call. We assume that that function is + * strict. Also, the function cannot be one that needs to + * look at FmgrInfo, since there won't be any. + */ +bool +DirectInputFunctionCallSafe(PGFunction func, char *str, + Oid typioparam, int32 typmod, + fmNodePtr escontext, + Datum *result) +{ + LOCAL_FCINFO(fcinfo, 3); + + if (str == NULL) + { + *result = (Datum) 0; /* just return null result */ + return true; + } + + InitFunctionCallInfoData(*fcinfo, NULL, 3, InvalidOid, escontext, NULL); + + fcinfo->args[0].value = CStringGetDatum(str); + fcinfo->args[0].isnull = false; + fcinfo->args[1].value = ObjectIdGetDatum(typioparam); + fcinfo->args[1].isnull = false; + fcinfo->args[2].value = Int32GetDatum(typmod); + fcinfo->args[2].isnull = false; + + *result = (*func) (fcinfo); + + /* Result value is garbage, and could be null, if an error was reported */ + if (SOFT_ERROR_OCCURRED(escontext)) + return false; + + /* Otherwise, shouldn't get null result */ + if (fcinfo->isnull) + elog(ERROR, "input function %p returned NULL", (void *) func); + + return true; +} + +/* + * Call a previously-looked-up datatype output function. + * + * Do not call this on NULL datums. + * + * This is currently little more than window dressing for FunctionCall1. + */ +char * +OutputFunctionCall(FmgrInfo *flinfo, Datum val) +{ + return DatumGetCString(FunctionCall1(flinfo, val)); +} + +/* + * Call a previously-looked-up datatype binary-input function. + * + * "buf" may be NULL to indicate we are reading a NULL. In this case + * the caller should assume the result is NULL, but we'll call the receive + * function anyway if it's not strict. So this is almost but not quite + * the same as FunctionCall3. + */ +Datum +ReceiveFunctionCall(FmgrInfo *flinfo, StringInfo buf, + Oid typioparam, int32 typmod) +{ + LOCAL_FCINFO(fcinfo, 3); + Datum result; + + if (buf == NULL && flinfo->fn_strict) + return (Datum) 0; /* just return null result */ + + InitFunctionCallInfoData(*fcinfo, flinfo, 3, InvalidOid, NULL, NULL); + + fcinfo->args[0].value = PointerGetDatum(buf); + fcinfo->args[0].isnull = false; + fcinfo->args[1].value = ObjectIdGetDatum(typioparam); + fcinfo->args[1].isnull = false; + fcinfo->args[2].value = Int32GetDatum(typmod); + fcinfo->args[2].isnull = false; + + result = FunctionCallInvoke(fcinfo); + + /* Should get null result if and only if buf is NULL */ + if (buf == NULL) + { + if (!fcinfo->isnull) + elog(ERROR, "receive function %u returned non-NULL", + flinfo->fn_oid); + } + else + { + if (fcinfo->isnull) + elog(ERROR, "receive function %u returned NULL", + flinfo->fn_oid); + } + + return result; +} + +/* + * Call a previously-looked-up datatype binary-output function. + * + * Do not call this on NULL datums. + * + * This is little more than window dressing for FunctionCall1, but it does + * guarantee a non-toasted result, which strictly speaking the underlying + * function doesn't. + */ +bytea * +SendFunctionCall(FmgrInfo *flinfo, Datum val) +{ + return DatumGetByteaP(FunctionCall1(flinfo, val)); +} + +/* + * As above, for I/O functions identified by OID. These are only to be used + * in seldom-executed code paths. They are not only slow but leak memory. + */ +Datum +OidInputFunctionCall(Oid functionId, char *str, Oid typioparam, int32 typmod) +{ + FmgrInfo flinfo; + + fmgr_info(functionId, &flinfo); + return InputFunctionCall(&flinfo, str, typioparam, typmod); +} + +char * +OidOutputFunctionCall(Oid functionId, Datum val) +{ + FmgrInfo flinfo; + + fmgr_info(functionId, &flinfo); + return OutputFunctionCall(&flinfo, val); +} + +Datum +OidReceiveFunctionCall(Oid functionId, StringInfo buf, + Oid typioparam, int32 typmod) +{ + FmgrInfo flinfo; + + fmgr_info(functionId, &flinfo); + return ReceiveFunctionCall(&flinfo, buf, typioparam, typmod); +} + +bytea * +OidSendFunctionCall(Oid functionId, Datum val) +{ + FmgrInfo flinfo; + + fmgr_info(functionId, &flinfo); + return SendFunctionCall(&flinfo, val); +} + + +/*------------------------------------------------------------------------- + * Support routines for standard maybe-pass-by-reference datatypes + * + * int8 and float8 can be passed by value if Datum is wide enough. + * (For backwards-compatibility reasons, we allow pass-by-ref to be chosen + * at compile time even if pass-by-val is possible.) + * + * Note: there is only one switch controlling the pass-by-value option for + * both int8 and float8; this is to avoid making things unduly complicated + * for the timestamp types, which might have either representation. + *------------------------------------------------------------------------- + */ + +#ifndef USE_FLOAT8_BYVAL /* controls int8 too */ + +Datum +Int64GetDatum(int64 X) +{ + int64 *retval = (int64 *) palloc(sizeof(int64)); + + *retval = X; + return PointerGetDatum(retval); +} + +Datum +Float8GetDatum(float8 X) +{ + float8 *retval = (float8 *) palloc(sizeof(float8)); + + *retval = X; + return PointerGetDatum(retval); +} +#endif /* USE_FLOAT8_BYVAL */ + + +/*------------------------------------------------------------------------- + * Support routines for toastable datatypes + *------------------------------------------------------------------------- + */ + +struct varlena * +pg_detoast_datum(struct varlena *datum) +{ + if (VARATT_IS_EXTENDED(datum)) + return detoast_attr(datum); + else + return datum; +} + +struct varlena * +pg_detoast_datum_copy(struct varlena *datum) +{ + if (VARATT_IS_EXTENDED(datum)) + return detoast_attr(datum); + else + { + /* Make a modifiable copy of the varlena object */ + Size len = VARSIZE(datum); + struct varlena *result = (struct varlena *) palloc(len); + + memcpy(result, datum, len); + return result; + } +} + +struct varlena * +pg_detoast_datum_slice(struct varlena *datum, int32 first, int32 count) +{ + /* Only get the specified portion from the toast rel */ + return detoast_attr_slice(datum, first, count); +} + +struct varlena * +pg_detoast_datum_packed(struct varlena *datum) +{ + if (VARATT_IS_COMPRESSED(datum) || VARATT_IS_EXTERNAL(datum)) + return detoast_attr(datum); + else + return datum; +} + +/*------------------------------------------------------------------------- + * Support routines for extracting info from fn_expr parse tree + * + * These are needed by polymorphic functions, which accept multiple possible + * input types and need help from the parser to know what they've got. + * Also, some functions might be interested in whether a parameter is constant. + * Functions taking VARIADIC ANY also need to know about the VARIADIC keyword. + *------------------------------------------------------------------------- + */ + +/* + * Get the actual type OID of the function return type + * + * Returns InvalidOid if information is not available + */ +Oid +get_fn_expr_rettype(FmgrInfo *flinfo) +{ + Node *expr; + + /* + * can't return anything useful if we have no FmgrInfo or if its fn_expr + * node has not been initialized + */ + if (!flinfo || !flinfo->fn_expr) + return InvalidOid; + + expr = flinfo->fn_expr; + + return exprType(expr); +} + +/* + * Get the actual type OID of a specific function argument (counting from 0) + * + * Returns InvalidOid if information is not available + */ +Oid +get_fn_expr_argtype(FmgrInfo *flinfo, int argnum) +{ + /* + * can't return anything useful if we have no FmgrInfo or if its fn_expr + * node has not been initialized + */ + if (!flinfo || !flinfo->fn_expr) + return InvalidOid; + + return get_call_expr_argtype(flinfo->fn_expr, argnum); +} + +/* + * Get the actual type OID of a specific function argument (counting from 0), + * but working from the calling expression tree instead of FmgrInfo + * + * Returns InvalidOid if information is not available + */ +Oid +get_call_expr_argtype(Node *expr, int argnum) +{ + List *args; + Oid argtype; + + if (expr == NULL) + return InvalidOid; + + if (IsA(expr, FuncExpr)) + args = ((FuncExpr *) expr)->args; + else if (IsA(expr, OpExpr)) + args = ((OpExpr *) expr)->args; + else if (IsA(expr, DistinctExpr)) + args = ((DistinctExpr *) expr)->args; + else if (IsA(expr, ScalarArrayOpExpr)) + args = ((ScalarArrayOpExpr *) expr)->args; + else if (IsA(expr, NullIfExpr)) + args = ((NullIfExpr *) expr)->args; + else if (IsA(expr, WindowFunc)) + args = ((WindowFunc *) expr)->args; + else + return InvalidOid; + + if (argnum < 0 || argnum >= list_length(args)) + return InvalidOid; + + argtype = exprType((Node *) list_nth(args, argnum)); + + /* + * special hack for ScalarArrayOpExpr: what the underlying function will + * actually get passed is the element type of the array. + */ + if (IsA(expr, ScalarArrayOpExpr) && + argnum == 1) + argtype = get_base_element_type(argtype); + + return argtype; +} + +/* + * Find out whether a specific function argument is constant for the + * duration of a query + * + * Returns false if information is not available + */ +bool +get_fn_expr_arg_stable(FmgrInfo *flinfo, int argnum) +{ + /* + * can't return anything useful if we have no FmgrInfo or if its fn_expr + * node has not been initialized + */ + if (!flinfo || !flinfo->fn_expr) + return false; + + return get_call_expr_arg_stable(flinfo->fn_expr, argnum); +} + +/* + * Find out whether a specific function argument is constant for the + * duration of a query, but working from the calling expression tree + * + * Returns false if information is not available + */ +bool +get_call_expr_arg_stable(Node *expr, int argnum) +{ + List *args; + Node *arg; + + if (expr == NULL) + return false; + + if (IsA(expr, FuncExpr)) + args = ((FuncExpr *) expr)->args; + else if (IsA(expr, OpExpr)) + args = ((OpExpr *) expr)->args; + else if (IsA(expr, DistinctExpr)) + args = ((DistinctExpr *) expr)->args; + else if (IsA(expr, ScalarArrayOpExpr)) + args = ((ScalarArrayOpExpr *) expr)->args; + else if (IsA(expr, NullIfExpr)) + args = ((NullIfExpr *) expr)->args; + else if (IsA(expr, WindowFunc)) + args = ((WindowFunc *) expr)->args; + else + return false; + + if (argnum < 0 || argnum >= list_length(args)) + return false; + + arg = (Node *) list_nth(args, argnum); + + /* + * Either a true Const or an external Param will have a value that doesn't + * change during the execution of the query. In future we might want to + * consider other cases too, e.g. now(). + */ + if (IsA(arg, Const)) + return true; + if (IsA(arg, Param) && + ((Param *) arg)->paramkind == PARAM_EXTERN) + return true; + + return false; +} + +/* + * Get the VARIADIC flag from the function invocation + * + * Returns false (the default assumption) if information is not available + * + * Note this is generally only of interest to VARIADIC ANY functions + */ +bool +get_fn_expr_variadic(FmgrInfo *flinfo) +{ + Node *expr; + + /* + * can't return anything useful if we have no FmgrInfo or if its fn_expr + * node has not been initialized + */ + if (!flinfo || !flinfo->fn_expr) + return false; + + expr = flinfo->fn_expr; + + if (IsA(expr, FuncExpr)) + return ((FuncExpr *) expr)->funcvariadic; + else + return false; +} + +/* + * Set options to FmgrInfo of opclass support function. + * + * Opclass support functions are called outside of expressions. Thanks to that + * we can use fn_expr to store opclass options as bytea constant. + */ +void +set_fn_opclass_options(FmgrInfo *flinfo, bytea *options) +{ + flinfo->fn_expr = (Node *) makeConst(BYTEAOID, -1, InvalidOid, -1, + PointerGetDatum(options), + options == NULL, false); +} + +/* + * Check if options are defined for opclass support function. + */ +bool +has_fn_opclass_options(FmgrInfo *flinfo) +{ + if (flinfo && flinfo->fn_expr && IsA(flinfo->fn_expr, Const)) + { + Const *expr = (Const *) flinfo->fn_expr; + + if (expr->consttype == BYTEAOID) + return !expr->constisnull; + } + return false; +} + +/* + * Get options for opclass support function. + */ +bytea * +get_fn_opclass_options(FmgrInfo *flinfo) +{ + if (flinfo && flinfo->fn_expr && IsA(flinfo->fn_expr, Const)) + { + Const *expr = (Const *) flinfo->fn_expr; + + if (expr->consttype == BYTEAOID) + return expr->constisnull ? NULL : DatumGetByteaP(expr->constvalue); + } + + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("operator class options info is absent in function call context"))); + + return NULL; +} + +/*------------------------------------------------------------------------- + * Support routines for procedural language implementations + *------------------------------------------------------------------------- + */ + +/* + * Verify that a validator is actually associated with the language of a + * particular function and that the user has access to both the language and + * the function. All validators should call this before doing anything + * substantial. Doing so ensures a user cannot achieve anything with explicit + * calls to validators that he could not achieve with CREATE FUNCTION or by + * simply calling an existing function. + * + * When this function returns false, callers should skip all validation work + * and call PG_RETURN_VOID(). This never happens at present; it is reserved + * for future expansion. + * + * In particular, checking that the validator corresponds to the function's + * language allows untrusted language validators to assume they process only + * superuser-chosen source code. (Untrusted language call handlers, by + * definition, do assume that.) A user lacking the USAGE language privilege + * would be unable to reach the validator through CREATE FUNCTION, so we check + * that to block explicit calls as well. Checking the EXECUTE privilege on + * the function is often superfluous, because most users can clone the + * function to get an executable copy. It is meaningful against users with no + * database TEMP right and no permanent schema CREATE right, thereby unable to + * create any function. Also, if the function tracks persistent state by + * function OID or name, validating the original function might permit more + * mischief than creating and validating a clone thereof. + */ +bool +CheckFunctionValidatorAccess(Oid validatorOid, Oid functionOid) +{ + HeapTuple procTup; + HeapTuple langTup; + Form_pg_proc procStruct; + Form_pg_language langStruct; + AclResult aclresult; + + /* + * Get the function's pg_proc entry. Throw a user-facing error for bad + * OID, because validators can be called with user-specified OIDs. + */ + procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(functionOid)); + if (!HeapTupleIsValid(procTup)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("function with OID %u does not exist", functionOid))); + procStruct = (Form_pg_proc) GETSTRUCT(procTup); + + /* + * Fetch pg_language entry to know if this is the correct validation + * function for that pg_proc entry. + */ + langTup = SearchSysCache1(LANGOID, ObjectIdGetDatum(procStruct->prolang)); + if (!HeapTupleIsValid(langTup)) + elog(ERROR, "cache lookup failed for language %u", procStruct->prolang); + langStruct = (Form_pg_language) GETSTRUCT(langTup); + + if (langStruct->lanvalidator != validatorOid) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("language validation function %u called for language %u instead of %u", + validatorOid, procStruct->prolang, + langStruct->lanvalidator))); + + /* first validate that we have permissions to use the language */ + aclresult = object_aclcheck(LanguageRelationId, procStruct->prolang, GetUserId(), + ACL_USAGE); + if (aclresult != ACLCHECK_OK) + aclcheck_error(aclresult, OBJECT_LANGUAGE, + NameStr(langStruct->lanname)); + + /* + * Check whether we are allowed to execute the function itself. If we can + * execute it, there should be no possible side-effect of + * compiling/validation that execution can't have. + */ + aclresult = object_aclcheck(ProcedureRelationId, functionOid, GetUserId(), ACL_EXECUTE); + if (aclresult != ACLCHECK_OK) + aclcheck_error(aclresult, OBJECT_FUNCTION, NameStr(procStruct->proname)); + + ReleaseSysCache(procTup); + ReleaseSysCache(langTup); + + return true; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/fmgr/funcapi.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/fmgr/funcapi.c new file mode 100644 index 00000000000..4e925d51f73 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/fmgr/funcapi.c @@ -0,0 +1,2101 @@ +/*------------------------------------------------------------------------- + * + * funcapi.c + * Utility and convenience functions for fmgr functions that return + * sets and/or composite types, or deal with VARIADIC inputs. + * + * Copyright (c) 2002-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/utils/fmgr/funcapi.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/htup_details.h" +#include "access/relation.h" +#include "catalog/namespace.h" +#include "catalog/pg_proc.h" +#include "catalog/pg_type.h" +#include "funcapi.h" +#include "miscadmin.h" +#include "nodes/nodeFuncs.h" +#include "utils/array.h" +#include "utils/builtins.h" +#include "utils/lsyscache.h" +#include "utils/memutils.h" +#include "utils/regproc.h" +#include "utils/rel.h" +#include "utils/syscache.h" +#include "utils/tuplestore.h" +#include "utils/typcache.h" + + +typedef struct polymorphic_actuals +{ + Oid anyelement_type; /* anyelement mapping, if known */ + Oid anyarray_type; /* anyarray mapping, if known */ + Oid anyrange_type; /* anyrange mapping, if known */ + Oid anymultirange_type; /* anymultirange mapping, if known */ +} polymorphic_actuals; + +static void shutdown_MultiFuncCall(Datum arg); +static TypeFuncClass internal_get_result_type(Oid funcid, + Node *call_expr, + ReturnSetInfo *rsinfo, + Oid *resultTypeId, + TupleDesc *resultTupleDesc); +static void resolve_anyelement_from_others(polymorphic_actuals *actuals); +static void resolve_anyarray_from_others(polymorphic_actuals *actuals); +static void resolve_anyrange_from_others(polymorphic_actuals *actuals); +static void resolve_anymultirange_from_others(polymorphic_actuals *actuals); +static bool resolve_polymorphic_tupdesc(TupleDesc tupdesc, + oidvector *declared_args, + Node *call_expr); +static TypeFuncClass get_type_func_class(Oid typid, Oid *base_typeid); + + +/* + * InitMaterializedSRF + * + * Helper function to build the state of a set-returning function used + * in the context of a single call with materialize mode. This code + * includes sanity checks on ReturnSetInfo, creates the Tuplestore and + * the TupleDesc used with the function and stores them into the + * function's ReturnSetInfo. + * + * "flags" can be set to MAT_SRF_USE_EXPECTED_DESC, to use the tuple + * descriptor coming from expectedDesc, which is the tuple descriptor + * expected by the caller. MAT_SRF_BLESS can be set to complete the + * information associated to the tuple descriptor, which is necessary + * in some cases where the tuple descriptor comes from a transient + * RECORD datatype. + */ +void +InitMaterializedSRF(FunctionCallInfo fcinfo, bits32 flags) +{ + bool random_access; + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + Tuplestorestate *tupstore; + MemoryContext old_context, + per_query_ctx; + TupleDesc stored_tupdesc; + + /* check to see if caller supports returning a tuplestore */ + if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("set-valued function called in context that cannot accept a set"))); + if (!(rsinfo->allowedModes & SFRM_Materialize) || + ((flags & MAT_SRF_USE_EXPECTED_DESC) != 0 && rsinfo->expectedDesc == NULL)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("materialize mode required, but it is not allowed in this context"))); + + /* + * Store the tuplestore and the tuple descriptor in ReturnSetInfo. This + * must be done in the per-query memory context. + */ + per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; + old_context = MemoryContextSwitchTo(per_query_ctx); + + /* build a tuple descriptor for our result type */ + if ((flags & MAT_SRF_USE_EXPECTED_DESC) != 0) + stored_tupdesc = CreateTupleDescCopy(rsinfo->expectedDesc); + else + { + if (get_call_result_type(fcinfo, NULL, &stored_tupdesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + } + + /* If requested, bless the tuple descriptor */ + if ((flags & MAT_SRF_BLESS) != 0) + BlessTupleDesc(stored_tupdesc); + + random_access = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0; + + tupstore = tuplestore_begin_heap(random_access, false, work_mem); + rsinfo->returnMode = SFRM_Materialize; + rsinfo->setResult = tupstore; + rsinfo->setDesc = stored_tupdesc; + MemoryContextSwitchTo(old_context); +} + + +/* + * init_MultiFuncCall + * Create an empty FuncCallContext data structure + * and do some other basic Multi-function call setup + * and error checking + */ +FuncCallContext * +init_MultiFuncCall(PG_FUNCTION_ARGS) +{ + FuncCallContext *retval; + + /* + * Bail if we're called in the wrong context + */ + if (fcinfo->resultinfo == NULL || !IsA(fcinfo->resultinfo, ReturnSetInfo)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("set-valued function called in context that cannot accept a set"))); + + if (fcinfo->flinfo->fn_extra == NULL) + { + /* + * First call + */ + ReturnSetInfo *rsi = (ReturnSetInfo *) fcinfo->resultinfo; + MemoryContext multi_call_ctx; + + /* + * Create a suitably long-lived context to hold cross-call data + */ + multi_call_ctx = AllocSetContextCreate(fcinfo->flinfo->fn_mcxt, + "SRF multi-call context", + ALLOCSET_SMALL_SIZES); + + /* + * Allocate suitably long-lived space and zero it + */ + retval = (FuncCallContext *) + MemoryContextAllocZero(multi_call_ctx, + sizeof(FuncCallContext)); + + /* + * initialize the elements + */ + retval->call_cntr = 0; + retval->max_calls = 0; + retval->user_fctx = NULL; + retval->attinmeta = NULL; + retval->tuple_desc = NULL; + retval->multi_call_memory_ctx = multi_call_ctx; + + /* + * save the pointer for cross-call use + */ + fcinfo->flinfo->fn_extra = retval; + + /* + * Ensure we will get shut down cleanly if the exprcontext is not run + * to completion. + */ + RegisterExprContextCallback(rsi->econtext, + shutdown_MultiFuncCall, + PointerGetDatum(fcinfo->flinfo)); + } + else + { + /* second and subsequent calls */ + elog(ERROR, "init_MultiFuncCall cannot be called more than once"); + + /* never reached, but keep compiler happy */ + retval = NULL; + } + + return retval; +} + +/* + * per_MultiFuncCall + * + * Do Multi-function per-call setup + */ +FuncCallContext * +per_MultiFuncCall(PG_FUNCTION_ARGS) +{ + FuncCallContext *retval = (FuncCallContext *) fcinfo->flinfo->fn_extra; + + return retval; +} + +/* + * end_MultiFuncCall + * Clean up after init_MultiFuncCall + */ +void +end_MultiFuncCall(PG_FUNCTION_ARGS, FuncCallContext *funcctx) +{ + ReturnSetInfo *rsi = (ReturnSetInfo *) fcinfo->resultinfo; + + /* Deregister the shutdown callback */ + UnregisterExprContextCallback(rsi->econtext, + shutdown_MultiFuncCall, + PointerGetDatum(fcinfo->flinfo)); + + /* But use it to do the real work */ + shutdown_MultiFuncCall(PointerGetDatum(fcinfo->flinfo)); +} + +/* + * shutdown_MultiFuncCall + * Shutdown function to clean up after init_MultiFuncCall + */ +static void +shutdown_MultiFuncCall(Datum arg) +{ + FmgrInfo *flinfo = (FmgrInfo *) DatumGetPointer(arg); + FuncCallContext *funcctx = (FuncCallContext *) flinfo->fn_extra; + + /* unbind from flinfo */ + flinfo->fn_extra = NULL; + + /* + * Delete context that holds all multi-call data, including the + * FuncCallContext itself + */ + MemoryContextDelete(funcctx->multi_call_memory_ctx); +} + + +/* + * get_call_result_type + * Given a function's call info record, determine the kind of datatype + * it is supposed to return. If resultTypeId isn't NULL, *resultTypeId + * receives the actual datatype OID (this is mainly useful for scalar + * result types). If resultTupleDesc isn't NULL, *resultTupleDesc + * receives a pointer to a TupleDesc when the result is of a composite + * type, or NULL when it's a scalar result. + * + * One hard case that this handles is resolution of actual rowtypes for + * functions returning RECORD (from either the function's OUT parameter + * list, or a ReturnSetInfo context node). TYPEFUNC_RECORD is returned + * only when we couldn't resolve the actual rowtype for lack of information. + * + * The other hard case that this handles is resolution of polymorphism. + * We will never return polymorphic pseudotypes (ANYELEMENT etc), either + * as a scalar result type or as a component of a rowtype. + * + * This function is relatively expensive --- in a function returning set, + * try to call it only the first time through. + */ +TypeFuncClass +get_call_result_type(FunctionCallInfo fcinfo, + Oid *resultTypeId, + TupleDesc *resultTupleDesc) +{ + return internal_get_result_type(fcinfo->flinfo->fn_oid, + fcinfo->flinfo->fn_expr, + (ReturnSetInfo *) fcinfo->resultinfo, + resultTypeId, + resultTupleDesc); +} + +/* + * get_expr_result_type + * As above, but work from a calling expression node tree + * + * Beware of using this on the funcexpr of a RTE that has a coldeflist. + * The correct conclusion in such cases is always that the function returns + * RECORD with the columns defined by the coldeflist fields (funccolnames etc). + * If it does not, it's the executor's responsibility to catch the discrepancy + * at runtime; but code processing the query in advance of that point might + * come to inconsistent conclusions if it checks the actual expression. + */ +TypeFuncClass +get_expr_result_type(Node *expr, + Oid *resultTypeId, + TupleDesc *resultTupleDesc) +{ + TypeFuncClass result; + + if (expr && IsA(expr, FuncExpr)) + result = internal_get_result_type(((FuncExpr *) expr)->funcid, + expr, + NULL, + resultTypeId, + resultTupleDesc); + else if (expr && IsA(expr, OpExpr)) + result = internal_get_result_type(get_opcode(((OpExpr *) expr)->opno), + expr, + NULL, + resultTypeId, + resultTupleDesc); + else if (expr && IsA(expr, RowExpr) && + ((RowExpr *) expr)->row_typeid == RECORDOID) + { + /* We can resolve the record type by generating the tupdesc directly */ + RowExpr *rexpr = (RowExpr *) expr; + TupleDesc tupdesc; + AttrNumber i = 1; + ListCell *lcc, + *lcn; + + tupdesc = CreateTemplateTupleDesc(list_length(rexpr->args)); + Assert(list_length(rexpr->args) == list_length(rexpr->colnames)); + forboth(lcc, rexpr->args, lcn, rexpr->colnames) + { + Node *col = (Node *) lfirst(lcc); + char *colname = strVal(lfirst(lcn)); + + TupleDescInitEntry(tupdesc, i, + colname, + exprType(col), + exprTypmod(col), + 0); + TupleDescInitEntryCollation(tupdesc, i, + exprCollation(col)); + i++; + } + if (resultTypeId) + *resultTypeId = rexpr->row_typeid; + if (resultTupleDesc) + *resultTupleDesc = BlessTupleDesc(tupdesc); + return TYPEFUNC_COMPOSITE; + } + else if (expr && IsA(expr, Const) && + ((Const *) expr)->consttype == RECORDOID && + !((Const *) expr)->constisnull) + { + /* + * When EXPLAIN'ing some queries with SEARCH/CYCLE clauses, we may + * need to resolve field names of a RECORD-type Const. The datum + * should contain a typmod that will tell us that. + */ + HeapTupleHeader rec; + Oid tupType; + int32 tupTypmod; + + rec = DatumGetHeapTupleHeader(((Const *) expr)->constvalue); + tupType = HeapTupleHeaderGetTypeId(rec); + tupTypmod = HeapTupleHeaderGetTypMod(rec); + if (resultTypeId) + *resultTypeId = tupType; + if (tupType != RECORDOID || tupTypmod >= 0) + { + /* Should be able to look it up */ + if (resultTupleDesc) + *resultTupleDesc = lookup_rowtype_tupdesc_copy(tupType, + tupTypmod); + return TYPEFUNC_COMPOSITE; + } + else + { + /* This shouldn't really happen ... */ + if (resultTupleDesc) + *resultTupleDesc = NULL; + return TYPEFUNC_RECORD; + } + } + else + { + /* handle as a generic expression; no chance to resolve RECORD */ + Oid typid = exprType(expr); + Oid base_typid; + + if (resultTypeId) + *resultTypeId = typid; + if (resultTupleDesc) + *resultTupleDesc = NULL; + result = get_type_func_class(typid, &base_typid); + if ((result == TYPEFUNC_COMPOSITE || + result == TYPEFUNC_COMPOSITE_DOMAIN) && + resultTupleDesc) + *resultTupleDesc = lookup_rowtype_tupdesc_copy(base_typid, -1); + } + + return result; +} + +/* + * get_func_result_type + * As above, but work from a function's OID only + * + * This will not be able to resolve pure-RECORD results nor polymorphism. + */ +TypeFuncClass +get_func_result_type(Oid functionId, + Oid *resultTypeId, + TupleDesc *resultTupleDesc) +{ + return internal_get_result_type(functionId, + NULL, + NULL, + resultTypeId, + resultTupleDesc); +} + +/* + * internal_get_result_type -- workhorse code implementing all the above + * + * funcid must always be supplied. call_expr and rsinfo can be NULL if not + * available. We will return TYPEFUNC_RECORD, and store NULL into + * *resultTupleDesc, if we cannot deduce the complete result rowtype from + * the available information. + */ +static TypeFuncClass +internal_get_result_type(Oid funcid, + Node *call_expr, + ReturnSetInfo *rsinfo, + Oid *resultTypeId, + TupleDesc *resultTupleDesc) +{ + TypeFuncClass result; + HeapTuple tp; + Form_pg_proc procform; + Oid rettype; + Oid base_rettype; + TupleDesc tupdesc; + + /* First fetch the function's pg_proc row to inspect its rettype */ + tp = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid)); + if (!HeapTupleIsValid(tp)) + elog(ERROR, "cache lookup failed for function %u", funcid); + procform = (Form_pg_proc) GETSTRUCT(tp); + + rettype = procform->prorettype; + + /* Check for OUT parameters defining a RECORD result */ + tupdesc = build_function_result_tupdesc_t(tp); + if (tupdesc) + { + /* + * It has OUT parameters, so it's basically like a regular composite + * type, except we have to be able to resolve any polymorphic OUT + * parameters. + */ + if (resultTypeId) + *resultTypeId = rettype; + + if (resolve_polymorphic_tupdesc(tupdesc, + &procform->proargtypes, + call_expr)) + { + if (tupdesc->tdtypeid == RECORDOID && + tupdesc->tdtypmod < 0) + assign_record_type_typmod(tupdesc); + if (resultTupleDesc) + *resultTupleDesc = tupdesc; + result = TYPEFUNC_COMPOSITE; + } + else + { + if (resultTupleDesc) + *resultTupleDesc = NULL; + result = TYPEFUNC_RECORD; + } + + ReleaseSysCache(tp); + + return result; + } + + /* + * If scalar polymorphic result, try to resolve it. + */ + if (IsPolymorphicType(rettype)) + { + Oid newrettype = exprType(call_expr); + + if (newrettype == InvalidOid) /* this probably should not happen */ + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("could not determine actual result type for function \"%s\" declared to return type %s", + NameStr(procform->proname), + format_type_be(rettype)))); + rettype = newrettype; + } + + if (resultTypeId) + *resultTypeId = rettype; + if (resultTupleDesc) + *resultTupleDesc = NULL; /* default result */ + + /* Classify the result type */ + result = get_type_func_class(rettype, &base_rettype); + switch (result) + { + case TYPEFUNC_COMPOSITE: + case TYPEFUNC_COMPOSITE_DOMAIN: + if (resultTupleDesc) + *resultTupleDesc = lookup_rowtype_tupdesc_copy(base_rettype, -1); + /* Named composite types can't have any polymorphic columns */ + break; + case TYPEFUNC_SCALAR: + break; + case TYPEFUNC_RECORD: + /* We must get the tupledesc from call context */ + if (rsinfo && IsA(rsinfo, ReturnSetInfo) && + rsinfo->expectedDesc != NULL) + { + result = TYPEFUNC_COMPOSITE; + if (resultTupleDesc) + *resultTupleDesc = rsinfo->expectedDesc; + /* Assume no polymorphic columns here, either */ + } + break; + default: + break; + } + + ReleaseSysCache(tp); + + return result; +} + +/* + * get_expr_result_tupdesc + * Get a tupdesc describing the result of a composite-valued expression + * + * If expression is not composite or rowtype can't be determined, returns NULL + * if noError is true, else throws error. + * + * This is a simpler version of get_expr_result_type() for use when the caller + * is only interested in determinate rowtype results. As with that function, + * beware of using this on the funcexpr of a RTE that has a coldeflist. + */ +TupleDesc +get_expr_result_tupdesc(Node *expr, bool noError) +{ + TupleDesc tupleDesc; + TypeFuncClass functypclass; + + functypclass = get_expr_result_type(expr, NULL, &tupleDesc); + + if (functypclass == TYPEFUNC_COMPOSITE || + functypclass == TYPEFUNC_COMPOSITE_DOMAIN) + return tupleDesc; + + if (!noError) + { + Oid exprTypeId = exprType(expr); + + if (exprTypeId != RECORDOID) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("type %s is not composite", + format_type_be(exprTypeId)))); + else + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("record type has not been registered"))); + } + + return NULL; +} + +/* + * Resolve actual type of ANYELEMENT from other polymorphic inputs + * + * Note: the error cases here and in the sibling functions below are not + * really user-facing; they could only occur if the function signature is + * incorrect or the parser failed to enforce consistency of the actual + * argument types. Hence, we don't sweat too much over the error messages. + */ +static void +resolve_anyelement_from_others(polymorphic_actuals *actuals) +{ + if (OidIsValid(actuals->anyarray_type)) + { + /* Use the element type corresponding to actual type */ + Oid array_base_type = getBaseType(actuals->anyarray_type); + Oid array_typelem = get_element_type(array_base_type); + + if (!OidIsValid(array_typelem)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("argument declared %s is not an array but type %s", + "anyarray", + format_type_be(array_base_type)))); + actuals->anyelement_type = array_typelem; + } + else if (OidIsValid(actuals->anyrange_type)) + { + /* Use the element type corresponding to actual type */ + Oid range_base_type = getBaseType(actuals->anyrange_type); + Oid range_typelem = get_range_subtype(range_base_type); + + if (!OidIsValid(range_typelem)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("argument declared %s is not a range type but type %s", + "anyrange", + format_type_be(range_base_type)))); + actuals->anyelement_type = range_typelem; + } + else if (OidIsValid(actuals->anymultirange_type)) + { + /* Use the element type based on the multirange type */ + Oid multirange_base_type; + Oid multirange_typelem; + Oid range_base_type; + Oid range_typelem; + + multirange_base_type = getBaseType(actuals->anymultirange_type); + multirange_typelem = get_multirange_range(multirange_base_type); + if (!OidIsValid(multirange_typelem)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("argument declared %s is not a multirange type but type %s", + "anymultirange", + format_type_be(multirange_base_type)))); + + range_base_type = getBaseType(multirange_typelem); + range_typelem = get_range_subtype(range_base_type); + + if (!OidIsValid(range_typelem)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("argument declared %s does not contain a range type but type %s", + "anymultirange", + format_type_be(range_base_type)))); + actuals->anyelement_type = range_typelem; + } + else + elog(ERROR, "could not determine polymorphic type"); +} + +/* + * Resolve actual type of ANYARRAY from other polymorphic inputs + */ +static void +resolve_anyarray_from_others(polymorphic_actuals *actuals) +{ + /* If we don't know ANYELEMENT, resolve that first */ + if (!OidIsValid(actuals->anyelement_type)) + resolve_anyelement_from_others(actuals); + + if (OidIsValid(actuals->anyelement_type)) + { + /* Use the array type corresponding to actual type */ + Oid array_typeid = get_array_type(actuals->anyelement_type); + + if (!OidIsValid(array_typeid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("could not find array type for data type %s", + format_type_be(actuals->anyelement_type)))); + actuals->anyarray_type = array_typeid; + } + else + elog(ERROR, "could not determine polymorphic type"); +} + +/* + * Resolve actual type of ANYRANGE from other polymorphic inputs + */ +static void +resolve_anyrange_from_others(polymorphic_actuals *actuals) +{ + /* + * We can't deduce a range type from other polymorphic array or base + * types, because there may be multiple range types with the same subtype, + * but we can deduce it from a polymorphic multirange type. + */ + if (OidIsValid(actuals->anymultirange_type)) + { + /* Use the element type based on the multirange type */ + Oid multirange_base_type = getBaseType(actuals->anymultirange_type); + Oid multirange_typelem = get_multirange_range(multirange_base_type); + + if (!OidIsValid(multirange_typelem)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("argument declared %s is not a multirange type but type %s", + "anymultirange", + format_type_be(multirange_base_type)))); + actuals->anyrange_type = multirange_typelem; + } + else + elog(ERROR, "could not determine polymorphic type"); +} + +/* + * Resolve actual type of ANYMULTIRANGE from other polymorphic inputs + */ +static void +resolve_anymultirange_from_others(polymorphic_actuals *actuals) +{ + /* + * We can't deduce a multirange type from polymorphic array or base types, + * because there may be multiple range types with the same subtype, but we + * can deduce it from a polymorphic range type. + */ + if (OidIsValid(actuals->anyrange_type)) + { + Oid range_base_type = getBaseType(actuals->anyrange_type); + Oid multirange_typeid = get_range_multirange(range_base_type); + + if (!OidIsValid(multirange_typeid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("could not find multirange type for data type %s", + format_type_be(actuals->anyrange_type)))); + actuals->anymultirange_type = multirange_typeid; + } + else + elog(ERROR, "could not determine polymorphic type"); +} + +/* + * Given the result tuple descriptor for a function with OUT parameters, + * replace any polymorphic column types (ANYELEMENT etc) in the tupdesc + * with concrete data types deduced from the input arguments. + * declared_args is an oidvector of the function's declared input arg types + * (showing which are polymorphic), and call_expr is the call expression. + * + * Returns true if able to deduce all types, false if necessary information + * is not provided (call_expr is NULL or arg types aren't identifiable). + */ +static bool +resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args, + Node *call_expr) +{ + int natts = tupdesc->natts; + int nargs = declared_args->dim1; + bool have_polymorphic_result = false; + bool have_anyelement_result = false; + bool have_anyarray_result = false; + bool have_anyrange_result = false; + bool have_anymultirange_result = false; + bool have_anycompatible_result = false; + bool have_anycompatible_array_result = false; + bool have_anycompatible_range_result = false; + bool have_anycompatible_multirange_result = false; + polymorphic_actuals poly_actuals; + polymorphic_actuals anyc_actuals; + Oid anycollation = InvalidOid; + Oid anycompatcollation = InvalidOid; + int i; + + /* See if there are any polymorphic outputs; quick out if not */ + for (i = 0; i < natts; i++) + { + switch (TupleDescAttr(tupdesc, i)->atttypid) + { + case ANYELEMENTOID: + case ANYNONARRAYOID: + case ANYENUMOID: + have_polymorphic_result = true; + have_anyelement_result = true; + break; + case ANYARRAYOID: + have_polymorphic_result = true; + have_anyarray_result = true; + break; + case ANYRANGEOID: + have_polymorphic_result = true; + have_anyrange_result = true; + break; + case ANYMULTIRANGEOID: + have_polymorphic_result = true; + have_anymultirange_result = true; + break; + case ANYCOMPATIBLEOID: + case ANYCOMPATIBLENONARRAYOID: + have_polymorphic_result = true; + have_anycompatible_result = true; + break; + case ANYCOMPATIBLEARRAYOID: + have_polymorphic_result = true; + have_anycompatible_array_result = true; + break; + case ANYCOMPATIBLERANGEOID: + have_polymorphic_result = true; + have_anycompatible_range_result = true; + break; + case ANYCOMPATIBLEMULTIRANGEOID: + have_polymorphic_result = true; + have_anycompatible_multirange_result = true; + break; + default: + break; + } + } + if (!have_polymorphic_result) + return true; + + /* + * Otherwise, extract actual datatype(s) from input arguments. (We assume + * the parser already validated consistency of the arguments. Also, for + * the ANYCOMPATIBLE pseudotype family, we expect that all matching + * arguments were coerced to the selected common supertype, so that it + * doesn't matter which one's exposed type we look at.) + */ + if (!call_expr) + return false; /* no hope */ + + memset(&poly_actuals, 0, sizeof(poly_actuals)); + memset(&anyc_actuals, 0, sizeof(anyc_actuals)); + + for (i = 0; i < nargs; i++) + { + switch (declared_args->values[i]) + { + case ANYELEMENTOID: + case ANYNONARRAYOID: + case ANYENUMOID: + if (!OidIsValid(poly_actuals.anyelement_type)) + { + poly_actuals.anyelement_type = + get_call_expr_argtype(call_expr, i); + if (!OidIsValid(poly_actuals.anyelement_type)) + return false; + } + break; + case ANYARRAYOID: + if (!OidIsValid(poly_actuals.anyarray_type)) + { + poly_actuals.anyarray_type = + get_call_expr_argtype(call_expr, i); + if (!OidIsValid(poly_actuals.anyarray_type)) + return false; + } + break; + case ANYRANGEOID: + if (!OidIsValid(poly_actuals.anyrange_type)) + { + poly_actuals.anyrange_type = + get_call_expr_argtype(call_expr, i); + if (!OidIsValid(poly_actuals.anyrange_type)) + return false; + } + break; + case ANYMULTIRANGEOID: + if (!OidIsValid(poly_actuals.anymultirange_type)) + { + poly_actuals.anymultirange_type = + get_call_expr_argtype(call_expr, i); + if (!OidIsValid(poly_actuals.anymultirange_type)) + return false; + } + break; + case ANYCOMPATIBLEOID: + case ANYCOMPATIBLENONARRAYOID: + if (!OidIsValid(anyc_actuals.anyelement_type)) + { + anyc_actuals.anyelement_type = + get_call_expr_argtype(call_expr, i); + if (!OidIsValid(anyc_actuals.anyelement_type)) + return false; + } + break; + case ANYCOMPATIBLEARRAYOID: + if (!OidIsValid(anyc_actuals.anyarray_type)) + { + anyc_actuals.anyarray_type = + get_call_expr_argtype(call_expr, i); + if (!OidIsValid(anyc_actuals.anyarray_type)) + return false; + } + break; + case ANYCOMPATIBLERANGEOID: + if (!OidIsValid(anyc_actuals.anyrange_type)) + { + anyc_actuals.anyrange_type = + get_call_expr_argtype(call_expr, i); + if (!OidIsValid(anyc_actuals.anyrange_type)) + return false; + } + break; + case ANYCOMPATIBLEMULTIRANGEOID: + if (!OidIsValid(anyc_actuals.anymultirange_type)) + { + anyc_actuals.anymultirange_type = + get_call_expr_argtype(call_expr, i); + if (!OidIsValid(anyc_actuals.anymultirange_type)) + return false; + } + break; + default: + break; + } + } + + /* If needed, deduce one polymorphic type from others */ + if (have_anyelement_result && !OidIsValid(poly_actuals.anyelement_type)) + resolve_anyelement_from_others(&poly_actuals); + + if (have_anyarray_result && !OidIsValid(poly_actuals.anyarray_type)) + resolve_anyarray_from_others(&poly_actuals); + + if (have_anyrange_result && !OidIsValid(poly_actuals.anyrange_type)) + resolve_anyrange_from_others(&poly_actuals); + + if (have_anymultirange_result && !OidIsValid(poly_actuals.anymultirange_type)) + resolve_anymultirange_from_others(&poly_actuals); + + if (have_anycompatible_result && !OidIsValid(anyc_actuals.anyelement_type)) + resolve_anyelement_from_others(&anyc_actuals); + + if (have_anycompatible_array_result && !OidIsValid(anyc_actuals.anyarray_type)) + resolve_anyarray_from_others(&anyc_actuals); + + if (have_anycompatible_range_result && !OidIsValid(anyc_actuals.anyrange_type)) + resolve_anyrange_from_others(&anyc_actuals); + + if (have_anycompatible_multirange_result && !OidIsValid(anyc_actuals.anymultirange_type)) + resolve_anymultirange_from_others(&anyc_actuals); + + /* + * Identify the collation to use for polymorphic OUT parameters. (It'll + * necessarily be the same for both anyelement and anyarray, likewise for + * anycompatible and anycompatiblearray.) Note that range types are not + * collatable, so any possible internal collation of a range type is not + * considered here. + */ + if (OidIsValid(poly_actuals.anyelement_type)) + anycollation = get_typcollation(poly_actuals.anyelement_type); + else if (OidIsValid(poly_actuals.anyarray_type)) + anycollation = get_typcollation(poly_actuals.anyarray_type); + + if (OidIsValid(anyc_actuals.anyelement_type)) + anycompatcollation = get_typcollation(anyc_actuals.anyelement_type); + else if (OidIsValid(anyc_actuals.anyarray_type)) + anycompatcollation = get_typcollation(anyc_actuals.anyarray_type); + + if (OidIsValid(anycollation) || OidIsValid(anycompatcollation)) + { + /* + * The types are collatable, so consider whether to use a nondefault + * collation. We do so if we can identify the input collation used + * for the function. + */ + Oid inputcollation = exprInputCollation(call_expr); + + if (OidIsValid(inputcollation)) + { + if (OidIsValid(anycollation)) + anycollation = inputcollation; + if (OidIsValid(anycompatcollation)) + anycompatcollation = inputcollation; + } + } + + /* And finally replace the tuple column types as needed */ + for (i = 0; i < natts; i++) + { + Form_pg_attribute att = TupleDescAttr(tupdesc, i); + + switch (att->atttypid) + { + case ANYELEMENTOID: + case ANYNONARRAYOID: + case ANYENUMOID: + TupleDescInitEntry(tupdesc, i + 1, + NameStr(att->attname), + poly_actuals.anyelement_type, + -1, + 0); + TupleDescInitEntryCollation(tupdesc, i + 1, anycollation); + break; + case ANYARRAYOID: + TupleDescInitEntry(tupdesc, i + 1, + NameStr(att->attname), + poly_actuals.anyarray_type, + -1, + 0); + TupleDescInitEntryCollation(tupdesc, i + 1, anycollation); + break; + case ANYRANGEOID: + TupleDescInitEntry(tupdesc, i + 1, + NameStr(att->attname), + poly_actuals.anyrange_type, + -1, + 0); + /* no collation should be attached to a range type */ + break; + case ANYMULTIRANGEOID: + TupleDescInitEntry(tupdesc, i + 1, + NameStr(att->attname), + poly_actuals.anymultirange_type, + -1, + 0); + /* no collation should be attached to a multirange type */ + break; + case ANYCOMPATIBLEOID: + case ANYCOMPATIBLENONARRAYOID: + TupleDescInitEntry(tupdesc, i + 1, + NameStr(att->attname), + anyc_actuals.anyelement_type, + -1, + 0); + TupleDescInitEntryCollation(tupdesc, i + 1, anycompatcollation); + break; + case ANYCOMPATIBLEARRAYOID: + TupleDescInitEntry(tupdesc, i + 1, + NameStr(att->attname), + anyc_actuals.anyarray_type, + -1, + 0); + TupleDescInitEntryCollation(tupdesc, i + 1, anycompatcollation); + break; + case ANYCOMPATIBLERANGEOID: + TupleDescInitEntry(tupdesc, i + 1, + NameStr(att->attname), + anyc_actuals.anyrange_type, + -1, + 0); + /* no collation should be attached to a range type */ + break; + case ANYCOMPATIBLEMULTIRANGEOID: + TupleDescInitEntry(tupdesc, i + 1, + NameStr(att->attname), + anyc_actuals.anymultirange_type, + -1, + 0); + /* no collation should be attached to a multirange type */ + break; + default: + break; + } + } + + return true; +} + +/* + * Given the declared argument types and modes for a function, replace any + * polymorphic types (ANYELEMENT etc) in argtypes[] with concrete data types + * deduced from the input arguments found in call_expr. + * + * Returns true if able to deduce all types, false if necessary information + * is not provided (call_expr is NULL or arg types aren't identifiable). + * + * This is the same logic as resolve_polymorphic_tupdesc, but with a different + * argument representation, and slightly different output responsibilities. + * + * argmodes may be NULL, in which case all arguments are assumed to be IN mode. + */ +bool +resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes, + Node *call_expr) +{ + bool have_polymorphic_result = false; + bool have_anyelement_result = false; + bool have_anyarray_result = false; + bool have_anyrange_result = false; + bool have_anymultirange_result = false; + bool have_anycompatible_result = false; + bool have_anycompatible_array_result = false; + bool have_anycompatible_range_result = false; + bool have_anycompatible_multirange_result = false; + polymorphic_actuals poly_actuals; + polymorphic_actuals anyc_actuals; + int inargno; + int i; + + /* + * First pass: resolve polymorphic inputs, check for outputs. As in + * resolve_polymorphic_tupdesc, we rely on the parser to have enforced + * type consistency and coerced ANYCOMPATIBLE args to a common supertype. + */ + memset(&poly_actuals, 0, sizeof(poly_actuals)); + memset(&anyc_actuals, 0, sizeof(anyc_actuals)); + inargno = 0; + for (i = 0; i < numargs; i++) + { + char argmode = argmodes ? argmodes[i] : PROARGMODE_IN; + + switch (argtypes[i]) + { + case ANYELEMENTOID: + case ANYNONARRAYOID: + case ANYENUMOID: + if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE) + { + have_polymorphic_result = true; + have_anyelement_result = true; + } + else + { + if (!OidIsValid(poly_actuals.anyelement_type)) + { + poly_actuals.anyelement_type = + get_call_expr_argtype(call_expr, inargno); + if (!OidIsValid(poly_actuals.anyelement_type)) + return false; + } + argtypes[i] = poly_actuals.anyelement_type; + } + break; + case ANYARRAYOID: + if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE) + { + have_polymorphic_result = true; + have_anyarray_result = true; + } + else + { + if (!OidIsValid(poly_actuals.anyarray_type)) + { + poly_actuals.anyarray_type = + get_call_expr_argtype(call_expr, inargno); + if (!OidIsValid(poly_actuals.anyarray_type)) + return false; + } + argtypes[i] = poly_actuals.anyarray_type; + } + break; + case ANYRANGEOID: + if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE) + { + have_polymorphic_result = true; + have_anyrange_result = true; + } + else + { + if (!OidIsValid(poly_actuals.anyrange_type)) + { + poly_actuals.anyrange_type = + get_call_expr_argtype(call_expr, inargno); + if (!OidIsValid(poly_actuals.anyrange_type)) + return false; + } + argtypes[i] = poly_actuals.anyrange_type; + } + break; + case ANYMULTIRANGEOID: + if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE) + { + have_polymorphic_result = true; + have_anymultirange_result = true; + } + else + { + if (!OidIsValid(poly_actuals.anymultirange_type)) + { + poly_actuals.anymultirange_type = + get_call_expr_argtype(call_expr, inargno); + if (!OidIsValid(poly_actuals.anymultirange_type)) + return false; + } + argtypes[i] = poly_actuals.anymultirange_type; + } + break; + case ANYCOMPATIBLEOID: + case ANYCOMPATIBLENONARRAYOID: + if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE) + { + have_polymorphic_result = true; + have_anycompatible_result = true; + } + else + { + if (!OidIsValid(anyc_actuals.anyelement_type)) + { + anyc_actuals.anyelement_type = + get_call_expr_argtype(call_expr, inargno); + if (!OidIsValid(anyc_actuals.anyelement_type)) + return false; + } + argtypes[i] = anyc_actuals.anyelement_type; + } + break; + case ANYCOMPATIBLEARRAYOID: + if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE) + { + have_polymorphic_result = true; + have_anycompatible_array_result = true; + } + else + { + if (!OidIsValid(anyc_actuals.anyarray_type)) + { + anyc_actuals.anyarray_type = + get_call_expr_argtype(call_expr, inargno); + if (!OidIsValid(anyc_actuals.anyarray_type)) + return false; + } + argtypes[i] = anyc_actuals.anyarray_type; + } + break; + case ANYCOMPATIBLERANGEOID: + if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE) + { + have_polymorphic_result = true; + have_anycompatible_range_result = true; + } + else + { + if (!OidIsValid(anyc_actuals.anyrange_type)) + { + anyc_actuals.anyrange_type = + get_call_expr_argtype(call_expr, inargno); + if (!OidIsValid(anyc_actuals.anyrange_type)) + return false; + } + argtypes[i] = anyc_actuals.anyrange_type; + } + break; + case ANYCOMPATIBLEMULTIRANGEOID: + if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE) + { + have_polymorphic_result = true; + have_anycompatible_multirange_result = true; + } + else + { + if (!OidIsValid(anyc_actuals.anymultirange_type)) + { + anyc_actuals.anymultirange_type = + get_call_expr_argtype(call_expr, inargno); + if (!OidIsValid(anyc_actuals.anymultirange_type)) + return false; + } + argtypes[i] = anyc_actuals.anymultirange_type; + } + break; + default: + break; + } + if (argmode != PROARGMODE_OUT && argmode != PROARGMODE_TABLE) + inargno++; + } + + /* Done? */ + if (!have_polymorphic_result) + return true; + + /* If needed, deduce one polymorphic type from others */ + if (have_anyelement_result && !OidIsValid(poly_actuals.anyelement_type)) + resolve_anyelement_from_others(&poly_actuals); + + if (have_anyarray_result && !OidIsValid(poly_actuals.anyarray_type)) + resolve_anyarray_from_others(&poly_actuals); + + if (have_anyrange_result && !OidIsValid(poly_actuals.anyrange_type)) + resolve_anyrange_from_others(&poly_actuals); + + if (have_anymultirange_result && !OidIsValid(poly_actuals.anymultirange_type)) + resolve_anymultirange_from_others(&poly_actuals); + + if (have_anycompatible_result && !OidIsValid(anyc_actuals.anyelement_type)) + resolve_anyelement_from_others(&anyc_actuals); + + if (have_anycompatible_array_result && !OidIsValid(anyc_actuals.anyarray_type)) + resolve_anyarray_from_others(&anyc_actuals); + + if (have_anycompatible_range_result && !OidIsValid(anyc_actuals.anyrange_type)) + resolve_anyrange_from_others(&anyc_actuals); + + if (have_anycompatible_multirange_result && !OidIsValid(anyc_actuals.anymultirange_type)) + resolve_anymultirange_from_others(&anyc_actuals); + + /* And finally replace the output column types as needed */ + for (i = 0; i < numargs; i++) + { + switch (argtypes[i]) + { + case ANYELEMENTOID: + case ANYNONARRAYOID: + case ANYENUMOID: + argtypes[i] = poly_actuals.anyelement_type; + break; + case ANYARRAYOID: + argtypes[i] = poly_actuals.anyarray_type; + break; + case ANYRANGEOID: + argtypes[i] = poly_actuals.anyrange_type; + break; + case ANYMULTIRANGEOID: + argtypes[i] = poly_actuals.anymultirange_type; + break; + case ANYCOMPATIBLEOID: + case ANYCOMPATIBLENONARRAYOID: + argtypes[i] = anyc_actuals.anyelement_type; + break; + case ANYCOMPATIBLEARRAYOID: + argtypes[i] = anyc_actuals.anyarray_type; + break; + case ANYCOMPATIBLERANGEOID: + argtypes[i] = anyc_actuals.anyrange_type; + break; + case ANYCOMPATIBLEMULTIRANGEOID: + argtypes[i] = anyc_actuals.anymultirange_type; + break; + default: + break; + } + } + + return true; +} + +/* + * get_type_func_class + * Given the type OID, obtain its TYPEFUNC classification. + * Also, if it's a domain, return the base type OID. + * + * This is intended to centralize a bunch of formerly ad-hoc code for + * classifying types. The categories used here are useful for deciding + * how to handle functions returning the datatype. + */ +static TypeFuncClass +get_type_func_class(Oid typid, Oid *base_typeid) +{ + *base_typeid = typid; + + switch (get_typtype(typid)) + { + case TYPTYPE_COMPOSITE: + return TYPEFUNC_COMPOSITE; + case TYPTYPE_BASE: + case TYPTYPE_ENUM: + case TYPTYPE_RANGE: + case TYPTYPE_MULTIRANGE: + return TYPEFUNC_SCALAR; + case TYPTYPE_DOMAIN: + *base_typeid = typid = getBaseType(typid); + if (get_typtype(typid) == TYPTYPE_COMPOSITE) + return TYPEFUNC_COMPOSITE_DOMAIN; + else /* domain base type can't be a pseudotype */ + return TYPEFUNC_SCALAR; + case TYPTYPE_PSEUDO: + if (typid == RECORDOID) + return TYPEFUNC_RECORD; + + /* + * We treat VOID and CSTRING as legitimate scalar datatypes, + * mostly for the convenience of the JDBC driver (which wants to + * be able to do "SELECT * FROM foo()" for all legitimately + * user-callable functions). + */ + if (typid == VOIDOID || typid == CSTRINGOID) + return TYPEFUNC_SCALAR; + return TYPEFUNC_OTHER; + } + /* shouldn't get here, probably */ + return TYPEFUNC_OTHER; +} + + +/* + * get_func_arg_info + * + * Fetch info about the argument types, names, and IN/OUT modes from the + * pg_proc tuple. Return value is the total number of arguments. + * Other results are palloc'd. *p_argtypes is always filled in, but + * *p_argnames and *p_argmodes will be set NULL in the default cases + * (no names, and all IN arguments, respectively). + * + * Note that this function simply fetches what is in the pg_proc tuple; + * it doesn't do any interpretation of polymorphic types. + */ +int +get_func_arg_info(HeapTuple procTup, + Oid **p_argtypes, char ***p_argnames, char **p_argmodes) +{ + Form_pg_proc procStruct = (Form_pg_proc) GETSTRUCT(procTup); + Datum proallargtypes; + Datum proargmodes; + Datum proargnames; + bool isNull; + ArrayType *arr; + int numargs; + Datum *elems; + int nelems; + int i; + + /* First discover the total number of parameters and get their types */ + proallargtypes = SysCacheGetAttr(PROCOID, procTup, + Anum_pg_proc_proallargtypes, + &isNull); + if (!isNull) + { + /* + * We expect the arrays to be 1-D arrays of the right types; verify + * that. For the OID and char arrays, we don't need to use + * deconstruct_array() since the array data is just going to look like + * a C array of values. + */ + arr = DatumGetArrayTypeP(proallargtypes); /* ensure not toasted */ + numargs = ARR_DIMS(arr)[0]; + if (ARR_NDIM(arr) != 1 || + numargs < 0 || + ARR_HASNULL(arr) || + ARR_ELEMTYPE(arr) != OIDOID) + elog(ERROR, "proallargtypes is not a 1-D Oid array or it contains nulls"); + Assert(numargs >= procStruct->pronargs); + *p_argtypes = (Oid *) palloc(numargs * sizeof(Oid)); + memcpy(*p_argtypes, ARR_DATA_PTR(arr), + numargs * sizeof(Oid)); + } + else + { + /* If no proallargtypes, use proargtypes */ + numargs = procStruct->proargtypes.dim1; + Assert(numargs == procStruct->pronargs); + *p_argtypes = (Oid *) palloc(numargs * sizeof(Oid)); + memcpy(*p_argtypes, procStruct->proargtypes.values, + numargs * sizeof(Oid)); + } + + /* Get argument names, if available */ + proargnames = SysCacheGetAttr(PROCOID, procTup, + Anum_pg_proc_proargnames, + &isNull); + if (isNull) + *p_argnames = NULL; + else + { + deconstruct_array_builtin(DatumGetArrayTypeP(proargnames), TEXTOID, + &elems, NULL, &nelems); + if (nelems != numargs) /* should not happen */ + elog(ERROR, "proargnames must have the same number of elements as the function has arguments"); + *p_argnames = (char **) palloc(sizeof(char *) * numargs); + for (i = 0; i < numargs; i++) + (*p_argnames)[i] = TextDatumGetCString(elems[i]); + } + + /* Get argument modes, if available */ + proargmodes = SysCacheGetAttr(PROCOID, procTup, + Anum_pg_proc_proargmodes, + &isNull); + if (isNull) + *p_argmodes = NULL; + else + { + arr = DatumGetArrayTypeP(proargmodes); /* ensure not toasted */ + if (ARR_NDIM(arr) != 1 || + ARR_DIMS(arr)[0] != numargs || + ARR_HASNULL(arr) || + ARR_ELEMTYPE(arr) != CHAROID) + elog(ERROR, "proargmodes is not a 1-D char array of length %d or it contains nulls", + numargs); + *p_argmodes = (char *) palloc(numargs * sizeof(char)); + memcpy(*p_argmodes, ARR_DATA_PTR(arr), + numargs * sizeof(char)); + } + + return numargs; +} + +/* + * get_func_trftypes + * + * Returns the number of transformed types used by the function. + * If there are any, a palloc'd array of the type OIDs is returned + * into *p_trftypes. + */ +int +get_func_trftypes(HeapTuple procTup, + Oid **p_trftypes) +{ + Datum protrftypes; + ArrayType *arr; + int nelems; + bool isNull; + + protrftypes = SysCacheGetAttr(PROCOID, procTup, + Anum_pg_proc_protrftypes, + &isNull); + if (!isNull) + { + /* + * We expect the arrays to be 1-D arrays of the right types; verify + * that. For the OID and char arrays, we don't need to use + * deconstruct_array() since the array data is just going to look like + * a C array of values. + */ + arr = DatumGetArrayTypeP(protrftypes); /* ensure not toasted */ + nelems = ARR_DIMS(arr)[0]; + if (ARR_NDIM(arr) != 1 || + nelems < 0 || + ARR_HASNULL(arr) || + ARR_ELEMTYPE(arr) != OIDOID) + elog(ERROR, "protrftypes is not a 1-D Oid array or it contains nulls"); + *p_trftypes = (Oid *) palloc(nelems * sizeof(Oid)); + memcpy(*p_trftypes, ARR_DATA_PTR(arr), + nelems * sizeof(Oid)); + + return nelems; + } + else + return 0; +} + +/* + * get_func_input_arg_names + * + * Extract the names of input arguments only, given a function's + * proargnames and proargmodes entries in Datum form. + * + * Returns the number of input arguments, which is the length of the + * palloc'd array returned to *arg_names. Entries for unnamed args + * are set to NULL. You don't get anything if proargnames is NULL. + */ +int +get_func_input_arg_names(Datum proargnames, Datum proargmodes, + char ***arg_names) +{ + ArrayType *arr; + int numargs; + Datum *argnames; + char *argmodes; + char **inargnames; + int numinargs; + int i; + + /* Do nothing if null proargnames */ + if (proargnames == PointerGetDatum(NULL)) + { + *arg_names = NULL; + return 0; + } + + /* + * We expect the arrays to be 1-D arrays of the right types; verify that. + * For proargmodes, we don't need to use deconstruct_array() since the + * array data is just going to look like a C array of values. + */ + arr = DatumGetArrayTypeP(proargnames); /* ensure not toasted */ + if (ARR_NDIM(arr) != 1 || + ARR_HASNULL(arr) || + ARR_ELEMTYPE(arr) != TEXTOID) + elog(ERROR, "proargnames is not a 1-D text array or it contains nulls"); + deconstruct_array_builtin(arr, TEXTOID, &argnames, NULL, &numargs); + if (proargmodes != PointerGetDatum(NULL)) + { + arr = DatumGetArrayTypeP(proargmodes); /* ensure not toasted */ + if (ARR_NDIM(arr) != 1 || + ARR_DIMS(arr)[0] != numargs || + ARR_HASNULL(arr) || + ARR_ELEMTYPE(arr) != CHAROID) + elog(ERROR, "proargmodes is not a 1-D char array of length %d or it contains nulls", + numargs); + argmodes = (char *) ARR_DATA_PTR(arr); + } + else + argmodes = NULL; + + /* zero elements probably shouldn't happen, but handle it gracefully */ + if (numargs <= 0) + { + *arg_names = NULL; + return 0; + } + + /* extract input-argument names */ + inargnames = (char **) palloc(numargs * sizeof(char *)); + numinargs = 0; + for (i = 0; i < numargs; i++) + { + if (argmodes == NULL || + argmodes[i] == PROARGMODE_IN || + argmodes[i] == PROARGMODE_INOUT || + argmodes[i] == PROARGMODE_VARIADIC) + { + char *pname = TextDatumGetCString(argnames[i]); + + if (pname[0] != '\0') + inargnames[numinargs] = pname; + else + inargnames[numinargs] = NULL; + numinargs++; + } + } + + *arg_names = inargnames; + return numinargs; +} + + +/* + * get_func_result_name + * + * If the function has exactly one output parameter, and that parameter + * is named, return the name (as a palloc'd string). Else return NULL. + * + * This is used to determine the default output column name for functions + * returning scalar types. + */ +char * +get_func_result_name(Oid functionId) +{ + char *result; + HeapTuple procTuple; + Datum proargmodes; + Datum proargnames; + ArrayType *arr; + int numargs; + char *argmodes; + Datum *argnames; + int numoutargs; + int nargnames; + int i; + + /* First fetch the function's pg_proc row */ + procTuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(functionId)); + if (!HeapTupleIsValid(procTuple)) + elog(ERROR, "cache lookup failed for function %u", functionId); + + /* If there are no named OUT parameters, return NULL */ + if (heap_attisnull(procTuple, Anum_pg_proc_proargmodes, NULL) || + heap_attisnull(procTuple, Anum_pg_proc_proargnames, NULL)) + result = NULL; + else + { + /* Get the data out of the tuple */ + proargmodes = SysCacheGetAttrNotNull(PROCOID, procTuple, + Anum_pg_proc_proargmodes); + proargnames = SysCacheGetAttrNotNull(PROCOID, procTuple, + Anum_pg_proc_proargnames); + + /* + * We expect the arrays to be 1-D arrays of the right types; verify + * that. For the char array, we don't need to use deconstruct_array() + * since the array data is just going to look like a C array of + * values. + */ + arr = DatumGetArrayTypeP(proargmodes); /* ensure not toasted */ + numargs = ARR_DIMS(arr)[0]; + if (ARR_NDIM(arr) != 1 || + numargs < 0 || + ARR_HASNULL(arr) || + ARR_ELEMTYPE(arr) != CHAROID) + elog(ERROR, "proargmodes is not a 1-D char array or it contains nulls"); + argmodes = (char *) ARR_DATA_PTR(arr); + arr = DatumGetArrayTypeP(proargnames); /* ensure not toasted */ + if (ARR_NDIM(arr) != 1 || + ARR_DIMS(arr)[0] != numargs || + ARR_HASNULL(arr) || + ARR_ELEMTYPE(arr) != TEXTOID) + elog(ERROR, "proargnames is not a 1-D text array of length %d or it contains nulls", + numargs); + deconstruct_array_builtin(arr, TEXTOID, &argnames, NULL, &nargnames); + Assert(nargnames == numargs); + + /* scan for output argument(s) */ + result = NULL; + numoutargs = 0; + for (i = 0; i < numargs; i++) + { + if (argmodes[i] == PROARGMODE_IN || + argmodes[i] == PROARGMODE_VARIADIC) + continue; + Assert(argmodes[i] == PROARGMODE_OUT || + argmodes[i] == PROARGMODE_INOUT || + argmodes[i] == PROARGMODE_TABLE); + if (++numoutargs > 1) + { + /* multiple out args, so forget it */ + result = NULL; + break; + } + result = TextDatumGetCString(argnames[i]); + if (result == NULL || result[0] == '\0') + { + /* Parameter is not named, so forget it */ + result = NULL; + break; + } + } + } + + ReleaseSysCache(procTuple); + + return result; +} + + +/* + * build_function_result_tupdesc_t + * + * Given a pg_proc row for a function, return a tuple descriptor for the + * result rowtype, or NULL if the function does not have OUT parameters. + * + * Note that this does not handle resolution of polymorphic types; + * that is deliberate. + */ +TupleDesc +build_function_result_tupdesc_t(HeapTuple procTuple) +{ + Form_pg_proc procform = (Form_pg_proc) GETSTRUCT(procTuple); + Datum proallargtypes; + Datum proargmodes; + Datum proargnames; + bool isnull; + + /* Return NULL if the function isn't declared to return RECORD */ + if (procform->prorettype != RECORDOID) + return NULL; + + /* If there are no OUT parameters, return NULL */ + if (heap_attisnull(procTuple, Anum_pg_proc_proallargtypes, NULL) || + heap_attisnull(procTuple, Anum_pg_proc_proargmodes, NULL)) + return NULL; + + /* Get the data out of the tuple */ + proallargtypes = SysCacheGetAttrNotNull(PROCOID, procTuple, + Anum_pg_proc_proallargtypes); + proargmodes = SysCacheGetAttrNotNull(PROCOID, procTuple, + Anum_pg_proc_proargmodes); + proargnames = SysCacheGetAttr(PROCOID, procTuple, + Anum_pg_proc_proargnames, + &isnull); + if (isnull) + proargnames = PointerGetDatum(NULL); /* just to be sure */ + + return build_function_result_tupdesc_d(procform->prokind, + proallargtypes, + proargmodes, + proargnames); +} + +/* + * build_function_result_tupdesc_d + * + * Build a RECORD function's tupledesc from the pg_proc proallargtypes, + * proargmodes, and proargnames arrays. This is split out for the + * convenience of ProcedureCreate, which needs to be able to compute the + * tupledesc before actually creating the function. + * + * For functions (but not for procedures), returns NULL if there are not at + * least two OUT or INOUT arguments. + */ +TupleDesc +build_function_result_tupdesc_d(char prokind, + Datum proallargtypes, + Datum proargmodes, + Datum proargnames) +{ + TupleDesc desc; + ArrayType *arr; + int numargs; + Oid *argtypes; + char *argmodes; + Datum *argnames = NULL; + Oid *outargtypes; + char **outargnames; + int numoutargs; + int nargnames; + int i; + + /* Can't have output args if columns are null */ + if (proallargtypes == PointerGetDatum(NULL) || + proargmodes == PointerGetDatum(NULL)) + return NULL; + + /* + * We expect the arrays to be 1-D arrays of the right types; verify that. + * For the OID and char arrays, we don't need to use deconstruct_array() + * since the array data is just going to look like a C array of values. + */ + arr = DatumGetArrayTypeP(proallargtypes); /* ensure not toasted */ + numargs = ARR_DIMS(arr)[0]; + if (ARR_NDIM(arr) != 1 || + numargs < 0 || + ARR_HASNULL(arr) || + ARR_ELEMTYPE(arr) != OIDOID) + elog(ERROR, "proallargtypes is not a 1-D Oid array or it contains nulls"); + argtypes = (Oid *) ARR_DATA_PTR(arr); + arr = DatumGetArrayTypeP(proargmodes); /* ensure not toasted */ + if (ARR_NDIM(arr) != 1 || + ARR_DIMS(arr)[0] != numargs || + ARR_HASNULL(arr) || + ARR_ELEMTYPE(arr) != CHAROID) + elog(ERROR, "proargmodes is not a 1-D char array of length %d or it contains nulls", + numargs); + argmodes = (char *) ARR_DATA_PTR(arr); + if (proargnames != PointerGetDatum(NULL)) + { + arr = DatumGetArrayTypeP(proargnames); /* ensure not toasted */ + if (ARR_NDIM(arr) != 1 || + ARR_DIMS(arr)[0] != numargs || + ARR_HASNULL(arr) || + ARR_ELEMTYPE(arr) != TEXTOID) + elog(ERROR, "proargnames is not a 1-D text array of length %d or it contains nulls", + numargs); + deconstruct_array_builtin(arr, TEXTOID, &argnames, NULL, &nargnames); + Assert(nargnames == numargs); + } + + /* zero elements probably shouldn't happen, but handle it gracefully */ + if (numargs <= 0) + return NULL; + + /* extract output-argument types and names */ + outargtypes = (Oid *) palloc(numargs * sizeof(Oid)); + outargnames = (char **) palloc(numargs * sizeof(char *)); + numoutargs = 0; + for (i = 0; i < numargs; i++) + { + char *pname; + + if (argmodes[i] == PROARGMODE_IN || + argmodes[i] == PROARGMODE_VARIADIC) + continue; + Assert(argmodes[i] == PROARGMODE_OUT || + argmodes[i] == PROARGMODE_INOUT || + argmodes[i] == PROARGMODE_TABLE); + outargtypes[numoutargs] = argtypes[i]; + if (argnames) + pname = TextDatumGetCString(argnames[i]); + else + pname = NULL; + if (pname == NULL || pname[0] == '\0') + { + /* Parameter is not named, so gin up a column name */ + pname = psprintf("column%d", numoutargs + 1); + } + outargnames[numoutargs] = pname; + numoutargs++; + } + + /* + * If there is no output argument, or only one, the function does not + * return tuples. + */ + if (numoutargs < 2 && prokind != PROKIND_PROCEDURE) + return NULL; + + desc = CreateTemplateTupleDesc(numoutargs); + for (i = 0; i < numoutargs; i++) + { + TupleDescInitEntry(desc, i + 1, + outargnames[i], + outargtypes[i], + -1, + 0); + } + + return desc; +} + + +/* + * RelationNameGetTupleDesc + * + * Given a (possibly qualified) relation name, build a TupleDesc. + * + * Note: while this works as advertised, it's seldom the best way to + * build a tupdesc for a function's result type. It's kept around + * only for backwards compatibility with existing user-written code. + */ +TupleDesc +RelationNameGetTupleDesc(const char *relname) +{ + RangeVar *relvar; + Relation rel; + TupleDesc tupdesc; + List *relname_list; + + /* Open relation and copy the tuple description */ + relname_list = stringToQualifiedNameList(relname, NULL); + relvar = makeRangeVarFromNameList(relname_list); + rel = relation_openrv(relvar, AccessShareLock); + tupdesc = CreateTupleDescCopy(RelationGetDescr(rel)); + relation_close(rel, AccessShareLock); + + return tupdesc; +} + +/* + * TypeGetTupleDesc + * + * Given a type Oid, build a TupleDesc. (In most cases you should be + * using get_call_result_type or one of its siblings instead of this + * routine, so that you can handle OUT parameters, RECORD result type, + * and polymorphic results.) + * + * If the type is composite, *and* a colaliases List is provided, *and* + * the List is of natts length, use the aliases instead of the relation + * attnames. (NB: this usage is deprecated since it may result in + * creation of unnecessary transient record types.) + * + * If the type is a base type, a single item alias List is required. + */ +TupleDesc +TypeGetTupleDesc(Oid typeoid, List *colaliases) +{ + Oid base_typeoid; + TypeFuncClass functypclass = get_type_func_class(typeoid, &base_typeoid); + TupleDesc tupdesc = NULL; + + /* + * Build a suitable tupledesc representing the output rows. We + * intentionally do not support TYPEFUNC_COMPOSITE_DOMAIN here, as it's + * unlikely that legacy callers of this obsolete function would be + * prepared to apply domain constraints. + */ + if (functypclass == TYPEFUNC_COMPOSITE) + { + /* Composite data type, e.g. a table's row type */ + tupdesc = lookup_rowtype_tupdesc_copy(base_typeoid, -1); + + if (colaliases != NIL) + { + int natts = tupdesc->natts; + int varattno; + + /* does the list length match the number of attributes? */ + if (list_length(colaliases) != natts) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("number of aliases does not match number of columns"))); + + /* OK, use the aliases instead */ + for (varattno = 0; varattno < natts; varattno++) + { + char *label = strVal(list_nth(colaliases, varattno)); + Form_pg_attribute attr = TupleDescAttr(tupdesc, varattno); + + if (label != NULL) + namestrcpy(&(attr->attname), label); + } + + /* The tuple type is now an anonymous record type */ + tupdesc->tdtypeid = RECORDOID; + tupdesc->tdtypmod = -1; + } + } + else if (functypclass == TYPEFUNC_SCALAR) + { + /* Base data type, i.e. scalar */ + char *attname; + + /* the alias list is required for base types */ + if (colaliases == NIL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("no column alias was provided"))); + + /* the alias list length must be 1 */ + if (list_length(colaliases) != 1) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("number of aliases does not match number of columns"))); + + /* OK, get the column alias */ + attname = strVal(linitial(colaliases)); + + tupdesc = CreateTemplateTupleDesc(1); + TupleDescInitEntry(tupdesc, + (AttrNumber) 1, + attname, + typeoid, + -1, + 0); + } + else if (functypclass == TYPEFUNC_RECORD) + { + /* XXX can't support this because typmod wasn't passed in ... */ + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("could not determine row description for function returning record"))); + } + else + { + /* crummy error message, but parser should have caught this */ + elog(ERROR, "function in FROM has unsupported return type"); + } + + return tupdesc; +} + +/* + * extract_variadic_args + * + * Extract a set of argument values, types and NULL markers for a given + * input function which makes use of a VARIADIC input whose argument list + * depends on the caller context. When doing a VARIADIC call, the caller + * has provided one argument made of an array of values, so deconstruct the + * array data before using it for the next processing. If no VARIADIC call + * is used, just fill in the status data based on all the arguments given + * by the caller. + * + * This function returns the number of arguments generated, or -1 in the + * case of "VARIADIC NULL". + */ +int +extract_variadic_args(FunctionCallInfo fcinfo, int variadic_start, + bool convert_unknown, Datum **args, Oid **types, + bool **nulls) +{ + bool variadic = get_fn_expr_variadic(fcinfo->flinfo); + Datum *args_res; + bool *nulls_res; + Oid *types_res; + int nargs, + i; + + *args = NULL; + *types = NULL; + *nulls = NULL; + + if (variadic) + { + ArrayType *array_in; + Oid element_type; + bool typbyval; + char typalign; + int16 typlen; + + Assert(PG_NARGS() == variadic_start + 1); + + if (PG_ARGISNULL(variadic_start)) + return -1; + + array_in = PG_GETARG_ARRAYTYPE_P(variadic_start); + element_type = ARR_ELEMTYPE(array_in); + + get_typlenbyvalalign(element_type, + &typlen, &typbyval, &typalign); + deconstruct_array(array_in, element_type, typlen, typbyval, + typalign, &args_res, &nulls_res, + &nargs); + + /* All the elements of the array have the same type */ + types_res = (Oid *) palloc0(nargs * sizeof(Oid)); + for (i = 0; i < nargs; i++) + types_res[i] = element_type; + } + else + { + nargs = PG_NARGS() - variadic_start; + Assert(nargs > 0); + nulls_res = (bool *) palloc0(nargs * sizeof(bool)); + args_res = (Datum *) palloc0(nargs * sizeof(Datum)); + types_res = (Oid *) palloc0(nargs * sizeof(Oid)); + + for (i = 0; i < nargs; i++) + { + nulls_res[i] = PG_ARGISNULL(i + variadic_start); + types_res[i] = get_fn_expr_argtype(fcinfo->flinfo, + i + variadic_start); + + /* + * Turn a constant (more or less literal) value that's of unknown + * type into text if required. Unknowns come in as a cstring + * pointer. Note: for functions declared as taking type "any", the + * parser will not do any type conversion on unknown-type literals + * (that is, undecorated strings or NULLs). + */ + if (convert_unknown && + types_res[i] == UNKNOWNOID && + get_fn_expr_arg_stable(fcinfo->flinfo, i + variadic_start)) + { + types_res[i] = TEXTOID; + + if (PG_ARGISNULL(i + variadic_start)) + args_res[i] = (Datum) 0; + else + args_res[i] = + CStringGetTextDatum(PG_GETARG_POINTER(i + variadic_start)); + } + else + { + /* no conversion needed, just take the datum as given */ + args_res[i] = PG_GETARG_DATUM(i + variadic_start); + } + + if (!OidIsValid(types_res[i]) || + (convert_unknown && types_res[i] == UNKNOWNOID)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("could not determine data type for argument %d", + i + 1))); + } + } + + /* Fill in results */ + *args = args_res; + *nulls = nulls_res; + *types = types_res; + + return nargs; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/fmgroids.h b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/fmgroids.h new file mode 100644 index 00000000000..b96a24bbaf5 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/fmgroids.h @@ -0,0 +1,3314 @@ +/*------------------------------------------------------------------------- + * + * fmgroids.h + * Macros that define the OIDs of built-in functions. + * + * These macros can be used to avoid a catalog lookup when a specific + * fmgr-callable function needs to be referenced. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * NOTES + * ****************************** + * *** DO NOT EDIT THIS FILE! *** + * ****************************** + * + * It has been GENERATED by src/backend/utils/Gen_fmgrtab.pl + * + *------------------------------------------------------------------------- + */ +#ifndef FMGROIDS_H +#define FMGROIDS_H + +/* + * Constant macros for the OIDs of entries in pg_proc. + * + * F_XXX macros are named after the proname field; if that is not unique, + * we append the proargtypes field, replacing spaces with underscores. + * For example, we have F_OIDEQ because that proname is unique, but + * F_POW_FLOAT8_FLOAT8 (among others) because that proname is not. + */ +#define F_HEAP_TABLEAM_HANDLER 3 +#define F_BYTEAOUT 31 +#define F_CHAROUT 33 +#define F_NAMEIN 34 +#define F_NAMEOUT 35 +#define F_INT2IN 38 +#define F_INT2OUT 39 +#define F_INT2VECTORIN 40 +#define F_INT2VECTOROUT 41 +#define F_INT4IN 42 +#define F_INT4OUT 43 +#define F_REGPROCIN 44 +#define F_REGPROCOUT 45 +#define F_TEXTIN 46 +#define F_TEXTOUT 47 +#define F_TIDIN 48 +#define F_TIDOUT 49 +#define F_XIDIN 50 +#define F_XIDOUT 51 +#define F_CIDIN 52 +#define F_CIDOUT 53 +#define F_OIDVECTORIN 54 +#define F_OIDVECTOROUT 55 +#define F_BOOLLT 56 +#define F_BOOLGT 57 +#define F_BOOLEQ 60 +#define F_CHAREQ 61 +#define F_NAMEEQ 62 +#define F_INT2EQ 63 +#define F_INT2LT 64 +#define F_INT4EQ 65 +#define F_INT4LT 66 +#define F_TEXTEQ 67 +#define F_XIDEQ 68 +#define F_CIDEQ 69 +#define F_CHARNE 70 +#define F_CHARLE 72 +#define F_CHARGT 73 +#define F_CHARGE 74 +#define F_INT4_CHAR 77 +#define F_CHAR_INT4 78 +#define F_NAMEREGEXEQ 79 +#define F_BOOLNE 84 +#define F_PG_DDL_COMMAND_IN 86 +#define F_PG_DDL_COMMAND_OUT 87 +#define F_PG_DDL_COMMAND_RECV 88 +#define F_VERSION 89 +#define F_PG_DDL_COMMAND_SEND 90 +#define F_EQSEL 101 +#define F_NEQSEL 102 +#define F_SCALARLTSEL 103 +#define F_SCALARGTSEL 104 +#define F_EQJOINSEL 105 +#define F_NEQJOINSEL 106 +#define F_SCALARLTJOINSEL 107 +#define F_SCALARGTJOINSEL 108 +#define F_UNKNOWNIN 109 +#define F_UNKNOWNOUT 110 +#define F_BOX_ABOVE_EQ 115 +#define F_BOX_BELOW_EQ 116 +#define F_POINT_IN 117 +#define F_POINT_OUT 118 +#define F_LSEG_IN 119 +#define F_LSEG_OUT 120 +#define F_PATH_IN 121 +#define F_PATH_OUT 122 +#define F_BOX_IN 123 +#define F_BOX_OUT 124 +#define F_BOX_OVERLAP 125 +#define F_BOX_GE 126 +#define F_BOX_GT 127 +#define F_BOX_EQ 128 +#define F_BOX_LT 129 +#define F_BOX_LE 130 +#define F_POINT_ABOVE 131 +#define F_POINT_LEFT 132 +#define F_POINT_RIGHT 133 +#define F_POINT_BELOW 134 +#define F_POINT_EQ 135 +#define F_ON_PB 136 +#define F_ON_PPATH 137 +#define F_BOX_CENTER 138 +#define F_AREASEL 139 +#define F_AREAJOINSEL 140 +#define F_INT4MUL 141 +#define F_INT4NE 144 +#define F_INT2NE 145 +#define F_INT2GT 146 +#define F_INT4GT 147 +#define F_INT2LE 148 +#define F_INT4LE 149 +#define F_INT4GE 150 +#define F_INT2GE 151 +#define F_INT2MUL 152 +#define F_INT2DIV 153 +#define F_INT4DIV 154 +#define F_INT2MOD 155 +#define F_INT4MOD 156 +#define F_TEXTNE 157 +#define F_INT24EQ 158 +#define F_INT42EQ 159 +#define F_INT24LT 160 +#define F_INT42LT 161 +#define F_INT24GT 162 +#define F_INT42GT 163 +#define F_INT24NE 164 +#define F_INT42NE 165 +#define F_INT24LE 166 +#define F_INT42LE 167 +#define F_INT24GE 168 +#define F_INT42GE 169 +#define F_INT24MUL 170 +#define F_INT42MUL 171 +#define F_INT24DIV 172 +#define F_INT42DIV 173 +#define F_INT2PL 176 +#define F_INT4PL 177 +#define F_INT24PL 178 +#define F_INT42PL 179 +#define F_INT2MI 180 +#define F_INT4MI 181 +#define F_INT24MI 182 +#define F_INT42MI 183 +#define F_OIDEQ 184 +#define F_OIDNE 185 +#define F_BOX_SAME 186 +#define F_BOX_CONTAIN 187 +#define F_BOX_LEFT 188 +#define F_BOX_OVERLEFT 189 +#define F_BOX_OVERRIGHT 190 +#define F_BOX_RIGHT 191 +#define F_BOX_CONTAINED 192 +#define F_BOX_CONTAIN_PT 193 +#define F_PG_NODE_TREE_IN 195 +#define F_PG_NODE_TREE_OUT 196 +#define F_PG_NODE_TREE_RECV 197 +#define F_PG_NODE_TREE_SEND 198 +#define F_FLOAT4IN 200 +#define F_FLOAT4OUT 201 +#define F_FLOAT4MUL 202 +#define F_FLOAT4DIV 203 +#define F_FLOAT4PL 204 +#define F_FLOAT4MI 205 +#define F_FLOAT4UM 206 +#define F_FLOAT4ABS 207 +#define F_FLOAT4_ACCUM 208 +#define F_FLOAT4LARGER 209 +#define F_FLOAT4SMALLER 211 +#define F_INT4UM 212 +#define F_INT2UM 213 +#define F_FLOAT8IN 214 +#define F_FLOAT8OUT 215 +#define F_FLOAT8MUL 216 +#define F_FLOAT8DIV 217 +#define F_FLOAT8PL 218 +#define F_FLOAT8MI 219 +#define F_FLOAT8UM 220 +#define F_FLOAT8ABS 221 +#define F_FLOAT8_ACCUM 222 +#define F_FLOAT8LARGER 223 +#define F_FLOAT8SMALLER 224 +#define F_LSEG_CENTER 225 +#define F_POLY_CENTER 227 +#define F_DROUND 228 +#define F_DTRUNC 229 +#define F_DSQRT 230 +#define F_DCBRT 231 +#define F_DPOW 232 +#define F_DEXP 233 +#define F_DLOG1 234 +#define F_FLOAT8_INT2 235 +#define F_FLOAT4_INT2 236 +#define F_INT2_FLOAT8 237 +#define F_INT2_FLOAT4 238 +#define F_LINE_DISTANCE 239 +#define F_NAMEEQTEXT 240 +#define F_NAMELTTEXT 241 +#define F_NAMELETEXT 242 +#define F_NAMEGETEXT 243 +#define F_NAMEGTTEXT 244 +#define F_NAMENETEXT 245 +#define F_BTNAMETEXTCMP 246 +#define F_TEXTEQNAME 247 +#define F_TEXTLTNAME 248 +#define F_TEXTLENAME 249 +#define F_TEXTGENAME 250 +#define F_TEXTGTNAME 251 +#define F_TEXTNENAME 252 +#define F_BTTEXTNAMECMP 253 +#define F_NAMECONCATOID 266 +#define F_TABLE_AM_HANDLER_IN 267 +#define F_TABLE_AM_HANDLER_OUT 268 +#define F_TIMEOFDAY 274 +#define F_PG_NEXTOID 275 +#define F_FLOAT8_COMBINE 276 +#define F_INTER_SL 277 +#define F_INTER_LB 278 +#define F_FLOAT48MUL 279 +#define F_FLOAT48DIV 280 +#define F_FLOAT48PL 281 +#define F_FLOAT48MI 282 +#define F_FLOAT84MUL 283 +#define F_FLOAT84DIV 284 +#define F_FLOAT84PL 285 +#define F_FLOAT84MI 286 +#define F_FLOAT4EQ 287 +#define F_FLOAT4NE 288 +#define F_FLOAT4LT 289 +#define F_FLOAT4LE 290 +#define F_FLOAT4GT 291 +#define F_FLOAT4GE 292 +#define F_FLOAT8EQ 293 +#define F_FLOAT8NE 294 +#define F_FLOAT8LT 295 +#define F_FLOAT8LE 296 +#define F_FLOAT8GT 297 +#define F_FLOAT8GE 298 +#define F_FLOAT48EQ 299 +#define F_FLOAT48NE 300 +#define F_FLOAT48LT 301 +#define F_FLOAT48LE 302 +#define F_FLOAT48GT 303 +#define F_FLOAT48GE 304 +#define F_FLOAT84EQ 305 +#define F_FLOAT84NE 306 +#define F_FLOAT84LT 307 +#define F_FLOAT84LE 308 +#define F_FLOAT84GT 309 +#define F_FLOAT84GE 310 +#define F_FLOAT8_FLOAT4 311 +#define F_FLOAT4_FLOAT8 312 +#define F_INT4_INT2 313 +#define F_INT2_INT4 314 +#define F_PG_JIT_AVAILABLE 315 +#define F_FLOAT8_INT4 316 +#define F_INT4_FLOAT8 317 +#define F_FLOAT4_INT4 318 +#define F_INT4_FLOAT4 319 +#define F_WIDTH_BUCKET_FLOAT8_FLOAT8_FLOAT8_INT4 320 +#define F_JSON_IN 321 +#define F_JSON_OUT 322 +#define F_JSON_RECV 323 +#define F_JSON_SEND 324 +#define F_INDEX_AM_HANDLER_IN 326 +#define F_INDEX_AM_HANDLER_OUT 327 +#define F_HASHMACADDR8 328 +#define F_HASH_ACLITEM 329 +#define F_BTHANDLER 330 +#define F_HASHHANDLER 331 +#define F_GISTHANDLER 332 +#define F_GINHANDLER 333 +#define F_SPGHANDLER 334 +#define F_BRINHANDLER 335 +#define F_SCALARLESEL 336 +#define F_SCALARGESEL 337 +#define F_AMVALIDATE 338 +#define F_POLY_SAME 339 +#define F_POLY_CONTAIN 340 +#define F_POLY_LEFT 341 +#define F_POLY_OVERLEFT 342 +#define F_POLY_OVERRIGHT 343 +#define F_POLY_RIGHT 344 +#define F_POLY_CONTAINED 345 +#define F_POLY_OVERLAP 346 +#define F_POLY_IN 347 +#define F_POLY_OUT 348 +#define F_BTINT2CMP 350 +#define F_BTINT4CMP 351 +#define F_BTFLOAT4CMP 354 +#define F_BTFLOAT8CMP 355 +#define F_BTOIDCMP 356 +#define F_DIST_BP 357 +#define F_BTCHARCMP 358 +#define F_BTNAMECMP 359 +#define F_BTTEXTCMP 360 +#define F_LSEG_DISTANCE 361 +#define F_LSEG_INTERPT 362 +#define F_DIST_PS 363 +#define F_DIST_PB 364 +#define F_DIST_SB 365 +#define F_CLOSE_PS 366 +#define F_CLOSE_PB 367 +#define F_CLOSE_SB 368 +#define F_ON_PS 369 +#define F_PATH_DISTANCE 370 +#define F_DIST_PPATH 371 +#define F_ON_SB 372 +#define F_INTER_SB 373 +#define F_STRING_TO_ARRAY_TEXT_TEXT_TEXT 376 +#define F_CASH_CMP 377 +#define F_ARRAY_APPEND 378 +#define F_ARRAY_PREPEND 379 +#define F_DIST_SP 380 +#define F_DIST_BS 381 +#define F_BTARRAYCMP 382 +#define F_ARRAY_CAT 383 +#define F_ARRAY_TO_STRING_ANYARRAY_TEXT_TEXT 384 +#define F_SCALARLEJOINSEL 386 +#define F_ARRAY_NE 390 +#define F_ARRAY_LT 391 +#define F_ARRAY_GT 392 +#define F_ARRAY_LE 393 +#define F_STRING_TO_ARRAY_TEXT_TEXT 394 +#define F_ARRAY_TO_STRING_ANYARRAY_TEXT 395 +#define F_ARRAY_GE 396 +#define F_SCALARGEJOINSEL 398 +#define F_HASHMACADDR 399 +#define F_HASHTEXT 400 +#define F_TEXT_BPCHAR 401 +#define F_BTOIDVECTORCMP 404 +#define F_TEXT_NAME 406 +#define F_NAME_TEXT 407 +#define F_BPCHAR_NAME 408 +#define F_NAME_BPCHAR 409 +#define F_DIST_PATHP 421 +#define F_HASHINET 422 +#define F_HASHINT4EXTENDED 425 +#define F_HASH_NUMERIC 432 +#define F_MACADDR_IN 436 +#define F_MACADDR_OUT 437 +#define F_NUM_NULLS 438 +#define F_NUM_NONNULLS 440 +#define F_HASHINT2EXTENDED 441 +#define F_HASHINT8EXTENDED 442 +#define F_HASHFLOAT4EXTENDED 443 +#define F_HASHFLOAT8EXTENDED 444 +#define F_HASHOIDEXTENDED 445 +#define F_HASHCHAREXTENDED 446 +#define F_HASHNAMEEXTENDED 447 +#define F_HASHTEXTEXTENDED 448 +#define F_HASHINT2 449 +#define F_HASHINT4 450 +#define F_HASHFLOAT4 451 +#define F_HASHFLOAT8 452 +#define F_HASHOID 453 +#define F_HASHCHAR 454 +#define F_HASHNAME 455 +#define F_HASHVARLENA 456 +#define F_HASHOIDVECTOR 457 +#define F_TEXT_LARGER 458 +#define F_TEXT_SMALLER 459 +#define F_INT8IN 460 +#define F_INT8OUT 461 +#define F_INT8UM 462 +#define F_INT8PL 463 +#define F_INT8MI 464 +#define F_INT8MUL 465 +#define F_INT8DIV 466 +#define F_INT8EQ 467 +#define F_INT8NE 468 +#define F_INT8LT 469 +#define F_INT8GT 470 +#define F_INT8LE 471 +#define F_INT8GE 472 +#define F_INT84EQ 474 +#define F_INT84NE 475 +#define F_INT84LT 476 +#define F_INT84GT 477 +#define F_INT84LE 478 +#define F_INT84GE 479 +#define F_INT4_INT8 480 +#define F_INT8_INT4 481 +#define F_FLOAT8_INT8 482 +#define F_INT8_FLOAT8 483 +#define F_ARRAY_LARGER 515 +#define F_ARRAY_SMALLER 516 +#define F_ABBREV_INET 598 +#define F_ABBREV_CIDR 599 +#define F_SET_MASKLEN_INET_INT4 605 +#define F_OIDVECTORNE 619 +#define F_HASH_ARRAY 626 +#define F_SET_MASKLEN_CIDR_INT4 635 +#define F_PG_INDEXAM_HAS_PROPERTY 636 +#define F_PG_INDEX_HAS_PROPERTY 637 +#define F_PG_INDEX_COLUMN_HAS_PROPERTY 638 +#define F_FLOAT4_INT8 652 +#define F_INT8_FLOAT4 653 +#define F_NAMELT 655 +#define F_NAMELE 656 +#define F_NAMEGT 657 +#define F_NAMEGE 658 +#define F_NAMENE 659 +#define F_BPCHAR_BPCHAR_INT4_BOOL 668 +#define F_VARCHAR_VARCHAR_INT4_BOOL 669 +#define F_PG_INDEXAM_PROGRESS_PHASENAME 676 +#define F_OIDVECTORLT 677 +#define F_OIDVECTORLE 678 +#define F_OIDVECTOREQ 679 +#define F_OIDVECTORGE 680 +#define F_OIDVECTORGT 681 +#define F_NETWORK 683 +#define F_NETMASK 696 +#define F_MASKLEN 697 +#define F_BROADCAST 698 +#define F_HOST 699 +#define F_DIST_LP 702 +#define F_DIST_LS 704 +#define F_GETPGUSERNAME 710 +#define F_FAMILY 711 +#define F_INT2_INT8 714 +#define F_LO_CREATE 715 +#define F_OIDLT 716 +#define F_OIDLE 717 +#define F_OCTET_LENGTH_BYTEA 720 +#define F_GET_BYTE 721 +#define F_SET_BYTE 722 +#define F_GET_BIT_BYTEA_INT8 723 +#define F_SET_BIT_BYTEA_INT8_INT4 724 +#define F_DIST_PL 725 +#define F_DIST_SL 727 +#define F_DIST_CPOLY 728 +#define F_POLY_DISTANCE 729 +#define F_TEXT_INET 730 +#define F_TEXT_LT 740 +#define F_TEXT_LE 741 +#define F_TEXT_GT 742 +#define F_TEXT_GE 743 +#define F_ARRAY_EQ 744 +#define F_CURRENT_USER 745 +#define F_SESSION_USER 746 +#define F_ARRAY_DIMS 747 +#define F_ARRAY_NDIMS 748 +#define F_OVERLAY_BYTEA_BYTEA_INT4_INT4 749 +#define F_ARRAY_IN 750 +#define F_ARRAY_OUT 751 +#define F_OVERLAY_BYTEA_BYTEA_INT4 752 +#define F_TRUNC_MACADDR 753 +#define F_INT8_INT2 754 +#define F_LO_IMPORT_TEXT 764 +#define F_LO_EXPORT 765 +#define F_INT4INC 766 +#define F_LO_IMPORT_TEXT_OID 767 +#define F_INT4LARGER 768 +#define F_INT4SMALLER 769 +#define F_INT2LARGER 770 +#define F_INT2SMALLER 771 +#define F_HASHVARLENAEXTENDED 772 +#define F_HASHOIDVECTOREXTENDED 776 +#define F_HASH_ACLITEM_EXTENDED 777 +#define F_HASHMACADDREXTENDED 778 +#define F_HASHINETEXTENDED 779 +#define F_HASH_NUMERIC_EXTENDED 780 +#define F_HASHMACADDR8EXTENDED 781 +#define F_HASH_ARRAY_EXTENDED 782 +#define F_DIST_POLYC 785 +#define F_PG_CLIENT_ENCODING 810 +#define F_CURRENT_QUERY 817 +#define F_MACADDR_EQ 830 +#define F_MACADDR_LT 831 +#define F_MACADDR_LE 832 +#define F_MACADDR_GT 833 +#define F_MACADDR_GE 834 +#define F_MACADDR_NE 835 +#define F_MACADDR_CMP 836 +#define F_INT82PL 837 +#define F_INT82MI 838 +#define F_INT82MUL 839 +#define F_INT82DIV 840 +#define F_INT28PL 841 +#define F_BTINT8CMP 842 +#define F_CASH_MUL_FLT4 846 +#define F_CASH_DIV_FLT4 847 +#define F_FLT4_MUL_CASH 848 +#define F_POSITION_TEXT_TEXT 849 +#define F_TEXTLIKE 850 +#define F_TEXTNLIKE 851 +#define F_INT48EQ 852 +#define F_INT48NE 853 +#define F_INT48LT 854 +#define F_INT48GT 855 +#define F_INT48LE 856 +#define F_INT48GE 857 +#define F_NAMELIKE 858 +#define F_NAMENLIKE 859 +#define F_BPCHAR_CHAR 860 +#define F_CURRENT_DATABASE 861 +#define F_INT4_MUL_CASH 862 +#define F_INT2_MUL_CASH 863 +#define F_CASH_MUL_INT4 864 +#define F_CASH_DIV_INT4 865 +#define F_CASH_MUL_INT2 866 +#define F_CASH_DIV_INT2 867 +#define F_STRPOS 868 +#define F_LOWER_TEXT 870 +#define F_UPPER_TEXT 871 +#define F_INITCAP 872 +#define F_LPAD_TEXT_INT4_TEXT 873 +#define F_RPAD_TEXT_INT4_TEXT 874 +#define F_LTRIM_TEXT_TEXT 875 +#define F_RTRIM_TEXT_TEXT 876 +#define F_SUBSTR_TEXT_INT4_INT4 877 +#define F_TRANSLATE 878 +#define F_LPAD_TEXT_INT4 879 +#define F_RPAD_TEXT_INT4 880 +#define F_LTRIM_TEXT 881 +#define F_RTRIM_TEXT 882 +#define F_SUBSTR_TEXT_INT4 883 +#define F_BTRIM_TEXT_TEXT 884 +#define F_BTRIM_TEXT 885 +#define F_CASH_IN 886 +#define F_CASH_OUT 887 +#define F_CASH_EQ 888 +#define F_CASH_NE 889 +#define F_CASH_LT 890 +#define F_CASH_LE 891 +#define F_CASH_GT 892 +#define F_CASH_GE 893 +#define F_CASH_PL 894 +#define F_CASH_MI 895 +#define F_CASH_MUL_FLT8 896 +#define F_CASH_DIV_FLT8 897 +#define F_CASHLARGER 898 +#define F_CASHSMALLER 899 +#define F_INET_IN 910 +#define F_INET_OUT 911 +#define F_FLT8_MUL_CASH 919 +#define F_NETWORK_EQ 920 +#define F_NETWORK_LT 921 +#define F_NETWORK_LE 922 +#define F_NETWORK_GT 923 +#define F_NETWORK_GE 924 +#define F_NETWORK_NE 925 +#define F_NETWORK_CMP 926 +#define F_NETWORK_SUB 927 +#define F_NETWORK_SUBEQ 928 +#define F_NETWORK_SUP 929 +#define F_NETWORK_SUPEQ 930 +#define F_CASH_WORDS 935 +#define F_SUBSTRING_TEXT_INT4_INT4 936 +#define F_SUBSTRING_TEXT_INT4 937 +#define F_GENERATE_SERIES_TIMESTAMP_TIMESTAMP_INTERVAL 938 +#define F_GENERATE_SERIES_TIMESTAMPTZ_TIMESTAMPTZ_INTERVAL 939 +#define F_MOD_INT2_INT2 940 +#define F_MOD_INT4_INT4 941 +#define F_INT28MI 942 +#define F_INT28MUL 943 +#define F_CHAR_TEXT 944 +#define F_INT8MOD 945 +#define F_TEXT_CHAR 946 +#define F_MOD_INT8_INT8 947 +#define F_INT28DIV 948 +#define F_HASHINT8 949 +#define F_LO_OPEN 952 +#define F_LO_CLOSE 953 +#define F_LOREAD 954 +#define F_LOWRITE 955 +#define F_LO_LSEEK 956 +#define F_LO_CREAT 957 +#define F_LO_TELL 958 +#define F_ON_PL 959 +#define F_ON_SL 960 +#define F_CLOSE_PL 961 +#define F_LO_UNLINK 964 +#define F_HASHBPCHAREXTENDED 972 +#define F_PATH_INTER 973 +#define F_AREA_BOX 975 +#define F_WIDTH 976 +#define F_HEIGHT 977 +#define F_BOX_DISTANCE 978 +#define F_AREA_PATH 979 +#define F_BOX_INTERSECT 980 +#define F_DIAGONAL 981 +#define F_PATH_N_LT 982 +#define F_PATH_N_GT 983 +#define F_PATH_N_EQ 984 +#define F_PATH_N_LE 985 +#define F_PATH_N_GE 986 +#define F_PATH_LENGTH 987 +#define F_POINT_NE 988 +#define F_POINT_VERT 989 +#define F_POINT_HORIZ 990 +#define F_POINT_DISTANCE 991 +#define F_SLOPE 992 +#define F_LSEG_POINT_POINT 993 +#define F_LSEG_INTERSECT 994 +#define F_LSEG_PARALLEL 995 +#define F_LSEG_PERP 996 +#define F_LSEG_VERTICAL 997 +#define F_LSEG_HORIZONTAL 998 +#define F_LSEG_EQ 999 +#define F_LO_TRUNCATE 1004 +#define F_TEXTLIKE_SUPPORT 1023 +#define F_TEXTICREGEXEQ_SUPPORT 1024 +#define F_TEXTICLIKE_SUPPORT 1025 +#define F_TIMEZONE_INTERVAL_TIMESTAMPTZ 1026 +#define F_GIST_POINT_COMPRESS 1030 +#define F_ACLITEMIN 1031 +#define F_ACLITEMOUT 1032 +#define F_ACLINSERT 1035 +#define F_ACLREMOVE 1036 +#define F_ACLCONTAINS 1037 +#define F_GETDATABASEENCODING 1039 +#define F_BPCHARIN 1044 +#define F_BPCHAROUT 1045 +#define F_VARCHARIN 1046 +#define F_VARCHAROUT 1047 +#define F_BPCHAREQ 1048 +#define F_BPCHARLT 1049 +#define F_BPCHARLE 1050 +#define F_BPCHARGT 1051 +#define F_BPCHARGE 1052 +#define F_BPCHARNE 1053 +#define F_ACLITEMEQ 1062 +#define F_BPCHAR_LARGER 1063 +#define F_BPCHAR_SMALLER 1064 +#define F_PG_PREPARED_XACT 1065 +#define F_GENERATE_SERIES_INT4_INT4_INT4 1066 +#define F_GENERATE_SERIES_INT4_INT4 1067 +#define F_GENERATE_SERIES_INT8_INT8_INT8 1068 +#define F_GENERATE_SERIES_INT8_INT8 1069 +#define F_BPCHARCMP 1078 +#define F_REGCLASS 1079 +#define F_HASHBPCHAR 1080 +#define F_FORMAT_TYPE 1081 +#define F_DATE_IN 1084 +#define F_DATE_OUT 1085 +#define F_DATE_EQ 1086 +#define F_DATE_LT 1087 +#define F_DATE_LE 1088 +#define F_DATE_GT 1089 +#define F_DATE_GE 1090 +#define F_DATE_NE 1091 +#define F_DATE_CMP 1092 +#define F_TIME_LT 1102 +#define F_TIME_LE 1103 +#define F_TIME_GT 1104 +#define F_TIME_GE 1105 +#define F_TIME_NE 1106 +#define F_TIME_CMP 1107 +#define F_PG_STAT_GET_WAL 1136 +#define F_PG_GET_WAL_REPLAY_PAUSE_STATE 1137 +#define F_DATE_LARGER 1138 +#define F_DATE_SMALLER 1139 +#define F_DATE_MI 1140 +#define F_DATE_PLI 1141 +#define F_DATE_MII 1142 +#define F_TIME_IN 1143 +#define F_TIME_OUT 1144 +#define F_TIME_EQ 1145 +#define F_CIRCLE_ADD_PT 1146 +#define F_CIRCLE_SUB_PT 1147 +#define F_CIRCLE_MUL_PT 1148 +#define F_CIRCLE_DIV_PT 1149 +#define F_TIMESTAMPTZ_IN 1150 +#define F_TIMESTAMPTZ_OUT 1151 +#define F_TIMESTAMPTZ_EQ 1152 +#define F_TIMESTAMPTZ_NE 1153 +#define F_TIMESTAMPTZ_LT 1154 +#define F_TIMESTAMPTZ_LE 1155 +#define F_TIMESTAMPTZ_GE 1156 +#define F_TIMESTAMPTZ_GT 1157 +#define F_TO_TIMESTAMP_FLOAT8 1158 +#define F_TIMEZONE_TEXT_TIMESTAMPTZ 1159 +#define F_INTERVAL_IN 1160 +#define F_INTERVAL_OUT 1161 +#define F_INTERVAL_EQ 1162 +#define F_INTERVAL_NE 1163 +#define F_INTERVAL_LT 1164 +#define F_INTERVAL_LE 1165 +#define F_INTERVAL_GE 1166 +#define F_INTERVAL_GT 1167 +#define F_INTERVAL_UM 1168 +#define F_INTERVAL_PL 1169 +#define F_INTERVAL_MI 1170 +#define F_DATE_PART_TEXT_TIMESTAMPTZ 1171 +#define F_DATE_PART_TEXT_INTERVAL 1172 +#define F_NETWORK_SUBSET_SUPPORT 1173 +#define F_TIMESTAMPTZ_DATE 1174 +#define F_JUSTIFY_HOURS 1175 +#define F_TIMESTAMPTZ_DATE_TIME 1176 +#define F_JSONB_PATH_EXISTS_TZ 1177 +#define F_DATE_TIMESTAMPTZ 1178 +#define F_JSONB_PATH_QUERY_TZ 1179 +#define F_JSONB_PATH_QUERY_ARRAY_TZ 1180 +#define F_AGE_XID 1181 +#define F_TIMESTAMPTZ_MI 1188 +#define F_TIMESTAMPTZ_PL_INTERVAL 1189 +#define F_TIMESTAMPTZ_MI_INTERVAL 1190 +#define F_GENERATE_SUBSCRIPTS_ANYARRAY_INT4_BOOL 1191 +#define F_GENERATE_SUBSCRIPTS_ANYARRAY_INT4 1192 +#define F_ARRAY_FILL_ANYELEMENT__INT4 1193 +#define F_LOG10_FLOAT8 1194 +#define F_TIMESTAMPTZ_SMALLER 1195 +#define F_TIMESTAMPTZ_LARGER 1196 +#define F_INTERVAL_SMALLER 1197 +#define F_INTERVAL_LARGER 1198 +#define F_AGE_TIMESTAMPTZ_TIMESTAMPTZ 1199 +#define F_INTERVAL_INTERVAL_INT4 1200 +#define F_OBJ_DESCRIPTION_OID_NAME 1215 +#define F_COL_DESCRIPTION 1216 +#define F_DATE_TRUNC_TEXT_TIMESTAMPTZ 1217 +#define F_DATE_TRUNC_TEXT_INTERVAL 1218 +#define F_INT8INC 1219 +#define F_INT8ABS 1230 +#define F_INT8LARGER 1236 +#define F_INT8SMALLER 1237 +#define F_TEXTICREGEXEQ 1238 +#define F_TEXTICREGEXNE 1239 +#define F_NAMEICREGEXEQ 1240 +#define F_NAMEICREGEXNE 1241 +#define F_BOOLIN 1242 +#define F_BOOLOUT 1243 +#define F_BYTEAIN 1244 +#define F_CHARIN 1245 +#define F_CHARLT 1246 +#define F_UNIQUE_KEY_RECHECK 1250 +#define F_INT4ABS 1251 +#define F_NAMEREGEXNE 1252 +#define F_INT2ABS 1253 +#define F_TEXTREGEXEQ 1254 +#define F_TEXTREGEXNE 1256 +#define F_TEXTLEN 1257 +#define F_TEXTCAT 1258 +#define F_PG_CHAR_TO_ENCODING 1264 +#define F_TIDNE 1265 +#define F_CIDR_IN 1267 +#define F_PARSE_IDENT 1268 +#define F_PG_COLUMN_SIZE 1269 +#define F_OVERLAPS_TIMETZ_TIMETZ_TIMETZ_TIMETZ 1271 +#define F_DATETIME_PL 1272 +#define F_DATE_PART_TEXT_TIMETZ 1273 +#define F_INT84PL 1274 +#define F_INT84MI 1275 +#define F_INT84MUL 1276 +#define F_INT84DIV 1277 +#define F_INT48PL 1278 +#define F_INT48MI 1279 +#define F_INT48MUL 1280 +#define F_INT48DIV 1281 +#define F_QUOTE_IDENT 1282 +#define F_QUOTE_LITERAL_TEXT 1283 +#define F_DATE_TRUNC_TEXT_TIMESTAMPTZ_TEXT 1284 +#define F_QUOTE_LITERAL_ANYELEMENT 1285 +#define F_ARRAY_FILL_ANYELEMENT__INT4__INT4 1286 +#define F_OID 1287 +#define F_INT8_OID 1288 +#define F_QUOTE_NULLABLE_TEXT 1289 +#define F_QUOTE_NULLABLE_ANYELEMENT 1290 +#define F_SUPPRESS_REDUNDANT_UPDATES_TRIGGER 1291 +#define F_TIDEQ 1292 +#define F_UNNEST_ANYMULTIRANGE 1293 +#define F_CURRTID2 1294 +#define F_JUSTIFY_DAYS 1295 +#define F_TIMEDATE_PL 1296 +#define F_DATETIMETZ_PL 1297 +#define F_TIMETZDATE_PL 1298 +#define F_NOW 1299 +#define F_POSITIONSEL 1300 +#define F_POSITIONJOINSEL 1301 +#define F_CONTSEL 1302 +#define F_CONTJOINSEL 1303 +#define F_OVERLAPS_TIMESTAMPTZ_TIMESTAMPTZ_TIMESTAMPTZ_TIMESTAMPTZ 1304 +#define F_OVERLAPS_TIMESTAMPTZ_INTERVAL_TIMESTAMPTZ_INTERVAL 1305 +#define F_OVERLAPS_TIMESTAMPTZ_TIMESTAMPTZ_TIMESTAMPTZ_INTERVAL 1306 +#define F_OVERLAPS_TIMESTAMPTZ_INTERVAL_TIMESTAMPTZ_TIMESTAMPTZ 1307 +#define F_OVERLAPS_TIME_TIME_TIME_TIME 1308 +#define F_OVERLAPS_TIME_INTERVAL_TIME_INTERVAL 1309 +#define F_OVERLAPS_TIME_TIME_TIME_INTERVAL 1310 +#define F_OVERLAPS_TIME_INTERVAL_TIME_TIME 1311 +#define F_TIMESTAMP_IN 1312 +#define F_TIMESTAMP_OUT 1313 +#define F_TIMESTAMPTZ_CMP 1314 +#define F_INTERVAL_CMP 1315 +#define F_TIME_TIMESTAMP 1316 +#define F_LENGTH_TEXT 1317 +#define F_LENGTH_BPCHAR 1318 +#define F_XIDEQINT4 1319 +#define F_INTERVAL_DIV 1326 +#define F_DLOG10 1339 +#define F_LOG_FLOAT8 1340 +#define F_LN_FLOAT8 1341 +#define F_ROUND_FLOAT8 1342 +#define F_TRUNC_FLOAT8 1343 +#define F_SQRT_FLOAT8 1344 +#define F_CBRT 1345 +#define F_POW_FLOAT8_FLOAT8 1346 +#define F_EXP_FLOAT8 1347 +#define F_OBJ_DESCRIPTION_OID 1348 +#define F_OIDVECTORTYPES 1349 +#define F_TIMETZ_IN 1350 +#define F_TIMETZ_OUT 1351 +#define F_TIMETZ_EQ 1352 +#define F_TIMETZ_NE 1353 +#define F_TIMETZ_LT 1354 +#define F_TIMETZ_LE 1355 +#define F_TIMETZ_GE 1356 +#define F_TIMETZ_GT 1357 +#define F_TIMETZ_CMP 1358 +#define F_TIMESTAMPTZ_DATE_TIMETZ 1359 +#define F_HOSTMASK 1362 +#define F_TEXTREGEXEQ_SUPPORT 1364 +#define F_MAKEACLITEM 1365 +#define F_CHARACTER_LENGTH_BPCHAR 1367 +#define F_POWER_FLOAT8_FLOAT8 1368 +#define F_CHARACTER_LENGTH_TEXT 1369 +#define F_INTERVAL_TIME 1370 +#define F_PG_LOCK_STATUS 1371 +#define F_CHAR_LENGTH_BPCHAR 1372 +#define F_ISFINITE_DATE 1373 +#define F_OCTET_LENGTH_TEXT 1374 +#define F_OCTET_LENGTH_BPCHAR 1375 +#define F_FACTORIAL 1376 +#define F_TIME_LARGER 1377 +#define F_TIME_SMALLER 1378 +#define F_TIMETZ_LARGER 1379 +#define F_TIMETZ_SMALLER 1380 +#define F_CHAR_LENGTH_TEXT 1381 +#define F_DATE_PART_TEXT_DATE 1384 +#define F_DATE_PART_TEXT_TIME 1385 +#define F_AGE_TIMESTAMPTZ 1386 +#define F_PG_GET_CONSTRAINTDEF_OID 1387 +#define F_TIMETZ_TIMESTAMPTZ 1388 +#define F_ISFINITE_TIMESTAMPTZ 1389 +#define F_ISFINITE_INTERVAL 1390 +#define F_PG_STAT_GET_BACKEND_START 1391 +#define F_PG_STAT_GET_BACKEND_CLIENT_ADDR 1392 +#define F_PG_STAT_GET_BACKEND_CLIENT_PORT 1393 +#define F_ABS_FLOAT4 1394 +#define F_ABS_FLOAT8 1395 +#define F_ABS_INT8 1396 +#define F_ABS_INT4 1397 +#define F_ABS_INT2 1398 +#define F_NAME_VARCHAR 1400 +#define F_VARCHAR_NAME 1401 +#define F_CURRENT_SCHEMA 1402 +#define F_CURRENT_SCHEMAS 1403 +#define F_OVERLAY_TEXT_TEXT_INT4_INT4 1404 +#define F_OVERLAY_TEXT_TEXT_INT4 1405 +#define F_ISVERTICAL_POINT_POINT 1406 +#define F_ISHORIZONTAL_POINT_POINT 1407 +#define F_ISPARALLEL_LSEG_LSEG 1408 +#define F_ISPERP_LSEG_LSEG 1409 +#define F_ISVERTICAL_LSEG 1410 +#define F_ISHORIZONTAL_LSEG 1411 +#define F_ISPARALLEL_LINE_LINE 1412 +#define F_ISPERP_LINE_LINE 1413 +#define F_ISVERTICAL_LINE 1414 +#define F_ISHORIZONTAL_LINE 1415 +#define F_POINT_CIRCLE 1416 +#define F_TIME_INTERVAL 1419 +#define F_BOX_POINT_POINT 1421 +#define F_BOX_ADD 1422 +#define F_BOX_SUB 1423 +#define F_BOX_MUL 1424 +#define F_BOX_DIV 1425 +#define F_PATH_CONTAIN_PT 1426 +#define F_CIDR_OUT 1427 +#define F_POLY_CONTAIN_PT 1428 +#define F_PT_CONTAINED_POLY 1429 +#define F_ISCLOSED 1430 +#define F_ISOPEN 1431 +#define F_PATH_NPOINTS 1432 +#define F_PCLOSE 1433 +#define F_POPEN 1434 +#define F_PATH_ADD 1435 +#define F_PATH_ADD_PT 1436 +#define F_PATH_SUB_PT 1437 +#define F_PATH_MUL_PT 1438 +#define F_PATH_DIV_PT 1439 +#define F_POINT_FLOAT8_FLOAT8 1440 +#define F_POINT_ADD 1441 +#define F_POINT_SUB 1442 +#define F_POINT_MUL 1443 +#define F_POINT_DIV 1444 +#define F_POLY_NPOINTS 1445 +#define F_BOX_POLYGON 1446 +#define F_PATH 1447 +#define F_POLYGON_BOX 1448 +#define F_POLYGON_PATH 1449 +#define F_CIRCLE_IN 1450 +#define F_CIRCLE_OUT 1451 +#define F_CIRCLE_SAME 1452 +#define F_CIRCLE_CONTAIN 1453 +#define F_CIRCLE_LEFT 1454 +#define F_CIRCLE_OVERLEFT 1455 +#define F_CIRCLE_OVERRIGHT 1456 +#define F_CIRCLE_RIGHT 1457 +#define F_CIRCLE_CONTAINED 1458 +#define F_CIRCLE_OVERLAP 1459 +#define F_CIRCLE_BELOW 1460 +#define F_CIRCLE_ABOVE 1461 +#define F_CIRCLE_EQ 1462 +#define F_CIRCLE_NE 1463 +#define F_CIRCLE_LT 1464 +#define F_CIRCLE_GT 1465 +#define F_CIRCLE_LE 1466 +#define F_CIRCLE_GE 1467 +#define F_AREA_CIRCLE 1468 +#define F_DIAMETER 1469 +#define F_RADIUS 1470 +#define F_CIRCLE_DISTANCE 1471 +#define F_CIRCLE_CENTER 1472 +#define F_CIRCLE_POINT_FLOAT8 1473 +#define F_CIRCLE_POLYGON 1474 +#define F_POLYGON_INT4_CIRCLE 1475 +#define F_DIST_PC 1476 +#define F_CIRCLE_CONTAIN_PT 1477 +#define F_PT_CONTAINED_CIRCLE 1478 +#define F_CIRCLE_BOX 1479 +#define F_BOX_CIRCLE 1480 +#define F_LOG10_NUMERIC 1481 +#define F_LSEG_NE 1482 +#define F_LSEG_LT 1483 +#define F_LSEG_LE 1484 +#define F_LSEG_GT 1485 +#define F_LSEG_GE 1486 +#define F_LSEG_LENGTH 1487 +#define F_CLOSE_LS 1488 +#define F_CLOSE_LSEG 1489 +#define F_LINE_IN 1490 +#define F_LINE_OUT 1491 +#define F_LINE_EQ 1492 +#define F_LINE 1493 +#define F_LINE_INTERPT 1494 +#define F_LINE_INTERSECT 1495 +#define F_LINE_PARALLEL 1496 +#define F_LINE_PERP 1497 +#define F_LINE_VERTICAL 1498 +#define F_LINE_HORIZONTAL 1499 +#define F_LENGTH_LSEG 1530 +#define F_LENGTH_PATH 1531 +#define F_POINT_LSEG 1532 +#define F_POINT_BOX 1534 +#define F_POINT_POLYGON 1540 +#define F_LSEG_BOX 1541 +#define F_CENTER_BOX 1542 +#define F_CENTER_CIRCLE 1543 +#define F_POLYGON_CIRCLE 1544 +#define F_NPOINTS_PATH 1545 +#define F_NPOINTS_POLYGON 1556 +#define F_BIT_IN 1564 +#define F_BIT_OUT 1565 +#define F_LIKE_TEXT_TEXT 1569 +#define F_NOTLIKE_TEXT_TEXT 1570 +#define F_LIKE_NAME_TEXT 1571 +#define F_NOTLIKE_NAME_TEXT 1572 +#define F_PG_GET_RULEDEF_OID 1573 +#define F_NEXTVAL 1574 +#define F_CURRVAL 1575 +#define F_SETVAL_REGCLASS_INT8 1576 +#define F_VARBIT_IN 1579 +#define F_VARBIT_OUT 1580 +#define F_BITEQ 1581 +#define F_BITNE 1582 +#define F_BITGE 1592 +#define F_BITGT 1593 +#define F_BITLE 1594 +#define F_BITLT 1595 +#define F_BITCMP 1596 +#define F_PG_ENCODING_TO_CHAR 1597 +#define F_RANDOM 1598 +#define F_SETSEED 1599 +#define F_ASIN 1600 +#define F_ACOS 1601 +#define F_ATAN 1602 +#define F_ATAN2 1603 +#define F_SIN 1604 +#define F_COS 1605 +#define F_TAN 1606 +#define F_COT 1607 +#define F_DEGREES 1608 +#define F_RADIANS 1609 +#define F_PI 1610 +#define F_INTERVAL_MUL 1618 +#define F_PG_TYPEOF 1619 +#define F_ASCII 1620 +#define F_CHR 1621 +#define F_REPEAT 1622 +#define F_SIMILAR_ESCAPE 1623 +#define F_MUL_D_INTERVAL 1624 +#define F_BPCHARLIKE 1631 +#define F_BPCHARNLIKE 1632 +#define F_TEXTICLIKE 1633 +#define F_TEXTICNLIKE 1634 +#define F_NAMEICLIKE 1635 +#define F_NAMEICNLIKE 1636 +#define F_LIKE_ESCAPE_TEXT_TEXT 1637 +#define F_OIDGT 1638 +#define F_OIDGE 1639 +#define F_PG_GET_VIEWDEF_TEXT 1640 +#define F_PG_GET_VIEWDEF_OID 1641 +#define F_PG_GET_USERBYID 1642 +#define F_PG_GET_INDEXDEF_OID 1643 +#define F_RI_FKEY_CHECK_INS 1644 +#define F_RI_FKEY_CHECK_UPD 1645 +#define F_RI_FKEY_CASCADE_DEL 1646 +#define F_RI_FKEY_CASCADE_UPD 1647 +#define F_RI_FKEY_RESTRICT_DEL 1648 +#define F_RI_FKEY_RESTRICT_UPD 1649 +#define F_RI_FKEY_SETNULL_DEL 1650 +#define F_RI_FKEY_SETNULL_UPD 1651 +#define F_RI_FKEY_SETDEFAULT_DEL 1652 +#define F_RI_FKEY_SETDEFAULT_UPD 1653 +#define F_RI_FKEY_NOACTION_DEL 1654 +#define F_RI_FKEY_NOACTION_UPD 1655 +#define F_BPCHARICREGEXEQ 1656 +#define F_BPCHARICREGEXNE 1657 +#define F_BPCHARREGEXEQ 1658 +#define F_BPCHARREGEXNE 1659 +#define F_BPCHARICLIKE 1660 +#define F_BPCHARICNLIKE 1661 +#define F_PG_GET_TRIGGERDEF_OID 1662 +#define F_PG_GET_SERIAL_SEQUENCE 1665 +#define F_VARBITEQ 1666 +#define F_VARBITNE 1667 +#define F_VARBITGE 1668 +#define F_VARBITGT 1669 +#define F_VARBITLE 1670 +#define F_VARBITLT 1671 +#define F_VARBITCMP 1672 +#define F_BITAND 1673 +#define F_BITOR 1674 +#define F_BITXOR 1675 +#define F_BITNOT 1676 +#define F_BITSHIFTLEFT 1677 +#define F_BITSHIFTRIGHT 1678 +#define F_BITCAT 1679 +#define F_SUBSTRING_BIT_INT4_INT4 1680 +#define F_LENGTH_BIT 1681 +#define F_OCTET_LENGTH_BIT 1682 +#define F_BIT_INT4_INT4 1683 +#define F_INT4_BIT 1684 +#define F_BIT_BIT_INT4_BOOL 1685 +#define F_PG_GET_KEYWORDS 1686 +#define F_VARBIT 1687 +#define F_TIME_HASH 1688 +#define F_ACLEXPLODE 1689 +#define F_TIME_MI_TIME 1690 +#define F_BOOLLE 1691 +#define F_BOOLGE 1692 +#define F_BTBOOLCMP 1693 +#define F_TIMETZ_HASH 1696 +#define F_INTERVAL_HASH 1697 +#define F_POSITION_BIT_BIT 1698 +#define F_SUBSTRING_BIT_INT4 1699 +#define F_NUMERIC_IN 1701 +#define F_NUMERIC_OUT 1702 +#define F_NUMERIC_NUMERIC_INT4 1703 +#define F_NUMERIC_ABS 1704 +#define F_ABS_NUMERIC 1705 +#define F_SIGN_NUMERIC 1706 +#define F_ROUND_NUMERIC_INT4 1707 +#define F_ROUND_NUMERIC 1708 +#define F_TRUNC_NUMERIC_INT4 1709 +#define F_TRUNC_NUMERIC 1710 +#define F_CEIL_NUMERIC 1711 +#define F_FLOOR_NUMERIC 1712 +#define F_LENGTH_BYTEA_NAME 1713 +#define F_CONVERT_FROM 1714 +#define F_CIDR 1715 +#define F_PG_GET_EXPR_PG_NODE_TREE_OID 1716 +#define F_CONVERT_TO 1717 +#define F_NUMERIC_EQ 1718 +#define F_NUMERIC_NE 1719 +#define F_NUMERIC_GT 1720 +#define F_NUMERIC_GE 1721 +#define F_NUMERIC_LT 1722 +#define F_NUMERIC_LE 1723 +#define F_NUMERIC_ADD 1724 +#define F_NUMERIC_SUB 1725 +#define F_NUMERIC_MUL 1726 +#define F_NUMERIC_DIV 1727 +#define F_MOD_NUMERIC_NUMERIC 1728 +#define F_NUMERIC_MOD 1729 +#define F_SQRT_NUMERIC 1730 +#define F_NUMERIC_SQRT 1731 +#define F_EXP_NUMERIC 1732 +#define F_NUMERIC_EXP 1733 +#define F_LN_NUMERIC 1734 +#define F_NUMERIC_LN 1735 +#define F_LOG_NUMERIC_NUMERIC 1736 +#define F_NUMERIC_LOG 1737 +#define F_POW_NUMERIC_NUMERIC 1738 +#define F_NUMERIC_POWER 1739 +#define F_NUMERIC_INT4 1740 +#define F_LOG_NUMERIC 1741 +#define F_NUMERIC_FLOAT4 1742 +#define F_NUMERIC_FLOAT8 1743 +#define F_INT4_NUMERIC 1744 +#define F_FLOAT4_NUMERIC 1745 +#define F_FLOAT8_NUMERIC 1746 +#define F_TIME_PL_INTERVAL 1747 +#define F_TIME_MI_INTERVAL 1748 +#define F_TIMETZ_PL_INTERVAL 1749 +#define F_TIMETZ_MI_INTERVAL 1750 +#define F_NUMERIC_INC 1764 +#define F_SETVAL_REGCLASS_INT8_BOOL 1765 +#define F_NUMERIC_SMALLER 1766 +#define F_NUMERIC_LARGER 1767 +#define F_TO_CHAR_INTERVAL_TEXT 1768 +#define F_NUMERIC_CMP 1769 +#define F_TO_CHAR_TIMESTAMPTZ_TEXT 1770 +#define F_NUMERIC_UMINUS 1771 +#define F_TO_CHAR_NUMERIC_TEXT 1772 +#define F_TO_CHAR_INT4_TEXT 1773 +#define F_TO_CHAR_INT8_TEXT 1774 +#define F_TO_CHAR_FLOAT4_TEXT 1775 +#define F_TO_CHAR_FLOAT8_TEXT 1776 +#define F_TO_NUMBER 1777 +#define F_TO_TIMESTAMP_TEXT_TEXT 1778 +#define F_INT8_NUMERIC 1779 +#define F_TO_DATE 1780 +#define F_NUMERIC_INT8 1781 +#define F_NUMERIC_INT2 1782 +#define F_INT2_NUMERIC 1783 +#define F_OIDIN 1798 +#define F_OIDOUT 1799 +#define F_BIT_LENGTH_BYTEA 1810 +#define F_BIT_LENGTH_TEXT 1811 +#define F_BIT_LENGTH_BIT 1812 +#define F_CONVERT 1813 +#define F_ICLIKESEL 1814 +#define F_ICNLIKESEL 1815 +#define F_ICLIKEJOINSEL 1816 +#define F_ICNLIKEJOINSEL 1817 +#define F_REGEXEQSEL 1818 +#define F_LIKESEL 1819 +#define F_ICREGEXEQSEL 1820 +#define F_REGEXNESEL 1821 +#define F_NLIKESEL 1822 +#define F_ICREGEXNESEL 1823 +#define F_REGEXEQJOINSEL 1824 +#define F_LIKEJOINSEL 1825 +#define F_ICREGEXEQJOINSEL 1826 +#define F_REGEXNEJOINSEL 1827 +#define F_NLIKEJOINSEL 1828 +#define F_ICREGEXNEJOINSEL 1829 +#define F_FLOAT8_AVG 1830 +#define F_FLOAT8_VAR_SAMP 1831 +#define F_FLOAT8_STDDEV_SAMP 1832 +#define F_NUMERIC_ACCUM 1833 +#define F_INT2_ACCUM 1834 +#define F_INT4_ACCUM 1835 +#define F_INT8_ACCUM 1836 +#define F_NUMERIC_AVG 1837 +#define F_NUMERIC_VAR_SAMP 1838 +#define F_NUMERIC_STDDEV_SAMP 1839 +#define F_INT2_SUM 1840 +#define F_INT4_SUM 1841 +#define F_INT8_SUM 1842 +#define F_INTERVAL_ACCUM 1843 +#define F_INTERVAL_AVG 1844 +#define F_TO_ASCII_TEXT 1845 +#define F_TO_ASCII_TEXT_INT4 1846 +#define F_TO_ASCII_TEXT_NAME 1847 +#define F_INTERVAL_PL_TIME 1848 +#define F_INT28EQ 1850 +#define F_INT28NE 1851 +#define F_INT28LT 1852 +#define F_INT28GT 1853 +#define F_INT28LE 1854 +#define F_INT28GE 1855 +#define F_INT82EQ 1856 +#define F_INT82NE 1857 +#define F_INT82LT 1858 +#define F_INT82GT 1859 +#define F_INT82LE 1860 +#define F_INT82GE 1861 +#define F_INT2AND 1892 +#define F_INT2OR 1893 +#define F_INT2XOR 1894 +#define F_INT2NOT 1895 +#define F_INT2SHL 1896 +#define F_INT2SHR 1897 +#define F_INT4AND 1898 +#define F_INT4OR 1899 +#define F_INT4XOR 1900 +#define F_INT4NOT 1901 +#define F_INT4SHL 1902 +#define F_INT4SHR 1903 +#define F_INT8AND 1904 +#define F_INT8OR 1905 +#define F_INT8XOR 1906 +#define F_INT8NOT 1907 +#define F_INT8SHL 1908 +#define F_INT8SHR 1909 +#define F_INT8UP 1910 +#define F_INT2UP 1911 +#define F_INT4UP 1912 +#define F_FLOAT4UP 1913 +#define F_FLOAT8UP 1914 +#define F_NUMERIC_UPLUS 1915 +#define F_HAS_TABLE_PRIVILEGE_NAME_TEXT_TEXT 1922 +#define F_HAS_TABLE_PRIVILEGE_NAME_OID_TEXT 1923 +#define F_HAS_TABLE_PRIVILEGE_OID_TEXT_TEXT 1924 +#define F_HAS_TABLE_PRIVILEGE_OID_OID_TEXT 1925 +#define F_HAS_TABLE_PRIVILEGE_TEXT_TEXT 1926 +#define F_HAS_TABLE_PRIVILEGE_OID_TEXT 1927 +#define F_PG_STAT_GET_NUMSCANS 1928 +#define F_PG_STAT_GET_TUPLES_RETURNED 1929 +#define F_PG_STAT_GET_TUPLES_FETCHED 1930 +#define F_PG_STAT_GET_TUPLES_INSERTED 1931 +#define F_PG_STAT_GET_TUPLES_UPDATED 1932 +#define F_PG_STAT_GET_TUPLES_DELETED 1933 +#define F_PG_STAT_GET_BLOCKS_FETCHED 1934 +#define F_PG_STAT_GET_BLOCKS_HIT 1935 +#define F_PG_STAT_GET_BACKEND_IDSET 1936 +#define F_PG_STAT_GET_BACKEND_PID 1937 +#define F_PG_STAT_GET_BACKEND_DBID 1938 +#define F_PG_STAT_GET_BACKEND_USERID 1939 +#define F_PG_STAT_GET_BACKEND_ACTIVITY 1940 +#define F_PG_STAT_GET_DB_NUMBACKENDS 1941 +#define F_PG_STAT_GET_DB_XACT_COMMIT 1942 +#define F_PG_STAT_GET_DB_XACT_ROLLBACK 1943 +#define F_PG_STAT_GET_DB_BLOCKS_FETCHED 1944 +#define F_PG_STAT_GET_DB_BLOCKS_HIT 1945 +#define F_ENCODE 1946 +#define F_DECODE 1947 +#define F_BYTEAEQ 1948 +#define F_BYTEALT 1949 +#define F_BYTEALE 1950 +#define F_BYTEAGT 1951 +#define F_BYTEAGE 1952 +#define F_BYTEANE 1953 +#define F_BYTEACMP 1954 +#define F_TIMESTAMP_TIMESTAMP_INT4 1961 +#define F_INT2_AVG_ACCUM 1962 +#define F_INT4_AVG_ACCUM 1963 +#define F_INT8_AVG 1964 +#define F_OIDLARGER 1965 +#define F_OIDSMALLER 1966 +#define F_TIMESTAMPTZ_TIMESTAMPTZ_INT4 1967 +#define F_TIME_TIME_INT4 1968 +#define F_TIMETZ_TIMETZ_INT4 1969 +#define F_PG_STAT_GET_TUPLES_HOT_UPDATED 1972 +#define F_DIV 1973 +#define F_NUMERIC_DIV_TRUNC 1980 +#define F_SIMILAR_TO_ESCAPE_TEXT_TEXT 1986 +#define F_SIMILAR_TO_ESCAPE_TEXT 1987 +#define F_SHOBJ_DESCRIPTION 1993 +#define F_TEXTANYCAT 2003 +#define F_ANYTEXTCAT 2004 +#define F_BYTEALIKE 2005 +#define F_BYTEANLIKE 2006 +#define F_LIKE_BYTEA_BYTEA 2007 +#define F_NOTLIKE_BYTEA_BYTEA 2008 +#define F_LIKE_ESCAPE_BYTEA_BYTEA 2009 +#define F_LENGTH_BYTEA 2010 +#define F_BYTEACAT 2011 +#define F_SUBSTRING_BYTEA_INT4_INT4 2012 +#define F_SUBSTRING_BYTEA_INT4 2013 +#define F_POSITION_BYTEA_BYTEA 2014 +#define F_BTRIM_BYTEA_BYTEA 2015 +#define F_TIME_TIMESTAMPTZ 2019 +#define F_DATE_TRUNC_TEXT_TIMESTAMP 2020 +#define F_DATE_PART_TEXT_TIMESTAMP 2021 +#define F_PG_STAT_GET_ACTIVITY 2022 +#define F_JSONB_PATH_QUERY_FIRST_TZ 2023 +#define F_TIMESTAMP_DATE 2024 +#define F_TIMESTAMP_DATE_TIME 2025 +#define F_PG_BACKEND_PID 2026 +#define F_TIMESTAMP_TIMESTAMPTZ 2027 +#define F_TIMESTAMPTZ_TIMESTAMP 2028 +#define F_DATE_TIMESTAMP 2029 +#define F_JSONB_PATH_MATCH_TZ 2030 +#define F_TIMESTAMP_MI 2031 +#define F_TIMESTAMP_PL_INTERVAL 2032 +#define F_TIMESTAMP_MI_INTERVAL 2033 +#define F_PG_CONF_LOAD_TIME 2034 +#define F_TIMESTAMP_SMALLER 2035 +#define F_TIMESTAMP_LARGER 2036 +#define F_TIMEZONE_TEXT_TIMETZ 2037 +#define F_TIMEZONE_INTERVAL_TIMETZ 2038 +#define F_TIMESTAMP_HASH 2039 +#define F_OVERLAPS_TIMESTAMP_TIMESTAMP_TIMESTAMP_TIMESTAMP 2041 +#define F_OVERLAPS_TIMESTAMP_INTERVAL_TIMESTAMP_INTERVAL 2042 +#define F_OVERLAPS_TIMESTAMP_TIMESTAMP_TIMESTAMP_INTERVAL 2043 +#define F_OVERLAPS_TIMESTAMP_INTERVAL_TIMESTAMP_TIMESTAMP 2044 +#define F_TIMESTAMP_CMP 2045 +#define F_TIME_TIMETZ 2046 +#define F_TIMETZ_TIME 2047 +#define F_ISFINITE_TIMESTAMP 2048 +#define F_TO_CHAR_TIMESTAMP_TEXT 2049 +#define F_MAX_ANYARRAY 2050 +#define F_MIN_ANYARRAY 2051 +#define F_TIMESTAMP_EQ 2052 +#define F_TIMESTAMP_NE 2053 +#define F_TIMESTAMP_LT 2054 +#define F_TIMESTAMP_LE 2055 +#define F_TIMESTAMP_GE 2056 +#define F_TIMESTAMP_GT 2057 +#define F_AGE_TIMESTAMP_TIMESTAMP 2058 +#define F_AGE_TIMESTAMP 2059 +#define F_TIMEZONE_TEXT_TIMESTAMP 2069 +#define F_TIMEZONE_INTERVAL_TIMESTAMP 2070 +#define F_DATE_PL_INTERVAL 2071 +#define F_DATE_MI_INTERVAL 2072 +#define F_SUBSTRING_TEXT_TEXT 2073 +#define F_SUBSTRING_TEXT_TEXT_TEXT 2074 +#define F_BIT_INT8_INT4 2075 +#define F_INT8_BIT 2076 +#define F_CURRENT_SETTING_TEXT 2077 +#define F_SET_CONFIG 2078 +#define F_PG_TABLE_IS_VISIBLE 2079 +#define F_PG_TYPE_IS_VISIBLE 2080 +#define F_PG_FUNCTION_IS_VISIBLE 2081 +#define F_PG_OPERATOR_IS_VISIBLE 2082 +#define F_PG_OPCLASS_IS_VISIBLE 2083 +#define F_PG_SHOW_ALL_SETTINGS 2084 +#define F_SUBSTR_BYTEA_INT4_INT4 2085 +#define F_SUBSTR_BYTEA_INT4 2086 +#define F_REPLACE 2087 +#define F_SPLIT_PART 2088 +#define F_TO_HEX_INT4 2089 +#define F_TO_HEX_INT8 2090 +#define F_ARRAY_LOWER 2091 +#define F_ARRAY_UPPER 2092 +#define F_PG_CONVERSION_IS_VISIBLE 2093 +#define F_PG_STAT_GET_BACKEND_ACTIVITY_START 2094 +#define F_PG_TERMINATE_BACKEND 2096 +#define F_PG_GET_FUNCTIONDEF 2098 +#define F_AVG_INT8 2100 +#define F_AVG_INT4 2101 +#define F_AVG_INT2 2102 +#define F_AVG_NUMERIC 2103 +#define F_AVG_FLOAT4 2104 +#define F_AVG_FLOAT8 2105 +#define F_AVG_INTERVAL 2106 +#define F_SUM_INT8 2107 +#define F_SUM_INT4 2108 +#define F_SUM_INT2 2109 +#define F_SUM_FLOAT4 2110 +#define F_SUM_FLOAT8 2111 +#define F_SUM_MONEY 2112 +#define F_SUM_INTERVAL 2113 +#define F_SUM_NUMERIC 2114 +#define F_MAX_INT8 2115 +#define F_MAX_INT4 2116 +#define F_MAX_INT2 2117 +#define F_MAX_OID 2118 +#define F_MAX_FLOAT4 2119 +#define F_MAX_FLOAT8 2120 +#define F_PG_COLUMN_COMPRESSION 2121 +#define F_MAX_DATE 2122 +#define F_MAX_TIME 2123 +#define F_MAX_TIMETZ 2124 +#define F_MAX_MONEY 2125 +#define F_MAX_TIMESTAMP 2126 +#define F_MAX_TIMESTAMPTZ 2127 +#define F_MAX_INTERVAL 2128 +#define F_MAX_TEXT 2129 +#define F_MAX_NUMERIC 2130 +#define F_MIN_INT8 2131 +#define F_MIN_INT4 2132 +#define F_MIN_INT2 2133 +#define F_MIN_OID 2134 +#define F_MIN_FLOAT4 2135 +#define F_MIN_FLOAT8 2136 +#define F_PG_STAT_FORCE_NEXT_FLUSH 2137 +#define F_MIN_DATE 2138 +#define F_MIN_TIME 2139 +#define F_MIN_TIMETZ 2140 +#define F_MIN_MONEY 2141 +#define F_MIN_TIMESTAMP 2142 +#define F_MIN_TIMESTAMPTZ 2143 +#define F_MIN_INTERVAL 2144 +#define F_MIN_TEXT 2145 +#define F_MIN_NUMERIC 2146 +#define F_COUNT_ANY 2147 +#define F_VARIANCE_INT8 2148 +#define F_VARIANCE_INT4 2149 +#define F_VARIANCE_INT2 2150 +#define F_VARIANCE_FLOAT4 2151 +#define F_VARIANCE_FLOAT8 2152 +#define F_VARIANCE_NUMERIC 2153 +#define F_STDDEV_INT8 2154 +#define F_STDDEV_INT4 2155 +#define F_STDDEV_INT2 2156 +#define F_STDDEV_FLOAT4 2157 +#define F_STDDEV_FLOAT8 2158 +#define F_STDDEV_NUMERIC 2159 +#define F_TEXT_PATTERN_LT 2160 +#define F_TEXT_PATTERN_LE 2161 +#define F_PG_GET_FUNCTION_ARGUMENTS 2162 +#define F_TEXT_PATTERN_GE 2163 +#define F_TEXT_PATTERN_GT 2164 +#define F_PG_GET_FUNCTION_RESULT 2165 +#define F_BTTEXT_PATTERN_CMP 2166 +#define F_CEILING_NUMERIC 2167 +#define F_PG_DATABASE_SIZE_NAME 2168 +#define F_POWER_NUMERIC_NUMERIC 2169 +#define F_WIDTH_BUCKET_NUMERIC_NUMERIC_NUMERIC_INT4 2170 +#define F_PG_CANCEL_BACKEND 2171 +#define F_PG_BACKUP_START 2172 +#define F_BPCHAR_PATTERN_LT 2174 +#define F_BPCHAR_PATTERN_LE 2175 +#define F_ARRAY_LENGTH 2176 +#define F_BPCHAR_PATTERN_GE 2177 +#define F_BPCHAR_PATTERN_GT 2178 +#define F_GIST_POINT_CONSISTENT 2179 +#define F_BTBPCHAR_PATTERN_CMP 2180 +#define F_HAS_SEQUENCE_PRIVILEGE_NAME_TEXT_TEXT 2181 +#define F_HAS_SEQUENCE_PRIVILEGE_NAME_OID_TEXT 2182 +#define F_HAS_SEQUENCE_PRIVILEGE_OID_TEXT_TEXT 2183 +#define F_HAS_SEQUENCE_PRIVILEGE_OID_OID_TEXT 2184 +#define F_HAS_SEQUENCE_PRIVILEGE_TEXT_TEXT 2185 +#define F_HAS_SEQUENCE_PRIVILEGE_OID_TEXT 2186 +#define F_BTINT48CMP 2188 +#define F_BTINT84CMP 2189 +#define F_BTINT24CMP 2190 +#define F_BTINT42CMP 2191 +#define F_BTINT28CMP 2192 +#define F_BTINT82CMP 2193 +#define F_BTFLOAT48CMP 2194 +#define F_BTFLOAT84CMP 2195 +#define F_INET_CLIENT_ADDR 2196 +#define F_INET_CLIENT_PORT 2197 +#define F_INET_SERVER_ADDR 2198 +#define F_INET_SERVER_PORT 2199 +#define F_REGPROCEDUREIN 2212 +#define F_REGPROCEDUREOUT 2213 +#define F_REGOPERIN 2214 +#define F_REGOPEROUT 2215 +#define F_REGOPERATORIN 2216 +#define F_REGOPERATOROUT 2217 +#define F_REGCLASSIN 2218 +#define F_REGCLASSOUT 2219 +#define F_REGTYPEIN 2220 +#define F_REGTYPEOUT 2221 +#define F_PG_STAT_CLEAR_SNAPSHOT 2230 +#define F_PG_GET_FUNCTION_IDENTITY_ARGUMENTS 2232 +#define F_HASHTID 2233 +#define F_HASHTIDEXTENDED 2234 +#define F_BIT_AND_INT2 2236 +#define F_BIT_OR_INT2 2237 +#define F_BIT_AND_INT4 2238 +#define F_BIT_OR_INT4 2239 +#define F_BIT_AND_INT8 2240 +#define F_BIT_OR_INT8 2241 +#define F_BIT_AND_BIT 2242 +#define F_BIT_OR_BIT 2243 +#define F_MAX_BPCHAR 2244 +#define F_MIN_BPCHAR 2245 +#define F_FMGR_INTERNAL_VALIDATOR 2246 +#define F_FMGR_C_VALIDATOR 2247 +#define F_FMGR_SQL_VALIDATOR 2248 +#define F_HAS_DATABASE_PRIVILEGE_NAME_TEXT_TEXT 2250 +#define F_HAS_DATABASE_PRIVILEGE_NAME_OID_TEXT 2251 +#define F_HAS_DATABASE_PRIVILEGE_OID_TEXT_TEXT 2252 +#define F_HAS_DATABASE_PRIVILEGE_OID_OID_TEXT 2253 +#define F_HAS_DATABASE_PRIVILEGE_TEXT_TEXT 2254 +#define F_HAS_DATABASE_PRIVILEGE_OID_TEXT 2255 +#define F_HAS_FUNCTION_PRIVILEGE_NAME_TEXT_TEXT 2256 +#define F_HAS_FUNCTION_PRIVILEGE_NAME_OID_TEXT 2257 +#define F_HAS_FUNCTION_PRIVILEGE_OID_TEXT_TEXT 2258 +#define F_HAS_FUNCTION_PRIVILEGE_OID_OID_TEXT 2259 +#define F_HAS_FUNCTION_PRIVILEGE_TEXT_TEXT 2260 +#define F_HAS_FUNCTION_PRIVILEGE_OID_TEXT 2261 +#define F_HAS_LANGUAGE_PRIVILEGE_NAME_TEXT_TEXT 2262 +#define F_HAS_LANGUAGE_PRIVILEGE_NAME_OID_TEXT 2263 +#define F_HAS_LANGUAGE_PRIVILEGE_OID_TEXT_TEXT 2264 +#define F_HAS_LANGUAGE_PRIVILEGE_OID_OID_TEXT 2265 +#define F_HAS_LANGUAGE_PRIVILEGE_TEXT_TEXT 2266 +#define F_HAS_LANGUAGE_PRIVILEGE_OID_TEXT 2267 +#define F_HAS_SCHEMA_PRIVILEGE_NAME_TEXT_TEXT 2268 +#define F_HAS_SCHEMA_PRIVILEGE_NAME_OID_TEXT 2269 +#define F_HAS_SCHEMA_PRIVILEGE_OID_TEXT_TEXT 2270 +#define F_HAS_SCHEMA_PRIVILEGE_OID_OID_TEXT 2271 +#define F_HAS_SCHEMA_PRIVILEGE_TEXT_TEXT 2272 +#define F_HAS_SCHEMA_PRIVILEGE_OID_TEXT 2273 +#define F_PG_STAT_RESET 2274 +#define F_PG_GET_BACKEND_MEMORY_CONTEXTS 2282 +#define F_REGEXP_REPLACE_TEXT_TEXT_TEXT 2284 +#define F_REGEXP_REPLACE_TEXT_TEXT_TEXT_TEXT 2285 +#define F_PG_TOTAL_RELATION_SIZE 2286 +#define F_PG_SIZE_PRETTY_INT8 2288 +#define F_PG_OPTIONS_TO_TABLE 2289 +#define F_RECORD_IN 2290 +#define F_RECORD_OUT 2291 +#define F_CSTRING_IN 2292 +#define F_CSTRING_OUT 2293 +#define F_ANY_IN 2294 +#define F_ANY_OUT 2295 +#define F_ANYARRAY_IN 2296 +#define F_ANYARRAY_OUT 2297 +#define F_VOID_IN 2298 +#define F_VOID_OUT 2299 +#define F_TRIGGER_IN 2300 +#define F_TRIGGER_OUT 2301 +#define F_LANGUAGE_HANDLER_IN 2302 +#define F_LANGUAGE_HANDLER_OUT 2303 +#define F_INTERNAL_IN 2304 +#define F_INTERNAL_OUT 2305 +#define F_PG_STAT_GET_SLRU 2306 +#define F_PG_STAT_RESET_SLRU 2307 +#define F_CEIL_FLOAT8 2308 +#define F_FLOOR_FLOAT8 2309 +#define F_SIGN_FLOAT8 2310 +#define F_MD5_TEXT 2311 +#define F_ANYELEMENT_IN 2312 +#define F_ANYELEMENT_OUT 2313 +#define F_POSTGRESQL_FDW_VALIDATOR 2316 +#define F_PG_ENCODING_MAX_LENGTH 2319 +#define F_CEILING_FLOAT8 2320 +#define F_MD5_BYTEA 2321 +#define F_PG_TABLESPACE_SIZE_OID 2322 +#define F_PG_TABLESPACE_SIZE_NAME 2323 +#define F_PG_DATABASE_SIZE_OID 2324 +#define F_PG_RELATION_SIZE_REGCLASS 2325 +#define F_UNNEST_ANYARRAY 2331 +#define F_PG_RELATION_SIZE_REGCLASS_TEXT 2332 +#define F_ARRAY_AGG_TRANSFN 2333 +#define F_ARRAY_AGG_FINALFN 2334 +#define F_ARRAY_AGG_ANYNONARRAY 2335 +#define F_DATE_LT_TIMESTAMP 2338 +#define F_DATE_LE_TIMESTAMP 2339 +#define F_DATE_EQ_TIMESTAMP 2340 +#define F_DATE_GT_TIMESTAMP 2341 +#define F_DATE_GE_TIMESTAMP 2342 +#define F_DATE_NE_TIMESTAMP 2343 +#define F_DATE_CMP_TIMESTAMP 2344 +#define F_DATE_LT_TIMESTAMPTZ 2351 +#define F_DATE_LE_TIMESTAMPTZ 2352 +#define F_DATE_EQ_TIMESTAMPTZ 2353 +#define F_DATE_GT_TIMESTAMPTZ 2354 +#define F_DATE_GE_TIMESTAMPTZ 2355 +#define F_DATE_NE_TIMESTAMPTZ 2356 +#define F_DATE_CMP_TIMESTAMPTZ 2357 +#define F_TIMESTAMP_LT_DATE 2364 +#define F_TIMESTAMP_LE_DATE 2365 +#define F_TIMESTAMP_EQ_DATE 2366 +#define F_TIMESTAMP_GT_DATE 2367 +#define F_TIMESTAMP_GE_DATE 2368 +#define F_TIMESTAMP_NE_DATE 2369 +#define F_TIMESTAMP_CMP_DATE 2370 +#define F_TIMESTAMPTZ_LT_DATE 2377 +#define F_TIMESTAMPTZ_LE_DATE 2378 +#define F_TIMESTAMPTZ_EQ_DATE 2379 +#define F_TIMESTAMPTZ_GT_DATE 2380 +#define F_TIMESTAMPTZ_GE_DATE 2381 +#define F_TIMESTAMPTZ_NE_DATE 2382 +#define F_TIMESTAMPTZ_CMP_DATE 2383 +#define F_HAS_TABLESPACE_PRIVILEGE_NAME_TEXT_TEXT 2390 +#define F_HAS_TABLESPACE_PRIVILEGE_NAME_OID_TEXT 2391 +#define F_HAS_TABLESPACE_PRIVILEGE_OID_TEXT_TEXT 2392 +#define F_HAS_TABLESPACE_PRIVILEGE_OID_OID_TEXT 2393 +#define F_HAS_TABLESPACE_PRIVILEGE_TEXT_TEXT 2394 +#define F_HAS_TABLESPACE_PRIVILEGE_OID_TEXT 2395 +#define F_SHELL_IN 2398 +#define F_SHELL_OUT 2399 +#define F_ARRAY_RECV 2400 +#define F_ARRAY_SEND 2401 +#define F_RECORD_RECV 2402 +#define F_RECORD_SEND 2403 +#define F_INT2RECV 2404 +#define F_INT2SEND 2405 +#define F_INT4RECV 2406 +#define F_INT4SEND 2407 +#define F_INT8RECV 2408 +#define F_INT8SEND 2409 +#define F_INT2VECTORRECV 2410 +#define F_INT2VECTORSEND 2411 +#define F_BYTEARECV 2412 +#define F_BYTEASEND 2413 +#define F_TEXTRECV 2414 +#define F_TEXTSEND 2415 +#define F_UNKNOWNRECV 2416 +#define F_UNKNOWNSEND 2417 +#define F_OIDRECV 2418 +#define F_OIDSEND 2419 +#define F_OIDVECTORRECV 2420 +#define F_OIDVECTORSEND 2421 +#define F_NAMERECV 2422 +#define F_NAMESEND 2423 +#define F_FLOAT4RECV 2424 +#define F_FLOAT4SEND 2425 +#define F_FLOAT8RECV 2426 +#define F_FLOAT8SEND 2427 +#define F_POINT_RECV 2428 +#define F_POINT_SEND 2429 +#define F_BPCHARRECV 2430 +#define F_BPCHARSEND 2431 +#define F_VARCHARRECV 2432 +#define F_VARCHARSEND 2433 +#define F_CHARRECV 2434 +#define F_CHARSEND 2435 +#define F_BOOLRECV 2436 +#define F_BOOLSEND 2437 +#define F_TIDRECV 2438 +#define F_TIDSEND 2439 +#define F_XIDRECV 2440 +#define F_XIDSEND 2441 +#define F_CIDRECV 2442 +#define F_CIDSEND 2443 +#define F_REGPROCRECV 2444 +#define F_REGPROCSEND 2445 +#define F_REGPROCEDURERECV 2446 +#define F_REGPROCEDURESEND 2447 +#define F_REGOPERRECV 2448 +#define F_REGOPERSEND 2449 +#define F_REGOPERATORRECV 2450 +#define F_REGOPERATORSEND 2451 +#define F_REGCLASSRECV 2452 +#define F_REGCLASSSEND 2453 +#define F_REGTYPERECV 2454 +#define F_REGTYPESEND 2455 +#define F_BIT_RECV 2456 +#define F_BIT_SEND 2457 +#define F_VARBIT_RECV 2458 +#define F_VARBIT_SEND 2459 +#define F_NUMERIC_RECV 2460 +#define F_NUMERIC_SEND 2461 +#define F_SINH 2462 +#define F_COSH 2463 +#define F_TANH 2464 +#define F_ASINH 2465 +#define F_ACOSH 2466 +#define F_ATANH 2467 +#define F_DATE_RECV 2468 +#define F_DATE_SEND 2469 +#define F_TIME_RECV 2470 +#define F_TIME_SEND 2471 +#define F_TIMETZ_RECV 2472 +#define F_TIMETZ_SEND 2473 +#define F_TIMESTAMP_RECV 2474 +#define F_TIMESTAMP_SEND 2475 +#define F_TIMESTAMPTZ_RECV 2476 +#define F_TIMESTAMPTZ_SEND 2477 +#define F_INTERVAL_RECV 2478 +#define F_INTERVAL_SEND 2479 +#define F_LSEG_RECV 2480 +#define F_LSEG_SEND 2481 +#define F_PATH_RECV 2482 +#define F_PATH_SEND 2483 +#define F_BOX_RECV 2484 +#define F_BOX_SEND 2485 +#define F_POLY_RECV 2486 +#define F_POLY_SEND 2487 +#define F_LINE_RECV 2488 +#define F_LINE_SEND 2489 +#define F_CIRCLE_RECV 2490 +#define F_CIRCLE_SEND 2491 +#define F_CASH_RECV 2492 +#define F_CASH_SEND 2493 +#define F_MACADDR_RECV 2494 +#define F_MACADDR_SEND 2495 +#define F_INET_RECV 2496 +#define F_INET_SEND 2497 +#define F_CIDR_RECV 2498 +#define F_CIDR_SEND 2499 +#define F_CSTRING_RECV 2500 +#define F_CSTRING_SEND 2501 +#define F_ANYARRAY_RECV 2502 +#define F_ANYARRAY_SEND 2503 +#define F_PG_GET_RULEDEF_OID_BOOL 2504 +#define F_PG_GET_VIEWDEF_TEXT_BOOL 2505 +#define F_PG_GET_VIEWDEF_OID_BOOL 2506 +#define F_PG_GET_INDEXDEF_OID_INT4_BOOL 2507 +#define F_PG_GET_CONSTRAINTDEF_OID_BOOL 2508 +#define F_PG_GET_EXPR_PG_NODE_TREE_OID_BOOL 2509 +#define F_PG_PREPARED_STATEMENT 2510 +#define F_PG_CURSOR 2511 +#define F_FLOAT8_VAR_POP 2512 +#define F_FLOAT8_STDDEV_POP 2513 +#define F_NUMERIC_VAR_POP 2514 +#define F_BOOLAND_STATEFUNC 2515 +#define F_BOOLOR_STATEFUNC 2516 +#define F_BOOL_AND 2517 +#define F_BOOL_OR 2518 +#define F_EVERY 2519 +#define F_TIMESTAMP_LT_TIMESTAMPTZ 2520 +#define F_TIMESTAMP_LE_TIMESTAMPTZ 2521 +#define F_TIMESTAMP_EQ_TIMESTAMPTZ 2522 +#define F_TIMESTAMP_GT_TIMESTAMPTZ 2523 +#define F_TIMESTAMP_GE_TIMESTAMPTZ 2524 +#define F_TIMESTAMP_NE_TIMESTAMPTZ 2525 +#define F_TIMESTAMP_CMP_TIMESTAMPTZ 2526 +#define F_TIMESTAMPTZ_LT_TIMESTAMP 2527 +#define F_TIMESTAMPTZ_LE_TIMESTAMP 2528 +#define F_TIMESTAMPTZ_EQ_TIMESTAMP 2529 +#define F_TIMESTAMPTZ_GT_TIMESTAMP 2530 +#define F_TIMESTAMPTZ_GE_TIMESTAMP 2531 +#define F_TIMESTAMPTZ_NE_TIMESTAMP 2532 +#define F_TIMESTAMPTZ_CMP_TIMESTAMP 2533 +#define F_INTERVAL_PL_DATE 2546 +#define F_INTERVAL_PL_TIMETZ 2547 +#define F_INTERVAL_PL_TIMESTAMP 2548 +#define F_INTERVAL_PL_TIMESTAMPTZ 2549 +#define F_INTEGER_PL_DATE 2550 +#define F_PG_TABLESPACE_DATABASES 2556 +#define F_BOOL_INT4 2557 +#define F_INT4_BOOL 2558 +#define F_LASTVAL 2559 +#define F_PG_POSTMASTER_START_TIME 2560 +#define F_PG_BLOCKING_PIDS 2561 +#define F_BOX_BELOW 2562 +#define F_BOX_OVERBELOW 2563 +#define F_BOX_OVERABOVE 2564 +#define F_BOX_ABOVE 2565 +#define F_POLY_BELOW 2566 +#define F_POLY_OVERBELOW 2567 +#define F_POLY_OVERABOVE 2568 +#define F_POLY_ABOVE 2569 +#define F_GIST_BOX_CONSISTENT 2578 +#define F_FLOAT8_JSONB 2580 +#define F_GIST_BOX_PENALTY 2581 +#define F_GIST_BOX_PICKSPLIT 2582 +#define F_GIST_BOX_UNION 2583 +#define F_GIST_BOX_SAME 2584 +#define F_GIST_POLY_CONSISTENT 2585 +#define F_GIST_POLY_COMPRESS 2586 +#define F_CIRCLE_OVERBELOW 2587 +#define F_CIRCLE_OVERABOVE 2588 +#define F_GIST_CIRCLE_CONSISTENT 2591 +#define F_GIST_CIRCLE_COMPRESS 2592 +#define F_NUMERIC_STDDEV_POP 2596 +#define F_DOMAIN_IN 2597 +#define F_DOMAIN_RECV 2598 +#define F_PG_TIMEZONE_ABBREVS 2599 +#define F_XMLEXISTS 2614 +#define F_PG_RELOAD_CONF 2621 +#define F_PG_ROTATE_LOGFILE 2622 +#define F_PG_STAT_FILE_TEXT 2623 +#define F_PG_READ_FILE_TEXT_INT8_INT8 2624 +#define F_PG_LS_DIR_TEXT 2625 +#define F_PG_SLEEP 2626 +#define F_INETNOT 2627 +#define F_INETAND 2628 +#define F_INETOR 2629 +#define F_INETPL 2630 +#define F_INT8PL_INET 2631 +#define F_INETMI_INT8 2632 +#define F_INETMI 2633 +#define F_VAR_SAMP_INT8 2641 +#define F_VAR_SAMP_INT4 2642 +#define F_VAR_SAMP_INT2 2643 +#define F_VAR_SAMP_FLOAT4 2644 +#define F_VAR_SAMP_FLOAT8 2645 +#define F_VAR_SAMP_NUMERIC 2646 +#define F_TRANSACTION_TIMESTAMP 2647 +#define F_STATEMENT_TIMESTAMP 2648 +#define F_CLOCK_TIMESTAMP 2649 +#define F_GIN_CMP_PREFIX 2700 +#define F_PG_HAS_ROLE_NAME_NAME_TEXT 2705 +#define F_PG_HAS_ROLE_NAME_OID_TEXT 2706 +#define F_PG_HAS_ROLE_OID_NAME_TEXT 2707 +#define F_PG_HAS_ROLE_OID_OID_TEXT 2708 +#define F_PG_HAS_ROLE_NAME_TEXT 2709 +#define F_PG_HAS_ROLE_OID_TEXT 2710 +#define F_JUSTIFY_INTERVAL 2711 +#define F_STDDEV_SAMP_INT8 2712 +#define F_STDDEV_SAMP_INT4 2713 +#define F_STDDEV_SAMP_INT2 2714 +#define F_STDDEV_SAMP_FLOAT4 2715 +#define F_STDDEV_SAMP_FLOAT8 2716 +#define F_STDDEV_SAMP_NUMERIC 2717 +#define F_VAR_POP_INT8 2718 +#define F_VAR_POP_INT4 2719 +#define F_VAR_POP_INT2 2720 +#define F_VAR_POP_FLOAT4 2721 +#define F_VAR_POP_FLOAT8 2722 +#define F_VAR_POP_NUMERIC 2723 +#define F_STDDEV_POP_INT8 2724 +#define F_STDDEV_POP_INT4 2725 +#define F_STDDEV_POP_INT2 2726 +#define F_STDDEV_POP_FLOAT4 2727 +#define F_STDDEV_POP_FLOAT8 2728 +#define F_STDDEV_POP_NUMERIC 2729 +#define F_PG_GET_TRIGGERDEF_OID_BOOL 2730 +#define F_ASIND 2731 +#define F_ACOSD 2732 +#define F_ATAND 2733 +#define F_ATAN2D 2734 +#define F_SIND 2735 +#define F_COSD 2736 +#define F_TAND 2737 +#define F_COTD 2738 +#define F_PG_BACKUP_STOP 2739 +#define F_NUMERIC_AVG_SERIALIZE 2740 +#define F_NUMERIC_AVG_DESERIALIZE 2741 +#define F_GINARRAYEXTRACT_ANYARRAY_INTERNAL_INTERNAL 2743 +#define F_GINARRAYCONSISTENT 2744 +#define F_INT8_AVG_ACCUM 2746 +#define F_ARRAYOVERLAP 2747 +#define F_ARRAYCONTAINS 2748 +#define F_ARRAYCONTAINED 2749 +#define F_PG_STAT_GET_DB_TUPLES_RETURNED 2758 +#define F_PG_STAT_GET_DB_TUPLES_FETCHED 2759 +#define F_PG_STAT_GET_DB_TUPLES_INSERTED 2760 +#define F_PG_STAT_GET_DB_TUPLES_UPDATED 2761 +#define F_PG_STAT_GET_DB_TUPLES_DELETED 2762 +#define F_REGEXP_MATCHES_TEXT_TEXT 2763 +#define F_REGEXP_MATCHES_TEXT_TEXT_TEXT 2764 +#define F_REGEXP_SPLIT_TO_TABLE_TEXT_TEXT 2765 +#define F_REGEXP_SPLIT_TO_TABLE_TEXT_TEXT_TEXT 2766 +#define F_REGEXP_SPLIT_TO_ARRAY_TEXT_TEXT 2767 +#define F_REGEXP_SPLIT_TO_ARRAY_TEXT_TEXT_TEXT 2768 +#define F_PG_STAT_GET_BGWRITER_TIMED_CHECKPOINTS 2769 +#define F_PG_STAT_GET_BGWRITER_REQUESTED_CHECKPOINTS 2770 +#define F_PG_STAT_GET_BGWRITER_BUF_WRITTEN_CHECKPOINTS 2771 +#define F_PG_STAT_GET_BGWRITER_BUF_WRITTEN_CLEAN 2772 +#define F_PG_STAT_GET_BGWRITER_MAXWRITTEN_CLEAN 2773 +#define F_GINQUERYARRAYEXTRACT 2774 +#define F_PG_STAT_GET_BUF_WRITTEN_BACKEND 2775 +#define F_ANYNONARRAY_IN 2777 +#define F_ANYNONARRAY_OUT 2778 +#define F_PG_STAT_GET_LAST_VACUUM_TIME 2781 +#define F_PG_STAT_GET_LAST_AUTOVACUUM_TIME 2782 +#define F_PG_STAT_GET_LAST_ANALYZE_TIME 2783 +#define F_PG_STAT_GET_LAST_AUTOANALYZE_TIME 2784 +#define F_INT8_AVG_COMBINE 2785 +#define F_INT8_AVG_SERIALIZE 2786 +#define F_INT8_AVG_DESERIALIZE 2787 +#define F_PG_STAT_GET_BACKEND_WAIT_EVENT_TYPE 2788 +#define F_TIDGT 2790 +#define F_TIDLT 2791 +#define F_TIDGE 2792 +#define F_TIDLE 2793 +#define F_BTTIDCMP 2794 +#define F_TIDLARGER 2795 +#define F_TIDSMALLER 2796 +#define F_MAX_TID 2797 +#define F_MIN_TID 2798 +#define F_COUNT_ 2803 +#define F_INT8INC_ANY 2804 +#define F_INT8INC_FLOAT8_FLOAT8 2805 +#define F_FLOAT8_REGR_ACCUM 2806 +#define F_FLOAT8_REGR_SXX 2807 +#define F_FLOAT8_REGR_SYY 2808 +#define F_FLOAT8_REGR_SXY 2809 +#define F_FLOAT8_REGR_AVGX 2810 +#define F_FLOAT8_REGR_AVGY 2811 +#define F_FLOAT8_REGR_R2 2812 +#define F_FLOAT8_REGR_SLOPE 2813 +#define F_FLOAT8_REGR_INTERCEPT 2814 +#define F_FLOAT8_COVAR_POP 2815 +#define F_FLOAT8_COVAR_SAMP 2816 +#define F_FLOAT8_CORR 2817 +#define F_REGR_COUNT 2818 +#define F_REGR_SXX 2819 +#define F_REGR_SYY 2820 +#define F_REGR_SXY 2821 +#define F_REGR_AVGX 2822 +#define F_REGR_AVGY 2823 +#define F_REGR_R2 2824 +#define F_REGR_SLOPE 2825 +#define F_REGR_INTERCEPT 2826 +#define F_COVAR_POP 2827 +#define F_COVAR_SAMP 2828 +#define F_CORR 2829 +#define F_PG_STAT_GET_DB_BLK_READ_TIME 2844 +#define F_PG_STAT_GET_DB_BLK_WRITE_TIME 2845 +#define F_PG_SWITCH_WAL 2848 +#define F_PG_CURRENT_WAL_LSN 2849 +#define F_PG_WALFILE_NAME_OFFSET 2850 +#define F_PG_WALFILE_NAME 2851 +#define F_PG_CURRENT_WAL_INSERT_LSN 2852 +#define F_PG_STAT_GET_BACKEND_WAIT_EVENT 2853 +#define F_PG_MY_TEMP_SCHEMA 2854 +#define F_PG_IS_OTHER_TEMP_SCHEMA 2855 +#define F_PG_TIMEZONE_NAMES 2856 +#define F_PG_STAT_GET_BACKEND_XACT_START 2857 +#define F_NUMERIC_AVG_ACCUM 2858 +#define F_PG_STAT_GET_BUF_ALLOC 2859 +#define F_PG_STAT_GET_LIVE_TUPLES 2878 +#define F_PG_STAT_GET_DEAD_TUPLES 2879 +#define F_PG_ADVISORY_LOCK_INT8 2880 +#define F_PG_ADVISORY_LOCK_SHARED_INT8 2881 +#define F_PG_TRY_ADVISORY_LOCK_INT8 2882 +#define F_PG_TRY_ADVISORY_LOCK_SHARED_INT8 2883 +#define F_PG_ADVISORY_UNLOCK_INT8 2884 +#define F_PG_ADVISORY_UNLOCK_SHARED_INT8 2885 +#define F_PG_ADVISORY_LOCK_INT4_INT4 2886 +#define F_PG_ADVISORY_LOCK_SHARED_INT4_INT4 2887 +#define F_PG_TRY_ADVISORY_LOCK_INT4_INT4 2888 +#define F_PG_TRY_ADVISORY_LOCK_SHARED_INT4_INT4 2889 +#define F_PG_ADVISORY_UNLOCK_INT4_INT4 2890 +#define F_PG_ADVISORY_UNLOCK_SHARED_INT4_INT4 2891 +#define F_PG_ADVISORY_UNLOCK_ALL 2892 +#define F_XML_IN 2893 +#define F_XML_OUT 2894 +#define F_XMLCOMMENT 2895 +#define F_XML 2896 +#define F_XMLVALIDATE 2897 +#define F_XML_RECV 2898 +#define F_XML_SEND 2899 +#define F_XMLCONCAT2 2900 +#define F_XMLAGG 2901 +#define F_VARBITTYPMODIN 2902 +#define F_INTERVALTYPMODIN 2903 +#define F_INTERVALTYPMODOUT 2904 +#define F_TIMESTAMPTYPMODIN 2905 +#define F_TIMESTAMPTYPMODOUT 2906 +#define F_TIMESTAMPTZTYPMODIN 2907 +#define F_TIMESTAMPTZTYPMODOUT 2908 +#define F_TIMETYPMODIN 2909 +#define F_TIMETYPMODOUT 2910 +#define F_TIMETZTYPMODIN 2911 +#define F_TIMETZTYPMODOUT 2912 +#define F_BPCHARTYPMODIN 2913 +#define F_BPCHARTYPMODOUT 2914 +#define F_VARCHARTYPMODIN 2915 +#define F_VARCHARTYPMODOUT 2916 +#define F_NUMERICTYPMODIN 2917 +#define F_NUMERICTYPMODOUT 2918 +#define F_BITTYPMODIN 2919 +#define F_BITTYPMODOUT 2920 +#define F_VARBITTYPMODOUT 2921 +#define F_TEXT_XML 2922 +#define F_TABLE_TO_XML 2923 +#define F_QUERY_TO_XML 2924 +#define F_CURSOR_TO_XML 2925 +#define F_TABLE_TO_XMLSCHEMA 2926 +#define F_QUERY_TO_XMLSCHEMA 2927 +#define F_CURSOR_TO_XMLSCHEMA 2928 +#define F_TABLE_TO_XML_AND_XMLSCHEMA 2929 +#define F_QUERY_TO_XML_AND_XMLSCHEMA 2930 +#define F_XPATH_TEXT_XML__TEXT 2931 +#define F_XPATH_TEXT_XML 2932 +#define F_SCHEMA_TO_XML 2933 +#define F_SCHEMA_TO_XMLSCHEMA 2934 +#define F_SCHEMA_TO_XML_AND_XMLSCHEMA 2935 +#define F_DATABASE_TO_XML 2936 +#define F_DATABASE_TO_XMLSCHEMA 2937 +#define F_DATABASE_TO_XML_AND_XMLSCHEMA 2938 +#define F_TXID_SNAPSHOT_IN 2939 +#define F_TXID_SNAPSHOT_OUT 2940 +#define F_TXID_SNAPSHOT_RECV 2941 +#define F_TXID_SNAPSHOT_SEND 2942 +#define F_TXID_CURRENT 2943 +#define F_TXID_CURRENT_SNAPSHOT 2944 +#define F_TXID_SNAPSHOT_XMIN 2945 +#define F_TXID_SNAPSHOT_XMAX 2946 +#define F_TXID_SNAPSHOT_XIP 2947 +#define F_TXID_VISIBLE_IN_SNAPSHOT 2948 +#define F_UUID_IN 2952 +#define F_UUID_OUT 2953 +#define F_UUID_LT 2954 +#define F_UUID_LE 2955 +#define F_UUID_EQ 2956 +#define F_UUID_GE 2957 +#define F_UUID_GT 2958 +#define F_UUID_NE 2959 +#define F_UUID_CMP 2960 +#define F_UUID_RECV 2961 +#define F_UUID_SEND 2962 +#define F_UUID_HASH 2963 +#define F_TEXT_BOOL 2971 +#define F_PG_STAT_GET_FUNCTION_CALLS 2978 +#define F_PG_STAT_GET_FUNCTION_TOTAL_TIME 2979 +#define F_PG_STAT_GET_FUNCTION_SELF_TIME 2980 +#define F_RECORD_EQ 2981 +#define F_RECORD_NE 2982 +#define F_RECORD_LT 2983 +#define F_RECORD_GT 2984 +#define F_RECORD_LE 2985 +#define F_RECORD_GE 2986 +#define F_BTRECORDCMP 2987 +#define F_PG_TABLE_SIZE 2997 +#define F_PG_INDEXES_SIZE 2998 +#define F_PG_RELATION_FILENODE 2999 +#define F_HAS_FOREIGN_DATA_WRAPPER_PRIVILEGE_NAME_TEXT_TEXT 3000 +#define F_HAS_FOREIGN_DATA_WRAPPER_PRIVILEGE_NAME_OID_TEXT 3001 +#define F_HAS_FOREIGN_DATA_WRAPPER_PRIVILEGE_OID_TEXT_TEXT 3002 +#define F_HAS_FOREIGN_DATA_WRAPPER_PRIVILEGE_OID_OID_TEXT 3003 +#define F_HAS_FOREIGN_DATA_WRAPPER_PRIVILEGE_TEXT_TEXT 3004 +#define F_HAS_FOREIGN_DATA_WRAPPER_PRIVILEGE_OID_TEXT 3005 +#define F_HAS_SERVER_PRIVILEGE_NAME_TEXT_TEXT 3006 +#define F_HAS_SERVER_PRIVILEGE_NAME_OID_TEXT 3007 +#define F_HAS_SERVER_PRIVILEGE_OID_TEXT_TEXT 3008 +#define F_HAS_SERVER_PRIVILEGE_OID_OID_TEXT 3009 +#define F_HAS_SERVER_PRIVILEGE_TEXT_TEXT 3010 +#define F_HAS_SERVER_PRIVILEGE_OID_TEXT 3011 +#define F_HAS_COLUMN_PRIVILEGE_NAME_TEXT_TEXT_TEXT 3012 +#define F_HAS_COLUMN_PRIVILEGE_NAME_TEXT_INT2_TEXT 3013 +#define F_HAS_COLUMN_PRIVILEGE_NAME_OID_TEXT_TEXT 3014 +#define F_HAS_COLUMN_PRIVILEGE_NAME_OID_INT2_TEXT 3015 +#define F_HAS_COLUMN_PRIVILEGE_OID_TEXT_TEXT_TEXT 3016 +#define F_HAS_COLUMN_PRIVILEGE_OID_TEXT_INT2_TEXT 3017 +#define F_HAS_COLUMN_PRIVILEGE_OID_OID_TEXT_TEXT 3018 +#define F_HAS_COLUMN_PRIVILEGE_OID_OID_INT2_TEXT 3019 +#define F_HAS_COLUMN_PRIVILEGE_TEXT_TEXT_TEXT 3020 +#define F_HAS_COLUMN_PRIVILEGE_TEXT_INT2_TEXT 3021 +#define F_HAS_COLUMN_PRIVILEGE_OID_TEXT_TEXT 3022 +#define F_HAS_COLUMN_PRIVILEGE_OID_INT2_TEXT 3023 +#define F_HAS_ANY_COLUMN_PRIVILEGE_NAME_TEXT_TEXT 3024 +#define F_HAS_ANY_COLUMN_PRIVILEGE_NAME_OID_TEXT 3025 +#define F_HAS_ANY_COLUMN_PRIVILEGE_OID_TEXT_TEXT 3026 +#define F_HAS_ANY_COLUMN_PRIVILEGE_OID_OID_TEXT 3027 +#define F_HAS_ANY_COLUMN_PRIVILEGE_TEXT_TEXT 3028 +#define F_HAS_ANY_COLUMN_PRIVILEGE_OID_TEXT 3029 +#define F_OVERLAY_BIT_BIT_INT4_INT4 3030 +#define F_OVERLAY_BIT_BIT_INT4 3031 +#define F_GET_BIT_BIT_INT4 3032 +#define F_SET_BIT_BIT_INT4_INT4 3033 +#define F_PG_RELATION_FILEPATH 3034 +#define F_PG_LISTENING_CHANNELS 3035 +#define F_PG_NOTIFY 3036 +#define F_PG_STAT_GET_XACT_NUMSCANS 3037 +#define F_PG_STAT_GET_XACT_TUPLES_RETURNED 3038 +#define F_PG_STAT_GET_XACT_TUPLES_FETCHED 3039 +#define F_PG_STAT_GET_XACT_TUPLES_INSERTED 3040 +#define F_PG_STAT_GET_XACT_TUPLES_UPDATED 3041 +#define F_PG_STAT_GET_XACT_TUPLES_DELETED 3042 +#define F_PG_STAT_GET_XACT_TUPLES_HOT_UPDATED 3043 +#define F_PG_STAT_GET_XACT_BLOCKS_FETCHED 3044 +#define F_PG_STAT_GET_XACT_BLOCKS_HIT 3045 +#define F_PG_STAT_GET_XACT_FUNCTION_CALLS 3046 +#define F_PG_STAT_GET_XACT_FUNCTION_TOTAL_TIME 3047 +#define F_PG_STAT_GET_XACT_FUNCTION_SELF_TIME 3048 +#define F_XPATH_EXISTS_TEXT_XML__TEXT 3049 +#define F_XPATH_EXISTS_TEXT_XML 3050 +#define F_XML_IS_WELL_FORMED 3051 +#define F_XML_IS_WELL_FORMED_DOCUMENT 3052 +#define F_XML_IS_WELL_FORMED_CONTENT 3053 +#define F_PG_STAT_GET_VACUUM_COUNT 3054 +#define F_PG_STAT_GET_AUTOVACUUM_COUNT 3055 +#define F_PG_STAT_GET_ANALYZE_COUNT 3056 +#define F_PG_STAT_GET_AUTOANALYZE_COUNT 3057 +#define F_CONCAT 3058 +#define F_CONCAT_WS 3059 +#define F_LEFT 3060 +#define F_RIGHT 3061 +#define F_REVERSE 3062 +#define F_PG_STAT_GET_BUF_FSYNC_BACKEND 3063 +#define F_GIST_POINT_DISTANCE 3064 +#define F_PG_STAT_GET_DB_CONFLICT_TABLESPACE 3065 +#define F_PG_STAT_GET_DB_CONFLICT_LOCK 3066 +#define F_PG_STAT_GET_DB_CONFLICT_SNAPSHOT 3067 +#define F_PG_STAT_GET_DB_CONFLICT_BUFFERPIN 3068 +#define F_PG_STAT_GET_DB_CONFLICT_STARTUP_DEADLOCK 3069 +#define F_PG_STAT_GET_DB_CONFLICT_ALL 3070 +#define F_PG_WAL_REPLAY_PAUSE 3071 +#define F_PG_WAL_REPLAY_RESUME 3072 +#define F_PG_IS_WAL_REPLAY_PAUSED 3073 +#define F_PG_STAT_GET_DB_STAT_RESET_TIME 3074 +#define F_PG_STAT_GET_BGWRITER_STAT_RESET_TIME 3075 +#define F_GINARRAYEXTRACT_ANYARRAY_INTERNAL 3076 +#define F_GIN_EXTRACT_TSVECTOR_TSVECTOR_INTERNAL 3077 +#define F_PG_SEQUENCE_PARAMETERS 3078 +#define F_PG_AVAILABLE_EXTENSIONS 3082 +#define F_PG_AVAILABLE_EXTENSION_VERSIONS 3083 +#define F_PG_EXTENSION_UPDATE_PATHS 3084 +#define F_PG_EXTENSION_CONFIG_DUMP 3086 +#define F_GIN_EXTRACT_TSQUERY_TSQUERY_INTERNAL_INT2_INTERNAL_INTERNAL 3087 +#define F_GIN_TSQUERY_CONSISTENT_INTERNAL_INT2_TSQUERY_INT4_INTERNAL_INTERNAL 3088 +#define F_PG_ADVISORY_XACT_LOCK_INT8 3089 +#define F_PG_ADVISORY_XACT_LOCK_SHARED_INT8 3090 +#define F_PG_TRY_ADVISORY_XACT_LOCK_INT8 3091 +#define F_PG_TRY_ADVISORY_XACT_LOCK_SHARED_INT8 3092 +#define F_PG_ADVISORY_XACT_LOCK_INT4_INT4 3093 +#define F_PG_ADVISORY_XACT_LOCK_SHARED_INT4_INT4 3094 +#define F_PG_TRY_ADVISORY_XACT_LOCK_INT4_INT4 3095 +#define F_PG_TRY_ADVISORY_XACT_LOCK_SHARED_INT4_INT4 3096 +#define F_VARCHAR_SUPPORT 3097 +#define F_PG_CREATE_RESTORE_POINT 3098 +#define F_PG_STAT_GET_WAL_SENDERS 3099 +#define F_ROW_NUMBER 3100 +#define F_RANK_ 3101 +#define F_DENSE_RANK_ 3102 +#define F_PERCENT_RANK_ 3103 +#define F_CUME_DIST_ 3104 +#define F_NTILE 3105 +#define F_LAG_ANYELEMENT 3106 +#define F_LAG_ANYELEMENT_INT4 3107 +#define F_LAG_ANYCOMPATIBLE_INT4_ANYCOMPATIBLE 3108 +#define F_LEAD_ANYELEMENT 3109 +#define F_LEAD_ANYELEMENT_INT4 3110 +#define F_LEAD_ANYCOMPATIBLE_INT4_ANYCOMPATIBLE 3111 +#define F_FIRST_VALUE 3112 +#define F_LAST_VALUE 3113 +#define F_NTH_VALUE 3114 +#define F_FDW_HANDLER_IN 3116 +#define F_FDW_HANDLER_OUT 3117 +#define F_VOID_RECV 3120 +#define F_VOID_SEND 3121 +#define F_BTINT2SORTSUPPORT 3129 +#define F_BTINT4SORTSUPPORT 3130 +#define F_BTINT8SORTSUPPORT 3131 +#define F_BTFLOAT4SORTSUPPORT 3132 +#define F_BTFLOAT8SORTSUPPORT 3133 +#define F_BTOIDSORTSUPPORT 3134 +#define F_BTNAMESORTSUPPORT 3135 +#define F_DATE_SORTSUPPORT 3136 +#define F_TIMESTAMP_SORTSUPPORT 3137 +#define F_HAS_TYPE_PRIVILEGE_NAME_TEXT_TEXT 3138 +#define F_HAS_TYPE_PRIVILEGE_NAME_OID_TEXT 3139 +#define F_HAS_TYPE_PRIVILEGE_OID_TEXT_TEXT 3140 +#define F_HAS_TYPE_PRIVILEGE_OID_OID_TEXT 3141 +#define F_HAS_TYPE_PRIVILEGE_TEXT_TEXT 3142 +#define F_HAS_TYPE_PRIVILEGE_OID_TEXT 3143 +#define F_MACADDR_NOT 3144 +#define F_MACADDR_AND 3145 +#define F_MACADDR_OR 3146 +#define F_PG_STAT_GET_DB_TEMP_FILES 3150 +#define F_PG_STAT_GET_DB_TEMP_BYTES 3151 +#define F_PG_STAT_GET_DB_DEADLOCKS 3152 +#define F_ARRAY_TO_JSON_ANYARRAY 3153 +#define F_ARRAY_TO_JSON_ANYARRAY_BOOL 3154 +#define F_ROW_TO_JSON_RECORD 3155 +#define F_ROW_TO_JSON_RECORD_BOOL 3156 +#define F_NUMERIC_SUPPORT 3157 +#define F_VARBIT_SUPPORT 3158 +#define F_PG_GET_VIEWDEF_OID_INT4 3159 +#define F_PG_STAT_GET_CHECKPOINT_WRITE_TIME 3160 +#define F_PG_STAT_GET_CHECKPOINT_SYNC_TIME 3161 +#define F_PG_COLLATION_FOR 3162 +#define F_PG_TRIGGER_DEPTH 3163 +#define F_PG_WAL_LSN_DIFF 3165 +#define F_PG_SIZE_PRETTY_NUMERIC 3166 +#define F_ARRAY_REMOVE 3167 +#define F_ARRAY_REPLACE 3168 +#define F_RANGESEL 3169 +#define F_LO_LSEEK64 3170 +#define F_LO_TELL64 3171 +#define F_LO_TRUNCATE64 3172 +#define F_JSON_AGG_TRANSFN 3173 +#define F_JSON_AGG_FINALFN 3174 +#define F_JSON_AGG 3175 +#define F_TO_JSON 3176 +#define F_PG_STAT_GET_MOD_SINCE_ANALYZE 3177 +#define F_NUMERIC_SUM 3178 +#define F_CARDINALITY 3179 +#define F_JSON_OBJECT_AGG_TRANSFN 3180 +#define F_RECORD_IMAGE_EQ 3181 +#define F_RECORD_IMAGE_NE 3182 +#define F_RECORD_IMAGE_LT 3183 +#define F_RECORD_IMAGE_GT 3184 +#define F_RECORD_IMAGE_LE 3185 +#define F_RECORD_IMAGE_GE 3186 +#define F_BTRECORDIMAGECMP 3187 +#define F_PG_STAT_GET_ARCHIVER 3195 +#define F_JSON_OBJECT_AGG_FINALFN 3196 +#define F_JSON_OBJECT_AGG 3197 +#define F_JSON_BUILD_ARRAY_ANY 3198 +#define F_JSON_BUILD_ARRAY_ 3199 +#define F_JSON_BUILD_OBJECT_ANY 3200 +#define F_JSON_BUILD_OBJECT_ 3201 +#define F_JSON_OBJECT__TEXT 3202 +#define F_JSON_OBJECT__TEXT__TEXT 3203 +#define F_JSON_TO_RECORD 3204 +#define F_JSON_TO_RECORDSET 3205 +#define F_JSONB_ARRAY_LENGTH 3207 +#define F_JSONB_EACH 3208 +#define F_JSONB_POPULATE_RECORD 3209 +#define F_JSONB_TYPEOF 3210 +#define F_JSONB_OBJECT_FIELD_TEXT 3214 +#define F_JSONB_ARRAY_ELEMENT 3215 +#define F_JSONB_ARRAY_ELEMENT_TEXT 3216 +#define F_JSONB_EXTRACT_PATH 3217 +#define F_WIDTH_BUCKET_ANYCOMPATIBLE_ANYCOMPATIBLEARRAY 3218 +#define F_JSONB_ARRAY_ELEMENTS 3219 +#define F_PG_LSN_IN 3229 +#define F_PG_LSN_OUT 3230 +#define F_PG_LSN_LT 3231 +#define F_PG_LSN_LE 3232 +#define F_PG_LSN_EQ 3233 +#define F_PG_LSN_GE 3234 +#define F_PG_LSN_GT 3235 +#define F_PG_LSN_NE 3236 +#define F_PG_LSN_MI 3237 +#define F_PG_LSN_RECV 3238 +#define F_PG_LSN_SEND 3239 +#define F_PG_LSN_CMP 3251 +#define F_PG_LSN_HASH 3252 +#define F_BTTEXTSORTSUPPORT 3255 +#define F_GENERATE_SERIES_NUMERIC_NUMERIC_NUMERIC 3259 +#define F_GENERATE_SERIES_NUMERIC_NUMERIC 3260 +#define F_JSON_STRIP_NULLS 3261 +#define F_JSONB_STRIP_NULLS 3262 +#define F_JSONB_OBJECT__TEXT 3263 +#define F_JSONB_OBJECT__TEXT__TEXT 3264 +#define F_JSONB_AGG_TRANSFN 3265 +#define F_JSONB_AGG_FINALFN 3266 +#define F_JSONB_AGG 3267 +#define F_JSONB_OBJECT_AGG_TRANSFN 3268 +#define F_JSONB_OBJECT_AGG_FINALFN 3269 +#define F_JSONB_OBJECT_AGG 3270 +#define F_JSONB_BUILD_ARRAY_ANY 3271 +#define F_JSONB_BUILD_ARRAY_ 3272 +#define F_JSONB_BUILD_OBJECT_ANY 3273 +#define F_JSONB_BUILD_OBJECT_ 3274 +#define F_DIST_PPOLY 3275 +#define F_ARRAY_POSITION_ANYCOMPATIBLEARRAY_ANYCOMPATIBLE 3277 +#define F_ARRAY_POSITION_ANYCOMPATIBLEARRAY_ANYCOMPATIBLE_INT4 3278 +#define F_ARRAY_POSITIONS 3279 +#define F_GIST_CIRCLE_DISTANCE 3280 +#define F_SCALE 3281 +#define F_GIST_POINT_FETCH 3282 +#define F_NUMERIC_SORTSUPPORT 3283 +#define F_GIST_POLY_DISTANCE 3288 +#define F_DIST_CPOINT 3290 +#define F_DIST_POLYP 3292 +#define F_PG_READ_FILE_TEXT_INT8_INT8_BOOL 3293 +#define F_CURRENT_SETTING_TEXT_BOOL 3294 +#define F_PG_READ_BINARY_FILE_TEXT_INT8_INT8_BOOL 3295 +#define F_PG_NOTIFICATION_QUEUE_USAGE 3296 +#define F_PG_LS_DIR_TEXT_BOOL_BOOL 3297 +#define F_ROW_SECURITY_ACTIVE_OID 3298 +#define F_ROW_SECURITY_ACTIVE_TEXT 3299 +#define F_UUID_SORTSUPPORT 3300 +#define F_JSONB_CONCAT 3301 +#define F_JSONB_DELETE_JSONB_TEXT 3302 +#define F_JSONB_DELETE_JSONB_INT4 3303 +#define F_JSONB_DELETE_PATH 3304 +#define F_JSONB_SET 3305 +#define F_JSONB_PRETTY 3306 +#define F_PG_STAT_FILE_TEXT_BOOL 3307 +#define F_XIDNEQ 3308 +#define F_XIDNEQINT4 3309 +#define F_TSM_HANDLER_IN 3311 +#define F_TSM_HANDLER_OUT 3312 +#define F_BERNOULLI 3313 +#define F_SYSTEM 3314 +#define F_PG_STAT_GET_WAL_RECEIVER 3317 +#define F_PG_STAT_GET_PROGRESS_INFO 3318 +#define F_TS_FILTER 3319 +#define F_SETWEIGHT_TSVECTOR_CHAR__TEXT 3320 +#define F_TS_DELETE_TSVECTOR_TEXT 3321 +#define F_UNNEST_TSVECTOR 3322 +#define F_TS_DELETE_TSVECTOR__TEXT 3323 +#define F_INT4_AVG_COMBINE 3324 +#define F_INTERVAL_COMBINE 3325 +#define F_TSVECTOR_TO_ARRAY 3326 +#define F_ARRAY_TO_TSVECTOR 3327 +#define F_BPCHAR_SORTSUPPORT 3328 +#define F_PG_SHOW_ALL_FILE_SETTINGS 3329 +#define F_PG_CURRENT_WAL_FLUSH_LSN 3330 +#define F_BYTEA_SORTSUPPORT 3331 +#define F_BTTEXT_PATTERN_SORTSUPPORT 3332 +#define F_BTBPCHAR_PATTERN_SORTSUPPORT 3333 +#define F_PG_SIZE_BYTES 3334 +#define F_NUMERIC_SERIALIZE 3335 +#define F_NUMERIC_DESERIALIZE 3336 +#define F_NUMERIC_AVG_COMBINE 3337 +#define F_NUMERIC_POLY_COMBINE 3338 +#define F_NUMERIC_POLY_SERIALIZE 3339 +#define F_NUMERIC_POLY_DESERIALIZE 3340 +#define F_NUMERIC_COMBINE 3341 +#define F_FLOAT8_REGR_COMBINE 3342 +#define F_JSONB_DELETE_JSONB__TEXT 3343 +#define F_CASH_MUL_INT8 3344 +#define F_CASH_DIV_INT8 3345 +#define F_TXID_CURRENT_IF_ASSIGNED 3348 +#define F_PG_GET_PARTKEYDEF 3352 +#define F_PG_LS_LOGDIR 3353 +#define F_PG_LS_WALDIR 3354 +#define F_PG_NDISTINCT_IN 3355 +#define F_PG_NDISTINCT_OUT 3356 +#define F_PG_NDISTINCT_RECV 3357 +#define F_PG_NDISTINCT_SEND 3358 +#define F_MACADDR_SORTSUPPORT 3359 +#define F_TXID_STATUS 3360 +#define F_PG_SAFE_SNAPSHOT_BLOCKING_PIDS 3376 +#define F_PG_ISOLATION_TEST_SESSION_IS_BLOCKED 3378 +#define F_PG_IDENTIFY_OBJECT_AS_ADDRESS 3382 +#define F_BRIN_MINMAX_OPCINFO 3383 +#define F_BRIN_MINMAX_ADD_VALUE 3384 +#define F_BRIN_MINMAX_CONSISTENT 3385 +#define F_BRIN_MINMAX_UNION 3386 +#define F_INT8_AVG_ACCUM_INV 3387 +#define F_NUMERIC_POLY_SUM 3388 +#define F_NUMERIC_POLY_AVG 3389 +#define F_NUMERIC_POLY_VAR_POP 3390 +#define F_NUMERIC_POLY_VAR_SAMP 3391 +#define F_NUMERIC_POLY_STDDEV_POP 3392 +#define F_NUMERIC_POLY_STDDEV_SAMP 3393 +#define F_REGEXP_MATCH_TEXT_TEXT 3396 +#define F_REGEXP_MATCH_TEXT_TEXT_TEXT 3397 +#define F_INT8_MUL_CASH 3399 +#define F_PG_CONFIG 3400 +#define F_PG_HBA_FILE_RULES 3401 +#define F_PG_STATISTICS_OBJ_IS_VISIBLE 3403 +#define F_PG_DEPENDENCIES_IN 3404 +#define F_PG_DEPENDENCIES_OUT 3405 +#define F_PG_DEPENDENCIES_RECV 3406 +#define F_PG_DEPENDENCIES_SEND 3407 +#define F_PG_GET_PARTITION_CONSTRAINTDEF 3408 +#define F_TIME_HASH_EXTENDED 3409 +#define F_TIMETZ_HASH_EXTENDED 3410 +#define F_TIMESTAMP_HASH_EXTENDED 3411 +#define F_UUID_HASH_EXTENDED 3412 +#define F_PG_LSN_HASH_EXTENDED 3413 +#define F_HASHENUMEXTENDED 3414 +#define F_PG_GET_STATISTICSOBJDEF 3415 +#define F_JSONB_HASH_EXTENDED 3416 +#define F_HASH_RANGE_EXTENDED 3417 +#define F_INTERVAL_HASH_EXTENDED 3418 +#define F_SHA224 3419 +#define F_SHA256 3420 +#define F_SHA384 3421 +#define F_SHA512 3422 +#define F_PG_PARTITION_TREE 3423 +#define F_PG_PARTITION_ROOT 3424 +#define F_PG_PARTITION_ANCESTORS 3425 +#define F_PG_STAT_GET_DB_CHECKSUM_FAILURES 3426 +#define F_PG_MCV_LIST_ITEMS 3427 +#define F_PG_STAT_GET_DB_CHECKSUM_LAST_FAILURE 3428 +#define F_GEN_RANDOM_UUID 3432 +#define F_GTSVECTOR_OPTIONS 3434 +#define F_GIST_POINT_SORTSUPPORT 3435 +#define F_PG_PROMOTE 3436 +#define F_PREFIXSEL 3437 +#define F_PREFIXJOINSEL 3438 +#define F_PG_CONTROL_SYSTEM 3441 +#define F_PG_CONTROL_CHECKPOINT 3442 +#define F_PG_CONTROL_RECOVERY 3443 +#define F_PG_CONTROL_INIT 3444 +#define F_PG_IMPORT_SYSTEM_COLLATIONS 3445 +#define F_MACADDR8_RECV 3446 +#define F_MACADDR8_SEND 3447 +#define F_PG_COLLATION_ACTUAL_VERSION 3448 +#define F_NUMERIC_JSONB 3449 +#define F_INT2_JSONB 3450 +#define F_INT4_JSONB 3451 +#define F_INT8_JSONB 3452 +#define F_FLOAT4_JSONB 3453 +#define F_PG_FILENODE_RELATION 3454 +#define F_LO_FROM_BYTEA 3457 +#define F_LO_GET_OID 3458 +#define F_LO_GET_OID_INT8_INT4 3459 +#define F_LO_PUT 3460 +#define F_MAKE_TIMESTAMP 3461 +#define F_MAKE_TIMESTAMPTZ_INT4_INT4_INT4_INT4_INT4_FLOAT8 3462 +#define F_MAKE_TIMESTAMPTZ_INT4_INT4_INT4_INT4_INT4_FLOAT8_TEXT 3463 +#define F_MAKE_INTERVAL 3464 +#define F_JSONB_ARRAY_ELEMENTS_TEXT 3465 +#define F_SPG_RANGE_QUAD_CONFIG 3469 +#define F_SPG_RANGE_QUAD_CHOOSE 3470 +#define F_SPG_RANGE_QUAD_PICKSPLIT 3471 +#define F_SPG_RANGE_QUAD_INNER_CONSISTENT 3472 +#define F_SPG_RANGE_QUAD_LEAF_CONSISTENT 3473 +#define F_JSONB_POPULATE_RECORDSET 3475 +#define F_TO_REGOPERATOR 3476 +#define F_JSONB_OBJECT_FIELD 3478 +#define F_TO_REGPROCEDURE 3479 +#define F_GIN_COMPARE_JSONB 3480 +#define F_GIN_EXTRACT_JSONB 3482 +#define F_GIN_EXTRACT_JSONB_QUERY 3483 +#define F_GIN_CONSISTENT_JSONB 3484 +#define F_GIN_EXTRACT_JSONB_PATH 3485 +#define F_GIN_EXTRACT_JSONB_QUERY_PATH 3486 +#define F_GIN_CONSISTENT_JSONB_PATH 3487 +#define F_GIN_TRICONSISTENT_JSONB 3488 +#define F_GIN_TRICONSISTENT_JSONB_PATH 3489 +#define F_JSONB_TO_RECORD 3490 +#define F_JSONB_TO_RECORDSET 3491 +#define F_TO_REGOPER 3492 +#define F_TO_REGTYPE 3493 +#define F_TO_REGPROC 3494 +#define F_TO_REGCLASS 3495 +#define F_BOOL_ACCUM 3496 +#define F_BOOL_ACCUM_INV 3497 +#define F_BOOL_ALLTRUE 3498 +#define F_BOOL_ANYTRUE 3499 +#define F_ANYENUM_IN 3504 +#define F_ANYENUM_OUT 3505 +#define F_ENUM_IN 3506 +#define F_ENUM_OUT 3507 +#define F_ENUM_EQ 3508 +#define F_ENUM_NE 3509 +#define F_ENUM_LT 3510 +#define F_ENUM_GT 3511 +#define F_ENUM_LE 3512 +#define F_ENUM_GE 3513 +#define F_ENUM_CMP 3514 +#define F_HASHENUM 3515 +#define F_ENUM_SMALLER 3524 +#define F_ENUM_LARGER 3525 +#define F_MAX_ANYENUM 3526 +#define F_MIN_ANYENUM 3527 +#define F_ENUM_FIRST 3528 +#define F_ENUM_LAST 3529 +#define F_ENUM_RANGE_ANYENUM_ANYENUM 3530 +#define F_ENUM_RANGE_ANYENUM 3531 +#define F_ENUM_RECV 3532 +#define F_ENUM_SEND 3533 +#define F_STRING_AGG_TRANSFN 3535 +#define F_STRING_AGG_FINALFN 3536 +#define F_PG_DESCRIBE_OBJECT 3537 +#define F_STRING_AGG_TEXT_TEXT 3538 +#define F_FORMAT_TEXT_ANY 3539 +#define F_FORMAT_TEXT 3540 +#define F_BYTEA_STRING_AGG_TRANSFN 3543 +#define F_BYTEA_STRING_AGG_FINALFN 3544 +#define F_STRING_AGG_BYTEA_BYTEA 3545 +#define F_INT8DEC 3546 +#define F_INT8DEC_ANY 3547 +#define F_NUMERIC_ACCUM_INV 3548 +#define F_INTERVAL_ACCUM_INV 3549 +#define F_NETWORK_OVERLAP 3551 +#define F_INET_GIST_CONSISTENT 3553 +#define F_INET_GIST_UNION 3554 +#define F_INET_GIST_COMPRESS 3555 +#define F_BOOL_JSONB 3556 +#define F_INET_GIST_PENALTY 3557 +#define F_INET_GIST_PICKSPLIT 3558 +#define F_INET_GIST_SAME 3559 +#define F_NETWORKSEL 3560 +#define F_NETWORKJOINSEL 3561 +#define F_NETWORK_LARGER 3562 +#define F_NETWORK_SMALLER 3563 +#define F_MAX_INET 3564 +#define F_MIN_INET 3565 +#define F_PG_EVENT_TRIGGER_DROPPED_OBJECTS 3566 +#define F_INT2_ACCUM_INV 3567 +#define F_INT4_ACCUM_INV 3568 +#define F_INT8_ACCUM_INV 3569 +#define F_INT2_AVG_ACCUM_INV 3570 +#define F_INT4_AVG_ACCUM_INV 3571 +#define F_INT2INT4_SUM 3572 +#define F_INET_GIST_FETCH 3573 +#define F_PG_LOGICAL_EMIT_MESSAGE_BOOL_TEXT_TEXT 3577 +#define F_PG_LOGICAL_EMIT_MESSAGE_BOOL_TEXT_BYTEA 3578 +#define F_JSONB_INSERT 3579 +#define F_PG_XACT_COMMIT_TIMESTAMP 3581 +#define F_BINARY_UPGRADE_SET_NEXT_PG_TYPE_OID 3582 +#define F_PG_LAST_COMMITTED_XACT 3583 +#define F_BINARY_UPGRADE_SET_NEXT_ARRAY_PG_TYPE_OID 3584 +#define F_BINARY_UPGRADE_SET_NEXT_HEAP_PG_CLASS_OID 3586 +#define F_BINARY_UPGRADE_SET_NEXT_INDEX_PG_CLASS_OID 3587 +#define F_BINARY_UPGRADE_SET_NEXT_TOAST_PG_CLASS_OID 3588 +#define F_BINARY_UPGRADE_SET_NEXT_PG_ENUM_OID 3589 +#define F_BINARY_UPGRADE_SET_NEXT_PG_AUTHID_OID 3590 +#define F_BINARY_UPGRADE_CREATE_EMPTY_EXTENSION 3591 +#define F_EVENT_TRIGGER_IN 3594 +#define F_EVENT_TRIGGER_OUT 3595 +#define F_TSVECTORIN 3610 +#define F_TSVECTOROUT 3611 +#define F_TSQUERYIN 3612 +#define F_TSQUERYOUT 3613 +#define F_TSVECTOR_LT 3616 +#define F_TSVECTOR_LE 3617 +#define F_TSVECTOR_EQ 3618 +#define F_TSVECTOR_NE 3619 +#define F_TSVECTOR_GE 3620 +#define F_TSVECTOR_GT 3621 +#define F_TSVECTOR_CMP 3622 +#define F_STRIP 3623 +#define F_SETWEIGHT_TSVECTOR_CHAR 3624 +#define F_TSVECTOR_CONCAT 3625 +#define F_TS_MATCH_VQ 3634 +#define F_TS_MATCH_QV 3635 +#define F_TSVECTORSEND 3638 +#define F_TSVECTORRECV 3639 +#define F_TSQUERYSEND 3640 +#define F_TSQUERYRECV 3641 +#define F_GTSVECTORIN 3646 +#define F_GTSVECTOROUT 3647 +#define F_GTSVECTOR_COMPRESS 3648 +#define F_GTSVECTOR_DECOMPRESS 3649 +#define F_GTSVECTOR_PICKSPLIT 3650 +#define F_GTSVECTOR_UNION 3651 +#define F_GTSVECTOR_SAME 3652 +#define F_GTSVECTOR_PENALTY 3653 +#define F_GTSVECTOR_CONSISTENT_INTERNAL_TSVECTOR_INT2_OID_INTERNAL 3654 +#define F_GIN_EXTRACT_TSVECTOR_TSVECTOR_INTERNAL_INTERNAL 3656 +#define F_GIN_EXTRACT_TSQUERY_TSVECTOR_INTERNAL_INT2_INTERNAL_INTERNAL_INTERNAL_INTERNAL 3657 +#define F_GIN_TSQUERY_CONSISTENT_INTERNAL_INT2_TSVECTOR_INT4_INTERNAL_INTERNAL_INTERNAL_INTERNAL 3658 +#define F_TSQUERY_LT 3662 +#define F_TSQUERY_LE 3663 +#define F_TSQUERY_EQ 3664 +#define F_TSQUERY_NE 3665 +#define F_TSQUERY_GE 3666 +#define F_TSQUERY_GT 3667 +#define F_TSQUERY_CMP 3668 +#define F_TSQUERY_AND 3669 +#define F_TSQUERY_OR 3670 +#define F_TSQUERY_NOT 3671 +#define F_NUMNODE 3672 +#define F_QUERYTREE 3673 +#define F_TS_REWRITE_TSQUERY_TSQUERY_TSQUERY 3684 +#define F_TS_REWRITE_TSQUERY_TEXT 3685 +#define F_TSMATCHSEL 3686 +#define F_TSMATCHJOINSEL 3687 +#define F_TS_TYPANALYZE 3688 +#define F_TS_STAT_TEXT 3689 +#define F_TS_STAT_TEXT_TEXT 3690 +#define F_TSQ_MCONTAINS 3691 +#define F_TSQ_MCONTAINED 3692 +#define F_GTSQUERY_COMPRESS 3695 +#define F_STARTS_WITH 3696 +#define F_GTSQUERY_PICKSPLIT 3697 +#define F_GTSQUERY_UNION 3698 +#define F_GTSQUERY_SAME 3699 +#define F_GTSQUERY_PENALTY 3700 +#define F_GTSQUERY_CONSISTENT_INTERNAL_TSQUERY_INT2_OID_INTERNAL 3701 +#define F_TS_RANK__FLOAT4_TSVECTOR_TSQUERY_INT4 3703 +#define F_TS_RANK__FLOAT4_TSVECTOR_TSQUERY 3704 +#define F_TS_RANK_TSVECTOR_TSQUERY_INT4 3705 +#define F_TS_RANK_TSVECTOR_TSQUERY 3706 +#define F_TS_RANK_CD__FLOAT4_TSVECTOR_TSQUERY_INT4 3707 +#define F_TS_RANK_CD__FLOAT4_TSVECTOR_TSQUERY 3708 +#define F_TS_RANK_CD_TSVECTOR_TSQUERY_INT4 3709 +#define F_TS_RANK_CD_TSVECTOR_TSQUERY 3710 +#define F_LENGTH_TSVECTOR 3711 +#define F_TS_TOKEN_TYPE_OID 3713 +#define F_TS_TOKEN_TYPE_TEXT 3714 +#define F_TS_PARSE_OID_TEXT 3715 +#define F_TS_PARSE_TEXT_TEXT 3716 +#define F_PRSD_START 3717 +#define F_PRSD_NEXTTOKEN 3718 +#define F_PRSD_END 3719 +#define F_PRSD_HEADLINE 3720 +#define F_PRSD_LEXTYPE 3721 +#define F_TS_LEXIZE 3723 +#define F_GIN_CMP_TSLEXEME 3724 +#define F_DSIMPLE_INIT 3725 +#define F_DSIMPLE_LEXIZE 3726 +#define F_DSYNONYM_INIT 3728 +#define F_DSYNONYM_LEXIZE 3729 +#define F_DISPELL_INIT 3731 +#define F_DISPELL_LEXIZE 3732 +#define F_REGCONFIGIN 3736 +#define F_REGCONFIGOUT 3737 +#define F_REGCONFIGRECV 3738 +#define F_REGCONFIGSEND 3739 +#define F_THESAURUS_INIT 3740 +#define F_THESAURUS_LEXIZE 3741 +#define F_TS_HEADLINE_REGCONFIG_TEXT_TSQUERY_TEXT 3743 +#define F_TS_HEADLINE_REGCONFIG_TEXT_TSQUERY 3744 +#define F_TO_TSVECTOR_REGCONFIG_TEXT 3745 +#define F_TO_TSQUERY_REGCONFIG_TEXT 3746 +#define F_PLAINTO_TSQUERY_REGCONFIG_TEXT 3747 +#define F_TO_TSVECTOR_TEXT 3749 +#define F_TO_TSQUERY_TEXT 3750 +#define F_PLAINTO_TSQUERY_TEXT 3751 +#define F_TSVECTOR_UPDATE_TRIGGER 3752 +#define F_TSVECTOR_UPDATE_TRIGGER_COLUMN 3753 +#define F_TS_HEADLINE_TEXT_TSQUERY_TEXT 3754 +#define F_TS_HEADLINE_TEXT_TSQUERY 3755 +#define F_PG_TS_PARSER_IS_VISIBLE 3756 +#define F_PG_TS_DICT_IS_VISIBLE 3757 +#define F_PG_TS_CONFIG_IS_VISIBLE 3758 +#define F_GET_CURRENT_TS_CONFIG 3759 +#define F_TS_MATCH_TT 3760 +#define F_TS_MATCH_TQ 3761 +#define F_PG_TS_TEMPLATE_IS_VISIBLE 3768 +#define F_REGDICTIONARYIN 3771 +#define F_REGDICTIONARYOUT 3772 +#define F_REGDICTIONARYRECV 3773 +#define F_REGDICTIONARYSEND 3774 +#define F_PG_STAT_RESET_SHARED 3775 +#define F_PG_STAT_RESET_SINGLE_TABLE_COUNTERS 3776 +#define F_PG_STAT_RESET_SINGLE_FUNCTION_COUNTERS 3777 +#define F_PG_TABLESPACE_LOCATION 3778 +#define F_PG_CREATE_PHYSICAL_REPLICATION_SLOT 3779 +#define F_PG_DROP_REPLICATION_SLOT 3780 +#define F_PG_GET_REPLICATION_SLOTS 3781 +#define F_PG_LOGICAL_SLOT_GET_CHANGES 3782 +#define F_PG_LOGICAL_SLOT_GET_BINARY_CHANGES 3783 +#define F_PG_LOGICAL_SLOT_PEEK_CHANGES 3784 +#define F_PG_LOGICAL_SLOT_PEEK_BINARY_CHANGES 3785 +#define F_PG_CREATE_LOGICAL_REPLICATION_SLOT 3786 +#define F_TO_JSONB 3787 +#define F_PG_STAT_GET_SNAPSHOT_TIMESTAMP 3788 +#define F_GIN_CLEAN_PENDING_LIST 3789 +#define F_GTSVECTOR_CONSISTENT_INTERNAL_GTSVECTOR_INT4_OID_INTERNAL 3790 +#define F_GIN_EXTRACT_TSQUERY_TSQUERY_INTERNAL_INT2_INTERNAL_INTERNAL_INTERNAL_INTERNAL 3791 +#define F_GIN_TSQUERY_CONSISTENT_INTERNAL_INT2_TSQUERY_INT4_INTERNAL_INTERNAL_INTERNAL_INTERNAL 3792 +#define F_GTSQUERY_CONSISTENT_INTERNAL_INTERNAL_INT4_OID_INTERNAL 3793 +#define F_INET_SPG_CONFIG 3795 +#define F_INET_SPG_CHOOSE 3796 +#define F_INET_SPG_PICKSPLIT 3797 +#define F_INET_SPG_INNER_CONSISTENT 3798 +#define F_INET_SPG_LEAF_CONSISTENT 3799 +#define F_PG_CURRENT_LOGFILE_ 3800 +#define F_PG_CURRENT_LOGFILE_TEXT 3801 +#define F_JSONB_SEND 3803 +#define F_JSONB_OUT 3804 +#define F_JSONB_RECV 3805 +#define F_JSONB_IN 3806 +#define F_PG_GET_FUNCTION_ARG_DEFAULT 3808 +#define F_PG_EXPORT_SNAPSHOT 3809 +#define F_PG_IS_IN_RECOVERY 3810 +#define F_MONEY_INT4 3811 +#define F_MONEY_INT8 3812 +#define F_PG_COLLATION_IS_VISIBLE 3815 +#define F_ARRAY_TYPANALYZE 3816 +#define F_ARRAYCONTSEL 3817 +#define F_ARRAYCONTJOINSEL 3818 +#define F_PG_GET_MULTIXACT_MEMBERS 3819 +#define F_PG_LAST_WAL_RECEIVE_LSN 3820 +#define F_PG_LAST_WAL_REPLAY_LSN 3821 +#define F_CASH_DIV_CASH 3822 +#define F_NUMERIC_MONEY 3823 +#define F_MONEY_NUMERIC 3824 +#define F_PG_READ_FILE_TEXT 3826 +#define F_PG_READ_BINARY_FILE_TEXT_INT8_INT8 3827 +#define F_PG_READ_BINARY_FILE_TEXT 3828 +#define F_PG_OPFAMILY_IS_VISIBLE 3829 +#define F_PG_LAST_XACT_REPLAY_TIMESTAMP 3830 +#define F_ANYRANGE_IN 3832 +#define F_ANYRANGE_OUT 3833 +#define F_RANGE_IN 3834 +#define F_RANGE_OUT 3835 +#define F_RANGE_RECV 3836 +#define F_RANGE_SEND 3837 +#define F_PG_IDENTIFY_OBJECT 3839 +#define F_INT4RANGE_INT4_INT4 3840 +#define F_INT4RANGE_INT4_INT4_TEXT 3841 +#define F_PG_RELATION_IS_UPDATABLE 3842 +#define F_PG_COLUMN_IS_UPDATABLE 3843 +#define F_NUMRANGE_NUMERIC_NUMERIC 3844 +#define F_NUMRANGE_NUMERIC_NUMERIC_TEXT 3845 +#define F_MAKE_DATE 3846 +#define F_MAKE_TIME 3847 +#define F_LOWER_ANYRANGE 3848 +#define F_UPPER_ANYRANGE 3849 +#define F_ISEMPTY_ANYRANGE 3850 +#define F_LOWER_INC_ANYRANGE 3851 +#define F_UPPER_INC_ANYRANGE 3852 +#define F_LOWER_INF_ANYRANGE 3853 +#define F_UPPER_INF_ANYRANGE 3854 +#define F_RANGE_EQ 3855 +#define F_RANGE_NE 3856 +#define F_RANGE_OVERLAPS 3857 +#define F_RANGE_CONTAINS_ELEM 3858 +#define F_RANGE_CONTAINS 3859 +#define F_ELEM_CONTAINED_BY_RANGE 3860 +#define F_RANGE_CONTAINED_BY 3861 +#define F_RANGE_ADJACENT 3862 +#define F_RANGE_BEFORE 3863 +#define F_RANGE_AFTER 3864 +#define F_RANGE_OVERLEFT 3865 +#define F_RANGE_OVERRIGHT 3866 +#define F_RANGE_UNION 3867 +#define F_RANGE_INTERSECT 3868 +#define F_RANGE_MINUS 3869 +#define F_RANGE_CMP 3870 +#define F_RANGE_LT 3871 +#define F_RANGE_LE 3872 +#define F_RANGE_GE 3873 +#define F_RANGE_GT 3874 +#define F_RANGE_GIST_CONSISTENT 3875 +#define F_RANGE_GIST_UNION 3876 +#define F_PG_REPLICATION_SLOT_ADVANCE 3878 +#define F_RANGE_GIST_PENALTY 3879 +#define F_RANGE_GIST_PICKSPLIT 3880 +#define F_RANGE_GIST_SAME 3881 +#define F_HASH_RANGE 3902 +#define F_INT4RANGE_CANONICAL 3914 +#define F_DATERANGE_CANONICAL 3915 +#define F_RANGE_TYPANALYZE 3916 +#define F_TIMESTAMP_SUPPORT 3917 +#define F_INTERVAL_SUPPORT 3918 +#define F_GINARRAYTRICONSISTENT 3920 +#define F_GIN_TSQUERY_TRICONSISTENT 3921 +#define F_INT4RANGE_SUBDIFF 3922 +#define F_INT8RANGE_SUBDIFF 3923 +#define F_NUMRANGE_SUBDIFF 3924 +#define F_DATERANGE_SUBDIFF 3925 +#define F_INT8RANGE_CANONICAL 3928 +#define F_TSRANGE_SUBDIFF 3929 +#define F_TSTZRANGE_SUBDIFF 3930 +#define F_JSONB_OBJECT_KEYS 3931 +#define F_JSONB_EACH_TEXT 3932 +#define F_TSRANGE_TIMESTAMP_TIMESTAMP 3933 +#define F_TSRANGE_TIMESTAMP_TIMESTAMP_TEXT 3934 +#define F_PG_SLEEP_FOR 3935 +#define F_PG_SLEEP_UNTIL 3936 +#define F_TSTZRANGE_TIMESTAMPTZ_TIMESTAMPTZ 3937 +#define F_TSTZRANGE_TIMESTAMPTZ_TIMESTAMPTZ_TEXT 3938 +#define F_MXID_AGE 3939 +#define F_JSONB_EXTRACT_PATH_TEXT 3940 +#define F_DATERANGE_DATE_DATE 3941 +#define F_DATERANGE_DATE_DATE_TEXT 3942 +#define F_ACLDEFAULT 3943 +#define F_TIME_SUPPORT 3944 +#define F_INT8RANGE_INT8_INT8 3945 +#define F_INT8RANGE_INT8_INT8_TEXT 3946 +#define F_JSON_OBJECT_FIELD 3947 +#define F_JSON_OBJECT_FIELD_TEXT 3948 +#define F_JSON_ARRAY_ELEMENT 3949 +#define F_JSON_ARRAY_ELEMENT_TEXT 3950 +#define F_JSON_EXTRACT_PATH 3951 +#define F_BRIN_SUMMARIZE_NEW_VALUES 3952 +#define F_JSON_EXTRACT_PATH_TEXT 3953 +#define F_PG_GET_OBJECT_ADDRESS 3954 +#define F_JSON_ARRAY_ELEMENTS 3955 +#define F_JSON_ARRAY_LENGTH 3956 +#define F_JSON_OBJECT_KEYS 3957 +#define F_JSON_EACH 3958 +#define F_JSON_EACH_TEXT 3959 +#define F_JSON_POPULATE_RECORD 3960 +#define F_JSON_POPULATE_RECORDSET 3961 +#define F_JSON_TYPEOF 3968 +#define F_JSON_ARRAY_ELEMENTS_TEXT 3969 +#define F_ORDERED_SET_TRANSITION 3970 +#define F_ORDERED_SET_TRANSITION_MULTI 3971 +#define F_PERCENTILE_DISC_FLOAT8_ANYELEMENT 3972 +#define F_PERCENTILE_DISC_FINAL 3973 +#define F_PERCENTILE_CONT_FLOAT8_FLOAT8 3974 +#define F_PERCENTILE_CONT_FLOAT8_FINAL 3975 +#define F_PERCENTILE_CONT_FLOAT8_INTERVAL 3976 +#define F_PERCENTILE_CONT_INTERVAL_FINAL 3977 +#define F_PERCENTILE_DISC__FLOAT8_ANYELEMENT 3978 +#define F_PERCENTILE_DISC_MULTI_FINAL 3979 +#define F_PERCENTILE_CONT__FLOAT8_FLOAT8 3980 +#define F_PERCENTILE_CONT_FLOAT8_MULTI_FINAL 3981 +#define F_PERCENTILE_CONT__FLOAT8_INTERVAL 3982 +#define F_PERCENTILE_CONT_INTERVAL_MULTI_FINAL 3983 +#define F_MODE 3984 +#define F_MODE_FINAL 3985 +#define F_RANK_ANY 3986 +#define F_RANK_FINAL 3987 +#define F_PERCENT_RANK_ANY 3988 +#define F_PERCENT_RANK_FINAL 3989 +#define F_CUME_DIST_ANY 3990 +#define F_CUME_DIST_FINAL 3991 +#define F_DENSE_RANK_ANY 3992 +#define F_DENSE_RANK_FINAL 3993 +#define F_GENERATE_SERIES_INT4_SUPPORT 3994 +#define F_GENERATE_SERIES_INT8_SUPPORT 3995 +#define F_ARRAY_UNNEST_SUPPORT 3996 +#define F_GIST_BOX_DISTANCE 3998 +#define F_BRIN_SUMMARIZE_RANGE 3999 +#define F_JSONPATH_IN 4001 +#define F_JSONPATH_RECV 4002 +#define F_JSONPATH_OUT 4003 +#define F_JSONPATH_SEND 4004 +#define F_JSONB_PATH_EXISTS 4005 +#define F_JSONB_PATH_QUERY 4006 +#define F_JSONB_PATH_QUERY_ARRAY 4007 +#define F_JSONB_PATH_QUERY_FIRST 4008 +#define F_JSONB_PATH_MATCH 4009 +#define F_JSONB_PATH_EXISTS_OPR 4010 +#define F_JSONB_PATH_MATCH_OPR 4011 +#define F_BRIN_DESUMMARIZE_RANGE 4014 +#define F_SPG_QUAD_CONFIG 4018 +#define F_SPG_QUAD_CHOOSE 4019 +#define F_SPG_QUAD_PICKSPLIT 4020 +#define F_SPG_QUAD_INNER_CONSISTENT 4021 +#define F_SPG_QUAD_LEAF_CONSISTENT 4022 +#define F_SPG_KD_CONFIG 4023 +#define F_SPG_KD_CHOOSE 4024 +#define F_SPG_KD_PICKSPLIT 4025 +#define F_SPG_KD_INNER_CONSISTENT 4026 +#define F_SPG_TEXT_CONFIG 4027 +#define F_SPG_TEXT_CHOOSE 4028 +#define F_SPG_TEXT_PICKSPLIT 4029 +#define F_SPG_TEXT_INNER_CONSISTENT 4030 +#define F_SPG_TEXT_LEAF_CONSISTENT 4031 +#define F_PG_SEQUENCE_LAST_VALUE 4032 +#define F_JSONB_NE 4038 +#define F_JSONB_LT 4039 +#define F_JSONB_GT 4040 +#define F_JSONB_LE 4041 +#define F_JSONB_GE 4042 +#define F_JSONB_EQ 4043 +#define F_JSONB_CMP 4044 +#define F_JSONB_HASH 4045 +#define F_JSONB_CONTAINS 4046 +#define F_JSONB_EXISTS 4047 +#define F_JSONB_EXISTS_ANY 4048 +#define F_JSONB_EXISTS_ALL 4049 +#define F_JSONB_CONTAINED 4050 +#define F_ARRAY_AGG_ARRAY_TRANSFN 4051 +#define F_ARRAY_AGG_ARRAY_FINALFN 4052 +#define F_ARRAY_AGG_ANYARRAY 4053 +#define F_RANGE_MERGE_ANYRANGE_ANYRANGE 4057 +#define F_INET_MERGE 4063 +#define F_BOUND_BOX 4067 +#define F_INET_SAME_FAMILY 4071 +#define F_BINARY_UPGRADE_SET_RECORD_INIT_PRIVS 4083 +#define F_REGNAMESPACEIN 4084 +#define F_REGNAMESPACEOUT 4085 +#define F_TO_REGNAMESPACE 4086 +#define F_REGNAMESPACERECV 4087 +#define F_REGNAMESPACESEND 4088 +#define F_BOX_POINT 4091 +#define F_REGROLEOUT 4092 +#define F_TO_REGROLE 4093 +#define F_REGROLERECV 4094 +#define F_REGROLESEND 4095 +#define F_REGROLEIN 4098 +#define F_PG_ROTATE_LOGFILE_OLD 4099 +#define F_PG_READ_FILE_OLD 4100 +#define F_BINARY_UPGRADE_SET_MISSING_VALUE 4101 +#define F_BRIN_INCLUSION_OPCINFO 4105 +#define F_BRIN_INCLUSION_ADD_VALUE 4106 +#define F_BRIN_INCLUSION_CONSISTENT 4107 +#define F_BRIN_INCLUSION_UNION 4108 +#define F_MACADDR8_IN 4110 +#define F_MACADDR8_OUT 4111 +#define F_TRUNC_MACADDR8 4112 +#define F_MACADDR8_EQ 4113 +#define F_MACADDR8_LT 4114 +#define F_MACADDR8_LE 4115 +#define F_MACADDR8_GT 4116 +#define F_MACADDR8_GE 4117 +#define F_MACADDR8_NE 4118 +#define F_MACADDR8_CMP 4119 +#define F_MACADDR8_NOT 4120 +#define F_MACADDR8_AND 4121 +#define F_MACADDR8_OR 4122 +#define F_MACADDR8 4123 +#define F_MACADDR 4124 +#define F_MACADDR8_SET7BIT 4125 +#define F_IN_RANGE_INT8_INT8_INT8_BOOL_BOOL 4126 +#define F_IN_RANGE_INT4_INT4_INT8_BOOL_BOOL 4127 +#define F_IN_RANGE_INT4_INT4_INT4_BOOL_BOOL 4128 +#define F_IN_RANGE_INT4_INT4_INT2_BOOL_BOOL 4129 +#define F_IN_RANGE_INT2_INT2_INT8_BOOL_BOOL 4130 +#define F_IN_RANGE_INT2_INT2_INT4_BOOL_BOOL 4131 +#define F_IN_RANGE_INT2_INT2_INT2_BOOL_BOOL 4132 +#define F_IN_RANGE_DATE_DATE_INTERVAL_BOOL_BOOL 4133 +#define F_IN_RANGE_TIMESTAMP_TIMESTAMP_INTERVAL_BOOL_BOOL 4134 +#define F_IN_RANGE_TIMESTAMPTZ_TIMESTAMPTZ_INTERVAL_BOOL_BOOL 4135 +#define F_IN_RANGE_INTERVAL_INTERVAL_INTERVAL_BOOL_BOOL 4136 +#define F_IN_RANGE_TIME_TIME_INTERVAL_BOOL_BOOL 4137 +#define F_IN_RANGE_TIMETZ_TIMETZ_INTERVAL_BOOL_BOOL 4138 +#define F_IN_RANGE_FLOAT8_FLOAT8_FLOAT8_BOOL_BOOL 4139 +#define F_IN_RANGE_FLOAT4_FLOAT4_FLOAT8_BOOL_BOOL 4140 +#define F_IN_RANGE_NUMERIC_NUMERIC_NUMERIC_BOOL_BOOL 4141 +#define F_PG_LSN_LARGER 4187 +#define F_PG_LSN_SMALLER 4188 +#define F_MAX_PG_LSN 4189 +#define F_MIN_PG_LSN 4190 +#define F_REGCOLLATIONIN 4193 +#define F_REGCOLLATIONOUT 4194 +#define F_TO_REGCOLLATION 4195 +#define F_REGCOLLATIONRECV 4196 +#define F_REGCOLLATIONSEND 4197 +#define F_TS_HEADLINE_REGCONFIG_JSONB_TSQUERY_TEXT 4201 +#define F_TS_HEADLINE_REGCONFIG_JSONB_TSQUERY 4202 +#define F_TS_HEADLINE_JSONB_TSQUERY_TEXT 4203 +#define F_TS_HEADLINE_JSONB_TSQUERY 4204 +#define F_TS_HEADLINE_REGCONFIG_JSON_TSQUERY_TEXT 4205 +#define F_TS_HEADLINE_REGCONFIG_JSON_TSQUERY 4206 +#define F_TS_HEADLINE_JSON_TSQUERY_TEXT 4207 +#define F_TS_HEADLINE_JSON_TSQUERY 4208 +#define F_TO_TSVECTOR_JSONB 4209 +#define F_TO_TSVECTOR_JSON 4210 +#define F_TO_TSVECTOR_REGCONFIG_JSONB 4211 +#define F_TO_TSVECTOR_REGCONFIG_JSON 4212 +#define F_JSONB_TO_TSVECTOR_JSONB_JSONB 4213 +#define F_JSONB_TO_TSVECTOR_REGCONFIG_JSONB_JSONB 4214 +#define F_JSON_TO_TSVECTOR_JSON_JSONB 4215 +#define F_JSON_TO_TSVECTOR_REGCONFIG_JSON_JSONB 4216 +#define F_PG_COPY_PHYSICAL_REPLICATION_SLOT_NAME_NAME_BOOL 4220 +#define F_PG_COPY_PHYSICAL_REPLICATION_SLOT_NAME_NAME 4221 +#define F_PG_COPY_LOGICAL_REPLICATION_SLOT_NAME_NAME_BOOL_NAME 4222 +#define F_PG_COPY_LOGICAL_REPLICATION_SLOT_NAME_NAME_BOOL 4223 +#define F_PG_COPY_LOGICAL_REPLICATION_SLOT_NAME_NAME 4224 +#define F_ANYCOMPATIBLEMULTIRANGE_IN 4226 +#define F_ANYCOMPATIBLEMULTIRANGE_OUT 4227 +#define F_RANGE_MERGE_ANYMULTIRANGE 4228 +#define F_ANYMULTIRANGE_IN 4229 +#define F_ANYMULTIRANGE_OUT 4230 +#define F_MULTIRANGE_IN 4231 +#define F_MULTIRANGE_OUT 4232 +#define F_MULTIRANGE_RECV 4233 +#define F_MULTIRANGE_SEND 4234 +#define F_LOWER_ANYMULTIRANGE 4235 +#define F_UPPER_ANYMULTIRANGE 4236 +#define F_ISEMPTY_ANYMULTIRANGE 4237 +#define F_LOWER_INC_ANYMULTIRANGE 4238 +#define F_UPPER_INC_ANYMULTIRANGE 4239 +#define F_LOWER_INF_ANYMULTIRANGE 4240 +#define F_UPPER_INF_ANYMULTIRANGE 4241 +#define F_MULTIRANGE_TYPANALYZE 4242 +#define F_MULTIRANGESEL 4243 +#define F_MULTIRANGE_EQ 4244 +#define F_MULTIRANGE_NE 4245 +#define F_RANGE_OVERLAPS_MULTIRANGE 4246 +#define F_MULTIRANGE_OVERLAPS_RANGE 4247 +#define F_MULTIRANGE_OVERLAPS_MULTIRANGE 4248 +#define F_MULTIRANGE_CONTAINS_ELEM 4249 +#define F_MULTIRANGE_CONTAINS_RANGE 4250 +#define F_MULTIRANGE_CONTAINS_MULTIRANGE 4251 +#define F_ELEM_CONTAINED_BY_MULTIRANGE 4252 +#define F_RANGE_CONTAINED_BY_MULTIRANGE 4253 +#define F_MULTIRANGE_CONTAINED_BY_MULTIRANGE 4254 +#define F_RANGE_ADJACENT_MULTIRANGE 4255 +#define F_MULTIRANGE_ADJACENT_MULTIRANGE 4256 +#define F_MULTIRANGE_ADJACENT_RANGE 4257 +#define F_RANGE_BEFORE_MULTIRANGE 4258 +#define F_MULTIRANGE_BEFORE_RANGE 4259 +#define F_MULTIRANGE_BEFORE_MULTIRANGE 4260 +#define F_RANGE_AFTER_MULTIRANGE 4261 +#define F_MULTIRANGE_AFTER_RANGE 4262 +#define F_MULTIRANGE_AFTER_MULTIRANGE 4263 +#define F_RANGE_OVERLEFT_MULTIRANGE 4264 +#define F_MULTIRANGE_OVERLEFT_RANGE 4265 +#define F_MULTIRANGE_OVERLEFT_MULTIRANGE 4266 +#define F_RANGE_OVERRIGHT_MULTIRANGE 4267 +#define F_MULTIRANGE_OVERRIGHT_RANGE 4268 +#define F_MULTIRANGE_OVERRIGHT_MULTIRANGE 4269 +#define F_MULTIRANGE_UNION 4270 +#define F_MULTIRANGE_MINUS 4271 +#define F_MULTIRANGE_INTERSECT 4272 +#define F_MULTIRANGE_CMP 4273 +#define F_MULTIRANGE_LT 4274 +#define F_MULTIRANGE_LE 4275 +#define F_MULTIRANGE_GE 4276 +#define F_MULTIRANGE_GT 4277 +#define F_HASH_MULTIRANGE 4278 +#define F_HASH_MULTIRANGE_EXTENDED 4279 +#define F_INT4MULTIRANGE_ 4280 +#define F_INT4MULTIRANGE_INT4RANGE 4281 +#define F_INT4MULTIRANGE__INT4RANGE 4282 +#define F_NUMMULTIRANGE_ 4283 +#define F_NUMMULTIRANGE_NUMRANGE 4284 +#define F_NUMMULTIRANGE__NUMRANGE 4285 +#define F_TSMULTIRANGE_ 4286 +#define F_TSMULTIRANGE_TSRANGE 4287 +#define F_TSMULTIRANGE__TSRANGE 4288 +#define F_TSTZMULTIRANGE_ 4289 +#define F_TSTZMULTIRANGE_TSTZRANGE 4290 +#define F_TSTZMULTIRANGE__TSTZRANGE 4291 +#define F_DATEMULTIRANGE_ 4292 +#define F_DATEMULTIRANGE_DATERANGE 4293 +#define F_DATEMULTIRANGE__DATERANGE 4294 +#define F_INT8MULTIRANGE_ 4295 +#define F_INT8MULTIRANGE_INT8RANGE 4296 +#define F_INT8MULTIRANGE__INT8RANGE 4297 +#define F_MULTIRANGE 4298 +#define F_RANGE_AGG_TRANSFN 4299 +#define F_RANGE_AGG_FINALFN 4300 +#define F_RANGE_AGG_ANYRANGE 4301 +#define F_KOI8R_TO_MIC 4302 +#define F_MIC_TO_KOI8R 4303 +#define F_ISO_TO_MIC 4304 +#define F_MIC_TO_ISO 4305 +#define F_WIN1251_TO_MIC 4306 +#define F_MIC_TO_WIN1251 4307 +#define F_WIN866_TO_MIC 4308 +#define F_MIC_TO_WIN866 4309 +#define F_KOI8R_TO_WIN1251 4310 +#define F_WIN1251_TO_KOI8R 4311 +#define F_KOI8R_TO_WIN866 4312 +#define F_WIN866_TO_KOI8R 4313 +#define F_WIN866_TO_WIN1251 4314 +#define F_WIN1251_TO_WIN866 4315 +#define F_ISO_TO_KOI8R 4316 +#define F_KOI8R_TO_ISO 4317 +#define F_ISO_TO_WIN1251 4318 +#define F_WIN1251_TO_ISO 4319 +#define F_ISO_TO_WIN866 4320 +#define F_WIN866_TO_ISO 4321 +#define F_EUC_CN_TO_MIC 4322 +#define F_MIC_TO_EUC_CN 4323 +#define F_EUC_JP_TO_SJIS 4324 +#define F_SJIS_TO_EUC_JP 4325 +#define F_EUC_JP_TO_MIC 4326 +#define F_SJIS_TO_MIC 4327 +#define F_MIC_TO_EUC_JP 4328 +#define F_MIC_TO_SJIS 4329 +#define F_EUC_KR_TO_MIC 4330 +#define F_MIC_TO_EUC_KR 4331 +#define F_EUC_TW_TO_BIG5 4332 +#define F_BIG5_TO_EUC_TW 4333 +#define F_EUC_TW_TO_MIC 4334 +#define F_BIG5_TO_MIC 4335 +#define F_MIC_TO_EUC_TW 4336 +#define F_MIC_TO_BIG5 4337 +#define F_LATIN2_TO_MIC 4338 +#define F_MIC_TO_LATIN2 4339 +#define F_WIN1250_TO_MIC 4340 +#define F_MIC_TO_WIN1250 4341 +#define F_LATIN2_TO_WIN1250 4342 +#define F_WIN1250_TO_LATIN2 4343 +#define F_LATIN1_TO_MIC 4344 +#define F_MIC_TO_LATIN1 4345 +#define F_LATIN3_TO_MIC 4346 +#define F_MIC_TO_LATIN3 4347 +#define F_LATIN4_TO_MIC 4348 +#define F_MIC_TO_LATIN4 4349 +#define F_NORMALIZE 4350 +#define F_IS_NORMALIZED 4351 +#define F_BIG5_TO_UTF8 4352 +#define F_UTF8_TO_BIG5 4353 +#define F_UTF8_TO_KOI8R 4354 +#define F_KOI8R_TO_UTF8 4355 +#define F_UTF8_TO_KOI8U 4356 +#define F_KOI8U_TO_UTF8 4357 +#define F_UTF8_TO_WIN 4358 +#define F_WIN_TO_UTF8 4359 +#define F_EUC_CN_TO_UTF8 4360 +#define F_UTF8_TO_EUC_CN 4361 +#define F_EUC_JP_TO_UTF8 4362 +#define F_UTF8_TO_EUC_JP 4363 +#define F_EUC_KR_TO_UTF8 4364 +#define F_UTF8_TO_EUC_KR 4365 +#define F_EUC_TW_TO_UTF8 4366 +#define F_UTF8_TO_EUC_TW 4367 +#define F_GB18030_TO_UTF8 4368 +#define F_UTF8_TO_GB18030 4369 +#define F_GBK_TO_UTF8 4370 +#define F_UTF8_TO_GBK 4371 +#define F_UTF8_TO_ISO8859 4372 +#define F_ISO8859_TO_UTF8 4373 +#define F_ISO8859_1_TO_UTF8 4374 +#define F_UTF8_TO_ISO8859_1 4375 +#define F_JOHAB_TO_UTF8 4376 +#define F_UTF8_TO_JOHAB 4377 +#define F_SJIS_TO_UTF8 4378 +#define F_UTF8_TO_SJIS 4379 +#define F_UHC_TO_UTF8 4380 +#define F_UTF8_TO_UHC 4381 +#define F_EUC_JIS_2004_TO_UTF8 4382 +#define F_UTF8_TO_EUC_JIS_2004 4383 +#define F_SHIFT_JIS_2004_TO_UTF8 4384 +#define F_UTF8_TO_SHIFT_JIS_2004 4385 +#define F_EUC_JIS_2004_TO_SHIFT_JIS_2004 4386 +#define F_SHIFT_JIS_2004_TO_EUC_JIS_2004 4387 +#define F_MULTIRANGE_INTERSECT_AGG_TRANSFN 4388 +#define F_RANGE_INTERSECT_AGG_ANYMULTIRANGE 4389 +#define F_BINARY_UPGRADE_SET_NEXT_MULTIRANGE_PG_TYPE_OID 4390 +#define F_BINARY_UPGRADE_SET_NEXT_MULTIRANGE_ARRAY_PG_TYPE_OID 4391 +#define F_RANGE_INTERSECT_AGG_TRANSFN 4401 +#define F_RANGE_INTERSECT_AGG_ANYRANGE 4450 +#define F_RANGE_CONTAINS_MULTIRANGE 4541 +#define F_MULTIRANGE_CONTAINED_BY_RANGE 4542 +#define F_PG_LOG_BACKEND_MEMORY_CONTEXTS 4543 +#define F_BINARY_UPGRADE_SET_NEXT_HEAP_RELFILENODE 4545 +#define F_BINARY_UPGRADE_SET_NEXT_INDEX_RELFILENODE 4546 +#define F_BINARY_UPGRADE_SET_NEXT_TOAST_RELFILENODE 4547 +#define F_BINARY_UPGRADE_SET_NEXT_PG_TABLESPACE_OID 4548 +#define F_PG_EVENT_TRIGGER_TABLE_REWRITE_OID 4566 +#define F_PG_EVENT_TRIGGER_TABLE_REWRITE_REASON 4567 +#define F_PG_EVENT_TRIGGER_DDL_COMMANDS 4568 +#define F_BRIN_BLOOM_OPCINFO 4591 +#define F_BRIN_BLOOM_ADD_VALUE 4592 +#define F_BRIN_BLOOM_CONSISTENT 4593 +#define F_BRIN_BLOOM_UNION 4594 +#define F_BRIN_BLOOM_OPTIONS 4595 +#define F_BRIN_BLOOM_SUMMARY_IN 4596 +#define F_BRIN_BLOOM_SUMMARY_OUT 4597 +#define F_BRIN_BLOOM_SUMMARY_RECV 4598 +#define F_BRIN_BLOOM_SUMMARY_SEND 4599 +#define F_BRIN_MINMAX_MULTI_OPCINFO 4616 +#define F_BRIN_MINMAX_MULTI_ADD_VALUE 4617 +#define F_BRIN_MINMAX_MULTI_CONSISTENT 4618 +#define F_BRIN_MINMAX_MULTI_UNION 4619 +#define F_BRIN_MINMAX_MULTI_OPTIONS 4620 +#define F_BRIN_MINMAX_MULTI_DISTANCE_INT2 4621 +#define F_BRIN_MINMAX_MULTI_DISTANCE_INT4 4622 +#define F_BRIN_MINMAX_MULTI_DISTANCE_INT8 4623 +#define F_BRIN_MINMAX_MULTI_DISTANCE_FLOAT4 4624 +#define F_BRIN_MINMAX_MULTI_DISTANCE_FLOAT8 4625 +#define F_BRIN_MINMAX_MULTI_DISTANCE_NUMERIC 4626 +#define F_BRIN_MINMAX_MULTI_DISTANCE_TID 4627 +#define F_BRIN_MINMAX_MULTI_DISTANCE_UUID 4628 +#define F_BRIN_MINMAX_MULTI_DISTANCE_DATE 4629 +#define F_BRIN_MINMAX_MULTI_DISTANCE_TIME 4630 +#define F_BRIN_MINMAX_MULTI_DISTANCE_INTERVAL 4631 +#define F_BRIN_MINMAX_MULTI_DISTANCE_TIMETZ 4632 +#define F_BRIN_MINMAX_MULTI_DISTANCE_PG_LSN 4633 +#define F_BRIN_MINMAX_MULTI_DISTANCE_MACADDR 4634 +#define F_BRIN_MINMAX_MULTI_DISTANCE_MACADDR8 4635 +#define F_BRIN_MINMAX_MULTI_DISTANCE_INET 4636 +#define F_BRIN_MINMAX_MULTI_DISTANCE_TIMESTAMP 4637 +#define F_BRIN_MINMAX_MULTI_SUMMARY_IN 4638 +#define F_BRIN_MINMAX_MULTI_SUMMARY_OUT 4639 +#define F_BRIN_MINMAX_MULTI_SUMMARY_RECV 4640 +#define F_BRIN_MINMAX_MULTI_SUMMARY_SEND 4641 +#define F_PHRASETO_TSQUERY_TEXT 5001 +#define F_TSQUERY_PHRASE_TSQUERY_TSQUERY 5003 +#define F_TSQUERY_PHRASE_TSQUERY_TSQUERY_INT4 5004 +#define F_PHRASETO_TSQUERY_REGCONFIG_TEXT 5006 +#define F_WEBSEARCH_TO_TSQUERY_REGCONFIG_TEXT 5007 +#define F_WEBSEARCH_TO_TSQUERY_TEXT 5009 +#define F_SPG_BBOX_QUAD_CONFIG 5010 +#define F_SPG_POLY_QUAD_COMPRESS 5011 +#define F_SPG_BOX_QUAD_CONFIG 5012 +#define F_SPG_BOX_QUAD_CHOOSE 5013 +#define F_SPG_BOX_QUAD_PICKSPLIT 5014 +#define F_SPG_BOX_QUAD_INNER_CONSISTENT 5015 +#define F_SPG_BOX_QUAD_LEAF_CONSISTENT 5016 +#define F_PG_MCV_LIST_IN 5018 +#define F_PG_MCV_LIST_OUT 5019 +#define F_PG_MCV_LIST_RECV 5020 +#define F_PG_MCV_LIST_SEND 5021 +#define F_PG_LSN_PLI 5022 +#define F_NUMERIC_PL_PG_LSN 5023 +#define F_PG_LSN_MII 5024 +#define F_SATISFIES_HASH_PARTITION 5028 +#define F_PG_LS_TMPDIR_ 5029 +#define F_PG_LS_TMPDIR_OID 5030 +#define F_PG_LS_ARCHIVE_STATUSDIR 5031 +#define F_NETWORK_SORTSUPPORT 5033 +#define F_XID8LT 5034 +#define F_XID8GT 5035 +#define F_XID8LE 5036 +#define F_XID8GE 5037 +#define F_MATCHINGSEL 5040 +#define F_MATCHINGJOINSEL 5041 +#define F_MIN_SCALE 5042 +#define F_TRIM_SCALE 5043 +#define F_GCD_INT4_INT4 5044 +#define F_GCD_INT8_INT8 5045 +#define F_LCM_INT4_INT4 5046 +#define F_LCM_INT8_INT8 5047 +#define F_GCD_NUMERIC_NUMERIC 5048 +#define F_LCM_NUMERIC_NUMERIC 5049 +#define F_BTVARSTREQUALIMAGE 5050 +#define F_BTEQUALIMAGE 5051 +#define F_PG_GET_SHMEM_ALLOCATIONS 5052 +#define F_PG_STAT_GET_INS_SINCE_VACUUM 5053 +#define F_JSONB_SET_LAX 5054 +#define F_PG_SNAPSHOT_IN 5055 +#define F_PG_SNAPSHOT_OUT 5056 +#define F_PG_SNAPSHOT_RECV 5057 +#define F_PG_SNAPSHOT_SEND 5058 +#define F_PG_CURRENT_XACT_ID 5059 +#define F_PG_CURRENT_XACT_ID_IF_ASSIGNED 5060 +#define F_PG_CURRENT_SNAPSHOT 5061 +#define F_PG_SNAPSHOT_XMIN 5062 +#define F_PG_SNAPSHOT_XMAX 5063 +#define F_PG_SNAPSHOT_XIP 5064 +#define F_PG_VISIBLE_IN_SNAPSHOT 5065 +#define F_PG_XACT_STATUS 5066 +#define F_XID8IN 5070 +#define F_XID 5071 +#define F_XID8OUT 5081 +#define F_XID8RECV 5082 +#define F_XID8SEND 5083 +#define F_XID8EQ 5084 +#define F_XID8NE 5085 +#define F_ANYCOMPATIBLE_IN 5086 +#define F_ANYCOMPATIBLE_OUT 5087 +#define F_ANYCOMPATIBLEARRAY_IN 5088 +#define F_ANYCOMPATIBLEARRAY_OUT 5089 +#define F_ANYCOMPATIBLEARRAY_RECV 5090 +#define F_ANYCOMPATIBLEARRAY_SEND 5091 +#define F_ANYCOMPATIBLENONARRAY_IN 5092 +#define F_ANYCOMPATIBLENONARRAY_OUT 5093 +#define F_ANYCOMPATIBLERANGE_IN 5094 +#define F_ANYCOMPATIBLERANGE_OUT 5095 +#define F_XID8CMP 5096 +#define F_XID8_LARGER 5097 +#define F_XID8_SMALLER 5098 +#define F_MAX_XID8 5099 +#define F_MIN_XID8 5100 +#define F_PG_REPLICATION_ORIGIN_CREATE 6003 +#define F_PG_REPLICATION_ORIGIN_DROP 6004 +#define F_PG_REPLICATION_ORIGIN_OID 6005 +#define F_PG_REPLICATION_ORIGIN_SESSION_SETUP 6006 +#define F_PG_REPLICATION_ORIGIN_SESSION_RESET 6007 +#define F_PG_REPLICATION_ORIGIN_SESSION_IS_SETUP 6008 +#define F_PG_REPLICATION_ORIGIN_SESSION_PROGRESS 6009 +#define F_PG_REPLICATION_ORIGIN_XACT_SETUP 6010 +#define F_PG_REPLICATION_ORIGIN_XACT_RESET 6011 +#define F_PG_REPLICATION_ORIGIN_ADVANCE 6012 +#define F_PG_REPLICATION_ORIGIN_PROGRESS 6013 +#define F_PG_SHOW_REPLICATION_ORIGIN_STATUS 6014 +#define F_JSONB_SUBSCRIPT_HANDLER 6098 +#define F_PG_LSN 6103 +#define F_PG_STAT_GET_BACKEND_SUBXACT 6107 +#define F_PG_STAT_GET_SUBSCRIPTION 6118 +#define F_PG_GET_PUBLICATION_TABLES 6119 +#define F_PG_GET_REPLICA_IDENTITY_INDEX 6120 +#define F_PG_RELATION_IS_PUBLISHABLE 6121 +#define F_MULTIRANGE_GIST_CONSISTENT 6154 +#define F_MULTIRANGE_GIST_COMPRESS 6156 +#define F_PG_GET_CATALOG_FOREIGN_KEYS 6159 +#define F_STRING_TO_TABLE_TEXT_TEXT 6160 +#define F_STRING_TO_TABLE_TEXT_TEXT_TEXT 6161 +#define F_BIT_COUNT_BIT 6162 +#define F_BIT_COUNT_BYTEA 6163 +#define F_BIT_XOR_INT2 6164 +#define F_BIT_XOR_INT4 6165 +#define F_BIT_XOR_INT8 6166 +#define F_BIT_XOR_BIT 6167 +#define F_PG_XACT_COMMIT_TIMESTAMP_ORIGIN 6168 +#define F_PG_STAT_GET_REPLICATION_SLOT 6169 +#define F_PG_STAT_RESET_REPLICATION_SLOT 6170 +#define F_TRIM_ARRAY 6172 +#define F_PG_GET_STATISTICSOBJDEF_EXPRESSIONS 6173 +#define F_PG_GET_STATISTICSOBJDEF_COLUMNS 6174 +#define F_DATE_BIN_INTERVAL_TIMESTAMP_TIMESTAMP 6177 +#define F_DATE_BIN_INTERVAL_TIMESTAMPTZ_TIMESTAMPTZ 6178 +#define F_ARRAY_SUBSCRIPT_HANDLER 6179 +#define F_RAW_ARRAY_SUBSCRIPT_HANDLER 6180 +#define F_TS_DEBUG_REGCONFIG_TEXT 6183 +#define F_TS_DEBUG_TEXT 6184 +#define F_PG_STAT_GET_DB_SESSION_TIME 6185 +#define F_PG_STAT_GET_DB_ACTIVE_TIME 6186 +#define F_PG_STAT_GET_DB_IDLE_IN_TRANSACTION_TIME 6187 +#define F_PG_STAT_GET_DB_SESSIONS 6188 +#define F_PG_STAT_GET_DB_SESSIONS_ABANDONED 6189 +#define F_PG_STAT_GET_DB_SESSIONS_FATAL 6190 +#define F_PG_STAT_GET_DB_SESSIONS_KILLED 6191 +#define F_HASH_RECORD 6192 +#define F_HASH_RECORD_EXTENDED 6193 +#define F_LTRIM_BYTEA_BYTEA 6195 +#define F_RTRIM_BYTEA_BYTEA 6196 +#define F_PG_GET_FUNCTION_SQLBODY 6197 +#define F_UNISTR 6198 +#define F_EXTRACT_TEXT_DATE 6199 +#define F_EXTRACT_TEXT_TIME 6200 +#define F_EXTRACT_TEXT_TIMETZ 6201 +#define F_EXTRACT_TEXT_TIMESTAMP 6202 +#define F_EXTRACT_TEXT_TIMESTAMPTZ 6203 +#define F_EXTRACT_TEXT_INTERVAL 6204 +#define F_HAS_PARAMETER_PRIVILEGE_NAME_TEXT_TEXT 6205 +#define F_HAS_PARAMETER_PRIVILEGE_OID_TEXT_TEXT 6206 +#define F_HAS_PARAMETER_PRIVILEGE_TEXT_TEXT 6207 +#define F_PG_READ_FILE_TEXT_BOOL 6208 +#define F_PG_READ_BINARY_FILE_TEXT_BOOL 6209 +#define F_PG_INPUT_IS_VALID 6210 +#define F_PG_INPUT_ERROR_INFO 6211 +#define F_RANDOM_NORMAL 6212 +#define F_PG_SPLIT_WALFILE_NAME 6213 +#define F_PG_STAT_GET_IO 6214 +#define F_ARRAY_SHUFFLE 6215 +#define F_ARRAY_SAMPLE 6216 +#define F_PG_STAT_GET_TUPLES_NEWPAGE_UPDATED 6217 +#define F_PG_STAT_GET_XACT_TUPLES_NEWPAGE_UPDATED 6218 +#define F_ERF 6219 +#define F_ERFC 6220 +#define F_DATE_ADD_TIMESTAMPTZ_INTERVAL 6221 +#define F_DATE_ADD_TIMESTAMPTZ_INTERVAL_TEXT 6222 +#define F_DATE_SUBTRACT_TIMESTAMPTZ_INTERVAL 6223 +#define F_PG_GET_WAL_RESOURCE_MANAGERS 6224 +#define F_MULTIRANGE_AGG_TRANSFN 6225 +#define F_MULTIRANGE_AGG_FINALFN 6226 +#define F_RANGE_AGG_ANYMULTIRANGE 6227 +#define F_PG_STAT_HAVE_STATS 6230 +#define F_PG_STAT_GET_SUBSCRIPTION_STATS 6231 +#define F_PG_STAT_RESET_SUBSCRIPTION_STATS 6232 +#define F_WINDOW_ROW_NUMBER_SUPPORT 6233 +#define F_WINDOW_RANK_SUPPORT 6234 +#define F_WINDOW_DENSE_RANK_SUPPORT 6235 +#define F_INT8INC_SUPPORT 6236 +#define F_PG_SETTINGS_GET_FLAGS 6240 +#define F_PG_STOP_MAKING_PINNED_OBJECTS 6241 +#define F_TEXT_STARTS_WITH_SUPPORT 6242 +#define F_PG_STAT_GET_RECOVERY_PREFETCH 6248 +#define F_PG_DATABASE_COLLATION_ACTUAL_VERSION 6249 +#define F_PG_IDENT_FILE_MAPPINGS 6250 +#define F_REGEXP_REPLACE_TEXT_TEXT_TEXT_INT4_INT4_TEXT 6251 +#define F_REGEXP_REPLACE_TEXT_TEXT_TEXT_INT4_INT4 6252 +#define F_REGEXP_REPLACE_TEXT_TEXT_TEXT_INT4 6253 +#define F_REGEXP_COUNT_TEXT_TEXT 6254 +#define F_REGEXP_COUNT_TEXT_TEXT_INT4 6255 +#define F_REGEXP_COUNT_TEXT_TEXT_INT4_TEXT 6256 +#define F_REGEXP_INSTR_TEXT_TEXT 6257 +#define F_REGEXP_INSTR_TEXT_TEXT_INT4 6258 +#define F_REGEXP_INSTR_TEXT_TEXT_INT4_INT4 6259 +#define F_REGEXP_INSTR_TEXT_TEXT_INT4_INT4_INT4 6260 +#define F_REGEXP_INSTR_TEXT_TEXT_INT4_INT4_INT4_TEXT 6261 +#define F_REGEXP_INSTR_TEXT_TEXT_INT4_INT4_INT4_TEXT_INT4 6262 +#define F_REGEXP_LIKE_TEXT_TEXT 6263 +#define F_REGEXP_LIKE_TEXT_TEXT_TEXT 6264 +#define F_REGEXP_SUBSTR_TEXT_TEXT 6265 +#define F_REGEXP_SUBSTR_TEXT_TEXT_INT4 6266 +#define F_REGEXP_SUBSTR_TEXT_TEXT_INT4_INT4 6267 +#define F_REGEXP_SUBSTR_TEXT_TEXT_INT4_INT4_TEXT 6268 +#define F_REGEXP_SUBSTR_TEXT_TEXT_INT4_INT4_TEXT_INT4 6269 +#define F_PG_LS_LOGICALSNAPDIR 6270 +#define F_PG_LS_LOGICALMAPDIR 6271 +#define F_PG_LS_REPLSLOTDIR 6272 +#define F_DATE_SUBTRACT_TIMESTAMPTZ_INTERVAL_TEXT 6273 +#define F_GENERATE_SERIES_TIMESTAMPTZ_TIMESTAMPTZ_INTERVAL_TEXT 6274 +#define F_JSON_AGG_STRICT_TRANSFN 6275 +#define F_JSON_AGG_STRICT 6276 +#define F_JSON_OBJECT_AGG_STRICT_TRANSFN 6277 +#define F_JSON_OBJECT_AGG_UNIQUE_TRANSFN 6278 +#define F_JSON_OBJECT_AGG_UNIQUE_STRICT_TRANSFN 6279 +#define F_JSON_OBJECT_AGG_STRICT 6280 +#define F_JSON_OBJECT_AGG_UNIQUE 6281 +#define F_JSON_OBJECT_AGG_UNIQUE_STRICT 6282 +#define F_JSONB_AGG_STRICT_TRANSFN 6283 +#define F_JSONB_AGG_STRICT 6284 +#define F_JSONB_OBJECT_AGG_STRICT_TRANSFN 6285 +#define F_JSONB_OBJECT_AGG_UNIQUE_TRANSFN 6286 +#define F_JSONB_OBJECT_AGG_UNIQUE_STRICT_TRANSFN 6287 +#define F_JSONB_OBJECT_AGG_STRICT 6288 +#define F_JSONB_OBJECT_AGG_UNIQUE 6289 +#define F_JSONB_OBJECT_AGG_UNIQUE_STRICT 6290 +#define F_ANY_VALUE 6291 +#define F_ANY_VALUE_TRANSFN 6292 +#define F_ARRAY_AGG_COMBINE 6293 +#define F_ARRAY_AGG_SERIALIZE 6294 +#define F_ARRAY_AGG_DESERIALIZE 6295 +#define F_ARRAY_AGG_ARRAY_COMBINE 6296 +#define F_ARRAY_AGG_ARRAY_SERIALIZE 6297 +#define F_ARRAY_AGG_ARRAY_DESERIALIZE 6298 +#define F_STRING_AGG_COMBINE 6299 +#define F_STRING_AGG_SERIALIZE 6300 +#define F_STRING_AGG_DESERIALIZE 6301 +#define F_PG_LOG_STANDBY_SNAPSHOT 6305 +#define F_WINDOW_PERCENT_RANK_SUPPORT 6306 +#define F_WINDOW_CUME_DIST_SUPPORT 6307 +#define F_WINDOW_NTILE_SUPPORT 6308 +#define F_PG_STAT_GET_DB_CONFLICT_LOGICALSLOT 6309 +#define F_PG_STAT_GET_LASTSCAN 6310 +#define F_SYSTEM_USER 6311 + +#endif /* FMGROIDS_H */ diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/fmgrprotos.h b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/fmgrprotos.h new file mode 100644 index 00000000000..d5054fea01b --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/fmgrprotos.h @@ -0,0 +1,2871 @@ +/*------------------------------------------------------------------------- + * + * fmgrprotos.h + * Prototypes for built-in functions. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * NOTES + * ****************************** + * *** DO NOT EDIT THIS FILE! *** + * ****************************** + * + * It has been GENERATED by src/backend/utils/Gen_fmgrtab.pl + * + *------------------------------------------------------------------------- + */ + +#ifndef FMGRPROTOS_H +#define FMGRPROTOS_H + +#include "fmgr.h" + +extern Datum heap_tableam_handler(PG_FUNCTION_ARGS); +extern Datum byteaout(PG_FUNCTION_ARGS); +extern Datum charout(PG_FUNCTION_ARGS); +extern Datum namein(PG_FUNCTION_ARGS); +extern Datum nameout(PG_FUNCTION_ARGS); +extern Datum int2in(PG_FUNCTION_ARGS); +extern Datum int2out(PG_FUNCTION_ARGS); +extern Datum int2vectorin(PG_FUNCTION_ARGS); +extern Datum int2vectorout(PG_FUNCTION_ARGS); +extern Datum int4in(PG_FUNCTION_ARGS); +extern Datum int4out(PG_FUNCTION_ARGS); +extern Datum regprocin(PG_FUNCTION_ARGS); +extern Datum regprocout(PG_FUNCTION_ARGS); +extern Datum textin(PG_FUNCTION_ARGS); +extern Datum textout(PG_FUNCTION_ARGS); +extern Datum tidin(PG_FUNCTION_ARGS); +extern Datum tidout(PG_FUNCTION_ARGS); +extern Datum xidin(PG_FUNCTION_ARGS); +extern Datum xidout(PG_FUNCTION_ARGS); +extern Datum cidin(PG_FUNCTION_ARGS); +extern Datum cidout(PG_FUNCTION_ARGS); +extern Datum oidvectorin(PG_FUNCTION_ARGS); +extern Datum oidvectorout(PG_FUNCTION_ARGS); +extern Datum boollt(PG_FUNCTION_ARGS); +extern Datum boolgt(PG_FUNCTION_ARGS); +extern Datum booleq(PG_FUNCTION_ARGS); +extern Datum chareq(PG_FUNCTION_ARGS); +extern Datum nameeq(PG_FUNCTION_ARGS); +extern Datum int2eq(PG_FUNCTION_ARGS); +extern Datum int2lt(PG_FUNCTION_ARGS); +extern Datum int4eq(PG_FUNCTION_ARGS); +extern Datum int4lt(PG_FUNCTION_ARGS); +extern Datum texteq(PG_FUNCTION_ARGS); +extern Datum xideq(PG_FUNCTION_ARGS); +extern Datum cideq(PG_FUNCTION_ARGS); +extern Datum charne(PG_FUNCTION_ARGS); +extern Datum charle(PG_FUNCTION_ARGS); +extern Datum chargt(PG_FUNCTION_ARGS); +extern Datum charge(PG_FUNCTION_ARGS); +extern Datum chartoi4(PG_FUNCTION_ARGS); +extern Datum i4tochar(PG_FUNCTION_ARGS); +extern Datum nameregexeq(PG_FUNCTION_ARGS); +extern Datum boolne(PG_FUNCTION_ARGS); +extern Datum pg_ddl_command_in(PG_FUNCTION_ARGS); +extern Datum pg_ddl_command_out(PG_FUNCTION_ARGS); +extern Datum pg_ddl_command_recv(PG_FUNCTION_ARGS); +extern Datum pgsql_version(PG_FUNCTION_ARGS); +extern Datum pg_ddl_command_send(PG_FUNCTION_ARGS); +extern Datum eqsel(PG_FUNCTION_ARGS); +extern Datum neqsel(PG_FUNCTION_ARGS); +extern Datum scalarltsel(PG_FUNCTION_ARGS); +extern Datum scalargtsel(PG_FUNCTION_ARGS); +extern Datum eqjoinsel(PG_FUNCTION_ARGS); +extern Datum neqjoinsel(PG_FUNCTION_ARGS); +extern Datum scalarltjoinsel(PG_FUNCTION_ARGS); +extern Datum scalargtjoinsel(PG_FUNCTION_ARGS); +extern Datum unknownin(PG_FUNCTION_ARGS); +extern Datum unknownout(PG_FUNCTION_ARGS); +extern Datum box_above_eq(PG_FUNCTION_ARGS); +extern Datum box_below_eq(PG_FUNCTION_ARGS); +extern Datum point_in(PG_FUNCTION_ARGS); +extern Datum point_out(PG_FUNCTION_ARGS); +extern Datum lseg_in(PG_FUNCTION_ARGS); +extern Datum lseg_out(PG_FUNCTION_ARGS); +extern Datum path_in(PG_FUNCTION_ARGS); +extern Datum path_out(PG_FUNCTION_ARGS); +extern Datum box_in(PG_FUNCTION_ARGS); +extern Datum box_out(PG_FUNCTION_ARGS); +extern Datum box_overlap(PG_FUNCTION_ARGS); +extern Datum box_ge(PG_FUNCTION_ARGS); +extern Datum box_gt(PG_FUNCTION_ARGS); +extern Datum box_eq(PG_FUNCTION_ARGS); +extern Datum box_lt(PG_FUNCTION_ARGS); +extern Datum box_le(PG_FUNCTION_ARGS); +extern Datum point_above(PG_FUNCTION_ARGS); +extern Datum point_left(PG_FUNCTION_ARGS); +extern Datum point_right(PG_FUNCTION_ARGS); +extern Datum point_below(PG_FUNCTION_ARGS); +extern Datum point_eq(PG_FUNCTION_ARGS); +extern Datum on_pb(PG_FUNCTION_ARGS); +extern Datum on_ppath(PG_FUNCTION_ARGS); +extern Datum box_center(PG_FUNCTION_ARGS); +extern Datum areasel(PG_FUNCTION_ARGS); +extern Datum areajoinsel(PG_FUNCTION_ARGS); +extern Datum int4mul(PG_FUNCTION_ARGS); +extern Datum int4ne(PG_FUNCTION_ARGS); +extern Datum int2ne(PG_FUNCTION_ARGS); +extern Datum int2gt(PG_FUNCTION_ARGS); +extern Datum int4gt(PG_FUNCTION_ARGS); +extern Datum int2le(PG_FUNCTION_ARGS); +extern Datum int4le(PG_FUNCTION_ARGS); +extern Datum int4ge(PG_FUNCTION_ARGS); +extern Datum int2ge(PG_FUNCTION_ARGS); +extern Datum int2mul(PG_FUNCTION_ARGS); +extern Datum int2div(PG_FUNCTION_ARGS); +extern Datum int4div(PG_FUNCTION_ARGS); +extern Datum int2mod(PG_FUNCTION_ARGS); +extern Datum int4mod(PG_FUNCTION_ARGS); +extern Datum textne(PG_FUNCTION_ARGS); +extern Datum int24eq(PG_FUNCTION_ARGS); +extern Datum int42eq(PG_FUNCTION_ARGS); +extern Datum int24lt(PG_FUNCTION_ARGS); +extern Datum int42lt(PG_FUNCTION_ARGS); +extern Datum int24gt(PG_FUNCTION_ARGS); +extern Datum int42gt(PG_FUNCTION_ARGS); +extern Datum int24ne(PG_FUNCTION_ARGS); +extern Datum int42ne(PG_FUNCTION_ARGS); +extern Datum int24le(PG_FUNCTION_ARGS); +extern Datum int42le(PG_FUNCTION_ARGS); +extern Datum int24ge(PG_FUNCTION_ARGS); +extern Datum int42ge(PG_FUNCTION_ARGS); +extern Datum int24mul(PG_FUNCTION_ARGS); +extern Datum int42mul(PG_FUNCTION_ARGS); +extern Datum int24div(PG_FUNCTION_ARGS); +extern Datum int42div(PG_FUNCTION_ARGS); +extern Datum int2pl(PG_FUNCTION_ARGS); +extern Datum int4pl(PG_FUNCTION_ARGS); +extern Datum int24pl(PG_FUNCTION_ARGS); +extern Datum int42pl(PG_FUNCTION_ARGS); +extern Datum int2mi(PG_FUNCTION_ARGS); +extern Datum int4mi(PG_FUNCTION_ARGS); +extern Datum int24mi(PG_FUNCTION_ARGS); +extern Datum int42mi(PG_FUNCTION_ARGS); +extern Datum oideq(PG_FUNCTION_ARGS); +extern Datum oidne(PG_FUNCTION_ARGS); +extern Datum box_same(PG_FUNCTION_ARGS); +extern Datum box_contain(PG_FUNCTION_ARGS); +extern Datum box_left(PG_FUNCTION_ARGS); +extern Datum box_overleft(PG_FUNCTION_ARGS); +extern Datum box_overright(PG_FUNCTION_ARGS); +extern Datum box_right(PG_FUNCTION_ARGS); +extern Datum box_contained(PG_FUNCTION_ARGS); +extern Datum box_contain_pt(PG_FUNCTION_ARGS); +extern Datum pg_node_tree_in(PG_FUNCTION_ARGS); +extern Datum pg_node_tree_out(PG_FUNCTION_ARGS); +extern Datum pg_node_tree_recv(PG_FUNCTION_ARGS); +extern Datum pg_node_tree_send(PG_FUNCTION_ARGS); +extern Datum float4in(PG_FUNCTION_ARGS); +extern Datum float4out(PG_FUNCTION_ARGS); +extern Datum float4mul(PG_FUNCTION_ARGS); +extern Datum float4div(PG_FUNCTION_ARGS); +extern Datum float4pl(PG_FUNCTION_ARGS); +extern Datum float4mi(PG_FUNCTION_ARGS); +extern Datum float4um(PG_FUNCTION_ARGS); +extern Datum float4abs(PG_FUNCTION_ARGS); +extern Datum float4_accum(PG_FUNCTION_ARGS); +extern Datum float4larger(PG_FUNCTION_ARGS); +extern Datum float4smaller(PG_FUNCTION_ARGS); +extern Datum int4um(PG_FUNCTION_ARGS); +extern Datum int2um(PG_FUNCTION_ARGS); +extern Datum float8in(PG_FUNCTION_ARGS); +extern Datum float8out(PG_FUNCTION_ARGS); +extern Datum float8mul(PG_FUNCTION_ARGS); +extern Datum float8div(PG_FUNCTION_ARGS); +extern Datum float8pl(PG_FUNCTION_ARGS); +extern Datum float8mi(PG_FUNCTION_ARGS); +extern Datum float8um(PG_FUNCTION_ARGS); +extern Datum float8abs(PG_FUNCTION_ARGS); +extern Datum float8_accum(PG_FUNCTION_ARGS); +extern Datum float8larger(PG_FUNCTION_ARGS); +extern Datum float8smaller(PG_FUNCTION_ARGS); +extern Datum lseg_center(PG_FUNCTION_ARGS); +extern Datum poly_center(PG_FUNCTION_ARGS); +extern Datum dround(PG_FUNCTION_ARGS); +extern Datum dtrunc(PG_FUNCTION_ARGS); +extern Datum dsqrt(PG_FUNCTION_ARGS); +extern Datum dcbrt(PG_FUNCTION_ARGS); +extern Datum dpow(PG_FUNCTION_ARGS); +extern Datum dexp(PG_FUNCTION_ARGS); +extern Datum dlog1(PG_FUNCTION_ARGS); +extern Datum i2tod(PG_FUNCTION_ARGS); +extern Datum i2tof(PG_FUNCTION_ARGS); +extern Datum dtoi2(PG_FUNCTION_ARGS); +extern Datum ftoi2(PG_FUNCTION_ARGS); +extern Datum line_distance(PG_FUNCTION_ARGS); +extern Datum nameeqtext(PG_FUNCTION_ARGS); +extern Datum namelttext(PG_FUNCTION_ARGS); +extern Datum nameletext(PG_FUNCTION_ARGS); +extern Datum namegetext(PG_FUNCTION_ARGS); +extern Datum namegttext(PG_FUNCTION_ARGS); +extern Datum namenetext(PG_FUNCTION_ARGS); +extern Datum btnametextcmp(PG_FUNCTION_ARGS); +extern Datum texteqname(PG_FUNCTION_ARGS); +extern Datum textltname(PG_FUNCTION_ARGS); +extern Datum textlename(PG_FUNCTION_ARGS); +extern Datum textgename(PG_FUNCTION_ARGS); +extern Datum textgtname(PG_FUNCTION_ARGS); +extern Datum textnename(PG_FUNCTION_ARGS); +extern Datum bttextnamecmp(PG_FUNCTION_ARGS); +extern Datum nameconcatoid(PG_FUNCTION_ARGS); +extern Datum table_am_handler_in(PG_FUNCTION_ARGS); +extern Datum table_am_handler_out(PG_FUNCTION_ARGS); +extern Datum timeofday(PG_FUNCTION_ARGS); +extern Datum pg_nextoid(PG_FUNCTION_ARGS); +extern Datum float8_combine(PG_FUNCTION_ARGS); +extern Datum inter_sl(PG_FUNCTION_ARGS); +extern Datum inter_lb(PG_FUNCTION_ARGS); +extern Datum float48mul(PG_FUNCTION_ARGS); +extern Datum float48div(PG_FUNCTION_ARGS); +extern Datum float48pl(PG_FUNCTION_ARGS); +extern Datum float48mi(PG_FUNCTION_ARGS); +extern Datum float84mul(PG_FUNCTION_ARGS); +extern Datum float84div(PG_FUNCTION_ARGS); +extern Datum float84pl(PG_FUNCTION_ARGS); +extern Datum float84mi(PG_FUNCTION_ARGS); +extern Datum float4eq(PG_FUNCTION_ARGS); +extern Datum float4ne(PG_FUNCTION_ARGS); +extern Datum float4lt(PG_FUNCTION_ARGS); +extern Datum float4le(PG_FUNCTION_ARGS); +extern Datum float4gt(PG_FUNCTION_ARGS); +extern Datum float4ge(PG_FUNCTION_ARGS); +extern Datum float8eq(PG_FUNCTION_ARGS); +extern Datum float8ne(PG_FUNCTION_ARGS); +extern Datum float8lt(PG_FUNCTION_ARGS); +extern Datum float8le(PG_FUNCTION_ARGS); +extern Datum float8gt(PG_FUNCTION_ARGS); +extern Datum float8ge(PG_FUNCTION_ARGS); +extern Datum float48eq(PG_FUNCTION_ARGS); +extern Datum float48ne(PG_FUNCTION_ARGS); +extern Datum float48lt(PG_FUNCTION_ARGS); +extern Datum float48le(PG_FUNCTION_ARGS); +extern Datum float48gt(PG_FUNCTION_ARGS); +extern Datum float48ge(PG_FUNCTION_ARGS); +extern Datum float84eq(PG_FUNCTION_ARGS); +extern Datum float84ne(PG_FUNCTION_ARGS); +extern Datum float84lt(PG_FUNCTION_ARGS); +extern Datum float84le(PG_FUNCTION_ARGS); +extern Datum float84gt(PG_FUNCTION_ARGS); +extern Datum float84ge(PG_FUNCTION_ARGS); +extern Datum ftod(PG_FUNCTION_ARGS); +extern Datum dtof(PG_FUNCTION_ARGS); +extern Datum i2toi4(PG_FUNCTION_ARGS); +extern Datum i4toi2(PG_FUNCTION_ARGS); +extern Datum pg_jit_available(PG_FUNCTION_ARGS); +extern Datum i4tod(PG_FUNCTION_ARGS); +extern Datum dtoi4(PG_FUNCTION_ARGS); +extern Datum i4tof(PG_FUNCTION_ARGS); +extern Datum ftoi4(PG_FUNCTION_ARGS); +extern Datum width_bucket_float8(PG_FUNCTION_ARGS); +extern Datum json_in(PG_FUNCTION_ARGS); +extern Datum json_out(PG_FUNCTION_ARGS); +extern Datum json_recv(PG_FUNCTION_ARGS); +extern Datum json_send(PG_FUNCTION_ARGS); +extern Datum index_am_handler_in(PG_FUNCTION_ARGS); +extern Datum index_am_handler_out(PG_FUNCTION_ARGS); +extern Datum hashmacaddr8(PG_FUNCTION_ARGS); +extern Datum hash_aclitem(PG_FUNCTION_ARGS); +extern Datum bthandler(PG_FUNCTION_ARGS); +extern Datum hashhandler(PG_FUNCTION_ARGS); +extern Datum gisthandler(PG_FUNCTION_ARGS); +extern Datum ginhandler(PG_FUNCTION_ARGS); +extern Datum spghandler(PG_FUNCTION_ARGS); +extern Datum brinhandler(PG_FUNCTION_ARGS); +extern Datum scalarlesel(PG_FUNCTION_ARGS); +extern Datum scalargesel(PG_FUNCTION_ARGS); +extern Datum amvalidate(PG_FUNCTION_ARGS); +extern Datum poly_same(PG_FUNCTION_ARGS); +extern Datum poly_contain(PG_FUNCTION_ARGS); +extern Datum poly_left(PG_FUNCTION_ARGS); +extern Datum poly_overleft(PG_FUNCTION_ARGS); +extern Datum poly_overright(PG_FUNCTION_ARGS); +extern Datum poly_right(PG_FUNCTION_ARGS); +extern Datum poly_contained(PG_FUNCTION_ARGS); +extern Datum poly_overlap(PG_FUNCTION_ARGS); +extern Datum poly_in(PG_FUNCTION_ARGS); +extern Datum poly_out(PG_FUNCTION_ARGS); +extern Datum btint2cmp(PG_FUNCTION_ARGS); +extern Datum btint4cmp(PG_FUNCTION_ARGS); +extern Datum btfloat4cmp(PG_FUNCTION_ARGS); +extern Datum btfloat8cmp(PG_FUNCTION_ARGS); +extern Datum btoidcmp(PG_FUNCTION_ARGS); +extern Datum dist_bp(PG_FUNCTION_ARGS); +extern Datum btcharcmp(PG_FUNCTION_ARGS); +extern Datum btnamecmp(PG_FUNCTION_ARGS); +extern Datum bttextcmp(PG_FUNCTION_ARGS); +extern Datum lseg_distance(PG_FUNCTION_ARGS); +extern Datum lseg_interpt(PG_FUNCTION_ARGS); +extern Datum dist_ps(PG_FUNCTION_ARGS); +extern Datum dist_pb(PG_FUNCTION_ARGS); +extern Datum dist_sb(PG_FUNCTION_ARGS); +extern Datum close_ps(PG_FUNCTION_ARGS); +extern Datum close_pb(PG_FUNCTION_ARGS); +extern Datum close_sb(PG_FUNCTION_ARGS); +extern Datum on_ps(PG_FUNCTION_ARGS); +extern Datum path_distance(PG_FUNCTION_ARGS); +extern Datum dist_ppath(PG_FUNCTION_ARGS); +extern Datum on_sb(PG_FUNCTION_ARGS); +extern Datum inter_sb(PG_FUNCTION_ARGS); +extern Datum text_to_array_null(PG_FUNCTION_ARGS); +extern Datum cash_cmp(PG_FUNCTION_ARGS); +extern Datum array_append(PG_FUNCTION_ARGS); +extern Datum array_prepend(PG_FUNCTION_ARGS); +extern Datum dist_sp(PG_FUNCTION_ARGS); +extern Datum dist_bs(PG_FUNCTION_ARGS); +extern Datum btarraycmp(PG_FUNCTION_ARGS); +extern Datum array_cat(PG_FUNCTION_ARGS); +extern Datum array_to_text_null(PG_FUNCTION_ARGS); +extern Datum scalarlejoinsel(PG_FUNCTION_ARGS); +extern Datum array_ne(PG_FUNCTION_ARGS); +extern Datum array_lt(PG_FUNCTION_ARGS); +extern Datum array_gt(PG_FUNCTION_ARGS); +extern Datum array_le(PG_FUNCTION_ARGS); +extern Datum text_to_array(PG_FUNCTION_ARGS); +extern Datum array_to_text(PG_FUNCTION_ARGS); +extern Datum array_ge(PG_FUNCTION_ARGS); +extern Datum scalargejoinsel(PG_FUNCTION_ARGS); +extern Datum hashmacaddr(PG_FUNCTION_ARGS); +extern Datum hashtext(PG_FUNCTION_ARGS); +extern Datum rtrim1(PG_FUNCTION_ARGS); +extern Datum btoidvectorcmp(PG_FUNCTION_ARGS); +extern Datum name_text(PG_FUNCTION_ARGS); +extern Datum text_name(PG_FUNCTION_ARGS); +extern Datum name_bpchar(PG_FUNCTION_ARGS); +extern Datum bpchar_name(PG_FUNCTION_ARGS); +extern Datum dist_pathp(PG_FUNCTION_ARGS); +extern Datum hashinet(PG_FUNCTION_ARGS); +extern Datum hashint4extended(PG_FUNCTION_ARGS); +extern Datum hash_numeric(PG_FUNCTION_ARGS); +extern Datum macaddr_in(PG_FUNCTION_ARGS); +extern Datum macaddr_out(PG_FUNCTION_ARGS); +extern Datum pg_num_nulls(PG_FUNCTION_ARGS); +extern Datum pg_num_nonnulls(PG_FUNCTION_ARGS); +extern Datum hashint2extended(PG_FUNCTION_ARGS); +extern Datum hashint8extended(PG_FUNCTION_ARGS); +extern Datum hashfloat4extended(PG_FUNCTION_ARGS); +extern Datum hashfloat8extended(PG_FUNCTION_ARGS); +extern Datum hashoidextended(PG_FUNCTION_ARGS); +extern Datum hashcharextended(PG_FUNCTION_ARGS); +extern Datum hashnameextended(PG_FUNCTION_ARGS); +extern Datum hashtextextended(PG_FUNCTION_ARGS); +extern Datum hashint2(PG_FUNCTION_ARGS); +extern Datum hashint4(PG_FUNCTION_ARGS); +extern Datum hashfloat4(PG_FUNCTION_ARGS); +extern Datum hashfloat8(PG_FUNCTION_ARGS); +extern Datum hashoid(PG_FUNCTION_ARGS); +extern Datum hashchar(PG_FUNCTION_ARGS); +extern Datum hashname(PG_FUNCTION_ARGS); +extern Datum hashvarlena(PG_FUNCTION_ARGS); +extern Datum hashoidvector(PG_FUNCTION_ARGS); +extern Datum text_larger(PG_FUNCTION_ARGS); +extern Datum text_smaller(PG_FUNCTION_ARGS); +extern Datum int8in(PG_FUNCTION_ARGS); +extern Datum int8out(PG_FUNCTION_ARGS); +extern Datum int8um(PG_FUNCTION_ARGS); +extern Datum int8pl(PG_FUNCTION_ARGS); +extern Datum int8mi(PG_FUNCTION_ARGS); +extern Datum int8mul(PG_FUNCTION_ARGS); +extern Datum int8div(PG_FUNCTION_ARGS); +extern Datum int8eq(PG_FUNCTION_ARGS); +extern Datum int8ne(PG_FUNCTION_ARGS); +extern Datum int8lt(PG_FUNCTION_ARGS); +extern Datum int8gt(PG_FUNCTION_ARGS); +extern Datum int8le(PG_FUNCTION_ARGS); +extern Datum int8ge(PG_FUNCTION_ARGS); +extern Datum int84eq(PG_FUNCTION_ARGS); +extern Datum int84ne(PG_FUNCTION_ARGS); +extern Datum int84lt(PG_FUNCTION_ARGS); +extern Datum int84gt(PG_FUNCTION_ARGS); +extern Datum int84le(PG_FUNCTION_ARGS); +extern Datum int84ge(PG_FUNCTION_ARGS); +extern Datum int84(PG_FUNCTION_ARGS); +extern Datum int48(PG_FUNCTION_ARGS); +extern Datum i8tod(PG_FUNCTION_ARGS); +extern Datum dtoi8(PG_FUNCTION_ARGS); +extern Datum array_larger(PG_FUNCTION_ARGS); +extern Datum array_smaller(PG_FUNCTION_ARGS); +extern Datum inet_abbrev(PG_FUNCTION_ARGS); +extern Datum cidr_abbrev(PG_FUNCTION_ARGS); +extern Datum inet_set_masklen(PG_FUNCTION_ARGS); +extern Datum oidvectorne(PG_FUNCTION_ARGS); +extern Datum hash_array(PG_FUNCTION_ARGS); +extern Datum cidr_set_masklen(PG_FUNCTION_ARGS); +extern Datum pg_indexam_has_property(PG_FUNCTION_ARGS); +extern Datum pg_index_has_property(PG_FUNCTION_ARGS); +extern Datum pg_index_column_has_property(PG_FUNCTION_ARGS); +extern Datum i8tof(PG_FUNCTION_ARGS); +extern Datum ftoi8(PG_FUNCTION_ARGS); +extern Datum namelt(PG_FUNCTION_ARGS); +extern Datum namele(PG_FUNCTION_ARGS); +extern Datum namegt(PG_FUNCTION_ARGS); +extern Datum namege(PG_FUNCTION_ARGS); +extern Datum namene(PG_FUNCTION_ARGS); +extern Datum bpchar(PG_FUNCTION_ARGS); +extern Datum varchar(PG_FUNCTION_ARGS); +extern Datum pg_indexam_progress_phasename(PG_FUNCTION_ARGS); +extern Datum oidvectorlt(PG_FUNCTION_ARGS); +extern Datum oidvectorle(PG_FUNCTION_ARGS); +extern Datum oidvectoreq(PG_FUNCTION_ARGS); +extern Datum oidvectorge(PG_FUNCTION_ARGS); +extern Datum oidvectorgt(PG_FUNCTION_ARGS); +extern Datum network_network(PG_FUNCTION_ARGS); +extern Datum network_netmask(PG_FUNCTION_ARGS); +extern Datum network_masklen(PG_FUNCTION_ARGS); +extern Datum network_broadcast(PG_FUNCTION_ARGS); +extern Datum network_host(PG_FUNCTION_ARGS); +extern Datum dist_lp(PG_FUNCTION_ARGS); +extern Datum dist_ls(PG_FUNCTION_ARGS); +extern Datum current_user(PG_FUNCTION_ARGS); +extern Datum network_family(PG_FUNCTION_ARGS); +extern Datum int82(PG_FUNCTION_ARGS); +extern Datum be_lo_create(PG_FUNCTION_ARGS); +extern Datum oidlt(PG_FUNCTION_ARGS); +extern Datum oidle(PG_FUNCTION_ARGS); +extern Datum byteaoctetlen(PG_FUNCTION_ARGS); +extern Datum byteaGetByte(PG_FUNCTION_ARGS); +extern Datum byteaSetByte(PG_FUNCTION_ARGS); +extern Datum byteaGetBit(PG_FUNCTION_ARGS); +extern Datum byteaSetBit(PG_FUNCTION_ARGS); +extern Datum dist_pl(PG_FUNCTION_ARGS); +extern Datum dist_sl(PG_FUNCTION_ARGS); +extern Datum dist_cpoly(PG_FUNCTION_ARGS); +extern Datum poly_distance(PG_FUNCTION_ARGS); +extern Datum network_show(PG_FUNCTION_ARGS); +extern Datum text_lt(PG_FUNCTION_ARGS); +extern Datum text_le(PG_FUNCTION_ARGS); +extern Datum text_gt(PG_FUNCTION_ARGS); +extern Datum text_ge(PG_FUNCTION_ARGS); +extern Datum array_eq(PG_FUNCTION_ARGS); +extern Datum session_user(PG_FUNCTION_ARGS); +extern Datum array_dims(PG_FUNCTION_ARGS); +extern Datum array_ndims(PG_FUNCTION_ARGS); +extern Datum byteaoverlay(PG_FUNCTION_ARGS); +extern Datum array_in(PG_FUNCTION_ARGS); +extern Datum array_out(PG_FUNCTION_ARGS); +extern Datum byteaoverlay_no_len(PG_FUNCTION_ARGS); +extern Datum macaddr_trunc(PG_FUNCTION_ARGS); +extern Datum int28(PG_FUNCTION_ARGS); +extern Datum be_lo_import(PG_FUNCTION_ARGS); +extern Datum be_lo_export(PG_FUNCTION_ARGS); +extern Datum int4inc(PG_FUNCTION_ARGS); +extern Datum be_lo_import_with_oid(PG_FUNCTION_ARGS); +extern Datum int4larger(PG_FUNCTION_ARGS); +extern Datum int4smaller(PG_FUNCTION_ARGS); +extern Datum int2larger(PG_FUNCTION_ARGS); +extern Datum int2smaller(PG_FUNCTION_ARGS); +extern Datum hashvarlenaextended(PG_FUNCTION_ARGS); +extern Datum hashoidvectorextended(PG_FUNCTION_ARGS); +extern Datum hash_aclitem_extended(PG_FUNCTION_ARGS); +extern Datum hashmacaddrextended(PG_FUNCTION_ARGS); +extern Datum hashinetextended(PG_FUNCTION_ARGS); +extern Datum hash_numeric_extended(PG_FUNCTION_ARGS); +extern Datum hashmacaddr8extended(PG_FUNCTION_ARGS); +extern Datum hash_array_extended(PG_FUNCTION_ARGS); +extern Datum dist_polyc(PG_FUNCTION_ARGS); +extern Datum pg_client_encoding(PG_FUNCTION_ARGS); +extern Datum current_query(PG_FUNCTION_ARGS); +extern Datum macaddr_eq(PG_FUNCTION_ARGS); +extern Datum macaddr_lt(PG_FUNCTION_ARGS); +extern Datum macaddr_le(PG_FUNCTION_ARGS); +extern Datum macaddr_gt(PG_FUNCTION_ARGS); +extern Datum macaddr_ge(PG_FUNCTION_ARGS); +extern Datum macaddr_ne(PG_FUNCTION_ARGS); +extern Datum macaddr_cmp(PG_FUNCTION_ARGS); +extern Datum int82pl(PG_FUNCTION_ARGS); +extern Datum int82mi(PG_FUNCTION_ARGS); +extern Datum int82mul(PG_FUNCTION_ARGS); +extern Datum int82div(PG_FUNCTION_ARGS); +extern Datum int28pl(PG_FUNCTION_ARGS); +extern Datum btint8cmp(PG_FUNCTION_ARGS); +extern Datum cash_mul_flt4(PG_FUNCTION_ARGS); +extern Datum cash_div_flt4(PG_FUNCTION_ARGS); +extern Datum flt4_mul_cash(PG_FUNCTION_ARGS); +extern Datum textpos(PG_FUNCTION_ARGS); +extern Datum textlike(PG_FUNCTION_ARGS); +extern Datum textnlike(PG_FUNCTION_ARGS); +extern Datum int48eq(PG_FUNCTION_ARGS); +extern Datum int48ne(PG_FUNCTION_ARGS); +extern Datum int48lt(PG_FUNCTION_ARGS); +extern Datum int48gt(PG_FUNCTION_ARGS); +extern Datum int48le(PG_FUNCTION_ARGS); +extern Datum int48ge(PG_FUNCTION_ARGS); +extern Datum namelike(PG_FUNCTION_ARGS); +extern Datum namenlike(PG_FUNCTION_ARGS); +extern Datum char_bpchar(PG_FUNCTION_ARGS); +extern Datum current_database(PG_FUNCTION_ARGS); +extern Datum int4_mul_cash(PG_FUNCTION_ARGS); +extern Datum int2_mul_cash(PG_FUNCTION_ARGS); +extern Datum cash_mul_int4(PG_FUNCTION_ARGS); +extern Datum cash_div_int4(PG_FUNCTION_ARGS); +extern Datum cash_mul_int2(PG_FUNCTION_ARGS); +extern Datum cash_div_int2(PG_FUNCTION_ARGS); +extern Datum lower(PG_FUNCTION_ARGS); +extern Datum upper(PG_FUNCTION_ARGS); +extern Datum initcap(PG_FUNCTION_ARGS); +extern Datum lpad(PG_FUNCTION_ARGS); +extern Datum rpad(PG_FUNCTION_ARGS); +extern Datum ltrim(PG_FUNCTION_ARGS); +extern Datum rtrim(PG_FUNCTION_ARGS); +extern Datum text_substr(PG_FUNCTION_ARGS); +extern Datum translate(PG_FUNCTION_ARGS); +extern Datum ltrim1(PG_FUNCTION_ARGS); +extern Datum text_substr_no_len(PG_FUNCTION_ARGS); +extern Datum btrim(PG_FUNCTION_ARGS); +extern Datum btrim1(PG_FUNCTION_ARGS); +extern Datum cash_in(PG_FUNCTION_ARGS); +extern Datum cash_out(PG_FUNCTION_ARGS); +extern Datum cash_eq(PG_FUNCTION_ARGS); +extern Datum cash_ne(PG_FUNCTION_ARGS); +extern Datum cash_lt(PG_FUNCTION_ARGS); +extern Datum cash_le(PG_FUNCTION_ARGS); +extern Datum cash_gt(PG_FUNCTION_ARGS); +extern Datum cash_ge(PG_FUNCTION_ARGS); +extern Datum cash_pl(PG_FUNCTION_ARGS); +extern Datum cash_mi(PG_FUNCTION_ARGS); +extern Datum cash_mul_flt8(PG_FUNCTION_ARGS); +extern Datum cash_div_flt8(PG_FUNCTION_ARGS); +extern Datum cashlarger(PG_FUNCTION_ARGS); +extern Datum cashsmaller(PG_FUNCTION_ARGS); +extern Datum inet_in(PG_FUNCTION_ARGS); +extern Datum inet_out(PG_FUNCTION_ARGS); +extern Datum flt8_mul_cash(PG_FUNCTION_ARGS); +extern Datum network_eq(PG_FUNCTION_ARGS); +extern Datum network_lt(PG_FUNCTION_ARGS); +extern Datum network_le(PG_FUNCTION_ARGS); +extern Datum network_gt(PG_FUNCTION_ARGS); +extern Datum network_ge(PG_FUNCTION_ARGS); +extern Datum network_ne(PG_FUNCTION_ARGS); +extern Datum network_cmp(PG_FUNCTION_ARGS); +extern Datum network_sub(PG_FUNCTION_ARGS); +extern Datum network_subeq(PG_FUNCTION_ARGS); +extern Datum network_sup(PG_FUNCTION_ARGS); +extern Datum network_supeq(PG_FUNCTION_ARGS); +extern Datum cash_words(PG_FUNCTION_ARGS); +extern Datum generate_series_timestamp(PG_FUNCTION_ARGS); +extern Datum generate_series_timestamptz(PG_FUNCTION_ARGS); +extern Datum int28mi(PG_FUNCTION_ARGS); +extern Datum int28mul(PG_FUNCTION_ARGS); +extern Datum text_char(PG_FUNCTION_ARGS); +extern Datum int8mod(PG_FUNCTION_ARGS); +extern Datum char_text(PG_FUNCTION_ARGS); +extern Datum int28div(PG_FUNCTION_ARGS); +extern Datum hashint8(PG_FUNCTION_ARGS); +extern Datum be_lo_open(PG_FUNCTION_ARGS); +extern Datum be_lo_close(PG_FUNCTION_ARGS); +extern Datum be_loread(PG_FUNCTION_ARGS); +extern Datum be_lowrite(PG_FUNCTION_ARGS); +extern Datum be_lo_lseek(PG_FUNCTION_ARGS); +extern Datum be_lo_creat(PG_FUNCTION_ARGS); +extern Datum be_lo_tell(PG_FUNCTION_ARGS); +extern Datum on_pl(PG_FUNCTION_ARGS); +extern Datum on_sl(PG_FUNCTION_ARGS); +extern Datum close_pl(PG_FUNCTION_ARGS); +extern Datum be_lo_unlink(PG_FUNCTION_ARGS); +extern Datum hashbpcharextended(PG_FUNCTION_ARGS); +extern Datum path_inter(PG_FUNCTION_ARGS); +extern Datum box_area(PG_FUNCTION_ARGS); +extern Datum box_width(PG_FUNCTION_ARGS); +extern Datum box_height(PG_FUNCTION_ARGS); +extern Datum box_distance(PG_FUNCTION_ARGS); +extern Datum path_area(PG_FUNCTION_ARGS); +extern Datum box_intersect(PG_FUNCTION_ARGS); +extern Datum box_diagonal(PG_FUNCTION_ARGS); +extern Datum path_n_lt(PG_FUNCTION_ARGS); +extern Datum path_n_gt(PG_FUNCTION_ARGS); +extern Datum path_n_eq(PG_FUNCTION_ARGS); +extern Datum path_n_le(PG_FUNCTION_ARGS); +extern Datum path_n_ge(PG_FUNCTION_ARGS); +extern Datum path_length(PG_FUNCTION_ARGS); +extern Datum point_ne(PG_FUNCTION_ARGS); +extern Datum point_vert(PG_FUNCTION_ARGS); +extern Datum point_horiz(PG_FUNCTION_ARGS); +extern Datum point_distance(PG_FUNCTION_ARGS); +extern Datum point_slope(PG_FUNCTION_ARGS); +extern Datum lseg_construct(PG_FUNCTION_ARGS); +extern Datum lseg_intersect(PG_FUNCTION_ARGS); +extern Datum lseg_parallel(PG_FUNCTION_ARGS); +extern Datum lseg_perp(PG_FUNCTION_ARGS); +extern Datum lseg_vertical(PG_FUNCTION_ARGS); +extern Datum lseg_horizontal(PG_FUNCTION_ARGS); +extern Datum lseg_eq(PG_FUNCTION_ARGS); +extern Datum be_lo_truncate(PG_FUNCTION_ARGS); +extern Datum textlike_support(PG_FUNCTION_ARGS); +extern Datum texticregexeq_support(PG_FUNCTION_ARGS); +extern Datum texticlike_support(PG_FUNCTION_ARGS); +extern Datum timestamptz_izone(PG_FUNCTION_ARGS); +extern Datum gist_point_compress(PG_FUNCTION_ARGS); +extern Datum aclitemin(PG_FUNCTION_ARGS); +extern Datum aclitemout(PG_FUNCTION_ARGS); +extern Datum aclinsert(PG_FUNCTION_ARGS); +extern Datum aclremove(PG_FUNCTION_ARGS); +extern Datum aclcontains(PG_FUNCTION_ARGS); +extern Datum getdatabaseencoding(PG_FUNCTION_ARGS); +extern Datum bpcharin(PG_FUNCTION_ARGS); +extern Datum bpcharout(PG_FUNCTION_ARGS); +extern Datum varcharin(PG_FUNCTION_ARGS); +extern Datum varcharout(PG_FUNCTION_ARGS); +extern Datum bpchareq(PG_FUNCTION_ARGS); +extern Datum bpcharlt(PG_FUNCTION_ARGS); +extern Datum bpcharle(PG_FUNCTION_ARGS); +extern Datum bpchargt(PG_FUNCTION_ARGS); +extern Datum bpcharge(PG_FUNCTION_ARGS); +extern Datum bpcharne(PG_FUNCTION_ARGS); +extern Datum aclitem_eq(PG_FUNCTION_ARGS); +extern Datum bpchar_larger(PG_FUNCTION_ARGS); +extern Datum bpchar_smaller(PG_FUNCTION_ARGS); +extern Datum pg_prepared_xact(PG_FUNCTION_ARGS); +extern Datum generate_series_step_int4(PG_FUNCTION_ARGS); +extern Datum generate_series_int4(PG_FUNCTION_ARGS); +extern Datum generate_series_step_int8(PG_FUNCTION_ARGS); +extern Datum generate_series_int8(PG_FUNCTION_ARGS); +extern Datum bpcharcmp(PG_FUNCTION_ARGS); +extern Datum text_regclass(PG_FUNCTION_ARGS); +extern Datum hashbpchar(PG_FUNCTION_ARGS); +extern Datum format_type(PG_FUNCTION_ARGS); +extern Datum date_in(PG_FUNCTION_ARGS); +extern Datum date_out(PG_FUNCTION_ARGS); +extern Datum date_eq(PG_FUNCTION_ARGS); +extern Datum date_lt(PG_FUNCTION_ARGS); +extern Datum date_le(PG_FUNCTION_ARGS); +extern Datum date_gt(PG_FUNCTION_ARGS); +extern Datum date_ge(PG_FUNCTION_ARGS); +extern Datum date_ne(PG_FUNCTION_ARGS); +extern Datum date_cmp(PG_FUNCTION_ARGS); +extern Datum time_lt(PG_FUNCTION_ARGS); +extern Datum time_le(PG_FUNCTION_ARGS); +extern Datum time_gt(PG_FUNCTION_ARGS); +extern Datum time_ge(PG_FUNCTION_ARGS); +extern Datum time_ne(PG_FUNCTION_ARGS); +extern Datum time_cmp(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_wal(PG_FUNCTION_ARGS); +extern Datum pg_get_wal_replay_pause_state(PG_FUNCTION_ARGS); +extern Datum date_larger(PG_FUNCTION_ARGS); +extern Datum date_smaller(PG_FUNCTION_ARGS); +extern Datum date_mi(PG_FUNCTION_ARGS); +extern Datum date_pli(PG_FUNCTION_ARGS); +extern Datum date_mii(PG_FUNCTION_ARGS); +extern Datum time_in(PG_FUNCTION_ARGS); +extern Datum time_out(PG_FUNCTION_ARGS); +extern Datum time_eq(PG_FUNCTION_ARGS); +extern Datum circle_add_pt(PG_FUNCTION_ARGS); +extern Datum circle_sub_pt(PG_FUNCTION_ARGS); +extern Datum circle_mul_pt(PG_FUNCTION_ARGS); +extern Datum circle_div_pt(PG_FUNCTION_ARGS); +extern Datum timestamptz_in(PG_FUNCTION_ARGS); +extern Datum timestamptz_out(PG_FUNCTION_ARGS); +extern Datum timestamp_eq(PG_FUNCTION_ARGS); +extern Datum timestamp_ne(PG_FUNCTION_ARGS); +extern Datum timestamp_lt(PG_FUNCTION_ARGS); +extern Datum timestamp_le(PG_FUNCTION_ARGS); +extern Datum timestamp_ge(PG_FUNCTION_ARGS); +extern Datum timestamp_gt(PG_FUNCTION_ARGS); +extern Datum float8_timestamptz(PG_FUNCTION_ARGS); +extern Datum timestamptz_zone(PG_FUNCTION_ARGS); +extern Datum interval_in(PG_FUNCTION_ARGS); +extern Datum interval_out(PG_FUNCTION_ARGS); +extern Datum interval_eq(PG_FUNCTION_ARGS); +extern Datum interval_ne(PG_FUNCTION_ARGS); +extern Datum interval_lt(PG_FUNCTION_ARGS); +extern Datum interval_le(PG_FUNCTION_ARGS); +extern Datum interval_ge(PG_FUNCTION_ARGS); +extern Datum interval_gt(PG_FUNCTION_ARGS); +extern Datum interval_um(PG_FUNCTION_ARGS); +extern Datum interval_pl(PG_FUNCTION_ARGS); +extern Datum interval_mi(PG_FUNCTION_ARGS); +extern Datum timestamptz_part(PG_FUNCTION_ARGS); +extern Datum interval_part(PG_FUNCTION_ARGS); +extern Datum network_subset_support(PG_FUNCTION_ARGS); +extern Datum date_timestamptz(PG_FUNCTION_ARGS); +extern Datum interval_justify_hours(PG_FUNCTION_ARGS); +extern Datum jsonb_path_exists_tz(PG_FUNCTION_ARGS); +extern Datum timestamptz_date(PG_FUNCTION_ARGS); +extern Datum jsonb_path_query_tz(PG_FUNCTION_ARGS); +extern Datum jsonb_path_query_array_tz(PG_FUNCTION_ARGS); +extern Datum xid_age(PG_FUNCTION_ARGS); +extern Datum timestamp_mi(PG_FUNCTION_ARGS); +extern Datum timestamptz_pl_interval(PG_FUNCTION_ARGS); +extern Datum timestamptz_mi_interval(PG_FUNCTION_ARGS); +extern Datum generate_subscripts(PG_FUNCTION_ARGS); +extern Datum generate_subscripts_nodir(PG_FUNCTION_ARGS); +extern Datum array_fill(PG_FUNCTION_ARGS); +extern Datum dlog10(PG_FUNCTION_ARGS); +extern Datum timestamp_smaller(PG_FUNCTION_ARGS); +extern Datum timestamp_larger(PG_FUNCTION_ARGS); +extern Datum interval_smaller(PG_FUNCTION_ARGS); +extern Datum interval_larger(PG_FUNCTION_ARGS); +extern Datum timestamptz_age(PG_FUNCTION_ARGS); +extern Datum interval_scale(PG_FUNCTION_ARGS); +extern Datum timestamptz_trunc(PG_FUNCTION_ARGS); +extern Datum interval_trunc(PG_FUNCTION_ARGS); +extern Datum int8inc(PG_FUNCTION_ARGS); +extern Datum int8abs(PG_FUNCTION_ARGS); +extern Datum int8larger(PG_FUNCTION_ARGS); +extern Datum int8smaller(PG_FUNCTION_ARGS); +extern Datum texticregexeq(PG_FUNCTION_ARGS); +extern Datum texticregexne(PG_FUNCTION_ARGS); +extern Datum nameicregexeq(PG_FUNCTION_ARGS); +extern Datum nameicregexne(PG_FUNCTION_ARGS); +extern Datum boolin(PG_FUNCTION_ARGS); +extern Datum boolout(PG_FUNCTION_ARGS); +extern Datum byteain(PG_FUNCTION_ARGS); +extern Datum charin(PG_FUNCTION_ARGS); +extern Datum charlt(PG_FUNCTION_ARGS); +extern Datum unique_key_recheck(PG_FUNCTION_ARGS); +extern Datum int4abs(PG_FUNCTION_ARGS); +extern Datum nameregexne(PG_FUNCTION_ARGS); +extern Datum int2abs(PG_FUNCTION_ARGS); +extern Datum textregexeq(PG_FUNCTION_ARGS); +extern Datum textregexne(PG_FUNCTION_ARGS); +extern Datum textlen(PG_FUNCTION_ARGS); +extern Datum textcat(PG_FUNCTION_ARGS); +extern Datum PG_char_to_encoding(PG_FUNCTION_ARGS); +extern Datum tidne(PG_FUNCTION_ARGS); +extern Datum cidr_in(PG_FUNCTION_ARGS); +extern Datum parse_ident(PG_FUNCTION_ARGS); +extern Datum pg_column_size(PG_FUNCTION_ARGS); +extern Datum overlaps_timetz(PG_FUNCTION_ARGS); +extern Datum datetime_timestamp(PG_FUNCTION_ARGS); +extern Datum timetz_part(PG_FUNCTION_ARGS); +extern Datum int84pl(PG_FUNCTION_ARGS); +extern Datum int84mi(PG_FUNCTION_ARGS); +extern Datum int84mul(PG_FUNCTION_ARGS); +extern Datum int84div(PG_FUNCTION_ARGS); +extern Datum int48pl(PG_FUNCTION_ARGS); +extern Datum int48mi(PG_FUNCTION_ARGS); +extern Datum int48mul(PG_FUNCTION_ARGS); +extern Datum int48div(PG_FUNCTION_ARGS); +extern Datum quote_ident(PG_FUNCTION_ARGS); +extern Datum quote_literal(PG_FUNCTION_ARGS); +extern Datum timestamptz_trunc_zone(PG_FUNCTION_ARGS); +extern Datum array_fill_with_lower_bounds(PG_FUNCTION_ARGS); +extern Datum i8tooid(PG_FUNCTION_ARGS); +extern Datum oidtoi8(PG_FUNCTION_ARGS); +extern Datum quote_nullable(PG_FUNCTION_ARGS); +extern Datum suppress_redundant_updates_trigger(PG_FUNCTION_ARGS); +extern Datum tideq(PG_FUNCTION_ARGS); +extern Datum multirange_unnest(PG_FUNCTION_ARGS); +extern Datum currtid_byrelname(PG_FUNCTION_ARGS); +extern Datum interval_justify_days(PG_FUNCTION_ARGS); +extern Datum datetimetz_timestamptz(PG_FUNCTION_ARGS); +extern Datum now(PG_FUNCTION_ARGS); +extern Datum positionsel(PG_FUNCTION_ARGS); +extern Datum positionjoinsel(PG_FUNCTION_ARGS); +extern Datum contsel(PG_FUNCTION_ARGS); +extern Datum contjoinsel(PG_FUNCTION_ARGS); +extern Datum overlaps_timestamp(PG_FUNCTION_ARGS); +extern Datum overlaps_time(PG_FUNCTION_ARGS); +extern Datum timestamp_in(PG_FUNCTION_ARGS); +extern Datum timestamp_out(PG_FUNCTION_ARGS); +extern Datum timestamp_cmp(PG_FUNCTION_ARGS); +extern Datum interval_cmp(PG_FUNCTION_ARGS); +extern Datum timestamp_time(PG_FUNCTION_ARGS); +extern Datum bpcharlen(PG_FUNCTION_ARGS); +extern Datum interval_div(PG_FUNCTION_ARGS); +extern Datum oidvectortypes(PG_FUNCTION_ARGS); +extern Datum timetz_in(PG_FUNCTION_ARGS); +extern Datum timetz_out(PG_FUNCTION_ARGS); +extern Datum timetz_eq(PG_FUNCTION_ARGS); +extern Datum timetz_ne(PG_FUNCTION_ARGS); +extern Datum timetz_lt(PG_FUNCTION_ARGS); +extern Datum timetz_le(PG_FUNCTION_ARGS); +extern Datum timetz_ge(PG_FUNCTION_ARGS); +extern Datum timetz_gt(PG_FUNCTION_ARGS); +extern Datum timetz_cmp(PG_FUNCTION_ARGS); +extern Datum network_hostmask(PG_FUNCTION_ARGS); +extern Datum textregexeq_support(PG_FUNCTION_ARGS); +extern Datum makeaclitem(PG_FUNCTION_ARGS); +extern Datum time_interval(PG_FUNCTION_ARGS); +extern Datum pg_lock_status(PG_FUNCTION_ARGS); +extern Datum date_finite(PG_FUNCTION_ARGS); +extern Datum textoctetlen(PG_FUNCTION_ARGS); +extern Datum bpcharoctetlen(PG_FUNCTION_ARGS); +extern Datum numeric_fac(PG_FUNCTION_ARGS); +extern Datum time_larger(PG_FUNCTION_ARGS); +extern Datum time_smaller(PG_FUNCTION_ARGS); +extern Datum timetz_larger(PG_FUNCTION_ARGS); +extern Datum timetz_smaller(PG_FUNCTION_ARGS); +extern Datum time_part(PG_FUNCTION_ARGS); +extern Datum pg_get_constraintdef(PG_FUNCTION_ARGS); +extern Datum timestamptz_timetz(PG_FUNCTION_ARGS); +extern Datum timestamp_finite(PG_FUNCTION_ARGS); +extern Datum interval_finite(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_backend_start(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_backend_client_addr(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_backend_client_port(PG_FUNCTION_ARGS); +extern Datum current_schema(PG_FUNCTION_ARGS); +extern Datum current_schemas(PG_FUNCTION_ARGS); +extern Datum textoverlay(PG_FUNCTION_ARGS); +extern Datum textoverlay_no_len(PG_FUNCTION_ARGS); +extern Datum line_parallel(PG_FUNCTION_ARGS); +extern Datum line_perp(PG_FUNCTION_ARGS); +extern Datum line_vertical(PG_FUNCTION_ARGS); +extern Datum line_horizontal(PG_FUNCTION_ARGS); +extern Datum circle_center(PG_FUNCTION_ARGS); +extern Datum interval_time(PG_FUNCTION_ARGS); +extern Datum points_box(PG_FUNCTION_ARGS); +extern Datum box_add(PG_FUNCTION_ARGS); +extern Datum box_sub(PG_FUNCTION_ARGS); +extern Datum box_mul(PG_FUNCTION_ARGS); +extern Datum box_div(PG_FUNCTION_ARGS); +extern Datum cidr_out(PG_FUNCTION_ARGS); +extern Datum poly_contain_pt(PG_FUNCTION_ARGS); +extern Datum pt_contained_poly(PG_FUNCTION_ARGS); +extern Datum path_isclosed(PG_FUNCTION_ARGS); +extern Datum path_isopen(PG_FUNCTION_ARGS); +extern Datum path_npoints(PG_FUNCTION_ARGS); +extern Datum path_close(PG_FUNCTION_ARGS); +extern Datum path_open(PG_FUNCTION_ARGS); +extern Datum path_add(PG_FUNCTION_ARGS); +extern Datum path_add_pt(PG_FUNCTION_ARGS); +extern Datum path_sub_pt(PG_FUNCTION_ARGS); +extern Datum path_mul_pt(PG_FUNCTION_ARGS); +extern Datum path_div_pt(PG_FUNCTION_ARGS); +extern Datum construct_point(PG_FUNCTION_ARGS); +extern Datum point_add(PG_FUNCTION_ARGS); +extern Datum point_sub(PG_FUNCTION_ARGS); +extern Datum point_mul(PG_FUNCTION_ARGS); +extern Datum point_div(PG_FUNCTION_ARGS); +extern Datum poly_npoints(PG_FUNCTION_ARGS); +extern Datum poly_box(PG_FUNCTION_ARGS); +extern Datum poly_path(PG_FUNCTION_ARGS); +extern Datum box_poly(PG_FUNCTION_ARGS); +extern Datum path_poly(PG_FUNCTION_ARGS); +extern Datum circle_in(PG_FUNCTION_ARGS); +extern Datum circle_out(PG_FUNCTION_ARGS); +extern Datum circle_same(PG_FUNCTION_ARGS); +extern Datum circle_contain(PG_FUNCTION_ARGS); +extern Datum circle_left(PG_FUNCTION_ARGS); +extern Datum circle_overleft(PG_FUNCTION_ARGS); +extern Datum circle_overright(PG_FUNCTION_ARGS); +extern Datum circle_right(PG_FUNCTION_ARGS); +extern Datum circle_contained(PG_FUNCTION_ARGS); +extern Datum circle_overlap(PG_FUNCTION_ARGS); +extern Datum circle_below(PG_FUNCTION_ARGS); +extern Datum circle_above(PG_FUNCTION_ARGS); +extern Datum circle_eq(PG_FUNCTION_ARGS); +extern Datum circle_ne(PG_FUNCTION_ARGS); +extern Datum circle_lt(PG_FUNCTION_ARGS); +extern Datum circle_gt(PG_FUNCTION_ARGS); +extern Datum circle_le(PG_FUNCTION_ARGS); +extern Datum circle_ge(PG_FUNCTION_ARGS); +extern Datum circle_area(PG_FUNCTION_ARGS); +extern Datum circle_diameter(PG_FUNCTION_ARGS); +extern Datum circle_radius(PG_FUNCTION_ARGS); +extern Datum circle_distance(PG_FUNCTION_ARGS); +extern Datum cr_circle(PG_FUNCTION_ARGS); +extern Datum poly_circle(PG_FUNCTION_ARGS); +extern Datum circle_poly(PG_FUNCTION_ARGS); +extern Datum dist_pc(PG_FUNCTION_ARGS); +extern Datum circle_contain_pt(PG_FUNCTION_ARGS); +extern Datum pt_contained_circle(PG_FUNCTION_ARGS); +extern Datum box_circle(PG_FUNCTION_ARGS); +extern Datum circle_box(PG_FUNCTION_ARGS); +extern Datum lseg_ne(PG_FUNCTION_ARGS); +extern Datum lseg_lt(PG_FUNCTION_ARGS); +extern Datum lseg_le(PG_FUNCTION_ARGS); +extern Datum lseg_gt(PG_FUNCTION_ARGS); +extern Datum lseg_ge(PG_FUNCTION_ARGS); +extern Datum lseg_length(PG_FUNCTION_ARGS); +extern Datum close_ls(PG_FUNCTION_ARGS); +extern Datum close_lseg(PG_FUNCTION_ARGS); +extern Datum line_in(PG_FUNCTION_ARGS); +extern Datum line_out(PG_FUNCTION_ARGS); +extern Datum line_eq(PG_FUNCTION_ARGS); +extern Datum line_construct_pp(PG_FUNCTION_ARGS); +extern Datum line_interpt(PG_FUNCTION_ARGS); +extern Datum line_intersect(PG_FUNCTION_ARGS); +extern Datum bit_in(PG_FUNCTION_ARGS); +extern Datum bit_out(PG_FUNCTION_ARGS); +extern Datum pg_get_ruledef(PG_FUNCTION_ARGS); +extern Datum nextval_oid(PG_FUNCTION_ARGS); +extern Datum currval_oid(PG_FUNCTION_ARGS); +extern Datum setval_oid(PG_FUNCTION_ARGS); +extern Datum varbit_in(PG_FUNCTION_ARGS); +extern Datum varbit_out(PG_FUNCTION_ARGS); +extern Datum biteq(PG_FUNCTION_ARGS); +extern Datum bitne(PG_FUNCTION_ARGS); +extern Datum bitge(PG_FUNCTION_ARGS); +extern Datum bitgt(PG_FUNCTION_ARGS); +extern Datum bitle(PG_FUNCTION_ARGS); +extern Datum bitlt(PG_FUNCTION_ARGS); +extern Datum bitcmp(PG_FUNCTION_ARGS); +extern Datum PG_encoding_to_char(PG_FUNCTION_ARGS); +extern Datum drandom(PG_FUNCTION_ARGS); +extern Datum setseed(PG_FUNCTION_ARGS); +extern Datum dasin(PG_FUNCTION_ARGS); +extern Datum dacos(PG_FUNCTION_ARGS); +extern Datum datan(PG_FUNCTION_ARGS); +extern Datum datan2(PG_FUNCTION_ARGS); +extern Datum dsin(PG_FUNCTION_ARGS); +extern Datum dcos(PG_FUNCTION_ARGS); +extern Datum dtan(PG_FUNCTION_ARGS); +extern Datum dcot(PG_FUNCTION_ARGS); +extern Datum degrees(PG_FUNCTION_ARGS); +extern Datum radians(PG_FUNCTION_ARGS); +extern Datum dpi(PG_FUNCTION_ARGS); +extern Datum interval_mul(PG_FUNCTION_ARGS); +extern Datum pg_typeof(PG_FUNCTION_ARGS); +extern Datum ascii(PG_FUNCTION_ARGS); +extern Datum chr(PG_FUNCTION_ARGS); +extern Datum repeat(PG_FUNCTION_ARGS); +extern Datum similar_escape(PG_FUNCTION_ARGS); +extern Datum mul_d_interval(PG_FUNCTION_ARGS); +extern Datum texticlike(PG_FUNCTION_ARGS); +extern Datum texticnlike(PG_FUNCTION_ARGS); +extern Datum nameiclike(PG_FUNCTION_ARGS); +extern Datum nameicnlike(PG_FUNCTION_ARGS); +extern Datum like_escape(PG_FUNCTION_ARGS); +extern Datum oidgt(PG_FUNCTION_ARGS); +extern Datum oidge(PG_FUNCTION_ARGS); +extern Datum pg_get_viewdef_name(PG_FUNCTION_ARGS); +extern Datum pg_get_viewdef(PG_FUNCTION_ARGS); +extern Datum pg_get_userbyid(PG_FUNCTION_ARGS); +extern Datum pg_get_indexdef(PG_FUNCTION_ARGS); +extern Datum RI_FKey_check_ins(PG_FUNCTION_ARGS); +extern Datum RI_FKey_check_upd(PG_FUNCTION_ARGS); +extern Datum RI_FKey_cascade_del(PG_FUNCTION_ARGS); +extern Datum RI_FKey_cascade_upd(PG_FUNCTION_ARGS); +extern Datum RI_FKey_restrict_del(PG_FUNCTION_ARGS); +extern Datum RI_FKey_restrict_upd(PG_FUNCTION_ARGS); +extern Datum RI_FKey_setnull_del(PG_FUNCTION_ARGS); +extern Datum RI_FKey_setnull_upd(PG_FUNCTION_ARGS); +extern Datum RI_FKey_setdefault_del(PG_FUNCTION_ARGS); +extern Datum RI_FKey_setdefault_upd(PG_FUNCTION_ARGS); +extern Datum RI_FKey_noaction_del(PG_FUNCTION_ARGS); +extern Datum RI_FKey_noaction_upd(PG_FUNCTION_ARGS); +extern Datum pg_get_triggerdef(PG_FUNCTION_ARGS); +extern Datum pg_get_serial_sequence(PG_FUNCTION_ARGS); +extern Datum bit_and(PG_FUNCTION_ARGS); +extern Datum bit_or(PG_FUNCTION_ARGS); +extern Datum bitxor(PG_FUNCTION_ARGS); +extern Datum bitnot(PG_FUNCTION_ARGS); +extern Datum bitshiftleft(PG_FUNCTION_ARGS); +extern Datum bitshiftright(PG_FUNCTION_ARGS); +extern Datum bitcat(PG_FUNCTION_ARGS); +extern Datum bitsubstr(PG_FUNCTION_ARGS); +extern Datum bitlength(PG_FUNCTION_ARGS); +extern Datum bitoctetlength(PG_FUNCTION_ARGS); +extern Datum bitfromint4(PG_FUNCTION_ARGS); +extern Datum bittoint4(PG_FUNCTION_ARGS); +extern Datum bit(PG_FUNCTION_ARGS); +extern Datum pg_get_keywords(PG_FUNCTION_ARGS); +extern Datum varbit(PG_FUNCTION_ARGS); +extern Datum time_hash(PG_FUNCTION_ARGS); +extern Datum aclexplode(PG_FUNCTION_ARGS); +extern Datum time_mi_time(PG_FUNCTION_ARGS); +extern Datum boolle(PG_FUNCTION_ARGS); +extern Datum boolge(PG_FUNCTION_ARGS); +extern Datum btboolcmp(PG_FUNCTION_ARGS); +extern Datum timetz_hash(PG_FUNCTION_ARGS); +extern Datum interval_hash(PG_FUNCTION_ARGS); +extern Datum bitposition(PG_FUNCTION_ARGS); +extern Datum bitsubstr_no_len(PG_FUNCTION_ARGS); +extern Datum numeric_in(PG_FUNCTION_ARGS); +extern Datum numeric_out(PG_FUNCTION_ARGS); +extern Datum numeric(PG_FUNCTION_ARGS); +extern Datum numeric_abs(PG_FUNCTION_ARGS); +extern Datum numeric_sign(PG_FUNCTION_ARGS); +extern Datum numeric_round(PG_FUNCTION_ARGS); +extern Datum numeric_trunc(PG_FUNCTION_ARGS); +extern Datum numeric_ceil(PG_FUNCTION_ARGS); +extern Datum numeric_floor(PG_FUNCTION_ARGS); +extern Datum length_in_encoding(PG_FUNCTION_ARGS); +extern Datum pg_convert_from(PG_FUNCTION_ARGS); +extern Datum inet_to_cidr(PG_FUNCTION_ARGS); +extern Datum pg_get_expr(PG_FUNCTION_ARGS); +extern Datum pg_convert_to(PG_FUNCTION_ARGS); +extern Datum numeric_eq(PG_FUNCTION_ARGS); +extern Datum numeric_ne(PG_FUNCTION_ARGS); +extern Datum numeric_gt(PG_FUNCTION_ARGS); +extern Datum numeric_ge(PG_FUNCTION_ARGS); +extern Datum numeric_lt(PG_FUNCTION_ARGS); +extern Datum numeric_le(PG_FUNCTION_ARGS); +extern Datum numeric_add(PG_FUNCTION_ARGS); +extern Datum numeric_sub(PG_FUNCTION_ARGS); +extern Datum numeric_mul(PG_FUNCTION_ARGS); +extern Datum numeric_div(PG_FUNCTION_ARGS); +extern Datum numeric_mod(PG_FUNCTION_ARGS); +extern Datum numeric_sqrt(PG_FUNCTION_ARGS); +extern Datum numeric_exp(PG_FUNCTION_ARGS); +extern Datum numeric_ln(PG_FUNCTION_ARGS); +extern Datum numeric_log(PG_FUNCTION_ARGS); +extern Datum numeric_power(PG_FUNCTION_ARGS); +extern Datum int4_numeric(PG_FUNCTION_ARGS); +extern Datum float4_numeric(PG_FUNCTION_ARGS); +extern Datum float8_numeric(PG_FUNCTION_ARGS); +extern Datum numeric_int4(PG_FUNCTION_ARGS); +extern Datum numeric_float4(PG_FUNCTION_ARGS); +extern Datum numeric_float8(PG_FUNCTION_ARGS); +extern Datum time_pl_interval(PG_FUNCTION_ARGS); +extern Datum time_mi_interval(PG_FUNCTION_ARGS); +extern Datum timetz_pl_interval(PG_FUNCTION_ARGS); +extern Datum timetz_mi_interval(PG_FUNCTION_ARGS); +extern Datum numeric_inc(PG_FUNCTION_ARGS); +extern Datum setval3_oid(PG_FUNCTION_ARGS); +extern Datum numeric_smaller(PG_FUNCTION_ARGS); +extern Datum numeric_larger(PG_FUNCTION_ARGS); +extern Datum interval_to_char(PG_FUNCTION_ARGS); +extern Datum numeric_cmp(PG_FUNCTION_ARGS); +extern Datum timestamptz_to_char(PG_FUNCTION_ARGS); +extern Datum numeric_uminus(PG_FUNCTION_ARGS); +extern Datum numeric_to_char(PG_FUNCTION_ARGS); +extern Datum int4_to_char(PG_FUNCTION_ARGS); +extern Datum int8_to_char(PG_FUNCTION_ARGS); +extern Datum float4_to_char(PG_FUNCTION_ARGS); +extern Datum float8_to_char(PG_FUNCTION_ARGS); +extern Datum numeric_to_number(PG_FUNCTION_ARGS); +extern Datum to_timestamp(PG_FUNCTION_ARGS); +extern Datum numeric_int8(PG_FUNCTION_ARGS); +extern Datum to_date(PG_FUNCTION_ARGS); +extern Datum int8_numeric(PG_FUNCTION_ARGS); +extern Datum int2_numeric(PG_FUNCTION_ARGS); +extern Datum numeric_int2(PG_FUNCTION_ARGS); +extern Datum oidin(PG_FUNCTION_ARGS); +extern Datum oidout(PG_FUNCTION_ARGS); +extern Datum pg_convert(PG_FUNCTION_ARGS); +extern Datum iclikesel(PG_FUNCTION_ARGS); +extern Datum icnlikesel(PG_FUNCTION_ARGS); +extern Datum iclikejoinsel(PG_FUNCTION_ARGS); +extern Datum icnlikejoinsel(PG_FUNCTION_ARGS); +extern Datum regexeqsel(PG_FUNCTION_ARGS); +extern Datum likesel(PG_FUNCTION_ARGS); +extern Datum icregexeqsel(PG_FUNCTION_ARGS); +extern Datum regexnesel(PG_FUNCTION_ARGS); +extern Datum nlikesel(PG_FUNCTION_ARGS); +extern Datum icregexnesel(PG_FUNCTION_ARGS); +extern Datum regexeqjoinsel(PG_FUNCTION_ARGS); +extern Datum likejoinsel(PG_FUNCTION_ARGS); +extern Datum icregexeqjoinsel(PG_FUNCTION_ARGS); +extern Datum regexnejoinsel(PG_FUNCTION_ARGS); +extern Datum nlikejoinsel(PG_FUNCTION_ARGS); +extern Datum icregexnejoinsel(PG_FUNCTION_ARGS); +extern Datum float8_avg(PG_FUNCTION_ARGS); +extern Datum float8_var_samp(PG_FUNCTION_ARGS); +extern Datum float8_stddev_samp(PG_FUNCTION_ARGS); +extern Datum numeric_accum(PG_FUNCTION_ARGS); +extern Datum int2_accum(PG_FUNCTION_ARGS); +extern Datum int4_accum(PG_FUNCTION_ARGS); +extern Datum int8_accum(PG_FUNCTION_ARGS); +extern Datum numeric_avg(PG_FUNCTION_ARGS); +extern Datum numeric_var_samp(PG_FUNCTION_ARGS); +extern Datum numeric_stddev_samp(PG_FUNCTION_ARGS); +extern Datum int2_sum(PG_FUNCTION_ARGS); +extern Datum int4_sum(PG_FUNCTION_ARGS); +extern Datum int8_sum(PG_FUNCTION_ARGS); +extern Datum interval_accum(PG_FUNCTION_ARGS); +extern Datum interval_avg(PG_FUNCTION_ARGS); +extern Datum to_ascii_default(PG_FUNCTION_ARGS); +extern Datum to_ascii_enc(PG_FUNCTION_ARGS); +extern Datum to_ascii_encname(PG_FUNCTION_ARGS); +extern Datum int28eq(PG_FUNCTION_ARGS); +extern Datum int28ne(PG_FUNCTION_ARGS); +extern Datum int28lt(PG_FUNCTION_ARGS); +extern Datum int28gt(PG_FUNCTION_ARGS); +extern Datum int28le(PG_FUNCTION_ARGS); +extern Datum int28ge(PG_FUNCTION_ARGS); +extern Datum int82eq(PG_FUNCTION_ARGS); +extern Datum int82ne(PG_FUNCTION_ARGS); +extern Datum int82lt(PG_FUNCTION_ARGS); +extern Datum int82gt(PG_FUNCTION_ARGS); +extern Datum int82le(PG_FUNCTION_ARGS); +extern Datum int82ge(PG_FUNCTION_ARGS); +extern Datum int2and(PG_FUNCTION_ARGS); +extern Datum int2or(PG_FUNCTION_ARGS); +extern Datum int2xor(PG_FUNCTION_ARGS); +extern Datum int2not(PG_FUNCTION_ARGS); +extern Datum int2shl(PG_FUNCTION_ARGS); +extern Datum int2shr(PG_FUNCTION_ARGS); +extern Datum int4and(PG_FUNCTION_ARGS); +extern Datum int4or(PG_FUNCTION_ARGS); +extern Datum int4xor(PG_FUNCTION_ARGS); +extern Datum int4not(PG_FUNCTION_ARGS); +extern Datum int4shl(PG_FUNCTION_ARGS); +extern Datum int4shr(PG_FUNCTION_ARGS); +extern Datum int8and(PG_FUNCTION_ARGS); +extern Datum int8or(PG_FUNCTION_ARGS); +extern Datum int8xor(PG_FUNCTION_ARGS); +extern Datum int8not(PG_FUNCTION_ARGS); +extern Datum int8shl(PG_FUNCTION_ARGS); +extern Datum int8shr(PG_FUNCTION_ARGS); +extern Datum int8up(PG_FUNCTION_ARGS); +extern Datum int2up(PG_FUNCTION_ARGS); +extern Datum int4up(PG_FUNCTION_ARGS); +extern Datum float4up(PG_FUNCTION_ARGS); +extern Datum float8up(PG_FUNCTION_ARGS); +extern Datum numeric_uplus(PG_FUNCTION_ARGS); +extern Datum has_table_privilege_name_name(PG_FUNCTION_ARGS); +extern Datum has_table_privilege_name_id(PG_FUNCTION_ARGS); +extern Datum has_table_privilege_id_name(PG_FUNCTION_ARGS); +extern Datum has_table_privilege_id_id(PG_FUNCTION_ARGS); +extern Datum has_table_privilege_name(PG_FUNCTION_ARGS); +extern Datum has_table_privilege_id(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_numscans(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_tuples_returned(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_tuples_fetched(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_tuples_inserted(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_tuples_updated(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_tuples_deleted(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_blocks_fetched(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_blocks_hit(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_backend_idset(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_backend_pid(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_backend_dbid(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_backend_userid(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_backend_activity(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_db_numbackends(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_db_xact_commit(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_db_xact_rollback(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_db_blocks_fetched(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_db_blocks_hit(PG_FUNCTION_ARGS); +extern Datum binary_encode(PG_FUNCTION_ARGS); +extern Datum binary_decode(PG_FUNCTION_ARGS); +extern Datum byteaeq(PG_FUNCTION_ARGS); +extern Datum bytealt(PG_FUNCTION_ARGS); +extern Datum byteale(PG_FUNCTION_ARGS); +extern Datum byteagt(PG_FUNCTION_ARGS); +extern Datum byteage(PG_FUNCTION_ARGS); +extern Datum byteane(PG_FUNCTION_ARGS); +extern Datum byteacmp(PG_FUNCTION_ARGS); +extern Datum timestamp_scale(PG_FUNCTION_ARGS); +extern Datum int2_avg_accum(PG_FUNCTION_ARGS); +extern Datum int4_avg_accum(PG_FUNCTION_ARGS); +extern Datum int8_avg(PG_FUNCTION_ARGS); +extern Datum oidlarger(PG_FUNCTION_ARGS); +extern Datum oidsmaller(PG_FUNCTION_ARGS); +extern Datum timestamptz_scale(PG_FUNCTION_ARGS); +extern Datum time_scale(PG_FUNCTION_ARGS); +extern Datum timetz_scale(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_tuples_hot_updated(PG_FUNCTION_ARGS); +extern Datum numeric_div_trunc(PG_FUNCTION_ARGS); +extern Datum similar_to_escape_2(PG_FUNCTION_ARGS); +extern Datum similar_to_escape_1(PG_FUNCTION_ARGS); +extern Datum bytealike(PG_FUNCTION_ARGS); +extern Datum byteanlike(PG_FUNCTION_ARGS); +extern Datum like_escape_bytea(PG_FUNCTION_ARGS); +extern Datum byteacat(PG_FUNCTION_ARGS); +extern Datum bytea_substr(PG_FUNCTION_ARGS); +extern Datum bytea_substr_no_len(PG_FUNCTION_ARGS); +extern Datum byteapos(PG_FUNCTION_ARGS); +extern Datum byteatrim(PG_FUNCTION_ARGS); +extern Datum timestamptz_time(PG_FUNCTION_ARGS); +extern Datum timestamp_trunc(PG_FUNCTION_ARGS); +extern Datum timestamp_part(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_activity(PG_FUNCTION_ARGS); +extern Datum jsonb_path_query_first_tz(PG_FUNCTION_ARGS); +extern Datum date_timestamp(PG_FUNCTION_ARGS); +extern Datum pg_backend_pid(PG_FUNCTION_ARGS); +extern Datum timestamptz_timestamp(PG_FUNCTION_ARGS); +extern Datum timestamp_timestamptz(PG_FUNCTION_ARGS); +extern Datum timestamp_date(PG_FUNCTION_ARGS); +extern Datum jsonb_path_match_tz(PG_FUNCTION_ARGS); +extern Datum timestamp_pl_interval(PG_FUNCTION_ARGS); +extern Datum timestamp_mi_interval(PG_FUNCTION_ARGS); +extern Datum pg_conf_load_time(PG_FUNCTION_ARGS); +extern Datum timetz_zone(PG_FUNCTION_ARGS); +extern Datum timetz_izone(PG_FUNCTION_ARGS); +extern Datum timestamp_hash(PG_FUNCTION_ARGS); +extern Datum timetz_time(PG_FUNCTION_ARGS); +extern Datum time_timetz(PG_FUNCTION_ARGS); +extern Datum timestamp_to_char(PG_FUNCTION_ARGS); +extern Datum timestamp_age(PG_FUNCTION_ARGS); +extern Datum timestamp_zone(PG_FUNCTION_ARGS); +extern Datum timestamp_izone(PG_FUNCTION_ARGS); +extern Datum date_pl_interval(PG_FUNCTION_ARGS); +extern Datum date_mi_interval(PG_FUNCTION_ARGS); +extern Datum textregexsubstr(PG_FUNCTION_ARGS); +extern Datum bitfromint8(PG_FUNCTION_ARGS); +extern Datum bittoint8(PG_FUNCTION_ARGS); +extern Datum show_config_by_name(PG_FUNCTION_ARGS); +extern Datum set_config_by_name(PG_FUNCTION_ARGS); +extern Datum pg_table_is_visible(PG_FUNCTION_ARGS); +extern Datum pg_type_is_visible(PG_FUNCTION_ARGS); +extern Datum pg_function_is_visible(PG_FUNCTION_ARGS); +extern Datum pg_operator_is_visible(PG_FUNCTION_ARGS); +extern Datum pg_opclass_is_visible(PG_FUNCTION_ARGS); +extern Datum show_all_settings(PG_FUNCTION_ARGS); +extern Datum replace_text(PG_FUNCTION_ARGS); +extern Datum split_part(PG_FUNCTION_ARGS); +extern Datum to_hex32(PG_FUNCTION_ARGS); +extern Datum to_hex64(PG_FUNCTION_ARGS); +extern Datum array_lower(PG_FUNCTION_ARGS); +extern Datum array_upper(PG_FUNCTION_ARGS); +extern Datum pg_conversion_is_visible(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_backend_activity_start(PG_FUNCTION_ARGS); +extern Datum pg_terminate_backend(PG_FUNCTION_ARGS); +extern Datum pg_get_functiondef(PG_FUNCTION_ARGS); +extern Datum pg_column_compression(PG_FUNCTION_ARGS); +extern Datum pg_stat_force_next_flush(PG_FUNCTION_ARGS); +extern Datum text_pattern_lt(PG_FUNCTION_ARGS); +extern Datum text_pattern_le(PG_FUNCTION_ARGS); +extern Datum pg_get_function_arguments(PG_FUNCTION_ARGS); +extern Datum text_pattern_ge(PG_FUNCTION_ARGS); +extern Datum text_pattern_gt(PG_FUNCTION_ARGS); +extern Datum pg_get_function_result(PG_FUNCTION_ARGS); +extern Datum bttext_pattern_cmp(PG_FUNCTION_ARGS); +extern Datum pg_database_size_name(PG_FUNCTION_ARGS); +extern Datum width_bucket_numeric(PG_FUNCTION_ARGS); +extern Datum pg_cancel_backend(PG_FUNCTION_ARGS); +extern Datum pg_backup_start(PG_FUNCTION_ARGS); +extern Datum bpchar_pattern_lt(PG_FUNCTION_ARGS); +extern Datum bpchar_pattern_le(PG_FUNCTION_ARGS); +extern Datum array_length(PG_FUNCTION_ARGS); +extern Datum bpchar_pattern_ge(PG_FUNCTION_ARGS); +extern Datum bpchar_pattern_gt(PG_FUNCTION_ARGS); +extern Datum gist_point_consistent(PG_FUNCTION_ARGS); +extern Datum btbpchar_pattern_cmp(PG_FUNCTION_ARGS); +extern Datum has_sequence_privilege_name_name(PG_FUNCTION_ARGS); +extern Datum has_sequence_privilege_name_id(PG_FUNCTION_ARGS); +extern Datum has_sequence_privilege_id_name(PG_FUNCTION_ARGS); +extern Datum has_sequence_privilege_id_id(PG_FUNCTION_ARGS); +extern Datum has_sequence_privilege_name(PG_FUNCTION_ARGS); +extern Datum has_sequence_privilege_id(PG_FUNCTION_ARGS); +extern Datum btint48cmp(PG_FUNCTION_ARGS); +extern Datum btint84cmp(PG_FUNCTION_ARGS); +extern Datum btint24cmp(PG_FUNCTION_ARGS); +extern Datum btint42cmp(PG_FUNCTION_ARGS); +extern Datum btint28cmp(PG_FUNCTION_ARGS); +extern Datum btint82cmp(PG_FUNCTION_ARGS); +extern Datum btfloat48cmp(PG_FUNCTION_ARGS); +extern Datum btfloat84cmp(PG_FUNCTION_ARGS); +extern Datum inet_client_addr(PG_FUNCTION_ARGS); +extern Datum inet_client_port(PG_FUNCTION_ARGS); +extern Datum inet_server_addr(PG_FUNCTION_ARGS); +extern Datum inet_server_port(PG_FUNCTION_ARGS); +extern Datum regprocedurein(PG_FUNCTION_ARGS); +extern Datum regprocedureout(PG_FUNCTION_ARGS); +extern Datum regoperin(PG_FUNCTION_ARGS); +extern Datum regoperout(PG_FUNCTION_ARGS); +extern Datum regoperatorin(PG_FUNCTION_ARGS); +extern Datum regoperatorout(PG_FUNCTION_ARGS); +extern Datum regclassin(PG_FUNCTION_ARGS); +extern Datum regclassout(PG_FUNCTION_ARGS); +extern Datum regtypein(PG_FUNCTION_ARGS); +extern Datum regtypeout(PG_FUNCTION_ARGS); +extern Datum pg_stat_clear_snapshot(PG_FUNCTION_ARGS); +extern Datum pg_get_function_identity_arguments(PG_FUNCTION_ARGS); +extern Datum hashtid(PG_FUNCTION_ARGS); +extern Datum hashtidextended(PG_FUNCTION_ARGS); +extern Datum fmgr_internal_validator(PG_FUNCTION_ARGS); +extern Datum fmgr_c_validator(PG_FUNCTION_ARGS); +extern Datum fmgr_sql_validator(PG_FUNCTION_ARGS); +extern Datum has_database_privilege_name_name(PG_FUNCTION_ARGS); +extern Datum has_database_privilege_name_id(PG_FUNCTION_ARGS); +extern Datum has_database_privilege_id_name(PG_FUNCTION_ARGS); +extern Datum has_database_privilege_id_id(PG_FUNCTION_ARGS); +extern Datum has_database_privilege_name(PG_FUNCTION_ARGS); +extern Datum has_database_privilege_id(PG_FUNCTION_ARGS); +extern Datum has_function_privilege_name_name(PG_FUNCTION_ARGS); +extern Datum has_function_privilege_name_id(PG_FUNCTION_ARGS); +extern Datum has_function_privilege_id_name(PG_FUNCTION_ARGS); +extern Datum has_function_privilege_id_id(PG_FUNCTION_ARGS); +extern Datum has_function_privilege_name(PG_FUNCTION_ARGS); +extern Datum has_function_privilege_id(PG_FUNCTION_ARGS); +extern Datum has_language_privilege_name_name(PG_FUNCTION_ARGS); +extern Datum has_language_privilege_name_id(PG_FUNCTION_ARGS); +extern Datum has_language_privilege_id_name(PG_FUNCTION_ARGS); +extern Datum has_language_privilege_id_id(PG_FUNCTION_ARGS); +extern Datum has_language_privilege_name(PG_FUNCTION_ARGS); +extern Datum has_language_privilege_id(PG_FUNCTION_ARGS); +extern Datum has_schema_privilege_name_name(PG_FUNCTION_ARGS); +extern Datum has_schema_privilege_name_id(PG_FUNCTION_ARGS); +extern Datum has_schema_privilege_id_name(PG_FUNCTION_ARGS); +extern Datum has_schema_privilege_id_id(PG_FUNCTION_ARGS); +extern Datum has_schema_privilege_name(PG_FUNCTION_ARGS); +extern Datum has_schema_privilege_id(PG_FUNCTION_ARGS); +extern Datum pg_stat_reset(PG_FUNCTION_ARGS); +extern Datum pg_get_backend_memory_contexts(PG_FUNCTION_ARGS); +extern Datum textregexreplace_noopt(PG_FUNCTION_ARGS); +extern Datum textregexreplace(PG_FUNCTION_ARGS); +extern Datum pg_total_relation_size(PG_FUNCTION_ARGS); +extern Datum pg_size_pretty(PG_FUNCTION_ARGS); +extern Datum pg_options_to_table(PG_FUNCTION_ARGS); +extern Datum record_in(PG_FUNCTION_ARGS); +extern Datum record_out(PG_FUNCTION_ARGS); +extern Datum cstring_in(PG_FUNCTION_ARGS); +extern Datum cstring_out(PG_FUNCTION_ARGS); +extern Datum any_in(PG_FUNCTION_ARGS); +extern Datum any_out(PG_FUNCTION_ARGS); +extern Datum anyarray_in(PG_FUNCTION_ARGS); +extern Datum anyarray_out(PG_FUNCTION_ARGS); +extern Datum void_in(PG_FUNCTION_ARGS); +extern Datum void_out(PG_FUNCTION_ARGS); +extern Datum trigger_in(PG_FUNCTION_ARGS); +extern Datum trigger_out(PG_FUNCTION_ARGS); +extern Datum language_handler_in(PG_FUNCTION_ARGS); +extern Datum language_handler_out(PG_FUNCTION_ARGS); +extern Datum internal_in(PG_FUNCTION_ARGS); +extern Datum internal_out(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_slru(PG_FUNCTION_ARGS); +extern Datum pg_stat_reset_slru(PG_FUNCTION_ARGS); +extern Datum dceil(PG_FUNCTION_ARGS); +extern Datum dfloor(PG_FUNCTION_ARGS); +extern Datum dsign(PG_FUNCTION_ARGS); +extern Datum md5_text(PG_FUNCTION_ARGS); +extern Datum anyelement_in(PG_FUNCTION_ARGS); +extern Datum anyelement_out(PG_FUNCTION_ARGS); +extern Datum postgresql_fdw_validator(PG_FUNCTION_ARGS); +extern Datum pg_encoding_max_length_sql(PG_FUNCTION_ARGS); +extern Datum md5_bytea(PG_FUNCTION_ARGS); +extern Datum pg_tablespace_size_oid(PG_FUNCTION_ARGS); +extern Datum pg_tablespace_size_name(PG_FUNCTION_ARGS); +extern Datum pg_database_size_oid(PG_FUNCTION_ARGS); +extern Datum array_unnest(PG_FUNCTION_ARGS); +extern Datum pg_relation_size(PG_FUNCTION_ARGS); +extern Datum array_agg_transfn(PG_FUNCTION_ARGS); +extern Datum array_agg_finalfn(PG_FUNCTION_ARGS); +extern Datum date_lt_timestamp(PG_FUNCTION_ARGS); +extern Datum date_le_timestamp(PG_FUNCTION_ARGS); +extern Datum date_eq_timestamp(PG_FUNCTION_ARGS); +extern Datum date_gt_timestamp(PG_FUNCTION_ARGS); +extern Datum date_ge_timestamp(PG_FUNCTION_ARGS); +extern Datum date_ne_timestamp(PG_FUNCTION_ARGS); +extern Datum date_cmp_timestamp(PG_FUNCTION_ARGS); +extern Datum date_lt_timestamptz(PG_FUNCTION_ARGS); +extern Datum date_le_timestamptz(PG_FUNCTION_ARGS); +extern Datum date_eq_timestamptz(PG_FUNCTION_ARGS); +extern Datum date_gt_timestamptz(PG_FUNCTION_ARGS); +extern Datum date_ge_timestamptz(PG_FUNCTION_ARGS); +extern Datum date_ne_timestamptz(PG_FUNCTION_ARGS); +extern Datum date_cmp_timestamptz(PG_FUNCTION_ARGS); +extern Datum timestamp_lt_date(PG_FUNCTION_ARGS); +extern Datum timestamp_le_date(PG_FUNCTION_ARGS); +extern Datum timestamp_eq_date(PG_FUNCTION_ARGS); +extern Datum timestamp_gt_date(PG_FUNCTION_ARGS); +extern Datum timestamp_ge_date(PG_FUNCTION_ARGS); +extern Datum timestamp_ne_date(PG_FUNCTION_ARGS); +extern Datum timestamp_cmp_date(PG_FUNCTION_ARGS); +extern Datum timestamptz_lt_date(PG_FUNCTION_ARGS); +extern Datum timestamptz_le_date(PG_FUNCTION_ARGS); +extern Datum timestamptz_eq_date(PG_FUNCTION_ARGS); +extern Datum timestamptz_gt_date(PG_FUNCTION_ARGS); +extern Datum timestamptz_ge_date(PG_FUNCTION_ARGS); +extern Datum timestamptz_ne_date(PG_FUNCTION_ARGS); +extern Datum timestamptz_cmp_date(PG_FUNCTION_ARGS); +extern Datum has_tablespace_privilege_name_name(PG_FUNCTION_ARGS); +extern Datum has_tablespace_privilege_name_id(PG_FUNCTION_ARGS); +extern Datum has_tablespace_privilege_id_name(PG_FUNCTION_ARGS); +extern Datum has_tablespace_privilege_id_id(PG_FUNCTION_ARGS); +extern Datum has_tablespace_privilege_name(PG_FUNCTION_ARGS); +extern Datum has_tablespace_privilege_id(PG_FUNCTION_ARGS); +extern Datum shell_in(PG_FUNCTION_ARGS); +extern Datum shell_out(PG_FUNCTION_ARGS); +extern Datum array_recv(PG_FUNCTION_ARGS); +extern Datum array_send(PG_FUNCTION_ARGS); +extern Datum record_recv(PG_FUNCTION_ARGS); +extern Datum record_send(PG_FUNCTION_ARGS); +extern Datum int2recv(PG_FUNCTION_ARGS); +extern Datum int2send(PG_FUNCTION_ARGS); +extern Datum int4recv(PG_FUNCTION_ARGS); +extern Datum int4send(PG_FUNCTION_ARGS); +extern Datum int8recv(PG_FUNCTION_ARGS); +extern Datum int8send(PG_FUNCTION_ARGS); +extern Datum int2vectorrecv(PG_FUNCTION_ARGS); +extern Datum int2vectorsend(PG_FUNCTION_ARGS); +extern Datum bytearecv(PG_FUNCTION_ARGS); +extern Datum byteasend(PG_FUNCTION_ARGS); +extern Datum textrecv(PG_FUNCTION_ARGS); +extern Datum textsend(PG_FUNCTION_ARGS); +extern Datum unknownrecv(PG_FUNCTION_ARGS); +extern Datum unknownsend(PG_FUNCTION_ARGS); +extern Datum oidrecv(PG_FUNCTION_ARGS); +extern Datum oidsend(PG_FUNCTION_ARGS); +extern Datum oidvectorrecv(PG_FUNCTION_ARGS); +extern Datum oidvectorsend(PG_FUNCTION_ARGS); +extern Datum namerecv(PG_FUNCTION_ARGS); +extern Datum namesend(PG_FUNCTION_ARGS); +extern Datum float4recv(PG_FUNCTION_ARGS); +extern Datum float4send(PG_FUNCTION_ARGS); +extern Datum float8recv(PG_FUNCTION_ARGS); +extern Datum float8send(PG_FUNCTION_ARGS); +extern Datum point_recv(PG_FUNCTION_ARGS); +extern Datum point_send(PG_FUNCTION_ARGS); +extern Datum bpcharrecv(PG_FUNCTION_ARGS); +extern Datum bpcharsend(PG_FUNCTION_ARGS); +extern Datum varcharrecv(PG_FUNCTION_ARGS); +extern Datum varcharsend(PG_FUNCTION_ARGS); +extern Datum charrecv(PG_FUNCTION_ARGS); +extern Datum charsend(PG_FUNCTION_ARGS); +extern Datum boolrecv(PG_FUNCTION_ARGS); +extern Datum boolsend(PG_FUNCTION_ARGS); +extern Datum tidrecv(PG_FUNCTION_ARGS); +extern Datum tidsend(PG_FUNCTION_ARGS); +extern Datum xidrecv(PG_FUNCTION_ARGS); +extern Datum xidsend(PG_FUNCTION_ARGS); +extern Datum cidrecv(PG_FUNCTION_ARGS); +extern Datum cidsend(PG_FUNCTION_ARGS); +extern Datum regprocrecv(PG_FUNCTION_ARGS); +extern Datum regprocsend(PG_FUNCTION_ARGS); +extern Datum regprocedurerecv(PG_FUNCTION_ARGS); +extern Datum regproceduresend(PG_FUNCTION_ARGS); +extern Datum regoperrecv(PG_FUNCTION_ARGS); +extern Datum regopersend(PG_FUNCTION_ARGS); +extern Datum regoperatorrecv(PG_FUNCTION_ARGS); +extern Datum regoperatorsend(PG_FUNCTION_ARGS); +extern Datum regclassrecv(PG_FUNCTION_ARGS); +extern Datum regclasssend(PG_FUNCTION_ARGS); +extern Datum regtyperecv(PG_FUNCTION_ARGS); +extern Datum regtypesend(PG_FUNCTION_ARGS); +extern Datum bit_recv(PG_FUNCTION_ARGS); +extern Datum bit_send(PG_FUNCTION_ARGS); +extern Datum varbit_recv(PG_FUNCTION_ARGS); +extern Datum varbit_send(PG_FUNCTION_ARGS); +extern Datum numeric_recv(PG_FUNCTION_ARGS); +extern Datum numeric_send(PG_FUNCTION_ARGS); +extern Datum dsinh(PG_FUNCTION_ARGS); +extern Datum dcosh(PG_FUNCTION_ARGS); +extern Datum dtanh(PG_FUNCTION_ARGS); +extern Datum dasinh(PG_FUNCTION_ARGS); +extern Datum dacosh(PG_FUNCTION_ARGS); +extern Datum datanh(PG_FUNCTION_ARGS); +extern Datum date_recv(PG_FUNCTION_ARGS); +extern Datum date_send(PG_FUNCTION_ARGS); +extern Datum time_recv(PG_FUNCTION_ARGS); +extern Datum time_send(PG_FUNCTION_ARGS); +extern Datum timetz_recv(PG_FUNCTION_ARGS); +extern Datum timetz_send(PG_FUNCTION_ARGS); +extern Datum timestamp_recv(PG_FUNCTION_ARGS); +extern Datum timestamp_send(PG_FUNCTION_ARGS); +extern Datum timestamptz_recv(PG_FUNCTION_ARGS); +extern Datum timestamptz_send(PG_FUNCTION_ARGS); +extern Datum interval_recv(PG_FUNCTION_ARGS); +extern Datum interval_send(PG_FUNCTION_ARGS); +extern Datum lseg_recv(PG_FUNCTION_ARGS); +extern Datum lseg_send(PG_FUNCTION_ARGS); +extern Datum path_recv(PG_FUNCTION_ARGS); +extern Datum path_send(PG_FUNCTION_ARGS); +extern Datum box_recv(PG_FUNCTION_ARGS); +extern Datum box_send(PG_FUNCTION_ARGS); +extern Datum poly_recv(PG_FUNCTION_ARGS); +extern Datum poly_send(PG_FUNCTION_ARGS); +extern Datum line_recv(PG_FUNCTION_ARGS); +extern Datum line_send(PG_FUNCTION_ARGS); +extern Datum circle_recv(PG_FUNCTION_ARGS); +extern Datum circle_send(PG_FUNCTION_ARGS); +extern Datum cash_recv(PG_FUNCTION_ARGS); +extern Datum cash_send(PG_FUNCTION_ARGS); +extern Datum macaddr_recv(PG_FUNCTION_ARGS); +extern Datum macaddr_send(PG_FUNCTION_ARGS); +extern Datum inet_recv(PG_FUNCTION_ARGS); +extern Datum inet_send(PG_FUNCTION_ARGS); +extern Datum cidr_recv(PG_FUNCTION_ARGS); +extern Datum cidr_send(PG_FUNCTION_ARGS); +extern Datum cstring_recv(PG_FUNCTION_ARGS); +extern Datum cstring_send(PG_FUNCTION_ARGS); +extern Datum anyarray_recv(PG_FUNCTION_ARGS); +extern Datum anyarray_send(PG_FUNCTION_ARGS); +extern Datum pg_get_ruledef_ext(PG_FUNCTION_ARGS); +extern Datum pg_get_viewdef_name_ext(PG_FUNCTION_ARGS); +extern Datum pg_get_viewdef_ext(PG_FUNCTION_ARGS); +extern Datum pg_get_indexdef_ext(PG_FUNCTION_ARGS); +extern Datum pg_get_constraintdef_ext(PG_FUNCTION_ARGS); +extern Datum pg_get_expr_ext(PG_FUNCTION_ARGS); +extern Datum pg_prepared_statement(PG_FUNCTION_ARGS); +extern Datum pg_cursor(PG_FUNCTION_ARGS); +extern Datum float8_var_pop(PG_FUNCTION_ARGS); +extern Datum float8_stddev_pop(PG_FUNCTION_ARGS); +extern Datum numeric_var_pop(PG_FUNCTION_ARGS); +extern Datum booland_statefunc(PG_FUNCTION_ARGS); +extern Datum boolor_statefunc(PG_FUNCTION_ARGS); +extern Datum timestamp_lt_timestamptz(PG_FUNCTION_ARGS); +extern Datum timestamp_le_timestamptz(PG_FUNCTION_ARGS); +extern Datum timestamp_eq_timestamptz(PG_FUNCTION_ARGS); +extern Datum timestamp_gt_timestamptz(PG_FUNCTION_ARGS); +extern Datum timestamp_ge_timestamptz(PG_FUNCTION_ARGS); +extern Datum timestamp_ne_timestamptz(PG_FUNCTION_ARGS); +extern Datum timestamp_cmp_timestamptz(PG_FUNCTION_ARGS); +extern Datum timestamptz_lt_timestamp(PG_FUNCTION_ARGS); +extern Datum timestamptz_le_timestamp(PG_FUNCTION_ARGS); +extern Datum timestamptz_eq_timestamp(PG_FUNCTION_ARGS); +extern Datum timestamptz_gt_timestamp(PG_FUNCTION_ARGS); +extern Datum timestamptz_ge_timestamp(PG_FUNCTION_ARGS); +extern Datum timestamptz_ne_timestamp(PG_FUNCTION_ARGS); +extern Datum timestamptz_cmp_timestamp(PG_FUNCTION_ARGS); +extern Datum pg_tablespace_databases(PG_FUNCTION_ARGS); +extern Datum int4_bool(PG_FUNCTION_ARGS); +extern Datum bool_int4(PG_FUNCTION_ARGS); +extern Datum lastval(PG_FUNCTION_ARGS); +extern Datum pg_postmaster_start_time(PG_FUNCTION_ARGS); +extern Datum pg_blocking_pids(PG_FUNCTION_ARGS); +extern Datum box_below(PG_FUNCTION_ARGS); +extern Datum box_overbelow(PG_FUNCTION_ARGS); +extern Datum box_overabove(PG_FUNCTION_ARGS); +extern Datum box_above(PG_FUNCTION_ARGS); +extern Datum poly_below(PG_FUNCTION_ARGS); +extern Datum poly_overbelow(PG_FUNCTION_ARGS); +extern Datum poly_overabove(PG_FUNCTION_ARGS); +extern Datum poly_above(PG_FUNCTION_ARGS); +extern Datum gist_box_consistent(PG_FUNCTION_ARGS); +extern Datum jsonb_float8(PG_FUNCTION_ARGS); +extern Datum gist_box_penalty(PG_FUNCTION_ARGS); +extern Datum gist_box_picksplit(PG_FUNCTION_ARGS); +extern Datum gist_box_union(PG_FUNCTION_ARGS); +extern Datum gist_box_same(PG_FUNCTION_ARGS); +extern Datum gist_poly_consistent(PG_FUNCTION_ARGS); +extern Datum gist_poly_compress(PG_FUNCTION_ARGS); +extern Datum circle_overbelow(PG_FUNCTION_ARGS); +extern Datum circle_overabove(PG_FUNCTION_ARGS); +extern Datum gist_circle_consistent(PG_FUNCTION_ARGS); +extern Datum gist_circle_compress(PG_FUNCTION_ARGS); +extern Datum numeric_stddev_pop(PG_FUNCTION_ARGS); +extern Datum domain_in(PG_FUNCTION_ARGS); +extern Datum domain_recv(PG_FUNCTION_ARGS); +extern Datum pg_timezone_abbrevs(PG_FUNCTION_ARGS); +extern Datum xmlexists(PG_FUNCTION_ARGS); +extern Datum pg_reload_conf(PG_FUNCTION_ARGS); +extern Datum pg_rotate_logfile_v2(PG_FUNCTION_ARGS); +extern Datum pg_stat_file_1arg(PG_FUNCTION_ARGS); +extern Datum pg_read_file_off_len(PG_FUNCTION_ARGS); +extern Datum pg_ls_dir_1arg(PG_FUNCTION_ARGS); +extern Datum pg_sleep(PG_FUNCTION_ARGS); +extern Datum inetnot(PG_FUNCTION_ARGS); +extern Datum inetand(PG_FUNCTION_ARGS); +extern Datum inetor(PG_FUNCTION_ARGS); +extern Datum inetpl(PG_FUNCTION_ARGS); +extern Datum inetmi_int8(PG_FUNCTION_ARGS); +extern Datum inetmi(PG_FUNCTION_ARGS); +extern Datum statement_timestamp(PG_FUNCTION_ARGS); +extern Datum clock_timestamp(PG_FUNCTION_ARGS); +extern Datum gin_cmp_prefix(PG_FUNCTION_ARGS); +extern Datum pg_has_role_name_name(PG_FUNCTION_ARGS); +extern Datum pg_has_role_name_id(PG_FUNCTION_ARGS); +extern Datum pg_has_role_id_name(PG_FUNCTION_ARGS); +extern Datum pg_has_role_id_id(PG_FUNCTION_ARGS); +extern Datum pg_has_role_name(PG_FUNCTION_ARGS); +extern Datum pg_has_role_id(PG_FUNCTION_ARGS); +extern Datum interval_justify_interval(PG_FUNCTION_ARGS); +extern Datum pg_get_triggerdef_ext(PG_FUNCTION_ARGS); +extern Datum dasind(PG_FUNCTION_ARGS); +extern Datum dacosd(PG_FUNCTION_ARGS); +extern Datum datand(PG_FUNCTION_ARGS); +extern Datum datan2d(PG_FUNCTION_ARGS); +extern Datum dsind(PG_FUNCTION_ARGS); +extern Datum dcosd(PG_FUNCTION_ARGS); +extern Datum dtand(PG_FUNCTION_ARGS); +extern Datum dcotd(PG_FUNCTION_ARGS); +extern Datum pg_backup_stop(PG_FUNCTION_ARGS); +extern Datum numeric_avg_serialize(PG_FUNCTION_ARGS); +extern Datum numeric_avg_deserialize(PG_FUNCTION_ARGS); +extern Datum ginarrayextract(PG_FUNCTION_ARGS); +extern Datum ginarrayconsistent(PG_FUNCTION_ARGS); +extern Datum int8_avg_accum(PG_FUNCTION_ARGS); +extern Datum arrayoverlap(PG_FUNCTION_ARGS); +extern Datum arraycontains(PG_FUNCTION_ARGS); +extern Datum arraycontained(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_db_tuples_returned(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_db_tuples_fetched(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_db_tuples_inserted(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_db_tuples_updated(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_db_tuples_deleted(PG_FUNCTION_ARGS); +extern Datum regexp_matches_no_flags(PG_FUNCTION_ARGS); +extern Datum regexp_matches(PG_FUNCTION_ARGS); +extern Datum regexp_split_to_table_no_flags(PG_FUNCTION_ARGS); +extern Datum regexp_split_to_table(PG_FUNCTION_ARGS); +extern Datum regexp_split_to_array_no_flags(PG_FUNCTION_ARGS); +extern Datum regexp_split_to_array(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_bgwriter_timed_checkpoints(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_bgwriter_requested_checkpoints(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_bgwriter_buf_written_checkpoints(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_bgwriter_buf_written_clean(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_bgwriter_maxwritten_clean(PG_FUNCTION_ARGS); +extern Datum ginqueryarrayextract(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_buf_written_backend(PG_FUNCTION_ARGS); +extern Datum anynonarray_in(PG_FUNCTION_ARGS); +extern Datum anynonarray_out(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_last_vacuum_time(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_last_autovacuum_time(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_last_analyze_time(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_last_autoanalyze_time(PG_FUNCTION_ARGS); +extern Datum int8_avg_combine(PG_FUNCTION_ARGS); +extern Datum int8_avg_serialize(PG_FUNCTION_ARGS); +extern Datum int8_avg_deserialize(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_backend_wait_event_type(PG_FUNCTION_ARGS); +extern Datum tidgt(PG_FUNCTION_ARGS); +extern Datum tidlt(PG_FUNCTION_ARGS); +extern Datum tidge(PG_FUNCTION_ARGS); +extern Datum tidle(PG_FUNCTION_ARGS); +extern Datum bttidcmp(PG_FUNCTION_ARGS); +extern Datum tidlarger(PG_FUNCTION_ARGS); +extern Datum tidsmaller(PG_FUNCTION_ARGS); +extern Datum int8inc_any(PG_FUNCTION_ARGS); +extern Datum int8inc_float8_float8(PG_FUNCTION_ARGS); +extern Datum float8_regr_accum(PG_FUNCTION_ARGS); +extern Datum float8_regr_sxx(PG_FUNCTION_ARGS); +extern Datum float8_regr_syy(PG_FUNCTION_ARGS); +extern Datum float8_regr_sxy(PG_FUNCTION_ARGS); +extern Datum float8_regr_avgx(PG_FUNCTION_ARGS); +extern Datum float8_regr_avgy(PG_FUNCTION_ARGS); +extern Datum float8_regr_r2(PG_FUNCTION_ARGS); +extern Datum float8_regr_slope(PG_FUNCTION_ARGS); +extern Datum float8_regr_intercept(PG_FUNCTION_ARGS); +extern Datum float8_covar_pop(PG_FUNCTION_ARGS); +extern Datum float8_covar_samp(PG_FUNCTION_ARGS); +extern Datum float8_corr(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_db_blk_read_time(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_db_blk_write_time(PG_FUNCTION_ARGS); +extern Datum pg_switch_wal(PG_FUNCTION_ARGS); +extern Datum pg_current_wal_lsn(PG_FUNCTION_ARGS); +extern Datum pg_walfile_name_offset(PG_FUNCTION_ARGS); +extern Datum pg_walfile_name(PG_FUNCTION_ARGS); +extern Datum pg_current_wal_insert_lsn(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_backend_wait_event(PG_FUNCTION_ARGS); +extern Datum pg_my_temp_schema(PG_FUNCTION_ARGS); +extern Datum pg_is_other_temp_schema(PG_FUNCTION_ARGS); +extern Datum pg_timezone_names(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_backend_xact_start(PG_FUNCTION_ARGS); +extern Datum numeric_avg_accum(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_buf_alloc(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_live_tuples(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_dead_tuples(PG_FUNCTION_ARGS); +extern Datum pg_advisory_lock_int8(PG_FUNCTION_ARGS); +extern Datum pg_advisory_lock_shared_int8(PG_FUNCTION_ARGS); +extern Datum pg_try_advisory_lock_int8(PG_FUNCTION_ARGS); +extern Datum pg_try_advisory_lock_shared_int8(PG_FUNCTION_ARGS); +extern Datum pg_advisory_unlock_int8(PG_FUNCTION_ARGS); +extern Datum pg_advisory_unlock_shared_int8(PG_FUNCTION_ARGS); +extern Datum pg_advisory_lock_int4(PG_FUNCTION_ARGS); +extern Datum pg_advisory_lock_shared_int4(PG_FUNCTION_ARGS); +extern Datum pg_try_advisory_lock_int4(PG_FUNCTION_ARGS); +extern Datum pg_try_advisory_lock_shared_int4(PG_FUNCTION_ARGS); +extern Datum pg_advisory_unlock_int4(PG_FUNCTION_ARGS); +extern Datum pg_advisory_unlock_shared_int4(PG_FUNCTION_ARGS); +extern Datum pg_advisory_unlock_all(PG_FUNCTION_ARGS); +extern Datum xml_in(PG_FUNCTION_ARGS); +extern Datum xml_out(PG_FUNCTION_ARGS); +extern Datum xmlcomment(PG_FUNCTION_ARGS); +extern Datum texttoxml(PG_FUNCTION_ARGS); +extern Datum xmlvalidate(PG_FUNCTION_ARGS); +extern Datum xml_recv(PG_FUNCTION_ARGS); +extern Datum xml_send(PG_FUNCTION_ARGS); +extern Datum xmlconcat2(PG_FUNCTION_ARGS); +extern Datum varbittypmodin(PG_FUNCTION_ARGS); +extern Datum intervaltypmodin(PG_FUNCTION_ARGS); +extern Datum intervaltypmodout(PG_FUNCTION_ARGS); +extern Datum timestamptypmodin(PG_FUNCTION_ARGS); +extern Datum timestamptypmodout(PG_FUNCTION_ARGS); +extern Datum timestamptztypmodin(PG_FUNCTION_ARGS); +extern Datum timestamptztypmodout(PG_FUNCTION_ARGS); +extern Datum timetypmodin(PG_FUNCTION_ARGS); +extern Datum timetypmodout(PG_FUNCTION_ARGS); +extern Datum timetztypmodin(PG_FUNCTION_ARGS); +extern Datum timetztypmodout(PG_FUNCTION_ARGS); +extern Datum bpchartypmodin(PG_FUNCTION_ARGS); +extern Datum bpchartypmodout(PG_FUNCTION_ARGS); +extern Datum varchartypmodin(PG_FUNCTION_ARGS); +extern Datum varchartypmodout(PG_FUNCTION_ARGS); +extern Datum numerictypmodin(PG_FUNCTION_ARGS); +extern Datum numerictypmodout(PG_FUNCTION_ARGS); +extern Datum bittypmodin(PG_FUNCTION_ARGS); +extern Datum bittypmodout(PG_FUNCTION_ARGS); +extern Datum varbittypmodout(PG_FUNCTION_ARGS); +extern Datum xmltotext(PG_FUNCTION_ARGS); +extern Datum table_to_xml(PG_FUNCTION_ARGS); +extern Datum query_to_xml(PG_FUNCTION_ARGS); +extern Datum cursor_to_xml(PG_FUNCTION_ARGS); +extern Datum table_to_xmlschema(PG_FUNCTION_ARGS); +extern Datum query_to_xmlschema(PG_FUNCTION_ARGS); +extern Datum cursor_to_xmlschema(PG_FUNCTION_ARGS); +extern Datum table_to_xml_and_xmlschema(PG_FUNCTION_ARGS); +extern Datum query_to_xml_and_xmlschema(PG_FUNCTION_ARGS); +extern Datum xpath(PG_FUNCTION_ARGS); +extern Datum schema_to_xml(PG_FUNCTION_ARGS); +extern Datum schema_to_xmlschema(PG_FUNCTION_ARGS); +extern Datum schema_to_xml_and_xmlschema(PG_FUNCTION_ARGS); +extern Datum database_to_xml(PG_FUNCTION_ARGS); +extern Datum database_to_xmlschema(PG_FUNCTION_ARGS); +extern Datum database_to_xml_and_xmlschema(PG_FUNCTION_ARGS); +extern Datum pg_snapshot_in(PG_FUNCTION_ARGS); +extern Datum pg_snapshot_out(PG_FUNCTION_ARGS); +extern Datum pg_snapshot_recv(PG_FUNCTION_ARGS); +extern Datum pg_snapshot_send(PG_FUNCTION_ARGS); +extern Datum pg_current_xact_id(PG_FUNCTION_ARGS); +extern Datum pg_current_snapshot(PG_FUNCTION_ARGS); +extern Datum pg_snapshot_xmin(PG_FUNCTION_ARGS); +extern Datum pg_snapshot_xmax(PG_FUNCTION_ARGS); +extern Datum pg_snapshot_xip(PG_FUNCTION_ARGS); +extern Datum pg_visible_in_snapshot(PG_FUNCTION_ARGS); +extern Datum uuid_in(PG_FUNCTION_ARGS); +extern Datum uuid_out(PG_FUNCTION_ARGS); +extern Datum uuid_lt(PG_FUNCTION_ARGS); +extern Datum uuid_le(PG_FUNCTION_ARGS); +extern Datum uuid_eq(PG_FUNCTION_ARGS); +extern Datum uuid_ge(PG_FUNCTION_ARGS); +extern Datum uuid_gt(PG_FUNCTION_ARGS); +extern Datum uuid_ne(PG_FUNCTION_ARGS); +extern Datum uuid_cmp(PG_FUNCTION_ARGS); +extern Datum uuid_recv(PG_FUNCTION_ARGS); +extern Datum uuid_send(PG_FUNCTION_ARGS); +extern Datum uuid_hash(PG_FUNCTION_ARGS); +extern Datum booltext(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_function_calls(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_function_total_time(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_function_self_time(PG_FUNCTION_ARGS); +extern Datum record_eq(PG_FUNCTION_ARGS); +extern Datum record_ne(PG_FUNCTION_ARGS); +extern Datum record_lt(PG_FUNCTION_ARGS); +extern Datum record_gt(PG_FUNCTION_ARGS); +extern Datum record_le(PG_FUNCTION_ARGS); +extern Datum record_ge(PG_FUNCTION_ARGS); +extern Datum btrecordcmp(PG_FUNCTION_ARGS); +extern Datum pg_table_size(PG_FUNCTION_ARGS); +extern Datum pg_indexes_size(PG_FUNCTION_ARGS); +extern Datum pg_relation_filenode(PG_FUNCTION_ARGS); +extern Datum has_foreign_data_wrapper_privilege_name_name(PG_FUNCTION_ARGS); +extern Datum has_foreign_data_wrapper_privilege_name_id(PG_FUNCTION_ARGS); +extern Datum has_foreign_data_wrapper_privilege_id_name(PG_FUNCTION_ARGS); +extern Datum has_foreign_data_wrapper_privilege_id_id(PG_FUNCTION_ARGS); +extern Datum has_foreign_data_wrapper_privilege_name(PG_FUNCTION_ARGS); +extern Datum has_foreign_data_wrapper_privilege_id(PG_FUNCTION_ARGS); +extern Datum has_server_privilege_name_name(PG_FUNCTION_ARGS); +extern Datum has_server_privilege_name_id(PG_FUNCTION_ARGS); +extern Datum has_server_privilege_id_name(PG_FUNCTION_ARGS); +extern Datum has_server_privilege_id_id(PG_FUNCTION_ARGS); +extern Datum has_server_privilege_name(PG_FUNCTION_ARGS); +extern Datum has_server_privilege_id(PG_FUNCTION_ARGS); +extern Datum has_column_privilege_name_name_name(PG_FUNCTION_ARGS); +extern Datum has_column_privilege_name_name_attnum(PG_FUNCTION_ARGS); +extern Datum has_column_privilege_name_id_name(PG_FUNCTION_ARGS); +extern Datum has_column_privilege_name_id_attnum(PG_FUNCTION_ARGS); +extern Datum has_column_privilege_id_name_name(PG_FUNCTION_ARGS); +extern Datum has_column_privilege_id_name_attnum(PG_FUNCTION_ARGS); +extern Datum has_column_privilege_id_id_name(PG_FUNCTION_ARGS); +extern Datum has_column_privilege_id_id_attnum(PG_FUNCTION_ARGS); +extern Datum has_column_privilege_name_name(PG_FUNCTION_ARGS); +extern Datum has_column_privilege_name_attnum(PG_FUNCTION_ARGS); +extern Datum has_column_privilege_id_name(PG_FUNCTION_ARGS); +extern Datum has_column_privilege_id_attnum(PG_FUNCTION_ARGS); +extern Datum has_any_column_privilege_name_name(PG_FUNCTION_ARGS); +extern Datum has_any_column_privilege_name_id(PG_FUNCTION_ARGS); +extern Datum has_any_column_privilege_id_name(PG_FUNCTION_ARGS); +extern Datum has_any_column_privilege_id_id(PG_FUNCTION_ARGS); +extern Datum has_any_column_privilege_name(PG_FUNCTION_ARGS); +extern Datum has_any_column_privilege_id(PG_FUNCTION_ARGS); +extern Datum bitoverlay(PG_FUNCTION_ARGS); +extern Datum bitoverlay_no_len(PG_FUNCTION_ARGS); +extern Datum bitgetbit(PG_FUNCTION_ARGS); +extern Datum bitsetbit(PG_FUNCTION_ARGS); +extern Datum pg_relation_filepath(PG_FUNCTION_ARGS); +extern Datum pg_listening_channels(PG_FUNCTION_ARGS); +extern Datum pg_notify(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_xact_numscans(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_xact_tuples_returned(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_xact_tuples_fetched(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_xact_tuples_inserted(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_xact_tuples_updated(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_xact_tuples_deleted(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_xact_tuples_hot_updated(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_xact_blocks_fetched(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_xact_blocks_hit(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_xact_function_calls(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_xact_function_total_time(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_xact_function_self_time(PG_FUNCTION_ARGS); +extern Datum xpath_exists(PG_FUNCTION_ARGS); +extern Datum xml_is_well_formed(PG_FUNCTION_ARGS); +extern Datum xml_is_well_formed_document(PG_FUNCTION_ARGS); +extern Datum xml_is_well_formed_content(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_vacuum_count(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_autovacuum_count(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_analyze_count(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_autoanalyze_count(PG_FUNCTION_ARGS); +extern Datum text_concat(PG_FUNCTION_ARGS); +extern Datum text_concat_ws(PG_FUNCTION_ARGS); +extern Datum text_left(PG_FUNCTION_ARGS); +extern Datum text_right(PG_FUNCTION_ARGS); +extern Datum text_reverse(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_buf_fsync_backend(PG_FUNCTION_ARGS); +extern Datum gist_point_distance(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_db_conflict_tablespace(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_db_conflict_lock(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_db_conflict_snapshot(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_db_conflict_bufferpin(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_db_conflict_startup_deadlock(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_db_conflict_all(PG_FUNCTION_ARGS); +extern Datum pg_wal_replay_pause(PG_FUNCTION_ARGS); +extern Datum pg_wal_replay_resume(PG_FUNCTION_ARGS); +extern Datum pg_is_wal_replay_paused(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_db_stat_reset_time(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_bgwriter_stat_reset_time(PG_FUNCTION_ARGS); +extern Datum ginarrayextract_2args(PG_FUNCTION_ARGS); +extern Datum gin_extract_tsvector_2args(PG_FUNCTION_ARGS); +extern Datum pg_sequence_parameters(PG_FUNCTION_ARGS); +extern Datum pg_available_extensions(PG_FUNCTION_ARGS); +extern Datum pg_available_extension_versions(PG_FUNCTION_ARGS); +extern Datum pg_extension_update_paths(PG_FUNCTION_ARGS); +extern Datum pg_extension_config_dump(PG_FUNCTION_ARGS); +extern Datum gin_extract_tsquery_5args(PG_FUNCTION_ARGS); +extern Datum gin_tsquery_consistent_6args(PG_FUNCTION_ARGS); +extern Datum pg_advisory_xact_lock_int8(PG_FUNCTION_ARGS); +extern Datum pg_advisory_xact_lock_shared_int8(PG_FUNCTION_ARGS); +extern Datum pg_try_advisory_xact_lock_int8(PG_FUNCTION_ARGS); +extern Datum pg_try_advisory_xact_lock_shared_int8(PG_FUNCTION_ARGS); +extern Datum pg_advisory_xact_lock_int4(PG_FUNCTION_ARGS); +extern Datum pg_advisory_xact_lock_shared_int4(PG_FUNCTION_ARGS); +extern Datum pg_try_advisory_xact_lock_int4(PG_FUNCTION_ARGS); +extern Datum pg_try_advisory_xact_lock_shared_int4(PG_FUNCTION_ARGS); +extern Datum varchar_support(PG_FUNCTION_ARGS); +extern Datum pg_create_restore_point(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_wal_senders(PG_FUNCTION_ARGS); +extern Datum window_row_number(PG_FUNCTION_ARGS); +extern Datum window_rank(PG_FUNCTION_ARGS); +extern Datum window_dense_rank(PG_FUNCTION_ARGS); +extern Datum window_percent_rank(PG_FUNCTION_ARGS); +extern Datum window_cume_dist(PG_FUNCTION_ARGS); +extern Datum window_ntile(PG_FUNCTION_ARGS); +extern Datum window_lag(PG_FUNCTION_ARGS); +extern Datum window_lag_with_offset(PG_FUNCTION_ARGS); +extern Datum window_lag_with_offset_and_default(PG_FUNCTION_ARGS); +extern Datum window_lead(PG_FUNCTION_ARGS); +extern Datum window_lead_with_offset(PG_FUNCTION_ARGS); +extern Datum window_lead_with_offset_and_default(PG_FUNCTION_ARGS); +extern Datum window_first_value(PG_FUNCTION_ARGS); +extern Datum window_last_value(PG_FUNCTION_ARGS); +extern Datum window_nth_value(PG_FUNCTION_ARGS); +extern Datum fdw_handler_in(PG_FUNCTION_ARGS); +extern Datum fdw_handler_out(PG_FUNCTION_ARGS); +extern Datum void_recv(PG_FUNCTION_ARGS); +extern Datum void_send(PG_FUNCTION_ARGS); +extern Datum btint2sortsupport(PG_FUNCTION_ARGS); +extern Datum btint4sortsupport(PG_FUNCTION_ARGS); +extern Datum btint8sortsupport(PG_FUNCTION_ARGS); +extern Datum btfloat4sortsupport(PG_FUNCTION_ARGS); +extern Datum btfloat8sortsupport(PG_FUNCTION_ARGS); +extern Datum btoidsortsupport(PG_FUNCTION_ARGS); +extern Datum btnamesortsupport(PG_FUNCTION_ARGS); +extern Datum date_sortsupport(PG_FUNCTION_ARGS); +extern Datum timestamp_sortsupport(PG_FUNCTION_ARGS); +extern Datum has_type_privilege_name_name(PG_FUNCTION_ARGS); +extern Datum has_type_privilege_name_id(PG_FUNCTION_ARGS); +extern Datum has_type_privilege_id_name(PG_FUNCTION_ARGS); +extern Datum has_type_privilege_id_id(PG_FUNCTION_ARGS); +extern Datum has_type_privilege_name(PG_FUNCTION_ARGS); +extern Datum has_type_privilege_id(PG_FUNCTION_ARGS); +extern Datum macaddr_not(PG_FUNCTION_ARGS); +extern Datum macaddr_and(PG_FUNCTION_ARGS); +extern Datum macaddr_or(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_db_temp_files(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_db_temp_bytes(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_db_deadlocks(PG_FUNCTION_ARGS); +extern Datum array_to_json(PG_FUNCTION_ARGS); +extern Datum array_to_json_pretty(PG_FUNCTION_ARGS); +extern Datum row_to_json(PG_FUNCTION_ARGS); +extern Datum row_to_json_pretty(PG_FUNCTION_ARGS); +extern Datum numeric_support(PG_FUNCTION_ARGS); +extern Datum varbit_support(PG_FUNCTION_ARGS); +extern Datum pg_get_viewdef_wrap(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_checkpoint_write_time(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_checkpoint_sync_time(PG_FUNCTION_ARGS); +extern Datum pg_collation_for(PG_FUNCTION_ARGS); +extern Datum pg_trigger_depth(PG_FUNCTION_ARGS); +extern Datum pg_wal_lsn_diff(PG_FUNCTION_ARGS); +extern Datum pg_size_pretty_numeric(PG_FUNCTION_ARGS); +extern Datum array_remove(PG_FUNCTION_ARGS); +extern Datum array_replace(PG_FUNCTION_ARGS); +extern Datum rangesel(PG_FUNCTION_ARGS); +extern Datum be_lo_lseek64(PG_FUNCTION_ARGS); +extern Datum be_lo_tell64(PG_FUNCTION_ARGS); +extern Datum be_lo_truncate64(PG_FUNCTION_ARGS); +extern Datum json_agg_transfn(PG_FUNCTION_ARGS); +extern Datum json_agg_finalfn(PG_FUNCTION_ARGS); +extern Datum to_json(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_mod_since_analyze(PG_FUNCTION_ARGS); +extern Datum numeric_sum(PG_FUNCTION_ARGS); +extern Datum array_cardinality(PG_FUNCTION_ARGS); +extern Datum json_object_agg_transfn(PG_FUNCTION_ARGS); +extern Datum record_image_eq(PG_FUNCTION_ARGS); +extern Datum record_image_ne(PG_FUNCTION_ARGS); +extern Datum record_image_lt(PG_FUNCTION_ARGS); +extern Datum record_image_gt(PG_FUNCTION_ARGS); +extern Datum record_image_le(PG_FUNCTION_ARGS); +extern Datum record_image_ge(PG_FUNCTION_ARGS); +extern Datum btrecordimagecmp(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_archiver(PG_FUNCTION_ARGS); +extern Datum json_object_agg_finalfn(PG_FUNCTION_ARGS); +extern Datum json_build_array(PG_FUNCTION_ARGS); +extern Datum json_build_array_noargs(PG_FUNCTION_ARGS); +extern Datum json_build_object(PG_FUNCTION_ARGS); +extern Datum json_build_object_noargs(PG_FUNCTION_ARGS); +extern Datum json_object(PG_FUNCTION_ARGS); +extern Datum json_object_two_arg(PG_FUNCTION_ARGS); +extern Datum json_to_record(PG_FUNCTION_ARGS); +extern Datum json_to_recordset(PG_FUNCTION_ARGS); +extern Datum jsonb_array_length(PG_FUNCTION_ARGS); +extern Datum jsonb_each(PG_FUNCTION_ARGS); +extern Datum jsonb_populate_record(PG_FUNCTION_ARGS); +extern Datum jsonb_typeof(PG_FUNCTION_ARGS); +extern Datum jsonb_object_field_text(PG_FUNCTION_ARGS); +extern Datum jsonb_array_element(PG_FUNCTION_ARGS); +extern Datum jsonb_array_element_text(PG_FUNCTION_ARGS); +extern Datum jsonb_extract_path(PG_FUNCTION_ARGS); +extern Datum width_bucket_array(PG_FUNCTION_ARGS); +extern Datum jsonb_array_elements(PG_FUNCTION_ARGS); +extern Datum pg_lsn_in(PG_FUNCTION_ARGS); +extern Datum pg_lsn_out(PG_FUNCTION_ARGS); +extern Datum pg_lsn_lt(PG_FUNCTION_ARGS); +extern Datum pg_lsn_le(PG_FUNCTION_ARGS); +extern Datum pg_lsn_eq(PG_FUNCTION_ARGS); +extern Datum pg_lsn_ge(PG_FUNCTION_ARGS); +extern Datum pg_lsn_gt(PG_FUNCTION_ARGS); +extern Datum pg_lsn_ne(PG_FUNCTION_ARGS); +extern Datum pg_lsn_mi(PG_FUNCTION_ARGS); +extern Datum pg_lsn_recv(PG_FUNCTION_ARGS); +extern Datum pg_lsn_send(PG_FUNCTION_ARGS); +extern Datum pg_lsn_cmp(PG_FUNCTION_ARGS); +extern Datum pg_lsn_hash(PG_FUNCTION_ARGS); +extern Datum bttextsortsupport(PG_FUNCTION_ARGS); +extern Datum generate_series_step_numeric(PG_FUNCTION_ARGS); +extern Datum generate_series_numeric(PG_FUNCTION_ARGS); +extern Datum json_strip_nulls(PG_FUNCTION_ARGS); +extern Datum jsonb_strip_nulls(PG_FUNCTION_ARGS); +extern Datum jsonb_object(PG_FUNCTION_ARGS); +extern Datum jsonb_object_two_arg(PG_FUNCTION_ARGS); +extern Datum jsonb_agg_transfn(PG_FUNCTION_ARGS); +extern Datum jsonb_agg_finalfn(PG_FUNCTION_ARGS); +extern Datum jsonb_object_agg_transfn(PG_FUNCTION_ARGS); +extern Datum jsonb_object_agg_finalfn(PG_FUNCTION_ARGS); +extern Datum jsonb_build_array(PG_FUNCTION_ARGS); +extern Datum jsonb_build_array_noargs(PG_FUNCTION_ARGS); +extern Datum jsonb_build_object(PG_FUNCTION_ARGS); +extern Datum jsonb_build_object_noargs(PG_FUNCTION_ARGS); +extern Datum dist_ppoly(PG_FUNCTION_ARGS); +extern Datum array_position(PG_FUNCTION_ARGS); +extern Datum array_position_start(PG_FUNCTION_ARGS); +extern Datum array_positions(PG_FUNCTION_ARGS); +extern Datum gist_circle_distance(PG_FUNCTION_ARGS); +extern Datum numeric_scale(PG_FUNCTION_ARGS); +extern Datum gist_point_fetch(PG_FUNCTION_ARGS); +extern Datum numeric_sortsupport(PG_FUNCTION_ARGS); +extern Datum gist_poly_distance(PG_FUNCTION_ARGS); +extern Datum dist_cpoint(PG_FUNCTION_ARGS); +extern Datum dist_polyp(PG_FUNCTION_ARGS); +extern Datum pg_read_file_off_len_missing(PG_FUNCTION_ARGS); +extern Datum show_config_by_name_missing_ok(PG_FUNCTION_ARGS); +extern Datum pg_read_binary_file_off_len_missing(PG_FUNCTION_ARGS); +extern Datum pg_notification_queue_usage(PG_FUNCTION_ARGS); +extern Datum pg_ls_dir(PG_FUNCTION_ARGS); +extern Datum row_security_active(PG_FUNCTION_ARGS); +extern Datum row_security_active_name(PG_FUNCTION_ARGS); +extern Datum uuid_sortsupport(PG_FUNCTION_ARGS); +extern Datum jsonb_concat(PG_FUNCTION_ARGS); +extern Datum jsonb_delete(PG_FUNCTION_ARGS); +extern Datum jsonb_delete_idx(PG_FUNCTION_ARGS); +extern Datum jsonb_delete_path(PG_FUNCTION_ARGS); +extern Datum jsonb_set(PG_FUNCTION_ARGS); +extern Datum jsonb_pretty(PG_FUNCTION_ARGS); +extern Datum pg_stat_file(PG_FUNCTION_ARGS); +extern Datum xidneq(PG_FUNCTION_ARGS); +extern Datum tsm_handler_in(PG_FUNCTION_ARGS); +extern Datum tsm_handler_out(PG_FUNCTION_ARGS); +extern Datum tsm_bernoulli_handler(PG_FUNCTION_ARGS); +extern Datum tsm_system_handler(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_wal_receiver(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_progress_info(PG_FUNCTION_ARGS); +extern Datum tsvector_filter(PG_FUNCTION_ARGS); +extern Datum tsvector_setweight_by_filter(PG_FUNCTION_ARGS); +extern Datum tsvector_delete_str(PG_FUNCTION_ARGS); +extern Datum tsvector_unnest(PG_FUNCTION_ARGS); +extern Datum tsvector_delete_arr(PG_FUNCTION_ARGS); +extern Datum int4_avg_combine(PG_FUNCTION_ARGS); +extern Datum interval_combine(PG_FUNCTION_ARGS); +extern Datum tsvector_to_array(PG_FUNCTION_ARGS); +extern Datum array_to_tsvector(PG_FUNCTION_ARGS); +extern Datum bpchar_sortsupport(PG_FUNCTION_ARGS); +extern Datum show_all_file_settings(PG_FUNCTION_ARGS); +extern Datum pg_current_wal_flush_lsn(PG_FUNCTION_ARGS); +extern Datum bytea_sortsupport(PG_FUNCTION_ARGS); +extern Datum bttext_pattern_sortsupport(PG_FUNCTION_ARGS); +extern Datum btbpchar_pattern_sortsupport(PG_FUNCTION_ARGS); +extern Datum pg_size_bytes(PG_FUNCTION_ARGS); +extern Datum numeric_serialize(PG_FUNCTION_ARGS); +extern Datum numeric_deserialize(PG_FUNCTION_ARGS); +extern Datum numeric_avg_combine(PG_FUNCTION_ARGS); +extern Datum numeric_poly_combine(PG_FUNCTION_ARGS); +extern Datum numeric_poly_serialize(PG_FUNCTION_ARGS); +extern Datum numeric_poly_deserialize(PG_FUNCTION_ARGS); +extern Datum numeric_combine(PG_FUNCTION_ARGS); +extern Datum float8_regr_combine(PG_FUNCTION_ARGS); +extern Datum jsonb_delete_array(PG_FUNCTION_ARGS); +extern Datum cash_mul_int8(PG_FUNCTION_ARGS); +extern Datum cash_div_int8(PG_FUNCTION_ARGS); +extern Datum pg_current_xact_id_if_assigned(PG_FUNCTION_ARGS); +extern Datum pg_get_partkeydef(PG_FUNCTION_ARGS); +extern Datum pg_ls_logdir(PG_FUNCTION_ARGS); +extern Datum pg_ls_waldir(PG_FUNCTION_ARGS); +extern Datum pg_ndistinct_in(PG_FUNCTION_ARGS); +extern Datum pg_ndistinct_out(PG_FUNCTION_ARGS); +extern Datum pg_ndistinct_recv(PG_FUNCTION_ARGS); +extern Datum pg_ndistinct_send(PG_FUNCTION_ARGS); +extern Datum macaddr_sortsupport(PG_FUNCTION_ARGS); +extern Datum pg_xact_status(PG_FUNCTION_ARGS); +extern Datum pg_safe_snapshot_blocking_pids(PG_FUNCTION_ARGS); +extern Datum pg_isolation_test_session_is_blocked(PG_FUNCTION_ARGS); +extern Datum pg_identify_object_as_address(PG_FUNCTION_ARGS); +extern Datum brin_minmax_opcinfo(PG_FUNCTION_ARGS); +extern Datum brin_minmax_add_value(PG_FUNCTION_ARGS); +extern Datum brin_minmax_consistent(PG_FUNCTION_ARGS); +extern Datum brin_minmax_union(PG_FUNCTION_ARGS); +extern Datum int8_avg_accum_inv(PG_FUNCTION_ARGS); +extern Datum numeric_poly_sum(PG_FUNCTION_ARGS); +extern Datum numeric_poly_avg(PG_FUNCTION_ARGS); +extern Datum numeric_poly_var_pop(PG_FUNCTION_ARGS); +extern Datum numeric_poly_var_samp(PG_FUNCTION_ARGS); +extern Datum numeric_poly_stddev_pop(PG_FUNCTION_ARGS); +extern Datum numeric_poly_stddev_samp(PG_FUNCTION_ARGS); +extern Datum regexp_match_no_flags(PG_FUNCTION_ARGS); +extern Datum regexp_match(PG_FUNCTION_ARGS); +extern Datum int8_mul_cash(PG_FUNCTION_ARGS); +extern Datum pg_config(PG_FUNCTION_ARGS); +extern Datum pg_hba_file_rules(PG_FUNCTION_ARGS); +extern Datum pg_statistics_obj_is_visible(PG_FUNCTION_ARGS); +extern Datum pg_dependencies_in(PG_FUNCTION_ARGS); +extern Datum pg_dependencies_out(PG_FUNCTION_ARGS); +extern Datum pg_dependencies_recv(PG_FUNCTION_ARGS); +extern Datum pg_dependencies_send(PG_FUNCTION_ARGS); +extern Datum pg_get_partition_constraintdef(PG_FUNCTION_ARGS); +extern Datum time_hash_extended(PG_FUNCTION_ARGS); +extern Datum timetz_hash_extended(PG_FUNCTION_ARGS); +extern Datum timestamp_hash_extended(PG_FUNCTION_ARGS); +extern Datum uuid_hash_extended(PG_FUNCTION_ARGS); +extern Datum pg_lsn_hash_extended(PG_FUNCTION_ARGS); +extern Datum hashenumextended(PG_FUNCTION_ARGS); +extern Datum pg_get_statisticsobjdef(PG_FUNCTION_ARGS); +extern Datum jsonb_hash_extended(PG_FUNCTION_ARGS); +extern Datum hash_range_extended(PG_FUNCTION_ARGS); +extern Datum interval_hash_extended(PG_FUNCTION_ARGS); +extern Datum sha224_bytea(PG_FUNCTION_ARGS); +extern Datum sha256_bytea(PG_FUNCTION_ARGS); +extern Datum sha384_bytea(PG_FUNCTION_ARGS); +extern Datum sha512_bytea(PG_FUNCTION_ARGS); +extern Datum pg_partition_tree(PG_FUNCTION_ARGS); +extern Datum pg_partition_root(PG_FUNCTION_ARGS); +extern Datum pg_partition_ancestors(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_db_checksum_failures(PG_FUNCTION_ARGS); +extern Datum pg_stats_ext_mcvlist_items(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_db_checksum_last_failure(PG_FUNCTION_ARGS); +extern Datum gen_random_uuid(PG_FUNCTION_ARGS); +extern Datum gtsvector_options(PG_FUNCTION_ARGS); +extern Datum gist_point_sortsupport(PG_FUNCTION_ARGS); +extern Datum pg_promote(PG_FUNCTION_ARGS); +extern Datum prefixsel(PG_FUNCTION_ARGS); +extern Datum prefixjoinsel(PG_FUNCTION_ARGS); +extern Datum pg_control_system(PG_FUNCTION_ARGS); +extern Datum pg_control_checkpoint(PG_FUNCTION_ARGS); +extern Datum pg_control_recovery(PG_FUNCTION_ARGS); +extern Datum pg_control_init(PG_FUNCTION_ARGS); +extern Datum pg_import_system_collations(PG_FUNCTION_ARGS); +extern Datum macaddr8_recv(PG_FUNCTION_ARGS); +extern Datum macaddr8_send(PG_FUNCTION_ARGS); +extern Datum pg_collation_actual_version(PG_FUNCTION_ARGS); +extern Datum jsonb_numeric(PG_FUNCTION_ARGS); +extern Datum jsonb_int2(PG_FUNCTION_ARGS); +extern Datum jsonb_int4(PG_FUNCTION_ARGS); +extern Datum jsonb_int8(PG_FUNCTION_ARGS); +extern Datum jsonb_float4(PG_FUNCTION_ARGS); +extern Datum pg_filenode_relation(PG_FUNCTION_ARGS); +extern Datum be_lo_from_bytea(PG_FUNCTION_ARGS); +extern Datum be_lo_get(PG_FUNCTION_ARGS); +extern Datum be_lo_get_fragment(PG_FUNCTION_ARGS); +extern Datum be_lo_put(PG_FUNCTION_ARGS); +extern Datum make_timestamp(PG_FUNCTION_ARGS); +extern Datum make_timestamptz(PG_FUNCTION_ARGS); +extern Datum make_timestamptz_at_timezone(PG_FUNCTION_ARGS); +extern Datum make_interval(PG_FUNCTION_ARGS); +extern Datum jsonb_array_elements_text(PG_FUNCTION_ARGS); +extern Datum spg_range_quad_config(PG_FUNCTION_ARGS); +extern Datum spg_range_quad_choose(PG_FUNCTION_ARGS); +extern Datum spg_range_quad_picksplit(PG_FUNCTION_ARGS); +extern Datum spg_range_quad_inner_consistent(PG_FUNCTION_ARGS); +extern Datum spg_range_quad_leaf_consistent(PG_FUNCTION_ARGS); +extern Datum jsonb_populate_recordset(PG_FUNCTION_ARGS); +extern Datum to_regoperator(PG_FUNCTION_ARGS); +extern Datum jsonb_object_field(PG_FUNCTION_ARGS); +extern Datum to_regprocedure(PG_FUNCTION_ARGS); +extern Datum gin_compare_jsonb(PG_FUNCTION_ARGS); +extern Datum gin_extract_jsonb(PG_FUNCTION_ARGS); +extern Datum gin_extract_jsonb_query(PG_FUNCTION_ARGS); +extern Datum gin_consistent_jsonb(PG_FUNCTION_ARGS); +extern Datum gin_extract_jsonb_path(PG_FUNCTION_ARGS); +extern Datum gin_extract_jsonb_query_path(PG_FUNCTION_ARGS); +extern Datum gin_consistent_jsonb_path(PG_FUNCTION_ARGS); +extern Datum gin_triconsistent_jsonb(PG_FUNCTION_ARGS); +extern Datum gin_triconsistent_jsonb_path(PG_FUNCTION_ARGS); +extern Datum jsonb_to_record(PG_FUNCTION_ARGS); +extern Datum jsonb_to_recordset(PG_FUNCTION_ARGS); +extern Datum to_regoper(PG_FUNCTION_ARGS); +extern Datum to_regtype(PG_FUNCTION_ARGS); +extern Datum to_regproc(PG_FUNCTION_ARGS); +extern Datum to_regclass(PG_FUNCTION_ARGS); +extern Datum bool_accum(PG_FUNCTION_ARGS); +extern Datum bool_accum_inv(PG_FUNCTION_ARGS); +extern Datum bool_alltrue(PG_FUNCTION_ARGS); +extern Datum bool_anytrue(PG_FUNCTION_ARGS); +extern Datum anyenum_in(PG_FUNCTION_ARGS); +extern Datum anyenum_out(PG_FUNCTION_ARGS); +extern Datum enum_in(PG_FUNCTION_ARGS); +extern Datum enum_out(PG_FUNCTION_ARGS); +extern Datum enum_eq(PG_FUNCTION_ARGS); +extern Datum enum_ne(PG_FUNCTION_ARGS); +extern Datum enum_lt(PG_FUNCTION_ARGS); +extern Datum enum_gt(PG_FUNCTION_ARGS); +extern Datum enum_le(PG_FUNCTION_ARGS); +extern Datum enum_ge(PG_FUNCTION_ARGS); +extern Datum enum_cmp(PG_FUNCTION_ARGS); +extern Datum hashenum(PG_FUNCTION_ARGS); +extern Datum enum_smaller(PG_FUNCTION_ARGS); +extern Datum enum_larger(PG_FUNCTION_ARGS); +extern Datum enum_first(PG_FUNCTION_ARGS); +extern Datum enum_last(PG_FUNCTION_ARGS); +extern Datum enum_range_bounds(PG_FUNCTION_ARGS); +extern Datum enum_range_all(PG_FUNCTION_ARGS); +extern Datum enum_recv(PG_FUNCTION_ARGS); +extern Datum enum_send(PG_FUNCTION_ARGS); +extern Datum string_agg_transfn(PG_FUNCTION_ARGS); +extern Datum string_agg_finalfn(PG_FUNCTION_ARGS); +extern Datum pg_describe_object(PG_FUNCTION_ARGS); +extern Datum text_format(PG_FUNCTION_ARGS); +extern Datum text_format_nv(PG_FUNCTION_ARGS); +extern Datum bytea_string_agg_transfn(PG_FUNCTION_ARGS); +extern Datum bytea_string_agg_finalfn(PG_FUNCTION_ARGS); +extern Datum int8dec(PG_FUNCTION_ARGS); +extern Datum int8dec_any(PG_FUNCTION_ARGS); +extern Datum numeric_accum_inv(PG_FUNCTION_ARGS); +extern Datum interval_accum_inv(PG_FUNCTION_ARGS); +extern Datum network_overlap(PG_FUNCTION_ARGS); +extern Datum inet_gist_consistent(PG_FUNCTION_ARGS); +extern Datum inet_gist_union(PG_FUNCTION_ARGS); +extern Datum inet_gist_compress(PG_FUNCTION_ARGS); +extern Datum jsonb_bool(PG_FUNCTION_ARGS); +extern Datum inet_gist_penalty(PG_FUNCTION_ARGS); +extern Datum inet_gist_picksplit(PG_FUNCTION_ARGS); +extern Datum inet_gist_same(PG_FUNCTION_ARGS); +extern Datum networksel(PG_FUNCTION_ARGS); +extern Datum networkjoinsel(PG_FUNCTION_ARGS); +extern Datum network_larger(PG_FUNCTION_ARGS); +extern Datum network_smaller(PG_FUNCTION_ARGS); +extern Datum pg_event_trigger_dropped_objects(PG_FUNCTION_ARGS); +extern Datum int2_accum_inv(PG_FUNCTION_ARGS); +extern Datum int4_accum_inv(PG_FUNCTION_ARGS); +extern Datum int8_accum_inv(PG_FUNCTION_ARGS); +extern Datum int2_avg_accum_inv(PG_FUNCTION_ARGS); +extern Datum int4_avg_accum_inv(PG_FUNCTION_ARGS); +extern Datum int2int4_sum(PG_FUNCTION_ARGS); +extern Datum inet_gist_fetch(PG_FUNCTION_ARGS); +extern Datum pg_logical_emit_message_text(PG_FUNCTION_ARGS); +extern Datum pg_logical_emit_message_bytea(PG_FUNCTION_ARGS); +extern Datum jsonb_insert(PG_FUNCTION_ARGS); +extern Datum pg_xact_commit_timestamp(PG_FUNCTION_ARGS); +extern Datum binary_upgrade_set_next_pg_type_oid(PG_FUNCTION_ARGS); +extern Datum pg_last_committed_xact(PG_FUNCTION_ARGS); +extern Datum binary_upgrade_set_next_array_pg_type_oid(PG_FUNCTION_ARGS); +extern Datum binary_upgrade_set_next_heap_pg_class_oid(PG_FUNCTION_ARGS); +extern Datum binary_upgrade_set_next_index_pg_class_oid(PG_FUNCTION_ARGS); +extern Datum binary_upgrade_set_next_toast_pg_class_oid(PG_FUNCTION_ARGS); +extern Datum binary_upgrade_set_next_pg_enum_oid(PG_FUNCTION_ARGS); +extern Datum binary_upgrade_set_next_pg_authid_oid(PG_FUNCTION_ARGS); +extern Datum binary_upgrade_create_empty_extension(PG_FUNCTION_ARGS); +extern Datum event_trigger_in(PG_FUNCTION_ARGS); +extern Datum event_trigger_out(PG_FUNCTION_ARGS); +extern Datum tsvectorin(PG_FUNCTION_ARGS); +extern Datum tsvectorout(PG_FUNCTION_ARGS); +extern Datum tsqueryin(PG_FUNCTION_ARGS); +extern Datum tsqueryout(PG_FUNCTION_ARGS); +extern Datum tsvector_lt(PG_FUNCTION_ARGS); +extern Datum tsvector_le(PG_FUNCTION_ARGS); +extern Datum tsvector_eq(PG_FUNCTION_ARGS); +extern Datum tsvector_ne(PG_FUNCTION_ARGS); +extern Datum tsvector_ge(PG_FUNCTION_ARGS); +extern Datum tsvector_gt(PG_FUNCTION_ARGS); +extern Datum tsvector_cmp(PG_FUNCTION_ARGS); +extern Datum tsvector_strip(PG_FUNCTION_ARGS); +extern Datum tsvector_setweight(PG_FUNCTION_ARGS); +extern Datum tsvector_concat(PG_FUNCTION_ARGS); +extern Datum ts_match_vq(PG_FUNCTION_ARGS); +extern Datum ts_match_qv(PG_FUNCTION_ARGS); +extern Datum tsvectorsend(PG_FUNCTION_ARGS); +extern Datum tsvectorrecv(PG_FUNCTION_ARGS); +extern Datum tsquerysend(PG_FUNCTION_ARGS); +extern Datum tsqueryrecv(PG_FUNCTION_ARGS); +extern Datum gtsvectorin(PG_FUNCTION_ARGS); +extern Datum gtsvectorout(PG_FUNCTION_ARGS); +extern Datum gtsvector_compress(PG_FUNCTION_ARGS); +extern Datum gtsvector_decompress(PG_FUNCTION_ARGS); +extern Datum gtsvector_picksplit(PG_FUNCTION_ARGS); +extern Datum gtsvector_union(PG_FUNCTION_ARGS); +extern Datum gtsvector_same(PG_FUNCTION_ARGS); +extern Datum gtsvector_penalty(PG_FUNCTION_ARGS); +extern Datum gtsvector_consistent(PG_FUNCTION_ARGS); +extern Datum gin_extract_tsvector(PG_FUNCTION_ARGS); +extern Datum gin_extract_tsquery(PG_FUNCTION_ARGS); +extern Datum gin_tsquery_consistent(PG_FUNCTION_ARGS); +extern Datum tsquery_lt(PG_FUNCTION_ARGS); +extern Datum tsquery_le(PG_FUNCTION_ARGS); +extern Datum tsquery_eq(PG_FUNCTION_ARGS); +extern Datum tsquery_ne(PG_FUNCTION_ARGS); +extern Datum tsquery_ge(PG_FUNCTION_ARGS); +extern Datum tsquery_gt(PG_FUNCTION_ARGS); +extern Datum tsquery_cmp(PG_FUNCTION_ARGS); +extern Datum tsquery_and(PG_FUNCTION_ARGS); +extern Datum tsquery_or(PG_FUNCTION_ARGS); +extern Datum tsquery_not(PG_FUNCTION_ARGS); +extern Datum tsquery_numnode(PG_FUNCTION_ARGS); +extern Datum tsquerytree(PG_FUNCTION_ARGS); +extern Datum tsquery_rewrite(PG_FUNCTION_ARGS); +extern Datum tsquery_rewrite_query(PG_FUNCTION_ARGS); +extern Datum tsmatchsel(PG_FUNCTION_ARGS); +extern Datum tsmatchjoinsel(PG_FUNCTION_ARGS); +extern Datum ts_typanalyze(PG_FUNCTION_ARGS); +extern Datum ts_stat1(PG_FUNCTION_ARGS); +extern Datum ts_stat2(PG_FUNCTION_ARGS); +extern Datum tsq_mcontains(PG_FUNCTION_ARGS); +extern Datum tsq_mcontained(PG_FUNCTION_ARGS); +extern Datum gtsquery_compress(PG_FUNCTION_ARGS); +extern Datum text_starts_with(PG_FUNCTION_ARGS); +extern Datum gtsquery_picksplit(PG_FUNCTION_ARGS); +extern Datum gtsquery_union(PG_FUNCTION_ARGS); +extern Datum gtsquery_same(PG_FUNCTION_ARGS); +extern Datum gtsquery_penalty(PG_FUNCTION_ARGS); +extern Datum gtsquery_consistent(PG_FUNCTION_ARGS); +extern Datum ts_rank_wttf(PG_FUNCTION_ARGS); +extern Datum ts_rank_wtt(PG_FUNCTION_ARGS); +extern Datum ts_rank_ttf(PG_FUNCTION_ARGS); +extern Datum ts_rank_tt(PG_FUNCTION_ARGS); +extern Datum ts_rankcd_wttf(PG_FUNCTION_ARGS); +extern Datum ts_rankcd_wtt(PG_FUNCTION_ARGS); +extern Datum ts_rankcd_ttf(PG_FUNCTION_ARGS); +extern Datum ts_rankcd_tt(PG_FUNCTION_ARGS); +extern Datum tsvector_length(PG_FUNCTION_ARGS); +extern Datum ts_token_type_byid(PG_FUNCTION_ARGS); +extern Datum ts_token_type_byname(PG_FUNCTION_ARGS); +extern Datum ts_parse_byid(PG_FUNCTION_ARGS); +extern Datum ts_parse_byname(PG_FUNCTION_ARGS); +extern Datum prsd_start(PG_FUNCTION_ARGS); +extern Datum prsd_nexttoken(PG_FUNCTION_ARGS); +extern Datum prsd_end(PG_FUNCTION_ARGS); +extern Datum prsd_headline(PG_FUNCTION_ARGS); +extern Datum prsd_lextype(PG_FUNCTION_ARGS); +extern Datum ts_lexize(PG_FUNCTION_ARGS); +extern Datum gin_cmp_tslexeme(PG_FUNCTION_ARGS); +extern Datum dsimple_init(PG_FUNCTION_ARGS); +extern Datum dsimple_lexize(PG_FUNCTION_ARGS); +extern Datum dsynonym_init(PG_FUNCTION_ARGS); +extern Datum dsynonym_lexize(PG_FUNCTION_ARGS); +extern Datum dispell_init(PG_FUNCTION_ARGS); +extern Datum dispell_lexize(PG_FUNCTION_ARGS); +extern Datum regconfigin(PG_FUNCTION_ARGS); +extern Datum regconfigout(PG_FUNCTION_ARGS); +extern Datum regconfigrecv(PG_FUNCTION_ARGS); +extern Datum regconfigsend(PG_FUNCTION_ARGS); +extern Datum thesaurus_init(PG_FUNCTION_ARGS); +extern Datum thesaurus_lexize(PG_FUNCTION_ARGS); +extern Datum ts_headline_byid_opt(PG_FUNCTION_ARGS); +extern Datum ts_headline_byid(PG_FUNCTION_ARGS); +extern Datum to_tsvector_byid(PG_FUNCTION_ARGS); +extern Datum to_tsquery_byid(PG_FUNCTION_ARGS); +extern Datum plainto_tsquery_byid(PG_FUNCTION_ARGS); +extern Datum to_tsvector(PG_FUNCTION_ARGS); +extern Datum to_tsquery(PG_FUNCTION_ARGS); +extern Datum plainto_tsquery(PG_FUNCTION_ARGS); +extern Datum tsvector_update_trigger_byid(PG_FUNCTION_ARGS); +extern Datum tsvector_update_trigger_bycolumn(PG_FUNCTION_ARGS); +extern Datum ts_headline_opt(PG_FUNCTION_ARGS); +extern Datum ts_headline(PG_FUNCTION_ARGS); +extern Datum pg_ts_parser_is_visible(PG_FUNCTION_ARGS); +extern Datum pg_ts_dict_is_visible(PG_FUNCTION_ARGS); +extern Datum pg_ts_config_is_visible(PG_FUNCTION_ARGS); +extern Datum get_current_ts_config(PG_FUNCTION_ARGS); +extern Datum ts_match_tt(PG_FUNCTION_ARGS); +extern Datum ts_match_tq(PG_FUNCTION_ARGS); +extern Datum pg_ts_template_is_visible(PG_FUNCTION_ARGS); +extern Datum regdictionaryin(PG_FUNCTION_ARGS); +extern Datum regdictionaryout(PG_FUNCTION_ARGS); +extern Datum regdictionaryrecv(PG_FUNCTION_ARGS); +extern Datum regdictionarysend(PG_FUNCTION_ARGS); +extern Datum pg_stat_reset_shared(PG_FUNCTION_ARGS); +extern Datum pg_stat_reset_single_table_counters(PG_FUNCTION_ARGS); +extern Datum pg_stat_reset_single_function_counters(PG_FUNCTION_ARGS); +extern Datum pg_tablespace_location(PG_FUNCTION_ARGS); +extern Datum pg_create_physical_replication_slot(PG_FUNCTION_ARGS); +extern Datum pg_drop_replication_slot(PG_FUNCTION_ARGS); +extern Datum pg_get_replication_slots(PG_FUNCTION_ARGS); +extern Datum pg_logical_slot_get_changes(PG_FUNCTION_ARGS); +extern Datum pg_logical_slot_get_binary_changes(PG_FUNCTION_ARGS); +extern Datum pg_logical_slot_peek_changes(PG_FUNCTION_ARGS); +extern Datum pg_logical_slot_peek_binary_changes(PG_FUNCTION_ARGS); +extern Datum pg_create_logical_replication_slot(PG_FUNCTION_ARGS); +extern Datum to_jsonb(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_snapshot_timestamp(PG_FUNCTION_ARGS); +extern Datum gin_clean_pending_list(PG_FUNCTION_ARGS); +extern Datum gtsvector_consistent_oldsig(PG_FUNCTION_ARGS); +extern Datum gin_extract_tsquery_oldsig(PG_FUNCTION_ARGS); +extern Datum gin_tsquery_consistent_oldsig(PG_FUNCTION_ARGS); +extern Datum gtsquery_consistent_oldsig(PG_FUNCTION_ARGS); +extern Datum inet_spg_config(PG_FUNCTION_ARGS); +extern Datum inet_spg_choose(PG_FUNCTION_ARGS); +extern Datum inet_spg_picksplit(PG_FUNCTION_ARGS); +extern Datum inet_spg_inner_consistent(PG_FUNCTION_ARGS); +extern Datum inet_spg_leaf_consistent(PG_FUNCTION_ARGS); +extern Datum pg_current_logfile(PG_FUNCTION_ARGS); +extern Datum pg_current_logfile_1arg(PG_FUNCTION_ARGS); +extern Datum jsonb_send(PG_FUNCTION_ARGS); +extern Datum jsonb_out(PG_FUNCTION_ARGS); +extern Datum jsonb_recv(PG_FUNCTION_ARGS); +extern Datum jsonb_in(PG_FUNCTION_ARGS); +extern Datum pg_get_function_arg_default(PG_FUNCTION_ARGS); +extern Datum pg_export_snapshot(PG_FUNCTION_ARGS); +extern Datum pg_is_in_recovery(PG_FUNCTION_ARGS); +extern Datum int4_cash(PG_FUNCTION_ARGS); +extern Datum int8_cash(PG_FUNCTION_ARGS); +extern Datum pg_collation_is_visible(PG_FUNCTION_ARGS); +extern Datum array_typanalyze(PG_FUNCTION_ARGS); +extern Datum arraycontsel(PG_FUNCTION_ARGS); +extern Datum arraycontjoinsel(PG_FUNCTION_ARGS); +extern Datum pg_get_multixact_members(PG_FUNCTION_ARGS); +extern Datum pg_last_wal_receive_lsn(PG_FUNCTION_ARGS); +extern Datum pg_last_wal_replay_lsn(PG_FUNCTION_ARGS); +extern Datum cash_div_cash(PG_FUNCTION_ARGS); +extern Datum cash_numeric(PG_FUNCTION_ARGS); +extern Datum numeric_cash(PG_FUNCTION_ARGS); +extern Datum pg_read_file_all(PG_FUNCTION_ARGS); +extern Datum pg_read_binary_file_off_len(PG_FUNCTION_ARGS); +extern Datum pg_read_binary_file_all(PG_FUNCTION_ARGS); +extern Datum pg_opfamily_is_visible(PG_FUNCTION_ARGS); +extern Datum pg_last_xact_replay_timestamp(PG_FUNCTION_ARGS); +extern Datum anyrange_in(PG_FUNCTION_ARGS); +extern Datum anyrange_out(PG_FUNCTION_ARGS); +extern Datum range_in(PG_FUNCTION_ARGS); +extern Datum range_out(PG_FUNCTION_ARGS); +extern Datum range_recv(PG_FUNCTION_ARGS); +extern Datum range_send(PG_FUNCTION_ARGS); +extern Datum pg_identify_object(PG_FUNCTION_ARGS); +extern Datum range_constructor2(PG_FUNCTION_ARGS); +extern Datum range_constructor3(PG_FUNCTION_ARGS); +extern Datum pg_relation_is_updatable(PG_FUNCTION_ARGS); +extern Datum pg_column_is_updatable(PG_FUNCTION_ARGS); +extern Datum make_date(PG_FUNCTION_ARGS); +extern Datum make_time(PG_FUNCTION_ARGS); +extern Datum range_lower(PG_FUNCTION_ARGS); +extern Datum range_upper(PG_FUNCTION_ARGS); +extern Datum range_empty(PG_FUNCTION_ARGS); +extern Datum range_lower_inc(PG_FUNCTION_ARGS); +extern Datum range_upper_inc(PG_FUNCTION_ARGS); +extern Datum range_lower_inf(PG_FUNCTION_ARGS); +extern Datum range_upper_inf(PG_FUNCTION_ARGS); +extern Datum range_eq(PG_FUNCTION_ARGS); +extern Datum range_ne(PG_FUNCTION_ARGS); +extern Datum range_overlaps(PG_FUNCTION_ARGS); +extern Datum range_contains_elem(PG_FUNCTION_ARGS); +extern Datum range_contains(PG_FUNCTION_ARGS); +extern Datum elem_contained_by_range(PG_FUNCTION_ARGS); +extern Datum range_contained_by(PG_FUNCTION_ARGS); +extern Datum range_adjacent(PG_FUNCTION_ARGS); +extern Datum range_before(PG_FUNCTION_ARGS); +extern Datum range_after(PG_FUNCTION_ARGS); +extern Datum range_overleft(PG_FUNCTION_ARGS); +extern Datum range_overright(PG_FUNCTION_ARGS); +extern Datum range_union(PG_FUNCTION_ARGS); +extern Datum range_intersect(PG_FUNCTION_ARGS); +extern Datum range_minus(PG_FUNCTION_ARGS); +extern Datum range_cmp(PG_FUNCTION_ARGS); +extern Datum range_lt(PG_FUNCTION_ARGS); +extern Datum range_le(PG_FUNCTION_ARGS); +extern Datum range_ge(PG_FUNCTION_ARGS); +extern Datum range_gt(PG_FUNCTION_ARGS); +extern Datum range_gist_consistent(PG_FUNCTION_ARGS); +extern Datum range_gist_union(PG_FUNCTION_ARGS); +extern Datum pg_replication_slot_advance(PG_FUNCTION_ARGS); +extern Datum range_gist_penalty(PG_FUNCTION_ARGS); +extern Datum range_gist_picksplit(PG_FUNCTION_ARGS); +extern Datum range_gist_same(PG_FUNCTION_ARGS); +extern Datum hash_range(PG_FUNCTION_ARGS); +extern Datum int4range_canonical(PG_FUNCTION_ARGS); +extern Datum daterange_canonical(PG_FUNCTION_ARGS); +extern Datum range_typanalyze(PG_FUNCTION_ARGS); +extern Datum timestamp_support(PG_FUNCTION_ARGS); +extern Datum interval_support(PG_FUNCTION_ARGS); +extern Datum ginarraytriconsistent(PG_FUNCTION_ARGS); +extern Datum gin_tsquery_triconsistent(PG_FUNCTION_ARGS); +extern Datum int4range_subdiff(PG_FUNCTION_ARGS); +extern Datum int8range_subdiff(PG_FUNCTION_ARGS); +extern Datum numrange_subdiff(PG_FUNCTION_ARGS); +extern Datum daterange_subdiff(PG_FUNCTION_ARGS); +extern Datum int8range_canonical(PG_FUNCTION_ARGS); +extern Datum tsrange_subdiff(PG_FUNCTION_ARGS); +extern Datum tstzrange_subdiff(PG_FUNCTION_ARGS); +extern Datum jsonb_object_keys(PG_FUNCTION_ARGS); +extern Datum jsonb_each_text(PG_FUNCTION_ARGS); +extern Datum mxid_age(PG_FUNCTION_ARGS); +extern Datum jsonb_extract_path_text(PG_FUNCTION_ARGS); +extern Datum acldefault_sql(PG_FUNCTION_ARGS); +extern Datum time_support(PG_FUNCTION_ARGS); +extern Datum json_object_field(PG_FUNCTION_ARGS); +extern Datum json_object_field_text(PG_FUNCTION_ARGS); +extern Datum json_array_element(PG_FUNCTION_ARGS); +extern Datum json_array_element_text(PG_FUNCTION_ARGS); +extern Datum json_extract_path(PG_FUNCTION_ARGS); +extern Datum brin_summarize_new_values(PG_FUNCTION_ARGS); +extern Datum json_extract_path_text(PG_FUNCTION_ARGS); +extern Datum pg_get_object_address(PG_FUNCTION_ARGS); +extern Datum json_array_elements(PG_FUNCTION_ARGS); +extern Datum json_array_length(PG_FUNCTION_ARGS); +extern Datum json_object_keys(PG_FUNCTION_ARGS); +extern Datum json_each(PG_FUNCTION_ARGS); +extern Datum json_each_text(PG_FUNCTION_ARGS); +extern Datum json_populate_record(PG_FUNCTION_ARGS); +extern Datum json_populate_recordset(PG_FUNCTION_ARGS); +extern Datum json_typeof(PG_FUNCTION_ARGS); +extern Datum json_array_elements_text(PG_FUNCTION_ARGS); +extern Datum ordered_set_transition(PG_FUNCTION_ARGS); +extern Datum ordered_set_transition_multi(PG_FUNCTION_ARGS); +extern Datum percentile_disc_final(PG_FUNCTION_ARGS); +extern Datum percentile_cont_float8_final(PG_FUNCTION_ARGS); +extern Datum percentile_cont_interval_final(PG_FUNCTION_ARGS); +extern Datum percentile_disc_multi_final(PG_FUNCTION_ARGS); +extern Datum percentile_cont_float8_multi_final(PG_FUNCTION_ARGS); +extern Datum percentile_cont_interval_multi_final(PG_FUNCTION_ARGS); +extern Datum mode_final(PG_FUNCTION_ARGS); +extern Datum hypothetical_rank_final(PG_FUNCTION_ARGS); +extern Datum hypothetical_percent_rank_final(PG_FUNCTION_ARGS); +extern Datum hypothetical_cume_dist_final(PG_FUNCTION_ARGS); +extern Datum hypothetical_dense_rank_final(PG_FUNCTION_ARGS); +extern Datum generate_series_int4_support(PG_FUNCTION_ARGS); +extern Datum generate_series_int8_support(PG_FUNCTION_ARGS); +extern Datum array_unnest_support(PG_FUNCTION_ARGS); +extern Datum gist_box_distance(PG_FUNCTION_ARGS); +extern Datum brin_summarize_range(PG_FUNCTION_ARGS); +extern Datum jsonpath_in(PG_FUNCTION_ARGS); +extern Datum jsonpath_recv(PG_FUNCTION_ARGS); +extern Datum jsonpath_out(PG_FUNCTION_ARGS); +extern Datum jsonpath_send(PG_FUNCTION_ARGS); +extern Datum jsonb_path_exists(PG_FUNCTION_ARGS); +extern Datum jsonb_path_query(PG_FUNCTION_ARGS); +extern Datum jsonb_path_query_array(PG_FUNCTION_ARGS); +extern Datum jsonb_path_query_first(PG_FUNCTION_ARGS); +extern Datum jsonb_path_match(PG_FUNCTION_ARGS); +extern Datum jsonb_path_exists_opr(PG_FUNCTION_ARGS); +extern Datum jsonb_path_match_opr(PG_FUNCTION_ARGS); +extern Datum brin_desummarize_range(PG_FUNCTION_ARGS); +extern Datum spg_quad_config(PG_FUNCTION_ARGS); +extern Datum spg_quad_choose(PG_FUNCTION_ARGS); +extern Datum spg_quad_picksplit(PG_FUNCTION_ARGS); +extern Datum spg_quad_inner_consistent(PG_FUNCTION_ARGS); +extern Datum spg_quad_leaf_consistent(PG_FUNCTION_ARGS); +extern Datum spg_kd_config(PG_FUNCTION_ARGS); +extern Datum spg_kd_choose(PG_FUNCTION_ARGS); +extern Datum spg_kd_picksplit(PG_FUNCTION_ARGS); +extern Datum spg_kd_inner_consistent(PG_FUNCTION_ARGS); +extern Datum spg_text_config(PG_FUNCTION_ARGS); +extern Datum spg_text_choose(PG_FUNCTION_ARGS); +extern Datum spg_text_picksplit(PG_FUNCTION_ARGS); +extern Datum spg_text_inner_consistent(PG_FUNCTION_ARGS); +extern Datum spg_text_leaf_consistent(PG_FUNCTION_ARGS); +extern Datum pg_sequence_last_value(PG_FUNCTION_ARGS); +extern Datum jsonb_ne(PG_FUNCTION_ARGS); +extern Datum jsonb_lt(PG_FUNCTION_ARGS); +extern Datum jsonb_gt(PG_FUNCTION_ARGS); +extern Datum jsonb_le(PG_FUNCTION_ARGS); +extern Datum jsonb_ge(PG_FUNCTION_ARGS); +extern Datum jsonb_eq(PG_FUNCTION_ARGS); +extern Datum jsonb_cmp(PG_FUNCTION_ARGS); +extern Datum jsonb_hash(PG_FUNCTION_ARGS); +extern Datum jsonb_contains(PG_FUNCTION_ARGS); +extern Datum jsonb_exists(PG_FUNCTION_ARGS); +extern Datum jsonb_exists_any(PG_FUNCTION_ARGS); +extern Datum jsonb_exists_all(PG_FUNCTION_ARGS); +extern Datum jsonb_contained(PG_FUNCTION_ARGS); +extern Datum array_agg_array_transfn(PG_FUNCTION_ARGS); +extern Datum array_agg_array_finalfn(PG_FUNCTION_ARGS); +extern Datum range_merge(PG_FUNCTION_ARGS); +extern Datum inet_merge(PG_FUNCTION_ARGS); +extern Datum boxes_bound_box(PG_FUNCTION_ARGS); +extern Datum inet_same_family(PG_FUNCTION_ARGS); +extern Datum binary_upgrade_set_record_init_privs(PG_FUNCTION_ARGS); +extern Datum regnamespacein(PG_FUNCTION_ARGS); +extern Datum regnamespaceout(PG_FUNCTION_ARGS); +extern Datum to_regnamespace(PG_FUNCTION_ARGS); +extern Datum regnamespacerecv(PG_FUNCTION_ARGS); +extern Datum regnamespacesend(PG_FUNCTION_ARGS); +extern Datum point_box(PG_FUNCTION_ARGS); +extern Datum regroleout(PG_FUNCTION_ARGS); +extern Datum to_regrole(PG_FUNCTION_ARGS); +extern Datum regrolerecv(PG_FUNCTION_ARGS); +extern Datum regrolesend(PG_FUNCTION_ARGS); +extern Datum regrolein(PG_FUNCTION_ARGS); +extern Datum pg_rotate_logfile(PG_FUNCTION_ARGS); +extern Datum pg_read_file(PG_FUNCTION_ARGS); +extern Datum binary_upgrade_set_missing_value(PG_FUNCTION_ARGS); +extern Datum brin_inclusion_opcinfo(PG_FUNCTION_ARGS); +extern Datum brin_inclusion_add_value(PG_FUNCTION_ARGS); +extern Datum brin_inclusion_consistent(PG_FUNCTION_ARGS); +extern Datum brin_inclusion_union(PG_FUNCTION_ARGS); +extern Datum macaddr8_in(PG_FUNCTION_ARGS); +extern Datum macaddr8_out(PG_FUNCTION_ARGS); +extern Datum macaddr8_trunc(PG_FUNCTION_ARGS); +extern Datum macaddr8_eq(PG_FUNCTION_ARGS); +extern Datum macaddr8_lt(PG_FUNCTION_ARGS); +extern Datum macaddr8_le(PG_FUNCTION_ARGS); +extern Datum macaddr8_gt(PG_FUNCTION_ARGS); +extern Datum macaddr8_ge(PG_FUNCTION_ARGS); +extern Datum macaddr8_ne(PG_FUNCTION_ARGS); +extern Datum macaddr8_cmp(PG_FUNCTION_ARGS); +extern Datum macaddr8_not(PG_FUNCTION_ARGS); +extern Datum macaddr8_and(PG_FUNCTION_ARGS); +extern Datum macaddr8_or(PG_FUNCTION_ARGS); +extern Datum macaddrtomacaddr8(PG_FUNCTION_ARGS); +extern Datum macaddr8tomacaddr(PG_FUNCTION_ARGS); +extern Datum macaddr8_set7bit(PG_FUNCTION_ARGS); +extern Datum in_range_int8_int8(PG_FUNCTION_ARGS); +extern Datum in_range_int4_int8(PG_FUNCTION_ARGS); +extern Datum in_range_int4_int4(PG_FUNCTION_ARGS); +extern Datum in_range_int4_int2(PG_FUNCTION_ARGS); +extern Datum in_range_int2_int8(PG_FUNCTION_ARGS); +extern Datum in_range_int2_int4(PG_FUNCTION_ARGS); +extern Datum in_range_int2_int2(PG_FUNCTION_ARGS); +extern Datum in_range_date_interval(PG_FUNCTION_ARGS); +extern Datum in_range_timestamp_interval(PG_FUNCTION_ARGS); +extern Datum in_range_timestamptz_interval(PG_FUNCTION_ARGS); +extern Datum in_range_interval_interval(PG_FUNCTION_ARGS); +extern Datum in_range_time_interval(PG_FUNCTION_ARGS); +extern Datum in_range_timetz_interval(PG_FUNCTION_ARGS); +extern Datum in_range_float8_float8(PG_FUNCTION_ARGS); +extern Datum in_range_float4_float8(PG_FUNCTION_ARGS); +extern Datum in_range_numeric_numeric(PG_FUNCTION_ARGS); +extern Datum pg_lsn_larger(PG_FUNCTION_ARGS); +extern Datum pg_lsn_smaller(PG_FUNCTION_ARGS); +extern Datum regcollationin(PG_FUNCTION_ARGS); +extern Datum regcollationout(PG_FUNCTION_ARGS); +extern Datum to_regcollation(PG_FUNCTION_ARGS); +extern Datum regcollationrecv(PG_FUNCTION_ARGS); +extern Datum regcollationsend(PG_FUNCTION_ARGS); +extern Datum ts_headline_jsonb_byid_opt(PG_FUNCTION_ARGS); +extern Datum ts_headline_jsonb_byid(PG_FUNCTION_ARGS); +extern Datum ts_headline_jsonb_opt(PG_FUNCTION_ARGS); +extern Datum ts_headline_jsonb(PG_FUNCTION_ARGS); +extern Datum ts_headline_json_byid_opt(PG_FUNCTION_ARGS); +extern Datum ts_headline_json_byid(PG_FUNCTION_ARGS); +extern Datum ts_headline_json_opt(PG_FUNCTION_ARGS); +extern Datum ts_headline_json(PG_FUNCTION_ARGS); +extern Datum jsonb_string_to_tsvector(PG_FUNCTION_ARGS); +extern Datum json_string_to_tsvector(PG_FUNCTION_ARGS); +extern Datum jsonb_string_to_tsvector_byid(PG_FUNCTION_ARGS); +extern Datum json_string_to_tsvector_byid(PG_FUNCTION_ARGS); +extern Datum jsonb_to_tsvector(PG_FUNCTION_ARGS); +extern Datum jsonb_to_tsvector_byid(PG_FUNCTION_ARGS); +extern Datum json_to_tsvector(PG_FUNCTION_ARGS); +extern Datum json_to_tsvector_byid(PG_FUNCTION_ARGS); +extern Datum pg_copy_physical_replication_slot_a(PG_FUNCTION_ARGS); +extern Datum pg_copy_physical_replication_slot_b(PG_FUNCTION_ARGS); +extern Datum pg_copy_logical_replication_slot_a(PG_FUNCTION_ARGS); +extern Datum pg_copy_logical_replication_slot_b(PG_FUNCTION_ARGS); +extern Datum pg_copy_logical_replication_slot_c(PG_FUNCTION_ARGS); +extern Datum anycompatiblemultirange_in(PG_FUNCTION_ARGS); +extern Datum anycompatiblemultirange_out(PG_FUNCTION_ARGS); +extern Datum range_merge_from_multirange(PG_FUNCTION_ARGS); +extern Datum anymultirange_in(PG_FUNCTION_ARGS); +extern Datum anymultirange_out(PG_FUNCTION_ARGS); +extern Datum multirange_in(PG_FUNCTION_ARGS); +extern Datum multirange_out(PG_FUNCTION_ARGS); +extern Datum multirange_recv(PG_FUNCTION_ARGS); +extern Datum multirange_send(PG_FUNCTION_ARGS); +extern Datum multirange_lower(PG_FUNCTION_ARGS); +extern Datum multirange_upper(PG_FUNCTION_ARGS); +extern Datum multirange_empty(PG_FUNCTION_ARGS); +extern Datum multirange_lower_inc(PG_FUNCTION_ARGS); +extern Datum multirange_upper_inc(PG_FUNCTION_ARGS); +extern Datum multirange_lower_inf(PG_FUNCTION_ARGS); +extern Datum multirange_upper_inf(PG_FUNCTION_ARGS); +extern Datum multirange_typanalyze(PG_FUNCTION_ARGS); +extern Datum multirangesel(PG_FUNCTION_ARGS); +extern Datum multirange_eq(PG_FUNCTION_ARGS); +extern Datum multirange_ne(PG_FUNCTION_ARGS); +extern Datum range_overlaps_multirange(PG_FUNCTION_ARGS); +extern Datum multirange_overlaps_range(PG_FUNCTION_ARGS); +extern Datum multirange_overlaps_multirange(PG_FUNCTION_ARGS); +extern Datum multirange_contains_elem(PG_FUNCTION_ARGS); +extern Datum multirange_contains_range(PG_FUNCTION_ARGS); +extern Datum multirange_contains_multirange(PG_FUNCTION_ARGS); +extern Datum elem_contained_by_multirange(PG_FUNCTION_ARGS); +extern Datum range_contained_by_multirange(PG_FUNCTION_ARGS); +extern Datum multirange_contained_by_multirange(PG_FUNCTION_ARGS); +extern Datum range_adjacent_multirange(PG_FUNCTION_ARGS); +extern Datum multirange_adjacent_multirange(PG_FUNCTION_ARGS); +extern Datum multirange_adjacent_range(PG_FUNCTION_ARGS); +extern Datum range_before_multirange(PG_FUNCTION_ARGS); +extern Datum multirange_before_range(PG_FUNCTION_ARGS); +extern Datum multirange_before_multirange(PG_FUNCTION_ARGS); +extern Datum range_after_multirange(PG_FUNCTION_ARGS); +extern Datum multirange_after_range(PG_FUNCTION_ARGS); +extern Datum multirange_after_multirange(PG_FUNCTION_ARGS); +extern Datum range_overleft_multirange(PG_FUNCTION_ARGS); +extern Datum multirange_overleft_range(PG_FUNCTION_ARGS); +extern Datum multirange_overleft_multirange(PG_FUNCTION_ARGS); +extern Datum range_overright_multirange(PG_FUNCTION_ARGS); +extern Datum multirange_overright_range(PG_FUNCTION_ARGS); +extern Datum multirange_overright_multirange(PG_FUNCTION_ARGS); +extern Datum multirange_union(PG_FUNCTION_ARGS); +extern Datum multirange_minus(PG_FUNCTION_ARGS); +extern Datum multirange_intersect(PG_FUNCTION_ARGS); +extern Datum multirange_cmp(PG_FUNCTION_ARGS); +extern Datum multirange_lt(PG_FUNCTION_ARGS); +extern Datum multirange_le(PG_FUNCTION_ARGS); +extern Datum multirange_ge(PG_FUNCTION_ARGS); +extern Datum multirange_gt(PG_FUNCTION_ARGS); +extern Datum hash_multirange(PG_FUNCTION_ARGS); +extern Datum hash_multirange_extended(PG_FUNCTION_ARGS); +extern Datum multirange_constructor0(PG_FUNCTION_ARGS); +extern Datum multirange_constructor1(PG_FUNCTION_ARGS); +extern Datum multirange_constructor2(PG_FUNCTION_ARGS); +extern Datum range_agg_transfn(PG_FUNCTION_ARGS); +extern Datum range_agg_finalfn(PG_FUNCTION_ARGS); +extern Datum unicode_normalize_func(PG_FUNCTION_ARGS); +extern Datum unicode_is_normalized(PG_FUNCTION_ARGS); +extern Datum multirange_intersect_agg_transfn(PG_FUNCTION_ARGS); +extern Datum binary_upgrade_set_next_multirange_pg_type_oid(PG_FUNCTION_ARGS); +extern Datum binary_upgrade_set_next_multirange_array_pg_type_oid(PG_FUNCTION_ARGS); +extern Datum range_intersect_agg_transfn(PG_FUNCTION_ARGS); +extern Datum range_contains_multirange(PG_FUNCTION_ARGS); +extern Datum multirange_contained_by_range(PG_FUNCTION_ARGS); +extern Datum pg_log_backend_memory_contexts(PG_FUNCTION_ARGS); +extern Datum binary_upgrade_set_next_heap_relfilenode(PG_FUNCTION_ARGS); +extern Datum binary_upgrade_set_next_index_relfilenode(PG_FUNCTION_ARGS); +extern Datum binary_upgrade_set_next_toast_relfilenode(PG_FUNCTION_ARGS); +extern Datum binary_upgrade_set_next_pg_tablespace_oid(PG_FUNCTION_ARGS); +extern Datum pg_event_trigger_table_rewrite_oid(PG_FUNCTION_ARGS); +extern Datum pg_event_trigger_table_rewrite_reason(PG_FUNCTION_ARGS); +extern Datum pg_event_trigger_ddl_commands(PG_FUNCTION_ARGS); +extern Datum brin_bloom_opcinfo(PG_FUNCTION_ARGS); +extern Datum brin_bloom_add_value(PG_FUNCTION_ARGS); +extern Datum brin_bloom_consistent(PG_FUNCTION_ARGS); +extern Datum brin_bloom_union(PG_FUNCTION_ARGS); +extern Datum brin_bloom_options(PG_FUNCTION_ARGS); +extern Datum brin_bloom_summary_in(PG_FUNCTION_ARGS); +extern Datum brin_bloom_summary_out(PG_FUNCTION_ARGS); +extern Datum brin_bloom_summary_recv(PG_FUNCTION_ARGS); +extern Datum brin_bloom_summary_send(PG_FUNCTION_ARGS); +extern Datum brin_minmax_multi_opcinfo(PG_FUNCTION_ARGS); +extern Datum brin_minmax_multi_add_value(PG_FUNCTION_ARGS); +extern Datum brin_minmax_multi_consistent(PG_FUNCTION_ARGS); +extern Datum brin_minmax_multi_union(PG_FUNCTION_ARGS); +extern Datum brin_minmax_multi_options(PG_FUNCTION_ARGS); +extern Datum brin_minmax_multi_distance_int2(PG_FUNCTION_ARGS); +extern Datum brin_minmax_multi_distance_int4(PG_FUNCTION_ARGS); +extern Datum brin_minmax_multi_distance_int8(PG_FUNCTION_ARGS); +extern Datum brin_minmax_multi_distance_float4(PG_FUNCTION_ARGS); +extern Datum brin_minmax_multi_distance_float8(PG_FUNCTION_ARGS); +extern Datum brin_minmax_multi_distance_numeric(PG_FUNCTION_ARGS); +extern Datum brin_minmax_multi_distance_tid(PG_FUNCTION_ARGS); +extern Datum brin_minmax_multi_distance_uuid(PG_FUNCTION_ARGS); +extern Datum brin_minmax_multi_distance_date(PG_FUNCTION_ARGS); +extern Datum brin_minmax_multi_distance_time(PG_FUNCTION_ARGS); +extern Datum brin_minmax_multi_distance_interval(PG_FUNCTION_ARGS); +extern Datum brin_minmax_multi_distance_timetz(PG_FUNCTION_ARGS); +extern Datum brin_minmax_multi_distance_pg_lsn(PG_FUNCTION_ARGS); +extern Datum brin_minmax_multi_distance_macaddr(PG_FUNCTION_ARGS); +extern Datum brin_minmax_multi_distance_macaddr8(PG_FUNCTION_ARGS); +extern Datum brin_minmax_multi_distance_inet(PG_FUNCTION_ARGS); +extern Datum brin_minmax_multi_distance_timestamp(PG_FUNCTION_ARGS); +extern Datum brin_minmax_multi_summary_in(PG_FUNCTION_ARGS); +extern Datum brin_minmax_multi_summary_out(PG_FUNCTION_ARGS); +extern Datum brin_minmax_multi_summary_recv(PG_FUNCTION_ARGS); +extern Datum brin_minmax_multi_summary_send(PG_FUNCTION_ARGS); +extern Datum phraseto_tsquery(PG_FUNCTION_ARGS); +extern Datum tsquery_phrase(PG_FUNCTION_ARGS); +extern Datum tsquery_phrase_distance(PG_FUNCTION_ARGS); +extern Datum phraseto_tsquery_byid(PG_FUNCTION_ARGS); +extern Datum websearch_to_tsquery_byid(PG_FUNCTION_ARGS); +extern Datum websearch_to_tsquery(PG_FUNCTION_ARGS); +extern Datum spg_bbox_quad_config(PG_FUNCTION_ARGS); +extern Datum spg_poly_quad_compress(PG_FUNCTION_ARGS); +extern Datum spg_box_quad_config(PG_FUNCTION_ARGS); +extern Datum spg_box_quad_choose(PG_FUNCTION_ARGS); +extern Datum spg_box_quad_picksplit(PG_FUNCTION_ARGS); +extern Datum spg_box_quad_inner_consistent(PG_FUNCTION_ARGS); +extern Datum spg_box_quad_leaf_consistent(PG_FUNCTION_ARGS); +extern Datum pg_mcv_list_in(PG_FUNCTION_ARGS); +extern Datum pg_mcv_list_out(PG_FUNCTION_ARGS); +extern Datum pg_mcv_list_recv(PG_FUNCTION_ARGS); +extern Datum pg_mcv_list_send(PG_FUNCTION_ARGS); +extern Datum pg_lsn_pli(PG_FUNCTION_ARGS); +extern Datum pg_lsn_mii(PG_FUNCTION_ARGS); +extern Datum satisfies_hash_partition(PG_FUNCTION_ARGS); +extern Datum pg_ls_tmpdir_noargs(PG_FUNCTION_ARGS); +extern Datum pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS); +extern Datum pg_ls_archive_statusdir(PG_FUNCTION_ARGS); +extern Datum network_sortsupport(PG_FUNCTION_ARGS); +extern Datum xid8lt(PG_FUNCTION_ARGS); +extern Datum xid8gt(PG_FUNCTION_ARGS); +extern Datum xid8le(PG_FUNCTION_ARGS); +extern Datum xid8ge(PG_FUNCTION_ARGS); +extern Datum matchingsel(PG_FUNCTION_ARGS); +extern Datum matchingjoinsel(PG_FUNCTION_ARGS); +extern Datum numeric_min_scale(PG_FUNCTION_ARGS); +extern Datum numeric_trim_scale(PG_FUNCTION_ARGS); +extern Datum int4gcd(PG_FUNCTION_ARGS); +extern Datum int8gcd(PG_FUNCTION_ARGS); +extern Datum int4lcm(PG_FUNCTION_ARGS); +extern Datum int8lcm(PG_FUNCTION_ARGS); +extern Datum numeric_gcd(PG_FUNCTION_ARGS); +extern Datum numeric_lcm(PG_FUNCTION_ARGS); +extern Datum btvarstrequalimage(PG_FUNCTION_ARGS); +extern Datum btequalimage(PG_FUNCTION_ARGS); +extern Datum pg_get_shmem_allocations(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_ins_since_vacuum(PG_FUNCTION_ARGS); +extern Datum jsonb_set_lax(PG_FUNCTION_ARGS); +extern Datum xid8in(PG_FUNCTION_ARGS); +extern Datum xid8toxid(PG_FUNCTION_ARGS); +extern Datum xid8out(PG_FUNCTION_ARGS); +extern Datum xid8recv(PG_FUNCTION_ARGS); +extern Datum xid8send(PG_FUNCTION_ARGS); +extern Datum xid8eq(PG_FUNCTION_ARGS); +extern Datum xid8ne(PG_FUNCTION_ARGS); +extern Datum anycompatible_in(PG_FUNCTION_ARGS); +extern Datum anycompatible_out(PG_FUNCTION_ARGS); +extern Datum anycompatiblearray_in(PG_FUNCTION_ARGS); +extern Datum anycompatiblearray_out(PG_FUNCTION_ARGS); +extern Datum anycompatiblearray_recv(PG_FUNCTION_ARGS); +extern Datum anycompatiblearray_send(PG_FUNCTION_ARGS); +extern Datum anycompatiblenonarray_in(PG_FUNCTION_ARGS); +extern Datum anycompatiblenonarray_out(PG_FUNCTION_ARGS); +extern Datum anycompatiblerange_in(PG_FUNCTION_ARGS); +extern Datum anycompatiblerange_out(PG_FUNCTION_ARGS); +extern Datum xid8cmp(PG_FUNCTION_ARGS); +extern Datum xid8_larger(PG_FUNCTION_ARGS); +extern Datum xid8_smaller(PG_FUNCTION_ARGS); +extern Datum pg_replication_origin_create(PG_FUNCTION_ARGS); +extern Datum pg_replication_origin_drop(PG_FUNCTION_ARGS); +extern Datum pg_replication_origin_oid(PG_FUNCTION_ARGS); +extern Datum pg_replication_origin_session_setup(PG_FUNCTION_ARGS); +extern Datum pg_replication_origin_session_reset(PG_FUNCTION_ARGS); +extern Datum pg_replication_origin_session_is_setup(PG_FUNCTION_ARGS); +extern Datum pg_replication_origin_session_progress(PG_FUNCTION_ARGS); +extern Datum pg_replication_origin_xact_setup(PG_FUNCTION_ARGS); +extern Datum pg_replication_origin_xact_reset(PG_FUNCTION_ARGS); +extern Datum pg_replication_origin_advance(PG_FUNCTION_ARGS); +extern Datum pg_replication_origin_progress(PG_FUNCTION_ARGS); +extern Datum pg_show_replication_origin_status(PG_FUNCTION_ARGS); +extern Datum jsonb_subscript_handler(PG_FUNCTION_ARGS); +extern Datum numeric_pg_lsn(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_backend_subxact(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_subscription(PG_FUNCTION_ARGS); +extern Datum pg_get_publication_tables(PG_FUNCTION_ARGS); +extern Datum pg_get_replica_identity_index(PG_FUNCTION_ARGS); +extern Datum pg_relation_is_publishable(PG_FUNCTION_ARGS); +extern Datum multirange_gist_consistent(PG_FUNCTION_ARGS); +extern Datum multirange_gist_compress(PG_FUNCTION_ARGS); +extern Datum pg_get_catalog_foreign_keys(PG_FUNCTION_ARGS); +extern Datum text_to_table(PG_FUNCTION_ARGS); +extern Datum text_to_table_null(PG_FUNCTION_ARGS); +extern Datum bit_bit_count(PG_FUNCTION_ARGS); +extern Datum bytea_bit_count(PG_FUNCTION_ARGS); +extern Datum pg_xact_commit_timestamp_origin(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_replication_slot(PG_FUNCTION_ARGS); +extern Datum pg_stat_reset_replication_slot(PG_FUNCTION_ARGS); +extern Datum trim_array(PG_FUNCTION_ARGS); +extern Datum pg_get_statisticsobjdef_expressions(PG_FUNCTION_ARGS); +extern Datum pg_get_statisticsobjdef_columns(PG_FUNCTION_ARGS); +extern Datum timestamp_bin(PG_FUNCTION_ARGS); +extern Datum timestamptz_bin(PG_FUNCTION_ARGS); +extern Datum array_subscript_handler(PG_FUNCTION_ARGS); +extern Datum raw_array_subscript_handler(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_db_session_time(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_db_active_time(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_db_idle_in_transaction_time(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_db_sessions(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_db_sessions_abandoned(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_db_sessions_fatal(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_db_sessions_killed(PG_FUNCTION_ARGS); +extern Datum hash_record(PG_FUNCTION_ARGS); +extern Datum hash_record_extended(PG_FUNCTION_ARGS); +extern Datum bytealtrim(PG_FUNCTION_ARGS); +extern Datum byteartrim(PG_FUNCTION_ARGS); +extern Datum pg_get_function_sqlbody(PG_FUNCTION_ARGS); +extern Datum unistr(PG_FUNCTION_ARGS); +extern Datum extract_date(PG_FUNCTION_ARGS); +extern Datum extract_time(PG_FUNCTION_ARGS); +extern Datum extract_timetz(PG_FUNCTION_ARGS); +extern Datum extract_timestamp(PG_FUNCTION_ARGS); +extern Datum extract_timestamptz(PG_FUNCTION_ARGS); +extern Datum extract_interval(PG_FUNCTION_ARGS); +extern Datum has_parameter_privilege_name_name(PG_FUNCTION_ARGS); +extern Datum has_parameter_privilege_id_name(PG_FUNCTION_ARGS); +extern Datum has_parameter_privilege_name(PG_FUNCTION_ARGS); +extern Datum pg_read_file_all_missing(PG_FUNCTION_ARGS); +extern Datum pg_read_binary_file_all_missing(PG_FUNCTION_ARGS); +extern Datum pg_input_is_valid(PG_FUNCTION_ARGS); +extern Datum pg_input_error_info(PG_FUNCTION_ARGS); +extern Datum drandom_normal(PG_FUNCTION_ARGS); +extern Datum pg_split_walfile_name(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_io(PG_FUNCTION_ARGS); +extern Datum array_shuffle(PG_FUNCTION_ARGS); +extern Datum array_sample(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_tuples_newpage_updated(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_xact_tuples_newpage_updated(PG_FUNCTION_ARGS); +extern Datum derf(PG_FUNCTION_ARGS); +extern Datum derfc(PG_FUNCTION_ARGS); +extern Datum timestamptz_pl_interval_at_zone(PG_FUNCTION_ARGS); +extern Datum pg_get_wal_resource_managers(PG_FUNCTION_ARGS); +extern Datum multirange_agg_transfn(PG_FUNCTION_ARGS); +extern Datum pg_stat_have_stats(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_subscription_stats(PG_FUNCTION_ARGS); +extern Datum pg_stat_reset_subscription_stats(PG_FUNCTION_ARGS); +extern Datum window_row_number_support(PG_FUNCTION_ARGS); +extern Datum window_rank_support(PG_FUNCTION_ARGS); +extern Datum window_dense_rank_support(PG_FUNCTION_ARGS); +extern Datum int8inc_support(PG_FUNCTION_ARGS); +extern Datum pg_settings_get_flags(PG_FUNCTION_ARGS); +extern Datum pg_stop_making_pinned_objects(PG_FUNCTION_ARGS); +extern Datum text_starts_with_support(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_recovery_prefetch(PG_FUNCTION_ARGS); +extern Datum pg_database_collation_actual_version(PG_FUNCTION_ARGS); +extern Datum pg_ident_file_mappings(PG_FUNCTION_ARGS); +extern Datum textregexreplace_extended(PG_FUNCTION_ARGS); +extern Datum textregexreplace_extended_no_flags(PG_FUNCTION_ARGS); +extern Datum textregexreplace_extended_no_n(PG_FUNCTION_ARGS); +extern Datum regexp_count_no_start(PG_FUNCTION_ARGS); +extern Datum regexp_count_no_flags(PG_FUNCTION_ARGS); +extern Datum regexp_count(PG_FUNCTION_ARGS); +extern Datum regexp_instr_no_start(PG_FUNCTION_ARGS); +extern Datum regexp_instr_no_n(PG_FUNCTION_ARGS); +extern Datum regexp_instr_no_endoption(PG_FUNCTION_ARGS); +extern Datum regexp_instr_no_flags(PG_FUNCTION_ARGS); +extern Datum regexp_instr_no_subexpr(PG_FUNCTION_ARGS); +extern Datum regexp_instr(PG_FUNCTION_ARGS); +extern Datum regexp_like_no_flags(PG_FUNCTION_ARGS); +extern Datum regexp_like(PG_FUNCTION_ARGS); +extern Datum regexp_substr_no_start(PG_FUNCTION_ARGS); +extern Datum regexp_substr_no_n(PG_FUNCTION_ARGS); +extern Datum regexp_substr_no_flags(PG_FUNCTION_ARGS); +extern Datum regexp_substr_no_subexpr(PG_FUNCTION_ARGS); +extern Datum regexp_substr(PG_FUNCTION_ARGS); +extern Datum pg_ls_logicalsnapdir(PG_FUNCTION_ARGS); +extern Datum pg_ls_logicalmapdir(PG_FUNCTION_ARGS); +extern Datum pg_ls_replslotdir(PG_FUNCTION_ARGS); +extern Datum timestamptz_mi_interval_at_zone(PG_FUNCTION_ARGS); +extern Datum generate_series_timestamptz_at_zone(PG_FUNCTION_ARGS); +extern Datum json_agg_strict_transfn(PG_FUNCTION_ARGS); +extern Datum json_object_agg_strict_transfn(PG_FUNCTION_ARGS); +extern Datum json_object_agg_unique_transfn(PG_FUNCTION_ARGS); +extern Datum json_object_agg_unique_strict_transfn(PG_FUNCTION_ARGS); +extern Datum jsonb_agg_strict_transfn(PG_FUNCTION_ARGS); +extern Datum jsonb_object_agg_strict_transfn(PG_FUNCTION_ARGS); +extern Datum jsonb_object_agg_unique_transfn(PG_FUNCTION_ARGS); +extern Datum jsonb_object_agg_unique_strict_transfn(PG_FUNCTION_ARGS); +extern Datum any_value_transfn(PG_FUNCTION_ARGS); +extern Datum array_agg_combine(PG_FUNCTION_ARGS); +extern Datum array_agg_serialize(PG_FUNCTION_ARGS); +extern Datum array_agg_deserialize(PG_FUNCTION_ARGS); +extern Datum array_agg_array_combine(PG_FUNCTION_ARGS); +extern Datum array_agg_array_serialize(PG_FUNCTION_ARGS); +extern Datum array_agg_array_deserialize(PG_FUNCTION_ARGS); +extern Datum string_agg_combine(PG_FUNCTION_ARGS); +extern Datum string_agg_serialize(PG_FUNCTION_ARGS); +extern Datum string_agg_deserialize(PG_FUNCTION_ARGS); +extern Datum pg_log_standby_snapshot(PG_FUNCTION_ARGS); +extern Datum window_percent_rank_support(PG_FUNCTION_ARGS); +extern Datum window_cume_dist_support(PG_FUNCTION_ARGS); +extern Datum window_ntile_support(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_db_conflict_logicalslot(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_lastscan(PG_FUNCTION_ARGS); +extern Datum system_user(PG_FUNCTION_ARGS); + +#endif /* FMGRPROTOS_H */ diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/fmgrtab.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/fmgrtab.c new file mode 100644 index 00000000000..559a095a425 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/fmgrtab.c @@ -0,0 +1,9337 @@ +/*------------------------------------------------------------------------- + * + * fmgrtab.c + * The function manager's table of internal functions. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * NOTES + * + * ****************************** + * *** DO NOT EDIT THIS FILE! *** + * ****************************** + * + * It has been GENERATED by src/backend/utils/Gen_fmgrtab.pl + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/transam.h" +#include "utils/fmgrtab.h" +#include "utils/fmgrprotos.h" + + +const FmgrBuiltin fmgr_builtins[] = { + { 3, 1, true, false, "heap_tableam_handler", heap_tableam_handler }, + { 31, 1, true, false, "byteaout", byteaout }, + { 33, 1, true, false, "charout", charout }, + { 34, 1, true, false, "namein", namein }, + { 35, 1, true, false, "nameout", nameout }, + { 38, 1, true, false, "int2in", int2in }, + { 39, 1, true, false, "int2out", int2out }, + { 40, 1, true, false, "int2vectorin", int2vectorin }, + { 41, 1, true, false, "int2vectorout", int2vectorout }, + { 42, 1, true, false, "int4in", int4in }, + { 43, 1, true, false, "int4out", int4out }, + { 44, 1, true, false, "regprocin", regprocin }, + { 45, 1, true, false, "regprocout", regprocout }, + { 46, 1, true, false, "textin", textin }, + { 47, 1, true, false, "textout", textout }, + { 48, 1, true, false, "tidin", tidin }, + { 49, 1, true, false, "tidout", tidout }, + { 50, 1, true, false, "xidin", xidin }, + { 51, 1, true, false, "xidout", xidout }, + { 52, 1, true, false, "cidin", cidin }, + { 53, 1, true, false, "cidout", cidout }, + { 54, 1, true, false, "oidvectorin", oidvectorin }, + { 55, 1, true, false, "oidvectorout", oidvectorout }, + { 56, 2, true, false, "boollt", boollt }, + { 57, 2, true, false, "boolgt", boolgt }, + { 60, 2, true, false, "booleq", booleq }, + { 61, 2, true, false, "chareq", chareq }, + { 62, 2, true, false, "nameeq", nameeq }, + { 63, 2, true, false, "int2eq", int2eq }, + { 64, 2, true, false, "int2lt", int2lt }, + { 65, 2, true, false, "int4eq", int4eq }, + { 66, 2, true, false, "int4lt", int4lt }, + { 67, 2, true, false, "texteq", texteq }, + { 68, 2, true, false, "xideq", xideq }, + { 69, 2, true, false, "cideq", cideq }, + { 70, 2, true, false, "charne", charne }, + { 72, 2, true, false, "charle", charle }, + { 73, 2, true, false, "chargt", chargt }, + { 74, 2, true, false, "charge", charge }, + { 77, 1, true, false, "chartoi4", chartoi4 }, + { 78, 1, true, false, "i4tochar", i4tochar }, + { 79, 2, true, false, "nameregexeq", nameregexeq }, + { 84, 2, true, false, "boolne", boolne }, + { 86, 1, true, false, "pg_ddl_command_in", pg_ddl_command_in }, + { 87, 1, true, false, "pg_ddl_command_out", pg_ddl_command_out }, + { 88, 1, true, false, "pg_ddl_command_recv", pg_ddl_command_recv }, + { 89, 0, true, false, "pgsql_version", pgsql_version }, + { 90, 1, true, false, "pg_ddl_command_send", pg_ddl_command_send }, + { 101, 4, true, false, "eqsel", eqsel }, + { 102, 4, true, false, "neqsel", neqsel }, + { 103, 4, true, false, "scalarltsel", scalarltsel }, + { 104, 4, true, false, "scalargtsel", scalargtsel }, + { 105, 5, true, false, "eqjoinsel", eqjoinsel }, + { 106, 5, true, false, "neqjoinsel", neqjoinsel }, + { 107, 5, true, false, "scalarltjoinsel", scalarltjoinsel }, + { 108, 5, true, false, "scalargtjoinsel", scalargtjoinsel }, + { 109, 1, true, false, "unknownin", unknownin }, + { 110, 1, true, false, "unknownout", unknownout }, + { 115, 2, true, false, "box_above_eq", box_above_eq }, + { 116, 2, true, false, "box_below_eq", box_below_eq }, + { 117, 1, true, false, "point_in", point_in }, + { 118, 1, true, false, "point_out", point_out }, + { 119, 1, true, false, "lseg_in", lseg_in }, + { 120, 1, true, false, "lseg_out", lseg_out }, + { 121, 1, true, false, "path_in", path_in }, + { 122, 1, true, false, "path_out", path_out }, + { 123, 1, true, false, "box_in", box_in }, + { 124, 1, true, false, "box_out", box_out }, + { 125, 2, true, false, "box_overlap", box_overlap }, + { 126, 2, true, false, "box_ge", box_ge }, + { 127, 2, true, false, "box_gt", box_gt }, + { 128, 2, true, false, "box_eq", box_eq }, + { 129, 2, true, false, "box_lt", box_lt }, + { 130, 2, true, false, "box_le", box_le }, + { 131, 2, true, false, "point_above", point_above }, + { 132, 2, true, false, "point_left", point_left }, + { 133, 2, true, false, "point_right", point_right }, + { 134, 2, true, false, "point_below", point_below }, + { 135, 2, true, false, "point_eq", point_eq }, + { 136, 2, true, false, "on_pb", on_pb }, + { 137, 2, true, false, "on_ppath", on_ppath }, + { 138, 1, true, false, "box_center", box_center }, + { 139, 4, true, false, "areasel", areasel }, + { 140, 5, true, false, "areajoinsel", areajoinsel }, + { 141, 2, true, false, "int4mul", int4mul }, + { 144, 2, true, false, "int4ne", int4ne }, + { 145, 2, true, false, "int2ne", int2ne }, + { 146, 2, true, false, "int2gt", int2gt }, + { 147, 2, true, false, "int4gt", int4gt }, + { 148, 2, true, false, "int2le", int2le }, + { 149, 2, true, false, "int4le", int4le }, + { 150, 2, true, false, "int4ge", int4ge }, + { 151, 2, true, false, "int2ge", int2ge }, + { 152, 2, true, false, "int2mul", int2mul }, + { 153, 2, true, false, "int2div", int2div }, + { 154, 2, true, false, "int4div", int4div }, + { 155, 2, true, false, "int2mod", int2mod }, + { 156, 2, true, false, "int4mod", int4mod }, + { 157, 2, true, false, "textne", textne }, + { 158, 2, true, false, "int24eq", int24eq }, + { 159, 2, true, false, "int42eq", int42eq }, + { 160, 2, true, false, "int24lt", int24lt }, + { 161, 2, true, false, "int42lt", int42lt }, + { 162, 2, true, false, "int24gt", int24gt }, + { 163, 2, true, false, "int42gt", int42gt }, + { 164, 2, true, false, "int24ne", int24ne }, + { 165, 2, true, false, "int42ne", int42ne }, + { 166, 2, true, false, "int24le", int24le }, + { 167, 2, true, false, "int42le", int42le }, + { 168, 2, true, false, "int24ge", int24ge }, + { 169, 2, true, false, "int42ge", int42ge }, + { 170, 2, true, false, "int24mul", int24mul }, + { 171, 2, true, false, "int42mul", int42mul }, + { 172, 2, true, false, "int24div", int24div }, + { 173, 2, true, false, "int42div", int42div }, + { 176, 2, true, false, "int2pl", int2pl }, + { 177, 2, true, false, "int4pl", int4pl }, + { 178, 2, true, false, "int24pl", int24pl }, + { 179, 2, true, false, "int42pl", int42pl }, + { 180, 2, true, false, "int2mi", int2mi }, + { 181, 2, true, false, "int4mi", int4mi }, + { 182, 2, true, false, "int24mi", int24mi }, + { 183, 2, true, false, "int42mi", int42mi }, + { 184, 2, true, false, "oideq", oideq }, + { 185, 2, true, false, "oidne", oidne }, + { 186, 2, true, false, "box_same", box_same }, + { 187, 2, true, false, "box_contain", box_contain }, + { 188, 2, true, false, "box_left", box_left }, + { 189, 2, true, false, "box_overleft", box_overleft }, + { 190, 2, true, false, "box_overright", box_overright }, + { 191, 2, true, false, "box_right", box_right }, + { 192, 2, true, false, "box_contained", box_contained }, + { 193, 2, true, false, "box_contain_pt", box_contain_pt }, + { 195, 1, true, false, "pg_node_tree_in", pg_node_tree_in }, + { 196, 1, true, false, "pg_node_tree_out", pg_node_tree_out }, + { 197, 1, true, false, "pg_node_tree_recv", pg_node_tree_recv }, + { 198, 1, true, false, "pg_node_tree_send", pg_node_tree_send }, + { 200, 1, true, false, "float4in", float4in }, + { 201, 1, true, false, "float4out", float4out }, + { 202, 2, true, false, "float4mul", float4mul }, + { 203, 2, true, false, "float4div", float4div }, + { 204, 2, true, false, "float4pl", float4pl }, + { 205, 2, true, false, "float4mi", float4mi }, + { 206, 1, true, false, "float4um", float4um }, + { 207, 1, true, false, "float4abs", float4abs }, + { 208, 2, true, false, "float4_accum", float4_accum }, + { 209, 2, true, false, "float4larger", float4larger }, + { 211, 2, true, false, "float4smaller", float4smaller }, + { 212, 1, true, false, "int4um", int4um }, + { 213, 1, true, false, "int2um", int2um }, + { 214, 1, true, false, "float8in", float8in }, + { 215, 1, true, false, "float8out", float8out }, + { 216, 2, true, false, "float8mul", float8mul }, + { 217, 2, true, false, "float8div", float8div }, + { 218, 2, true, false, "float8pl", float8pl }, + { 219, 2, true, false, "float8mi", float8mi }, + { 220, 1, true, false, "float8um", float8um }, + { 221, 1, true, false, "float8abs", float8abs }, + { 222, 2, true, false, "float8_accum", float8_accum }, + { 223, 2, true, false, "float8larger", float8larger }, + { 224, 2, true, false, "float8smaller", float8smaller }, + { 225, 1, true, false, "lseg_center", lseg_center }, + { 227, 1, true, false, "poly_center", poly_center }, + { 228, 1, true, false, "dround", dround }, + { 229, 1, true, false, "dtrunc", dtrunc }, + { 230, 1, true, false, "dsqrt", dsqrt }, + { 231, 1, true, false, "dcbrt", dcbrt }, + { 232, 2, true, false, "dpow", dpow }, + { 233, 1, true, false, "dexp", dexp }, + { 234, 1, true, false, "dlog1", dlog1 }, + { 235, 1, true, false, "i2tod", i2tod }, + { 236, 1, true, false, "i2tof", i2tof }, + { 237, 1, true, false, "dtoi2", dtoi2 }, + { 238, 1, true, false, "ftoi2", ftoi2 }, + { 239, 2, true, false, "line_distance", line_distance }, + { 240, 2, true, false, "nameeqtext", nameeqtext }, + { 241, 2, true, false, "namelttext", namelttext }, + { 242, 2, true, false, "nameletext", nameletext }, + { 243, 2, true, false, "namegetext", namegetext }, + { 244, 2, true, false, "namegttext", namegttext }, + { 245, 2, true, false, "namenetext", namenetext }, + { 246, 2, true, false, "btnametextcmp", btnametextcmp }, + { 247, 2, true, false, "texteqname", texteqname }, + { 248, 2, true, false, "textltname", textltname }, + { 249, 2, true, false, "textlename", textlename }, + { 250, 2, true, false, "textgename", textgename }, + { 251, 2, true, false, "textgtname", textgtname }, + { 252, 2, true, false, "textnename", textnename }, + { 253, 2, true, false, "bttextnamecmp", bttextnamecmp }, + { 266, 2, true, false, "nameconcatoid", nameconcatoid }, + { 267, 1, false, false, "table_am_handler_in", table_am_handler_in }, + { 268, 1, true, false, "table_am_handler_out", table_am_handler_out }, + { 274, 0, true, false, "timeofday", timeofday }, + { 275, 3, true, false, "pg_nextoid", pg_nextoid }, + { 276, 2, true, false, "float8_combine", float8_combine }, + { 277, 2, true, false, "inter_sl", inter_sl }, + { 278, 2, true, false, "inter_lb", inter_lb }, + { 279, 2, true, false, "float48mul", float48mul }, + { 280, 2, true, false, "float48div", float48div }, + { 281, 2, true, false, "float48pl", float48pl }, + { 282, 2, true, false, "float48mi", float48mi }, + { 283, 2, true, false, "float84mul", float84mul }, + { 284, 2, true, false, "float84div", float84div }, + { 285, 2, true, false, "float84pl", float84pl }, + { 286, 2, true, false, "float84mi", float84mi }, + { 287, 2, true, false, "float4eq", float4eq }, + { 288, 2, true, false, "float4ne", float4ne }, + { 289, 2, true, false, "float4lt", float4lt }, + { 290, 2, true, false, "float4le", float4le }, + { 291, 2, true, false, "float4gt", float4gt }, + { 292, 2, true, false, "float4ge", float4ge }, + { 293, 2, true, false, "float8eq", float8eq }, + { 294, 2, true, false, "float8ne", float8ne }, + { 295, 2, true, false, "float8lt", float8lt }, + { 296, 2, true, false, "float8le", float8le }, + { 297, 2, true, false, "float8gt", float8gt }, + { 298, 2, true, false, "float8ge", float8ge }, + { 299, 2, true, false, "float48eq", float48eq }, + { 300, 2, true, false, "float48ne", float48ne }, + { 301, 2, true, false, "float48lt", float48lt }, + { 302, 2, true, false, "float48le", float48le }, + { 303, 2, true, false, "float48gt", float48gt }, + { 304, 2, true, false, "float48ge", float48ge }, + { 305, 2, true, false, "float84eq", float84eq }, + { 306, 2, true, false, "float84ne", float84ne }, + { 307, 2, true, false, "float84lt", float84lt }, + { 308, 2, true, false, "float84le", float84le }, + { 309, 2, true, false, "float84gt", float84gt }, + { 310, 2, true, false, "float84ge", float84ge }, + { 311, 1, true, false, "ftod", ftod }, + { 312, 1, true, false, "dtof", dtof }, + { 313, 1, true, false, "i2toi4", i2toi4 }, + { 314, 1, true, false, "i4toi2", i4toi2 }, + { 315, 0, true, false, "pg_jit_available", pg_jit_available }, + { 316, 1, true, false, "i4tod", i4tod }, + { 317, 1, true, false, "dtoi4", dtoi4 }, + { 318, 1, true, false, "i4tof", i4tof }, + { 319, 1, true, false, "ftoi4", ftoi4 }, + { 320, 4, true, false, "width_bucket_float8", width_bucket_float8 }, + { 321, 1, true, false, "json_in", json_in }, + { 322, 1, true, false, "json_out", json_out }, + { 323, 1, true, false, "json_recv", json_recv }, + { 324, 1, true, false, "json_send", json_send }, + { 326, 1, false, false, "index_am_handler_in", index_am_handler_in }, + { 327, 1, true, false, "index_am_handler_out", index_am_handler_out }, + { 328, 1, true, false, "hashmacaddr8", hashmacaddr8 }, + { 329, 1, true, false, "hash_aclitem", hash_aclitem }, + { 330, 1, true, false, "bthandler", bthandler }, + { 331, 1, true, false, "hashhandler", hashhandler }, + { 332, 1, true, false, "gisthandler", gisthandler }, + { 333, 1, true, false, "ginhandler", ginhandler }, + { 334, 1, true, false, "spghandler", spghandler }, + { 335, 1, true, false, "brinhandler", brinhandler }, + { 336, 4, true, false, "scalarlesel", scalarlesel }, + { 337, 4, true, false, "scalargesel", scalargesel }, + { 338, 1, true, false, "amvalidate", amvalidate }, + { 339, 2, true, false, "poly_same", poly_same }, + { 340, 2, true, false, "poly_contain", poly_contain }, + { 341, 2, true, false, "poly_left", poly_left }, + { 342, 2, true, false, "poly_overleft", poly_overleft }, + { 343, 2, true, false, "poly_overright", poly_overright }, + { 344, 2, true, false, "poly_right", poly_right }, + { 345, 2, true, false, "poly_contained", poly_contained }, + { 346, 2, true, false, "poly_overlap", poly_overlap }, + { 347, 1, true, false, "poly_in", poly_in }, + { 348, 1, true, false, "poly_out", poly_out }, + { 350, 2, true, false, "btint2cmp", btint2cmp }, + { 351, 2, true, false, "btint4cmp", btint4cmp }, + { 354, 2, true, false, "btfloat4cmp", btfloat4cmp }, + { 355, 2, true, false, "btfloat8cmp", btfloat8cmp }, + { 356, 2, true, false, "btoidcmp", btoidcmp }, + { 357, 2, true, false, "dist_bp", dist_bp }, + { 358, 2, true, false, "btcharcmp", btcharcmp }, + { 359, 2, true, false, "btnamecmp", btnamecmp }, + { 360, 2, true, false, "bttextcmp", bttextcmp }, + { 361, 2, true, false, "lseg_distance", lseg_distance }, + { 362, 2, true, false, "lseg_interpt", lseg_interpt }, + { 363, 2, true, false, "dist_ps", dist_ps }, + { 364, 2, true, false, "dist_pb", dist_pb }, + { 365, 2, true, false, "dist_sb", dist_sb }, + { 366, 2, true, false, "close_ps", close_ps }, + { 367, 2, true, false, "close_pb", close_pb }, + { 368, 2, true, false, "close_sb", close_sb }, + { 369, 2, true, false, "on_ps", on_ps }, + { 370, 2, true, false, "path_distance", path_distance }, + { 371, 2, true, false, "dist_ppath", dist_ppath }, + { 372, 2, true, false, "on_sb", on_sb }, + { 373, 2, true, false, "inter_sb", inter_sb }, + { 376, 3, false, false, "text_to_array_null", text_to_array_null }, + { 377, 2, true, false, "cash_cmp", cash_cmp }, + { 378, 2, false, false, "array_append", array_append }, + { 379, 2, false, false, "array_prepend", array_prepend }, + { 380, 2, true, false, "dist_sp", dist_sp }, + { 381, 2, true, false, "dist_bs", dist_bs }, + { 382, 2, true, false, "btarraycmp", btarraycmp }, + { 383, 2, false, false, "array_cat", array_cat }, + { 384, 3, false, false, "array_to_text_null", array_to_text_null }, + { 386, 5, true, false, "scalarlejoinsel", scalarlejoinsel }, + { 390, 2, true, false, "array_ne", array_ne }, + { 391, 2, true, false, "array_lt", array_lt }, + { 392, 2, true, false, "array_gt", array_gt }, + { 393, 2, true, false, "array_le", array_le }, + { 394, 2, false, false, "text_to_array", text_to_array }, + { 395, 2, true, false, "array_to_text", array_to_text }, + { 396, 2, true, false, "array_ge", array_ge }, + { 398, 5, true, false, "scalargejoinsel", scalargejoinsel }, + { 399, 1, true, false, "hashmacaddr", hashmacaddr }, + { 400, 1, true, false, "hashtext", hashtext }, + { 401, 1, true, false, "rtrim1", rtrim1 }, + { 404, 2, true, false, "btoidvectorcmp", btoidvectorcmp }, + { 406, 1, true, false, "name_text", name_text }, + { 407, 1, true, false, "text_name", text_name }, + { 408, 1, true, false, "name_bpchar", name_bpchar }, + { 409, 1, true, false, "bpchar_name", bpchar_name }, + { 421, 2, true, false, "dist_pathp", dist_pathp }, + { 422, 1, true, false, "hashinet", hashinet }, + { 425, 2, true, false, "hashint4extended", hashint4extended }, + { 432, 1, true, false, "hash_numeric", hash_numeric }, + { 436, 1, true, false, "macaddr_in", macaddr_in }, + { 437, 1, true, false, "macaddr_out", macaddr_out }, + { 438, 1, false, false, "pg_num_nulls", pg_num_nulls }, + { 440, 1, false, false, "pg_num_nonnulls", pg_num_nonnulls }, + { 441, 2, true, false, "hashint2extended", hashint2extended }, + { 442, 2, true, false, "hashint8extended", hashint8extended }, + { 443, 2, true, false, "hashfloat4extended", hashfloat4extended }, + { 444, 2, true, false, "hashfloat8extended", hashfloat8extended }, + { 445, 2, true, false, "hashoidextended", hashoidextended }, + { 446, 2, true, false, "hashcharextended", hashcharextended }, + { 447, 2, true, false, "hashnameextended", hashnameextended }, + { 448, 2, true, false, "hashtextextended", hashtextextended }, + { 449, 1, true, false, "hashint2", hashint2 }, + { 450, 1, true, false, "hashint4", hashint4 }, + { 451, 1, true, false, "hashfloat4", hashfloat4 }, + { 452, 1, true, false, "hashfloat8", hashfloat8 }, + { 453, 1, true, false, "hashoid", hashoid }, + { 454, 1, true, false, "hashchar", hashchar }, + { 455, 1, true, false, "hashname", hashname }, + { 456, 1, true, false, "hashvarlena", hashvarlena }, + { 457, 1, true, false, "hashoidvector", hashoidvector }, + { 458, 2, true, false, "text_larger", text_larger }, + { 459, 2, true, false, "text_smaller", text_smaller }, + { 460, 1, true, false, "int8in", int8in }, + { 461, 1, true, false, "int8out", int8out }, + { 462, 1, true, false, "int8um", int8um }, + { 463, 2, true, false, "int8pl", int8pl }, + { 464, 2, true, false, "int8mi", int8mi }, + { 465, 2, true, false, "int8mul", int8mul }, + { 466, 2, true, false, "int8div", int8div }, + { 467, 2, true, false, "int8eq", int8eq }, + { 468, 2, true, false, "int8ne", int8ne }, + { 469, 2, true, false, "int8lt", int8lt }, + { 470, 2, true, false, "int8gt", int8gt }, + { 471, 2, true, false, "int8le", int8le }, + { 472, 2, true, false, "int8ge", int8ge }, + { 474, 2, true, false, "int84eq", int84eq }, + { 475, 2, true, false, "int84ne", int84ne }, + { 476, 2, true, false, "int84lt", int84lt }, + { 477, 2, true, false, "int84gt", int84gt }, + { 478, 2, true, false, "int84le", int84le }, + { 479, 2, true, false, "int84ge", int84ge }, + { 480, 1, true, false, "int84", int84 }, + { 481, 1, true, false, "int48", int48 }, + { 482, 1, true, false, "i8tod", i8tod }, + { 483, 1, true, false, "dtoi8", dtoi8 }, + { 515, 2, true, false, "array_larger", array_larger }, + { 516, 2, true, false, "array_smaller", array_smaller }, + { 598, 1, true, false, "inet_abbrev", inet_abbrev }, + { 599, 1, true, false, "cidr_abbrev", cidr_abbrev }, + { 605, 2, true, false, "inet_set_masklen", inet_set_masklen }, + { 619, 2, true, false, "oidvectorne", oidvectorne }, + { 626, 1, true, false, "hash_array", hash_array }, + { 635, 2, true, false, "cidr_set_masklen", cidr_set_masklen }, + { 636, 2, true, false, "pg_indexam_has_property", pg_indexam_has_property }, + { 637, 2, true, false, "pg_index_has_property", pg_index_has_property }, + { 638, 3, true, false, "pg_index_column_has_property", pg_index_column_has_property }, + { 652, 1, true, false, "i8tof", i8tof }, + { 653, 1, true, false, "ftoi8", ftoi8 }, + { 655, 2, true, false, "namelt", namelt }, + { 656, 2, true, false, "namele", namele }, + { 657, 2, true, false, "namegt", namegt }, + { 658, 2, true, false, "namege", namege }, + { 659, 2, true, false, "namene", namene }, + { 668, 3, true, false, "bpchar", bpchar }, + { 669, 3, true, false, "varchar", varchar }, + { 676, 2, true, false, "pg_indexam_progress_phasename", pg_indexam_progress_phasename }, + { 677, 2, true, false, "oidvectorlt", oidvectorlt }, + { 678, 2, true, false, "oidvectorle", oidvectorle }, + { 679, 2, true, false, "oidvectoreq", oidvectoreq }, + { 680, 2, true, false, "oidvectorge", oidvectorge }, + { 681, 2, true, false, "oidvectorgt", oidvectorgt }, + { 683, 1, true, false, "network_network", network_network }, + { 696, 1, true, false, "network_netmask", network_netmask }, + { 697, 1, true, false, "network_masklen", network_masklen }, + { 698, 1, true, false, "network_broadcast", network_broadcast }, + { 699, 1, true, false, "network_host", network_host }, + { 702, 2, true, false, "dist_lp", dist_lp }, + { 704, 2, true, false, "dist_ls", dist_ls }, + { 710, 0, true, false, "current_user", current_user }, + { 711, 1, true, false, "network_family", network_family }, + { 714, 1, true, false, "int82", int82 }, + { 715, 1, true, false, "be_lo_create", be_lo_create }, + { 716, 2, true, false, "oidlt", oidlt }, + { 717, 2, true, false, "oidle", oidle }, + { 720, 1, true, false, "byteaoctetlen", byteaoctetlen }, + { 721, 2, true, false, "byteaGetByte", byteaGetByte }, + { 722, 3, true, false, "byteaSetByte", byteaSetByte }, + { 723, 2, true, false, "byteaGetBit", byteaGetBit }, + { 724, 3, true, false, "byteaSetBit", byteaSetBit }, + { 725, 2, true, false, "dist_pl", dist_pl }, + { 727, 2, true, false, "dist_sl", dist_sl }, + { 728, 2, true, false, "dist_cpoly", dist_cpoly }, + { 729, 2, true, false, "poly_distance", poly_distance }, + { 730, 1, true, false, "network_show", network_show }, + { 740, 2, true, false, "text_lt", text_lt }, + { 741, 2, true, false, "text_le", text_le }, + { 742, 2, true, false, "text_gt", text_gt }, + { 743, 2, true, false, "text_ge", text_ge }, + { 744, 2, true, false, "array_eq", array_eq }, + { 745, 0, true, false, "current_user", current_user }, + { 746, 0, true, false, "session_user", session_user }, + { 747, 1, true, false, "array_dims", array_dims }, + { 748, 1, true, false, "array_ndims", array_ndims }, + { 749, 4, true, false, "byteaoverlay", byteaoverlay }, + { 750, 3, true, false, "array_in", array_in }, + { 751, 1, true, false, "array_out", array_out }, + { 752, 3, true, false, "byteaoverlay_no_len", byteaoverlay_no_len }, + { 753, 1, true, false, "macaddr_trunc", macaddr_trunc }, + { 754, 1, true, false, "int28", int28 }, + { 764, 1, true, false, "be_lo_import", be_lo_import }, + { 765, 2, true, false, "be_lo_export", be_lo_export }, + { 766, 1, true, false, "int4inc", int4inc }, + { 767, 2, true, false, "be_lo_import_with_oid", be_lo_import_with_oid }, + { 768, 2, true, false, "int4larger", int4larger }, + { 769, 2, true, false, "int4smaller", int4smaller }, + { 770, 2, true, false, "int2larger", int2larger }, + { 771, 2, true, false, "int2smaller", int2smaller }, + { 772, 2, true, false, "hashvarlenaextended", hashvarlenaextended }, + { 776, 2, true, false, "hashoidvectorextended", hashoidvectorextended }, + { 777, 2, true, false, "hash_aclitem_extended", hash_aclitem_extended }, + { 778, 2, true, false, "hashmacaddrextended", hashmacaddrextended }, + { 779, 2, true, false, "hashinetextended", hashinetextended }, + { 780, 2, true, false, "hash_numeric_extended", hash_numeric_extended }, + { 781, 2, true, false, "hashmacaddr8extended", hashmacaddr8extended }, + { 782, 2, true, false, "hash_array_extended", hash_array_extended }, + { 785, 2, true, false, "dist_polyc", dist_polyc }, + { 810, 0, true, false, "pg_client_encoding", pg_client_encoding }, + { 817, 0, false, false, "current_query", current_query }, + { 830, 2, true, false, "macaddr_eq", macaddr_eq }, + { 831, 2, true, false, "macaddr_lt", macaddr_lt }, + { 832, 2, true, false, "macaddr_le", macaddr_le }, + { 833, 2, true, false, "macaddr_gt", macaddr_gt }, + { 834, 2, true, false, "macaddr_ge", macaddr_ge }, + { 835, 2, true, false, "macaddr_ne", macaddr_ne }, + { 836, 2, true, false, "macaddr_cmp", macaddr_cmp }, + { 837, 2, true, false, "int82pl", int82pl }, + { 838, 2, true, false, "int82mi", int82mi }, + { 839, 2, true, false, "int82mul", int82mul }, + { 840, 2, true, false, "int82div", int82div }, + { 841, 2, true, false, "int28pl", int28pl }, + { 842, 2, true, false, "btint8cmp", btint8cmp }, + { 846, 2, true, false, "cash_mul_flt4", cash_mul_flt4 }, + { 847, 2, true, false, "cash_div_flt4", cash_div_flt4 }, + { 848, 2, true, false, "flt4_mul_cash", flt4_mul_cash }, + { 849, 2, true, false, "textpos", textpos }, + { 850, 2, true, false, "textlike", textlike }, + { 851, 2, true, false, "textnlike", textnlike }, + { 852, 2, true, false, "int48eq", int48eq }, + { 853, 2, true, false, "int48ne", int48ne }, + { 854, 2, true, false, "int48lt", int48lt }, + { 855, 2, true, false, "int48gt", int48gt }, + { 856, 2, true, false, "int48le", int48le }, + { 857, 2, true, false, "int48ge", int48ge }, + { 858, 2, true, false, "namelike", namelike }, + { 859, 2, true, false, "namenlike", namenlike }, + { 860, 1, true, false, "char_bpchar", char_bpchar }, + { 861, 0, true, false, "current_database", current_database }, + { 862, 2, true, false, "int4_mul_cash", int4_mul_cash }, + { 863, 2, true, false, "int2_mul_cash", int2_mul_cash }, + { 864, 2, true, false, "cash_mul_int4", cash_mul_int4 }, + { 865, 2, true, false, "cash_div_int4", cash_div_int4 }, + { 866, 2, true, false, "cash_mul_int2", cash_mul_int2 }, + { 867, 2, true, false, "cash_div_int2", cash_div_int2 }, + { 868, 2, true, false, "textpos", textpos }, + { 870, 1, true, false, "lower", lower }, + { 871, 1, true, false, "upper", upper }, + { 872, 1, true, false, "initcap", initcap }, + { 873, 3, true, false, "lpad", lpad }, + { 874, 3, true, false, "rpad", rpad }, + { 875, 2, true, false, "ltrim", ltrim }, + { 876, 2, true, false, "rtrim", rtrim }, + { 877, 3, true, false, "text_substr", text_substr }, + { 878, 3, true, false, "translate", translate }, + { 881, 1, true, false, "ltrim1", ltrim1 }, + { 882, 1, true, false, "rtrim1", rtrim1 }, + { 883, 2, true, false, "text_substr_no_len", text_substr_no_len }, + { 884, 2, true, false, "btrim", btrim }, + { 885, 1, true, false, "btrim1", btrim1 }, + { 886, 1, true, false, "cash_in", cash_in }, + { 887, 1, true, false, "cash_out", cash_out }, + { 888, 2, true, false, "cash_eq", cash_eq }, + { 889, 2, true, false, "cash_ne", cash_ne }, + { 890, 2, true, false, "cash_lt", cash_lt }, + { 891, 2, true, false, "cash_le", cash_le }, + { 892, 2, true, false, "cash_gt", cash_gt }, + { 893, 2, true, false, "cash_ge", cash_ge }, + { 894, 2, true, false, "cash_pl", cash_pl }, + { 895, 2, true, false, "cash_mi", cash_mi }, + { 896, 2, true, false, "cash_mul_flt8", cash_mul_flt8 }, + { 897, 2, true, false, "cash_div_flt8", cash_div_flt8 }, + { 898, 2, true, false, "cashlarger", cashlarger }, + { 899, 2, true, false, "cashsmaller", cashsmaller }, + { 910, 1, true, false, "inet_in", inet_in }, + { 911, 1, true, false, "inet_out", inet_out }, + { 919, 2, true, false, "flt8_mul_cash", flt8_mul_cash }, + { 920, 2, true, false, "network_eq", network_eq }, + { 921, 2, true, false, "network_lt", network_lt }, + { 922, 2, true, false, "network_le", network_le }, + { 923, 2, true, false, "network_gt", network_gt }, + { 924, 2, true, false, "network_ge", network_ge }, + { 925, 2, true, false, "network_ne", network_ne }, + { 926, 2, true, false, "network_cmp", network_cmp }, + { 927, 2, true, false, "network_sub", network_sub }, + { 928, 2, true, false, "network_subeq", network_subeq }, + { 929, 2, true, false, "network_sup", network_sup }, + { 930, 2, true, false, "network_supeq", network_supeq }, + { 935, 1, true, false, "cash_words", cash_words }, + { 936, 3, true, false, "text_substr", text_substr }, + { 937, 2, true, false, "text_substr_no_len", text_substr_no_len }, + { 938, 3, true, true, "generate_series_timestamp", generate_series_timestamp }, + { 939, 3, true, true, "generate_series_timestamptz", generate_series_timestamptz }, + { 940, 2, true, false, "int2mod", int2mod }, + { 941, 2, true, false, "int4mod", int4mod }, + { 942, 2, true, false, "int28mi", int28mi }, + { 943, 2, true, false, "int28mul", int28mul }, + { 944, 1, true, false, "text_char", text_char }, + { 945, 2, true, false, "int8mod", int8mod }, + { 946, 1, true, false, "char_text", char_text }, + { 947, 2, true, false, "int8mod", int8mod }, + { 948, 2, true, false, "int28div", int28div }, + { 949, 1, true, false, "hashint8", hashint8 }, + { 952, 2, true, false, "be_lo_open", be_lo_open }, + { 953, 1, true, false, "be_lo_close", be_lo_close }, + { 954, 2, true, false, "be_loread", be_loread }, + { 955, 2, true, false, "be_lowrite", be_lowrite }, + { 956, 3, true, false, "be_lo_lseek", be_lo_lseek }, + { 957, 1, true, false, "be_lo_creat", be_lo_creat }, + { 958, 1, true, false, "be_lo_tell", be_lo_tell }, + { 959, 2, true, false, "on_pl", on_pl }, + { 960, 2, true, false, "on_sl", on_sl }, + { 961, 2, true, false, "close_pl", close_pl }, + { 964, 1, true, false, "be_lo_unlink", be_lo_unlink }, + { 972, 2, true, false, "hashbpcharextended", hashbpcharextended }, + { 973, 2, true, false, "path_inter", path_inter }, + { 975, 1, true, false, "box_area", box_area }, + { 976, 1, true, false, "box_width", box_width }, + { 977, 1, true, false, "box_height", box_height }, + { 978, 2, true, false, "box_distance", box_distance }, + { 979, 1, true, false, "path_area", path_area }, + { 980, 2, true, false, "box_intersect", box_intersect }, + { 981, 1, true, false, "box_diagonal", box_diagonal }, + { 982, 2, true, false, "path_n_lt", path_n_lt }, + { 983, 2, true, false, "path_n_gt", path_n_gt }, + { 984, 2, true, false, "path_n_eq", path_n_eq }, + { 985, 2, true, false, "path_n_le", path_n_le }, + { 986, 2, true, false, "path_n_ge", path_n_ge }, + { 987, 1, true, false, "path_length", path_length }, + { 988, 2, true, false, "point_ne", point_ne }, + { 989, 2, true, false, "point_vert", point_vert }, + { 990, 2, true, false, "point_horiz", point_horiz }, + { 991, 2, true, false, "point_distance", point_distance }, + { 992, 2, true, false, "point_slope", point_slope }, + { 993, 2, true, false, "lseg_construct", lseg_construct }, + { 994, 2, true, false, "lseg_intersect", lseg_intersect }, + { 995, 2, true, false, "lseg_parallel", lseg_parallel }, + { 996, 2, true, false, "lseg_perp", lseg_perp }, + { 997, 1, true, false, "lseg_vertical", lseg_vertical }, + { 998, 1, true, false, "lseg_horizontal", lseg_horizontal }, + { 999, 2, true, false, "lseg_eq", lseg_eq }, + { 1004, 2, true, false, "be_lo_truncate", be_lo_truncate }, + { 1023, 1, true, false, "textlike_support", textlike_support }, + { 1024, 1, true, false, "texticregexeq_support", texticregexeq_support }, + { 1025, 1, true, false, "texticlike_support", texticlike_support }, + { 1026, 2, true, false, "timestamptz_izone", timestamptz_izone }, + { 1030, 1, true, false, "gist_point_compress", gist_point_compress }, + { 1031, 1, true, false, "aclitemin", aclitemin }, + { 1032, 1, true, false, "aclitemout", aclitemout }, + { 1035, 2, true, false, "aclinsert", aclinsert }, + { 1036, 2, true, false, "aclremove", aclremove }, + { 1037, 2, true, false, "aclcontains", aclcontains }, + { 1039, 0, true, false, "getdatabaseencoding", getdatabaseencoding }, + { 1044, 3, true, false, "bpcharin", bpcharin }, + { 1045, 1, true, false, "bpcharout", bpcharout }, + { 1046, 3, true, false, "varcharin", varcharin }, + { 1047, 1, true, false, "varcharout", varcharout }, + { 1048, 2, true, false, "bpchareq", bpchareq }, + { 1049, 2, true, false, "bpcharlt", bpcharlt }, + { 1050, 2, true, false, "bpcharle", bpcharle }, + { 1051, 2, true, false, "bpchargt", bpchargt }, + { 1052, 2, true, false, "bpcharge", bpcharge }, + { 1053, 2, true, false, "bpcharne", bpcharne }, + { 1062, 2, true, false, "aclitem_eq", aclitem_eq }, + { 1063, 2, true, false, "bpchar_larger", bpchar_larger }, + { 1064, 2, true, false, "bpchar_smaller", bpchar_smaller }, + { 1065, 0, true, true, "pg_prepared_xact", pg_prepared_xact }, + { 1066, 3, true, true, "generate_series_step_int4", generate_series_step_int4 }, + { 1067, 2, true, true, "generate_series_int4", generate_series_int4 }, + { 1068, 3, true, true, "generate_series_step_int8", generate_series_step_int8 }, + { 1069, 2, true, true, "generate_series_int8", generate_series_int8 }, + { 1078, 2, true, false, "bpcharcmp", bpcharcmp }, + { 1079, 1, true, false, "text_regclass", text_regclass }, + { 1080, 1, true, false, "hashbpchar", hashbpchar }, + { 1081, 2, false, false, "format_type", format_type }, + { 1084, 1, true, false, "date_in", date_in }, + { 1085, 1, true, false, "date_out", date_out }, + { 1086, 2, true, false, "date_eq", date_eq }, + { 1087, 2, true, false, "date_lt", date_lt }, + { 1088, 2, true, false, "date_le", date_le }, + { 1089, 2, true, false, "date_gt", date_gt }, + { 1090, 2, true, false, "date_ge", date_ge }, + { 1091, 2, true, false, "date_ne", date_ne }, + { 1092, 2, true, false, "date_cmp", date_cmp }, + { 1102, 2, true, false, "time_lt", time_lt }, + { 1103, 2, true, false, "time_le", time_le }, + { 1104, 2, true, false, "time_gt", time_gt }, + { 1105, 2, true, false, "time_ge", time_ge }, + { 1106, 2, true, false, "time_ne", time_ne }, + { 1107, 2, true, false, "time_cmp", time_cmp }, + { 1136, 0, false, false, "pg_stat_get_wal", pg_stat_get_wal }, + { 1137, 0, true, false, "pg_get_wal_replay_pause_state", pg_get_wal_replay_pause_state }, + { 1138, 2, true, false, "date_larger", date_larger }, + { 1139, 2, true, false, "date_smaller", date_smaller }, + { 1140, 2, true, false, "date_mi", date_mi }, + { 1141, 2, true, false, "date_pli", date_pli }, + { 1142, 2, true, false, "date_mii", date_mii }, + { 1143, 3, true, false, "time_in", time_in }, + { 1144, 1, true, false, "time_out", time_out }, + { 1145, 2, true, false, "time_eq", time_eq }, + { 1146, 2, true, false, "circle_add_pt", circle_add_pt }, + { 1147, 2, true, false, "circle_sub_pt", circle_sub_pt }, + { 1148, 2, true, false, "circle_mul_pt", circle_mul_pt }, + { 1149, 2, true, false, "circle_div_pt", circle_div_pt }, + { 1150, 3, true, false, "timestamptz_in", timestamptz_in }, + { 1151, 1, true, false, "timestamptz_out", timestamptz_out }, + { 1152, 2, true, false, "timestamp_eq", timestamp_eq }, + { 1153, 2, true, false, "timestamp_ne", timestamp_ne }, + { 1154, 2, true, false, "timestamp_lt", timestamp_lt }, + { 1155, 2, true, false, "timestamp_le", timestamp_le }, + { 1156, 2, true, false, "timestamp_ge", timestamp_ge }, + { 1157, 2, true, false, "timestamp_gt", timestamp_gt }, + { 1158, 1, true, false, "float8_timestamptz", float8_timestamptz }, + { 1159, 2, true, false, "timestamptz_zone", timestamptz_zone }, + { 1160, 3, true, false, "interval_in", interval_in }, + { 1161, 1, true, false, "interval_out", interval_out }, + { 1162, 2, true, false, "interval_eq", interval_eq }, + { 1163, 2, true, false, "interval_ne", interval_ne }, + { 1164, 2, true, false, "interval_lt", interval_lt }, + { 1165, 2, true, false, "interval_le", interval_le }, + { 1166, 2, true, false, "interval_ge", interval_ge }, + { 1167, 2, true, false, "interval_gt", interval_gt }, + { 1168, 1, true, false, "interval_um", interval_um }, + { 1169, 2, true, false, "interval_pl", interval_pl }, + { 1170, 2, true, false, "interval_mi", interval_mi }, + { 1171, 2, true, false, "timestamptz_part", timestamptz_part }, + { 1172, 2, true, false, "interval_part", interval_part }, + { 1173, 1, true, false, "network_subset_support", network_subset_support }, + { 1174, 1, true, false, "date_timestamptz", date_timestamptz }, + { 1175, 1, true, false, "interval_justify_hours", interval_justify_hours }, + { 1177, 4, true, false, "jsonb_path_exists_tz", jsonb_path_exists_tz }, + { 1178, 1, true, false, "timestamptz_date", timestamptz_date }, + { 1179, 4, true, true, "jsonb_path_query_tz", jsonb_path_query_tz }, + { 1180, 4, true, false, "jsonb_path_query_array_tz", jsonb_path_query_array_tz }, + { 1181, 1, true, false, "xid_age", xid_age }, + { 1188, 2, true, false, "timestamp_mi", timestamp_mi }, + { 1189, 2, true, false, "timestamptz_pl_interval", timestamptz_pl_interval }, + { 1190, 2, true, false, "timestamptz_mi_interval", timestamptz_mi_interval }, + { 1191, 3, true, true, "generate_subscripts", generate_subscripts }, + { 1192, 2, true, true, "generate_subscripts_nodir", generate_subscripts_nodir }, + { 1193, 2, false, false, "array_fill", array_fill }, + { 1194, 1, true, false, "dlog10", dlog10 }, + { 1195, 2, true, false, "timestamp_smaller", timestamp_smaller }, + { 1196, 2, true, false, "timestamp_larger", timestamp_larger }, + { 1197, 2, true, false, "interval_smaller", interval_smaller }, + { 1198, 2, true, false, "interval_larger", interval_larger }, + { 1199, 2, true, false, "timestamptz_age", timestamptz_age }, + { 1200, 2, true, false, "interval_scale", interval_scale }, + { 1217, 2, true, false, "timestamptz_trunc", timestamptz_trunc }, + { 1218, 2, true, false, "interval_trunc", interval_trunc }, + { 1219, 1, true, false, "int8inc", int8inc }, + { 1230, 1, true, false, "int8abs", int8abs }, + { 1236, 2, true, false, "int8larger", int8larger }, + { 1237, 2, true, false, "int8smaller", int8smaller }, + { 1238, 2, true, false, "texticregexeq", texticregexeq }, + { 1239, 2, true, false, "texticregexne", texticregexne }, + { 1240, 2, true, false, "nameicregexeq", nameicregexeq }, + { 1241, 2, true, false, "nameicregexne", nameicregexne }, + { 1242, 1, true, false, "boolin", boolin }, + { 1243, 1, true, false, "boolout", boolout }, + { 1244, 1, true, false, "byteain", byteain }, + { 1245, 1, true, false, "charin", charin }, + { 1246, 2, true, false, "charlt", charlt }, + { 1250, 0, true, false, "unique_key_recheck", unique_key_recheck }, + { 1251, 1, true, false, "int4abs", int4abs }, + { 1252, 2, true, false, "nameregexne", nameregexne }, + { 1253, 1, true, false, "int2abs", int2abs }, + { 1254, 2, true, false, "textregexeq", textregexeq }, + { 1256, 2, true, false, "textregexne", textregexne }, + { 1257, 1, true, false, "textlen", textlen }, + { 1258, 2, true, false, "textcat", textcat }, + { 1264, 1, true, false, "PG_char_to_encoding", PG_char_to_encoding }, + { 1265, 2, true, false, "tidne", tidne }, + { 1267, 1, true, false, "cidr_in", cidr_in }, + { 1268, 2, true, false, "parse_ident", parse_ident }, + { 1269, 1, true, false, "pg_column_size", pg_column_size }, + { 1271, 4, false, false, "overlaps_timetz", overlaps_timetz }, + { 1272, 2, true, false, "datetime_timestamp", datetime_timestamp }, + { 1273, 2, true, false, "timetz_part", timetz_part }, + { 1274, 2, true, false, "int84pl", int84pl }, + { 1275, 2, true, false, "int84mi", int84mi }, + { 1276, 2, true, false, "int84mul", int84mul }, + { 1277, 2, true, false, "int84div", int84div }, + { 1278, 2, true, false, "int48pl", int48pl }, + { 1279, 2, true, false, "int48mi", int48mi }, + { 1280, 2, true, false, "int48mul", int48mul }, + { 1281, 2, true, false, "int48div", int48div }, + { 1282, 1, true, false, "quote_ident", quote_ident }, + { 1283, 1, true, false, "quote_literal", quote_literal }, + { 1284, 3, true, false, "timestamptz_trunc_zone", timestamptz_trunc_zone }, + { 1286, 3, false, false, "array_fill_with_lower_bounds", array_fill_with_lower_bounds }, + { 1287, 1, true, false, "i8tooid", i8tooid }, + { 1288, 1, true, false, "oidtoi8", oidtoi8 }, + { 1289, 1, false, false, "quote_nullable", quote_nullable }, + { 1291, 0, true, false, "suppress_redundant_updates_trigger", suppress_redundant_updates_trigger }, + { 1292, 2, true, false, "tideq", tideq }, + { 1293, 1, true, true, "multirange_unnest", multirange_unnest }, + { 1294, 2, true, false, "currtid_byrelname", currtid_byrelname }, + { 1295, 1, true, false, "interval_justify_days", interval_justify_days }, + { 1297, 2, true, false, "datetimetz_timestamptz", datetimetz_timestamptz }, + { 1299, 0, true, false, "now", now }, + { 1300, 4, true, false, "positionsel", positionsel }, + { 1301, 5, true, false, "positionjoinsel", positionjoinsel }, + { 1302, 4, true, false, "contsel", contsel }, + { 1303, 5, true, false, "contjoinsel", contjoinsel }, + { 1304, 4, false, false, "overlaps_timestamp", overlaps_timestamp }, + { 1308, 4, false, false, "overlaps_time", overlaps_time }, + { 1312, 3, true, false, "timestamp_in", timestamp_in }, + { 1313, 1, true, false, "timestamp_out", timestamp_out }, + { 1314, 2, true, false, "timestamp_cmp", timestamp_cmp }, + { 1315, 2, true, false, "interval_cmp", interval_cmp }, + { 1316, 1, true, false, "timestamp_time", timestamp_time }, + { 1317, 1, true, false, "textlen", textlen }, + { 1318, 1, true, false, "bpcharlen", bpcharlen }, + { 1319, 2, true, false, "xideq", xideq }, + { 1326, 2, true, false, "interval_div", interval_div }, + { 1339, 1, true, false, "dlog10", dlog10 }, + { 1340, 1, true, false, "dlog10", dlog10 }, + { 1341, 1, true, false, "dlog1", dlog1 }, + { 1342, 1, true, false, "dround", dround }, + { 1343, 1, true, false, "dtrunc", dtrunc }, + { 1344, 1, true, false, "dsqrt", dsqrt }, + { 1345, 1, true, false, "dcbrt", dcbrt }, + { 1346, 2, true, false, "dpow", dpow }, + { 1347, 1, true, false, "dexp", dexp }, + { 1349, 1, true, false, "oidvectortypes", oidvectortypes }, + { 1350, 3, true, false, "timetz_in", timetz_in }, + { 1351, 1, true, false, "timetz_out", timetz_out }, + { 1352, 2, true, false, "timetz_eq", timetz_eq }, + { 1353, 2, true, false, "timetz_ne", timetz_ne }, + { 1354, 2, true, false, "timetz_lt", timetz_lt }, + { 1355, 2, true, false, "timetz_le", timetz_le }, + { 1356, 2, true, false, "timetz_ge", timetz_ge }, + { 1357, 2, true, false, "timetz_gt", timetz_gt }, + { 1358, 2, true, false, "timetz_cmp", timetz_cmp }, + { 1359, 2, true, false, "datetimetz_timestamptz", datetimetz_timestamptz }, + { 1362, 1, true, false, "network_hostmask", network_hostmask }, + { 1364, 1, true, false, "textregexeq_support", textregexeq_support }, + { 1365, 4, true, false, "makeaclitem", makeaclitem }, + { 1367, 1, true, false, "bpcharlen", bpcharlen }, + { 1368, 2, true, false, "dpow", dpow }, + { 1369, 1, true, false, "textlen", textlen }, + { 1370, 1, true, false, "time_interval", time_interval }, + { 1371, 0, true, true, "pg_lock_status", pg_lock_status }, + { 1372, 1, true, false, "bpcharlen", bpcharlen }, + { 1373, 1, true, false, "date_finite", date_finite }, + { 1374, 1, true, false, "textoctetlen", textoctetlen }, + { 1375, 1, true, false, "bpcharoctetlen", bpcharoctetlen }, + { 1376, 1, true, false, "numeric_fac", numeric_fac }, + { 1377, 2, true, false, "time_larger", time_larger }, + { 1378, 2, true, false, "time_smaller", time_smaller }, + { 1379, 2, true, false, "timetz_larger", timetz_larger }, + { 1380, 2, true, false, "timetz_smaller", timetz_smaller }, + { 1381, 1, true, false, "textlen", textlen }, + { 1385, 2, true, false, "time_part", time_part }, + { 1387, 1, true, false, "pg_get_constraintdef", pg_get_constraintdef }, + { 1388, 1, true, false, "timestamptz_timetz", timestamptz_timetz }, + { 1389, 1, true, false, "timestamp_finite", timestamp_finite }, + { 1390, 1, true, false, "interval_finite", interval_finite }, + { 1391, 1, true, false, "pg_stat_get_backend_start", pg_stat_get_backend_start }, + { 1392, 1, true, false, "pg_stat_get_backend_client_addr", pg_stat_get_backend_client_addr }, + { 1393, 1, true, false, "pg_stat_get_backend_client_port", pg_stat_get_backend_client_port }, + { 1394, 1, true, false, "float4abs", float4abs }, + { 1395, 1, true, false, "float8abs", float8abs }, + { 1396, 1, true, false, "int8abs", int8abs }, + { 1397, 1, true, false, "int4abs", int4abs }, + { 1398, 1, true, false, "int2abs", int2abs }, + { 1400, 1, true, false, "text_name", text_name }, + { 1401, 1, true, false, "name_text", name_text }, + { 1402, 0, true, false, "current_schema", current_schema }, + { 1403, 1, true, false, "current_schemas", current_schemas }, + { 1404, 4, true, false, "textoverlay", textoverlay }, + { 1405, 3, true, false, "textoverlay_no_len", textoverlay_no_len }, + { 1406, 2, true, false, "point_vert", point_vert }, + { 1407, 2, true, false, "point_horiz", point_horiz }, + { 1408, 2, true, false, "lseg_parallel", lseg_parallel }, + { 1409, 2, true, false, "lseg_perp", lseg_perp }, + { 1410, 1, true, false, "lseg_vertical", lseg_vertical }, + { 1411, 1, true, false, "lseg_horizontal", lseg_horizontal }, + { 1412, 2, true, false, "line_parallel", line_parallel }, + { 1413, 2, true, false, "line_perp", line_perp }, + { 1414, 1, true, false, "line_vertical", line_vertical }, + { 1415, 1, true, false, "line_horizontal", line_horizontal }, + { 1416, 1, true, false, "circle_center", circle_center }, + { 1419, 1, true, false, "interval_time", interval_time }, + { 1421, 2, true, false, "points_box", points_box }, + { 1422, 2, true, false, "box_add", box_add }, + { 1423, 2, true, false, "box_sub", box_sub }, + { 1424, 2, true, false, "box_mul", box_mul }, + { 1425, 2, true, false, "box_div", box_div }, + { 1427, 1, true, false, "cidr_out", cidr_out }, + { 1428, 2, true, false, "poly_contain_pt", poly_contain_pt }, + { 1429, 2, true, false, "pt_contained_poly", pt_contained_poly }, + { 1430, 1, true, false, "path_isclosed", path_isclosed }, + { 1431, 1, true, false, "path_isopen", path_isopen }, + { 1432, 1, true, false, "path_npoints", path_npoints }, + { 1433, 1, true, false, "path_close", path_close }, + { 1434, 1, true, false, "path_open", path_open }, + { 1435, 2, true, false, "path_add", path_add }, + { 1436, 2, true, false, "path_add_pt", path_add_pt }, + { 1437, 2, true, false, "path_sub_pt", path_sub_pt }, + { 1438, 2, true, false, "path_mul_pt", path_mul_pt }, + { 1439, 2, true, false, "path_div_pt", path_div_pt }, + { 1440, 2, true, false, "construct_point", construct_point }, + { 1441, 2, true, false, "point_add", point_add }, + { 1442, 2, true, false, "point_sub", point_sub }, + { 1443, 2, true, false, "point_mul", point_mul }, + { 1444, 2, true, false, "point_div", point_div }, + { 1445, 1, true, false, "poly_npoints", poly_npoints }, + { 1446, 1, true, false, "poly_box", poly_box }, + { 1447, 1, true, false, "poly_path", poly_path }, + { 1448, 1, true, false, "box_poly", box_poly }, + { 1449, 1, true, false, "path_poly", path_poly }, + { 1450, 1, true, false, "circle_in", circle_in }, + { 1451, 1, true, false, "circle_out", circle_out }, + { 1452, 2, true, false, "circle_same", circle_same }, + { 1453, 2, true, false, "circle_contain", circle_contain }, + { 1454, 2, true, false, "circle_left", circle_left }, + { 1455, 2, true, false, "circle_overleft", circle_overleft }, + { 1456, 2, true, false, "circle_overright", circle_overright }, + { 1457, 2, true, false, "circle_right", circle_right }, + { 1458, 2, true, false, "circle_contained", circle_contained }, + { 1459, 2, true, false, "circle_overlap", circle_overlap }, + { 1460, 2, true, false, "circle_below", circle_below }, + { 1461, 2, true, false, "circle_above", circle_above }, + { 1462, 2, true, false, "circle_eq", circle_eq }, + { 1463, 2, true, false, "circle_ne", circle_ne }, + { 1464, 2, true, false, "circle_lt", circle_lt }, + { 1465, 2, true, false, "circle_gt", circle_gt }, + { 1466, 2, true, false, "circle_le", circle_le }, + { 1467, 2, true, false, "circle_ge", circle_ge }, + { 1468, 1, true, false, "circle_area", circle_area }, + { 1469, 1, true, false, "circle_diameter", circle_diameter }, + { 1470, 1, true, false, "circle_radius", circle_radius }, + { 1471, 2, true, false, "circle_distance", circle_distance }, + { 1472, 1, true, false, "circle_center", circle_center }, + { 1473, 2, true, false, "cr_circle", cr_circle }, + { 1474, 1, true, false, "poly_circle", poly_circle }, + { 1475, 2, true, false, "circle_poly", circle_poly }, + { 1476, 2, true, false, "dist_pc", dist_pc }, + { 1477, 2, true, false, "circle_contain_pt", circle_contain_pt }, + { 1478, 2, true, false, "pt_contained_circle", pt_contained_circle }, + { 1479, 1, true, false, "box_circle", box_circle }, + { 1480, 1, true, false, "circle_box", circle_box }, + { 1482, 2, true, false, "lseg_ne", lseg_ne }, + { 1483, 2, true, false, "lseg_lt", lseg_lt }, + { 1484, 2, true, false, "lseg_le", lseg_le }, + { 1485, 2, true, false, "lseg_gt", lseg_gt }, + { 1486, 2, true, false, "lseg_ge", lseg_ge }, + { 1487, 1, true, false, "lseg_length", lseg_length }, + { 1488, 2, true, false, "close_ls", close_ls }, + { 1489, 2, true, false, "close_lseg", close_lseg }, + { 1490, 1, true, false, "line_in", line_in }, + { 1491, 1, true, false, "line_out", line_out }, + { 1492, 2, true, false, "line_eq", line_eq }, + { 1493, 2, true, false, "line_construct_pp", line_construct_pp }, + { 1494, 2, true, false, "line_interpt", line_interpt }, + { 1495, 2, true, false, "line_intersect", line_intersect }, + { 1496, 2, true, false, "line_parallel", line_parallel }, + { 1497, 2, true, false, "line_perp", line_perp }, + { 1498, 1, true, false, "line_vertical", line_vertical }, + { 1499, 1, true, false, "line_horizontal", line_horizontal }, + { 1530, 1, true, false, "lseg_length", lseg_length }, + { 1531, 1, true, false, "path_length", path_length }, + { 1532, 1, true, false, "lseg_center", lseg_center }, + { 1534, 1, true, false, "box_center", box_center }, + { 1540, 1, true, false, "poly_center", poly_center }, + { 1541, 1, true, false, "box_diagonal", box_diagonal }, + { 1542, 1, true, false, "box_center", box_center }, + { 1543, 1, true, false, "circle_center", circle_center }, + { 1545, 1, true, false, "path_npoints", path_npoints }, + { 1556, 1, true, false, "poly_npoints", poly_npoints }, + { 1564, 3, true, false, "bit_in", bit_in }, + { 1565, 1, true, false, "bit_out", bit_out }, + { 1569, 2, true, false, "textlike", textlike }, + { 1570, 2, true, false, "textnlike", textnlike }, + { 1571, 2, true, false, "namelike", namelike }, + { 1572, 2, true, false, "namenlike", namenlike }, + { 1573, 1, true, false, "pg_get_ruledef", pg_get_ruledef }, + { 1574, 1, true, false, "nextval_oid", nextval_oid }, + { 1575, 1, true, false, "currval_oid", currval_oid }, + { 1576, 2, true, false, "setval_oid", setval_oid }, + { 1579, 3, true, false, "varbit_in", varbit_in }, + { 1580, 1, true, false, "varbit_out", varbit_out }, + { 1581, 2, true, false, "biteq", biteq }, + { 1582, 2, true, false, "bitne", bitne }, + { 1592, 2, true, false, "bitge", bitge }, + { 1593, 2, true, false, "bitgt", bitgt }, + { 1594, 2, true, false, "bitle", bitle }, + { 1595, 2, true, false, "bitlt", bitlt }, + { 1596, 2, true, false, "bitcmp", bitcmp }, + { 1597, 1, true, false, "PG_encoding_to_char", PG_encoding_to_char }, + { 1598, 0, true, false, "drandom", drandom }, + { 1599, 1, true, false, "setseed", setseed }, + { 1600, 1, true, false, "dasin", dasin }, + { 1601, 1, true, false, "dacos", dacos }, + { 1602, 1, true, false, "datan", datan }, + { 1603, 2, true, false, "datan2", datan2 }, + { 1604, 1, true, false, "dsin", dsin }, + { 1605, 1, true, false, "dcos", dcos }, + { 1606, 1, true, false, "dtan", dtan }, + { 1607, 1, true, false, "dcot", dcot }, + { 1608, 1, true, false, "degrees", degrees }, + { 1609, 1, true, false, "radians", radians }, + { 1610, 0, true, false, "dpi", dpi }, + { 1618, 2, true, false, "interval_mul", interval_mul }, + { 1619, 1, false, false, "pg_typeof", pg_typeof }, + { 1620, 1, true, false, "ascii", ascii }, + { 1621, 1, true, false, "chr", chr }, + { 1622, 2, true, false, "repeat", repeat }, + { 1623, 2, false, false, "similar_escape", similar_escape }, + { 1624, 2, true, false, "mul_d_interval", mul_d_interval }, + { 1631, 2, true, false, "textlike", textlike }, + { 1632, 2, true, false, "textnlike", textnlike }, + { 1633, 2, true, false, "texticlike", texticlike }, + { 1634, 2, true, false, "texticnlike", texticnlike }, + { 1635, 2, true, false, "nameiclike", nameiclike }, + { 1636, 2, true, false, "nameicnlike", nameicnlike }, + { 1637, 2, true, false, "like_escape", like_escape }, + { 1638, 2, true, false, "oidgt", oidgt }, + { 1639, 2, true, false, "oidge", oidge }, + { 1640, 1, true, false, "pg_get_viewdef_name", pg_get_viewdef_name }, + { 1641, 1, true, false, "pg_get_viewdef", pg_get_viewdef }, + { 1642, 1, true, false, "pg_get_userbyid", pg_get_userbyid }, + { 1643, 1, true, false, "pg_get_indexdef", pg_get_indexdef }, + { 1644, 0, true, false, "RI_FKey_check_ins", RI_FKey_check_ins }, + { 1645, 0, true, false, "RI_FKey_check_upd", RI_FKey_check_upd }, + { 1646, 0, true, false, "RI_FKey_cascade_del", RI_FKey_cascade_del }, + { 1647, 0, true, false, "RI_FKey_cascade_upd", RI_FKey_cascade_upd }, + { 1648, 0, true, false, "RI_FKey_restrict_del", RI_FKey_restrict_del }, + { 1649, 0, true, false, "RI_FKey_restrict_upd", RI_FKey_restrict_upd }, + { 1650, 0, true, false, "RI_FKey_setnull_del", RI_FKey_setnull_del }, + { 1651, 0, true, false, "RI_FKey_setnull_upd", RI_FKey_setnull_upd }, + { 1652, 0, true, false, "RI_FKey_setdefault_del", RI_FKey_setdefault_del }, + { 1653, 0, true, false, "RI_FKey_setdefault_upd", RI_FKey_setdefault_upd }, + { 1654, 0, true, false, "RI_FKey_noaction_del", RI_FKey_noaction_del }, + { 1655, 0, true, false, "RI_FKey_noaction_upd", RI_FKey_noaction_upd }, + { 1656, 2, true, false, "texticregexeq", texticregexeq }, + { 1657, 2, true, false, "texticregexne", texticregexne }, + { 1658, 2, true, false, "textregexeq", textregexeq }, + { 1659, 2, true, false, "textregexne", textregexne }, + { 1660, 2, true, false, "texticlike", texticlike }, + { 1661, 2, true, false, "texticnlike", texticnlike }, + { 1662, 1, true, false, "pg_get_triggerdef", pg_get_triggerdef }, + { 1665, 2, true, false, "pg_get_serial_sequence", pg_get_serial_sequence }, + { 1666, 2, true, false, "biteq", biteq }, + { 1667, 2, true, false, "bitne", bitne }, + { 1668, 2, true, false, "bitge", bitge }, + { 1669, 2, true, false, "bitgt", bitgt }, + { 1670, 2, true, false, "bitle", bitle }, + { 1671, 2, true, false, "bitlt", bitlt }, + { 1672, 2, true, false, "bitcmp", bitcmp }, + { 1673, 2, true, false, "bit_and", bit_and }, + { 1674, 2, true, false, "bit_or", bit_or }, + { 1675, 2, true, false, "bitxor", bitxor }, + { 1676, 1, true, false, "bitnot", bitnot }, + { 1677, 2, true, false, "bitshiftleft", bitshiftleft }, + { 1678, 2, true, false, "bitshiftright", bitshiftright }, + { 1679, 2, true, false, "bitcat", bitcat }, + { 1680, 3, true, false, "bitsubstr", bitsubstr }, + { 1681, 1, true, false, "bitlength", bitlength }, + { 1682, 1, true, false, "bitoctetlength", bitoctetlength }, + { 1683, 2, true, false, "bitfromint4", bitfromint4 }, + { 1684, 1, true, false, "bittoint4", bittoint4 }, + { 1685, 3, true, false, "bit", bit }, + { 1686, 0, true, true, "pg_get_keywords", pg_get_keywords }, + { 1687, 3, true, false, "varbit", varbit }, + { 1688, 1, true, false, "time_hash", time_hash }, + { 1689, 1, true, true, "aclexplode", aclexplode }, + { 1690, 2, true, false, "time_mi_time", time_mi_time }, + { 1691, 2, true, false, "boolle", boolle }, + { 1692, 2, true, false, "boolge", boolge }, + { 1693, 2, true, false, "btboolcmp", btboolcmp }, + { 1696, 1, true, false, "timetz_hash", timetz_hash }, + { 1697, 1, true, false, "interval_hash", interval_hash }, + { 1698, 2, true, false, "bitposition", bitposition }, + { 1699, 2, true, false, "bitsubstr_no_len", bitsubstr_no_len }, + { 1701, 3, true, false, "numeric_in", numeric_in }, + { 1702, 1, true, false, "numeric_out", numeric_out }, + { 1703, 2, true, false, "numeric", numeric }, + { 1704, 1, true, false, "numeric_abs", numeric_abs }, + { 1705, 1, true, false, "numeric_abs", numeric_abs }, + { 1706, 1, true, false, "numeric_sign", numeric_sign }, + { 1707, 2, true, false, "numeric_round", numeric_round }, + { 1709, 2, true, false, "numeric_trunc", numeric_trunc }, + { 1711, 1, true, false, "numeric_ceil", numeric_ceil }, + { 1712, 1, true, false, "numeric_floor", numeric_floor }, + { 1713, 2, true, false, "length_in_encoding", length_in_encoding }, + { 1714, 2, true, false, "pg_convert_from", pg_convert_from }, + { 1715, 1, true, false, "inet_to_cidr", inet_to_cidr }, + { 1716, 2, true, false, "pg_get_expr", pg_get_expr }, + { 1717, 2, true, false, "pg_convert_to", pg_convert_to }, + { 1718, 2, true, false, "numeric_eq", numeric_eq }, + { 1719, 2, true, false, "numeric_ne", numeric_ne }, + { 1720, 2, true, false, "numeric_gt", numeric_gt }, + { 1721, 2, true, false, "numeric_ge", numeric_ge }, + { 1722, 2, true, false, "numeric_lt", numeric_lt }, + { 1723, 2, true, false, "numeric_le", numeric_le }, + { 1724, 2, true, false, "numeric_add", numeric_add }, + { 1725, 2, true, false, "numeric_sub", numeric_sub }, + { 1726, 2, true, false, "numeric_mul", numeric_mul }, + { 1727, 2, true, false, "numeric_div", numeric_div }, + { 1728, 2, true, false, "numeric_mod", numeric_mod }, + { 1729, 2, true, false, "numeric_mod", numeric_mod }, + { 1730, 1, true, false, "numeric_sqrt", numeric_sqrt }, + { 1731, 1, true, false, "numeric_sqrt", numeric_sqrt }, + { 1732, 1, true, false, "numeric_exp", numeric_exp }, + { 1733, 1, true, false, "numeric_exp", numeric_exp }, + { 1734, 1, true, false, "numeric_ln", numeric_ln }, + { 1735, 1, true, false, "numeric_ln", numeric_ln }, + { 1736, 2, true, false, "numeric_log", numeric_log }, + { 1737, 2, true, false, "numeric_log", numeric_log }, + { 1738, 2, true, false, "numeric_power", numeric_power }, + { 1739, 2, true, false, "numeric_power", numeric_power }, + { 1740, 1, true, false, "int4_numeric", int4_numeric }, + { 1742, 1, true, false, "float4_numeric", float4_numeric }, + { 1743, 1, true, false, "float8_numeric", float8_numeric }, + { 1744, 1, true, false, "numeric_int4", numeric_int4 }, + { 1745, 1, true, false, "numeric_float4", numeric_float4 }, + { 1746, 1, true, false, "numeric_float8", numeric_float8 }, + { 1747, 2, true, false, "time_pl_interval", time_pl_interval }, + { 1748, 2, true, false, "time_mi_interval", time_mi_interval }, + { 1749, 2, true, false, "timetz_pl_interval", timetz_pl_interval }, + { 1750, 2, true, false, "timetz_mi_interval", timetz_mi_interval }, + { 1764, 1, true, false, "numeric_inc", numeric_inc }, + { 1765, 3, true, false, "setval3_oid", setval3_oid }, + { 1766, 2, true, false, "numeric_smaller", numeric_smaller }, + { 1767, 2, true, false, "numeric_larger", numeric_larger }, + { 1768, 2, true, false, "interval_to_char", interval_to_char }, + { 1769, 2, true, false, "numeric_cmp", numeric_cmp }, + { 1770, 2, true, false, "timestamptz_to_char", timestamptz_to_char }, + { 1771, 1, true, false, "numeric_uminus", numeric_uminus }, + { 1772, 2, true, false, "numeric_to_char", numeric_to_char }, + { 1773, 2, true, false, "int4_to_char", int4_to_char }, + { 1774, 2, true, false, "int8_to_char", int8_to_char }, + { 1775, 2, true, false, "float4_to_char", float4_to_char }, + { 1776, 2, true, false, "float8_to_char", float8_to_char }, + { 1777, 2, true, false, "numeric_to_number", numeric_to_number }, + { 1778, 2, true, false, "to_timestamp", to_timestamp }, + { 1779, 1, true, false, "numeric_int8", numeric_int8 }, + { 1780, 2, true, false, "to_date", to_date }, + { 1781, 1, true, false, "int8_numeric", int8_numeric }, + { 1782, 1, true, false, "int2_numeric", int2_numeric }, + { 1783, 1, true, false, "numeric_int2", numeric_int2 }, + { 1798, 1, true, false, "oidin", oidin }, + { 1799, 1, true, false, "oidout", oidout }, + { 1813, 3, true, false, "pg_convert", pg_convert }, + { 1814, 4, true, false, "iclikesel", iclikesel }, + { 1815, 4, true, false, "icnlikesel", icnlikesel }, + { 1816, 5, true, false, "iclikejoinsel", iclikejoinsel }, + { 1817, 5, true, false, "icnlikejoinsel", icnlikejoinsel }, + { 1818, 4, true, false, "regexeqsel", regexeqsel }, + { 1819, 4, true, false, "likesel", likesel }, + { 1820, 4, true, false, "icregexeqsel", icregexeqsel }, + { 1821, 4, true, false, "regexnesel", regexnesel }, + { 1822, 4, true, false, "nlikesel", nlikesel }, + { 1823, 4, true, false, "icregexnesel", icregexnesel }, + { 1824, 5, true, false, "regexeqjoinsel", regexeqjoinsel }, + { 1825, 5, true, false, "likejoinsel", likejoinsel }, + { 1826, 5, true, false, "icregexeqjoinsel", icregexeqjoinsel }, + { 1827, 5, true, false, "regexnejoinsel", regexnejoinsel }, + { 1828, 5, true, false, "nlikejoinsel", nlikejoinsel }, + { 1829, 5, true, false, "icregexnejoinsel", icregexnejoinsel }, + { 1830, 1, true, false, "float8_avg", float8_avg }, + { 1831, 1, true, false, "float8_var_samp", float8_var_samp }, + { 1832, 1, true, false, "float8_stddev_samp", float8_stddev_samp }, + { 1833, 2, false, false, "numeric_accum", numeric_accum }, + { 1834, 2, false, false, "int2_accum", int2_accum }, + { 1835, 2, false, false, "int4_accum", int4_accum }, + { 1836, 2, false, false, "int8_accum", int8_accum }, + { 1837, 1, false, false, "numeric_avg", numeric_avg }, + { 1838, 1, false, false, "numeric_var_samp", numeric_var_samp }, + { 1839, 1, false, false, "numeric_stddev_samp", numeric_stddev_samp }, + { 1840, 2, false, false, "int2_sum", int2_sum }, + { 1841, 2, false, false, "int4_sum", int4_sum }, + { 1842, 2, false, false, "int8_sum", int8_sum }, + { 1843, 2, true, false, "interval_accum", interval_accum }, + { 1844, 1, true, false, "interval_avg", interval_avg }, + { 1845, 1, true, false, "to_ascii_default", to_ascii_default }, + { 1846, 2, true, false, "to_ascii_enc", to_ascii_enc }, + { 1847, 2, true, false, "to_ascii_encname", to_ascii_encname }, + { 1850, 2, true, false, "int28eq", int28eq }, + { 1851, 2, true, false, "int28ne", int28ne }, + { 1852, 2, true, false, "int28lt", int28lt }, + { 1853, 2, true, false, "int28gt", int28gt }, + { 1854, 2, true, false, "int28le", int28le }, + { 1855, 2, true, false, "int28ge", int28ge }, + { 1856, 2, true, false, "int82eq", int82eq }, + { 1857, 2, true, false, "int82ne", int82ne }, + { 1858, 2, true, false, "int82lt", int82lt }, + { 1859, 2, true, false, "int82gt", int82gt }, + { 1860, 2, true, false, "int82le", int82le }, + { 1861, 2, true, false, "int82ge", int82ge }, + { 1892, 2, true, false, "int2and", int2and }, + { 1893, 2, true, false, "int2or", int2or }, + { 1894, 2, true, false, "int2xor", int2xor }, + { 1895, 1, true, false, "int2not", int2not }, + { 1896, 2, true, false, "int2shl", int2shl }, + { 1897, 2, true, false, "int2shr", int2shr }, + { 1898, 2, true, false, "int4and", int4and }, + { 1899, 2, true, false, "int4or", int4or }, + { 1900, 2, true, false, "int4xor", int4xor }, + { 1901, 1, true, false, "int4not", int4not }, + { 1902, 2, true, false, "int4shl", int4shl }, + { 1903, 2, true, false, "int4shr", int4shr }, + { 1904, 2, true, false, "int8and", int8and }, + { 1905, 2, true, false, "int8or", int8or }, + { 1906, 2, true, false, "int8xor", int8xor }, + { 1907, 1, true, false, "int8not", int8not }, + { 1908, 2, true, false, "int8shl", int8shl }, + { 1909, 2, true, false, "int8shr", int8shr }, + { 1910, 1, true, false, "int8up", int8up }, + { 1911, 1, true, false, "int2up", int2up }, + { 1912, 1, true, false, "int4up", int4up }, + { 1913, 1, true, false, "float4up", float4up }, + { 1914, 1, true, false, "float8up", float8up }, + { 1915, 1, true, false, "numeric_uplus", numeric_uplus }, + { 1922, 3, true, false, "has_table_privilege_name_name", has_table_privilege_name_name }, + { 1923, 3, true, false, "has_table_privilege_name_id", has_table_privilege_name_id }, + { 1924, 3, true, false, "has_table_privilege_id_name", has_table_privilege_id_name }, + { 1925, 3, true, false, "has_table_privilege_id_id", has_table_privilege_id_id }, + { 1926, 2, true, false, "has_table_privilege_name", has_table_privilege_name }, + { 1927, 2, true, false, "has_table_privilege_id", has_table_privilege_id }, + { 1928, 1, true, false, "pg_stat_get_numscans", pg_stat_get_numscans }, + { 1929, 1, true, false, "pg_stat_get_tuples_returned", pg_stat_get_tuples_returned }, + { 1930, 1, true, false, "pg_stat_get_tuples_fetched", pg_stat_get_tuples_fetched }, + { 1931, 1, true, false, "pg_stat_get_tuples_inserted", pg_stat_get_tuples_inserted }, + { 1932, 1, true, false, "pg_stat_get_tuples_updated", pg_stat_get_tuples_updated }, + { 1933, 1, true, false, "pg_stat_get_tuples_deleted", pg_stat_get_tuples_deleted }, + { 1934, 1, true, false, "pg_stat_get_blocks_fetched", pg_stat_get_blocks_fetched }, + { 1935, 1, true, false, "pg_stat_get_blocks_hit", pg_stat_get_blocks_hit }, + { 1936, 0, true, true, "pg_stat_get_backend_idset", pg_stat_get_backend_idset }, + { 1937, 1, true, false, "pg_stat_get_backend_pid", pg_stat_get_backend_pid }, + { 1938, 1, true, false, "pg_stat_get_backend_dbid", pg_stat_get_backend_dbid }, + { 1939, 1, true, false, "pg_stat_get_backend_userid", pg_stat_get_backend_userid }, + { 1940, 1, true, false, "pg_stat_get_backend_activity", pg_stat_get_backend_activity }, + { 1941, 1, true, false, "pg_stat_get_db_numbackends", pg_stat_get_db_numbackends }, + { 1942, 1, true, false, "pg_stat_get_db_xact_commit", pg_stat_get_db_xact_commit }, + { 1943, 1, true, false, "pg_stat_get_db_xact_rollback", pg_stat_get_db_xact_rollback }, + { 1944, 1, true, false, "pg_stat_get_db_blocks_fetched", pg_stat_get_db_blocks_fetched }, + { 1945, 1, true, false, "pg_stat_get_db_blocks_hit", pg_stat_get_db_blocks_hit }, + { 1946, 2, true, false, "binary_encode", binary_encode }, + { 1947, 2, true, false, "binary_decode", binary_decode }, + { 1948, 2, true, false, "byteaeq", byteaeq }, + { 1949, 2, true, false, "bytealt", bytealt }, + { 1950, 2, true, false, "byteale", byteale }, + { 1951, 2, true, false, "byteagt", byteagt }, + { 1952, 2, true, false, "byteage", byteage }, + { 1953, 2, true, false, "byteane", byteane }, + { 1954, 2, true, false, "byteacmp", byteacmp }, + { 1961, 2, true, false, "timestamp_scale", timestamp_scale }, + { 1962, 2, true, false, "int2_avg_accum", int2_avg_accum }, + { 1963, 2, true, false, "int4_avg_accum", int4_avg_accum }, + { 1964, 1, true, false, "int8_avg", int8_avg }, + { 1965, 2, true, false, "oidlarger", oidlarger }, + { 1966, 2, true, false, "oidsmaller", oidsmaller }, + { 1967, 2, true, false, "timestamptz_scale", timestamptz_scale }, + { 1968, 2, true, false, "time_scale", time_scale }, + { 1969, 2, true, false, "timetz_scale", timetz_scale }, + { 1972, 1, true, false, "pg_stat_get_tuples_hot_updated", pg_stat_get_tuples_hot_updated }, + { 1973, 2, true, false, "numeric_div_trunc", numeric_div_trunc }, + { 1980, 2, true, false, "numeric_div_trunc", numeric_div_trunc }, + { 1986, 2, true, false, "similar_to_escape_2", similar_to_escape_2 }, + { 1987, 1, true, false, "similar_to_escape_1", similar_to_escape_1 }, + { 2005, 2, true, false, "bytealike", bytealike }, + { 2006, 2, true, false, "byteanlike", byteanlike }, + { 2007, 2, true, false, "bytealike", bytealike }, + { 2008, 2, true, false, "byteanlike", byteanlike }, + { 2009, 2, true, false, "like_escape_bytea", like_escape_bytea }, + { 2010, 1, true, false, "byteaoctetlen", byteaoctetlen }, + { 2011, 2, true, false, "byteacat", byteacat }, + { 2012, 3, true, false, "bytea_substr", bytea_substr }, + { 2013, 2, true, false, "bytea_substr_no_len", bytea_substr_no_len }, + { 2014, 2, true, false, "byteapos", byteapos }, + { 2015, 2, true, false, "byteatrim", byteatrim }, + { 2019, 1, true, false, "timestamptz_time", timestamptz_time }, + { 2020, 2, true, false, "timestamp_trunc", timestamp_trunc }, + { 2021, 2, true, false, "timestamp_part", timestamp_part }, + { 2022, 1, false, true, "pg_stat_get_activity", pg_stat_get_activity }, + { 2023, 4, true, false, "jsonb_path_query_first_tz", jsonb_path_query_first_tz }, + { 2024, 1, true, false, "date_timestamp", date_timestamp }, + { 2025, 2, true, false, "datetime_timestamp", datetime_timestamp }, + { 2026, 0, true, false, "pg_backend_pid", pg_backend_pid }, + { 2027, 1, true, false, "timestamptz_timestamp", timestamptz_timestamp }, + { 2028, 1, true, false, "timestamp_timestamptz", timestamp_timestamptz }, + { 2029, 1, true, false, "timestamp_date", timestamp_date }, + { 2030, 4, true, false, "jsonb_path_match_tz", jsonb_path_match_tz }, + { 2031, 2, true, false, "timestamp_mi", timestamp_mi }, + { 2032, 2, true, false, "timestamp_pl_interval", timestamp_pl_interval }, + { 2033, 2, true, false, "timestamp_mi_interval", timestamp_mi_interval }, + { 2034, 0, true, false, "pg_conf_load_time", pg_conf_load_time }, + { 2035, 2, true, false, "timestamp_smaller", timestamp_smaller }, + { 2036, 2, true, false, "timestamp_larger", timestamp_larger }, + { 2037, 2, true, false, "timetz_zone", timetz_zone }, + { 2038, 2, true, false, "timetz_izone", timetz_izone }, + { 2039, 1, true, false, "timestamp_hash", timestamp_hash }, + { 2041, 4, false, false, "overlaps_timestamp", overlaps_timestamp }, + { 2045, 2, true, false, "timestamp_cmp", timestamp_cmp }, + { 2046, 1, true, false, "timetz_time", timetz_time }, + { 2047, 1, true, false, "time_timetz", time_timetz }, + { 2048, 1, true, false, "timestamp_finite", timestamp_finite }, + { 2049, 2, true, false, "timestamp_to_char", timestamp_to_char }, + { 2052, 2, true, false, "timestamp_eq", timestamp_eq }, + { 2053, 2, true, false, "timestamp_ne", timestamp_ne }, + { 2054, 2, true, false, "timestamp_lt", timestamp_lt }, + { 2055, 2, true, false, "timestamp_le", timestamp_le }, + { 2056, 2, true, false, "timestamp_ge", timestamp_ge }, + { 2057, 2, true, false, "timestamp_gt", timestamp_gt }, + { 2058, 2, true, false, "timestamp_age", timestamp_age }, + { 2069, 2, true, false, "timestamp_zone", timestamp_zone }, + { 2070, 2, true, false, "timestamp_izone", timestamp_izone }, + { 2071, 2, true, false, "date_pl_interval", date_pl_interval }, + { 2072, 2, true, false, "date_mi_interval", date_mi_interval }, + { 2073, 2, true, false, "textregexsubstr", textregexsubstr }, + { 2075, 2, true, false, "bitfromint8", bitfromint8 }, + { 2076, 1, true, false, "bittoint8", bittoint8 }, + { 2077, 1, true, false, "show_config_by_name", show_config_by_name }, + { 2078, 3, false, false, "set_config_by_name", set_config_by_name }, + { 2079, 1, true, false, "pg_table_is_visible", pg_table_is_visible }, + { 2080, 1, true, false, "pg_type_is_visible", pg_type_is_visible }, + { 2081, 1, true, false, "pg_function_is_visible", pg_function_is_visible }, + { 2082, 1, true, false, "pg_operator_is_visible", pg_operator_is_visible }, + { 2083, 1, true, false, "pg_opclass_is_visible", pg_opclass_is_visible }, + { 2084, 0, true, true, "show_all_settings", show_all_settings }, + { 2085, 3, true, false, "bytea_substr", bytea_substr }, + { 2086, 2, true, false, "bytea_substr_no_len", bytea_substr_no_len }, + { 2087, 3, true, false, "replace_text", replace_text }, + { 2088, 3, true, false, "split_part", split_part }, + { 2089, 1, true, false, "to_hex32", to_hex32 }, + { 2090, 1, true, false, "to_hex64", to_hex64 }, + { 2091, 2, true, false, "array_lower", array_lower }, + { 2092, 2, true, false, "array_upper", array_upper }, + { 2093, 1, true, false, "pg_conversion_is_visible", pg_conversion_is_visible }, + { 2094, 1, true, false, "pg_stat_get_backend_activity_start", pg_stat_get_backend_activity_start }, + { 2096, 2, true, false, "pg_terminate_backend", pg_terminate_backend }, + { 2098, 1, true, false, "pg_get_functiondef", pg_get_functiondef }, + { 2121, 1, true, false, "pg_column_compression", pg_column_compression }, + { 2137, 0, false, false, "pg_stat_force_next_flush", pg_stat_force_next_flush }, + { 2160, 2, true, false, "text_pattern_lt", text_pattern_lt }, + { 2161, 2, true, false, "text_pattern_le", text_pattern_le }, + { 2162, 1, true, false, "pg_get_function_arguments", pg_get_function_arguments }, + { 2163, 2, true, false, "text_pattern_ge", text_pattern_ge }, + { 2164, 2, true, false, "text_pattern_gt", text_pattern_gt }, + { 2165, 1, true, false, "pg_get_function_result", pg_get_function_result }, + { 2166, 2, true, false, "bttext_pattern_cmp", bttext_pattern_cmp }, + { 2167, 1, true, false, "numeric_ceil", numeric_ceil }, + { 2168, 1, true, false, "pg_database_size_name", pg_database_size_name }, + { 2169, 2, true, false, "numeric_power", numeric_power }, + { 2170, 4, true, false, "width_bucket_numeric", width_bucket_numeric }, + { 2171, 1, true, false, "pg_cancel_backend", pg_cancel_backend }, + { 2172, 2, true, false, "pg_backup_start", pg_backup_start }, + { 2174, 2, true, false, "bpchar_pattern_lt", bpchar_pattern_lt }, + { 2175, 2, true, false, "bpchar_pattern_le", bpchar_pattern_le }, + { 2176, 2, true, false, "array_length", array_length }, + { 2177, 2, true, false, "bpchar_pattern_ge", bpchar_pattern_ge }, + { 2178, 2, true, false, "bpchar_pattern_gt", bpchar_pattern_gt }, + { 2179, 5, true, false, "gist_point_consistent", gist_point_consistent }, + { 2180, 2, true, false, "btbpchar_pattern_cmp", btbpchar_pattern_cmp }, + { 2181, 3, true, false, "has_sequence_privilege_name_name", has_sequence_privilege_name_name }, + { 2182, 3, true, false, "has_sequence_privilege_name_id", has_sequence_privilege_name_id }, + { 2183, 3, true, false, "has_sequence_privilege_id_name", has_sequence_privilege_id_name }, + { 2184, 3, true, false, "has_sequence_privilege_id_id", has_sequence_privilege_id_id }, + { 2185, 2, true, false, "has_sequence_privilege_name", has_sequence_privilege_name }, + { 2186, 2, true, false, "has_sequence_privilege_id", has_sequence_privilege_id }, + { 2188, 2, true, false, "btint48cmp", btint48cmp }, + { 2189, 2, true, false, "btint84cmp", btint84cmp }, + { 2190, 2, true, false, "btint24cmp", btint24cmp }, + { 2191, 2, true, false, "btint42cmp", btint42cmp }, + { 2192, 2, true, false, "btint28cmp", btint28cmp }, + { 2193, 2, true, false, "btint82cmp", btint82cmp }, + { 2194, 2, true, false, "btfloat48cmp", btfloat48cmp }, + { 2195, 2, true, false, "btfloat84cmp", btfloat84cmp }, + { 2196, 0, false, false, "inet_client_addr", inet_client_addr }, + { 2197, 0, false, false, "inet_client_port", inet_client_port }, + { 2198, 0, false, false, "inet_server_addr", inet_server_addr }, + { 2199, 0, false, false, "inet_server_port", inet_server_port }, + { 2212, 1, true, false, "regprocedurein", regprocedurein }, + { 2213, 1, true, false, "regprocedureout", regprocedureout }, + { 2214, 1, true, false, "regoperin", regoperin }, + { 2215, 1, true, false, "regoperout", regoperout }, + { 2216, 1, true, false, "regoperatorin", regoperatorin }, + { 2217, 1, true, false, "regoperatorout", regoperatorout }, + { 2218, 1, true, false, "regclassin", regclassin }, + { 2219, 1, true, false, "regclassout", regclassout }, + { 2220, 1, true, false, "regtypein", regtypein }, + { 2221, 1, true, false, "regtypeout", regtypeout }, + { 2230, 0, false, false, "pg_stat_clear_snapshot", pg_stat_clear_snapshot }, + { 2232, 1, true, false, "pg_get_function_identity_arguments", pg_get_function_identity_arguments }, + { 2233, 1, true, false, "hashtid", hashtid }, + { 2234, 2, true, false, "hashtidextended", hashtidextended }, + { 2246, 1, true, false, "fmgr_internal_validator", fmgr_internal_validator }, + { 2247, 1, true, false, "fmgr_c_validator", fmgr_c_validator }, + { 2248, 1, true, false, "fmgr_sql_validator", fmgr_sql_validator }, + { 2250, 3, true, false, "has_database_privilege_name_name", has_database_privilege_name_name }, + { 2251, 3, true, false, "has_database_privilege_name_id", has_database_privilege_name_id }, + { 2252, 3, true, false, "has_database_privilege_id_name", has_database_privilege_id_name }, + { 2253, 3, true, false, "has_database_privilege_id_id", has_database_privilege_id_id }, + { 2254, 2, true, false, "has_database_privilege_name", has_database_privilege_name }, + { 2255, 2, true, false, "has_database_privilege_id", has_database_privilege_id }, + { 2256, 3, true, false, "has_function_privilege_name_name", has_function_privilege_name_name }, + { 2257, 3, true, false, "has_function_privilege_name_id", has_function_privilege_name_id }, + { 2258, 3, true, false, "has_function_privilege_id_name", has_function_privilege_id_name }, + { 2259, 3, true, false, "has_function_privilege_id_id", has_function_privilege_id_id }, + { 2260, 2, true, false, "has_function_privilege_name", has_function_privilege_name }, + { 2261, 2, true, false, "has_function_privilege_id", has_function_privilege_id }, + { 2262, 3, true, false, "has_language_privilege_name_name", has_language_privilege_name_name }, + { 2263, 3, true, false, "has_language_privilege_name_id", has_language_privilege_name_id }, + { 2264, 3, true, false, "has_language_privilege_id_name", has_language_privilege_id_name }, + { 2265, 3, true, false, "has_language_privilege_id_id", has_language_privilege_id_id }, + { 2266, 2, true, false, "has_language_privilege_name", has_language_privilege_name }, + { 2267, 2, true, false, "has_language_privilege_id", has_language_privilege_id }, + { 2268, 3, true, false, "has_schema_privilege_name_name", has_schema_privilege_name_name }, + { 2269, 3, true, false, "has_schema_privilege_name_id", has_schema_privilege_name_id }, + { 2270, 3, true, false, "has_schema_privilege_id_name", has_schema_privilege_id_name }, + { 2271, 3, true, false, "has_schema_privilege_id_id", has_schema_privilege_id_id }, + { 2272, 2, true, false, "has_schema_privilege_name", has_schema_privilege_name }, + { 2273, 2, true, false, "has_schema_privilege_id", has_schema_privilege_id }, + { 2274, 0, false, false, "pg_stat_reset", pg_stat_reset }, + { 2282, 0, true, true, "pg_get_backend_memory_contexts", pg_get_backend_memory_contexts }, + { 2284, 3, true, false, "textregexreplace_noopt", textregexreplace_noopt }, + { 2285, 4, true, false, "textregexreplace", textregexreplace }, + { 2286, 1, true, false, "pg_total_relation_size", pg_total_relation_size }, + { 2288, 1, true, false, "pg_size_pretty", pg_size_pretty }, + { 2289, 1, true, true, "pg_options_to_table", pg_options_to_table }, + { 2290, 3, true, false, "record_in", record_in }, + { 2291, 1, true, false, "record_out", record_out }, + { 2292, 1, true, false, "cstring_in", cstring_in }, + { 2293, 1, true, false, "cstring_out", cstring_out }, + { 2294, 1, true, false, "any_in", any_in }, + { 2295, 1, true, false, "any_out", any_out }, + { 2296, 1, true, false, "anyarray_in", anyarray_in }, + { 2297, 1, true, false, "anyarray_out", anyarray_out }, + { 2298, 1, true, false, "void_in", void_in }, + { 2299, 1, true, false, "void_out", void_out }, + { 2300, 1, false, false, "trigger_in", trigger_in }, + { 2301, 1, true, false, "trigger_out", trigger_out }, + { 2302, 1, false, false, "language_handler_in", language_handler_in }, + { 2303, 1, true, false, "language_handler_out", language_handler_out }, + { 2304, 1, false, false, "internal_in", internal_in }, + { 2305, 1, true, false, "internal_out", internal_out }, + { 2306, 0, false, true, "pg_stat_get_slru", pg_stat_get_slru }, + { 2307, 1, false, false, "pg_stat_reset_slru", pg_stat_reset_slru }, + { 2308, 1, true, false, "dceil", dceil }, + { 2309, 1, true, false, "dfloor", dfloor }, + { 2310, 1, true, false, "dsign", dsign }, + { 2311, 1, true, false, "md5_text", md5_text }, + { 2312, 1, true, false, "anyelement_in", anyelement_in }, + { 2313, 1, true, false, "anyelement_out", anyelement_out }, + { 2316, 2, true, false, "postgresql_fdw_validator", postgresql_fdw_validator }, + { 2319, 1, true, false, "pg_encoding_max_length_sql", pg_encoding_max_length_sql }, + { 2320, 1, true, false, "dceil", dceil }, + { 2321, 1, true, false, "md5_bytea", md5_bytea }, + { 2322, 1, true, false, "pg_tablespace_size_oid", pg_tablespace_size_oid }, + { 2323, 1, true, false, "pg_tablespace_size_name", pg_tablespace_size_name }, + { 2324, 1, true, false, "pg_database_size_oid", pg_database_size_oid }, + { 2331, 1, true, true, "array_unnest", array_unnest }, + { 2332, 2, true, false, "pg_relation_size", pg_relation_size }, + { 2333, 2, false, false, "array_agg_transfn", array_agg_transfn }, + { 2334, 2, false, false, "array_agg_finalfn", array_agg_finalfn }, + { 2338, 2, true, false, "date_lt_timestamp", date_lt_timestamp }, + { 2339, 2, true, false, "date_le_timestamp", date_le_timestamp }, + { 2340, 2, true, false, "date_eq_timestamp", date_eq_timestamp }, + { 2341, 2, true, false, "date_gt_timestamp", date_gt_timestamp }, + { 2342, 2, true, false, "date_ge_timestamp", date_ge_timestamp }, + { 2343, 2, true, false, "date_ne_timestamp", date_ne_timestamp }, + { 2344, 2, true, false, "date_cmp_timestamp", date_cmp_timestamp }, + { 2351, 2, true, false, "date_lt_timestamptz", date_lt_timestamptz }, + { 2352, 2, true, false, "date_le_timestamptz", date_le_timestamptz }, + { 2353, 2, true, false, "date_eq_timestamptz", date_eq_timestamptz }, + { 2354, 2, true, false, "date_gt_timestamptz", date_gt_timestamptz }, + { 2355, 2, true, false, "date_ge_timestamptz", date_ge_timestamptz }, + { 2356, 2, true, false, "date_ne_timestamptz", date_ne_timestamptz }, + { 2357, 2, true, false, "date_cmp_timestamptz", date_cmp_timestamptz }, + { 2364, 2, true, false, "timestamp_lt_date", timestamp_lt_date }, + { 2365, 2, true, false, "timestamp_le_date", timestamp_le_date }, + { 2366, 2, true, false, "timestamp_eq_date", timestamp_eq_date }, + { 2367, 2, true, false, "timestamp_gt_date", timestamp_gt_date }, + { 2368, 2, true, false, "timestamp_ge_date", timestamp_ge_date }, + { 2369, 2, true, false, "timestamp_ne_date", timestamp_ne_date }, + { 2370, 2, true, false, "timestamp_cmp_date", timestamp_cmp_date }, + { 2377, 2, true, false, "timestamptz_lt_date", timestamptz_lt_date }, + { 2378, 2, true, false, "timestamptz_le_date", timestamptz_le_date }, + { 2379, 2, true, false, "timestamptz_eq_date", timestamptz_eq_date }, + { 2380, 2, true, false, "timestamptz_gt_date", timestamptz_gt_date }, + { 2381, 2, true, false, "timestamptz_ge_date", timestamptz_ge_date }, + { 2382, 2, true, false, "timestamptz_ne_date", timestamptz_ne_date }, + { 2383, 2, true, false, "timestamptz_cmp_date", timestamptz_cmp_date }, + { 2390, 3, true, false, "has_tablespace_privilege_name_name", has_tablespace_privilege_name_name }, + { 2391, 3, true, false, "has_tablespace_privilege_name_id", has_tablespace_privilege_name_id }, + { 2392, 3, true, false, "has_tablespace_privilege_id_name", has_tablespace_privilege_id_name }, + { 2393, 3, true, false, "has_tablespace_privilege_id_id", has_tablespace_privilege_id_id }, + { 2394, 2, true, false, "has_tablespace_privilege_name", has_tablespace_privilege_name }, + { 2395, 2, true, false, "has_tablespace_privilege_id", has_tablespace_privilege_id }, + { 2398, 1, false, false, "shell_in", shell_in }, + { 2399, 1, true, false, "shell_out", shell_out }, + { 2400, 3, true, false, "array_recv", array_recv }, + { 2401, 1, true, false, "array_send", array_send }, + { 2402, 3, true, false, "record_recv", record_recv }, + { 2403, 1, true, false, "record_send", record_send }, + { 2404, 1, true, false, "int2recv", int2recv }, + { 2405, 1, true, false, "int2send", int2send }, + { 2406, 1, true, false, "int4recv", int4recv }, + { 2407, 1, true, false, "int4send", int4send }, + { 2408, 1, true, false, "int8recv", int8recv }, + { 2409, 1, true, false, "int8send", int8send }, + { 2410, 1, true, false, "int2vectorrecv", int2vectorrecv }, + { 2411, 1, true, false, "int2vectorsend", int2vectorsend }, + { 2412, 1, true, false, "bytearecv", bytearecv }, + { 2413, 1, true, false, "byteasend", byteasend }, + { 2414, 1, true, false, "textrecv", textrecv }, + { 2415, 1, true, false, "textsend", textsend }, + { 2416, 1, true, false, "unknownrecv", unknownrecv }, + { 2417, 1, true, false, "unknownsend", unknownsend }, + { 2418, 1, true, false, "oidrecv", oidrecv }, + { 2419, 1, true, false, "oidsend", oidsend }, + { 2420, 1, true, false, "oidvectorrecv", oidvectorrecv }, + { 2421, 1, true, false, "oidvectorsend", oidvectorsend }, + { 2422, 1, true, false, "namerecv", namerecv }, + { 2423, 1, true, false, "namesend", namesend }, + { 2424, 1, true, false, "float4recv", float4recv }, + { 2425, 1, true, false, "float4send", float4send }, + { 2426, 1, true, false, "float8recv", float8recv }, + { 2427, 1, true, false, "float8send", float8send }, + { 2428, 1, true, false, "point_recv", point_recv }, + { 2429, 1, true, false, "point_send", point_send }, + { 2430, 3, true, false, "bpcharrecv", bpcharrecv }, + { 2431, 1, true, false, "bpcharsend", bpcharsend }, + { 2432, 3, true, false, "varcharrecv", varcharrecv }, + { 2433, 1, true, false, "varcharsend", varcharsend }, + { 2434, 1, true, false, "charrecv", charrecv }, + { 2435, 1, true, false, "charsend", charsend }, + { 2436, 1, true, false, "boolrecv", boolrecv }, + { 2437, 1, true, false, "boolsend", boolsend }, + { 2438, 1, true, false, "tidrecv", tidrecv }, + { 2439, 1, true, false, "tidsend", tidsend }, + { 2440, 1, true, false, "xidrecv", xidrecv }, + { 2441, 1, true, false, "xidsend", xidsend }, + { 2442, 1, true, false, "cidrecv", cidrecv }, + { 2443, 1, true, false, "cidsend", cidsend }, + { 2444, 1, true, false, "regprocrecv", regprocrecv }, + { 2445, 1, true, false, "regprocsend", regprocsend }, + { 2446, 1, true, false, "regprocedurerecv", regprocedurerecv }, + { 2447, 1, true, false, "regproceduresend", regproceduresend }, + { 2448, 1, true, false, "regoperrecv", regoperrecv }, + { 2449, 1, true, false, "regopersend", regopersend }, + { 2450, 1, true, false, "regoperatorrecv", regoperatorrecv }, + { 2451, 1, true, false, "regoperatorsend", regoperatorsend }, + { 2452, 1, true, false, "regclassrecv", regclassrecv }, + { 2453, 1, true, false, "regclasssend", regclasssend }, + { 2454, 1, true, false, "regtyperecv", regtyperecv }, + { 2455, 1, true, false, "regtypesend", regtypesend }, + { 2456, 3, true, false, "bit_recv", bit_recv }, + { 2457, 1, true, false, "bit_send", bit_send }, + { 2458, 3, true, false, "varbit_recv", varbit_recv }, + { 2459, 1, true, false, "varbit_send", varbit_send }, + { 2460, 3, true, false, "numeric_recv", numeric_recv }, + { 2461, 1, true, false, "numeric_send", numeric_send }, + { 2462, 1, true, false, "dsinh", dsinh }, + { 2463, 1, true, false, "dcosh", dcosh }, + { 2464, 1, true, false, "dtanh", dtanh }, + { 2465, 1, true, false, "dasinh", dasinh }, + { 2466, 1, true, false, "dacosh", dacosh }, + { 2467, 1, true, false, "datanh", datanh }, + { 2468, 1, true, false, "date_recv", date_recv }, + { 2469, 1, true, false, "date_send", date_send }, + { 2470, 3, true, false, "time_recv", time_recv }, + { 2471, 1, true, false, "time_send", time_send }, + { 2472, 3, true, false, "timetz_recv", timetz_recv }, + { 2473, 1, true, false, "timetz_send", timetz_send }, + { 2474, 3, true, false, "timestamp_recv", timestamp_recv }, + { 2475, 1, true, false, "timestamp_send", timestamp_send }, + { 2476, 3, true, false, "timestamptz_recv", timestamptz_recv }, + { 2477, 1, true, false, "timestamptz_send", timestamptz_send }, + { 2478, 3, true, false, "interval_recv", interval_recv }, + { 2479, 1, true, false, "interval_send", interval_send }, + { 2480, 1, true, false, "lseg_recv", lseg_recv }, + { 2481, 1, true, false, "lseg_send", lseg_send }, + { 2482, 1, true, false, "path_recv", path_recv }, + { 2483, 1, true, false, "path_send", path_send }, + { 2484, 1, true, false, "box_recv", box_recv }, + { 2485, 1, true, false, "box_send", box_send }, + { 2486, 1, true, false, "poly_recv", poly_recv }, + { 2487, 1, true, false, "poly_send", poly_send }, + { 2488, 1, true, false, "line_recv", line_recv }, + { 2489, 1, true, false, "line_send", line_send }, + { 2490, 1, true, false, "circle_recv", circle_recv }, + { 2491, 1, true, false, "circle_send", circle_send }, + { 2492, 1, true, false, "cash_recv", cash_recv }, + { 2493, 1, true, false, "cash_send", cash_send }, + { 2494, 1, true, false, "macaddr_recv", macaddr_recv }, + { 2495, 1, true, false, "macaddr_send", macaddr_send }, + { 2496, 1, true, false, "inet_recv", inet_recv }, + { 2497, 1, true, false, "inet_send", inet_send }, + { 2498, 1, true, false, "cidr_recv", cidr_recv }, + { 2499, 1, true, false, "cidr_send", cidr_send }, + { 2500, 1, true, false, "cstring_recv", cstring_recv }, + { 2501, 1, true, false, "cstring_send", cstring_send }, + { 2502, 1, true, false, "anyarray_recv", anyarray_recv }, + { 2503, 1, true, false, "anyarray_send", anyarray_send }, + { 2504, 2, true, false, "pg_get_ruledef_ext", pg_get_ruledef_ext }, + { 2505, 2, true, false, "pg_get_viewdef_name_ext", pg_get_viewdef_name_ext }, + { 2506, 2, true, false, "pg_get_viewdef_ext", pg_get_viewdef_ext }, + { 2507, 3, true, false, "pg_get_indexdef_ext", pg_get_indexdef_ext }, + { 2508, 2, true, false, "pg_get_constraintdef_ext", pg_get_constraintdef_ext }, + { 2509, 3, true, false, "pg_get_expr_ext", pg_get_expr_ext }, + { 2510, 0, true, true, "pg_prepared_statement", pg_prepared_statement }, + { 2511, 0, true, true, "pg_cursor", pg_cursor }, + { 2512, 1, true, false, "float8_var_pop", float8_var_pop }, + { 2513, 1, true, false, "float8_stddev_pop", float8_stddev_pop }, + { 2514, 1, false, false, "numeric_var_pop", numeric_var_pop }, + { 2515, 2, true, false, "booland_statefunc", booland_statefunc }, + { 2516, 2, true, false, "boolor_statefunc", boolor_statefunc }, + { 2520, 2, true, false, "timestamp_lt_timestamptz", timestamp_lt_timestamptz }, + { 2521, 2, true, false, "timestamp_le_timestamptz", timestamp_le_timestamptz }, + { 2522, 2, true, false, "timestamp_eq_timestamptz", timestamp_eq_timestamptz }, + { 2523, 2, true, false, "timestamp_gt_timestamptz", timestamp_gt_timestamptz }, + { 2524, 2, true, false, "timestamp_ge_timestamptz", timestamp_ge_timestamptz }, + { 2525, 2, true, false, "timestamp_ne_timestamptz", timestamp_ne_timestamptz }, + { 2526, 2, true, false, "timestamp_cmp_timestamptz", timestamp_cmp_timestamptz }, + { 2527, 2, true, false, "timestamptz_lt_timestamp", timestamptz_lt_timestamp }, + { 2528, 2, true, false, "timestamptz_le_timestamp", timestamptz_le_timestamp }, + { 2529, 2, true, false, "timestamptz_eq_timestamp", timestamptz_eq_timestamp }, + { 2530, 2, true, false, "timestamptz_gt_timestamp", timestamptz_gt_timestamp }, + { 2531, 2, true, false, "timestamptz_ge_timestamp", timestamptz_ge_timestamp }, + { 2532, 2, true, false, "timestamptz_ne_timestamp", timestamptz_ne_timestamp }, + { 2533, 2, true, false, "timestamptz_cmp_timestamp", timestamptz_cmp_timestamp }, + { 2556, 1, true, true, "pg_tablespace_databases", pg_tablespace_databases }, + { 2557, 1, true, false, "int4_bool", int4_bool }, + { 2558, 1, true, false, "bool_int4", bool_int4 }, + { 2559, 0, true, false, "lastval", lastval }, + { 2560, 0, true, false, "pg_postmaster_start_time", pg_postmaster_start_time }, + { 2561, 1, true, false, "pg_blocking_pids", pg_blocking_pids }, + { 2562, 2, true, false, "box_below", box_below }, + { 2563, 2, true, false, "box_overbelow", box_overbelow }, + { 2564, 2, true, false, "box_overabove", box_overabove }, + { 2565, 2, true, false, "box_above", box_above }, + { 2566, 2, true, false, "poly_below", poly_below }, + { 2567, 2, true, false, "poly_overbelow", poly_overbelow }, + { 2568, 2, true, false, "poly_overabove", poly_overabove }, + { 2569, 2, true, false, "poly_above", poly_above }, + { 2578, 5, true, false, "gist_box_consistent", gist_box_consistent }, + { 2580, 1, true, false, "jsonb_float8", jsonb_float8 }, + { 2581, 3, true, false, "gist_box_penalty", gist_box_penalty }, + { 2582, 2, true, false, "gist_box_picksplit", gist_box_picksplit }, + { 2583, 2, true, false, "gist_box_union", gist_box_union }, + { 2584, 3, true, false, "gist_box_same", gist_box_same }, + { 2585, 5, true, false, "gist_poly_consistent", gist_poly_consistent }, + { 2586, 1, true, false, "gist_poly_compress", gist_poly_compress }, + { 2587, 2, true, false, "circle_overbelow", circle_overbelow }, + { 2588, 2, true, false, "circle_overabove", circle_overabove }, + { 2591, 5, true, false, "gist_circle_consistent", gist_circle_consistent }, + { 2592, 1, true, false, "gist_circle_compress", gist_circle_compress }, + { 2596, 1, false, false, "numeric_stddev_pop", numeric_stddev_pop }, + { 2597, 3, false, false, "domain_in", domain_in }, + { 2598, 3, false, false, "domain_recv", domain_recv }, + { 2599, 0, true, true, "pg_timezone_abbrevs", pg_timezone_abbrevs }, + { 2614, 2, true, false, "xmlexists", xmlexists }, + { 2621, 0, true, false, "pg_reload_conf", pg_reload_conf }, + { 2622, 0, true, false, "pg_rotate_logfile_v2", pg_rotate_logfile_v2 }, + { 2623, 1, true, false, "pg_stat_file_1arg", pg_stat_file_1arg }, + { 2624, 3, true, false, "pg_read_file_off_len", pg_read_file_off_len }, + { 2625, 1, true, true, "pg_ls_dir_1arg", pg_ls_dir_1arg }, + { 2626, 1, true, false, "pg_sleep", pg_sleep }, + { 2627, 1, true, false, "inetnot", inetnot }, + { 2628, 2, true, false, "inetand", inetand }, + { 2629, 2, true, false, "inetor", inetor }, + { 2630, 2, true, false, "inetpl", inetpl }, + { 2632, 2, true, false, "inetmi_int8", inetmi_int8 }, + { 2633, 2, true, false, "inetmi", inetmi }, + { 2647, 0, true, false, "now", now }, + { 2648, 0, true, false, "statement_timestamp", statement_timestamp }, + { 2649, 0, true, false, "clock_timestamp", clock_timestamp }, + { 2700, 4, true, false, "gin_cmp_prefix", gin_cmp_prefix }, + { 2705, 3, true, false, "pg_has_role_name_name", pg_has_role_name_name }, + { 2706, 3, true, false, "pg_has_role_name_id", pg_has_role_name_id }, + { 2707, 3, true, false, "pg_has_role_id_name", pg_has_role_id_name }, + { 2708, 3, true, false, "pg_has_role_id_id", pg_has_role_id_id }, + { 2709, 2, true, false, "pg_has_role_name", pg_has_role_name }, + { 2710, 2, true, false, "pg_has_role_id", pg_has_role_id }, + { 2711, 1, true, false, "interval_justify_interval", interval_justify_interval }, + { 2730, 2, true, false, "pg_get_triggerdef_ext", pg_get_triggerdef_ext }, + { 2731, 1, true, false, "dasind", dasind }, + { 2732, 1, true, false, "dacosd", dacosd }, + { 2733, 1, true, false, "datand", datand }, + { 2734, 2, true, false, "datan2d", datan2d }, + { 2735, 1, true, false, "dsind", dsind }, + { 2736, 1, true, false, "dcosd", dcosd }, + { 2737, 1, true, false, "dtand", dtand }, + { 2738, 1, true, false, "dcotd", dcotd }, + { 2739, 1, true, false, "pg_backup_stop", pg_backup_stop }, + { 2740, 1, true, false, "numeric_avg_serialize", numeric_avg_serialize }, + { 2741, 2, true, false, "numeric_avg_deserialize", numeric_avg_deserialize }, + { 2743, 3, true, false, "ginarrayextract", ginarrayextract }, + { 2744, 8, true, false, "ginarrayconsistent", ginarrayconsistent }, + { 2746, 2, false, false, "int8_avg_accum", int8_avg_accum }, + { 2747, 2, true, false, "arrayoverlap", arrayoverlap }, + { 2748, 2, true, false, "arraycontains", arraycontains }, + { 2749, 2, true, false, "arraycontained", arraycontained }, + { 2758, 1, true, false, "pg_stat_get_db_tuples_returned", pg_stat_get_db_tuples_returned }, + { 2759, 1, true, false, "pg_stat_get_db_tuples_fetched", pg_stat_get_db_tuples_fetched }, + { 2760, 1, true, false, "pg_stat_get_db_tuples_inserted", pg_stat_get_db_tuples_inserted }, + { 2761, 1, true, false, "pg_stat_get_db_tuples_updated", pg_stat_get_db_tuples_updated }, + { 2762, 1, true, false, "pg_stat_get_db_tuples_deleted", pg_stat_get_db_tuples_deleted }, + { 2763, 2, true, true, "regexp_matches_no_flags", regexp_matches_no_flags }, + { 2764, 3, true, true, "regexp_matches", regexp_matches }, + { 2765, 2, true, true, "regexp_split_to_table_no_flags", regexp_split_to_table_no_flags }, + { 2766, 3, true, true, "regexp_split_to_table", regexp_split_to_table }, + { 2767, 2, true, false, "regexp_split_to_array_no_flags", regexp_split_to_array_no_flags }, + { 2768, 3, true, false, "regexp_split_to_array", regexp_split_to_array }, + { 2769, 0, true, false, "pg_stat_get_bgwriter_timed_checkpoints", pg_stat_get_bgwriter_timed_checkpoints }, + { 2770, 0, true, false, "pg_stat_get_bgwriter_requested_checkpoints", pg_stat_get_bgwriter_requested_checkpoints }, + { 2771, 0, true, false, "pg_stat_get_bgwriter_buf_written_checkpoints", pg_stat_get_bgwriter_buf_written_checkpoints }, + { 2772, 0, true, false, "pg_stat_get_bgwriter_buf_written_clean", pg_stat_get_bgwriter_buf_written_clean }, + { 2773, 0, true, false, "pg_stat_get_bgwriter_maxwritten_clean", pg_stat_get_bgwriter_maxwritten_clean }, + { 2774, 7, true, false, "ginqueryarrayextract", ginqueryarrayextract }, + { 2775, 0, true, false, "pg_stat_get_buf_written_backend", pg_stat_get_buf_written_backend }, + { 2777, 1, true, false, "anynonarray_in", anynonarray_in }, + { 2778, 1, true, false, "anynonarray_out", anynonarray_out }, + { 2781, 1, true, false, "pg_stat_get_last_vacuum_time", pg_stat_get_last_vacuum_time }, + { 2782, 1, true, false, "pg_stat_get_last_autovacuum_time", pg_stat_get_last_autovacuum_time }, + { 2783, 1, true, false, "pg_stat_get_last_analyze_time", pg_stat_get_last_analyze_time }, + { 2784, 1, true, false, "pg_stat_get_last_autoanalyze_time", pg_stat_get_last_autoanalyze_time }, + { 2785, 2, false, false, "int8_avg_combine", int8_avg_combine }, + { 2786, 1, true, false, "int8_avg_serialize", int8_avg_serialize }, + { 2787, 2, true, false, "int8_avg_deserialize", int8_avg_deserialize }, + { 2788, 1, true, false, "pg_stat_get_backend_wait_event_type", pg_stat_get_backend_wait_event_type }, + { 2790, 2, true, false, "tidgt", tidgt }, + { 2791, 2, true, false, "tidlt", tidlt }, + { 2792, 2, true, false, "tidge", tidge }, + { 2793, 2, true, false, "tidle", tidle }, + { 2794, 2, true, false, "bttidcmp", bttidcmp }, + { 2795, 2, true, false, "tidlarger", tidlarger }, + { 2796, 2, true, false, "tidsmaller", tidsmaller }, + { 2804, 2, true, false, "int8inc_any", int8inc_any }, + { 2805, 3, true, false, "int8inc_float8_float8", int8inc_float8_float8 }, + { 2806, 3, true, false, "float8_regr_accum", float8_regr_accum }, + { 2807, 1, true, false, "float8_regr_sxx", float8_regr_sxx }, + { 2808, 1, true, false, "float8_regr_syy", float8_regr_syy }, + { 2809, 1, true, false, "float8_regr_sxy", float8_regr_sxy }, + { 2810, 1, true, false, "float8_regr_avgx", float8_regr_avgx }, + { 2811, 1, true, false, "float8_regr_avgy", float8_regr_avgy }, + { 2812, 1, true, false, "float8_regr_r2", float8_regr_r2 }, + { 2813, 1, true, false, "float8_regr_slope", float8_regr_slope }, + { 2814, 1, true, false, "float8_regr_intercept", float8_regr_intercept }, + { 2815, 1, true, false, "float8_covar_pop", float8_covar_pop }, + { 2816, 1, true, false, "float8_covar_samp", float8_covar_samp }, + { 2817, 1, true, false, "float8_corr", float8_corr }, + { 2844, 1, true, false, "pg_stat_get_db_blk_read_time", pg_stat_get_db_blk_read_time }, + { 2845, 1, true, false, "pg_stat_get_db_blk_write_time", pg_stat_get_db_blk_write_time }, + { 2848, 0, true, false, "pg_switch_wal", pg_switch_wal }, + { 2849, 0, true, false, "pg_current_wal_lsn", pg_current_wal_lsn }, + { 2850, 1, true, false, "pg_walfile_name_offset", pg_walfile_name_offset }, + { 2851, 1, true, false, "pg_walfile_name", pg_walfile_name }, + { 2852, 0, true, false, "pg_current_wal_insert_lsn", pg_current_wal_insert_lsn }, + { 2853, 1, true, false, "pg_stat_get_backend_wait_event", pg_stat_get_backend_wait_event }, + { 2854, 0, true, false, "pg_my_temp_schema", pg_my_temp_schema }, + { 2855, 1, true, false, "pg_is_other_temp_schema", pg_is_other_temp_schema }, + { 2856, 0, true, true, "pg_timezone_names", pg_timezone_names }, + { 2857, 1, true, false, "pg_stat_get_backend_xact_start", pg_stat_get_backend_xact_start }, + { 2858, 2, false, false, "numeric_avg_accum", numeric_avg_accum }, + { 2859, 0, true, false, "pg_stat_get_buf_alloc", pg_stat_get_buf_alloc }, + { 2878, 1, true, false, "pg_stat_get_live_tuples", pg_stat_get_live_tuples }, + { 2879, 1, true, false, "pg_stat_get_dead_tuples", pg_stat_get_dead_tuples }, + { 2880, 1, true, false, "pg_advisory_lock_int8", pg_advisory_lock_int8 }, + { 2881, 1, true, false, "pg_advisory_lock_shared_int8", pg_advisory_lock_shared_int8 }, + { 2882, 1, true, false, "pg_try_advisory_lock_int8", pg_try_advisory_lock_int8 }, + { 2883, 1, true, false, "pg_try_advisory_lock_shared_int8", pg_try_advisory_lock_shared_int8 }, + { 2884, 1, true, false, "pg_advisory_unlock_int8", pg_advisory_unlock_int8 }, + { 2885, 1, true, false, "pg_advisory_unlock_shared_int8", pg_advisory_unlock_shared_int8 }, + { 2886, 2, true, false, "pg_advisory_lock_int4", pg_advisory_lock_int4 }, + { 2887, 2, true, false, "pg_advisory_lock_shared_int4", pg_advisory_lock_shared_int4 }, + { 2888, 2, true, false, "pg_try_advisory_lock_int4", pg_try_advisory_lock_int4 }, + { 2889, 2, true, false, "pg_try_advisory_lock_shared_int4", pg_try_advisory_lock_shared_int4 }, + { 2890, 2, true, false, "pg_advisory_unlock_int4", pg_advisory_unlock_int4 }, + { 2891, 2, true, false, "pg_advisory_unlock_shared_int4", pg_advisory_unlock_shared_int4 }, + { 2892, 0, true, false, "pg_advisory_unlock_all", pg_advisory_unlock_all }, + { 2893, 1, true, false, "xml_in", xml_in }, + { 2894, 1, true, false, "xml_out", xml_out }, + { 2895, 1, true, false, "xmlcomment", xmlcomment }, + { 2896, 1, true, false, "texttoxml", texttoxml }, + { 2897, 2, true, false, "xmlvalidate", xmlvalidate }, + { 2898, 1, true, false, "xml_recv", xml_recv }, + { 2899, 1, true, false, "xml_send", xml_send }, + { 2900, 2, false, false, "xmlconcat2", xmlconcat2 }, + { 2902, 1, true, false, "varbittypmodin", varbittypmodin }, + { 2903, 1, true, false, "intervaltypmodin", intervaltypmodin }, + { 2904, 1, true, false, "intervaltypmodout", intervaltypmodout }, + { 2905, 1, true, false, "timestamptypmodin", timestamptypmodin }, + { 2906, 1, true, false, "timestamptypmodout", timestamptypmodout }, + { 2907, 1, true, false, "timestamptztypmodin", timestamptztypmodin }, + { 2908, 1, true, false, "timestamptztypmodout", timestamptztypmodout }, + { 2909, 1, true, false, "timetypmodin", timetypmodin }, + { 2910, 1, true, false, "timetypmodout", timetypmodout }, + { 2911, 1, true, false, "timetztypmodin", timetztypmodin }, + { 2912, 1, true, false, "timetztypmodout", timetztypmodout }, + { 2913, 1, true, false, "bpchartypmodin", bpchartypmodin }, + { 2914, 1, true, false, "bpchartypmodout", bpchartypmodout }, + { 2915, 1, true, false, "varchartypmodin", varchartypmodin }, + { 2916, 1, true, false, "varchartypmodout", varchartypmodout }, + { 2917, 1, true, false, "numerictypmodin", numerictypmodin }, + { 2918, 1, true, false, "numerictypmodout", numerictypmodout }, + { 2919, 1, true, false, "bittypmodin", bittypmodin }, + { 2920, 1, true, false, "bittypmodout", bittypmodout }, + { 2921, 1, true, false, "varbittypmodout", varbittypmodout }, + { 2922, 1, true, false, "xmltotext", xmltotext }, + { 2923, 4, true, false, "table_to_xml", table_to_xml }, + { 2924, 4, true, false, "query_to_xml", query_to_xml }, + { 2925, 5, true, false, "cursor_to_xml", cursor_to_xml }, + { 2926, 4, true, false, "table_to_xmlschema", table_to_xmlschema }, + { 2927, 4, true, false, "query_to_xmlschema", query_to_xmlschema }, + { 2928, 4, true, false, "cursor_to_xmlschema", cursor_to_xmlschema }, + { 2929, 4, true, false, "table_to_xml_and_xmlschema", table_to_xml_and_xmlschema }, + { 2930, 4, true, false, "query_to_xml_and_xmlschema", query_to_xml_and_xmlschema }, + { 2931, 3, true, false, "xpath", xpath }, + { 2933, 4, true, false, "schema_to_xml", schema_to_xml }, + { 2934, 4, true, false, "schema_to_xmlschema", schema_to_xmlschema }, + { 2935, 4, true, false, "schema_to_xml_and_xmlschema", schema_to_xml_and_xmlschema }, + { 2936, 3, true, false, "database_to_xml", database_to_xml }, + { 2937, 3, true, false, "database_to_xmlschema", database_to_xmlschema }, + { 2938, 3, true, false, "database_to_xml_and_xmlschema", database_to_xml_and_xmlschema }, + { 2939, 1, true, false, "pg_snapshot_in", pg_snapshot_in }, + { 2940, 1, true, false, "pg_snapshot_out", pg_snapshot_out }, + { 2941, 1, true, false, "pg_snapshot_recv", pg_snapshot_recv }, + { 2942, 1, true, false, "pg_snapshot_send", pg_snapshot_send }, + { 2943, 0, true, false, "pg_current_xact_id", pg_current_xact_id }, + { 2944, 0, true, false, "pg_current_snapshot", pg_current_snapshot }, + { 2945, 1, true, false, "pg_snapshot_xmin", pg_snapshot_xmin }, + { 2946, 1, true, false, "pg_snapshot_xmax", pg_snapshot_xmax }, + { 2947, 1, true, true, "pg_snapshot_xip", pg_snapshot_xip }, + { 2948, 2, true, false, "pg_visible_in_snapshot", pg_visible_in_snapshot }, + { 2952, 1, true, false, "uuid_in", uuid_in }, + { 2953, 1, true, false, "uuid_out", uuid_out }, + { 2954, 2, true, false, "uuid_lt", uuid_lt }, + { 2955, 2, true, false, "uuid_le", uuid_le }, + { 2956, 2, true, false, "uuid_eq", uuid_eq }, + { 2957, 2, true, false, "uuid_ge", uuid_ge }, + { 2958, 2, true, false, "uuid_gt", uuid_gt }, + { 2959, 2, true, false, "uuid_ne", uuid_ne }, + { 2960, 2, true, false, "uuid_cmp", uuid_cmp }, + { 2961, 1, true, false, "uuid_recv", uuid_recv }, + { 2962, 1, true, false, "uuid_send", uuid_send }, + { 2963, 1, true, false, "uuid_hash", uuid_hash }, + { 2971, 1, true, false, "booltext", booltext }, + { 2978, 1, true, false, "pg_stat_get_function_calls", pg_stat_get_function_calls }, + { 2979, 1, true, false, "pg_stat_get_function_total_time", pg_stat_get_function_total_time }, + { 2980, 1, true, false, "pg_stat_get_function_self_time", pg_stat_get_function_self_time }, + { 2981, 2, true, false, "record_eq", record_eq }, + { 2982, 2, true, false, "record_ne", record_ne }, + { 2983, 2, true, false, "record_lt", record_lt }, + { 2984, 2, true, false, "record_gt", record_gt }, + { 2985, 2, true, false, "record_le", record_le }, + { 2986, 2, true, false, "record_ge", record_ge }, + { 2987, 2, true, false, "btrecordcmp", btrecordcmp }, + { 2997, 1, true, false, "pg_table_size", pg_table_size }, + { 2998, 1, true, false, "pg_indexes_size", pg_indexes_size }, + { 2999, 1, true, false, "pg_relation_filenode", pg_relation_filenode }, + { 3000, 3, true, false, "has_foreign_data_wrapper_privilege_name_name", has_foreign_data_wrapper_privilege_name_name }, + { 3001, 3, true, false, "has_foreign_data_wrapper_privilege_name_id", has_foreign_data_wrapper_privilege_name_id }, + { 3002, 3, true, false, "has_foreign_data_wrapper_privilege_id_name", has_foreign_data_wrapper_privilege_id_name }, + { 3003, 3, true, false, "has_foreign_data_wrapper_privilege_id_id", has_foreign_data_wrapper_privilege_id_id }, + { 3004, 2, true, false, "has_foreign_data_wrapper_privilege_name", has_foreign_data_wrapper_privilege_name }, + { 3005, 2, true, false, "has_foreign_data_wrapper_privilege_id", has_foreign_data_wrapper_privilege_id }, + { 3006, 3, true, false, "has_server_privilege_name_name", has_server_privilege_name_name }, + { 3007, 3, true, false, "has_server_privilege_name_id", has_server_privilege_name_id }, + { 3008, 3, true, false, "has_server_privilege_id_name", has_server_privilege_id_name }, + { 3009, 3, true, false, "has_server_privilege_id_id", has_server_privilege_id_id }, + { 3010, 2, true, false, "has_server_privilege_name", has_server_privilege_name }, + { 3011, 2, true, false, "has_server_privilege_id", has_server_privilege_id }, + { 3012, 4, true, false, "has_column_privilege_name_name_name", has_column_privilege_name_name_name }, + { 3013, 4, true, false, "has_column_privilege_name_name_attnum", has_column_privilege_name_name_attnum }, + { 3014, 4, true, false, "has_column_privilege_name_id_name", has_column_privilege_name_id_name }, + { 3015, 4, true, false, "has_column_privilege_name_id_attnum", has_column_privilege_name_id_attnum }, + { 3016, 4, true, false, "has_column_privilege_id_name_name", has_column_privilege_id_name_name }, + { 3017, 4, true, false, "has_column_privilege_id_name_attnum", has_column_privilege_id_name_attnum }, + { 3018, 4, true, false, "has_column_privilege_id_id_name", has_column_privilege_id_id_name }, + { 3019, 4, true, false, "has_column_privilege_id_id_attnum", has_column_privilege_id_id_attnum }, + { 3020, 3, true, false, "has_column_privilege_name_name", has_column_privilege_name_name }, + { 3021, 3, true, false, "has_column_privilege_name_attnum", has_column_privilege_name_attnum }, + { 3022, 3, true, false, "has_column_privilege_id_name", has_column_privilege_id_name }, + { 3023, 3, true, false, "has_column_privilege_id_attnum", has_column_privilege_id_attnum }, + { 3024, 3, true, false, "has_any_column_privilege_name_name", has_any_column_privilege_name_name }, + { 3025, 3, true, false, "has_any_column_privilege_name_id", has_any_column_privilege_name_id }, + { 3026, 3, true, false, "has_any_column_privilege_id_name", has_any_column_privilege_id_name }, + { 3027, 3, true, false, "has_any_column_privilege_id_id", has_any_column_privilege_id_id }, + { 3028, 2, true, false, "has_any_column_privilege_name", has_any_column_privilege_name }, + { 3029, 2, true, false, "has_any_column_privilege_id", has_any_column_privilege_id }, + { 3030, 4, true, false, "bitoverlay", bitoverlay }, + { 3031, 3, true, false, "bitoverlay_no_len", bitoverlay_no_len }, + { 3032, 2, true, false, "bitgetbit", bitgetbit }, + { 3033, 3, true, false, "bitsetbit", bitsetbit }, + { 3034, 1, true, false, "pg_relation_filepath", pg_relation_filepath }, + { 3035, 0, true, true, "pg_listening_channels", pg_listening_channels }, + { 3036, 2, false, false, "pg_notify", pg_notify }, + { 3037, 1, true, false, "pg_stat_get_xact_numscans", pg_stat_get_xact_numscans }, + { 3038, 1, true, false, "pg_stat_get_xact_tuples_returned", pg_stat_get_xact_tuples_returned }, + { 3039, 1, true, false, "pg_stat_get_xact_tuples_fetched", pg_stat_get_xact_tuples_fetched }, + { 3040, 1, true, false, "pg_stat_get_xact_tuples_inserted", pg_stat_get_xact_tuples_inserted }, + { 3041, 1, true, false, "pg_stat_get_xact_tuples_updated", pg_stat_get_xact_tuples_updated }, + { 3042, 1, true, false, "pg_stat_get_xact_tuples_deleted", pg_stat_get_xact_tuples_deleted }, + { 3043, 1, true, false, "pg_stat_get_xact_tuples_hot_updated", pg_stat_get_xact_tuples_hot_updated }, + { 3044, 1, true, false, "pg_stat_get_xact_blocks_fetched", pg_stat_get_xact_blocks_fetched }, + { 3045, 1, true, false, "pg_stat_get_xact_blocks_hit", pg_stat_get_xact_blocks_hit }, + { 3046, 1, true, false, "pg_stat_get_xact_function_calls", pg_stat_get_xact_function_calls }, + { 3047, 1, true, false, "pg_stat_get_xact_function_total_time", pg_stat_get_xact_function_total_time }, + { 3048, 1, true, false, "pg_stat_get_xact_function_self_time", pg_stat_get_xact_function_self_time }, + { 3049, 3, true, false, "xpath_exists", xpath_exists }, + { 3051, 1, true, false, "xml_is_well_formed", xml_is_well_formed }, + { 3052, 1, true, false, "xml_is_well_formed_document", xml_is_well_formed_document }, + { 3053, 1, true, false, "xml_is_well_formed_content", xml_is_well_formed_content }, + { 3054, 1, true, false, "pg_stat_get_vacuum_count", pg_stat_get_vacuum_count }, + { 3055, 1, true, false, "pg_stat_get_autovacuum_count", pg_stat_get_autovacuum_count }, + { 3056, 1, true, false, "pg_stat_get_analyze_count", pg_stat_get_analyze_count }, + { 3057, 1, true, false, "pg_stat_get_autoanalyze_count", pg_stat_get_autoanalyze_count }, + { 3058, 1, false, false, "text_concat", text_concat }, + { 3059, 2, false, false, "text_concat_ws", text_concat_ws }, + { 3060, 2, true, false, "text_left", text_left }, + { 3061, 2, true, false, "text_right", text_right }, + { 3062, 1, true, false, "text_reverse", text_reverse }, + { 3063, 0, true, false, "pg_stat_get_buf_fsync_backend", pg_stat_get_buf_fsync_backend }, + { 3064, 5, true, false, "gist_point_distance", gist_point_distance }, + { 3065, 1, true, false, "pg_stat_get_db_conflict_tablespace", pg_stat_get_db_conflict_tablespace }, + { 3066, 1, true, false, "pg_stat_get_db_conflict_lock", pg_stat_get_db_conflict_lock }, + { 3067, 1, true, false, "pg_stat_get_db_conflict_snapshot", pg_stat_get_db_conflict_snapshot }, + { 3068, 1, true, false, "pg_stat_get_db_conflict_bufferpin", pg_stat_get_db_conflict_bufferpin }, + { 3069, 1, true, false, "pg_stat_get_db_conflict_startup_deadlock", pg_stat_get_db_conflict_startup_deadlock }, + { 3070, 1, true, false, "pg_stat_get_db_conflict_all", pg_stat_get_db_conflict_all }, + { 3071, 0, true, false, "pg_wal_replay_pause", pg_wal_replay_pause }, + { 3072, 0, true, false, "pg_wal_replay_resume", pg_wal_replay_resume }, + { 3073, 0, true, false, "pg_is_wal_replay_paused", pg_is_wal_replay_paused }, + { 3074, 1, true, false, "pg_stat_get_db_stat_reset_time", pg_stat_get_db_stat_reset_time }, + { 3075, 0, true, false, "pg_stat_get_bgwriter_stat_reset_time", pg_stat_get_bgwriter_stat_reset_time }, + { 3076, 2, true, false, "ginarrayextract_2args", ginarrayextract_2args }, + { 3077, 2, true, false, "gin_extract_tsvector_2args", gin_extract_tsvector_2args }, + { 3078, 1, true, false, "pg_sequence_parameters", pg_sequence_parameters }, + { 3082, 0, true, true, "pg_available_extensions", pg_available_extensions }, + { 3083, 0, true, true, "pg_available_extension_versions", pg_available_extension_versions }, + { 3084, 1, true, true, "pg_extension_update_paths", pg_extension_update_paths }, + { 3086, 2, true, false, "pg_extension_config_dump", pg_extension_config_dump }, + { 3087, 5, true, false, "gin_extract_tsquery_5args", gin_extract_tsquery_5args }, + { 3088, 6, true, false, "gin_tsquery_consistent_6args", gin_tsquery_consistent_6args }, + { 3089, 1, true, false, "pg_advisory_xact_lock_int8", pg_advisory_xact_lock_int8 }, + { 3090, 1, true, false, "pg_advisory_xact_lock_shared_int8", pg_advisory_xact_lock_shared_int8 }, + { 3091, 1, true, false, "pg_try_advisory_xact_lock_int8", pg_try_advisory_xact_lock_int8 }, + { 3092, 1, true, false, "pg_try_advisory_xact_lock_shared_int8", pg_try_advisory_xact_lock_shared_int8 }, + { 3093, 2, true, false, "pg_advisory_xact_lock_int4", pg_advisory_xact_lock_int4 }, + { 3094, 2, true, false, "pg_advisory_xact_lock_shared_int4", pg_advisory_xact_lock_shared_int4 }, + { 3095, 2, true, false, "pg_try_advisory_xact_lock_int4", pg_try_advisory_xact_lock_int4 }, + { 3096, 2, true, false, "pg_try_advisory_xact_lock_shared_int4", pg_try_advisory_xact_lock_shared_int4 }, + { 3097, 1, true, false, "varchar_support", varchar_support }, + { 3098, 1, true, false, "pg_create_restore_point", pg_create_restore_point }, + { 3099, 0, false, true, "pg_stat_get_wal_senders", pg_stat_get_wal_senders }, + { 3100, 0, false, false, "window_row_number", window_row_number }, + { 3101, 0, false, false, "window_rank", window_rank }, + { 3102, 0, false, false, "window_dense_rank", window_dense_rank }, + { 3103, 0, false, false, "window_percent_rank", window_percent_rank }, + { 3104, 0, false, false, "window_cume_dist", window_cume_dist }, + { 3105, 1, true, false, "window_ntile", window_ntile }, + { 3106, 1, true, false, "window_lag", window_lag }, + { 3107, 2, true, false, "window_lag_with_offset", window_lag_with_offset }, + { 3108, 3, true, false, "window_lag_with_offset_and_default", window_lag_with_offset_and_default }, + { 3109, 1, true, false, "window_lead", window_lead }, + { 3110, 2, true, false, "window_lead_with_offset", window_lead_with_offset }, + { 3111, 3, true, false, "window_lead_with_offset_and_default", window_lead_with_offset_and_default }, + { 3112, 1, true, false, "window_first_value", window_first_value }, + { 3113, 1, true, false, "window_last_value", window_last_value }, + { 3114, 2, true, false, "window_nth_value", window_nth_value }, + { 3116, 1, false, false, "fdw_handler_in", fdw_handler_in }, + { 3117, 1, true, false, "fdw_handler_out", fdw_handler_out }, + { 3120, 1, true, false, "void_recv", void_recv }, + { 3121, 1, true, false, "void_send", void_send }, + { 3129, 1, true, false, "btint2sortsupport", btint2sortsupport }, + { 3130, 1, true, false, "btint4sortsupport", btint4sortsupport }, + { 3131, 1, true, false, "btint8sortsupport", btint8sortsupport }, + { 3132, 1, true, false, "btfloat4sortsupport", btfloat4sortsupport }, + { 3133, 1, true, false, "btfloat8sortsupport", btfloat8sortsupport }, + { 3134, 1, true, false, "btoidsortsupport", btoidsortsupport }, + { 3135, 1, true, false, "btnamesortsupport", btnamesortsupport }, + { 3136, 1, true, false, "date_sortsupport", date_sortsupport }, + { 3137, 1, true, false, "timestamp_sortsupport", timestamp_sortsupport }, + { 3138, 3, true, false, "has_type_privilege_name_name", has_type_privilege_name_name }, + { 3139, 3, true, false, "has_type_privilege_name_id", has_type_privilege_name_id }, + { 3140, 3, true, false, "has_type_privilege_id_name", has_type_privilege_id_name }, + { 3141, 3, true, false, "has_type_privilege_id_id", has_type_privilege_id_id }, + { 3142, 2, true, false, "has_type_privilege_name", has_type_privilege_name }, + { 3143, 2, true, false, "has_type_privilege_id", has_type_privilege_id }, + { 3144, 1, true, false, "macaddr_not", macaddr_not }, + { 3145, 2, true, false, "macaddr_and", macaddr_and }, + { 3146, 2, true, false, "macaddr_or", macaddr_or }, + { 3150, 1, true, false, "pg_stat_get_db_temp_files", pg_stat_get_db_temp_files }, + { 3151, 1, true, false, "pg_stat_get_db_temp_bytes", pg_stat_get_db_temp_bytes }, + { 3152, 1, true, false, "pg_stat_get_db_deadlocks", pg_stat_get_db_deadlocks }, + { 3153, 1, true, false, "array_to_json", array_to_json }, + { 3154, 2, true, false, "array_to_json_pretty", array_to_json_pretty }, + { 3155, 1, true, false, "row_to_json", row_to_json }, + { 3156, 2, true, false, "row_to_json_pretty", row_to_json_pretty }, + { 3157, 1, true, false, "numeric_support", numeric_support }, + { 3158, 1, true, false, "varbit_support", varbit_support }, + { 3159, 2, true, false, "pg_get_viewdef_wrap", pg_get_viewdef_wrap }, + { 3160, 0, true, false, "pg_stat_get_checkpoint_write_time", pg_stat_get_checkpoint_write_time }, + { 3161, 0, true, false, "pg_stat_get_checkpoint_sync_time", pg_stat_get_checkpoint_sync_time }, + { 3162, 1, false, false, "pg_collation_for", pg_collation_for }, + { 3163, 0, true, false, "pg_trigger_depth", pg_trigger_depth }, + { 3165, 2, true, false, "pg_wal_lsn_diff", pg_wal_lsn_diff }, + { 3166, 1, true, false, "pg_size_pretty_numeric", pg_size_pretty_numeric }, + { 3167, 2, false, false, "array_remove", array_remove }, + { 3168, 3, false, false, "array_replace", array_replace }, + { 3169, 4, true, false, "rangesel", rangesel }, + { 3170, 3, true, false, "be_lo_lseek64", be_lo_lseek64 }, + { 3171, 1, true, false, "be_lo_tell64", be_lo_tell64 }, + { 3172, 2, true, false, "be_lo_truncate64", be_lo_truncate64 }, + { 3173, 2, false, false, "json_agg_transfn", json_agg_transfn }, + { 3174, 1, false, false, "json_agg_finalfn", json_agg_finalfn }, + { 3176, 1, true, false, "to_json", to_json }, + { 3177, 1, true, false, "pg_stat_get_mod_since_analyze", pg_stat_get_mod_since_analyze }, + { 3178, 1, false, false, "numeric_sum", numeric_sum }, + { 3179, 1, true, false, "array_cardinality", array_cardinality }, + { 3180, 3, false, false, "json_object_agg_transfn", json_object_agg_transfn }, + { 3181, 2, true, false, "record_image_eq", record_image_eq }, + { 3182, 2, true, false, "record_image_ne", record_image_ne }, + { 3183, 2, true, false, "record_image_lt", record_image_lt }, + { 3184, 2, true, false, "record_image_gt", record_image_gt }, + { 3185, 2, true, false, "record_image_le", record_image_le }, + { 3186, 2, true, false, "record_image_ge", record_image_ge }, + { 3187, 2, true, false, "btrecordimagecmp", btrecordimagecmp }, + { 3195, 0, false, false, "pg_stat_get_archiver", pg_stat_get_archiver }, + { 3196, 1, false, false, "json_object_agg_finalfn", json_object_agg_finalfn }, + { 3198, 1, false, false, "json_build_array", json_build_array }, + { 3199, 0, false, false, "json_build_array_noargs", json_build_array_noargs }, + { 3200, 1, false, false, "json_build_object", json_build_object }, + { 3201, 0, false, false, "json_build_object_noargs", json_build_object_noargs }, + { 3202, 1, true, false, "json_object", json_object }, + { 3203, 2, true, false, "json_object_two_arg", json_object_two_arg }, + { 3204, 1, true, false, "json_to_record", json_to_record }, + { 3205, 1, false, true, "json_to_recordset", json_to_recordset }, + { 3207, 1, true, false, "jsonb_array_length", jsonb_array_length }, + { 3208, 1, true, true, "jsonb_each", jsonb_each }, + { 3209, 2, false, false, "jsonb_populate_record", jsonb_populate_record }, + { 3210, 1, true, false, "jsonb_typeof", jsonb_typeof }, + { 3214, 2, true, false, "jsonb_object_field_text", jsonb_object_field_text }, + { 3215, 2, true, false, "jsonb_array_element", jsonb_array_element }, + { 3216, 2, true, false, "jsonb_array_element_text", jsonb_array_element_text }, + { 3217, 2, true, false, "jsonb_extract_path", jsonb_extract_path }, + { 3218, 2, true, false, "width_bucket_array", width_bucket_array }, + { 3219, 1, true, true, "jsonb_array_elements", jsonb_array_elements }, + { 3229, 1, true, false, "pg_lsn_in", pg_lsn_in }, + { 3230, 1, true, false, "pg_lsn_out", pg_lsn_out }, + { 3231, 2, true, false, "pg_lsn_lt", pg_lsn_lt }, + { 3232, 2, true, false, "pg_lsn_le", pg_lsn_le }, + { 3233, 2, true, false, "pg_lsn_eq", pg_lsn_eq }, + { 3234, 2, true, false, "pg_lsn_ge", pg_lsn_ge }, + { 3235, 2, true, false, "pg_lsn_gt", pg_lsn_gt }, + { 3236, 2, true, false, "pg_lsn_ne", pg_lsn_ne }, + { 3237, 2, true, false, "pg_lsn_mi", pg_lsn_mi }, + { 3238, 1, true, false, "pg_lsn_recv", pg_lsn_recv }, + { 3239, 1, true, false, "pg_lsn_send", pg_lsn_send }, + { 3251, 2, true, false, "pg_lsn_cmp", pg_lsn_cmp }, + { 3252, 1, true, false, "pg_lsn_hash", pg_lsn_hash }, + { 3255, 1, true, false, "bttextsortsupport", bttextsortsupport }, + { 3259, 3, true, true, "generate_series_step_numeric", generate_series_step_numeric }, + { 3260, 2, true, true, "generate_series_numeric", generate_series_numeric }, + { 3261, 1, true, false, "json_strip_nulls", json_strip_nulls }, + { 3262, 1, true, false, "jsonb_strip_nulls", jsonb_strip_nulls }, + { 3263, 1, true, false, "jsonb_object", jsonb_object }, + { 3264, 2, true, false, "jsonb_object_two_arg", jsonb_object_two_arg }, + { 3265, 2, false, false, "jsonb_agg_transfn", jsonb_agg_transfn }, + { 3266, 1, false, false, "jsonb_agg_finalfn", jsonb_agg_finalfn }, + { 3268, 3, false, false, "jsonb_object_agg_transfn", jsonb_object_agg_transfn }, + { 3269, 1, false, false, "jsonb_object_agg_finalfn", jsonb_object_agg_finalfn }, + { 3271, 1, false, false, "jsonb_build_array", jsonb_build_array }, + { 3272, 0, false, false, "jsonb_build_array_noargs", jsonb_build_array_noargs }, + { 3273, 1, false, false, "jsonb_build_object", jsonb_build_object }, + { 3274, 0, false, false, "jsonb_build_object_noargs", jsonb_build_object_noargs }, + { 3275, 2, true, false, "dist_ppoly", dist_ppoly }, + { 3277, 2, false, false, "array_position", array_position }, + { 3278, 3, false, false, "array_position_start", array_position_start }, + { 3279, 2, false, false, "array_positions", array_positions }, + { 3280, 5, true, false, "gist_circle_distance", gist_circle_distance }, + { 3281, 1, true, false, "numeric_scale", numeric_scale }, + { 3282, 1, true, false, "gist_point_fetch", gist_point_fetch }, + { 3283, 1, true, false, "numeric_sortsupport", numeric_sortsupport }, + { 3288, 5, true, false, "gist_poly_distance", gist_poly_distance }, + { 3290, 2, true, false, "dist_cpoint", dist_cpoint }, + { 3292, 2, true, false, "dist_polyp", dist_polyp }, + { 3293, 4, true, false, "pg_read_file_off_len_missing", pg_read_file_off_len_missing }, + { 3294, 2, true, false, "show_config_by_name_missing_ok", show_config_by_name_missing_ok }, + { 3295, 4, true, false, "pg_read_binary_file_off_len_missing", pg_read_binary_file_off_len_missing }, + { 3296, 0, true, false, "pg_notification_queue_usage", pg_notification_queue_usage }, + { 3297, 3, true, true, "pg_ls_dir", pg_ls_dir }, + { 3298, 1, true, false, "row_security_active", row_security_active }, + { 3299, 1, true, false, "row_security_active_name", row_security_active_name }, + { 3300, 1, true, false, "uuid_sortsupport", uuid_sortsupport }, + { 3301, 2, true, false, "jsonb_concat", jsonb_concat }, + { 3302, 2, true, false, "jsonb_delete", jsonb_delete }, + { 3303, 2, true, false, "jsonb_delete_idx", jsonb_delete_idx }, + { 3304, 2, true, false, "jsonb_delete_path", jsonb_delete_path }, + { 3305, 4, true, false, "jsonb_set", jsonb_set }, + { 3306, 1, true, false, "jsonb_pretty", jsonb_pretty }, + { 3307, 2, true, false, "pg_stat_file", pg_stat_file }, + { 3308, 2, true, false, "xidneq", xidneq }, + { 3309, 2, true, false, "xidneq", xidneq }, + { 3311, 1, false, false, "tsm_handler_in", tsm_handler_in }, + { 3312, 1, true, false, "tsm_handler_out", tsm_handler_out }, + { 3313, 1, true, false, "tsm_bernoulli_handler", tsm_bernoulli_handler }, + { 3314, 1, true, false, "tsm_system_handler", tsm_system_handler }, + { 3317, 0, false, false, "pg_stat_get_wal_receiver", pg_stat_get_wal_receiver }, + { 3318, 1, true, true, "pg_stat_get_progress_info", pg_stat_get_progress_info }, + { 3319, 2, true, false, "tsvector_filter", tsvector_filter }, + { 3320, 3, true, false, "tsvector_setweight_by_filter", tsvector_setweight_by_filter }, + { 3321, 2, true, false, "tsvector_delete_str", tsvector_delete_str }, + { 3322, 1, true, true, "tsvector_unnest", tsvector_unnest }, + { 3323, 2, true, false, "tsvector_delete_arr", tsvector_delete_arr }, + { 3324, 2, true, false, "int4_avg_combine", int4_avg_combine }, + { 3325, 2, true, false, "interval_combine", interval_combine }, + { 3326, 1, true, false, "tsvector_to_array", tsvector_to_array }, + { 3327, 1, true, false, "array_to_tsvector", array_to_tsvector }, + { 3328, 1, true, false, "bpchar_sortsupport", bpchar_sortsupport }, + { 3329, 0, true, true, "show_all_file_settings", show_all_file_settings }, + { 3330, 0, true, false, "pg_current_wal_flush_lsn", pg_current_wal_flush_lsn }, + { 3331, 1, true, false, "bytea_sortsupport", bytea_sortsupport }, + { 3332, 1, true, false, "bttext_pattern_sortsupport", bttext_pattern_sortsupport }, + { 3333, 1, true, false, "btbpchar_pattern_sortsupport", btbpchar_pattern_sortsupport }, + { 3334, 1, true, false, "pg_size_bytes", pg_size_bytes }, + { 3335, 1, true, false, "numeric_serialize", numeric_serialize }, + { 3336, 2, true, false, "numeric_deserialize", numeric_deserialize }, + { 3337, 2, false, false, "numeric_avg_combine", numeric_avg_combine }, + { 3338, 2, false, false, "numeric_poly_combine", numeric_poly_combine }, + { 3339, 1, true, false, "numeric_poly_serialize", numeric_poly_serialize }, + { 3340, 2, true, false, "numeric_poly_deserialize", numeric_poly_deserialize }, + { 3341, 2, false, false, "numeric_combine", numeric_combine }, + { 3342, 2, true, false, "float8_regr_combine", float8_regr_combine }, + { 3343, 2, true, false, "jsonb_delete_array", jsonb_delete_array }, + { 3344, 2, true, false, "cash_mul_int8", cash_mul_int8 }, + { 3345, 2, true, false, "cash_div_int8", cash_div_int8 }, + { 3348, 0, true, false, "pg_current_xact_id_if_assigned", pg_current_xact_id_if_assigned }, + { 3352, 1, true, false, "pg_get_partkeydef", pg_get_partkeydef }, + { 3353, 0, true, true, "pg_ls_logdir", pg_ls_logdir }, + { 3354, 0, true, true, "pg_ls_waldir", pg_ls_waldir }, + { 3355, 1, true, false, "pg_ndistinct_in", pg_ndistinct_in }, + { 3356, 1, true, false, "pg_ndistinct_out", pg_ndistinct_out }, + { 3357, 1, true, false, "pg_ndistinct_recv", pg_ndistinct_recv }, + { 3358, 1, true, false, "pg_ndistinct_send", pg_ndistinct_send }, + { 3359, 1, true, false, "macaddr_sortsupport", macaddr_sortsupport }, + { 3360, 1, true, false, "pg_xact_status", pg_xact_status }, + { 3376, 1, true, false, "pg_safe_snapshot_blocking_pids", pg_safe_snapshot_blocking_pids }, + { 3378, 2, true, false, "pg_isolation_test_session_is_blocked", pg_isolation_test_session_is_blocked }, + { 3382, 3, true, false, "pg_identify_object_as_address", pg_identify_object_as_address }, + { 3383, 1, true, false, "brin_minmax_opcinfo", brin_minmax_opcinfo }, + { 3384, 4, true, false, "brin_minmax_add_value", brin_minmax_add_value }, + { 3385, 3, true, false, "brin_minmax_consistent", brin_minmax_consistent }, + { 3386, 3, true, false, "brin_minmax_union", brin_minmax_union }, + { 3387, 2, false, false, "int8_avg_accum_inv", int8_avg_accum_inv }, + { 3388, 1, false, false, "numeric_poly_sum", numeric_poly_sum }, + { 3389, 1, false, false, "numeric_poly_avg", numeric_poly_avg }, + { 3390, 1, false, false, "numeric_poly_var_pop", numeric_poly_var_pop }, + { 3391, 1, false, false, "numeric_poly_var_samp", numeric_poly_var_samp }, + { 3392, 1, false, false, "numeric_poly_stddev_pop", numeric_poly_stddev_pop }, + { 3393, 1, false, false, "numeric_poly_stddev_samp", numeric_poly_stddev_samp }, + { 3396, 2, true, false, "regexp_match_no_flags", regexp_match_no_flags }, + { 3397, 3, true, false, "regexp_match", regexp_match }, + { 3399, 2, true, false, "int8_mul_cash", int8_mul_cash }, + { 3400, 0, true, true, "pg_config", pg_config }, + { 3401, 0, true, true, "pg_hba_file_rules", pg_hba_file_rules }, + { 3403, 1, true, false, "pg_statistics_obj_is_visible", pg_statistics_obj_is_visible }, + { 3404, 1, true, false, "pg_dependencies_in", pg_dependencies_in }, + { 3405, 1, true, false, "pg_dependencies_out", pg_dependencies_out }, + { 3406, 1, true, false, "pg_dependencies_recv", pg_dependencies_recv }, + { 3407, 1, true, false, "pg_dependencies_send", pg_dependencies_send }, + { 3408, 1, true, false, "pg_get_partition_constraintdef", pg_get_partition_constraintdef }, + { 3409, 2, true, false, "time_hash_extended", time_hash_extended }, + { 3410, 2, true, false, "timetz_hash_extended", timetz_hash_extended }, + { 3411, 2, true, false, "timestamp_hash_extended", timestamp_hash_extended }, + { 3412, 2, true, false, "uuid_hash_extended", uuid_hash_extended }, + { 3413, 2, true, false, "pg_lsn_hash_extended", pg_lsn_hash_extended }, + { 3414, 2, true, false, "hashenumextended", hashenumextended }, + { 3415, 1, true, false, "pg_get_statisticsobjdef", pg_get_statisticsobjdef }, + { 3416, 2, true, false, "jsonb_hash_extended", jsonb_hash_extended }, + { 3417, 2, true, false, "hash_range_extended", hash_range_extended }, + { 3418, 2, true, false, "interval_hash_extended", interval_hash_extended }, + { 3419, 1, true, false, "sha224_bytea", sha224_bytea }, + { 3420, 1, true, false, "sha256_bytea", sha256_bytea }, + { 3421, 1, true, false, "sha384_bytea", sha384_bytea }, + { 3422, 1, true, false, "sha512_bytea", sha512_bytea }, + { 3423, 1, true, true, "pg_partition_tree", pg_partition_tree }, + { 3424, 1, true, false, "pg_partition_root", pg_partition_root }, + { 3425, 1, true, true, "pg_partition_ancestors", pg_partition_ancestors }, + { 3426, 1, true, false, "pg_stat_get_db_checksum_failures", pg_stat_get_db_checksum_failures }, + { 3427, 1, true, true, "pg_stats_ext_mcvlist_items", pg_stats_ext_mcvlist_items }, + { 3428, 1, true, false, "pg_stat_get_db_checksum_last_failure", pg_stat_get_db_checksum_last_failure }, + { 3432, 0, true, false, "gen_random_uuid", gen_random_uuid }, + { 3434, 1, false, false, "gtsvector_options", gtsvector_options }, + { 3435, 1, true, false, "gist_point_sortsupport", gist_point_sortsupport }, + { 3436, 2, true, false, "pg_promote", pg_promote }, + { 3437, 4, true, false, "prefixsel", prefixsel }, + { 3438, 5, true, false, "prefixjoinsel", prefixjoinsel }, + { 3441, 0, true, false, "pg_control_system", pg_control_system }, + { 3442, 0, true, false, "pg_control_checkpoint", pg_control_checkpoint }, + { 3443, 0, true, false, "pg_control_recovery", pg_control_recovery }, + { 3444, 0, true, false, "pg_control_init", pg_control_init }, + { 3445, 1, true, false, "pg_import_system_collations", pg_import_system_collations }, + { 3446, 1, true, false, "macaddr8_recv", macaddr8_recv }, + { 3447, 1, true, false, "macaddr8_send", macaddr8_send }, + { 3448, 1, true, false, "pg_collation_actual_version", pg_collation_actual_version }, + { 3449, 1, true, false, "jsonb_numeric", jsonb_numeric }, + { 3450, 1, true, false, "jsonb_int2", jsonb_int2 }, + { 3451, 1, true, false, "jsonb_int4", jsonb_int4 }, + { 3452, 1, true, false, "jsonb_int8", jsonb_int8 }, + { 3453, 1, true, false, "jsonb_float4", jsonb_float4 }, + { 3454, 2, true, false, "pg_filenode_relation", pg_filenode_relation }, + { 3457, 2, true, false, "be_lo_from_bytea", be_lo_from_bytea }, + { 3458, 1, true, false, "be_lo_get", be_lo_get }, + { 3459, 3, true, false, "be_lo_get_fragment", be_lo_get_fragment }, + { 3460, 3, true, false, "be_lo_put", be_lo_put }, + { 3461, 6, true, false, "make_timestamp", make_timestamp }, + { 3462, 6, true, false, "make_timestamptz", make_timestamptz }, + { 3463, 7, true, false, "make_timestamptz_at_timezone", make_timestamptz_at_timezone }, + { 3464, 7, true, false, "make_interval", make_interval }, + { 3465, 1, true, true, "jsonb_array_elements_text", jsonb_array_elements_text }, + { 3469, 2, true, false, "spg_range_quad_config", spg_range_quad_config }, + { 3470, 2, true, false, "spg_range_quad_choose", spg_range_quad_choose }, + { 3471, 2, true, false, "spg_range_quad_picksplit", spg_range_quad_picksplit }, + { 3472, 2, true, false, "spg_range_quad_inner_consistent", spg_range_quad_inner_consistent }, + { 3473, 2, true, false, "spg_range_quad_leaf_consistent", spg_range_quad_leaf_consistent }, + { 3475, 2, false, true, "jsonb_populate_recordset", jsonb_populate_recordset }, + { 3476, 1, true, false, "to_regoperator", to_regoperator }, + { 3478, 2, true, false, "jsonb_object_field", jsonb_object_field }, + { 3479, 1, true, false, "to_regprocedure", to_regprocedure }, + { 3480, 2, true, false, "gin_compare_jsonb", gin_compare_jsonb }, + { 3482, 3, true, false, "gin_extract_jsonb", gin_extract_jsonb }, + { 3483, 7, true, false, "gin_extract_jsonb_query", gin_extract_jsonb_query }, + { 3484, 8, true, false, "gin_consistent_jsonb", gin_consistent_jsonb }, + { 3485, 3, true, false, "gin_extract_jsonb_path", gin_extract_jsonb_path }, + { 3486, 7, true, false, "gin_extract_jsonb_query_path", gin_extract_jsonb_query_path }, + { 3487, 8, true, false, "gin_consistent_jsonb_path", gin_consistent_jsonb_path }, + { 3488, 7, true, false, "gin_triconsistent_jsonb", gin_triconsistent_jsonb }, + { 3489, 7, true, false, "gin_triconsistent_jsonb_path", gin_triconsistent_jsonb_path }, + { 3490, 1, true, false, "jsonb_to_record", jsonb_to_record }, + { 3491, 1, false, true, "jsonb_to_recordset", jsonb_to_recordset }, + { 3492, 1, true, false, "to_regoper", to_regoper }, + { 3493, 1, true, false, "to_regtype", to_regtype }, + { 3494, 1, true, false, "to_regproc", to_regproc }, + { 3495, 1, true, false, "to_regclass", to_regclass }, + { 3496, 2, false, false, "bool_accum", bool_accum }, + { 3497, 2, false, false, "bool_accum_inv", bool_accum_inv }, + { 3498, 1, true, false, "bool_alltrue", bool_alltrue }, + { 3499, 1, true, false, "bool_anytrue", bool_anytrue }, + { 3504, 1, true, false, "anyenum_in", anyenum_in }, + { 3505, 1, true, false, "anyenum_out", anyenum_out }, + { 3506, 2, true, false, "enum_in", enum_in }, + { 3507, 1, true, false, "enum_out", enum_out }, + { 3508, 2, true, false, "enum_eq", enum_eq }, + { 3509, 2, true, false, "enum_ne", enum_ne }, + { 3510, 2, true, false, "enum_lt", enum_lt }, + { 3511, 2, true, false, "enum_gt", enum_gt }, + { 3512, 2, true, false, "enum_le", enum_le }, + { 3513, 2, true, false, "enum_ge", enum_ge }, + { 3514, 2, true, false, "enum_cmp", enum_cmp }, + { 3515, 1, true, false, "hashenum", hashenum }, + { 3524, 2, true, false, "enum_smaller", enum_smaller }, + { 3525, 2, true, false, "enum_larger", enum_larger }, + { 3528, 1, false, false, "enum_first", enum_first }, + { 3529, 1, false, false, "enum_last", enum_last }, + { 3530, 2, false, false, "enum_range_bounds", enum_range_bounds }, + { 3531, 1, false, false, "enum_range_all", enum_range_all }, + { 3532, 2, true, false, "enum_recv", enum_recv }, + { 3533, 1, true, false, "enum_send", enum_send }, + { 3535, 3, false, false, "string_agg_transfn", string_agg_transfn }, + { 3536, 1, false, false, "string_agg_finalfn", string_agg_finalfn }, + { 3537, 3, true, false, "pg_describe_object", pg_describe_object }, + { 3539, 2, false, false, "text_format", text_format }, + { 3540, 1, false, false, "text_format_nv", text_format_nv }, + { 3543, 3, false, false, "bytea_string_agg_transfn", bytea_string_agg_transfn }, + { 3544, 1, false, false, "bytea_string_agg_finalfn", bytea_string_agg_finalfn }, + { 3546, 1, true, false, "int8dec", int8dec }, + { 3547, 2, true, false, "int8dec_any", int8dec_any }, + { 3548, 2, false, false, "numeric_accum_inv", numeric_accum_inv }, + { 3549, 2, true, false, "interval_accum_inv", interval_accum_inv }, + { 3551, 2, true, false, "network_overlap", network_overlap }, + { 3553, 5, true, false, "inet_gist_consistent", inet_gist_consistent }, + { 3554, 2, true, false, "inet_gist_union", inet_gist_union }, + { 3555, 1, true, false, "inet_gist_compress", inet_gist_compress }, + { 3556, 1, true, false, "jsonb_bool", jsonb_bool }, + { 3557, 3, true, false, "inet_gist_penalty", inet_gist_penalty }, + { 3558, 2, true, false, "inet_gist_picksplit", inet_gist_picksplit }, + { 3559, 3, true, false, "inet_gist_same", inet_gist_same }, + { 3560, 4, true, false, "networksel", networksel }, + { 3561, 5, true, false, "networkjoinsel", networkjoinsel }, + { 3562, 2, true, false, "network_larger", network_larger }, + { 3563, 2, true, false, "network_smaller", network_smaller }, + { 3566, 0, true, true, "pg_event_trigger_dropped_objects", pg_event_trigger_dropped_objects }, + { 3567, 2, false, false, "int2_accum_inv", int2_accum_inv }, + { 3568, 2, false, false, "int4_accum_inv", int4_accum_inv }, + { 3569, 2, false, false, "int8_accum_inv", int8_accum_inv }, + { 3570, 2, true, false, "int2_avg_accum_inv", int2_avg_accum_inv }, + { 3571, 2, true, false, "int4_avg_accum_inv", int4_avg_accum_inv }, + { 3572, 1, true, false, "int2int4_sum", int2int4_sum }, + { 3573, 1, true, false, "inet_gist_fetch", inet_gist_fetch }, + { 3577, 3, true, false, "pg_logical_emit_message_text", pg_logical_emit_message_text }, + { 3578, 3, true, false, "pg_logical_emit_message_bytea", pg_logical_emit_message_bytea }, + { 3579, 4, true, false, "jsonb_insert", jsonb_insert }, + { 3581, 1, true, false, "pg_xact_commit_timestamp", pg_xact_commit_timestamp }, + { 3582, 1, true, false, "binary_upgrade_set_next_pg_type_oid", binary_upgrade_set_next_pg_type_oid }, + { 3583, 0, true, false, "pg_last_committed_xact", pg_last_committed_xact }, + { 3584, 1, true, false, "binary_upgrade_set_next_array_pg_type_oid", binary_upgrade_set_next_array_pg_type_oid }, + { 3586, 1, true, false, "binary_upgrade_set_next_heap_pg_class_oid", binary_upgrade_set_next_heap_pg_class_oid }, + { 3587, 1, true, false, "binary_upgrade_set_next_index_pg_class_oid", binary_upgrade_set_next_index_pg_class_oid }, + { 3588, 1, true, false, "binary_upgrade_set_next_toast_pg_class_oid", binary_upgrade_set_next_toast_pg_class_oid }, + { 3589, 1, true, false, "binary_upgrade_set_next_pg_enum_oid", binary_upgrade_set_next_pg_enum_oid }, + { 3590, 1, true, false, "binary_upgrade_set_next_pg_authid_oid", binary_upgrade_set_next_pg_authid_oid }, + { 3591, 7, false, false, "binary_upgrade_create_empty_extension", binary_upgrade_create_empty_extension }, + { 3594, 1, false, false, "event_trigger_in", event_trigger_in }, + { 3595, 1, true, false, "event_trigger_out", event_trigger_out }, + { 3610, 1, true, false, "tsvectorin", tsvectorin }, + { 3611, 1, true, false, "tsvectorout", tsvectorout }, + { 3612, 1, true, false, "tsqueryin", tsqueryin }, + { 3613, 1, true, false, "tsqueryout", tsqueryout }, + { 3616, 2, true, false, "tsvector_lt", tsvector_lt }, + { 3617, 2, true, false, "tsvector_le", tsvector_le }, + { 3618, 2, true, false, "tsvector_eq", tsvector_eq }, + { 3619, 2, true, false, "tsvector_ne", tsvector_ne }, + { 3620, 2, true, false, "tsvector_ge", tsvector_ge }, + { 3621, 2, true, false, "tsvector_gt", tsvector_gt }, + { 3622, 2, true, false, "tsvector_cmp", tsvector_cmp }, + { 3623, 1, true, false, "tsvector_strip", tsvector_strip }, + { 3624, 2, true, false, "tsvector_setweight", tsvector_setweight }, + { 3625, 2, true, false, "tsvector_concat", tsvector_concat }, + { 3634, 2, true, false, "ts_match_vq", ts_match_vq }, + { 3635, 2, true, false, "ts_match_qv", ts_match_qv }, + { 3638, 1, true, false, "tsvectorsend", tsvectorsend }, + { 3639, 1, true, false, "tsvectorrecv", tsvectorrecv }, + { 3640, 1, true, false, "tsquerysend", tsquerysend }, + { 3641, 1, true, false, "tsqueryrecv", tsqueryrecv }, + { 3646, 1, true, false, "gtsvectorin", gtsvectorin }, + { 3647, 1, true, false, "gtsvectorout", gtsvectorout }, + { 3648, 1, true, false, "gtsvector_compress", gtsvector_compress }, + { 3649, 1, true, false, "gtsvector_decompress", gtsvector_decompress }, + { 3650, 2, true, false, "gtsvector_picksplit", gtsvector_picksplit }, + { 3651, 2, true, false, "gtsvector_union", gtsvector_union }, + { 3652, 3, true, false, "gtsvector_same", gtsvector_same }, + { 3653, 3, true, false, "gtsvector_penalty", gtsvector_penalty }, + { 3654, 5, true, false, "gtsvector_consistent", gtsvector_consistent }, + { 3656, 3, true, false, "gin_extract_tsvector", gin_extract_tsvector }, + { 3657, 7, true, false, "gin_extract_tsquery", gin_extract_tsquery }, + { 3658, 8, true, false, "gin_tsquery_consistent", gin_tsquery_consistent }, + { 3662, 2, true, false, "tsquery_lt", tsquery_lt }, + { 3663, 2, true, false, "tsquery_le", tsquery_le }, + { 3664, 2, true, false, "tsquery_eq", tsquery_eq }, + { 3665, 2, true, false, "tsquery_ne", tsquery_ne }, + { 3666, 2, true, false, "tsquery_ge", tsquery_ge }, + { 3667, 2, true, false, "tsquery_gt", tsquery_gt }, + { 3668, 2, true, false, "tsquery_cmp", tsquery_cmp }, + { 3669, 2, true, false, "tsquery_and", tsquery_and }, + { 3670, 2, true, false, "tsquery_or", tsquery_or }, + { 3671, 1, true, false, "tsquery_not", tsquery_not }, + { 3672, 1, true, false, "tsquery_numnode", tsquery_numnode }, + { 3673, 1, true, false, "tsquerytree", tsquerytree }, + { 3684, 3, true, false, "tsquery_rewrite", tsquery_rewrite }, + { 3685, 2, true, false, "tsquery_rewrite_query", tsquery_rewrite_query }, + { 3686, 4, true, false, "tsmatchsel", tsmatchsel }, + { 3687, 5, true, false, "tsmatchjoinsel", tsmatchjoinsel }, + { 3688, 1, true, false, "ts_typanalyze", ts_typanalyze }, + { 3689, 1, true, true, "ts_stat1", ts_stat1 }, + { 3690, 2, true, true, "ts_stat2", ts_stat2 }, + { 3691, 2, true, false, "tsq_mcontains", tsq_mcontains }, + { 3692, 2, true, false, "tsq_mcontained", tsq_mcontained }, + { 3695, 1, true, false, "gtsquery_compress", gtsquery_compress }, + { 3696, 2, true, false, "text_starts_with", text_starts_with }, + { 3697, 2, true, false, "gtsquery_picksplit", gtsquery_picksplit }, + { 3698, 2, true, false, "gtsquery_union", gtsquery_union }, + { 3699, 3, true, false, "gtsquery_same", gtsquery_same }, + { 3700, 3, true, false, "gtsquery_penalty", gtsquery_penalty }, + { 3701, 5, true, false, "gtsquery_consistent", gtsquery_consistent }, + { 3703, 4, true, false, "ts_rank_wttf", ts_rank_wttf }, + { 3704, 3, true, false, "ts_rank_wtt", ts_rank_wtt }, + { 3705, 3, true, false, "ts_rank_ttf", ts_rank_ttf }, + { 3706, 2, true, false, "ts_rank_tt", ts_rank_tt }, + { 3707, 4, true, false, "ts_rankcd_wttf", ts_rankcd_wttf }, + { 3708, 3, true, false, "ts_rankcd_wtt", ts_rankcd_wtt }, + { 3709, 3, true, false, "ts_rankcd_ttf", ts_rankcd_ttf }, + { 3710, 2, true, false, "ts_rankcd_tt", ts_rankcd_tt }, + { 3711, 1, true, false, "tsvector_length", tsvector_length }, + { 3713, 1, true, true, "ts_token_type_byid", ts_token_type_byid }, + { 3714, 1, true, true, "ts_token_type_byname", ts_token_type_byname }, + { 3715, 2, true, true, "ts_parse_byid", ts_parse_byid }, + { 3716, 2, true, true, "ts_parse_byname", ts_parse_byname }, + { 3717, 2, true, false, "prsd_start", prsd_start }, + { 3718, 3, true, false, "prsd_nexttoken", prsd_nexttoken }, + { 3719, 1, true, false, "prsd_end", prsd_end }, + { 3720, 3, true, false, "prsd_headline", prsd_headline }, + { 3721, 1, true, false, "prsd_lextype", prsd_lextype }, + { 3723, 2, true, false, "ts_lexize", ts_lexize }, + { 3724, 2, true, false, "gin_cmp_tslexeme", gin_cmp_tslexeme }, + { 3725, 1, true, false, "dsimple_init", dsimple_init }, + { 3726, 4, true, false, "dsimple_lexize", dsimple_lexize }, + { 3728, 1, true, false, "dsynonym_init", dsynonym_init }, + { 3729, 4, true, false, "dsynonym_lexize", dsynonym_lexize }, + { 3731, 1, true, false, "dispell_init", dispell_init }, + { 3732, 4, true, false, "dispell_lexize", dispell_lexize }, + { 3736, 1, true, false, "regconfigin", regconfigin }, + { 3737, 1, true, false, "regconfigout", regconfigout }, + { 3738, 1, true, false, "regconfigrecv", regconfigrecv }, + { 3739, 1, true, false, "regconfigsend", regconfigsend }, + { 3740, 1, true, false, "thesaurus_init", thesaurus_init }, + { 3741, 4, true, false, "thesaurus_lexize", thesaurus_lexize }, + { 3743, 4, true, false, "ts_headline_byid_opt", ts_headline_byid_opt }, + { 3744, 3, true, false, "ts_headline_byid", ts_headline_byid }, + { 3745, 2, true, false, "to_tsvector_byid", to_tsvector_byid }, + { 3746, 2, true, false, "to_tsquery_byid", to_tsquery_byid }, + { 3747, 2, true, false, "plainto_tsquery_byid", plainto_tsquery_byid }, + { 3749, 1, true, false, "to_tsvector", to_tsvector }, + { 3750, 1, true, false, "to_tsquery", to_tsquery }, + { 3751, 1, true, false, "plainto_tsquery", plainto_tsquery }, + { 3752, 0, false, false, "tsvector_update_trigger_byid", tsvector_update_trigger_byid }, + { 3753, 0, false, false, "tsvector_update_trigger_bycolumn", tsvector_update_trigger_bycolumn }, + { 3754, 3, true, false, "ts_headline_opt", ts_headline_opt }, + { 3755, 2, true, false, "ts_headline", ts_headline }, + { 3756, 1, true, false, "pg_ts_parser_is_visible", pg_ts_parser_is_visible }, + { 3757, 1, true, false, "pg_ts_dict_is_visible", pg_ts_dict_is_visible }, + { 3758, 1, true, false, "pg_ts_config_is_visible", pg_ts_config_is_visible }, + { 3759, 0, true, false, "get_current_ts_config", get_current_ts_config }, + { 3760, 2, true, false, "ts_match_tt", ts_match_tt }, + { 3761, 2, true, false, "ts_match_tq", ts_match_tq }, + { 3768, 1, true, false, "pg_ts_template_is_visible", pg_ts_template_is_visible }, + { 3771, 1, true, false, "regdictionaryin", regdictionaryin }, + { 3772, 1, true, false, "regdictionaryout", regdictionaryout }, + { 3773, 1, true, false, "regdictionaryrecv", regdictionaryrecv }, + { 3774, 1, true, false, "regdictionarysend", regdictionarysend }, + { 3775, 1, true, false, "pg_stat_reset_shared", pg_stat_reset_shared }, + { 3776, 1, true, false, "pg_stat_reset_single_table_counters", pg_stat_reset_single_table_counters }, + { 3777, 1, true, false, "pg_stat_reset_single_function_counters", pg_stat_reset_single_function_counters }, + { 3778, 1, true, false, "pg_tablespace_location", pg_tablespace_location }, + { 3779, 3, true, false, "pg_create_physical_replication_slot", pg_create_physical_replication_slot }, + { 3780, 1, true, false, "pg_drop_replication_slot", pg_drop_replication_slot }, + { 3781, 0, false, true, "pg_get_replication_slots", pg_get_replication_slots }, + { 3782, 4, false, true, "pg_logical_slot_get_changes", pg_logical_slot_get_changes }, + { 3783, 4, false, true, "pg_logical_slot_get_binary_changes", pg_logical_slot_get_binary_changes }, + { 3784, 4, false, true, "pg_logical_slot_peek_changes", pg_logical_slot_peek_changes }, + { 3785, 4, false, true, "pg_logical_slot_peek_binary_changes", pg_logical_slot_peek_binary_changes }, + { 3786, 4, true, false, "pg_create_logical_replication_slot", pg_create_logical_replication_slot }, + { 3787, 1, true, false, "to_jsonb", to_jsonb }, + { 3788, 0, true, false, "pg_stat_get_snapshot_timestamp", pg_stat_get_snapshot_timestamp }, + { 3789, 1, true, false, "gin_clean_pending_list", gin_clean_pending_list }, + { 3790, 5, true, false, "gtsvector_consistent_oldsig", gtsvector_consistent_oldsig }, + { 3791, 7, true, false, "gin_extract_tsquery_oldsig", gin_extract_tsquery_oldsig }, + { 3792, 8, true, false, "gin_tsquery_consistent_oldsig", gin_tsquery_consistent_oldsig }, + { 3793, 5, true, false, "gtsquery_consistent_oldsig", gtsquery_consistent_oldsig }, + { 3795, 2, true, false, "inet_spg_config", inet_spg_config }, + { 3796, 2, true, false, "inet_spg_choose", inet_spg_choose }, + { 3797, 2, true, false, "inet_spg_picksplit", inet_spg_picksplit }, + { 3798, 2, true, false, "inet_spg_inner_consistent", inet_spg_inner_consistent }, + { 3799, 2, true, false, "inet_spg_leaf_consistent", inet_spg_leaf_consistent }, + { 3800, 0, false, false, "pg_current_logfile", pg_current_logfile }, + { 3801, 1, false, false, "pg_current_logfile_1arg", pg_current_logfile_1arg }, + { 3803, 1, true, false, "jsonb_send", jsonb_send }, + { 3804, 1, true, false, "jsonb_out", jsonb_out }, + { 3805, 1, true, false, "jsonb_recv", jsonb_recv }, + { 3806, 1, true, false, "jsonb_in", jsonb_in }, + { 3808, 2, true, false, "pg_get_function_arg_default", pg_get_function_arg_default }, + { 3809, 0, true, false, "pg_export_snapshot", pg_export_snapshot }, + { 3810, 0, true, false, "pg_is_in_recovery", pg_is_in_recovery }, + { 3811, 1, true, false, "int4_cash", int4_cash }, + { 3812, 1, true, false, "int8_cash", int8_cash }, + { 3815, 1, true, false, "pg_collation_is_visible", pg_collation_is_visible }, + { 3816, 1, true, false, "array_typanalyze", array_typanalyze }, + { 3817, 4, true, false, "arraycontsel", arraycontsel }, + { 3818, 5, true, false, "arraycontjoinsel", arraycontjoinsel }, + { 3819, 1, true, true, "pg_get_multixact_members", pg_get_multixact_members }, + { 3820, 0, true, false, "pg_last_wal_receive_lsn", pg_last_wal_receive_lsn }, + { 3821, 0, true, false, "pg_last_wal_replay_lsn", pg_last_wal_replay_lsn }, + { 3822, 2, true, false, "cash_div_cash", cash_div_cash }, + { 3823, 1, true, false, "cash_numeric", cash_numeric }, + { 3824, 1, true, false, "numeric_cash", numeric_cash }, + { 3826, 1, true, false, "pg_read_file_all", pg_read_file_all }, + { 3827, 3, true, false, "pg_read_binary_file_off_len", pg_read_binary_file_off_len }, + { 3828, 1, true, false, "pg_read_binary_file_all", pg_read_binary_file_all }, + { 3829, 1, true, false, "pg_opfamily_is_visible", pg_opfamily_is_visible }, + { 3830, 0, true, false, "pg_last_xact_replay_timestamp", pg_last_xact_replay_timestamp }, + { 3832, 3, true, false, "anyrange_in", anyrange_in }, + { 3833, 1, true, false, "anyrange_out", anyrange_out }, + { 3834, 3, true, false, "range_in", range_in }, + { 3835, 1, true, false, "range_out", range_out }, + { 3836, 3, true, false, "range_recv", range_recv }, + { 3837, 1, true, false, "range_send", range_send }, + { 3839, 3, true, false, "pg_identify_object", pg_identify_object }, + { 3840, 2, false, false, "range_constructor2", range_constructor2 }, + { 3841, 3, false, false, "range_constructor3", range_constructor3 }, + { 3842, 2, true, false, "pg_relation_is_updatable", pg_relation_is_updatable }, + { 3843, 3, true, false, "pg_column_is_updatable", pg_column_is_updatable }, + { 3844, 2, false, false, "range_constructor2", range_constructor2 }, + { 3845, 3, false, false, "range_constructor3", range_constructor3 }, + { 3846, 3, true, false, "make_date", make_date }, + { 3847, 3, true, false, "make_time", make_time }, + { 3848, 1, true, false, "range_lower", range_lower }, + { 3849, 1, true, false, "range_upper", range_upper }, + { 3850, 1, true, false, "range_empty", range_empty }, + { 3851, 1, true, false, "range_lower_inc", range_lower_inc }, + { 3852, 1, true, false, "range_upper_inc", range_upper_inc }, + { 3853, 1, true, false, "range_lower_inf", range_lower_inf }, + { 3854, 1, true, false, "range_upper_inf", range_upper_inf }, + { 3855, 2, true, false, "range_eq", range_eq }, + { 3856, 2, true, false, "range_ne", range_ne }, + { 3857, 2, true, false, "range_overlaps", range_overlaps }, + { 3858, 2, true, false, "range_contains_elem", range_contains_elem }, + { 3859, 2, true, false, "range_contains", range_contains }, + { 3860, 2, true, false, "elem_contained_by_range", elem_contained_by_range }, + { 3861, 2, true, false, "range_contained_by", range_contained_by }, + { 3862, 2, true, false, "range_adjacent", range_adjacent }, + { 3863, 2, true, false, "range_before", range_before }, + { 3864, 2, true, false, "range_after", range_after }, + { 3865, 2, true, false, "range_overleft", range_overleft }, + { 3866, 2, true, false, "range_overright", range_overright }, + { 3867, 2, true, false, "range_union", range_union }, + { 3868, 2, true, false, "range_intersect", range_intersect }, + { 3869, 2, true, false, "range_minus", range_minus }, + { 3870, 2, true, false, "range_cmp", range_cmp }, + { 3871, 2, true, false, "range_lt", range_lt }, + { 3872, 2, true, false, "range_le", range_le }, + { 3873, 2, true, false, "range_ge", range_ge }, + { 3874, 2, true, false, "range_gt", range_gt }, + { 3875, 5, true, false, "range_gist_consistent", range_gist_consistent }, + { 3876, 2, true, false, "range_gist_union", range_gist_union }, + { 3878, 2, true, false, "pg_replication_slot_advance", pg_replication_slot_advance }, + { 3879, 3, true, false, "range_gist_penalty", range_gist_penalty }, + { 3880, 2, true, false, "range_gist_picksplit", range_gist_picksplit }, + { 3881, 3, true, false, "range_gist_same", range_gist_same }, + { 3902, 1, true, false, "hash_range", hash_range }, + { 3914, 1, true, false, "int4range_canonical", int4range_canonical }, + { 3915, 1, true, false, "daterange_canonical", daterange_canonical }, + { 3916, 1, true, false, "range_typanalyze", range_typanalyze }, + { 3917, 1, true, false, "timestamp_support", timestamp_support }, + { 3918, 1, true, false, "interval_support", interval_support }, + { 3920, 7, true, false, "ginarraytriconsistent", ginarraytriconsistent }, + { 3921, 7, true, false, "gin_tsquery_triconsistent", gin_tsquery_triconsistent }, + { 3922, 2, true, false, "int4range_subdiff", int4range_subdiff }, + { 3923, 2, true, false, "int8range_subdiff", int8range_subdiff }, + { 3924, 2, true, false, "numrange_subdiff", numrange_subdiff }, + { 3925, 2, true, false, "daterange_subdiff", daterange_subdiff }, + { 3928, 1, true, false, "int8range_canonical", int8range_canonical }, + { 3929, 2, true, false, "tsrange_subdiff", tsrange_subdiff }, + { 3930, 2, true, false, "tstzrange_subdiff", tstzrange_subdiff }, + { 3931, 1, true, true, "jsonb_object_keys", jsonb_object_keys }, + { 3932, 1, true, true, "jsonb_each_text", jsonb_each_text }, + { 3933, 2, false, false, "range_constructor2", range_constructor2 }, + { 3934, 3, false, false, "range_constructor3", range_constructor3 }, + { 3937, 2, false, false, "range_constructor2", range_constructor2 }, + { 3938, 3, false, false, "range_constructor3", range_constructor3 }, + { 3939, 1, true, false, "mxid_age", mxid_age }, + { 3940, 2, true, false, "jsonb_extract_path_text", jsonb_extract_path_text }, + { 3941, 2, false, false, "range_constructor2", range_constructor2 }, + { 3942, 3, false, false, "range_constructor3", range_constructor3 }, + { 3943, 2, true, false, "acldefault_sql", acldefault_sql }, + { 3944, 1, true, false, "time_support", time_support }, + { 3945, 2, false, false, "range_constructor2", range_constructor2 }, + { 3946, 3, false, false, "range_constructor3", range_constructor3 }, + { 3947, 2, true, false, "json_object_field", json_object_field }, + { 3948, 2, true, false, "json_object_field_text", json_object_field_text }, + { 3949, 2, true, false, "json_array_element", json_array_element }, + { 3950, 2, true, false, "json_array_element_text", json_array_element_text }, + { 3951, 2, true, false, "json_extract_path", json_extract_path }, + { 3952, 1, true, false, "brin_summarize_new_values", brin_summarize_new_values }, + { 3953, 2, true, false, "json_extract_path_text", json_extract_path_text }, + { 3954, 3, true, false, "pg_get_object_address", pg_get_object_address }, + { 3955, 1, true, true, "json_array_elements", json_array_elements }, + { 3956, 1, true, false, "json_array_length", json_array_length }, + { 3957, 1, true, true, "json_object_keys", json_object_keys }, + { 3958, 1, true, true, "json_each", json_each }, + { 3959, 1, true, true, "json_each_text", json_each_text }, + { 3960, 3, false, false, "json_populate_record", json_populate_record }, + { 3961, 3, false, true, "json_populate_recordset", json_populate_recordset }, + { 3968, 1, true, false, "json_typeof", json_typeof }, + { 3969, 1, true, true, "json_array_elements_text", json_array_elements_text }, + { 3970, 2, false, false, "ordered_set_transition", ordered_set_transition }, + { 3971, 2, false, false, "ordered_set_transition_multi", ordered_set_transition_multi }, + { 3973, 3, false, false, "percentile_disc_final", percentile_disc_final }, + { 3975, 2, false, false, "percentile_cont_float8_final", percentile_cont_float8_final }, + { 3977, 2, false, false, "percentile_cont_interval_final", percentile_cont_interval_final }, + { 3979, 3, false, false, "percentile_disc_multi_final", percentile_disc_multi_final }, + { 3981, 2, false, false, "percentile_cont_float8_multi_final", percentile_cont_float8_multi_final }, + { 3983, 2, false, false, "percentile_cont_interval_multi_final", percentile_cont_interval_multi_final }, + { 3985, 2, false, false, "mode_final", mode_final }, + { 3987, 2, false, false, "hypothetical_rank_final", hypothetical_rank_final }, + { 3989, 2, false, false, "hypothetical_percent_rank_final", hypothetical_percent_rank_final }, + { 3991, 2, false, false, "hypothetical_cume_dist_final", hypothetical_cume_dist_final }, + { 3993, 2, false, false, "hypothetical_dense_rank_final", hypothetical_dense_rank_final }, + { 3994, 1, true, false, "generate_series_int4_support", generate_series_int4_support }, + { 3995, 1, true, false, "generate_series_int8_support", generate_series_int8_support }, + { 3996, 1, true, false, "array_unnest_support", array_unnest_support }, + { 3998, 5, true, false, "gist_box_distance", gist_box_distance }, + { 3999, 2, true, false, "brin_summarize_range", brin_summarize_range }, + { 4001, 1, true, false, "jsonpath_in", jsonpath_in }, + { 4002, 1, true, false, "jsonpath_recv", jsonpath_recv }, + { 4003, 1, true, false, "jsonpath_out", jsonpath_out }, + { 4004, 1, true, false, "jsonpath_send", jsonpath_send }, + { 4005, 4, true, false, "jsonb_path_exists", jsonb_path_exists }, + { 4006, 4, true, true, "jsonb_path_query", jsonb_path_query }, + { 4007, 4, true, false, "jsonb_path_query_array", jsonb_path_query_array }, + { 4008, 4, true, false, "jsonb_path_query_first", jsonb_path_query_first }, + { 4009, 4, true, false, "jsonb_path_match", jsonb_path_match }, + { 4010, 2, true, false, "jsonb_path_exists_opr", jsonb_path_exists_opr }, + { 4011, 2, true, false, "jsonb_path_match_opr", jsonb_path_match_opr }, + { 4014, 2, true, false, "brin_desummarize_range", brin_desummarize_range }, + { 4018, 2, true, false, "spg_quad_config", spg_quad_config }, + { 4019, 2, true, false, "spg_quad_choose", spg_quad_choose }, + { 4020, 2, true, false, "spg_quad_picksplit", spg_quad_picksplit }, + { 4021, 2, true, false, "spg_quad_inner_consistent", spg_quad_inner_consistent }, + { 4022, 2, true, false, "spg_quad_leaf_consistent", spg_quad_leaf_consistent }, + { 4023, 2, true, false, "spg_kd_config", spg_kd_config }, + { 4024, 2, true, false, "spg_kd_choose", spg_kd_choose }, + { 4025, 2, true, false, "spg_kd_picksplit", spg_kd_picksplit }, + { 4026, 2, true, false, "spg_kd_inner_consistent", spg_kd_inner_consistent }, + { 4027, 2, true, false, "spg_text_config", spg_text_config }, + { 4028, 2, true, false, "spg_text_choose", spg_text_choose }, + { 4029, 2, true, false, "spg_text_picksplit", spg_text_picksplit }, + { 4030, 2, true, false, "spg_text_inner_consistent", spg_text_inner_consistent }, + { 4031, 2, true, false, "spg_text_leaf_consistent", spg_text_leaf_consistent }, + { 4032, 1, true, false, "pg_sequence_last_value", pg_sequence_last_value }, + { 4038, 2, true, false, "jsonb_ne", jsonb_ne }, + { 4039, 2, true, false, "jsonb_lt", jsonb_lt }, + { 4040, 2, true, false, "jsonb_gt", jsonb_gt }, + { 4041, 2, true, false, "jsonb_le", jsonb_le }, + { 4042, 2, true, false, "jsonb_ge", jsonb_ge }, + { 4043, 2, true, false, "jsonb_eq", jsonb_eq }, + { 4044, 2, true, false, "jsonb_cmp", jsonb_cmp }, + { 4045, 1, true, false, "jsonb_hash", jsonb_hash }, + { 4046, 2, true, false, "jsonb_contains", jsonb_contains }, + { 4047, 2, true, false, "jsonb_exists", jsonb_exists }, + { 4048, 2, true, false, "jsonb_exists_any", jsonb_exists_any }, + { 4049, 2, true, false, "jsonb_exists_all", jsonb_exists_all }, + { 4050, 2, true, false, "jsonb_contained", jsonb_contained }, + { 4051, 2, false, false, "array_agg_array_transfn", array_agg_array_transfn }, + { 4052, 2, false, false, "array_agg_array_finalfn", array_agg_array_finalfn }, + { 4057, 2, true, false, "range_merge", range_merge }, + { 4063, 2, true, false, "inet_merge", inet_merge }, + { 4067, 2, true, false, "boxes_bound_box", boxes_bound_box }, + { 4071, 2, true, false, "inet_same_family", inet_same_family }, + { 4083, 1, true, false, "binary_upgrade_set_record_init_privs", binary_upgrade_set_record_init_privs }, + { 4084, 1, true, false, "regnamespacein", regnamespacein }, + { 4085, 1, true, false, "regnamespaceout", regnamespaceout }, + { 4086, 1, true, false, "to_regnamespace", to_regnamespace }, + { 4087, 1, true, false, "regnamespacerecv", regnamespacerecv }, + { 4088, 1, true, false, "regnamespacesend", regnamespacesend }, + { 4091, 1, true, false, "point_box", point_box }, + { 4092, 1, true, false, "regroleout", regroleout }, + { 4093, 1, true, false, "to_regrole", to_regrole }, + { 4094, 1, true, false, "regrolerecv", regrolerecv }, + { 4095, 1, true, false, "regrolesend", regrolesend }, + { 4098, 1, true, false, "regrolein", regrolein }, + { 4099, 0, true, false, "pg_rotate_logfile", pg_rotate_logfile }, + { 4100, 3, true, false, "pg_read_file", pg_read_file }, + { 4101, 3, true, false, "binary_upgrade_set_missing_value", binary_upgrade_set_missing_value }, + { 4105, 1, true, false, "brin_inclusion_opcinfo", brin_inclusion_opcinfo }, + { 4106, 4, true, false, "brin_inclusion_add_value", brin_inclusion_add_value }, + { 4107, 3, true, false, "brin_inclusion_consistent", brin_inclusion_consistent }, + { 4108, 3, true, false, "brin_inclusion_union", brin_inclusion_union }, + { 4110, 1, true, false, "macaddr8_in", macaddr8_in }, + { 4111, 1, true, false, "macaddr8_out", macaddr8_out }, + { 4112, 1, true, false, "macaddr8_trunc", macaddr8_trunc }, + { 4113, 2, true, false, "macaddr8_eq", macaddr8_eq }, + { 4114, 2, true, false, "macaddr8_lt", macaddr8_lt }, + { 4115, 2, true, false, "macaddr8_le", macaddr8_le }, + { 4116, 2, true, false, "macaddr8_gt", macaddr8_gt }, + { 4117, 2, true, false, "macaddr8_ge", macaddr8_ge }, + { 4118, 2, true, false, "macaddr8_ne", macaddr8_ne }, + { 4119, 2, true, false, "macaddr8_cmp", macaddr8_cmp }, + { 4120, 1, true, false, "macaddr8_not", macaddr8_not }, + { 4121, 2, true, false, "macaddr8_and", macaddr8_and }, + { 4122, 2, true, false, "macaddr8_or", macaddr8_or }, + { 4123, 1, true, false, "macaddrtomacaddr8", macaddrtomacaddr8 }, + { 4124, 1, true, false, "macaddr8tomacaddr", macaddr8tomacaddr }, + { 4125, 1, true, false, "macaddr8_set7bit", macaddr8_set7bit }, + { 4126, 5, true, false, "in_range_int8_int8", in_range_int8_int8 }, + { 4127, 5, true, false, "in_range_int4_int8", in_range_int4_int8 }, + { 4128, 5, true, false, "in_range_int4_int4", in_range_int4_int4 }, + { 4129, 5, true, false, "in_range_int4_int2", in_range_int4_int2 }, + { 4130, 5, true, false, "in_range_int2_int8", in_range_int2_int8 }, + { 4131, 5, true, false, "in_range_int2_int4", in_range_int2_int4 }, + { 4132, 5, true, false, "in_range_int2_int2", in_range_int2_int2 }, + { 4133, 5, true, false, "in_range_date_interval", in_range_date_interval }, + { 4134, 5, true, false, "in_range_timestamp_interval", in_range_timestamp_interval }, + { 4135, 5, true, false, "in_range_timestamptz_interval", in_range_timestamptz_interval }, + { 4136, 5, true, false, "in_range_interval_interval", in_range_interval_interval }, + { 4137, 5, true, false, "in_range_time_interval", in_range_time_interval }, + { 4138, 5, true, false, "in_range_timetz_interval", in_range_timetz_interval }, + { 4139, 5, true, false, "in_range_float8_float8", in_range_float8_float8 }, + { 4140, 5, true, false, "in_range_float4_float8", in_range_float4_float8 }, + { 4141, 5, true, false, "in_range_numeric_numeric", in_range_numeric_numeric }, + { 4187, 2, true, false, "pg_lsn_larger", pg_lsn_larger }, + { 4188, 2, true, false, "pg_lsn_smaller", pg_lsn_smaller }, + { 4193, 1, true, false, "regcollationin", regcollationin }, + { 4194, 1, true, false, "regcollationout", regcollationout }, + { 4195, 1, true, false, "to_regcollation", to_regcollation }, + { 4196, 1, true, false, "regcollationrecv", regcollationrecv }, + { 4197, 1, true, false, "regcollationsend", regcollationsend }, + { 4201, 4, true, false, "ts_headline_jsonb_byid_opt", ts_headline_jsonb_byid_opt }, + { 4202, 3, true, false, "ts_headline_jsonb_byid", ts_headline_jsonb_byid }, + { 4203, 3, true, false, "ts_headline_jsonb_opt", ts_headline_jsonb_opt }, + { 4204, 2, true, false, "ts_headline_jsonb", ts_headline_jsonb }, + { 4205, 4, true, false, "ts_headline_json_byid_opt", ts_headline_json_byid_opt }, + { 4206, 3, true, false, "ts_headline_json_byid", ts_headline_json_byid }, + { 4207, 3, true, false, "ts_headline_json_opt", ts_headline_json_opt }, + { 4208, 2, true, false, "ts_headline_json", ts_headline_json }, + { 4209, 1, true, false, "jsonb_string_to_tsvector", jsonb_string_to_tsvector }, + { 4210, 1, true, false, "json_string_to_tsvector", json_string_to_tsvector }, + { 4211, 2, true, false, "jsonb_string_to_tsvector_byid", jsonb_string_to_tsvector_byid }, + { 4212, 2, true, false, "json_string_to_tsvector_byid", json_string_to_tsvector_byid }, + { 4213, 2, true, false, "jsonb_to_tsvector", jsonb_to_tsvector }, + { 4214, 3, true, false, "jsonb_to_tsvector_byid", jsonb_to_tsvector_byid }, + { 4215, 2, true, false, "json_to_tsvector", json_to_tsvector }, + { 4216, 3, true, false, "json_to_tsvector_byid", json_to_tsvector_byid }, + { 4220, 3, true, false, "pg_copy_physical_replication_slot_a", pg_copy_physical_replication_slot_a }, + { 4221, 2, true, false, "pg_copy_physical_replication_slot_b", pg_copy_physical_replication_slot_b }, + { 4222, 4, true, false, "pg_copy_logical_replication_slot_a", pg_copy_logical_replication_slot_a }, + { 4223, 3, true, false, "pg_copy_logical_replication_slot_b", pg_copy_logical_replication_slot_b }, + { 4224, 2, true, false, "pg_copy_logical_replication_slot_c", pg_copy_logical_replication_slot_c }, + { 4226, 3, true, false, "anycompatiblemultirange_in", anycompatiblemultirange_in }, + { 4227, 1, true, false, "anycompatiblemultirange_out", anycompatiblemultirange_out }, + { 4228, 1, true, false, "range_merge_from_multirange", range_merge_from_multirange }, + { 4229, 3, true, false, "anymultirange_in", anymultirange_in }, + { 4230, 1, true, false, "anymultirange_out", anymultirange_out }, + { 4231, 3, true, false, "multirange_in", multirange_in }, + { 4232, 1, true, false, "multirange_out", multirange_out }, + { 4233, 3, true, false, "multirange_recv", multirange_recv }, + { 4234, 1, true, false, "multirange_send", multirange_send }, + { 4235, 1, true, false, "multirange_lower", multirange_lower }, + { 4236, 1, true, false, "multirange_upper", multirange_upper }, + { 4237, 1, true, false, "multirange_empty", multirange_empty }, + { 4238, 1, true, false, "multirange_lower_inc", multirange_lower_inc }, + { 4239, 1, true, false, "multirange_upper_inc", multirange_upper_inc }, + { 4240, 1, true, false, "multirange_lower_inf", multirange_lower_inf }, + { 4241, 1, true, false, "multirange_upper_inf", multirange_upper_inf }, + { 4242, 1, true, false, "multirange_typanalyze", multirange_typanalyze }, + { 4243, 4, true, false, "multirangesel", multirangesel }, + { 4244, 2, true, false, "multirange_eq", multirange_eq }, + { 4245, 2, true, false, "multirange_ne", multirange_ne }, + { 4246, 2, true, false, "range_overlaps_multirange", range_overlaps_multirange }, + { 4247, 2, true, false, "multirange_overlaps_range", multirange_overlaps_range }, + { 4248, 2, true, false, "multirange_overlaps_multirange", multirange_overlaps_multirange }, + { 4249, 2, true, false, "multirange_contains_elem", multirange_contains_elem }, + { 4250, 2, true, false, "multirange_contains_range", multirange_contains_range }, + { 4251, 2, true, false, "multirange_contains_multirange", multirange_contains_multirange }, + { 4252, 2, true, false, "elem_contained_by_multirange", elem_contained_by_multirange }, + { 4253, 2, true, false, "range_contained_by_multirange", range_contained_by_multirange }, + { 4254, 2, true, false, "multirange_contained_by_multirange", multirange_contained_by_multirange }, + { 4255, 2, true, false, "range_adjacent_multirange", range_adjacent_multirange }, + { 4256, 2, true, false, "multirange_adjacent_multirange", multirange_adjacent_multirange }, + { 4257, 2, true, false, "multirange_adjacent_range", multirange_adjacent_range }, + { 4258, 2, true, false, "range_before_multirange", range_before_multirange }, + { 4259, 2, true, false, "multirange_before_range", multirange_before_range }, + { 4260, 2, true, false, "multirange_before_multirange", multirange_before_multirange }, + { 4261, 2, true, false, "range_after_multirange", range_after_multirange }, + { 4262, 2, true, false, "multirange_after_range", multirange_after_range }, + { 4263, 2, true, false, "multirange_after_multirange", multirange_after_multirange }, + { 4264, 2, true, false, "range_overleft_multirange", range_overleft_multirange }, + { 4265, 2, true, false, "multirange_overleft_range", multirange_overleft_range }, + { 4266, 2, true, false, "multirange_overleft_multirange", multirange_overleft_multirange }, + { 4267, 2, true, false, "range_overright_multirange", range_overright_multirange }, + { 4268, 2, true, false, "multirange_overright_range", multirange_overright_range }, + { 4269, 2, true, false, "multirange_overright_multirange", multirange_overright_multirange }, + { 4270, 2, true, false, "multirange_union", multirange_union }, + { 4271, 2, true, false, "multirange_minus", multirange_minus }, + { 4272, 2, true, false, "multirange_intersect", multirange_intersect }, + { 4273, 2, true, false, "multirange_cmp", multirange_cmp }, + { 4274, 2, true, false, "multirange_lt", multirange_lt }, + { 4275, 2, true, false, "multirange_le", multirange_le }, + { 4276, 2, true, false, "multirange_ge", multirange_ge }, + { 4277, 2, true, false, "multirange_gt", multirange_gt }, + { 4278, 1, true, false, "hash_multirange", hash_multirange }, + { 4279, 2, true, false, "hash_multirange_extended", hash_multirange_extended }, + { 4280, 0, true, false, "multirange_constructor0", multirange_constructor0 }, + { 4281, 1, true, false, "multirange_constructor1", multirange_constructor1 }, + { 4282, 1, true, false, "multirange_constructor2", multirange_constructor2 }, + { 4283, 0, true, false, "multirange_constructor0", multirange_constructor0 }, + { 4284, 1, true, false, "multirange_constructor1", multirange_constructor1 }, + { 4285, 1, true, false, "multirange_constructor2", multirange_constructor2 }, + { 4286, 0, true, false, "multirange_constructor0", multirange_constructor0 }, + { 4287, 1, true, false, "multirange_constructor1", multirange_constructor1 }, + { 4288, 1, true, false, "multirange_constructor2", multirange_constructor2 }, + { 4289, 0, true, false, "multirange_constructor0", multirange_constructor0 }, + { 4290, 1, true, false, "multirange_constructor1", multirange_constructor1 }, + { 4291, 1, true, false, "multirange_constructor2", multirange_constructor2 }, + { 4292, 0, true, false, "multirange_constructor0", multirange_constructor0 }, + { 4293, 1, true, false, "multirange_constructor1", multirange_constructor1 }, + { 4294, 1, true, false, "multirange_constructor2", multirange_constructor2 }, + { 4295, 0, true, false, "multirange_constructor0", multirange_constructor0 }, + { 4296, 1, true, false, "multirange_constructor1", multirange_constructor1 }, + { 4297, 1, true, false, "multirange_constructor2", multirange_constructor2 }, + { 4298, 1, true, false, "multirange_constructor1", multirange_constructor1 }, + { 4299, 2, false, false, "range_agg_transfn", range_agg_transfn }, + { 4300, 2, false, false, "range_agg_finalfn", range_agg_finalfn }, + { 4350, 2, true, false, "unicode_normalize_func", unicode_normalize_func }, + { 4351, 2, true, false, "unicode_is_normalized", unicode_is_normalized }, + { 4388, 2, true, false, "multirange_intersect_agg_transfn", multirange_intersect_agg_transfn }, + { 4390, 1, true, false, "binary_upgrade_set_next_multirange_pg_type_oid", binary_upgrade_set_next_multirange_pg_type_oid }, + { 4391, 1, true, false, "binary_upgrade_set_next_multirange_array_pg_type_oid", binary_upgrade_set_next_multirange_array_pg_type_oid }, + { 4401, 2, true, false, "range_intersect_agg_transfn", range_intersect_agg_transfn }, + { 4541, 2, true, false, "range_contains_multirange", range_contains_multirange }, + { 4542, 2, true, false, "multirange_contained_by_range", multirange_contained_by_range }, + { 4543, 1, true, false, "pg_log_backend_memory_contexts", pg_log_backend_memory_contexts }, + { 4545, 1, true, false, "binary_upgrade_set_next_heap_relfilenode", binary_upgrade_set_next_heap_relfilenode }, + { 4546, 1, true, false, "binary_upgrade_set_next_index_relfilenode", binary_upgrade_set_next_index_relfilenode }, + { 4547, 1, true, false, "binary_upgrade_set_next_toast_relfilenode", binary_upgrade_set_next_toast_relfilenode }, + { 4548, 1, true, false, "binary_upgrade_set_next_pg_tablespace_oid", binary_upgrade_set_next_pg_tablespace_oid }, + { 4566, 0, true, false, "pg_event_trigger_table_rewrite_oid", pg_event_trigger_table_rewrite_oid }, + { 4567, 0, true, false, "pg_event_trigger_table_rewrite_reason", pg_event_trigger_table_rewrite_reason }, + { 4568, 0, true, true, "pg_event_trigger_ddl_commands", pg_event_trigger_ddl_commands }, + { 4591, 1, true, false, "brin_bloom_opcinfo", brin_bloom_opcinfo }, + { 4592, 4, true, false, "brin_bloom_add_value", brin_bloom_add_value }, + { 4593, 4, true, false, "brin_bloom_consistent", brin_bloom_consistent }, + { 4594, 3, true, false, "brin_bloom_union", brin_bloom_union }, + { 4595, 1, false, false, "brin_bloom_options", brin_bloom_options }, + { 4596, 1, true, false, "brin_bloom_summary_in", brin_bloom_summary_in }, + { 4597, 1, true, false, "brin_bloom_summary_out", brin_bloom_summary_out }, + { 4598, 1, true, false, "brin_bloom_summary_recv", brin_bloom_summary_recv }, + { 4599, 1, true, false, "brin_bloom_summary_send", brin_bloom_summary_send }, + { 4616, 1, true, false, "brin_minmax_multi_opcinfo", brin_minmax_multi_opcinfo }, + { 4617, 4, true, false, "brin_minmax_multi_add_value", brin_minmax_multi_add_value }, + { 4618, 4, true, false, "brin_minmax_multi_consistent", brin_minmax_multi_consistent }, + { 4619, 3, true, false, "brin_minmax_multi_union", brin_minmax_multi_union }, + { 4620, 1, false, false, "brin_minmax_multi_options", brin_minmax_multi_options }, + { 4621, 2, true, false, "brin_minmax_multi_distance_int2", brin_minmax_multi_distance_int2 }, + { 4622, 2, true, false, "brin_minmax_multi_distance_int4", brin_minmax_multi_distance_int4 }, + { 4623, 2, true, false, "brin_minmax_multi_distance_int8", brin_minmax_multi_distance_int8 }, + { 4624, 2, true, false, "brin_minmax_multi_distance_float4", brin_minmax_multi_distance_float4 }, + { 4625, 2, true, false, "brin_minmax_multi_distance_float8", brin_minmax_multi_distance_float8 }, + { 4626, 2, true, false, "brin_minmax_multi_distance_numeric", brin_minmax_multi_distance_numeric }, + { 4627, 2, true, false, "brin_minmax_multi_distance_tid", brin_minmax_multi_distance_tid }, + { 4628, 2, true, false, "brin_minmax_multi_distance_uuid", brin_minmax_multi_distance_uuid }, + { 4629, 2, true, false, "brin_minmax_multi_distance_date", brin_minmax_multi_distance_date }, + { 4630, 2, true, false, "brin_minmax_multi_distance_time", brin_minmax_multi_distance_time }, + { 4631, 2, true, false, "brin_minmax_multi_distance_interval", brin_minmax_multi_distance_interval }, + { 4632, 2, true, false, "brin_minmax_multi_distance_timetz", brin_minmax_multi_distance_timetz }, + { 4633, 2, true, false, "brin_minmax_multi_distance_pg_lsn", brin_minmax_multi_distance_pg_lsn }, + { 4634, 2, true, false, "brin_minmax_multi_distance_macaddr", brin_minmax_multi_distance_macaddr }, + { 4635, 2, true, false, "brin_minmax_multi_distance_macaddr8", brin_minmax_multi_distance_macaddr8 }, + { 4636, 2, true, false, "brin_minmax_multi_distance_inet", brin_minmax_multi_distance_inet }, + { 4637, 2, true, false, "brin_minmax_multi_distance_timestamp", brin_minmax_multi_distance_timestamp }, + { 4638, 1, true, false, "brin_minmax_multi_summary_in", brin_minmax_multi_summary_in }, + { 4639, 1, true, false, "brin_minmax_multi_summary_out", brin_minmax_multi_summary_out }, + { 4640, 1, true, false, "brin_minmax_multi_summary_recv", brin_minmax_multi_summary_recv }, + { 4641, 1, true, false, "brin_minmax_multi_summary_send", brin_minmax_multi_summary_send }, + { 5001, 1, true, false, "phraseto_tsquery", phraseto_tsquery }, + { 5003, 2, true, false, "tsquery_phrase", tsquery_phrase }, + { 5004, 3, true, false, "tsquery_phrase_distance", tsquery_phrase_distance }, + { 5006, 2, true, false, "phraseto_tsquery_byid", phraseto_tsquery_byid }, + { 5007, 2, true, false, "websearch_to_tsquery_byid", websearch_to_tsquery_byid }, + { 5009, 1, true, false, "websearch_to_tsquery", websearch_to_tsquery }, + { 5010, 2, true, false, "spg_bbox_quad_config", spg_bbox_quad_config }, + { 5011, 1, true, false, "spg_poly_quad_compress", spg_poly_quad_compress }, + { 5012, 2, true, false, "spg_box_quad_config", spg_box_quad_config }, + { 5013, 2, true, false, "spg_box_quad_choose", spg_box_quad_choose }, + { 5014, 2, true, false, "spg_box_quad_picksplit", spg_box_quad_picksplit }, + { 5015, 2, true, false, "spg_box_quad_inner_consistent", spg_box_quad_inner_consistent }, + { 5016, 2, true, false, "spg_box_quad_leaf_consistent", spg_box_quad_leaf_consistent }, + { 5018, 1, true, false, "pg_mcv_list_in", pg_mcv_list_in }, + { 5019, 1, true, false, "pg_mcv_list_out", pg_mcv_list_out }, + { 5020, 1, true, false, "pg_mcv_list_recv", pg_mcv_list_recv }, + { 5021, 1, true, false, "pg_mcv_list_send", pg_mcv_list_send }, + { 5022, 2, true, false, "pg_lsn_pli", pg_lsn_pli }, + { 5024, 2, true, false, "pg_lsn_mii", pg_lsn_mii }, + { 5028, 4, false, false, "satisfies_hash_partition", satisfies_hash_partition }, + { 5029, 0, true, true, "pg_ls_tmpdir_noargs", pg_ls_tmpdir_noargs }, + { 5030, 1, true, true, "pg_ls_tmpdir_1arg", pg_ls_tmpdir_1arg }, + { 5031, 0, true, true, "pg_ls_archive_statusdir", pg_ls_archive_statusdir }, + { 5033, 1, true, false, "network_sortsupport", network_sortsupport }, + { 5034, 2, true, false, "xid8lt", xid8lt }, + { 5035, 2, true, false, "xid8gt", xid8gt }, + { 5036, 2, true, false, "xid8le", xid8le }, + { 5037, 2, true, false, "xid8ge", xid8ge }, + { 5040, 4, true, false, "matchingsel", matchingsel }, + { 5041, 5, true, false, "matchingjoinsel", matchingjoinsel }, + { 5042, 1, true, false, "numeric_min_scale", numeric_min_scale }, + { 5043, 1, true, false, "numeric_trim_scale", numeric_trim_scale }, + { 5044, 2, true, false, "int4gcd", int4gcd }, + { 5045, 2, true, false, "int8gcd", int8gcd }, + { 5046, 2, true, false, "int4lcm", int4lcm }, + { 5047, 2, true, false, "int8lcm", int8lcm }, + { 5048, 2, true, false, "numeric_gcd", numeric_gcd }, + { 5049, 2, true, false, "numeric_lcm", numeric_lcm }, + { 5050, 1, true, false, "btvarstrequalimage", btvarstrequalimage }, + { 5051, 1, true, false, "btequalimage", btequalimage }, + { 5052, 0, true, true, "pg_get_shmem_allocations", pg_get_shmem_allocations }, + { 5053, 1, true, false, "pg_stat_get_ins_since_vacuum", pg_stat_get_ins_since_vacuum }, + { 5054, 5, false, false, "jsonb_set_lax", jsonb_set_lax }, + { 5055, 1, true, false, "pg_snapshot_in", pg_snapshot_in }, + { 5056, 1, true, false, "pg_snapshot_out", pg_snapshot_out }, + { 5057, 1, true, false, "pg_snapshot_recv", pg_snapshot_recv }, + { 5058, 1, true, false, "pg_snapshot_send", pg_snapshot_send }, + { 5059, 0, true, false, "pg_current_xact_id", pg_current_xact_id }, + { 5060, 0, true, false, "pg_current_xact_id_if_assigned", pg_current_xact_id_if_assigned }, + { 5061, 0, true, false, "pg_current_snapshot", pg_current_snapshot }, + { 5062, 1, true, false, "pg_snapshot_xmin", pg_snapshot_xmin }, + { 5063, 1, true, false, "pg_snapshot_xmax", pg_snapshot_xmax }, + { 5064, 1, true, true, "pg_snapshot_xip", pg_snapshot_xip }, + { 5065, 2, true, false, "pg_visible_in_snapshot", pg_visible_in_snapshot }, + { 5066, 1, true, false, "pg_xact_status", pg_xact_status }, + { 5070, 1, true, false, "xid8in", xid8in }, + { 5071, 1, true, false, "xid8toxid", xid8toxid }, + { 5081, 1, true, false, "xid8out", xid8out }, + { 5082, 1, true, false, "xid8recv", xid8recv }, + { 5083, 1, true, false, "xid8send", xid8send }, + { 5084, 2, true, false, "xid8eq", xid8eq }, + { 5085, 2, true, false, "xid8ne", xid8ne }, + { 5086, 1, true, false, "anycompatible_in", anycompatible_in }, + { 5087, 1, true, false, "anycompatible_out", anycompatible_out }, + { 5088, 1, true, false, "anycompatiblearray_in", anycompatiblearray_in }, + { 5089, 1, true, false, "anycompatiblearray_out", anycompatiblearray_out }, + { 5090, 1, true, false, "anycompatiblearray_recv", anycompatiblearray_recv }, + { 5091, 1, true, false, "anycompatiblearray_send", anycompatiblearray_send }, + { 5092, 1, true, false, "anycompatiblenonarray_in", anycompatiblenonarray_in }, + { 5093, 1, true, false, "anycompatiblenonarray_out", anycompatiblenonarray_out }, + { 5094, 3, true, false, "anycompatiblerange_in", anycompatiblerange_in }, + { 5095, 1, true, false, "anycompatiblerange_out", anycompatiblerange_out }, + { 5096, 2, true, false, "xid8cmp", xid8cmp }, + { 5097, 2, true, false, "xid8_larger", xid8_larger }, + { 5098, 2, true, false, "xid8_smaller", xid8_smaller }, + { 6003, 1, true, false, "pg_replication_origin_create", pg_replication_origin_create }, + { 6004, 1, true, false, "pg_replication_origin_drop", pg_replication_origin_drop }, + { 6005, 1, true, false, "pg_replication_origin_oid", pg_replication_origin_oid }, + { 6006, 1, true, false, "pg_replication_origin_session_setup", pg_replication_origin_session_setup }, + { 6007, 0, true, false, "pg_replication_origin_session_reset", pg_replication_origin_session_reset }, + { 6008, 0, true, false, "pg_replication_origin_session_is_setup", pg_replication_origin_session_is_setup }, + { 6009, 1, true, false, "pg_replication_origin_session_progress", pg_replication_origin_session_progress }, + { 6010, 2, true, false, "pg_replication_origin_xact_setup", pg_replication_origin_xact_setup }, + { 6011, 0, true, false, "pg_replication_origin_xact_reset", pg_replication_origin_xact_reset }, + { 6012, 2, true, false, "pg_replication_origin_advance", pg_replication_origin_advance }, + { 6013, 2, true, false, "pg_replication_origin_progress", pg_replication_origin_progress }, + { 6014, 0, false, true, "pg_show_replication_origin_status", pg_show_replication_origin_status }, + { 6098, 1, true, false, "jsonb_subscript_handler", jsonb_subscript_handler }, + { 6103, 1, true, false, "numeric_pg_lsn", numeric_pg_lsn }, + { 6107, 1, true, false, "pg_stat_get_backend_subxact", pg_stat_get_backend_subxact }, + { 6118, 1, false, true, "pg_stat_get_subscription", pg_stat_get_subscription }, + { 6119, 1, true, true, "pg_get_publication_tables", pg_get_publication_tables }, + { 6120, 1, true, false, "pg_get_replica_identity_index", pg_get_replica_identity_index }, + { 6121, 1, true, false, "pg_relation_is_publishable", pg_relation_is_publishable }, + { 6154, 5, true, false, "multirange_gist_consistent", multirange_gist_consistent }, + { 6156, 1, true, false, "multirange_gist_compress", multirange_gist_compress }, + { 6159, 0, true, true, "pg_get_catalog_foreign_keys", pg_get_catalog_foreign_keys }, + { 6160, 2, false, true, "text_to_table", text_to_table }, + { 6161, 3, false, true, "text_to_table_null", text_to_table_null }, + { 6162, 1, true, false, "bit_bit_count", bit_bit_count }, + { 6163, 1, true, false, "bytea_bit_count", bytea_bit_count }, + { 6168, 1, true, false, "pg_xact_commit_timestamp_origin", pg_xact_commit_timestamp_origin }, + { 6169, 1, true, false, "pg_stat_get_replication_slot", pg_stat_get_replication_slot }, + { 6170, 1, false, false, "pg_stat_reset_replication_slot", pg_stat_reset_replication_slot }, + { 6172, 2, true, false, "trim_array", trim_array }, + { 6173, 1, true, false, "pg_get_statisticsobjdef_expressions", pg_get_statisticsobjdef_expressions }, + { 6174, 1, true, false, "pg_get_statisticsobjdef_columns", pg_get_statisticsobjdef_columns }, + { 6177, 3, true, false, "timestamp_bin", timestamp_bin }, + { 6178, 3, true, false, "timestamptz_bin", timestamptz_bin }, + { 6179, 1, true, false, "array_subscript_handler", array_subscript_handler }, + { 6180, 1, true, false, "raw_array_subscript_handler", raw_array_subscript_handler }, + { 6185, 1, true, false, "pg_stat_get_db_session_time", pg_stat_get_db_session_time }, + { 6186, 1, true, false, "pg_stat_get_db_active_time", pg_stat_get_db_active_time }, + { 6187, 1, true, false, "pg_stat_get_db_idle_in_transaction_time", pg_stat_get_db_idle_in_transaction_time }, + { 6188, 1, true, false, "pg_stat_get_db_sessions", pg_stat_get_db_sessions }, + { 6189, 1, true, false, "pg_stat_get_db_sessions_abandoned", pg_stat_get_db_sessions_abandoned }, + { 6190, 1, true, false, "pg_stat_get_db_sessions_fatal", pg_stat_get_db_sessions_fatal }, + { 6191, 1, true, false, "pg_stat_get_db_sessions_killed", pg_stat_get_db_sessions_killed }, + { 6192, 1, true, false, "hash_record", hash_record }, + { 6193, 2, true, false, "hash_record_extended", hash_record_extended }, + { 6195, 2, true, false, "bytealtrim", bytealtrim }, + { 6196, 2, true, false, "byteartrim", byteartrim }, + { 6197, 1, true, false, "pg_get_function_sqlbody", pg_get_function_sqlbody }, + { 6198, 1, true, false, "unistr", unistr }, + { 6199, 2, true, false, "extract_date", extract_date }, + { 6200, 2, true, false, "extract_time", extract_time }, + { 6201, 2, true, false, "extract_timetz", extract_timetz }, + { 6202, 2, true, false, "extract_timestamp", extract_timestamp }, + { 6203, 2, true, false, "extract_timestamptz", extract_timestamptz }, + { 6204, 2, true, false, "extract_interval", extract_interval }, + { 6205, 3, true, false, "has_parameter_privilege_name_name", has_parameter_privilege_name_name }, + { 6206, 3, true, false, "has_parameter_privilege_id_name", has_parameter_privilege_id_name }, + { 6207, 2, true, false, "has_parameter_privilege_name", has_parameter_privilege_name }, + { 6208, 2, true, false, "pg_read_file_all_missing", pg_read_file_all_missing }, + { 6209, 2, true, false, "pg_read_binary_file_all_missing", pg_read_binary_file_all_missing }, + { 6210, 2, true, false, "pg_input_is_valid", pg_input_is_valid }, + { 6211, 2, true, false, "pg_input_error_info", pg_input_error_info }, + { 6212, 2, true, false, "drandom_normal", drandom_normal }, + { 6213, 1, true, false, "pg_split_walfile_name", pg_split_walfile_name }, + { 6214, 0, true, true, "pg_stat_get_io", pg_stat_get_io }, + { 6215, 1, true, false, "array_shuffle", array_shuffle }, + { 6216, 2, true, false, "array_sample", array_sample }, + { 6217, 1, true, false, "pg_stat_get_tuples_newpage_updated", pg_stat_get_tuples_newpage_updated }, + { 6218, 1, true, false, "pg_stat_get_xact_tuples_newpage_updated", pg_stat_get_xact_tuples_newpage_updated }, + { 6219, 1, true, false, "derf", derf }, + { 6220, 1, true, false, "derfc", derfc }, + { 6221, 2, true, false, "timestamptz_pl_interval", timestamptz_pl_interval }, + { 6222, 3, true, false, "timestamptz_pl_interval_at_zone", timestamptz_pl_interval_at_zone }, + { 6223, 2, true, false, "timestamptz_mi_interval", timestamptz_mi_interval }, + { 6224, 0, true, true, "pg_get_wal_resource_managers", pg_get_wal_resource_managers }, + { 6225, 2, false, false, "multirange_agg_transfn", multirange_agg_transfn }, + { 6226, 2, false, false, "range_agg_finalfn", range_agg_finalfn }, + { 6230, 3, true, false, "pg_stat_have_stats", pg_stat_have_stats }, + { 6231, 1, true, false, "pg_stat_get_subscription_stats", pg_stat_get_subscription_stats }, + { 6232, 1, false, false, "pg_stat_reset_subscription_stats", pg_stat_reset_subscription_stats }, + { 6233, 1, true, false, "window_row_number_support", window_row_number_support }, + { 6234, 1, true, false, "window_rank_support", window_rank_support }, + { 6235, 1, true, false, "window_dense_rank_support", window_dense_rank_support }, + { 6236, 1, true, false, "int8inc_support", int8inc_support }, + { 6240, 1, true, false, "pg_settings_get_flags", pg_settings_get_flags }, + { 6241, 0, true, false, "pg_stop_making_pinned_objects", pg_stop_making_pinned_objects }, + { 6242, 1, true, false, "text_starts_with_support", text_starts_with_support }, + { 6248, 0, true, true, "pg_stat_get_recovery_prefetch", pg_stat_get_recovery_prefetch }, + { 6249, 1, true, false, "pg_database_collation_actual_version", pg_database_collation_actual_version }, + { 6250, 0, true, true, "pg_ident_file_mappings", pg_ident_file_mappings }, + { 6251, 6, true, false, "textregexreplace_extended", textregexreplace_extended }, + { 6252, 5, true, false, "textregexreplace_extended_no_flags", textregexreplace_extended_no_flags }, + { 6253, 4, true, false, "textregexreplace_extended_no_n", textregexreplace_extended_no_n }, + { 6254, 2, true, false, "regexp_count_no_start", regexp_count_no_start }, + { 6255, 3, true, false, "regexp_count_no_flags", regexp_count_no_flags }, + { 6256, 4, true, false, "regexp_count", regexp_count }, + { 6257, 2, true, false, "regexp_instr_no_start", regexp_instr_no_start }, + { 6258, 3, true, false, "regexp_instr_no_n", regexp_instr_no_n }, + { 6259, 4, true, false, "regexp_instr_no_endoption", regexp_instr_no_endoption }, + { 6260, 5, true, false, "regexp_instr_no_flags", regexp_instr_no_flags }, + { 6261, 6, true, false, "regexp_instr_no_subexpr", regexp_instr_no_subexpr }, + { 6262, 7, true, false, "regexp_instr", regexp_instr }, + { 6263, 2, true, false, "regexp_like_no_flags", regexp_like_no_flags }, + { 6264, 3, true, false, "regexp_like", regexp_like }, + { 6265, 2, true, false, "regexp_substr_no_start", regexp_substr_no_start }, + { 6266, 3, true, false, "regexp_substr_no_n", regexp_substr_no_n }, + { 6267, 4, true, false, "regexp_substr_no_flags", regexp_substr_no_flags }, + { 6268, 5, true, false, "regexp_substr_no_subexpr", regexp_substr_no_subexpr }, + { 6269, 6, true, false, "regexp_substr", regexp_substr }, + { 6270, 0, true, true, "pg_ls_logicalsnapdir", pg_ls_logicalsnapdir }, + { 6271, 0, true, true, "pg_ls_logicalmapdir", pg_ls_logicalmapdir }, + { 6272, 1, true, true, "pg_ls_replslotdir", pg_ls_replslotdir }, + { 6273, 3, true, false, "timestamptz_mi_interval_at_zone", timestamptz_mi_interval_at_zone }, + { 6274, 4, true, true, "generate_series_timestamptz_at_zone", generate_series_timestamptz_at_zone }, + { 6275, 2, false, false, "json_agg_strict_transfn", json_agg_strict_transfn }, + { 6277, 3, false, false, "json_object_agg_strict_transfn", json_object_agg_strict_transfn }, + { 6278, 3, false, false, "json_object_agg_unique_transfn", json_object_agg_unique_transfn }, + { 6279, 3, false, false, "json_object_agg_unique_strict_transfn", json_object_agg_unique_strict_transfn }, + { 6283, 2, false, false, "jsonb_agg_strict_transfn", jsonb_agg_strict_transfn }, + { 6285, 3, false, false, "jsonb_object_agg_strict_transfn", jsonb_object_agg_strict_transfn }, + { 6286, 3, false, false, "jsonb_object_agg_unique_transfn", jsonb_object_agg_unique_transfn }, + { 6287, 3, false, false, "jsonb_object_agg_unique_strict_transfn", jsonb_object_agg_unique_strict_transfn }, + { 6292, 2, true, false, "any_value_transfn", any_value_transfn }, + { 6293, 2, false, false, "array_agg_combine", array_agg_combine }, + { 6294, 1, true, false, "array_agg_serialize", array_agg_serialize }, + { 6295, 2, true, false, "array_agg_deserialize", array_agg_deserialize }, + { 6296, 2, false, false, "array_agg_array_combine", array_agg_array_combine }, + { 6297, 1, true, false, "array_agg_array_serialize", array_agg_array_serialize }, + { 6298, 2, true, false, "array_agg_array_deserialize", array_agg_array_deserialize }, + { 6299, 2, false, false, "string_agg_combine", string_agg_combine }, + { 6300, 1, true, false, "string_agg_serialize", string_agg_serialize }, + { 6301, 2, true, false, "string_agg_deserialize", string_agg_deserialize }, + { 6305, 0, true, false, "pg_log_standby_snapshot", pg_log_standby_snapshot }, + { 6306, 1, true, false, "window_percent_rank_support", window_percent_rank_support }, + { 6307, 1, true, false, "window_cume_dist_support", window_cume_dist_support }, + { 6308, 1, true, false, "window_ntile_support", window_ntile_support }, + { 6309, 1, true, false, "pg_stat_get_db_conflict_logicalslot", pg_stat_get_db_conflict_logicalslot }, + { 6310, 1, true, false, "pg_stat_get_lastscan", pg_stat_get_lastscan }, + { 6311, 0, true, false, "system_user", system_user } +}; + +const int fmgr_nbuiltins = (sizeof(fmgr_builtins) / sizeof(FmgrBuiltin)); + +const Oid fmgr_last_builtin_oid = 6311; + +const uint16 fmgr_builtin_oid_index[6312] = { + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 0, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1, + InvalidOidBuiltinMapping, + 2, + 3, + 4, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + InvalidOidBuiltinMapping, + 36, + 37, + 38, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 39, + 40, + 41, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 42, + InvalidOidBuiltinMapping, + 43, + 44, + 45, + 46, + 47, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 58, + 59, + 60, + 61, + 62, + 63, + 64, + 65, + 66, + 67, + 68, + 69, + 70, + 71, + 72, + 73, + 74, + 75, + 76, + 77, + 78, + 79, + 80, + 81, + 82, + 83, + 84, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 85, + 86, + 87, + 88, + 89, + 90, + 91, + 92, + 93, + 94, + 95, + 96, + 97, + 98, + 99, + 100, + 101, + 102, + 103, + 104, + 105, + 106, + 107, + 108, + 109, + 110, + 111, + 112, + 113, + 114, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 115, + 116, + 117, + 118, + 119, + 120, + 121, + 122, + 123, + 124, + 125, + 126, + 127, + 128, + 129, + 130, + 131, + 132, + InvalidOidBuiltinMapping, + 133, + 134, + 135, + 136, + InvalidOidBuiltinMapping, + 137, + 138, + 139, + 140, + 141, + 142, + 143, + 144, + 145, + 146, + InvalidOidBuiltinMapping, + 147, + 148, + 149, + 150, + 151, + 152, + 153, + 154, + 155, + 156, + 157, + 158, + 159, + 160, + 161, + InvalidOidBuiltinMapping, + 162, + 163, + 164, + 165, + 166, + 167, + 168, + 169, + 170, + 171, + 172, + 173, + 174, + 175, + 176, + 177, + 178, + 179, + 180, + 181, + 182, + 183, + 184, + 185, + 186, + 187, + 188, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 189, + 190, + 191, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 192, + 193, + 194, + 195, + 196, + 197, + 198, + 199, + 200, + 201, + 202, + 203, + 204, + 205, + 206, + 207, + 208, + 209, + 210, + 211, + 212, + 213, + 214, + 215, + 216, + 217, + 218, + 219, + 220, + 221, + 222, + 223, + 224, + 225, + 226, + 227, + 228, + 229, + 230, + 231, + 232, + 233, + 234, + 235, + 236, + 237, + 238, + 239, + 240, + 241, + 242, + InvalidOidBuiltinMapping, + 243, + 244, + 245, + 246, + 247, + 248, + 249, + 250, + 251, + 252, + 253, + 254, + 255, + 256, + 257, + 258, + 259, + 260, + 261, + 262, + 263, + 264, + 265, + InvalidOidBuiltinMapping, + 266, + 267, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 268, + 269, + 270, + 271, + 272, + 273, + 274, + 275, + 276, + 277, + 278, + 279, + 280, + 281, + 282, + 283, + 284, + 285, + 286, + 287, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 288, + 289, + 290, + 291, + 292, + 293, + 294, + 295, + 296, + InvalidOidBuiltinMapping, + 297, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 298, + 299, + 300, + 301, + 302, + 303, + 304, + InvalidOidBuiltinMapping, + 305, + 306, + 307, + 308, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 309, + InvalidOidBuiltinMapping, + 310, + 311, + 312, + 313, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 314, + 315, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 316, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 317, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 318, + 319, + 320, + InvalidOidBuiltinMapping, + 321, + 322, + 323, + 324, + 325, + 326, + 327, + 328, + 329, + 330, + 331, + 332, + 333, + 334, + 335, + 336, + 337, + 338, + 339, + 340, + 341, + 342, + 343, + 344, + 345, + 346, + 347, + 348, + 349, + 350, + 351, + 352, + 353, + InvalidOidBuiltinMapping, + 354, + 355, + 356, + 357, + 358, + 359, + 360, + 361, + 362, + 363, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 364, + 365, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 366, + 367, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 368, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 369, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 370, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 371, + 372, + 373, + 374, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 375, + 376, + InvalidOidBuiltinMapping, + 377, + 378, + 379, + 380, + 381, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 382, + 383, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 384, + 385, + 386, + 387, + 388, + 389, + InvalidOidBuiltinMapping, + 390, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 391, + 392, + 393, + 394, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 395, + InvalidOidBuiltinMapping, + 396, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 397, + 398, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 399, + 400, + 401, + 402, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 403, + 404, + 405, + 406, + 407, + 408, + InvalidOidBuiltinMapping, + 409, + 410, + 411, + 412, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 413, + 414, + 415, + 416, + 417, + 418, + 419, + 420, + 421, + 422, + 423, + 424, + 425, + 426, + 427, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 428, + 429, + 430, + 431, + 432, + 433, + 434, + 435, + 436, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 437, + 438, + 439, + 440, + 441, + 442, + 443, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 444, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 445, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 446, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 447, + 448, + 449, + 450, + 451, + 452, + 453, + 454, + 455, + 456, + 457, + 458, + 459, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 460, + 461, + 462, + 463, + 464, + 465, + 466, + 467, + 468, + 469, + 470, + 471, + 472, + 473, + 474, + 475, + 476, + 477, + 478, + 479, + 480, + 481, + 482, + InvalidOidBuiltinMapping, + 483, + 484, + 485, + 486, + 487, + 488, + 489, + 490, + 491, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 492, + 493, + 494, + 495, + 496, + 497, + 498, + 499, + 500, + 501, + 502, + 503, + 504, + 505, + 506, + 507, + 508, + 509, + 510, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 511, + 512, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 513, + 514, + 515, + 516, + 517, + 518, + 519, + 520, + 521, + 522, + 523, + 524, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 525, + 526, + 527, + 528, + 529, + 530, + 531, + 532, + 533, + 534, + 535, + 536, + 537, + 538, + 539, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 540, + 541, + 542, + 543, + 544, + 545, + 546, + 547, + 548, + 549, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 550, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 551, + 552, + InvalidOidBuiltinMapping, + 553, + 554, + 555, + 556, + 557, + 558, + 559, + 560, + 561, + 562, + 563, + 564, + 565, + 566, + 567, + 568, + 569, + 570, + 571, + 572, + 573, + 574, + 575, + 576, + 577, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 578, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 579, + 580, + 581, + 582, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 583, + 584, + 585, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 586, + 587, + 588, + InvalidOidBuiltinMapping, + 589, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 590, + 591, + 592, + 593, + 594, + 595, + 596, + 597, + 598, + 599, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 600, + 601, + 602, + 603, + 604, + 605, + 606, + 607, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 608, + 609, + 610, + 611, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 612, + 613, + 614, + 615, + 616, + 617, + 618, + 619, + 620, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 621, + 622, + 623, + 624, + 625, + 626, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 627, + 628, + 629, + 630, + 631, + 632, + 633, + 634, + 635, + 636, + 637, + 638, + 639, + 640, + 641, + 642, + 643, + 644, + 645, + 646, + 647, + 648, + 649, + 650, + 651, + 652, + 653, + 654, + 655, + 656, + 657, + 658, + 659, + 660, + 661, + 662, + 663, + 664, + 665, + 666, + InvalidOidBuiltinMapping, + 667, + 668, + 669, + 670, + 671, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 672, + 673, + 674, + 675, + 676, + 677, + 678, + 679, + 680, + 681, + 682, + 683, + 684, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 685, + 686, + 687, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 688, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 689, + 690, + 691, + 692, + 693, + 694, + 695, + 696, + 697, + 698, + 699, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 700, + 701, + 702, + 703, + 704, + InvalidOidBuiltinMapping, + 705, + 706, + 707, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 708, + 709, + InvalidOidBuiltinMapping, + 710, + 711, + 712, + InvalidOidBuiltinMapping, + 713, + 714, + 715, + 716, + 717, + 718, + 719, + 720, + 721, + 722, + 723, + 724, + 725, + 726, + InvalidOidBuiltinMapping, + 727, + 728, + 729, + 730, + InvalidOidBuiltinMapping, + 731, + 732, + 733, + 734, + 735, + InvalidOidBuiltinMapping, + 736, + InvalidOidBuiltinMapping, + 737, + 738, + 739, + 740, + 741, + 742, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 743, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 744, + 745, + 746, + 747, + 748, + 749, + 750, + 751, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 752, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 753, + 754, + 755, + 756, + 757, + 758, + 759, + 760, + 761, + InvalidOidBuiltinMapping, + 762, + 763, + 764, + 765, + 766, + 767, + 768, + 769, + 770, + 771, + 772, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 773, + InvalidOidBuiltinMapping, + 774, + 775, + InvalidOidBuiltinMapping, + 776, + 777, + 778, + 779, + 780, + 781, + 782, + 783, + 784, + 785, + 786, + 787, + 788, + 789, + 790, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 791, + InvalidOidBuiltinMapping, + 792, + 793, + 794, + 795, + 796, + 797, + 798, + 799, + 800, + 801, + 802, + 803, + InvalidOidBuiltinMapping, + 804, + 805, + 806, + 807, + 808, + 809, + 810, + 811, + 812, + 813, + 814, + 815, + 816, + 817, + 818, + 819, + 820, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 821, + InvalidOidBuiltinMapping, + 822, + 823, + 824, + 825, + 826, + InvalidOidBuiltinMapping, + 827, + 828, + 829, + 830, + 831, + 832, + 833, + 834, + 835, + 836, + 837, + 838, + 839, + 840, + 841, + 842, + 843, + 844, + 845, + 846, + 847, + 848, + 849, + 850, + 851, + 852, + 853, + 854, + 855, + 856, + 857, + 858, + 859, + 860, + 861, + 862, + 863, + 864, + 865, + 866, + 867, + 868, + 869, + 870, + 871, + 872, + 873, + 874, + 875, + 876, + 877, + 878, + 879, + 880, + InvalidOidBuiltinMapping, + 881, + 882, + 883, + 884, + 885, + 886, + 887, + 888, + 889, + 890, + 891, + 892, + 893, + 894, + 895, + 896, + 897, + 898, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 899, + 900, + 901, + InvalidOidBuiltinMapping, + 902, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 903, + 904, + 905, + 906, + InvalidOidBuiltinMapping, + 907, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 908, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 909, + 910, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 911, + 912, + 913, + 914, + 915, + 916, + 917, + 918, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 919, + 920, + 921, + 922, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 923, + 924, + 925, + 926, + 927, + 928, + 929, + 930, + 931, + 932, + 933, + 934, + 935, + 936, + 937, + 938, + 939, + 940, + 941, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 942, + 943, + 944, + 945, + 946, + 947, + 948, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 949, + 950, + 951, + 952, + 953, + 954, + 955, + 956, + 957, + 958, + 959, + 960, + 961, + 962, + 963, + 964, + 965, + 966, + 967, + 968, + 969, + 970, + 971, + 972, + 973, + 974, + 975, + 976, + 977, + 978, + 979, + 980, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 981, + 982, + 983, + 984, + 985, + 986, + 987, + 988, + 989, + 990, + 991, + 992, + 993, + 994, + 995, + 996, + 997, + 998, + 999, + 1000, + 1001, + 1002, + 1003, + 1004, + 1005, + 1006, + 1007, + 1008, + 1009, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1010, + 1011, + 1012, + 1013, + InvalidOidBuiltinMapping, + 1014, + 1015, + 1016, + 1017, + 1018, + 1019, + 1020, + InvalidOidBuiltinMapping, + 1021, + InvalidOidBuiltinMapping, + 1022, + 1023, + 1024, + 1025, + 1026, + 1027, + 1028, + 1029, + 1030, + 1031, + 1032, + 1033, + 1034, + 1035, + 1036, + 1037, + 1038, + 1039, + 1040, + 1041, + 1042, + 1043, + 1044, + 1045, + 1046, + 1047, + 1048, + 1049, + 1050, + 1051, + InvalidOidBuiltinMapping, + 1052, + 1053, + 1054, + 1055, + 1056, + 1057, + 1058, + 1059, + 1060, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1061, + 1062, + 1063, + 1064, + 1065, + 1066, + 1067, + 1068, + 1069, + 1070, + 1071, + 1072, + 1073, + 1074, + 1075, + 1076, + 1077, + 1078, + 1079, + 1080, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1081, + 1082, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1083, + 1084, + 1085, + 1086, + 1087, + 1088, + 1089, + 1090, + 1091, + 1092, + 1093, + 1094, + 1095, + 1096, + 1097, + 1098, + 1099, + 1100, + 1101, + 1102, + 1103, + 1104, + 1105, + 1106, + 1107, + 1108, + 1109, + 1110, + 1111, + 1112, + 1113, + 1114, + 1115, + 1116, + 1117, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1118, + 1119, + 1120, + 1121, + 1122, + 1123, + 1124, + 1125, + 1126, + 1127, + 1128, + 1129, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1130, + 1131, + 1132, + 1133, + 1134, + 1135, + 1136, + 1137, + 1138, + 1139, + 1140, + 1141, + 1142, + 1143, + 1144, + 1145, + 1146, + 1147, + 1148, + 1149, + 1150, + 1151, + 1152, + 1153, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1154, + 1155, + 1156, + 1157, + 1158, + 1159, + 1160, + 1161, + 1162, + 1163, + 1164, + 1165, + 1166, + 1167, + 1168, + 1169, + 1170, + 1171, + 1172, + 1173, + 1174, + 1175, + 1176, + 1177, + 1178, + 1179, + 1180, + 1181, + 1182, + 1183, + 1184, + 1185, + 1186, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1187, + 1188, + 1189, + 1190, + 1191, + 1192, + 1193, + 1194, + 1195, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1196, + 1197, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1198, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1199, + 1200, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1201, + 1202, + 1203, + 1204, + 1205, + 1206, + 1207, + 1208, + 1209, + 1210, + 1211, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1212, + 1213, + 1214, + 1215, + 1216, + 1217, + 1218, + 1219, + 1220, + 1221, + 1222, + 1223, + 1224, + 1225, + 1226, + 1227, + 1228, + 1229, + 1230, + 1231, + 1232, + InvalidOidBuiltinMapping, + 1233, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1234, + 1235, + 1236, + 1237, + 1238, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1239, + 1240, + 1241, + 1242, + 1243, + 1244, + 1245, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1246, + 1247, + 1248, + 1249, + 1250, + InvalidOidBuiltinMapping, + 1251, + 1252, + 1253, + 1254, + 1255, + 1256, + 1257, + 1258, + 1259, + 1260, + 1261, + 1262, + 1263, + 1264, + 1265, + 1266, + 1267, + 1268, + 1269, + 1270, + InvalidOidBuiltinMapping, + 1271, + InvalidOidBuiltinMapping, + 1272, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1273, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1274, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1275, + 1276, + 1277, + 1278, + 1279, + 1280, + 1281, + 1282, + 1283, + 1284, + 1285, + 1286, + 1287, + InvalidOidBuiltinMapping, + 1288, + 1289, + 1290, + 1291, + 1292, + 1293, + 1294, + 1295, + 1296, + 1297, + 1298, + 1299, + 1300, + InvalidOidBuiltinMapping, + 1301, + 1302, + 1303, + 1304, + 1305, + 1306, + 1307, + 1308, + 1309, + 1310, + 1311, + 1312, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1313, + 1314, + 1315, + 1316, + 1317, + 1318, + 1319, + 1320, + 1321, + 1322, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1323, + InvalidOidBuiltinMapping, + 1324, + 1325, + 1326, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1327, + 1328, + 1329, + InvalidOidBuiltinMapping, + 1330, + 1331, + 1332, + 1333, + 1334, + 1335, + 1336, + 1337, + 1338, + 1339, + 1340, + 1341, + 1342, + 1343, + 1344, + 1345, + 1346, + 1347, + 1348, + 1349, + 1350, + 1351, + 1352, + 1353, + 1354, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1355, + InvalidOidBuiltinMapping, + 1356, + 1357, + 1358, + InvalidOidBuiltinMapping, + 1359, + 1360, + 1361, + 1362, + 1363, + 1364, + 1365, + 1366, + 1367, + 1368, + 1369, + 1370, + 1371, + 1372, + 1373, + 1374, + 1375, + 1376, + 1377, + 1378, + 1379, + 1380, + 1381, + 1382, + 1383, + 1384, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1385, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1386, + 1387, + 1388, + 1389, + 1390, + 1391, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1392, + 1393, + 1394, + 1395, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1396, + 1397, + 1398, + 1399, + 1400, + 1401, + 1402, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1403, + 1404, + 1405, + 1406, + 1407, + 1408, + 1409, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1410, + 1411, + 1412, + 1413, + 1414, + 1415, + 1416, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1417, + 1418, + 1419, + 1420, + 1421, + 1422, + 1423, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1424, + 1425, + 1426, + 1427, + 1428, + 1429, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1430, + 1431, + 1432, + 1433, + 1434, + 1435, + 1436, + 1437, + 1438, + 1439, + 1440, + 1441, + 1442, + 1443, + 1444, + 1445, + 1446, + 1447, + 1448, + 1449, + 1450, + 1451, + 1452, + 1453, + 1454, + 1455, + 1456, + 1457, + 1458, + 1459, + 1460, + 1461, + 1462, + 1463, + 1464, + 1465, + 1466, + 1467, + 1468, + 1469, + 1470, + 1471, + 1472, + 1473, + 1474, + 1475, + 1476, + 1477, + 1478, + 1479, + 1480, + 1481, + 1482, + 1483, + 1484, + 1485, + 1486, + 1487, + 1488, + 1489, + 1490, + 1491, + 1492, + 1493, + 1494, + 1495, + 1496, + 1497, + 1498, + 1499, + 1500, + 1501, + 1502, + 1503, + 1504, + 1505, + 1506, + 1507, + 1508, + 1509, + 1510, + 1511, + 1512, + 1513, + 1514, + 1515, + 1516, + 1517, + 1518, + 1519, + 1520, + 1521, + 1522, + 1523, + 1524, + 1525, + 1526, + 1527, + 1528, + 1529, + 1530, + 1531, + 1532, + 1533, + 1534, + 1535, + 1536, + 1537, + 1538, + 1539, + 1540, + 1541, + 1542, + 1543, + 1544, + 1545, + 1546, + 1547, + 1548, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1549, + 1550, + 1551, + 1552, + 1553, + 1554, + 1555, + 1556, + 1557, + 1558, + 1559, + 1560, + 1561, + 1562, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1563, + 1564, + 1565, + 1566, + 1567, + 1568, + 1569, + 1570, + 1571, + 1572, + 1573, + 1574, + 1575, + 1576, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1577, + InvalidOidBuiltinMapping, + 1578, + 1579, + 1580, + 1581, + 1582, + 1583, + 1584, + 1585, + 1586, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1587, + 1588, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1589, + 1590, + 1591, + 1592, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1593, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1594, + 1595, + 1596, + 1597, + 1598, + 1599, + 1600, + 1601, + 1602, + 1603, + InvalidOidBuiltinMapping, + 1604, + 1605, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1606, + 1607, + 1608, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1609, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1610, + 1611, + 1612, + 1613, + 1614, + 1615, + 1616, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1617, + 1618, + 1619, + 1620, + 1621, + 1622, + 1623, + 1624, + 1625, + 1626, + 1627, + 1628, + InvalidOidBuiltinMapping, + 1629, + 1630, + InvalidOidBuiltinMapping, + 1631, + 1632, + 1633, + 1634, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1635, + 1636, + 1637, + 1638, + 1639, + 1640, + 1641, + 1642, + 1643, + 1644, + 1645, + 1646, + 1647, + 1648, + 1649, + 1650, + 1651, + 1652, + InvalidOidBuiltinMapping, + 1653, + 1654, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1655, + 1656, + 1657, + 1658, + 1659, + 1660, + 1661, + 1662, + InvalidOidBuiltinMapping, + 1663, + 1664, + 1665, + 1666, + 1667, + 1668, + 1669, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1670, + 1671, + 1672, + 1673, + 1674, + 1675, + 1676, + 1677, + 1678, + 1679, + 1680, + 1681, + 1682, + 1683, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1684, + 1685, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1686, + 1687, + 1688, + 1689, + 1690, + 1691, + 1692, + 1693, + 1694, + 1695, + 1696, + 1697, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1698, + 1699, + 1700, + 1701, + 1702, + 1703, + 1704, + 1705, + 1706, + 1707, + 1708, + 1709, + 1710, + 1711, + 1712, + 1713, + 1714, + 1715, + 1716, + 1717, + 1718, + 1719, + 1720, + InvalidOidBuiltinMapping, + 1721, + 1722, + 1723, + 1724, + 1725, + 1726, + 1727, + 1728, + 1729, + 1730, + 1731, + 1732, + 1733, + 1734, + 1735, + 1736, + 1737, + 1738, + 1739, + 1740, + 1741, + 1742, + 1743, + 1744, + 1745, + 1746, + 1747, + 1748, + 1749, + 1750, + InvalidOidBuiltinMapping, + 1751, + 1752, + 1753, + 1754, + 1755, + 1756, + 1757, + 1758, + 1759, + 1760, + 1761, + 1762, + 1763, + 1764, + 1765, + 1766, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1767, + 1768, + 1769, + 1770, + 1771, + 1772, + 1773, + 1774, + 1775, + 1776, + 1777, + 1778, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1779, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1780, + 1781, + 1782, + 1783, + 1784, + 1785, + 1786, + 1787, + 1788, + 1789, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1790, + 1791, + 1792, + 1793, + 1794, + 1795, + 1796, + 1797, + 1798, + 1799, + 1800, + 1801, + 1802, + 1803, + 1804, + 1805, + 1806, + 1807, + 1808, + 1809, + 1810, + 1811, + 1812, + 1813, + 1814, + 1815, + 1816, + 1817, + 1818, + 1819, + 1820, + 1821, + 1822, + 1823, + 1824, + 1825, + 1826, + 1827, + 1828, + 1829, + 1830, + 1831, + 1832, + 1833, + 1834, + 1835, + 1836, + 1837, + 1838, + 1839, + 1840, + 1841, + 1842, + InvalidOidBuiltinMapping, + 1843, + 1844, + 1845, + 1846, + 1847, + 1848, + 1849, + 1850, + 1851, + 1852, + 1853, + 1854, + 1855, + 1856, + 1857, + 1858, + 1859, + 1860, + 1861, + 1862, + 1863, + 1864, + 1865, + 1866, + 1867, + 1868, + 1869, + 1870, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1871, + 1872, + 1873, + InvalidOidBuiltinMapping, + 1874, + 1875, + 1876, + 1877, + 1878, + 1879, + 1880, + 1881, + 1882, + 1883, + 1884, + 1885, + 1886, + 1887, + 1888, + 1889, + 1890, + 1891, + 1892, + 1893, + 1894, + 1895, + 1896, + 1897, + 1898, + 1899, + 1900, + 1901, + 1902, + InvalidOidBuiltinMapping, + 1903, + 1904, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1905, + 1906, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1907, + 1908, + 1909, + 1910, + 1911, + 1912, + 1913, + 1914, + 1915, + 1916, + 1917, + 1918, + 1919, + 1920, + 1921, + 1922, + 1923, + 1924, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1925, + 1926, + 1927, + 1928, + 1929, + 1930, + 1931, + 1932, + 1933, + 1934, + 1935, + 1936, + 1937, + 1938, + InvalidOidBuiltinMapping, + 1939, + 1940, + 1941, + 1942, + 1943, + 1944, + 1945, + 1946, + 1947, + 1948, + InvalidOidBuiltinMapping, + 1949, + 1950, + 1951, + 1952, + 1953, + 1954, + 1955, + 1956, + 1957, + 1958, + 1959, + 1960, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1961, + 1962, + InvalidOidBuiltinMapping, + 1963, + 1964, + 1965, + 1966, + 1967, + 1968, + 1969, + 1970, + InvalidOidBuiltinMapping, + 1971, + 1972, + 1973, + 1974, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1975, + 1976, + 1977, + 1978, + 1979, + 1980, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1981, + 1982, + 1983, + 1984, + 1985, + 1986, + 1987, + 1988, + 1989, + 1990, + 1991, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1992, + 1993, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1994, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 1995, + 1996, + 1997, + 1998, + 1999, + 2000, + 2001, + 2002, + InvalidOidBuiltinMapping, + 2003, + 2004, + InvalidOidBuiltinMapping, + 2005, + 2006, + 2007, + 2008, + 2009, + InvalidOidBuiltinMapping, + 2010, + 2011, + 2012, + 2013, + 2014, + 2015, + 2016, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2017, + InvalidOidBuiltinMapping, + 2018, + InvalidOidBuiltinMapping, + 2019, + 2020, + 2021, + 2022, + 2023, + 2024, + 2025, + 2026, + 2027, + 2028, + 2029, + 2030, + 2031, + 2032, + 2033, + 2034, + 2035, + 2036, + InvalidOidBuiltinMapping, + 2037, + 2038, + 2039, + 2040, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2041, + 2042, + 2043, + 2044, + 2045, + 2046, + 2047, + 2048, + 2049, + 2050, + 2051, + 2052, + 2053, + 2054, + 2055, + 2056, + 2057, + 2058, + 2059, + 2060, + 2061, + 2062, + 2063, + 2064, + 2065, + 2066, + 2067, + 2068, + 2069, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2070, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2071, + 2072, + 2073, + 2074, + 2075, + 2076, + 2077, + 2078, + 2079, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2080, + InvalidOidBuiltinMapping, + 2081, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2082, + 2083, + 2084, + 2085, + 2086, + 2087, + 2088, + 2089, + 2090, + 2091, + 2092, + 2093, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2094, + 2095, + InvalidOidBuiltinMapping, + 2096, + 2097, + 2098, + InvalidOidBuiltinMapping, + 2099, + 2100, + 2101, + 2102, + 2103, + 2104, + 2105, + 2106, + 2107, + 2108, + 2109, + 2110, + 2111, + 2112, + 2113, + 2114, + 2115, + 2116, + 2117, + 2118, + 2119, + 2120, + 2121, + 2122, + 2123, + 2124, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2125, + InvalidOidBuiltinMapping, + 2126, + 2127, + 2128, + 2129, + 2130, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2131, + 2132, + 2133, + 2134, + 2135, + 2136, + 2137, + 2138, + 2139, + 2140, + 2141, + 2142, + 2143, + 2144, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2145, + 2146, + 2147, + 2148, + 2149, + 2150, + 2151, + 2152, + 2153, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2154, + 2155, + 2156, + 2157, + 2158, + InvalidOidBuiltinMapping, + 2159, + 2160, + InvalidOidBuiltinMapping, + 2161, + 2162, + 2163, + InvalidOidBuiltinMapping, + 2164, + 2165, + 2166, + 2167, + 2168, + 2169, + 2170, + 2171, + 2172, + 2173, + 2174, + 2175, + 2176, + 2177, + 2178, + 2179, + 2180, + 2181, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2182, + 2183, + 2184, + 2185, + 2186, + 2187, + 2188, + 2189, + 2190, + 2191, + 2192, + 2193, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2194, + 2195, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2196, + 2197, + 2198, + 2199, + 2200, + 2201, + InvalidOidBuiltinMapping, + 2202, + 2203, + 2204, + InvalidOidBuiltinMapping, + 2205, + 2206, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2207, + 2208, + InvalidOidBuiltinMapping, + 2209, + 2210, + 2211, + 2212, + InvalidOidBuiltinMapping, + 2213, + InvalidOidBuiltinMapping, + 2214, + 2215, + 2216, + 2217, + 2218, + 2219, + 2220, + 2221, + 2222, + 2223, + 2224, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2225, + 2226, + 2227, + 2228, + 2229, + 2230, + 2231, + 2232, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2233, + 2234, + 2235, + InvalidOidBuiltinMapping, + 2236, + 2237, + 2238, + 2239, + InvalidOidBuiltinMapping, + 2240, + 2241, + 2242, + 2243, + 2244, + 2245, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2246, + 2247, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2248, + 2249, + 2250, + 2251, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2252, + 2253, + 2254, + 2255, + 2256, + 2257, + 2258, + 2259, + 2260, + 2261, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2262, + 2263, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2264, + 2265, + 2266, + 2267, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2268, + 2269, + 2270, + 2271, + 2272, + 2273, + 2274, + 2275, + 2276, + InvalidOidBuiltinMapping, + 2277, + 2278, + 2279, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2280, + 2281, + 2282, + 2283, + 2284, + 2285, + 2286, + 2287, + 2288, + 2289, + 2290, + 2291, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2292, + 2293, + 2294, + 2295, + 2296, + 2297, + 2298, + 2299, + 2300, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2301, + 2302, + 2303, + 2304, + 2305, + 2306, + 2307, + InvalidOidBuiltinMapping, + 2308, + 2309, + 2310, + 2311, + 2312, + 2313, + 2314, + 2315, + 2316, + InvalidOidBuiltinMapping, + 2317, + 2318, + 2319, + 2320, + 2321, + 2322, + 2323, + 2324, + 2325, + InvalidOidBuiltinMapping, + 2326, + 2327, + 2328, + 2329, + InvalidOidBuiltinMapping, + 2330, + 2331, + InvalidOidBuiltinMapping, + 2332, + 2333, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2334, + 2335, + 2336, + 2337, + 2338, + 2339, + InvalidOidBuiltinMapping, + 2340, + 2341, + 2342, + 2343, + 2344, + InvalidOidBuiltinMapping, + 2345, + 2346, + 2347, + 2348, + 2349, + 2350, + 2351, + 2352, + 2353, + 2354, + 2355, + 2356, + 2357, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2358, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2359, + 2360, + 2361, + 2362, + 2363, + 2364, + 2365, + 2366, + 2367, + 2368, + 2369, + 2370, + 2371, + 2372, + 2373, + 2374, + 2375, + 2376, + 2377, + 2378, + 2379, + 2380, + 2381, + InvalidOidBuiltinMapping, + 2382, + 2383, + 2384, + 2385, + 2386, + 2387, + 2388, + InvalidOidBuiltinMapping, + 2389, + 2390, + 2391, + 2392, + InvalidOidBuiltinMapping, + 2393, + 2394, + 2395, + 2396, + 2397, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2398, + 2399, + 2400, + 2401, + 2402, + 2403, + 2404, + 2405, + 2406, + 2407, + InvalidOidBuiltinMapping, + 2408, + 2409, + 2410, + 2411, + 2412, + InvalidOidBuiltinMapping, + 2413, + 2414, + 2415, + 2416, + 2417, + 2418, + InvalidOidBuiltinMapping, + 2419, + 2420, + 2421, + 2422, + 2423, + 2424, + 2425, + 2426, + 2427, + 2428, + 2429, + 2430, + 2431, + 2432, + 2433, + 2434, + 2435, + 2436, + 2437, + 2438, + 2439, + 2440, + 2441, + 2442, + 2443, + 2444, + 2445, + 2446, + 2447, + 2448, + 2449, + 2450, + 2451, + 2452, + 2453, + 2454, + 2455, + 2456, + InvalidOidBuiltinMapping, + 2457, + 2458, + 2459, + 2460, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2461, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2462, + 2463, + 2464, + 2465, + 2466, + InvalidOidBuiltinMapping, + 2467, + 2468, + 2469, + 2470, + 2471, + 2472, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2473, + 2474, + 2475, + 2476, + 2477, + 2478, + 2479, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2480, + 2481, + 2482, + 2483, + 2484, + 2485, + 2486, + 2487, + 2488, + 2489, + 2490, + 2491, + 2492, + 2493, + 2494, + 2495, + 2496, + 2497, + 2498, + 2499, + 2500, + 2501, + 2502, + 2503, + 2504, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2505, + 2506, + 2507, + 2508, + InvalidOidBuiltinMapping, + 2509, + InvalidOidBuiltinMapping, + 2510, + InvalidOidBuiltinMapping, + 2511, + InvalidOidBuiltinMapping, + 2512, + InvalidOidBuiltinMapping, + 2513, + InvalidOidBuiltinMapping, + 2514, + InvalidOidBuiltinMapping, + 2515, + InvalidOidBuiltinMapping, + 2516, + InvalidOidBuiltinMapping, + 2517, + InvalidOidBuiltinMapping, + 2518, + InvalidOidBuiltinMapping, + 2519, + 2520, + 2521, + 2522, + InvalidOidBuiltinMapping, + 2523, + 2524, + InvalidOidBuiltinMapping, + 2525, + 2526, + 2527, + 2528, + 2529, + 2530, + 2531, + 2532, + 2533, + 2534, + 2535, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2536, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2537, + 2538, + 2539, + 2540, + 2541, + 2542, + 2543, + 2544, + 2545, + 2546, + 2547, + 2548, + 2549, + 2550, + 2551, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2552, + 2553, + 2554, + 2555, + 2556, + 2557, + 2558, + 2559, + 2560, + 2561, + 2562, + 2563, + 2564, + 2565, + 2566, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2567, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2568, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2569, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2570, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2571, + 2572, + 2573, + 2574, + 2575, + 2576, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2577, + 2578, + 2579, + 2580, + 2581, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2582, + 2583, + 2584, + 2585, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2586, + 2587, + 2588, + 2589, + InvalidOidBuiltinMapping, + 2590, + 2591, + 2592, + 2593, + 2594, + 2595, + 2596, + 2597, + 2598, + 2599, + 2600, + 2601, + 2602, + 2603, + 2604, + 2605, + 2606, + 2607, + 2608, + 2609, + 2610, + 2611, + 2612, + 2613, + 2614, + 2615, + 2616, + 2617, + 2618, + 2619, + 2620, + 2621, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2622, + 2623, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2624, + 2625, + 2626, + 2627, + 2628, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2629, + 2630, + 2631, + 2632, + 2633, + 2634, + 2635, + 2636, + 2637, + 2638, + 2639, + 2640, + 2641, + 2642, + 2643, + 2644, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2645, + 2646, + 2647, + 2648, + 2649, + InvalidOidBuiltinMapping, + 2650, + 2651, + 2652, + 2653, + 2654, + 2655, + 2656, + 2657, + 2658, + 2659, + 2660, + 2661, + 2662, + 2663, + 2664, + 2665, + 2666, + 2667, + 2668, + 2669, + 2670, + 2671, + 2672, + 2673, + 2674, + 2675, + 2676, + 2677, + 2678, + 2679, + 2680, + 2681, + 2682, + 2683, + 2684, + 2685, + 2686, + 2687, + 2688, + 2689, + 2690, + 2691, + 2692, + 2693, + 2694, + 2695, + 2696, + 2697, + 2698, + 2699, + 2700, + 2701, + 2702, + 2703, + 2704, + 2705, + 2706, + 2707, + 2708, + 2709, + 2710, + 2711, + 2712, + 2713, + 2714, + 2715, + 2716, + 2717, + 2718, + 2719, + 2720, + 2721, + 2722, + 2723, + 2724, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2725, + 2726, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2727, + InvalidOidBuiltinMapping, + 2728, + 2729, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2730, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2731, + 2732, + 2733, + InvalidOidBuiltinMapping, + 2734, + 2735, + 2736, + 2737, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2738, + 2739, + 2740, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2741, + 2742, + 2743, + 2744, + 2745, + 2746, + 2747, + 2748, + 2749, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2750, + 2751, + 2752, + 2753, + 2754, + 2755, + 2756, + 2757, + 2758, + 2759, + 2760, + 2761, + 2762, + 2763, + 2764, + 2765, + 2766, + 2767, + 2768, + 2769, + 2770, + 2771, + 2772, + 2773, + 2774, + 2775, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2776, + InvalidOidBuiltinMapping, + 2777, + 2778, + InvalidOidBuiltinMapping, + 2779, + 2780, + InvalidOidBuiltinMapping, + 2781, + 2782, + 2783, + 2784, + 2785, + 2786, + 2787, + 2788, + InvalidOidBuiltinMapping, + 2789, + 2790, + 2791, + 2792, + 2793, + InvalidOidBuiltinMapping, + 2794, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2795, + 2796, + 2797, + 2798, + InvalidOidBuiltinMapping, + 2799, + 2800, + 2801, + 2802, + 2803, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2804, + 2805, + 2806, + 2807, + 2808, + 2809, + 2810, + 2811, + 2812, + 2813, + 2814, + 2815, + 2816, + 2817, + 2818, + 2819, + 2820, + 2821, + 2822, + 2823, + 2824, + 2825, + 2826, + 2827, + 2828, + 2829, + 2830, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2831, + 2832, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2833, + 2834, + 2835, + 2836, + 2837, + 2838, + 2839, + 2840, + 2841, + 2842, + 2843, + 2844, + 2845, + 2846, + 2847, + 2848, + 2849, + 2850, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2851, + 2852, + 2853, + 2854, + 2855, + 2856, + 2857, + 2858, + 2859, + 2860, + 2861, + 2862, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2863, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2864, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2865, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2866, + 2867, + 2868, + 2869, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2870, + InvalidOidBuiltinMapping, + 2871, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2872, + 2873, + 2874, + 2875, + 2876, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2877, + 2878, + 2879, + InvalidOidBuiltinMapping, + 2880, + 2881, + 2882, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2883, + 2884, + 2885, + 2886, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2887, + 2888, + 2889, + 2890, + 2891, + 2892, + 2893, + 2894, + 2895, + InvalidOidBuiltinMapping, + 2896, + 2897, + 2898, + 2899, + 2900, + 2901, + 2902, + 2903, + 2904, + 2905, + 2906, + 2907, + 2908, + 2909, + 2910, + 2911, + 2912, + 2913, + 2914, + 2915, + 2916, + 2917, + 2918, + 2919, + 2920, + 2921, + 2922, + 2923, + 2924, + 2925, + 2926, + 2927, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2928, + 2929, + 2930, + 2931, + 2932, + 2933, + 2934, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2935, + 2936, + 2937, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2938, + 2939, + 2940, + 2941, + 2942, + 2943, + 2944, + 2945, + 2946, + 2947, + 2948, + 2949, + 2950, + 2951, + 2952, + 2953, + 2954, + 2955, + 2956, + 2957, + 2958, + 2959, + 2960, + 2961, + 2962, + 2963, + 2964, + 2965, + InvalidOidBuiltinMapping, + 2966, + 2967, + 2968, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2969, + InvalidOidBuiltinMapping, + 2970, + 2971, + 2972, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2973, + 2974, + 2975, + 2976, + 2977, + 2978, + 2979, + 2980, + 2981, + 2982, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + InvalidOidBuiltinMapping, + 2983, + 2984, + 2985, + 2986, + 2987, + 2988, + 2989 +}; diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/hash/dynahash.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/hash/dynahash.c new file mode 100644 index 00000000000..3ab45a8bd9f --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/hash/dynahash.c @@ -0,0 +1,1925 @@ +/*------------------------------------------------------------------------- + * + * dynahash.c + * dynamic chained hash tables + * + * dynahash.c supports both local-to-a-backend hash tables and hash tables in + * shared memory. For shared hash tables, it is the caller's responsibility + * to provide appropriate access interlocking. The simplest convention is + * that a single LWLock protects the whole hash table. Searches (HASH_FIND or + * hash_seq_search) need only shared lock, but any update requires exclusive + * lock. For heavily-used shared tables, the single-lock approach creates a + * concurrency bottleneck, so we also support "partitioned" locking wherein + * there are multiple LWLocks guarding distinct subsets of the table. To use + * a hash table in partitioned mode, the HASH_PARTITION flag must be given + * to hash_create. This prevents any attempt to split buckets on-the-fly. + * Therefore, each hash bucket chain operates independently, and no fields + * of the hash header change after init except nentries and freeList. + * (A partitioned table uses multiple copies of those fields, guarded by + * spinlocks, for additional concurrency.) + * This lets any subset of the hash buckets be treated as a separately + * lockable partition. We expect callers to use the low-order bits of a + * lookup key's hash value as a partition number --- this will work because + * of the way calc_bucket() maps hash values to bucket numbers. + * + * For hash tables in shared memory, the memory allocator function should + * match malloc's semantics of returning NULL on failure. For hash tables + * in local memory, we typically use palloc() which will throw error on + * failure. The code in this file has to cope with both cases. + * + * dynahash.c provides support for these types of lookup keys: + * + * 1. Null-terminated C strings (truncated if necessary to fit in keysize), + * compared as though by strcmp(). This is selected by specifying the + * HASH_STRINGS flag to hash_create. + * + * 2. Arbitrary binary data of size keysize, compared as though by memcmp(). + * (Caller must ensure there are no undefined padding bits in the keys!) + * This is selected by specifying the HASH_BLOBS flag to hash_create. + * + * 3. More complex key behavior can be selected by specifying user-supplied + * hashing, comparison, and/or key-copying functions. At least a hashing + * function must be supplied; comparison defaults to memcmp() and key copying + * to memcpy() when a user-defined hashing function is selected. + * + * Compared to simplehash, dynahash has the following benefits: + * + * - It supports partitioning, which is useful for shared memory access using + * locks. + * - Shared memory hashes are allocated in a fixed size area at startup and + * are discoverable by name from other processes. + * - Because entries don't need to be moved in the case of hash conflicts, + * dynahash has better performance for large entries. + * - Guarantees stable pointers to entries. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/hash/dynahash.c + * + *------------------------------------------------------------------------- + */ + +/* + * Original comments: + * + * Dynamic hashing, after CACM April 1988 pp 446-457, by Per-Ake Larson. + * Coded into C, with minor code improvements, and with hsearch(3) interface, + * by ejp@ausmelb.oz, Jul 26, 1988: 13:16; + * also, hcreate/hdestroy routines added to simulate hsearch(3). + * + * These routines simulate hsearch(3) and family, with the important + * difference that the hash table is dynamic - can grow indefinitely + * beyond its original size (as supplied to hcreate()). + * + * Performance appears to be comparable to that of hsearch(3). + * The 'source-code' options referred to in hsearch(3)'s 'man' page + * are not implemented; otherwise functionality is identical. + * + * Compilation controls: + * HASH_DEBUG controls some informative traces, mainly for debugging. + * HASH_STATISTICS causes HashAccesses and HashCollisions to be maintained; + * when combined with HASH_DEBUG, these are displayed by hdestroy(). + * + * Problems & fixes to ejp@ausmelb.oz. WARNING: relies on pre-processor + * concatenation property, in probably unnecessary code 'optimization'. + * + * Modified margo@postgres.berkeley.edu February 1990 + * added multiple table interface + * Modified by sullivan@postgres.berkeley.edu April 1990 + * changed ctl structure for shared memory + */ + +#include "postgres.h" + +#include <limits.h> + +#include "access/xact.h" +#include "common/hashfn.h" +#include "port/pg_bitutils.h" +#include "storage/shmem.h" +#include "storage/spin.h" +#include "utils/dynahash.h" +#include "utils/memutils.h" + + +/* + * Constants + * + * A hash table has a top-level "directory", each of whose entries points + * to a "segment" of ssize bucket headers. The maximum number of hash + * buckets is thus dsize * ssize (but dsize may be expansible). Of course, + * the number of records in the table can be larger, but we don't want a + * whole lot of records per bucket or performance goes down. + * + * In a hash table allocated in shared memory, the directory cannot be + * expanded because it must stay at a fixed address. The directory size + * should be selected using hash_select_dirsize (and you'd better have + * a good idea of the maximum number of entries!). For non-shared hash + * tables, the initial directory size can be left at the default. + */ +#define DEF_SEGSIZE 256 +#define DEF_SEGSIZE_SHIFT 8 /* must be log2(DEF_SEGSIZE) */ +#define DEF_DIRSIZE 256 + +/* Number of freelists to be used for a partitioned hash table. */ +#define NUM_FREELISTS 32 + +/* A hash bucket is a linked list of HASHELEMENTs */ +typedef HASHELEMENT *HASHBUCKET; + +/* A hash segment is an array of bucket headers */ +typedef HASHBUCKET *HASHSEGMENT; + +/* + * Per-freelist data. + * + * In a partitioned hash table, each freelist is associated with a specific + * set of hashcodes, as determined by the FREELIST_IDX() macro below. + * nentries tracks the number of live hashtable entries having those hashcodes + * (NOT the number of entries in the freelist, as you might expect). + * + * The coverage of a freelist might be more or less than one partition, so it + * needs its own lock rather than relying on caller locking. Relying on that + * wouldn't work even if the coverage was the same, because of the occasional + * need to "borrow" entries from another freelist; see get_hash_entry(). + * + * Using an array of FreeListData instead of separate arrays of mutexes, + * nentries and freeLists helps to reduce sharing of cache lines between + * different mutexes. + */ +typedef struct +{ + slock_t mutex; /* spinlock for this freelist */ + long nentries; /* number of entries in associated buckets */ + HASHELEMENT *freeList; /* chain of free elements */ +} FreeListData; + +/* + * Header structure for a hash table --- contains all changeable info + * + * In a shared-memory hash table, the HASHHDR is in shared memory, while + * each backend has a local HTAB struct. For a non-shared table, there isn't + * any functional difference between HASHHDR and HTAB, but we separate them + * anyway to share code between shared and non-shared tables. + */ +struct HASHHDR +{ + /* + * The freelist can become a point of contention in high-concurrency hash + * tables, so we use an array of freelists, each with its own mutex and + * nentries count, instead of just a single one. Although the freelists + * normally operate independently, we will scavenge entries from freelists + * other than a hashcode's default freelist when necessary. + * + * If the hash table is not partitioned, only freeList[0] is used and its + * spinlock is not used at all; callers' locking is assumed sufficient. + */ + FreeListData freeList[NUM_FREELISTS]; + + /* These fields can change, but not in a partitioned table */ + /* Also, dsize can't change in a shared table, even if unpartitioned */ + long dsize; /* directory size */ + long nsegs; /* number of allocated segments (<= dsize) */ + uint32 max_bucket; /* ID of maximum bucket in use */ + uint32 high_mask; /* mask to modulo into entire table */ + uint32 low_mask; /* mask to modulo into lower half of table */ + + /* These fields are fixed at hashtable creation */ + Size keysize; /* hash key length in bytes */ + Size entrysize; /* total user element size in bytes */ + long num_partitions; /* # partitions (must be power of 2), or 0 */ + long max_dsize; /* 'dsize' limit if directory is fixed size */ + long ssize; /* segment size --- must be power of 2 */ + int sshift; /* segment shift = log2(ssize) */ + int nelem_alloc; /* number of entries to allocate at once */ + +#ifdef HASH_STATISTICS + + /* + * Count statistics here. NB: stats code doesn't bother with mutex, so + * counts could be corrupted a bit in a partitioned table. + */ + long accesses; + long collisions; +#endif +}; + +#define IS_PARTITIONED(hctl) ((hctl)->num_partitions != 0) + +#define FREELIST_IDX(hctl, hashcode) \ + (IS_PARTITIONED(hctl) ? (hashcode) % NUM_FREELISTS : 0) + +/* + * Top control structure for a hashtable --- in a shared table, each backend + * has its own copy (OK since no fields change at runtime) + */ +struct HTAB +{ + HASHHDR *hctl; /* => shared control information */ + HASHSEGMENT *dir; /* directory of segment starts */ + HashValueFunc hash; /* hash function */ + HashCompareFunc match; /* key comparison function */ + HashCopyFunc keycopy; /* key copying function */ + HashAllocFunc alloc; /* memory allocator */ + MemoryContext hcxt; /* memory context if default allocator used */ + char *tabname; /* table name (for error messages) */ + bool isshared; /* true if table is in shared memory */ + bool isfixed; /* if true, don't enlarge */ + + /* freezing a shared table isn't allowed, so we can keep state here */ + bool frozen; /* true = no more inserts allowed */ + + /* We keep local copies of these fixed values to reduce contention */ + Size keysize; /* hash key length in bytes */ + long ssize; /* segment size --- must be power of 2 */ + int sshift; /* segment shift = log2(ssize) */ +}; + +/* + * Key (also entry) part of a HASHELEMENT + */ +#define ELEMENTKEY(helem) (((char *)(helem)) + MAXALIGN(sizeof(HASHELEMENT))) + +/* + * Obtain element pointer given pointer to key + */ +#define ELEMENT_FROM_KEY(key) \ + ((HASHELEMENT *) (((char *) (key)) - MAXALIGN(sizeof(HASHELEMENT)))) + +/* + * Fast MOD arithmetic, assuming that y is a power of 2 ! + */ +#define MOD(x,y) ((x) & ((y)-1)) + +#ifdef HASH_STATISTICS +static long hash_accesses, + hash_collisions, + hash_expansions; +#endif + +/* + * Private function prototypes + */ +static void *DynaHashAlloc(Size size); +static HASHSEGMENT seg_alloc(HTAB *hashp); +static bool element_alloc(HTAB *hashp, int nelem, int freelist_idx); +static bool dir_realloc(HTAB *hashp); +static bool expand_table(HTAB *hashp); +static HASHBUCKET get_hash_entry(HTAB *hashp, int freelist_idx); +static void hdefault(HTAB *hashp); +static int choose_nelem_alloc(Size entrysize); +static bool init_htab(HTAB *hashp, long nelem); +static void hash_corrupted(HTAB *hashp); +static long next_pow2_long(long num); +static int next_pow2_int(long num); +static void register_seq_scan(HTAB *hashp); +static void deregister_seq_scan(HTAB *hashp); +static bool has_seq_scans(HTAB *hashp); + + +/* + * memory allocation support + */ +static __thread MemoryContext CurrentDynaHashCxt = NULL; + +static void * +DynaHashAlloc(Size size) +{ + Assert(MemoryContextIsValid(CurrentDynaHashCxt)); + return MemoryContextAllocExtended(CurrentDynaHashCxt, size, + MCXT_ALLOC_NO_OOM); +} + + +/* + * HashCompareFunc for string keys + * + * Because we copy keys with strlcpy(), they will be truncated at keysize-1 + * bytes, so we can only compare that many ... hence strncmp is almost but + * not quite the right thing. + */ +static int +string_compare(const char *key1, const char *key2, Size keysize) +{ + return strncmp(key1, key2, keysize - 1); +} + + +/************************** CREATE ROUTINES **********************/ + +/* + * hash_create -- create a new dynamic hash table + * + * tabname: a name for the table (for debugging purposes) + * nelem: maximum number of elements expected + * *info: additional table parameters, as indicated by flags + * flags: bitmask indicating which parameters to take from *info + * + * The flags value *must* include HASH_ELEM. (Formerly, this was nominally + * optional, but the default keysize and entrysize values were useless.) + * The flags value must also include exactly one of HASH_STRINGS, HASH_BLOBS, + * or HASH_FUNCTION, to define the key hashing semantics (C strings, + * binary blobs, or custom, respectively). Callers specifying a custom + * hash function will likely also want to use HASH_COMPARE, and perhaps + * also HASH_KEYCOPY, to control key comparison and copying. + * Another often-used flag is HASH_CONTEXT, to allocate the hash table + * under info->hcxt rather than under TopMemoryContext; the default + * behavior is only suitable for session-lifespan hash tables. + * Other flags bits are special-purpose and seldom used, except for those + * associated with shared-memory hash tables, for which see ShmemInitHash(). + * + * Fields in *info are read only when the associated flags bit is set. + * It is not necessary to initialize other fields of *info. + * Neither tabname nor *info need persist after the hash_create() call. + * + * Note: It is deprecated for callers of hash_create() to explicitly specify + * string_hash, tag_hash, uint32_hash, or oid_hash. Just set HASH_STRINGS or + * HASH_BLOBS. Use HASH_FUNCTION only when you want something other than + * one of these. + * + * Note: for a shared-memory hashtable, nelem needs to be a pretty good + * estimate, since we can't expand the table on the fly. But an unshared + * hashtable can be expanded on-the-fly, so it's better for nelem to be + * on the small side and let the table grow if it's exceeded. An overly + * large nelem will penalize hash_seq_search speed without buying much. + */ +HTAB * +hash_create(const char *tabname, long nelem, const HASHCTL *info, int flags) +{ + HTAB *hashp; + HASHHDR *hctl; + + /* + * Hash tables now allocate space for key and data, but you have to say + * how much space to allocate. + */ + Assert(flags & HASH_ELEM); + Assert(info->keysize > 0); + Assert(info->entrysize >= info->keysize); + + /* + * For shared hash tables, we have a local hash header (HTAB struct) that + * we allocate in TopMemoryContext; all else is in shared memory. + * + * For non-shared hash tables, everything including the hash header is in + * a memory context created specially for the hash table --- this makes + * hash_destroy very simple. The memory context is made a child of either + * a context specified by the caller, or TopMemoryContext if nothing is + * specified. + */ + if (flags & HASH_SHARED_MEM) + { + /* Set up to allocate the hash header */ + CurrentDynaHashCxt = TopMemoryContext; + } + else + { + /* Create the hash table's private memory context */ + if (flags & HASH_CONTEXT) + CurrentDynaHashCxt = info->hcxt; + else + CurrentDynaHashCxt = TopMemoryContext; + CurrentDynaHashCxt = AllocSetContextCreate(CurrentDynaHashCxt, + "dynahash", + ALLOCSET_DEFAULT_SIZES); + } + + /* Initialize the hash header, plus a copy of the table name */ + hashp = (HTAB *) DynaHashAlloc(sizeof(HTAB) + strlen(tabname) + 1); + MemSet(hashp, 0, sizeof(HTAB)); + + hashp->tabname = (char *) (hashp + 1); + strcpy(hashp->tabname, tabname); + + /* If we have a private context, label it with hashtable's name */ + if (!(flags & HASH_SHARED_MEM)) + MemoryContextSetIdentifier(CurrentDynaHashCxt, hashp->tabname); + + /* + * Select the appropriate hash function (see comments at head of file). + */ + if (flags & HASH_FUNCTION) + { + Assert(!(flags & (HASH_BLOBS | HASH_STRINGS))); + hashp->hash = info->hash; + } + else if (flags & HASH_BLOBS) + { + Assert(!(flags & HASH_STRINGS)); + /* We can optimize hashing for common key sizes */ + if (info->keysize == sizeof(uint32)) + hashp->hash = uint32_hash; + else + hashp->hash = tag_hash; + } + else + { + /* + * string_hash used to be considered the default hash method, and in a + * non-assert build it effectively still is. But we now consider it + * an assertion error to not say HASH_STRINGS explicitly. To help + * catch mistaken usage of HASH_STRINGS, we also insist on a + * reasonably long string length: if the keysize is only 4 or 8 bytes, + * it's almost certainly an integer or pointer not a string. + */ + Assert(flags & HASH_STRINGS); + Assert(info->keysize > 8); + + hashp->hash = string_hash; + } + + /* + * If you don't specify a match function, it defaults to string_compare if + * you used string_hash, and to memcmp otherwise. + * + * Note: explicitly specifying string_hash is deprecated, because this + * might not work for callers in loadable modules on some platforms due to + * referencing a trampoline instead of the string_hash function proper. + * Specify HASH_STRINGS instead. + */ + if (flags & HASH_COMPARE) + hashp->match = info->match; + else if (hashp->hash == string_hash) + hashp->match = (HashCompareFunc) string_compare; + else + hashp->match = memcmp; + + /* + * Similarly, the key-copying function defaults to strlcpy or memcpy. + */ + if (flags & HASH_KEYCOPY) + hashp->keycopy = info->keycopy; + else if (hashp->hash == string_hash) + { + /* + * The signature of keycopy is meant for memcpy(), which returns + * void*, but strlcpy() returns size_t. Since we never use the return + * value of keycopy, and size_t is pretty much always the same size as + * void *, this should be safe. The extra cast in the middle is to + * avoid warnings from -Wcast-function-type. + */ + hashp->keycopy = (HashCopyFunc) (pg_funcptr_t) strlcpy; + } + else + hashp->keycopy = memcpy; + + /* And select the entry allocation function, too. */ + if (flags & HASH_ALLOC) + hashp->alloc = info->alloc; + else + hashp->alloc = DynaHashAlloc; + + if (flags & HASH_SHARED_MEM) + { + /* + * ctl structure and directory are preallocated for shared memory + * tables. Note that HASH_DIRSIZE and HASH_ALLOC had better be set as + * well. + */ + hashp->hctl = info->hctl; + hashp->dir = (HASHSEGMENT *) (((char *) info->hctl) + sizeof(HASHHDR)); + hashp->hcxt = NULL; + hashp->isshared = true; + + /* hash table already exists, we're just attaching to it */ + if (flags & HASH_ATTACH) + { + /* make local copies of some heavily-used values */ + hctl = hashp->hctl; + hashp->keysize = hctl->keysize; + hashp->ssize = hctl->ssize; + hashp->sshift = hctl->sshift; + + return hashp; + } + } + else + { + /* setup hash table defaults */ + hashp->hctl = NULL; + hashp->dir = NULL; + hashp->hcxt = CurrentDynaHashCxt; + hashp->isshared = false; + } + + if (!hashp->hctl) + { + hashp->hctl = (HASHHDR *) hashp->alloc(sizeof(HASHHDR)); + if (!hashp->hctl) + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"))); + } + + hashp->frozen = false; + + hdefault(hashp); + + hctl = hashp->hctl; + + if (flags & HASH_PARTITION) + { + /* Doesn't make sense to partition a local hash table */ + Assert(flags & HASH_SHARED_MEM); + + /* + * The number of partitions had better be a power of 2. Also, it must + * be less than INT_MAX (see init_htab()), so call the int version of + * next_pow2. + */ + Assert(info->num_partitions == next_pow2_int(info->num_partitions)); + + hctl->num_partitions = info->num_partitions; + } + + if (flags & HASH_SEGMENT) + { + hctl->ssize = info->ssize; + hctl->sshift = my_log2(info->ssize); + /* ssize had better be a power of 2 */ + Assert(hctl->ssize == (1L << hctl->sshift)); + } + + /* + * SHM hash tables have fixed directory size passed by the caller. + */ + if (flags & HASH_DIRSIZE) + { + hctl->max_dsize = info->max_dsize; + hctl->dsize = info->dsize; + } + + /* remember the entry sizes, too */ + hctl->keysize = info->keysize; + hctl->entrysize = info->entrysize; + + /* make local copies of heavily-used constant fields */ + hashp->keysize = hctl->keysize; + hashp->ssize = hctl->ssize; + hashp->sshift = hctl->sshift; + + /* Build the hash directory structure */ + if (!init_htab(hashp, nelem)) + elog(ERROR, "failed to initialize hash table \"%s\"", hashp->tabname); + + /* + * For a shared hash table, preallocate the requested number of elements. + * This reduces problems with run-time out-of-shared-memory conditions. + * + * For a non-shared hash table, preallocate the requested number of + * elements if it's less than our chosen nelem_alloc. This avoids wasting + * space if the caller correctly estimates a small table size. + */ + if ((flags & HASH_SHARED_MEM) || + nelem < hctl->nelem_alloc) + { + int i, + freelist_partitions, + nelem_alloc, + nelem_alloc_first; + + /* + * If hash table is partitioned, give each freelist an equal share of + * the initial allocation. Otherwise only freeList[0] is used. + */ + if (IS_PARTITIONED(hashp->hctl)) + freelist_partitions = NUM_FREELISTS; + else + freelist_partitions = 1; + + nelem_alloc = nelem / freelist_partitions; + if (nelem_alloc <= 0) + nelem_alloc = 1; + + /* + * Make sure we'll allocate all the requested elements; freeList[0] + * gets the excess if the request isn't divisible by NUM_FREELISTS. + */ + if (nelem_alloc * freelist_partitions < nelem) + nelem_alloc_first = + nelem - nelem_alloc * (freelist_partitions - 1); + else + nelem_alloc_first = nelem_alloc; + + for (i = 0; i < freelist_partitions; i++) + { + int temp = (i == 0) ? nelem_alloc_first : nelem_alloc; + + if (!element_alloc(hashp, temp, i)) + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"))); + } + } + + if (flags & HASH_FIXED_SIZE) + hashp->isfixed = true; + return hashp; +} + +/* + * Set default HASHHDR parameters. + */ +static void +hdefault(HTAB *hashp) +{ + HASHHDR *hctl = hashp->hctl; + + MemSet(hctl, 0, sizeof(HASHHDR)); + + hctl->dsize = DEF_DIRSIZE; + hctl->nsegs = 0; + + hctl->num_partitions = 0; /* not partitioned */ + + /* table has no fixed maximum size */ + hctl->max_dsize = NO_MAX_DSIZE; + + hctl->ssize = DEF_SEGSIZE; + hctl->sshift = DEF_SEGSIZE_SHIFT; + +#ifdef HASH_STATISTICS + hctl->accesses = hctl->collisions = 0; +#endif +} + +/* + * Given the user-specified entry size, choose nelem_alloc, ie, how many + * elements to add to the hash table when we need more. + */ +static int +choose_nelem_alloc(Size entrysize) +{ + int nelem_alloc; + Size elementSize; + Size allocSize; + + /* Each element has a HASHELEMENT header plus user data. */ + /* NB: this had better match element_alloc() */ + elementSize = MAXALIGN(sizeof(HASHELEMENT)) + MAXALIGN(entrysize); + + /* + * The idea here is to choose nelem_alloc at least 32, but round up so + * that the allocation request will be a power of 2 or just less. This + * makes little difference for hash tables in shared memory, but for hash + * tables managed by palloc, the allocation request will be rounded up to + * a power of 2 anyway. If we fail to take this into account, we'll waste + * as much as half the allocated space. + */ + allocSize = 32 * 4; /* assume elementSize at least 8 */ + do + { + allocSize <<= 1; + nelem_alloc = allocSize / elementSize; + } while (nelem_alloc < 32); + + return nelem_alloc; +} + +/* + * Compute derived fields of hctl and build the initial directory/segment + * arrays + */ +static bool +init_htab(HTAB *hashp, long nelem) +{ + HASHHDR *hctl = hashp->hctl; + HASHSEGMENT *segp; + int nbuckets; + int nsegs; + int i; + + /* + * initialize mutexes if it's a partitioned table + */ + if (IS_PARTITIONED(hctl)) + for (i = 0; i < NUM_FREELISTS; i++) + SpinLockInit(&(hctl->freeList[i].mutex)); + + /* + * Allocate space for the next greater power of two number of buckets, + * assuming a desired maximum load factor of 1. + */ + nbuckets = next_pow2_int(nelem); + + /* + * In a partitioned table, nbuckets must be at least equal to + * num_partitions; were it less, keys with apparently different partition + * numbers would map to the same bucket, breaking partition independence. + * (Normally nbuckets will be much bigger; this is just a safety check.) + */ + while (nbuckets < hctl->num_partitions) + nbuckets <<= 1; + + hctl->max_bucket = hctl->low_mask = nbuckets - 1; + hctl->high_mask = (nbuckets << 1) - 1; + + /* + * Figure number of directory segments needed, round up to a power of 2 + */ + nsegs = (nbuckets - 1) / hctl->ssize + 1; + nsegs = next_pow2_int(nsegs); + + /* + * Make sure directory is big enough. If pre-allocated directory is too + * small, choke (caller screwed up). + */ + if (nsegs > hctl->dsize) + { + if (!(hashp->dir)) + hctl->dsize = nsegs; + else + return false; + } + + /* Allocate a directory */ + if (!(hashp->dir)) + { + CurrentDynaHashCxt = hashp->hcxt; + hashp->dir = (HASHSEGMENT *) + hashp->alloc(hctl->dsize * sizeof(HASHSEGMENT)); + if (!hashp->dir) + return false; + } + + /* Allocate initial segments */ + for (segp = hashp->dir; hctl->nsegs < nsegs; hctl->nsegs++, segp++) + { + *segp = seg_alloc(hashp); + if (*segp == NULL) + return false; + } + + /* Choose number of entries to allocate at a time */ + hctl->nelem_alloc = choose_nelem_alloc(hctl->entrysize); + +#ifdef HASH_DEBUG + fprintf(stderr, "init_htab:\n%s%p\n%s%ld\n%s%ld\n%s%d\n%s%ld\n%s%u\n%s%x\n%s%x\n%s%ld\n", + "TABLE POINTER ", hashp, + "DIRECTORY SIZE ", hctl->dsize, + "SEGMENT SIZE ", hctl->ssize, + "SEGMENT SHIFT ", hctl->sshift, + "MAX BUCKET ", hctl->max_bucket, + "HIGH MASK ", hctl->high_mask, + "LOW MASK ", hctl->low_mask, + "NSEGS ", hctl->nsegs); +#endif + return true; +} + +/* + * Estimate the space needed for a hashtable containing the given number + * of entries of given size. + * NOTE: this is used to estimate the footprint of hashtables in shared + * memory; therefore it does not count HTAB which is in local memory. + * NB: assumes that all hash structure parameters have default values! + */ +Size +hash_estimate_size(long num_entries, Size entrysize) +{ + Size size; + long nBuckets, + nSegments, + nDirEntries, + nElementAllocs, + elementSize, + elementAllocCnt; + + /* estimate number of buckets wanted */ + nBuckets = next_pow2_long(num_entries); + /* # of segments needed for nBuckets */ + nSegments = next_pow2_long((nBuckets - 1) / DEF_SEGSIZE + 1); + /* directory entries */ + nDirEntries = DEF_DIRSIZE; + while (nDirEntries < nSegments) + nDirEntries <<= 1; /* dir_alloc doubles dsize at each call */ + + /* fixed control info */ + size = MAXALIGN(sizeof(HASHHDR)); /* but not HTAB, per above */ + /* directory */ + size = add_size(size, mul_size(nDirEntries, sizeof(HASHSEGMENT))); + /* segments */ + size = add_size(size, mul_size(nSegments, + MAXALIGN(DEF_SEGSIZE * sizeof(HASHBUCKET)))); + /* elements --- allocated in groups of choose_nelem_alloc() entries */ + elementAllocCnt = choose_nelem_alloc(entrysize); + nElementAllocs = (num_entries - 1) / elementAllocCnt + 1; + elementSize = MAXALIGN(sizeof(HASHELEMENT)) + MAXALIGN(entrysize); + size = add_size(size, + mul_size(nElementAllocs, + mul_size(elementAllocCnt, elementSize))); + + return size; +} + +/* + * Select an appropriate directory size for a hashtable with the given + * maximum number of entries. + * This is only needed for hashtables in shared memory, whose directories + * cannot be expanded dynamically. + * NB: assumes that all hash structure parameters have default values! + * + * XXX this had better agree with the behavior of init_htab()... + */ +long +hash_select_dirsize(long num_entries) +{ + long nBuckets, + nSegments, + nDirEntries; + + /* estimate number of buckets wanted */ + nBuckets = next_pow2_long(num_entries); + /* # of segments needed for nBuckets */ + nSegments = next_pow2_long((nBuckets - 1) / DEF_SEGSIZE + 1); + /* directory entries */ + nDirEntries = DEF_DIRSIZE; + while (nDirEntries < nSegments) + nDirEntries <<= 1; /* dir_alloc doubles dsize at each call */ + + return nDirEntries; +} + +/* + * Compute the required initial memory allocation for a shared-memory + * hashtable with the given parameters. We need space for the HASHHDR + * and for the (non expansible) directory. + */ +Size +hash_get_shared_size(HASHCTL *info, int flags) +{ + Assert(flags & HASH_DIRSIZE); + Assert(info->dsize == info->max_dsize); + return sizeof(HASHHDR) + info->dsize * sizeof(HASHSEGMENT); +} + + +/********************** DESTROY ROUTINES ************************/ + +void +hash_destroy(HTAB *hashp) +{ + if (hashp != NULL) + { + /* allocation method must be one we know how to free, too */ + Assert(hashp->alloc == DynaHashAlloc); + /* so this hashtable must have its own context */ + Assert(hashp->hcxt != NULL); + + hash_stats("destroy", hashp); + + /* + * Free everything by destroying the hash table's memory context. + */ + MemoryContextDelete(hashp->hcxt); + } +} + +void +hash_stats(const char *where, HTAB *hashp) +{ +#ifdef HASH_STATISTICS + fprintf(stderr, "%s: this HTAB -- accesses %ld collisions %ld\n", + where, hashp->hctl->accesses, hashp->hctl->collisions); + + fprintf(stderr, "hash_stats: entries %ld keysize %ld maxp %u segmentcount %ld\n", + hash_get_num_entries(hashp), (long) hashp->hctl->keysize, + hashp->hctl->max_bucket, hashp->hctl->nsegs); + fprintf(stderr, "%s: total accesses %ld total collisions %ld\n", + where, hash_accesses, hash_collisions); + fprintf(stderr, "hash_stats: total expansions %ld\n", + hash_expansions); +#endif +} + +/*******************************SEARCH ROUTINES *****************************/ + + +/* + * get_hash_value -- exported routine to calculate a key's hash value + * + * We export this because for partitioned tables, callers need to compute + * the partition number (from the low-order bits of the hash value) before + * searching. + */ +uint32 +get_hash_value(HTAB *hashp, const void *keyPtr) +{ + return hashp->hash(keyPtr, hashp->keysize); +} + +/* Convert a hash value to a bucket number */ +static inline uint32 +calc_bucket(HASHHDR *hctl, uint32 hash_val) +{ + uint32 bucket; + + bucket = hash_val & hctl->high_mask; + if (bucket > hctl->max_bucket) + bucket = bucket & hctl->low_mask; + + return bucket; +} + +/* + * hash_search -- look up key in table and perform action + * hash_search_with_hash_value -- same, with key's hash value already computed + * + * action is one of: + * HASH_FIND: look up key in table + * HASH_ENTER: look up key in table, creating entry if not present + * HASH_ENTER_NULL: same, but return NULL if out of memory + * HASH_REMOVE: look up key in table, remove entry if present + * + * Return value is a pointer to the element found/entered/removed if any, + * or NULL if no match was found. (NB: in the case of the REMOVE action, + * the result is a dangling pointer that shouldn't be dereferenced!) + * + * HASH_ENTER will normally ereport a generic "out of memory" error if + * it is unable to create a new entry. The HASH_ENTER_NULL operation is + * the same except it will return NULL if out of memory. + * + * If foundPtr isn't NULL, then *foundPtr is set true if we found an + * existing entry in the table, false otherwise. This is needed in the + * HASH_ENTER case, but is redundant with the return value otherwise. + * + * For hash_search_with_hash_value, the hashvalue parameter must have been + * calculated with get_hash_value(). + */ +void * +hash_search(HTAB *hashp, + const void *keyPtr, + HASHACTION action, + bool *foundPtr) +{ + return hash_search_with_hash_value(hashp, + keyPtr, + hashp->hash(keyPtr, hashp->keysize), + action, + foundPtr); +} + +void * +hash_search_with_hash_value(HTAB *hashp, + const void *keyPtr, + uint32 hashvalue, + HASHACTION action, + bool *foundPtr) +{ + HASHHDR *hctl = hashp->hctl; + int freelist_idx = FREELIST_IDX(hctl, hashvalue); + Size keysize; + uint32 bucket; + long segment_num; + long segment_ndx; + HASHSEGMENT segp; + HASHBUCKET currBucket; + HASHBUCKET *prevBucketPtr; + HashCompareFunc match; + +#ifdef HASH_STATISTICS + hash_accesses++; + hctl->accesses++; +#endif + + /* + * If inserting, check if it is time to split a bucket. + * + * NOTE: failure to expand table is not a fatal error, it just means we + * have to run at higher fill factor than we wanted. However, if we're + * using the palloc allocator then it will throw error anyway on + * out-of-memory, so we must do this before modifying the table. + */ + if (action == HASH_ENTER || action == HASH_ENTER_NULL) + { + /* + * Can't split if running in partitioned mode, nor if frozen, nor if + * table is the subject of any active hash_seq_search scans. + */ + if (hctl->freeList[0].nentries > (long) hctl->max_bucket && + !IS_PARTITIONED(hctl) && !hashp->frozen && + !has_seq_scans(hashp)) + (void) expand_table(hashp); + } + + /* + * Do the initial lookup + */ + bucket = calc_bucket(hctl, hashvalue); + + segment_num = bucket >> hashp->sshift; + segment_ndx = MOD(bucket, hashp->ssize); + + segp = hashp->dir[segment_num]; + + if (segp == NULL) + hash_corrupted(hashp); + + prevBucketPtr = &segp[segment_ndx]; + currBucket = *prevBucketPtr; + + /* + * Follow collision chain looking for matching key + */ + match = hashp->match; /* save one fetch in inner loop */ + keysize = hashp->keysize; /* ditto */ + + while (currBucket != NULL) + { + if (currBucket->hashvalue == hashvalue && + match(ELEMENTKEY(currBucket), keyPtr, keysize) == 0) + break; + prevBucketPtr = &(currBucket->link); + currBucket = *prevBucketPtr; +#ifdef HASH_STATISTICS + hash_collisions++; + hctl->collisions++; +#endif + } + + if (foundPtr) + *foundPtr = (bool) (currBucket != NULL); + + /* + * OK, now what? + */ + switch (action) + { + case HASH_FIND: + if (currBucket != NULL) + return (void *) ELEMENTKEY(currBucket); + return NULL; + + case HASH_REMOVE: + if (currBucket != NULL) + { + /* if partitioned, must lock to touch nentries and freeList */ + if (IS_PARTITIONED(hctl)) + SpinLockAcquire(&(hctl->freeList[freelist_idx].mutex)); + + /* delete the record from the appropriate nentries counter. */ + Assert(hctl->freeList[freelist_idx].nentries > 0); + hctl->freeList[freelist_idx].nentries--; + + /* remove record from hash bucket's chain. */ + *prevBucketPtr = currBucket->link; + + /* add the record to the appropriate freelist. */ + currBucket->link = hctl->freeList[freelist_idx].freeList; + hctl->freeList[freelist_idx].freeList = currBucket; + + if (IS_PARTITIONED(hctl)) + SpinLockRelease(&hctl->freeList[freelist_idx].mutex); + + /* + * better hope the caller is synchronizing access to this + * element, because someone else is going to reuse it the next + * time something is added to the table + */ + return (void *) ELEMENTKEY(currBucket); + } + return NULL; + + case HASH_ENTER: + case HASH_ENTER_NULL: + /* Return existing element if found, else create one */ + if (currBucket != NULL) + return (void *) ELEMENTKEY(currBucket); + + /* disallow inserts if frozen */ + if (hashp->frozen) + elog(ERROR, "cannot insert into frozen hashtable \"%s\"", + hashp->tabname); + + currBucket = get_hash_entry(hashp, freelist_idx); + if (currBucket == NULL) + { + /* out of memory */ + if (action == HASH_ENTER_NULL) + return NULL; + /* report a generic message */ + if (hashp->isshared) + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of shared memory"))); + else + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"))); + } + + /* link into hashbucket chain */ + *prevBucketPtr = currBucket; + currBucket->link = NULL; + + /* copy key into record */ + currBucket->hashvalue = hashvalue; + hashp->keycopy(ELEMENTKEY(currBucket), keyPtr, keysize); + + /* + * Caller is expected to fill the data field on return. DO NOT + * insert any code that could possibly throw error here, as doing + * so would leave the table entry incomplete and hence corrupt the + * caller's data structure. + */ + + return (void *) ELEMENTKEY(currBucket); + } + + elog(ERROR, "unrecognized hash action code: %d", (int) action); + + return NULL; /* keep compiler quiet */ +} + +/* + * hash_update_hash_key -- change the hash key of an existing table entry + * + * This is equivalent to removing the entry, making a new entry, and copying + * over its data, except that the entry never goes to the table's freelist. + * Therefore this cannot suffer an out-of-memory failure, even if there are + * other processes operating in other partitions of the hashtable. + * + * Returns true if successful, false if the requested new hash key is already + * present. Throws error if the specified entry pointer isn't actually a + * table member. + * + * NB: currently, there is no special case for old and new hash keys being + * identical, which means we'll report false for that situation. This is + * preferable for existing uses. + * + * NB: for a partitioned hashtable, caller must hold lock on both relevant + * partitions, if the new hash key would belong to a different partition. + */ +bool +hash_update_hash_key(HTAB *hashp, + void *existingEntry, + const void *newKeyPtr) +{ + HASHELEMENT *existingElement = ELEMENT_FROM_KEY(existingEntry); + HASHHDR *hctl = hashp->hctl; + uint32 newhashvalue; + Size keysize; + uint32 bucket; + uint32 newbucket; + long segment_num; + long segment_ndx; + HASHSEGMENT segp; + HASHBUCKET currBucket; + HASHBUCKET *prevBucketPtr; + HASHBUCKET *oldPrevPtr; + HashCompareFunc match; + +#ifdef HASH_STATISTICS + hash_accesses++; + hctl->accesses++; +#endif + + /* disallow updates if frozen */ + if (hashp->frozen) + elog(ERROR, "cannot update in frozen hashtable \"%s\"", + hashp->tabname); + + /* + * Lookup the existing element using its saved hash value. We need to do + * this to be able to unlink it from its hash chain, but as a side benefit + * we can verify the validity of the passed existingEntry pointer. + */ + bucket = calc_bucket(hctl, existingElement->hashvalue); + + segment_num = bucket >> hashp->sshift; + segment_ndx = MOD(bucket, hashp->ssize); + + segp = hashp->dir[segment_num]; + + if (segp == NULL) + hash_corrupted(hashp); + + prevBucketPtr = &segp[segment_ndx]; + currBucket = *prevBucketPtr; + + while (currBucket != NULL) + { + if (currBucket == existingElement) + break; + prevBucketPtr = &(currBucket->link); + currBucket = *prevBucketPtr; + } + + if (currBucket == NULL) + elog(ERROR, "hash_update_hash_key argument is not in hashtable \"%s\"", + hashp->tabname); + + oldPrevPtr = prevBucketPtr; + + /* + * Now perform the equivalent of a HASH_ENTER operation to locate the hash + * chain we want to put the entry into. + */ + newhashvalue = hashp->hash(newKeyPtr, hashp->keysize); + + newbucket = calc_bucket(hctl, newhashvalue); + + segment_num = newbucket >> hashp->sshift; + segment_ndx = MOD(newbucket, hashp->ssize); + + segp = hashp->dir[segment_num]; + + if (segp == NULL) + hash_corrupted(hashp); + + prevBucketPtr = &segp[segment_ndx]; + currBucket = *prevBucketPtr; + + /* + * Follow collision chain looking for matching key + */ + match = hashp->match; /* save one fetch in inner loop */ + keysize = hashp->keysize; /* ditto */ + + while (currBucket != NULL) + { + if (currBucket->hashvalue == newhashvalue && + match(ELEMENTKEY(currBucket), newKeyPtr, keysize) == 0) + break; + prevBucketPtr = &(currBucket->link); + currBucket = *prevBucketPtr; +#ifdef HASH_STATISTICS + hash_collisions++; + hctl->collisions++; +#endif + } + + if (currBucket != NULL) + return false; /* collision with an existing entry */ + + currBucket = existingElement; + + /* + * If old and new hash values belong to the same bucket, we need not + * change any chain links, and indeed should not since this simplistic + * update will corrupt the list if currBucket is the last element. (We + * cannot fall out earlier, however, since we need to scan the bucket to + * check for duplicate keys.) + */ + if (bucket != newbucket) + { + /* OK to remove record from old hash bucket's chain. */ + *oldPrevPtr = currBucket->link; + + /* link into new hashbucket chain */ + *prevBucketPtr = currBucket; + currBucket->link = NULL; + } + + /* copy new key into record */ + currBucket->hashvalue = newhashvalue; + hashp->keycopy(ELEMENTKEY(currBucket), newKeyPtr, keysize); + + /* rest of record is untouched */ + + return true; +} + +/* + * Allocate a new hashtable entry if possible; return NULL if out of memory. + * (Or, if the underlying space allocator throws error for out-of-memory, + * we won't return at all.) + */ +static HASHBUCKET +get_hash_entry(HTAB *hashp, int freelist_idx) +{ + HASHHDR *hctl = hashp->hctl; + HASHBUCKET newElement; + + for (;;) + { + /* if partitioned, must lock to touch nentries and freeList */ + if (IS_PARTITIONED(hctl)) + SpinLockAcquire(&hctl->freeList[freelist_idx].mutex); + + /* try to get an entry from the freelist */ + newElement = hctl->freeList[freelist_idx].freeList; + + if (newElement != NULL) + break; + + if (IS_PARTITIONED(hctl)) + SpinLockRelease(&hctl->freeList[freelist_idx].mutex); + + /* + * No free elements in this freelist. In a partitioned table, there + * might be entries in other freelists, but to reduce contention we + * prefer to first try to get another chunk of buckets from the main + * shmem allocator. If that fails, though, we *MUST* root through all + * the other freelists before giving up. There are multiple callers + * that assume that they can allocate every element in the initially + * requested table size, or that deleting an element guarantees they + * can insert a new element, even if shared memory is entirely full. + * Failing because the needed element is in a different freelist is + * not acceptable. + */ + if (!element_alloc(hashp, hctl->nelem_alloc, freelist_idx)) + { + int borrow_from_idx; + + if (!IS_PARTITIONED(hctl)) + return NULL; /* out of memory */ + + /* try to borrow element from another freelist */ + borrow_from_idx = freelist_idx; + for (;;) + { + borrow_from_idx = (borrow_from_idx + 1) % NUM_FREELISTS; + if (borrow_from_idx == freelist_idx) + break; /* examined all freelists, fail */ + + SpinLockAcquire(&(hctl->freeList[borrow_from_idx].mutex)); + newElement = hctl->freeList[borrow_from_idx].freeList; + + if (newElement != NULL) + { + hctl->freeList[borrow_from_idx].freeList = newElement->link; + SpinLockRelease(&(hctl->freeList[borrow_from_idx].mutex)); + + /* careful: count the new element in its proper freelist */ + SpinLockAcquire(&hctl->freeList[freelist_idx].mutex); + hctl->freeList[freelist_idx].nentries++; + SpinLockRelease(&hctl->freeList[freelist_idx].mutex); + + return newElement; + } + + SpinLockRelease(&(hctl->freeList[borrow_from_idx].mutex)); + } + + /* no elements available to borrow either, so out of memory */ + return NULL; + } + } + + /* remove entry from freelist, bump nentries */ + hctl->freeList[freelist_idx].freeList = newElement->link; + hctl->freeList[freelist_idx].nentries++; + + if (IS_PARTITIONED(hctl)) + SpinLockRelease(&hctl->freeList[freelist_idx].mutex); + + return newElement; +} + +/* + * hash_get_num_entries -- get the number of entries in a hashtable + */ +long +hash_get_num_entries(HTAB *hashp) +{ + int i; + long sum = hashp->hctl->freeList[0].nentries; + + /* + * We currently don't bother with acquiring the mutexes; it's only + * sensible to call this function if you've got lock on all partitions of + * the table. + */ + if (IS_PARTITIONED(hashp->hctl)) + { + for (i = 1; i < NUM_FREELISTS; i++) + sum += hashp->hctl->freeList[i].nentries; + } + + return sum; +} + +/* + * hash_seq_init/_search/_term + * Sequentially search through hash table and return + * all the elements one by one, return NULL when no more. + * + * hash_seq_term should be called if and only if the scan is abandoned before + * completion; if hash_seq_search returns NULL then it has already done the + * end-of-scan cleanup. + * + * NOTE: caller may delete the returned element before continuing the scan. + * However, deleting any other element while the scan is in progress is + * UNDEFINED (it might be the one that curIndex is pointing at!). Also, + * if elements are added to the table while the scan is in progress, it is + * unspecified whether they will be visited by the scan or not. + * + * NOTE: it is possible to use hash_seq_init/hash_seq_search without any + * worry about hash_seq_term cleanup, if the hashtable is first locked against + * further insertions by calling hash_freeze. + * + * NOTE: to use this with a partitioned hashtable, caller had better hold + * at least shared lock on all partitions of the table throughout the scan! + * We can cope with insertions or deletions by our own backend, but *not* + * with concurrent insertions or deletions by another. + */ +void +hash_seq_init(HASH_SEQ_STATUS *status, HTAB *hashp) +{ + status->hashp = hashp; + status->curBucket = 0; + status->curEntry = NULL; + if (!hashp->frozen) + register_seq_scan(hashp); +} + +void * +hash_seq_search(HASH_SEQ_STATUS *status) +{ + HTAB *hashp; + HASHHDR *hctl; + uint32 max_bucket; + long ssize; + long segment_num; + long segment_ndx; + HASHSEGMENT segp; + uint32 curBucket; + HASHELEMENT *curElem; + + if ((curElem = status->curEntry) != NULL) + { + /* Continuing scan of curBucket... */ + status->curEntry = curElem->link; + if (status->curEntry == NULL) /* end of this bucket */ + ++status->curBucket; + return (void *) ELEMENTKEY(curElem); + } + + /* + * Search for next nonempty bucket starting at curBucket. + */ + curBucket = status->curBucket; + hashp = status->hashp; + hctl = hashp->hctl; + ssize = hashp->ssize; + max_bucket = hctl->max_bucket; + + if (curBucket > max_bucket) + { + hash_seq_term(status); + return NULL; /* search is done */ + } + + /* + * first find the right segment in the table directory. + */ + segment_num = curBucket >> hashp->sshift; + segment_ndx = MOD(curBucket, ssize); + + segp = hashp->dir[segment_num]; + + /* + * Pick up the first item in this bucket's chain. If chain is not empty + * we can begin searching it. Otherwise we have to advance to find the + * next nonempty bucket. We try to optimize that case since searching a + * near-empty hashtable has to iterate this loop a lot. + */ + while ((curElem = segp[segment_ndx]) == NULL) + { + /* empty bucket, advance to next */ + if (++curBucket > max_bucket) + { + status->curBucket = curBucket; + hash_seq_term(status); + return NULL; /* search is done */ + } + if (++segment_ndx >= ssize) + { + segment_num++; + segment_ndx = 0; + segp = hashp->dir[segment_num]; + } + } + + /* Begin scan of curBucket... */ + status->curEntry = curElem->link; + if (status->curEntry == NULL) /* end of this bucket */ + ++curBucket; + status->curBucket = curBucket; + return (void *) ELEMENTKEY(curElem); +} + +void +hash_seq_term(HASH_SEQ_STATUS *status) +{ + if (!status->hashp->frozen) + deregister_seq_scan(status->hashp); +} + +/* + * hash_freeze + * Freeze a hashtable against future insertions (deletions are + * still allowed) + * + * The reason for doing this is that by preventing any more bucket splits, + * we no longer need to worry about registering hash_seq_search scans, + * and thus caller need not be careful about ensuring hash_seq_term gets + * called at the right times. + * + * Multiple calls to hash_freeze() are allowed, but you can't freeze a table + * with active scans (since hash_seq_term would then do the wrong thing). + */ +void +hash_freeze(HTAB *hashp) +{ + if (hashp->isshared) + elog(ERROR, "cannot freeze shared hashtable \"%s\"", hashp->tabname); + if (!hashp->frozen && has_seq_scans(hashp)) + elog(ERROR, "cannot freeze hashtable \"%s\" because it has active scans", + hashp->tabname); + hashp->frozen = true; +} + + +/********************************* UTILITIES ************************/ + +/* + * Expand the table by adding one more hash bucket. + */ +static bool +expand_table(HTAB *hashp) +{ + HASHHDR *hctl = hashp->hctl; + HASHSEGMENT old_seg, + new_seg; + long old_bucket, + new_bucket; + long new_segnum, + new_segndx; + long old_segnum, + old_segndx; + HASHBUCKET *oldlink, + *newlink; + HASHBUCKET currElement, + nextElement; + + Assert(!IS_PARTITIONED(hctl)); + +#ifdef HASH_STATISTICS + hash_expansions++; +#endif + + new_bucket = hctl->max_bucket + 1; + new_segnum = new_bucket >> hashp->sshift; + new_segndx = MOD(new_bucket, hashp->ssize); + + if (new_segnum >= hctl->nsegs) + { + /* Allocate new segment if necessary -- could fail if dir full */ + if (new_segnum >= hctl->dsize) + if (!dir_realloc(hashp)) + return false; + if (!(hashp->dir[new_segnum] = seg_alloc(hashp))) + return false; + hctl->nsegs++; + } + + /* OK, we created a new bucket */ + hctl->max_bucket++; + + /* + * *Before* changing masks, find old bucket corresponding to same hash + * values; values in that bucket may need to be relocated to new bucket. + * Note that new_bucket is certainly larger than low_mask at this point, + * so we can skip the first step of the regular hash mask calc. + */ + old_bucket = (new_bucket & hctl->low_mask); + + /* + * If we crossed a power of 2, readjust masks. + */ + if ((uint32) new_bucket > hctl->high_mask) + { + hctl->low_mask = hctl->high_mask; + hctl->high_mask = (uint32) new_bucket | hctl->low_mask; + } + + /* + * Relocate records to the new bucket. NOTE: because of the way the hash + * masking is done in calc_bucket, only one old bucket can need to be + * split at this point. With a different way of reducing the hash value, + * that might not be true! + */ + old_segnum = old_bucket >> hashp->sshift; + old_segndx = MOD(old_bucket, hashp->ssize); + + old_seg = hashp->dir[old_segnum]; + new_seg = hashp->dir[new_segnum]; + + oldlink = &old_seg[old_segndx]; + newlink = &new_seg[new_segndx]; + + for (currElement = *oldlink; + currElement != NULL; + currElement = nextElement) + { + nextElement = currElement->link; + if ((long) calc_bucket(hctl, currElement->hashvalue) == old_bucket) + { + *oldlink = currElement; + oldlink = &currElement->link; + } + else + { + *newlink = currElement; + newlink = &currElement->link; + } + } + /* don't forget to terminate the rebuilt hash chains... */ + *oldlink = NULL; + *newlink = NULL; + + return true; +} + + +static bool +dir_realloc(HTAB *hashp) +{ + HASHSEGMENT *p; + HASHSEGMENT *old_p; + long new_dsize; + long old_dirsize; + long new_dirsize; + + if (hashp->hctl->max_dsize != NO_MAX_DSIZE) + return false; + + /* Reallocate directory */ + new_dsize = hashp->hctl->dsize << 1; + old_dirsize = hashp->hctl->dsize * sizeof(HASHSEGMENT); + new_dirsize = new_dsize * sizeof(HASHSEGMENT); + + old_p = hashp->dir; + CurrentDynaHashCxt = hashp->hcxt; + p = (HASHSEGMENT *) hashp->alloc((Size) new_dirsize); + + if (p != NULL) + { + memcpy(p, old_p, old_dirsize); + MemSet(((char *) p) + old_dirsize, 0, new_dirsize - old_dirsize); + hashp->dir = p; + hashp->hctl->dsize = new_dsize; + + /* XXX assume the allocator is palloc, so we know how to free */ + Assert(hashp->alloc == DynaHashAlloc); + pfree(old_p); + + return true; + } + + return false; +} + + +static HASHSEGMENT +seg_alloc(HTAB *hashp) +{ + HASHSEGMENT segp; + + CurrentDynaHashCxt = hashp->hcxt; + segp = (HASHSEGMENT) hashp->alloc(sizeof(HASHBUCKET) * hashp->ssize); + + if (!segp) + return NULL; + + MemSet(segp, 0, sizeof(HASHBUCKET) * hashp->ssize); + + return segp; +} + +/* + * allocate some new elements and link them into the indicated free list + */ +static bool +element_alloc(HTAB *hashp, int nelem, int freelist_idx) +{ + HASHHDR *hctl = hashp->hctl; + Size elementSize; + HASHELEMENT *firstElement; + HASHELEMENT *tmpElement; + HASHELEMENT *prevElement; + int i; + + if (hashp->isfixed) + return false; + + /* Each element has a HASHELEMENT header plus user data. */ + elementSize = MAXALIGN(sizeof(HASHELEMENT)) + MAXALIGN(hctl->entrysize); + + CurrentDynaHashCxt = hashp->hcxt; + firstElement = (HASHELEMENT *) hashp->alloc(nelem * elementSize); + + if (!firstElement) + return false; + + /* prepare to link all the new entries into the freelist */ + prevElement = NULL; + tmpElement = firstElement; + for (i = 0; i < nelem; i++) + { + tmpElement->link = prevElement; + prevElement = tmpElement; + tmpElement = (HASHELEMENT *) (((char *) tmpElement) + elementSize); + } + + /* if partitioned, must lock to touch freeList */ + if (IS_PARTITIONED(hctl)) + SpinLockAcquire(&hctl->freeList[freelist_idx].mutex); + + /* freelist could be nonempty if two backends did this concurrently */ + firstElement->link = hctl->freeList[freelist_idx].freeList; + hctl->freeList[freelist_idx].freeList = prevElement; + + if (IS_PARTITIONED(hctl)) + SpinLockRelease(&hctl->freeList[freelist_idx].mutex); + + return true; +} + +/* complain when we have detected a corrupted hashtable */ +static void +hash_corrupted(HTAB *hashp) +{ + /* + * If the corruption is in a shared hashtable, we'd better force a + * systemwide restart. Otherwise, just shut down this one backend. + */ + if (hashp->isshared) + elog(PANIC, "hash table \"%s\" corrupted", hashp->tabname); + else + elog(FATAL, "hash table \"%s\" corrupted", hashp->tabname); +} + +/* calculate ceil(log base 2) of num */ +int +my_log2(long num) +{ + /* + * guard against too-large input, which would be invalid for + * pg_ceil_log2_*() + */ + if (num > LONG_MAX / 2) + num = LONG_MAX / 2; + +#if SIZEOF_LONG < 8 + return pg_ceil_log2_32(num); +#else + return pg_ceil_log2_64(num); +#endif +} + +/* calculate first power of 2 >= num, bounded to what will fit in a long */ +static long +next_pow2_long(long num) +{ + /* my_log2's internal range check is sufficient */ + return 1L << my_log2(num); +} + +/* calculate first power of 2 >= num, bounded to what will fit in an int */ +static int +next_pow2_int(long num) +{ + if (num > INT_MAX / 2) + num = INT_MAX / 2; + return 1 << my_log2(num); +} + + +/************************* SEQ SCAN TRACKING ************************/ + +/* + * We track active hash_seq_search scans here. The need for this mechanism + * comes from the fact that a scan will get confused if a bucket split occurs + * while it's in progress: it might visit entries twice, or even miss some + * entirely (if it's partway through the same bucket that splits). Hence + * we want to inhibit bucket splits if there are any active scans on the + * table being inserted into. This is a fairly rare case in current usage, + * so just postponing the split until the next insertion seems sufficient. + * + * Given present usages of the function, only a few scans are likely to be + * open concurrently; so a finite-size stack of open scans seems sufficient, + * and we don't worry that linear search is too slow. Note that we do + * allow multiple scans of the same hashtable to be open concurrently. + * + * This mechanism can support concurrent scan and insertion in a shared + * hashtable if it's the same backend doing both. It would fail otherwise, + * but locking reasons seem to preclude any such scenario anyway, so we don't + * worry. + * + * This arrangement is reasonably robust if a transient hashtable is deleted + * without notifying us. The absolute worst case is we might inhibit splits + * in another table created later at exactly the same address. We will give + * a warning at transaction end for reference leaks, so any bugs leading to + * lack of notification should be easy to catch. + */ + +#define MAX_SEQ_SCANS 100 + +static __thread HTAB *seq_scan_tables[MAX_SEQ_SCANS]; /* tables being scanned */ +static __thread int seq_scan_level[MAX_SEQ_SCANS]; /* subtransaction nest level */ +static __thread int num_seq_scans = 0; + + +/* Register a table as having an active hash_seq_search scan */ +static void +register_seq_scan(HTAB *hashp) +{ + if (num_seq_scans >= MAX_SEQ_SCANS) + elog(ERROR, "too many active hash_seq_search scans, cannot start one on \"%s\"", + hashp->tabname); + seq_scan_tables[num_seq_scans] = hashp; + seq_scan_level[num_seq_scans] = GetCurrentTransactionNestLevel(); + num_seq_scans++; +} + +/* Deregister an active scan */ +static void +deregister_seq_scan(HTAB *hashp) +{ + int i; + + /* Search backward since it's most likely at the stack top */ + for (i = num_seq_scans - 1; i >= 0; i--) + { + if (seq_scan_tables[i] == hashp) + { + seq_scan_tables[i] = seq_scan_tables[num_seq_scans - 1]; + seq_scan_level[i] = seq_scan_level[num_seq_scans - 1]; + num_seq_scans--; + return; + } + } + elog(ERROR, "no hash_seq_search scan for hash table \"%s\"", + hashp->tabname); +} + +/* Check if a table has any active scan */ +static bool +has_seq_scans(HTAB *hashp) +{ + int i; + + for (i = 0; i < num_seq_scans; i++) + { + if (seq_scan_tables[i] == hashp) + return true; + } + return false; +} + +/* Clean up any open scans at end of transaction */ +void +AtEOXact_HashTables(bool isCommit) +{ + /* + * During abort cleanup, open scans are expected; just silently clean 'em + * out. An open scan at commit means someone forgot a hash_seq_term() + * call, so complain. + * + * Note: it's tempting to try to print the tabname here, but refrain for + * fear of touching deallocated memory. This isn't a user-facing message + * anyway, so it needn't be pretty. + */ + if (isCommit) + { + int i; + + for (i = 0; i < num_seq_scans; i++) + { + elog(WARNING, "leaked hash_seq_search scan for hash table %p", + seq_scan_tables[i]); + } + } + num_seq_scans = 0; +} + +/* Clean up any open scans at end of subtransaction */ +void +AtEOSubXact_HashTables(bool isCommit, int nestDepth) +{ + int i; + + /* + * Search backward to make cleanup easy. Note we must check all entries, + * not only those at the end of the array, because deletion technique + * doesn't keep them in order. + */ + for (i = num_seq_scans - 1; i >= 0; i--) + { + if (seq_scan_level[i] >= nestDepth) + { + if (isCommit) + elog(WARNING, "leaked hash_seq_search scan for hash table %p", + seq_scan_tables[i]); + seq_scan_tables[i] = seq_scan_tables[num_seq_scans - 1]; + seq_scan_level[i] = seq_scan_level[num_seq_scans - 1]; + num_seq_scans--; + } + } +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/hash/pg_crc.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/hash/pg_crc.c new file mode 100644 index 00000000000..123ff62c476 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/hash/pg_crc.c @@ -0,0 +1,97 @@ +/*------------------------------------------------------------------------- + * + * pg_crc.c + * PostgreSQL CRC support + * + * See Ross Williams' excellent introduction + * A PAINLESS GUIDE TO CRC ERROR DETECTION ALGORITHMS, available from + * http://ross.net/crc/download/crc_v3.txt or several other net sites. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/hash/pg_crc.c + * + *------------------------------------------------------------------------- + */ + +#include "c.h" + +#include "utils/pg_crc.h" + +/* + * Lookup table for calculating CRC-32 using Sarwate's algorithm. + * + * This table is based on the polynomial + * x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x+1. + * (This is the same polynomial used in Ethernet checksums, for instance.) + * Using Williams' terms, this is the "normal", not "reflected" version. + */ +const uint32 pg_crc32_table[256] = { + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, + 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, + 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, + 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, + 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, + 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, + 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, + 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, + 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, + 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, + 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, + 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, + 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, + 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, + 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, + 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, + 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, + 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, + 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, + 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, + 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, + 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, + 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, + 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, + 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, + 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, + 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, + 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, + 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, + 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, + 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, + 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, + 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, + 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, + 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, + 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, + 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, + 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, + 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, + 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, + 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, + 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, + 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, + 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, + 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, + 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, + 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, + 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, + 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, + 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, + 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, + 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, + 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, + 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, + 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, + 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, + 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, + 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, + 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, + 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, + 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, + 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, + 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, + 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D +}; diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/init/globals.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/init/globals.c new file mode 100644 index 00000000000..bbc8ab1c24a --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/init/globals.c @@ -0,0 +1,157 @@ +/*------------------------------------------------------------------------- + * + * globals.c + * global variable declarations + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/init/globals.c + * + * NOTES + * Globals used all over the place should be declared here and not + * in other modules. + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "common/file_perm.h" +#include "libpq/libpq-be.h" +#include "libpq/pqcomm.h" +#include "miscadmin.h" +#include "storage/backendid.h" + + +__thread ProtocolVersion FrontendProtocol; + +__thread volatile sig_atomic_t InterruptPending = false; +volatile sig_atomic_t* ImplPtrInterruptPending() { return &InterruptPending; } +__thread volatile sig_atomic_t QueryCancelPending = false; +__thread volatile sig_atomic_t ProcDiePending = false; +__thread volatile sig_atomic_t CheckClientConnectionPending = false; +__thread volatile sig_atomic_t ClientConnectionLost = false; +__thread volatile sig_atomic_t IdleInTransactionSessionTimeoutPending = false; +__thread volatile sig_atomic_t IdleSessionTimeoutPending = false; +__thread volatile sig_atomic_t ProcSignalBarrierPending = false; +__thread volatile sig_atomic_t LogMemoryContextPending = false; +__thread volatile sig_atomic_t IdleStatsUpdateTimeoutPending = false; +__thread volatile uint32 InterruptHoldoffCount = 0; +__thread volatile uint32 QueryCancelHoldoffCount = 0; +__thread volatile uint32 CritSectionCount = 0; + +__thread int MyProcPid; +__thread pg_time_t MyStartTime; +__thread TimestampTz MyStartTimestamp; +__thread struct Port *MyProcPort; +__thread int32 MyCancelKey; +__thread int MyPMChildSlot; + +/* + * MyLatch points to the latch that should be used for signal handling by the + * current process. It will either point to a process local latch if the + * current process does not have a PGPROC entry in that moment, or to + * PGPROC->procLatch if it has. Thus it can always be used in signal handlers, + * without checking for its existence. + */ +__thread struct Latch *MyLatch; + +/* + * DataDir is the absolute path to the top level of the PGDATA directory tree. + * Except during early startup, this is also the server's working directory; + * most code therefore can simply use relative paths and not reference DataDir + * explicitly. + */ +__thread char *DataDir = NULL; + +/* + * Mode of the data directory. The default is 0700 but it may be changed in + * checkDataDir() to 0750 if the data directory actually has that mode. + */ +__thread int data_directory_mode = PG_DIR_MODE_OWNER; + +__thread char OutputFileName[MAXPGPATH]; /* debugging output file */ + +__thread char my_exec_path[MAXPGPATH]; /* full path to my executable */ +__thread char pkglib_path[MAXPGPATH]; /* full path to lib directory */ + +#ifdef EXEC_BACKEND +char postgres_exec_path[MAXPGPATH]; /* full path to backend */ + +/* note: currently this is not valid in backend processes */ +#endif + +__thread BackendId MyBackendId = InvalidBackendId; + +__thread BackendId ParallelLeaderBackendId = InvalidBackendId; + +__thread Oid MyDatabaseId = InvalidOid; + +__thread Oid MyDatabaseTableSpace = InvalidOid; + +/* + * DatabasePath is the path (relative to DataDir) of my database's + * primary directory, ie, its directory in the default tablespace. + */ +__thread char *DatabasePath = NULL; + +__thread pid_t PostmasterPid = 0; + +/* + * IsPostmasterEnvironment is true in a postmaster process and any postmaster + * child process; it is false in a standalone process (bootstrap or + * standalone backend). IsUnderPostmaster is true in postmaster child + * processes. Note that "child process" includes all children, not only + * regular backends. These should be set correctly as early as possible + * in the execution of a process, so that error handling will do the right + * things if an error should occur during process initialization. + * + * These are initialized for the bootstrap/standalone case. + */ +__thread bool IsPostmasterEnvironment = false; +__thread bool IsUnderPostmaster = false; +__thread bool IsBinaryUpgrade = false; +__thread bool IsBackgroundWorker = false; + +__thread bool ExitOnAnyError = false; + +__thread int DateStyle = USE_ISO_DATES; +__thread int DateOrder = DATEORDER_MDY; +__thread int IntervalStyle = INTSTYLE_POSTGRES; + +__thread bool enableFsync = true; +__thread bool allowSystemTableMods = false; +__thread int work_mem = 4096; +__thread double hash_mem_multiplier = 2.0; +__thread int maintenance_work_mem = 65536; +__thread int max_parallel_maintenance_workers = 2; + +/* + * Primary determinants of sizes of shared-memory structures. + * + * MaxBackends is computed by PostmasterMain after modules have had a chance to + * register background workers. + */ +__thread int NBuffers = 16384; +__thread int MaxConnections = 100; +__thread int max_worker_processes = 8; +__thread int max_parallel_workers = 8; +__thread int MaxBackends = 0; + +/* GUC parameters for vacuum */ +__thread int VacuumBufferUsageLimit = 256; + +__thread int VacuumCostPageHit = 1; +__thread int VacuumCostPageMiss = 2; +__thread int VacuumCostPageDirty = 20; +__thread int VacuumCostLimit = 200; +__thread double VacuumCostDelay = 0; + +__thread int64 VacuumPageHit = 0; +__thread int64 VacuumPageMiss = 0; +__thread int64 VacuumPageDirty = 0; + +__thread int VacuumCostBalance = 0; /* working state for vacuum */ +__thread bool VacuumCostActive = false; diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/init/miscinit.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/init/miscinit.c new file mode 100644 index 00000000000..9c8ff28c20b --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/init/miscinit.c @@ -0,0 +1,1905 @@ +/*------------------------------------------------------------------------- + * + * miscinit.c + * miscellaneous initialization support stuff + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/init/miscinit.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <sys/param.h> +#include <signal.h> +#include <time.h> +#include <sys/file.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <fcntl.h> +#include <unistd.h> +#include <grp.h> +#include <pwd.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <utime.h> + +#include "access/htup_details.h" +#include "catalog/pg_authid.h" +#include "common/file_perm.h" +#include "libpq/libpq.h" +#include "libpq/pqsignal.h" +#include "mb/pg_wchar.h" +#include "miscadmin.h" +#include "pgstat.h" +#include "postmaster/autovacuum.h" +#include "postmaster/interrupt.h" +#include "postmaster/pgarch.h" +#include "postmaster/postmaster.h" +#include "storage/fd.h" +#include "storage/ipc.h" +#include "storage/latch.h" +#include "storage/pg_shmem.h" +#include "storage/pmsignal.h" +#include "storage/proc.h" +#include "storage/procarray.h" +#include "utils/builtins.h" +#include "utils/guc.h" +#include "utils/inval.h" +#include "utils/memutils.h" +#include "utils/pidfile.h" +#include "utils/syscache.h" +#include "utils/varlena.h" + + +#define DIRECTORY_LOCK_FILE "postmaster.pid" + +__thread ProcessingMode Mode = InitProcessing; + +__thread BackendType MyBackendType; + +/* List of lock files to be removed at proc exit */ +static __thread List *lock_files = NIL; + +__thread Latch LocalLatchData; + +/* ---------------------------------------------------------------- + * ignoring system indexes support stuff + * + * NOTE: "ignoring system indexes" means we do not use the system indexes + * for lookups (either in hardwired catalog accesses or in planner-generated + * plans). We do, however, still update the indexes when a catalog + * modification is made. + * ---------------------------------------------------------------- + */ + +__thread bool IgnoreSystemIndexes = false; + + +/* ---------------------------------------------------------------- + * common process startup code + * ---------------------------------------------------------------- + */ + +/* + * Initialize the basic environment for a postmaster child + * + * Should be called as early as possible after the child's startup. However, + * on EXEC_BACKEND builds it does need to be after read_backend_variables(). + */ +void +InitPostmasterChild(void) +{ + IsUnderPostmaster = true; /* we are a postmaster subprocess now */ + + /* + * Start our win32 signal implementation. This has to be done after we + * read the backend variables, because we need to pick up the signal pipe + * from the parent process. + */ +#ifdef WIN32 + pgwin32_signal_initialize(); +#endif + + /* + * Set reference point for stack-depth checking. This might seem + * redundant in !EXEC_BACKEND builds, but it's better to keep the depth + * logic the same with and without that build option. + */ + (void) set_stack_base(); + + InitProcessGlobals(); + + /* + * make sure stderr is in binary mode before anything can possibly be + * written to it, in case it's actually the syslogger pipe, so the pipe + * chunking protocol isn't disturbed. Non-logpipe data gets translated on + * redirection (e.g. via pg_ctl -l) anyway. + */ +#ifdef WIN32 + _setmode(fileno(stderr), _O_BINARY); +#endif + + /* We don't want the postmaster's proc_exit() handlers */ + on_exit_reset(); + + /* In EXEC_BACKEND case we will not have inherited BlockSig etc values */ +#ifdef EXEC_BACKEND + pqinitmask(); +#endif + + /* Initialize process-local latch support */ + InitializeLatchSupport(); + InitProcessLocalLatch(); + InitializeLatchWaitSet(); + + /* + * If possible, make this process a group leader, so that the postmaster + * can signal any child processes too. Not all processes will have + * children, but for consistency we make all postmaster child processes do + * this. + */ +#ifdef HAVE_SETSID + if (setsid() < 0) + elog(FATAL, "setsid() failed: %m"); +#endif + + /* + * Every postmaster child process is expected to respond promptly to + * SIGQUIT at all times. Therefore we centrally remove SIGQUIT from + * BlockSig and install a suitable signal handler. (Client-facing + * processes may choose to replace this default choice of handler with + * quickdie().) All other blockable signals remain blocked for now. + */ + pqsignal(SIGQUIT, SignalHandlerForCrashExit); + + sigdelset(&BlockSig, SIGQUIT); + sigprocmask(SIG_SETMASK, &BlockSig, NULL); + + /* Request a signal if the postmaster dies, if possible. */ + PostmasterDeathSignalInit(); + + /* Don't give the pipe to subprograms that we execute. */ +#ifndef WIN32 + if (fcntl(postmaster_alive_fds[POSTMASTER_FD_WATCH], F_SETFD, FD_CLOEXEC) < 0) + ereport(FATAL, + (errcode_for_socket_access(), + errmsg_internal("could not set postmaster death monitoring pipe to FD_CLOEXEC mode: %m"))); +#endif +} + +/* + * Initialize the basic environment for a standalone process. + * + * argv0 has to be suitable to find the program's executable. + */ +void +InitStandaloneProcess(const char *argv0) +{ + Assert(!IsPostmasterEnvironment); + + MyBackendType = B_STANDALONE_BACKEND; + + /* + * Start our win32 signal implementation + */ +#ifdef WIN32 + pgwin32_signal_initialize(); +#endif + + InitProcessGlobals(); + + /* Initialize process-local latch support */ + InitializeLatchSupport(); + InitProcessLocalLatch(); + InitializeLatchWaitSet(); + + /* + * For consistency with InitPostmasterChild, initialize signal mask here. + * But we don't unblock SIGQUIT or provide a default handler for it. + */ + pqinitmask(); + sigprocmask(SIG_SETMASK, &BlockSig, NULL); + + /* Compute paths, no postmaster to inherit from */ + if (my_exec_path[0] == '\0') + { + if (find_my_exec(argv0, my_exec_path) < 0) + elog(FATAL, "%s: could not locate my own executable path", + argv0); + } + + if (pkglib_path[0] == '\0') + get_pkglib_path(my_exec_path, pkglib_path); +} + +void +SwitchToSharedLatch(void) +{ + Assert(MyLatch == &LocalLatchData); + Assert(MyProc != NULL); + + MyLatch = &MyProc->procLatch; + + if (FeBeWaitSet) + ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetLatchPos, WL_LATCH_SET, + MyLatch); + + /* + * Set the shared latch as the local one might have been set. This + * shouldn't normally be necessary as code is supposed to check the + * condition before waiting for the latch, but a bit care can't hurt. + */ + SetLatch(MyLatch); +} + +void +InitProcessLocalLatch(void) +{ + MyLatch = &LocalLatchData; + InitLatch(MyLatch); +} + +void +SwitchBackToLocalLatch(void) +{ + Assert(MyLatch != &LocalLatchData); + Assert(MyProc != NULL && MyLatch == &MyProc->procLatch); + + MyLatch = &LocalLatchData; + + if (FeBeWaitSet) + ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetLatchPos, WL_LATCH_SET, + MyLatch); + + SetLatch(MyLatch); +} + +const char * +GetBackendTypeDesc(BackendType backendType) +{ + const char *backendDesc = "unknown process type"; + + switch (backendType) + { + case B_INVALID: + backendDesc = "not initialized"; + break; + case B_ARCHIVER: + backendDesc = "archiver"; + break; + case B_AUTOVAC_LAUNCHER: + backendDesc = "autovacuum launcher"; + break; + case B_AUTOVAC_WORKER: + backendDesc = "autovacuum worker"; + break; + case B_BACKEND: + backendDesc = "client backend"; + break; + case B_BG_WORKER: + backendDesc = "background worker"; + break; + case B_BG_WRITER: + backendDesc = "background writer"; + break; + case B_CHECKPOINTER: + backendDesc = "checkpointer"; + break; + case B_LOGGER: + backendDesc = "logger"; + break; + case B_STANDALONE_BACKEND: + backendDesc = "standalone backend"; + break; + case B_STARTUP: + backendDesc = "startup"; + break; + case B_WAL_RECEIVER: + backendDesc = "walreceiver"; + break; + case B_WAL_SENDER: + backendDesc = "walsender"; + break; + case B_WAL_WRITER: + backendDesc = "walwriter"; + break; + } + + return backendDesc; +} + +/* ---------------------------------------------------------------- + * database path / name support stuff + * ---------------------------------------------------------------- + */ + +void +SetDatabasePath(const char *path) +{ + /* This should happen only once per process */ + Assert(!DatabasePath); + DatabasePath = MemoryContextStrdup(TopMemoryContext, path); +} + +/* + * Validate the proposed data directory. + * + * Also initialize file and directory create modes and mode mask. + */ +void +checkDataDir(void) +{ + struct stat stat_buf; + + Assert(DataDir); + + if (stat(DataDir, &stat_buf) != 0) + { + if (errno == ENOENT) + ereport(FATAL, + (errcode_for_file_access(), + errmsg("data directory \"%s\" does not exist", + DataDir))); + else + ereport(FATAL, + (errcode_for_file_access(), + errmsg("could not read permissions of directory \"%s\": %m", + DataDir))); + } + + /* eventual chdir would fail anyway, but let's test ... */ + if (!S_ISDIR(stat_buf.st_mode)) + ereport(FATAL, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("specified data directory \"%s\" is not a directory", + DataDir))); + + /* + * Check that the directory belongs to my userid; if not, reject. + * + * This check is an essential part of the interlock that prevents two + * postmasters from starting in the same directory (see CreateLockFile()). + * Do not remove or weaken it. + * + * XXX can we safely enable this check on Windows? + */ +#if !defined(WIN32) && !defined(__CYGWIN__) + if (stat_buf.st_uid != geteuid()) + ereport(FATAL, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("data directory \"%s\" has wrong ownership", + DataDir), + errhint("The server must be started by the user that owns the data directory."))); +#endif + + /* + * Check if the directory has correct permissions. If not, reject. + * + * Only two possible modes are allowed, 0700 and 0750. The latter mode + * indicates that group read/execute should be allowed on all newly + * created files and directories. + * + * XXX temporarily suppress check when on Windows, because there may not + * be proper support for Unix-y file permissions. Need to think of a + * reasonable check to apply on Windows. + */ +#if !defined(WIN32) && !defined(__CYGWIN__) + if (stat_buf.st_mode & PG_MODE_MASK_GROUP) + ereport(FATAL, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("data directory \"%s\" has invalid permissions", + DataDir), + errdetail("Permissions should be u=rwx (0700) or u=rwx,g=rx (0750)."))); +#endif + + /* + * Reset creation modes and mask based on the mode of the data directory. + * + * The mask was set earlier in startup to disallow group permissions on + * newly created files and directories. However, if group read/execute + * are present on the data directory then modify the create modes and mask + * to allow group read/execute on newly created files and directories and + * set the data_directory_mode GUC. + * + * Suppress when on Windows, because there may not be proper support for + * Unix-y file permissions. + */ +#if !defined(WIN32) && !defined(__CYGWIN__) + SetDataDirectoryCreatePerm(stat_buf.st_mode); + + umask(pg_mode_mask); + data_directory_mode = pg_dir_create_mode; +#endif + + /* Check for PG_VERSION */ + ValidatePgVersion(DataDir); +} + +/* + * Set data directory, but make sure it's an absolute path. Use this, + * never set DataDir directly. + */ +void +SetDataDir(const char *dir) +{ + char *new; + + Assert(dir); + + /* If presented path is relative, convert to absolute */ + new = make_absolute_path(dir); + + free(DataDir); + DataDir = new; +} + +/* + * Change working directory to DataDir. Most of the postmaster and backend + * code assumes that we are in DataDir so it can use relative paths to access + * stuff in and under the data directory. For convenience during path + * setup, however, we don't force the chdir to occur during SetDataDir. + */ +void +ChangeToDataDir(void) +{ + Assert(DataDir); + + if (chdir(DataDir) < 0) + ereport(FATAL, + (errcode_for_file_access(), + errmsg("could not change directory to \"%s\": %m", + DataDir))); +} + + +/* ---------------------------------------------------------------- + * User ID state + * + * We have to track several different values associated with the concept + * of "user ID". + * + * AuthenticatedUserId is determined at connection start and never changes. + * + * SessionUserId is initially the same as AuthenticatedUserId, but can be + * changed by SET SESSION AUTHORIZATION (if AuthenticatedUserIsSuperuser). + * This is the ID reported by the SESSION_USER SQL function. + * + * OuterUserId is the current user ID in effect at the "outer level" (outside + * any transaction or function). This is initially the same as SessionUserId, + * but can be changed by SET ROLE to any role that SessionUserId is a + * member of. (XXX rename to something like CurrentRoleId?) + * + * CurrentUserId is the current effective user ID; this is the one to use + * for all normal permissions-checking purposes. At outer level this will + * be the same as OuterUserId, but it changes during calls to SECURITY + * DEFINER functions, as well as locally in some specialized commands. + * + * SecurityRestrictionContext holds flags indicating reason(s) for changing + * CurrentUserId. In some cases we need to lock down operations that are + * not directly controlled by privilege settings, and this provides a + * convenient way to do it. + * ---------------------------------------------------------------- + */ +static __thread Oid AuthenticatedUserId = InvalidOid; +static __thread Oid SessionUserId = InvalidOid; +static __thread Oid OuterUserId = InvalidOid; +static __thread Oid CurrentUserId = InvalidOid; +static __thread const char *SystemUser = NULL; + +/* We also have to remember the superuser state of some of these levels */ +static __thread bool AuthenticatedUserIsSuperuser = false; +static __thread bool SessionUserIsSuperuser = false; + +static __thread int SecurityRestrictionContext = 0; + +/* We also remember if a SET ROLE is currently active */ +static __thread bool SetRoleIsActive = false; + +/* + * GetUserId - get the current effective user ID. + * + * Note: there's no SetUserId() anymore; use SetUserIdAndSecContext(). + */ +Oid +GetUserId(void) +{ + Assert(OidIsValid(CurrentUserId)); + return CurrentUserId; +} + + +/* + * GetOuterUserId/SetOuterUserId - get/set the outer-level user ID. + */ +Oid +GetOuterUserId(void) +{ + Assert(OidIsValid(OuterUserId)); + return OuterUserId; +} + + +static void +SetOuterUserId(Oid userid) +{ + Assert(SecurityRestrictionContext == 0); + Assert(OidIsValid(userid)); + OuterUserId = userid; + + /* We force the effective user ID to match, too */ + CurrentUserId = userid; +} + + +/* + * GetSessionUserId/SetSessionUserId - get/set the session user ID. + */ +Oid +GetSessionUserId(void) +{ + Assert(OidIsValid(SessionUserId)); + return SessionUserId; +} + + +static void +SetSessionUserId(Oid userid, bool is_superuser) +{ + Assert(SecurityRestrictionContext == 0); + Assert(OidIsValid(userid)); + SessionUserId = userid; + SessionUserIsSuperuser = is_superuser; + SetRoleIsActive = false; + + /* We force the effective user IDs to match, too */ + OuterUserId = userid; + CurrentUserId = userid; +} + +/* + * Return the system user representing the authenticated identity. + * It is defined in InitializeSystemUser() as auth_method:authn_id. + */ +const char * +GetSystemUser(void) +{ + return SystemUser; +} + +/* + * GetAuthenticatedUserId - get the authenticated user ID + */ +Oid +GetAuthenticatedUserId(void) +{ + Assert(OidIsValid(AuthenticatedUserId)); + return AuthenticatedUserId; +} + + +/* + * GetUserIdAndSecContext/SetUserIdAndSecContext - get/set the current user ID + * and the SecurityRestrictionContext flags. + * + * Currently there are three valid bits in SecurityRestrictionContext: + * + * SECURITY_LOCAL_USERID_CHANGE indicates that we are inside an operation + * that is temporarily changing CurrentUserId via these functions. This is + * needed to indicate that the actual value of CurrentUserId is not in sync + * with guc.c's internal state, so SET ROLE has to be disallowed. + * + * SECURITY_RESTRICTED_OPERATION indicates that we are inside an operation + * that does not wish to trust called user-defined functions at all. The + * policy is to use this before operations, e.g. autovacuum and REINDEX, that + * enumerate relations of a database or schema and run functions associated + * with each found relation. The relation owner is the new user ID. Set this + * as soon as possible after locking the relation. Restore the old user ID as + * late as possible before closing the relation; restoring it shortly after + * close is also tolerable. If a command has both relation-enumerating and + * non-enumerating modes, e.g. ANALYZE, both modes set this bit. This bit + * prevents not only SET ROLE, but various other changes of session state that + * normally is unprotected but might possibly be used to subvert the calling + * session later. An example is replacing an existing prepared statement with + * new code, which will then be executed with the outer session's permissions + * when the prepared statement is next used. These restrictions are fairly + * draconian, but the functions called in relation-enumerating operations are + * really supposed to be side-effect-free anyway. + * + * SECURITY_NOFORCE_RLS indicates that we are inside an operation which should + * ignore the FORCE ROW LEVEL SECURITY per-table indication. This is used to + * ensure that FORCE RLS does not mistakenly break referential integrity + * checks. Note that this is intentionally only checked when running as the + * owner of the table (which should always be the case for referential + * integrity checks). + * + * Unlike GetUserId, GetUserIdAndSecContext does *not* Assert that the current + * value of CurrentUserId is valid; nor does SetUserIdAndSecContext require + * the new value to be valid. In fact, these routines had better not + * ever throw any kind of error. This is because they are used by + * StartTransaction and AbortTransaction to save/restore the settings, + * and during the first transaction within a backend, the value to be saved + * and perhaps restored is indeed invalid. We have to be able to get + * through AbortTransaction without asserting in case InitPostgres fails. + */ +void +GetUserIdAndSecContext(Oid *userid, int *sec_context) +{ + *userid = CurrentUserId; + *sec_context = SecurityRestrictionContext; +} + +void +SetUserIdAndSecContext(Oid userid, int sec_context) +{ + CurrentUserId = userid; + SecurityRestrictionContext = sec_context; +} + + +/* + * InLocalUserIdChange - are we inside a local change of CurrentUserId? + */ +bool +InLocalUserIdChange(void) +{ + return (SecurityRestrictionContext & SECURITY_LOCAL_USERID_CHANGE) != 0; +} + +/* + * InSecurityRestrictedOperation - are we inside a security-restricted command? + */ +bool +InSecurityRestrictedOperation(void) +{ + return (SecurityRestrictionContext & SECURITY_RESTRICTED_OPERATION) != 0; +} + +/* + * InNoForceRLSOperation - are we ignoring FORCE ROW LEVEL SECURITY ? + */ +bool +InNoForceRLSOperation(void) +{ + return (SecurityRestrictionContext & SECURITY_NOFORCE_RLS) != 0; +} + + +/* + * These are obsolete versions of Get/SetUserIdAndSecContext that are + * only provided for bug-compatibility with some rather dubious code in + * pljava. We allow the userid to be set, but only when not inside a + * security restriction context. + */ +void +GetUserIdAndContext(Oid *userid, bool *sec_def_context) +{ + *userid = CurrentUserId; + *sec_def_context = InLocalUserIdChange(); +} + +void +SetUserIdAndContext(Oid userid, bool sec_def_context) +{ + /* We throw the same error SET ROLE would. */ + if (InSecurityRestrictedOperation()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("cannot set parameter \"%s\" within security-restricted operation", + "role"))); + CurrentUserId = userid; + if (sec_def_context) + SecurityRestrictionContext |= SECURITY_LOCAL_USERID_CHANGE; + else + SecurityRestrictionContext &= ~SECURITY_LOCAL_USERID_CHANGE; +} + + +/* + * Check whether specified role has explicit REPLICATION privilege + */ +bool +has_rolreplication(Oid roleid) +{ + bool result = false; + HeapTuple utup; + + /* Superusers bypass all permission checking. */ + if (superuser_arg(roleid)) + return true; + + utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid)); + if (HeapTupleIsValid(utup)) + { + result = ((Form_pg_authid) GETSTRUCT(utup))->rolreplication; + ReleaseSysCache(utup); + } + return result; +} + +/* + * Initialize user identity during normal backend startup + */ +void +InitializeSessionUserId(const char *rolename, Oid roleid) +{ + HeapTuple roleTup; + Form_pg_authid rform; + char *rname; + + /* + * Don't do scans if we're bootstrapping, none of the system catalogs + * exist yet, and they should be owned by postgres anyway. + */ + Assert(!IsBootstrapProcessingMode()); + + /* call only once */ + Assert(!OidIsValid(AuthenticatedUserId)); + + /* + * Make sure syscache entries are flushed for recent catalog changes. This + * allows us to find roles that were created on-the-fly during + * authentication. + */ + AcceptInvalidationMessages(); + + if (rolename != NULL) + { + roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(rolename)); + if (!HeapTupleIsValid(roleTup)) + ereport(FATAL, + (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), + errmsg("role \"%s\" does not exist", rolename))); + } + else + { + roleTup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid)); + if (!HeapTupleIsValid(roleTup)) + ereport(FATAL, + (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), + errmsg("role with OID %u does not exist", roleid))); + } + + rform = (Form_pg_authid) GETSTRUCT(roleTup); + roleid = rform->oid; + rname = NameStr(rform->rolname); + + AuthenticatedUserId = roleid; + AuthenticatedUserIsSuperuser = rform->rolsuper; + + /* This sets OuterUserId/CurrentUserId too */ + SetSessionUserId(roleid, AuthenticatedUserIsSuperuser); + + /* Also mark our PGPROC entry with the authenticated user id */ + /* (We assume this is an atomic store so no lock is needed) */ + MyProc->roleId = roleid; + + /* + * These next checks are not enforced when in standalone mode, so that + * there is a way to recover from sillinesses like "UPDATE pg_authid SET + * rolcanlogin = false;". + */ + if (IsUnderPostmaster) + { + /* + * Is role allowed to login at all? + */ + if (!rform->rolcanlogin) + ereport(FATAL, + (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), + errmsg("role \"%s\" is not permitted to log in", + rname))); + + /* + * Check connection limit for this role. + * + * There is a race condition here --- we create our PGPROC before + * checking for other PGPROCs. If two backends did this at about the + * same time, they might both think they were over the limit, while + * ideally one should succeed and one fail. Getting that to work + * exactly seems more trouble than it is worth, however; instead we + * just document that the connection limit is approximate. + */ + if (rform->rolconnlimit >= 0 && + !AuthenticatedUserIsSuperuser && + CountUserBackends(roleid) > rform->rolconnlimit) + ereport(FATAL, + (errcode(ERRCODE_TOO_MANY_CONNECTIONS), + errmsg("too many connections for role \"%s\"", + rname))); + } + + /* Record username and superuser status as GUC settings too */ + /*SetConfigOption("session_authorization", rname, + PGC_BACKEND, PGC_S_OVERRIDE); + SetConfigOption("is_superuser", + AuthenticatedUserIsSuperuser ? "on" : "off", + PGC_INTERNAL, PGC_S_DYNAMIC_DEFAULT); + */ + + ReleaseSysCache(roleTup); +} + + +/* + * Initialize user identity during special backend startup + */ +void +InitializeSessionUserIdStandalone(void) +{ + /* + * This function should only be called in single-user mode, in autovacuum + * workers, and in background workers. + */ + Assert(!IsUnderPostmaster || IsAutoVacuumWorkerProcess() || IsBackgroundWorker); + + /* call only once */ + Assert(!OidIsValid(AuthenticatedUserId)); + + AuthenticatedUserId = BOOTSTRAP_SUPERUSERID; + AuthenticatedUserIsSuperuser = true; + + SetSessionUserId(BOOTSTRAP_SUPERUSERID, true); + + /* + * XXX This should set SetConfigOption("session_authorization"), too. + * Since we don't, C code will get NULL, and current_setting() will get an + * empty string. + */ + SetConfigOption("is_superuser", "on", + PGC_INTERNAL, PGC_S_DYNAMIC_DEFAULT); +} + +/* + * Initialize the system user. + * + * This is built as auth_method:authn_id. + */ +void +InitializeSystemUser(const char *authn_id, const char *auth_method) +{ + char *system_user; + + /* call only once */ + Assert(SystemUser == NULL); + + /* + * InitializeSystemUser should be called only when authn_id is not NULL, + * meaning that auth_method is valid. + */ + Assert(authn_id != NULL); + + system_user = psprintf("%s:%s", auth_method, authn_id); + + /* Store SystemUser in long-lived storage */ + SystemUser = MemoryContextStrdup(TopMemoryContext, system_user); + pfree(system_user); +} + +/* + * SQL-function SYSTEM_USER + */ +Datum +system_user(PG_FUNCTION_ARGS) +{ + const char *sysuser = GetSystemUser(); + + if (sysuser) + PG_RETURN_DATUM(CStringGetTextDatum(sysuser)); + else + PG_RETURN_NULL(); +} + +/* + * Change session auth ID while running + * + * Only a superuser may set auth ID to something other than himself. Note + * that in case of multiple SETs in a single session, the original userid's + * superuserness is what matters. But we set the GUC variable is_superuser + * to indicate whether the *current* session userid is a superuser. + * + * Note: this is not an especially clean place to do the permission check. + * It's OK because the check does not require catalog access and can't + * fail during an end-of-transaction GUC reversion, but we may someday + * have to push it up into assign_session_authorization. + */ +void +SetSessionAuthorization(Oid userid, bool is_superuser) +{ + /* Must have authenticated already, else can't make permission check */ + Assert(OidIsValid(AuthenticatedUserId)); + + if (userid != AuthenticatedUserId && + !AuthenticatedUserIsSuperuser) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied to set session authorization"))); + + SetSessionUserId(userid, is_superuser); + + SetConfigOption("is_superuser", + is_superuser ? "on" : "off", + PGC_INTERNAL, PGC_S_DYNAMIC_DEFAULT); +} + +/* + * Report current role id + * This follows the semantics of SET ROLE, ie return the outer-level ID + * not the current effective ID, and return InvalidOid when the setting + * is logically SET ROLE NONE. + */ +Oid +GetCurrentRoleId(void) +{ + if (SetRoleIsActive) + return OuterUserId; + else + return InvalidOid; +} + +/* + * Change Role ID while running (SET ROLE) + * + * If roleid is InvalidOid, we are doing SET ROLE NONE: revert to the + * session user authorization. In this case the is_superuser argument + * is ignored. + * + * When roleid is not InvalidOid, the caller must have checked whether + * the session user has permission to become that role. (We cannot check + * here because this routine must be able to execute in a failed transaction + * to restore a prior value of the ROLE GUC variable.) + */ +void +SetCurrentRoleId(Oid roleid, bool is_superuser) +{ + /* + * Get correct info if it's SET ROLE NONE + * + * If SessionUserId hasn't been set yet, just do nothing --- the eventual + * SetSessionUserId call will fix everything. This is needed since we + * will get called during GUC initialization. + */ + if (!OidIsValid(roleid)) + { + if (!OidIsValid(SessionUserId)) + return; + + roleid = SessionUserId; + is_superuser = SessionUserIsSuperuser; + + SetRoleIsActive = false; + } + else + SetRoleIsActive = true; + + SetOuterUserId(roleid); + + SetConfigOption("is_superuser", + is_superuser ? "on" : "off", + PGC_INTERNAL, PGC_S_DYNAMIC_DEFAULT); +} + + +/* + * Get user name from user oid, returns NULL for nonexistent roleid if noerr + * is true. + */ +char * +GetUserNameFromId(Oid roleid, bool noerr) +{ + HeapTuple tuple; + char *result; + + tuple = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid)); + if (!HeapTupleIsValid(tuple)) + { + if (!noerr) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("invalid role OID: %u", roleid))); + result = NULL; + } + else + { + result = pstrdup(NameStr(((Form_pg_authid) GETSTRUCT(tuple))->rolname)); + ReleaseSysCache(tuple); + } + return result; +} + +/* ------------------------------------------------------------------------ + * Client connection state shared with parallel workers + * + * ClientConnectionInfo contains pieces of information about the client that + * need to be synced to parallel workers when they initialize. + *------------------------------------------------------------------------- + */ + +__thread ClientConnectionInfo MyClientConnectionInfo; + +/* + * Intermediate representation of ClientConnectionInfo for easier + * serialization. Variable-length fields are allocated right after this + * header. + */ +typedef struct SerializedClientConnectionInfo +{ + int32 authn_id_len; /* strlen(authn_id), or -1 if NULL */ + UserAuth auth_method; +} SerializedClientConnectionInfo; + +/* + * Calculate the space needed to serialize MyClientConnectionInfo. + */ +Size +EstimateClientConnectionInfoSpace(void) +{ + Size size = 0; + + size = add_size(size, sizeof(SerializedClientConnectionInfo)); + + if (MyClientConnectionInfo.authn_id) + size = add_size(size, strlen(MyClientConnectionInfo.authn_id) + 1); + + return size; +} + +/* + * Serialize MyClientConnectionInfo for use by parallel workers. + */ +void +SerializeClientConnectionInfo(Size maxsize, char *start_address) +{ + SerializedClientConnectionInfo serialized = {0}; + + serialized.authn_id_len = -1; + serialized.auth_method = MyClientConnectionInfo.auth_method; + + if (MyClientConnectionInfo.authn_id) + serialized.authn_id_len = strlen(MyClientConnectionInfo.authn_id); + + /* Copy serialized representation to buffer */ + Assert(maxsize >= sizeof(serialized)); + memcpy(start_address, &serialized, sizeof(serialized)); + + maxsize -= sizeof(serialized); + start_address += sizeof(serialized); + + /* Copy authn_id into the space after the struct */ + if (serialized.authn_id_len >= 0) + { + (void)maxsize; + Assert(maxsize >= (serialized.authn_id_len + 1)); + memcpy(start_address, + MyClientConnectionInfo.authn_id, + /* include the NULL terminator to ease deserialization */ + serialized.authn_id_len + 1); + } +} + +/* + * Restore MyClientConnectionInfo from its serialized representation. + */ +void +RestoreClientConnectionInfo(char *conninfo) +{ + SerializedClientConnectionInfo serialized; + + memcpy(&serialized, conninfo, sizeof(serialized)); + + /* Copy the fields back into place */ + MyClientConnectionInfo.authn_id = NULL; + MyClientConnectionInfo.auth_method = serialized.auth_method; + + if (serialized.authn_id_len >= 0) + { + char *authn_id; + + authn_id = conninfo + sizeof(serialized); + MyClientConnectionInfo.authn_id = MemoryContextStrdup(TopMemoryContext, + authn_id); + } +} + + +/*------------------------------------------------------------------------- + * Interlock-file support + * + * These routines are used to create both a data-directory lockfile + * ($DATADIR/postmaster.pid) and Unix-socket-file lockfiles ($SOCKFILE.lock). + * Both kinds of files contain the same info initially, although we can add + * more information to a data-directory lockfile after it's created, using + * AddToDataDirLockFile(). See pidfile.h for documentation of the contents + * of these lockfiles. + * + * On successful lockfile creation, a proc_exit callback to remove the + * lockfile is automatically created. + *------------------------------------------------------------------------- + */ + +/* + * proc_exit callback to remove lockfiles. + */ +static void +UnlinkLockFiles(int status, Datum arg) +{ + ListCell *l; + + foreach(l, lock_files) + { + char *curfile = (char *) lfirst(l); + + unlink(curfile); + /* Should we complain if the unlink fails? */ + } + /* Since we're about to exit, no need to reclaim storage */ + lock_files = NIL; + + /* + * Lock file removal should always be the last externally visible action + * of a postmaster or standalone backend, while we won't come here at all + * when exiting postmaster child processes. Therefore, this is a good + * place to log completion of shutdown. We could alternatively teach + * proc_exit() to do it, but that seems uglier. In a standalone backend, + * use NOTICE elevel to be less chatty. + */ + ereport(IsPostmasterEnvironment ? LOG : NOTICE, + (errmsg("database system is shut down"))); +} + +/* + * Create a lockfile. + * + * filename is the path name of the lockfile to create. + * amPostmaster is used to determine how to encode the output PID. + * socketDir is the Unix socket directory path to include (possibly empty). + * isDDLock and refName are used to determine what error message to produce. + */ +static void +CreateLockFile(const char *filename, bool amPostmaster, + const char *socketDir, + bool isDDLock, const char *refName) +{ + int fd; + char buffer[MAXPGPATH * 2 + 256]; + int ntries; + int len; + int encoded_pid; + pid_t other_pid; + pid_t my_pid, + my_p_pid, + my_gp_pid; + const char *envvar; + + /* + * If the PID in the lockfile is our own PID or our parent's or + * grandparent's PID, then the file must be stale (probably left over from + * a previous system boot cycle). We need to check this because of the + * likelihood that a reboot will assign exactly the same PID as we had in + * the previous reboot, or one that's only one or two counts larger and + * hence the lockfile's PID now refers to an ancestor shell process. We + * allow pg_ctl to pass down its parent shell PID (our grandparent PID) + * via the environment variable PG_GRANDPARENT_PID; this is so that + * launching the postmaster via pg_ctl can be just as reliable as + * launching it directly. There is no provision for detecting + * further-removed ancestor processes, but if the init script is written + * carefully then all but the immediate parent shell will be root-owned + * processes and so the kill test will fail with EPERM. Note that we + * cannot get a false negative this way, because an existing postmaster + * would surely never launch a competing postmaster or pg_ctl process + * directly. + */ + my_pid = getpid(); + +#ifndef WIN32 + my_p_pid = getppid(); +#else + + /* + * Windows hasn't got getppid(), but doesn't need it since it's not using + * real kill() either... + */ + my_p_pid = 0; +#endif + + envvar = getenv("PG_GRANDPARENT_PID"); + if (envvar) + my_gp_pid = atoi(envvar); + else + my_gp_pid = 0; + + /* + * We need a loop here because of race conditions. But don't loop forever + * (for example, a non-writable $PGDATA directory might cause a failure + * that won't go away). 100 tries seems like plenty. + */ + for (ntries = 0;; ntries++) + { + /* + * Try to create the lock file --- O_EXCL makes this atomic. + * + * Think not to make the file protection weaker than 0600/0640. See + * comments below. + */ + fd = open(filename, O_RDWR | O_CREAT | O_EXCL, pg_file_create_mode); + if (fd >= 0) + break; /* Success; exit the retry loop */ + + /* + * Couldn't create the pid file. Probably it already exists. + */ + if ((errno != EEXIST && errno != EACCES) || ntries > 100) + ereport(FATAL, + (errcode_for_file_access(), + errmsg("could not create lock file \"%s\": %m", + filename))); + + /* + * Read the file to get the old owner's PID. Note race condition + * here: file might have been deleted since we tried to create it. + */ + fd = open(filename, O_RDONLY, pg_file_create_mode); + if (fd < 0) + { + if (errno == ENOENT) + continue; /* race condition; try again */ + ereport(FATAL, + (errcode_for_file_access(), + errmsg("could not open lock file \"%s\": %m", + filename))); + } + pgstat_report_wait_start(WAIT_EVENT_LOCK_FILE_CREATE_READ); + if ((len = read(fd, buffer, sizeof(buffer) - 1)) < 0) + ereport(FATAL, + (errcode_for_file_access(), + errmsg("could not read lock file \"%s\": %m", + filename))); + pgstat_report_wait_end(); + close(fd); + + if (len == 0) + { + ereport(FATAL, + (errcode(ERRCODE_LOCK_FILE_EXISTS), + errmsg("lock file \"%s\" is empty", filename), + errhint("Either another server is starting, or the lock file is the remnant of a previous server startup crash."))); + } + + buffer[len] = '\0'; + encoded_pid = atoi(buffer); + + /* if pid < 0, the pid is for postgres, not postmaster */ + other_pid = (pid_t) (encoded_pid < 0 ? -encoded_pid : encoded_pid); + + if (other_pid <= 0) + elog(FATAL, "bogus data in lock file \"%s\": \"%s\"", + filename, buffer); + + /* + * Check to see if the other process still exists + * + * Per discussion above, my_pid, my_p_pid, and my_gp_pid can be + * ignored as false matches. + * + * Normally kill() will fail with ESRCH if the given PID doesn't + * exist. + * + * We can treat the EPERM-error case as okay because that error + * implies that the existing process has a different userid than we + * do, which means it cannot be a competing postmaster. A postmaster + * cannot successfully attach to a data directory owned by a userid + * other than its own, as enforced in checkDataDir(). Also, since we + * create the lockfiles mode 0600/0640, we'd have failed above if the + * lockfile belonged to another userid --- which means that whatever + * process kill() is reporting about isn't the one that made the + * lockfile. (NOTE: this last consideration is the only one that + * keeps us from blowing away a Unix socket file belonging to an + * instance of Postgres being run by someone else, at least on + * machines where /tmp hasn't got a stickybit.) + */ + if (other_pid != my_pid && other_pid != my_p_pid && + other_pid != my_gp_pid) + { + if (kill(other_pid, 0) == 0 || + (errno != ESRCH && errno != EPERM)) + { + /* lockfile belongs to a live process */ + ereport(FATAL, + (errcode(ERRCODE_LOCK_FILE_EXISTS), + errmsg("lock file \"%s\" already exists", + filename), + isDDLock ? + (encoded_pid < 0 ? + errhint("Is another postgres (PID %d) running in data directory \"%s\"?", + (int) other_pid, refName) : + errhint("Is another postmaster (PID %d) running in data directory \"%s\"?", + (int) other_pid, refName)) : + (encoded_pid < 0 ? + errhint("Is another postgres (PID %d) using socket file \"%s\"?", + (int) other_pid, refName) : + errhint("Is another postmaster (PID %d) using socket file \"%s\"?", + (int) other_pid, refName)))); + } + } + + /* + * No, the creating process did not exist. However, it could be that + * the postmaster crashed (or more likely was kill -9'd by a clueless + * admin) but has left orphan backends behind. Check for this by + * looking to see if there is an associated shmem segment that is + * still in use. + * + * Note: because postmaster.pid is written in multiple steps, we might + * not find the shmem ID values in it; we can't treat that as an + * error. + */ + if (isDDLock) + { + char *ptr = buffer; + unsigned long id1, + id2; + int lineno; + + for (lineno = 1; lineno < LOCK_FILE_LINE_SHMEM_KEY; lineno++) + { + if ((ptr = strchr(ptr, '\n')) == NULL) + break; + ptr++; + } + + if (ptr != NULL && + sscanf(ptr, "%lu %lu", &id1, &id2) == 2) + { + if (PGSharedMemoryIsInUse(id1, id2)) + ereport(FATAL, + (errcode(ERRCODE_LOCK_FILE_EXISTS), + errmsg("pre-existing shared memory block (key %lu, ID %lu) is still in use", + id1, id2), + errhint("Terminate any old server processes associated with data directory \"%s\".", + refName))); + } + } + + /* + * Looks like nobody's home. Unlink the file and try again to create + * it. Need a loop because of possible race condition against other + * would-be creators. + */ + if (unlink(filename) < 0) + ereport(FATAL, + (errcode_for_file_access(), + errmsg("could not remove old lock file \"%s\": %m", + filename), + errhint("The file seems accidentally left over, but " + "it could not be removed. Please remove the file " + "by hand and try again."))); + } + + /* + * Successfully created the file, now fill it. See comment in pidfile.h + * about the contents. Note that we write the same first five lines into + * both datadir and socket lockfiles; although more stuff may get added to + * the datadir lockfile later. + */ + snprintf(buffer, sizeof(buffer), "%d\n%s\n%ld\n%d\n%s\n", + amPostmaster ? (int) my_pid : -((int) my_pid), + DataDir, + (long) MyStartTime, + PostPortNumber, + socketDir); + + /* + * In a standalone backend, the next line (LOCK_FILE_LINE_LISTEN_ADDR) + * will never receive data, so fill it in as empty now. + */ + if (isDDLock && !amPostmaster) + strlcat(buffer, "\n", sizeof(buffer)); + + errno = 0; + pgstat_report_wait_start(WAIT_EVENT_LOCK_FILE_CREATE_WRITE); + if (write(fd, buffer, strlen(buffer)) != strlen(buffer)) + { + int save_errno = errno; + + close(fd); + unlink(filename); + /* if write didn't set errno, assume problem is no disk space */ + errno = save_errno ? save_errno : ENOSPC; + ereport(FATAL, + (errcode_for_file_access(), + errmsg("could not write lock file \"%s\": %m", filename))); + } + pgstat_report_wait_end(); + + pgstat_report_wait_start(WAIT_EVENT_LOCK_FILE_CREATE_SYNC); + if (pg_fsync(fd) != 0) + { + int save_errno = errno; + + close(fd); + unlink(filename); + errno = save_errno; + ereport(FATAL, + (errcode_for_file_access(), + errmsg("could not write lock file \"%s\": %m", filename))); + } + pgstat_report_wait_end(); + if (close(fd) != 0) + { + int save_errno = errno; + + unlink(filename); + errno = save_errno; + ereport(FATAL, + (errcode_for_file_access(), + errmsg("could not write lock file \"%s\": %m", filename))); + } + + /* + * Arrange to unlink the lock file(s) at proc_exit. If this is the first + * one, set up the on_proc_exit function to do it; then add this lock file + * to the list of files to unlink. + */ + if (lock_files == NIL) + on_proc_exit(UnlinkLockFiles, 0); + + /* + * Use lcons so that the lock files are unlinked in reverse order of + * creation; this is critical! + */ + lock_files = lcons(pstrdup(filename), lock_files); +} + +/* + * Create the data directory lockfile. + * + * When this is called, we must have already switched the working + * directory to DataDir, so we can just use a relative path. This + * helps ensure that we are locking the directory we should be. + * + * Note that the socket directory path line is initially written as empty. + * postmaster.c will rewrite it upon creating the first Unix socket. + */ +void +CreateDataDirLockFile(bool amPostmaster) +{ + CreateLockFile(DIRECTORY_LOCK_FILE, amPostmaster, "", true, DataDir); +} + +/* + * Create a lockfile for the specified Unix socket file. + */ +void +CreateSocketLockFile(const char *socketfile, bool amPostmaster, + const char *socketDir) +{ + char lockfile[MAXPGPATH]; + + snprintf(lockfile, sizeof(lockfile), "%s.lock", socketfile); + CreateLockFile(lockfile, amPostmaster, socketDir, false, socketfile); +} + +/* + * TouchSocketLockFiles -- mark socket lock files as recently accessed + * + * This routine should be called every so often to ensure that the socket + * lock files have a recent mod or access date. That saves them + * from being removed by overenthusiastic /tmp-directory-cleaner daemons. + * (Another reason we should never have put the socket file in /tmp...) + */ +void +TouchSocketLockFiles(void) +{ + ListCell *l; + + foreach(l, lock_files) + { + char *socketLockFile = (char *) lfirst(l); + + /* No need to touch the data directory lock file, we trust */ + if (strcmp(socketLockFile, DIRECTORY_LOCK_FILE) == 0) + continue; + + /* we just ignore any error here */ + (void) utime(socketLockFile, NULL); + } +} + + +/* + * Add (or replace) a line in the data directory lock file. + * The given string should not include a trailing newline. + * + * Note: because we don't truncate the file, if we were to rewrite a line + * with less data than it had before, there would be garbage after the last + * line. While we could fix that by adding a truncate call, that would make + * the file update non-atomic, which we'd rather avoid. Therefore, callers + * should endeavor never to shorten a line once it's been written. + */ +void +AddToDataDirLockFile(int target_line, const char *str) +{ + int fd; + int len; + int lineno; + char *srcptr; + char *destptr; + char srcbuffer[BLCKSZ]; + char destbuffer[BLCKSZ]; + + fd = open(DIRECTORY_LOCK_FILE, O_RDWR | PG_BINARY, 0); + if (fd < 0) + { + ereport(LOG, + (errcode_for_file_access(), + errmsg("could not open file \"%s\": %m", + DIRECTORY_LOCK_FILE))); + return; + } + pgstat_report_wait_start(WAIT_EVENT_LOCK_FILE_ADDTODATADIR_READ); + len = read(fd, srcbuffer, sizeof(srcbuffer) - 1); + pgstat_report_wait_end(); + if (len < 0) + { + ereport(LOG, + (errcode_for_file_access(), + errmsg("could not read from file \"%s\": %m", + DIRECTORY_LOCK_FILE))); + close(fd); + return; + } + srcbuffer[len] = '\0'; + + /* + * Advance over lines we are not supposed to rewrite, then copy them to + * destbuffer. + */ + srcptr = srcbuffer; + for (lineno = 1; lineno < target_line; lineno++) + { + char *eol = strchr(srcptr, '\n'); + + if (eol == NULL) + break; /* not enough lines in file yet */ + srcptr = eol + 1; + } + memcpy(destbuffer, srcbuffer, srcptr - srcbuffer); + destptr = destbuffer + (srcptr - srcbuffer); + + /* + * Fill in any missing lines before the target line, in case lines are + * added to the file out of order. + */ + for (; lineno < target_line; lineno++) + { + if (destptr < destbuffer + sizeof(destbuffer)) + *destptr++ = '\n'; + } + + /* + * Write or rewrite the target line. + */ + snprintf(destptr, destbuffer + sizeof(destbuffer) - destptr, "%s\n", str); + destptr += strlen(destptr); + + /* + * If there are more lines in the old file, append them to destbuffer. + */ + if ((srcptr = strchr(srcptr, '\n')) != NULL) + { + srcptr++; + snprintf(destptr, destbuffer + sizeof(destbuffer) - destptr, "%s", + srcptr); + } + + /* + * And rewrite the data. Since we write in a single kernel call, this + * update should appear atomic to onlookers. + */ + len = strlen(destbuffer); + errno = 0; + pgstat_report_wait_start(WAIT_EVENT_LOCK_FILE_ADDTODATADIR_WRITE); + if (pg_pwrite(fd, destbuffer, len, 0) != len) + { + pgstat_report_wait_end(); + /* if write didn't set errno, assume problem is no disk space */ + if (errno == 0) + errno = ENOSPC; + ereport(LOG, + (errcode_for_file_access(), + errmsg("could not write to file \"%s\": %m", + DIRECTORY_LOCK_FILE))); + close(fd); + return; + } + pgstat_report_wait_end(); + pgstat_report_wait_start(WAIT_EVENT_LOCK_FILE_ADDTODATADIR_SYNC); + if (pg_fsync(fd) != 0) + { + ereport(LOG, + (errcode_for_file_access(), + errmsg("could not write to file \"%s\": %m", + DIRECTORY_LOCK_FILE))); + } + pgstat_report_wait_end(); + if (close(fd) != 0) + { + ereport(LOG, + (errcode_for_file_access(), + errmsg("could not write to file \"%s\": %m", + DIRECTORY_LOCK_FILE))); + } +} + + +/* + * Recheck that the data directory lock file still exists with expected + * content. Return true if the lock file appears OK, false if it isn't. + * + * We call this periodically in the postmaster. The idea is that if the + * lock file has been removed or replaced by another postmaster, we should + * do a panic database shutdown. Therefore, we should return true if there + * is any doubt: we do not want to cause a panic shutdown unnecessarily. + * Transient failures like EINTR or ENFILE should not cause us to fail. + * (If there really is something wrong, we'll detect it on a future recheck.) + */ +bool +RecheckDataDirLockFile(void) +{ + int fd; + int len; + long file_pid; + char buffer[BLCKSZ]; + + fd = open(DIRECTORY_LOCK_FILE, O_RDWR | PG_BINARY, 0); + if (fd < 0) + { + /* + * There are many foreseeable false-positive error conditions. For + * safety, fail only on enumerated clearly-something-is-wrong + * conditions. + */ + switch (errno) + { + case ENOENT: + case ENOTDIR: + /* disaster */ + ereport(LOG, + (errcode_for_file_access(), + errmsg("could not open file \"%s\": %m", + DIRECTORY_LOCK_FILE))); + return false; + default: + /* non-fatal, at least for now */ + ereport(LOG, + (errcode_for_file_access(), + errmsg("could not open file \"%s\": %m; continuing anyway", + DIRECTORY_LOCK_FILE))); + return true; + } + } + pgstat_report_wait_start(WAIT_EVENT_LOCK_FILE_RECHECKDATADIR_READ); + len = read(fd, buffer, sizeof(buffer) - 1); + pgstat_report_wait_end(); + if (len < 0) + { + ereport(LOG, + (errcode_for_file_access(), + errmsg("could not read from file \"%s\": %m", + DIRECTORY_LOCK_FILE))); + close(fd); + return true; /* treat read failure as nonfatal */ + } + buffer[len] = '\0'; + close(fd); + file_pid = atol(buffer); + if (file_pid == getpid()) + return true; /* all is well */ + + /* Trouble: someone's overwritten the lock file */ + ereport(LOG, + (errmsg("lock file \"%s\" contains wrong PID: %ld instead of %ld", + DIRECTORY_LOCK_FILE, file_pid, (long) getpid()))); + return false; +} + + +/*------------------------------------------------------------------------- + * Version checking support + *------------------------------------------------------------------------- + */ + +/* + * Determine whether the PG_VERSION file in directory `path' indicates + * a data version compatible with the version of this program. + * + * If compatible, return. Otherwise, ereport(FATAL). + */ +void +ValidatePgVersion(const char *path) +{ + char full_path[MAXPGPATH]; + FILE *file; + int ret; + long file_major; + long my_major; + char *endptr; + char file_version_string[64]; + const char *my_version_string = PG_VERSION; + + my_major = strtol(my_version_string, &endptr, 10); + + snprintf(full_path, sizeof(full_path), "%s/PG_VERSION", path); + + file = AllocateFile(full_path, "r"); + if (!file) + { + if (errno == ENOENT) + ereport(FATAL, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("\"%s\" is not a valid data directory", + path), + errdetail("File \"%s\" is missing.", full_path))); + else + ereport(FATAL, + (errcode_for_file_access(), + errmsg("could not open file \"%s\": %m", full_path))); + } + + file_version_string[0] = '\0'; + ret = fscanf(file, "%63s", file_version_string); + file_major = strtol(file_version_string, &endptr, 10); + + if (ret != 1 || endptr == file_version_string) + ereport(FATAL, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("\"%s\" is not a valid data directory", + path), + errdetail("File \"%s\" does not contain valid data.", + full_path), + errhint("You might need to initdb."))); + + FreeFile(file); + + if (my_major != file_major) + ereport(FATAL, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("database files are incompatible with server"), + errdetail("The data directory was initialized by PostgreSQL version %s, " + "which is not compatible with this version %s.", + file_version_string, my_version_string))); +} + +/*------------------------------------------------------------------------- + * Library preload support + *------------------------------------------------------------------------- + */ + +/* + * GUC variables: lists of library names to be preloaded at postmaster + * start and at backend start + */ +__thread char *session_preload_libraries_string = NULL; +__thread char *shared_preload_libraries_string = NULL; +__thread char *local_preload_libraries_string = NULL; + +/* Flag telling that we are loading shared_preload_libraries */ +__thread bool process_shared_preload_libraries_in_progress = false; +__thread bool process_shared_preload_libraries_done = false; + +__thread shmem_request_hook_type shmem_request_hook = NULL; +__thread bool process_shmem_requests_in_progress = false; + +/* + * load the shared libraries listed in 'libraries' + * + * 'gucname': name of GUC variable, for error reports + * 'restricted': if true, force libraries to be in $libdir/plugins/ + */ +static void +load_libraries(const char *libraries, const char *gucname, bool restricted) +{ + char *rawstring; + List *elemlist; + ListCell *l; + + if (libraries == NULL || libraries[0] == '\0') + return; /* nothing to do */ + + /* Need a modifiable copy of string */ + rawstring = pstrdup(libraries); + + /* Parse string into list of filename paths */ + if (!SplitDirectoriesString(rawstring, ',', &elemlist)) + { + /* syntax error in list */ + list_free_deep(elemlist); + pfree(rawstring); + ereport(LOG, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid list syntax in parameter \"%s\"", + gucname))); + return; + } + + foreach(l, elemlist) + { + /* Note that filename was already canonicalized */ + char *filename = (char *) lfirst(l); + char *expanded = NULL; + + /* If restricting, insert $libdir/plugins if not mentioned already */ + if (restricted && first_dir_separator(filename) == NULL) + { + expanded = psprintf("$libdir/plugins/%s", filename); + filename = expanded; + } + load_file(filename, restricted); + ereport(DEBUG1, + (errmsg_internal("loaded library \"%s\"", filename))); + if (expanded) + pfree(expanded); + } + + list_free_deep(elemlist); + pfree(rawstring); +} + +/* + * process any libraries that should be preloaded at postmaster start + */ +void +process_shared_preload_libraries(void) +{ + process_shared_preload_libraries_in_progress = true; + load_libraries(shared_preload_libraries_string, + "shared_preload_libraries", + false); + process_shared_preload_libraries_in_progress = false; + process_shared_preload_libraries_done = true; +} + +/* + * process any libraries that should be preloaded at backend start + */ +void +process_session_preload_libraries(void) +{ + load_libraries(session_preload_libraries_string, + "session_preload_libraries", + false); + load_libraries(local_preload_libraries_string, + "local_preload_libraries", + true); +} + +/* + * process any shared memory requests from preloaded libraries + */ +void +process_shmem_requests(void) +{ + process_shmem_requests_in_progress = true; + if (shmem_request_hook) + shmem_request_hook(); + process_shmem_requests_in_progress = false; +} + +void +pg_bindtextdomain(const char *domain) +{ +#ifdef ENABLE_NLS + if (my_exec_path[0] != '\0') + { + char locale_path[MAXPGPATH]; + + get_locale_path(my_exec_path, locale_path); + bindtextdomain(domain, locale_path); + pg_bind_textdomain_codeset(domain); + } +#endif +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/init/postinit.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/init/postinit.c new file mode 100644 index 00000000000..df4d15a50fb --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/init/postinit.c @@ -0,0 +1,1445 @@ +/*------------------------------------------------------------------------- + * + * postinit.c + * postgres initialization utilities + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/init/postinit.c + * + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <ctype.h> +#include <fcntl.h> +#include <unistd.h> + +#include "access/genam.h" +#include "access/heapam.h" +#include "access/htup_details.h" +#include "access/session.h" +#include "access/sysattr.h" +#include "access/tableam.h" +#include "access/xact.h" +#include "access/xlog.h" +#include "access/xloginsert.h" +#include "catalog/catalog.h" +#include "catalog/namespace.h" +#include "catalog/pg_authid.h" +#include "catalog/pg_collation.h" +#include "catalog/pg_database.h" +#include "catalog/pg_db_role_setting.h" +#include "catalog/pg_tablespace.h" +#include "libpq/auth.h" +#include "libpq/libpq-be.h" +#include "mb/pg_wchar.h" +#include "miscadmin.h" +#include "pgstat.h" +#include "postmaster/autovacuum.h" +#include "postmaster/postmaster.h" +#include "replication/slot.h" +#include "replication/walsender.h" +#include "storage/bufmgr.h" +#include "storage/fd.h" +#include "storage/ipc.h" +#include "storage/lmgr.h" +#include "storage/proc.h" +#include "storage/procarray.h" +#include "storage/procsignal.h" +#include "storage/sinvaladt.h" +#include "storage/smgr.h" +#include "storage/sync.h" +#include "tcop/tcopprot.h" +#include "utils/acl.h" +#include "utils/builtins.h" +#include "utils/fmgroids.h" +#include "utils/guc_hooks.h" +#include "utils/memutils.h" +#include "utils/pg_locale.h" +#include "utils/portal.h" +#include "utils/ps_status.h" +#include "utils/snapmgr.h" +#include "utils/syscache.h" +#include "utils/timeout.h" + +static HeapTuple GetDatabaseTuple(const char *dbname); +static HeapTuple GetDatabaseTupleByOid(Oid dboid); +static void PerformAuthentication(Port *port); +static void CheckMyDatabase(const char *name, bool am_superuser, bool override_allow_connections); +static void ShutdownPostgres(int code, Datum arg); +static void StatementTimeoutHandler(void); +static void LockTimeoutHandler(void); +static void IdleInTransactionSessionTimeoutHandler(void); +static void IdleSessionTimeoutHandler(void); +static void IdleStatsUpdateTimeoutHandler(void); +static void ClientCheckTimeoutHandler(void); +static bool ThereIsAtLeastOneRole(void); +static void process_startup_options(Port *port, bool am_superuser); +static void process_settings(Oid databaseid, Oid roleid); + + +/*** InitPostgres support ***/ + + +/* + * GetDatabaseTuple -- fetch the pg_database row for a database + * + * This is used during backend startup when we don't yet have any access to + * system catalogs in general. In the worst case, we can seqscan pg_database + * using nothing but the hard-wired descriptor that relcache.c creates for + * pg_database. In more typical cases, relcache.c was able to load + * descriptors for both pg_database and its indexes from the shared relcache + * cache file, and so we can do an indexscan. criticalSharedRelcachesBuilt + * tells whether we got the cached descriptors. + */ +static HeapTuple +GetDatabaseTuple(const char *dbname) +{ + HeapTuple tuple; + Relation relation; + SysScanDesc scan; + ScanKeyData key[1]; + + /* + * form a scan key + */ + ScanKeyInit(&key[0], + Anum_pg_database_datname, + BTEqualStrategyNumber, F_NAMEEQ, + CStringGetDatum(dbname)); + + /* + * Open pg_database and fetch a tuple. Force heap scan if we haven't yet + * built the critical shared relcache entries (i.e., we're starting up + * without a shared relcache cache file). + */ + relation = table_open(DatabaseRelationId, AccessShareLock); + scan = systable_beginscan(relation, DatabaseNameIndexId, + criticalSharedRelcachesBuilt, + NULL, + 1, key); + + tuple = systable_getnext(scan); + + /* Must copy tuple before releasing buffer */ + if (HeapTupleIsValid(tuple)) + tuple = heap_copytuple(tuple); + + /* all done */ + systable_endscan(scan); + table_close(relation, AccessShareLock); + + return tuple; +} + +/* + * GetDatabaseTupleByOid -- as above, but search by database OID + */ +static HeapTuple +GetDatabaseTupleByOid(Oid dboid) +{ + HeapTuple tuple; + Relation relation; + SysScanDesc scan; + ScanKeyData key[1]; + + /* + * form a scan key + */ + ScanKeyInit(&key[0], + Anum_pg_database_oid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(dboid)); + + /* + * Open pg_database and fetch a tuple. Force heap scan if we haven't yet + * built the critical shared relcache entries (i.e., we're starting up + * without a shared relcache cache file). + */ + relation = table_open(DatabaseRelationId, AccessShareLock); + scan = systable_beginscan(relation, DatabaseOidIndexId, + criticalSharedRelcachesBuilt, + NULL, + 1, key); + + tuple = systable_getnext(scan); + + /* Must copy tuple before releasing buffer */ + if (HeapTupleIsValid(tuple)) + tuple = heap_copytuple(tuple); + + /* all done */ + systable_endscan(scan); + table_close(relation, AccessShareLock); + + return tuple; +} + + +/* + * PerformAuthentication -- authenticate a remote client + * + * returns: nothing. Will not return at all if there's any failure. + */ +static void +PerformAuthentication(Port *port) +{ + /* This should be set already, but let's make sure */ + ClientAuthInProgress = true; /* limit visibility of log messages */ + + /* + * In EXEC_BACKEND case, we didn't inherit the contents of pg_hba.conf + * etcetera from the postmaster, and have to load them ourselves. + * + * FIXME: [fork/exec] Ugh. Is there a way around this overhead? + */ +#ifdef EXEC_BACKEND + + /* + * load_hba() and load_ident() want to work within the PostmasterContext, + * so create that if it doesn't exist (which it won't). We'll delete it + * again later, in PostgresMain. + */ + if (PostmasterContext == NULL) + PostmasterContext = AllocSetContextCreate(TopMemoryContext, + "Postmaster", + ALLOCSET_DEFAULT_SIZES); + + if (!load_hba()) + { + /* + * It makes no sense to continue if we fail to load the HBA file, + * since there is no way to connect to the database in this case. + */ + ereport(FATAL, + /* translator: %s is a configuration file */ + (errmsg("could not load %s", HbaFileName))); + } + + if (!load_ident()) + { + /* + * It is ok to continue if we fail to load the IDENT file, although it + * means that you cannot log in using any of the authentication + * methods that need a user name mapping. load_ident() already logged + * the details of error to the log. + */ + } +#endif + + /* + * Set up a timeout in case a buggy or malicious client fails to respond + * during authentication. Since we're inside a transaction and might do + * database access, we have to use the statement_timeout infrastructure. + */ + enable_timeout_after(STATEMENT_TIMEOUT, AuthenticationTimeout * 1000); + + /* + * Now perform authentication exchange. + */ + set_ps_display("authentication"); + ClientAuthentication(port); /* might not return, if failure */ + + /* + * Done with authentication. Disable the timeout, and log if needed. + */ + disable_timeout(STATEMENT_TIMEOUT, false); + + if (Log_connections) + { + StringInfoData logmsg; + + initStringInfo(&logmsg); + if (am_walsender) + appendStringInfo(&logmsg, _("replication connection authorized: user=%s"), + port->user_name); + else + appendStringInfo(&logmsg, _("connection authorized: user=%s"), + port->user_name); + if (!am_walsender) + appendStringInfo(&logmsg, _(" database=%s"), port->database_name); + + if (port->application_name != NULL) + appendStringInfo(&logmsg, _(" application_name=%s"), + port->application_name); + +#ifdef USE_SSL + if (port->ssl_in_use) + appendStringInfo(&logmsg, _(" SSL enabled (protocol=%s, cipher=%s, bits=%d)"), + be_tls_get_version(port), + be_tls_get_cipher(port), + be_tls_get_cipher_bits(port)); +#endif +#ifdef ENABLE_GSS + if (port->gss) + { + const char *princ = be_gssapi_get_princ(port); + + if (princ) + appendStringInfo(&logmsg, + _(" GSS (authenticated=%s, encrypted=%s, delegated_credentials=%s, principal=%s)"), + be_gssapi_get_auth(port) ? _("yes") : _("no"), + be_gssapi_get_enc(port) ? _("yes") : _("no"), + be_gssapi_get_delegation(port) ? _("yes") : _("no"), + princ); + else + appendStringInfo(&logmsg, + _(" GSS (authenticated=%s, encrypted=%s, delegated_credentials=%s)"), + be_gssapi_get_auth(port) ? _("yes") : _("no"), + be_gssapi_get_enc(port) ? _("yes") : _("no"), + be_gssapi_get_delegation(port) ? _("yes") : _("no")); + } +#endif + + ereport(LOG, errmsg_internal("%s", logmsg.data)); + pfree(logmsg.data); + } + + set_ps_display("startup"); + + ClientAuthInProgress = false; /* client_min_messages is active now */ +} + + +/* + * CheckMyDatabase -- fetch information from the pg_database entry for our DB + */ +static void +CheckMyDatabase(const char *name, bool am_superuser, bool override_allow_connections) +{ + HeapTuple tup; + Form_pg_database dbform; + Datum datum; + bool isnull; + char *collate; + char *ctype; + char *iculocale; + + /* Fetch our pg_database row normally, via syscache */ + tup = SearchSysCache1(DATABASEOID, ObjectIdGetDatum(MyDatabaseId)); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for database %u", MyDatabaseId); + dbform = (Form_pg_database) GETSTRUCT(tup); + + /* This recheck is strictly paranoia */ + if (strcmp(name, NameStr(dbform->datname)) != 0) + ereport(FATAL, + (errcode(ERRCODE_UNDEFINED_DATABASE), + errmsg("database \"%s\" has disappeared from pg_database", + name), + errdetail("Database OID %u now seems to belong to \"%s\".", + MyDatabaseId, NameStr(dbform->datname)))); + + /* + * Check permissions to connect to the database. + * + * These checks are not enforced when in standalone mode, so that there is + * a way to recover from disabling all access to all databases, for + * example "UPDATE pg_database SET datallowconn = false;". + * + * We do not enforce them for autovacuum worker processes either. + */ + if (IsUnderPostmaster && !IsAutoVacuumWorkerProcess()) + { + /* + * Check that the database is currently allowing connections. + */ + if (!dbform->datallowconn && !override_allow_connections) + ereport(FATAL, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("database \"%s\" is not currently accepting connections", + name))); + + /* + * Check privilege to connect to the database. (The am_superuser test + * is redundant, but since we have the flag, might as well check it + * and save a few cycles.) + */ + if (!am_superuser && + object_aclcheck(DatabaseRelationId, MyDatabaseId, GetUserId(), + ACL_CONNECT) != ACLCHECK_OK) + ereport(FATAL, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied for database \"%s\"", name), + errdetail("User does not have CONNECT privilege."))); + + /* + * Check connection limit for this database. + * + * There is a race condition here --- we create our PGPROC before + * checking for other PGPROCs. If two backends did this at about the + * same time, they might both think they were over the limit, while + * ideally one should succeed and one fail. Getting that to work + * exactly seems more trouble than it is worth, however; instead we + * just document that the connection limit is approximate. + */ + if (dbform->datconnlimit >= 0 && + !am_superuser && + CountDBConnections(MyDatabaseId) > dbform->datconnlimit) + ereport(FATAL, + (errcode(ERRCODE_TOO_MANY_CONNECTIONS), + errmsg("too many connections for database \"%s\"", + name))); + } + + /* + * OK, we're golden. Next to-do item is to save the encoding info out of + * the pg_database tuple. + */ + SetDatabaseEncoding(dbform->encoding); + /* Record it as a GUC internal option, too */ + SetConfigOption("server_encoding", GetDatabaseEncodingName(), + PGC_INTERNAL, PGC_S_DYNAMIC_DEFAULT); + /* If we have no other source of client_encoding, use server encoding */ + SetConfigOption("client_encoding", GetDatabaseEncodingName(), + PGC_BACKEND, PGC_S_DYNAMIC_DEFAULT); + + /* assign locale variables */ + datum = SysCacheGetAttrNotNull(DATABASEOID, tup, Anum_pg_database_datcollate); + collate = TextDatumGetCString(datum); + datum = SysCacheGetAttrNotNull(DATABASEOID, tup, Anum_pg_database_datctype); + ctype = TextDatumGetCString(datum); + + if (pg_perm_setlocale(LC_COLLATE, collate) == NULL) + ereport(FATAL, + (errmsg("database locale is incompatible with operating system"), + errdetail("The database was initialized with LC_COLLATE \"%s\", " + " which is not recognized by setlocale().", collate), + errhint("Recreate the database with another locale or install the missing locale."))); + + if (pg_perm_setlocale(LC_CTYPE, ctype) == NULL) + ereport(FATAL, + (errmsg("database locale is incompatible with operating system"), + errdetail("The database was initialized with LC_CTYPE \"%s\", " + " which is not recognized by setlocale().", ctype), + errhint("Recreate the database with another locale or install the missing locale."))); + + if (strcmp(ctype, "C") == 0 || + strcmp(ctype, "POSIX") == 0) + database_ctype_is_c = true; + + if (dbform->datlocprovider == COLLPROVIDER_ICU) + { + char *icurules; + + datum = SysCacheGetAttrNotNull(DATABASEOID, tup, Anum_pg_database_daticulocale); + iculocale = TextDatumGetCString(datum); + + datum = SysCacheGetAttr(DATABASEOID, tup, Anum_pg_database_daticurules, &isnull); + if (!isnull) + icurules = TextDatumGetCString(datum); + else + icurules = NULL; + + make_icu_collator(iculocale, icurules, &default_locale); + } + else + iculocale = NULL; + + default_locale.provider = dbform->datlocprovider; + + /* + * Default locale is currently always deterministic. Nondeterministic + * locales currently don't support pattern matching, which would break a + * lot of things if applied globally. + */ + default_locale.deterministic = true; + + /* + * Check collation version. See similar code in + * pg_newlocale_from_collation(). Note that here we warn instead of error + * in any case, so that we don't prevent connecting. + */ + datum = SysCacheGetAttr(DATABASEOID, tup, Anum_pg_database_datcollversion, + &isnull); + if (!isnull) + { + char *actual_versionstr; + char *collversionstr; + + collversionstr = TextDatumGetCString(datum); + + actual_versionstr = get_collation_actual_version(dbform->datlocprovider, dbform->datlocprovider == COLLPROVIDER_ICU ? iculocale : collate); + if (!actual_versionstr) + /* should not happen */ + elog(WARNING, + "database \"%s\" has no actual collation version, but a version was recorded", + name); + else if (strcmp(actual_versionstr, collversionstr) != 0) + ereport(WARNING, + (errmsg("database \"%s\" has a collation version mismatch", + name), + errdetail("The database was created using collation version %s, " + "but the operating system provides version %s.", + collversionstr, actual_versionstr), + errhint("Rebuild all objects in this database that use the default collation and run " + "ALTER DATABASE %s REFRESH COLLATION VERSION, " + "or build PostgreSQL with the right library version.", + quote_identifier(name)))); + } + + ReleaseSysCache(tup); +} + + +/* + * pg_split_opts -- split a string of options and append it to an argv array + * + * The caller is responsible for ensuring the argv array is large enough. The + * maximum possible number of arguments added by this routine is + * (strlen(optstr) + 1) / 2. + * + * Because some option values can contain spaces we allow escaping using + * backslashes, with \\ representing a literal backslash. + */ +void +pg_split_opts(char **argv, int *argcp, const char *optstr) +{ + StringInfoData s; + + initStringInfo(&s); + + while (*optstr) + { + bool last_was_escape = false; + + resetStringInfo(&s); + + /* skip over leading space */ + while (isspace((unsigned char) *optstr)) + optstr++; + + if (*optstr == '\0') + break; + + /* + * Parse a single option, stopping at the first space, unless it's + * escaped. + */ + while (*optstr) + { + if (isspace((unsigned char) *optstr) && !last_was_escape) + break; + + if (!last_was_escape && *optstr == '\\') + last_was_escape = true; + else + { + last_was_escape = false; + appendStringInfoChar(&s, *optstr); + } + + optstr++; + } + + /* now store the option in the next argv[] position */ + argv[(*argcp)++] = pstrdup(s.data); + } + + pfree(s.data); +} + +/* + * Initialize MaxBackends value from config options. + * + * This must be called after modules have had the chance to alter GUCs in + * shared_preload_libraries and before shared memory size is determined. + * + * Note that in EXEC_BACKEND environment, the value is passed down from + * postmaster to subprocesses via BackendParameters in SubPostmasterMain; only + * postmaster itself and processes not under postmaster control should call + * this. + */ +void +InitializeMaxBackends(void) +{ + Assert(MaxBackends == 0); + + /* the extra unit accounts for the autovacuum launcher */ + MaxBackends = MaxConnections + autovacuum_max_workers + 1 + + max_worker_processes + max_wal_senders; + + /* internal error because the values were all checked previously */ + if (MaxBackends > MAX_BACKENDS) + elog(ERROR, "too many backends configured"); +} + +/* + * GUC check_hook for max_connections + */ +bool +check_max_connections(int *newval, void **extra, GucSource source) +{ + if (*newval + autovacuum_max_workers + 1 + + max_worker_processes + max_wal_senders > MAX_BACKENDS) + return false; + return true; +} + +/* + * GUC check_hook for autovacuum_max_workers + */ +bool +check_autovacuum_max_workers(int *newval, void **extra, GucSource source) +{ + if (MaxConnections + *newval + 1 + + max_worker_processes + max_wal_senders > MAX_BACKENDS) + return false; + return true; +} + +/* + * GUC check_hook for max_worker_processes + */ +bool +check_max_worker_processes(int *newval, void **extra, GucSource source) +{ + if (MaxConnections + autovacuum_max_workers + 1 + + *newval + max_wal_senders > MAX_BACKENDS) + return false; + return true; +} + +/* + * GUC check_hook for max_wal_senders + */ +bool +check_max_wal_senders(int *newval, void **extra, GucSource source) +{ + if (MaxConnections + autovacuum_max_workers + 1 + + max_worker_processes + *newval > MAX_BACKENDS) + return false; + return true; +} + +/* + * Early initialization of a backend (either standalone or under postmaster). + * This happens even before InitPostgres. + * + * This is separate from InitPostgres because it is also called by auxiliary + * processes, such as the background writer process, which may not call + * InitPostgres at all. + */ +void +BaseInit(void) +{ + Assert(MyProc != NULL); + + /* + * Initialize our input/output/debugging file descriptors. + */ + DebugFileOpen(); + + /* + * Initialize file access. Done early so other subsystems can access + * files. + */ + InitFileAccess(); + + /* + * Initialize statistics reporting. This needs to happen early to ensure + * that pgstat's shutdown callback runs after the shutdown callbacks of + * all subsystems that can produce stats (like e.g. transaction commits + * can). + */ + pgstat_initialize(); + + /* Do local initialization of storage and buffer managers */ + InitSync(); + smgrinit(); + InitBufferPoolAccess(); + + /* + * Initialize temporary file access after pgstat, so that the temporary + * file shutdown hook can report temporary file statistics. + */ + InitTemporaryFileAccess(); + + /* + * Initialize local buffers for WAL record construction, in case we ever + * try to insert XLOG. + */ + InitXLogInsert(); + + /* + * Initialize replication slots after pgstat. The exit hook might need to + * drop ephemeral slots, which in turn triggers stats reporting. + */ + ReplicationSlotInitialize(); +} + + +/* -------------------------------- + * InitPostgres + * Initialize POSTGRES. + * + * Parameters: + * in_dbname, dboid: specify database to connect to, as described below + * username, useroid: specify role to connect as, as described below + * load_session_libraries: TRUE to honor [session|local]_preload_libraries + * override_allow_connections: TRUE to connect despite !datallowconn + * out_dbname: optional output parameter, see below; pass NULL if not used + * + * The database can be specified by name, using the in_dbname parameter, or by + * OID, using the dboid parameter. Specify NULL or InvalidOid respectively + * for the unused parameter. If dboid is provided, the actual database + * name can be returned to the caller in out_dbname. If out_dbname isn't + * NULL, it must point to a buffer of size NAMEDATALEN. + * + * Similarly, the role can be passed by name, using the username parameter, + * or by OID using the useroid parameter. + * + * In bootstrap mode the database and username parameters are NULL/InvalidOid. + * The autovacuum launcher process doesn't specify these parameters either, + * because it only goes far enough to be able to read pg_database; it doesn't + * connect to any particular database. An autovacuum worker specifies a + * database but not a username; conversely, a physical walsender specifies + * username but not database. + * + * By convention, load_session_libraries should be passed as true in + * "interactive" sessions (including standalone backends), but false in + * background processes such as autovacuum. Note in particular that it + * shouldn't be true in parallel worker processes; those have another + * mechanism for replicating their leader's set of loaded libraries. + * + * We expect that InitProcess() was already called, so we already have a + * PGPROC struct ... but it's not completely filled in yet. + * + * Note: + * Be very careful with the order of calls in the InitPostgres function. + * -------------------------------- + */ +void +InitPostgres(const char *in_dbname, Oid dboid, + const char *username, Oid useroid, + bool load_session_libraries, + bool override_allow_connections, + char *out_dbname) +{ + bool bootstrap = IsBootstrapProcessingMode(); + bool am_superuser; + char *fullpath; + char dbname[NAMEDATALEN]; + int nfree = 0; + + elog(DEBUG3, "InitPostgres"); + + /* + * Add my PGPROC struct to the ProcArray. + * + * Once I have done this, I am visible to other backends! + */ + InitProcessPhase2(); + + /* + * Initialize my entry in the shared-invalidation manager's array of + * per-backend data. + * + * Sets up MyBackendId, a unique backend identifier. + */ + MyBackendId = InvalidBackendId; + + SharedInvalBackendInit(false); + + if (MyBackendId > MaxBackends || MyBackendId <= 0) + elog(FATAL, "bad backend ID: %d", MyBackendId); + + /* Now that we have a BackendId, we can participate in ProcSignal */ + ProcSignalInit(MyBackendId); + + /* + * Also set up timeout handlers needed for backend operation. We need + * these in every case except bootstrap. + */ + if (!bootstrap) + { + RegisterTimeout(DEADLOCK_TIMEOUT, CheckDeadLockAlert); + RegisterTimeout(STATEMENT_TIMEOUT, StatementTimeoutHandler); + RegisterTimeout(LOCK_TIMEOUT, LockTimeoutHandler); + RegisterTimeout(IDLE_IN_TRANSACTION_SESSION_TIMEOUT, + IdleInTransactionSessionTimeoutHandler); + RegisterTimeout(IDLE_SESSION_TIMEOUT, IdleSessionTimeoutHandler); + RegisterTimeout(CLIENT_CONNECTION_CHECK_TIMEOUT, ClientCheckTimeoutHandler); + RegisterTimeout(IDLE_STATS_UPDATE_TIMEOUT, + IdleStatsUpdateTimeoutHandler); + } + + /* + * If this is either a bootstrap process or a standalone backend, start up + * the XLOG machinery, and register to have it closed down at exit. In + * other cases, the startup process is responsible for starting up the + * XLOG machinery, and the checkpointer for closing it down. + */ + if (!IsUnderPostmaster) + { + /* + * We don't yet have an aux-process resource owner, but StartupXLOG + * and ShutdownXLOG will need one. Hence, create said resource owner + * (and register a callback to clean it up after ShutdownXLOG runs). + */ + CreateAuxProcessResourceOwner(); + + StartupXLOG(); + /* Release (and warn about) any buffer pins leaked in StartupXLOG */ + ReleaseAuxProcessResources(true); + /* Reset CurrentResourceOwner to nothing for the moment */ + CurrentResourceOwner = NULL; + + /* + * Use before_shmem_exit() so that ShutdownXLOG() can rely on DSM + * segments etc to work (which in turn is required for pgstats). + */ + before_shmem_exit(pgstat_before_server_shutdown, 0); + before_shmem_exit(ShutdownXLOG, 0); + } + + /* + * Initialize the relation cache and the system catalog caches. Note that + * no catalog access happens here; we only set up the hashtable structure. + * We must do this before starting a transaction because transaction abort + * would try to touch these hashtables. + */ + RelationCacheInitialize(); + InitCatalogCache(); + InitPlanCache(); + + /* Initialize portal manager */ + EnablePortalManager(); + + /* Initialize status reporting */ + pgstat_beinit(); + + /* + * Load relcache entries for the shared system catalogs. This must create + * at least entries for pg_database and catalogs used for authentication. + */ + RelationCacheInitializePhase2(); + + /* + * Set up process-exit callback to do pre-shutdown cleanup. This is the + * one of the first before_shmem_exit callbacks we register; thus, this + * will be one the last things we do before low-level modules like the + * buffer manager begin to close down. We need to have this in place + * before we begin our first transaction --- if we fail during the + * initialization transaction, as is entirely possible, we need the + * AbortTransaction call to clean up. + */ + before_shmem_exit(ShutdownPostgres, 0); + + /* The autovacuum launcher is done here */ + if (IsAutoVacuumLauncherProcess()) + { + /* report this backend in the PgBackendStatus array */ + pgstat_bestart(); + + return; + } + + /* + * Start a new transaction here before first access to db, and get a + * snapshot. We don't have a use for the snapshot itself, but we're + * interested in the secondary effect that it sets RecentGlobalXmin. (This + * is critical for anything that reads heap pages, because HOT may decide + * to prune them even if the process doesn't attempt to modify any + * tuples.) + * + * FIXME: This comment is inaccurate / the code buggy. A snapshot that is + * not pushed/active does not reliably prevent HOT pruning (->xmin could + * e.g. be cleared when cache invalidations are processed). + */ + if (!bootstrap) + { + /* statement_timestamp must be set for timeouts to work correctly */ + SetCurrentStatementStartTimestamp(); + StartTransactionCommand(); + + /* + * transaction_isolation will have been set to the default by the + * above. If the default is "serializable", and we are in hot + * standby, we will fail if we don't change it to something lower. + * Fortunately, "read committed" is plenty good enough. + */ + XactIsoLevel = XACT_READ_COMMITTED; + + (void) GetTransactionSnapshot(); + } + + /* + * Perform client authentication if necessary, then figure out our + * postgres user ID, and see if we are a superuser. + * + * In standalone mode and in autovacuum worker processes, we use a fixed + * ID, otherwise we figure it out from the authenticated user name. + */ + if (bootstrap || IsAutoVacuumWorkerProcess()) + { + InitializeSessionUserIdStandalone(); + am_superuser = true; + } + else if (!IsUnderPostmaster) + { + InitializeSessionUserIdStandalone(); + am_superuser = true; + if (!ThereIsAtLeastOneRole()) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("no roles are defined in this database system"), + errhint("You should immediately run CREATE USER \"%s\" SUPERUSER;.", + username != NULL ? username : "postgres"))); + } + else if (IsBackgroundWorker) + { + if (username == NULL && !OidIsValid(useroid)) + { + InitializeSessionUserIdStandalone(); + am_superuser = true; + } + else + { + InitializeSessionUserId(username, useroid); + am_superuser = superuser(); + } + } + else + { + /* normal multiuser case */ + Assert(MyProcPort != NULL); + PerformAuthentication(MyProcPort); + InitializeSessionUserId(username, useroid); + /* ensure that auth_method is actually valid, aka authn_id is not NULL */ + if (MyClientConnectionInfo.authn_id) + InitializeSystemUser(MyClientConnectionInfo.authn_id, + hba_authname(MyClientConnectionInfo.auth_method)); + am_superuser = superuser(); + } + + /* + * Binary upgrades only allowed super-user connections + */ + if (IsBinaryUpgrade && !am_superuser) + { + ereport(FATAL, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to connect in binary upgrade mode"))); + } + + /* + * The last few connection slots are reserved for superusers and roles + * with privileges of pg_use_reserved_connections. Replication + * connections are drawn from slots reserved with max_wal_senders and are + * not limited by max_connections, superuser_reserved_connections, or + * reserved_connections. + * + * Note: At this point, the new backend has already claimed a proc struct, + * so we must check whether the number of free slots is strictly less than + * the reserved connection limits. + */ + if (!am_superuser && !am_walsender && + (SuperuserReservedConnections + ReservedConnections) > 0 && + !HaveNFreeProcs(SuperuserReservedConnections + ReservedConnections, &nfree)) + { + if (nfree < SuperuserReservedConnections) + ereport(FATAL, + (errcode(ERRCODE_TOO_MANY_CONNECTIONS), + errmsg("remaining connection slots are reserved for roles with the %s attribute", + "SUPERUSER"))); + + if (!has_privs_of_role(GetUserId(), ROLE_PG_USE_RESERVED_CONNECTIONS)) + ereport(FATAL, + (errcode(ERRCODE_TOO_MANY_CONNECTIONS), + errmsg("remaining connection slots are reserved for roles with privileges of the \"%s\" role", + "pg_use_reserved_connections"))); + } + + /* Check replication permissions needed for walsender processes. */ + if (am_walsender) + { + Assert(!bootstrap); + + if (!has_rolreplication(GetUserId())) + ereport(FATAL, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied to start WAL sender"), + errdetail("Only roles with the %s attribute may start a WAL sender process.", + "REPLICATION"))); + } + + /* + * If this is a plain walsender only supporting physical replication, we + * don't want to connect to any particular database. Just finish the + * backend startup by processing any options from the startup packet, and + * we're done. + */ + if (am_walsender && !am_db_walsender) + { + /* process any options passed in the startup packet */ + if (MyProcPort != NULL) + process_startup_options(MyProcPort, am_superuser); + + /* Apply PostAuthDelay as soon as we've read all options */ + if (PostAuthDelay > 0) + pg_usleep(PostAuthDelay * 1000000L); + + /* initialize client encoding */ + InitializeClientEncoding(); + + /* report this backend in the PgBackendStatus array */ + pgstat_bestart(); + + /* close the transaction we started above */ + CommitTransactionCommand(); + + return; + } + + /* + * Set up the global variables holding database id and default tablespace. + * But note we won't actually try to touch the database just yet. + * + * We take a shortcut in the bootstrap case, otherwise we have to look up + * the db's entry in pg_database. + */ + if (bootstrap) + { + dboid = Template1DbOid; + MyDatabaseTableSpace = DEFAULTTABLESPACE_OID; + } + else if (in_dbname != NULL) + { + HeapTuple tuple; + Form_pg_database dbform; + + tuple = GetDatabaseTuple(in_dbname); + if (!HeapTupleIsValid(tuple)) + ereport(FATAL, + (errcode(ERRCODE_UNDEFINED_DATABASE), + errmsg("database \"%s\" does not exist", in_dbname))); + dbform = (Form_pg_database) GETSTRUCT(tuple); + dboid = dbform->oid; + } + else if (!OidIsValid(dboid)) + { + /* + * If this is a background worker not bound to any particular + * database, we're done now. Everything that follows only makes sense + * if we are bound to a specific database. We do need to close the + * transaction we started before returning. + */ + if (!bootstrap) + { + pgstat_bestart(); + CommitTransactionCommand(); + } + return; + } + + /* + * Now, take a writer's lock on the database we are trying to connect to. + * If there is a concurrently running DROP DATABASE on that database, this + * will block us until it finishes (and has committed its update of + * pg_database). + * + * Note that the lock is not held long, only until the end of this startup + * transaction. This is OK since we will advertise our use of the + * database in the ProcArray before dropping the lock (in fact, that's the + * next thing to do). Anyone trying a DROP DATABASE after this point will + * see us in the array once they have the lock. Ordering is important for + * this because we don't want to advertise ourselves as being in this + * database until we have the lock; otherwise we create what amounts to a + * deadlock with CountOtherDBBackends(). + * + * Note: use of RowExclusiveLock here is reasonable because we envision + * our session as being a concurrent writer of the database. If we had a + * way of declaring a session as being guaranteed-read-only, we could use + * AccessShareLock for such sessions and thereby not conflict against + * CREATE DATABASE. + */ + if (!bootstrap) + LockSharedObject(DatabaseRelationId, dboid, 0, RowExclusiveLock); + + /* + * Recheck pg_database to make sure the target database hasn't gone away. + * If there was a concurrent DROP DATABASE, this ensures we will die + * cleanly without creating a mess. + */ + if (!bootstrap) + { + HeapTuple tuple; + Form_pg_database datform; + + tuple = GetDatabaseTupleByOid(dboid); + if (HeapTupleIsValid(tuple)) + datform = (Form_pg_database) GETSTRUCT(tuple); + + if (!HeapTupleIsValid(tuple) || + (in_dbname && namestrcmp(&datform->datname, in_dbname))) + { + if (in_dbname) + ereport(FATAL, + (errcode(ERRCODE_UNDEFINED_DATABASE), + errmsg("database \"%s\" does not exist", in_dbname), + errdetail("It seems to have just been dropped or renamed."))); + else + ereport(FATAL, + (errcode(ERRCODE_UNDEFINED_DATABASE), + errmsg("database %u does not exist", dboid))); + } + + strlcpy(dbname, NameStr(datform->datname), sizeof(dbname)); + + if (database_is_invalid_form(datform)) + { + ereport(FATAL, + errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("cannot connect to invalid database \"%s\"", dbname), + errhint("Use DROP DATABASE to drop invalid databases.")); + } + + MyDatabaseTableSpace = datform->dattablespace; + /* pass the database name back to the caller */ + if (out_dbname) + strcpy(out_dbname, dbname); + } + + /* + * Now that we rechecked, we are certain to be connected to a database and + * thus can set MyDatabaseId. + * + * It is important that MyDatabaseId only be set once we are sure that the + * target database can no longer be concurrently dropped or renamed. For + * example, without this guarantee, pgstat_update_dbstats() could create + * entries for databases that were just dropped in the pgstat shutdown + * callback, which could confuse other code paths like the autovacuum + * scheduler. + */ + MyDatabaseId = dboid; + + /* + * Now we can mark our PGPROC entry with the database ID. + * + * We assume this is an atomic store so no lock is needed; though actually + * things would work fine even if it weren't atomic. Anyone searching the + * ProcArray for this database's ID should hold the database lock, so they + * would not be executing concurrently with this store. A process looking + * for another database's ID could in theory see a chance match if it read + * a partially-updated databaseId value; but as long as all such searches + * wait and retry, as in CountOtherDBBackends(), they will certainly see + * the correct value on their next try. + */ + MyProc->databaseId = MyDatabaseId; + + /* + * We established a catalog snapshot while reading pg_authid and/or + * pg_database; but until we have set up MyDatabaseId, we won't react to + * incoming sinval messages for unshared catalogs, so we won't realize it + * if the snapshot has been invalidated. Assume it's no good anymore. + */ + InvalidateCatalogSnapshot(); + + /* + * Now we should be able to access the database directory safely. Verify + * it's there and looks reasonable. + */ + fullpath = GetDatabasePath(MyDatabaseId, MyDatabaseTableSpace); + + if (!bootstrap) + { + if (access(fullpath, F_OK) == -1) + { + if (errno == ENOENT) + ereport(FATAL, + (errcode(ERRCODE_UNDEFINED_DATABASE), + errmsg("database \"%s\" does not exist", + dbname), + errdetail("The database subdirectory \"%s\" is missing.", + fullpath))); + else + ereport(FATAL, + (errcode_for_file_access(), + errmsg("could not access directory \"%s\": %m", + fullpath))); + } + + ValidatePgVersion(fullpath); + } + + SetDatabasePath(fullpath); + pfree(fullpath); + + /* + * It's now possible to do real access to the system catalogs. + * + * Load relcache entries for the system catalogs. This must create at + * least the minimum set of "nailed-in" cache entries. + */ + RelationCacheInitializePhase3(); + + /* set up ACL framework (so CheckMyDatabase can check permissions) */ + initialize_acl(); + + /* + * Re-read the pg_database row for our database, check permissions and set + * up database-specific GUC settings. We can't do this until all the + * database-access infrastructure is up. (Also, it wants to know if the + * user is a superuser, so the above stuff has to happen first.) + */ + if (!bootstrap) + CheckMyDatabase(dbname, am_superuser, override_allow_connections); + + /* + * Now process any command-line switches and any additional GUC variable + * settings passed in the startup packet. We couldn't do this before + * because we didn't know if client is a superuser. + */ + if (MyProcPort != NULL) + process_startup_options(MyProcPort, am_superuser); + + /* Process pg_db_role_setting options */ + process_settings(MyDatabaseId, GetSessionUserId()); + + /* Apply PostAuthDelay as soon as we've read all options */ + if (PostAuthDelay > 0) + pg_usleep(PostAuthDelay * 1000000L); + + /* + * Initialize various default states that can't be set up until we've + * selected the active user and gotten the right GUC settings. + */ + + /* set default namespace search path */ + InitializeSearchPath(); + + /* initialize client encoding */ + InitializeClientEncoding(); + + /* Initialize this backend's session state. */ + InitializeSession(); + + /* + * If this is an interactive session, load any libraries that should be + * preloaded at backend start. Since those are determined by GUCs, this + * can't happen until GUC settings are complete, but we want it to happen + * during the initial transaction in case anything that requires database + * access needs to be done. + */ + if (load_session_libraries) + process_session_preload_libraries(); + + /* report this backend in the PgBackendStatus array */ + if (!bootstrap) + pgstat_bestart(); + + /* close the transaction we started above */ + if (!bootstrap) + CommitTransactionCommand(); +} + +/* + * Process any command-line switches and any additional GUC variable + * settings passed in the startup packet. + */ +static void +process_startup_options(Port *port, bool am_superuser) +{ + GucContext gucctx; + ListCell *gucopts; + + gucctx = am_superuser ? PGC_SU_BACKEND : PGC_BACKEND; + + /* + * First process any command-line switches that were included in the + * startup packet, if we are in a regular backend. + */ + if (port->cmdline_options != NULL) + { + /* + * The maximum possible number of commandline arguments that could + * come from port->cmdline_options is (strlen + 1) / 2; see + * pg_split_opts(). + */ + char **av; + int maxac; + int ac; + + maxac = 2 + (strlen(port->cmdline_options) + 1) / 2; + + av = (char **) palloc(maxac * sizeof(char *)); + ac = 0; + + av[ac++] = "postgres"; + + pg_split_opts(av, &ac, port->cmdline_options); + + av[ac] = NULL; + + Assert(ac < maxac); + + (void) process_postgres_switches(ac, av, gucctx, NULL); + } + + /* + * Process any additional GUC variable settings passed in startup packet. + * These are handled exactly like command-line variables. + */ + gucopts = list_head(port->guc_options); + while (gucopts) + { + char *name; + char *value; + + name = lfirst(gucopts); + gucopts = lnext(port->guc_options, gucopts); + + value = lfirst(gucopts); + gucopts = lnext(port->guc_options, gucopts); + + SetConfigOption(name, value, gucctx, PGC_S_CLIENT); + } +} + +/* + * Load GUC settings from pg_db_role_setting. + * + * We try specific settings for the database/role combination, as well as + * general for this database and for this user. + */ +static void +process_settings(Oid databaseid, Oid roleid) +{ + Relation relsetting; + Snapshot snapshot; + + if (!IsUnderPostmaster) + return; + + relsetting = table_open(DbRoleSettingRelationId, AccessShareLock); + + /* read all the settings under the same snapshot for efficiency */ + snapshot = RegisterSnapshot(GetCatalogSnapshot(DbRoleSettingRelationId)); + + /* Later settings are ignored if set earlier. */ + ApplySetting(snapshot, databaseid, roleid, relsetting, PGC_S_DATABASE_USER); + ApplySetting(snapshot, InvalidOid, roleid, relsetting, PGC_S_USER); + ApplySetting(snapshot, databaseid, InvalidOid, relsetting, PGC_S_DATABASE); + ApplySetting(snapshot, InvalidOid, InvalidOid, relsetting, PGC_S_GLOBAL); + + UnregisterSnapshot(snapshot); + table_close(relsetting, AccessShareLock); +} + +/* + * Backend-shutdown callback. Do cleanup that we want to be sure happens + * before all the supporting modules begin to nail their doors shut via + * their own callbacks. + * + * User-level cleanup, such as temp-relation removal and UNLISTEN, happens + * via separate callbacks that execute before this one. We don't combine the + * callbacks because we still want this one to happen if the user-level + * cleanup fails. + */ +static void +ShutdownPostgres(int code, Datum arg) +{ + /* Make sure we've killed any active transaction */ + AbortOutOfAnyTransaction(); + + /* + * User locks are not released by transaction end, so be sure to release + * them explicitly. + */ + LockReleaseAll(USER_LOCKMETHOD, true); +} + + +/* + * STATEMENT_TIMEOUT handler: trigger a query-cancel interrupt. + */ +static void +StatementTimeoutHandler(void) +{ + int sig = SIGINT; + + /* + * During authentication the timeout is used to deal with + * authentication_timeout - we want to quit in response to such timeouts. + */ + if (ClientAuthInProgress) + sig = SIGTERM; + +#ifdef HAVE_SETSID + /* try to signal whole process group */ + kill(-MyProcPid, sig); +#endif + kill(MyProcPid, sig); +} + +/* + * LOCK_TIMEOUT handler: trigger a query-cancel interrupt. + */ +static void +LockTimeoutHandler(void) +{ +#ifdef HAVE_SETSID + /* try to signal whole process group */ + kill(-MyProcPid, SIGINT); +#endif + kill(MyProcPid, SIGINT); +} + +static void +IdleInTransactionSessionTimeoutHandler(void) +{ + IdleInTransactionSessionTimeoutPending = true; + InterruptPending = true; + SetLatch(MyLatch); +} + +static void +IdleSessionTimeoutHandler(void) +{ + IdleSessionTimeoutPending = true; + InterruptPending = true; + SetLatch(MyLatch); +} + +static void +IdleStatsUpdateTimeoutHandler(void) +{ + IdleStatsUpdateTimeoutPending = true; + InterruptPending = true; + SetLatch(MyLatch); +} + +static void +ClientCheckTimeoutHandler(void) +{ + CheckClientConnectionPending = true; + InterruptPending = true; + SetLatch(MyLatch); +} + +/* + * Returns true if at least one role is defined in this database cluster. + */ +static bool +ThereIsAtLeastOneRole(void) +{ + Relation pg_authid_rel; + TableScanDesc scan; + bool result; + + pg_authid_rel = table_open(AuthIdRelationId, AccessShareLock); + + scan = table_beginscan_catalog(pg_authid_rel, 0, NULL); + result = (heap_getnext(scan, ForwardScanDirection) != NULL); + + table_endscan(scan); + table_close(pg_authid_rel, AccessShareLock); + + return result; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/init/usercontext.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/init/usercontext.c new file mode 100644 index 00000000000..dd9a0dd6a83 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/init/usercontext.c @@ -0,0 +1,92 @@ +/*------------------------------------------------------------------------- + * + * usercontext.c + * Convenience functions for running code as a different database user. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/init/usercontext.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "miscadmin.h" +#include "utils/acl.h" +#include "utils/guc.h" +#include "utils/usercontext.h" + +/* + * Temporarily switch to a new user ID. + * + * If the current user doesn't have permission to SET ROLE to the new user, + * an ERROR occurs. + * + * If the new user doesn't have permission to SET ROLE to the current user, + * SECURITY_RESTRICTED_OPERATION is imposed and a new GUC nest level is + * created so that any settings changes can be rolled back. + */ +void +SwitchToUntrustedUser(Oid userid, UserContext *context) +{ + /* Get the current user ID and security context. */ + GetUserIdAndSecContext(&context->save_userid, + &context->save_sec_context); + + /* Check that we have sufficient privileges to assume the target role. */ + if (!member_can_set_role(context->save_userid, userid)) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("role \"%s\" cannot SET ROLE to \"%s\"", + GetUserNameFromId(context->save_userid, false), + GetUserNameFromId(userid, false)))); + + /* + * Try to prevent the user to which we're switching from assuming the + * privileges of the current user, unless they can SET ROLE to that user + * anyway. + */ + if (member_can_set_role(userid, context->save_userid)) + { + /* + * Each user can SET ROLE to the other, so there's no point in + * imposing any security restrictions. Just let the user do whatever + * they want. + */ + SetUserIdAndSecContext(userid, context->save_sec_context); + context->save_nestlevel = -1; + } + else + { + int sec_context = context->save_sec_context; + + /* + * This user can SET ROLE to the target user, but not the other way + * around, so protect ourselves against the target user by setting + * SECURITY_RESTRICTED_OPERATION to prevent certain changes to the + * session state. Also set up a new GUC nest level, so that we can + * roll back any GUC changes that may be made by code running as the + * target user, inasmuch as they could be malicious. + */ + sec_context |= SECURITY_RESTRICTED_OPERATION; + SetUserIdAndSecContext(userid, sec_context); + context->save_nestlevel = NewGUCNestLevel(); + } +} + +/* + * Switch back to the original user ID. + * + * If we created a new GUC nest level, also roll back any changes that were + * made within it. + */ +void +RestoreUserContext(UserContext *context) +{ + if (context->save_nestlevel != -1) + AtEOXact_GUC(false, context->save_nestlevel); + SetUserIdAndSecContext(context->save_userid, context->save_sec_context); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/mb/conv.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/mb/conv.c new file mode 100644 index 00000000000..82bc1ac6af3 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/mb/conv.c @@ -0,0 +1,838 @@ +/*------------------------------------------------------------------------- + * + * Utility functions for conversion procs. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/utils/mb/conv.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" +#include "mb/pg_wchar.h" + + +/* + * local2local: a generic single byte charset encoding + * conversion between two ASCII-superset encodings. + * + * l points to the source string of length len + * p is the output area (must be large enough!) + * src_encoding is the PG identifier for the source encoding + * dest_encoding is the PG identifier for the target encoding + * tab holds conversion entries for the source charset + * starting from 128 (0x80). each entry in the table holds the corresponding + * code point for the target charset, or 0 if there is no equivalent code. + * + * Returns the number of input bytes consumed. If noError is true, this can + * be less than 'len'. + */ +int +local2local(const unsigned char *l, + unsigned char *p, + int len, + int src_encoding, + int dest_encoding, + const unsigned char *tab, + bool noError) +{ + const unsigned char *start = l; + unsigned char c1, + c2; + + while (len > 0) + { + c1 = *l; + if (c1 == 0) + { + if (noError) + break; + report_invalid_encoding(src_encoding, (const char *) l, len); + } + if (!IS_HIGHBIT_SET(c1)) + *p++ = c1; + else + { + c2 = tab[c1 - HIGHBIT]; + if (c2) + *p++ = c2; + else + { + if (noError) + break; + report_untranslatable_char(src_encoding, dest_encoding, + (const char *) l, len); + } + } + l++; + len--; + } + *p = '\0'; + + return l - start; +} + +/* + * LATINn ---> MIC when the charset's local codes map directly to MIC + * + * l points to the source string of length len + * p is the output area (must be large enough!) + * lc is the mule character set id for the local encoding + * encoding is the PG identifier for the local encoding + * + * Returns the number of input bytes consumed. If noError is true, this can + * be less than 'len'. + */ +int +latin2mic(const unsigned char *l, unsigned char *p, int len, + int lc, int encoding, bool noError) +{ + const unsigned char *start = l; + int c1; + + while (len > 0) + { + c1 = *l; + if (c1 == 0) + { + if (noError) + break; + report_invalid_encoding(encoding, (const char *) l, len); + } + if (IS_HIGHBIT_SET(c1)) + *p++ = lc; + *p++ = c1; + l++; + len--; + } + *p = '\0'; + + return l - start; +} + +/* + * MIC ---> LATINn when the charset's local codes map directly to MIC + * + * mic points to the source string of length len + * p is the output area (must be large enough!) + * lc is the mule character set id for the local encoding + * encoding is the PG identifier for the local encoding + * + * Returns the number of input bytes consumed. If noError is true, this can + * be less than 'len'. + */ +int +mic2latin(const unsigned char *mic, unsigned char *p, int len, + int lc, int encoding, bool noError) +{ + const unsigned char *start = mic; + int c1; + + while (len > 0) + { + c1 = *mic; + if (c1 == 0) + { + if (noError) + break; + report_invalid_encoding(PG_MULE_INTERNAL, (const char *) mic, len); + } + if (!IS_HIGHBIT_SET(c1)) + { + /* easy for ASCII */ + *p++ = c1; + mic++; + len--; + } + else + { + int l = pg_mule_mblen(mic); + + if (len < l) + { + if (noError) + break; + report_invalid_encoding(PG_MULE_INTERNAL, (const char *) mic, + len); + } + if (l != 2 || c1 != lc || !IS_HIGHBIT_SET(mic[1])) + { + if (noError) + break; + report_untranslatable_char(PG_MULE_INTERNAL, encoding, + (const char *) mic, len); + } + *p++ = mic[1]; + mic += 2; + len -= 2; + } + } + *p = '\0'; + + return mic - start; +} + + +/* + * latin2mic_with_table: a generic single byte charset encoding + * conversion from a local charset to the mule internal code. + * + * l points to the source string of length len + * p is the output area (must be large enough!) + * lc is the mule character set id for the local encoding + * encoding is the PG identifier for the local encoding + * tab holds conversion entries for the local charset + * starting from 128 (0x80). each entry in the table holds the corresponding + * code point for the mule encoding, or 0 if there is no equivalent code. + * + * Returns the number of input bytes consumed. If noError is true, this can + * be less than 'len'. + */ +int +latin2mic_with_table(const unsigned char *l, + unsigned char *p, + int len, + int lc, + int encoding, + const unsigned char *tab, + bool noError) +{ + const unsigned char *start = l; + unsigned char c1, + c2; + + while (len > 0) + { + c1 = *l; + if (c1 == 0) + { + if (noError) + break; + report_invalid_encoding(encoding, (const char *) l, len); + } + if (!IS_HIGHBIT_SET(c1)) + *p++ = c1; + else + { + c2 = tab[c1 - HIGHBIT]; + if (c2) + { + *p++ = lc; + *p++ = c2; + } + else + { + if (noError) + break; + report_untranslatable_char(encoding, PG_MULE_INTERNAL, + (const char *) l, len); + } + } + l++; + len--; + } + *p = '\0'; + + return l - start; +} + +/* + * mic2latin_with_table: a generic single byte charset encoding + * conversion from the mule internal code to a local charset. + * + * mic points to the source string of length len + * p is the output area (must be large enough!) + * lc is the mule character set id for the local encoding + * encoding is the PG identifier for the local encoding + * tab holds conversion entries for the mule internal code's second byte, + * starting from 128 (0x80). each entry in the table holds the corresponding + * code point for the local charset, or 0 if there is no equivalent code. + * + * Returns the number of input bytes consumed. If noError is true, this can + * be less than 'len'. + */ +int +mic2latin_with_table(const unsigned char *mic, + unsigned char *p, + int len, + int lc, + int encoding, + const unsigned char *tab, + bool noError) +{ + const unsigned char *start = mic; + unsigned char c1, + c2; + + while (len > 0) + { + c1 = *mic; + if (c1 == 0) + { + if (noError) + break; + report_invalid_encoding(PG_MULE_INTERNAL, (const char *) mic, len); + } + if (!IS_HIGHBIT_SET(c1)) + { + /* easy for ASCII */ + *p++ = c1; + mic++; + len--; + } + else + { + int l = pg_mule_mblen(mic); + + if (len < l) + { + if (noError) + break; + report_invalid_encoding(PG_MULE_INTERNAL, (const char *) mic, + len); + } + if (l != 2 || c1 != lc || !IS_HIGHBIT_SET(mic[1]) || + (c2 = tab[mic[1] - HIGHBIT]) == 0) + { + if (noError) + break; + report_untranslatable_char(PG_MULE_INTERNAL, encoding, + (const char *) mic, len); + break; /* keep compiler quiet */ + } + *p++ = c2; + mic += 2; + len -= 2; + } + } + *p = '\0'; + + return mic - start; +} + +/* + * comparison routine for bsearch() + * this routine is intended for combined UTF8 -> local code + */ +static int +compare3(const void *p1, const void *p2) +{ + uint32 s1, + s2, + d1, + d2; + + s1 = *(const uint32 *) p1; + s2 = *((const uint32 *) p1 + 1); + d1 = ((const pg_utf_to_local_combined *) p2)->utf1; + d2 = ((const pg_utf_to_local_combined *) p2)->utf2; + return (s1 > d1 || (s1 == d1 && s2 > d2)) ? 1 : ((s1 == d1 && s2 == d2) ? 0 : -1); +} + +/* + * comparison routine for bsearch() + * this routine is intended for local code -> combined UTF8 + */ +static int +compare4(const void *p1, const void *p2) +{ + uint32 v1, + v2; + + v1 = *(const uint32 *) p1; + v2 = ((const pg_local_to_utf_combined *) p2)->code; + return (v1 > v2) ? 1 : ((v1 == v2) ? 0 : -1); +} + +/* + * store 32bit character representation into multibyte stream + */ +static inline unsigned char * +store_coded_char(unsigned char *dest, uint32 code) +{ + if (code & 0xff000000) + *dest++ = code >> 24; + if (code & 0x00ff0000) + *dest++ = code >> 16; + if (code & 0x0000ff00) + *dest++ = code >> 8; + if (code & 0x000000ff) + *dest++ = code; + return dest; +} + +/* + * Convert a character using a conversion radix tree. + * + * 'l' is the length of the input character in bytes, and b1-b4 are + * the input character's bytes. + */ +static inline uint32 +pg_mb_radix_conv(const pg_mb_radix_tree *rt, + int l, + unsigned char b1, + unsigned char b2, + unsigned char b3, + unsigned char b4) +{ + if (l == 4) + { + /* 4-byte code */ + + /* check code validity */ + if (b1 < rt->b4_1_lower || b1 > rt->b4_1_upper || + b2 < rt->b4_2_lower || b2 > rt->b4_2_upper || + b3 < rt->b4_3_lower || b3 > rt->b4_3_upper || + b4 < rt->b4_4_lower || b4 > rt->b4_4_upper) + return 0; + + /* perform lookup */ + if (rt->chars32) + { + uint32 idx = rt->b4root; + + idx = rt->chars32[b1 + idx - rt->b4_1_lower]; + idx = rt->chars32[b2 + idx - rt->b4_2_lower]; + idx = rt->chars32[b3 + idx - rt->b4_3_lower]; + return rt->chars32[b4 + idx - rt->b4_4_lower]; + } + else + { + uint16 idx = rt->b4root; + + idx = rt->chars16[b1 + idx - rt->b4_1_lower]; + idx = rt->chars16[b2 + idx - rt->b4_2_lower]; + idx = rt->chars16[b3 + idx - rt->b4_3_lower]; + return rt->chars16[b4 + idx - rt->b4_4_lower]; + } + } + else if (l == 3) + { + /* 3-byte code */ + + /* check code validity */ + if (b2 < rt->b3_1_lower || b2 > rt->b3_1_upper || + b3 < rt->b3_2_lower || b3 > rt->b3_2_upper || + b4 < rt->b3_3_lower || b4 > rt->b3_3_upper) + return 0; + + /* perform lookup */ + if (rt->chars32) + { + uint32 idx = rt->b3root; + + idx = rt->chars32[b2 + idx - rt->b3_1_lower]; + idx = rt->chars32[b3 + idx - rt->b3_2_lower]; + return rt->chars32[b4 + idx - rt->b3_3_lower]; + } + else + { + uint16 idx = rt->b3root; + + idx = rt->chars16[b2 + idx - rt->b3_1_lower]; + idx = rt->chars16[b3 + idx - rt->b3_2_lower]; + return rt->chars16[b4 + idx - rt->b3_3_lower]; + } + } + else if (l == 2) + { + /* 2-byte code */ + + /* check code validity - first byte */ + if (b3 < rt->b2_1_lower || b3 > rt->b2_1_upper || + b4 < rt->b2_2_lower || b4 > rt->b2_2_upper) + return 0; + + /* perform lookup */ + if (rt->chars32) + { + uint32 idx = rt->b2root; + + idx = rt->chars32[b3 + idx - rt->b2_1_lower]; + return rt->chars32[b4 + idx - rt->b2_2_lower]; + } + else + { + uint16 idx = rt->b2root; + + idx = rt->chars16[b3 + idx - rt->b2_1_lower]; + return rt->chars16[b4 + idx - rt->b2_2_lower]; + } + } + else if (l == 1) + { + /* 1-byte code */ + + /* check code validity - first byte */ + if (b4 < rt->b1_lower || b4 > rt->b1_upper) + return 0; + + /* perform lookup */ + if (rt->chars32) + return rt->chars32[b4 + rt->b1root - rt->b1_lower]; + else + return rt->chars16[b4 + rt->b1root - rt->b1_lower]; + } + return 0; /* shouldn't happen */ +} + +/* + * UTF8 ---> local code + * + * utf: input string in UTF8 encoding (need not be null-terminated) + * len: length of input string (in bytes) + * iso: pointer to the output area (must be large enough!) + (output string will be null-terminated) + * map: conversion map for single characters + * cmap: conversion map for combined characters + * (optional, pass NULL if none) + * cmapsize: number of entries in the conversion map for combined characters + * (optional, pass 0 if none) + * conv_func: algorithmic encoding conversion function + * (optional, pass NULL if none) + * encoding: PG identifier for the local encoding + * + * For each character, the cmap (if provided) is consulted first; if no match, + * the map is consulted next; if still no match, the conv_func (if provided) + * is applied. An error is raised if no match is found. + * + * See pg_wchar.h for more details about the data structures used here. + * + * Returns the number of input bytes consumed. If noError is true, this can + * be less than 'len'. + */ +int +UtfToLocal(const unsigned char *utf, int len, + unsigned char *iso, + const pg_mb_radix_tree *map, + const pg_utf_to_local_combined *cmap, int cmapsize, + utf_local_conversion_func conv_func, + int encoding, bool noError) +{ + uint32 iutf; + int l; + const pg_utf_to_local_combined *cp; + const unsigned char *start = utf; + + if (!PG_VALID_ENCODING(encoding)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid encoding number: %d", encoding))); + + for (; len > 0; len -= l) + { + unsigned char b1 = 0; + unsigned char b2 = 0; + unsigned char b3 = 0; + unsigned char b4 = 0; + + /* "break" cases all represent errors */ + if (*utf == '\0') + break; + + l = pg_utf_mblen(utf); + if (len < l) + break; + + if (!pg_utf8_islegal(utf, l)) + break; + + if (l == 1) + { + /* ASCII case is easy, assume it's one-to-one conversion */ + *iso++ = *utf++; + continue; + } + + /* collect coded char of length l */ + if (l == 2) + { + b3 = *utf++; + b4 = *utf++; + } + else if (l == 3) + { + b2 = *utf++; + b3 = *utf++; + b4 = *utf++; + } + else if (l == 4) + { + b1 = *utf++; + b2 = *utf++; + b3 = *utf++; + b4 = *utf++; + } + else + { + elog(ERROR, "unsupported character length %d", l); + iutf = 0; /* keep compiler quiet */ + } + iutf = (b1 << 24 | b2 << 16 | b3 << 8 | b4); + + /* First, try with combined map if possible */ + if (cmap && len > l) + { + const unsigned char *utf_save = utf; + int len_save = len; + int l_save = l; + + /* collect next character, same as above */ + len -= l; + + l = pg_utf_mblen(utf); + if (len < l) + { + /* need more data to decide if this is a combined char */ + utf -= l_save; + break; + } + + if (!pg_utf8_islegal(utf, l)) + { + if (!noError) + report_invalid_encoding(PG_UTF8, (const char *) utf, len); + utf -= l_save; + break; + } + + /* We assume ASCII character cannot be in combined map */ + if (l > 1) + { + uint32 iutf2; + uint32 cutf[2]; + + if (l == 2) + { + iutf2 = *utf++ << 8; + iutf2 |= *utf++; + } + else if (l == 3) + { + iutf2 = *utf++ << 16; + iutf2 |= *utf++ << 8; + iutf2 |= *utf++; + } + else if (l == 4) + { + iutf2 = *utf++ << 24; + iutf2 |= *utf++ << 16; + iutf2 |= *utf++ << 8; + iutf2 |= *utf++; + } + else + { + elog(ERROR, "unsupported character length %d", l); + iutf2 = 0; /* keep compiler quiet */ + } + + cutf[0] = iutf; + cutf[1] = iutf2; + + cp = bsearch(cutf, cmap, cmapsize, + sizeof(pg_utf_to_local_combined), compare3); + + if (cp) + { + iso = store_coded_char(iso, cp->code); + continue; + } + } + + /* fail, so back up to reprocess second character next time */ + utf = utf_save; + len = len_save; + l = l_save; + } + + /* Now check ordinary map */ + if (map) + { + uint32 converted = pg_mb_radix_conv(map, l, b1, b2, b3, b4); + + if (converted) + { + iso = store_coded_char(iso, converted); + continue; + } + } + + /* if there's a conversion function, try that */ + if (conv_func) + { + uint32 converted = (*conv_func) (iutf); + + if (converted) + { + iso = store_coded_char(iso, converted); + continue; + } + } + + /* failed to translate this character */ + utf -= l; + if (noError) + break; + report_untranslatable_char(PG_UTF8, encoding, + (const char *) utf, len); + } + + /* if we broke out of loop early, must be invalid input */ + if (len > 0 && !noError) + report_invalid_encoding(PG_UTF8, (const char *) utf, len); + + *iso = '\0'; + + return utf - start; +} + +/* + * local code ---> UTF8 + * + * iso: input string in local encoding (need not be null-terminated) + * len: length of input string (in bytes) + * utf: pointer to the output area (must be large enough!) + (output string will be null-terminated) + * map: conversion map for single characters + * cmap: conversion map for combined characters + * (optional, pass NULL if none) + * cmapsize: number of entries in the conversion map for combined characters + * (optional, pass 0 if none) + * conv_func: algorithmic encoding conversion function + * (optional, pass NULL if none) + * encoding: PG identifier for the local encoding + * + * For each character, the map is consulted first; if no match, the cmap + * (if provided) is consulted next; if still no match, the conv_func + * (if provided) is applied. An error is raised if no match is found. + * + * See pg_wchar.h for more details about the data structures used here. + * + * Returns the number of input bytes consumed. If noError is true, this can + * be less than 'len'. + */ +int +LocalToUtf(const unsigned char *iso, int len, + unsigned char *utf, + const pg_mb_radix_tree *map, + const pg_local_to_utf_combined *cmap, int cmapsize, + utf_local_conversion_func conv_func, + int encoding, + bool noError) +{ + uint32 iiso; + int l; + const pg_local_to_utf_combined *cp; + const unsigned char *start = iso; + + if (!PG_VALID_ENCODING(encoding)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid encoding number: %d", encoding))); + + for (; len > 0; len -= l) + { + unsigned char b1 = 0; + unsigned char b2 = 0; + unsigned char b3 = 0; + unsigned char b4 = 0; + + /* "break" cases all represent errors */ + if (*iso == '\0') + break; + + if (!IS_HIGHBIT_SET(*iso)) + { + /* ASCII case is easy, assume it's one-to-one conversion */ + *utf++ = *iso++; + l = 1; + continue; + } + + l = pg_encoding_verifymbchar(encoding, (const char *) iso, len); + if (l < 0) + break; + + /* collect coded char of length l */ + if (l == 1) + b4 = *iso++; + else if (l == 2) + { + b3 = *iso++; + b4 = *iso++; + } + else if (l == 3) + { + b2 = *iso++; + b3 = *iso++; + b4 = *iso++; + } + else if (l == 4) + { + b1 = *iso++; + b2 = *iso++; + b3 = *iso++; + b4 = *iso++; + } + else + { + elog(ERROR, "unsupported character length %d", l); + iiso = 0; /* keep compiler quiet */ + } + iiso = (b1 << 24 | b2 << 16 | b3 << 8 | b4); + + if (map) + { + uint32 converted = pg_mb_radix_conv(map, l, b1, b2, b3, b4); + + if (converted) + { + utf = store_coded_char(utf, converted); + continue; + } + + /* If there's a combined character map, try that */ + if (cmap) + { + cp = bsearch(&iiso, cmap, cmapsize, + sizeof(pg_local_to_utf_combined), compare4); + + if (cp) + { + utf = store_coded_char(utf, cp->utf1); + utf = store_coded_char(utf, cp->utf2); + continue; + } + } + } + + /* if there's a conversion function, try that */ + if (conv_func) + { + uint32 converted = (*conv_func) (iiso); + + if (converted) + { + utf = store_coded_char(utf, converted); + continue; + } + } + + /* failed to translate this character */ + iso -= l; + if (noError) + break; + report_untranslatable_char(encoding, PG_UTF8, + (const char *) iso, len); + } + + /* if we broke out of loop early, must be invalid input */ + if (len > 0 && !noError) + report_invalid_encoding(encoding, (const char *) iso, len); + + *utf = '\0'; + + return iso - start; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/mb/mbutils.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/mb/mbutils.c new file mode 100644 index 00000000000..e4f8eaef365 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/mb/mbutils.c @@ -0,0 +1,1836 @@ +/*------------------------------------------------------------------------- + * + * mbutils.c + * This file contains functions for encoding conversion. + * + * The string-conversion functions in this file share some API quirks. + * Note the following: + * + * The functions return a palloc'd, null-terminated string if conversion + * is required. However, if no conversion is performed, the given source + * string pointer is returned as-is. + * + * Although the presence of a length argument means that callers can pass + * non-null-terminated strings, care is required because the same string + * will be passed back if no conversion occurs. Such callers *must* check + * whether result == src and handle that case differently. + * + * If the source and destination encodings are the same, the source string + * is returned without any verification; it's assumed to be valid data. + * If that might not be the case, the caller is responsible for validating + * the string using a separate call to pg_verify_mbstr(). Whenever the + * source and destination encodings are different, the functions ensure that + * the result is validly encoded according to the destination encoding. + * + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/mb/mbutils.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/xact.h" +#include "catalog/namespace.h" +#include "mb/pg_wchar.h" +#include "utils/builtins.h" +#include "utils/memutils.h" +#include "utils/syscache.h" +#include "varatt.h" + +/* + * We maintain a simple linked list caching the fmgr lookup info for the + * currently selected conversion functions, as well as any that have been + * selected previously in the current session. (We remember previous + * settings because we must be able to restore a previous setting during + * transaction rollback, without doing any fresh catalog accesses.) + * + * Since we'll never release this data, we just keep it in TopMemoryContext. + */ +typedef struct ConvProcInfo +{ + int s_encoding; /* server and client encoding IDs */ + int c_encoding; + FmgrInfo to_server_info; /* lookup info for conversion procs */ + FmgrInfo to_client_info; +} ConvProcInfo; + +static __thread List *ConvProcList = NIL; /* List of ConvProcInfo */ + +/* + * These variables point to the currently active conversion functions, + * or are NULL when no conversion is needed. + */ +static __thread FmgrInfo *ToServerConvProc = NULL; +static __thread FmgrInfo *ToClientConvProc = NULL; + +/* + * This variable stores the conversion function to convert from UTF-8 + * to the server encoding. It's NULL if the server encoding *is* UTF-8, + * or if we lack a conversion function for this. + */ +static __thread FmgrInfo *Utf8ToServerConvProc = NULL; + +/* + * These variables track the currently-selected encodings. + */ +static __thread const pg_enc2name *ClientEncoding = &pg_enc2name_tbl[PG_SQL_ASCII]; +static __thread const pg_enc2name *DatabaseEncoding = &pg_enc2name_tbl[PG_SQL_ASCII]; +static __thread const pg_enc2name *MessageEncoding = &pg_enc2name_tbl[PG_SQL_ASCII]; + +/* + * During backend startup we can't set client encoding because we (a) + * can't look up the conversion functions, and (b) may not know the database + * encoding yet either. So SetClientEncoding() just accepts anything and + * remembers it for InitializeClientEncoding() to apply later. + */ +static __thread bool backend_startup_complete = false; +static __thread int pending_client_encoding = PG_SQL_ASCII; + + +/* Internal functions */ +static char *perform_default_encoding_conversion(const char *src, + int len, bool is_client_to_server); +static int cliplen(const char *str, int len, int limit); + + +/* + * Prepare for a future call to SetClientEncoding. Success should mean + * that SetClientEncoding is guaranteed to succeed for this encoding request. + * + * (But note that success before backend_startup_complete does not guarantee + * success after ...) + * + * Returns 0 if okay, -1 if not (bad encoding or can't support conversion) + */ +int +PrepareClientEncoding(int encoding) +{ + int current_server_encoding; + ListCell *lc; + + if (!PG_VALID_FE_ENCODING(encoding)) + return -1; + + /* Can't do anything during startup, per notes above */ + if (!backend_startup_complete) + return 0; + + current_server_encoding = GetDatabaseEncoding(); + + /* + * Check for cases that require no conversion function. + */ + if (current_server_encoding == encoding || + current_server_encoding == PG_SQL_ASCII || + encoding == PG_SQL_ASCII) + return 0; + + if (IsTransactionState()) + { + /* + * If we're in a live transaction, it's safe to access the catalogs, + * so look up the functions. We repeat the lookup even if the info is + * already cached, so that we can react to changes in the contents of + * pg_conversion. + */ + Oid to_server_proc, + to_client_proc; + ConvProcInfo *convinfo; + MemoryContext oldcontext; + + to_server_proc = FindDefaultConversionProc(encoding, + current_server_encoding); + if (!OidIsValid(to_server_proc)) + return -1; + to_client_proc = FindDefaultConversionProc(current_server_encoding, + encoding); + if (!OidIsValid(to_client_proc)) + return -1; + + /* + * Load the fmgr info into TopMemoryContext (could still fail here) + */ + convinfo = (ConvProcInfo *) MemoryContextAlloc(TopMemoryContext, + sizeof(ConvProcInfo)); + convinfo->s_encoding = current_server_encoding; + convinfo->c_encoding = encoding; + fmgr_info_cxt(to_server_proc, &convinfo->to_server_info, + TopMemoryContext); + fmgr_info_cxt(to_client_proc, &convinfo->to_client_info, + TopMemoryContext); + + /* Attach new info to head of list */ + oldcontext = MemoryContextSwitchTo(TopMemoryContext); + ConvProcList = lcons(convinfo, ConvProcList); + MemoryContextSwitchTo(oldcontext); + + /* + * We cannot yet remove any older entry for the same encoding pair, + * since it could still be in use. SetClientEncoding will clean up. + */ + + return 0; /* success */ + } + else + { + /* + * If we're not in a live transaction, the only thing we can do is + * restore a previous setting using the cache. This covers all + * transaction-rollback cases. The only case it might not work for is + * trying to change client_encoding on the fly by editing + * postgresql.conf and SIGHUP'ing. Which would probably be a stupid + * thing to do anyway. + */ + foreach(lc, ConvProcList) + { + ConvProcInfo *oldinfo = (ConvProcInfo *) lfirst(lc); + + if (oldinfo->s_encoding == current_server_encoding && + oldinfo->c_encoding == encoding) + return 0; + } + + return -1; /* it's not cached, so fail */ + } +} + +/* + * Set the active client encoding and set up the conversion-function pointers. + * PrepareClientEncoding should have been called previously for this encoding. + * + * Returns 0 if okay, -1 if not (bad encoding or can't support conversion) + */ +int +SetClientEncoding(int encoding) +{ + int current_server_encoding; + bool found; + ListCell *lc; + + if (!PG_VALID_FE_ENCODING(encoding)) + return -1; + + /* Can't do anything during startup, per notes above */ + if (!backend_startup_complete) + { + pending_client_encoding = encoding; + return 0; + } + + current_server_encoding = GetDatabaseEncoding(); + + /* + * Check for cases that require no conversion function. + */ + if (current_server_encoding == encoding || + current_server_encoding == PG_SQL_ASCII || + encoding == PG_SQL_ASCII) + { + ClientEncoding = &pg_enc2name_tbl[encoding]; + ToServerConvProc = NULL; + ToClientConvProc = NULL; + return 0; + } + + /* + * Search the cache for the entry previously prepared by + * PrepareClientEncoding; if there isn't one, we lose. While at it, + * release any duplicate entries so that repeated Prepare/Set cycles don't + * leak memory. + */ + found = false; + foreach(lc, ConvProcList) + { + ConvProcInfo *convinfo = (ConvProcInfo *) lfirst(lc); + + if (convinfo->s_encoding == current_server_encoding && + convinfo->c_encoding == encoding) + { + if (!found) + { + /* Found newest entry, so set up */ + ClientEncoding = &pg_enc2name_tbl[encoding]; + ToServerConvProc = &convinfo->to_server_info; + ToClientConvProc = &convinfo->to_client_info; + found = true; + } + else + { + /* Duplicate entry, release it */ + ConvProcList = foreach_delete_current(ConvProcList, lc); + pfree(convinfo); + } + } + } + + if (found) + return 0; /* success */ + else + return -1; /* it's not cached, so fail */ +} + +/* + * Initialize client encoding conversions. + * Called from InitPostgres() once during backend startup. + */ +void +InitializeClientEncoding(void) +{ + int current_server_encoding; + + Assert(!backend_startup_complete); + backend_startup_complete = true; + + if (PrepareClientEncoding(pending_client_encoding) < 0 || + SetClientEncoding(pending_client_encoding) < 0) + { + /* + * Oops, the requested conversion is not available. We couldn't fail + * before, but we can now. + */ + ereport(FATAL, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("conversion between %s and %s is not supported", + pg_enc2name_tbl[pending_client_encoding].name, + GetDatabaseEncodingName()))); + } + + /* + * Also look up the UTF8-to-server conversion function if needed. Since + * the server encoding is fixed within any one backend process, we don't + * have to do this more than once. + */ + current_server_encoding = GetDatabaseEncoding(); + if (current_server_encoding != PG_UTF8 && + current_server_encoding != PG_SQL_ASCII) + { + Oid utf8_to_server_proc; + + Assert(IsTransactionState()); + utf8_to_server_proc = + FindDefaultConversionProc(PG_UTF8, + current_server_encoding); + /* If there's no such conversion, just leave the pointer as NULL */ + if (OidIsValid(utf8_to_server_proc)) + { + FmgrInfo *finfo; + + finfo = (FmgrInfo *) MemoryContextAlloc(TopMemoryContext, + sizeof(FmgrInfo)); + fmgr_info_cxt(utf8_to_server_proc, finfo, + TopMemoryContext); + /* Set Utf8ToServerConvProc only after data is fully valid */ + Utf8ToServerConvProc = finfo; + } + } +} + +/* + * returns the current client encoding + */ +int +pg_get_client_encoding(void) +{ + return ClientEncoding->encoding; +} + +/* + * returns the current client encoding name + */ +const char * +pg_get_client_encoding_name(void) +{ + return ClientEncoding->name; +} + +/* + * Convert src string to another encoding (general case). + * + * See the notes about string conversion functions at the top of this file. + */ +unsigned char * +pg_do_encoding_conversion(unsigned char *src, int len, + int src_encoding, int dest_encoding) +{ + unsigned char *result; + Oid proc; + + if (len <= 0) + return src; /* empty string is always valid */ + + if (src_encoding == dest_encoding) + return src; /* no conversion required, assume valid */ + + if (dest_encoding == PG_SQL_ASCII) + return src; /* any string is valid in SQL_ASCII */ + + if (src_encoding == PG_SQL_ASCII) + { + /* No conversion is possible, but we must validate the result */ + (void) pg_verify_mbstr(dest_encoding, (const char *) src, len, false); + return src; + } + + if (!IsTransactionState()) /* shouldn't happen */ + elog(ERROR, "cannot perform encoding conversion outside a transaction"); + + proc = FindDefaultConversionProc(src_encoding, dest_encoding); + if (!OidIsValid(proc)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("default conversion function for encoding \"%s\" to \"%s\" does not exist", + pg_encoding_to_char(src_encoding), + pg_encoding_to_char(dest_encoding)))); + + /* + * Allocate space for conversion result, being wary of integer overflow. + * + * len * MAX_CONVERSION_GROWTH is typically a vast overestimate of the + * required space, so it might exceed MaxAllocSize even though the result + * would actually fit. We do not want to hand back a result string that + * exceeds MaxAllocSize, because callers might not cope gracefully --- but + * if we just allocate more than that, and don't use it, that's fine. + */ + if ((Size) len >= (MaxAllocHugeSize / (Size) MAX_CONVERSION_GROWTH)) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("out of memory"), + errdetail("String of %d bytes is too long for encoding conversion.", + len))); + + result = (unsigned char *) + MemoryContextAllocHuge(CurrentMemoryContext, + (Size) len * MAX_CONVERSION_GROWTH + 1); + + (void) OidFunctionCall6(proc, + Int32GetDatum(src_encoding), + Int32GetDatum(dest_encoding), + CStringGetDatum((char *) src), + CStringGetDatum((char *) result), + Int32GetDatum(len), + BoolGetDatum(false)); + + /* + * If the result is large, it's worth repalloc'ing to release any extra + * space we asked for. The cutoff here is somewhat arbitrary, but we + * *must* check when len * MAX_CONVERSION_GROWTH exceeds MaxAllocSize. + */ + if (len > 1000000) + { + Size resultlen = strlen((char *) result); + + if (resultlen >= MaxAllocSize) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("out of memory"), + errdetail("String of %d bytes is too long for encoding conversion.", + len))); + + result = (unsigned char *) repalloc(result, resultlen + 1); + } + + return result; +} + +/* + * Convert src string to another encoding. + * + * This function has a different API than the other conversion functions. + * The caller should've looked up the conversion function using + * FindDefaultConversionProc(). Unlike the other functions, the converted + * result is not palloc'd. It is written to the caller-supplied buffer + * instead. + * + * src_encoding - encoding to convert from + * dest_encoding - encoding to convert to + * src, srclen - input buffer and its length in bytes + * dest, destlen - destination buffer and its size in bytes + * + * The output is null-terminated. + * + * If destlen < srclen * MAX_CONVERSION_INPUT_LENGTH + 1, the converted output + * wouldn't necessarily fit in the output buffer, and the function will not + * convert the whole input. + * + * TODO: The conversion function interface is not great. Firstly, it + * would be nice to pass through the destination buffer size to the + * conversion function, so that if you pass a shorter destination buffer, it + * could still continue to fill up the whole buffer. Currently, we have to + * assume worst case expansion and stop the conversion short, even if there + * is in fact space left in the destination buffer. Secondly, it would be + * nice to return the number of bytes written to the caller, to avoid a call + * to strlen(). + */ +int +pg_do_encoding_conversion_buf(Oid proc, + int src_encoding, + int dest_encoding, + unsigned char *src, int srclen, + unsigned char *dest, int destlen, + bool noError) +{ + Datum result; + + /* + * If the destination buffer is not large enough to hold the result in the + * worst case, limit the input size passed to the conversion function. + */ + if ((Size) srclen >= ((destlen - 1) / (Size) MAX_CONVERSION_GROWTH)) + srclen = ((destlen - 1) / (Size) MAX_CONVERSION_GROWTH); + + result = OidFunctionCall6(proc, + Int32GetDatum(src_encoding), + Int32GetDatum(dest_encoding), + CStringGetDatum((char *) src), + CStringGetDatum((char *) dest), + Int32GetDatum(srclen), + BoolGetDatum(noError)); + return DatumGetInt32(result); +} + +/* + * Convert string to encoding encoding_name. The source + * encoding is the DB encoding. + * + * BYTEA convert_to(TEXT string, NAME encoding_name) */ +Datum +pg_convert_to(PG_FUNCTION_ARGS) +{ + Datum string = PG_GETARG_DATUM(0); + Datum dest_encoding_name = PG_GETARG_DATUM(1); + Datum src_encoding_name = DirectFunctionCall1(namein, + CStringGetDatum(DatabaseEncoding->name)); + Datum result; + + /* + * pg_convert expects a bytea as its first argument. We're passing it a + * text argument here, relying on the fact that they are both in fact + * varlena types, and thus structurally identical. + */ + result = DirectFunctionCall3(pg_convert, string, + src_encoding_name, dest_encoding_name); + + PG_RETURN_DATUM(result); +} + +/* + * Convert string from encoding encoding_name. The destination + * encoding is the DB encoding. + * + * TEXT convert_from(BYTEA string, NAME encoding_name) */ +Datum +pg_convert_from(PG_FUNCTION_ARGS) +{ + Datum string = PG_GETARG_DATUM(0); + Datum src_encoding_name = PG_GETARG_DATUM(1); + Datum dest_encoding_name = DirectFunctionCall1(namein, + CStringGetDatum(DatabaseEncoding->name)); + Datum result; + + result = DirectFunctionCall3(pg_convert, string, + src_encoding_name, dest_encoding_name); + + /* + * pg_convert returns a bytea, which we in turn return as text, relying on + * the fact that they are both in fact varlena types, and thus + * structurally identical. Although not all bytea values are valid text, + * in this case it will be because we've told pg_convert to return one + * that is valid as text in the current database encoding. + */ + PG_RETURN_DATUM(result); +} + +/* + * Convert string between two arbitrary encodings. + * + * BYTEA convert(BYTEA string, NAME src_encoding_name, NAME dest_encoding_name) + */ +Datum +pg_convert(PG_FUNCTION_ARGS) +{ + bytea *string = PG_GETARG_BYTEA_PP(0); + char *src_encoding_name = NameStr(*PG_GETARG_NAME(1)); + int src_encoding = pg_char_to_encoding(src_encoding_name); + char *dest_encoding_name = NameStr(*PG_GETARG_NAME(2)); + int dest_encoding = pg_char_to_encoding(dest_encoding_name); + const char *src_str; + char *dest_str; + bytea *retval; + int len; + + if (src_encoding < 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid source encoding name \"%s\"", + src_encoding_name))); + if (dest_encoding < 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid destination encoding name \"%s\"", + dest_encoding_name))); + + /* make sure that source string is valid */ + len = VARSIZE_ANY_EXHDR(string); + src_str = VARDATA_ANY(string); + (void) pg_verify_mbstr(src_encoding, src_str, len, false); + + /* perform conversion */ + dest_str = (char *) pg_do_encoding_conversion((unsigned char *) unconstify(char *, src_str), + len, + src_encoding, + dest_encoding); + + /* update len if conversion actually happened */ + if (dest_str != src_str) + len = strlen(dest_str); + + /* + * build bytea data type structure. + */ + retval = (bytea *) palloc(len + VARHDRSZ); + SET_VARSIZE(retval, len + VARHDRSZ); + memcpy(VARDATA(retval), dest_str, len); + + if (dest_str != src_str) + pfree(dest_str); + + /* free memory if allocated by the toaster */ + PG_FREE_IF_COPY(string, 0); + + PG_RETURN_BYTEA_P(retval); +} + +/* + * get the length of the string considered as text in the specified + * encoding. Raises an error if the data is not valid in that + * encoding. + * + * INT4 length (BYTEA string, NAME src_encoding_name) + */ +Datum +length_in_encoding(PG_FUNCTION_ARGS) +{ + bytea *string = PG_GETARG_BYTEA_PP(0); + char *src_encoding_name = NameStr(*PG_GETARG_NAME(1)); + int src_encoding = pg_char_to_encoding(src_encoding_name); + const char *src_str; + int len; + int retval; + + if (src_encoding < 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid encoding name \"%s\"", + src_encoding_name))); + + len = VARSIZE_ANY_EXHDR(string); + src_str = VARDATA_ANY(string); + + retval = pg_verify_mbstr_len(src_encoding, src_str, len, false); + + PG_RETURN_INT32(retval); +} + +/* + * Get maximum multibyte character length in the specified encoding. + * + * Note encoding is specified numerically, not by name as above. + */ +Datum +pg_encoding_max_length_sql(PG_FUNCTION_ARGS) +{ + int encoding = PG_GETARG_INT32(0); + + if (PG_VALID_ENCODING(encoding)) + PG_RETURN_INT32(pg_wchar_table[encoding].maxmblen); + else + PG_RETURN_NULL(); +} + +/* + * Convert client encoding to server encoding. + * + * See the notes about string conversion functions at the top of this file. + */ +char * +pg_client_to_server(const char *s, int len) +{ + return pg_any_to_server(s, len, ClientEncoding->encoding); +} + +/* + * Convert any encoding to server encoding. + * + * See the notes about string conversion functions at the top of this file. + * + * Unlike the other string conversion functions, this will apply validation + * even if encoding == DatabaseEncoding->encoding. This is because this is + * used to process data coming in from outside the database, and we never + * want to just assume validity. + */ +char * +pg_any_to_server(const char *s, int len, int encoding) +{ + if (len <= 0) + return unconstify(char *, s); /* empty string is always valid */ + + if (encoding == DatabaseEncoding->encoding || + encoding == PG_SQL_ASCII) + { + /* + * No conversion is needed, but we must still validate the data. + */ + (void) pg_verify_mbstr(DatabaseEncoding->encoding, s, len, false); + return unconstify(char *, s); + } + + if (DatabaseEncoding->encoding == PG_SQL_ASCII) + { + /* + * No conversion is possible, but we must still validate the data, + * because the client-side code might have done string escaping using + * the selected client_encoding. If the client encoding is ASCII-safe + * then we just do a straight validation under that encoding. For an + * ASCII-unsafe encoding we have a problem: we dare not pass such data + * to the parser but we have no way to convert it. We compromise by + * rejecting the data if it contains any non-ASCII characters. + */ + if (PG_VALID_BE_ENCODING(encoding)) + (void) pg_verify_mbstr(encoding, s, len, false); + else + { + int i; + + for (i = 0; i < len; i++) + { + if (s[i] == '\0' || IS_HIGHBIT_SET(s[i])) + ereport(ERROR, + (errcode(ERRCODE_CHARACTER_NOT_IN_REPERTOIRE), + errmsg("invalid byte value for encoding \"%s\": 0x%02x", + pg_enc2name_tbl[PG_SQL_ASCII].name, + (unsigned char) s[i]))); + } + } + return unconstify(char *, s); + } + + /* Fast path if we can use cached conversion function */ + if (encoding == ClientEncoding->encoding) + return perform_default_encoding_conversion(s, len, true); + + /* General case ... will not work outside transactions */ + return (char *) pg_do_encoding_conversion((unsigned char *) unconstify(char *, s), + len, + encoding, + DatabaseEncoding->encoding); +} + +/* + * Convert server encoding to client encoding. + * + * See the notes about string conversion functions at the top of this file. + */ +char * +pg_server_to_client(const char *s, int len) +{ + return pg_server_to_any(s, len, ClientEncoding->encoding); +} + +/* + * Convert server encoding to any encoding. + * + * See the notes about string conversion functions at the top of this file. + */ +char * +pg_server_to_any(const char *s, int len, int encoding) +{ + if (len <= 0) + return unconstify(char *, s); /* empty string is always valid */ + + if (encoding == DatabaseEncoding->encoding || + encoding == PG_SQL_ASCII) + return unconstify(char *, s); /* assume data is valid */ + + if (DatabaseEncoding->encoding == PG_SQL_ASCII) + { + /* No conversion is possible, but we must validate the result */ + (void) pg_verify_mbstr(encoding, s, len, false); + return unconstify(char *, s); + } + + /* Fast path if we can use cached conversion function */ + if (encoding == ClientEncoding->encoding) + return perform_default_encoding_conversion(s, len, false); + + /* General case ... will not work outside transactions */ + return (char *) pg_do_encoding_conversion((unsigned char *) unconstify(char *, s), + len, + DatabaseEncoding->encoding, + encoding); +} + +/* + * Perform default encoding conversion using cached FmgrInfo. Since + * this function does not access database at all, it is safe to call + * outside transactions. If the conversion has not been set up by + * SetClientEncoding(), no conversion is performed. + */ +static char * +perform_default_encoding_conversion(const char *src, int len, + bool is_client_to_server) +{ + char *result; + int src_encoding, + dest_encoding; + FmgrInfo *flinfo; + + if (is_client_to_server) + { + src_encoding = ClientEncoding->encoding; + dest_encoding = DatabaseEncoding->encoding; + flinfo = ToServerConvProc; + } + else + { + src_encoding = DatabaseEncoding->encoding; + dest_encoding = ClientEncoding->encoding; + flinfo = ToClientConvProc; + } + + if (flinfo == NULL) + return unconstify(char *, src); + + /* + * Allocate space for conversion result, being wary of integer overflow. + * See comments in pg_do_encoding_conversion. + */ + if ((Size) len >= (MaxAllocHugeSize / (Size) MAX_CONVERSION_GROWTH)) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("out of memory"), + errdetail("String of %d bytes is too long for encoding conversion.", + len))); + + result = (char *) + MemoryContextAllocHuge(CurrentMemoryContext, + (Size) len * MAX_CONVERSION_GROWTH + 1); + + FunctionCall6(flinfo, + Int32GetDatum(src_encoding), + Int32GetDatum(dest_encoding), + CStringGetDatum(src), + CStringGetDatum(result), + Int32GetDatum(len), + BoolGetDatum(false)); + + /* + * Release extra space if there might be a lot --- see comments in + * pg_do_encoding_conversion. + */ + if (len > 1000000) + { + Size resultlen = strlen(result); + + if (resultlen >= MaxAllocSize) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("out of memory"), + errdetail("String of %d bytes is too long for encoding conversion.", + len))); + + result = (char *) repalloc(result, resultlen + 1); + } + + return result; +} + +/* + * Convert a single Unicode code point into a string in the server encoding. + * + * The code point given by "c" is converted and stored at *s, which must + * have at least MAX_UNICODE_EQUIVALENT_STRING+1 bytes available. + * The output will have a trailing '\0'. Throws error if the conversion + * cannot be performed. + * + * Note that this relies on having previously looked up any required + * conversion function. That's partly for speed but mostly because the parser + * may call this outside any transaction, or in an aborted transaction. + */ +void +pg_unicode_to_server(pg_wchar c, unsigned char *s) +{ + unsigned char c_as_utf8[MAX_MULTIBYTE_CHAR_LEN + 1]; + int c_as_utf8_len; + int server_encoding; + + /* + * Complain if invalid Unicode code point. The choice of errcode here is + * debatable, but really our caller should have checked this anyway. + */ + if (!is_valid_unicode_codepoint(c)) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid Unicode code point"))); + + /* Otherwise, if it's in ASCII range, conversion is trivial */ + if (c <= 0x7F) + { + s[0] = (unsigned char) c; + s[1] = '\0'; + return; + } + + /* If the server encoding is UTF-8, we just need to reformat the code */ + server_encoding = GetDatabaseEncoding(); + if (server_encoding == PG_UTF8) + { + unicode_to_utf8(c, s); + s[pg_utf_mblen(s)] = '\0'; + return; + } + + /* For all other cases, we must have a conversion function available */ + if (Utf8ToServerConvProc == NULL) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("conversion between %s and %s is not supported", + pg_enc2name_tbl[PG_UTF8].name, + GetDatabaseEncodingName()))); + + /* Construct UTF-8 source string */ + unicode_to_utf8(c, c_as_utf8); + c_as_utf8_len = pg_utf_mblen(c_as_utf8); + c_as_utf8[c_as_utf8_len] = '\0'; + + /* Convert, or throw error if we can't */ + FunctionCall6(Utf8ToServerConvProc, + Int32GetDatum(PG_UTF8), + Int32GetDatum(server_encoding), + CStringGetDatum((char *) c_as_utf8), + CStringGetDatum((char *) s), + Int32GetDatum(c_as_utf8_len), + BoolGetDatum(false)); +} + +/* + * Convert a single Unicode code point into a string in the server encoding. + * + * Same as pg_unicode_to_server(), except that we don't throw errors, + * but simply return false on conversion failure. + */ +bool +pg_unicode_to_server_noerror(pg_wchar c, unsigned char *s) +{ + unsigned char c_as_utf8[MAX_MULTIBYTE_CHAR_LEN + 1]; + int c_as_utf8_len; + int converted_len; + int server_encoding; + + /* Fail if invalid Unicode code point */ + if (!is_valid_unicode_codepoint(c)) + return false; + + /* Otherwise, if it's in ASCII range, conversion is trivial */ + if (c <= 0x7F) + { + s[0] = (unsigned char) c; + s[1] = '\0'; + return true; + } + + /* If the server encoding is UTF-8, we just need to reformat the code */ + server_encoding = GetDatabaseEncoding(); + if (server_encoding == PG_UTF8) + { + unicode_to_utf8(c, s); + s[pg_utf_mblen(s)] = '\0'; + return true; + } + + /* For all other cases, we must have a conversion function available */ + if (Utf8ToServerConvProc == NULL) + return false; + + /* Construct UTF-8 source string */ + unicode_to_utf8(c, c_as_utf8); + c_as_utf8_len = pg_utf_mblen(c_as_utf8); + c_as_utf8[c_as_utf8_len] = '\0'; + + /* Convert, but without throwing error if we can't */ + converted_len = DatumGetInt32(FunctionCall6(Utf8ToServerConvProc, + Int32GetDatum(PG_UTF8), + Int32GetDatum(server_encoding), + CStringGetDatum((char *) c_as_utf8), + CStringGetDatum((char *) s), + Int32GetDatum(c_as_utf8_len), + BoolGetDatum(true))); + + /* Conversion was successful iff it consumed the whole input */ + return (converted_len == c_as_utf8_len); +} + + +/* convert a multibyte string to a wchar */ +int +pg_mb2wchar(const char *from, pg_wchar *to) +{ + return pg_wchar_table[DatabaseEncoding->encoding].mb2wchar_with_len((const unsigned char *) from, to, strlen(from)); +} + +/* convert a multibyte string to a wchar with a limited length */ +int +pg_mb2wchar_with_len(const char *from, pg_wchar *to, int len) +{ + return pg_wchar_table[DatabaseEncoding->encoding].mb2wchar_with_len((const unsigned char *) from, to, len); +} + +/* same, with any encoding */ +int +pg_encoding_mb2wchar_with_len(int encoding, + const char *from, pg_wchar *to, int len) +{ + return pg_wchar_table[encoding].mb2wchar_with_len((const unsigned char *) from, to, len); +} + +/* convert a wchar string to a multibyte */ +int +pg_wchar2mb(const pg_wchar *from, char *to) +{ + return pg_wchar_table[DatabaseEncoding->encoding].wchar2mb_with_len(from, (unsigned char *) to, pg_wchar_strlen(from)); +} + +/* convert a wchar string to a multibyte with a limited length */ +int +pg_wchar2mb_with_len(const pg_wchar *from, char *to, int len) +{ + return pg_wchar_table[DatabaseEncoding->encoding].wchar2mb_with_len(from, (unsigned char *) to, len); +} + +/* same, with any encoding */ +int +pg_encoding_wchar2mb_with_len(int encoding, + const pg_wchar *from, char *to, int len) +{ + return pg_wchar_table[encoding].wchar2mb_with_len(from, (unsigned char *) to, len); +} + +/* returns the byte length of a multibyte character */ +int +pg_mblen(const char *mbstr) +{ + return pg_wchar_table[DatabaseEncoding->encoding].mblen((const unsigned char *) mbstr); +} + +/* returns the display length of a multibyte character */ +int +pg_dsplen(const char *mbstr) +{ + return pg_wchar_table[DatabaseEncoding->encoding].dsplen((const unsigned char *) mbstr); +} + +/* returns the length (counted in wchars) of a multibyte string */ +int +pg_mbstrlen(const char *mbstr) +{ + int len = 0; + + /* optimization for single byte encoding */ + if (pg_database_encoding_max_length() == 1) + return strlen(mbstr); + + while (*mbstr) + { + mbstr += pg_mblen(mbstr); + len++; + } + return len; +} + +/* returns the length (counted in wchars) of a multibyte string + * (not necessarily NULL terminated) + */ +int +pg_mbstrlen_with_len(const char *mbstr, int limit) +{ + int len = 0; + + /* optimization for single byte encoding */ + if (pg_database_encoding_max_length() == 1) + return limit; + + while (limit > 0 && *mbstr) + { + int l = pg_mblen(mbstr); + + limit -= l; + mbstr += l; + len++; + } + return len; +} + +/* + * returns the byte length of a multibyte string + * (not necessarily NULL terminated) + * that is no longer than limit. + * this function does not break multibyte character boundary. + */ +int +pg_mbcliplen(const char *mbstr, int len, int limit) +{ + return pg_encoding_mbcliplen(DatabaseEncoding->encoding, mbstr, + len, limit); +} + +/* + * pg_mbcliplen with specified encoding + */ +int +pg_encoding_mbcliplen(int encoding, const char *mbstr, + int len, int limit) +{ + mblen_converter mblen_fn; + int clen = 0; + int l; + + /* optimization for single byte encoding */ + if (pg_encoding_max_length(encoding) == 1) + return cliplen(mbstr, len, limit); + + mblen_fn = pg_wchar_table[encoding].mblen; + + while (len > 0 && *mbstr) + { + l = (*mblen_fn) ((const unsigned char *) mbstr); + if ((clen + l) > limit) + break; + clen += l; + if (clen == limit) + break; + len -= l; + mbstr += l; + } + return clen; +} + +/* + * Similar to pg_mbcliplen except the limit parameter specifies the + * character length, not the byte length. + */ +int +pg_mbcharcliplen(const char *mbstr, int len, int limit) +{ + int clen = 0; + int nch = 0; + int l; + + /* optimization for single byte encoding */ + if (pg_database_encoding_max_length() == 1) + return cliplen(mbstr, len, limit); + + while (len > 0 && *mbstr) + { + l = pg_mblen(mbstr); + nch++; + if (nch > limit) + break; + clen += l; + len -= l; + mbstr += l; + } + return clen; +} + +/* mbcliplen for any single-byte encoding */ +static int +cliplen(const char *str, int len, int limit) +{ + int l = 0; + + len = Min(len, limit); + while (l < len && str[l]) + l++; + return l; +} + +void +SetDatabaseEncoding(int encoding) +{ + if (!PG_VALID_BE_ENCODING(encoding)) + elog(ERROR, "invalid database encoding: %d", encoding); + + DatabaseEncoding = &pg_enc2name_tbl[encoding]; + Assert(DatabaseEncoding->encoding == encoding); +} + +void +SetMessageEncoding(int encoding) +{ + /* Some calls happen before we can elog()! */ + Assert(PG_VALID_ENCODING(encoding)); + + MessageEncoding = &pg_enc2name_tbl[encoding]; + Assert(MessageEncoding->encoding == encoding); +} + +#ifdef ENABLE_NLS +/* + * Make one bind_textdomain_codeset() call, translating a pg_enc to a gettext + * codeset. Fails for MULE_INTERNAL, an encoding unknown to gettext; can also + * fail for gettext-internal causes like out-of-memory. + */ +static bool +raw_pg_bind_textdomain_codeset(const char *domainname, int encoding) +{ + bool elog_ok = (CurrentMemoryContext != NULL); + int i; + + for (i = 0; pg_enc2gettext_tbl[i].name != NULL; i++) + { + if (pg_enc2gettext_tbl[i].encoding == encoding) + { + if (bind_textdomain_codeset(domainname, + pg_enc2gettext_tbl[i].name) != NULL) + return true; + + if (elog_ok) + elog(LOG, "bind_textdomain_codeset failed"); + else + write_stderr("bind_textdomain_codeset failed"); + + break; + } + } + + return false; +} + +/* + * Bind a gettext message domain to the codeset corresponding to the database + * encoding. For SQL_ASCII, instead bind to the codeset implied by LC_CTYPE. + * Return the MessageEncoding implied by the new settings. + * + * On most platforms, gettext defaults to the codeset implied by LC_CTYPE. + * When that matches the database encoding, we don't need to do anything. In + * CREATE DATABASE, we enforce or trust that the locale's codeset matches the + * database encoding, except for the C locale. (On Windows, we also permit a + * discrepancy under the UTF8 encoding.) For the C locale, explicitly bind + * gettext to the right codeset. + * + * On Windows, gettext defaults to the Windows ANSI code page. This is a + * convenient departure for software that passes the strings to Windows ANSI + * APIs, but we don't do that. Compel gettext to use database encoding or, + * failing that, the LC_CTYPE encoding as it would on other platforms. + * + * This function is called before elog() and palloc() are usable. + */ +int +pg_bind_textdomain_codeset(const char *domainname) +{ + bool elog_ok = (CurrentMemoryContext != NULL); + int encoding = GetDatabaseEncoding(); + int new_msgenc; + +#ifndef WIN32 + const char *ctype = setlocale(LC_CTYPE, NULL); + + if (pg_strcasecmp(ctype, "C") == 0 || pg_strcasecmp(ctype, "POSIX") == 0) +#endif + if (encoding != PG_SQL_ASCII && + raw_pg_bind_textdomain_codeset(domainname, encoding)) + return encoding; + + new_msgenc = pg_get_encoding_from_locale(NULL, elog_ok); + if (new_msgenc < 0) + new_msgenc = PG_SQL_ASCII; + +#ifdef WIN32 + if (!raw_pg_bind_textdomain_codeset(domainname, new_msgenc)) + /* On failure, the old message encoding remains valid. */ + return GetMessageEncoding(); +#endif + + return new_msgenc; +} +#endif + +/* + * The database encoding, also called the server encoding, represents the + * encoding of data stored in text-like data types. Affected types include + * cstring, text, varchar, name, xml, and json. + */ +int +GetDatabaseEncoding(void) +{ + return DatabaseEncoding->encoding; +} + +const char * +GetDatabaseEncodingName(void) +{ + return DatabaseEncoding->name; +} + +Datum +getdatabaseencoding(PG_FUNCTION_ARGS) +{ + return DirectFunctionCall1(namein, CStringGetDatum(DatabaseEncoding->name)); +} + +Datum +pg_client_encoding(PG_FUNCTION_ARGS) +{ + return DirectFunctionCall1(namein, CStringGetDatum(ClientEncoding->name)); +} + +Datum +PG_char_to_encoding(PG_FUNCTION_ARGS) +{ + Name s = PG_GETARG_NAME(0); + + PG_RETURN_INT32(pg_char_to_encoding(NameStr(*s))); +} + +Datum +PG_encoding_to_char(PG_FUNCTION_ARGS) +{ + int32 encoding = PG_GETARG_INT32(0); + const char *encoding_name = pg_encoding_to_char(encoding); + + return DirectFunctionCall1(namein, CStringGetDatum(encoding_name)); +} + +/* + * gettext() returns messages in this encoding. This often matches the + * database encoding, but it differs for SQL_ASCII databases, for processes + * not attached to a database, and under a database encoding lacking iconv + * support (MULE_INTERNAL). + */ +int +GetMessageEncoding(void) +{ + return MessageEncoding->encoding; +} + + +/* + * Generic character incrementer function. + * + * Not knowing anything about the properties of the encoding in use, we just + * keep incrementing the last byte until we get a validly-encoded result, + * or we run out of values to try. We don't bother to try incrementing + * higher-order bytes, so there's no growth in runtime for wider characters. + * (If we did try to do that, we'd need to consider the likelihood that 255 + * is not a valid final byte in the encoding.) + */ +static bool +pg_generic_charinc(unsigned char *charptr, int len) +{ + unsigned char *lastbyte = charptr + len - 1; + mbchar_verifier mbverify; + + /* We can just invoke the character verifier directly. */ + mbverify = pg_wchar_table[GetDatabaseEncoding()].mbverifychar; + + while (*lastbyte < (unsigned char) 255) + { + (*lastbyte)++; + if ((*mbverify) (charptr, len) == len) + return true; + } + + return false; +} + +/* + * UTF-8 character incrementer function. + * + * For a one-byte character less than 0x7F, we just increment the byte. + * + * For a multibyte character, every byte but the first must fall between 0x80 + * and 0xBF; and the first byte must be between 0xC0 and 0xF4. We increment + * the last byte that's not already at its maximum value. If we can't find a + * byte that's less than the maximum allowable value, we simply fail. We also + * need some special-case logic to skip regions used for surrogate pair + * handling, as those should not occur in valid UTF-8. + * + * Note that we don't reset lower-order bytes back to their minimums, since + * we can't afford to make an exhaustive search (see make_greater_string). + */ +static bool +pg_utf8_increment(unsigned char *charptr, int length) +{ + unsigned char a; + unsigned char limit; + + switch (length) + { + default: + /* reject lengths 5 and 6 for now */ + return false; + case 4: + a = charptr[3]; + if (a < 0xBF) + { + charptr[3]++; + break; + } + /* FALL THRU */ + case 3: + a = charptr[2]; + if (a < 0xBF) + { + charptr[2]++; + break; + } + /* FALL THRU */ + case 2: + a = charptr[1]; + switch (*charptr) + { + case 0xED: + limit = 0x9F; + break; + case 0xF4: + limit = 0x8F; + break; + default: + limit = 0xBF; + break; + } + if (a < limit) + { + charptr[1]++; + break; + } + /* FALL THRU */ + case 1: + a = *charptr; + if (a == 0x7F || a == 0xDF || a == 0xEF || a == 0xF4) + return false; + charptr[0]++; + break; + } + + return true; +} + +/* + * EUC-JP character incrementer function. + * + * If the sequence starts with SS2 (0x8e), it must be a two-byte sequence + * representing JIS X 0201 characters with the second byte ranging between + * 0xa1 and 0xdf. We just increment the last byte if it's less than 0xdf, + * and otherwise rewrite the whole sequence to 0xa1 0xa1. + * + * If the sequence starts with SS3 (0x8f), it must be a three-byte sequence + * in which the last two bytes range between 0xa1 and 0xfe. The last byte + * is incremented if possible, otherwise the second-to-last byte. + * + * If the sequence starts with a value other than the above and its MSB + * is set, it must be a two-byte sequence representing JIS X 0208 characters + * with both bytes ranging between 0xa1 and 0xfe. The last byte is + * incremented if possible, otherwise the second-to-last byte. + * + * Otherwise, the sequence is a single-byte ASCII character. It is + * incremented up to 0x7f. + */ +static bool +pg_eucjp_increment(unsigned char *charptr, int length) +{ + unsigned char c1, + c2; + int i; + + c1 = *charptr; + + switch (c1) + { + case SS2: /* JIS X 0201 */ + if (length != 2) + return false; + + c2 = charptr[1]; + + if (c2 >= 0xdf) + charptr[0] = charptr[1] = 0xa1; + else if (c2 < 0xa1) + charptr[1] = 0xa1; + else + charptr[1]++; + break; + + case SS3: /* JIS X 0212 */ + if (length != 3) + return false; + + for (i = 2; i > 0; i--) + { + c2 = charptr[i]; + if (c2 < 0xa1) + { + charptr[i] = 0xa1; + return true; + } + else if (c2 < 0xfe) + { + charptr[i]++; + return true; + } + } + + /* Out of 3-byte code region */ + return false; + + default: + if (IS_HIGHBIT_SET(c1)) /* JIS X 0208? */ + { + if (length != 2) + return false; + + for (i = 1; i >= 0; i--) + { + c2 = charptr[i]; + if (c2 < 0xa1) + { + charptr[i] = 0xa1; + return true; + } + else if (c2 < 0xfe) + { + charptr[i]++; + return true; + } + } + + /* Out of 2 byte code region */ + return false; + } + else + { /* ASCII, single byte */ + if (c1 > 0x7e) + return false; + (*charptr)++; + } + break; + } + + return true; +} + +/* + * get the character incrementer for the encoding for the current database + */ +mbcharacter_incrementer +pg_database_encoding_character_incrementer(void) +{ + /* + * Eventually it might be best to add a field to pg_wchar_table[], but for + * now we just use a switch. + */ + switch (GetDatabaseEncoding()) + { + case PG_UTF8: + return pg_utf8_increment; + + case PG_EUC_JP: + return pg_eucjp_increment; + + default: + return pg_generic_charinc; + } +} + +/* + * fetch maximum length of the encoding for the current database + */ +int +pg_database_encoding_max_length(void) +{ + return pg_wchar_table[GetDatabaseEncoding()].maxmblen; +} + +/* + * Verify mbstr to make sure that it is validly encoded in the current + * database encoding. Otherwise same as pg_verify_mbstr(). + */ +bool +pg_verifymbstr(const char *mbstr, int len, bool noError) +{ + return pg_verify_mbstr(GetDatabaseEncoding(), mbstr, len, noError); +} + +/* + * Verify mbstr to make sure that it is validly encoded in the specified + * encoding. + */ +bool +pg_verify_mbstr(int encoding, const char *mbstr, int len, bool noError) +{ + int oklen; + + Assert(PG_VALID_ENCODING(encoding)); + + oklen = pg_wchar_table[encoding].mbverifystr((const unsigned char *) mbstr, len); + if (oklen != len) + { + if (noError) + return false; + report_invalid_encoding(encoding, mbstr + oklen, len - oklen); + } + return true; +} + +/* + * Verify mbstr to make sure that it is validly encoded in the specified + * encoding. + * + * mbstr is not necessarily zero terminated; length of mbstr is + * specified by len. + * + * If OK, return length of string in the encoding. + * If a problem is found, return -1 when noError is + * true; when noError is false, ereport() a descriptive message. + * + * Note: We cannot use the faster encoding-specific mbverifystr() function + * here, because we need to count the number of characters in the string. + */ +int +pg_verify_mbstr_len(int encoding, const char *mbstr, int len, bool noError) +{ + mbchar_verifier mbverifychar; + int mb_len; + + Assert(PG_VALID_ENCODING(encoding)); + + /* + * In single-byte encodings, we need only reject nulls (\0). + */ + if (pg_encoding_max_length(encoding) <= 1) + { + const char *nullpos = memchr(mbstr, 0, len); + + if (nullpos == NULL) + return len; + if (noError) + return -1; + report_invalid_encoding(encoding, nullpos, 1); + } + + /* fetch function pointer just once */ + mbverifychar = pg_wchar_table[encoding].mbverifychar; + + mb_len = 0; + + while (len > 0) + { + int l; + + /* fast path for ASCII-subset characters */ + if (!IS_HIGHBIT_SET(*mbstr)) + { + if (*mbstr != '\0') + { + mb_len++; + mbstr++; + len--; + continue; + } + if (noError) + return -1; + report_invalid_encoding(encoding, mbstr, len); + } + + l = (*mbverifychar) ((const unsigned char *) mbstr, len); + + if (l < 0) + { + if (noError) + return -1; + report_invalid_encoding(encoding, mbstr, len); + } + + mbstr += l; + len -= l; + mb_len++; + } + return mb_len; +} + +/* + * check_encoding_conversion_args: check arguments of a conversion function + * + * "expected" arguments can be either an encoding ID or -1 to indicate that + * the caller will check whether it accepts the ID. + * + * Note: the errors here are not really user-facing, so elog instead of + * ereport seems sufficient. Also, we trust that the "expected" encoding + * arguments are valid encoding IDs, but we don't trust the actuals. + */ +void +check_encoding_conversion_args(int src_encoding, + int dest_encoding, + int len, + int expected_src_encoding, + int expected_dest_encoding) +{ + if (!PG_VALID_ENCODING(src_encoding)) + elog(ERROR, "invalid source encoding ID: %d", src_encoding); + if (src_encoding != expected_src_encoding && expected_src_encoding >= 0) + elog(ERROR, "expected source encoding \"%s\", but got \"%s\"", + pg_enc2name_tbl[expected_src_encoding].name, + pg_enc2name_tbl[src_encoding].name); + if (!PG_VALID_ENCODING(dest_encoding)) + elog(ERROR, "invalid destination encoding ID: %d", dest_encoding); + if (dest_encoding != expected_dest_encoding && expected_dest_encoding >= 0) + elog(ERROR, "expected destination encoding \"%s\", but got \"%s\"", + pg_enc2name_tbl[expected_dest_encoding].name, + pg_enc2name_tbl[dest_encoding].name); + if (len < 0) + elog(ERROR, "encoding conversion length must not be negative"); +} + +/* + * report_invalid_encoding: complain about invalid multibyte character + * + * note: len is remaining length of string, not length of character; + * len must be greater than zero, as we always examine the first byte. + */ +void +report_invalid_encoding(int encoding, const char *mbstr, int len) +{ + int l = pg_encoding_mblen(encoding, mbstr); + char buf[8 * 5 + 1]; + char *p = buf; + int j, + jlimit; + + jlimit = Min(l, len); + jlimit = Min(jlimit, 8); /* prevent buffer overrun */ + + for (j = 0; j < jlimit; j++) + { + p += sprintf(p, "0x%02x", (unsigned char) mbstr[j]); + if (j < jlimit - 1) + p += sprintf(p, " "); + } + + ereport(ERROR, + (errcode(ERRCODE_CHARACTER_NOT_IN_REPERTOIRE), + errmsg("invalid byte sequence for encoding \"%s\": %s", + pg_enc2name_tbl[encoding].name, + buf))); +} + +/* + * report_untranslatable_char: complain about untranslatable character + * + * note: len is remaining length of string, not length of character; + * len must be greater than zero, as we always examine the first byte. + */ +void +report_untranslatable_char(int src_encoding, int dest_encoding, + const char *mbstr, int len) +{ + int l = pg_encoding_mblen(src_encoding, mbstr); + char buf[8 * 5 + 1]; + char *p = buf; + int j, + jlimit; + + jlimit = Min(l, len); + jlimit = Min(jlimit, 8); /* prevent buffer overrun */ + + for (j = 0; j < jlimit; j++) + { + p += sprintf(p, "0x%02x", (unsigned char) mbstr[j]); + if (j < jlimit - 1) + p += sprintf(p, " "); + } + + ereport(ERROR, + (errcode(ERRCODE_UNTRANSLATABLE_CHARACTER), + errmsg("character with byte sequence %s in encoding \"%s\" has no equivalent in encoding \"%s\"", + buf, + pg_enc2name_tbl[src_encoding].name, + pg_enc2name_tbl[dest_encoding].name))); +} + + +#ifdef WIN32 +/* + * Convert from MessageEncoding to a palloc'ed, null-terminated utf16 + * string. The character length is also passed to utf16len if not + * null. Returns NULL iff failed. Before MessageEncoding initialization, "str" + * should be ASCII-only; this will function as though MessageEncoding is UTF8. + */ +WCHAR * +pgwin32_message_to_UTF16(const char *str, int len, int *utf16len) +{ + int msgenc = GetMessageEncoding(); + WCHAR *utf16; + int dstlen; + UINT codepage; + + if (msgenc == PG_SQL_ASCII) + /* No conversion is possible, and SQL_ASCII is never utf16. */ + return NULL; + + codepage = pg_enc2name_tbl[msgenc].codepage; + + /* + * Use MultiByteToWideChar directly if there is a corresponding codepage, + * or double conversion through UTF8 if not. Double conversion is needed, + * for example, in an ENCODING=LATIN8, LC_CTYPE=C database. + */ + if (codepage != 0) + { + utf16 = (WCHAR *) palloc(sizeof(WCHAR) * (len + 1)); + dstlen = MultiByteToWideChar(codepage, 0, str, len, utf16, len); + utf16[dstlen] = (WCHAR) 0; + } + else + { + char *utf8; + + /* + * XXX pg_do_encoding_conversion() requires a transaction. In the + * absence of one, hope for the input to be valid UTF8. + */ + if (IsTransactionState()) + { + utf8 = (char *) pg_do_encoding_conversion((unsigned char *) str, + len, + msgenc, + PG_UTF8); + if (utf8 != str) + len = strlen(utf8); + } + else + utf8 = (char *) str; + + utf16 = (WCHAR *) palloc(sizeof(WCHAR) * (len + 1)); + dstlen = MultiByteToWideChar(CP_UTF8, 0, utf8, len, utf16, len); + utf16[dstlen] = (WCHAR) 0; + + if (utf8 != str) + pfree(utf8); + } + + if (dstlen == 0 && len > 0) + { + pfree(utf16); + return NULL; /* error */ + } + + if (utf16len) + *utf16len = dstlen; + return utf16; +} + +#endif /* WIN32 */ diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/mb/stringinfo_mb.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/mb/stringinfo_mb.c new file mode 100644 index 00000000000..67a958d72be --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/mb/stringinfo_mb.c @@ -0,0 +1,86 @@ +/*------------------------------------------------------------------------- + * + * stringinfo_mb.c + * Multibyte encoding-aware additional StringInfo facilities + * + * This is separate from common/stringinfo.c so that frontend users + * of that file need not pull in unnecessary multibyte-encoding support + * code. + * + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/mb/stringinfo_mb.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "mb/stringinfo_mb.h" +#include "mb/pg_wchar.h" + + +/* + * appendStringInfoStringQuoted + * + * Append up to maxlen bytes from s to str, or the whole input string if + * maxlen < 0, adding single quotes around it and doubling all single quotes. + * Add an ellipsis if the copy is incomplete. + */ +void +appendStringInfoStringQuoted(StringInfo str, const char *s, int maxlen) +{ + char *copy = NULL; + const char *chunk_search_start, + *chunk_copy_start, + *chunk_end; + int slen; + bool ellipsis; + + Assert(str != NULL); + + slen = strlen(s); + if (maxlen >= 0 && maxlen < slen) + { + int finallen = pg_mbcliplen(s, slen, maxlen); + + copy = pnstrdup(s, finallen); + chunk_search_start = copy; + chunk_copy_start = copy; + + ellipsis = true; + } + else + { + chunk_search_start = s; + chunk_copy_start = s; + + ellipsis = false; + } + + appendStringInfoCharMacro(str, '\''); + + while ((chunk_end = strchr(chunk_search_start, '\'')) != NULL) + { + /* copy including the found delimiting ' */ + appendBinaryStringInfoNT(str, + chunk_copy_start, + chunk_end - chunk_copy_start + 1); + + /* in order to double it, include this ' into the next chunk as well */ + chunk_copy_start = chunk_end; + chunk_search_start = chunk_end + 1; + } + + /* copy the last chunk and terminate */ + if (ellipsis) + appendStringInfo(str, "%s...'", chunk_copy_start); + else + appendStringInfo(str, "%s'", chunk_copy_start); + + if (copy) + pfree(copy); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/mb/wstrcmp.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/mb/wstrcmp.c new file mode 100644 index 00000000000..dad3ae023a3 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/mb/wstrcmp.c @@ -0,0 +1,47 @@ +/* + * src/backend/utils/mb/wstrcmp.c + * + *- + * Copyright (c) 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Chris Torek. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +/* can be used in either frontend or backend */ +#include "postgres_fe.h" + +#include "mb/pg_wchar.h" + +int +pg_char_and_wchar_strcmp(const char *s1, const pg_wchar *s2) +{ + while ((pg_wchar) *s1 == *s2++) + if (*s1++ == 0) + return 0; + return *(const unsigned char *) s1 - *(const pg_wchar *) (s2 - 1); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/mb/wstrncmp.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/mb/wstrncmp.c new file mode 100644 index 00000000000..ea4823fc6f8 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/mb/wstrncmp.c @@ -0,0 +1,77 @@ +/* + * src/backend/utils/mb/wstrncmp.c + * + * + * Copyright (c) 1989, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from FreeBSD 2.2.1-RELEASE software. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +/* can be used in either frontend or backend */ +#include "postgres_fe.h" + +#include "mb/pg_wchar.h" + +int +pg_wchar_strncmp(const pg_wchar *s1, const pg_wchar *s2, size_t n) +{ + if (n == 0) + return 0; + do + { + if (*s1 != *s2++) + return (*s1 - *(s2 - 1)); + if (*s1++ == 0) + break; + } while (--n != 0); + return 0; +} + +int +pg_char_and_wchar_strncmp(const char *s1, const pg_wchar *s2, size_t n) +{ + if (n == 0) + return 0; + do + { + if ((pg_wchar) ((unsigned char) *s1) != *s2++) + return ((pg_wchar) ((unsigned char) *s1) - *(s2 - 1)); + if (*s1++ == 0) + break; + } while (--n != 0); + return 0; +} + +size_t +pg_wchar_strlen(const pg_wchar *str) +{ + const pg_wchar *s; + + for (s = str; *s; ++s) + ; + return (s - str); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/misc/conffiles.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/misc/conffiles.c new file mode 100644 index 00000000000..376a5c885bb --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/misc/conffiles.c @@ -0,0 +1,164 @@ +/*-------------------------------------------------------------------- + * conffiles.c + * + * Utilities related to the handling of configuration files. + * + * This file contains some generic tools to work on configuration files + * used by PostgreSQL, be they related to GUCs or authentication. + * + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/utils/misc/conffiles.c + * + *-------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include <dirent.h> + +#include "common/file_utils.h" +#include "miscadmin.h" +#include "storage/fd.h" +#include "utils/conffiles.h" + +/* + * AbsoluteConfigLocation + * + * Given a configuration file or directory location that may be a relative + * path, return an absolute one. We consider the location to be relative to + * the directory holding the calling file, or to DataDir if no calling file. + */ +char * +AbsoluteConfigLocation(const char *location, const char *calling_file) +{ + if (is_absolute_path(location)) + return pstrdup(location); + else + { + char abs_path[MAXPGPATH]; + + if (calling_file != NULL) + { + strlcpy(abs_path, calling_file, sizeof(abs_path)); + get_parent_directory(abs_path); + join_path_components(abs_path, abs_path, location); + canonicalize_path(abs_path); + } + else + { + Assert(DataDir); + join_path_components(abs_path, DataDir, location); + canonicalize_path(abs_path); + } + return pstrdup(abs_path); + } +} + + +/* + * GetConfFilesInDir + * + * Returns the list of config files located in a directory, in alphabetical + * order. On error, returns NULL with details about the error stored in + * "err_msg". + */ +char ** +GetConfFilesInDir(const char *includedir, const char *calling_file, + int elevel, int *num_filenames, char **err_msg) +{ + char *directory; + DIR *d; + struct dirent *de; + char **filenames = NULL; + int size_filenames; + + /* + * Reject directory name that is all-blank (including empty), as that + * leads to confusion --- we'd read the containing directory, typically + * resulting in recursive inclusion of the same file(s). + */ + if (strspn(includedir, " \t\r\n") == strlen(includedir)) + { + ereport(elevel, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("empty configuration directory name: \"%s\"", + includedir))); + *err_msg = "empty configuration directory name"; + return NULL; + } + + directory = AbsoluteConfigLocation(includedir, calling_file); + d = AllocateDir(directory); + if (d == NULL) + { + ereport(elevel, + (errcode_for_file_access(), + errmsg("could not open configuration directory \"%s\": %m", + directory))); + *err_msg = psprintf("could not open directory \"%s\"", directory); + goto cleanup; + } + + /* + * Read the directory and put the filenames in an array, so we can sort + * them prior to caller processing the contents. + */ + size_filenames = 32; + filenames = (char **) palloc(size_filenames * sizeof(char *)); + *num_filenames = 0; + + while ((de = ReadDir(d, directory)) != NULL) + { + PGFileType de_type; + char filename[MAXPGPATH]; + + /* + * Only parse files with names ending in ".conf". Explicitly reject + * files starting with ".". This excludes things like "." and "..", + * as well as typical hidden files, backup files, and editor debris. + */ + if (strlen(de->d_name) < 6) + continue; + if (de->d_name[0] == '.') + continue; + if (strcmp(de->d_name + strlen(de->d_name) - 5, ".conf") != 0) + continue; + + join_path_components(filename, directory, de->d_name); + canonicalize_path(filename); + de_type = get_dirent_type(filename, de, true, elevel); + if (de_type == PGFILETYPE_ERROR) + { + *err_msg = psprintf("could not stat file \"%s\"", filename); + pfree(filenames); + filenames = NULL; + goto cleanup; + } + else if (de_type != PGFILETYPE_DIR) + { + /* Add file to array, increasing its size in blocks of 32 */ + if (*num_filenames >= size_filenames) + { + size_filenames += 32; + filenames = (char **) repalloc(filenames, + size_filenames * sizeof(char *)); + } + filenames[*num_filenames] = pstrdup(filename); + (*num_filenames)++; + } + } + + /* Sort the files by name before leaving */ + if (*num_filenames > 0) + qsort(filenames, *num_filenames, sizeof(char *), pg_qsort_strcmp); + +cleanup: + if (d) + FreeDir(d); + pfree(directory); + return filenames; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/misc/guc-file.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/misc/guc-file.c new file mode 100644 index 00000000000..7f6dd3943aa --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/misc/guc-file.c @@ -0,0 +1,2736 @@ +#line 2 "guc-file.c" +/* + * Scanner for the configuration file + * + * Copyright (c) 2000-2023, PostgreSQL Global Development Group + * + * src/backend/utils/misc/guc-file.l + */ + +#include "postgres.h" + +#include <ctype.h> +#include <unistd.h> + +#include "common/file_utils.h" +#include "guc_internal.h" +#include "mb/pg_wchar.h" +#include "miscadmin.h" +#include "storage/fd.h" +#include "utils/conffiles.h" +#include "utils/memutils.h" + +#line 24 "guc-file.c" + +#define YY_INT_ALIGNED short int + +/* A lexical scanner generated by flex */ + +#define yy_create_buffer GUC_yy_create_buffer +#define yy_delete_buffer GUC_yy_delete_buffer +#define yy_scan_buffer GUC_yy_scan_buffer +#define yy_scan_string GUC_yy_scan_string +#define yy_scan_bytes GUC_yy_scan_bytes +#define yy_init_buffer GUC_yy_init_buffer +#define yy_flush_buffer GUC_yy_flush_buffer +#define yy_load_buffer_state GUC_yy_load_buffer_state +#define yy_switch_to_buffer GUC_yy_switch_to_buffer +#define yypush_buffer_state GUC_yypush_buffer_state +#define yypop_buffer_state GUC_yypop_buffer_state +#define yyensure_buffer_stack GUC_yyensure_buffer_stack +#define yy_flex_debug GUC_yy_flex_debug +#define yyin GUC_yyin +#define yyleng GUC_yyleng +#define yylex GUC_yylex +#define yylineno GUC_yylineno +#define yyout GUC_yyout +#define yyrestart GUC_yyrestart +#define yytext GUC_yytext +#define yywrap GUC_yywrap +#define yyalloc GUC_yyalloc +#define yyrealloc GUC_yyrealloc +#define yyfree GUC_yyfree + +#define FLEX_SCANNER +#define YY_FLEX_MAJOR_VERSION 2 +#define YY_FLEX_MINOR_VERSION 6 +#define YY_FLEX_SUBMINOR_VERSION 4 +#if YY_FLEX_SUBMINOR_VERSION > 0 +#define FLEX_BETA +#endif + +#ifdef yy_create_buffer +#define GUC_yy_create_buffer_ALREADY_DEFINED +#else +#define yy_create_buffer GUC_yy_create_buffer +#endif + +#ifdef yy_delete_buffer +#define GUC_yy_delete_buffer_ALREADY_DEFINED +#else +#define yy_delete_buffer GUC_yy_delete_buffer +#endif + +#ifdef yy_scan_buffer +#define GUC_yy_scan_buffer_ALREADY_DEFINED +#else +#define yy_scan_buffer GUC_yy_scan_buffer +#endif + +#ifdef yy_scan_string +#define GUC_yy_scan_string_ALREADY_DEFINED +#else +#define yy_scan_string GUC_yy_scan_string +#endif + +#ifdef yy_scan_bytes +#define GUC_yy_scan_bytes_ALREADY_DEFINED +#else +#define yy_scan_bytes GUC_yy_scan_bytes +#endif + +#ifdef yy_init_buffer +#define GUC_yy_init_buffer_ALREADY_DEFINED +#else +#define yy_init_buffer GUC_yy_init_buffer +#endif + +#ifdef yy_flush_buffer +#define GUC_yy_flush_buffer_ALREADY_DEFINED +#else +#define yy_flush_buffer GUC_yy_flush_buffer +#endif + +#ifdef yy_load_buffer_state +#define GUC_yy_load_buffer_state_ALREADY_DEFINED +#else +#define yy_load_buffer_state GUC_yy_load_buffer_state +#endif + +#ifdef yy_switch_to_buffer +#define GUC_yy_switch_to_buffer_ALREADY_DEFINED +#else +#define yy_switch_to_buffer GUC_yy_switch_to_buffer +#endif + +#ifdef yypush_buffer_state +#define GUC_yypush_buffer_state_ALREADY_DEFINED +#else +#define yypush_buffer_state GUC_yypush_buffer_state +#endif + +#ifdef yypop_buffer_state +#define GUC_yypop_buffer_state_ALREADY_DEFINED +#else +#define yypop_buffer_state GUC_yypop_buffer_state +#endif + +#ifdef yyensure_buffer_stack +#define GUC_yyensure_buffer_stack_ALREADY_DEFINED +#else +#define yyensure_buffer_stack GUC_yyensure_buffer_stack +#endif + +#ifdef yylex +#define GUC_yylex_ALREADY_DEFINED +#else +#define yylex GUC_yylex +#endif + +#ifdef yyrestart +#define GUC_yyrestart_ALREADY_DEFINED +#else +#define yyrestart GUC_yyrestart +#endif + +#ifdef yylex_init +#define GUC_yylex_init_ALREADY_DEFINED +#else +#define yylex_init GUC_yylex_init +#endif + +#ifdef yylex_init_extra +#define GUC_yylex_init_extra_ALREADY_DEFINED +#else +#define yylex_init_extra GUC_yylex_init_extra +#endif + +#ifdef yylex_destroy +#define GUC_yylex_destroy_ALREADY_DEFINED +#else +#define yylex_destroy GUC_yylex_destroy +#endif + +#ifdef yyget_debug +#define GUC_yyget_debug_ALREADY_DEFINED +#else +#define yyget_debug GUC_yyget_debug +#endif + +#ifdef yyset_debug +#define GUC_yyset_debug_ALREADY_DEFINED +#else +#define yyset_debug GUC_yyset_debug +#endif + +#ifdef yyget_extra +#define GUC_yyget_extra_ALREADY_DEFINED +#else +#define yyget_extra GUC_yyget_extra +#endif + +#ifdef yyset_extra +#define GUC_yyset_extra_ALREADY_DEFINED +#else +#define yyset_extra GUC_yyset_extra +#endif + +#ifdef yyget_in +#define GUC_yyget_in_ALREADY_DEFINED +#else +#define yyget_in GUC_yyget_in +#endif + +#ifdef yyset_in +#define GUC_yyset_in_ALREADY_DEFINED +#else +#define yyset_in GUC_yyset_in +#endif + +#ifdef yyget_out +#define GUC_yyget_out_ALREADY_DEFINED +#else +#define yyget_out GUC_yyget_out +#endif + +#ifdef yyset_out +#define GUC_yyset_out_ALREADY_DEFINED +#else +#define yyset_out GUC_yyset_out +#endif + +#ifdef yyget_leng +#define GUC_yyget_leng_ALREADY_DEFINED +#else +#define yyget_leng GUC_yyget_leng +#endif + +#ifdef yyget_text +#define GUC_yyget_text_ALREADY_DEFINED +#else +#define yyget_text GUC_yyget_text +#endif + +#ifdef yyget_lineno +#define GUC_yyget_lineno_ALREADY_DEFINED +#else +#define yyget_lineno GUC_yyget_lineno +#endif + +#ifdef yyset_lineno +#define GUC_yyset_lineno_ALREADY_DEFINED +#else +#define yyset_lineno GUC_yyset_lineno +#endif + +#ifdef yywrap +#define GUC_yywrap_ALREADY_DEFINED +#else +#define yywrap GUC_yywrap +#endif + +#ifdef yyalloc +#define GUC_yyalloc_ALREADY_DEFINED +#else +#define yyalloc GUC_yyalloc +#endif + +#ifdef yyrealloc +#define GUC_yyrealloc_ALREADY_DEFINED +#else +#define yyrealloc GUC_yyrealloc +#endif + +#ifdef yyfree +#define GUC_yyfree_ALREADY_DEFINED +#else +#define yyfree GUC_yyfree +#endif + +#ifdef yytext +#define GUC_yytext_ALREADY_DEFINED +#else +#define yytext GUC_yytext +#endif + +#ifdef yyleng +#define GUC_yyleng_ALREADY_DEFINED +#else +#define yyleng GUC_yyleng +#endif + +#ifdef yyin +#define GUC_yyin_ALREADY_DEFINED +#else +#define yyin GUC_yyin +#endif + +#ifdef yyout +#define GUC_yyout_ALREADY_DEFINED +#else +#define yyout GUC_yyout +#endif + +#ifdef yy_flex_debug +#define GUC_yy_flex_debug_ALREADY_DEFINED +#else +#define yy_flex_debug GUC_yy_flex_debug +#endif + +#ifdef yylineno +#define GUC_yylineno_ALREADY_DEFINED +#else +#define yylineno GUC_yylineno +#endif + +/* First, we deal with platform-specific or compiler-specific issues. */ + +/* begin standard C headers. */ +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <stdlib.h> + +/* end standard C headers. */ + +/* flex integer type definitions */ + +#ifndef FLEXINT_H +#define FLEXINT_H + +/* C99 systems have <inttypes.h>. Non-C99 systems may or may not. */ + +#if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L + +/* C99 says to define __STDC_LIMIT_MACROS before including stdint.h, + * if you want the limit (max/min) macros for int types. + */ +#ifndef __STDC_LIMIT_MACROS +#define __STDC_LIMIT_MACROS 1 +#endif + +#include <inttypes.h> +typedef int8_t flex_int8_t; +typedef uint8_t flex_uint8_t; +typedef int16_t flex_int16_t; +typedef uint16_t flex_uint16_t; +typedef int32_t flex_int32_t; +typedef uint32_t flex_uint32_t; +#else +typedef signed char flex_int8_t; +typedef short int flex_int16_t; +typedef int flex_int32_t; +typedef unsigned char flex_uint8_t; +typedef unsigned short int flex_uint16_t; +typedef unsigned int flex_uint32_t; + +/* Limits of integral types. */ +#ifndef INT8_MIN +#define INT8_MIN (-128) +#endif +#ifndef INT16_MIN +#define INT16_MIN (-32767-1) +#endif +#ifndef INT32_MIN +#define INT32_MIN (-2147483647-1) +#endif +#ifndef INT8_MAX +#define INT8_MAX (127) +#endif +#ifndef INT16_MAX +#define INT16_MAX (32767) +#endif +#ifndef INT32_MAX +#define INT32_MAX (2147483647) +#endif +#ifndef UINT8_MAX +#define UINT8_MAX (255U) +#endif +#ifndef UINT16_MAX +#define UINT16_MAX (65535U) +#endif +#ifndef UINT32_MAX +#define UINT32_MAX (4294967295U) +#endif + +#ifndef SIZE_MAX +#define SIZE_MAX (~(size_t)0) +#endif + +#endif /* ! C99 */ + +#endif /* ! FLEXINT_H */ + +/* begin standard C++ headers. */ + +/* TODO: this is always defined, so inline it */ +#define yyconst const + +#if defined(__GNUC__) && __GNUC__ >= 3 +#define yynoreturn __attribute__((__noreturn__)) +#else +#define yynoreturn +#endif + +/* Returned upon end-of-file. */ +#define YY_NULL 0 + +/* Promotes a possibly negative, possibly signed char to an + * integer in range [0..255] for use as an array index. + */ +#define YY_SC_TO_UI(c) ((YY_CHAR) (c)) + +/* Enter a start condition. This macro really ought to take a parameter, + * but we do it the disgusting crufty way forced on us by the ()-less + * definition of BEGIN. + */ +#define BEGIN (yy_start) = 1 + 2 * +/* Translate the current start state into a value that can be later handed + * to BEGIN to return to the state. The YYSTATE alias is for lex + * compatibility. + */ +#define YY_START (((yy_start) - 1) / 2) +#define YYSTATE YY_START +/* Action number for EOF rule of a given start state. */ +#define YY_STATE_EOF(state) (YY_END_OF_BUFFER + state + 1) +/* Special action meaning "start processing a new file". */ +#define YY_NEW_FILE yyrestart( yyin ) +#define YY_END_OF_BUFFER_CHAR 0 + +/* Size of default input buffer. */ +#ifndef YY_BUF_SIZE +#ifdef __ia64__ +/* On IA-64, the buffer size is 16k, not 8k. + * Moreover, YY_BUF_SIZE is 2*YY_READ_BUF_SIZE in the general case. + * Ditto for the __ia64__ case accordingly. + */ +#define YY_BUF_SIZE 32768 +#else +#define YY_BUF_SIZE 16384 +#endif /* __ia64__ */ +#endif + +/* The state buf must be large enough to hold one state per character in the main buffer. + */ +#define YY_STATE_BUF_SIZE ((YY_BUF_SIZE + 2) * sizeof(yy_state_type)) + +#ifndef YY_TYPEDEF_YY_BUFFER_STATE +#define YY_TYPEDEF_YY_BUFFER_STATE +typedef struct yy_buffer_state *YY_BUFFER_STATE; +#endif + +#ifndef YY_TYPEDEF_YY_SIZE_T +#define YY_TYPEDEF_YY_SIZE_T +typedef size_t yy_size_t; +#endif + +extern __thread int yyleng; + +extern __thread FILE *yyin, *yyout; + +#define EOB_ACT_CONTINUE_SCAN 0 +#define EOB_ACT_END_OF_FILE 1 +#define EOB_ACT_LAST_MATCH 2 + + #define YY_LESS_LINENO(n) + #define YY_LINENO_REWIND_TO(ptr) + +/* Return all but the first "n" matched characters back to the input stream. */ +#define yyless(n) \ + do \ + { \ + /* Undo effects of setting up yytext. */ \ + int yyless_macro_arg = (n); \ + YY_LESS_LINENO(yyless_macro_arg);\ + *yy_cp = (yy_hold_char); \ + YY_RESTORE_YY_MORE_OFFSET \ + (yy_c_buf_p) = yy_cp = yy_bp + yyless_macro_arg - YY_MORE_ADJ; \ + YY_DO_BEFORE_ACTION; /* set up yytext again */ \ + } \ + while ( 0 ) +#define unput(c) yyunput( c, (yytext_ptr) ) + +#ifndef YY_STRUCT_YY_BUFFER_STATE +#define YY_STRUCT_YY_BUFFER_STATE +struct yy_buffer_state + { + FILE *yy_input_file; + + char *yy_ch_buf; /* input buffer */ + char *yy_buf_pos; /* current position in input buffer */ + + /* Size of input buffer in bytes, not including room for EOB + * characters. + */ + int yy_buf_size; + + /* Number of characters read into yy_ch_buf, not including EOB + * characters. + */ + int yy_n_chars; + + /* Whether we "own" the buffer - i.e., we know we created it, + * and can realloc() it to grow it, and should free() it to + * delete it. + */ + int yy_is_our_buffer; + + /* Whether this is an "interactive" input source; if so, and + * if we're using stdio for input, then we want to use getc() + * instead of fread(), to make sure we stop fetching input after + * each newline. + */ + int yy_is_interactive; + + /* Whether we're considered to be at the beginning of a line. + * If so, '^' rules will be active on the next match, otherwise + * not. + */ + int yy_at_bol; + + int yy_bs_lineno; /**< The line count. */ + int yy_bs_column; /**< The column count. */ + + /* Whether to try to fill the input buffer when we reach the + * end of it. + */ + int yy_fill_buffer; + + int yy_buffer_status; + +#define YY_BUFFER_NEW 0 +#define YY_BUFFER_NORMAL 1 + /* When an EOF's been seen but there's still some text to process + * then we mark the buffer as YY_EOF_PENDING, to indicate that we + * shouldn't try reading from the input source any more. We might + * still have a bunch of tokens to match, though, because of + * possible backing-up. + * + * When we actually see the EOF, we change the status to "new" + * (via yyrestart()), so that the user can continue scanning by + * just pointing yyin at a new input file. + */ +#define YY_BUFFER_EOF_PENDING 2 + + }; +#endif /* !YY_STRUCT_YY_BUFFER_STATE */ + +/* Stack of input buffers. */ +static __thread size_t yy_buffer_stack_top = 0; /**< index of top of stack. */ +static __thread size_t yy_buffer_stack_max = 0; /**< capacity of stack. */ +static __thread YY_BUFFER_STATE * yy_buffer_stack = NULL; /**< Stack as an array. */ + +/* We provide macros for accessing buffer states in case in the + * future we want to put the buffer states in a more general + * "scanner state". + * + * Returns the top of the stack, or NULL. + */ +#define YY_CURRENT_BUFFER ( (yy_buffer_stack) \ + ? (yy_buffer_stack)[(yy_buffer_stack_top)] \ + : NULL) +/* Same as previous macro, but useful when we know that the buffer stack is not + * NULL or when we need an lvalue. For internal use only. + */ +#define YY_CURRENT_BUFFER_LVALUE (yy_buffer_stack)[(yy_buffer_stack_top)] + +/* yy_hold_char holds the character lost when yytext is formed. */ +static __thread char yy_hold_char; +static __thread int yy_n_chars; /* number of characters read into yy_ch_buf */ +__thread int yyleng; + +/* Points to current character in buffer. */ +static __thread char *yy_c_buf_p = NULL; +static __thread int yy_init = 0; /* whether we need to initialize */ +static __thread int yy_start = 0; /* start state number */ + +/* Flag which is used to allow yywrap()'s to do buffer switches + * instead of setting up a fresh yyin. A bit of a hack ... + */ +static __thread int yy_did_buffer_switch_on_eof; + +void yyrestart ( FILE *input_file ); +void yy_switch_to_buffer ( YY_BUFFER_STATE new_buffer ); +YY_BUFFER_STATE yy_create_buffer ( FILE *file, int size ); +void yy_delete_buffer ( YY_BUFFER_STATE b ); +void yy_flush_buffer ( YY_BUFFER_STATE b ); +void yypush_buffer_state ( YY_BUFFER_STATE new_buffer ); +void yypop_buffer_state ( void ); + +static void yyensure_buffer_stack ( void ); +static void yy_load_buffer_state ( void ); +static void yy_init_buffer ( YY_BUFFER_STATE b, FILE *file ); +#define YY_FLUSH_BUFFER yy_flush_buffer( YY_CURRENT_BUFFER ) + +YY_BUFFER_STATE yy_scan_buffer ( char *base, yy_size_t size ); +YY_BUFFER_STATE yy_scan_string ( const char *yy_str ); +YY_BUFFER_STATE yy_scan_bytes ( const char *bytes, int len ); + +void *yyalloc ( yy_size_t ); +void *yyrealloc ( void *, yy_size_t ); +void yyfree ( void * ); + +#define yy_new_buffer yy_create_buffer +#define yy_set_interactive(is_interactive) \ + { \ + if ( ! YY_CURRENT_BUFFER ){ \ + yyensure_buffer_stack (); \ + YY_CURRENT_BUFFER_LVALUE = \ + yy_create_buffer( yyin, YY_BUF_SIZE ); \ + } \ + YY_CURRENT_BUFFER_LVALUE->yy_is_interactive = is_interactive; \ + } +#define yy_set_bol(at_bol) \ + { \ + if ( ! YY_CURRENT_BUFFER ){\ + yyensure_buffer_stack (); \ + YY_CURRENT_BUFFER_LVALUE = \ + yy_create_buffer( yyin, YY_BUF_SIZE ); \ + } \ + YY_CURRENT_BUFFER_LVALUE->yy_at_bol = at_bol; \ + } +#define YY_AT_BOL() (YY_CURRENT_BUFFER_LVALUE->yy_at_bol) + +/* Begin user sect3 */ + +#define GUC_yywrap() (/*CONSTCOND*/1) +#define YY_SKIP_YYWRAP +typedef flex_uint8_t YY_CHAR; + +__thread FILE *yyin = NULL, *yyout = NULL; + +typedef int yy_state_type; + +extern __thread int yylineno; +__thread int yylineno = 1; + +extern __thread char *yytext; +#ifdef yytext_ptr +#undef yytext_ptr +#endif +#define yytext_ptr yytext + +static yy_state_type yy_get_previous_state ( void ); +static yy_state_type yy_try_NUL_trans ( yy_state_type current_state ); +static int yy_get_next_buffer ( void ); +static void yynoreturn yy_fatal_error ( const char* msg ); + +/* Done after the current pattern has been matched and before the + * corresponding action - sets up yytext. + */ +#define YY_DO_BEFORE_ACTION \ + (yytext_ptr) = yy_bp; \ + yyleng = (int) (yy_cp - yy_bp); \ + (yy_hold_char) = *yy_cp; \ + *yy_cp = '\0'; \ + (yy_c_buf_p) = yy_cp; +#define YY_NUM_RULES 12 +#define YY_END_OF_BUFFER 13 +/* This struct is not used in this scanner, + but its presence is necessary. */ +struct yy_trans_info + { + flex_int32_t yy_verify; + flex_int32_t yy_nxt; + }; +static const flex_int16_t yy_accept[41] = + { 0, + 0, 0, 13, 11, 2, 1, 3, 11, 11, 9, + 8, 8, 10, 4, 2, 3, 0, 6, 0, 9, + 8, 8, 9, 0, 8, 8, 7, 7, 4, 4, + 0, 9, 8, 8, 7, 5, 5, 5, 5, 0 + } ; + +static const YY_CHAR yy_ec[256] = + { 0, + 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, + 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 2, 1, 1, 4, 1, 1, 1, 5, 1, + 1, 1, 6, 1, 7, 8, 9, 10, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 9, 1, 1, + 12, 1, 1, 1, 13, 13, 13, 13, 14, 13, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 1, 16, 1, 1, 17, 1, 13, 13, 13, 13, + + 14, 13, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 18, + 15, 15, 1, 1, 1, 1, 1, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19 + } ; + +static const YY_CHAR yy_meta[20] = + { 0, + 1, 1, 2, 1, 1, 1, 3, 3, 3, 4, + 4, 1, 5, 6, 5, 1, 3, 5, 3 + } ; + +static const flex_int16_t yy_base[48] = + { 0, + 0, 0, 50, 148, 43, 148, 0, 15, 24, 30, + 28, 22, 148, 40, 35, 0, 17, 25, 0, 15, + 0, 10, 0, 52, 0, 54, 10, 66, 79, 0, + 13, 15, 0, 0, 4, 90, 101, 0, 0, 148, + 118, 124, 127, 131, 133, 137, 141 + } ; + +static const flex_int16_t yy_def[48] = + { 0, + 40, 1, 40, 40, 40, 40, 41, 42, 40, 43, + 40, 11, 40, 44, 40, 41, 42, 40, 42, 43, + 11, 11, 20, 40, 45, 40, 46, 40, 44, 29, + 40, 40, 26, 26, 46, 47, 47, 37, 37, 0, + 40, 40, 40, 40, 40, 40, 40 + } ; + +static const flex_int16_t yy_nxt[168] = + { 0, + 4, 5, 6, 7, 8, 9, 9, 10, 4, 11, + 12, 13, 14, 14, 14, 4, 14, 14, 14, 18, + 35, 18, 32, 32, 32, 32, 35, 25, 24, 17, + 19, 20, 19, 21, 22, 20, 15, 22, 22, 25, + 25, 25, 25, 24, 15, 26, 27, 28, 27, 40, + 40, 40, 40, 40, 40, 40, 30, 31, 31, 40, + 40, 32, 32, 33, 33, 40, 34, 34, 25, 40, + 40, 25, 27, 27, 27, 27, 27, 40, 36, 36, + 36, 40, 37, 36, 36, 27, 28, 27, 40, 40, + 40, 40, 40, 40, 40, 30, 27, 27, 27, 40, + + 40, 40, 40, 40, 40, 40, 39, 27, 27, 27, + 40, 40, 40, 40, 40, 40, 40, 39, 16, 40, + 16, 16, 16, 16, 17, 40, 17, 17, 17, 17, + 23, 40, 23, 29, 29, 29, 29, 25, 25, 27, + 27, 27, 27, 38, 38, 38, 38, 3, 40, 40, + 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, + 40, 40, 40, 40, 40, 40, 40 + } ; + +static const flex_int16_t yy_chk[168] = + { 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, + 35, 17, 31, 31, 32, 32, 27, 22, 20, 18, + 8, 9, 17, 9, 9, 11, 15, 11, 11, 12, + 11, 11, 11, 10, 5, 11, 14, 14, 14, 3, + 0, 0, 0, 0, 0, 0, 14, 24, 24, 0, + 0, 24, 24, 26, 26, 0, 26, 26, 26, 0, + 0, 26, 28, 28, 28, 28, 28, 0, 28, 28, + 28, 0, 28, 28, 28, 29, 29, 29, 0, 0, + 0, 0, 0, 0, 0, 29, 36, 36, 36, 0, + + 0, 0, 0, 0, 0, 0, 36, 37, 37, 37, + 0, 0, 0, 0, 0, 0, 0, 37, 41, 0, + 41, 41, 41, 41, 42, 0, 42, 42, 42, 42, + 43, 0, 43, 44, 44, 44, 44, 45, 45, 46, + 46, 46, 46, 47, 47, 47, 47, 40, 40, 40, + 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, + 40, 40, 40, 40, 40, 40, 40 + } ; + +static __thread yy_state_type yy_last_accepting_state; +static __thread char *yy_last_accepting_cpos; + +extern __thread int yy_flex_debug; +__thread int yy_flex_debug = 0; + +/* The intent behind this definition is that it'll catch + * any uses of REJECT which flex missed. + */ +#define REJECT reject_used_but_not_detected +#define yymore() yymore_used_but_not_detected +#define YY_MORE_ADJ 0 +#define YY_RESTORE_YY_MORE_OFFSET +__thread char *yytext; +#line 1 "guc-file.l" + +#line 26 "guc-file.l" +/* + * flex emits a yy_fatal_error() function that it calls in response to + * critical errors like malloc failure, file I/O errors, and detection of + * internal inconsistency. That function prints a message and calls exit(). + * Mutate it to instead call our handler, which jumps out of the parser. + */ +#undef fprintf +#define fprintf(file, fmt, msg) GUC_flex_fatal(msg) + +enum +{ + GUC_ID = 1, + GUC_STRING = 2, + GUC_INTEGER = 3, + GUC_REAL = 4, + GUC_EQUALS = 5, + GUC_UNQUOTED_STRING = 6, + GUC_QUALIFIED_ID = 7, + GUC_EOL = 99, + GUC_ERROR = 100 +}; + +static __thread unsigned int ConfigFileLineno; +static __thread const char *GUC_flex_fatal_errmsg; +static __thread sigjmp_buf *GUC_flex_fatal_jmp; + +static void FreeConfigVariable(ConfigVariable *item); + +static int GUC_flex_fatal(const char *msg); + +/* LCOV_EXCL_START */ + +#line 804 "guc-file.c" +#define YY_NO_INPUT 1 +#line 806 "guc-file.c" + +#define INITIAL 0 + +#ifndef YY_NO_UNISTD_H +/* Special case for "unistd.h", since it is non-ANSI. We include it way + * down here because we want the user's section 1 to have been scanned first. + * The user has a chance to override it with an option. + */ +#include <unistd.h> +#endif + +#ifndef YY_EXTRA_TYPE +#define YY_EXTRA_TYPE void * +#endif + +static int yy_init_globals ( void ); + +/* Accessor methods to globals. + These are made visible to non-reentrant scanners for convenience. */ + +int yylex_destroy ( void ); + +int yyget_debug ( void ); + +void yyset_debug ( int debug_flag ); + +YY_EXTRA_TYPE yyget_extra ( void ); + +void yyset_extra ( YY_EXTRA_TYPE user_defined ); + +FILE *yyget_in ( void ); + +void yyset_in ( FILE * _in_str ); + +FILE *yyget_out ( void ); + +void yyset_out ( FILE * _out_str ); + + int yyget_leng ( void ); + +char *yyget_text ( void ); + +int yyget_lineno ( void ); + +void yyset_lineno ( int _line_number ); + +/* Macros after this point can all be overridden by user definitions in + * section 1. + */ + +#ifndef YY_SKIP_YYWRAP +#ifdef __cplusplus +extern "C" int yywrap ( void ); +#else +extern int yywrap ( void ); +#endif +#endif + +#ifndef YY_NO_UNPUT + +#endif + +#ifndef yytext_ptr +static void yy_flex_strncpy ( char *, const char *, int ); +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen ( const char * ); +#endif + +#ifndef YY_NO_INPUT +#ifdef __cplusplus +static int yyinput ( void ); +#else +static int input ( void ); +#endif + +#endif + +/* Amount of stuff to slurp up with each read. */ +#ifndef YY_READ_BUF_SIZE +#ifdef __ia64__ +/* On IA-64, the buffer size is 16k, not 8k */ +#define YY_READ_BUF_SIZE 16384 +#else +#define YY_READ_BUF_SIZE 8192 +#endif /* __ia64__ */ +#endif + +/* Copy whatever the last rule matched to the standard output. */ +#ifndef ECHO +/* This used to be an fputs(), but since the string might contain NUL's, + * we now use fwrite(). + */ +#define ECHO do { if (fwrite( yytext, (size_t) yyleng, 1, yyout )) {} } while (0) +#endif + +/* Gets input and stuffs it into "buf". number of characters read, or YY_NULL, + * is returned in "result". + */ +#ifndef YY_INPUT +#define YY_INPUT(buf,result,max_size) \ + if ( YY_CURRENT_BUFFER_LVALUE->yy_is_interactive ) \ + { \ + int c = '*'; \ + int n; \ + for ( n = 0; n < max_size && \ + (c = getc( yyin )) != EOF && c != '\n'; ++n ) \ + buf[n] = (char) c; \ + if ( c == '\n' ) \ + buf[n++] = (char) c; \ + if ( c == EOF && ferror( yyin ) ) \ + YY_FATAL_ERROR( "input in flex scanner failed" ); \ + result = n; \ + } \ + else \ + { \ + errno=0; \ + while ( (result = (int) fread(buf, 1, (yy_size_t) max_size, yyin)) == 0 && ferror(yyin)) \ + { \ + if( errno != EINTR) \ + { \ + YY_FATAL_ERROR( "input in flex scanner failed" ); \ + break; \ + } \ + errno=0; \ + clearerr(yyin); \ + } \ + }\ +\ + +#endif + +/* No semi-colon after return; correct usage is to write "yyterminate();" - + * we don't want an extra ';' after the "return" because that will cause + * some compilers to complain about unreachable statements. + */ +#ifndef yyterminate +#define yyterminate() return YY_NULL +#endif + +/* Number of entries by which start-condition stack grows. */ +#ifndef YY_START_STACK_INCR +#define YY_START_STACK_INCR 25 +#endif + +/* Report a fatal error. */ +#ifndef YY_FATAL_ERROR +#define YY_FATAL_ERROR(msg) yy_fatal_error( msg ) +#endif + +/* end tables serialization structures and prototypes */ + +/* Default declaration of generated scanner - a define so the user can + * easily add parameters. + */ +#ifndef YY_DECL +#define YY_DECL_IS_OURS 1 + +extern int yylex (void); + +#define YY_DECL int yylex (void) +#endif /* !YY_DECL */ + +/* Code executed at the beginning of each rule, after yytext and yyleng + * have been set up. + */ +#ifndef YY_USER_ACTION +#define YY_USER_ACTION +#endif + +/* Code executed at the end of each rule. */ +#ifndef YY_BREAK +#define YY_BREAK /*LINTED*/break; +#endif + +#define YY_RULE_SETUP \ + YY_USER_ACTION + +/** The main scanner function which does all the work. + */ +YY_DECL +{ + yy_state_type yy_current_state; + char *yy_cp, *yy_bp; + int yy_act; + + if ( !(yy_init) ) + { + (yy_init) = 1; + +#ifdef YY_USER_INIT + YY_USER_INIT; +#endif + + if ( ! (yy_start) ) + (yy_start) = 1; /* first start state */ + + if ( ! yyin ) + yyin = stdin; + + if ( ! yyout ) + yyout = stdout; + + if ( ! YY_CURRENT_BUFFER ) { + yyensure_buffer_stack (); + YY_CURRENT_BUFFER_LVALUE = + yy_create_buffer( yyin, YY_BUF_SIZE ); + } + + yy_load_buffer_state( ); + } + + { +#line 90 "guc-file.l" + + +#line 1024 "guc-file.c" + + while ( /*CONSTCOND*/1 ) /* loops until end-of-file is reached */ + { + yy_cp = (yy_c_buf_p); + + /* Support of yytext. */ + *yy_cp = (yy_hold_char); + + /* yy_bp points to the position in yy_ch_buf of the start of + * the current run. + */ + yy_bp = yy_cp; + + yy_current_state = (yy_start); +yy_match: + do + { + YY_CHAR yy_c = yy_ec[YY_SC_TO_UI(*yy_cp)] ; + if ( yy_accept[yy_current_state] ) + { + (yy_last_accepting_state) = yy_current_state; + (yy_last_accepting_cpos) = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 41 ) + yy_c = yy_meta[yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c]; + ++yy_cp; + } + while ( yy_current_state != 40 ); + yy_cp = (yy_last_accepting_cpos); + yy_current_state = (yy_last_accepting_state); + +yy_find_action: + yy_act = yy_accept[yy_current_state]; + + YY_DO_BEFORE_ACTION; + +do_action: /* This label is used only to access EOF actions. */ + + switch ( yy_act ) + { /* beginning of action switch */ + case 0: /* must back up */ + /* undo the effects of YY_DO_BEFORE_ACTION */ + *yy_cp = (yy_hold_char); + yy_cp = (yy_last_accepting_cpos); + yy_current_state = (yy_last_accepting_state); + goto yy_find_action; + +case 1: +/* rule 1 can match eol */ +YY_RULE_SETUP +#line 92 "guc-file.l" +ConfigFileLineno++; return GUC_EOL; + YY_BREAK +case 2: +YY_RULE_SETUP +#line 93 "guc-file.l" +/* eat whitespace */ + YY_BREAK +case 3: +YY_RULE_SETUP +#line 94 "guc-file.l" +/* eat comment (.* matches anything until newline) */ + YY_BREAK +case 4: +YY_RULE_SETUP +#line 96 "guc-file.l" +return GUC_ID; + YY_BREAK +case 5: +YY_RULE_SETUP +#line 97 "guc-file.l" +return GUC_QUALIFIED_ID; + YY_BREAK +case 6: +YY_RULE_SETUP +#line 98 "guc-file.l" +return GUC_STRING; + YY_BREAK +case 7: +YY_RULE_SETUP +#line 99 "guc-file.l" +return GUC_UNQUOTED_STRING; + YY_BREAK +case 8: +YY_RULE_SETUP +#line 100 "guc-file.l" +return GUC_INTEGER; + YY_BREAK +case 9: +YY_RULE_SETUP +#line 101 "guc-file.l" +return GUC_REAL; + YY_BREAK +case 10: +YY_RULE_SETUP +#line 102 "guc-file.l" +return GUC_EQUALS; + YY_BREAK +case 11: +YY_RULE_SETUP +#line 104 "guc-file.l" +return GUC_ERROR; + YY_BREAK +case 12: +YY_RULE_SETUP +#line 106 "guc-file.l" +YY_FATAL_ERROR( "flex scanner jammed" ); + YY_BREAK +#line 1138 "guc-file.c" +case YY_STATE_EOF(INITIAL): + yyterminate(); + + case YY_END_OF_BUFFER: + { + /* Amount of text matched not including the EOB char. */ + int yy_amount_of_matched_text = (int) (yy_cp - (yytext_ptr)) - 1; + + /* Undo the effects of YY_DO_BEFORE_ACTION. */ + *yy_cp = (yy_hold_char); + YY_RESTORE_YY_MORE_OFFSET + + if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_NEW ) + { + /* We're scanning a new file or input source. It's + * possible that this happened because the user + * just pointed yyin at a new source and called + * yylex(). If so, then we have to assure + * consistency between YY_CURRENT_BUFFER and our + * globals. Here is the right place to do so, because + * this is the first action (other than possibly a + * back-up) that will match for the new input source. + */ + (yy_n_chars) = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; + YY_CURRENT_BUFFER_LVALUE->yy_input_file = yyin; + YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = YY_BUFFER_NORMAL; + } + + /* Note that here we test for yy_c_buf_p "<=" to the position + * of the first EOB in the buffer, since yy_c_buf_p will + * already have been incremented past the NUL character + * (since all states make transitions on EOB to the + * end-of-buffer state). Contrast this with the test + * in input(). + */ + if ( (yy_c_buf_p) <= &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] ) + { /* This was really a NUL. */ + yy_state_type yy_next_state; + + (yy_c_buf_p) = (yytext_ptr) + yy_amount_of_matched_text; + + yy_current_state = yy_get_previous_state( ); + + /* Okay, we're now positioned to make the NUL + * transition. We couldn't have + * yy_get_previous_state() go ahead and do it + * for us because it doesn't know how to deal + * with the possibility of jamming (and we don't + * want to build jamming into it because then it + * will run more slowly). + */ + + yy_next_state = yy_try_NUL_trans( yy_current_state ); + + yy_bp = (yytext_ptr) + YY_MORE_ADJ; + + if ( yy_next_state ) + { + /* Consume the NUL. */ + yy_cp = ++(yy_c_buf_p); + yy_current_state = yy_next_state; + goto yy_match; + } + + else + { + yy_cp = (yy_last_accepting_cpos); + yy_current_state = (yy_last_accepting_state); + goto yy_find_action; + } + } + + else switch ( yy_get_next_buffer( ) ) + { + case EOB_ACT_END_OF_FILE: + { + (yy_did_buffer_switch_on_eof) = 0; + + if ( yywrap( ) ) + { + /* Note: because we've taken care in + * yy_get_next_buffer() to have set up + * yytext, we can now set up + * yy_c_buf_p so that if some total + * hoser (like flex itself) wants to + * call the scanner after we return the + * YY_NULL, it'll still work - another + * YY_NULL will get returned. + */ + (yy_c_buf_p) = (yytext_ptr) + YY_MORE_ADJ; + + yy_act = YY_STATE_EOF(YY_START); + goto do_action; + } + + else + { + if ( ! (yy_did_buffer_switch_on_eof) ) + YY_NEW_FILE; + } + break; + } + + case EOB_ACT_CONTINUE_SCAN: + (yy_c_buf_p) = + (yytext_ptr) + yy_amount_of_matched_text; + + yy_current_state = yy_get_previous_state( ); + + yy_cp = (yy_c_buf_p); + yy_bp = (yytext_ptr) + YY_MORE_ADJ; + goto yy_match; + + case EOB_ACT_LAST_MATCH: + (yy_c_buf_p) = + &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)]; + + yy_current_state = yy_get_previous_state( ); + + yy_cp = (yy_c_buf_p); + yy_bp = (yytext_ptr) + YY_MORE_ADJ; + goto yy_find_action; + } + break; + } + + default: + YY_FATAL_ERROR( + "fatal flex scanner internal error--no action found" ); + } /* end of action switch */ + } /* end of scanning one token */ + } /* end of user's declarations */ +} /* end of yylex */ + +/* yy_get_next_buffer - try to read in a new buffer + * + * Returns a code representing an action: + * EOB_ACT_LAST_MATCH - + * EOB_ACT_CONTINUE_SCAN - continue scanning from current position + * EOB_ACT_END_OF_FILE - end of file + */ +static int yy_get_next_buffer (void) +{ + char *dest = YY_CURRENT_BUFFER_LVALUE->yy_ch_buf; + char *source = (yytext_ptr); + int number_to_move, i; + int ret_val; + + if ( (yy_c_buf_p) > &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars) + 1] ) + YY_FATAL_ERROR( + "fatal flex scanner internal error--end of buffer missed" ); + + if ( YY_CURRENT_BUFFER_LVALUE->yy_fill_buffer == 0 ) + { /* Don't try to fill the buffer, so this is an EOF. */ + if ( (yy_c_buf_p) - (yytext_ptr) - YY_MORE_ADJ == 1 ) + { + /* We matched a single character, the EOB, so + * treat this as a final EOF. + */ + return EOB_ACT_END_OF_FILE; + } + + else + { + /* We matched some text prior to the EOB, first + * process it. + */ + return EOB_ACT_LAST_MATCH; + } + } + + /* Try to read more data. */ + + /* First move last chars to start of buffer. */ + number_to_move = (int) ((yy_c_buf_p) - (yytext_ptr) - 1); + + for ( i = 0; i < number_to_move; ++i ) + *(dest++) = *(source++); + + if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_EOF_PENDING ) + /* don't do the read, it's not guaranteed to return an EOF, + * just force an EOF + */ + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars) = 0; + + else + { + int num_to_read = + YY_CURRENT_BUFFER_LVALUE->yy_buf_size - number_to_move - 1; + + while ( num_to_read <= 0 ) + { /* Not enough room in the buffer - grow it. */ + + /* just a shorter name for the current buffer */ + YY_BUFFER_STATE b = YY_CURRENT_BUFFER_LVALUE; + + int yy_c_buf_p_offset = + (int) ((yy_c_buf_p) - b->yy_ch_buf); + + if ( b->yy_is_our_buffer ) + { + int new_size = b->yy_buf_size * 2; + + if ( new_size <= 0 ) + b->yy_buf_size += b->yy_buf_size / 8; + else + b->yy_buf_size *= 2; + + b->yy_ch_buf = (char *) + /* Include room in for 2 EOB chars. */ + yyrealloc( (void *) b->yy_ch_buf, + (yy_size_t) (b->yy_buf_size + 2) ); + } + else + /* Can't grow it, we don't own it. */ + b->yy_ch_buf = NULL; + + if ( ! b->yy_ch_buf ) + YY_FATAL_ERROR( + "fatal error - scanner input buffer overflow" ); + + (yy_c_buf_p) = &b->yy_ch_buf[yy_c_buf_p_offset]; + + num_to_read = YY_CURRENT_BUFFER_LVALUE->yy_buf_size - + number_to_move - 1; + + } + + if ( num_to_read > YY_READ_BUF_SIZE ) + num_to_read = YY_READ_BUF_SIZE; + + /* Read in more data. */ + YY_INPUT( (&YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move]), + (yy_n_chars), num_to_read ); + + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars); + } + + if ( (yy_n_chars) == 0 ) + { + if ( number_to_move == YY_MORE_ADJ ) + { + ret_val = EOB_ACT_END_OF_FILE; + yyrestart( yyin ); + } + + else + { + ret_val = EOB_ACT_LAST_MATCH; + YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = + YY_BUFFER_EOF_PENDING; + } + } + + else + ret_val = EOB_ACT_CONTINUE_SCAN; + + if (((yy_n_chars) + number_to_move) > YY_CURRENT_BUFFER_LVALUE->yy_buf_size) { + /* Extend the array by 50%, plus the number we really need. */ + int new_size = (yy_n_chars) + number_to_move + ((yy_n_chars) >> 1); + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf = (char *) yyrealloc( + (void *) YY_CURRENT_BUFFER_LVALUE->yy_ch_buf, (yy_size_t) new_size ); + if ( ! YY_CURRENT_BUFFER_LVALUE->yy_ch_buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_get_next_buffer()" ); + /* "- 2" to take care of EOB's */ + YY_CURRENT_BUFFER_LVALUE->yy_buf_size = (int) (new_size - 2); + } + + (yy_n_chars) += number_to_move; + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] = YY_END_OF_BUFFER_CHAR; + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars) + 1] = YY_END_OF_BUFFER_CHAR; + + (yytext_ptr) = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[0]; + + return ret_val; +} + +/* yy_get_previous_state - get the state just before the EOB char was reached */ + + static yy_state_type yy_get_previous_state (void) +{ + yy_state_type yy_current_state; + char *yy_cp; + + yy_current_state = (yy_start); + + for ( yy_cp = (yytext_ptr) + YY_MORE_ADJ; yy_cp < (yy_c_buf_p); ++yy_cp ) + { + YY_CHAR yy_c = (*yy_cp ? yy_ec[YY_SC_TO_UI(*yy_cp)] : 1); + if ( yy_accept[yy_current_state] ) + { + (yy_last_accepting_state) = yy_current_state; + (yy_last_accepting_cpos) = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 41 ) + yy_c = yy_meta[yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c]; + } + + return yy_current_state; +} + +/* yy_try_NUL_trans - try to make a transition on the NUL character + * + * synopsis + * next_state = yy_try_NUL_trans( current_state ); + */ + static yy_state_type yy_try_NUL_trans (yy_state_type yy_current_state ) +{ + int yy_is_jam; + char *yy_cp = (yy_c_buf_p); + + YY_CHAR yy_c = 1; + if ( yy_accept[yy_current_state] ) + { + (yy_last_accepting_state) = yy_current_state; + (yy_last_accepting_cpos) = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 41 ) + yy_c = yy_meta[yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c]; + yy_is_jam = (yy_current_state == 40); + + return yy_is_jam ? 0 : yy_current_state; +} + +#ifndef YY_NO_UNPUT + +#endif + +#ifndef YY_NO_INPUT +#ifdef __cplusplus + static int yyinput (void) +#else + static int input (void) +#endif + +{ + int c; + + *(yy_c_buf_p) = (yy_hold_char); + + if ( *(yy_c_buf_p) == YY_END_OF_BUFFER_CHAR ) + { + /* yy_c_buf_p now points to the character we want to return. + * If this occurs *before* the EOB characters, then it's a + * valid NUL; if not, then we've hit the end of the buffer. + */ + if ( (yy_c_buf_p) < &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] ) + /* This was really a NUL. */ + *(yy_c_buf_p) = '\0'; + + else + { /* need more input */ + int offset = (int) ((yy_c_buf_p) - (yytext_ptr)); + ++(yy_c_buf_p); + + switch ( yy_get_next_buffer( ) ) + { + case EOB_ACT_LAST_MATCH: + /* This happens because yy_g_n_b() + * sees that we've accumulated a + * token and flags that we need to + * try matching the token before + * proceeding. But for input(), + * there's no matching to consider. + * So convert the EOB_ACT_LAST_MATCH + * to EOB_ACT_END_OF_FILE. + */ + + /* Reset buffer status. */ + yyrestart( yyin ); + + /*FALLTHROUGH*/ + + case EOB_ACT_END_OF_FILE: + { + if ( yywrap( ) ) + return 0; + + if ( ! (yy_did_buffer_switch_on_eof) ) + YY_NEW_FILE; +#ifdef __cplusplus + return yyinput(); +#else + return input(); +#endif + } + + case EOB_ACT_CONTINUE_SCAN: + (yy_c_buf_p) = (yytext_ptr) + offset; + break; + } + } + } + + c = *(unsigned char *) (yy_c_buf_p); /* cast for 8-bit char's */ + *(yy_c_buf_p) = '\0'; /* preserve yytext */ + (yy_hold_char) = *++(yy_c_buf_p); + + return c; +} +#endif /* ifndef YY_NO_INPUT */ + +/** Immediately switch to a different input stream. + * @param input_file A readable stream. + * + * @note This function does not reset the start condition to @c INITIAL . + */ + void yyrestart (FILE * input_file ) +{ + + if ( ! YY_CURRENT_BUFFER ){ + yyensure_buffer_stack (); + YY_CURRENT_BUFFER_LVALUE = + yy_create_buffer( yyin, YY_BUF_SIZE ); + } + + yy_init_buffer( YY_CURRENT_BUFFER, input_file ); + yy_load_buffer_state( ); +} + +/** Switch to a different input buffer. + * @param new_buffer The new input buffer. + * + */ + void yy_switch_to_buffer (YY_BUFFER_STATE new_buffer ) +{ + + /* TODO. We should be able to replace this entire function body + * with + * yypop_buffer_state(); + * yypush_buffer_state(new_buffer); + */ + yyensure_buffer_stack (); + if ( YY_CURRENT_BUFFER == new_buffer ) + return; + + if ( YY_CURRENT_BUFFER ) + { + /* Flush out information for old buffer. */ + *(yy_c_buf_p) = (yy_hold_char); + YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = (yy_c_buf_p); + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars); + } + + YY_CURRENT_BUFFER_LVALUE = new_buffer; + yy_load_buffer_state( ); + + /* We don't actually know whether we did this switch during + * EOF (yywrap()) processing, but the only time this flag + * is looked at is after yywrap() is called, so it's safe + * to go ahead and always set it. + */ + (yy_did_buffer_switch_on_eof) = 1; +} + +static void yy_load_buffer_state (void) +{ + (yy_n_chars) = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; + (yytext_ptr) = (yy_c_buf_p) = YY_CURRENT_BUFFER_LVALUE->yy_buf_pos; + yyin = YY_CURRENT_BUFFER_LVALUE->yy_input_file; + (yy_hold_char) = *(yy_c_buf_p); +} + +/** Allocate and initialize an input buffer state. + * @param file A readable stream. + * @param size The character buffer size in bytes. When in doubt, use @c YY_BUF_SIZE. + * + * @return the allocated buffer state. + */ + YY_BUFFER_STATE yy_create_buffer (FILE * file, int size ) +{ + YY_BUFFER_STATE b; + + b = (YY_BUFFER_STATE) yyalloc( sizeof( struct yy_buffer_state ) ); + if ( ! b ) + YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" ); + + b->yy_buf_size = size; + + /* yy_ch_buf has to be 2 characters longer than the size given because + * we need to put in 2 end-of-buffer characters. + */ + b->yy_ch_buf = (char *) yyalloc( (yy_size_t) (b->yy_buf_size + 2) ); + if ( ! b->yy_ch_buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" ); + + b->yy_is_our_buffer = 1; + + yy_init_buffer( b, file ); + + return b; +} + +/** Destroy the buffer. + * @param b a buffer created with yy_create_buffer() + * + */ + void yy_delete_buffer (YY_BUFFER_STATE b ) +{ + + if ( ! b ) + return; + + if ( b == YY_CURRENT_BUFFER ) /* Not sure if we should pop here. */ + YY_CURRENT_BUFFER_LVALUE = (YY_BUFFER_STATE) 0; + + if ( b->yy_is_our_buffer ) + yyfree( (void *) b->yy_ch_buf ); + + yyfree( (void *) b ); +} + +/* Initializes or reinitializes a buffer. + * This function is sometimes called more than once on the same buffer, + * such as during a yyrestart() or at EOF. + */ + static void yy_init_buffer (YY_BUFFER_STATE b, FILE * file ) + +{ + int oerrno = errno; + + yy_flush_buffer( b ); + + b->yy_input_file = file; + b->yy_fill_buffer = 1; + + /* If b is the current buffer, then yy_init_buffer was _probably_ + * called from yyrestart() or through yy_get_next_buffer. + * In that case, we don't want to reset the lineno or column. + */ + if (b != YY_CURRENT_BUFFER){ + b->yy_bs_lineno = 1; + b->yy_bs_column = 0; + } + + b->yy_is_interactive = 0; + + errno = oerrno; +} + +/** Discard all buffered characters. On the next scan, YY_INPUT will be called. + * @param b the buffer state to be flushed, usually @c YY_CURRENT_BUFFER. + * + */ + void yy_flush_buffer (YY_BUFFER_STATE b ) +{ + if ( ! b ) + return; + + b->yy_n_chars = 0; + + /* We always need two end-of-buffer characters. The first causes + * a transition to the end-of-buffer state. The second causes + * a jam in that state. + */ + b->yy_ch_buf[0] = YY_END_OF_BUFFER_CHAR; + b->yy_ch_buf[1] = YY_END_OF_BUFFER_CHAR; + + b->yy_buf_pos = &b->yy_ch_buf[0]; + + b->yy_at_bol = 1; + b->yy_buffer_status = YY_BUFFER_NEW; + + if ( b == YY_CURRENT_BUFFER ) + yy_load_buffer_state( ); +} + +/** Pushes the new state onto the stack. The new state becomes + * the current state. This function will allocate the stack + * if necessary. + * @param new_buffer The new state. + * + */ +void yypush_buffer_state (YY_BUFFER_STATE new_buffer ) +{ + if (new_buffer == NULL) + return; + + yyensure_buffer_stack(); + + /* This block is copied from yy_switch_to_buffer. */ + if ( YY_CURRENT_BUFFER ) + { + /* Flush out information for old buffer. */ + *(yy_c_buf_p) = (yy_hold_char); + YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = (yy_c_buf_p); + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars); + } + + /* Only push if top exists. Otherwise, replace top. */ + if (YY_CURRENT_BUFFER) + (yy_buffer_stack_top)++; + YY_CURRENT_BUFFER_LVALUE = new_buffer; + + /* copied from yy_switch_to_buffer. */ + yy_load_buffer_state( ); + (yy_did_buffer_switch_on_eof) = 1; +} + +/** Removes and deletes the top of the stack, if present. + * The next element becomes the new top. + * + */ +void yypop_buffer_state (void) +{ + if (!YY_CURRENT_BUFFER) + return; + + yy_delete_buffer(YY_CURRENT_BUFFER ); + YY_CURRENT_BUFFER_LVALUE = NULL; + if ((yy_buffer_stack_top) > 0) + --(yy_buffer_stack_top); + + if (YY_CURRENT_BUFFER) { + yy_load_buffer_state( ); + (yy_did_buffer_switch_on_eof) = 1; + } +} + +/* Allocates the stack if it does not exist. + * Guarantees space for at least one push. + */ +static void yyensure_buffer_stack (void) +{ + yy_size_t num_to_alloc; + + if (!(yy_buffer_stack)) { + + /* First allocation is just for 2 elements, since we don't know if this + * scanner will even need a stack. We use 2 instead of 1 to avoid an + * immediate realloc on the next call. + */ + num_to_alloc = 1; /* After all that talk, this was set to 1 anyways... */ + (yy_buffer_stack) = (struct yy_buffer_state**)yyalloc + (num_to_alloc * sizeof(struct yy_buffer_state*) + ); + if ( ! (yy_buffer_stack) ) + YY_FATAL_ERROR( "out of dynamic memory in yyensure_buffer_stack()" ); + + memset((yy_buffer_stack), 0, num_to_alloc * sizeof(struct yy_buffer_state*)); + + (yy_buffer_stack_max) = num_to_alloc; + (yy_buffer_stack_top) = 0; + return; + } + + if ((yy_buffer_stack_top) >= ((yy_buffer_stack_max)) - 1){ + + /* Increase the buffer to prepare for a possible push. */ + yy_size_t grow_size = 8 /* arbitrary grow size */; + + num_to_alloc = (yy_buffer_stack_max) + grow_size; + (yy_buffer_stack) = (struct yy_buffer_state**)yyrealloc + ((yy_buffer_stack), + num_to_alloc * sizeof(struct yy_buffer_state*) + ); + if ( ! (yy_buffer_stack) ) + YY_FATAL_ERROR( "out of dynamic memory in yyensure_buffer_stack()" ); + + /* zero only the new slots.*/ + memset((yy_buffer_stack) + (yy_buffer_stack_max), 0, grow_size * sizeof(struct yy_buffer_state*)); + (yy_buffer_stack_max) = num_to_alloc; + } +} + +/** Setup the input buffer state to scan directly from a user-specified character buffer. + * @param base the character buffer + * @param size the size in bytes of the character buffer + * + * @return the newly allocated buffer state object. + */ +YY_BUFFER_STATE yy_scan_buffer (char * base, yy_size_t size ) +{ + YY_BUFFER_STATE b; + + if ( size < 2 || + base[size-2] != YY_END_OF_BUFFER_CHAR || + base[size-1] != YY_END_OF_BUFFER_CHAR ) + /* They forgot to leave room for the EOB's. */ + return NULL; + + b = (YY_BUFFER_STATE) yyalloc( sizeof( struct yy_buffer_state ) ); + if ( ! b ) + YY_FATAL_ERROR( "out of dynamic memory in yy_scan_buffer()" ); + + b->yy_buf_size = (int) (size - 2); /* "- 2" to take care of EOB's */ + b->yy_buf_pos = b->yy_ch_buf = base; + b->yy_is_our_buffer = 0; + b->yy_input_file = NULL; + b->yy_n_chars = b->yy_buf_size; + b->yy_is_interactive = 0; + b->yy_at_bol = 1; + b->yy_fill_buffer = 0; + b->yy_buffer_status = YY_BUFFER_NEW; + + yy_switch_to_buffer( b ); + + return b; +} + +/** Setup the input buffer state to scan a string. The next call to yylex() will + * scan from a @e copy of @a str. + * @param yystr a NUL-terminated string to scan + * + * @return the newly allocated buffer state object. + * @note If you want to scan bytes that may contain NUL values, then use + * yy_scan_bytes() instead. + */ +YY_BUFFER_STATE yy_scan_string (const char * yystr ) +{ + + return yy_scan_bytes( yystr, (int) strlen(yystr) ); +} + +/** Setup the input buffer state to scan the given bytes. The next call to yylex() will + * scan from a @e copy of @a bytes. + * @param yybytes the byte buffer to scan + * @param _yybytes_len the number of bytes in the buffer pointed to by @a bytes. + * + * @return the newly allocated buffer state object. + */ +YY_BUFFER_STATE yy_scan_bytes (const char * yybytes, int _yybytes_len ) +{ + YY_BUFFER_STATE b; + char *buf; + yy_size_t n; + int i; + + /* Get memory for full buffer, including space for trailing EOB's. */ + n = (yy_size_t) (_yybytes_len + 2); + buf = (char *) yyalloc( n ); + if ( ! buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_scan_bytes()" ); + + for ( i = 0; i < _yybytes_len; ++i ) + buf[i] = yybytes[i]; + + buf[_yybytes_len] = buf[_yybytes_len+1] = YY_END_OF_BUFFER_CHAR; + + b = yy_scan_buffer( buf, n ); + if ( ! b ) + YY_FATAL_ERROR( "bad buffer in yy_scan_bytes()" ); + + /* It's okay to grow etc. this buffer, and we should throw it + * away when we're done. + */ + b->yy_is_our_buffer = 1; + + return b; +} + +#ifndef YY_EXIT_FAILURE +#define YY_EXIT_FAILURE 2 +#endif + +static void yynoreturn yy_fatal_error (const char* msg ) +{ + fprintf( stderr, "%s\n", msg ); + exit( YY_EXIT_FAILURE ); +} + +/* Redefine yyless() so it works in section 3 code. */ + +#undef yyless +#define yyless(n) \ + do \ + { \ + /* Undo effects of setting up yytext. */ \ + int yyless_macro_arg = (n); \ + YY_LESS_LINENO(yyless_macro_arg);\ + yytext[yyleng] = (yy_hold_char); \ + (yy_c_buf_p) = yytext + yyless_macro_arg; \ + (yy_hold_char) = *(yy_c_buf_p); \ + *(yy_c_buf_p) = '\0'; \ + yyleng = yyless_macro_arg; \ + } \ + while ( 0 ) + +/* Accessor methods (get/set functions) to struct members. */ + +/** Get the current line number. + * + */ +int yyget_lineno (void) +{ + + return yylineno; +} + +/** Get the input stream. + * + */ +FILE *yyget_in (void) +{ + return yyin; +} + +/** Get the output stream. + * + */ +FILE *yyget_out (void) +{ + return yyout; +} + +/** Get the length of the current token. + * + */ +int yyget_leng (void) +{ + return yyleng; +} + +/** Get the current token. + * + */ + +char *yyget_text (void) +{ + return yytext; +} + +/** Set the current line number. + * @param _line_number line number + * + */ +void yyset_lineno (int _line_number ) +{ + + yylineno = _line_number; +} + +/** Set the input stream. This does not discard the current + * input buffer. + * @param _in_str A readable stream. + * + * @see yy_switch_to_buffer + */ +void yyset_in (FILE * _in_str ) +{ + yyin = _in_str ; +} + +void yyset_out (FILE * _out_str ) +{ + yyout = _out_str ; +} + +int yyget_debug (void) +{ + return yy_flex_debug; +} + +void yyset_debug (int _bdebug ) +{ + yy_flex_debug = _bdebug ; +} + +static int yy_init_globals (void) +{ + /* Initialization is the same as for the non-reentrant scanner. + * This function is called from yylex_destroy(), so don't allocate here. + */ + + (yy_buffer_stack) = NULL; + (yy_buffer_stack_top) = 0; + (yy_buffer_stack_max) = 0; + (yy_c_buf_p) = NULL; + (yy_init) = 0; + (yy_start) = 0; + +/* Defined in main.c */ +#ifdef YY_STDINIT + yyin = stdin; + yyout = stdout; +#else + yyin = NULL; + yyout = NULL; +#endif + + /* For future reference: Set errno on error, since we are called by + * yylex_init() + */ + return 0; +} + +/* yylex_destroy is for both reentrant and non-reentrant scanners. */ +int yylex_destroy (void) +{ + + /* Pop the buffer stack, destroying each element. */ + while(YY_CURRENT_BUFFER){ + yy_delete_buffer( YY_CURRENT_BUFFER ); + YY_CURRENT_BUFFER_LVALUE = NULL; + yypop_buffer_state(); + } + + /* Destroy the stack itself. */ + yyfree((yy_buffer_stack) ); + (yy_buffer_stack) = NULL; + + /* Reset the globals. This is important in a non-reentrant scanner so the next time + * yylex() is called, initialization will occur. */ + yy_init_globals( ); + + return 0; +} + +/* + * Internal utility routines. + */ + +#ifndef yytext_ptr +static void yy_flex_strncpy (char* s1, const char * s2, int n ) +{ + + int i; + for ( i = 0; i < n; ++i ) + s1[i] = s2[i]; +} +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen (const char * s ) +{ + int n; + for ( n = 0; s[n]; ++n ) + ; + + return n; +} +#endif + +void *yyalloc (yy_size_t size ) +{ + return malloc(size); +} + +void *yyrealloc (void * ptr, yy_size_t size ) +{ + + /* The cast to (char *) in the following accommodates both + * implementations that use char* generic pointers, and those + * that use void* generic pointers. It works with the latter + * because both ANSI C and C++ allow castless assignment from + * any pointer type to void*, and deal with argument conversions + * as though doing an assignment. + */ + return realloc(ptr, size); +} + +void yyfree (void * ptr ) +{ + free( (char *) ptr ); /* see yyrealloc() for (char *) cast */ +} + +#define YYTABLES_NAME "yytables" + +#line 106 "guc-file.l" + + +/* LCOV_EXCL_STOP */ + +/* + * Exported function to read and process the configuration file. The + * parameter indicates in what context the file is being read --- either + * postmaster startup (including standalone-backend startup) or SIGHUP. + * All options mentioned in the configuration file are set to new values. + * If a hard error occurs, no values will be changed. (There can also be + * errors that prevent just one value from being changed.) + */ +void +ProcessConfigFile(GucContext context) +{ + int elevel; + MemoryContext config_cxt; + MemoryContext caller_cxt; + + /* + * Config files are processed on startup (by the postmaster only) and on + * SIGHUP (by the postmaster and its children) + */ + Assert((context == PGC_POSTMASTER && !IsUnderPostmaster) || + context == PGC_SIGHUP); + + /* + * To avoid cluttering the log, only the postmaster bleats loudly about + * problems with the config file. + */ + elevel = IsUnderPostmaster ? DEBUG2 : LOG; + + /* + * This function is usually called within a process-lifespan memory + * context. To ensure that any memory leaked during GUC processing does + * not accumulate across repeated SIGHUP cycles, do the work in a private + * context that we can free at exit. + */ + config_cxt = AllocSetContextCreate(CurrentMemoryContext, + "config file processing", + ALLOCSET_DEFAULT_SIZES); + caller_cxt = MemoryContextSwitchTo(config_cxt); + + /* + * Read and apply the config file. We don't need to examine the result. + */ + (void) ProcessConfigFileInternal(context, true, elevel); + + /* Clean up */ + MemoryContextSwitchTo(caller_cxt); + MemoryContextDelete(config_cxt); +} + +/* + * Read and parse a single configuration file. This function recurses + * to handle "include" directives. + * + * If "strict" is true, treat failure to open the config file as an error, + * otherwise just skip the file. + * + * calling_file/calling_lineno identify the source of the request. + * Pass NULL/0 if not recursing from an inclusion request. + * + * See ParseConfigFp for further details. This one merely adds opening the + * config file rather than working from a caller-supplied file descriptor, + * and absolute-ifying the path name if necessary. + */ +bool +ParseConfigFile(const char *config_file, bool strict, + const char *calling_file, int calling_lineno, + int depth, int elevel, + ConfigVariable **head_p, + ConfigVariable **tail_p) +{ + char *abs_path; + bool OK = true; + FILE *fp; + + /* + * Reject file name that is all-blank (including empty), as that leads to + * confusion --- we'd try to read the containing directory as a file. + */ + if (strspn(config_file, " \t\r\n") == strlen(config_file)) + { + ereport(elevel, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("empty configuration file name: \"%s\"", + config_file))); + record_config_file_error("empty configuration file name", + calling_file, calling_lineno, + head_p, tail_p); + return false; + } + + /* + * Reject too-deep include nesting depth. This is just a safety check to + * avoid dumping core due to stack overflow if an include file loops back + * to itself. The maximum nesting depth is pretty arbitrary. + */ + if (depth > CONF_FILE_MAX_DEPTH) + { + ereport(elevel, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("could not open configuration file \"%s\": maximum nesting depth exceeded", + config_file))); + record_config_file_error("nesting depth exceeded", + calling_file, calling_lineno, + head_p, tail_p); + return false; + } + + abs_path = AbsoluteConfigLocation(config_file, calling_file); + + /* + * Reject direct recursion. Indirect recursion is also possible, but it's + * harder to detect and so doesn't seem worth the trouble. (We test at + * this step because the canonicalization done by AbsoluteConfigLocation + * makes it more likely that a simple strcmp comparison will match.) + */ + if (calling_file && strcmp(abs_path, calling_file) == 0) + { + ereport(elevel, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("configuration file recursion in \"%s\"", + calling_file))); + record_config_file_error("configuration file recursion", + calling_file, calling_lineno, + head_p, tail_p); + pfree(abs_path); + return false; + } + + fp = AllocateFile(abs_path, "r"); + if (!fp) + { + if (strict) + { + ereport(elevel, + (errcode_for_file_access(), + errmsg("could not open configuration file \"%s\": %m", + abs_path))); + record_config_file_error(psprintf("could not open file \"%s\"", + abs_path), + calling_file, calling_lineno, + head_p, tail_p); + OK = false; + } + else + { + ereport(LOG, + (errmsg("skipping missing configuration file \"%s\"", + abs_path))); + } + goto cleanup; + } + + OK = ParseConfigFp(fp, abs_path, depth, elevel, head_p, tail_p); + +cleanup: + if (fp) + FreeFile(fp); + pfree(abs_path); + + return OK; +} + +/* + * Capture an error message in the ConfigVariable list returned by + * config file parsing. + */ +void +record_config_file_error(const char *errmsg, + const char *config_file, + int lineno, + ConfigVariable **head_p, + ConfigVariable **tail_p) +{ + ConfigVariable *item; + + item = palloc(sizeof *item); + item->name = NULL; + item->value = NULL; + item->errmsg = pstrdup(errmsg); + item->filename = config_file ? pstrdup(config_file) : NULL; + item->sourceline = lineno; + item->ignore = true; + item->applied = false; + item->next = NULL; + if (*head_p == NULL) + *head_p = item; + else + (*tail_p)->next = item; + *tail_p = item; +} + +/* + * Flex fatal errors bring us here. Stash the error message and jump back to + * ParseConfigFp(). Assume all msg arguments point to string constants; this + * holds for flex 2.5.35 (earliest we support). Otherwise, we would need to + * copy the message. + * + * We return "int" since this takes the place of calls to fprintf(). +*/ +static int +GUC_flex_fatal(const char *msg) +{ + GUC_flex_fatal_errmsg = msg; + siglongjmp(*GUC_flex_fatal_jmp, 1); + return 0; /* keep compiler quiet */ +} + +/* + * Read and parse a single configuration file. This function recurses + * to handle "include" directives. + * + * Input parameters: + * fp: file pointer from AllocateFile for the configuration file to parse + * config_file: absolute or relative path name of the configuration file + * depth: recursion depth (should be CONF_FILE_START_DEPTH in the outermost + * call) + * elevel: error logging level to use + * Input/Output parameters: + * head_p, tail_p: head and tail of linked list of name/value pairs + * + * *head_p and *tail_p must be initialized, either to NULL or valid pointers + * to a ConfigVariable list, before calling the outer recursion level. Any + * name-value pairs read from the input file(s) will be appended to the list. + * Error reports will also be appended to the list, if elevel < ERROR. + * + * Returns TRUE if successful, FALSE if an error occurred. The error has + * already been ereport'd, it is only necessary for the caller to clean up + * its own state and release the ConfigVariable list. + * + * Note: if elevel >= ERROR then an error will not return control to the + * caller, so there is no need to check the return value in that case. + * + * Note: this function is used to parse not only postgresql.conf, but + * various other configuration files that use the same "name = value" + * syntax. Hence, do not do anything here or in the subsidiary routines + * ParseConfigFile/ParseConfigDirectory that assumes we are processing + * GUCs specifically. + */ +bool +ParseConfigFp(FILE *fp, const char *config_file, int depth, int elevel, + ConfigVariable **head_p, ConfigVariable **tail_p) +{ + volatile bool OK = true; + unsigned int save_ConfigFileLineno = ConfigFileLineno; + sigjmp_buf *save_GUC_flex_fatal_jmp = GUC_flex_fatal_jmp; + sigjmp_buf flex_fatal_jmp; + volatile YY_BUFFER_STATE lex_buffer = NULL; + int errorcount; + int token; + + if (sigsetjmp(flex_fatal_jmp, 1) == 0) + GUC_flex_fatal_jmp = &flex_fatal_jmp; + else + { + /* + * Regain control after a fatal, internal flex error. It may have + * corrupted parser state. Consequently, abandon the file, but trust + * that the state remains sane enough for yy_delete_buffer(). + */ + elog(elevel, "%s at file \"%s\" line %u", + GUC_flex_fatal_errmsg, config_file, ConfigFileLineno); + record_config_file_error(GUC_flex_fatal_errmsg, + config_file, ConfigFileLineno, + head_p, tail_p); + OK = false; + goto cleanup; + } + + /* + * Parse + */ + ConfigFileLineno = 1; + errorcount = 0; + + lex_buffer = yy_create_buffer(fp, YY_BUF_SIZE); + yy_switch_to_buffer(lex_buffer); + + /* This loop iterates once per logical line */ + while ((token = yylex())) + { + char *opt_name = NULL; + char *opt_value = NULL; + ConfigVariable *item; + + if (token == GUC_EOL) /* empty or comment line */ + continue; + + /* first token on line is option name */ + if (token != GUC_ID && token != GUC_QUALIFIED_ID) + goto parse_error; + opt_name = pstrdup(yytext); + + /* next we have an optional equal sign; discard if present */ + token = yylex(); + if (token == GUC_EQUALS) + token = yylex(); + + /* now we must have the option value */ + if (token != GUC_ID && + token != GUC_STRING && + token != GUC_INTEGER && + token != GUC_REAL && + token != GUC_UNQUOTED_STRING) + goto parse_error; + if (token == GUC_STRING) /* strip quotes and escapes */ + opt_value = DeescapeQuotedString(yytext); + else + opt_value = pstrdup(yytext); + + /* now we'd like an end of line, or possibly EOF */ + token = yylex(); + if (token != GUC_EOL) + { + if (token != 0) + goto parse_error; + /* treat EOF like \n for line numbering purposes, cf bug 4752 */ + ConfigFileLineno++; + } + + /* OK, process the option name and value */ + if (guc_name_compare(opt_name, "include_dir") == 0) + { + /* + * An include_dir directive isn't a variable and should be + * processed immediately. + */ + if (!ParseConfigDirectory(opt_value, + config_file, ConfigFileLineno - 1, + depth + 1, elevel, + head_p, tail_p)) + OK = false; + yy_switch_to_buffer(lex_buffer); + pfree(opt_name); + pfree(opt_value); + } + else if (guc_name_compare(opt_name, "include_if_exists") == 0) + { + /* + * An include_if_exists directive isn't a variable and should be + * processed immediately. + */ + if (!ParseConfigFile(opt_value, false, + config_file, ConfigFileLineno - 1, + depth + 1, elevel, + head_p, tail_p)) + OK = false; + yy_switch_to_buffer(lex_buffer); + pfree(opt_name); + pfree(opt_value); + } + else if (guc_name_compare(opt_name, "include") == 0) + { + /* + * An include directive isn't a variable and should be processed + * immediately. + */ + if (!ParseConfigFile(opt_value, true, + config_file, ConfigFileLineno - 1, + depth + 1, elevel, + head_p, tail_p)) + OK = false; + yy_switch_to_buffer(lex_buffer); + pfree(opt_name); + pfree(opt_value); + } + else + { + /* ordinary variable, append to list */ + item = palloc(sizeof *item); + item->name = opt_name; + item->value = opt_value; + item->errmsg = NULL; + item->filename = pstrdup(config_file); + item->sourceline = ConfigFileLineno - 1; + item->ignore = false; + item->applied = false; + item->next = NULL; + if (*head_p == NULL) + *head_p = item; + else + (*tail_p)->next = item; + *tail_p = item; + } + + /* break out of loop if read EOF, else loop for next line */ + if (token == 0) + break; + continue; + +parse_error: + /* release storage if we allocated any on this line */ + if (opt_name) + pfree(opt_name); + if (opt_value) + pfree(opt_value); + + /* report the error */ + if (token == GUC_EOL || token == 0) + { + ereport(elevel, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("syntax error in file \"%s\" line %u, near end of line", + config_file, ConfigFileLineno - 1))); + record_config_file_error("syntax error", + config_file, ConfigFileLineno - 1, + head_p, tail_p); + } + else + { + ereport(elevel, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("syntax error in file \"%s\" line %u, near token \"%s\"", + config_file, ConfigFileLineno, yytext))); + record_config_file_error("syntax error", + config_file, ConfigFileLineno, + head_p, tail_p); + } + OK = false; + errorcount++; + + /* + * To avoid producing too much noise when fed a totally bogus file, + * give up after 100 syntax errors per file (an arbitrary number). + * Also, if we're only logging the errors at DEBUG level anyway, might + * as well give up immediately. (This prevents postmaster children + * from bloating the logs with duplicate complaints.) + */ + if (errorcount >= 100 || elevel <= DEBUG1) + { + ereport(elevel, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("too many syntax errors found, abandoning file \"%s\"", + config_file))); + break; + } + + /* resync to next end-of-line or EOF */ + while (token != GUC_EOL && token != 0) + token = yylex(); + /* break out of loop on EOF */ + if (token == 0) + break; + } + +cleanup: + yy_delete_buffer(lex_buffer); + /* Each recursion level must save and restore these static variables. */ + ConfigFileLineno = save_ConfigFileLineno; + GUC_flex_fatal_jmp = save_GUC_flex_fatal_jmp; + return OK; +} + +/* + * Read and parse all config files in a subdirectory in alphabetical order + * + * includedir is the absolute or relative path to the subdirectory to scan. + * + * calling_file/calling_lineno identify the source of the request. + * Pass NULL/0 if not recursing from an inclusion request. + * + * See ParseConfigFp for further details. + */ +bool +ParseConfigDirectory(const char *includedir, + const char *calling_file, int calling_lineno, + int depth, int elevel, + ConfigVariable **head_p, + ConfigVariable **tail_p) +{ + char *err_msg; + char **filenames; + int num_filenames; + + filenames = GetConfFilesInDir(includedir, calling_file, elevel, + &num_filenames, &err_msg); + + if (!filenames) + { + record_config_file_error(err_msg, calling_file, calling_lineno, head_p, + tail_p); + return false; + } + + for (int i = 0; i < num_filenames; i++) + { + if (!ParseConfigFile(filenames[i], true, + calling_file, calling_lineno, + depth, elevel, + head_p, tail_p)) + return false; + } + + return true; +} + +/* + * Free a list of ConfigVariables, including the names and the values + */ +void +FreeConfigVariables(ConfigVariable *list) +{ + ConfigVariable *item; + + item = list; + while (item) + { + ConfigVariable *next = item->next; + + FreeConfigVariable(item); + item = next; + } +} + +/* + * Free a single ConfigVariable + */ +static void +FreeConfigVariable(ConfigVariable *item) +{ + if (item->name) + pfree(item->name); + if (item->value) + pfree(item->value); + if (item->errmsg) + pfree(item->errmsg); + if (item->filename) + pfree(item->filename); + pfree(item); +} + + +/* + * DeescapeQuotedString + * + * Strip the quotes surrounding the given string, and collapse any embedded + * '' sequences and backslash escapes. + * + * The string returned is palloc'd and should eventually be pfree'd by the + * caller. + * + * This is exported because it is also used by the bootstrap scanner. + */ +char * +DeescapeQuotedString(const char *s) +{ + char *newStr; + int len, + i, + j; + + /* We just Assert that there are leading and trailing quotes */ + Assert(s != NULL && s[0] == '\''); + len = strlen(s); + Assert(len >= 2); + Assert(s[len - 1] == '\''); + + /* Skip the leading quote; we'll handle the trailing quote below */ + s++, len--; + + /* Since len still includes trailing quote, this is enough space */ + newStr = palloc(len); + + for (i = 0, j = 0; i < len; i++) + { + if (s[i] == '\\') + { + i++; + switch (s[i]) + { + case 'b': + newStr[j] = '\b'; + break; + case 'f': + newStr[j] = '\f'; + break; + case 'n': + newStr[j] = '\n'; + break; + case 'r': + newStr[j] = '\r'; + break; + case 't': + newStr[j] = '\t'; + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + { + int k; + long octVal = 0; + + for (k = 0; + s[i + k] >= '0' && s[i + k] <= '7' && k < 3; + k++) + octVal = (octVal << 3) + (s[i + k] - '0'); + i += k - 1; + newStr[j] = ((char) octVal); + } + break; + default: + newStr[j] = s[i]; + break; + } /* switch */ + } + else if (s[i] == '\'' && s[i + 1] == '\'') + { + /* doubled quote becomes just one quote */ + newStr[j] = s[++i]; + } + else + newStr[j] = s[i]; + j++; + } + + /* We copied the ending quote to newStr, so replace with \0 */ + Assert(j > 0 && j <= len); + newStr[--j] = '\0'; + + return newStr; +} + diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/misc/guc.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/misc/guc.c new file mode 100644 index 00000000000..6406361744b --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/misc/guc.c @@ -0,0 +1,6657 @@ +/*-------------------------------------------------------------------- + * guc.c + * + * Support for grand unified configuration scheme, including SET + * command, configuration file, and command line options. + * + * This file contains the generic option processing infrastructure. + * guc_funcs.c contains SQL-level functionality, including SET/SHOW + * commands and various system-administration SQL functions. + * guc_tables.c contains the arrays that define all the built-in + * GUC variables. Code that implements variable-specific behavior + * is scattered around the system in check, assign, and show hooks. + * + * See src/backend/utils/misc/README for more information. + * + * + * Copyright (c) 2000-2023, PostgreSQL Global Development Group + * Written by Peter Eisentraut <peter_e@gmx.net>. + * + * IDENTIFICATION + * src/backend/utils/misc/guc.c + * + *-------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <limits.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "access/xact.h" +#include "access/xlog.h" +#include "catalog/objectaccess.h" +#include "catalog/pg_authid.h" +#include "catalog/pg_parameter_acl.h" +#include "guc_internal.h" +#include "libpq/pqformat.h" +#include "parser/scansup.h" +#include "port/pg_bitutils.h" +#include "storage/fd.h" +#include "storage/lwlock.h" +#include "storage/shmem.h" +#include "tcop/tcopprot.h" +#include "utils/acl.h" +#include "utils/backend_status.h" +#include "utils/builtins.h" +#include "utils/conffiles.h" +#include "utils/float.h" +#include "utils/guc_tables.h" +#include "utils/memutils.h" +#include "utils/timestamp.h" + + +#define CONFIG_FILENAME "postgresql.conf" +#define HBA_FILENAME "pg_hba.conf" +#define IDENT_FILENAME "pg_ident.conf" + +#ifdef EXEC_BACKEND +#define CONFIG_EXEC_PARAMS "global/config_exec_params" +#define CONFIG_EXEC_PARAMS_NEW "global/config_exec_params.new" +#endif + +/* + * Precision with which REAL type guc values are to be printed for GUC + * serialization. + */ +#define REALTYPE_PRECISION 17 + +static __thread int GUC_check_errcode_value; + +static __thread List *reserved_class_prefix = NIL; + +/* global variables for check hook support */ +__thread char *GUC_check_errmsg_string; +__thread char *GUC_check_errdetail_string; +__thread char *GUC_check_errhint_string; + +/* Kluge: for speed, we examine this GUC variable's value directly */ +extern __thread bool in_hot_standby_guc; + + +/* + * Unit conversion tables. + * + * There are two tables, one for memory units, and another for time units. + * For each supported conversion from one unit to another, we have an entry + * in the table. + * + * To keep things simple, and to avoid possible roundoff error, + * conversions are never chained. There needs to be a direct conversion + * between all units (of the same type). + * + * The conversions for each base unit must be kept in order from greatest to + * smallest human-friendly unit; convert_xxx_from_base_unit() rely on that. + * (The order of the base-unit groups does not matter.) + */ +#define MAX_UNIT_LEN 3 /* length of longest recognized unit string */ + +typedef struct +{ + char unit[MAX_UNIT_LEN + 1]; /* unit, as a string, like "kB" or + * "min" */ + int base_unit; /* GUC_UNIT_XXX */ + double multiplier; /* Factor for converting unit -> base_unit */ +} unit_conversion; + +/* Ensure that the constants in the tables don't overflow or underflow */ +#if BLCKSZ < 1024 || BLCKSZ > (1024*1024) +#error BLCKSZ must be between 1KB and 1MB +#endif +#if XLOG_BLCKSZ < 1024 || XLOG_BLCKSZ > (1024*1024) +#error XLOG_BLCKSZ must be between 1KB and 1MB +#endif + +static __thread const char *memory_units_hint = gettext_noop("Valid units for this parameter are \"B\", \"kB\", \"MB\", \"GB\", and \"TB\"."); + +static const unit_conversion memory_unit_conversion_table[] = +{ + {"TB", GUC_UNIT_BYTE, 1024.0 * 1024.0 * 1024.0 * 1024.0}, + {"GB", GUC_UNIT_BYTE, 1024.0 * 1024.0 * 1024.0}, + {"MB", GUC_UNIT_BYTE, 1024.0 * 1024.0}, + {"kB", GUC_UNIT_BYTE, 1024.0}, + {"B", GUC_UNIT_BYTE, 1.0}, + + {"TB", GUC_UNIT_KB, 1024.0 * 1024.0 * 1024.0}, + {"GB", GUC_UNIT_KB, 1024.0 * 1024.0}, + {"MB", GUC_UNIT_KB, 1024.0}, + {"kB", GUC_UNIT_KB, 1.0}, + {"B", GUC_UNIT_KB, 1.0 / 1024.0}, + + {"TB", GUC_UNIT_MB, 1024.0 * 1024.0}, + {"GB", GUC_UNIT_MB, 1024.0}, + {"MB", GUC_UNIT_MB, 1.0}, + {"kB", GUC_UNIT_MB, 1.0 / 1024.0}, + {"B", GUC_UNIT_MB, 1.0 / (1024.0 * 1024.0)}, + + {"TB", GUC_UNIT_BLOCKS, (1024.0 * 1024.0 * 1024.0) / (BLCKSZ / 1024)}, + {"GB", GUC_UNIT_BLOCKS, (1024.0 * 1024.0) / (BLCKSZ / 1024)}, + {"MB", GUC_UNIT_BLOCKS, 1024.0 / (BLCKSZ / 1024)}, + {"kB", GUC_UNIT_BLOCKS, 1.0 / (BLCKSZ / 1024)}, + {"B", GUC_UNIT_BLOCKS, 1.0 / BLCKSZ}, + + {"TB", GUC_UNIT_XBLOCKS, (1024.0 * 1024.0 * 1024.0) / (XLOG_BLCKSZ / 1024)}, + {"GB", GUC_UNIT_XBLOCKS, (1024.0 * 1024.0) / (XLOG_BLCKSZ / 1024)}, + {"MB", GUC_UNIT_XBLOCKS, 1024.0 / (XLOG_BLCKSZ / 1024)}, + {"kB", GUC_UNIT_XBLOCKS, 1.0 / (XLOG_BLCKSZ / 1024)}, + {"B", GUC_UNIT_XBLOCKS, 1.0 / XLOG_BLCKSZ}, + + {""} /* end of table marker */ +}; + +static __thread const char *time_units_hint = gettext_noop("Valid units for this parameter are \"us\", \"ms\", \"s\", \"min\", \"h\", and \"d\"."); + +static const unit_conversion time_unit_conversion_table[] = +{ + {"d", GUC_UNIT_MS, 1000 * 60 * 60 * 24}, + {"h", GUC_UNIT_MS, 1000 * 60 * 60}, + {"min", GUC_UNIT_MS, 1000 * 60}, + {"s", GUC_UNIT_MS, 1000}, + {"ms", GUC_UNIT_MS, 1}, + {"us", GUC_UNIT_MS, 1.0 / 1000}, + + {"d", GUC_UNIT_S, 60 * 60 * 24}, + {"h", GUC_UNIT_S, 60 * 60}, + {"min", GUC_UNIT_S, 60}, + {"s", GUC_UNIT_S, 1}, + {"ms", GUC_UNIT_S, 1.0 / 1000}, + {"us", GUC_UNIT_S, 1.0 / (1000 * 1000)}, + + {"d", GUC_UNIT_MIN, 60 * 24}, + {"h", GUC_UNIT_MIN, 60}, + {"min", GUC_UNIT_MIN, 1}, + {"s", GUC_UNIT_MIN, 1.0 / 60}, + {"ms", GUC_UNIT_MIN, 1.0 / (1000 * 60)}, + {"us", GUC_UNIT_MIN, 1.0 / (1000 * 1000 * 60)}, + + {""} /* end of table marker */ +}; + +/* + * To allow continued support of obsolete names for GUC variables, we apply + * the following mappings to any unrecognized name. Note that an old name + * should be mapped to a new one only if the new variable has very similar + * semantics to the old. + */ +static const char *const map_old_guc_names[] = { + "sort_mem", "work_mem", + "vacuum_mem", "maintenance_work_mem", + NULL +}; + + +/* Memory context holding all GUC-related data */ +static __thread MemoryContext GUCMemoryContext; + +/* + * We use a dynahash table to look up GUCs by name, or to iterate through + * all the GUCs. The gucname field is redundant with gucvar->name, but + * dynahash makes it too painful to not store the hash key separately. + */ +typedef struct +{ + const char *gucname; /* hash key */ + struct config_generic *gucvar; /* -> GUC's defining structure */ +} GUCHashEntry; + +static __thread HTAB *guc_hashtab; /* entries are GUCHashEntrys */ + +/* + * In addition to the hash table, variables having certain properties are + * linked into these lists, so that we can find them without scanning the + * whole hash table. In most applications, only a small fraction of the + * GUCs appear in these lists at any given time. The usage of the stack + * and report lists is stylized enough that they can be slists, but the + * nondef list has to be a dlist to avoid O(N) deletes in common cases. + */ +static __thread dlist_head guc_nondef_list; /* list of variables that have source + * different from PGC_S_DEFAULT */ +static __thread slist_head guc_stack_list; /* list of variables that have non-NULL + * stack */ +static __thread slist_head guc_report_list; /* list of variables that have the + * GUC_NEEDS_REPORT bit set in status */ + +static __thread bool reporting_enabled; /* true to enable GUC_REPORT */ + +static __thread int GUCNestLevel = 0; /* 1 when in main transaction */ + + +static int guc_var_compare(const void *a, const void *b); +static uint32 guc_name_hash(const void *key, Size keysize); +static int guc_name_match(const void *key1, const void *key2, Size keysize); +static void InitializeGUCOptionsFromEnvironment(void); +static void InitializeOneGUCOption(struct config_generic *gconf); +static void RemoveGUCFromLists(struct config_generic *gconf); +static void set_guc_source(struct config_generic *gconf, GucSource newsource); +static void pg_timezone_abbrev_initialize(void); +static void push_old_value(struct config_generic *gconf, GucAction action); +static void ReportGUCOption(struct config_generic *record); +static void set_config_sourcefile(const char *name, char *sourcefile, + int sourceline); +static void reapply_stacked_values(struct config_generic *variable, + struct config_string *pHolder, + GucStack *stack, + const char *curvalue, + GucContext curscontext, GucSource cursource, + Oid cursrole); +static bool validate_option_array_item(const char *name, const char *value, + bool skipIfNoPermissions); +static void write_auto_conf_file(int fd, const char *filename, ConfigVariable *head); +static void replace_auto_config_value(ConfigVariable **head_p, ConfigVariable **tail_p, + const char *name, const char *value); +static bool valid_custom_variable_name(const char *name); +static void do_serialize(char **destptr, Size *maxbytes, + const char *fmt,...) pg_attribute_printf(3, 4); +static bool call_bool_check_hook(struct config_bool *conf, bool *newval, + void **extra, GucSource source, int elevel); +static bool call_int_check_hook(struct config_int *conf, int *newval, + void **extra, GucSource source, int elevel); +static bool call_real_check_hook(struct config_real *conf, double *newval, + void **extra, GucSource source, int elevel); +static bool call_string_check_hook(struct config_string *conf, char **newval, + void **extra, GucSource source, int elevel); +static bool call_enum_check_hook(struct config_enum *conf, int *newval, + void **extra, GucSource source, int elevel); + + +/* + * This function handles both actual config file (re)loads and execution of + * show_all_file_settings() (i.e., the pg_file_settings view). In the latter + * case we don't apply any of the settings, but we make all the usual validity + * checks, and we return the ConfigVariable list so that it can be printed out + * by show_all_file_settings(). + */ +ConfigVariable * +ProcessConfigFileInternal(GucContext context, bool applySettings, int elevel) +{ + bool error = false; + bool applying = false; + const char *ConfFileWithError; + ConfigVariable *item, + *head, + *tail; + HASH_SEQ_STATUS status; + GUCHashEntry *hentry; + + /* Parse the main config file into a list of option names and values */ + ConfFileWithError = ConfigFileName; + head = tail = NULL; + + if (!ParseConfigFile(ConfigFileName, true, + NULL, 0, CONF_FILE_START_DEPTH, elevel, + &head, &tail)) + { + /* Syntax error(s) detected in the file, so bail out */ + error = true; + goto bail_out; + } + + /* + * Parse the PG_AUTOCONF_FILENAME file, if present, after the main file to + * replace any parameters set by ALTER SYSTEM command. Because this file + * is in the data directory, we can't read it until the DataDir has been + * set. + */ + if (DataDir) + { + if (!ParseConfigFile(PG_AUTOCONF_FILENAME, false, + NULL, 0, CONF_FILE_START_DEPTH, elevel, + &head, &tail)) + { + /* Syntax error(s) detected in the file, so bail out */ + error = true; + ConfFileWithError = PG_AUTOCONF_FILENAME; + goto bail_out; + } + } + else + { + /* + * If DataDir is not set, the PG_AUTOCONF_FILENAME file cannot be + * read. In this case, we don't want to accept any settings but + * data_directory from postgresql.conf, because they might be + * overwritten with settings in the PG_AUTOCONF_FILENAME file which + * will be read later. OTOH, since data_directory isn't allowed in the + * PG_AUTOCONF_FILENAME file, it will never be overwritten later. + */ + ConfigVariable *newlist = NULL; + + /* + * Prune all items except the last "data_directory" from the list. + */ + for (item = head; item; item = item->next) + { + if (!item->ignore && + strcmp(item->name, "data_directory") == 0) + newlist = item; + } + + if (newlist) + newlist->next = NULL; + head = tail = newlist; + + /* + * Quick exit if data_directory is not present in file. + * + * We need not do any further processing, in particular we don't set + * PgReloadTime; that will be set soon by subsequent full loading of + * the config file. + */ + if (head == NULL) + goto bail_out; + } + + /* + * Mark all extant GUC variables as not present in the config file. We + * need this so that we can tell below which ones have been removed from + * the file since we last processed it. + */ + hash_seq_init(&status, guc_hashtab); + while ((hentry = (GUCHashEntry *) hash_seq_search(&status)) != NULL) + { + struct config_generic *gconf = hentry->gucvar; + + gconf->status &= ~GUC_IS_IN_FILE; + } + + /* + * Check if all the supplied option names are valid, as an additional + * quasi-syntactic check on the validity of the config file. It is + * important that the postmaster and all backends agree on the results of + * this phase, else we will have strange inconsistencies about which + * processes accept a config file update and which don't. Hence, unknown + * custom variable names have to be accepted without complaint. For the + * same reason, we don't attempt to validate the options' values here. + * + * In addition, the GUC_IS_IN_FILE flag is set on each existing GUC + * variable mentioned in the file; and we detect duplicate entries in the + * file and mark the earlier occurrences as ignorable. + */ + for (item = head; item; item = item->next) + { + struct config_generic *record; + + /* Ignore anything already marked as ignorable */ + if (item->ignore) + continue; + + /* + * Try to find the variable; but do not create a custom placeholder if + * it's not there already. + */ + record = find_option(item->name, false, true, elevel); + + if (record) + { + /* If it's already marked, then this is a duplicate entry */ + if (record->status & GUC_IS_IN_FILE) + { + /* + * Mark the earlier occurrence(s) as dead/ignorable. We could + * avoid the O(N^2) behavior here with some additional state, + * but it seems unlikely to be worth the trouble. + */ + ConfigVariable *pitem; + + for (pitem = head; pitem != item; pitem = pitem->next) + { + if (!pitem->ignore && + strcmp(pitem->name, item->name) == 0) + pitem->ignore = true; + } + } + /* Now mark it as present in file */ + record->status |= GUC_IS_IN_FILE; + } + else if (!valid_custom_variable_name(item->name)) + { + /* Invalid non-custom variable, so complain */ + ereport(elevel, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("unrecognized configuration parameter \"%s\" in file \"%s\" line %d", + item->name, + item->filename, item->sourceline))); + item->errmsg = pstrdup("unrecognized configuration parameter"); + error = true; + ConfFileWithError = item->filename; + } + } + + /* + * If we've detected any errors so far, we don't want to risk applying any + * changes. + */ + if (error) + goto bail_out; + + /* Otherwise, set flag that we're beginning to apply changes */ + applying = true; + + /* + * Check for variables having been removed from the config file, and + * revert their reset values (and perhaps also effective values) to the + * boot-time defaults. If such a variable can't be changed after startup, + * report that and continue. + */ + hash_seq_init(&status, guc_hashtab); + while ((hentry = (GUCHashEntry *) hash_seq_search(&status)) != NULL) + { + struct config_generic *gconf = hentry->gucvar; + GucStack *stack; + + if (gconf->reset_source != PGC_S_FILE || + (gconf->status & GUC_IS_IN_FILE)) + continue; + if (gconf->context < PGC_SIGHUP) + { + /* The removal can't be effective without a restart */ + gconf->status |= GUC_PENDING_RESTART; + ereport(elevel, + (errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM), + errmsg("parameter \"%s\" cannot be changed without restarting the server", + gconf->name))); + record_config_file_error(psprintf("parameter \"%s\" cannot be changed without restarting the server", + gconf->name), + NULL, 0, + &head, &tail); + error = true; + continue; + } + + /* No more to do if we're just doing show_all_file_settings() */ + if (!applySettings) + continue; + + /* + * Reset any "file" sources to "default", else set_config_option will + * not override those settings. + */ + if (gconf->reset_source == PGC_S_FILE) + gconf->reset_source = PGC_S_DEFAULT; + if (gconf->source == PGC_S_FILE) + set_guc_source(gconf, PGC_S_DEFAULT); + for (stack = gconf->stack; stack; stack = stack->prev) + { + if (stack->source == PGC_S_FILE) + stack->source = PGC_S_DEFAULT; + } + + /* Now we can re-apply the wired-in default (i.e., the boot_val) */ + if (set_config_option(gconf->name, NULL, + context, PGC_S_DEFAULT, + GUC_ACTION_SET, true, 0, false) > 0) + { + /* Log the change if appropriate */ + if (context == PGC_SIGHUP) + ereport(elevel, + (errmsg("parameter \"%s\" removed from configuration file, reset to default", + gconf->name))); + } + } + + /* + * Restore any variables determined by environment variables or + * dynamically-computed defaults. This is a no-op except in the case + * where one of these had been in the config file and is now removed. + * + * In particular, we *must not* do this during the postmaster's initial + * loading of the file, since the timezone functions in particular should + * be run only after initialization is complete. + * + * XXX this is an unmaintainable crock, because we have to know how to set + * (or at least what to call to set) every non-PGC_INTERNAL variable that + * could potentially have PGC_S_DYNAMIC_DEFAULT or PGC_S_ENV_VAR source. + */ + if (context == PGC_SIGHUP && applySettings) + { + InitializeGUCOptionsFromEnvironment(); + pg_timezone_abbrev_initialize(); + /* this selects SQL_ASCII in processes not connected to a database */ + SetConfigOption("client_encoding", GetDatabaseEncodingName(), + PGC_BACKEND, PGC_S_DYNAMIC_DEFAULT); + } + + /* + * Now apply the values from the config file. + */ + for (item = head; item; item = item->next) + { + char *pre_value = NULL; + int scres; + + /* Ignore anything marked as ignorable */ + if (item->ignore) + continue; + + /* In SIGHUP cases in the postmaster, we want to report changes */ + if (context == PGC_SIGHUP && applySettings && !IsUnderPostmaster) + { + const char *preval = GetConfigOption(item->name, true, false); + + /* If option doesn't exist yet or is NULL, treat as empty string */ + if (!preval) + preval = ""; + /* must dup, else might have dangling pointer below */ + pre_value = pstrdup(preval); + } + + scres = set_config_option(item->name, item->value, + context, PGC_S_FILE, + GUC_ACTION_SET, applySettings, 0, false); + if (scres > 0) + { + /* variable was updated, so log the change if appropriate */ + if (pre_value) + { + const char *post_value = GetConfigOption(item->name, true, false); + + if (!post_value) + post_value = ""; + if (strcmp(pre_value, post_value) != 0) + ereport(elevel, + (errmsg("parameter \"%s\" changed to \"%s\"", + item->name, item->value))); + } + item->applied = true; + } + else if (scres == 0) + { + error = true; + item->errmsg = pstrdup("setting could not be applied"); + ConfFileWithError = item->filename; + } + else + { + /* no error, but variable's active value was not changed */ + item->applied = true; + } + + /* + * We should update source location unless there was an error, since + * even if the active value didn't change, the reset value might have. + * (In the postmaster, there won't be a difference, but it does matter + * in backends.) + */ + if (scres != 0 && applySettings) + set_config_sourcefile(item->name, item->filename, + item->sourceline); + + if (pre_value) + pfree(pre_value); + } + + /* Remember when we last successfully loaded the config file. */ + if (applySettings) + PgReloadTime = GetCurrentTimestamp(); + +bail_out: + if (error && applySettings) + { + /* During postmaster startup, any error is fatal */ + if (context == PGC_POSTMASTER) + ereport(ERROR, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("configuration file \"%s\" contains errors", + ConfFileWithError))); + else if (applying) + ereport(elevel, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("configuration file \"%s\" contains errors; unaffected changes were applied", + ConfFileWithError))); + else + ereport(elevel, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("configuration file \"%s\" contains errors; no changes were applied", + ConfFileWithError))); + } + + /* Successful or otherwise, return the collected data list */ + return head; +} + + +/* + * Some infrastructure for GUC-related memory allocation + * + * These functions are generally modeled on libc's malloc/realloc/etc, + * but any OOM issue is reported at the specified elevel. + * (Thus, control returns only if that's less than ERROR.) + */ +void * +guc_malloc(int elevel, size_t size) +{ + void *data; + + data = MemoryContextAllocExtended(GUCMemoryContext, size, + MCXT_ALLOC_NO_OOM); + if (unlikely(data == NULL)) + ereport(elevel, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"))); + return data; +} + +void * +guc_realloc(int elevel, void *old, size_t size) +{ + void *data; + + if (old != NULL) + { + /* This is to help catch old code that malloc's GUC data. */ + Assert(GetMemoryChunkContext(old) == GUCMemoryContext); + data = repalloc_extended(old, size, + MCXT_ALLOC_NO_OOM); + } + else + { + /* Like realloc(3), but not like repalloc(), we allow old == NULL. */ + data = MemoryContextAllocExtended(GUCMemoryContext, size, + MCXT_ALLOC_NO_OOM); + } + if (unlikely(data == NULL)) + ereport(elevel, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"))); + return data; +} + +char * +guc_strdup(int elevel, const char *src) +{ + char *data; + size_t len = strlen(src) + 1; + + data = guc_malloc(elevel, len); + if (likely(data != NULL)) + memcpy(data, src, len); + return data; +} + +void +guc_free(void *ptr) +{ + /* + * Historically, GUC-related code has relied heavily on the ability to do + * free(NULL), so we allow that here even though pfree() doesn't. + */ + if (ptr != NULL) + { + /* This is to help catch old code that malloc's GUC data. */ + Assert(GetMemoryChunkContext(ptr) == GUCMemoryContext); + pfree(ptr); + } +} + + +/* + * Detect whether strval is referenced anywhere in a GUC string item + */ +static bool +string_field_used(struct config_string *conf, char *strval) +{ + GucStack *stack; + + if (strval == *(conf->variable) || + strval == conf->reset_val || + strval == conf->boot_val) + return true; + for (stack = conf->gen.stack; stack; stack = stack->prev) + { + if (strval == stack->prior.val.stringval || + strval == stack->masked.val.stringval) + return true; + } + return false; +} + +/* + * Support for assigning to a field of a string GUC item. Free the prior + * value if it's not referenced anywhere else in the item (including stacked + * states). + */ +static void +set_string_field(struct config_string *conf, char **field, char *newval) +{ + char *oldval = *field; + + /* Do the assignment */ + *field = newval; + + /* Free old value if it's not NULL and isn't referenced anymore */ + if (oldval && !string_field_used(conf, oldval)) + guc_free(oldval); +} + +/* + * Detect whether an "extra" struct is referenced anywhere in a GUC item + */ +static bool +extra_field_used(struct config_generic *gconf, void *extra) +{ + GucStack *stack; + + if (extra == gconf->extra) + return true; + switch (gconf->vartype) + { + case PGC_BOOL: + if (extra == ((struct config_bool *) gconf)->reset_extra) + return true; + break; + case PGC_INT: + if (extra == ((struct config_int *) gconf)->reset_extra) + return true; + break; + case PGC_REAL: + if (extra == ((struct config_real *) gconf)->reset_extra) + return true; + break; + case PGC_STRING: + if (extra == ((struct config_string *) gconf)->reset_extra) + return true; + break; + case PGC_ENUM: + if (extra == ((struct config_enum *) gconf)->reset_extra) + return true; + break; + } + for (stack = gconf->stack; stack; stack = stack->prev) + { + if (extra == stack->prior.extra || + extra == stack->masked.extra) + return true; + } + + return false; +} + +/* + * Support for assigning to an "extra" field of a GUC item. Free the prior + * value if it's not referenced anywhere else in the item (including stacked + * states). + */ +static void +set_extra_field(struct config_generic *gconf, void **field, void *newval) +{ + void *oldval = *field; + + /* Do the assignment */ + *field = newval; + + /* Free old value if it's not NULL and isn't referenced anymore */ + if (oldval && !extra_field_used(gconf, oldval)) + guc_free(oldval); +} + +/* + * Support for copying a variable's active value into a stack entry. + * The "extra" field associated with the active value is copied, too. + * + * NB: be sure stringval and extra fields of a new stack entry are + * initialized to NULL before this is used, else we'll try to guc_free() them. + */ +static void +set_stack_value(struct config_generic *gconf, config_var_value *val) +{ + switch (gconf->vartype) + { + case PGC_BOOL: + val->val.boolval = + *((struct config_bool *) gconf)->variable; + break; + case PGC_INT: + val->val.intval = + *((struct config_int *) gconf)->variable; + break; + case PGC_REAL: + val->val.realval = + *((struct config_real *) gconf)->variable; + break; + case PGC_STRING: + set_string_field((struct config_string *) gconf, + &(val->val.stringval), + *((struct config_string *) gconf)->variable); + break; + case PGC_ENUM: + val->val.enumval = + *((struct config_enum *) gconf)->variable; + break; + } + set_extra_field(gconf, &(val->extra), gconf->extra); +} + +/* + * Support for discarding a no-longer-needed value in a stack entry. + * The "extra" field associated with the stack entry is cleared, too. + */ +static void +discard_stack_value(struct config_generic *gconf, config_var_value *val) +{ + switch (gconf->vartype) + { + case PGC_BOOL: + case PGC_INT: + case PGC_REAL: + case PGC_ENUM: + /* no need to do anything */ + break; + case PGC_STRING: + set_string_field((struct config_string *) gconf, + &(val->val.stringval), + NULL); + break; + } + set_extra_field(gconf, &(val->extra), NULL); +} + + +/* + * Fetch a palloc'd, sorted array of GUC struct pointers + * + * The array length is returned into *num_vars. + */ +struct config_generic ** +get_guc_variables(int *num_vars) +{ + struct config_generic **result; + HASH_SEQ_STATUS status; + GUCHashEntry *hentry; + int i; + + *num_vars = hash_get_num_entries(guc_hashtab); + result = palloc(sizeof(struct config_generic *) * *num_vars); + + /* Extract pointers from the hash table */ + i = 0; + hash_seq_init(&status, guc_hashtab); + while ((hentry = (GUCHashEntry *) hash_seq_search(&status)) != NULL) + result[i++] = hentry->gucvar; + Assert(i == *num_vars); + + /* Sort by name */ + qsort(result, *num_vars, + sizeof(struct config_generic *), guc_var_compare); + + return result; +} + + +/* + * Build the GUC hash table. This is split out so that help_config.c can + * extract all the variables without running all of InitializeGUCOptions. + * It's not meant for use anyplace else. + */ +void +build_guc_variables(void) +{ +} + +/* + * Add a new GUC variable to the hash of known variables. The + * hash is expanded if needed. + */ +static bool +add_guc_variable(struct config_generic *var, int elevel) +{ + GUCHashEntry *hentry; + bool found; + + hentry = (GUCHashEntry *) hash_search(guc_hashtab, + &var->name, + HASH_ENTER_NULL, + &found); + if (unlikely(hentry == NULL)) + { + ereport(elevel, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"))); + return false; /* out of memory */ + } + Assert(!found); + hentry->gucvar = var; + return true; +} + +/* + * Decide whether a proposed custom variable name is allowed. + * + * It must be two or more identifiers separated by dots, where the rules + * for what is an identifier agree with scan.l. (If you change this rule, + * adjust the errdetail in find_option().) + */ +static bool +valid_custom_variable_name(const char *name) +{ + bool saw_sep = false; + bool name_start = true; + + for (const char *p = name; *p; p++) + { + if (*p == GUC_QUALIFIER_SEPARATOR) + { + if (name_start) + return false; /* empty name component */ + saw_sep = true; + name_start = true; + } + else if (strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz_", *p) != NULL || + IS_HIGHBIT_SET(*p)) + { + /* okay as first or non-first character */ + name_start = false; + } + else if (!name_start && strchr("0123456789$", *p) != NULL) + /* okay as non-first character */ ; + else + return false; + } + if (name_start) + return false; /* empty name component */ + /* OK if we found at least one separator */ + return saw_sep; +} + +/* + * Create and add a placeholder variable for a custom variable name. + */ +static struct config_generic * +add_placeholder_variable(const char *name, int elevel) +{ + size_t sz = sizeof(struct config_string) + sizeof(char *); + struct config_string *var; + struct config_generic *gen; + + var = (struct config_string *) guc_malloc(elevel, sz); + if (var == NULL) + return NULL; + memset(var, 0, sz); + gen = &var->gen; + + gen->name = guc_strdup(elevel, name); + if (gen->name == NULL) + { + guc_free(var); + return NULL; + } + + gen->context = PGC_USERSET; + gen->group = CUSTOM_OPTIONS; + gen->short_desc = "GUC placeholder variable"; + gen->flags = GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE | GUC_CUSTOM_PLACEHOLDER; + gen->vartype = PGC_STRING; + + /* + * The char* is allocated at the end of the struct since we have no + * 'static' place to point to. Note that the current value, as well as + * the boot and reset values, start out NULL. + */ + var->variable = (char **) (var + 1); + + if (!add_guc_variable((struct config_generic *) var, elevel)) + { + guc_free(unconstify(char *, gen->name)); + guc_free(var); + return NULL; + } + + return gen; +} + +/* + * Look up option "name". If it exists, return a pointer to its record. + * Otherwise, if create_placeholders is true and name is a valid-looking + * custom variable name, we'll create and return a placeholder record. + * Otherwise, if skip_errors is true, then we silently return NULL for + * an unrecognized or invalid name. Otherwise, the error is reported at + * error level elevel (and we return NULL if that's less than ERROR). + * + * Note: internal errors, primarily out-of-memory, draw an elevel-level + * report and NULL return regardless of skip_errors. Hence, callers must + * handle a NULL return whenever elevel < ERROR, but they should not need + * to emit any additional error message. (In practice, internal errors + * can only happen when create_placeholders is true, so callers passing + * false need not think terribly hard about this.) + */ +struct config_generic * +find_option(const char *name, bool create_placeholders, bool skip_errors, + int elevel) +{ + GUCHashEntry *hentry; + int i; + + Assert(name); + + /* Look it up using the hash table. */ + hentry = (GUCHashEntry *) hash_search(guc_hashtab, + &name, + HASH_FIND, + NULL); + if (hentry) + return hentry->gucvar; + + /* + * See if the name is an obsolete name for a variable. We assume that the + * set of supported old names is short enough that a brute-force search is + * the best way. + */ + for (i = 0; map_old_guc_names[i] != NULL; i += 2) + { + if (guc_name_compare(name, map_old_guc_names[i]) == 0) + return find_option(map_old_guc_names[i + 1], false, + skip_errors, elevel); + } + + if (create_placeholders) + { + /* + * Check if the name is valid, and if so, add a placeholder. If it + * doesn't contain a separator, don't assume that it was meant to be a + * placeholder. + */ + const char *sep = strchr(name, GUC_QUALIFIER_SEPARATOR); + + if (sep != NULL) + { + size_t classLen = sep - name; + ListCell *lc; + + /* The name must be syntactically acceptable ... */ + if (!valid_custom_variable_name(name)) + { + if (!skip_errors) + ereport(elevel, + (errcode(ERRCODE_INVALID_NAME), + errmsg("invalid configuration parameter name \"%s\"", + name), + errdetail("Custom parameter names must be two or more simple identifiers separated by dots."))); + return NULL; + } + /* ... and it must not match any previously-reserved prefix */ + foreach(lc, reserved_class_prefix) + { + const char *rcprefix = lfirst(lc); + + if (strlen(rcprefix) == classLen && + strncmp(name, rcprefix, classLen) == 0) + { + if (!skip_errors) + ereport(elevel, + (errcode(ERRCODE_INVALID_NAME), + errmsg("invalid configuration parameter name \"%s\"", + name), + errdetail("\"%s\" is a reserved prefix.", + rcprefix))); + return NULL; + } + } + /* OK, create it */ + return add_placeholder_variable(name, elevel); + } + } + + /* Unknown name */ + if (!skip_errors) + ereport(elevel, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("unrecognized configuration parameter \"%s\"", + name))); + return NULL; +} + + +/* + * comparator for qsorting an array of GUC pointers + */ +static int +guc_var_compare(const void *a, const void *b) +{ + const struct config_generic *confa = *(struct config_generic *const *) a; + const struct config_generic *confb = *(struct config_generic *const *) b; + + return guc_name_compare(confa->name, confb->name); +} + +/* + * the bare comparison function for GUC names + */ +int +guc_name_compare(const char *namea, const char *nameb) +{ + /* + * The temptation to use strcasecmp() here must be resisted, because the + * hash mapping has to remain stable across setlocale() calls. So, build + * our own with a simple ASCII-only downcasing. + */ + while (*namea && *nameb) + { + char cha = *namea++; + char chb = *nameb++; + + if (cha >= 'A' && cha <= 'Z') + cha += 'a' - 'A'; + if (chb >= 'A' && chb <= 'Z') + chb += 'a' - 'A'; + if (cha != chb) + return cha - chb; + } + if (*namea) + return 1; /* a is longer */ + if (*nameb) + return -1; /* b is longer */ + return 0; +} + +/* + * Hash function that's compatible with guc_name_compare + */ +static uint32 +guc_name_hash(const void *key, Size keysize) +{ + uint32 result = 0; + const char *name = *(const char *const *) key; + + while (*name) + { + char ch = *name++; + + /* Case-fold in the same way as guc_name_compare */ + if (ch >= 'A' && ch <= 'Z') + ch += 'a' - 'A'; + + /* Merge into hash ... not very bright, but it needn't be */ + result = pg_rotate_left32(result, 5); + result ^= (uint32) ch; + } + return result; +} + +/* + * Dynahash match function to use in guc_hashtab + */ +static int +guc_name_match(const void *key1, const void *key2, Size keysize) +{ + const char *name1 = *(const char *const *) key1; + const char *name2 = *(const char *const *) key2; + + return guc_name_compare(name1, name2); +} + + +/* + * Convert a GUC name to the form that should be used in pg_parameter_acl. + * + * We need to canonicalize entries since, for example, case should not be + * significant. In addition, we apply the map_old_guc_names[] mapping so that + * any obsolete names will be converted when stored in a new PG version. + * Note however that this function does not verify legality of the name. + * + * The result is a palloc'd string. + */ +char * +convert_GUC_name_for_parameter_acl(const char *name) +{ + char *result; + + /* Apply old-GUC-name mapping. */ + for (int i = 0; map_old_guc_names[i] != NULL; i += 2) + { + if (guc_name_compare(name, map_old_guc_names[i]) == 0) + { + name = map_old_guc_names[i + 1]; + break; + } + } + + /* Apply case-folding that matches guc_name_compare(). */ + result = pstrdup(name); + for (char *ptr = result; *ptr != '\0'; ptr++) + { + char ch = *ptr; + + if (ch >= 'A' && ch <= 'Z') + { + ch += 'a' - 'A'; + *ptr = ch; + } + } + + return result; +} + +/* + * Check whether we should allow creation of a pg_parameter_acl entry + * for the given name. (This can be applied either before or after + * canonicalizing it.) + */ +bool +check_GUC_name_for_parameter_acl(const char *name) +{ + /* OK if the GUC exists. */ + if (find_option(name, false, true, DEBUG1) != NULL) + return true; + /* Otherwise, it'd better be a valid custom GUC name. */ + if (valid_custom_variable_name(name)) + return true; + return false; +} + +/* + * Routine in charge of checking various states of a GUC. + * + * This performs two sanity checks. First, it checks that the initial + * value of a GUC is the same when declared and when loaded to prevent + * anybody looking at the C declarations of these GUCs from being fooled by + * mismatched values. Second, it checks for incorrect flag combinations. + * + * The following validation rules apply for the values: + * bool - can be false, otherwise must be same as the boot_val + * int - can be 0, otherwise must be same as the boot_val + * real - can be 0.0, otherwise must be same as the boot_val + * string - can be NULL, otherwise must be strcmp equal to the boot_val + * enum - must be same as the boot_val + */ +#ifdef USE_ASSERT_CHECKING +static bool +check_GUC_init(struct config_generic *gconf) +{ + /* Checks on values */ + switch (gconf->vartype) + { + case PGC_BOOL: + { + struct config_bool *conf = (struct config_bool *) gconf; + + if (*conf->variable && !conf->boot_val) + { + elog(LOG, "GUC (PGC_BOOL) %s, boot_val=%d, C-var=%d", + conf->gen.name, conf->boot_val, *conf->variable); + return false; + } + break; + } + case PGC_INT: + { + struct config_int *conf = (struct config_int *) gconf; + + if (*conf->variable != 0 && *conf->variable != conf->boot_val) + { + elog(LOG, "GUC (PGC_INT) %s, boot_val=%d, C-var=%d", + conf->gen.name, conf->boot_val, *conf->variable); + return false; + } + break; + } + case PGC_REAL: + { + struct config_real *conf = (struct config_real *) gconf; + + if (*conf->variable != 0.0 && *conf->variable != conf->boot_val) + { + elog(LOG, "GUC (PGC_REAL) %s, boot_val=%g, C-var=%g", + conf->gen.name, conf->boot_val, *conf->variable); + return false; + } + break; + } + case PGC_STRING: + { + struct config_string *conf = (struct config_string *) gconf; + + if (*conf->variable != NULL && + (conf->boot_val == NULL || + strcmp(*conf->variable, conf->boot_val) != 0)) + { + elog(LOG, "GUC (PGC_STRING) %s, boot_val=%s, C-var=%s", + conf->gen.name, conf->boot_val ? conf->boot_val : "<null>", *conf->variable); + return false; + } + break; + } + case PGC_ENUM: + { + struct config_enum *conf = (struct config_enum *) gconf; + + if (*conf->variable != conf->boot_val) + { + elog(LOG, "GUC (PGC_ENUM) %s, boot_val=%d, C-var=%d", + conf->gen.name, conf->boot_val, *conf->variable); + return false; + } + break; + } + } + + /* Flag combinations */ + + /* + * GUC_NO_SHOW_ALL requires GUC_NOT_IN_SAMPLE, as a parameter not part of + * SHOW ALL should not be hidden in postgresql.conf.sample. + */ + if ((gconf->flags & GUC_NO_SHOW_ALL) && + !(gconf->flags & GUC_NOT_IN_SAMPLE)) + { + elog(LOG, "GUC %s flags: NO_SHOW_ALL and !NOT_IN_SAMPLE", + gconf->name); + return false; + } + + return true; +} +#endif + +/* + * Initialize GUC options during program startup. + * + * Note that we cannot read the config file yet, since we have not yet + * processed command-line switches. + */ +void +InitializeGUCOptions(void) +{ + HASH_SEQ_STATUS status; + GUCHashEntry *hentry; + + /* + * Before log_line_prefix could possibly receive a nonempty setting, make + * sure that timezone processing is minimally alive (see elog.c). + */ + pg_timezone_initialize(); + + /* + * Create GUCMemoryContext and build hash table of all GUC variables. + */ + build_guc_variables(); + + /* + * Load all variables with their compiled-in defaults, and initialize + * status fields as needed. + */ + hash_seq_init(&status, guc_hashtab); + while ((hentry = (GUCHashEntry *) hash_seq_search(&status)) != NULL) + { + /* Check mapping between initial and default value */ + Assert(check_GUC_init(hentry->gucvar)); + + InitializeOneGUCOption(hentry->gucvar); + } + + reporting_enabled = false; + + /* + * Prevent any attempt to override the transaction modes from + * non-interactive sources. + */ + SetConfigOption("transaction_isolation", "read committed", + PGC_POSTMASTER, PGC_S_OVERRIDE); + SetConfigOption("transaction_read_only", "no", + PGC_POSTMASTER, PGC_S_OVERRIDE); + SetConfigOption("transaction_deferrable", "no", + PGC_POSTMASTER, PGC_S_OVERRIDE); + + /* + * For historical reasons, some GUC parameters can receive defaults from + * environment variables. Process those settings. + */ + InitializeGUCOptionsFromEnvironment(); +} + +/* + * Assign any GUC values that can come from the server's environment. + * + * This is called from InitializeGUCOptions, and also from ProcessConfigFile + * to deal with the possibility that a setting has been removed from + * postgresql.conf and should now get a value from the environment. + * (The latter is a kludge that should probably go away someday; if so, + * fold this back into InitializeGUCOptions.) + */ +static void +InitializeGUCOptionsFromEnvironment(void) +{ + char *env; + long stack_rlimit; + + env = getenv("PGPORT"); + if (env != NULL) + SetConfigOption("port", env, PGC_POSTMASTER, PGC_S_ENV_VAR); + + env = getenv("PGDATESTYLE"); + if (env != NULL) + SetConfigOption("datestyle", env, PGC_POSTMASTER, PGC_S_ENV_VAR); + + env = getenv("PGCLIENTENCODING"); + if (env != NULL) + SetConfigOption("client_encoding", env, PGC_POSTMASTER, PGC_S_ENV_VAR); + + /* + * rlimit isn't exactly an "environment variable", but it behaves about + * the same. If we can identify the platform stack depth rlimit, increase + * default stack depth setting up to whatever is safe (but at most 2MB). + * Report the value's source as PGC_S_DYNAMIC_DEFAULT if it's 2MB, or as + * PGC_S_ENV_VAR if it's reflecting the rlimit limit. + */ + stack_rlimit = get_stack_depth_rlimit(); + if (stack_rlimit > 0) + { + long new_limit = (stack_rlimit - STACK_DEPTH_SLOP) / 1024L; + + if (new_limit > 100) + { + GucSource source; + char limbuf[16]; + + if (new_limit < 2048) + source = PGC_S_ENV_VAR; + else + { + new_limit = 2048; + source = PGC_S_DYNAMIC_DEFAULT; + } + snprintf(limbuf, sizeof(limbuf), "%ld", new_limit); + SetConfigOption("max_stack_depth", limbuf, + PGC_POSTMASTER, source); + } + } +} + +/* + * Initialize one GUC option variable to its compiled-in default. + * + * Note: the reason for calling check_hooks is not that we think the boot_val + * might fail, but that the hooks might wish to compute an "extra" struct. + */ +static void +InitializeOneGUCOption(struct config_generic *gconf) +{ + gconf->status = 0; + gconf->source = PGC_S_DEFAULT; + gconf->reset_source = PGC_S_DEFAULT; + gconf->scontext = PGC_INTERNAL; + gconf->reset_scontext = PGC_INTERNAL; + gconf->srole = BOOTSTRAP_SUPERUSERID; + gconf->reset_srole = BOOTSTRAP_SUPERUSERID; + gconf->stack = NULL; + gconf->extra = NULL; + gconf->last_reported = NULL; + gconf->sourcefile = NULL; + gconf->sourceline = 0; + + switch (gconf->vartype) + { + case PGC_BOOL: + { + struct config_bool *conf = (struct config_bool *) gconf; + bool newval = conf->boot_val; + void *extra = NULL; + + if (!call_bool_check_hook(conf, &newval, &extra, + PGC_S_DEFAULT, LOG)) + elog(FATAL, "failed to initialize %s to %d", + conf->gen.name, (int) newval); + if (conf->assign_hook) + conf->assign_hook(newval, extra); + *conf->variable = conf->reset_val = newval; + conf->gen.extra = conf->reset_extra = extra; + break; + } + case PGC_INT: + { + struct config_int *conf = (struct config_int *) gconf; + int newval = conf->boot_val; + void *extra = NULL; + + Assert(newval >= conf->min); + Assert(newval <= conf->max); + if (!call_int_check_hook(conf, &newval, &extra, + PGC_S_DEFAULT, LOG)) + elog(FATAL, "failed to initialize %s to %d", + conf->gen.name, newval); + if (conf->assign_hook) + conf->assign_hook(newval, extra); + *conf->variable = conf->reset_val = newval; + conf->gen.extra = conf->reset_extra = extra; + break; + } + case PGC_REAL: + { + struct config_real *conf = (struct config_real *) gconf; + double newval = conf->boot_val; + void *extra = NULL; + + Assert(newval >= conf->min); + Assert(newval <= conf->max); + if (!call_real_check_hook(conf, &newval, &extra, + PGC_S_DEFAULT, LOG)) + elog(FATAL, "failed to initialize %s to %g", + conf->gen.name, newval); + if (conf->assign_hook) + conf->assign_hook(newval, extra); + *conf->variable = conf->reset_val = newval; + conf->gen.extra = conf->reset_extra = extra; + break; + } + case PGC_STRING: + { + struct config_string *conf = (struct config_string *) gconf; + char *newval; + void *extra = NULL; + + /* non-NULL boot_val must always get strdup'd */ + if (conf->boot_val != NULL) + newval = guc_strdup(FATAL, conf->boot_val); + else + newval = NULL; + + if (!call_string_check_hook(conf, &newval, &extra, + PGC_S_DEFAULT, LOG)) + elog(FATAL, "failed to initialize %s to \"%s\"", + conf->gen.name, newval ? newval : ""); + if (conf->assign_hook) + conf->assign_hook(newval, extra); + *conf->variable = conf->reset_val = newval; + conf->gen.extra = conf->reset_extra = extra; + break; + } + case PGC_ENUM: + { + struct config_enum *conf = (struct config_enum *) gconf; + int newval = conf->boot_val; + void *extra = NULL; + + if (!call_enum_check_hook(conf, &newval, &extra, + PGC_S_DEFAULT, LOG)) + elog(FATAL, "failed to initialize %s to %d", + conf->gen.name, newval); + if (conf->assign_hook) + conf->assign_hook(newval, extra); + *conf->variable = conf->reset_val = newval; + conf->gen.extra = conf->reset_extra = extra; + break; + } + } +} + +/* + * Summarily remove a GUC variable from any linked lists it's in. + * + * We use this in cases where the variable is about to be deleted or reset. + * These aren't common operations, so it's okay if this is a bit slow. + */ +static void +RemoveGUCFromLists(struct config_generic *gconf) +{ + if (gconf->source != PGC_S_DEFAULT) + dlist_delete(&gconf->nondef_link); + if (gconf->stack != NULL) + slist_delete(&guc_stack_list, &gconf->stack_link); + if (gconf->status & GUC_NEEDS_REPORT) + slist_delete(&guc_report_list, &gconf->report_link); +} + + +/* + * Select the configuration files and data directory to be used, and + * do the initial read of postgresql.conf. + * + * This is called after processing command-line switches. + * userDoption is the -D switch value if any (NULL if unspecified). + * progname is just for use in error messages. + * + * Returns true on success; on failure, prints a suitable error message + * to stderr and returns false. + */ +bool +SelectConfigFiles(const char *userDoption, const char *progname) +{ + char *configdir; + char *fname; + bool fname_is_malloced; + struct stat stat_buf; + struct config_string *data_directory_rec; + + /* configdir is -D option, or $PGDATA if no -D */ + if (userDoption) + configdir = make_absolute_path(userDoption); + else + configdir = make_absolute_path(getenv("PGDATA")); + + if (configdir && stat(configdir, &stat_buf) != 0) + { + write_stderr("%s: could not access directory \"%s\": %s\n", + progname, + configdir, + strerror(errno)); + if (errno == ENOENT) + write_stderr("Run initdb or pg_basebackup to initialize a PostgreSQL data directory.\n"); + return false; + } + + /* + * Find the configuration file: if config_file was specified on the + * command line, use it, else use configdir/postgresql.conf. In any case + * ensure the result is an absolute path, so that it will be interpreted + * the same way by future backends. + */ + if (ConfigFileName) + { + fname = make_absolute_path(ConfigFileName); + fname_is_malloced = true; + } + else if (configdir) + { + fname = guc_malloc(FATAL, + strlen(configdir) + strlen(CONFIG_FILENAME) + 2); + sprintf(fname, "%s/%s", configdir, CONFIG_FILENAME); + fname_is_malloced = false; + } + else + { + write_stderr("%s does not know where to find the server configuration file.\n" + "You must specify the --config-file or -D invocation " + "option or set the PGDATA environment variable.\n", + progname); + return false; + } + + /* + * Set the ConfigFileName GUC variable to its final value, ensuring that + * it can't be overridden later. + */ + SetConfigOption("config_file", fname, PGC_POSTMASTER, PGC_S_OVERRIDE); + + if (fname_is_malloced) + free(fname); + else + guc_free(fname); + + /* + * Now read the config file for the first time. + */ + if (stat(ConfigFileName, &stat_buf) != 0) + { + write_stderr("%s: could not access the server configuration file \"%s\": %s\n", + progname, ConfigFileName, strerror(errno)); + free(configdir); + return false; + } + + /* + * Read the configuration file for the first time. This time only the + * data_directory parameter is picked up to determine the data directory, + * so that we can read the PG_AUTOCONF_FILENAME file next time. + */ + ProcessConfigFile(PGC_POSTMASTER); + + /* + * If the data_directory GUC variable has been set, use that as DataDir; + * otherwise use configdir if set; else punt. + * + * Note: SetDataDir will copy and absolute-ize its argument, so we don't + * have to. + */ + data_directory_rec = (struct config_string *) + find_option("data_directory", false, false, PANIC); + if (*data_directory_rec->variable) + SetDataDir(*data_directory_rec->variable); + else if (configdir) + SetDataDir(configdir); + else + { + write_stderr("%s does not know where to find the database system data.\n" + "This can be specified as \"data_directory\" in \"%s\", " + "or by the -D invocation option, or by the " + "PGDATA environment variable.\n", + progname, ConfigFileName); + return false; + } + + /* + * Reflect the final DataDir value back into the data_directory GUC var. + * (If you are wondering why we don't just make them a single variable, + * it's because the EXEC_BACKEND case needs DataDir to be transmitted to + * child backends specially. XXX is that still true? Given that we now + * chdir to DataDir, EXEC_BACKEND can read the config file without knowing + * DataDir in advance.) + */ + SetConfigOption("data_directory", DataDir, PGC_POSTMASTER, PGC_S_OVERRIDE); + + /* + * Now read the config file a second time, allowing any settings in the + * PG_AUTOCONF_FILENAME file to take effect. (This is pretty ugly, but + * since we have to determine the DataDir before we can find the autoconf + * file, the alternatives seem worse.) + */ + ProcessConfigFile(PGC_POSTMASTER); + + /* + * If timezone_abbreviations wasn't set in the configuration file, install + * the default value. We do it this way because we can't safely install a + * "real" value until my_exec_path is set, which may not have happened + * when InitializeGUCOptions runs, so the bootstrap default value cannot + * be the real desired default. + */ + pg_timezone_abbrev_initialize(); + + /* + * Figure out where pg_hba.conf is, and make sure the path is absolute. + */ + if (HbaFileName) + { + fname = make_absolute_path(HbaFileName); + fname_is_malloced = true; + } + else if (configdir) + { + fname = guc_malloc(FATAL, + strlen(configdir) + strlen(HBA_FILENAME) + 2); + sprintf(fname, "%s/%s", configdir, HBA_FILENAME); + fname_is_malloced = false; + } + else + { + write_stderr("%s does not know where to find the \"hba\" configuration file.\n" + "This can be specified as \"hba_file\" in \"%s\", " + "or by the -D invocation option, or by the " + "PGDATA environment variable.\n", + progname, ConfigFileName); + return false; + } + SetConfigOption("hba_file", fname, PGC_POSTMASTER, PGC_S_OVERRIDE); + + if (fname_is_malloced) + free(fname); + else + guc_free(fname); + + /* + * Likewise for pg_ident.conf. + */ + if (IdentFileName) + { + fname = make_absolute_path(IdentFileName); + fname_is_malloced = true; + } + else if (configdir) + { + fname = guc_malloc(FATAL, + strlen(configdir) + strlen(IDENT_FILENAME) + 2); + sprintf(fname, "%s/%s", configdir, IDENT_FILENAME); + fname_is_malloced = false; + } + else + { + write_stderr("%s does not know where to find the \"ident\" configuration file.\n" + "This can be specified as \"ident_file\" in \"%s\", " + "or by the -D invocation option, or by the " + "PGDATA environment variable.\n", + progname, ConfigFileName); + return false; + } + SetConfigOption("ident_file", fname, PGC_POSTMASTER, PGC_S_OVERRIDE); + + if (fname_is_malloced) + free(fname); + else + guc_free(fname); + + free(configdir); + + return true; +} + +/* + * pg_timezone_abbrev_initialize --- set default value if not done already + * + * This is called after initial loading of postgresql.conf. If no + * timezone_abbreviations setting was found therein, select default. + * If a non-default value is already installed, nothing will happen. + * + * This can also be called from ProcessConfigFile to establish the default + * value after a postgresql.conf entry for it is removed. + */ +static void +pg_timezone_abbrev_initialize(void) +{ + SetConfigOption("timezone_abbreviations", "Default", + PGC_POSTMASTER, PGC_S_DYNAMIC_DEFAULT); +} + + +/* + * Reset all options to their saved default values (implements RESET ALL) + */ +void +ResetAllOptions(void) +{ + dlist_mutable_iter iter; + + /* We need only consider GUCs not already at PGC_S_DEFAULT */ + dlist_foreach_modify(iter, &guc_nondef_list) + { + struct config_generic *gconf = dlist_container(struct config_generic, + nondef_link, iter.cur); + + /* Don't reset non-SET-able values */ + if (gconf->context != PGC_SUSET && + gconf->context != PGC_USERSET) + continue; + /* Don't reset if special exclusion from RESET ALL */ + if (gconf->flags & GUC_NO_RESET_ALL) + continue; + /* No need to reset if wasn't SET */ + if (gconf->source <= PGC_S_OVERRIDE) + continue; + + /* Save old value to support transaction abort */ + push_old_value(gconf, GUC_ACTION_SET); + + switch (gconf->vartype) + { + case PGC_BOOL: + { + struct config_bool *conf = (struct config_bool *) gconf; + + if (conf->assign_hook) + conf->assign_hook(conf->reset_val, + conf->reset_extra); + *conf->variable = conf->reset_val; + set_extra_field(&conf->gen, &conf->gen.extra, + conf->reset_extra); + break; + } + case PGC_INT: + { + struct config_int *conf = (struct config_int *) gconf; + + if (conf->assign_hook) + conf->assign_hook(conf->reset_val, + conf->reset_extra); + *conf->variable = conf->reset_val; + set_extra_field(&conf->gen, &conf->gen.extra, + conf->reset_extra); + break; + } + case PGC_REAL: + { + struct config_real *conf = (struct config_real *) gconf; + + if (conf->assign_hook) + conf->assign_hook(conf->reset_val, + conf->reset_extra); + *conf->variable = conf->reset_val; + set_extra_field(&conf->gen, &conf->gen.extra, + conf->reset_extra); + break; + } + case PGC_STRING: + { + struct config_string *conf = (struct config_string *) gconf; + + if (conf->assign_hook) + conf->assign_hook(conf->reset_val, + conf->reset_extra); + set_string_field(conf, conf->variable, conf->reset_val); + set_extra_field(&conf->gen, &conf->gen.extra, + conf->reset_extra); + break; + } + case PGC_ENUM: + { + struct config_enum *conf = (struct config_enum *) gconf; + + if (conf->assign_hook) + conf->assign_hook(conf->reset_val, + conf->reset_extra); + *conf->variable = conf->reset_val; + set_extra_field(&conf->gen, &conf->gen.extra, + conf->reset_extra); + break; + } + } + + set_guc_source(gconf, gconf->reset_source); + gconf->scontext = gconf->reset_scontext; + gconf->srole = gconf->reset_srole; + + if ((gconf->flags & GUC_REPORT) && !(gconf->status & GUC_NEEDS_REPORT)) + { + gconf->status |= GUC_NEEDS_REPORT; + slist_push_head(&guc_report_list, &gconf->report_link); + } + } +} + + +/* + * Apply a change to a GUC variable's "source" field. + * + * Use this rather than just assigning, to ensure that the variable's + * membership in guc_nondef_list is updated correctly. + */ +static void +set_guc_source(struct config_generic *gconf, GucSource newsource) +{ + /* Adjust nondef list membership if appropriate for change */ + if (gconf->source == PGC_S_DEFAULT) + { + if (newsource != PGC_S_DEFAULT) + dlist_push_tail(&guc_nondef_list, &gconf->nondef_link); + } + else + { + if (newsource == PGC_S_DEFAULT) + dlist_delete(&gconf->nondef_link); + } + /* Now update the source field */ + gconf->source = newsource; +} + + +/* + * push_old_value + * Push previous state during transactional assignment to a GUC variable. + */ +static void +push_old_value(struct config_generic *gconf, GucAction action) +{ + GucStack *stack; + + /* If we're not inside a nest level, do nothing */ + if (GUCNestLevel == 0) + return; + + /* Do we already have a stack entry of the current nest level? */ + stack = gconf->stack; + if (stack && stack->nest_level >= GUCNestLevel) + { + /* Yes, so adjust its state if necessary */ + Assert(stack->nest_level == GUCNestLevel); + switch (action) + { + case GUC_ACTION_SET: + /* SET overrides any prior action at same nest level */ + if (stack->state == GUC_SET_LOCAL) + { + /* must discard old masked value */ + discard_stack_value(gconf, &stack->masked); + } + stack->state = GUC_SET; + break; + case GUC_ACTION_LOCAL: + if (stack->state == GUC_SET) + { + /* SET followed by SET LOCAL, remember SET's value */ + stack->masked_scontext = gconf->scontext; + stack->masked_srole = gconf->srole; + set_stack_value(gconf, &stack->masked); + stack->state = GUC_SET_LOCAL; + } + /* in all other cases, no change to stack entry */ + break; + case GUC_ACTION_SAVE: + /* Could only have a prior SAVE of same variable */ + Assert(stack->state == GUC_SAVE); + break; + } + return; + } + + /* + * Push a new stack entry + * + * We keep all the stack entries in TopTransactionContext for simplicity. + */ + stack = (GucStack *) MemoryContextAllocZero(TopTransactionContext, + sizeof(GucStack)); + + stack->prev = gconf->stack; + stack->nest_level = GUCNestLevel; + switch (action) + { + case GUC_ACTION_SET: + stack->state = GUC_SET; + break; + case GUC_ACTION_LOCAL: + stack->state = GUC_LOCAL; + break; + case GUC_ACTION_SAVE: + stack->state = GUC_SAVE; + break; + } + stack->source = gconf->source; + stack->scontext = gconf->scontext; + stack->srole = gconf->srole; + set_stack_value(gconf, &stack->prior); + + if (gconf->stack == NULL) + slist_push_head(&guc_stack_list, &gconf->stack_link); + gconf->stack = stack; +} + + +/* + * Do GUC processing at main transaction start. + */ +void +AtStart_GUC(void) +{ + /* + * The nest level should be 0 between transactions; if it isn't, somebody + * didn't call AtEOXact_GUC, or called it with the wrong nestLevel. We + * throw a warning but make no other effort to clean up. + */ + if (GUCNestLevel != 0) + elog(WARNING, "GUC nest level = %d at transaction start", + GUCNestLevel); + GUCNestLevel = 1; +} + +/* + * Enter a new nesting level for GUC values. This is called at subtransaction + * start, and when entering a function that has proconfig settings, and in + * some other places where we want to set GUC variables transiently. + * NOTE we must not risk error here, else subtransaction start will be unhappy. + */ +int +NewGUCNestLevel(void) +{ + return ++GUCNestLevel; +} + +/* + * Do GUC processing at transaction or subtransaction commit or abort, or + * when exiting a function that has proconfig settings, or when undoing a + * transient assignment to some GUC variables. (The name is thus a bit of + * a misnomer; perhaps it should be ExitGUCNestLevel or some such.) + * During abort, we discard all GUC settings that were applied at nesting + * levels >= nestLevel. nestLevel == 1 corresponds to the main transaction. + */ +void +AtEOXact_GUC(bool isCommit, int nestLevel) +{ + slist_mutable_iter iter; + + /* + * Note: it's possible to get here with GUCNestLevel == nestLevel-1 during + * abort, if there is a failure during transaction start before + * AtStart_GUC is called. + */ + Assert(nestLevel > 0 && + (nestLevel <= GUCNestLevel || + (nestLevel == GUCNestLevel + 1 && !isCommit))); + + /* We need only process GUCs having nonempty stacks */ + slist_foreach_modify(iter, &guc_stack_list) + { + struct config_generic *gconf = slist_container(struct config_generic, + stack_link, iter.cur); + GucStack *stack; + + /* + * Process and pop each stack entry within the nest level. To simplify + * fmgr_security_definer() and other places that use GUC_ACTION_SAVE, + * we allow failure exit from code that uses a local nest level to be + * recovered at the surrounding transaction or subtransaction abort; + * so there could be more than one stack entry to pop. + */ + while ((stack = gconf->stack) != NULL && + stack->nest_level >= nestLevel) + { + GucStack *prev = stack->prev; + bool restorePrior = false; + bool restoreMasked = false; + bool changed; + + /* + * In this next bit, if we don't set either restorePrior or + * restoreMasked, we must "discard" any unwanted fields of the + * stack entries to avoid leaking memory. If we do set one of + * those flags, unused fields will be cleaned up after restoring. + */ + if (!isCommit) /* if abort, always restore prior value */ + restorePrior = true; + else if (stack->state == GUC_SAVE) + restorePrior = true; + else if (stack->nest_level == 1) + { + /* transaction commit */ + if (stack->state == GUC_SET_LOCAL) + restoreMasked = true; + else if (stack->state == GUC_SET) + { + /* we keep the current active value */ + discard_stack_value(gconf, &stack->prior); + } + else /* must be GUC_LOCAL */ + restorePrior = true; + } + else if (prev == NULL || + prev->nest_level < stack->nest_level - 1) + { + /* decrement entry's level and do not pop it */ + stack->nest_level--; + continue; + } + else + { + /* + * We have to merge this stack entry into prev. See README for + * discussion of this bit. + */ + switch (stack->state) + { + case GUC_SAVE: + Assert(false); /* can't get here */ + break; + + case GUC_SET: + /* next level always becomes SET */ + discard_stack_value(gconf, &stack->prior); + if (prev->state == GUC_SET_LOCAL) + discard_stack_value(gconf, &prev->masked); + prev->state = GUC_SET; + break; + + case GUC_LOCAL: + if (prev->state == GUC_SET) + { + /* LOCAL migrates down */ + prev->masked_scontext = stack->scontext; + prev->masked_srole = stack->srole; + prev->masked = stack->prior; + prev->state = GUC_SET_LOCAL; + } + else + { + /* else just forget this stack level */ + discard_stack_value(gconf, &stack->prior); + } + break; + + case GUC_SET_LOCAL: + /* prior state at this level no longer wanted */ + discard_stack_value(gconf, &stack->prior); + /* copy down the masked state */ + prev->masked_scontext = stack->masked_scontext; + prev->masked_srole = stack->masked_srole; + if (prev->state == GUC_SET_LOCAL) + discard_stack_value(gconf, &prev->masked); + prev->masked = stack->masked; + prev->state = GUC_SET_LOCAL; + break; + } + } + + changed = false; + + if (restorePrior || restoreMasked) + { + /* Perform appropriate restoration of the stacked value */ + config_var_value newvalue; + GucSource newsource; + GucContext newscontext; + Oid newsrole; + + if (restoreMasked) + { + newvalue = stack->masked; + newsource = PGC_S_SESSION; + newscontext = stack->masked_scontext; + newsrole = stack->masked_srole; + } + else + { + newvalue = stack->prior; + newsource = stack->source; + newscontext = stack->scontext; + newsrole = stack->srole; + } + + switch (gconf->vartype) + { + case PGC_BOOL: + { + struct config_bool *conf = (struct config_bool *) gconf; + bool newval = newvalue.val.boolval; + void *newextra = newvalue.extra; + + if (*conf->variable != newval || + conf->gen.extra != newextra) + { + if (conf->assign_hook) + conf->assign_hook(newval, newextra); + *conf->variable = newval; + set_extra_field(&conf->gen, &conf->gen.extra, + newextra); + changed = true; + } + break; + } + case PGC_INT: + { + struct config_int *conf = (struct config_int *) gconf; + int newval = newvalue.val.intval; + void *newextra = newvalue.extra; + + if (*conf->variable != newval || + conf->gen.extra != newextra) + { + if (conf->assign_hook) + conf->assign_hook(newval, newextra); + *conf->variable = newval; + set_extra_field(&conf->gen, &conf->gen.extra, + newextra); + changed = true; + } + break; + } + case PGC_REAL: + { + struct config_real *conf = (struct config_real *) gconf; + double newval = newvalue.val.realval; + void *newextra = newvalue.extra; + + if (*conf->variable != newval || + conf->gen.extra != newextra) + { + if (conf->assign_hook) + conf->assign_hook(newval, newextra); + *conf->variable = newval; + set_extra_field(&conf->gen, &conf->gen.extra, + newextra); + changed = true; + } + break; + } + case PGC_STRING: + { + struct config_string *conf = (struct config_string *) gconf; + char *newval = newvalue.val.stringval; + void *newextra = newvalue.extra; + + if (*conf->variable != newval || + conf->gen.extra != newextra) + { + if (conf->assign_hook) + conf->assign_hook(newval, newextra); + set_string_field(conf, conf->variable, newval); + set_extra_field(&conf->gen, &conf->gen.extra, + newextra); + changed = true; + } + + /* + * Release stacked values if not used anymore. We + * could use discard_stack_value() here, but since + * we have type-specific code anyway, might as + * well inline it. + */ + set_string_field(conf, &stack->prior.val.stringval, NULL); + set_string_field(conf, &stack->masked.val.stringval, NULL); + break; + } + case PGC_ENUM: + { + struct config_enum *conf = (struct config_enum *) gconf; + int newval = newvalue.val.enumval; + void *newextra = newvalue.extra; + + if (*conf->variable != newval || + conf->gen.extra != newextra) + { + if (conf->assign_hook) + conf->assign_hook(newval, newextra); + *conf->variable = newval; + set_extra_field(&conf->gen, &conf->gen.extra, + newextra); + changed = true; + } + break; + } + } + + /* + * Release stacked extra values if not used anymore. + */ + set_extra_field(gconf, &(stack->prior.extra), NULL); + set_extra_field(gconf, &(stack->masked.extra), NULL); + + /* And restore source information */ + set_guc_source(gconf, newsource); + gconf->scontext = newscontext; + gconf->srole = newsrole; + } + + /* + * Pop the GUC's state stack; if it's now empty, remove the GUC + * from guc_stack_list. + */ + gconf->stack = prev; + if (prev == NULL) + slist_delete_current(&iter); + pfree(stack); + + /* Report new value if we changed it */ + if (changed && (gconf->flags & GUC_REPORT) && + !(gconf->status & GUC_NEEDS_REPORT)) + { + gconf->status |= GUC_NEEDS_REPORT; + slist_push_head(&guc_report_list, &gconf->report_link); + } + } /* end of stack-popping loop */ + } + + /* Update nesting level */ + GUCNestLevel = nestLevel - 1; +} + + +/* + * Start up automatic reporting of changes to variables marked GUC_REPORT. + * This is executed at completion of backend startup. + */ +void +BeginReportingGUCOptions(void) +{ + HASH_SEQ_STATUS status; + GUCHashEntry *hentry; + + /* + * Don't do anything unless talking to an interactive frontend. + */ + if (whereToSendOutput != DestRemote) + return; + + reporting_enabled = true; + + /* + * Hack for in_hot_standby: set the GUC value true if appropriate. This + * is kind of an ugly place to do it, but there's few better options. + * + * (This could be out of date by the time we actually send it, in which + * case the next ReportChangedGUCOptions call will send a duplicate + * report.) + */ + if (RecoveryInProgress()) + SetConfigOption("in_hot_standby", "true", + PGC_INTERNAL, PGC_S_OVERRIDE); + + /* Transmit initial values of interesting variables */ + hash_seq_init(&status, guc_hashtab); + while ((hentry = (GUCHashEntry *) hash_seq_search(&status)) != NULL) + { + struct config_generic *conf = hentry->gucvar; + + if (conf->flags & GUC_REPORT) + ReportGUCOption(conf); + } +} + +/* + * ReportChangedGUCOptions: report recently-changed GUC_REPORT variables + * + * This is called just before we wait for a new client query. + * + * By handling things this way, we ensure that a ParameterStatus message + * is sent at most once per variable per query, even if the variable + * changed multiple times within the query. That's quite possible when + * using features such as function SET clauses. Function SET clauses + * also tend to cause values to change intraquery but eventually revert + * to their prevailing values; ReportGUCOption is responsible for avoiding + * redundant reports in such cases. + */ +void +ReportChangedGUCOptions(void) +{ + slist_mutable_iter iter; + + /* Quick exit if not (yet) enabled */ + if (!reporting_enabled) + return; + + /* + * Since in_hot_standby isn't actually changed by normal GUC actions, we + * need a hack to check whether a new value needs to be reported to the + * client. For speed, we rely on the assumption that it can never + * transition from false to true. + */ + if (in_hot_standby_guc && !RecoveryInProgress()) + SetConfigOption("in_hot_standby", "false", + PGC_INTERNAL, PGC_S_OVERRIDE); + + /* Transmit new values of interesting variables */ + slist_foreach_modify(iter, &guc_report_list) + { + struct config_generic *conf = slist_container(struct config_generic, + report_link, iter.cur); + + Assert((conf->flags & GUC_REPORT) && (conf->status & GUC_NEEDS_REPORT)); + ReportGUCOption(conf); + conf->status &= ~GUC_NEEDS_REPORT; + slist_delete_current(&iter); + } +} + +/* + * ReportGUCOption: if appropriate, transmit option value to frontend + * + * We need not transmit the value if it's the same as what we last + * transmitted. + */ +static void +ReportGUCOption(struct config_generic *record) +{ + char *val = ShowGUCOption(record, false); + + if (record->last_reported == NULL || + strcmp(val, record->last_reported) != 0) + { + StringInfoData msgbuf; + + pq_beginmessage(&msgbuf, 'S'); + pq_sendstring(&msgbuf, record->name); + pq_sendstring(&msgbuf, val); + pq_endmessage(&msgbuf); + + /* + * We need a long-lifespan copy. If guc_strdup() fails due to OOM, + * we'll set last_reported to NULL and thereby possibly make a + * duplicate report later. + */ + guc_free(record->last_reported); + record->last_reported = guc_strdup(LOG, val); + } + + pfree(val); +} + +/* + * Convert a value from one of the human-friendly units ("kB", "min" etc.) + * to the given base unit. 'value' and 'unit' are the input value and unit + * to convert from (there can be trailing spaces in the unit string). + * The converted value is stored in *base_value. + * It's caller's responsibility to round off the converted value as necessary + * and check for out-of-range. + * + * Returns true on success, false if the input unit is not recognized. + */ +static bool +convert_to_base_unit(double value, const char *unit, + int base_unit, double *base_value) +{ + char unitstr[MAX_UNIT_LEN + 1]; + int unitlen; + const unit_conversion *table; + int i; + + /* extract unit string to compare to table entries */ + unitlen = 0; + while (*unit != '\0' && !isspace((unsigned char) *unit) && + unitlen < MAX_UNIT_LEN) + unitstr[unitlen++] = *(unit++); + unitstr[unitlen] = '\0'; + /* allow whitespace after unit */ + while (isspace((unsigned char) *unit)) + unit++; + if (*unit != '\0') + return false; /* unit too long, or garbage after it */ + + /* now search the appropriate table */ + if (base_unit & GUC_UNIT_MEMORY) + table = memory_unit_conversion_table; + else + table = time_unit_conversion_table; + + for (i = 0; *table[i].unit; i++) + { + if (base_unit == table[i].base_unit && + strcmp(unitstr, table[i].unit) == 0) + { + double cvalue = value * table[i].multiplier; + + /* + * If the user gave a fractional value such as "30.1GB", round it + * off to the nearest multiple of the next smaller unit, if there + * is one. + */ + if (*table[i + 1].unit && + base_unit == table[i + 1].base_unit) + cvalue = rint(cvalue / table[i + 1].multiplier) * + table[i + 1].multiplier; + + *base_value = cvalue; + return true; + } + } + return false; +} + +/* + * Convert an integer value in some base unit to a human-friendly unit. + * + * The output unit is chosen so that it's the greatest unit that can represent + * the value without loss. For example, if the base unit is GUC_UNIT_KB, 1024 + * is converted to 1 MB, but 1025 is represented as 1025 kB. + */ +static void +convert_int_from_base_unit(int64 base_value, int base_unit, + int64 *value, const char **unit) +{ + const unit_conversion *table; + int i; + + *unit = NULL; + + if (base_unit & GUC_UNIT_MEMORY) + table = memory_unit_conversion_table; + else + table = time_unit_conversion_table; + + for (i = 0; *table[i].unit; i++) + { + if (base_unit == table[i].base_unit) + { + /* + * Accept the first conversion that divides the value evenly. We + * assume that the conversions for each base unit are ordered from + * greatest unit to the smallest! + */ + if (table[i].multiplier <= 1.0 || + base_value % (int64) table[i].multiplier == 0) + { + *value = (int64) rint(base_value / table[i].multiplier); + *unit = table[i].unit; + break; + } + } + } + + Assert(*unit != NULL); +} + +/* + * Convert a floating-point value in some base unit to a human-friendly unit. + * + * Same as above, except we have to do the math a bit differently, and + * there's a possibility that we don't find any exact divisor. + */ +static void +convert_real_from_base_unit(double base_value, int base_unit, + double *value, const char **unit) +{ + const unit_conversion *table; + int i; + + *unit = NULL; + + if (base_unit & GUC_UNIT_MEMORY) + table = memory_unit_conversion_table; + else + table = time_unit_conversion_table; + + for (i = 0; *table[i].unit; i++) + { + if (base_unit == table[i].base_unit) + { + /* + * Accept the first conversion that divides the value evenly; or + * if there is none, use the smallest (last) target unit. + * + * What we actually care about here is whether snprintf with "%g" + * will print the value as an integer, so the obvious test of + * "*value == rint(*value)" is too strict; roundoff error might + * make us choose an unreasonably small unit. As a compromise, + * accept a divisor that is within 1e-8 of producing an integer. + */ + *value = base_value / table[i].multiplier; + *unit = table[i].unit; + if (*value > 0 && + fabs((rint(*value) / *value) - 1.0) <= 1e-8) + break; + } + } + + Assert(*unit != NULL); +} + +/* + * Return the name of a GUC's base unit (e.g. "ms") given its flags. + * Return NULL if the GUC is unitless. + */ +const char * +get_config_unit_name(int flags) +{ + switch (flags & GUC_UNIT) + { + case 0: + return NULL; /* GUC has no units */ + case GUC_UNIT_BYTE: + return "B"; + case GUC_UNIT_KB: + return "kB"; + case GUC_UNIT_MB: + return "MB"; + case GUC_UNIT_BLOCKS: + { + static __thread char bbuf[8]; + + /* initialize if first time through */ + if (bbuf[0] == '\0') + snprintf(bbuf, sizeof(bbuf), "%dkB", BLCKSZ / 1024); + return bbuf; + } + case GUC_UNIT_XBLOCKS: + { + static __thread char xbuf[8]; + + /* initialize if first time through */ + if (xbuf[0] == '\0') + snprintf(xbuf, sizeof(xbuf), "%dkB", XLOG_BLCKSZ / 1024); + return xbuf; + } + case GUC_UNIT_MS: + return "ms"; + case GUC_UNIT_S: + return "s"; + case GUC_UNIT_MIN: + return "min"; + default: + elog(ERROR, "unrecognized GUC units value: %d", + flags & GUC_UNIT); + return NULL; + } +} + + +/* + * Try to parse value as an integer. The accepted formats are the + * usual decimal, octal, or hexadecimal formats, as well as floating-point + * formats (which will be rounded to integer after any units conversion). + * Optionally, the value can be followed by a unit name if "flags" indicates + * a unit is allowed. + * + * If the string parses okay, return true, else false. + * If okay and result is not NULL, return the value in *result. + * If not okay and hintmsg is not NULL, *hintmsg is set to a suitable + * HINT message, or NULL if no hint provided. + */ +bool +parse_int(const char *value, int *result, int flags, const char **hintmsg) +{ + /* + * We assume here that double is wide enough to represent any integer + * value with adequate precision. + */ + double val; + char *endptr; + + /* To suppress compiler warnings, always set output params */ + if (result) + *result = 0; + if (hintmsg) + *hintmsg = NULL; + + /* + * Try to parse as an integer (allowing octal or hex input). If the + * conversion stops at a decimal point or 'e', or overflows, re-parse as + * float. This should work fine as long as we have no unit names starting + * with 'e'. If we ever do, the test could be extended to check for a + * sign or digit after 'e', but for now that's unnecessary. + */ + errno = 0; + val = strtol(value, &endptr, 0); + if (*endptr == '.' || *endptr == 'e' || *endptr == 'E' || + errno == ERANGE) + { + errno = 0; + val = strtod(value, &endptr); + } + + if (endptr == value || errno == ERANGE) + return false; /* no HINT for these cases */ + + /* reject NaN (infinities will fail range check below) */ + if (isnan(val)) + return false; /* treat same as syntax error; no HINT */ + + /* allow whitespace between number and unit */ + while (isspace((unsigned char) *endptr)) + endptr++; + + /* Handle possible unit */ + if (*endptr != '\0') + { + if ((flags & GUC_UNIT) == 0) + return false; /* this setting does not accept a unit */ + + if (!convert_to_base_unit(val, + endptr, (flags & GUC_UNIT), + &val)) + { + /* invalid unit, or garbage after the unit; set hint and fail. */ + if (hintmsg) + { + if (flags & GUC_UNIT_MEMORY) + *hintmsg = memory_units_hint; + else + *hintmsg = time_units_hint; + } + return false; + } + } + + /* Round to int, then check for overflow */ + val = rint(val); + + if (val > INT_MAX || val < INT_MIN) + { + if (hintmsg) + *hintmsg = gettext_noop("Value exceeds integer range."); + return false; + } + + if (result) + *result = (int) val; + return true; +} + +/* + * Try to parse value as a floating point number in the usual format. + * Optionally, the value can be followed by a unit name if "flags" indicates + * a unit is allowed. + * + * If the string parses okay, return true, else false. + * If okay and result is not NULL, return the value in *result. + * If not okay and hintmsg is not NULL, *hintmsg is set to a suitable + * HINT message, or NULL if no hint provided. + */ +bool +parse_real(const char *value, double *result, int flags, const char **hintmsg) +{ + double val; + char *endptr; + + /* To suppress compiler warnings, always set output params */ + if (result) + *result = 0; + if (hintmsg) + *hintmsg = NULL; + + errno = 0; + val = strtod(value, &endptr); + + if (endptr == value || errno == ERANGE) + return false; /* no HINT for these cases */ + + /* reject NaN (infinities will fail range checks later) */ + if (isnan(val)) + return false; /* treat same as syntax error; no HINT */ + + /* allow whitespace between number and unit */ + while (isspace((unsigned char) *endptr)) + endptr++; + + /* Handle possible unit */ + if (*endptr != '\0') + { + if ((flags & GUC_UNIT) == 0) + return false; /* this setting does not accept a unit */ + + if (!convert_to_base_unit(val, + endptr, (flags & GUC_UNIT), + &val)) + { + /* invalid unit, or garbage after the unit; set hint and fail. */ + if (hintmsg) + { + if (flags & GUC_UNIT_MEMORY) + *hintmsg = memory_units_hint; + else + *hintmsg = time_units_hint; + } + return false; + } + } + + if (result) + *result = val; + return true; +} + + +/* + * Lookup the name for an enum option with the selected value. + * Should only ever be called with known-valid values, so throws + * an elog(ERROR) if the enum option is not found. + * + * The returned string is a pointer to static data and not + * allocated for modification. + */ +const char * +config_enum_lookup_by_value(struct config_enum *record, int val) +{ + const struct config_enum_entry *entry; + + for (entry = record->options; entry && entry->name; entry++) + { + if (entry->val == val) + return entry->name; + } + + elog(ERROR, "could not find enum option %d for %s", + val, record->gen.name); + return NULL; /* silence compiler */ +} + + +/* + * Lookup the value for an enum option with the selected name + * (case-insensitive). + * If the enum option is found, sets the retval value and returns + * true. If it's not found, return false and retval is set to 0. + */ +bool +config_enum_lookup_by_name(struct config_enum *record, const char *value, + int *retval) +{ + const struct config_enum_entry *entry; + + for (entry = record->options; entry && entry->name; entry++) + { + if (pg_strcasecmp(value, entry->name) == 0) + { + *retval = entry->val; + return true; + } + } + + *retval = 0; + return false; +} + + +/* + * Return a palloc'd string listing all the available options for an enum GUC + * (excluding hidden ones), separated by the given separator. + * If prefix is non-NULL, it is added before the first enum value. + * If suffix is non-NULL, it is added to the end of the string. + */ +char * +config_enum_get_options(struct config_enum *record, const char *prefix, + const char *suffix, const char *separator) +{ + const struct config_enum_entry *entry; + StringInfoData retstr; + int seplen; + + initStringInfo(&retstr); + appendStringInfoString(&retstr, prefix); + + seplen = strlen(separator); + for (entry = record->options; entry && entry->name; entry++) + { + if (!entry->hidden) + { + appendStringInfoString(&retstr, entry->name); + appendBinaryStringInfo(&retstr, separator, seplen); + } + } + + /* + * All the entries may have been hidden, leaving the string empty if no + * prefix was given. This indicates a broken GUC setup, since there is no + * use for an enum without any values, so we just check to make sure we + * don't write to invalid memory instead of actually trying to do + * something smart with it. + */ + if (retstr.len >= seplen) + { + /* Replace final separator */ + retstr.data[retstr.len - seplen] = '\0'; + retstr.len -= seplen; + } + + appendStringInfoString(&retstr, suffix); + + return retstr.data; +} + +/* + * Parse and validate a proposed value for the specified configuration + * parameter. + * + * This does built-in checks (such as range limits for an integer parameter) + * and also calls any check hook the parameter may have. + * + * record: GUC variable's info record + * name: variable name (should match the record of course) + * value: proposed value, as a string + * source: identifies source of value (check hooks may need this) + * elevel: level to log any error reports at + * newval: on success, converted parameter value is returned here + * newextra: on success, receives any "extra" data returned by check hook + * (caller must initialize *newextra to NULL) + * + * Returns true if OK, false if not (or throws error, if elevel >= ERROR) + */ +static bool +parse_and_validate_value(struct config_generic *record, + const char *name, const char *value, + GucSource source, int elevel, + union config_var_val *newval, void **newextra) +{ + switch (record->vartype) + { + case PGC_BOOL: + { + struct config_bool *conf = (struct config_bool *) record; + + if (!parse_bool(value, &newval->boolval)) + { + ereport(elevel, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("parameter \"%s\" requires a Boolean value", + name))); + return false; + } + + if (!call_bool_check_hook(conf, &newval->boolval, newextra, + source, elevel)) + return false; + } + break; + case PGC_INT: + { + struct config_int *conf = (struct config_int *) record; + const char *hintmsg; + + if (!parse_int(value, &newval->intval, + conf->gen.flags, &hintmsg)) + { + ereport(elevel, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for parameter \"%s\": \"%s\"", + name, value), + hintmsg ? errhint("%s", _(hintmsg)) : 0)); + return false; + } + + if (newval->intval < conf->min || newval->intval > conf->max) + { + const char *unit = get_config_unit_name(conf->gen.flags); + + ereport(elevel, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("%d%s%s is outside the valid range for parameter \"%s\" (%d .. %d)", + newval->intval, + unit ? " " : "", + unit ? unit : "", + name, + conf->min, conf->max))); + return false; + } + + if (!call_int_check_hook(conf, &newval->intval, newextra, + source, elevel)) + return false; + } + break; + case PGC_REAL: + { + struct config_real *conf = (struct config_real *) record; + const char *hintmsg; + + if (!parse_real(value, &newval->realval, + conf->gen.flags, &hintmsg)) + { + ereport(elevel, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for parameter \"%s\": \"%s\"", + name, value), + hintmsg ? errhint("%s", _(hintmsg)) : 0)); + return false; + } + + if (newval->realval < conf->min || newval->realval > conf->max) + { + const char *unit = get_config_unit_name(conf->gen.flags); + + ereport(elevel, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("%g%s%s is outside the valid range for parameter \"%s\" (%g .. %g)", + newval->realval, + unit ? " " : "", + unit ? unit : "", + name, + conf->min, conf->max))); + return false; + } + + if (!call_real_check_hook(conf, &newval->realval, newextra, + source, elevel)) + return false; + } + break; + case PGC_STRING: + { + struct config_string *conf = (struct config_string *) record; + + /* + * The value passed by the caller could be transient, so we + * always strdup it. + */ + newval->stringval = guc_strdup(elevel, value); + if (newval->stringval == NULL) + return false; + + /* + * The only built-in "parsing" check we have is to apply + * truncation if GUC_IS_NAME. + */ + if (conf->gen.flags & GUC_IS_NAME) + truncate_identifier(newval->stringval, + strlen(newval->stringval), + true); + + if (!call_string_check_hook(conf, &newval->stringval, newextra, + source, elevel)) + { + guc_free(newval->stringval); + newval->stringval = NULL; + return false; + } + } + break; + case PGC_ENUM: + { + struct config_enum *conf = (struct config_enum *) record; + + if (!config_enum_lookup_by_name(conf, value, &newval->enumval)) + { + char *hintmsg; + + hintmsg = config_enum_get_options(conf, + "Available values: ", + ".", ", "); + + ereport(elevel, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for parameter \"%s\": \"%s\"", + name, value), + hintmsg ? errhint("%s", _(hintmsg)) : 0)); + + if (hintmsg) + pfree(hintmsg); + return false; + } + + if (!call_enum_check_hook(conf, &newval->enumval, newextra, + source, elevel)) + return false; + } + break; + } + + return true; +} + + +/* + * set_config_option: sets option `name' to given value. + * + * The value should be a string, which will be parsed and converted to + * the appropriate data type. The context and source parameters indicate + * in which context this function is being called, so that it can apply the + * access restrictions properly. + * + * If value is NULL, set the option to its default value (normally the + * reset_val, but if source == PGC_S_DEFAULT we instead use the boot_val). + * + * action indicates whether to set the value globally in the session, locally + * to the current top transaction, or just for the duration of a function call. + * + * If changeVal is false then don't really set the option but do all + * the checks to see if it would work. + * + * elevel should normally be passed as zero, allowing this function to make + * its standard choice of ereport level. However some callers need to be + * able to override that choice; they should pass the ereport level to use. + * + * is_reload should be true only when called from read_nondefault_variables() + * or RestoreGUCState(), where we are trying to load some other process's + * GUC settings into a new process. + * + * Return value: + * +1: the value is valid and was successfully applied. + * 0: the name or value is invalid (but see below). + * -1: the value was not applied because of context, priority, or changeVal. + * + * If there is an error (non-existing option, invalid value) then an + * ereport(ERROR) is thrown *unless* this is called for a source for which + * we don't want an ERROR (currently, those are defaults, the config file, + * and per-database or per-user settings, as well as callers who specify + * a less-than-ERROR elevel). In those cases we write a suitable error + * message via ereport() and return 0. + * + * See also SetConfigOption for an external interface. + */ +int +set_config_option(const char *name, const char *value, + GucContext context, GucSource source, + GucAction action, bool changeVal, int elevel, + bool is_reload) +{ + Oid srole; + + /* + * Non-interactive sources should be treated as having all privileges, + * except for PGC_S_CLIENT. Note in particular that this is true for + * pg_db_role_setting sources (PGC_S_GLOBAL etc): we assume a suitable + * privilege check was done when the pg_db_role_setting entry was made. + */ + if (source >= PGC_S_INTERACTIVE || source == PGC_S_CLIENT) + srole = GetUserId(); + else + srole = BOOTSTRAP_SUPERUSERID; + + return set_config_option_ext(name, value, + context, source, srole, + action, changeVal, elevel, + is_reload); +} + +/* + * set_config_option_ext: sets option `name' to given value. + * + * This API adds the ability to explicitly specify which role OID + * is considered to be setting the value. Most external callers can use + * set_config_option() and let it determine that based on the GucSource, + * but there are a few that are supplying a value that was determined + * in some special way and need to override the decision. Also, when + * restoring a previously-assigned value, it's important to supply the + * same role OID that set the value originally; so all guc.c callers + * that are doing that type of thing need to call this directly. + * + * Generally, srole should be GetUserId() when the source is a SQL operation, + * or BOOTSTRAP_SUPERUSERID if the source is a config file or similar. + */ +int +set_config_option_ext(const char *name, const char *value, + GucContext context, GucSource source, Oid srole, + GucAction action, bool changeVal, int elevel, + bool is_reload) +{ + struct config_generic *record; + union config_var_val newval_union; + void *newextra = NULL; + bool prohibitValueChange = false; + bool makeDefault; + + if (elevel == 0) + { + if (source == PGC_S_DEFAULT || source == PGC_S_FILE) + { + /* + * To avoid cluttering the log, only the postmaster bleats loudly + * about problems with the config file. + */ + elevel = IsUnderPostmaster ? DEBUG3 : LOG; + } + else if (source == PGC_S_GLOBAL || + source == PGC_S_DATABASE || + source == PGC_S_USER || + source == PGC_S_DATABASE_USER) + elevel = WARNING; + else + elevel = ERROR; + } + + /* + * GUC_ACTION_SAVE changes are acceptable during a parallel operation, + * because the current worker will also pop the change. We're probably + * dealing with a function having a proconfig entry. Only the function's + * body should observe the change, and peer workers do not share in the + * execution of a function call started by this worker. + * + * Other changes might need to affect other workers, so forbid them. + */ + if (IsInParallelMode() && changeVal && action != GUC_ACTION_SAVE) + { + ereport(elevel, + (errcode(ERRCODE_INVALID_TRANSACTION_STATE), + errmsg("cannot set parameters during a parallel operation"))); + return -1; + } + + record = find_option(name, true, true, elevel); + if (record == NULL) + return 0; + + /* + * Check if the option can be set at this time. See guc.h for the precise + * rules. + */ + switch (record->context) + { + case PGC_INTERNAL: + if (context != PGC_INTERNAL) + { + ereport(elevel, + (errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM), + errmsg("parameter \"%s\" cannot be changed", + name))); + return 0; + } + break; + case PGC_POSTMASTER: + if (context == PGC_SIGHUP) + { + /* + * We are re-reading a PGC_POSTMASTER variable from + * postgresql.conf. We can't change the setting, so we should + * give a warning if the DBA tries to change it. However, + * because of variant formats, canonicalization by check + * hooks, etc, we can't just compare the given string directly + * to what's stored. Set a flag to check below after we have + * the final storable value. + */ + prohibitValueChange = true; + } + else if (context != PGC_POSTMASTER) + { + ereport(elevel, + (errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM), + errmsg("parameter \"%s\" cannot be changed without restarting the server", + name))); + return 0; + } + break; + case PGC_SIGHUP: + if (context != PGC_SIGHUP && context != PGC_POSTMASTER) + { + ereport(elevel, + (errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM), + errmsg("parameter \"%s\" cannot be changed now", + name))); + return 0; + } + + /* + * Hmm, the idea of the SIGHUP context is "ought to be global, but + * can be changed after postmaster start". But there's nothing + * that prevents a crafty administrator from sending SIGHUP + * signals to individual backends only. + */ + break; + case PGC_SU_BACKEND: + if (context == PGC_BACKEND) + { + /* + * Check whether the requesting user has been granted + * privilege to set this GUC. + */ + AclResult aclresult; + + aclresult = pg_parameter_aclcheck(name, srole, ACL_SET); + if (aclresult != ACLCHECK_OK) + { + /* No granted privilege */ + ereport(elevel, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied to set parameter \"%s\"", + name))); + return 0; + } + } + /* fall through to process the same as PGC_BACKEND */ + /* FALLTHROUGH */ + case PGC_BACKEND: + if (context == PGC_SIGHUP) + { + /* + * If a PGC_BACKEND or PGC_SU_BACKEND parameter is changed in + * the config file, we want to accept the new value in the + * postmaster (whence it will propagate to + * subsequently-started backends), but ignore it in existing + * backends. This is a tad klugy, but necessary because we + * don't re-read the config file during backend start. + * + * However, if changeVal is false then plow ahead anyway since + * we are trying to find out if the value is potentially good, + * not actually use it. + * + * In EXEC_BACKEND builds, this works differently: we load all + * non-default settings from the CONFIG_EXEC_PARAMS file + * during backend start. In that case we must accept + * PGC_SIGHUP settings, so as to have the same value as if + * we'd forked from the postmaster. This can also happen when + * using RestoreGUCState() within a background worker that + * needs to have the same settings as the user backend that + * started it. is_reload will be true when either situation + * applies. + */ + if (IsUnderPostmaster && changeVal && !is_reload) + return -1; + } + else if (context != PGC_POSTMASTER && + context != PGC_BACKEND && + context != PGC_SU_BACKEND && + source != PGC_S_CLIENT) + { + ereport(elevel, + (errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM), + errmsg("parameter \"%s\" cannot be set after connection start", + name))); + return 0; + } + break; + case PGC_SUSET: + if (context == PGC_USERSET || context == PGC_BACKEND) + { + /* + * Check whether the requesting user has been granted + * privilege to set this GUC. + */ + AclResult aclresult; + + aclresult = pg_parameter_aclcheck(name, srole, ACL_SET); + if (aclresult != ACLCHECK_OK) + { + /* No granted privilege */ + ereport(elevel, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied to set parameter \"%s\"", + name))); + return 0; + } + } + break; + case PGC_USERSET: + /* always okay */ + break; + } + + /* + * Disallow changing GUC_NOT_WHILE_SEC_REST values if we are inside a + * security restriction context. We can reject this regardless of the GUC + * context or source, mainly because sources that it might be reasonable + * to override for won't be seen while inside a function. + * + * Note: variables marked GUC_NOT_WHILE_SEC_REST should usually be marked + * GUC_NO_RESET_ALL as well, because ResetAllOptions() doesn't check this. + * An exception might be made if the reset value is assumed to be "safe". + * + * Note: this flag is currently used for "session_authorization" and + * "role". We need to prohibit changing these inside a local userid + * context because when we exit it, GUC won't be notified, leaving things + * out of sync. (This could be fixed by forcing a new GUC nesting level, + * but that would change behavior in possibly-undesirable ways.) Also, we + * prohibit changing these in a security-restricted operation because + * otherwise RESET could be used to regain the session user's privileges. + */ + if (record->flags & GUC_NOT_WHILE_SEC_REST) + { + if (InLocalUserIdChange()) + { + /* + * Phrasing of this error message is historical, but it's the most + * common case. + */ + ereport(elevel, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("cannot set parameter \"%s\" within security-definer function", + name))); + return 0; + } + if (InSecurityRestrictedOperation()) + { + ereport(elevel, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("cannot set parameter \"%s\" within security-restricted operation", + name))); + return 0; + } + } + + /* Disallow resetting and saving GUC_NO_RESET values */ + if (record->flags & GUC_NO_RESET) + { + if (value == NULL) + { + ereport(elevel, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("parameter \"%s\" cannot be reset", name))); + return 0; + } + if (action == GUC_ACTION_SAVE) + { + ereport(elevel, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("parameter \"%s\" cannot be set locally in functions", + name))); + return 0; + } + } + + /* + * Should we set reset/stacked values? (If so, the behavior is not + * transactional.) This is done either when we get a default value from + * the database's/user's/client's default settings or when we reset a + * value to its default. + */ + makeDefault = changeVal && (source <= PGC_S_OVERRIDE) && + ((value != NULL) || source == PGC_S_DEFAULT); + + /* + * Ignore attempted set if overridden by previously processed setting. + * However, if changeVal is false then plow ahead anyway since we are + * trying to find out if the value is potentially good, not actually use + * it. Also keep going if makeDefault is true, since we may want to set + * the reset/stacked values even if we can't set the variable itself. + */ + if (record->source > source) + { + if (changeVal && !makeDefault) + { + elog(DEBUG3, "\"%s\": setting ignored because previous source is higher priority", + name); + return -1; + } + changeVal = false; + } + + /* + * Evaluate value and set variable. + */ + switch (record->vartype) + { + case PGC_BOOL: + { + struct config_bool *conf = (struct config_bool *) record; + +#define newval (newval_union.boolval) + + if (value) + { + if (!parse_and_validate_value(record, name, value, + source, elevel, + &newval_union, &newextra)) + return 0; + } + else if (source == PGC_S_DEFAULT) + { + newval = conf->boot_val; + if (!call_bool_check_hook(conf, &newval, &newextra, + source, elevel)) + return 0; + } + else + { + newval = conf->reset_val; + newextra = conf->reset_extra; + source = conf->gen.reset_source; + context = conf->gen.reset_scontext; + srole = conf->gen.reset_srole; + } + + if (prohibitValueChange) + { + /* Release newextra, unless it's reset_extra */ + if (newextra && !extra_field_used(&conf->gen, newextra)) + guc_free(newextra); + + if (*conf->variable != newval) + { + record->status |= GUC_PENDING_RESTART; + ereport(elevel, + (errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM), + errmsg("parameter \"%s\" cannot be changed without restarting the server", + name))); + return 0; + } + record->status &= ~GUC_PENDING_RESTART; + return -1; + } + + if (changeVal) + { + /* Save old value to support transaction abort */ + if (!makeDefault) + push_old_value(&conf->gen, action); + + if (conf->assign_hook) + conf->assign_hook(newval, newextra); + *conf->variable = newval; + set_extra_field(&conf->gen, &conf->gen.extra, + newextra); + set_guc_source(&conf->gen, source); + conf->gen.scontext = context; + conf->gen.srole = srole; + } + if (makeDefault) + { + GucStack *stack; + + if (conf->gen.reset_source <= source) + { + conf->reset_val = newval; + set_extra_field(&conf->gen, &conf->reset_extra, + newextra); + conf->gen.reset_source = source; + conf->gen.reset_scontext = context; + conf->gen.reset_srole = srole; + } + for (stack = conf->gen.stack; stack; stack = stack->prev) + { + if (stack->source <= source) + { + stack->prior.val.boolval = newval; + set_extra_field(&conf->gen, &stack->prior.extra, + newextra); + stack->source = source; + stack->scontext = context; + stack->srole = srole; + } + } + } + + /* Perhaps we didn't install newextra anywhere */ + if (newextra && !extra_field_used(&conf->gen, newextra)) + guc_free(newextra); + break; + +#undef newval + } + + case PGC_INT: + { + struct config_int *conf = (struct config_int *) record; + +#define newval (newval_union.intval) + + if (value) + { + if (!parse_and_validate_value(record, name, value, + source, elevel, + &newval_union, &newextra)) + return 0; + } + else if (source == PGC_S_DEFAULT) + { + newval = conf->boot_val; + if (!call_int_check_hook(conf, &newval, &newextra, + source, elevel)) + return 0; + } + else + { + newval = conf->reset_val; + newextra = conf->reset_extra; + source = conf->gen.reset_source; + context = conf->gen.reset_scontext; + srole = conf->gen.reset_srole; + } + + if (prohibitValueChange) + { + /* Release newextra, unless it's reset_extra */ + if (newextra && !extra_field_used(&conf->gen, newextra)) + guc_free(newextra); + + if (*conf->variable != newval) + { + record->status |= GUC_PENDING_RESTART; + ereport(elevel, + (errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM), + errmsg("parameter \"%s\" cannot be changed without restarting the server", + name))); + return 0; + } + record->status &= ~GUC_PENDING_RESTART; + return -1; + } + + if (changeVal) + { + /* Save old value to support transaction abort */ + if (!makeDefault) + push_old_value(&conf->gen, action); + + if (conf->assign_hook) + conf->assign_hook(newval, newextra); + *conf->variable = newval; + set_extra_field(&conf->gen, &conf->gen.extra, + newextra); + set_guc_source(&conf->gen, source); + conf->gen.scontext = context; + conf->gen.srole = srole; + } + if (makeDefault) + { + GucStack *stack; + + if (conf->gen.reset_source <= source) + { + conf->reset_val = newval; + set_extra_field(&conf->gen, &conf->reset_extra, + newextra); + conf->gen.reset_source = source; + conf->gen.reset_scontext = context; + conf->gen.reset_srole = srole; + } + for (stack = conf->gen.stack; stack; stack = stack->prev) + { + if (stack->source <= source) + { + stack->prior.val.intval = newval; + set_extra_field(&conf->gen, &stack->prior.extra, + newextra); + stack->source = source; + stack->scontext = context; + stack->srole = srole; + } + } + } + + /* Perhaps we didn't install newextra anywhere */ + if (newextra && !extra_field_used(&conf->gen, newextra)) + guc_free(newextra); + break; + +#undef newval + } + + case PGC_REAL: + { + struct config_real *conf = (struct config_real *) record; + +#define newval (newval_union.realval) + + if (value) + { + if (!parse_and_validate_value(record, name, value, + source, elevel, + &newval_union, &newextra)) + return 0; + } + else if (source == PGC_S_DEFAULT) + { + newval = conf->boot_val; + if (!call_real_check_hook(conf, &newval, &newextra, + source, elevel)) + return 0; + } + else + { + newval = conf->reset_val; + newextra = conf->reset_extra; + source = conf->gen.reset_source; + context = conf->gen.reset_scontext; + srole = conf->gen.reset_srole; + } + + if (prohibitValueChange) + { + /* Release newextra, unless it's reset_extra */ + if (newextra && !extra_field_used(&conf->gen, newextra)) + guc_free(newextra); + + if (*conf->variable != newval) + { + record->status |= GUC_PENDING_RESTART; + ereport(elevel, + (errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM), + errmsg("parameter \"%s\" cannot be changed without restarting the server", + name))); + return 0; + } + record->status &= ~GUC_PENDING_RESTART; + return -1; + } + + if (changeVal) + { + /* Save old value to support transaction abort */ + if (!makeDefault) + push_old_value(&conf->gen, action); + + if (conf->assign_hook) + conf->assign_hook(newval, newextra); + *conf->variable = newval; + set_extra_field(&conf->gen, &conf->gen.extra, + newextra); + set_guc_source(&conf->gen, source); + conf->gen.scontext = context; + conf->gen.srole = srole; + } + if (makeDefault) + { + GucStack *stack; + + if (conf->gen.reset_source <= source) + { + conf->reset_val = newval; + set_extra_field(&conf->gen, &conf->reset_extra, + newextra); + conf->gen.reset_source = source; + conf->gen.reset_scontext = context; + conf->gen.reset_srole = srole; + } + for (stack = conf->gen.stack; stack; stack = stack->prev) + { + if (stack->source <= source) + { + stack->prior.val.realval = newval; + set_extra_field(&conf->gen, &stack->prior.extra, + newextra); + stack->source = source; + stack->scontext = context; + stack->srole = srole; + } + } + } + + /* Perhaps we didn't install newextra anywhere */ + if (newextra && !extra_field_used(&conf->gen, newextra)) + guc_free(newextra); + break; + +#undef newval + } + + case PGC_STRING: + { + struct config_string *conf = (struct config_string *) record; + +#define newval (newval_union.stringval) + + if (value) + { + if (!parse_and_validate_value(record, name, value, + source, elevel, + &newval_union, &newextra)) + return 0; + } + else if (source == PGC_S_DEFAULT) + { + /* non-NULL boot_val must always get strdup'd */ + if (conf->boot_val != NULL) + { + newval = guc_strdup(elevel, conf->boot_val); + if (newval == NULL) + return 0; + } + else + newval = NULL; + + if (!call_string_check_hook(conf, &newval, &newextra, + source, elevel)) + { + guc_free(newval); + return 0; + } + } + else + { + /* + * strdup not needed, since reset_val is already under + * guc.c's control + */ + newval = conf->reset_val; + newextra = conf->reset_extra; + source = conf->gen.reset_source; + context = conf->gen.reset_scontext; + srole = conf->gen.reset_srole; + } + + if (prohibitValueChange) + { + bool newval_different; + + /* newval shouldn't be NULL, so we're a bit sloppy here */ + newval_different = (*conf->variable == NULL || + newval == NULL || + strcmp(*conf->variable, newval) != 0); + + /* Release newval, unless it's reset_val */ + if (newval && !string_field_used(conf, newval)) + guc_free(newval); + /* Release newextra, unless it's reset_extra */ + if (newextra && !extra_field_used(&conf->gen, newextra)) + guc_free(newextra); + + if (newval_different) + { + record->status |= GUC_PENDING_RESTART; + ereport(elevel, + (errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM), + errmsg("parameter \"%s\" cannot be changed without restarting the server", + name))); + return 0; + } + record->status &= ~GUC_PENDING_RESTART; + return -1; + } + + if (changeVal) + { + /* Save old value to support transaction abort */ + if (!makeDefault) + push_old_value(&conf->gen, action); + + if (conf->assign_hook) + conf->assign_hook(newval, newextra); + set_string_field(conf, conf->variable, newval); + set_extra_field(&conf->gen, &conf->gen.extra, + newextra); + set_guc_source(&conf->gen, source); + conf->gen.scontext = context; + conf->gen.srole = srole; + } + + if (makeDefault) + { + GucStack *stack; + + if (conf->gen.reset_source <= source) + { + set_string_field(conf, &conf->reset_val, newval); + set_extra_field(&conf->gen, &conf->reset_extra, + newextra); + conf->gen.reset_source = source; + conf->gen.reset_scontext = context; + conf->gen.reset_srole = srole; + } + for (stack = conf->gen.stack; stack; stack = stack->prev) + { + if (stack->source <= source) + { + set_string_field(conf, &stack->prior.val.stringval, + newval); + set_extra_field(&conf->gen, &stack->prior.extra, + newextra); + stack->source = source; + stack->scontext = context; + stack->srole = srole; + } + } + } + + /* Perhaps we didn't install newval anywhere */ + if (newval && !string_field_used(conf, newval)) + guc_free(newval); + /* Perhaps we didn't install newextra anywhere */ + if (newextra && !extra_field_used(&conf->gen, newextra)) + guc_free(newextra); + break; + +#undef newval + } + + case PGC_ENUM: + { + struct config_enum *conf = (struct config_enum *) record; + +#define newval (newval_union.enumval) + + if (value) + { + if (!parse_and_validate_value(record, name, value, + source, elevel, + &newval_union, &newextra)) + return 0; + } + else if (source == PGC_S_DEFAULT) + { + newval = conf->boot_val; + if (!call_enum_check_hook(conf, &newval, &newextra, + source, elevel)) + return 0; + } + else + { + newval = conf->reset_val; + newextra = conf->reset_extra; + source = conf->gen.reset_source; + context = conf->gen.reset_scontext; + srole = conf->gen.reset_srole; + } + + if (prohibitValueChange) + { + /* Release newextra, unless it's reset_extra */ + if (newextra && !extra_field_used(&conf->gen, newextra)) + guc_free(newextra); + + if (*conf->variable != newval) + { + record->status |= GUC_PENDING_RESTART; + ereport(elevel, + (errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM), + errmsg("parameter \"%s\" cannot be changed without restarting the server", + name))); + return 0; + } + record->status &= ~GUC_PENDING_RESTART; + return -1; + } + + if (changeVal) + { + /* Save old value to support transaction abort */ + if (!makeDefault) + push_old_value(&conf->gen, action); + + if (conf->assign_hook) + conf->assign_hook(newval, newextra); + *conf->variable = newval; + set_extra_field(&conf->gen, &conf->gen.extra, + newextra); + set_guc_source(&conf->gen, source); + conf->gen.scontext = context; + conf->gen.srole = srole; + } + if (makeDefault) + { + GucStack *stack; + + if (conf->gen.reset_source <= source) + { + conf->reset_val = newval; + set_extra_field(&conf->gen, &conf->reset_extra, + newextra); + conf->gen.reset_source = source; + conf->gen.reset_scontext = context; + conf->gen.reset_srole = srole; + } + for (stack = conf->gen.stack; stack; stack = stack->prev) + { + if (stack->source <= source) + { + stack->prior.val.enumval = newval; + set_extra_field(&conf->gen, &stack->prior.extra, + newextra); + stack->source = source; + stack->scontext = context; + stack->srole = srole; + } + } + } + + /* Perhaps we didn't install newextra anywhere */ + if (newextra && !extra_field_used(&conf->gen, newextra)) + guc_free(newextra); + break; + +#undef newval + } + } + + if (changeVal && (record->flags & GUC_REPORT) && + !(record->status & GUC_NEEDS_REPORT)) + { + record->status |= GUC_NEEDS_REPORT; + slist_push_head(&guc_report_list, &record->report_link); + } + + return changeVal ? 1 : -1; +} + + +/* + * Set the fields for source file and line number the setting came from. + */ +static void +set_config_sourcefile(const char *name, char *sourcefile, int sourceline) +{ + struct config_generic *record; + int elevel; + + /* + * To avoid cluttering the log, only the postmaster bleats loudly about + * problems with the config file. + */ + elevel = IsUnderPostmaster ? DEBUG3 : LOG; + + record = find_option(name, true, false, elevel); + /* should not happen */ + if (record == NULL) + return; + + sourcefile = guc_strdup(elevel, sourcefile); + guc_free(record->sourcefile); + record->sourcefile = sourcefile; + record->sourceline = sourceline; +} + +/* + * Set a config option to the given value. + * + * See also set_config_option; this is just the wrapper to be called from + * outside GUC. (This function should be used when possible, because its API + * is more stable than set_config_option's.) + * + * Note: there is no support here for setting source file/line, as it + * is currently not needed. + */ +void +SetConfigOption(const char *name, const char *value, + GucContext context, GucSource source) +{ + (void) set_config_option(name, value, context, source, + GUC_ACTION_SET, true, 0, false); +} + + + +/* + * Fetch the current value of the option `name', as a string. + * + * If the option doesn't exist, return NULL if missing_ok is true (NOTE that + * this cannot be distinguished from a string variable with a NULL value!), + * otherwise throw an ereport and don't return. + * + * If restrict_privileged is true, we also enforce that only superusers and + * members of the pg_read_all_settings role can see GUC_SUPERUSER_ONLY + * variables. This should only be passed as true in user-driven calls. + * + * The string is *not* allocated for modification and is really only + * valid until the next call to configuration related functions. + */ +const char * +GetConfigOption(const char *name, bool missing_ok, bool restrict_privileged) +{ + struct config_generic *record; + static __thread char buffer[256]; + + record = find_option(name, false, missing_ok, ERROR); + if (record == NULL) + return NULL; + if (restrict_privileged && + !ConfigOptionIsVisible(record)) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied to examine \"%s\"", name), + errdetail("Only roles with privileges of the \"%s\" role may examine this parameter.", + "pg_read_all_settings"))); + + switch (record->vartype) + { + case PGC_BOOL: + return *((struct config_bool *) record)->variable ? "on" : "off"; + + case PGC_INT: + snprintf(buffer, sizeof(buffer), "%d", + *((struct config_int *) record)->variable); + return buffer; + + case PGC_REAL: + snprintf(buffer, sizeof(buffer), "%g", + *((struct config_real *) record)->variable); + return buffer; + + case PGC_STRING: + return *((struct config_string *) record)->variable; + + case PGC_ENUM: + return config_enum_lookup_by_value((struct config_enum *) record, + *((struct config_enum *) record)->variable); + } + return NULL; +} + +/* + * Get the RESET value associated with the given option. + * + * Note: this is not re-entrant, due to use of static result buffer; + * not to mention that a string variable could have its reset_val changed. + * Beware of assuming the result value is good for very long. + */ +const char * +GetConfigOptionResetString(const char *name) +{ + struct config_generic *record; + static __thread char buffer[256]; + + record = find_option(name, false, false, ERROR); + Assert(record != NULL); + if (!ConfigOptionIsVisible(record)) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied to examine \"%s\"", name), + errdetail("Only roles with privileges of the \"%s\" role may examine this parameter.", + "pg_read_all_settings"))); + + switch (record->vartype) + { + case PGC_BOOL: + return ((struct config_bool *) record)->reset_val ? "on" : "off"; + + case PGC_INT: + snprintf(buffer, sizeof(buffer), "%d", + ((struct config_int *) record)->reset_val); + return buffer; + + case PGC_REAL: + snprintf(buffer, sizeof(buffer), "%g", + ((struct config_real *) record)->reset_val); + return buffer; + + case PGC_STRING: + return ((struct config_string *) record)->reset_val; + + case PGC_ENUM: + return config_enum_lookup_by_value((struct config_enum *) record, + ((struct config_enum *) record)->reset_val); + } + return NULL; +} + +/* + * Get the GUC flags associated with the given option. + * + * If the option doesn't exist, return 0 if missing_ok is true, + * otherwise throw an ereport and don't return. + */ +int +GetConfigOptionFlags(const char *name, bool missing_ok) +{ + struct config_generic *record; + + record = find_option(name, false, missing_ok, ERROR); + if (record == NULL) + return 0; + return record->flags; +} + + +/* + * Write updated configuration parameter values into a temporary file. + * This function traverses the list of parameters and quotes the string + * values before writing them. + */ +static void +write_auto_conf_file(int fd, const char *filename, ConfigVariable *head) +{ + StringInfoData buf; + ConfigVariable *item; + + initStringInfo(&buf); + + /* Emit file header containing warning comment */ + appendStringInfoString(&buf, "# Do not edit this file manually!\n"); + appendStringInfoString(&buf, "# It will be overwritten by the ALTER SYSTEM command.\n"); + + errno = 0; + if (write(fd, buf.data, buf.len) != buf.len) + { + /* if write didn't set errno, assume problem is no disk space */ + if (errno == 0) + errno = ENOSPC; + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not write to file \"%s\": %m", filename))); + } + + /* Emit each parameter, properly quoting the value */ + for (item = head; item != NULL; item = item->next) + { + char *escaped; + + resetStringInfo(&buf); + + appendStringInfoString(&buf, item->name); + appendStringInfoString(&buf, " = '"); + + escaped = escape_single_quotes_ascii(item->value); + if (!escaped) + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"))); + appendStringInfoString(&buf, escaped); + free(escaped); + + appendStringInfoString(&buf, "'\n"); + + errno = 0; + if (write(fd, buf.data, buf.len) != buf.len) + { + /* if write didn't set errno, assume problem is no disk space */ + if (errno == 0) + errno = ENOSPC; + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not write to file \"%s\": %m", filename))); + } + } + + /* fsync before considering the write to be successful */ + if (pg_fsync(fd) != 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not fsync file \"%s\": %m", filename))); + + pfree(buf.data); +} + +/* + * Update the given list of configuration parameters, adding, replacing + * or deleting the entry for item "name" (delete if "value" == NULL). + */ +static void +replace_auto_config_value(ConfigVariable **head_p, ConfigVariable **tail_p, + const char *name, const char *value) +{ + ConfigVariable *item, + *next, + *prev = NULL; + + /* + * Remove any existing match(es) for "name". Normally there'd be at most + * one, but if external tools have modified the config file, there could + * be more. + */ + for (item = *head_p; item != NULL; item = next) + { + next = item->next; + if (guc_name_compare(item->name, name) == 0) + { + /* found a match, delete it */ + if (prev) + prev->next = next; + else + *head_p = next; + if (next == NULL) + *tail_p = prev; + + pfree(item->name); + pfree(item->value); + pfree(item->filename); + pfree(item); + } + else + prev = item; + } + + /* Done if we're trying to delete it */ + if (value == NULL) + return; + + /* OK, append a new entry */ + item = palloc(sizeof *item); + item->name = pstrdup(name); + item->value = pstrdup(value); + item->errmsg = NULL; + item->filename = pstrdup(""); /* new item has no location */ + item->sourceline = 0; + item->ignore = false; + item->applied = false; + item->next = NULL; + + if (*head_p == NULL) + *head_p = item; + else + (*tail_p)->next = item; + *tail_p = item; +} + + +/* + * Execute ALTER SYSTEM statement. + * + * Read the old PG_AUTOCONF_FILENAME file, merge in the new variable value, + * and write out an updated file. If the command is ALTER SYSTEM RESET ALL, + * we can skip reading the old file and just write an empty file. + * + * An LWLock is used to serialize updates of the configuration file. + * + * In case of an error, we leave the original automatic + * configuration file (PG_AUTOCONF_FILENAME) intact. + */ +void +AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt) +{ + char *name; + char *value; + bool resetall = false; + ConfigVariable *head = NULL; + ConfigVariable *tail = NULL; + volatile int Tmpfd; + char AutoConfFileName[MAXPGPATH]; + char AutoConfTmpFileName[MAXPGPATH]; + + /* + * Extract statement arguments + */ + name = altersysstmt->setstmt->name; + + switch (altersysstmt->setstmt->kind) + { + case VAR_SET_VALUE: + value = ExtractSetVariableArgs(altersysstmt->setstmt); + break; + + case VAR_SET_DEFAULT: + case VAR_RESET: + value = NULL; + break; + + case VAR_RESET_ALL: + value = NULL; + resetall = true; + break; + + default: + elog(ERROR, "unrecognized alter system stmt type: %d", + altersysstmt->setstmt->kind); + break; + } + + /* + * Check permission to run ALTER SYSTEM on the target variable + */ + if (!superuser()) + { + if (resetall) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied to perform ALTER SYSTEM RESET ALL"))); + else + { + AclResult aclresult; + + aclresult = pg_parameter_aclcheck(name, GetUserId(), + ACL_ALTER_SYSTEM); + if (aclresult != ACLCHECK_OK) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied to set parameter \"%s\"", + name))); + } + } + + /* + * Unless it's RESET_ALL, validate the target variable and value + */ + if (!resetall) + { + struct config_generic *record; + + record = find_option(name, false, false, ERROR); + Assert(record != NULL); + + /* + * Don't allow parameters that can't be set in configuration files to + * be set in PG_AUTOCONF_FILENAME file. + */ + if ((record->context == PGC_INTERNAL) || + (record->flags & GUC_DISALLOW_IN_FILE) || + (record->flags & GUC_DISALLOW_IN_AUTO_FILE)) + ereport(ERROR, + (errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM), + errmsg("parameter \"%s\" cannot be changed", + name))); + + /* + * If a value is specified, verify that it's sane. + */ + if (value) + { + union config_var_val newval; + void *newextra = NULL; + + /* Check that it's acceptable for the indicated parameter */ + if (!parse_and_validate_value(record, name, value, + PGC_S_FILE, ERROR, + &newval, &newextra)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for parameter \"%s\": \"%s\"", + name, value))); + + if (record->vartype == PGC_STRING && newval.stringval != NULL) + guc_free(newval.stringval); + guc_free(newextra); + + /* + * We must also reject values containing newlines, because the + * grammar for config files doesn't support embedded newlines in + * string literals. + */ + if (strchr(value, '\n')) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("parameter value for ALTER SYSTEM must not contain a newline"))); + } + } + + /* + * PG_AUTOCONF_FILENAME and its corresponding temporary file are always in + * the data directory, so we can reference them by simple relative paths. + */ + snprintf(AutoConfFileName, sizeof(AutoConfFileName), "%s", + PG_AUTOCONF_FILENAME); + snprintf(AutoConfTmpFileName, sizeof(AutoConfTmpFileName), "%s.%s", + AutoConfFileName, + "tmp"); + + /* + * Only one backend is allowed to operate on PG_AUTOCONF_FILENAME at a + * time. Use AutoFileLock to ensure that. We must hold the lock while + * reading the old file contents. + */ + LWLockAcquire(AutoFileLock, LW_EXCLUSIVE); + + /* + * If we're going to reset everything, then no need to open or parse the + * old file. We'll just write out an empty list. + */ + if (!resetall) + { + struct stat st; + + if (stat(AutoConfFileName, &st) == 0) + { + /* open old file PG_AUTOCONF_FILENAME */ + FILE *infile; + + infile = AllocateFile(AutoConfFileName, "r"); + if (infile == NULL) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not open file \"%s\": %m", + AutoConfFileName))); + + /* parse it */ + if (!ParseConfigFp(infile, AutoConfFileName, CONF_FILE_START_DEPTH, + LOG, &head, &tail)) + ereport(ERROR, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("could not parse contents of file \"%s\"", + AutoConfFileName))); + + FreeFile(infile); + } + + /* + * Now, replace any existing entry with the new value, or add it if + * not present. + */ + replace_auto_config_value(&head, &tail, name, value); + } + + /* + * Invoke the post-alter hook for setting this GUC variable. GUCs + * typically do not have corresponding entries in pg_parameter_acl, so we + * call the hook using the name rather than a potentially-non-existent + * OID. Nonetheless, we pass ParameterAclRelationId so that this call + * context can be distinguished from others. (Note that "name" will be + * NULL in the RESET ALL case.) + * + * We do this here rather than at the end, because ALTER SYSTEM is not + * transactional. If the hook aborts our transaction, it will be cleaner + * to do so before we touch any files. + */ + InvokeObjectPostAlterHookArgStr(ParameterAclRelationId, name, + ACL_ALTER_SYSTEM, + altersysstmt->setstmt->kind, + false); + + /* + * To ensure crash safety, first write the new file data to a temp file, + * then atomically rename it into place. + * + * If there is a temp file left over due to a previous crash, it's okay to + * truncate and reuse it. + */ + Tmpfd = BasicOpenFile(AutoConfTmpFileName, + O_CREAT | O_RDWR | O_TRUNC); + if (Tmpfd < 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not open file \"%s\": %m", + AutoConfTmpFileName))); + + /* + * Use a TRY block to clean up the file if we fail. Since we need a TRY + * block anyway, OK to use BasicOpenFile rather than OpenTransientFile. + */ + PG_TRY(); + { + /* Write and sync the new contents to the temporary file */ + write_auto_conf_file(Tmpfd, AutoConfTmpFileName, head); + + /* Close before renaming; may be required on some platforms */ + close(Tmpfd); + Tmpfd = -1; + + /* + * As the rename is atomic operation, if any problem occurs after this + * at worst it can lose the parameters set by last ALTER SYSTEM + * command. + */ + durable_rename(AutoConfTmpFileName, AutoConfFileName, ERROR); + } + PG_CATCH(); + { + /* Close file first, else unlink might fail on some platforms */ + if (Tmpfd >= 0) + close(Tmpfd); + + /* Unlink, but ignore any error */ + (void) unlink(AutoConfTmpFileName); + + PG_RE_THROW(); + } + PG_END_TRY(); + + FreeConfigVariables(head); + + LWLockRelease(AutoFileLock); +} + + +/* + * Common code for DefineCustomXXXVariable subroutines: allocate the + * new variable's config struct and fill in generic fields. + */ +static struct config_generic * +init_custom_variable(const char *name, + const char *short_desc, + const char *long_desc, + GucContext context, + int flags, + enum config_type type, + size_t sz) +{ + struct config_generic *gen; + + /* + * Only allow custom PGC_POSTMASTER variables to be created during shared + * library preload; any later than that, we can't ensure that the value + * doesn't change after startup. This is a fatal elog if it happens; just + * erroring out isn't safe because we don't know what the calling loadable + * module might already have hooked into. + */ + if (context == PGC_POSTMASTER && + !process_shared_preload_libraries_in_progress) + elog(FATAL, "cannot create PGC_POSTMASTER variables after startup"); + + /* + * We can't support custom GUC_LIST_QUOTE variables, because the wrong + * things would happen if such a variable were set or pg_dump'd when the + * defining extension isn't loaded. Again, treat this as fatal because + * the loadable module may be partly initialized already. + */ + if (flags & GUC_LIST_QUOTE) + elog(FATAL, "extensions cannot define GUC_LIST_QUOTE variables"); + + /* + * Before pljava commit 398f3b876ed402bdaec8bc804f29e2be95c75139 + * (2015-12-15), two of that module's PGC_USERSET variables facilitated + * trivial escalation to superuser privileges. Restrict the variables to + * protect sites that have yet to upgrade pljava. + */ + if (context == PGC_USERSET && + (strcmp(name, "pljava.classpath") == 0 || + strcmp(name, "pljava.vmoptions") == 0)) + context = PGC_SUSET; + + gen = (struct config_generic *) guc_malloc(ERROR, sz); + memset(gen, 0, sz); + + gen->name = guc_strdup(ERROR, name); + gen->context = context; + gen->group = CUSTOM_OPTIONS; + gen->short_desc = short_desc; + gen->long_desc = long_desc; + gen->flags = flags; + gen->vartype = type; + + return gen; +} + +/* + * Common code for DefineCustomXXXVariable subroutines: insert the new + * variable into the GUC variable hash, replacing any placeholder. + */ +static void +define_custom_variable(struct config_generic *variable) +{ + const char *name = variable->name; + GUCHashEntry *hentry; + struct config_string *pHolder; + + /* Check mapping between initial and default value */ + Assert(check_GUC_init(variable)); + + /* + * See if there's a placeholder by the same name. + */ + hentry = (GUCHashEntry *) hash_search(guc_hashtab, + &name, + HASH_FIND, + NULL); + if (hentry == NULL) + { + /* + * No placeholder to replace, so we can just add it ... but first, + * make sure it's initialized to its default value. + */ + InitializeOneGUCOption(variable); + add_guc_variable(variable, ERROR); + return; + } + + /* + * This better be a placeholder + */ + if ((hentry->gucvar->flags & GUC_CUSTOM_PLACEHOLDER) == 0) + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("attempt to redefine parameter \"%s\"", name))); + + Assert(hentry->gucvar->vartype == PGC_STRING); + pHolder = (struct config_string *) hentry->gucvar; + + /* + * First, set the variable to its default value. We must do this even + * though we intend to immediately apply a new value, since it's possible + * that the new value is invalid. + */ + InitializeOneGUCOption(variable); + + /* + * Replace the placeholder in the hash table. We aren't changing the name + * (at least up to case-folding), so the hash value is unchanged. + */ + hentry->gucname = name; + hentry->gucvar = variable; + + /* + * Remove the placeholder from any lists it's in, too. + */ + RemoveGUCFromLists(&pHolder->gen); + + /* + * Assign the string value(s) stored in the placeholder to the real + * variable. Essentially, we need to duplicate all the active and stacked + * values, but with appropriate validation and datatype adjustment. + * + * If an assignment fails, we report a WARNING and keep going. We don't + * want to throw ERROR for bad values, because it'd bollix the add-on + * module that's presumably halfway through getting loaded. In such cases + * the default or previous state will become active instead. + */ + + /* First, apply the reset value if any */ + if (pHolder->reset_val) + (void) set_config_option_ext(name, pHolder->reset_val, + pHolder->gen.reset_scontext, + pHolder->gen.reset_source, + pHolder->gen.reset_srole, + GUC_ACTION_SET, true, WARNING, false); + /* That should not have resulted in stacking anything */ + Assert(variable->stack == NULL); + + /* Now, apply current and stacked values, in the order they were stacked */ + reapply_stacked_values(variable, pHolder, pHolder->gen.stack, + *(pHolder->variable), + pHolder->gen.scontext, pHolder->gen.source, + pHolder->gen.srole); + + /* Also copy over any saved source-location information */ + if (pHolder->gen.sourcefile) + set_config_sourcefile(name, pHolder->gen.sourcefile, + pHolder->gen.sourceline); + + /* + * Free up as much as we conveniently can of the placeholder structure. + * (This neglects any stack items, so it's possible for some memory to be + * leaked. Since this can only happen once per session per variable, it + * doesn't seem worth spending much code on.) + */ + set_string_field(pHolder, pHolder->variable, NULL); + set_string_field(pHolder, &pHolder->reset_val, NULL); + + guc_free(pHolder); +} + +/* + * Recursive subroutine for define_custom_variable: reapply non-reset values + * + * We recurse so that the values are applied in the same order as originally. + * At each recursion level, apply the upper-level value (passed in) in the + * fashion implied by the stack entry. + */ +static void +reapply_stacked_values(struct config_generic *variable, + struct config_string *pHolder, + GucStack *stack, + const char *curvalue, + GucContext curscontext, GucSource cursource, + Oid cursrole) +{ + const char *name = variable->name; + GucStack *oldvarstack = variable->stack; + + if (stack != NULL) + { + /* First, recurse, so that stack items are processed bottom to top */ + reapply_stacked_values(variable, pHolder, stack->prev, + stack->prior.val.stringval, + stack->scontext, stack->source, stack->srole); + + /* See how to apply the passed-in value */ + switch (stack->state) + { + case GUC_SAVE: + (void) set_config_option_ext(name, curvalue, + curscontext, cursource, cursrole, + GUC_ACTION_SAVE, true, + WARNING, false); + break; + + case GUC_SET: + (void) set_config_option_ext(name, curvalue, + curscontext, cursource, cursrole, + GUC_ACTION_SET, true, + WARNING, false); + break; + + case GUC_LOCAL: + (void) set_config_option_ext(name, curvalue, + curscontext, cursource, cursrole, + GUC_ACTION_LOCAL, true, + WARNING, false); + break; + + case GUC_SET_LOCAL: + /* first, apply the masked value as SET */ + (void) set_config_option_ext(name, stack->masked.val.stringval, + stack->masked_scontext, + PGC_S_SESSION, + stack->masked_srole, + GUC_ACTION_SET, true, + WARNING, false); + /* then apply the current value as LOCAL */ + (void) set_config_option_ext(name, curvalue, + curscontext, cursource, cursrole, + GUC_ACTION_LOCAL, true, + WARNING, false); + break; + } + + /* If we successfully made a stack entry, adjust its nest level */ + if (variable->stack != oldvarstack) + variable->stack->nest_level = stack->nest_level; + } + else + { + /* + * We are at the end of the stack. If the active/previous value is + * different from the reset value, it must represent a previously + * committed session value. Apply it, and then drop the stack entry + * that set_config_option will have created under the impression that + * this is to be just a transactional assignment. (We leak the stack + * entry.) + */ + if (curvalue != pHolder->reset_val || + curscontext != pHolder->gen.reset_scontext || + cursource != pHolder->gen.reset_source || + cursrole != pHolder->gen.reset_srole) + { + (void) set_config_option_ext(name, curvalue, + curscontext, cursource, cursrole, + GUC_ACTION_SET, true, WARNING, false); + if (variable->stack != NULL) + { + slist_delete(&guc_stack_list, &variable->stack_link); + variable->stack = NULL; + } + } + } +} + +/* + * Functions for extensions to call to define their custom GUC variables. + */ +void +DefineCustomBoolVariable(const char *name, + const char *short_desc, + const char *long_desc, + bool *valueAddr, + bool bootValue, + GucContext context, + int flags, + GucBoolCheckHook check_hook, + GucBoolAssignHook assign_hook, + GucShowHook show_hook) +{ + struct config_bool *var; + + var = (struct config_bool *) + init_custom_variable(name, short_desc, long_desc, context, flags, + PGC_BOOL, sizeof(struct config_bool)); + var->variable = valueAddr; + var->boot_val = bootValue; + var->reset_val = bootValue; + var->check_hook = check_hook; + var->assign_hook = assign_hook; + var->show_hook = show_hook; + define_custom_variable(&var->gen); +} + +void +DefineCustomIntVariable(const char *name, + const char *short_desc, + const char *long_desc, + int *valueAddr, + int bootValue, + int minValue, + int maxValue, + GucContext context, + int flags, + GucIntCheckHook check_hook, + GucIntAssignHook assign_hook, + GucShowHook show_hook) +{ + struct config_int *var; + + var = (struct config_int *) + init_custom_variable(name, short_desc, long_desc, context, flags, + PGC_INT, sizeof(struct config_int)); + var->variable = valueAddr; + var->boot_val = bootValue; + var->reset_val = bootValue; + var->min = minValue; + var->max = maxValue; + var->check_hook = check_hook; + var->assign_hook = assign_hook; + var->show_hook = show_hook; + define_custom_variable(&var->gen); +} + +void +DefineCustomRealVariable(const char *name, + const char *short_desc, + const char *long_desc, + double *valueAddr, + double bootValue, + double minValue, + double maxValue, + GucContext context, + int flags, + GucRealCheckHook check_hook, + GucRealAssignHook assign_hook, + GucShowHook show_hook) +{ + struct config_real *var; + + var = (struct config_real *) + init_custom_variable(name, short_desc, long_desc, context, flags, + PGC_REAL, sizeof(struct config_real)); + var->variable = valueAddr; + var->boot_val = bootValue; + var->reset_val = bootValue; + var->min = minValue; + var->max = maxValue; + var->check_hook = check_hook; + var->assign_hook = assign_hook; + var->show_hook = show_hook; + define_custom_variable(&var->gen); +} + +void +DefineCustomStringVariable(const char *name, + const char *short_desc, + const char *long_desc, + char **valueAddr, + const char *bootValue, + GucContext context, + int flags, + GucStringCheckHook check_hook, + GucStringAssignHook assign_hook, + GucShowHook show_hook) +{ + struct config_string *var; + + var = (struct config_string *) + init_custom_variable(name, short_desc, long_desc, context, flags, + PGC_STRING, sizeof(struct config_string)); + var->variable = valueAddr; + var->boot_val = bootValue; + var->check_hook = check_hook; + var->assign_hook = assign_hook; + var->show_hook = show_hook; + define_custom_variable(&var->gen); +} + +void +DefineCustomEnumVariable(const char *name, + const char *short_desc, + const char *long_desc, + int *valueAddr, + int bootValue, + const struct config_enum_entry *options, + GucContext context, + int flags, + GucEnumCheckHook check_hook, + GucEnumAssignHook assign_hook, + GucShowHook show_hook) +{ + struct config_enum *var; + + var = (struct config_enum *) + init_custom_variable(name, short_desc, long_desc, context, flags, + PGC_ENUM, sizeof(struct config_enum)); + var->variable = valueAddr; + var->boot_val = bootValue; + var->reset_val = bootValue; + var->options = options; + var->check_hook = check_hook; + var->assign_hook = assign_hook; + var->show_hook = show_hook; + define_custom_variable(&var->gen); +} + +/* + * Mark the given GUC prefix as "reserved". + * + * This deletes any existing placeholders matching the prefix, + * and then prevents new ones from being created. + * Extensions should call this after they've defined all of their custom + * GUCs, to help catch misspelled config-file entries. + */ +void +MarkGUCPrefixReserved(const char *className) +{ + int classLen = strlen(className); + HASH_SEQ_STATUS status; + GUCHashEntry *hentry; + MemoryContext oldcontext; + + /* + * Check for existing placeholders. We must actually remove invalid + * placeholders, else future parallel worker startups will fail. (We + * don't bother trying to free associated memory, since this shouldn't + * happen often.) + */ + hash_seq_init(&status, guc_hashtab); + while ((hentry = (GUCHashEntry *) hash_seq_search(&status)) != NULL) + { + struct config_generic *var = hentry->gucvar; + + if ((var->flags & GUC_CUSTOM_PLACEHOLDER) != 0 && + strncmp(className, var->name, classLen) == 0 && + var->name[classLen] == GUC_QUALIFIER_SEPARATOR) + { + ereport(WARNING, + (errcode(ERRCODE_INVALID_NAME), + errmsg("invalid configuration parameter name \"%s\", removing it", + var->name), + errdetail("\"%s\" is now a reserved prefix.", + className))); + /* Remove it from the hash table */ + hash_search(guc_hashtab, + &var->name, + HASH_REMOVE, + NULL); + /* Remove it from any lists it's in, too */ + RemoveGUCFromLists(var); + } + } + + /* And remember the name so we can prevent future mistakes. */ + oldcontext = MemoryContextSwitchTo(GUCMemoryContext); + reserved_class_prefix = lappend(reserved_class_prefix, pstrdup(className)); + MemoryContextSwitchTo(oldcontext); +} + + +/* + * Return an array of modified GUC options to show in EXPLAIN. + * + * We only report options related to query planning (marked with GUC_EXPLAIN), + * with values different from their built-in defaults. + */ +struct config_generic ** +get_explain_guc_options(int *num) +{ + struct config_generic **result; + dlist_iter iter; + + *num = 0; + + /* + * While only a fraction of all the GUC variables are marked GUC_EXPLAIN, + * it doesn't seem worth dynamically resizing this array. + */ + result = palloc(sizeof(struct config_generic *) * hash_get_num_entries(guc_hashtab)); + + /* We need only consider GUCs with source not PGC_S_DEFAULT */ + dlist_foreach(iter, &guc_nondef_list) + { + struct config_generic *conf = dlist_container(struct config_generic, + nondef_link, iter.cur); + bool modified; + + /* return only parameters marked for inclusion in explain */ + if (!(conf->flags & GUC_EXPLAIN)) + continue; + + /* return only options visible to the current user */ + if (!ConfigOptionIsVisible(conf)) + continue; + + /* return only options that are different from their boot values */ + modified = false; + + switch (conf->vartype) + { + case PGC_BOOL: + { + struct config_bool *lconf = (struct config_bool *) conf; + + modified = (lconf->boot_val != *(lconf->variable)); + } + break; + + case PGC_INT: + { + struct config_int *lconf = (struct config_int *) conf; + + modified = (lconf->boot_val != *(lconf->variable)); + } + break; + + case PGC_REAL: + { + struct config_real *lconf = (struct config_real *) conf; + + modified = (lconf->boot_val != *(lconf->variable)); + } + break; + + case PGC_STRING: + { + struct config_string *lconf = (struct config_string *) conf; + + if (lconf->boot_val == NULL && + *lconf->variable == NULL) + modified = false; + else if (lconf->boot_val == NULL || + *lconf->variable == NULL) + modified = true; + else + modified = (strcmp(lconf->boot_val, *(lconf->variable)) != 0); + } + break; + + case PGC_ENUM: + { + struct config_enum *lconf = (struct config_enum *) conf; + + modified = (lconf->boot_val != *(lconf->variable)); + } + break; + + default: + elog(ERROR, "unexpected GUC type: %d", conf->vartype); + } + + if (!modified) + continue; + + /* OK, report it */ + result[*num] = conf; + *num = *num + 1; + } + + return result; +} + +/* + * Return GUC variable value by name; optionally return canonical form of + * name. If the GUC is unset, then throw an error unless missing_ok is true, + * in which case return NULL. Return value is palloc'd (but *varname isn't). + */ +char * +GetConfigOptionByName(const char *name, const char **varname, bool missing_ok) +{ + struct config_generic *record; + + record = find_option(name, false, missing_ok, ERROR); + if (record == NULL) + { + if (varname) + *varname = NULL; + return NULL; + } + + if (!ConfigOptionIsVisible(record)) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied to examine \"%s\"", name), + errdetail("Only roles with privileges of the \"%s\" role may examine this parameter.", + "pg_read_all_settings"))); + + if (varname) + *varname = record->name; + + return ShowGUCOption(record, true); +} + +/* + * ShowGUCOption: get string value of variable + * + * We express a numeric value in appropriate units if it has units and + * use_units is true; else you just get the raw number. + * The result string is palloc'd. + */ +char * +ShowGUCOption(struct config_generic *record, bool use_units) +{ + char buffer[256]; + const char *val; + + switch (record->vartype) + { + case PGC_BOOL: + { + struct config_bool *conf = (struct config_bool *) record; + + if (conf->show_hook) + val = conf->show_hook(); + else + val = *conf->variable ? "on" : "off"; + } + break; + + case PGC_INT: + { + struct config_int *conf = (struct config_int *) record; + + if (conf->show_hook) + val = conf->show_hook(); + else + { + /* + * Use int64 arithmetic to avoid overflows in units + * conversion. + */ + int64 result = *conf->variable; + const char *unit; + + if (use_units && result > 0 && (record->flags & GUC_UNIT)) + convert_int_from_base_unit(result, + record->flags & GUC_UNIT, + &result, &unit); + else + unit = ""; + + snprintf(buffer, sizeof(buffer), INT64_FORMAT "%s", + result, unit); + val = buffer; + } + } + break; + + case PGC_REAL: + { + struct config_real *conf = (struct config_real *) record; + + if (conf->show_hook) + val = conf->show_hook(); + else + { + double result = *conf->variable; + const char *unit; + + if (use_units && result > 0 && (record->flags & GUC_UNIT)) + convert_real_from_base_unit(result, + record->flags & GUC_UNIT, + &result, &unit); + else + unit = ""; + + snprintf(buffer, sizeof(buffer), "%g%s", + result, unit); + val = buffer; + } + } + break; + + case PGC_STRING: + { + struct config_string *conf = (struct config_string *) record; + + if (conf->show_hook) + val = conf->show_hook(); + else if (*conf->variable && **conf->variable) + val = *conf->variable; + else + val = ""; + } + break; + + case PGC_ENUM: + { + struct config_enum *conf = (struct config_enum *) record; + + if (conf->show_hook) + val = conf->show_hook(); + else + val = config_enum_lookup_by_value(conf, *conf->variable); + } + break; + + default: + /* just to keep compiler quiet */ + val = "???"; + break; + } + + return pstrdup(val); +} + + +#ifdef EXEC_BACKEND + +/* + * These routines dump out all non-default GUC options into a binary + * file that is read by all exec'ed backends. The format is: + * + * variable name, string, null terminated + * variable value, string, null terminated + * variable sourcefile, string, null terminated (empty if none) + * variable sourceline, integer + * variable source, integer + * variable scontext, integer +* variable srole, OID + */ +static void +write_one_nondefault_variable(FILE *fp, struct config_generic *gconf) +{ + Assert(gconf->source != PGC_S_DEFAULT); + + fprintf(fp, "%s", gconf->name); + fputc(0, fp); + + switch (gconf->vartype) + { + case PGC_BOOL: + { + struct config_bool *conf = (struct config_bool *) gconf; + + if (*conf->variable) + fprintf(fp, "true"); + else + fprintf(fp, "false"); + } + break; + + case PGC_INT: + { + struct config_int *conf = (struct config_int *) gconf; + + fprintf(fp, "%d", *conf->variable); + } + break; + + case PGC_REAL: + { + struct config_real *conf = (struct config_real *) gconf; + + fprintf(fp, "%.17g", *conf->variable); + } + break; + + case PGC_STRING: + { + struct config_string *conf = (struct config_string *) gconf; + + if (*conf->variable) + fprintf(fp, "%s", *conf->variable); + } + break; + + case PGC_ENUM: + { + struct config_enum *conf = (struct config_enum *) gconf; + + fprintf(fp, "%s", + config_enum_lookup_by_value(conf, *conf->variable)); + } + break; + } + + fputc(0, fp); + + if (gconf->sourcefile) + fprintf(fp, "%s", gconf->sourcefile); + fputc(0, fp); + + fwrite(&gconf->sourceline, 1, sizeof(gconf->sourceline), fp); + fwrite(&gconf->source, 1, sizeof(gconf->source), fp); + fwrite(&gconf->scontext, 1, sizeof(gconf->scontext), fp); + fwrite(&gconf->srole, 1, sizeof(gconf->srole), fp); +} + +void +write_nondefault_variables(GucContext context) +{ + int elevel; + FILE *fp; + dlist_iter iter; + + Assert(context == PGC_POSTMASTER || context == PGC_SIGHUP); + + elevel = (context == PGC_SIGHUP) ? LOG : ERROR; + + /* + * Open file + */ + fp = AllocateFile(CONFIG_EXEC_PARAMS_NEW, "w"); + if (!fp) + { + ereport(elevel, + (errcode_for_file_access(), + errmsg("could not write to file \"%s\": %m", + CONFIG_EXEC_PARAMS_NEW))); + return; + } + + /* We need only consider GUCs with source not PGC_S_DEFAULT */ + dlist_foreach(iter, &guc_nondef_list) + { + struct config_generic *gconf = dlist_container(struct config_generic, + nondef_link, iter.cur); + + write_one_nondefault_variable(fp, gconf); + } + + if (FreeFile(fp)) + { + ereport(elevel, + (errcode_for_file_access(), + errmsg("could not write to file \"%s\": %m", + CONFIG_EXEC_PARAMS_NEW))); + return; + } + + /* + * Put new file in place. This could delay on Win32, but we don't hold + * any exclusive locks. + */ + rename(CONFIG_EXEC_PARAMS_NEW, CONFIG_EXEC_PARAMS); +} + + +/* + * Read string, including null byte from file + * + * Return NULL on EOF and nothing read + */ +static char * +read_string_with_null(FILE *fp) +{ + int i = 0, + ch, + maxlen = 256; + char *str = NULL; + + do + { + if ((ch = fgetc(fp)) == EOF) + { + if (i == 0) + return NULL; + else + elog(FATAL, "invalid format of exec config params file"); + } + if (i == 0) + str = guc_malloc(FATAL, maxlen); + else if (i == maxlen) + str = guc_realloc(FATAL, str, maxlen *= 2); + str[i++] = ch; + } while (ch != 0); + + return str; +} + + +/* + * This routine loads a previous postmaster dump of its non-default + * settings. + */ +void +read_nondefault_variables(void) +{ + FILE *fp; + char *varname, + *varvalue, + *varsourcefile; + int varsourceline; + GucSource varsource; + GucContext varscontext; + Oid varsrole; + + /* + * Open file + */ + fp = AllocateFile(CONFIG_EXEC_PARAMS, "r"); + if (!fp) + { + /* File not found is fine */ + if (errno != ENOENT) + ereport(FATAL, + (errcode_for_file_access(), + errmsg("could not read from file \"%s\": %m", + CONFIG_EXEC_PARAMS))); + return; + } + + for (;;) + { + if ((varname = read_string_with_null(fp)) == NULL) + break; + + if (find_option(varname, true, false, FATAL) == NULL) + elog(FATAL, "failed to locate variable \"%s\" in exec config params file", varname); + + if ((varvalue = read_string_with_null(fp)) == NULL) + elog(FATAL, "invalid format of exec config params file"); + if ((varsourcefile = read_string_with_null(fp)) == NULL) + elog(FATAL, "invalid format of exec config params file"); + if (fread(&varsourceline, 1, sizeof(varsourceline), fp) != sizeof(varsourceline)) + elog(FATAL, "invalid format of exec config params file"); + if (fread(&varsource, 1, sizeof(varsource), fp) != sizeof(varsource)) + elog(FATAL, "invalid format of exec config params file"); + if (fread(&varscontext, 1, sizeof(varscontext), fp) != sizeof(varscontext)) + elog(FATAL, "invalid format of exec config params file"); + if (fread(&varsrole, 1, sizeof(varsrole), fp) != sizeof(varsrole)) + elog(FATAL, "invalid format of exec config params file"); + + (void) set_config_option_ext(varname, varvalue, + varscontext, varsource, varsrole, + GUC_ACTION_SET, true, 0, true); + if (varsourcefile[0]) + set_config_sourcefile(varname, varsourcefile, varsourceline); + + guc_free(varname); + guc_free(varvalue); + guc_free(varsourcefile); + } + + FreeFile(fp); +} +#endif /* EXEC_BACKEND */ + +/* + * can_skip_gucvar: + * Decide whether SerializeGUCState can skip sending this GUC variable, + * or whether RestoreGUCState can skip resetting this GUC to default. + * + * It is somewhat magical and fragile that the same test works for both cases. + * Realize in particular that we are very likely selecting different sets of + * GUCs on the leader and worker sides! Be sure you've understood the + * comments here and in RestoreGUCState thoroughly before changing this. + */ +static bool +can_skip_gucvar(struct config_generic *gconf) +{ + /* + * We can skip GUCs that are guaranteed to have the same values in leaders + * and workers. (Note it is critical that the leader and worker have the + * same idea of which GUCs fall into this category. It's okay to consider + * context and name for this purpose, since those are unchanging + * properties of a GUC.) + * + * PGC_POSTMASTER variables always have the same value in every child of a + * particular postmaster, so the worker will certainly have the right + * value already. Likewise, PGC_INTERNAL variables are set by special + * mechanisms (if indeed they aren't compile-time constants). So we may + * always skip these. + * + * Role must be handled specially because its current value can be an + * invalid value (for instance, if someone dropped the role since we set + * it). So if we tried to serialize it normally, we might get a failure. + * We skip it here, and use another mechanism to ensure the worker has the + * right value. + * + * For all other GUCs, we skip if the GUC has its compiled-in default + * value (i.e., source == PGC_S_DEFAULT). On the leader side, this means + * we don't send GUCs that have their default values, which typically + * saves lots of work. On the worker side, this means we don't need to + * reset the GUC to default because it already has that value. See + * comments in RestoreGUCState for more info. + */ + return gconf->context == PGC_POSTMASTER || + gconf->context == PGC_INTERNAL || gconf->source == PGC_S_DEFAULT || + strcmp(gconf->name, "role") == 0; +} + +/* + * estimate_variable_size: + * Compute space needed for dumping the given GUC variable. + * + * It's OK to overestimate, but not to underestimate. + */ +static Size +estimate_variable_size(struct config_generic *gconf) +{ + Size size; + Size valsize = 0; + + /* Skippable GUCs consume zero space. */ + if (can_skip_gucvar(gconf)) + return 0; + + /* Name, plus trailing zero byte. */ + size = strlen(gconf->name) + 1; + + /* Get the maximum display length of the GUC value. */ + switch (gconf->vartype) + { + case PGC_BOOL: + { + valsize = 5; /* max(strlen('true'), strlen('false')) */ + } + break; + + case PGC_INT: + { + struct config_int *conf = (struct config_int *) gconf; + + /* + * Instead of getting the exact display length, use max + * length. Also reduce the max length for typical ranges of + * small values. Maximum value is 2147483647, i.e. 10 chars. + * Include one byte for sign. + */ + if (abs(*conf->variable) < 1000) + valsize = 3 + 1; + else + valsize = 10 + 1; + } + break; + + case PGC_REAL: + { + /* + * We are going to print it with %e with REALTYPE_PRECISION + * fractional digits. Account for sign, leading digit, + * decimal point, and exponent with up to 3 digits. E.g. + * -3.99329042340000021e+110 + */ + valsize = 1 + 1 + 1 + REALTYPE_PRECISION + 5; + } + break; + + case PGC_STRING: + { + struct config_string *conf = (struct config_string *) gconf; + + /* + * If the value is NULL, we transmit it as an empty string. + * Although this is not physically the same value, GUC + * generally treats a NULL the same as empty string. + */ + if (*conf->variable) + valsize = strlen(*conf->variable); + else + valsize = 0; + } + break; + + case PGC_ENUM: + { + struct config_enum *conf = (struct config_enum *) gconf; + + valsize = strlen(config_enum_lookup_by_value(conf, *conf->variable)); + } + break; + } + + /* Allow space for terminating zero-byte for value */ + size = add_size(size, valsize + 1); + + if (gconf->sourcefile) + size = add_size(size, strlen(gconf->sourcefile)); + + /* Allow space for terminating zero-byte for sourcefile */ + size = add_size(size, 1); + + /* Include line whenever file is nonempty. */ + if (gconf->sourcefile && gconf->sourcefile[0]) + size = add_size(size, sizeof(gconf->sourceline)); + + size = add_size(size, sizeof(gconf->source)); + size = add_size(size, sizeof(gconf->scontext)); + size = add_size(size, sizeof(gconf->srole)); + + return size; +} + +/* + * EstimateGUCStateSpace: + * Returns the size needed to store the GUC state for the current process + */ +Size +EstimateGUCStateSpace(void) +{ + Size size; + dlist_iter iter; + + /* Add space reqd for saving the data size of the guc state */ + size = sizeof(Size); + + /* + * Add up the space needed for each GUC variable. + * + * We need only process non-default GUCs. + */ + dlist_foreach(iter, &guc_nondef_list) + { + struct config_generic *gconf = dlist_container(struct config_generic, + nondef_link, iter.cur); + + size = add_size(size, estimate_variable_size(gconf)); + } + + return size; +} + +/* + * do_serialize: + * Copies the formatted string into the destination. Moves ahead the + * destination pointer, and decrements the maxbytes by that many bytes. If + * maxbytes is not sufficient to copy the string, error out. + */ +static void +do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) +{ + va_list vargs; + int n; + + if (*maxbytes <= 0) + elog(ERROR, "not enough space to serialize GUC state"); + + va_start(vargs, fmt); + n = vsnprintf(*destptr, *maxbytes, fmt, vargs); + va_end(vargs); + + if (n < 0) + { + /* Shouldn't happen. Better show errno description. */ + elog(ERROR, "vsnprintf failed: %m with format string \"%s\"", fmt); + } + if (n >= *maxbytes) + { + /* This shouldn't happen either, really. */ + elog(ERROR, "not enough space to serialize GUC state"); + } + + /* Shift the destptr ahead of the null terminator */ + *destptr += n + 1; + *maxbytes -= n + 1; +} + +/* Binary copy version of do_serialize() */ +static void +do_serialize_binary(char **destptr, Size *maxbytes, void *val, Size valsize) +{ + if (valsize > *maxbytes) + elog(ERROR, "not enough space to serialize GUC state"); + + memcpy(*destptr, val, valsize); + *destptr += valsize; + *maxbytes -= valsize; +} + +/* + * serialize_variable: + * Dumps name, value and other information of a GUC variable into destptr. + */ +static void +serialize_variable(char **destptr, Size *maxbytes, + struct config_generic *gconf) +{ + /* Ignore skippable GUCs. */ + if (can_skip_gucvar(gconf)) + return; + + do_serialize(destptr, maxbytes, "%s", gconf->name); + + switch (gconf->vartype) + { + case PGC_BOOL: + { + struct config_bool *conf = (struct config_bool *) gconf; + + do_serialize(destptr, maxbytes, + (*conf->variable ? "true" : "false")); + } + break; + + case PGC_INT: + { + struct config_int *conf = (struct config_int *) gconf; + + do_serialize(destptr, maxbytes, "%d", *conf->variable); + } + break; + + case PGC_REAL: + { + struct config_real *conf = (struct config_real *) gconf; + + do_serialize(destptr, maxbytes, "%.*e", + REALTYPE_PRECISION, *conf->variable); + } + break; + + case PGC_STRING: + { + struct config_string *conf = (struct config_string *) gconf; + + /* NULL becomes empty string, see estimate_variable_size() */ + do_serialize(destptr, maxbytes, "%s", + *conf->variable ? *conf->variable : ""); + } + break; + + case PGC_ENUM: + { + struct config_enum *conf = (struct config_enum *) gconf; + + do_serialize(destptr, maxbytes, "%s", + config_enum_lookup_by_value(conf, *conf->variable)); + } + break; + } + + do_serialize(destptr, maxbytes, "%s", + (gconf->sourcefile ? gconf->sourcefile : "")); + + if (gconf->sourcefile && gconf->sourcefile[0]) + do_serialize_binary(destptr, maxbytes, &gconf->sourceline, + sizeof(gconf->sourceline)); + + do_serialize_binary(destptr, maxbytes, &gconf->source, + sizeof(gconf->source)); + do_serialize_binary(destptr, maxbytes, &gconf->scontext, + sizeof(gconf->scontext)); + do_serialize_binary(destptr, maxbytes, &gconf->srole, + sizeof(gconf->srole)); +} + +/* + * SerializeGUCState: + * Dumps the complete GUC state onto the memory location at start_address. + */ +void +SerializeGUCState(Size maxsize, char *start_address) +{ + char *curptr; + Size actual_size; + Size bytes_left; + dlist_iter iter; + + /* Reserve space for saving the actual size of the guc state */ + Assert(maxsize > sizeof(actual_size)); + curptr = start_address + sizeof(actual_size); + bytes_left = maxsize - sizeof(actual_size); + + /* We need only consider GUCs with source not PGC_S_DEFAULT */ + dlist_foreach(iter, &guc_nondef_list) + { + struct config_generic *gconf = dlist_container(struct config_generic, + nondef_link, iter.cur); + + serialize_variable(&curptr, &bytes_left, gconf); + } + + /* Store actual size without assuming alignment of start_address. */ + actual_size = maxsize - bytes_left - sizeof(actual_size); + memcpy(start_address, &actual_size, sizeof(actual_size)); +} + +/* + * read_gucstate: + * Actually it does not read anything, just returns the srcptr. But it does + * move the srcptr past the terminating zero byte, so that the caller is ready + * to read the next string. + */ +static char * +read_gucstate(char **srcptr, char *srcend) +{ + char *retptr = *srcptr; + char *ptr; + + if (*srcptr >= srcend) + elog(ERROR, "incomplete GUC state"); + + /* The string variables are all null terminated */ + for (ptr = *srcptr; ptr < srcend && *ptr != '\0'; ptr++) + ; + + if (ptr >= srcend) + elog(ERROR, "could not find null terminator in GUC state"); + + /* Set the new position to the byte following the terminating NUL */ + *srcptr = ptr + 1; + + return retptr; +} + +/* Binary read version of read_gucstate(). Copies into dest */ +static void +read_gucstate_binary(char **srcptr, char *srcend, void *dest, Size size) +{ + if (*srcptr + size > srcend) + elog(ERROR, "incomplete GUC state"); + + memcpy(dest, *srcptr, size); + *srcptr += size; +} + +/* + * Callback used to add a context message when reporting errors that occur + * while trying to restore GUCs in parallel workers. + */ +static void +guc_restore_error_context_callback(void *arg) +{ + char **error_context_name_and_value = (char **) arg; + + if (error_context_name_and_value) + errcontext("while setting parameter \"%s\" to \"%s\"", + error_context_name_and_value[0], + error_context_name_and_value[1]); +} + +/* + * RestoreGUCState: + * Reads the GUC state at the specified address and sets this process's + * GUCs to match. + * + * Note that this provides the worker with only a very shallow view of the + * leader's GUC state: we'll know about the currently active values, but not + * about stacked or reset values. That's fine since the worker is just + * executing one part of a query, within which the active values won't change + * and the stacked values are invisible. + */ +void +RestoreGUCState(void *gucstate) +{ + char *varname, + *varvalue, + *varsourcefile; + int varsourceline; + GucSource varsource; + GucContext varscontext; + Oid varsrole; + char *srcptr = (char *) gucstate; + char *srcend; + Size len; + dlist_mutable_iter iter; + ErrorContextCallback error_context_callback; + + /* + * First, ensure that all potentially-shippable GUCs are reset to their + * default values. We must not touch those GUCs that the leader will + * never ship, while there is no need to touch those that are shippable + * but already have their default values. Thus, this ends up being the + * same test that SerializeGUCState uses, even though the sets of + * variables involved may well be different since the leader's set of + * variables-not-at-default-values can differ from the set that are + * not-default in this freshly started worker. + * + * Once we have set all the potentially-shippable GUCs to default values, + * restoring the GUCs that the leader sent (because they had non-default + * values over there) leads us to exactly the set of GUC values that the + * leader has. This is true even though the worker may have initially + * absorbed postgresql.conf settings that the leader hasn't yet seen, or + * ALTER USER/DATABASE SET settings that were established after the leader + * started. + * + * Note that ensuring all the potential target GUCs are at PGC_S_DEFAULT + * also ensures that set_config_option won't refuse to set them because of + * source-priority comparisons. + */ + dlist_foreach_modify(iter, &guc_nondef_list) + { + struct config_generic *gconf = dlist_container(struct config_generic, + nondef_link, iter.cur); + + /* Do nothing if non-shippable or if already at PGC_S_DEFAULT. */ + if (can_skip_gucvar(gconf)) + continue; + + /* + * We can use InitializeOneGUCOption to reset the GUC to default, but + * first we must free any existing subsidiary data to avoid leaking + * memory. The stack must be empty, but we have to clean up all other + * fields. Beware that there might be duplicate value or "extra" + * pointers. We also have to be sure to take it out of any lists it's + * in. + */ + Assert(gconf->stack == NULL); + guc_free(gconf->extra); + guc_free(gconf->last_reported); + guc_free(gconf->sourcefile); + switch (gconf->vartype) + { + case PGC_BOOL: + { + struct config_bool *conf = (struct config_bool *) gconf; + + if (conf->reset_extra && conf->reset_extra != gconf->extra) + guc_free(conf->reset_extra); + break; + } + case PGC_INT: + { + struct config_int *conf = (struct config_int *) gconf; + + if (conf->reset_extra && conf->reset_extra != gconf->extra) + guc_free(conf->reset_extra); + break; + } + case PGC_REAL: + { + struct config_real *conf = (struct config_real *) gconf; + + if (conf->reset_extra && conf->reset_extra != gconf->extra) + guc_free(conf->reset_extra); + break; + } + case PGC_STRING: + { + struct config_string *conf = (struct config_string *) gconf; + + guc_free(*conf->variable); + if (conf->reset_val && conf->reset_val != *conf->variable) + guc_free(conf->reset_val); + if (conf->reset_extra && conf->reset_extra != gconf->extra) + guc_free(conf->reset_extra); + break; + } + case PGC_ENUM: + { + struct config_enum *conf = (struct config_enum *) gconf; + + if (conf->reset_extra && conf->reset_extra != gconf->extra) + guc_free(conf->reset_extra); + break; + } + } + /* Remove it from any lists it's in. */ + RemoveGUCFromLists(gconf); + /* Now we can reset the struct to PGS_S_DEFAULT state. */ + InitializeOneGUCOption(gconf); + } + + /* First item is the length of the subsequent data */ + memcpy(&len, gucstate, sizeof(len)); + + srcptr += sizeof(len); + srcend = srcptr + len; + + /* If the GUC value check fails, we want errors to show useful context. */ + error_context_callback.callback = guc_restore_error_context_callback; + error_context_callback.previous = error_context_stack; + error_context_callback.arg = NULL; + error_context_stack = &error_context_callback; + + /* Restore all the listed GUCs. */ + while (srcptr < srcend) + { + int result; + char *error_context_name_and_value[2]; + + varname = read_gucstate(&srcptr, srcend); + varvalue = read_gucstate(&srcptr, srcend); + varsourcefile = read_gucstate(&srcptr, srcend); + if (varsourcefile[0]) + read_gucstate_binary(&srcptr, srcend, + &varsourceline, sizeof(varsourceline)); + else + varsourceline = 0; + read_gucstate_binary(&srcptr, srcend, + &varsource, sizeof(varsource)); + read_gucstate_binary(&srcptr, srcend, + &varscontext, sizeof(varscontext)); + read_gucstate_binary(&srcptr, srcend, + &varsrole, sizeof(varsrole)); + + error_context_name_and_value[0] = varname; + error_context_name_and_value[1] = varvalue; + error_context_callback.arg = &error_context_name_and_value[0]; + result = set_config_option_ext(varname, varvalue, + varscontext, varsource, varsrole, + GUC_ACTION_SET, true, ERROR, true); + if (result <= 0) + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("parameter \"%s\" could not be set", varname))); + if (varsourcefile[0]) + set_config_sourcefile(varname, varsourcefile, varsourceline); + error_context_callback.arg = NULL; + } + + error_context_stack = error_context_callback.previous; +} + +/* + * A little "long argument" simulation, although not quite GNU + * compliant. Takes a string of the form "some-option=some value" and + * returns name = "some_option" and value = "some value" in palloc'ed + * storage. Note that '-' is converted to '_' in the option name. If + * there is no '=' in the input string then value will be NULL. + */ +void +ParseLongOption(const char *string, char **name, char **value) +{ + size_t equal_pos; + char *cp; + + Assert(string); + Assert(name); + Assert(value); + + equal_pos = strcspn(string, "="); + + if (string[equal_pos] == '=') + { + *name = palloc(equal_pos + 1); + strlcpy(*name, string, equal_pos + 1); + + *value = pstrdup(&string[equal_pos + 1]); + } + else + { + /* no equal sign in string */ + *name = pstrdup(string); + *value = NULL; + } + + for (cp = *name; *cp; cp++) + if (*cp == '-') + *cp = '_'; +} + + +/* + * Handle options fetched from pg_db_role_setting.setconfig, + * pg_proc.proconfig, etc. Caller must specify proper context/source/action. + * + * The array parameter must be an array of TEXT (it must not be NULL). + */ +void +ProcessGUCArray(ArrayType *array, + GucContext context, GucSource source, GucAction action) +{ + int i; + + Assert(array != NULL); + Assert(ARR_ELEMTYPE(array) == TEXTOID); + Assert(ARR_NDIM(array) == 1); + Assert(ARR_LBOUND(array)[0] == 1); + + for (i = 1; i <= ARR_DIMS(array)[0]; i++) + { + Datum d; + bool isnull; + char *s; + char *name; + char *value; + + d = array_ref(array, 1, &i, + -1 /* varlenarray */ , + -1 /* TEXT's typlen */ , + false /* TEXT's typbyval */ , + TYPALIGN_INT /* TEXT's typalign */ , + &isnull); + + if (isnull) + continue; + + s = TextDatumGetCString(d); + + ParseLongOption(s, &name, &value); + if (!value) + { + ereport(WARNING, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("could not parse setting for parameter \"%s\"", + name))); + pfree(name); + continue; + } + + (void) set_config_option(name, value, + context, source, + action, true, 0, false); + + pfree(name); + pfree(value); + pfree(s); + } +} + + +/* + * Add an entry to an option array. The array parameter may be NULL + * to indicate the current table entry is NULL. + */ +ArrayType * +GUCArrayAdd(ArrayType *array, const char *name, const char *value) +{ + struct config_generic *record; + Datum datum; + char *newval; + ArrayType *a; + + Assert(name); + Assert(value); + + /* test if the option is valid and we're allowed to set it */ + (void) validate_option_array_item(name, value, false); + + /* normalize name (converts obsolete GUC names to modern spellings) */ + record = find_option(name, false, true, WARNING); + if (record) + name = record->name; + + /* build new item for array */ + newval = psprintf("%s=%s", name, value); + datum = CStringGetTextDatum(newval); + + if (array) + { + int index; + bool isnull; + int i; + + Assert(ARR_ELEMTYPE(array) == TEXTOID); + Assert(ARR_NDIM(array) == 1); + Assert(ARR_LBOUND(array)[0] == 1); + + index = ARR_DIMS(array)[0] + 1; /* add after end */ + + for (i = 1; i <= ARR_DIMS(array)[0]; i++) + { + Datum d; + char *current; + + d = array_ref(array, 1, &i, + -1 /* varlenarray */ , + -1 /* TEXT's typlen */ , + false /* TEXT's typbyval */ , + TYPALIGN_INT /* TEXT's typalign */ , + &isnull); + if (isnull) + continue; + current = TextDatumGetCString(d); + + /* check for match up through and including '=' */ + if (strncmp(current, newval, strlen(name) + 1) == 0) + { + index = i; + break; + } + } + + a = array_set(array, 1, &index, + datum, + false, + -1 /* varlena array */ , + -1 /* TEXT's typlen */ , + false /* TEXT's typbyval */ , + TYPALIGN_INT /* TEXT's typalign */ ); + } + else + a = construct_array_builtin(&datum, 1, TEXTOID); + + return a; +} + + +/* + * Delete an entry from an option array. The array parameter may be NULL + * to indicate the current table entry is NULL. Also, if the return value + * is NULL then a null should be stored. + */ +ArrayType * +GUCArrayDelete(ArrayType *array, const char *name) +{ + struct config_generic *record; + ArrayType *newarray; + int i; + int index; + + Assert(name); + + /* test if the option is valid and we're allowed to set it */ + (void) validate_option_array_item(name, NULL, false); + + /* normalize name (converts obsolete GUC names to modern spellings) */ + record = find_option(name, false, true, WARNING); + if (record) + name = record->name; + + /* if array is currently null, then surely nothing to delete */ + if (!array) + return NULL; + + newarray = NULL; + index = 1; + + for (i = 1; i <= ARR_DIMS(array)[0]; i++) + { + Datum d; + char *val; + bool isnull; + + d = array_ref(array, 1, &i, + -1 /* varlenarray */ , + -1 /* TEXT's typlen */ , + false /* TEXT's typbyval */ , + TYPALIGN_INT /* TEXT's typalign */ , + &isnull); + if (isnull) + continue; + val = TextDatumGetCString(d); + + /* ignore entry if it's what we want to delete */ + if (strncmp(val, name, strlen(name)) == 0 + && val[strlen(name)] == '=') + continue; + + /* else add it to the output array */ + if (newarray) + newarray = array_set(newarray, 1, &index, + d, + false, + -1 /* varlenarray */ , + -1 /* TEXT's typlen */ , + false /* TEXT's typbyval */ , + TYPALIGN_INT /* TEXT's typalign */ ); + else + newarray = construct_array_builtin(&d, 1, TEXTOID); + + index++; + } + + return newarray; +} + + +/* + * Given a GUC array, delete all settings from it that our permission + * level allows: if superuser, delete them all; if regular user, only + * those that are PGC_USERSET or we have permission to set + */ +ArrayType * +GUCArrayReset(ArrayType *array) +{ + ArrayType *newarray; + int i; + int index; + + /* if array is currently null, nothing to do */ + if (!array) + return NULL; + + /* if we're superuser, we can delete everything, so just do it */ + if (superuser()) + return NULL; + + newarray = NULL; + index = 1; + + for (i = 1; i <= ARR_DIMS(array)[0]; i++) + { + Datum d; + char *val; + char *eqsgn; + bool isnull; + + d = array_ref(array, 1, &i, + -1 /* varlenarray */ , + -1 /* TEXT's typlen */ , + false /* TEXT's typbyval */ , + TYPALIGN_INT /* TEXT's typalign */ , + &isnull); + if (isnull) + continue; + val = TextDatumGetCString(d); + + eqsgn = strchr(val, '='); + *eqsgn = '\0'; + + /* skip if we have permission to delete it */ + if (validate_option_array_item(val, NULL, true)) + continue; + + /* else add it to the output array */ + if (newarray) + newarray = array_set(newarray, 1, &index, + d, + false, + -1 /* varlenarray */ , + -1 /* TEXT's typlen */ , + false /* TEXT's typbyval */ , + TYPALIGN_INT /* TEXT's typalign */ ); + else + newarray = construct_array_builtin(&d, 1, TEXTOID); + + index++; + pfree(val); + } + + return newarray; +} + +/* + * Validate a proposed option setting for GUCArrayAdd/Delete/Reset. + * + * name is the option name. value is the proposed value for the Add case, + * or NULL for the Delete/Reset cases. If skipIfNoPermissions is true, it's + * not an error to have no permissions to set the option. + * + * Returns true if OK, false if skipIfNoPermissions is true and user does not + * have permission to change this option (all other error cases result in an + * error being thrown). + */ +static bool +validate_option_array_item(const char *name, const char *value, + bool skipIfNoPermissions) + +{ + struct config_generic *gconf; + + /* + * There are three cases to consider: + * + * name is a known GUC variable. Check the value normally, check + * permissions normally (i.e., allow if variable is USERSET, or if it's + * SUSET and user is superuser or holds ACL_SET permissions). + * + * name is not known, but exists or can be created as a placeholder (i.e., + * it has a valid custom name). We allow this case if you're a superuser, + * otherwise not. Superusers are assumed to know what they're doing. We + * can't allow it for other users, because when the placeholder is + * resolved it might turn out to be a SUSET variable. (With currently + * available infrastructure, we can actually handle such cases within the + * current session --- but once an entry is made in pg_db_role_setting, + * it's assumed to be fully validated.) + * + * name is not known and can't be created as a placeholder. Throw error, + * unless skipIfNoPermissions is true, in which case return false. + */ + gconf = find_option(name, true, skipIfNoPermissions, ERROR); + if (!gconf) + { + /* not known, failed to make a placeholder */ + return false; + } + + if (gconf->flags & GUC_CUSTOM_PLACEHOLDER) + { + /* + * We cannot do any meaningful check on the value, so only permissions + * are useful to check. + */ + if (superuser() || + pg_parameter_aclcheck(name, GetUserId(), ACL_SET) == ACLCHECK_OK) + return true; + if (skipIfNoPermissions) + return false; + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied to set parameter \"%s\"", name))); + } + + /* manual permissions check so we can avoid an error being thrown */ + if (gconf->context == PGC_USERSET) + /* ok */ ; + else if (gconf->context == PGC_SUSET && + (superuser() || + pg_parameter_aclcheck(name, GetUserId(), ACL_SET) == ACLCHECK_OK)) + /* ok */ ; + else if (skipIfNoPermissions) + return false; + /* if a permissions error should be thrown, let set_config_option do it */ + + /* test for permissions and valid option value */ + (void) set_config_option(name, value, + superuser() ? PGC_SUSET : PGC_USERSET, + PGC_S_TEST, GUC_ACTION_SET, false, 0, false); + + return true; +} + + +/* + * Called by check_hooks that want to override the normal + * ERRCODE_INVALID_PARAMETER_VALUE SQLSTATE for check hook failures. + * + * Note that GUC_check_errmsg() etc are just macros that result in a direct + * assignment to the associated variables. That is ugly, but forced by the + * limitations of C's macro mechanisms. + */ +void +GUC_check_errcode(int sqlerrcode) +{ + GUC_check_errcode_value = sqlerrcode; +} + + +/* + * Convenience functions to manage calling a variable's check_hook. + * These mostly take care of the protocol for letting check hooks supply + * portions of the error report on failure. + */ + +static bool +call_bool_check_hook(struct config_bool *conf, bool *newval, void **extra, + GucSource source, int elevel) +{ + /* Quick success if no hook */ + if (!conf->check_hook) + return true; + + /* Reset variables that might be set by hook */ + GUC_check_errcode_value = ERRCODE_INVALID_PARAMETER_VALUE; + GUC_check_errmsg_string = NULL; + GUC_check_errdetail_string = NULL; + GUC_check_errhint_string = NULL; + + if (!conf->check_hook(newval, extra, source)) + { + ereport(elevel, + (errcode(GUC_check_errcode_value), + GUC_check_errmsg_string ? + errmsg_internal("%s", GUC_check_errmsg_string) : + errmsg("invalid value for parameter \"%s\": %d", + conf->gen.name, (int) *newval), + GUC_check_errdetail_string ? + errdetail_internal("%s", GUC_check_errdetail_string) : 0, + GUC_check_errhint_string ? + errhint("%s", GUC_check_errhint_string) : 0)); + /* Flush any strings created in ErrorContext */ + FlushErrorState(); + return false; + } + + return true; +} + +static bool +call_int_check_hook(struct config_int *conf, int *newval, void **extra, + GucSource source, int elevel) +{ + /* Quick success if no hook */ + if (!conf->check_hook) + return true; + + /* Reset variables that might be set by hook */ + GUC_check_errcode_value = ERRCODE_INVALID_PARAMETER_VALUE; + GUC_check_errmsg_string = NULL; + GUC_check_errdetail_string = NULL; + GUC_check_errhint_string = NULL; + + if (!conf->check_hook(newval, extra, source)) + { + ereport(elevel, + (errcode(GUC_check_errcode_value), + GUC_check_errmsg_string ? + errmsg_internal("%s", GUC_check_errmsg_string) : + errmsg("invalid value for parameter \"%s\": %d", + conf->gen.name, *newval), + GUC_check_errdetail_string ? + errdetail_internal("%s", GUC_check_errdetail_string) : 0, + GUC_check_errhint_string ? + errhint("%s", GUC_check_errhint_string) : 0)); + /* Flush any strings created in ErrorContext */ + FlushErrorState(); + return false; + } + + return true; +} + +static bool +call_real_check_hook(struct config_real *conf, double *newval, void **extra, + GucSource source, int elevel) +{ + /* Quick success if no hook */ + if (!conf->check_hook) + return true; + + /* Reset variables that might be set by hook */ + GUC_check_errcode_value = ERRCODE_INVALID_PARAMETER_VALUE; + GUC_check_errmsg_string = NULL; + GUC_check_errdetail_string = NULL; + GUC_check_errhint_string = NULL; + + if (!conf->check_hook(newval, extra, source)) + { + ereport(elevel, + (errcode(GUC_check_errcode_value), + GUC_check_errmsg_string ? + errmsg_internal("%s", GUC_check_errmsg_string) : + errmsg("invalid value for parameter \"%s\": %g", + conf->gen.name, *newval), + GUC_check_errdetail_string ? + errdetail_internal("%s", GUC_check_errdetail_string) : 0, + GUC_check_errhint_string ? + errhint("%s", GUC_check_errhint_string) : 0)); + /* Flush any strings created in ErrorContext */ + FlushErrorState(); + return false; + } + + return true; +} + +static bool +call_string_check_hook(struct config_string *conf, char **newval, void **extra, + GucSource source, int elevel) +{ + volatile bool result = true; + + /* Quick success if no hook */ + if (!conf->check_hook) + return true; + + /* + * If elevel is ERROR, or if the check_hook itself throws an elog + * (undesirable, but not always avoidable), make sure we don't leak the + * already-malloc'd newval string. + */ + PG_TRY(); + { + /* Reset variables that might be set by hook */ + GUC_check_errcode_value = ERRCODE_INVALID_PARAMETER_VALUE; + GUC_check_errmsg_string = NULL; + GUC_check_errdetail_string = NULL; + GUC_check_errhint_string = NULL; + + if (!conf->check_hook(newval, extra, source)) + { + ereport(elevel, + (errcode(GUC_check_errcode_value), + GUC_check_errmsg_string ? + errmsg_internal("%s", GUC_check_errmsg_string) : + errmsg("invalid value for parameter \"%s\": \"%s\"", + conf->gen.name, *newval ? *newval : ""), + GUC_check_errdetail_string ? + errdetail_internal("%s", GUC_check_errdetail_string) : 0, + GUC_check_errhint_string ? + errhint("%s", GUC_check_errhint_string) : 0)); + /* Flush any strings created in ErrorContext */ + FlushErrorState(); + result = false; + } + } + PG_CATCH(); + { + guc_free(*newval); + PG_RE_THROW(); + } + PG_END_TRY(); + + return result; +} + +static bool +call_enum_check_hook(struct config_enum *conf, int *newval, void **extra, + GucSource source, int elevel) +{ + /* Quick success if no hook */ + if (!conf->check_hook) + return true; + + /* Reset variables that might be set by hook */ + GUC_check_errcode_value = ERRCODE_INVALID_PARAMETER_VALUE; + GUC_check_errmsg_string = NULL; + GUC_check_errdetail_string = NULL; + GUC_check_errhint_string = NULL; + + if (!conf->check_hook(newval, extra, source)) + { + ereport(elevel, + (errcode(GUC_check_errcode_value), + GUC_check_errmsg_string ? + errmsg_internal("%s", GUC_check_errmsg_string) : + errmsg("invalid value for parameter \"%s\": \"%s\"", + conf->gen.name, + config_enum_lookup_by_value(conf, *newval)), + GUC_check_errdetail_string ? + errdetail_internal("%s", GUC_check_errdetail_string) : 0, + GUC_check_errhint_string ? + errhint("%s", GUC_check_errhint_string) : 0)); + /* Flush any strings created in ErrorContext */ + FlushErrorState(); + return false; + } + + return true; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/misc/guc_funcs.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/misc/guc_funcs.c new file mode 100644 index 00000000000..da67f37aacd --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/misc/guc_funcs.c @@ -0,0 +1,1048 @@ +/*-------------------------------------------------------------------- + * + * guc_funcs.c + * + * SQL commands and SQL-accessible functions related to GUC variables. + * + * + * Copyright (c) 2000-2023, PostgreSQL Global Development Group + * Written by Peter Eisentraut <peter_e@gmx.net>. + * + * IDENTIFICATION + * src/backend/utils/misc/guc_funcs.c + * + *-------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <sys/stat.h> +#include <unistd.h> + +#include "access/xact.h" +#include "catalog/objectaccess.h" +#include "catalog/pg_authid.h" +#include "catalog/pg_parameter_acl.h" +#include "funcapi.h" +#include "guc_internal.h" +#include "parser/parse_type.h" +#include "utils/acl.h" +#include "utils/backend_status.h" +#include "utils/builtins.h" +#include "utils/guc_tables.h" +#include "utils/snapmgr.h" + +static char *flatten_set_variable_args(const char *name, List *args); +static void ShowGUCConfigOption(const char *name, DestReceiver *dest); +static void ShowAllGUCConfig(DestReceiver *dest); + + +/* + * SET command + */ +void +ExecSetVariableStmt(VariableSetStmt *stmt, bool isTopLevel) +{ + GucAction action = stmt->is_local ? GUC_ACTION_LOCAL : GUC_ACTION_SET; + + /* + * Workers synchronize these parameters at the start of the parallel + * operation; then, we block SET during the operation. + */ + if (IsInParallelMode()) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TRANSACTION_STATE), + errmsg("cannot set parameters during a parallel operation"))); + + switch (stmt->kind) + { + case VAR_SET_VALUE: + case VAR_SET_CURRENT: + if (stmt->is_local) + WarnNoTransactionBlock(isTopLevel, "SET LOCAL"); + (void) set_config_option(stmt->name, + ExtractSetVariableArgs(stmt), + (superuser() ? PGC_SUSET : PGC_USERSET), + PGC_S_SESSION, + action, true, 0, false); + break; + case VAR_SET_MULTI: + + /* + * Special-case SQL syntaxes. The TRANSACTION and SESSION + * CHARACTERISTICS cases effectively set more than one variable + * per statement. TRANSACTION SNAPSHOT only takes one argument, + * but we put it here anyway since it's a special case and not + * related to any GUC variable. + */ + if (strcmp(stmt->name, "TRANSACTION") == 0) + { + ListCell *head; + + WarnNoTransactionBlock(isTopLevel, "SET TRANSACTION"); + + foreach(head, stmt->args) + { + DefElem *item = (DefElem *) lfirst(head); + + if (strcmp(item->defname, "transaction_isolation") == 0) + SetPGVariable("transaction_isolation", + list_make1(item->arg), stmt->is_local); + else if (strcmp(item->defname, "transaction_read_only") == 0) + SetPGVariable("transaction_read_only", + list_make1(item->arg), stmt->is_local); + else if (strcmp(item->defname, "transaction_deferrable") == 0) + SetPGVariable("transaction_deferrable", + list_make1(item->arg), stmt->is_local); + else + elog(ERROR, "unexpected SET TRANSACTION element: %s", + item->defname); + } + } + else if (strcmp(stmt->name, "SESSION CHARACTERISTICS") == 0) + { + ListCell *head; + + foreach(head, stmt->args) + { + DefElem *item = (DefElem *) lfirst(head); + + if (strcmp(item->defname, "transaction_isolation") == 0) + SetPGVariable("default_transaction_isolation", + list_make1(item->arg), stmt->is_local); + else if (strcmp(item->defname, "transaction_read_only") == 0) + SetPGVariable("default_transaction_read_only", + list_make1(item->arg), stmt->is_local); + else if (strcmp(item->defname, "transaction_deferrable") == 0) + SetPGVariable("default_transaction_deferrable", + list_make1(item->arg), stmt->is_local); + else + elog(ERROR, "unexpected SET SESSION element: %s", + item->defname); + } + } + else if (strcmp(stmt->name, "TRANSACTION SNAPSHOT") == 0) + { + A_Const *con = linitial_node(A_Const, stmt->args); + + if (stmt->is_local) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("SET LOCAL TRANSACTION SNAPSHOT is not implemented"))); + + WarnNoTransactionBlock(isTopLevel, "SET TRANSACTION"); + ImportSnapshot(strVal(&con->val)); + } + else + elog(ERROR, "unexpected SET MULTI element: %s", + stmt->name); + break; + case VAR_SET_DEFAULT: + if (stmt->is_local) + WarnNoTransactionBlock(isTopLevel, "SET LOCAL"); + /* fall through */ + case VAR_RESET: + (void) set_config_option(stmt->name, + NULL, + (superuser() ? PGC_SUSET : PGC_USERSET), + PGC_S_SESSION, + action, true, 0, false); + break; + case VAR_RESET_ALL: + ResetAllOptions(); + break; + } + + /* Invoke the post-alter hook for setting this GUC variable, by name. */ + InvokeObjectPostAlterHookArgStr(ParameterAclRelationId, stmt->name, + ACL_SET, stmt->kind, false); +} + +/* + * Get the value to assign for a VariableSetStmt, or NULL if it's RESET. + * The result is palloc'd. + * + * This is exported for use by actions such as ALTER ROLE SET. + */ +char * +ExtractSetVariableArgs(VariableSetStmt *stmt) +{ + switch (stmt->kind) + { + case VAR_SET_VALUE: + return flatten_set_variable_args(stmt->name, stmt->args); + case VAR_SET_CURRENT: + return GetConfigOptionByName(stmt->name, NULL, false); + default: + return NULL; + } +} + +/* + * flatten_set_variable_args + * Given a parsenode List as emitted by the grammar for SET, + * convert to the flat string representation used by GUC. + * + * We need to be told the name of the variable the args are for, because + * the flattening rules vary (ugh). + * + * The result is NULL if args is NIL (i.e., SET ... TO DEFAULT), otherwise + * a palloc'd string. + */ +static char * +flatten_set_variable_args(const char *name, List *args) +{ + struct config_generic *record; + int flags; + StringInfoData buf; + ListCell *l; + + /* Fast path if just DEFAULT */ + if (args == NIL) + return NULL; + + /* + * Get flags for the variable; if it's not known, use default flags. + * (Caller might throw error later, but not our business to do so here.) + */ + record = find_option(name, false, true, WARNING); + if (record) + flags = record->flags; + else + flags = 0; + + /* Complain if list input and non-list variable */ + if ((flags & GUC_LIST_INPUT) == 0 && + list_length(args) != 1) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("SET %s takes only one argument", name))); + + initStringInfo(&buf); + + /* + * Each list member may be a plain A_Const node, or an A_Const within a + * TypeCast; the latter case is supported only for ConstInterval arguments + * (for SET TIME ZONE). + */ + foreach(l, args) + { + Node *arg = (Node *) lfirst(l); + char *val; + TypeName *typeName = NULL; + A_Const *con; + + if (l != list_head(args)) + appendStringInfoString(&buf, ", "); + + if (IsA(arg, TypeCast)) + { + TypeCast *tc = (TypeCast *) arg; + + arg = tc->arg; + typeName = tc->typeName; + } + + if (!IsA(arg, A_Const)) + elog(ERROR, "unrecognized node type: %d", (int) nodeTag(arg)); + con = (A_Const *) arg; + + switch (nodeTag(&con->val)) + { + case T_Integer: + appendStringInfo(&buf, "%d", intVal(&con->val)); + break; + case T_Float: + /* represented as a string, so just copy it */ + appendStringInfoString(&buf, castNode(Float, &con->val)->fval); + break; + case T_String: + val = strVal(&con->val); + if (typeName != NULL) + { + /* + * Must be a ConstInterval argument for TIME ZONE. Coerce + * to interval and back to normalize the value and account + * for any typmod. + */ + Oid typoid; + int32 typmod; + Datum interval; + char *intervalout; + + typenameTypeIdAndMod(NULL, typeName, &typoid, &typmod); + Assert(typoid == INTERVALOID); + + interval = + DirectFunctionCall3(interval_in, + CStringGetDatum(val), + ObjectIdGetDatum(InvalidOid), + Int32GetDatum(typmod)); + + intervalout = + DatumGetCString(DirectFunctionCall1(interval_out, + interval)); + appendStringInfo(&buf, "INTERVAL '%s'", intervalout); + } + else + { + /* + * Plain string literal or identifier. For quote mode, + * quote it if it's not a vanilla identifier. + */ + if (flags & GUC_LIST_QUOTE) + appendStringInfoString(&buf, quote_identifier(val)); + else + appendStringInfoString(&buf, val); + } + break; + default: + elog(ERROR, "unrecognized node type: %d", + (int) nodeTag(&con->val)); + break; + } + } + + return buf.data; +} + +/* + * SetPGVariable - SET command exported as an easily-C-callable function. + * + * This provides access to SET TO value, as well as SET TO DEFAULT (expressed + * by passing args == NIL), but not SET FROM CURRENT functionality. + */ +void +SetPGVariable(const char *name, List *args, bool is_local) +{ + char *argstring = flatten_set_variable_args(name, args); + + /* Note SET DEFAULT (argstring == NULL) is equivalent to RESET */ + (void) set_config_option(name, + argstring, + (superuser() ? PGC_SUSET : PGC_USERSET), + PGC_S_SESSION, + is_local ? GUC_ACTION_LOCAL : GUC_ACTION_SET, + true, 0, false); +} + +/* + * SET command wrapped as a SQL callable function. + */ +Datum +set_config_by_name(PG_FUNCTION_ARGS) +{ + char *name; + char *value; + char *new_value; + bool is_local; + + if (PG_ARGISNULL(0)) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("SET requires parameter name"))); + + /* Get the GUC variable name */ + name = TextDatumGetCString(PG_GETARG_DATUM(0)); + + /* Get the desired value or set to NULL for a reset request */ + if (PG_ARGISNULL(1)) + value = NULL; + else + value = TextDatumGetCString(PG_GETARG_DATUM(1)); + + /* + * Get the desired state of is_local. Default to false if provided value + * is NULL + */ + if (PG_ARGISNULL(2)) + is_local = false; + else + is_local = PG_GETARG_BOOL(2); + + /* Note SET DEFAULT (argstring == NULL) is equivalent to RESET */ + (void) set_config_option(name, + value, + (superuser() ? PGC_SUSET : PGC_USERSET), + PGC_S_SESSION, + is_local ? GUC_ACTION_LOCAL : GUC_ACTION_SET, + true, 0, false); + + /* get the new current value */ + new_value = GetConfigOptionByName(name, NULL, false); + + /* Convert return string to text */ + PG_RETURN_TEXT_P(cstring_to_text(new_value)); +} + + +/* + * SHOW command + */ +void +GetPGVariable(const char *name, DestReceiver *dest) +{ + if (guc_name_compare(name, "all") == 0) + ShowAllGUCConfig(dest); + else + ShowGUCConfigOption(name, dest); +} + +/* + * Get a tuple descriptor for SHOW's result + */ +TupleDesc +GetPGVariableResultDesc(const char *name) +{ + TupleDesc tupdesc; + + if (guc_name_compare(name, "all") == 0) + { + /* need a tuple descriptor representing three TEXT columns */ + tupdesc = CreateTemplateTupleDesc(3); + TupleDescInitEntry(tupdesc, (AttrNumber) 1, "name", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 2, "setting", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 3, "description", + TEXTOID, -1, 0); + } + else + { + const char *varname; + + /* Get the canonical spelling of name */ + (void) GetConfigOptionByName(name, &varname, false); + + /* need a tuple descriptor representing a single TEXT column */ + tupdesc = CreateTemplateTupleDesc(1); + TupleDescInitEntry(tupdesc, (AttrNumber) 1, varname, + TEXTOID, -1, 0); + } + return tupdesc; +} + +/* + * SHOW one variable + */ +static void +ShowGUCConfigOption(const char *name, DestReceiver *dest) +{ + TupOutputState *tstate; + TupleDesc tupdesc; + const char *varname; + char *value; + + /* Get the value and canonical spelling of name */ + value = GetConfigOptionByName(name, &varname, false); + + /* need a tuple descriptor representing a single TEXT column */ + tupdesc = CreateTemplateTupleDesc(1); + TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 1, varname, + TEXTOID, -1, 0); + + /* prepare for projection of tuples */ + tstate = begin_tup_output_tupdesc(dest, tupdesc, &TTSOpsVirtual); + + /* Send it */ + do_text_output_oneline(tstate, value); + + end_tup_output(tstate); +} + +/* + * SHOW ALL command + */ +static void +ShowAllGUCConfig(DestReceiver *dest) +{ + struct config_generic **guc_vars; + int num_vars; + TupOutputState *tstate; + TupleDesc tupdesc; + Datum values[3]; + bool isnull[3] = {false, false, false}; + + /* collect the variables, in sorted order */ + guc_vars = get_guc_variables(&num_vars); + + /* need a tuple descriptor representing three TEXT columns */ + tupdesc = CreateTemplateTupleDesc(3); + TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 1, "name", + TEXTOID, -1, 0); + TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 2, "setting", + TEXTOID, -1, 0); + TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 3, "description", + TEXTOID, -1, 0); + + /* prepare for projection of tuples */ + tstate = begin_tup_output_tupdesc(dest, tupdesc, &TTSOpsVirtual); + + for (int i = 0; i < num_vars; i++) + { + struct config_generic *conf = guc_vars[i]; + char *setting; + + /* skip if marked NO_SHOW_ALL */ + if (conf->flags & GUC_NO_SHOW_ALL) + continue; + + /* return only options visible to the current user */ + if (!ConfigOptionIsVisible(conf)) + continue; + + /* assign to the values array */ + values[0] = PointerGetDatum(cstring_to_text(conf->name)); + + setting = ShowGUCOption(conf, true); + if (setting) + { + values[1] = PointerGetDatum(cstring_to_text(setting)); + isnull[1] = false; + } + else + { + values[1] = PointerGetDatum(NULL); + isnull[1] = true; + } + + if (conf->short_desc) + { + values[2] = PointerGetDatum(cstring_to_text(conf->short_desc)); + isnull[2] = false; + } + else + { + values[2] = PointerGetDatum(NULL); + isnull[2] = true; + } + + /* send it to dest */ + do_tup_output(tstate, values, isnull); + + /* clean up */ + pfree(DatumGetPointer(values[0])); + if (setting) + { + pfree(setting); + pfree(DatumGetPointer(values[1])); + } + if (conf->short_desc) + pfree(DatumGetPointer(values[2])); + } + + end_tup_output(tstate); +} + +/* + * Return some of the flags associated to the specified GUC in the shape of + * a text array, and NULL if it does not exist. An empty array is returned + * if the GUC exists without any meaningful flags to show. + */ +Datum +pg_settings_get_flags(PG_FUNCTION_ARGS) +{ +#define MAX_GUC_FLAGS 6 + char *varname = TextDatumGetCString(PG_GETARG_DATUM(0)); + struct config_generic *record; + int cnt = 0; + Datum flags[MAX_GUC_FLAGS]; + ArrayType *a; + + record = find_option(varname, false, true, ERROR); + + /* return NULL if no such variable */ + if (record == NULL) + PG_RETURN_NULL(); + + if (record->flags & GUC_EXPLAIN) + flags[cnt++] = CStringGetTextDatum("EXPLAIN"); + if (record->flags & GUC_NO_RESET) + flags[cnt++] = CStringGetTextDatum("NO_RESET"); + if (record->flags & GUC_NO_RESET_ALL) + flags[cnt++] = CStringGetTextDatum("NO_RESET_ALL"); + if (record->flags & GUC_NO_SHOW_ALL) + flags[cnt++] = CStringGetTextDatum("NO_SHOW_ALL"); + if (record->flags & GUC_NOT_IN_SAMPLE) + flags[cnt++] = CStringGetTextDatum("NOT_IN_SAMPLE"); + if (record->flags & GUC_RUNTIME_COMPUTED) + flags[cnt++] = CStringGetTextDatum("RUNTIME_COMPUTED"); + + Assert(cnt <= MAX_GUC_FLAGS); + + /* Returns the record as Datum */ + a = construct_array_builtin(flags, cnt, TEXTOID); + PG_RETURN_ARRAYTYPE_P(a); +} + +/* + * Return whether or not the GUC variable is visible to the current user. + */ +bool +ConfigOptionIsVisible(struct config_generic *conf) +{ + if ((conf->flags & GUC_SUPERUSER_ONLY) && + !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS)) + return false; + else + return true; +} + +/* + * Extract fields to show in pg_settings for given variable. + */ +static void +GetConfigOptionValues(struct config_generic *conf, const char **values) +{ + char buffer[256]; + + /* first get the generic attributes */ + + /* name */ + values[0] = conf->name; + + /* setting: use ShowGUCOption in order to avoid duplicating the logic */ + values[1] = ShowGUCOption(conf, false); + + /* unit, if any (NULL is fine) */ + values[2] = get_config_unit_name(conf->flags); + + /* group */ + values[3] = _(config_group_names[conf->group]); + + /* short_desc */ + values[4] = conf->short_desc != NULL ? _(conf->short_desc) : NULL; + + /* extra_desc */ + values[5] = conf->long_desc != NULL ? _(conf->long_desc) : NULL; + + /* context */ + values[6] = GucContext_Names[conf->context]; + + /* vartype */ + values[7] = config_type_names[conf->vartype]; + + /* source */ + values[8] = GucSource_Names[conf->source]; + + /* now get the type specific attributes */ + switch (conf->vartype) + { + case PGC_BOOL: + { + struct config_bool *lconf = (struct config_bool *) conf; + + /* min_val */ + values[9] = NULL; + + /* max_val */ + values[10] = NULL; + + /* enumvals */ + values[11] = NULL; + + /* boot_val */ + values[12] = pstrdup(lconf->boot_val ? "on" : "off"); + + /* reset_val */ + values[13] = pstrdup(lconf->reset_val ? "on" : "off"); + } + break; + + case PGC_INT: + { + struct config_int *lconf = (struct config_int *) conf; + + /* min_val */ + snprintf(buffer, sizeof(buffer), "%d", lconf->min); + values[9] = pstrdup(buffer); + + /* max_val */ + snprintf(buffer, sizeof(buffer), "%d", lconf->max); + values[10] = pstrdup(buffer); + + /* enumvals */ + values[11] = NULL; + + /* boot_val */ + snprintf(buffer, sizeof(buffer), "%d", lconf->boot_val); + values[12] = pstrdup(buffer); + + /* reset_val */ + snprintf(buffer, sizeof(buffer), "%d", lconf->reset_val); + values[13] = pstrdup(buffer); + } + break; + + case PGC_REAL: + { + struct config_real *lconf = (struct config_real *) conf; + + /* min_val */ + snprintf(buffer, sizeof(buffer), "%g", lconf->min); + values[9] = pstrdup(buffer); + + /* max_val */ + snprintf(buffer, sizeof(buffer), "%g", lconf->max); + values[10] = pstrdup(buffer); + + /* enumvals */ + values[11] = NULL; + + /* boot_val */ + snprintf(buffer, sizeof(buffer), "%g", lconf->boot_val); + values[12] = pstrdup(buffer); + + /* reset_val */ + snprintf(buffer, sizeof(buffer), "%g", lconf->reset_val); + values[13] = pstrdup(buffer); + } + break; + + case PGC_STRING: + { + struct config_string *lconf = (struct config_string *) conf; + + /* min_val */ + values[9] = NULL; + + /* max_val */ + values[10] = NULL; + + /* enumvals */ + values[11] = NULL; + + /* boot_val */ + if (lconf->boot_val == NULL) + values[12] = NULL; + else + values[12] = pstrdup(lconf->boot_val); + + /* reset_val */ + if (lconf->reset_val == NULL) + values[13] = NULL; + else + values[13] = pstrdup(lconf->reset_val); + } + break; + + case PGC_ENUM: + { + struct config_enum *lconf = (struct config_enum *) conf; + + /* min_val */ + values[9] = NULL; + + /* max_val */ + values[10] = NULL; + + /* enumvals */ + + /* + * NOTE! enumvals with double quotes in them are not + * supported! + */ + values[11] = config_enum_get_options((struct config_enum *) conf, + "{\"", "\"}", "\",\""); + + /* boot_val */ + values[12] = pstrdup(config_enum_lookup_by_value(lconf, + lconf->boot_val)); + + /* reset_val */ + values[13] = pstrdup(config_enum_lookup_by_value(lconf, + lconf->reset_val)); + } + break; + + default: + { + /* + * should never get here, but in case we do, set 'em to NULL + */ + + /* min_val */ + values[9] = NULL; + + /* max_val */ + values[10] = NULL; + + /* enumvals */ + values[11] = NULL; + + /* boot_val */ + values[12] = NULL; + + /* reset_val */ + values[13] = NULL; + } + break; + } + + /* + * If the setting came from a config file, set the source location. For + * security reasons, we don't show source file/line number for + * insufficiently-privileged users. + */ + if (conf->source == PGC_S_FILE && + has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS)) + { + values[14] = conf->sourcefile; + snprintf(buffer, sizeof(buffer), "%d", conf->sourceline); + values[15] = pstrdup(buffer); + } + else + { + values[14] = NULL; + values[15] = NULL; + } + + values[16] = (conf->status & GUC_PENDING_RESTART) ? "t" : "f"; +} + +/* + * show_config_by_name - equiv to SHOW X command but implemented as + * a function. + */ +Datum +show_config_by_name(PG_FUNCTION_ARGS) +{ + char *varname = TextDatumGetCString(PG_GETARG_DATUM(0)); + char *varval; + + /* Get the value */ + varval = GetConfigOptionByName(varname, NULL, false); + + /* Convert to text */ + PG_RETURN_TEXT_P(cstring_to_text(varval)); +} + +/* + * show_config_by_name_missing_ok - equiv to SHOW X command but implemented as + * a function. If X does not exist, suppress the error and just return NULL + * if missing_ok is true. + */ +Datum +show_config_by_name_missing_ok(PG_FUNCTION_ARGS) +{ + char *varname = TextDatumGetCString(PG_GETARG_DATUM(0)); + bool missing_ok = PG_GETARG_BOOL(1); + char *varval; + + /* Get the value */ + varval = GetConfigOptionByName(varname, NULL, missing_ok); + + /* return NULL if no such variable */ + if (varval == NULL) + PG_RETURN_NULL(); + + /* Convert to text */ + PG_RETURN_TEXT_P(cstring_to_text(varval)); +} + +/* + * show_all_settings - equiv to SHOW ALL command but implemented as + * a Table Function. + */ +#define NUM_PG_SETTINGS_ATTS 17 + +Datum +show_all_settings(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + struct config_generic **guc_vars; + int num_vars; + TupleDesc tupdesc; + int call_cntr; + int max_calls; + AttInMetadata *attinmeta; + MemoryContext oldcontext; + + /* stuff done only on the first call of the function */ + if (SRF_IS_FIRSTCALL()) + { + /* 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); + + /* + * need a tuple descriptor representing NUM_PG_SETTINGS_ATTS columns + * of the appropriate types + */ + tupdesc = CreateTemplateTupleDesc(NUM_PG_SETTINGS_ATTS); + TupleDescInitEntry(tupdesc, (AttrNumber) 1, "name", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 2, "setting", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 3, "unit", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 4, "category", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 5, "short_desc", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 6, "extra_desc", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 7, "context", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 8, "vartype", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 9, "source", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 10, "min_val", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 11, "max_val", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 12, "enumvals", + TEXTARRAYOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 13, "boot_val", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 14, "reset_val", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 15, "sourcefile", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 16, "sourceline", + INT4OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 17, "pending_restart", + BOOLOID, -1, 0); + + /* + * Generate attribute metadata needed later to produce tuples from raw + * C strings + */ + attinmeta = TupleDescGetAttInMetadata(tupdesc); + funcctx->attinmeta = attinmeta; + + /* collect the variables, in sorted order */ + guc_vars = NULL; + num_vars = 0; + + /* use user_fctx to remember the array location */ + funcctx->user_fctx = guc_vars; + + /* total number of tuples to be returned */ + funcctx->max_calls = num_vars; + + MemoryContextSwitchTo(oldcontext); + } + + /* stuff done on every call of the function */ + funcctx = SRF_PERCALL_SETUP(); + + guc_vars = (struct config_generic **) funcctx->user_fctx; + call_cntr = funcctx->call_cntr; + max_calls = funcctx->max_calls; + attinmeta = funcctx->attinmeta; + + while (call_cntr < max_calls) /* do when there is more left to send */ + { + struct config_generic *conf = guc_vars[call_cntr]; + char *values[NUM_PG_SETTINGS_ATTS]; + HeapTuple tuple; + Datum result; + + /* skip if marked NO_SHOW_ALL or if not visible to current user */ + if ((conf->flags & GUC_NO_SHOW_ALL) || + !ConfigOptionIsVisible(conf)) + { + call_cntr = ++funcctx->call_cntr; + continue; + } + + /* extract values for the current variable */ + GetConfigOptionValues(conf, (const char **) values); + + /* build a tuple */ + tuple = BuildTupleFromCStrings(attinmeta, values); + + /* make the tuple into a datum */ + result = HeapTupleGetDatum(tuple); + + SRF_RETURN_NEXT(funcctx, result); + } + + /* do when there is no more left */ + SRF_RETURN_DONE(funcctx); +} + +/* + * show_all_file_settings + * + * Returns a table of all parameter settings in all configuration files + * which includes the config file pathname, the line number, a sequence number + * indicating the order in which the settings were encountered, the parameter + * name and value, a bool showing if the value could be applied, and possibly + * an associated error message. (For problems such as syntax errors, the + * parameter name/value might be NULL.) + * + * Note: no filtering is done here, instead we depend on the GRANT system + * to prevent unprivileged users from accessing this function or the view + * built on top of it. + */ +Datum +show_all_file_settings(PG_FUNCTION_ARGS) +{ +#define NUM_PG_FILE_SETTINGS_ATTS 7 + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + ConfigVariable *conf; + int seqno; + + /* Scan the config files using current context as workspace */ + conf = ProcessConfigFileInternal(PGC_SIGHUP, false, DEBUG3); + + /* Build a tuplestore to return our results in */ + InitMaterializedSRF(fcinfo, 0); + + /* Process the results and create a tuplestore */ + for (seqno = 1; conf != NULL; conf = conf->next, seqno++) + { + Datum values[NUM_PG_FILE_SETTINGS_ATTS]; + bool nulls[NUM_PG_FILE_SETTINGS_ATTS]; + + memset(values, 0, sizeof(values)); + memset(nulls, 0, sizeof(nulls)); + + /* sourcefile */ + if (conf->filename) + values[0] = PointerGetDatum(cstring_to_text(conf->filename)); + else + nulls[0] = true; + + /* sourceline (not meaningful if no sourcefile) */ + if (conf->filename) + values[1] = Int32GetDatum(conf->sourceline); + else + nulls[1] = true; + + /* seqno */ + values[2] = Int32GetDatum(seqno); + + /* name */ + if (conf->name) + values[3] = PointerGetDatum(cstring_to_text(conf->name)); + else + nulls[3] = true; + + /* setting */ + if (conf->value) + values[4] = PointerGetDatum(cstring_to_text(conf->value)); + else + nulls[4] = true; + + /* applied */ + values[5] = BoolGetDatum(conf->applied); + + /* error */ + if (conf->errmsg) + values[6] = PointerGetDatum(cstring_to_text(conf->errmsg)); + else + nulls[6] = true; + + /* shove row into tuplestore */ + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + } + + return (Datum) 0; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/misc/guc_internal.h b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/misc/guc_internal.h new file mode 100644 index 00000000000..608c8ae4784 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/misc/guc_internal.h @@ -0,0 +1,26 @@ +/*-------------------------------------------------------------------- + * guc_internal.h + * + * Declarations shared between backend/utils/misc/guc.c and + * backend/utils/misc/guc-file.l + * + * Copyright (c) 2000-2023, PostgreSQL Global Development Group + * + * src/include/utils/guc_internal.h + *-------------------------------------------------------------------- + */ +#ifndef GUC_INTERNAL_H +#define GUC_INTERNAL_H + +#include "utils/guc.h" + +extern int guc_name_compare(const char *namea, const char *nameb); +extern ConfigVariable *ProcessConfigFileInternal(GucContext context, + bool applySettings, int elevel); +extern void record_config_file_error(const char *errmsg, + const char *config_file, + int lineno, + ConfigVariable **head_p, + ConfigVariable **tail_p); + +#endif /* GUC_INTERNAL_H */ diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/misc/guc_tables.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/misc/guc_tables.c new file mode 100644 index 00000000000..c4b72ac71c1 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/misc/guc_tables.c @@ -0,0 +1,808 @@ +/*-------------------------------------------------------------------- + * + * guc_tables.c + * + * Static tables for the Grand Unified Configuration scheme. + * + * Many of these tables are const. However, ConfigureNamesBool[] + * and so on are not, because the structs in those arrays are actually + * the live per-variable state data that guc.c manipulates. While many of + * their fields are intended to be constant, some fields change at runtime. + * + * + * Copyright (c) 2000-2023, PostgreSQL Global Development Group + * Written by Peter Eisentraut <peter_e@gmx.net>. + * + * IDENTIFICATION + * src/backend/utils/misc/guc_tables.c + * + *-------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <float.h> +#include <limits.h> +#ifdef HAVE_SYSLOG +#include <syslog.h> +#endif + +#include "access/commit_ts.h" +#include "access/gin.h" +#include "access/toast_compression.h" +#include "access/twophase.h" +#include "access/xlog_internal.h" +#include "access/xlogprefetcher.h" +#include "access/xlogrecovery.h" +#include "archive/archive_module.h" +#include "catalog/namespace.h" +#include "catalog/storage.h" +#include "commands/async.h" +#include "commands/tablespace.h" +#include "commands/trigger.h" +#include "commands/user.h" +#include "commands/vacuum.h" +#include "common/scram-common.h" +#include "jit/jit.h" +#include "libpq/auth.h" +#include "libpq/libpq.h" +#include "libpq/scram.h" +#include "nodes/queryjumble.h" +#include "optimizer/cost.h" +#include "optimizer/geqo.h" +#include "optimizer/optimizer.h" +#include "optimizer/paths.h" +#include "optimizer/planmain.h" +#include "parser/parse_expr.h" +#include "parser/parser.h" +#include "pgstat.h" +#include "postmaster/autovacuum.h" +#include "postmaster/bgworker_internals.h" +#include "postmaster/bgwriter.h" +#include "postmaster/postmaster.h" +#include "postmaster/startup.h" +#include "postmaster/syslogger.h" +#include "postmaster/walwriter.h" +#include "replication/logicallauncher.h" +#include "replication/slot.h" +#include "replication/syncrep.h" +#include "storage/bufmgr.h" +#include "storage/large_object.h" +#include "storage/pg_shmem.h" +#include "storage/predicate.h" +#include "storage/standby.h" +#include "tcop/tcopprot.h" +#include "tsearch/ts_cache.h" +#include "utils/builtins.h" +#include "utils/bytea.h" +#include "utils/float.h" +#include "utils/guc_hooks.h" +#include "utils/guc_tables.h" +#include "utils/memutils.h" +#include "utils/pg_locale.h" +#include "utils/portal.h" +#include "utils/ps_status.h" +#include "utils/inval.h" +#include "utils/xml.h" + +/* This value is normally passed in from the Makefile */ +#ifndef PG_KRB_SRVTAB +#define PG_KRB_SRVTAB "" +#endif + +/* XXX these should appear in other modules' header files */ +extern __thread bool Log_disconnections; +extern __thread int CommitDelay; +extern __thread int CommitSiblings; +extern __thread char *default_tablespace; +extern __thread char *temp_tablespaces; +extern __thread bool ignore_checksum_failure; +extern __thread bool ignore_invalid_pages; + +#ifdef TRACE_SYNCSCAN +extern bool trace_syncscan; +#endif +#ifdef DEBUG_BOUNDED_SORT +extern bool optimize_bounded_sort; +#endif + +/* + * Options for enum values defined in this module. + * + * NOTE! Option values may not contain double quotes! + */ + +static const struct config_enum_entry bytea_output_options[] = { + {"escape", BYTEA_OUTPUT_ESCAPE, false}, + {"hex", BYTEA_OUTPUT_HEX, false}, + {NULL, 0, false} +}; + +StaticAssertDecl(lengthof(bytea_output_options) == (BYTEA_OUTPUT_HEX + 2), + "array length mismatch"); + +/* + * We have different sets for client and server message level options because + * they sort slightly different (see "log" level), and because "fatal"/"panic" + * aren't sensible for client_min_messages. + */ +static const struct config_enum_entry client_message_level_options[] = { + {"debug5", DEBUG5, false}, + {"debug4", DEBUG4, false}, + {"debug3", DEBUG3, false}, + {"debug2", DEBUG2, false}, + {"debug1", DEBUG1, false}, + {"debug", DEBUG2, true}, + {"log", LOG, false}, + {"info", INFO, true}, + {"notice", NOTICE, false}, + {"warning", WARNING, false}, + {"error", ERROR, false}, + {NULL, 0, false} +}; + +static const struct config_enum_entry server_message_level_options[] = { + {"debug5", DEBUG5, false}, + {"debug4", DEBUG4, false}, + {"debug3", DEBUG3, false}, + {"debug2", DEBUG2, false}, + {"debug1", DEBUG1, false}, + {"debug", DEBUG2, true}, + {"info", INFO, false}, + {"notice", NOTICE, false}, + {"warning", WARNING, false}, + {"error", ERROR, false}, + {"log", LOG, false}, + {"fatal", FATAL, false}, + {"panic", PANIC, false}, + {NULL, 0, false} +}; + +static const struct config_enum_entry intervalstyle_options[] = { + {"postgres", INTSTYLE_POSTGRES, false}, + {"postgres_verbose", INTSTYLE_POSTGRES_VERBOSE, false}, + {"sql_standard", INTSTYLE_SQL_STANDARD, false}, + {"iso_8601", INTSTYLE_ISO_8601, false}, + {NULL, 0, false} +}; + +static const struct config_enum_entry icu_validation_level_options[] = { + {"disabled", -1, false}, + {"debug5", DEBUG5, false}, + {"debug4", DEBUG4, false}, + {"debug3", DEBUG3, false}, + {"debug2", DEBUG2, false}, + {"debug1", DEBUG1, false}, + {"debug", DEBUG2, true}, + {"log", LOG, false}, + {"info", INFO, true}, + {"notice", NOTICE, false}, + {"warning", WARNING, false}, + {"error", ERROR, false}, + {NULL, 0, false} +}; + +StaticAssertDecl(lengthof(intervalstyle_options) == (INTSTYLE_ISO_8601 + 2), + "array length mismatch"); + +static const struct config_enum_entry log_error_verbosity_options[] = { + {"terse", PGERROR_TERSE, false}, + {"default", PGERROR_DEFAULT, false}, + {"verbose", PGERROR_VERBOSE, false}, + {NULL, 0, false} +}; + +StaticAssertDecl(lengthof(log_error_verbosity_options) == (PGERROR_VERBOSE + 2), + "array length mismatch"); + +static const struct config_enum_entry log_statement_options[] = { + {"none", LOGSTMT_NONE, false}, + {"ddl", LOGSTMT_DDL, false}, + {"mod", LOGSTMT_MOD, false}, + {"all", LOGSTMT_ALL, false}, + {NULL, 0, false} +}; + +StaticAssertDecl(lengthof(log_statement_options) == (LOGSTMT_ALL + 2), + "array length mismatch"); + +static const struct config_enum_entry isolation_level_options[] = { + {"serializable", XACT_SERIALIZABLE, false}, + {"repeatable read", XACT_REPEATABLE_READ, false}, + {"read committed", XACT_READ_COMMITTED, false}, + {"read uncommitted", XACT_READ_UNCOMMITTED, false}, + {NULL, 0} +}; + +static const struct config_enum_entry session_replication_role_options[] = { + {"origin", SESSION_REPLICATION_ROLE_ORIGIN, false}, + {"replica", SESSION_REPLICATION_ROLE_REPLICA, false}, + {"local", SESSION_REPLICATION_ROLE_LOCAL, false}, + {NULL, 0, false} +}; + +StaticAssertDecl(lengthof(session_replication_role_options) == (SESSION_REPLICATION_ROLE_LOCAL + 2), + "array length mismatch"); + +static const struct config_enum_entry syslog_facility_options[] = { +#ifdef HAVE_SYSLOG + {"local0", LOG_LOCAL0, false}, + {"local1", LOG_LOCAL1, false}, + {"local2", LOG_LOCAL2, false}, + {"local3", LOG_LOCAL3, false}, + {"local4", LOG_LOCAL4, false}, + {"local5", LOG_LOCAL5, false}, + {"local6", LOG_LOCAL6, false}, + {"local7", LOG_LOCAL7, false}, +#else + {"none", 0, false}, +#endif + {NULL, 0} +}; + +static const struct config_enum_entry track_function_options[] = { + {"none", TRACK_FUNC_OFF, false}, + {"pl", TRACK_FUNC_PL, false}, + {"all", TRACK_FUNC_ALL, false}, + {NULL, 0, false} +}; + +StaticAssertDecl(lengthof(track_function_options) == (TRACK_FUNC_ALL + 2), + "array length mismatch"); + +static const struct config_enum_entry stats_fetch_consistency[] = { + {"none", PGSTAT_FETCH_CONSISTENCY_NONE, false}, + {"cache", PGSTAT_FETCH_CONSISTENCY_CACHE, false}, + {"snapshot", PGSTAT_FETCH_CONSISTENCY_SNAPSHOT, false}, + {NULL, 0, false} +}; + +StaticAssertDecl(lengthof(stats_fetch_consistency) == (PGSTAT_FETCH_CONSISTENCY_SNAPSHOT + 2), + "array length mismatch"); + +static const struct config_enum_entry xmlbinary_options[] = { + {"base64", XMLBINARY_BASE64, false}, + {"hex", XMLBINARY_HEX, false}, + {NULL, 0, false} +}; + +StaticAssertDecl(lengthof(xmlbinary_options) == (XMLBINARY_HEX + 2), + "array length mismatch"); + +static const struct config_enum_entry xmloption_options[] = { + {"content", XMLOPTION_CONTENT, false}, + {"document", XMLOPTION_DOCUMENT, false}, + {NULL, 0, false} +}; + +StaticAssertDecl(lengthof(xmloption_options) == (XMLOPTION_CONTENT + 2), + "array length mismatch"); + +/* + * Although only "on", "off", and "safe_encoding" are documented, we + * accept all the likely variants of "on" and "off". + */ +static const const struct config_enum_entry backslash_quote_options[] = { + {"safe_encoding", BACKSLASH_QUOTE_SAFE_ENCODING, false}, + {"on", BACKSLASH_QUOTE_ON, false}, + {"off", BACKSLASH_QUOTE_OFF, false}, + {"true", BACKSLASH_QUOTE_ON, true}, + {"false", BACKSLASH_QUOTE_OFF, true}, + {"yes", BACKSLASH_QUOTE_ON, true}, + {"no", BACKSLASH_QUOTE_OFF, true}, + {"1", BACKSLASH_QUOTE_ON, true}, + {"0", BACKSLASH_QUOTE_OFF, true}, + {NULL, 0, false} +}; + +/* + * Although only "on", "off", and "auto" are documented, we accept + * all the likely variants of "on" and "off". + */ +static const struct config_enum_entry compute_query_id_options[] = { + {"auto", COMPUTE_QUERY_ID_AUTO, false}, + {"regress", COMPUTE_QUERY_ID_REGRESS, false}, + {"on", COMPUTE_QUERY_ID_ON, false}, + {"off", COMPUTE_QUERY_ID_OFF, false}, + {"true", COMPUTE_QUERY_ID_ON, true}, + {"false", COMPUTE_QUERY_ID_OFF, true}, + {"yes", COMPUTE_QUERY_ID_ON, true}, + {"no", COMPUTE_QUERY_ID_OFF, true}, + {"1", COMPUTE_QUERY_ID_ON, true}, + {"0", COMPUTE_QUERY_ID_OFF, true}, + {NULL, 0, false} +}; + +/* + * Although only "on", "off", and "partition" are documented, we + * accept all the likely variants of "on" and "off". + */ +static const struct config_enum_entry constraint_exclusion_options[] = { + {"partition", CONSTRAINT_EXCLUSION_PARTITION, false}, + {"on", CONSTRAINT_EXCLUSION_ON, false}, + {"off", CONSTRAINT_EXCLUSION_OFF, false}, + {"true", CONSTRAINT_EXCLUSION_ON, true}, + {"false", CONSTRAINT_EXCLUSION_OFF, true}, + {"yes", CONSTRAINT_EXCLUSION_ON, true}, + {"no", CONSTRAINT_EXCLUSION_OFF, true}, + {"1", CONSTRAINT_EXCLUSION_ON, true}, + {"0", CONSTRAINT_EXCLUSION_OFF, true}, + {NULL, 0, false} +}; + +/* + * Although only "on", "off", "remote_apply", "remote_write", and "local" are + * documented, we accept all the likely variants of "on" and "off". + */ +static const struct config_enum_entry synchronous_commit_options[] = { + {"local", SYNCHRONOUS_COMMIT_LOCAL_FLUSH, false}, + {"remote_write", SYNCHRONOUS_COMMIT_REMOTE_WRITE, false}, + {"remote_apply", SYNCHRONOUS_COMMIT_REMOTE_APPLY, false}, + {"on", SYNCHRONOUS_COMMIT_ON, false}, + {"off", SYNCHRONOUS_COMMIT_OFF, false}, + {"true", SYNCHRONOUS_COMMIT_ON, true}, + {"false", SYNCHRONOUS_COMMIT_OFF, true}, + {"yes", SYNCHRONOUS_COMMIT_ON, true}, + {"no", SYNCHRONOUS_COMMIT_OFF, true}, + {"1", SYNCHRONOUS_COMMIT_ON, true}, + {"0", SYNCHRONOUS_COMMIT_OFF, true}, + {NULL, 0, false} +}; + +/* + * Although only "on", "off", "try" are documented, we accept all the likely + * variants of "on" and "off". + */ +static const struct config_enum_entry huge_pages_options[] = { + {"off", HUGE_PAGES_OFF, false}, + {"on", HUGE_PAGES_ON, false}, + {"try", HUGE_PAGES_TRY, false}, + {"true", HUGE_PAGES_ON, true}, + {"false", HUGE_PAGES_OFF, true}, + {"yes", HUGE_PAGES_ON, true}, + {"no", HUGE_PAGES_OFF, true}, + {"1", HUGE_PAGES_ON, true}, + {"0", HUGE_PAGES_OFF, true}, + {NULL, 0, false} +}; + +static const struct config_enum_entry recovery_prefetch_options[] = { + {"off", RECOVERY_PREFETCH_OFF, false}, + {"on", RECOVERY_PREFETCH_ON, false}, + {"try", RECOVERY_PREFETCH_TRY, false}, + {"true", RECOVERY_PREFETCH_ON, true}, + {"false", RECOVERY_PREFETCH_OFF, true}, + {"yes", RECOVERY_PREFETCH_ON, true}, + {"no", RECOVERY_PREFETCH_OFF, true}, + {"1", RECOVERY_PREFETCH_ON, true}, + {"0", RECOVERY_PREFETCH_OFF, true}, + {NULL, 0, false} +}; + +static const struct config_enum_entry debug_parallel_query_options[] = { + {"off", DEBUG_PARALLEL_OFF, false}, + {"on", DEBUG_PARALLEL_ON, false}, + {"regress", DEBUG_PARALLEL_REGRESS, false}, + {"true", DEBUG_PARALLEL_ON, true}, + {"false", DEBUG_PARALLEL_OFF, true}, + {"yes", DEBUG_PARALLEL_ON, true}, + {"no", DEBUG_PARALLEL_OFF, true}, + {"1", DEBUG_PARALLEL_ON, true}, + {"0", DEBUG_PARALLEL_OFF, true}, + {NULL, 0, false} +}; + +static const struct config_enum_entry plan_cache_mode_options[] = { + {"auto", PLAN_CACHE_MODE_AUTO, false}, + {"force_generic_plan", PLAN_CACHE_MODE_FORCE_GENERIC_PLAN, false}, + {"force_custom_plan", PLAN_CACHE_MODE_FORCE_CUSTOM_PLAN, false}, + {NULL, 0, false} +}; + +static const struct config_enum_entry password_encryption_options[] = { + {"md5", PASSWORD_TYPE_MD5, false}, + {"scram-sha-256", PASSWORD_TYPE_SCRAM_SHA_256, false}, + {NULL, 0, false} +}; + +static const struct config_enum_entry ssl_protocol_versions_info[] = { + {"", PG_TLS_ANY, false}, + {"TLSv1", PG_TLS1_VERSION, false}, + {"TLSv1.1", PG_TLS1_1_VERSION, false}, + {"TLSv1.2", PG_TLS1_2_VERSION, false}, + {"TLSv1.3", PG_TLS1_3_VERSION, false}, + {NULL, 0, false} +}; + +static const struct config_enum_entry debug_logical_replication_streaming_options[] = { + {"buffered", DEBUG_LOGICAL_REP_STREAMING_BUFFERED, false}, + {"immediate", DEBUG_LOGICAL_REP_STREAMING_IMMEDIATE, false}, + {NULL, 0, false} +}; + +StaticAssertDecl(lengthof(ssl_protocol_versions_info) == (PG_TLS1_3_VERSION + 2), + "array length mismatch"); + +static const struct config_enum_entry recovery_init_sync_method_options[] = { + {"fsync", RECOVERY_INIT_SYNC_METHOD_FSYNC, false}, +#ifdef HAVE_SYNCFS + {"syncfs", RECOVERY_INIT_SYNC_METHOD_SYNCFS, false}, +#endif + {NULL, 0, false} +}; + +static const struct config_enum_entry shared_memory_options[] = { +#ifndef WIN32 + {"sysv", SHMEM_TYPE_SYSV, false}, +#endif +#ifndef EXEC_BACKEND + {"mmap", SHMEM_TYPE_MMAP, false}, +#endif +#ifdef WIN32 + {"windows", SHMEM_TYPE_WINDOWS, false}, +#endif + {NULL, 0, false} +}; + +static const struct config_enum_entry default_toast_compression_options[] = { + {"pglz", TOAST_PGLZ_COMPRESSION, false}, +#ifdef USE_LZ4 + {"lz4", TOAST_LZ4_COMPRESSION, false}, +#endif + {NULL, 0, false} +}; + +static const struct config_enum_entry wal_compression_options[] = { + {"pglz", WAL_COMPRESSION_PGLZ, false}, +#ifdef USE_LZ4 + {"lz4", WAL_COMPRESSION_LZ4, false}, +#endif +#ifdef USE_ZSTD + {"zstd", WAL_COMPRESSION_ZSTD, false}, +#endif + {"on", WAL_COMPRESSION_PGLZ, false}, + {"off", WAL_COMPRESSION_NONE, false}, + {"true", WAL_COMPRESSION_PGLZ, true}, + {"false", WAL_COMPRESSION_NONE, true}, + {"yes", WAL_COMPRESSION_PGLZ, true}, + {"no", WAL_COMPRESSION_NONE, true}, + {"1", WAL_COMPRESSION_PGLZ, true}, + {"0", WAL_COMPRESSION_NONE, true}, + {NULL, 0, false} +}; + +/* + * Options for enum values stored in other modules + */ +extern const struct config_enum_entry wal_level_options[]; +extern const struct config_enum_entry archive_mode_options[]; +extern const struct config_enum_entry recovery_target_action_options[]; +extern const struct config_enum_entry sync_method_options[]; +extern const struct config_enum_entry dynamic_shared_memory_options[]; + +/* + * GUC option variables that are exported from this module + */ +__thread bool log_duration = false; +__thread bool Debug_print_plan = false; +__thread bool Debug_print_parse = false; +__thread bool Debug_print_rewritten = false; +__thread bool Debug_pretty_print = true; + +__thread bool log_parser_stats = false; +__thread bool log_planner_stats = false; +__thread bool log_executor_stats = false; +__thread bool log_statement_stats = false; /* this is sort of all three above + * together */ +__thread bool log_btree_build_stats = false; +__thread char *event_source; + +__thread bool row_security; +__thread bool check_function_bodies = true; + +/* + * This GUC exists solely for backward compatibility, check its definition for + * details. + */ +__thread bool default_with_oids = false; +__thread bool session_auth_is_superuser; + +__thread int log_min_error_statement = ERROR; +__thread int log_min_messages = WARNING; +__thread int client_min_messages = NOTICE; +__thread int log_min_duration_sample = -1; +__thread int log_min_duration_statement = -1; +__thread int log_parameter_max_length = -1; +__thread int log_parameter_max_length_on_error = 0; +__thread int log_temp_files = -1; +__thread double log_statement_sample_rate = 1.0; +__thread double log_xact_sample_rate = 0; +__thread int trace_recovery_messages = LOG; +__thread char *backtrace_functions; + +__thread int temp_file_limit = -1; + +__thread int num_temp_buffers = 1024; + +__thread char *cluster_name = ""; +__thread char *ConfigFileName; +__thread char *HbaFileName; +__thread char *IdentFileName; +__thread char *external_pid_file; + +__thread char *application_name; + +__thread int tcp_keepalives_idle; +__thread int tcp_keepalives_interval; +__thread int tcp_keepalives_count; +__thread int tcp_user_timeout; + +/* + * SSL renegotiation was been removed in PostgreSQL 9.5, but we tolerate it + * being set to zero (meaning never renegotiate) for backward compatibility. + * This avoids breaking compatibility with clients that have never supported + * renegotiation and therefore always try to zero it. + */ +__thread int ssl_renegotiation_limit; + +/* + * This really belongs in pg_shmem.c, but is defined here so that it doesn't + * need to be duplicated in all the different implementations of pg_shmem.c. + */ +__thread int huge_pages = HUGE_PAGES_TRY; +__thread int huge_page_size; + +/* + * These variables are all dummies that don't do anything, except in some + * cases provide the value for SHOW to display. The real state is elsewhere + * and is kept in sync by assign_hooks. + */ +static __thread char *syslog_ident_str; +static __thread double phony_random_seed; +static __thread char *client_encoding_string; +static __thread char *datestyle_string; +static __thread char *server_encoding_string; +static __thread char *server_version_string; +static __thread int server_version_num; +static __thread char *debug_io_direct_string; + +#ifdef HAVE_SYSLOG +#define DEFAULT_SYSLOG_FACILITY LOG_LOCAL0 +#else +#define DEFAULT_SYSLOG_FACILITY 0 +#endif +static __thread int syslog_facility = DEFAULT_SYSLOG_FACILITY; + +static __thread char *timezone_string; +static __thread char *log_timezone_string; +static __thread char *timezone_abbreviations_string; +static __thread char *data_directory; +static __thread char *session_authorization_string; +static __thread int max_function_args; +static __thread int max_index_keys; +static __thread int max_identifier_length; +static __thread int block_size; +static __thread int segment_size; +static __thread int shared_memory_size_mb; +static __thread int shared_memory_size_in_huge_pages; +static __thread int wal_block_size; +static __thread bool data_checksums; +static __thread bool integer_datetimes; + +#ifdef USE_ASSERT_CHECKING +#define DEFAULT_ASSERT_ENABLED true +#else +#define DEFAULT_ASSERT_ENABLED false +#endif +static __thread bool assert_enabled = DEFAULT_ASSERT_ENABLED; + +static __thread char *recovery_target_timeline_string; +static __thread char *recovery_target_string; +static __thread char *recovery_target_xid_string; +static __thread char *recovery_target_name_string; +static __thread char *recovery_target_lsn_string; + +/* should be static, but commands/variable.c needs to get at this */ +__thread char *role_string; + +/* should be static, but guc.c needs to get at this */ +__thread bool in_hot_standby_guc; + + +/* + * Displayable names for context types (enum GucContext) + * + * Note: these strings are deliberately not localized. + */ +const char *const GucContext_Names[] = +{ + /* PGC_INTERNAL */ "internal", + /* PGC_POSTMASTER */ "postmaster", + /* PGC_SIGHUP */ "sighup", + /* PGC_SU_BACKEND */ "superuser-backend", + /* PGC_BACKEND */ "backend", + /* PGC_SUSET */ "superuser", + /* PGC_USERSET */ "user" +}; + +StaticAssertDecl(lengthof(GucContext_Names) == (PGC_USERSET + 1), + "array length mismatch"); + +/* + * Displayable names for source types (enum GucSource) + * + * Note: these strings are deliberately not localized. + */ +const char *const GucSource_Names[] = +{ + /* PGC_S_DEFAULT */ "default", + /* PGC_S_DYNAMIC_DEFAULT */ "default", + /* PGC_S_ENV_VAR */ "environment variable", + /* PGC_S_FILE */ "configuration file", + /* PGC_S_ARGV */ "command line", + /* PGC_S_GLOBAL */ "global", + /* PGC_S_DATABASE */ "database", + /* PGC_S_USER */ "user", + /* PGC_S_DATABASE_USER */ "database user", + /* PGC_S_CLIENT */ "client", + /* PGC_S_OVERRIDE */ "override", + /* PGC_S_INTERACTIVE */ "interactive", + /* PGC_S_TEST */ "test", + /* PGC_S_SESSION */ "session" +}; + +StaticAssertDecl(lengthof(GucSource_Names) == (PGC_S_SESSION + 1), + "array length mismatch"); + +/* + * Displayable names for the groupings defined in enum config_group + */ +const char *const config_group_names[] = +{ + /* UNGROUPED */ + gettext_noop("Ungrouped"), + /* FILE_LOCATIONS */ + gettext_noop("File Locations"), + /* CONN_AUTH_SETTINGS */ + gettext_noop("Connections and Authentication / Connection Settings"), + /* CONN_AUTH_TCP */ + gettext_noop("Connections and Authentication / TCP Settings"), + /* CONN_AUTH_AUTH */ + gettext_noop("Connections and Authentication / Authentication"), + /* CONN_AUTH_SSL */ + gettext_noop("Connections and Authentication / SSL"), + /* RESOURCES_MEM */ + gettext_noop("Resource Usage / Memory"), + /* RESOURCES_DISK */ + gettext_noop("Resource Usage / Disk"), + /* RESOURCES_KERNEL */ + gettext_noop("Resource Usage / Kernel Resources"), + /* RESOURCES_VACUUM_DELAY */ + gettext_noop("Resource Usage / Cost-Based Vacuum Delay"), + /* RESOURCES_BGWRITER */ + gettext_noop("Resource Usage / Background Writer"), + /* RESOURCES_ASYNCHRONOUS */ + gettext_noop("Resource Usage / Asynchronous Behavior"), + /* WAL_SETTINGS */ + gettext_noop("Write-Ahead Log / Settings"), + /* WAL_CHECKPOINTS */ + gettext_noop("Write-Ahead Log / Checkpoints"), + /* WAL_ARCHIVING */ + gettext_noop("Write-Ahead Log / Archiving"), + /* WAL_RECOVERY */ + gettext_noop("Write-Ahead Log / Recovery"), + /* WAL_ARCHIVE_RECOVERY */ + gettext_noop("Write-Ahead Log / Archive Recovery"), + /* WAL_RECOVERY_TARGET */ + gettext_noop("Write-Ahead Log / Recovery Target"), + /* REPLICATION_SENDING */ + gettext_noop("Replication / Sending Servers"), + /* REPLICATION_PRIMARY */ + gettext_noop("Replication / Primary Server"), + /* REPLICATION_STANDBY */ + gettext_noop("Replication / Standby Servers"), + /* REPLICATION_SUBSCRIBERS */ + gettext_noop("Replication / Subscribers"), + /* QUERY_TUNING_METHOD */ + gettext_noop("Query Tuning / Planner Method Configuration"), + /* QUERY_TUNING_COST */ + gettext_noop("Query Tuning / Planner Cost Constants"), + /* QUERY_TUNING_GEQO */ + gettext_noop("Query Tuning / Genetic Query Optimizer"), + /* QUERY_TUNING_OTHER */ + gettext_noop("Query Tuning / Other Planner Options"), + /* LOGGING_WHERE */ + gettext_noop("Reporting and Logging / Where to Log"), + /* LOGGING_WHEN */ + gettext_noop("Reporting and Logging / When to Log"), + /* LOGGING_WHAT */ + gettext_noop("Reporting and Logging / What to Log"), + /* PROCESS_TITLE */ + gettext_noop("Reporting and Logging / Process Title"), + /* STATS_MONITORING */ + gettext_noop("Statistics / Monitoring"), + /* STATS_CUMULATIVE */ + gettext_noop("Statistics / Cumulative Query and Index Statistics"), + /* AUTOVACUUM */ + gettext_noop("Autovacuum"), + /* CLIENT_CONN_STATEMENT */ + gettext_noop("Client Connection Defaults / Statement Behavior"), + /* CLIENT_CONN_LOCALE */ + gettext_noop("Client Connection Defaults / Locale and Formatting"), + /* CLIENT_CONN_PRELOAD */ + gettext_noop("Client Connection Defaults / Shared Library Preloading"), + /* CLIENT_CONN_OTHER */ + gettext_noop("Client Connection Defaults / Other Defaults"), + /* LOCK_MANAGEMENT */ + gettext_noop("Lock Management"), + /* COMPAT_OPTIONS_PREVIOUS */ + gettext_noop("Version and Platform Compatibility / Previous PostgreSQL Versions"), + /* COMPAT_OPTIONS_CLIENT */ + gettext_noop("Version and Platform Compatibility / Other Platforms and Clients"), + /* ERROR_HANDLING_OPTIONS */ + gettext_noop("Error Handling"), + /* PRESET_OPTIONS */ + gettext_noop("Preset Options"), + /* CUSTOM_OPTIONS */ + gettext_noop("Customized Options"), + /* DEVELOPER_OPTIONS */ + gettext_noop("Developer Options"), + /* help_config wants this array to be null-terminated */ + NULL +}; + +StaticAssertDecl(lengthof(config_group_names) == (DEVELOPER_OPTIONS + 2), + "array length mismatch"); + +/* + * Displayable names for GUC variable types (enum config_type) + * + * Note: these strings are deliberately not localized. + */ +const char *const config_type_names[] = +{ + /* PGC_BOOL */ "bool", + /* PGC_INT */ "integer", + /* PGC_REAL */ "real", + /* PGC_STRING */ "string", + /* PGC_ENUM */ "enum" +}; + +StaticAssertDecl(lengthof(config_type_names) == (PGC_ENUM + 1), + "array length mismatch"); + + +/* + * Contents of GUC tables + * + * See src/backend/utils/misc/README for design notes. + * + * TO ADD AN OPTION: + * + * 1. Declare a global variable of type bool, int, double, or char* + * and make use of it. + * + * 2. Decide at what times it's safe to set the option. See guc.h for + * details. + * + * 3. Decide on a name, a default value, upper and lower bounds (if + * applicable), etc. + * + * 4. Add a record below. + * + * 5. Add it to src/backend/utils/misc/postgresql.conf.sample, if + * appropriate. + * + * 6. Don't forget to document the option (at least in config.sgml). + * + * 7. If it's a new GUC_LIST_QUOTE option, you must add it to + * variable_is_guc_list_quote() in src/bin/pg_dump/dumputils.c. + */ + + + + + + + + + diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/misc/help_config.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/misc/help_config.c new file mode 100644 index 00000000000..94c8a16ac1e --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/misc/help_config.c @@ -0,0 +1,136 @@ +/*------------------------------------------------------------------------- + * help_config.c + * + * Displays available options under grand unified configuration scheme + * + * Options whose flag bits are set to GUC_NO_SHOW_ALL, GUC_NOT_IN_SAMPLE, + * or GUC_DISALLOW_IN_FILE are not displayed, unless the user specifically + * requests that variable by name + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/utils/misc/help_config.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <limits.h> +#include <unistd.h> + +#include "utils/guc_tables.h" +#include "utils/help_config.h" + + +/* + * This union allows us to mix the numerous different types of structs + * that we are organizing. + */ +typedef union +{ + struct config_generic generic; + struct config_bool _bool; + struct config_real real; + struct config_int integer; + struct config_string string; + struct config_enum _enum; +} mixedStruct; + + +static void printMixedStruct(mixedStruct *structToPrint); +static bool displayStruct(mixedStruct *structToDisplay); + + +void +GucInfoMain(void) +{ + struct config_generic **guc_vars; + int numOpts, + i; + + /* Initialize the GUC hash table */ + build_guc_variables(); + + guc_vars = get_guc_variables(&numOpts); + + for (i = 0; i < numOpts; i++) + { + mixedStruct *var = (mixedStruct *) guc_vars[i]; + + if (displayStruct(var)) + printMixedStruct(var); + } + + exit(0); +} + + +/* + * This function will return true if the struct passed to it + * should be displayed to the user. + */ +static bool +displayStruct(mixedStruct *structToDisplay) +{ + return !(structToDisplay->generic.flags & (GUC_NO_SHOW_ALL | + GUC_NOT_IN_SAMPLE | + GUC_DISALLOW_IN_FILE)); +} + + +/* + * This function prints out the generic struct passed to it. It will print out + * a different format, depending on what the user wants to see. + */ +static void +printMixedStruct(mixedStruct *structToPrint) +{ + printf("%s\t%s\t%s\t", + structToPrint->generic.name, + GucContext_Names[structToPrint->generic.context], + _(config_group_names[structToPrint->generic.group])); + + switch (structToPrint->generic.vartype) + { + + case PGC_BOOL: + printf("BOOLEAN\t%s\t\t\t", + (structToPrint->_bool.reset_val == 0) ? + "FALSE" : "TRUE"); + break; + + case PGC_INT: + printf("INTEGER\t%d\t%d\t%d\t", + structToPrint->integer.reset_val, + structToPrint->integer.min, + structToPrint->integer.max); + break; + + case PGC_REAL: + printf("REAL\t%g\t%g\t%g\t", + structToPrint->real.reset_val, + structToPrint->real.min, + structToPrint->real.max); + break; + + case PGC_STRING: + printf("STRING\t%s\t\t\t", + structToPrint->string.boot_val ? structToPrint->string.boot_val : ""); + break; + + case PGC_ENUM: + printf("ENUM\t%s\t\t\t", + config_enum_lookup_by_value(&structToPrint->_enum, + structToPrint->_enum.boot_val)); + break; + + default: + write_stderr("internal error: unrecognized run-time parameter type\n"); + break; + } + + printf("%s\t%s\n", + (structToPrint->generic.short_desc == NULL) ? "" : _(structToPrint->generic.short_desc), + (structToPrint->generic.long_desc == NULL) ? "" : _(structToPrint->generic.long_desc)); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/misc/pg_config.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/misc/pg_config.c new file mode 100644 index 00000000000..5ca8445a4cf --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/misc/pg_config.c @@ -0,0 +1,51 @@ +/*------------------------------------------------------------------------- + * + * pg_config.c + * Expose same output as pg_config except as an SRF + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/utils/misc/pg_config.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "catalog/pg_type.h" +#include "common/config_info.h" +#include "funcapi.h" +#include "miscadmin.h" +#include "port.h" +#include "utils/builtins.h" + +Datum +pg_config(PG_FUNCTION_ARGS) +{ + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + ConfigData *configdata; + size_t configdata_len; + int i = 0; + + /* initialize our tuplestore */ + InitMaterializedSRF(fcinfo, 0); + + configdata = get_configdata(my_exec_path, &configdata_len); + for (i = 0; i < configdata_len; i++) + { + Datum values[2]; + bool nulls[2]; + + memset(values, 0, sizeof(values)); + memset(nulls, 0, sizeof(nulls)); + + values[0] = CStringGetTextDatum(configdata[i].name); + values[1] = CStringGetTextDatum(configdata[i].setting); + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + } + + return (Datum) 0; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/misc/pg_controldata.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/misc/pg_controldata.c new file mode 100644 index 00000000000..a1003a464dc --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/misc/pg_controldata.c @@ -0,0 +1,261 @@ +/*------------------------------------------------------------------------- + * + * pg_controldata.c + * + * Routines to expose the contents of the control data file via + * a set of SQL functions. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/utils/misc/pg_controldata.c + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/htup_details.h" +#include "access/transam.h" +#include "access/xlog.h" +#include "access/xlog_internal.h" +#include "catalog/pg_control.h" +#include "catalog/pg_type.h" +#include "common/controldata_utils.h" +#include "funcapi.h" +#include "miscadmin.h" +#include "storage/lwlock.h" +#include "utils/builtins.h" +#include "utils/pg_lsn.h" +#include "utils/timestamp.h" + +Datum +pg_control_system(PG_FUNCTION_ARGS) +{ + Datum values[4]; + bool nulls[4]; + TupleDesc tupdesc; + HeapTuple htup; + ControlFileData *ControlFile; + bool crc_ok; + + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + + /* read the control file */ + LWLockAcquire(ControlFileLock, LW_SHARED); + ControlFile = get_controlfile(DataDir, &crc_ok); + LWLockRelease(ControlFileLock); + if (!crc_ok) + ereport(ERROR, + (errmsg("calculated CRC checksum does not match value stored in file"))); + + values[0] = Int32GetDatum(ControlFile->pg_control_version); + nulls[0] = false; + + values[1] = Int32GetDatum(ControlFile->catalog_version_no); + nulls[1] = false; + + values[2] = Int64GetDatum(ControlFile->system_identifier); + nulls[2] = false; + + values[3] = TimestampTzGetDatum(time_t_to_timestamptz(ControlFile->time)); + nulls[3] = false; + + htup = heap_form_tuple(tupdesc, values, nulls); + + PG_RETURN_DATUM(HeapTupleGetDatum(htup)); +} + +Datum +pg_control_checkpoint(PG_FUNCTION_ARGS) +{ + Datum values[18]; + bool nulls[18]; + TupleDesc tupdesc; + HeapTuple htup; + ControlFileData *ControlFile; + XLogSegNo segno; + char xlogfilename[MAXFNAMELEN]; + bool crc_ok; + + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + + /* Read the control file. */ + LWLockAcquire(ControlFileLock, LW_SHARED); + ControlFile = get_controlfile(DataDir, &crc_ok); + LWLockRelease(ControlFileLock); + if (!crc_ok) + ereport(ERROR, + (errmsg("calculated CRC checksum does not match value stored in file"))); + + /* + * Calculate name of the WAL file containing the latest checkpoint's REDO + * start point. + */ + XLByteToSeg(ControlFile->checkPointCopy.redo, segno, wal_segment_size); + XLogFileName(xlogfilename, ControlFile->checkPointCopy.ThisTimeLineID, + segno, wal_segment_size); + + /* Populate the values and null arrays */ + values[0] = LSNGetDatum(ControlFile->checkPoint); + nulls[0] = false; + + values[1] = LSNGetDatum(ControlFile->checkPointCopy.redo); + nulls[1] = false; + + values[2] = CStringGetTextDatum(xlogfilename); + nulls[2] = false; + + values[3] = Int32GetDatum(ControlFile->checkPointCopy.ThisTimeLineID); + nulls[3] = false; + + values[4] = Int32GetDatum(ControlFile->checkPointCopy.PrevTimeLineID); + nulls[4] = false; + + values[5] = BoolGetDatum(ControlFile->checkPointCopy.fullPageWrites); + nulls[5] = false; + + values[6] = CStringGetTextDatum(psprintf("%u:%u", + EpochFromFullTransactionId(ControlFile->checkPointCopy.nextXid), + XidFromFullTransactionId(ControlFile->checkPointCopy.nextXid))); + nulls[6] = false; + + values[7] = ObjectIdGetDatum(ControlFile->checkPointCopy.nextOid); + nulls[7] = false; + + values[8] = TransactionIdGetDatum(ControlFile->checkPointCopy.nextMulti); + nulls[8] = false; + + values[9] = TransactionIdGetDatum(ControlFile->checkPointCopy.nextMultiOffset); + nulls[9] = false; + + values[10] = TransactionIdGetDatum(ControlFile->checkPointCopy.oldestXid); + nulls[10] = false; + + values[11] = ObjectIdGetDatum(ControlFile->checkPointCopy.oldestXidDB); + nulls[11] = false; + + values[12] = TransactionIdGetDatum(ControlFile->checkPointCopy.oldestActiveXid); + nulls[12] = false; + + values[13] = TransactionIdGetDatum(ControlFile->checkPointCopy.oldestMulti); + nulls[13] = false; + + values[14] = ObjectIdGetDatum(ControlFile->checkPointCopy.oldestMultiDB); + nulls[14] = false; + + values[15] = TransactionIdGetDatum(ControlFile->checkPointCopy.oldestCommitTsXid); + nulls[15] = false; + + values[16] = TransactionIdGetDatum(ControlFile->checkPointCopy.newestCommitTsXid); + nulls[16] = false; + + values[17] = TimestampTzGetDatum(time_t_to_timestamptz(ControlFile->checkPointCopy.time)); + nulls[17] = false; + + htup = heap_form_tuple(tupdesc, values, nulls); + + PG_RETURN_DATUM(HeapTupleGetDatum(htup)); +} + +Datum +pg_control_recovery(PG_FUNCTION_ARGS) +{ + Datum values[5]; + bool nulls[5]; + TupleDesc tupdesc; + HeapTuple htup; + ControlFileData *ControlFile; + bool crc_ok; + + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + + /* read the control file */ + LWLockAcquire(ControlFileLock, LW_SHARED); + ControlFile = get_controlfile(DataDir, &crc_ok); + LWLockRelease(ControlFileLock); + if (!crc_ok) + ereport(ERROR, + (errmsg("calculated CRC checksum does not match value stored in file"))); + + values[0] = LSNGetDatum(ControlFile->minRecoveryPoint); + nulls[0] = false; + + values[1] = Int32GetDatum(ControlFile->minRecoveryPointTLI); + nulls[1] = false; + + values[2] = LSNGetDatum(ControlFile->backupStartPoint); + nulls[2] = false; + + values[3] = LSNGetDatum(ControlFile->backupEndPoint); + nulls[3] = false; + + values[4] = BoolGetDatum(ControlFile->backupEndRequired); + nulls[4] = false; + + htup = heap_form_tuple(tupdesc, values, nulls); + + PG_RETURN_DATUM(HeapTupleGetDatum(htup)); +} + +Datum +pg_control_init(PG_FUNCTION_ARGS) +{ + Datum values[11]; + bool nulls[11]; + TupleDesc tupdesc; + HeapTuple htup; + ControlFileData *ControlFile; + bool crc_ok; + + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + + /* read the control file */ + LWLockAcquire(ControlFileLock, LW_SHARED); + ControlFile = get_controlfile(DataDir, &crc_ok); + LWLockRelease(ControlFileLock); + if (!crc_ok) + ereport(ERROR, + (errmsg("calculated CRC checksum does not match value stored in file"))); + + values[0] = Int32GetDatum(ControlFile->maxAlign); + nulls[0] = false; + + values[1] = Int32GetDatum(ControlFile->blcksz); + nulls[1] = false; + + values[2] = Int32GetDatum(ControlFile->relseg_size); + nulls[2] = false; + + values[3] = Int32GetDatum(ControlFile->xlog_blcksz); + nulls[3] = false; + + values[4] = Int32GetDatum(ControlFile->xlog_seg_size); + nulls[4] = false; + + values[5] = Int32GetDatum(ControlFile->nameDataLen); + nulls[5] = false; + + values[6] = Int32GetDatum(ControlFile->indexMaxKeys); + nulls[6] = false; + + values[7] = Int32GetDatum(ControlFile->toast_max_chunk_size); + nulls[7] = false; + + values[8] = Int32GetDatum(ControlFile->loblksize); + nulls[8] = false; + + values[9] = BoolGetDatum(ControlFile->float8ByVal); + nulls[9] = false; + + values[10] = Int32GetDatum(ControlFile->data_checksum_version); + nulls[10] = false; + + htup = heap_form_tuple(tupdesc, values, nulls); + + PG_RETURN_DATUM(HeapTupleGetDatum(htup)); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/misc/pg_rusage.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/misc/pg_rusage.c new file mode 100644 index 00000000000..bd0ba96c3d0 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/misc/pg_rusage.c @@ -0,0 +1,73 @@ +/*------------------------------------------------------------------------- + * + * pg_rusage.c + * Resource usage measurement support routines. + * + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/misc/pg_rusage.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <unistd.h> + +#include "utils/pg_rusage.h" + + +/* + * Initialize usage snapshot. + */ +void +pg_rusage_init(PGRUsage *ru0) +{ + getrusage(RUSAGE_SELF, &ru0->ru); + gettimeofday(&ru0->tv, NULL); +} + +/* + * Compute elapsed time since ru0 usage snapshot, and format into + * a displayable string. Result is in a static string, which is + * tacky, but no one ever claimed that the Postgres backend is + * threadable... + */ +const char * +pg_rusage_show(const PGRUsage *ru0) +{ + static __thread char result[100]; + PGRUsage ru1; + + pg_rusage_init(&ru1); + + if (ru1.tv.tv_usec < ru0->tv.tv_usec) + { + ru1.tv.tv_sec--; + ru1.tv.tv_usec += 1000000; + } + if (ru1.ru.ru_stime.tv_usec < ru0->ru.ru_stime.tv_usec) + { + ru1.ru.ru_stime.tv_sec--; + ru1.ru.ru_stime.tv_usec += 1000000; + } + if (ru1.ru.ru_utime.tv_usec < ru0->ru.ru_utime.tv_usec) + { + ru1.ru.ru_utime.tv_sec--; + ru1.ru.ru_utime.tv_usec += 1000000; + } + + snprintf(result, sizeof(result), + _("CPU: user: %d.%02d s, system: %d.%02d s, elapsed: %d.%02d s"), + (int) (ru1.ru.ru_utime.tv_sec - ru0->ru.ru_utime.tv_sec), + (int) (ru1.ru.ru_utime.tv_usec - ru0->ru.ru_utime.tv_usec) / 10000, + (int) (ru1.ru.ru_stime.tv_sec - ru0->ru.ru_stime.tv_sec), + (int) (ru1.ru.ru_stime.tv_usec - ru0->ru.ru_stime.tv_usec) / 10000, + (int) (ru1.tv.tv_sec - ru0->tv.tv_sec), + (int) (ru1.tv.tv_usec - ru0->tv.tv_usec) / 10000); + + return result; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/misc/ps_status.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/misc/ps_status.c new file mode 100644 index 00000000000..92fd11b38d1 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/misc/ps_status.c @@ -0,0 +1,551 @@ +/*-------------------------------------------------------------------- + * ps_status.c + * + * Routines to support changing the ps display of PostgreSQL backends + * to contain some useful information. Mechanism differs wildly across + * platforms. + * + * src/backend/utils/misc/ps_status.c + * + * Copyright (c) 2000-2023, PostgreSQL Global Development Group + * various details abducted from various places + *-------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include <unistd.h> +#if defined(__darwin__) +#include <crt_externs.h> +#endif + +#include "libpq/libpq.h" +#include "miscadmin.h" +#include "pgstat.h" +#include "utils/guc.h" +#include "utils/ps_status.h" + +extern char **environ; + +/* GUC variable */ +__thread bool update_process_title = DEFAULT_UPDATE_PROCESS_TITLE; + +/* + * Alternative ways of updating ps display: + * + * PS_USE_SETPROCTITLE_FAST + * use the function setproctitle_fast(const char *, ...) + * (FreeBSD) + * PS_USE_SETPROCTITLE + * use the function setproctitle(const char *, ...) + * (other BSDs) + * PS_USE_CLOBBER_ARGV + * write over the argv and environment area + * (Linux and most SysV-like systems) + * PS_USE_WIN32 + * push the string out as the name of a Windows event + * PS_USE_NONE + * don't update ps display + * (This is the default, as it is safest.) + */ +#if defined(HAVE_SETPROCTITLE_FAST) +#define PS_USE_SETPROCTITLE_FAST +#elif defined(HAVE_SETPROCTITLE) +#define PS_USE_SETPROCTITLE +#elif defined(__linux__) || defined(_AIX) || defined(__sun) || defined(__darwin__) +#define PS_USE_CLOBBER_ARGV +#elif defined(WIN32) +#define PS_USE_WIN32 +#else +#define PS_USE_NONE +#endif + + +/* Different systems want the buffer padded differently */ +#if defined(_AIX) || defined(__linux__) || defined(__darwin__) +#define PS_PADDING '\0' +#else +#define PS_PADDING ' ' +#endif + + +#ifndef PS_USE_NONE + +#ifndef PS_USE_CLOBBER_ARGV +/* all but one option need a buffer to write their ps line in */ +#define PS_BUFFER_SIZE 256 +static __thread char ps_buffer[PS_BUFFER_SIZE]; +static __thread const size_t ps_buffer_size = PS_BUFFER_SIZE; +#else /* PS_USE_CLOBBER_ARGV */ +static __thread char *ps_buffer; /* will point to argv area */ +static __thread size_t ps_buffer_size; /* space determined at run time */ +static __thread size_t last_status_len; /* use to minimize length of clobber */ +#endif /* PS_USE_CLOBBER_ARGV */ + +static __thread size_t ps_buffer_cur_len; /* nominal strlen(ps_buffer) */ + +static __thread size_t ps_buffer_fixed_size; /* size of the constant prefix */ + +/* + * Length of ps_buffer before the suffix was appended to the end, or 0 if we + * didn't set a suffix. + */ +static __thread size_t ps_buffer_nosuffix_len; + +static void flush_ps_display(void); + +#endif /* not PS_USE_NONE */ + +/* save the original argv[] location here */ +static __thread int save_argc; +static __thread char **save_argv; + + +/* + * Call this early in startup to save the original argc/argv values. + * If needed, we make a copy of the original argv[] array to preserve it + * from being clobbered by subsequent ps_display actions. + * + * (The original argv[] will not be overwritten by this routine, but may be + * overwritten during init_ps_display. Also, the physical location of the + * environment strings may be moved, so this should be called before any code + * that might try to hang onto a getenv() result. But see hack for musl + * within.) + * + * Note that in case of failure this cannot call elog() as that is not + * initialized yet. We rely on write_stderr() instead. + */ +char ** +save_ps_display_args(int argc, char **argv) +{ + save_argc = argc; + save_argv = argv; + +#if defined(PS_USE_CLOBBER_ARGV) + + /* + * If we're going to overwrite the argv area, count the available space. + * Also move the environment strings to make additional room. + */ + { + char *end_of_area = NULL; + char **new_environ; + int i; + + /* + * check for contiguous argv strings + */ + for (i = 0; i < argc; i++) + { + if (i == 0 || end_of_area + 1 == argv[i]) + end_of_area = argv[i] + strlen(argv[i]); + } + + if (end_of_area == NULL) /* probably can't happen? */ + { + ps_buffer = NULL; + ps_buffer_size = 0; + return argv; + } + + /* + * check for contiguous environ strings following argv + */ + for (i = 0; environ[i] != NULL; i++) + { + if (end_of_area + 1 == environ[i]) + { + /* + * The musl dynamic linker keeps a static pointer to the + * initial value of LD_LIBRARY_PATH, if that is defined in the + * process's environment. Therefore, we must not overwrite the + * value of that setting and thus cannot advance end_of_area + * beyond it. Musl does not define any identifying compiler + * symbol, so we have to do this unless we see a symbol + * identifying a Linux libc we know is safe. + */ +#if defined(__linux__) && (!defined(__GLIBC__) && !defined(__UCLIBC__)) + if (strncmp(environ[i], "LD_LIBRARY_PATH=", 16) == 0) + { + /* + * We can overwrite the name, but stop at the equals sign. + * Future loop iterations will not find any more + * contiguous space, but we don't break early because we + * need to count the total number of environ[] entries. + */ + end_of_area = environ[i] + 15; + } + else +#endif + { + end_of_area = environ[i] + strlen(environ[i]); + } + } + } + + ps_buffer = argv[0]; + last_status_len = ps_buffer_size = end_of_area - argv[0]; + + /* + * move the environment out of the way + */ + new_environ = (char **) malloc((i + 1) * sizeof(char *)); + if (!new_environ) + { + write_stderr("out of memory\n"); + exit(1); + } + for (i = 0; environ[i] != NULL; i++) + { + new_environ[i] = strdup(environ[i]); + if (!new_environ[i]) + { + write_stderr("out of memory\n"); + exit(1); + } + } + new_environ[i] = NULL; + environ = new_environ; + } + + /* + * If we're going to change the original argv[] then make a copy for + * argument parsing purposes. + * + * NB: do NOT think to remove the copying of argv[], even though + * postmaster.c finishes looking at argv[] long before we ever consider + * changing the ps display. On some platforms, getopt() keeps pointers + * into the argv array, and will get horribly confused when it is + * re-called to analyze a subprocess' argument string if the argv storage + * has been clobbered meanwhile. Other platforms have other dependencies + * on argv[]. + */ + { + char **new_argv; + int i; + + new_argv = (char **) malloc((argc + 1) * sizeof(char *)); + if (!new_argv) + { + write_stderr("out of memory\n"); + exit(1); + } + for (i = 0; i < argc; i++) + { + new_argv[i] = strdup(argv[i]); + if (!new_argv[i]) + { + write_stderr("out of memory\n"); + exit(1); + } + } + new_argv[argc] = NULL; + +#if defined(__darwin__) + + /* + * macOS has a static copy of the argv pointer, which we may fix like + * so: + */ + *_NSGetArgv() = new_argv; +#endif + + argv = new_argv; + } +#endif /* PS_USE_CLOBBER_ARGV */ + + return argv; +} + +/* + * Call this once during subprocess startup to set the identification + * values. + * + * If fixed_part is NULL, a default will be obtained from MyBackendType. + * + * At this point, the original argv[] array may be overwritten. + */ +void +init_ps_display(const char *fixed_part) +{ +#ifndef PS_USE_NONE + bool save_update_process_title; +#endif + + Assert(fixed_part || MyBackendType); + if (!fixed_part) + fixed_part = GetBackendTypeDesc(MyBackendType); + +#ifndef PS_USE_NONE + /* no ps display for stand-alone backend */ + if (!IsUnderPostmaster) + return; + + /* no ps display if you didn't call save_ps_display_args() */ + if (!save_argv) + return; + +#ifdef PS_USE_CLOBBER_ARGV + /* If ps_buffer is a pointer, it might still be null */ + if (!ps_buffer) + return; + + /* make extra argv slots point at end_of_area (a NUL) */ + for (int i = 1; i < save_argc; i++) + save_argv[i] = ps_buffer + ps_buffer_size; +#endif /* PS_USE_CLOBBER_ARGV */ + + /* + * Make fixed prefix of ps display. + */ + +#if defined(PS_USE_SETPROCTITLE) || defined(PS_USE_SETPROCTITLE_FAST) + + /* + * apparently setproctitle() already adds a `progname:' prefix to the ps + * line + */ +#define PROGRAM_NAME_PREFIX "" +#else +#define PROGRAM_NAME_PREFIX "postgres: " +#endif + + if (*cluster_name == '\0') + { + snprintf(ps_buffer, ps_buffer_size, + PROGRAM_NAME_PREFIX "%s ", + fixed_part); + } + else + { + snprintf(ps_buffer, ps_buffer_size, + PROGRAM_NAME_PREFIX "%s: %s ", + cluster_name, fixed_part); + } + + ps_buffer_cur_len = ps_buffer_fixed_size = strlen(ps_buffer); + + /* + * On the first run, force the update. + */ + save_update_process_title = update_process_title; + update_process_title = true; + set_ps_display(""); + update_process_title = save_update_process_title; +#endif /* not PS_USE_NONE */ +} + +#ifndef PS_USE_NONE +/* + * update_ps_display_precheck + * Helper function to determine if updating the process title is + * something that we need to do. + */ +static bool +update_ps_display_precheck(void) +{ + /* update_process_title=off disables updates */ + if (!update_process_title) + return false; + + /* no ps display for stand-alone backend */ + if (!IsUnderPostmaster) + return false; + +#ifdef PS_USE_CLOBBER_ARGV + /* If ps_buffer is a pointer, it might still be null */ + if (!ps_buffer) + return false; +#endif + + return true; +} +#endif /* not PS_USE_NONE */ + +/* + * set_ps_display_suffix + * Adjust the process title to append 'suffix' onto the end with a space + * between it and the current process title. + */ +void +set_ps_display_suffix(const char *suffix) +{ +#ifndef PS_USE_NONE + size_t len; + + /* first, check if we need to update the process title */ + if (!update_ps_display_precheck()) + return; + + /* if there's already a suffix, overwrite it */ + if (ps_buffer_nosuffix_len > 0) + ps_buffer_cur_len = ps_buffer_nosuffix_len; + else + ps_buffer_nosuffix_len = ps_buffer_cur_len; + + len = strlen(suffix); + + /* check if we have enough space to append the suffix */ + if (ps_buffer_cur_len + len + 1 >= ps_buffer_size) + { + /* not enough space. Check the buffer isn't full already */ + if (ps_buffer_cur_len < ps_buffer_size - 1) + { + /* append a space before the suffix */ + ps_buffer[ps_buffer_cur_len++] = ' '; + + /* just add what we can and fill the ps_buffer */ + memcpy(ps_buffer + ps_buffer_cur_len, suffix, + ps_buffer_size - ps_buffer_cur_len - 1); + ps_buffer[ps_buffer_size - 1] = '\0'; + ps_buffer_cur_len = ps_buffer_size - 1; + } + } + else + { + ps_buffer[ps_buffer_cur_len++] = ' '; + memcpy(ps_buffer + ps_buffer_cur_len, suffix, len + 1); + ps_buffer_cur_len = ps_buffer_cur_len + len; + } + + Assert(strlen(ps_buffer) == ps_buffer_cur_len); + + /* and set the new title */ + flush_ps_display(); +#endif /* not PS_USE_NONE */ +} + +/* + * set_ps_display_remove_suffix + * Remove the process display suffix added by set_ps_display_suffix + */ +void +set_ps_display_remove_suffix(void) +{ +#ifndef PS_USE_NONE + /* first, check if we need to update the process title */ + if (!update_ps_display_precheck()) + return; + + /* check we added a suffix */ + if (ps_buffer_nosuffix_len == 0) + return; /* no suffix */ + + /* remove the suffix from ps_buffer */ + ps_buffer[ps_buffer_nosuffix_len] = '\0'; + ps_buffer_cur_len = ps_buffer_nosuffix_len; + ps_buffer_nosuffix_len = 0; + + Assert(ps_buffer_cur_len == strlen(ps_buffer)); + + /* and set the new title */ + flush_ps_display(); +#endif /* not PS_USE_NONE */ +} + +/* + * Call this to update the ps status display to a fixed prefix plus an + * indication of what you're currently doing passed in the argument. + * + * 'len' must be the same as strlen(activity) + */ +void +set_ps_display_with_len(const char *activity, size_t len) +{ + Assert(strlen(activity) == len); + +#ifndef PS_USE_NONE + /* first, check if we need to update the process title */ + if (!update_ps_display_precheck()) + return; + + /* wipe out any suffix when the title is completely changed */ + ps_buffer_nosuffix_len = 0; + + /* Update ps_buffer to contain both fixed part and activity */ + if (ps_buffer_fixed_size + len >= ps_buffer_size) + { + /* handle the case where ps_buffer doesn't have enough space */ + memcpy(ps_buffer + ps_buffer_fixed_size, activity, + ps_buffer_size - ps_buffer_fixed_size - 1); + ps_buffer[ps_buffer_size - 1] = '\0'; + ps_buffer_cur_len = ps_buffer_size - 1; + } + else + { + memcpy(ps_buffer + ps_buffer_fixed_size, activity, len + 1); + ps_buffer_cur_len = ps_buffer_fixed_size + len; + } + Assert(strlen(ps_buffer) == ps_buffer_cur_len); + + /* Transmit new setting to kernel, if necessary */ + flush_ps_display(); +#endif /* not PS_USE_NONE */ +} + +#ifndef PS_USE_NONE +static void +flush_ps_display(void) +{ +#ifdef PS_USE_SETPROCTITLE + setproctitle("%s", ps_buffer); +#elif defined(PS_USE_SETPROCTITLE_FAST) + setproctitle_fast("%s", ps_buffer); +#endif + +#ifdef PS_USE_CLOBBER_ARGV + /* pad unused memory; need only clobber remainder of old status string */ + if (last_status_len > ps_buffer_cur_len) + MemSet(ps_buffer + ps_buffer_cur_len, PS_PADDING, + last_status_len - ps_buffer_cur_len); + last_status_len = ps_buffer_cur_len; +#endif /* PS_USE_CLOBBER_ARGV */ + +#ifdef PS_USE_WIN32 + { + /* + * Win32 does not support showing any changed arguments. To make it at + * all possible to track which backend is doing what, we create a + * named object that can be viewed with for example Process Explorer. + */ + static HANDLE ident_handle = INVALID_HANDLE_VALUE; + char name[PS_BUFFER_SIZE + 32]; + + if (ident_handle != INVALID_HANDLE_VALUE) + CloseHandle(ident_handle); + + sprintf(name, "pgident(%d): %s", MyProcPid, ps_buffer); + + ident_handle = CreateEvent(NULL, TRUE, FALSE, name); + } +#endif /* PS_USE_WIN32 */ +} +#endif /* not PS_USE_NONE */ + +/* + * Returns what's currently in the ps display, in case someone needs + * it. Note that only the activity part is returned. On some platforms + * the string will not be null-terminated, so return the effective + * length into *displen. + */ +const char * +get_ps_display(int *displen) +{ +#ifdef PS_USE_CLOBBER_ARGV + /* If ps_buffer is a pointer, it might still be null */ + if (!ps_buffer) + { + *displen = 0; + return ""; + } +#endif + +#ifndef PS_USE_NONE + *displen = (int) (ps_buffer_cur_len - ps_buffer_fixed_size); + + return ps_buffer + ps_buffer_fixed_size; +#else + *displen = 0; + return ""; +#endif +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/misc/queryenvironment.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/misc/queryenvironment.c new file mode 100644 index 00000000000..430a587948b --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/misc/queryenvironment.c @@ -0,0 +1,144 @@ +/*------------------------------------------------------------------------- + * + * queryenvironment.c + * Query environment, to store context-specific values like ephemeral named + * relations. Initial use is for named tuplestores for delta information + * from "normal" relations. + * + * The initial implementation uses a list because the number of such relations + * in any one context is expected to be very small. If that becomes a + * performance problem, the implementation can be changed with no other impact + * on callers, since this is an opaque structure. This is the reason to + * require a create function. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/misc/queryenvironment.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/table.h" +#include "utils/queryenvironment.h" +#include "utils/rel.h" + +/* + * Private state of a query environment. + */ +struct QueryEnvironment +{ + List *namedRelList; +}; + + +QueryEnvironment * +create_queryEnv(void) +{ + return (QueryEnvironment *) palloc0(sizeof(QueryEnvironment)); +} + +EphemeralNamedRelationMetadata +get_visible_ENR_metadata(QueryEnvironment *queryEnv, const char *refname) +{ + EphemeralNamedRelation enr; + + Assert(refname != NULL); + + if (queryEnv == NULL) + return NULL; + + enr = get_ENR(queryEnv, refname); + + if (enr) + return &(enr->md); + + return NULL; +} + +/* + * Register a named relation for use in the given environment. + * + * If this is intended exclusively for planning purposes, the tstate field can + * be left NULL; + */ +void +register_ENR(QueryEnvironment *queryEnv, EphemeralNamedRelation enr) +{ + Assert(enr != NULL); + Assert(get_ENR(queryEnv, enr->md.name) == NULL); + + queryEnv->namedRelList = lappend(queryEnv->namedRelList, enr); +} + +/* + * Unregister an ephemeral relation by name. This will probably be a rarely + * used function, but seems like it should be provided "just in case". + */ +void +unregister_ENR(QueryEnvironment *queryEnv, const char *name) +{ + EphemeralNamedRelation match; + + match = get_ENR(queryEnv, name); + if (match) + queryEnv->namedRelList = list_delete(queryEnv->namedRelList, match); +} + +/* + * This returns an ENR if there is a name match in the given collection. It + * must quietly return NULL if no match is found. + */ +EphemeralNamedRelation +get_ENR(QueryEnvironment *queryEnv, const char *name) +{ + ListCell *lc; + + Assert(name != NULL); + + if (queryEnv == NULL) + return NULL; + + foreach(lc, queryEnv->namedRelList) + { + EphemeralNamedRelation enr = (EphemeralNamedRelation) lfirst(lc); + + if (strcmp(enr->md.name, name) == 0) + return enr; + } + + return NULL; +} + +/* + * Gets the TupleDesc for a Ephemeral Named Relation, based on which field was + * filled. + * + * When the TupleDesc is based on a relation from the catalogs, we count on + * that relation being used at the same time, so that appropriate locks will + * already be held. Locking here would be too late anyway. + */ +TupleDesc +ENRMetadataGetTupDesc(EphemeralNamedRelationMetadata enrmd) +{ + TupleDesc tupdesc; + + /* One, and only one, of these fields must be filled. */ + Assert((enrmd->reliddesc == InvalidOid) != (enrmd->tupdesc == NULL)); + + if (enrmd->tupdesc != NULL) + tupdesc = enrmd->tupdesc; + else + { + Relation relation; + + relation = table_open(enrmd->reliddesc, NoLock); + tupdesc = relation->rd_att; + table_close(relation, NoLock); + } + + return tupdesc; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/misc/queryjumble.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/misc/queryjumble.c new file mode 100644 index 00000000000..b61403fbe75 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/misc/queryjumble.c @@ -0,0 +1,869 @@ +/*------------------------------------------------------------------------- + * + * queryjumble.c + * Query normalization and fingerprinting. + * + * Normalization is a process whereby similar queries, typically differing only + * in their constants (though the exact rules are somewhat more subtle than + * that) are recognized as equivalent, and are tracked as a single entry. This + * is particularly useful for non-prepared queries. + * + * Normalization is implemented by fingerprinting queries, selectively + * serializing those fields of each query tree's nodes that are judged to be + * essential to the query. This is referred to as a query jumble. This is + * distinct from a regular serialization in that various extraneous + * information is ignored as irrelevant or not essential to the query, such + * as the collations of Vars and, most notably, the values of constants. + * + * This jumble is acquired at the end of parse analysis of each query, and + * a 64-bit hash of it is stored into the query's Query.queryId field. + * The server then copies this value around, making it available in plan + * tree(s) generated from the query. The executor can then use this value + * to blame query costs on the proper queryId. + * + * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/misc/queryjumble.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "common/hashfn.h" +#include "miscadmin.h" +#include "parser/scansup.h" +#include "utils/queryjumble.h" + +#define JUMBLE_SIZE 1024 /* query serialization buffer size */ + +/* GUC parameters */ +__thread int compute_query_id = COMPUTE_QUERY_ID_AUTO; + +/* True when compute_query_id is ON, or AUTO and a module requests them */ +__thread bool query_id_enabled = false; + +static uint64 compute_utility_query_id(const char *str, int query_location, int query_len); +static void AppendJumble(JumbleState *jstate, + const unsigned char *item, Size size); +static void JumbleQueryInternal(JumbleState *jstate, Query *query); +static void JumbleRangeTable(JumbleState *jstate, List *rtable); +static void JumbleRowMarks(JumbleState *jstate, List *rowMarks); +static void JumbleExpr(JumbleState *jstate, Node *node); +static void RecordConstLocation(JumbleState *jstate, int location); + +/* + * Given a possibly multi-statement source string, confine our attention to the + * relevant part of the string. + */ +const char * +CleanQuerytext(const char *query, int *location, int *len) +{ + int query_location = *location; + int query_len = *len; + + /* First apply starting offset, unless it's -1 (unknown). */ + if (query_location >= 0) + { + Assert(query_location <= strlen(query)); + query += query_location; + /* Length of 0 (or -1) means "rest of string" */ + if (query_len <= 0) + query_len = strlen(query); + else + Assert(query_len <= strlen(query)); + } + else + { + /* If query location is unknown, distrust query_len as well */ + query_location = 0; + query_len = strlen(query); + } + + /* + * Discard leading and trailing whitespace, too. Use scanner_isspace() + * not libc's isspace(), because we want to match the lexer's behavior. + */ + while (query_len > 0 && scanner_isspace(query[0])) + query++, query_location++, query_len--; + while (query_len > 0 && scanner_isspace(query[query_len - 1])) + query_len--; + + *location = query_location; + *len = query_len; + + return query; +} + +JumbleState * +JumbleQuery(Query *query, const char *querytext) +{ + JumbleState *jstate = NULL; + + Assert(IsQueryIdEnabled()); + + if (query->utilityStmt) + { + query->queryId = compute_utility_query_id(querytext, + query->stmt_location, + query->stmt_len); + } + else + { + jstate = (JumbleState *) palloc(sizeof(JumbleState)); + + /* Set up workspace for query jumbling */ + jstate->jumble = (unsigned char *) palloc(JUMBLE_SIZE); + jstate->jumble_len = 0; + jstate->clocations_buf_size = 32; + jstate->clocations = (LocationLen *) + palloc(jstate->clocations_buf_size * sizeof(LocationLen)); + jstate->clocations_count = 0; + jstate->highest_extern_param_id = 0; + + /* Compute query ID and mark the Query node with it */ + JumbleQueryInternal(jstate, query); + query->queryId = DatumGetUInt64(hash_any_extended(jstate->jumble, + jstate->jumble_len, + 0)); + + /* + * If we are unlucky enough to get a hash of zero, use 1 instead, to + * prevent confusion with the utility-statement case. + */ + if (query->queryId == UINT64CONST(0)) + query->queryId = UINT64CONST(1); + } + + return jstate; +} + +/* + * Enables query identifier computation. + * + * Third-party plugins can use this function to inform core that they require + * a query identifier to be computed. + */ +void +EnableQueryId(void) +{ + if (compute_query_id != COMPUTE_QUERY_ID_OFF) + query_id_enabled = true; +} + +/* + * Compute a query identifier for the given utility query string. + */ +static uint64 +compute_utility_query_id(const char *query_text, int query_location, int query_len) +{ + uint64 queryId; + const char *sql; + + /* + * Confine our attention to the relevant part of the string, if the query + * is a portion of a multi-statement source string. + */ + sql = CleanQuerytext(query_text, &query_location, &query_len); + + queryId = DatumGetUInt64(hash_any_extended((const unsigned char *) sql, + query_len, 0)); + + /* + * If we are unlucky enough to get a hash of zero(invalid), use queryID as + * 2 instead, queryID 1 is already in use for normal statements. + */ + if (queryId == UINT64CONST(0)) + queryId = UINT64CONST(2); + + return queryId; +} + +/* + * AppendJumble: Append a value that is substantive in a given query to + * the current jumble. + */ +static void +AppendJumble(JumbleState *jstate, const unsigned char *item, Size size) +{ + unsigned char *jumble = jstate->jumble; + Size jumble_len = jstate->jumble_len; + + /* + * Whenever the jumble buffer is full, we hash the current contents and + * reset the buffer to contain just that hash value, thus relying on the + * hash to summarize everything so far. + */ + while (size > 0) + { + Size part_size; + + if (jumble_len >= JUMBLE_SIZE) + { + uint64 start_hash; + + start_hash = DatumGetUInt64(hash_any_extended(jumble, + JUMBLE_SIZE, 0)); + memcpy(jumble, &start_hash, sizeof(start_hash)); + jumble_len = sizeof(start_hash); + } + part_size = Min(size, JUMBLE_SIZE - jumble_len); + memcpy(jumble + jumble_len, item, part_size); + jumble_len += part_size; + item += part_size; + size -= part_size; + } + jstate->jumble_len = jumble_len; +} + +/* + * Wrappers around AppendJumble to encapsulate details of serialization + * of individual local variable elements. + */ +#define APP_JUMB(item) \ + AppendJumble(jstate, (const unsigned char *) &(item), sizeof(item)) +#define APP_JUMB_STRING(str) \ + AppendJumble(jstate, (const unsigned char *) (str), strlen(str) + 1) + +/* + * JumbleQueryInternal: Selectively serialize the query tree, appending + * significant data to the "query jumble" while ignoring nonsignificant data. + * + * Rule of thumb for what to include is that we should ignore anything not + * semantically significant (such as alias names) as well as anything that can + * be deduced from child nodes (else we'd just be double-hashing that piece + * of information). + */ +static void +JumbleQueryInternal(JumbleState *jstate, Query *query) +{ + Assert(IsA(query, Query)); + Assert(query->utilityStmt == NULL); + + APP_JUMB(query->commandType); + /* resultRelation is usually predictable from commandType */ + JumbleExpr(jstate, (Node *) query->cteList); + JumbleRangeTable(jstate, query->rtable); + JumbleExpr(jstate, (Node *) query->jointree); + JumbleExpr(jstate, (Node *) query->mergeActionList); + JumbleExpr(jstate, (Node *) query->targetList); + JumbleExpr(jstate, (Node *) query->onConflict); + JumbleExpr(jstate, (Node *) query->returningList); + JumbleExpr(jstate, (Node *) query->groupClause); + APP_JUMB(query->groupDistinct); + JumbleExpr(jstate, (Node *) query->groupingSets); + JumbleExpr(jstate, query->havingQual); + JumbleExpr(jstate, (Node *) query->windowClause); + JumbleExpr(jstate, (Node *) query->distinctClause); + JumbleExpr(jstate, (Node *) query->sortClause); + JumbleExpr(jstate, query->limitOffset); + JumbleExpr(jstate, query->limitCount); + APP_JUMB(query->limitOption); + JumbleRowMarks(jstate, query->rowMarks); + JumbleExpr(jstate, query->setOperations); +} + +/* + * Jumble a range table + */ +static void +JumbleRangeTable(JumbleState *jstate, List *rtable) +{ + ListCell *lc; + + foreach(lc, rtable) + { + RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc); + + APP_JUMB(rte->rtekind); + switch (rte->rtekind) + { + case RTE_RELATION: + APP_JUMB(rte->relid); + JumbleExpr(jstate, (Node *) rte->tablesample); + APP_JUMB(rte->inh); + break; + case RTE_SUBQUERY: + JumbleQueryInternal(jstate, rte->subquery); + break; + case RTE_JOIN: + APP_JUMB(rte->jointype); + break; + case RTE_FUNCTION: + JumbleExpr(jstate, (Node *) rte->functions); + break; + case RTE_TABLEFUNC: + JumbleExpr(jstate, (Node *) rte->tablefunc); + break; + case RTE_VALUES: + JumbleExpr(jstate, (Node *) rte->values_lists); + break; + case RTE_CTE: + + /* + * Depending on the CTE name here isn't ideal, but it's the + * only info we have to identify the referenced WITH item. + */ + APP_JUMB_STRING(rte->ctename); + APP_JUMB(rte->ctelevelsup); + break; + case RTE_NAMEDTUPLESTORE: + APP_JUMB_STRING(rte->enrname); + break; + case RTE_RESULT: + break; + default: + elog(ERROR, "unrecognized RTE kind: %d", (int) rte->rtekind); + break; + } + } +} + +/* + * Jumble a rowMarks list + */ +static void +JumbleRowMarks(JumbleState *jstate, List *rowMarks) +{ + ListCell *lc; + + foreach(lc, rowMarks) + { + RowMarkClause *rowmark = lfirst_node(RowMarkClause, lc); + + if (!rowmark->pushedDown) + { + APP_JUMB(rowmark->rti); + APP_JUMB(rowmark->strength); + APP_JUMB(rowmark->waitPolicy); + } + } +} + +/* + * Jumble an expression tree + * + * In general this function should handle all the same node types that + * expression_tree_walker() does, and therefore it's coded to be as parallel + * to that function as possible. However, since we are only invoked on + * queries immediately post-parse-analysis, we need not handle node types + * that only appear in planning. + * + * Note: the reason we don't simply use expression_tree_walker() is that the + * point of that function is to support tree walkers that don't care about + * most tree node types, but here we care about all types. We should complain + * about any unrecognized node type. + */ +static void +JumbleExpr(JumbleState *jstate, Node *node) +{ + ListCell *temp; + + if (node == NULL) + return; + + /* Guard against stack overflow due to overly complex expressions */ + check_stack_depth(); + + /* + * We always emit the node's NodeTag, then any additional fields that are + * considered significant, and then we recurse to any child nodes. + */ + APP_JUMB(node->type); + + switch (nodeTag(node)) + { + case T_Var: + { + Var *var = (Var *) node; + + APP_JUMB(var->varno); + APP_JUMB(var->varattno); + APP_JUMB(var->varlevelsup); + } + break; + case T_Const: + { + Const *c = (Const *) node; + + /* We jumble only the constant's type, not its value */ + APP_JUMB(c->consttype); + /* Also, record its parse location for query normalization */ + RecordConstLocation(jstate, c->location); + } + break; + case T_Param: + { + Param *p = (Param *) node; + + APP_JUMB(p->paramkind); + APP_JUMB(p->paramid); + APP_JUMB(p->paramtype); + /* Also, track the highest external Param id */ + if (p->paramkind == PARAM_EXTERN && + p->paramid > jstate->highest_extern_param_id) + jstate->highest_extern_param_id = p->paramid; + } + break; + case T_Aggref: + { + Aggref *expr = (Aggref *) node; + + APP_JUMB(expr->aggfnoid); + JumbleExpr(jstate, (Node *) expr->aggdirectargs); + JumbleExpr(jstate, (Node *) expr->args); + JumbleExpr(jstate, (Node *) expr->aggorder); + JumbleExpr(jstate, (Node *) expr->aggdistinct); + JumbleExpr(jstate, (Node *) expr->aggfilter); + } + break; + case T_GroupingFunc: + { + GroupingFunc *grpnode = (GroupingFunc *) node; + + JumbleExpr(jstate, (Node *) grpnode->refs); + APP_JUMB(grpnode->agglevelsup); + } + break; + case T_WindowFunc: + { + WindowFunc *expr = (WindowFunc *) node; + + APP_JUMB(expr->winfnoid); + APP_JUMB(expr->winref); + JumbleExpr(jstate, (Node *) expr->args); + JumbleExpr(jstate, (Node *) expr->aggfilter); + } + break; + case T_SubscriptingRef: + { + SubscriptingRef *sbsref = (SubscriptingRef *) node; + + JumbleExpr(jstate, (Node *) sbsref->refupperindexpr); + JumbleExpr(jstate, (Node *) sbsref->reflowerindexpr); + JumbleExpr(jstate, (Node *) sbsref->refexpr); + JumbleExpr(jstate, (Node *) sbsref->refassgnexpr); + } + break; + case T_FuncExpr: + { + FuncExpr *expr = (FuncExpr *) node; + + APP_JUMB(expr->funcid); + JumbleExpr(jstate, (Node *) expr->args); + } + break; + case T_NamedArgExpr: + { + NamedArgExpr *nae = (NamedArgExpr *) node; + + APP_JUMB(nae->argnumber); + JumbleExpr(jstate, (Node *) nae->arg); + } + break; + case T_OpExpr: + case T_DistinctExpr: /* struct-equivalent to OpExpr */ + case T_NullIfExpr: /* struct-equivalent to OpExpr */ + { + OpExpr *expr = (OpExpr *) node; + + APP_JUMB(expr->opno); + JumbleExpr(jstate, (Node *) expr->args); + } + break; + case T_ScalarArrayOpExpr: + { + ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) node; + + APP_JUMB(expr->opno); + APP_JUMB(expr->useOr); + JumbleExpr(jstate, (Node *) expr->args); + } + break; + case T_BoolExpr: + { + BoolExpr *expr = (BoolExpr *) node; + + APP_JUMB(expr->boolop); + JumbleExpr(jstate, (Node *) expr->args); + } + break; + case T_SubLink: + { + SubLink *sublink = (SubLink *) node; + + APP_JUMB(sublink->subLinkType); + APP_JUMB(sublink->subLinkId); + JumbleExpr(jstate, (Node *) sublink->testexpr); + JumbleQueryInternal(jstate, castNode(Query, sublink->subselect)); + } + break; + case T_FieldSelect: + { + FieldSelect *fs = (FieldSelect *) node; + + APP_JUMB(fs->fieldnum); + JumbleExpr(jstate, (Node *) fs->arg); + } + break; + case T_FieldStore: + { + FieldStore *fstore = (FieldStore *) node; + + JumbleExpr(jstate, (Node *) fstore->arg); + JumbleExpr(jstate, (Node *) fstore->newvals); + } + break; + case T_RelabelType: + { + RelabelType *rt = (RelabelType *) node; + + APP_JUMB(rt->resulttype); + JumbleExpr(jstate, (Node *) rt->arg); + } + break; + case T_CoerceViaIO: + { + CoerceViaIO *cio = (CoerceViaIO *) node; + + APP_JUMB(cio->resulttype); + JumbleExpr(jstate, (Node *) cio->arg); + } + break; + case T_ArrayCoerceExpr: + { + ArrayCoerceExpr *acexpr = (ArrayCoerceExpr *) node; + + APP_JUMB(acexpr->resulttype); + JumbleExpr(jstate, (Node *) acexpr->arg); + JumbleExpr(jstate, (Node *) acexpr->elemexpr); + } + break; + case T_ConvertRowtypeExpr: + { + ConvertRowtypeExpr *crexpr = (ConvertRowtypeExpr *) node; + + APP_JUMB(crexpr->resulttype); + JumbleExpr(jstate, (Node *) crexpr->arg); + } + break; + case T_CollateExpr: + { + CollateExpr *ce = (CollateExpr *) node; + + APP_JUMB(ce->collOid); + JumbleExpr(jstate, (Node *) ce->arg); + } + break; + case T_CaseExpr: + { + CaseExpr *caseexpr = (CaseExpr *) node; + + JumbleExpr(jstate, (Node *) caseexpr->arg); + foreach(temp, caseexpr->args) + { + CaseWhen *when = lfirst_node(CaseWhen, temp); + + JumbleExpr(jstate, (Node *) when->expr); + JumbleExpr(jstate, (Node *) when->result); + } + JumbleExpr(jstate, (Node *) caseexpr->defresult); + } + break; + case T_CaseTestExpr: + { + CaseTestExpr *ct = (CaseTestExpr *) node; + + APP_JUMB(ct->typeId); + } + break; + case T_ArrayExpr: + JumbleExpr(jstate, (Node *) ((ArrayExpr *) node)->elements); + break; + case T_RowExpr: + JumbleExpr(jstate, (Node *) ((RowExpr *) node)->args); + break; + case T_RowCompareExpr: + { + RowCompareExpr *rcexpr = (RowCompareExpr *) node; + + APP_JUMB(rcexpr->rctype); + JumbleExpr(jstate, (Node *) rcexpr->largs); + JumbleExpr(jstate, (Node *) rcexpr->rargs); + } + break; + case T_CoalesceExpr: + JumbleExpr(jstate, (Node *) ((CoalesceExpr *) node)->args); + break; + case T_MinMaxExpr: + { + MinMaxExpr *mmexpr = (MinMaxExpr *) node; + + APP_JUMB(mmexpr->op); + JumbleExpr(jstate, (Node *) mmexpr->args); + } + break; + case T_SQLValueFunction: + { + SQLValueFunction *svf = (SQLValueFunction *) node; + + APP_JUMB(svf->op); + /* type is fully determined by op */ + APP_JUMB(svf->typmod); + } + break; + case T_XmlExpr: + { + XmlExpr *xexpr = (XmlExpr *) node; + + APP_JUMB(xexpr->op); + JumbleExpr(jstate, (Node *) xexpr->named_args); + JumbleExpr(jstate, (Node *) xexpr->args); + } + break; + case T_NullTest: + { + NullTest *nt = (NullTest *) node; + + APP_JUMB(nt->nulltesttype); + JumbleExpr(jstate, (Node *) nt->arg); + } + break; + case T_BooleanTest: + { + BooleanTest *bt = (BooleanTest *) node; + + APP_JUMB(bt->booltesttype); + JumbleExpr(jstate, (Node *) bt->arg); + } + break; + case T_CoerceToDomain: + { + CoerceToDomain *cd = (CoerceToDomain *) node; + + APP_JUMB(cd->resulttype); + JumbleExpr(jstate, (Node *) cd->arg); + } + break; + case T_CoerceToDomainValue: + { + CoerceToDomainValue *cdv = (CoerceToDomainValue *) node; + + APP_JUMB(cdv->typeId); + } + break; + case T_SetToDefault: + { + SetToDefault *sd = (SetToDefault *) node; + + APP_JUMB(sd->typeId); + } + break; + case T_CurrentOfExpr: + { + CurrentOfExpr *ce = (CurrentOfExpr *) node; + + APP_JUMB(ce->cvarno); + if (ce->cursor_name) + APP_JUMB_STRING(ce->cursor_name); + APP_JUMB(ce->cursor_param); + } + break; + case T_NextValueExpr: + { + NextValueExpr *nve = (NextValueExpr *) node; + + APP_JUMB(nve->seqid); + APP_JUMB(nve->typeId); + } + break; + case T_InferenceElem: + { + InferenceElem *ie = (InferenceElem *) node; + + APP_JUMB(ie->infercollid); + APP_JUMB(ie->inferopclass); + JumbleExpr(jstate, ie->expr); + } + break; + case T_TargetEntry: + { + TargetEntry *tle = (TargetEntry *) node; + + APP_JUMB(tle->resno); + APP_JUMB(tle->ressortgroupref); + JumbleExpr(jstate, (Node *) tle->expr); + } + break; + case T_RangeTblRef: + { + RangeTblRef *rtr = (RangeTblRef *) node; + + APP_JUMB(rtr->rtindex); + } + break; + case T_JoinExpr: + { + JoinExpr *join = (JoinExpr *) node; + + APP_JUMB(join->jointype); + APP_JUMB(join->isNatural); + APP_JUMB(join->rtindex); + JumbleExpr(jstate, join->larg); + JumbleExpr(jstate, join->rarg); + JumbleExpr(jstate, join->quals); + } + break; + case T_FromExpr: + { + FromExpr *from = (FromExpr *) node; + + JumbleExpr(jstate, (Node *) from->fromlist); + JumbleExpr(jstate, from->quals); + } + break; + case T_OnConflictExpr: + { + OnConflictExpr *conf = (OnConflictExpr *) node; + + APP_JUMB(conf->action); + JumbleExpr(jstate, (Node *) conf->arbiterElems); + JumbleExpr(jstate, conf->arbiterWhere); + JumbleExpr(jstate, (Node *) conf->onConflictSet); + JumbleExpr(jstate, conf->onConflictWhere); + APP_JUMB(conf->constraint); + APP_JUMB(conf->exclRelIndex); + JumbleExpr(jstate, (Node *) conf->exclRelTlist); + } + break; + case T_MergeAction: + { + MergeAction *mergeaction = (MergeAction *) node; + + APP_JUMB(mergeaction->matched); + APP_JUMB(mergeaction->commandType); + JumbleExpr(jstate, mergeaction->qual); + JumbleExpr(jstate, (Node *) mergeaction->targetList); + } + break; + case T_List: + foreach(temp, (List *) node) + { + JumbleExpr(jstate, (Node *) lfirst(temp)); + } + break; + case T_IntList: + foreach(temp, (List *) node) + { + APP_JUMB(lfirst_int(temp)); + } + break; + case T_SortGroupClause: + { + SortGroupClause *sgc = (SortGroupClause *) node; + + APP_JUMB(sgc->tleSortGroupRef); + APP_JUMB(sgc->eqop); + APP_JUMB(sgc->sortop); + APP_JUMB(sgc->nulls_first); + } + break; + case T_GroupingSet: + { + GroupingSet *gsnode = (GroupingSet *) node; + + JumbleExpr(jstate, (Node *) gsnode->content); + } + break; + case T_WindowClause: + { + WindowClause *wc = (WindowClause *) node; + + APP_JUMB(wc->winref); + APP_JUMB(wc->frameOptions); + JumbleExpr(jstate, (Node *) wc->partitionClause); + JumbleExpr(jstate, (Node *) wc->orderClause); + JumbleExpr(jstate, wc->startOffset); + JumbleExpr(jstate, wc->endOffset); + } + break; + case T_CommonTableExpr: + { + CommonTableExpr *cte = (CommonTableExpr *) node; + + /* we store the string name because RTE_CTE RTEs need it */ + APP_JUMB_STRING(cte->ctename); + APP_JUMB(cte->ctematerialized); + JumbleQueryInternal(jstate, castNode(Query, cte->ctequery)); + } + break; + case T_SetOperationStmt: + { + SetOperationStmt *setop = (SetOperationStmt *) node; + + APP_JUMB(setop->op); + APP_JUMB(setop->all); + JumbleExpr(jstate, setop->larg); + JumbleExpr(jstate, setop->rarg); + } + break; + case T_RangeTblFunction: + { + RangeTblFunction *rtfunc = (RangeTblFunction *) node; + + JumbleExpr(jstate, rtfunc->funcexpr); + } + break; + case T_TableFunc: + { + TableFunc *tablefunc = (TableFunc *) node; + + JumbleExpr(jstate, tablefunc->docexpr); + JumbleExpr(jstate, tablefunc->rowexpr); + JumbleExpr(jstate, (Node *) tablefunc->colexprs); + } + break; + case T_TableSampleClause: + { + TableSampleClause *tsc = (TableSampleClause *) node; + + APP_JUMB(tsc->tsmhandler); + JumbleExpr(jstate, (Node *) tsc->args); + JumbleExpr(jstate, (Node *) tsc->repeatable); + } + break; + default: + /* Only a warning, since we can stumble along anyway */ + elog(WARNING, "unrecognized node type: %d", + (int) nodeTag(node)); + break; + } +} + +/* + * Record location of constant within query string of query tree + * that is currently being walked. + */ +static void +RecordConstLocation(JumbleState *jstate, int location) +{ + /* -1 indicates unknown or undefined location */ + if (location >= 0) + { + /* enlarge array if needed */ + if (jstate->clocations_count >= jstate->clocations_buf_size) + { + jstate->clocations_buf_size *= 2; + jstate->clocations = (LocationLen *) + repalloc(jstate->clocations, + jstate->clocations_buf_size * + sizeof(LocationLen)); + } + jstate->clocations[jstate->clocations_count].location = location; + /* initialize lengths to -1 to simplify third-party module usage */ + jstate->clocations[jstate->clocations_count].length = -1; + jstate->clocations_count++; + } +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/misc/rls.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/misc/rls.c new file mode 100644 index 00000000000..b8c9a133a8b --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/misc/rls.c @@ -0,0 +1,167 @@ +/*------------------------------------------------------------------------- + * + * rls.c + * RLS-related utility functions. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/misc/rls.c + * + *------------------------------------------------------------------------- +*/ +#include "postgres.h" + +#include "access/htup.h" +#include "access/htup_details.h" +#include "access/transam.h" +#include "catalog/namespace.h" +#include "catalog/pg_class.h" +#include "miscadmin.h" +#include "utils/acl.h" +#include "utils/builtins.h" +#include "utils/lsyscache.h" +#include "utils/rls.h" +#include "utils/syscache.h" +#include "utils/varlena.h" + + +/* + * check_enable_rls + * + * Determine, based on the relation, row_security setting, and current role, + * if RLS is applicable to this query. RLS_NONE_ENV indicates that, while + * RLS is not to be added for this query, a change in the environment may change + * that. RLS_NONE means that RLS is not on the relation at all and therefore + * we don't need to worry about it. RLS_ENABLED means RLS should be implemented + * for the table and the plan cache needs to be invalidated if the environment + * changes. + * + * Handle checking as another role via checkAsUser (for views, etc). Pass + * InvalidOid to check the current user. + * + * If noError is set to 'true' then we just return RLS_ENABLED instead of doing + * an ereport() if the user has attempted to bypass RLS and they are not + * allowed to. This allows users to check if RLS is enabled without having to + * deal with the actual error case (eg: error cases which are trying to decide + * if the user should get data from the relation back as part of the error). + */ +int +check_enable_rls(Oid relid, Oid checkAsUser, bool noError) +{ + Oid user_id = OidIsValid(checkAsUser) ? checkAsUser : GetUserId(); + HeapTuple tuple; + Form_pg_class classform; + bool relrowsecurity; + bool relforcerowsecurity; + bool amowner; + + /* Nothing to do for built-in relations */ + if (relid < (Oid) FirstNormalObjectId) + return RLS_NONE; + + /* Fetch relation's relrowsecurity and relforcerowsecurity flags */ + tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(tuple)) + return RLS_NONE; + classform = (Form_pg_class) GETSTRUCT(tuple); + + relrowsecurity = classform->relrowsecurity; + relforcerowsecurity = classform->relforcerowsecurity; + + ReleaseSysCache(tuple); + + /* Nothing to do if the relation does not have RLS */ + if (!relrowsecurity) + return RLS_NONE; + + /* + * BYPASSRLS users always bypass RLS. Note that superusers are always + * considered to have BYPASSRLS. + * + * Return RLS_NONE_ENV to indicate that this decision depends on the + * environment (in this case, the user_id). + */ + if (has_bypassrls_privilege(user_id)) + return RLS_NONE_ENV; + + /* + * Table owners generally bypass RLS, except if the table has been set (by + * an owner) to FORCE ROW SECURITY, and this is not a referential + * integrity check. + * + * Return RLS_NONE_ENV to indicate that this decision depends on the + * environment (in this case, the user_id). + */ + amowner = object_ownercheck(RelationRelationId, relid, user_id); + if (amowner) + { + /* + * If FORCE ROW LEVEL SECURITY has been set on the relation then we + * should return RLS_ENABLED to indicate that RLS should be applied. + * If not, or if we are in an InNoForceRLSOperation context, we return + * RLS_NONE_ENV. + * + * InNoForceRLSOperation indicates that we should not apply RLS even + * if the table has FORCE RLS set - IF the current user is the owner. + * This is specifically to ensure that referential integrity checks + * are able to still run correctly. + * + * This is intentionally only done after we have checked that the user + * is the table owner, which should always be the case for referential + * integrity checks. + */ + if (!relforcerowsecurity || InNoForceRLSOperation()) + return RLS_NONE_ENV; + } + + /* + * We should apply RLS. However, the user may turn off the row_security + * GUC to get a forced error instead. + */ + if (!row_security && !noError) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("query would be affected by row-level security policy for table \"%s\"", + get_rel_name(relid)), + amowner ? errhint("To disable the policy for the table's owner, use ALTER TABLE NO FORCE ROW LEVEL SECURITY.") : 0)); + + /* RLS should be fully enabled for this relation. */ + return RLS_ENABLED; +} + +/* + * row_security_active + * + * check_enable_rls wrapped as a SQL callable function except + * RLS_NONE_ENV and RLS_NONE are the same for this purpose. + */ +Datum +row_security_active(PG_FUNCTION_ARGS) +{ + /* By OID */ + Oid tableoid = PG_GETARG_OID(0); + int rls_status; + + rls_status = check_enable_rls(tableoid, InvalidOid, true); + PG_RETURN_BOOL(rls_status == RLS_ENABLED); +} + +Datum +row_security_active_name(PG_FUNCTION_ARGS) +{ + /* By qualified name */ + text *tablename = PG_GETARG_TEXT_PP(0); + RangeVar *tablerel; + Oid tableoid; + int rls_status; + + /* Look up table name. Can't lock it - we might not have privileges. */ + tablerel = makeRangeVarFromNameList(textToQualifiedNameList(tablename)); + tableoid = RangeVarGetRelid(tablerel, NoLock, false); + + rls_status = check_enable_rls(tableoid, InvalidOid, true); + PG_RETURN_BOOL(rls_status == RLS_ENABLED); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/misc/sampling.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/misc/sampling.c new file mode 100644 index 00000000000..abc3cdd4961 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/misc/sampling.c @@ -0,0 +1,304 @@ +/*------------------------------------------------------------------------- + * + * sampling.c + * Relation block sampling routines. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/misc/sampling.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include <math.h> + +#include "utils/sampling.h" + + +/* + * BlockSampler_Init -- prepare for random sampling of blocknumbers + * + * BlockSampler provides algorithm for block level sampling of a relation + * as discussed on pgsql-hackers 2004-04-02 (subject "Large DB") + * It selects a random sample of samplesize blocks out of + * the nblocks blocks in the table. If the table has less than + * samplesize blocks, all blocks are selected. + * + * Since we know the total number of blocks in advance, we can use the + * straightforward Algorithm S from Knuth 3.4.2, rather than Vitter's + * algorithm. + * + * Returns the number of blocks that BlockSampler_Next will return. + */ +BlockNumber +BlockSampler_Init(BlockSampler bs, BlockNumber nblocks, int samplesize, + uint32 randseed) +{ + bs->N = nblocks; /* measured table size */ + + /* + * If we decide to reduce samplesize for tables that have less or not much + * more than samplesize blocks, here is the place to do it. + */ + bs->n = samplesize; + bs->t = 0; /* blocks scanned so far */ + bs->m = 0; /* blocks selected so far */ + + sampler_random_init_state(randseed, &bs->randstate); + + return Min(bs->n, bs->N); +} + +bool +BlockSampler_HasMore(BlockSampler bs) +{ + return (bs->t < bs->N) && (bs->m < bs->n); +} + +BlockNumber +BlockSampler_Next(BlockSampler bs) +{ + BlockNumber K = bs->N - bs->t; /* remaining blocks */ + int k = bs->n - bs->m; /* blocks still to sample */ + double p; /* probability to skip block */ + double V; /* random */ + + Assert(BlockSampler_HasMore(bs)); /* hence K > 0 and k > 0 */ + + if ((BlockNumber) k >= K) + { + /* need all the rest */ + bs->m++; + return bs->t++; + } + + /*---------- + * It is not obvious that this code matches Knuth's Algorithm S. + * Knuth says to skip the current block with probability 1 - k/K. + * If we are to skip, we should advance t (hence decrease K), and + * repeat the same probabilistic test for the next block. The naive + * implementation thus requires a sampler_random_fract() call for each + * block number. But we can reduce this to one sampler_random_fract() + * call per selected block, by noting that each time the while-test + * succeeds, we can reinterpret V as a uniform random number in the range + * 0 to p. Therefore, instead of choosing a new V, we just adjust p to be + * the appropriate fraction of its former value, and our next loop + * makes the appropriate probabilistic test. + * + * We have initially K > k > 0. If the loop reduces K to equal k, + * the next while-test must fail since p will become exactly zero + * (we assume there will not be roundoff error in the division). + * (Note: Knuth suggests a "<=" loop condition, but we use "<" just + * to be doubly sure about roundoff error.) Therefore K cannot become + * less than k, which means that we cannot fail to select enough blocks. + *---------- + */ + V = sampler_random_fract(&bs->randstate); + p = 1.0 - (double) k / (double) K; + while (V < p) + { + /* skip */ + bs->t++; + K--; /* keep K == N - t */ + + /* adjust p to be new cutoff point in reduced range */ + p *= 1.0 - (double) k / (double) K; + } + + /* select */ + bs->m++; + return bs->t++; +} + +/* + * These two routines embody Algorithm Z from "Random sampling with a + * reservoir" by Jeffrey S. Vitter, in ACM Trans. Math. Softw. 11, 1 + * (Mar. 1985), Pages 37-57. Vitter describes his algorithm in terms + * of the count S of records to skip before processing another record. + * It is computed primarily based on t, the number of records already read. + * The only extra state needed between calls is W, a random state variable. + * + * reservoir_init_selection_state computes the initial W value. + * + * Given that we've already read t records (t >= n), reservoir_get_next_S + * determines the number of records to skip before the next record is + * processed. + */ +void +reservoir_init_selection_state(ReservoirState rs, int n) +{ + /* + * Reservoir sampling is not used anywhere where it would need to return + * repeatable results so we can initialize it randomly. + */ + sampler_random_init_state(pg_prng_uint32(&pg_global_prng_state), + &rs->randstate); + + /* Initial value of W (for use when Algorithm Z is first applied) */ + rs->W = exp(-log(sampler_random_fract(&rs->randstate)) / n); +} + +double +reservoir_get_next_S(ReservoirState rs, double t, int n) +{ + double S; + + /* The magic constant here is T from Vitter's paper */ + if (t <= (22.0 * n)) + { + /* Process records using Algorithm X until t is large enough */ + double V, + quot; + + V = sampler_random_fract(&rs->randstate); /* Generate V */ + S = 0; + t += 1; + /* Note: "num" in Vitter's code is always equal to t - n */ + quot = (t - (double) n) / t; + /* Find min S satisfying (4.1) */ + while (quot > V) + { + S += 1; + t += 1; + quot *= (t - (double) n) / t; + } + } + else + { + /* Now apply Algorithm Z */ + double W = rs->W; + double term = t - (double) n + 1; + + for (;;) + { + double numer, + numer_lim, + denom; + double U, + X, + lhs, + rhs, + y, + tmp; + + /* Generate U and X */ + U = sampler_random_fract(&rs->randstate); + X = t * (W - 1.0); + S = floor(X); /* S is tentatively set to floor(X) */ + /* Test if U <= h(S)/cg(X) in the manner of (6.3) */ + tmp = (t + 1) / term; + lhs = exp(log(((U * tmp * tmp) * (term + S)) / (t + X)) / n); + rhs = (((t + X) / (term + S)) * term) / t; + if (lhs <= rhs) + { + W = rhs / lhs; + break; + } + /* Test if U <= f(S)/cg(X) */ + y = (((U * (t + 1)) / term) * (t + S + 1)) / (t + X); + if ((double) n < S) + { + denom = t; + numer_lim = term + S; + } + else + { + denom = t - (double) n + S; + numer_lim = t + 1; + } + for (numer = t + S; numer >= numer_lim; numer -= 1) + { + y *= numer / denom; + denom -= 1; + } + W = exp(-log(sampler_random_fract(&rs->randstate)) / n); /* Generate W in advance */ + if (exp(log(y) / n) <= (t + X) / t) + break; + } + rs->W = W; + } + return S; +} + + +/*---------- + * Random number generator used by sampling + *---------- + */ +void +sampler_random_init_state(uint32 seed, pg_prng_state *randstate) +{ + pg_prng_seed(randstate, (uint64) seed); +} + +/* Select a random value R uniformly distributed in (0 - 1) */ +double +sampler_random_fract(pg_prng_state *randstate) +{ + double res; + + /* pg_prng_double returns a value in [0.0 - 1.0), so we must reject 0.0 */ + do + { + res = pg_prng_double(randstate); + } while (unlikely(res == 0.0)); + return res; +} + + +/* + * Backwards-compatible API for block sampling + * + * This code is now deprecated, but since it's still in use by many FDWs, + * we should keep it for awhile at least. The functionality is the same as + * sampler_random_fract/reservoir_init_selection_state/reservoir_get_next_S, + * except that a common random state is used across all callers. + */ +static __thread ReservoirStateData oldrs; +static __thread bool oldrs_initialized = false; + +double +anl_random_fract(void) +{ + /* initialize if first time through */ + if (unlikely(!oldrs_initialized)) + { + sampler_random_init_state(pg_prng_uint32(&pg_global_prng_state), + &oldrs.randstate); + oldrs_initialized = true; + } + + /* and compute a random fraction */ + return sampler_random_fract(&oldrs.randstate); +} + +double +anl_init_selection_state(int n) +{ + /* initialize if first time through */ + if (unlikely(!oldrs_initialized)) + { + sampler_random_init_state(pg_prng_uint32(&pg_global_prng_state), + &oldrs.randstate); + oldrs_initialized = true; + } + + /* Initial value of W (for use when Algorithm Z is first applied) */ + return exp(-log(sampler_random_fract(&oldrs.randstate)) / n); +} + +double +anl_get_next_S(double t, int n, double *stateptr) +{ + double result; + + oldrs.W = *stateptr; + result = reservoir_get_next_S(&oldrs, t, n); + *stateptr = oldrs.W; + return result; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/misc/superuser.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/misc/superuser.c new file mode 100644 index 00000000000..674dfe95364 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/misc/superuser.c @@ -0,0 +1,107 @@ +/*------------------------------------------------------------------------- + * + * superuser.c + * The superuser() function. Determines if user has superuser privilege. + * + * All code should use either of these two functions to find out + * whether a given user is a superuser, rather than examining + * pg_authid.rolsuper directly, so that the escape hatch built in for + * the single-user case works. + * + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/misc/superuser.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/htup_details.h" +#include "catalog/pg_authid.h" +#include "miscadmin.h" +#include "utils/inval.h" +#include "utils/syscache.h" + +/* + * In common cases the same roleid (ie, the session or current ID) will + * be queried repeatedly. So we maintain a simple one-entry cache for + * the status of the last requested roleid. The cache can be flushed + * at need by watching for cache update events on pg_authid. + */ +static __thread Oid last_roleid = InvalidOid; /* InvalidOid == cache not valid */ +static __thread bool last_roleid_is_super = false; +static __thread bool roleid_callback_registered = false; + +static void RoleidCallback(Datum arg, int cacheid, uint32 hashvalue); + + +/* + * The Postgres user running this command has Postgres superuser privileges + */ +bool +superuser(void) +{ + return superuser_arg(GetUserId()); +} + + +/* + * The specified role has Postgres superuser privileges + */ +bool +superuser_arg_original(Oid roleid) +{ + bool result; + HeapTuple rtup; + + /* Quick out for cache hit */ + if (OidIsValid(last_roleid) && last_roleid == roleid) + return last_roleid_is_super; + + /* Special escape path in case you deleted all your users. */ + if (!IsUnderPostmaster && roleid == BOOTSTRAP_SUPERUSERID) + return true; + + /* OK, look up the information in pg_authid */ + rtup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid)); + if (HeapTupleIsValid(rtup)) + { + result = ((Form_pg_authid) GETSTRUCT(rtup))->rolsuper; + ReleaseSysCache(rtup); + } + else + { + /* Report "not superuser" for invalid roleids */ + result = false; + } + + /* If first time through, set up callback for cache flushes */ + if (!roleid_callback_registered) + { + CacheRegisterSyscacheCallback(AUTHOID, + RoleidCallback, + (Datum) 0); + roleid_callback_registered = true; + } + + /* Cache the result for next time */ + last_roleid = roleid; + last_roleid_is_super = result; + + return result; +} + +/* + * RoleidCallback + * Syscache inval callback function + */ +static void +RoleidCallback(Datum arg, int cacheid, uint32 hashvalue) +{ + /* Invalidate our local cache in case role's superuserness changed */ + last_roleid = InvalidOid; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/misc/timeout.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/misc/timeout.c new file mode 100644 index 00000000000..2ee75cd3de6 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/misc/timeout.c @@ -0,0 +1,834 @@ +/*------------------------------------------------------------------------- + * + * timeout.c + * Routines to multiplex SIGALRM interrupts for multiple timeout reasons. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/misc/timeout.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <sys/time.h> + +#include "miscadmin.h" +#include "storage/proc.h" +#include "utils/timeout.h" +#include "utils/timestamp.h" + + +/* Data about any one timeout reason */ +typedef struct timeout_params +{ + TimeoutId index; /* identifier of timeout reason */ + + /* volatile because these may be changed from the signal handler */ + volatile bool active; /* true if timeout is in active_timeouts[] */ + volatile bool indicator; /* true if timeout has occurred */ + + /* callback function for timeout, or NULL if timeout not registered */ + timeout_handler_proc timeout_handler; + + TimestampTz start_time; /* time that timeout was last activated */ + TimestampTz fin_time; /* time it is, or was last, due to fire */ + int interval_in_ms; /* time between firings, or 0 if just once */ +} timeout_params; + +/* + * List of possible timeout reasons in the order of enum TimeoutId. + */ +static __thread timeout_params all_timeouts[MAX_TIMEOUTS]; +static __thread bool all_timeouts_initialized = false; + +/* + * List of active timeouts ordered by their fin_time and priority. + * This list is subject to change by the interrupt handler, so it's volatile. + */ +static __thread volatile int num_active_timeouts = 0; +static __thread timeout_params *volatile active_timeouts[MAX_TIMEOUTS]; + +/* + * Flag controlling whether the signal handler is allowed to do anything. + * This is useful to avoid race conditions with the handler. Note in + * particular that this lets us make changes in the data structures without + * tediously disabling and re-enabling the timer signal. Most of the time, + * no interrupt would happen anyway during such critical sections, but if + * one does, this rule ensures it's safe. Leaving the signal enabled across + * multiple operations can greatly reduce the number of kernel calls we make, + * too. See comments in schedule_alarm() about that. + * + * We leave this "false" when we're not expecting interrupts, just in case. + */ +static __thread volatile sig_atomic_t alarm_enabled = false; + +#define disable_alarm() (alarm_enabled = false) +#define enable_alarm() (alarm_enabled = true) + +/* + * State recording if and when we next expect the interrupt to fire. + * (signal_due_at is valid only when signal_pending is true.) + * Note that the signal handler will unconditionally reset signal_pending to + * false, so that can change asynchronously even when alarm_enabled is false. + */ +static __thread volatile sig_atomic_t signal_pending = false; +static __thread volatile TimestampTz signal_due_at = 0; + + +/***************************************************************************** + * Internal helper functions + * + * For all of these, it is caller's responsibility to protect them from + * interruption by the signal handler. Generally, call disable_alarm() + * first to prevent interruption, then update state, and last call + * schedule_alarm(), which will re-enable the signal handler if needed. + *****************************************************************************/ + +/* + * Find the index of a given timeout reason in the active array. + * If it's not there, return -1. + */ +static int +find_active_timeout(TimeoutId id) +{ + int i; + + for (i = 0; i < num_active_timeouts; i++) + { + if (active_timeouts[i]->index == id) + return i; + } + + return -1; +} + +/* + * Insert specified timeout reason into the list of active timeouts + * at the given index. + */ +static void +insert_timeout(TimeoutId id, int index) +{ + int i; + + if (index < 0 || index > num_active_timeouts) + elog(FATAL, "timeout index %d out of range 0..%d", index, + num_active_timeouts); + + Assert(!all_timeouts[id].active); + all_timeouts[id].active = true; + + for (i = num_active_timeouts - 1; i >= index; i--) + active_timeouts[i + 1] = active_timeouts[i]; + + active_timeouts[index] = &all_timeouts[id]; + + num_active_timeouts++; +} + +/* + * Remove the index'th element from the timeout list. + */ +static void +remove_timeout_index(int index) +{ + int i; + + if (index < 0 || index >= num_active_timeouts) + elog(FATAL, "timeout index %d out of range 0..%d", index, + num_active_timeouts - 1); + + Assert(active_timeouts[index]->active); + active_timeouts[index]->active = false; + + for (i = index + 1; i < num_active_timeouts; i++) + active_timeouts[i - 1] = active_timeouts[i]; + + num_active_timeouts--; +} + +/* + * Enable the specified timeout reason + */ +static void +enable_timeout(TimeoutId id, TimestampTz now, TimestampTz fin_time, + int interval_in_ms) +{ + int i; + + /* Assert request is sane */ + Assert(all_timeouts_initialized); + Assert(all_timeouts[id].timeout_handler != NULL); + + /* + * If this timeout was already active, momentarily disable it. We + * interpret the call as a directive to reschedule the timeout. + */ + if (all_timeouts[id].active) + remove_timeout_index(find_active_timeout(id)); + + /* + * Find out the index where to insert the new timeout. We sort by + * fin_time, and for equal fin_time by priority. + */ + for (i = 0; i < num_active_timeouts; i++) + { + timeout_params *old_timeout = active_timeouts[i]; + + if (fin_time < old_timeout->fin_time) + break; + if (fin_time == old_timeout->fin_time && id < old_timeout->index) + break; + } + + /* + * Mark the timeout active, and insert it into the active list. + */ + all_timeouts[id].indicator = false; + all_timeouts[id].start_time = now; + all_timeouts[id].fin_time = fin_time; + all_timeouts[id].interval_in_ms = interval_in_ms; + + insert_timeout(id, i); +} + +/* + * Schedule alarm for the next active timeout, if any + * + * We assume the caller has obtained the current time, or a close-enough + * approximation. (It's okay if a tick or two has passed since "now", or + * if a little more time elapses before we reach the kernel call; that will + * cause us to ask for an interrupt a tick or two later than the nearest + * timeout, which is no big deal. Passing a "now" value that's in the future + * would be bad though.) + */ +static void +schedule_alarm(TimestampTz now) +{ + if (num_active_timeouts > 0) + { + struct itimerval timeval; + TimestampTz nearest_timeout; + long secs; + int usecs; + + MemSet(&timeval, 0, sizeof(struct itimerval)); + + /* + * If we think there's a signal pending, but current time is more than + * 10ms past when the signal was due, then assume that the timeout + * request got lost somehow; clear signal_pending so that we'll reset + * the interrupt request below. (10ms corresponds to the worst-case + * timeout granularity on modern systems.) It won't hurt us if the + * interrupt does manage to fire between now and when we reach the + * setitimer() call. + */ + if (signal_pending && now > signal_due_at + 10 * 1000) + signal_pending = false; + + /* + * Get the time remaining till the nearest pending timeout. If it is + * negative, assume that we somehow missed an interrupt, and clear + * signal_pending. This gives us another chance to recover if the + * kernel drops a timeout request for some reason. + */ + nearest_timeout = active_timeouts[0]->fin_time; + if (now > nearest_timeout) + { + signal_pending = false; + /* force an interrupt as soon as possible */ + secs = 0; + usecs = 1; + } + else + { + TimestampDifference(now, nearest_timeout, + &secs, &usecs); + + /* + * It's possible that the difference is less than a microsecond; + * ensure we don't cancel, rather than set, the interrupt. + */ + if (secs == 0 && usecs == 0) + usecs = 1; + } + + timeval.it_value.tv_sec = secs; + timeval.it_value.tv_usec = usecs; + + /* + * We must enable the signal handler before calling setitimer(); if we + * did it in the other order, we'd have a race condition wherein the + * interrupt could occur before we can set alarm_enabled, so that the + * signal handler would fail to do anything. + * + * Because we didn't bother to disable the timer in disable_alarm(), + * it's possible that a previously-set interrupt will fire between + * enable_alarm() and setitimer(). This is safe, however. There are + * two possible outcomes: + * + * 1. The signal handler finds nothing to do (because the nearest + * timeout event is still in the future). It will re-set the timer + * and return. Then we'll overwrite the timer value with a new one. + * This will mean that the timer fires a little later than we + * intended, but only by the amount of time it takes for the signal + * handler to do nothing useful, which shouldn't be much. + * + * 2. The signal handler executes and removes one or more timeout + * events. When it returns, either the queue is now empty or the + * frontmost event is later than the one we looked at above. So we'll + * overwrite the timer value with one that is too soon (plus or minus + * the signal handler's execution time), causing a useless interrupt + * to occur. But the handler will then re-set the timer and + * everything will still work as expected. + * + * Since these cases are of very low probability (the window here + * being quite narrow), it's not worth adding cycles to the mainline + * code to prevent occasional wasted interrupts. + */ + enable_alarm(); + + /* + * If there is already an interrupt pending that's at or before the + * needed time, we need not do anything more. The signal handler will + * do the right thing in the first case, and re-schedule the interrupt + * for later in the second case. It might seem that the extra + * interrupt is wasted work, but it's not terribly much work, and this + * method has very significant advantages in the common use-case where + * we repeatedly set a timeout that we don't expect to reach and then + * cancel it. Instead of invoking setitimer() every time the timeout + * is set or canceled, we perform one interrupt and a re-scheduling + * setitimer() call at intervals roughly equal to the timeout delay. + * For example, with statement_timeout = 1s and a throughput of + * thousands of queries per second, this method requires an interrupt + * and setitimer() call roughly once a second, rather than thousands + * of setitimer() calls per second. + * + * Because of the possible passage of time between when we obtained + * "now" and when we reach setitimer(), the kernel's opinion of when + * to trigger the interrupt is likely to be a bit later than + * signal_due_at. That's fine, for the same reasons described above. + */ + if (signal_pending && nearest_timeout >= signal_due_at) + return; + + /* + * As with calling enable_alarm(), we must set signal_pending *before* + * calling setitimer(); if we did it after, the signal handler could + * trigger before we set it, leaving us with a false opinion that a + * signal is still coming. + * + * Other race conditions involved with setting/checking signal_pending + * are okay, for the reasons described above. One additional point is + * that the signal handler could fire after we set signal_due_at, but + * still before the setitimer() call. Then the handler could + * overwrite signal_due_at with a value it computes, which will be the + * same as or perhaps later than what we just computed. After we + * perform setitimer(), the net effect would be that signal_due_at + * gives a time later than when the interrupt will really happen; + * which is a safe situation. + */ + signal_due_at = nearest_timeout; + signal_pending = true; + + /* Set the alarm timer */ + if (setitimer(ITIMER_REAL, &timeval, NULL) != 0) + { + /* + * Clearing signal_pending here is a bit pro forma, but not + * entirely so, since something in the FATAL exit path could try + * to use timeout facilities. + */ + signal_pending = false; + elog(FATAL, "could not enable SIGALRM timer: %m"); + } + } +} + + +/***************************************************************************** + * Signal handler + *****************************************************************************/ + +/* + * Signal handler for SIGALRM + * + * Process any active timeout reasons and then reschedule the interrupt + * as needed. + */ +static void +handle_sig_alarm(SIGNAL_ARGS) +{ + int save_errno = errno; + + /* + * Bump the holdoff counter, to make sure nothing we call will process + * interrupts directly. No timeout handler should do that, but these + * failures are hard to debug, so better be sure. + */ + HOLD_INTERRUPTS(); + + /* + * SIGALRM is always cause for waking anything waiting on the process + * latch. + */ + SetLatch(MyLatch); + + /* + * Always reset signal_pending, even if !alarm_enabled, since indeed no + * signal is now pending. + */ + signal_pending = false; + + /* + * Fire any pending timeouts, but only if we're enabled to do so. + */ + if (alarm_enabled) + { + /* + * Disable alarms, just in case this platform allows signal handlers + * to interrupt themselves. schedule_alarm() will re-enable if + * appropriate. + */ + disable_alarm(); + + if (num_active_timeouts > 0) + { + TimestampTz now = GetCurrentTimestamp(); + + /* While the first pending timeout has been reached ... */ + while (num_active_timeouts > 0 && + now >= active_timeouts[0]->fin_time) + { + timeout_params *this_timeout = active_timeouts[0]; + + /* Remove it from the active list */ + remove_timeout_index(0); + + /* Mark it as fired */ + this_timeout->indicator = true; + + /* And call its handler function */ + this_timeout->timeout_handler(); + + /* If it should fire repeatedly, re-enable it. */ + if (this_timeout->interval_in_ms > 0) + { + TimestampTz new_fin_time; + + /* + * To guard against drift, schedule the next instance of + * the timeout based on the intended firing time rather + * than the actual firing time. But if the timeout was so + * late that we missed an entire cycle, fall back to + * scheduling based on the actual firing time. + */ + new_fin_time = + TimestampTzPlusMilliseconds(this_timeout->fin_time, + this_timeout->interval_in_ms); + if (new_fin_time < now) + new_fin_time = + TimestampTzPlusMilliseconds(now, + this_timeout->interval_in_ms); + enable_timeout(this_timeout->index, now, new_fin_time, + this_timeout->interval_in_ms); + } + + /* + * The handler might not take negligible time (CheckDeadLock + * for instance isn't too cheap), so let's update our idea of + * "now" after each one. + */ + now = GetCurrentTimestamp(); + } + + /* Done firing timeouts, so reschedule next interrupt if any */ + schedule_alarm(now); + } + } + + RESUME_INTERRUPTS(); + + errno = save_errno; +} + + +/***************************************************************************** + * Public API + *****************************************************************************/ + +/* + * Initialize timeout module. + * + * This must be called in every process that wants to use timeouts. + * + * If the process was forked from another one that was also using this + * module, be sure to call this before re-enabling signals; else handlers + * meant to run in the parent process might get invoked in this one. + */ +void +InitializeTimeouts(void) +{ + int i; + + /* Initialize, or re-initialize, all local state */ + disable_alarm(); + + num_active_timeouts = 0; + + for (i = 0; i < MAX_TIMEOUTS; i++) + { + all_timeouts[i].index = i; + all_timeouts[i].active = false; + all_timeouts[i].indicator = false; + all_timeouts[i].timeout_handler = NULL; + all_timeouts[i].start_time = 0; + all_timeouts[i].fin_time = 0; + all_timeouts[i].interval_in_ms = 0; + } + + all_timeouts_initialized = true; + + /* Now establish the signal handler */ + pqsignal(SIGALRM, handle_sig_alarm); +} + +/* + * Register a timeout reason + * + * For predefined timeouts, this just registers the callback function. + * + * For user-defined timeouts, pass id == USER_TIMEOUT; we then allocate and + * return a timeout ID. + */ +TimeoutId +RegisterTimeout(TimeoutId id, timeout_handler_proc handler) +{ + Assert(all_timeouts_initialized); + + /* There's no need to disable the signal handler here. */ + + if (id >= USER_TIMEOUT) + { + /* Allocate a user-defined timeout reason */ + for (id = USER_TIMEOUT; id < MAX_TIMEOUTS; id++) + if (all_timeouts[id].timeout_handler == NULL) + break; + if (id >= MAX_TIMEOUTS) + ereport(FATAL, + (errcode(ERRCODE_CONFIGURATION_LIMIT_EXCEEDED), + errmsg("cannot add more timeout reasons"))); + } + + Assert(all_timeouts[id].timeout_handler == NULL); + + all_timeouts[id].timeout_handler = handler; + + return id; +} + +/* + * Reschedule any pending SIGALRM interrupt. + * + * This can be used during error recovery in case query cancel resulted in loss + * of a SIGALRM event (due to longjmp'ing out of handle_sig_alarm before it + * could do anything). But note it's not necessary if any of the public + * enable_ or disable_timeout functions are called in the same area, since + * those all do schedule_alarm() internally if needed. + */ +void +reschedule_timeouts(void) +{ + /* For flexibility, allow this to be called before we're initialized. */ + if (!all_timeouts_initialized) + return; + + /* Disable timeout interrupts for safety. */ + disable_alarm(); + + /* Reschedule the interrupt, if any timeouts remain active. */ + if (num_active_timeouts > 0) + schedule_alarm(GetCurrentTimestamp()); +} + +/* + * Enable the specified timeout to fire after the specified delay. + * + * Delay is given in milliseconds. + */ +void +enable_timeout_after(TimeoutId id, int delay_ms) +{ + TimestampTz now; + TimestampTz fin_time; + + /* Disable timeout interrupts for safety. */ + disable_alarm(); + + /* Queue the timeout at the appropriate time. */ + now = GetCurrentTimestamp(); + fin_time = TimestampTzPlusMilliseconds(now, delay_ms); + enable_timeout(id, now, fin_time, 0); + + /* Set the timer interrupt. */ + schedule_alarm(now); +} + +/* + * Enable the specified timeout to fire periodically, with the specified + * delay as the time between firings. + * + * Delay is given in milliseconds. + */ +void +enable_timeout_every(TimeoutId id, TimestampTz fin_time, int delay_ms) +{ + TimestampTz now; + + /* Disable timeout interrupts for safety. */ + disable_alarm(); + + /* Queue the timeout at the appropriate time. */ + now = GetCurrentTimestamp(); + enable_timeout(id, now, fin_time, delay_ms); + + /* Set the timer interrupt. */ + schedule_alarm(now); +} + +/* + * Enable the specified timeout to fire at the specified time. + * + * This is provided to support cases where there's a reason to calculate + * the timeout by reference to some point other than "now". If there isn't, + * use enable_timeout_after(), to avoid calling GetCurrentTimestamp() twice. + */ +void +enable_timeout_at(TimeoutId id, TimestampTz fin_time) +{ + TimestampTz now; + + /* Disable timeout interrupts for safety. */ + disable_alarm(); + + /* Queue the timeout at the appropriate time. */ + now = GetCurrentTimestamp(); + enable_timeout(id, now, fin_time, 0); + + /* Set the timer interrupt. */ + schedule_alarm(now); +} + +/* + * Enable multiple timeouts at once. + * + * This works like calling enable_timeout_after() and/or enable_timeout_at() + * multiple times. Use this to reduce the number of GetCurrentTimestamp() + * and setitimer() calls needed to establish multiple timeouts. + */ +void +enable_timeouts(const EnableTimeoutParams *timeouts, int count) +{ + TimestampTz now; + int i; + + /* Disable timeout interrupts for safety. */ + disable_alarm(); + + /* Queue the timeout(s) at the appropriate times. */ + now = GetCurrentTimestamp(); + + for (i = 0; i < count; i++) + { + TimeoutId id = timeouts[i].id; + TimestampTz fin_time; + + switch (timeouts[i].type) + { + case TMPARAM_AFTER: + fin_time = TimestampTzPlusMilliseconds(now, + timeouts[i].delay_ms); + enable_timeout(id, now, fin_time, 0); + break; + + case TMPARAM_AT: + enable_timeout(id, now, timeouts[i].fin_time, 0); + break; + + case TMPARAM_EVERY: + fin_time = TimestampTzPlusMilliseconds(now, + timeouts[i].delay_ms); + enable_timeout(id, now, fin_time, timeouts[i].delay_ms); + break; + + default: + elog(ERROR, "unrecognized timeout type %d", + (int) timeouts[i].type); + break; + } + } + + /* Set the timer interrupt. */ + schedule_alarm(now); +} + +/* + * Cancel the specified timeout. + * + * The timeout's I've-been-fired indicator is reset, + * unless keep_indicator is true. + * + * When a timeout is canceled, any other active timeout remains in force. + * It's not an error to disable a timeout that is not enabled. + */ +void +disable_timeout(TimeoutId id, bool keep_indicator) +{ + /* Assert request is sane */ + Assert(all_timeouts_initialized); + Assert(all_timeouts[id].timeout_handler != NULL); + + /* Disable timeout interrupts for safety. */ + disable_alarm(); + + /* Find the timeout and remove it from the active list. */ + if (all_timeouts[id].active) + remove_timeout_index(find_active_timeout(id)); + + /* Mark it inactive, whether it was active or not. */ + if (!keep_indicator) + all_timeouts[id].indicator = false; + + /* Reschedule the interrupt, if any timeouts remain active. */ + if (num_active_timeouts > 0) + schedule_alarm(GetCurrentTimestamp()); +} + +/* + * Cancel multiple timeouts at once. + * + * The timeouts' I've-been-fired indicators are reset, + * unless timeouts[i].keep_indicator is true. + * + * This works like calling disable_timeout() multiple times. + * Use this to reduce the number of GetCurrentTimestamp() + * and setitimer() calls needed to cancel multiple timeouts. + */ +void +disable_timeouts(const DisableTimeoutParams *timeouts, int count) +{ + int i; + + Assert(all_timeouts_initialized); + + /* Disable timeout interrupts for safety. */ + disable_alarm(); + + /* Cancel the timeout(s). */ + for (i = 0; i < count; i++) + { + TimeoutId id = timeouts[i].id; + + Assert(all_timeouts[id].timeout_handler != NULL); + + if (all_timeouts[id].active) + remove_timeout_index(find_active_timeout(id)); + + if (!timeouts[i].keep_indicator) + all_timeouts[id].indicator = false; + } + + /* Reschedule the interrupt, if any timeouts remain active. */ + if (num_active_timeouts > 0) + schedule_alarm(GetCurrentTimestamp()); +} + +/* + * Disable the signal handler, remove all timeouts from the active list, + * and optionally reset their timeout indicators. + */ +void +disable_all_timeouts(bool keep_indicators) +{ + int i; + + disable_alarm(); + + /* + * We used to disable the timer interrupt here, but in common usage + * patterns it's cheaper to leave it enabled; that may save us from having + * to enable it again shortly. See comments in schedule_alarm(). + */ + + num_active_timeouts = 0; + + for (i = 0; i < MAX_TIMEOUTS; i++) + { + all_timeouts[i].active = false; + if (!keep_indicators) + all_timeouts[i].indicator = false; + } +} + +/* + * Return true if the timeout is active (enabled and not yet fired) + * + * This is, of course, subject to race conditions, as the timeout could fire + * immediately after we look. + */ +bool +get_timeout_active(TimeoutId id) +{ + return all_timeouts[id].active; +} + +/* + * Return the timeout's I've-been-fired indicator + * + * If reset_indicator is true, reset the indicator when returning true. + * To avoid missing timeouts due to race conditions, we are careful not to + * reset the indicator when returning false. + */ +bool +get_timeout_indicator(TimeoutId id, bool reset_indicator) +{ + if (all_timeouts[id].indicator) + { + if (reset_indicator) + all_timeouts[id].indicator = false; + return true; + } + return false; +} + +/* + * Return the time when the timeout was most recently activated + * + * Note: will return 0 if timeout has never been activated in this process. + * However, we do *not* reset the start_time when a timeout occurs, so as + * not to create a race condition if SIGALRM fires just as some code is + * about to fetch the value. + */ +TimestampTz +get_timeout_start_time(TimeoutId id) +{ + return all_timeouts[id].start_time; +} + +/* + * Return the time when the timeout is, or most recently was, due to fire + * + * Note: will return 0 if timeout has never been activated in this process. + * However, we do *not* reset the fin_time when a timeout occurs, so as + * not to create a race condition if SIGALRM fires just as some code is + * about to fetch the value. + */ +TimestampTz +get_timeout_finish_time(TimeoutId id) +{ + return all_timeouts[id].fin_time; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/misc/tzparser.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/misc/tzparser.c new file mode 100644 index 00000000000..dfb0253150e --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/misc/tzparser.c @@ -0,0 +1,484 @@ +/*------------------------------------------------------------------------- + * + * tzparser.c + * Functions for parsing timezone offset files + * + * Note: this code is invoked from the check_hook for the GUC variable + * timezone_abbreviations. Therefore, it should report problems using + * GUC_check_errmsg() and related functions, and try to avoid throwing + * elog(ERROR). This is not completely bulletproof at present --- in + * particular out-of-memory will throw an error. Could probably fix with + * PG_TRY if necessary. + * + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/utils/misc/tzparser.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include <ctype.h> + +#include "miscadmin.h" +#include "storage/fd.h" +#include "utils/guc.h" +#include "utils/memutils.h" +#include "utils/tzparser.h" + + +#define WHITESPACE " \t\n\r" + +static bool validateTzEntry(tzEntry *tzentry); +static bool splitTzLine(const char *filename, int lineno, + char *line, tzEntry *tzentry); +static int addToArray(tzEntry **base, int *arraysize, int n, + tzEntry *entry, bool override); +static int ParseTzFile(const char *filename, int depth, + tzEntry **base, int *arraysize, int n); + + +/* + * Apply additional validation checks to a tzEntry + * + * Returns true if OK, else false + */ +static bool +validateTzEntry(tzEntry *tzentry) +{ + unsigned char *p; + + /* + * Check restrictions imposed by datetktbl storage format (see datetime.c) + */ + if (strlen(tzentry->abbrev) > TOKMAXLEN) + { + GUC_check_errmsg("time zone abbreviation \"%s\" is too long (maximum %d characters) in time zone file \"%s\", line %d", + tzentry->abbrev, TOKMAXLEN, + tzentry->filename, tzentry->lineno); + return false; + } + + /* + * Sanity-check the offset: shouldn't exceed 14 hours + */ + if (tzentry->offset > 14 * 60 * 60 || + tzentry->offset < -14 * 60 * 60) + { + GUC_check_errmsg("time zone offset %d is out of range in time zone file \"%s\", line %d", + tzentry->offset, + tzentry->filename, tzentry->lineno); + return false; + } + + /* + * Convert abbrev to lowercase (must match datetime.c's conversion) + */ + for (p = (unsigned char *) tzentry->abbrev; *p; p++) + *p = pg_tolower(*p); + + return true; +} + +/* + * Attempt to parse the line as a timezone abbrev spec + * + * Valid formats are: + * name zone + * name offset dst + * + * Returns true if OK, else false; data is stored in *tzentry + */ +static bool +splitTzLine(const char *filename, int lineno, char *line, tzEntry *tzentry) +{ + char *abbrev; + char *offset; + char *offset_endptr; + char *remain; + char *is_dst; + + tzentry->lineno = lineno; + tzentry->filename = filename; + + abbrev = strtok(line, WHITESPACE); + if (!abbrev) + { + GUC_check_errmsg("missing time zone abbreviation in time zone file \"%s\", line %d", + filename, lineno); + return false; + } + tzentry->abbrev = pstrdup(abbrev); + + offset = strtok(NULL, WHITESPACE); + if (!offset) + { + GUC_check_errmsg("missing time zone offset in time zone file \"%s\", line %d", + filename, lineno); + return false; + } + + /* We assume zone names don't begin with a digit or sign */ + if (isdigit((unsigned char) *offset) || *offset == '+' || *offset == '-') + { + tzentry->zone = NULL; + tzentry->offset = strtol(offset, &offset_endptr, 10); + if (offset_endptr == offset || *offset_endptr != '\0') + { + GUC_check_errmsg("invalid number for time zone offset in time zone file \"%s\", line %d", + filename, lineno); + return false; + } + + is_dst = strtok(NULL, WHITESPACE); + if (is_dst && pg_strcasecmp(is_dst, "D") == 0) + { + tzentry->is_dst = true; + remain = strtok(NULL, WHITESPACE); + } + else + { + /* there was no 'D' dst specifier */ + tzentry->is_dst = false; + remain = is_dst; + } + } + else + { + /* + * Assume entry is a zone name. We do not try to validate it by + * looking up the zone, because that would force loading of a lot of + * zones that probably will never be used in the current session. + */ + tzentry->zone = pstrdup(offset); + tzentry->offset = 0; + tzentry->is_dst = false; + remain = strtok(NULL, WHITESPACE); + } + + if (!remain) /* no more non-whitespace chars */ + return true; + + if (remain[0] != '#') /* must be a comment */ + { + GUC_check_errmsg("invalid syntax in time zone file \"%s\", line %d", + filename, lineno); + return false; + } + return true; +} + +/* + * Insert entry into sorted array + * + * *base: base address of array (changeable if must enlarge array) + * *arraysize: allocated length of array (changeable if must enlarge array) + * n: current number of valid elements in array + * entry: new data to insert + * override: true if OK to override + * + * Returns the new array length (new value for n), or -1 if error + */ +static int +addToArray(tzEntry **base, int *arraysize, int n, + tzEntry *entry, bool override) +{ + tzEntry *arrayptr; + int low; + int high; + + /* + * Search the array for a duplicate; as a useful side effect, the array is + * maintained in sorted order. We use strcmp() to ensure we match the + * sort order datetime.c expects. + */ + arrayptr = *base; + low = 0; + high = n - 1; + while (low <= high) + { + int mid = (low + high) >> 1; + tzEntry *midptr = arrayptr + mid; + int cmp; + + cmp = strcmp(entry->abbrev, midptr->abbrev); + if (cmp < 0) + high = mid - 1; + else if (cmp > 0) + low = mid + 1; + else + { + /* + * Found a duplicate entry; complain unless it's the same. + */ + if ((midptr->zone == NULL && entry->zone == NULL && + midptr->offset == entry->offset && + midptr->is_dst == entry->is_dst) || + (midptr->zone != NULL && entry->zone != NULL && + strcmp(midptr->zone, entry->zone) == 0)) + { + /* return unchanged array */ + return n; + } + if (override) + { + /* same abbrev but something is different, override */ + midptr->zone = entry->zone; + midptr->offset = entry->offset; + midptr->is_dst = entry->is_dst; + return n; + } + /* same abbrev but something is different, complain */ + GUC_check_errmsg("time zone abbreviation \"%s\" is multiply defined", + entry->abbrev); + GUC_check_errdetail("Entry in time zone file \"%s\", line %d, conflicts with entry in file \"%s\", line %d.", + midptr->filename, midptr->lineno, + entry->filename, entry->lineno); + return -1; + } + } + + /* + * No match, insert at position "low". + */ + if (n >= *arraysize) + { + *arraysize *= 2; + *base = (tzEntry *) repalloc(*base, *arraysize * sizeof(tzEntry)); + } + + arrayptr = *base + low; + + memmove(arrayptr + 1, arrayptr, (n - low) * sizeof(tzEntry)); + + memcpy(arrayptr, entry, sizeof(tzEntry)); + + return n + 1; +} + +/* + * Parse a single timezone abbrev file --- can recurse to handle @INCLUDE + * + * filename: user-specified file name (does not include path) + * depth: current recursion depth + * *base: array for results (changeable if must enlarge array) + * *arraysize: allocated length of array (changeable if must enlarge array) + * n: current number of valid elements in array + * + * Returns the new array length (new value for n), or -1 if error + */ +static int +ParseTzFile(const char *filename, int depth, + tzEntry **base, int *arraysize, int n) +{ + char share_path[MAXPGPATH]; + char file_path[MAXPGPATH]; + FILE *tzFile; + char tzbuf[1024]; + char *line; + tzEntry tzentry; + int lineno = 0; + bool override = false; + const char *p; + + /* + * We enforce that the filename is all alpha characters. This may be + * overly restrictive, but we don't want to allow access to anything + * outside the timezonesets directory, so for instance '/' *must* be + * rejected. + */ + for (p = filename; *p; p++) + { + if (!isalpha((unsigned char) *p)) + { + /* at level 0, just use guc.c's regular "invalid value" message */ + if (depth > 0) + GUC_check_errmsg("invalid time zone file name \"%s\"", + filename); + return -1; + } + } + + /* + * The maximal recursion depth is a pretty arbitrary setting. It is hard + * to imagine that someone needs more than 3 levels so stick with this + * conservative setting until someone complains. + */ + if (depth > 3) + { + GUC_check_errmsg("time zone file recursion limit exceeded in file \"%s\"", + filename); + return -1; + } + + get_share_path(my_exec_path, share_path); + snprintf(file_path, sizeof(file_path), "%s/timezonesets/%s", + share_path, filename); + tzFile = AllocateFile(file_path, "r"); + if (!tzFile) + { + /* + * Check to see if the problem is not the filename but the directory. + * This is worth troubling over because if the installation share/ + * directory is missing or unreadable, this is likely to be the first + * place we notice a problem during postmaster startup. + */ + int save_errno = errno; + DIR *tzdir; + + snprintf(file_path, sizeof(file_path), "%s/timezonesets", + share_path); + tzdir = AllocateDir(file_path); + if (tzdir == NULL) + { + GUC_check_errmsg("could not open directory \"%s\": %m", + file_path); + GUC_check_errhint("This may indicate an incomplete PostgreSQL installation, or that the file \"%s\" has been moved away from its proper location.", + my_exec_path); + return -1; + } + FreeDir(tzdir); + errno = save_errno; + + /* + * otherwise, if file doesn't exist and it's level 0, guc.c's + * complaint is enough + */ + if (errno != ENOENT || depth > 0) + GUC_check_errmsg("could not read time zone file \"%s\": %m", + filename); + + return -1; + } + + while (!feof(tzFile)) + { + lineno++; + if (fgets(tzbuf, sizeof(tzbuf), tzFile) == NULL) + { + if (ferror(tzFile)) + { + GUC_check_errmsg("could not read time zone file \"%s\": %m", + filename); + n = -1; + break; + } + /* else we're at EOF after all */ + break; + } + if (strlen(tzbuf) == sizeof(tzbuf) - 1) + { + /* the line is too long for tzbuf */ + GUC_check_errmsg("line is too long in time zone file \"%s\", line %d", + filename, lineno); + n = -1; + break; + } + + /* skip over whitespace */ + line = tzbuf; + while (*line && isspace((unsigned char) *line)) + line++; + + if (*line == '\0') /* empty line */ + continue; + if (*line == '#') /* comment line */ + continue; + + if (pg_strncasecmp(line, "@INCLUDE", strlen("@INCLUDE")) == 0) + { + /* pstrdup so we can use filename in result data structure */ + char *includeFile = pstrdup(line + strlen("@INCLUDE")); + + includeFile = strtok(includeFile, WHITESPACE); + if (!includeFile || !*includeFile) + { + GUC_check_errmsg("@INCLUDE without file name in time zone file \"%s\", line %d", + filename, lineno); + n = -1; + break; + } + n = ParseTzFile(includeFile, depth + 1, + base, arraysize, n); + if (n < 0) + break; + continue; + } + + if (pg_strncasecmp(line, "@OVERRIDE", strlen("@OVERRIDE")) == 0) + { + override = true; + continue; + } + + if (!splitTzLine(filename, lineno, line, &tzentry)) + { + n = -1; + break; + } + if (!validateTzEntry(&tzentry)) + { + n = -1; + break; + } + n = addToArray(base, arraysize, n, &tzentry, override); + if (n < 0) + break; + } + + FreeFile(tzFile); + + return n; +} + +/* + * load_tzoffsets --- read and parse the specified timezone offset file + * + * On success, return a filled-in TimeZoneAbbrevTable, which must have been + * guc_malloc'd not palloc'd. On failure, return NULL, using GUC_check_errmsg + * and friends to give details of the problem. + */ +TimeZoneAbbrevTable * +load_tzoffsets(const char *filename) +{ + TimeZoneAbbrevTable *result = NULL; + MemoryContext tmpContext; + MemoryContext oldContext; + tzEntry *array; + int arraysize; + int n; + + /* + * Create a temp memory context to work in. This makes it easy to clean + * up afterwards. + */ + tmpContext = AllocSetContextCreate(CurrentMemoryContext, + "TZParserMemory", + ALLOCSET_SMALL_SIZES); + oldContext = MemoryContextSwitchTo(tmpContext); + + /* Initialize array at a reasonable size */ + arraysize = 128; + array = (tzEntry *) palloc(arraysize * sizeof(tzEntry)); + + /* Parse the file(s) */ + n = ParseTzFile(filename, 0, &array, &arraysize, 0); + + /* If no errors so far, let datetime.c allocate memory & convert format */ + if (n >= 0) + { + result = ConvertTimeZoneAbbrevs(array, n); + if (!result) + GUC_check_errmsg("out of memory"); + } + + /* Clean up */ + MemoryContextSwitchTo(oldContext); + MemoryContextDelete(tmpContext); + + return result; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/mmgr/alignedalloc.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/mmgr/alignedalloc.c new file mode 100644 index 00000000000..627e988852b --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/mmgr/alignedalloc.c @@ -0,0 +1,154 @@ +/*------------------------------------------------------------------------- + * + * alignedalloc.c + * Allocator functions to implement palloc_aligned + * + * This is not a fully-fledged MemoryContext type as there is no means to + * create a MemoryContext of this type. The code here only serves to allow + * operations such as pfree() and repalloc() to work correctly on a memory + * chunk that was allocated by palloc_aligned(). + * + * Portions Copyright (c) 2022-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/utils/mmgr/alignedalloc.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "utils/memdebug.h" +#include "utils/memutils_memorychunk.h" + +/* + * AlignedAllocFree +* Frees allocated memory; memory is removed from its owning context. +*/ +void +AlignedAllocFree(void *pointer) +{ + MemoryChunk *chunk = PointerGetMemoryChunk(pointer); + void *unaligned; + + VALGRIND_MAKE_MEM_DEFINED(chunk, sizeof(MemoryChunk)); + + Assert(!MemoryChunkIsExternal(chunk)); + + /* obtain the original (unaligned) allocated pointer */ + unaligned = MemoryChunkGetBlock(chunk); + +#ifdef MEMORY_CONTEXT_CHECKING + /* Test for someone scribbling on unused space in chunk */ + if (!sentinel_ok(pointer, chunk->requested_size)) + elog(WARNING, "detected write past chunk end in %s %p", + GetMemoryChunkContext(unaligned)->name, chunk); +#endif + + pfree(unaligned); +} + +/* + * AlignedAllocRealloc + * Change the allocated size of a chunk and return possibly a different + * pointer to a memory address aligned to the same boundary as the + * originally requested alignment. The contents of 'pointer' will be + * copied into the returned pointer up until 'size'. Any additional + * memory will be uninitialized. + */ +void * +AlignedAllocRealloc(void *pointer, Size size) +{ + MemoryChunk *redirchunk = PointerGetMemoryChunk(pointer); + Size alignto; + void *unaligned; + MemoryContext ctx; + Size old_size; + void *newptr; + + VALGRIND_MAKE_MEM_DEFINED(redirchunk, sizeof(MemoryChunk)); + + alignto = MemoryChunkGetValue(redirchunk); + unaligned = MemoryChunkGetBlock(redirchunk); + + /* sanity check this is a power of 2 value */ + Assert((alignto & (alignto - 1)) == 0); + + /* + * Determine the size of the original allocation. We can't determine this + * exactly as GetMemoryChunkSpace() returns the total space used for the + * allocation, which for contexts like aset includes rounding up to the + * next power of 2. However, this value is just used to memcpy() the old + * data into the new allocation, so we only need to concern ourselves with + * not reading beyond the end of the original allocation's memory. The + * drawback here is that we may copy more bytes than we need to, which + * only amounts to wasted effort. We can safely subtract the extra bytes + * that we requested to allow us to align the pointer. We must also + * subtract the space for the unaligned pointer's MemoryChunk since + * GetMemoryChunkSpace should have included that. This does assume that + * all context types use MemoryChunk as a chunk header. + */ + old_size = GetMemoryChunkSpace(unaligned) - + PallocAlignedExtraBytes(alignto) - sizeof(MemoryChunk); + +#ifdef MEMORY_CONTEXT_CHECKING + /* check that GetMemoryChunkSpace returned something realistic */ + Assert(old_size >= redirchunk->requested_size); +#endif + + ctx = GetMemoryChunkContext(unaligned); + newptr = MemoryContextAllocAligned(ctx, size, alignto, 0); + + /* + * We may memcpy beyond the end of the original allocation request size, + * so we must mark the entire allocation as defined. + */ + VALGRIND_MAKE_MEM_DEFINED(pointer, old_size); + memcpy(newptr, pointer, Min(size, old_size)); + pfree(unaligned); + + return newptr; +} + +/* + * AlignedAllocGetChunkContext + * Return the MemoryContext that 'pointer' belongs to. + */ +MemoryContext +AlignedAllocGetChunkContext(void *pointer) +{ + MemoryChunk *redirchunk = PointerGetMemoryChunk(pointer); + MemoryContext cxt; + + VALGRIND_MAKE_MEM_DEFINED(redirchunk, sizeof(MemoryChunk)); + + Assert(!MemoryChunkIsExternal(redirchunk)); + + cxt = GetMemoryChunkContext(MemoryChunkGetBlock(redirchunk)); + + VALGRIND_MAKE_MEM_NOACCESS(redirchunk, sizeof(MemoryChunk)); + + return cxt; +} + +/* + * AlignedAllocGetChunkSpace + * Given a currently-allocated chunk, determine the total space + * it occupies (including all memory-allocation overhead). + */ +Size +AlignedAllocGetChunkSpace(void *pointer) +{ + MemoryChunk *redirchunk = PointerGetMemoryChunk(pointer); + void *unaligned; + Size space; + + VALGRIND_MAKE_MEM_DEFINED(redirchunk, sizeof(MemoryChunk)); + + unaligned = MemoryChunkGetBlock(redirchunk); + space = GetMemoryChunkSpace(unaligned); + + VALGRIND_MAKE_MEM_NOACCESS(redirchunk, sizeof(MemoryChunk)); + + return space; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/mmgr/aset.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/mmgr/aset.c new file mode 100644 index 00000000000..fa39038a388 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/mmgr/aset.c @@ -0,0 +1,1659 @@ +/*------------------------------------------------------------------------- + * + * aset.c + * Allocation set definitions. + * + * AllocSet is our standard implementation of the abstract MemoryContext + * type. + * + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/utils/mmgr/aset.c + * + * NOTE: + * This is a new (Feb. 05, 1999) implementation of the allocation set + * routines. AllocSet...() does not use OrderedSet...() any more. + * Instead it manages allocations in a block pool by itself, combining + * many small allocations in a few bigger blocks. AllocSetFree() normally + * doesn't free() memory really. It just add's the free'd area to some + * list for later reuse by AllocSetAlloc(). All memory blocks are free()'d + * at once on AllocSetReset(), which happens when the memory context gets + * destroyed. + * Jan Wieck + * + * Performance improvement from Tom Lane, 8/99: for extremely large request + * sizes, we do want to be able to give the memory back to free() as soon + * as it is pfree()'d. Otherwise we risk tying up a lot of memory in + * freelist entries that might never be usable. This is specially needed + * when the caller is repeatedly repalloc()'ing a block bigger and bigger; + * the previous instances of the block were guaranteed to be wasted until + * AllocSetReset() under the old way. + * + * Further improvement 12/00: as the code stood, request sizes in the + * midrange between "small" and "large" were handled very inefficiently, + * because any sufficiently large free chunk would be used to satisfy a + * request, even if it was much larger than necessary. This led to more + * and more wasted space in allocated chunks over time. To fix, get rid + * of the midrange behavior: we now handle only "small" power-of-2-size + * chunks as chunks. Anything "large" is passed off to malloc(). Change + * the number of freelists to change the small/large boundary. + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "port/pg_bitutils.h" +#include "utils/memdebug.h" +#include "utils/memutils.h" +#include "utils/memutils_memorychunk.h" +#include "utils/memutils_internal.h" + +/*-------------------- + * Chunk freelist k holds chunks of size 1 << (k + ALLOC_MINBITS), + * for k = 0 .. ALLOCSET_NUM_FREELISTS-1. + * + * Note that all chunks in the freelists have power-of-2 sizes. This + * improves recyclability: we may waste some space, but the wasted space + * should stay pretty constant as requests are made and released. + * + * A request too large for the last freelist is handled by allocating a + * dedicated block from malloc(). The block still has a block header and + * chunk header, but when the chunk is freed we'll return the whole block + * to malloc(), not put it on our freelists. + * + * CAUTION: ALLOC_MINBITS must be large enough so that + * 1<<ALLOC_MINBITS is at least MAXALIGN, + * or we may fail to align the smallest chunks adequately. + * 8-byte alignment is enough on all currently known machines. This 8-byte + * minimum also allows us to store a pointer to the next freelist item within + * the chunk of memory itself. + * + * With the current parameters, request sizes up to 8K are treated as chunks, + * larger requests go into dedicated blocks. Change ALLOCSET_NUM_FREELISTS + * to adjust the boundary point; and adjust ALLOCSET_SEPARATE_THRESHOLD in + * memutils.h to agree. (Note: in contexts with small maxBlockSize, we may + * set the allocChunkLimit to less than 8K, so as to avoid space wastage.) + *-------------------- + */ + +#define ALLOC_MINBITS 3 /* smallest chunk size is 8 bytes */ +#define ALLOCSET_NUM_FREELISTS 11 +#define ALLOC_CHUNK_LIMIT (1 << (ALLOCSET_NUM_FREELISTS-1+ALLOC_MINBITS)) +/* Size of largest chunk that we use a fixed size for */ +#define ALLOC_CHUNK_FRACTION 4 +/* We allow chunks to be at most 1/4 of maxBlockSize (less overhead) */ + +/*-------------------- + * The first block allocated for an allocset has size initBlockSize. + * Each time we have to allocate another block, we double the block size + * (if possible, and without exceeding maxBlockSize), so as to reduce + * the bookkeeping load on malloc(). + * + * Blocks allocated to hold oversize chunks do not follow this rule, however; + * they are just however big they need to be to hold that single chunk. + * + * Also, if a minContextSize is specified, the first block has that size, + * and then initBlockSize is used for the next one. + *-------------------- + */ + +#define ALLOC_BLOCKHDRSZ MAXALIGN(sizeof(AllocBlockData)) +#define ALLOC_CHUNKHDRSZ sizeof(MemoryChunk) + +typedef struct AllocBlockData *AllocBlock; /* forward reference */ + +/* + * AllocPointer + * Aligned pointer which may be a member of an allocation set. + */ +typedef void *AllocPointer; + +/* + * AllocFreeListLink + * When pfreeing memory, if we maintain a freelist for the given chunk's + * size then we use a AllocFreeListLink to point to the current item in + * the AllocSetContext's freelist and then set the given freelist element + * to point to the chunk being freed. + */ +typedef struct AllocFreeListLink +{ + MemoryChunk *next; +} AllocFreeListLink; + +/* + * Obtain a AllocFreeListLink for the given chunk. Allocation sizes are + * always at least sizeof(AllocFreeListLink), so we reuse the pointer's memory + * itself to store the freelist link. + */ +#define GetFreeListLink(chkptr) \ + (AllocFreeListLink *) ((char *) (chkptr) + ALLOC_CHUNKHDRSZ) + +/* Validate a freelist index retrieved from a chunk header */ +#define FreeListIdxIsValid(fidx) \ + ((fidx) >= 0 && (fidx) < ALLOCSET_NUM_FREELISTS) + +/* Determine the size of the chunk based on the freelist index */ +#define GetChunkSizeFromFreeListIdx(fidx) \ + ((((Size) 1) << ALLOC_MINBITS) << (fidx)) + +/* + * AllocSetContext is our standard implementation of MemoryContext. + * + * Note: header.isReset means there is nothing for AllocSetReset to do. + * This is different from the aset being physically empty (empty blocks list) + * because we will still have a keeper block. It's also different from the set + * being logically empty, because we don't attempt to detect pfree'ing the + * last active chunk. + */ +typedef struct AllocSetContext +{ + MemoryContextData header; /* Standard memory-context fields */ + /* Info about storage allocated in this context: */ + AllocBlock blocks; /* head of list of blocks in this set */ + MemoryChunk *freelist[ALLOCSET_NUM_FREELISTS]; /* free chunk lists */ + /* Allocation parameters for this context: */ + Size initBlockSize; /* initial block size */ + Size maxBlockSize; /* maximum block size */ + Size nextBlockSize; /* next block size to allocate */ + Size allocChunkLimit; /* effective chunk size limit */ + AllocBlock keeper; /* keep this block over resets */ + /* freelist this context could be put in, or -1 if not a candidate: */ + int freeListIndex; /* index in context_freelists[], or -1 */ +} AllocSetContext; + +typedef AllocSetContext *AllocSet; + +/* + * AllocBlock + * An AllocBlock is the unit of memory that is obtained by aset.c + * from malloc(). It contains one or more MemoryChunks, which are + * the units requested by palloc() and freed by pfree(). MemoryChunks + * cannot be returned to malloc() individually, instead they are put + * on freelists by pfree() and re-used by the next palloc() that has + * a matching request size. + * + * AllocBlockData is the header data for a block --- the usable space + * within the block begins at the next alignment boundary. + */ +typedef struct AllocBlockData +{ + AllocSet aset; /* aset that owns this block */ + AllocBlock prev; /* prev block in aset's blocks list, if any */ + AllocBlock next; /* next block in aset's blocks list, if any */ + char *freeptr; /* start of free space in this block */ + char *endptr; /* end of space in this block */ +} AllocBlockData; + +/* + * AllocPointerIsValid + * True iff pointer is valid allocation pointer. + */ +#define AllocPointerIsValid(pointer) PointerIsValid(pointer) + +/* + * AllocSetIsValid + * True iff set is valid allocation set. + */ +#define AllocSetIsValid(set) \ + (PointerIsValid(set) && IsA(set, AllocSetContext)) + +/* + * AllocBlockIsValid + * True iff block is valid block of allocation set. + */ +#define AllocBlockIsValid(block) \ + (PointerIsValid(block) && AllocSetIsValid((block)->aset)) + +/* + * We always store external chunks on a dedicated block. This makes fetching + * the block from an external chunk easy since it's always the first and only + * chunk on the block. + */ +#define ExternalChunkGetBlock(chunk) \ + (AllocBlock) ((char *) chunk - ALLOC_BLOCKHDRSZ) + +/* + * Rather than repeatedly creating and deleting memory contexts, we keep some + * freed contexts in freelists so that we can hand them out again with little + * work. Before putting a context in a freelist, we reset it so that it has + * only its initial malloc chunk and no others. To be a candidate for a + * freelist, a context must have the same minContextSize/initBlockSize as + * other contexts in the list; but its maxBlockSize is irrelevant since that + * doesn't affect the size of the initial chunk. + * + * We currently provide one freelist for ALLOCSET_DEFAULT_SIZES contexts + * and one for ALLOCSET_SMALL_SIZES contexts; the latter works for + * ALLOCSET_START_SMALL_SIZES too, since only the maxBlockSize differs. + * + * Ordinarily, we re-use freelist contexts in last-in-first-out order, in + * hopes of improving locality of reference. But if there get to be too + * many contexts in the list, we'd prefer to drop the most-recently-created + * contexts in hopes of keeping the process memory map compact. + * We approximate that by simply deleting all existing entries when the list + * overflows, on the assumption that queries that allocate a lot of contexts + * will probably free them in more or less reverse order of allocation. + * + * Contexts in a freelist are chained via their nextchild pointers. + */ +#define MAX_FREE_CONTEXTS 100 /* arbitrary limit on freelist length */ + +typedef struct AllocSetFreeList +{ + int num_free; /* current list length */ + AllocSetContext *first_free; /* list header */ +} AllocSetFreeList; + +/* context_freelists[0] is for default params, [1] for small params */ +static __thread AllocSetFreeList context_freelists[2] = +{ + { + 0, NULL + }, + { + 0, NULL + } +}; + + +/* ---------- + * AllocSetFreeIndex - + * + * Depending on the size of an allocation compute which freechunk + * list of the alloc set it belongs to. Caller must have verified + * that size <= ALLOC_CHUNK_LIMIT. + * ---------- + */ +static inline int +AllocSetFreeIndex(Size size) +{ + int idx; + + if (size > (1 << ALLOC_MINBITS)) + { + /*---------- + * At this point we must compute ceil(log2(size >> ALLOC_MINBITS)). + * This is the same as + * pg_leftmost_one_pos32((size - 1) >> ALLOC_MINBITS) + 1 + * or equivalently + * pg_leftmost_one_pos32(size - 1) - ALLOC_MINBITS + 1 + * + * However, for platforms without intrinsic support, we duplicate the + * logic here, allowing an additional optimization. It's reasonable + * to assume that ALLOC_CHUNK_LIMIT fits in 16 bits, so we can unroll + * the byte-at-a-time loop in pg_leftmost_one_pos32 and just handle + * the last two bytes. + * + * Yes, this function is enough of a hot-spot to make it worth this + * much trouble. + *---------- + */ +#ifdef HAVE_BITSCAN_REVERSE + idx = pg_leftmost_one_pos32((uint32) size - 1) - ALLOC_MINBITS + 1; +#else + uint32 t, + tsize; + + /* Statically assert that we only have a 16-bit input value. */ + StaticAssertDecl(ALLOC_CHUNK_LIMIT < (1 << 16), + "ALLOC_CHUNK_LIMIT must be less than 64kB"); + + tsize = size - 1; + t = tsize >> 8; + idx = t ? pg_leftmost_one_pos[t] + 8 : pg_leftmost_one_pos[tsize]; + idx -= ALLOC_MINBITS - 1; +#endif + + Assert(idx < ALLOCSET_NUM_FREELISTS); + } + else + idx = 0; + + return idx; +} + + +/* + * Public routines + */ + + +/* + * AllocSetContextCreateInternal + * Create a new AllocSet context. + * + * parent: parent context, or NULL if top-level context + * name: name of context (must be statically allocated) + * minContextSize: minimum context size + * initBlockSize: initial allocation block size + * maxBlockSize: maximum allocation block size + * + * Most callers should abstract the context size parameters using a macro + * such as ALLOCSET_DEFAULT_SIZES. + * + * Note: don't call this directly; go through the wrapper macro + * AllocSetContextCreate. + */ +MemoryContext +AllocSetContextCreateInternal(MemoryContext parent, + const char *name, + Size minContextSize, + Size initBlockSize, + Size maxBlockSize) +{ + int freeListIndex; + Size firstBlockSize; + AllocSet set; + AllocBlock block; + + /* ensure MemoryChunk's size is properly maxaligned */ + StaticAssertDecl(ALLOC_CHUNKHDRSZ == MAXALIGN(ALLOC_CHUNKHDRSZ), + "sizeof(MemoryChunk) is not maxaligned"); + /* check we have enough space to store the freelist link */ + StaticAssertDecl(sizeof(AllocFreeListLink) <= (1 << ALLOC_MINBITS), + "sizeof(AllocFreeListLink) larger than minimum allocation size"); + + /* + * First, validate allocation parameters. Once these were regular runtime + * tests and elog's, but in practice Asserts seem sufficient because + * nobody varies their parameters at runtime. We somewhat arbitrarily + * enforce a minimum 1K block size. We restrict the maximum block size to + * MEMORYCHUNK_MAX_BLOCKOFFSET as MemoryChunks are limited to this in + * regards to addressing the offset between the chunk and the block that + * the chunk is stored on. We would be unable to store the offset between + * the chunk and block for any chunks that were beyond + * MEMORYCHUNK_MAX_BLOCKOFFSET bytes into the block if the block was to be + * larger than this. + */ + Assert(initBlockSize == MAXALIGN(initBlockSize) && + initBlockSize >= 1024); + Assert(maxBlockSize == MAXALIGN(maxBlockSize) && + maxBlockSize >= initBlockSize && + AllocHugeSizeIsValid(maxBlockSize)); /* must be safe to double */ + Assert(minContextSize == 0 || + (minContextSize == MAXALIGN(minContextSize) && + minContextSize >= 1024 && + minContextSize <= maxBlockSize)); + Assert(maxBlockSize <= MEMORYCHUNK_MAX_BLOCKOFFSET); + + /* + * Check whether the parameters match either available freelist. We do + * not need to demand a match of maxBlockSize. + */ + if (minContextSize == ALLOCSET_DEFAULT_MINSIZE && + initBlockSize == ALLOCSET_DEFAULT_INITSIZE) + freeListIndex = 0; + else if (minContextSize == ALLOCSET_SMALL_MINSIZE && + initBlockSize == ALLOCSET_SMALL_INITSIZE) + freeListIndex = 1; + else + freeListIndex = -1; + + freeListIndex = -1; + + /* + * If a suitable freelist entry exists, just recycle that context. + */ + if (freeListIndex >= 0) + { + AllocSetFreeList *freelist = &context_freelists[freeListIndex]; + + if (freelist->first_free != NULL) + { + /* Remove entry from freelist */ + set = freelist->first_free; + freelist->first_free = (AllocSet) set->header.nextchild; + freelist->num_free--; + + /* Update its maxBlockSize; everything else should be OK */ + set->maxBlockSize = maxBlockSize; + + /* Reinitialize its header, installing correct name and parent */ + MemoryContextCreate((MemoryContext) set, + T_AllocSetContext, + MCTX_ASET_ID, + parent, + name); + + ((MemoryContext) set)->mem_allocated = + set->keeper->endptr - ((char *) set); + + return (MemoryContext) set; + } + } + + /* Determine size of initial block */ + firstBlockSize = MAXALIGN(sizeof(AllocSetContext)) + + ALLOC_BLOCKHDRSZ + ALLOC_CHUNKHDRSZ; + if (minContextSize != 0) + firstBlockSize = Max(firstBlockSize, minContextSize); + else + firstBlockSize = Max(firstBlockSize, initBlockSize); + + /* + * Allocate the initial block. Unlike other aset.c blocks, it starts with + * the context header and its block header follows that. + */ + set = (AllocSet) malloc(firstBlockSize); + if (set == NULL) + { + if (TopMemoryContext) + MemoryContextStats(TopMemoryContext); + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"), + errdetail("Failed while creating memory context \"%s\".", + name))); + } + + /* + * Avoid writing code that can fail between here and MemoryContextCreate; + * we'd leak the header/initial block if we ereport in this stretch. + */ + + /* Fill in the initial block's block header */ + block = (AllocBlock) (((char *) set) + MAXALIGN(sizeof(AllocSetContext))); + block->aset = set; + block->freeptr = ((char *) block) + ALLOC_BLOCKHDRSZ; + block->endptr = ((char *) set) + firstBlockSize; + block->prev = NULL; + block->next = NULL; + + /* Mark unallocated space NOACCESS; leave the block header alone. */ + VALGRIND_MAKE_MEM_NOACCESS(block->freeptr, block->endptr - block->freeptr); + + /* Remember block as part of block list */ + set->blocks = block; + /* Mark block as not to be released at reset time */ + set->keeper = block; + + /* Finish filling in aset-specific parts of the context header */ + MemSetAligned(set->freelist, 0, sizeof(set->freelist)); + + set->initBlockSize = initBlockSize; + set->maxBlockSize = maxBlockSize; + set->nextBlockSize = initBlockSize; + set->freeListIndex = freeListIndex; + + /* + * Compute the allocation chunk size limit for this context. It can't be + * more than ALLOC_CHUNK_LIMIT because of the fixed number of freelists. + * If maxBlockSize is small then requests exceeding the maxBlockSize, or + * even a significant fraction of it, should be treated as large chunks + * too. For the typical case of maxBlockSize a power of 2, the chunk size + * limit will be at most 1/8th maxBlockSize, so that given a stream of + * requests that are all the maximum chunk size we will waste at most + * 1/8th of the allocated space. + * + * Also, allocChunkLimit must not exceed ALLOCSET_SEPARATE_THRESHOLD. + */ + StaticAssertStmt(ALLOC_CHUNK_LIMIT == ALLOCSET_SEPARATE_THRESHOLD, + "ALLOC_CHUNK_LIMIT != ALLOCSET_SEPARATE_THRESHOLD"); + + /* + * Determine the maximum size that a chunk can be before we allocate an + * entire AllocBlock dedicated for that chunk. We set the absolute limit + * of that size as ALLOC_CHUNK_LIMIT but we reduce it further so that we + * can fit about ALLOC_CHUNK_FRACTION chunks this size on a maximally + * sized block. (We opt to keep allocChunkLimit a power-of-2 value + * primarily for legacy reasons rather than calculating it so that exactly + * ALLOC_CHUNK_FRACTION chunks fit on a maximally sized block.) + */ + set->allocChunkLimit = ALLOC_CHUNK_LIMIT; + while ((Size) (set->allocChunkLimit + ALLOC_CHUNKHDRSZ) > + (Size) ((maxBlockSize - ALLOC_BLOCKHDRSZ) / ALLOC_CHUNK_FRACTION)) + set->allocChunkLimit >>= 1; + + /* Finally, do the type-independent part of context creation */ + MemoryContextCreate((MemoryContext) set, + T_AllocSetContext, + MCTX_ASET_ID, + parent, + name); + + ((MemoryContext) set)->mem_allocated = firstBlockSize; + + return (MemoryContext) set; +} + +/* + * AllocSetReset + * Frees all memory which is allocated in the given set. + * + * Actually, this routine has some discretion about what to do. + * It should mark all allocated chunks freed, but it need not necessarily + * give back all the resources the set owns. Our actual implementation is + * that we give back all but the "keeper" block (which we must keep, since + * it shares a malloc chunk with the context header). In this way, we don't + * thrash malloc() when a context is repeatedly reset after small allocations, + * which is typical behavior for per-tuple contexts. + */ +void +AllocSetReset(MemoryContext context) +{ + AllocSet set = (AllocSet) context; + AllocBlock block; + Size keepersize PG_USED_FOR_ASSERTS_ONLY; + + Assert(AllocSetIsValid(set)); + +#ifdef MEMORY_CONTEXT_CHECKING + /* Check for corruption and leaks before freeing */ + AllocSetCheck(context); +#endif + + /* Remember keeper block size for Assert below */ + keepersize = set->keeper->endptr - ((char *) set); + + /* Clear chunk freelists */ + MemSetAligned(set->freelist, 0, sizeof(set->freelist)); + + block = set->blocks; + + /* New blocks list will be just the keeper block */ + set->blocks = set->keeper; + + while (block != NULL) + { + AllocBlock next = block->next; + + if (block == set->keeper) + { + /* Reset the block, but don't return it to malloc */ + char *datastart = ((char *) block) + ALLOC_BLOCKHDRSZ; + +#ifdef CLOBBER_FREED_MEMORY + wipe_mem(datastart, block->freeptr - datastart); +#else + /* wipe_mem() would have done this */ + VALGRIND_MAKE_MEM_NOACCESS(datastart, block->freeptr - datastart); +#endif + block->freeptr = datastart; + block->prev = NULL; + block->next = NULL; + } + else + { + /* Normal case, release the block */ + context->mem_allocated -= block->endptr - ((char *) block); + +#ifdef CLOBBER_FREED_MEMORY + wipe_mem(block, block->freeptr - ((char *) block)); +#endif + free(block); + } + block = next; + } + + Assert(context->mem_allocated == keepersize); + + /* Reset block size allocation sequence, too */ + set->nextBlockSize = set->initBlockSize; +} + +/* + * AllocSetDelete + * Frees all memory which is allocated in the given set, + * in preparation for deletion of the set. + * + * Unlike AllocSetReset, this *must* free all resources of the set. + */ +void +AllocSetDelete(MemoryContext context) +{ + AllocSet set = (AllocSet) context; + AllocBlock block = set->blocks; + Size keepersize PG_USED_FOR_ASSERTS_ONLY; + + Assert(AllocSetIsValid(set)); + +#ifdef MEMORY_CONTEXT_CHECKING + /* Check for corruption and leaks before freeing */ + AllocSetCheck(context); +#endif + + /* Remember keeper block size for Assert below */ + keepersize = set->keeper->endptr - ((char *) set); + + /* + * If the context is a candidate for a freelist, put it into that freelist + * instead of destroying it. + */ + if (set->freeListIndex >= 0) + { + AllocSetFreeList *freelist = &context_freelists[set->freeListIndex]; + + /* + * Reset the context, if it needs it, so that we aren't hanging on to + * more than the initial malloc chunk. + */ + if (!context->isReset) + MemoryContextResetOnly(context); + + /* + * If the freelist is full, just discard what's already in it. See + * comments with context_freelists[]. + */ + if (freelist->num_free >= MAX_FREE_CONTEXTS) + { + while (freelist->first_free != NULL) + { + AllocSetContext *oldset = freelist->first_free; + + freelist->first_free = (AllocSetContext *) oldset->header.nextchild; + freelist->num_free--; + + /* All that remains is to free the header/initial block */ + free(oldset); + } + Assert(freelist->num_free == 0); + } + + /* Now add the just-deleted context to the freelist. */ + set->header.nextchild = (MemoryContext) freelist->first_free; + freelist->first_free = set; + freelist->num_free++; + + return; + } + + /* Free all blocks, except the keeper which is part of context header */ + while (block != NULL) + { + AllocBlock next = block->next; + + if (block != set->keeper) + context->mem_allocated -= block->endptr - ((char *) block); + +#ifdef CLOBBER_FREED_MEMORY + wipe_mem(block, block->freeptr - ((char *) block)); +#endif + + if (block != set->keeper) + free(block); + + block = next; + } + + Assert(context->mem_allocated == keepersize); + + /* Finally, free the context header, including the keeper block */ + free(set); +} + +/* + * AllocSetAlloc + * Returns pointer to allocated memory of given size or NULL if + * request could not be completed; memory is added to the set. + * + * No request may exceed: + * MAXALIGN_DOWN(SIZE_MAX) - ALLOC_BLOCKHDRSZ - ALLOC_CHUNKHDRSZ + * All callers use a much-lower limit. + * + * Note: when using valgrind, it doesn't matter how the returned allocation + * is marked, as mcxt.c will set it to UNDEFINED. In some paths we will + * return space that is marked NOACCESS - AllocSetRealloc has to beware! + */ +void * +AllocSetAlloc(MemoryContext context, Size size) +{ + AllocSet set = (AllocSet) context; + AllocBlock block; + MemoryChunk *chunk; + int fidx; + Size chunk_size; + Size blksize; + + Assert(AllocSetIsValid(set)); + + /* + * If requested size exceeds maximum for chunks, allocate an entire block + * for this request. + */ + if (size > set->allocChunkLimit) + { +#ifdef MEMORY_CONTEXT_CHECKING + /* ensure there's always space for the sentinel byte */ + chunk_size = MAXALIGN(size + 1); +#else + chunk_size = MAXALIGN(size); +#endif + + blksize = chunk_size + ALLOC_BLOCKHDRSZ + ALLOC_CHUNKHDRSZ; + block = (AllocBlock) malloc(blksize); + if (block == NULL) + return NULL; + + context->mem_allocated += blksize; + + block->aset = set; + block->freeptr = block->endptr = ((char *) block) + blksize; + + chunk = (MemoryChunk *) (((char *) block) + ALLOC_BLOCKHDRSZ); + + /* mark the MemoryChunk as externally managed */ + MemoryChunkSetHdrMaskExternal(chunk, MCTX_ASET_ID); + +#ifdef MEMORY_CONTEXT_CHECKING + chunk->requested_size = size; + /* set mark to catch clobber of "unused" space */ + Assert(size < chunk_size); + set_sentinel(MemoryChunkGetPointer(chunk), size); +#endif +#ifdef RANDOMIZE_ALLOCATED_MEMORY + /* fill the allocated space with junk */ + randomize_mem((char *) MemoryChunkGetPointer(chunk), size); +#endif + + /* + * Stick the new block underneath the active allocation block, if any, + * so that we don't lose the use of the space remaining therein. + */ + if (set->blocks != NULL) + { + block->prev = set->blocks; + block->next = set->blocks->next; + if (block->next) + block->next->prev = block; + set->blocks->next = block; + } + else + { + block->prev = NULL; + block->next = NULL; + set->blocks = block; + } + + /* Ensure any padding bytes are marked NOACCESS. */ + VALGRIND_MAKE_MEM_NOACCESS((char *) MemoryChunkGetPointer(chunk) + size, + chunk_size - size); + + /* Disallow access to the chunk header. */ + VALGRIND_MAKE_MEM_NOACCESS(chunk, ALLOC_CHUNKHDRSZ); + + return MemoryChunkGetPointer(chunk); + } + + /* + * Request is small enough to be treated as a chunk. Look in the + * corresponding free list to see if there is a free chunk we could reuse. + * If one is found, remove it from the free list, make it again a member + * of the alloc set and return its data address. + * + * Note that we don't attempt to ensure there's space for the sentinel + * byte here. We expect a large proportion of allocations to be for sizes + * which are already a power of 2. If we were to always make space for a + * sentinel byte in MEMORY_CONTEXT_CHECKING builds, then we'd end up + * doubling the memory requirements for such allocations. + */ + fidx = AllocSetFreeIndex(size); + chunk = set->freelist[fidx]; + if (chunk != NULL) + { + AllocFreeListLink *link = GetFreeListLink(chunk); + + /* Allow access to the chunk header. */ + VALGRIND_MAKE_MEM_DEFINED(chunk, ALLOC_CHUNKHDRSZ); + + Assert(fidx == MemoryChunkGetValue(chunk)); + + /* pop this chunk off the freelist */ + VALGRIND_MAKE_MEM_DEFINED(link, sizeof(AllocFreeListLink)); + set->freelist[fidx] = link->next; + VALGRIND_MAKE_MEM_NOACCESS(link, sizeof(AllocFreeListLink)); + +#ifdef MEMORY_CONTEXT_CHECKING + chunk->requested_size = size; + /* set mark to catch clobber of "unused" space */ + if (size < GetChunkSizeFromFreeListIdx(fidx)) + set_sentinel(MemoryChunkGetPointer(chunk), size); +#endif +#ifdef RANDOMIZE_ALLOCATED_MEMORY + /* fill the allocated space with junk */ + randomize_mem((char *) MemoryChunkGetPointer(chunk), size); +#endif + + /* Ensure any padding bytes are marked NOACCESS. */ + VALGRIND_MAKE_MEM_NOACCESS((char *) MemoryChunkGetPointer(chunk) + size, + GetChunkSizeFromFreeListIdx(fidx) - size); + + /* Disallow access to the chunk header. */ + VALGRIND_MAKE_MEM_NOACCESS(chunk, ALLOC_CHUNKHDRSZ); + + return MemoryChunkGetPointer(chunk); + } + + /* + * Choose the actual chunk size to allocate. + */ + chunk_size = GetChunkSizeFromFreeListIdx(fidx); + Assert(chunk_size >= size); + + /* + * If there is enough room in the active allocation block, we will put the + * chunk into that block. Else must start a new one. + */ + if ((block = set->blocks) != NULL) + { + Size availspace = block->endptr - block->freeptr; + + if (availspace < (chunk_size + ALLOC_CHUNKHDRSZ)) + { + /* + * The existing active (top) block does not have enough room for + * the requested allocation, but it might still have a useful + * amount of space in it. Once we push it down in the block list, + * we'll never try to allocate more space from it. So, before we + * do that, carve up its free space into chunks that we can put on + * the set's freelists. + * + * Because we can only get here when there's less than + * ALLOC_CHUNK_LIMIT left in the block, this loop cannot iterate + * more than ALLOCSET_NUM_FREELISTS-1 times. + */ + while (availspace >= ((1 << ALLOC_MINBITS) + ALLOC_CHUNKHDRSZ)) + { + AllocFreeListLink *link; + Size availchunk = availspace - ALLOC_CHUNKHDRSZ; + int a_fidx = AllocSetFreeIndex(availchunk); + + /* + * In most cases, we'll get back the index of the next larger + * freelist than the one we need to put this chunk on. The + * exception is when availchunk is exactly a power of 2. + */ + if (availchunk != GetChunkSizeFromFreeListIdx(a_fidx)) + { + a_fidx--; + Assert(a_fidx >= 0); + availchunk = GetChunkSizeFromFreeListIdx(a_fidx); + } + + chunk = (MemoryChunk *) (block->freeptr); + + /* Prepare to initialize the chunk header. */ + VALGRIND_MAKE_MEM_UNDEFINED(chunk, ALLOC_CHUNKHDRSZ); + block->freeptr += (availchunk + ALLOC_CHUNKHDRSZ); + availspace -= (availchunk + ALLOC_CHUNKHDRSZ); + + /* store the freelist index in the value field */ + MemoryChunkSetHdrMask(chunk, block, a_fidx, MCTX_ASET_ID); +#ifdef MEMORY_CONTEXT_CHECKING + chunk->requested_size = InvalidAllocSize; /* mark it free */ +#endif + /* push this chunk onto the free list */ + link = GetFreeListLink(chunk); + + VALGRIND_MAKE_MEM_DEFINED(link, sizeof(AllocFreeListLink)); + link->next = set->freelist[a_fidx]; + VALGRIND_MAKE_MEM_NOACCESS(link, sizeof(AllocFreeListLink)); + + set->freelist[a_fidx] = chunk; + } + /* Mark that we need to create a new block */ + block = NULL; + } + } + + /* + * Time to create a new regular (multi-chunk) block? + */ + if (block == NULL) + { + Size required_size; + + /* + * The first such block has size initBlockSize, and we double the + * space in each succeeding block, but not more than maxBlockSize. + */ + blksize = set->nextBlockSize; + set->nextBlockSize <<= 1; + if (set->nextBlockSize > set->maxBlockSize) + set->nextBlockSize = set->maxBlockSize; + + /* + * If initBlockSize is less than ALLOC_CHUNK_LIMIT, we could need more + * space... but try to keep it a power of 2. + */ + required_size = chunk_size + ALLOC_BLOCKHDRSZ + ALLOC_CHUNKHDRSZ; + while (blksize < required_size) + blksize <<= 1; + + /* Try to allocate it */ + block = (AllocBlock) malloc(blksize); + + /* + * We could be asking for pretty big blocks here, so cope if malloc + * fails. But give up if there's less than 1 MB or so available... + */ + while (block == NULL && blksize > 1024 * 1024) + { + blksize >>= 1; + if (blksize < required_size) + break; + block = (AllocBlock) malloc(blksize); + } + + if (block == NULL) + return NULL; + + context->mem_allocated += blksize; + + block->aset = set; + block->freeptr = ((char *) block) + ALLOC_BLOCKHDRSZ; + block->endptr = ((char *) block) + blksize; + + /* Mark unallocated space NOACCESS. */ + VALGRIND_MAKE_MEM_NOACCESS(block->freeptr, + blksize - ALLOC_BLOCKHDRSZ); + + block->prev = NULL; + block->next = set->blocks; + if (block->next) + block->next->prev = block; + set->blocks = block; + } + + /* + * OK, do the allocation + */ + chunk = (MemoryChunk *) (block->freeptr); + + /* Prepare to initialize the chunk header. */ + VALGRIND_MAKE_MEM_UNDEFINED(chunk, ALLOC_CHUNKHDRSZ); + + block->freeptr += (chunk_size + ALLOC_CHUNKHDRSZ); + Assert(block->freeptr <= block->endptr); + + /* store the free list index in the value field */ + MemoryChunkSetHdrMask(chunk, block, fidx, MCTX_ASET_ID); + +#ifdef MEMORY_CONTEXT_CHECKING + chunk->requested_size = size; + /* set mark to catch clobber of "unused" space */ + if (size < chunk_size) + set_sentinel(MemoryChunkGetPointer(chunk), size); +#endif +#ifdef RANDOMIZE_ALLOCATED_MEMORY + /* fill the allocated space with junk */ + randomize_mem((char *) MemoryChunkGetPointer(chunk), size); +#endif + + /* Ensure any padding bytes are marked NOACCESS. */ + VALGRIND_MAKE_MEM_NOACCESS((char *) MemoryChunkGetPointer(chunk) + size, + chunk_size - size); + + /* Disallow access to the chunk header. */ + VALGRIND_MAKE_MEM_NOACCESS(chunk, ALLOC_CHUNKHDRSZ); + + return MemoryChunkGetPointer(chunk); +} + +/* + * AllocSetFree + * Frees allocated memory; memory is removed from the set. + */ +void +AllocSetFree(void *pointer) +{ + AllocSet set; + MemoryChunk *chunk = PointerGetMemoryChunk(pointer); + + /* Allow access to the chunk header. */ + VALGRIND_MAKE_MEM_DEFINED(chunk, ALLOC_CHUNKHDRSZ); + + if (MemoryChunkIsExternal(chunk)) + { + /* Release single-chunk block. */ + AllocBlock block = ExternalChunkGetBlock(chunk); + + /* + * Try to verify that we have a sane block pointer: the block header + * should reference an aset and the freeptr should match the endptr. + */ + if (!AllocBlockIsValid(block) || block->freeptr != block->endptr) + elog(ERROR, "could not find block containing chunk %p", chunk); + + set = block->aset; + +#ifdef MEMORY_CONTEXT_CHECKING + { + /* Test for someone scribbling on unused space in chunk */ + Assert(chunk->requested_size < (block->endptr - (char *) pointer)); + if (!sentinel_ok(pointer, chunk->requested_size)) + elog(WARNING, "detected write past chunk end in %s %p", + set->header.name, chunk); + } +#endif + + /* OK, remove block from aset's list and free it */ + if (block->prev) + block->prev->next = block->next; + else + set->blocks = block->next; + if (block->next) + block->next->prev = block->prev; + + set->header.mem_allocated -= block->endptr - ((char *) block); + +#ifdef CLOBBER_FREED_MEMORY + wipe_mem(block, block->freeptr - ((char *) block)); +#endif + free(block); + } + else + { + AllocBlock block = MemoryChunkGetBlock(chunk); + int fidx; + AllocFreeListLink *link; + + /* + * In this path, for speed reasons we just Assert that the referenced + * block is good. We can also Assert that the value field is sane. + * Future field experience may show that these Asserts had better + * become regular runtime test-and-elog checks. + */ + Assert(AllocBlockIsValid(block)); + set = block->aset; + + fidx = MemoryChunkGetValue(chunk); + Assert(FreeListIdxIsValid(fidx)); + link = GetFreeListLink(chunk); + +#ifdef MEMORY_CONTEXT_CHECKING + /* Test for someone scribbling on unused space in chunk */ + if (chunk->requested_size < GetChunkSizeFromFreeListIdx(fidx)) + if (!sentinel_ok(pointer, chunk->requested_size)) + elog(WARNING, "detected write past chunk end in %s %p", + set->header.name, chunk); +#endif + +#ifdef CLOBBER_FREED_MEMORY + wipe_mem(pointer, GetChunkSizeFromFreeListIdx(fidx)); +#endif + /* push this chunk onto the top of the free list */ + VALGRIND_MAKE_MEM_DEFINED(link, sizeof(AllocFreeListLink)); + link->next = set->freelist[fidx]; + VALGRIND_MAKE_MEM_NOACCESS(link, sizeof(AllocFreeListLink)); + set->freelist[fidx] = chunk; + +#ifdef MEMORY_CONTEXT_CHECKING + + /* + * Reset requested_size to InvalidAllocSize in chunks that are on free + * list. + */ + chunk->requested_size = InvalidAllocSize; +#endif + } +} + +/* + * AllocSetRealloc + * Returns new pointer to allocated memory of given size or NULL if + * request could not be completed; this memory is added to the set. + * Memory associated with given pointer is copied into the new memory, + * and the old memory is freed. + * + * Without MEMORY_CONTEXT_CHECKING, we don't know the old request size. This + * makes our Valgrind client requests less-precise, hazarding false negatives. + * (In principle, we could use VALGRIND_GET_VBITS() to rediscover the old + * request size.) + */ +void * +AllocSetRealloc(void *pointer, Size size) +{ + AllocBlock block; + AllocSet set; + MemoryChunk *chunk = PointerGetMemoryChunk(pointer); + Size oldchksize; + int fidx; + + /* Allow access to the chunk header. */ + VALGRIND_MAKE_MEM_DEFINED(chunk, ALLOC_CHUNKHDRSZ); + + if (MemoryChunkIsExternal(chunk)) + { + /* + * The chunk must have been allocated as a single-chunk block. Use + * realloc() to make the containing block bigger, or smaller, with + * minimum space wastage. + */ + Size chksize; + Size blksize; + Size oldblksize; + + block = ExternalChunkGetBlock(chunk); + + /* + * Try to verify that we have a sane block pointer: the block header + * should reference an aset and the freeptr should match the endptr. + */ + if (!AllocBlockIsValid(block) || block->freeptr != block->endptr) + elog(ERROR, "could not find block containing chunk %p", chunk); + + set = block->aset; + + oldchksize = block->endptr - (char *) pointer; + +#ifdef MEMORY_CONTEXT_CHECKING + /* Test for someone scribbling on unused space in chunk */ + Assert(chunk->requested_size < oldchksize); + if (!sentinel_ok(pointer, chunk->requested_size)) + elog(WARNING, "detected write past chunk end in %s %p", + set->header.name, chunk); +#endif + +#ifdef MEMORY_CONTEXT_CHECKING + /* ensure there's always space for the sentinel byte */ + chksize = MAXALIGN(size + 1); +#else + chksize = MAXALIGN(size); +#endif + + /* Do the realloc */ + blksize = chksize + ALLOC_BLOCKHDRSZ + ALLOC_CHUNKHDRSZ; + oldblksize = block->endptr - ((char *) block); + + block = (AllocBlock) realloc(block, blksize); + if (block == NULL) + { + /* Disallow access to the chunk header. */ + VALGRIND_MAKE_MEM_NOACCESS(chunk, ALLOC_CHUNKHDRSZ); + return NULL; + } + + /* updated separately, not to underflow when (oldblksize > blksize) */ + set->header.mem_allocated -= oldblksize; + set->header.mem_allocated += blksize; + + block->freeptr = block->endptr = ((char *) block) + blksize; + + /* Update pointers since block has likely been moved */ + chunk = (MemoryChunk *) (((char *) block) + ALLOC_BLOCKHDRSZ); + pointer = MemoryChunkGetPointer(chunk); + if (block->prev) + block->prev->next = block; + else + set->blocks = block; + if (block->next) + block->next->prev = block; + +#ifdef MEMORY_CONTEXT_CHECKING +#ifdef RANDOMIZE_ALLOCATED_MEMORY + + /* + * We can only randomize the extra space if we know the prior request. + * When using Valgrind, randomize_mem() also marks memory UNDEFINED. + */ + if (size > chunk->requested_size) + randomize_mem((char *) pointer + chunk->requested_size, + size - chunk->requested_size); +#else + + /* + * If this is an increase, realloc() will have marked any + * newly-allocated part (from oldchksize to chksize) UNDEFINED, but we + * also need to adjust trailing bytes from the old allocation (from + * chunk->requested_size to oldchksize) as they are marked NOACCESS. + * Make sure not to mark too many bytes in case chunk->requested_size + * < size < oldchksize. + */ +#ifdef USE_VALGRIND + if (Min(size, oldchksize) > chunk->requested_size) + VALGRIND_MAKE_MEM_UNDEFINED((char *) pointer + chunk->requested_size, + Min(size, oldchksize) - chunk->requested_size); +#endif +#endif + + chunk->requested_size = size; + /* set mark to catch clobber of "unused" space */ + Assert(size < chksize); + set_sentinel(pointer, size); +#else /* !MEMORY_CONTEXT_CHECKING */ + + /* + * We may need to adjust marking of bytes from the old allocation as + * some of them may be marked NOACCESS. We don't know how much of the + * old chunk size was the requested size; it could have been as small + * as one byte. We have to be conservative and just mark the entire + * old portion DEFINED. Make sure not to mark memory beyond the new + * allocation in case it's smaller than the old one. + */ + VALGRIND_MAKE_MEM_DEFINED(pointer, Min(size, oldchksize)); +#endif + + /* Ensure any padding bytes are marked NOACCESS. */ + VALGRIND_MAKE_MEM_NOACCESS((char *) pointer + size, chksize - size); + + /* Disallow access to the chunk header . */ + VALGRIND_MAKE_MEM_NOACCESS(chunk, ALLOC_CHUNKHDRSZ); + + return pointer; + } + + block = MemoryChunkGetBlock(chunk); + + /* + * In this path, for speed reasons we just Assert that the referenced + * block is good. We can also Assert that the value field is sane. Future + * field experience may show that these Asserts had better become regular + * runtime test-and-elog checks. + */ + Assert(AllocBlockIsValid(block)); + set = block->aset; + + fidx = MemoryChunkGetValue(chunk); + Assert(FreeListIdxIsValid(fidx)); + oldchksize = GetChunkSizeFromFreeListIdx(fidx); + +#ifdef MEMORY_CONTEXT_CHECKING + /* Test for someone scribbling on unused space in chunk */ + if (chunk->requested_size < oldchksize) + if (!sentinel_ok(pointer, chunk->requested_size)) + elog(WARNING, "detected write past chunk end in %s %p", + set->header.name, chunk); +#endif + + /* + * Chunk sizes are aligned to power of 2 in AllocSetAlloc(). Maybe the + * allocated area already is >= the new size. (In particular, we will + * fall out here if the requested size is a decrease.) + */ + if (oldchksize >= size) + { +#ifdef MEMORY_CONTEXT_CHECKING + Size oldrequest = chunk->requested_size; + +#ifdef RANDOMIZE_ALLOCATED_MEMORY + /* We can only fill the extra space if we know the prior request */ + if (size > oldrequest) + randomize_mem((char *) pointer + oldrequest, + size - oldrequest); +#endif + + chunk->requested_size = size; + + /* + * If this is an increase, mark any newly-available part UNDEFINED. + * Otherwise, mark the obsolete part NOACCESS. + */ + if (size > oldrequest) + VALGRIND_MAKE_MEM_UNDEFINED((char *) pointer + oldrequest, + size - oldrequest); + else + VALGRIND_MAKE_MEM_NOACCESS((char *) pointer + size, + oldchksize - size); + + /* set mark to catch clobber of "unused" space */ + if (size < oldchksize) + set_sentinel(pointer, size); +#else /* !MEMORY_CONTEXT_CHECKING */ + + /* + * We don't have the information to determine whether we're growing + * the old request or shrinking it, so we conservatively mark the + * entire new allocation DEFINED. + */ + VALGRIND_MAKE_MEM_NOACCESS(pointer, oldchksize); + VALGRIND_MAKE_MEM_DEFINED(pointer, size); +#endif + + /* Disallow access to the chunk header. */ + VALGRIND_MAKE_MEM_NOACCESS(chunk, ALLOC_CHUNKHDRSZ); + + return pointer; + } + else + { + /* + * Enlarge-a-small-chunk case. We just do this by brute force, ie, + * allocate a new chunk and copy the data. Since we know the existing + * data isn't huge, this won't involve any great memcpy expense, so + * it's not worth being smarter. (At one time we tried to avoid + * memcpy when it was possible to enlarge the chunk in-place, but that + * turns out to misbehave unpleasantly for repeated cycles of + * palloc/repalloc/pfree: the eventually freed chunks go into the + * wrong freelist for the next initial palloc request, and so we leak + * memory indefinitely. See pgsql-hackers archives for 2007-08-11.) + */ + AllocPointer newPointer; + Size oldsize; + + /* allocate new chunk */ + newPointer = AllocSetAlloc((MemoryContext) set, size); + + /* leave immediately if request was not completed */ + if (newPointer == NULL) + { + /* Disallow access to the chunk header. */ + VALGRIND_MAKE_MEM_NOACCESS(chunk, ALLOC_CHUNKHDRSZ); + return NULL; + } + + /* + * AllocSetAlloc() may have returned a region that is still NOACCESS. + * Change it to UNDEFINED for the moment; memcpy() will then transfer + * definedness from the old allocation to the new. If we know the old + * allocation, copy just that much. Otherwise, make the entire old + * chunk defined to avoid errors as we copy the currently-NOACCESS + * trailing bytes. + */ + VALGRIND_MAKE_MEM_UNDEFINED(newPointer, size); +#ifdef MEMORY_CONTEXT_CHECKING + oldsize = chunk->requested_size; +#else + oldsize = oldchksize; + VALGRIND_MAKE_MEM_DEFINED(pointer, oldsize); +#endif + + /* transfer existing data (certain to fit) */ + memcpy(newPointer, pointer, oldsize); + + /* free old chunk */ + AllocSetFree(pointer); + + return newPointer; + } +} + +/* + * AllocSetGetChunkContext + * Return the MemoryContext that 'pointer' belongs to. + */ +MemoryContext +AllocSetGetChunkContext(void *pointer) +{ + MemoryChunk *chunk = PointerGetMemoryChunk(pointer); + AllocBlock block; + AllocSet set; + + /* Allow access to the chunk header. */ + VALGRIND_MAKE_MEM_DEFINED(chunk, ALLOC_CHUNKHDRSZ); + + if (MemoryChunkIsExternal(chunk)) + block = ExternalChunkGetBlock(chunk); + else + block = (AllocBlock) MemoryChunkGetBlock(chunk); + + /* Disallow access to the chunk header. */ + VALGRIND_MAKE_MEM_NOACCESS(chunk, ALLOC_CHUNKHDRSZ); + + Assert(AllocBlockIsValid(block)); + set = block->aset; + + return &set->header; +} + +/* + * AllocSetGetChunkSpace + * Given a currently-allocated chunk, determine the total space + * it occupies (including all memory-allocation overhead). + */ +Size +AllocSetGetChunkSpace(void *pointer) +{ + MemoryChunk *chunk = PointerGetMemoryChunk(pointer); + int fidx; + + /* Allow access to the chunk header. */ + VALGRIND_MAKE_MEM_DEFINED(chunk, ALLOC_CHUNKHDRSZ); + + if (MemoryChunkIsExternal(chunk)) + { + AllocBlock block = ExternalChunkGetBlock(chunk); + + /* Disallow access to the chunk header. */ + VALGRIND_MAKE_MEM_NOACCESS(chunk, ALLOC_CHUNKHDRSZ); + + Assert(AllocBlockIsValid(block)); + + return block->endptr - (char *) chunk; + } + + fidx = MemoryChunkGetValue(chunk); + Assert(FreeListIdxIsValid(fidx)); + + /* Disallow access to the chunk header. */ + VALGRIND_MAKE_MEM_NOACCESS(chunk, ALLOC_CHUNKHDRSZ); + + return GetChunkSizeFromFreeListIdx(fidx) + ALLOC_CHUNKHDRSZ; +} + +/* + * AllocSetIsEmpty + * Is an allocset empty of any allocated space? + */ +bool +AllocSetIsEmpty(MemoryContext context) +{ + Assert(AllocSetIsValid(context)); + + /* + * For now, we say "empty" only if the context is new or just reset. We + * could examine the freelists to determine if all space has been freed, + * but it's not really worth the trouble for present uses of this + * functionality. + */ + if (context->isReset) + return true; + return false; +} + +/* + * AllocSetStats + * Compute stats about memory consumption of an allocset. + * + * printfunc: if not NULL, pass a human-readable stats string to this. + * passthru: pass this pointer through to printfunc. + * totals: if not NULL, add stats about this context into *totals. + * print_to_stderr: print stats to stderr if true, elog otherwise. + */ +void +AllocSetStats(MemoryContext context, + MemoryStatsPrintFunc printfunc, void *passthru, + MemoryContextCounters *totals, bool print_to_stderr) +{ + AllocSet set = (AllocSet) context; + Size nblocks = 0; + Size freechunks = 0; + Size totalspace; + Size freespace = 0; + AllocBlock block; + int fidx; + + Assert(AllocSetIsValid(set)); + + /* Include context header in totalspace */ + totalspace = MAXALIGN(sizeof(AllocSetContext)); + + for (block = set->blocks; block != NULL; block = block->next) + { + nblocks++; + totalspace += block->endptr - ((char *) block); + freespace += block->endptr - block->freeptr; + } + for (fidx = 0; fidx < ALLOCSET_NUM_FREELISTS; fidx++) + { + Size chksz = GetChunkSizeFromFreeListIdx(fidx); + MemoryChunk *chunk = set->freelist[fidx]; + + while (chunk != NULL) + { + AllocFreeListLink *link = GetFreeListLink(chunk); + + /* Allow access to the chunk header. */ + VALGRIND_MAKE_MEM_DEFINED(chunk, ALLOC_CHUNKHDRSZ); + Assert(MemoryChunkGetValue(chunk) == fidx); + VALGRIND_MAKE_MEM_NOACCESS(chunk, ALLOC_CHUNKHDRSZ); + + freechunks++; + freespace += chksz + ALLOC_CHUNKHDRSZ; + + VALGRIND_MAKE_MEM_DEFINED(link, sizeof(AllocFreeListLink)); + chunk = link->next; + VALGRIND_MAKE_MEM_NOACCESS(link, sizeof(AllocFreeListLink)); + } + } + + if (printfunc) + { + char stats_string[200]; + + snprintf(stats_string, sizeof(stats_string), + "%zu total in %zu blocks; %zu free (%zu chunks); %zu used", + totalspace, nblocks, freespace, freechunks, + totalspace - freespace); + printfunc(context, passthru, stats_string, print_to_stderr); + } + + if (totals) + { + totals->nblocks += nblocks; + totals->freechunks += freechunks; + totals->totalspace += totalspace; + totals->freespace += freespace; + } +} + + +#ifdef MEMORY_CONTEXT_CHECKING + +/* + * AllocSetCheck + * Walk through chunks and check consistency of memory. + * + * NOTE: report errors as WARNING, *not* ERROR or FATAL. Otherwise you'll + * find yourself in an infinite loop when trouble occurs, because this + * routine will be entered again when elog cleanup tries to release memory! + */ +void +AllocSetCheck(MemoryContext context) +{ + AllocSet set = (AllocSet) context; + const char *name = set->header.name; + AllocBlock prevblock; + AllocBlock block; + Size total_allocated = 0; + + for (prevblock = NULL, block = set->blocks; + block != NULL; + prevblock = block, block = block->next) + { + char *bpoz = ((char *) block) + ALLOC_BLOCKHDRSZ; + long blk_used = block->freeptr - bpoz; + long blk_data = 0; + long nchunks = 0; + bool has_external_chunk = false; + + if (set->keeper == block) + total_allocated += block->endptr - ((char *) set); + else + total_allocated += block->endptr - ((char *) block); + + /* + * Empty block - empty can be keeper-block only + */ + if (!blk_used) + { + if (set->keeper != block) + elog(WARNING, "problem in alloc set %s: empty block %p", + name, block); + } + + /* + * Check block header fields + */ + if (block->aset != set || + block->prev != prevblock || + block->freeptr < bpoz || + block->freeptr > block->endptr) + elog(WARNING, "problem in alloc set %s: corrupt header in block %p", + name, block); + + /* + * Chunk walker + */ + while (bpoz < block->freeptr) + { + MemoryChunk *chunk = (MemoryChunk *) bpoz; + Size chsize, + dsize; + + /* Allow access to the chunk header. */ + VALGRIND_MAKE_MEM_DEFINED(chunk, ALLOC_CHUNKHDRSZ); + + if (MemoryChunkIsExternal(chunk)) + { + chsize = block->endptr - (char *) MemoryChunkGetPointer(chunk); /* aligned chunk size */ + has_external_chunk = true; + + /* make sure this chunk consumes the entire block */ + if (chsize + ALLOC_CHUNKHDRSZ != blk_used) + elog(WARNING, "problem in alloc set %s: bad single-chunk %p in block %p", + name, chunk, block); + } + else + { + int fidx = MemoryChunkGetValue(chunk); + + if (!FreeListIdxIsValid(fidx)) + elog(WARNING, "problem in alloc set %s: bad chunk size for chunk %p in block %p", + name, chunk, block); + + chsize = GetChunkSizeFromFreeListIdx(fidx); /* aligned chunk size */ + + /* + * Check the stored block offset correctly references this + * block. + */ + if (block != MemoryChunkGetBlock(chunk)) + elog(WARNING, "problem in alloc set %s: bad block offset for chunk %p in block %p", + name, chunk, block); + } + dsize = chunk->requested_size; /* real data */ + + /* an allocated chunk's requested size must be <= the chsize */ + if (dsize != InvalidAllocSize && dsize > chsize) + elog(WARNING, "problem in alloc set %s: req size > alloc size for chunk %p in block %p", + name, chunk, block); + + /* chsize must not be smaller than the first freelist's size */ + if (chsize < (1 << ALLOC_MINBITS)) + elog(WARNING, "problem in alloc set %s: bad size %zu for chunk %p in block %p", + name, chsize, chunk, block); + + /* + * Check for overwrite of padding space in an allocated chunk. + */ + if (dsize != InvalidAllocSize && dsize < chsize && + !sentinel_ok(chunk, ALLOC_CHUNKHDRSZ + dsize)) + elog(WARNING, "problem in alloc set %s: detected write past chunk end in block %p, chunk %p", + name, block, chunk); + + /* if chunk is allocated, disallow access to the chunk header */ + if (dsize != InvalidAllocSize) + VALGRIND_MAKE_MEM_NOACCESS(chunk, ALLOC_CHUNKHDRSZ); + + blk_data += chsize; + nchunks++; + + bpoz += ALLOC_CHUNKHDRSZ + chsize; + } + + if ((blk_data + (nchunks * ALLOC_CHUNKHDRSZ)) != blk_used) + elog(WARNING, "problem in alloc set %s: found inconsistent memory block %p", + name, block); + + if (has_external_chunk && nchunks > 1) + elog(WARNING, "problem in alloc set %s: external chunk on non-dedicated block %p", + name, block); + } + + Assert(total_allocated == context->mem_allocated); +} + +#endif /* MEMORY_CONTEXT_CHECKING */ diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/mmgr/dsa.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/mmgr/dsa.c new file mode 100644 index 00000000000..2739169165e --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/mmgr/dsa.c @@ -0,0 +1,2331 @@ +/*------------------------------------------------------------------------- + * + * dsa.c + * Dynamic shared memory areas. + * + * This module provides dynamic shared memory areas which are built on top of + * DSM segments. While dsm.c allows segments of memory of shared memory to be + * created and shared between backends, it isn't designed to deal with small + * objects. A DSA area is a shared memory heap usually backed by one or more + * DSM segments which can allocate memory using dsa_allocate() and dsa_free(). + * Alternatively, it can be created in pre-existing shared memory, including a + * DSM segment, and then create extra DSM segments as required. Unlike the + * regular system heap, it deals in pseudo-pointers which must be converted to + * backend-local pointers before they are dereferenced. These pseudo-pointers + * can however be shared with other backends, and can be used to construct + * shared data structures. + * + * Each DSA area manages a set of DSM segments, adding new segments as + * required and detaching them when they are no longer needed. Each segment + * contains a number of 4KB pages, a free page manager for tracking + * consecutive runs of free pages, and a page map for tracking the source of + * objects allocated on each page. Allocation requests above 8KB are handled + * by choosing a segment and finding consecutive free pages in its free page + * manager. Allocation requests for smaller sizes are handled using pools of + * objects of a selection of sizes. Each pool consists of a number of 16 page + * (64KB) superblocks allocated in the same way as large objects. Allocation + * of large objects and new superblocks is serialized by a single LWLock, but + * allocation of small objects from pre-existing superblocks uses one LWLock + * per pool. Currently there is one pool, and therefore one lock, per size + * class. Per-core pools to increase concurrency and strategies for reducing + * the resulting fragmentation are areas for future research. Each superblock + * is managed with a 'span', which tracks the superblock's freelist. Free + * requests are handled by looking in the page map to find which span an + * address was allocated from, so that small objects can be returned to the + * appropriate free list, and large object pages can be returned directly to + * the free page map. When allocating, simple heuristics for selecting + * segments and superblocks try to encourage occupied memory to be + * concentrated, increasing the likelihood that whole superblocks can become + * empty and be returned to the free page manager, and whole segments can + * become empty and be returned to the operating system. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/utils/mmgr/dsa.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "port/atomics.h" +#include "port/pg_bitutils.h" +#include "storage/dsm.h" +#include "storage/ipc.h" +#include "storage/lwlock.h" +#include "storage/shmem.h" +#include "utils/dsa.h" +#include "utils/freepage.h" +#include "utils/memutils.h" + +/* + * The size of the initial DSM segment that backs a dsa_area created by + * dsa_create. After creating some number of segments of this size we'll + * double this size, and so on. Larger segments may be created if necessary + * to satisfy large requests. + */ +#define DSA_INITIAL_SEGMENT_SIZE ((size_t) (1 * 1024 * 1024)) + +/* + * How many segments to create before we double the segment size. If this is + * low, then there is likely to be a lot of wasted space in the largest + * segment. If it is high, then we risk running out of segment slots (see + * dsm.c's limits on total number of segments), or limiting the total size + * an area can manage when using small pointers. + */ +#define DSA_NUM_SEGMENTS_AT_EACH_SIZE 2 + +/* + * The number of bits used to represent the offset part of a dsa_pointer. + * This controls the maximum size of a segment, the maximum possible + * allocation size and also the maximum number of segments per area. + */ +#if SIZEOF_DSA_POINTER == 4 +#define DSA_OFFSET_WIDTH 27 /* 32 segments of size up to 128MB */ +#else +#define DSA_OFFSET_WIDTH 40 /* 1024 segments of size up to 1TB */ +#endif + +/* + * The maximum number of DSM segments that an area can own, determined by + * the number of bits remaining (but capped at 1024). + */ +#define DSA_MAX_SEGMENTS \ + Min(1024, (1 << ((SIZEOF_DSA_POINTER * 8) - DSA_OFFSET_WIDTH))) + +/* The bitmask for extracting the offset from a dsa_pointer. */ +#define DSA_OFFSET_BITMASK (((dsa_pointer) 1 << DSA_OFFSET_WIDTH) - 1) + +/* The maximum size of a DSM segment. */ +#define DSA_MAX_SEGMENT_SIZE ((size_t) 1 << DSA_OFFSET_WIDTH) + +/* Number of pages (see FPM_PAGE_SIZE) per regular superblock. */ +#define DSA_PAGES_PER_SUPERBLOCK 16 + +/* + * A magic number used as a sanity check for following DSM segments belonging + * to a DSA area (this number will be XORed with the area handle and + * the segment index). + */ +#define DSA_SEGMENT_HEADER_MAGIC 0x0ce26608 + +/* Build a dsa_pointer given a segment number and offset. */ +#define DSA_MAKE_POINTER(segment_number, offset) \ + (((dsa_pointer) (segment_number) << DSA_OFFSET_WIDTH) | (offset)) + +/* Extract the segment number from a dsa_pointer. */ +#define DSA_EXTRACT_SEGMENT_NUMBER(dp) ((dp) >> DSA_OFFSET_WIDTH) + +/* Extract the offset from a dsa_pointer. */ +#define DSA_EXTRACT_OFFSET(dp) ((dp) & DSA_OFFSET_BITMASK) + +/* The type used for index segment indexes (zero based). */ +typedef size_t dsa_segment_index; + +/* Sentinel value for dsa_segment_index indicating 'none' or 'end'. */ +#define DSA_SEGMENT_INDEX_NONE (~(dsa_segment_index)0) + +/* + * How many bins of segments do we have? The bins are used to categorize + * segments by their largest contiguous run of free pages. + */ +#define DSA_NUM_SEGMENT_BINS 16 + +/* + * What is the lowest bin that holds segments that *might* have n contiguous + * free pages? There is no point in looking in segments in lower bins; they + * definitely can't service a request for n free pages. + */ +static inline size_t +contiguous_pages_to_segment_bin(size_t n) +{ + size_t bin; + + if (n == 0) + bin = 0; + else + bin = pg_leftmost_one_pos_size_t(n) + 1; + + return Min(bin, DSA_NUM_SEGMENT_BINS - 1); +} + +/* Macros for access to locks. */ +#define DSA_AREA_LOCK(area) (&area->control->lock) +#define DSA_SCLASS_LOCK(area, sclass) (&area->control->pools[sclass].lock) + +/* + * The header for an individual segment. This lives at the start of each DSM + * segment owned by a DSA area including the first segment (where it appears + * as part of the dsa_area_control struct). + */ +typedef struct +{ + /* Sanity check magic value. */ + uint32 magic; + /* Total number of pages in this segment (excluding metadata area). */ + size_t usable_pages; + /* Total size of this segment in bytes. */ + size_t size; + + /* + * Index of the segment that precedes this one in the same segment bin, or + * DSA_SEGMENT_INDEX_NONE if this is the first one. + */ + dsa_segment_index prev; + + /* + * Index of the segment that follows this one in the same segment bin, or + * DSA_SEGMENT_INDEX_NONE if this is the last one. + */ + dsa_segment_index next; + /* The index of the bin that contains this segment. */ + size_t bin; + + /* + * A flag raised to indicate that this segment is being returned to the + * operating system and has been unpinned. + */ + bool freed; +} dsa_segment_header; + +/* + * Metadata for one superblock. + * + * For most blocks, span objects are stored out-of-line; that is, the span + * object is not stored within the block itself. But, as an exception, for a + * "span of spans", the span object is stored "inline". The allocation is + * always exactly one page, and the dsa_area_span object is located at + * the beginning of that page. The size class is DSA_SCLASS_BLOCK_OF_SPANS, + * and the remaining fields are used just as they would be in an ordinary + * block. We can't allocate spans out of ordinary superblocks because + * creating an ordinary superblock requires us to be able to allocate a span + * *first*. Doing it this way avoids that circularity. + */ +typedef struct +{ + dsa_pointer pool; /* Containing pool. */ + dsa_pointer prevspan; /* Previous span. */ + dsa_pointer nextspan; /* Next span. */ + dsa_pointer start; /* Starting address. */ + size_t npages; /* Length of span in pages. */ + uint16 size_class; /* Size class. */ + uint16 ninitialized; /* Maximum number of objects ever allocated. */ + uint16 nallocatable; /* Number of objects currently allocatable. */ + uint16 firstfree; /* First object on free list. */ + uint16 nmax; /* Maximum number of objects ever possible. */ + uint16 fclass; /* Current fullness class. */ +} dsa_area_span; + +/* + * Given a pointer to an object in a span, access the index of the next free + * object in the same span (ie in the span's freelist) as an L-value. + */ +#define NextFreeObjectIndex(object) (* (uint16 *) (object)) + +/* + * Small allocations are handled by dividing a single block of memory into + * many small objects of equal size. The possible allocation sizes are + * defined by the following array. Larger size classes are spaced more widely + * than smaller size classes. We fudge the spacing for size classes >1kB to + * avoid space wastage: based on the knowledge that we plan to allocate 64kB + * blocks, we bump the maximum object size up to the largest multiple of + * 8 bytes that still lets us fit the same number of objects into one block. + * + * NB: Because of this fudging, if we were ever to use differently-sized blocks + * for small allocations, these size classes would need to be reworked to be + * optimal for the new size. + * + * NB: The optimal spacing for size classes, as well as the size of the blocks + * out of which small objects are allocated, is not a question that has one + * right answer. Some allocators (such as tcmalloc) use more closely-spaced + * size classes than we do here, while others (like aset.c) use more + * widely-spaced classes. Spacing the classes more closely avoids wasting + * memory within individual chunks, but also means a larger number of + * potentially-unfilled blocks. + */ +static const uint16 dsa_size_classes[] = { + sizeof(dsa_area_span), 0, /* special size classes */ + 8, 16, 24, 32, 40, 48, 56, 64, /* 8 classes separated by 8 bytes */ + 80, 96, 112, 128, /* 4 classes separated by 16 bytes */ + 160, 192, 224, 256, /* 4 classes separated by 32 bytes */ + 320, 384, 448, 512, /* 4 classes separated by 64 bytes */ + 640, 768, 896, 1024, /* 4 classes separated by 128 bytes */ + 1280, 1560, 1816, 2048, /* 4 classes separated by ~256 bytes */ + 2616, 3120, 3640, 4096, /* 4 classes separated by ~512 bytes */ + 5456, 6552, 7280, 8192 /* 4 classes separated by ~1024 bytes */ +}; +#define DSA_NUM_SIZE_CLASSES lengthof(dsa_size_classes) + +/* Special size classes. */ +#define DSA_SCLASS_BLOCK_OF_SPANS 0 +#define DSA_SCLASS_SPAN_LARGE 1 + +/* + * The following lookup table is used to map the size of small objects + * (less than 1kB) onto the corresponding size class. To use this table, + * round the size of the object up to the next multiple of 8 bytes, and then + * index into this array. + */ +static const uint8 dsa_size_class_map[] = { + 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 11, 11, 12, 12, 13, 13, + 14, 14, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 17, 17, 17, 17, + 18, 18, 18, 18, 18, 18, 18, 18, 19, 19, 19, 19, 19, 19, 19, 19, + 20, 20, 20, 20, 20, 20, 20, 20, 21, 21, 21, 21, 21, 21, 21, 21, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25 +}; +#define DSA_SIZE_CLASS_MAP_QUANTUM 8 + +/* + * Superblocks are binned by how full they are. Generally, each fullness + * class corresponds to one quartile, but the block being used for + * allocations is always at the head of the list for fullness class 1, + * regardless of how full it really is. + */ +#define DSA_FULLNESS_CLASSES 4 + +/* + * A dsa_area_pool represents a set of objects of a given size class. + * + * Perhaps there should be multiple pools for the same size class for + * contention avoidance, but for now there is just one! + */ +typedef struct +{ + /* A lock protecting access to this pool. */ + LWLock lock; + /* A set of linked lists of spans, arranged by fullness. */ + dsa_pointer spans[DSA_FULLNESS_CLASSES]; + /* Should we pad this out to a cacheline boundary? */ +} dsa_area_pool; + +/* + * The control block for an area. This lives in shared memory, at the start of + * the first DSM segment controlled by this area. + */ +typedef struct +{ + /* The segment header for the first segment. */ + dsa_segment_header segment_header; + /* The handle for this area. */ + dsa_handle handle; + /* The handles of the segments owned by this area. */ + dsm_handle segment_handles[DSA_MAX_SEGMENTS]; + /* Lists of segments, binned by maximum contiguous run of free pages. */ + dsa_segment_index segment_bins[DSA_NUM_SEGMENT_BINS]; + /* The object pools for each size class. */ + dsa_area_pool pools[DSA_NUM_SIZE_CLASSES]; + /* The total size of all active segments. */ + size_t total_segment_size; + /* The maximum total size of backing storage we are allowed. */ + size_t max_total_segment_size; + /* Highest used segment index in the history of this area. */ + dsa_segment_index high_segment_index; + /* The reference count for this area. */ + int refcnt; + /* A flag indicating that this area has been pinned. */ + bool pinned; + /* The number of times that segments have been freed. */ + size_t freed_segment_counter; + /* The LWLock tranche ID. */ + int lwlock_tranche_id; + /* The general lock (protects everything except object pools). */ + LWLock lock; +} dsa_area_control; + +/* Given a pointer to a pool, find a dsa_pointer. */ +#define DsaAreaPoolToDsaPointer(area, p) \ + DSA_MAKE_POINTER(0, (char *) p - (char *) area->control) + +/* + * A dsa_segment_map is stored within the backend-private memory of each + * individual backend. It holds the base address of the segment within that + * backend, plus the addresses of key objects within the segment. Those + * could instead be derived from the base address but it's handy to have them + * around. + */ +typedef struct +{ + dsm_segment *segment; /* DSM segment */ + char *mapped_address; /* Address at which segment is mapped */ + dsa_segment_header *header; /* Header (same as mapped_address) */ + FreePageManager *fpm; /* Free page manager within segment. */ + dsa_pointer *pagemap; /* Page map within segment. */ +} dsa_segment_map; + +/* + * Per-backend state for a storage area. Backends obtain one of these by + * creating an area or attaching to an existing one using a handle. Each + * process that needs to use an area uses its own object to track where the + * segments are mapped. + */ +struct dsa_area +{ + /* Pointer to the control object in shared memory. */ + dsa_area_control *control; + + /* Has the mapping been pinned? */ + bool mapping_pinned; + + /* + * This backend's array of segment maps, ordered by segment index + * corresponding to control->segment_handles. Some of the area's segments + * may not be mapped in this backend yet, and some slots may have been + * freed and need to be detached; these operations happen on demand. + */ + dsa_segment_map segment_maps[DSA_MAX_SEGMENTS]; + + /* The highest segment index this backend has ever mapped. */ + dsa_segment_index high_segment_index; + + /* The last observed freed_segment_counter. */ + size_t freed_segment_counter; +}; + +#define DSA_SPAN_NOTHING_FREE ((uint16) -1) +#define DSA_SUPERBLOCK_SIZE (DSA_PAGES_PER_SUPERBLOCK * FPM_PAGE_SIZE) + +/* Given a pointer to a segment_map, obtain a segment index number. */ +#define get_segment_index(area, segment_map_ptr) \ + (segment_map_ptr - &area->segment_maps[0]) + +static void init_span(dsa_area *area, dsa_pointer span_pointer, + dsa_area_pool *pool, dsa_pointer start, size_t npages, + uint16 size_class); +static bool transfer_first_span(dsa_area *area, dsa_area_pool *pool, + int fromclass, int toclass); +static inline dsa_pointer alloc_object(dsa_area *area, int size_class); +static bool ensure_active_superblock(dsa_area *area, dsa_area_pool *pool, + int size_class); +static dsa_segment_map *get_segment_by_index(dsa_area *area, + dsa_segment_index index); +static void destroy_superblock(dsa_area *area, dsa_pointer span_pointer); +static void unlink_span(dsa_area *area, dsa_area_span *span); +static void add_span_to_fullness_class(dsa_area *area, dsa_area_span *span, + dsa_pointer span_pointer, int fclass); +static void unlink_segment(dsa_area *area, dsa_segment_map *segment_map); +static dsa_segment_map *get_best_segment(dsa_area *area, size_t npages); +static dsa_segment_map *make_new_segment(dsa_area *area, size_t requested_pages); +static dsa_area *create_internal(void *place, size_t size, + int tranche_id, + dsm_handle control_handle, + dsm_segment *control_segment); +static dsa_area *attach_internal(void *place, dsm_segment *segment, + dsa_handle handle); +static void check_for_freed_segments(dsa_area *area); +static void check_for_freed_segments_locked(dsa_area *area); +static void rebin_segment(dsa_area *area, dsa_segment_map *segment_map); + +/* + * Create a new shared area in a new DSM segment. Further DSM segments will + * be allocated as required to extend the available space. + * + * We can't allocate a LWLock tranche_id within this function, because tranche + * IDs are a scarce resource; there are only 64k available, using low numbers + * when possible matters, and we have no provision for recycling them. So, + * we require the caller to provide one. + */ +dsa_area * +dsa_create(int tranche_id) +{ + dsm_segment *segment; + dsa_area *area; + + /* + * Create the DSM segment that will hold the shared control object and the + * first segment of usable space. + */ + segment = dsm_create(DSA_INITIAL_SEGMENT_SIZE, 0); + + /* + * All segments backing this area are pinned, so that DSA can explicitly + * control their lifetime (otherwise a newly created segment belonging to + * this area might be freed when the only backend that happens to have it + * mapped in ends, corrupting the area). + */ + dsm_pin_segment(segment); + + /* Create a new DSA area with the control object in this segment. */ + area = create_internal(dsm_segment_address(segment), + DSA_INITIAL_SEGMENT_SIZE, + tranche_id, + dsm_segment_handle(segment), segment); + + /* Clean up when the control segment detaches. */ + on_dsm_detach(segment, &dsa_on_dsm_detach_release_in_place, + PointerGetDatum(dsm_segment_address(segment))); + + return area; +} + +/* + * Create a new shared area in an existing shared memory space, which may be + * either DSM or Postmaster-initialized memory. DSM segments will be + * allocated as required to extend the available space, though that can be + * prevented with dsa_set_size_limit(area, size) using the same size provided + * to dsa_create_in_place. + * + * Areas created in-place must eventually be released by the backend that + * created them and all backends that attach to them. This can be done + * explicitly with dsa_release_in_place, or, in the special case that 'place' + * happens to be in a pre-existing DSM segment, by passing in a pointer to the + * segment so that a detach hook can be registered with the containing DSM + * segment. + * + * See dsa_create() for a note about the tranche arguments. + */ +dsa_area * +dsa_create_in_place(void *place, size_t size, + int tranche_id, dsm_segment *segment) +{ + dsa_area *area; + + area = create_internal(place, size, tranche_id, + DSM_HANDLE_INVALID, NULL); + + /* + * Clean up when the control segment detaches, if a containing DSM segment + * was provided. + */ + if (segment != NULL) + on_dsm_detach(segment, &dsa_on_dsm_detach_release_in_place, + PointerGetDatum(place)); + + return area; +} + +/* + * Obtain a handle that can be passed to other processes so that they can + * attach to the given area. Cannot be called for areas created with + * dsa_create_in_place. + */ +dsa_handle +dsa_get_handle(dsa_area *area) +{ + Assert(area->control->handle != DSA_HANDLE_INVALID); + return area->control->handle; +} + +/* + * Attach to an area given a handle generated (possibly in another process) by + * dsa_get_handle. The area must have been created with dsa_create (not + * dsa_create_in_place). + */ +dsa_area * +dsa_attach(dsa_handle handle) +{ + dsm_segment *segment; + dsa_area *area; + + /* + * An area handle is really a DSM segment handle for the first segment, so + * we go ahead and attach to that. + */ + segment = dsm_attach(handle); + if (segment == NULL) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("could not attach to dynamic shared area"))); + + area = attach_internal(dsm_segment_address(segment), segment, handle); + + /* Clean up when the control segment detaches. */ + on_dsm_detach(segment, &dsa_on_dsm_detach_release_in_place, + PointerGetDatum(dsm_segment_address(segment))); + + return area; +} + +/* + * Attach to an area that was created with dsa_create_in_place. The caller + * must somehow know the location in memory that was used when the area was + * created, though it may be mapped at a different virtual address in this + * process. + * + * See dsa_create_in_place for note about releasing in-place areas, and the + * optional 'segment' argument which can be provided to allow automatic + * release if the containing memory happens to be a DSM segment. + */ +dsa_area * +dsa_attach_in_place(void *place, dsm_segment *segment) +{ + dsa_area *area; + + area = attach_internal(place, NULL, DSA_HANDLE_INVALID); + + /* + * Clean up when the control segment detaches, if a containing DSM segment + * was provided. + */ + if (segment != NULL) + on_dsm_detach(segment, &dsa_on_dsm_detach_release_in_place, + PointerGetDatum(place)); + + return area; +} + +/* + * Release a DSA area that was produced by dsa_create_in_place or + * dsa_attach_in_place. The 'segment' argument is ignored but provides an + * interface suitable for on_dsm_detach, for the convenience of users who want + * to create a DSA segment inside an existing DSM segment and have it + * automatically released when the containing DSM segment is detached. + * 'place' should be the address of the place where the area was created. + * + * This callback is automatically registered for the DSM segment containing + * the control object of in-place areas when a segment is provided to + * dsa_create_in_place or dsa_attach_in_place, and also for all areas created + * with dsa_create. + */ +void +dsa_on_dsm_detach_release_in_place(dsm_segment *segment, Datum place) +{ + dsa_release_in_place(DatumGetPointer(place)); +} + +/* + * Release a DSA area that was produced by dsa_create_in_place or + * dsa_attach_in_place. The 'code' argument is ignored but provides an + * interface suitable for on_shmem_exit or before_shmem_exit, for the + * convenience of users who want to create a DSA segment inside shared memory + * other than a DSM segment and have it automatically release at backend exit. + * 'place' should be the address of the place where the area was created. + */ +void +dsa_on_shmem_exit_release_in_place(int code, Datum place) +{ + dsa_release_in_place(DatumGetPointer(place)); +} + +/* + * Release a DSA area that was produced by dsa_create_in_place or + * dsa_attach_in_place. It is preferable to use one of the 'dsa_on_XXX' + * callbacks so that this is managed automatically, because failure to release + * an area created in-place leaks its segments permanently. + * + * This is also called automatically for areas produced by dsa_create or + * dsa_attach as an implementation detail. + */ +void +dsa_release_in_place(void *place) +{ + dsa_area_control *control = (dsa_area_control *) place; + int i; + + LWLockAcquire(&control->lock, LW_EXCLUSIVE); + Assert(control->segment_header.magic == + (DSA_SEGMENT_HEADER_MAGIC ^ control->handle ^ 0)); + Assert(control->refcnt > 0); + if (--control->refcnt == 0) + { + for (i = 0; i <= control->high_segment_index; ++i) + { + dsm_handle handle; + + handle = control->segment_handles[i]; + if (handle != DSM_HANDLE_INVALID) + dsm_unpin_segment(handle); + } + } + LWLockRelease(&control->lock); +} + +/* + * Keep a DSA area attached until end of session or explicit detach. + * + * By default, areas are owned by the current resource owner, which means they + * are detached automatically when that scope ends. + */ +void +dsa_pin_mapping(dsa_area *area) +{ + int i; + + Assert(!area->mapping_pinned); + area->mapping_pinned = true; + + for (i = 0; i <= area->high_segment_index; ++i) + if (area->segment_maps[i].segment != NULL) + dsm_pin_mapping(area->segment_maps[i].segment); +} + +/* + * Allocate memory in this storage area. The return value is a dsa_pointer + * that can be passed to other processes, and converted to a local pointer + * with dsa_get_address. 'flags' is a bitmap which should be constructed + * from the following values: + * + * DSA_ALLOC_HUGE allows allocations >= 1GB. Otherwise, such allocations + * will result in an ERROR. + * + * DSA_ALLOC_NO_OOM causes this function to return InvalidDsaPointer when + * no memory is available or a size limit established by dsa_set_size_limit + * would be exceeded. Otherwise, such allocations will result in an ERROR. + * + * DSA_ALLOC_ZERO causes the allocated memory to be zeroed. Otherwise, the + * contents of newly-allocated memory are indeterminate. + * + * These flags correspond to similarly named flags used by + * MemoryContextAllocExtended(). See also the macros dsa_allocate and + * dsa_allocate0 which expand to a call to this function with commonly used + * flags. + */ +dsa_pointer +dsa_allocate_extended(dsa_area *area, size_t size, int flags) +{ + uint16 size_class; + dsa_pointer start_pointer; + dsa_segment_map *segment_map; + dsa_pointer result; + + Assert(size > 0); + + /* Sanity check on huge individual allocation size. */ + if (((flags & DSA_ALLOC_HUGE) != 0 && !AllocHugeSizeIsValid(size)) || + ((flags & DSA_ALLOC_HUGE) == 0 && !AllocSizeIsValid(size))) + elog(ERROR, "invalid DSA memory alloc request size %zu", size); + + /* + * If bigger than the largest size class, just grab a run of pages from + * the free page manager, instead of allocating an object from a pool. + * There will still be a span, but it's a special class of span that + * manages this whole allocation and simply gives all pages back to the + * free page manager when dsa_free is called. + */ + if (size > dsa_size_classes[lengthof(dsa_size_classes) - 1]) + { + size_t npages = fpm_size_to_pages(size); + size_t first_page; + dsa_pointer span_pointer; + dsa_area_pool *pool = &area->control->pools[DSA_SCLASS_SPAN_LARGE]; + + /* Obtain a span object. */ + span_pointer = alloc_object(area, DSA_SCLASS_BLOCK_OF_SPANS); + if (!DsaPointerIsValid(span_pointer)) + { + /* Raise error unless asked not to. */ + if ((flags & DSA_ALLOC_NO_OOM) == 0) + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"), + errdetail("Failed on DSA request of size %zu.", + size))); + return InvalidDsaPointer; + } + + LWLockAcquire(DSA_AREA_LOCK(area), LW_EXCLUSIVE); + + /* Find a segment from which to allocate. */ + segment_map = get_best_segment(area, npages); + if (segment_map == NULL) + segment_map = make_new_segment(area, npages); + if (segment_map == NULL) + { + /* Can't make any more segments: game over. */ + LWLockRelease(DSA_AREA_LOCK(area)); + dsa_free(area, span_pointer); + + /* Raise error unless asked not to. */ + if ((flags & DSA_ALLOC_NO_OOM) == 0) + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"), + errdetail("Failed on DSA request of size %zu.", + size))); + return InvalidDsaPointer; + } + + /* + * Ask the free page manager for a run of pages. This should always + * succeed, since both get_best_segment and make_new_segment should + * only return a non-NULL pointer if it actually contains enough + * contiguous freespace. If it does fail, something in our backend + * private state is out of whack, so use FATAL to kill the process. + */ + if (!FreePageManagerGet(segment_map->fpm, npages, &first_page)) + elog(FATAL, + "dsa_allocate could not find %zu free pages", npages); + LWLockRelease(DSA_AREA_LOCK(area)); + + start_pointer = DSA_MAKE_POINTER(get_segment_index(area, segment_map), + first_page * FPM_PAGE_SIZE); + + /* Initialize span and pagemap. */ + LWLockAcquire(DSA_SCLASS_LOCK(area, DSA_SCLASS_SPAN_LARGE), + LW_EXCLUSIVE); + init_span(area, span_pointer, pool, start_pointer, npages, + DSA_SCLASS_SPAN_LARGE); + segment_map->pagemap[first_page] = span_pointer; + LWLockRelease(DSA_SCLASS_LOCK(area, DSA_SCLASS_SPAN_LARGE)); + + /* Zero-initialize the memory if requested. */ + if ((flags & DSA_ALLOC_ZERO) != 0) + memset(dsa_get_address(area, start_pointer), 0, size); + + return start_pointer; + } + + /* Map allocation to a size class. */ + if (size < lengthof(dsa_size_class_map) * DSA_SIZE_CLASS_MAP_QUANTUM) + { + int mapidx; + + /* For smaller sizes we have a lookup table... */ + mapidx = ((size + DSA_SIZE_CLASS_MAP_QUANTUM - 1) / + DSA_SIZE_CLASS_MAP_QUANTUM) - 1; + size_class = dsa_size_class_map[mapidx]; + } + else + { + uint16 min; + uint16 max; + + /* ... and for the rest we search by binary chop. */ + min = dsa_size_class_map[lengthof(dsa_size_class_map) - 1]; + max = lengthof(dsa_size_classes) - 1; + + while (min < max) + { + uint16 mid = (min + max) / 2; + uint16 class_size = dsa_size_classes[mid]; + + if (class_size < size) + min = mid + 1; + else + max = mid; + } + + size_class = min; + } + Assert(size <= dsa_size_classes[size_class]); + Assert(size_class == 0 || size > dsa_size_classes[size_class - 1]); + + /* Attempt to allocate an object from the appropriate pool. */ + result = alloc_object(area, size_class); + + /* Check for failure to allocate. */ + if (!DsaPointerIsValid(result)) + { + /* Raise error unless asked not to. */ + if ((flags & DSA_ALLOC_NO_OOM) == 0) + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"), + errdetail("Failed on DSA request of size %zu.", size))); + return InvalidDsaPointer; + } + + /* Zero-initialize the memory if requested. */ + if ((flags & DSA_ALLOC_ZERO) != 0) + memset(dsa_get_address(area, result), 0, size); + + return result; +} + +/* + * Free memory obtained with dsa_allocate. + */ +void +dsa_free(dsa_area *area, dsa_pointer dp) +{ + dsa_segment_map *segment_map; + int pageno; + dsa_pointer span_pointer; + dsa_area_span *span; + char *superblock; + char *object; + size_t size; + int size_class; + + /* Make sure we don't have a stale segment in the slot 'dp' refers to. */ + check_for_freed_segments(area); + + /* Locate the object, span and pool. */ + segment_map = get_segment_by_index(area, DSA_EXTRACT_SEGMENT_NUMBER(dp)); + pageno = DSA_EXTRACT_OFFSET(dp) / FPM_PAGE_SIZE; + span_pointer = segment_map->pagemap[pageno]; + span = dsa_get_address(area, span_pointer); + superblock = dsa_get_address(area, span->start); + object = dsa_get_address(area, dp); + size_class = span->size_class; + if (size_class >= lengthof(dsa_size_classes)) + { + ereport(ERROR, + (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("invalid span size"), + errdetail("Invalid span->size_class value %zu, but dsa_size_classes size is %zu.", size_class, lengthof(dsa_size_classes)))); + } + size = dsa_size_classes[size_class]; + + /* + * Special case for large objects that live in a special span: we return + * those pages directly to the free page manager and free the span. + */ + if (span->size_class == DSA_SCLASS_SPAN_LARGE) + { + +#ifdef CLOBBER_FREED_MEMORY + memset(object, 0x7f, span->npages * FPM_PAGE_SIZE); +#endif + + /* Give pages back to free page manager. */ + LWLockAcquire(DSA_AREA_LOCK(area), LW_EXCLUSIVE); + FreePageManagerPut(segment_map->fpm, + DSA_EXTRACT_OFFSET(span->start) / FPM_PAGE_SIZE, + span->npages); + + /* Move segment to appropriate bin if necessary. */ + rebin_segment(area, segment_map); + LWLockRelease(DSA_AREA_LOCK(area)); + + /* Unlink span. */ + LWLockAcquire(DSA_SCLASS_LOCK(area, DSA_SCLASS_SPAN_LARGE), + LW_EXCLUSIVE); + unlink_span(area, span); + LWLockRelease(DSA_SCLASS_LOCK(area, DSA_SCLASS_SPAN_LARGE)); + /* Free the span object so it can be reused. */ + dsa_free(area, span_pointer); + return; + } + +#ifdef CLOBBER_FREED_MEMORY + memset(object, 0x7f, size); +#endif + + LWLockAcquire(DSA_SCLASS_LOCK(area, size_class), LW_EXCLUSIVE); + + /* Put the object on the span's freelist. */ + Assert(object >= superblock); + Assert(object < superblock + DSA_SUPERBLOCK_SIZE); + Assert((object - superblock) % size == 0); + NextFreeObjectIndex(object) = span->firstfree; + span->firstfree = (object - superblock) / size; + ++span->nallocatable; + + /* + * See if the span needs to moved to a different fullness class, or be + * freed so its pages can be given back to the segment. + */ + if (span->nallocatable == 1 && span->fclass == DSA_FULLNESS_CLASSES - 1) + { + /* + * The block was completely full and is located in the + * highest-numbered fullness class, which is never scanned for free + * chunks. We must move it to the next-lower fullness class. + */ + unlink_span(area, span); + add_span_to_fullness_class(area, span, span_pointer, + DSA_FULLNESS_CLASSES - 2); + + /* + * If this is the only span, and there is no active span, then we + * should probably move this span to fullness class 1. (Otherwise if + * you allocate exactly all the objects in the only span, it moves to + * class 3, then you free them all, it moves to 2, and then is given + * back, leaving no active span). + */ + } + else if (span->nallocatable == span->nmax && + (span->fclass != 1 || span->prevspan != InvalidDsaPointer)) + { + /* + * This entire block is free, and it's not the active block for this + * size class. Return the memory to the free page manager. We don't + * do this for the active block to prevent hysteresis: if we + * repeatedly allocate and free the only chunk in the active block, it + * will be very inefficient if we deallocate and reallocate the block + * every time. + */ + destroy_superblock(area, span_pointer); + } + + LWLockRelease(DSA_SCLASS_LOCK(area, size_class)); +} + +/* + * Obtain a backend-local address for a dsa_pointer. 'dp' must point to + * memory allocated by the given area (possibly in another process) that + * hasn't yet been freed. This may cause a segment to be mapped into the + * current process if required, and may cause freed segments to be unmapped. + */ +void * +dsa_get_address(dsa_area *area, dsa_pointer dp) +{ + dsa_segment_index index; + size_t offset; + + /* Convert InvalidDsaPointer to NULL. */ + if (!DsaPointerIsValid(dp)) + return NULL; + + /* Process any requests to detach from freed segments. */ + check_for_freed_segments(area); + + /* Break the dsa_pointer into its components. */ + index = DSA_EXTRACT_SEGMENT_NUMBER(dp); + offset = DSA_EXTRACT_OFFSET(dp); + Assert(index < DSA_MAX_SEGMENTS); + + /* Check if we need to cause this segment to be mapped in. */ + if (unlikely(area->segment_maps[index].mapped_address == NULL)) + { + /* Call for effect (we don't need the result). */ + get_segment_by_index(area, index); + } + + return area->segment_maps[index].mapped_address + offset; +} + +/* + * Pin this area, so that it will continue to exist even if all backends + * detach from it. In that case, the area can still be reattached to if a + * handle has been recorded somewhere. + */ +void +dsa_pin(dsa_area *area) +{ + LWLockAcquire(DSA_AREA_LOCK(area), LW_EXCLUSIVE); + if (area->control->pinned) + { + LWLockRelease(DSA_AREA_LOCK(area)); + elog(ERROR, "dsa_area already pinned"); + } + area->control->pinned = true; + ++area->control->refcnt; + LWLockRelease(DSA_AREA_LOCK(area)); +} + +/* + * Undo the effects of dsa_pin, so that the given area can be freed when no + * backends are attached to it. May be called only if dsa_pin has been + * called. + */ +void +dsa_unpin(dsa_area *area) +{ + LWLockAcquire(DSA_AREA_LOCK(area), LW_EXCLUSIVE); + Assert(area->control->refcnt > 1); + if (!area->control->pinned) + { + LWLockRelease(DSA_AREA_LOCK(area)); + elog(ERROR, "dsa_area not pinned"); + } + area->control->pinned = false; + --area->control->refcnt; + LWLockRelease(DSA_AREA_LOCK(area)); +} + +/* + * Set the total size limit for this area. This limit is checked whenever new + * segments need to be allocated from the operating system. If the new size + * limit is already exceeded, this has no immediate effect. + * + * Note that the total virtual memory usage may be temporarily larger than + * this limit when segments have been freed, but not yet detached by all + * backends that have attached to them. + */ +void +dsa_set_size_limit(dsa_area *area, size_t limit) +{ + LWLockAcquire(DSA_AREA_LOCK(area), LW_EXCLUSIVE); + area->control->max_total_segment_size = limit; + LWLockRelease(DSA_AREA_LOCK(area)); +} + +/* + * Aggressively free all spare memory in the hope of returning DSM segments to + * the operating system. + */ +void +dsa_trim(dsa_area *area) +{ + int size_class; + + /* + * Trim in reverse pool order so we get to the spans-of-spans last, just + * in case any become entirely free while processing all the other pools. + */ + for (size_class = DSA_NUM_SIZE_CLASSES - 1; size_class >= 0; --size_class) + { + dsa_area_pool *pool = &area->control->pools[size_class]; + dsa_pointer span_pointer; + + if (size_class == DSA_SCLASS_SPAN_LARGE) + { + /* Large object frees give back segments aggressively already. */ + continue; + } + + /* + * Search fullness class 1 only. That is where we expect to find an + * entirely empty superblock (entirely empty superblocks in other + * fullness classes are returned to the free page map by dsa_free). + */ + LWLockAcquire(DSA_SCLASS_LOCK(area, size_class), LW_EXCLUSIVE); + span_pointer = pool->spans[1]; + while (DsaPointerIsValid(span_pointer)) + { + dsa_area_span *span = dsa_get_address(area, span_pointer); + dsa_pointer next = span->nextspan; + + if (span->nallocatable == span->nmax) + destroy_superblock(area, span_pointer); + + span_pointer = next; + } + LWLockRelease(DSA_SCLASS_LOCK(area, size_class)); + } +} + +/* + * Print out debugging information about the internal state of the shared + * memory area. + */ +void +dsa_dump(dsa_area *area) +{ + size_t i, + j; + + /* + * Note: This gives an inconsistent snapshot as it acquires and releases + * individual locks as it goes... + */ + + LWLockAcquire(DSA_AREA_LOCK(area), LW_EXCLUSIVE); + check_for_freed_segments_locked(area); + fprintf(stderr, "dsa_area handle %x:\n", area->control->handle); + fprintf(stderr, " max_total_segment_size: %zu\n", + area->control->max_total_segment_size); + fprintf(stderr, " total_segment_size: %zu\n", + area->control->total_segment_size); + fprintf(stderr, " refcnt: %d\n", area->control->refcnt); + fprintf(stderr, " pinned: %c\n", area->control->pinned ? 't' : 'f'); + fprintf(stderr, " segment bins:\n"); + for (i = 0; i < DSA_NUM_SEGMENT_BINS; ++i) + { + if (area->control->segment_bins[i] != DSA_SEGMENT_INDEX_NONE) + { + dsa_segment_index segment_index; + + if (i == 0) + fprintf(stderr, + " segment bin %zu (no contiguous free pages):\n", i); + else + fprintf(stderr, + " segment bin %zu (at least %d contiguous pages free):\n", + i, 1 << (i - 1)); + segment_index = area->control->segment_bins[i]; + while (segment_index != DSA_SEGMENT_INDEX_NONE) + { + dsa_segment_map *segment_map; + + segment_map = + get_segment_by_index(area, segment_index); + + fprintf(stderr, + " segment index %zu, usable_pages = %zu, " + "contiguous_pages = %zu, mapped at %p\n", + segment_index, + segment_map->header->usable_pages, + fpm_largest(segment_map->fpm), + segment_map->mapped_address); + segment_index = segment_map->header->next; + } + } + } + LWLockRelease(DSA_AREA_LOCK(area)); + + fprintf(stderr, " pools:\n"); + for (i = 0; i < DSA_NUM_SIZE_CLASSES; ++i) + { + bool found = false; + + LWLockAcquire(DSA_SCLASS_LOCK(area, i), LW_EXCLUSIVE); + for (j = 0; j < DSA_FULLNESS_CLASSES; ++j) + if (DsaPointerIsValid(area->control->pools[i].spans[j])) + found = true; + if (found) + { + if (i == DSA_SCLASS_BLOCK_OF_SPANS) + fprintf(stderr, " pool for blocks of span objects:\n"); + else if (i == DSA_SCLASS_SPAN_LARGE) + fprintf(stderr, " pool for large object spans:\n"); + else + fprintf(stderr, + " pool for size class %zu (object size %hu bytes):\n", + i, dsa_size_classes[i]); + for (j = 0; j < DSA_FULLNESS_CLASSES; ++j) + { + if (!DsaPointerIsValid(area->control->pools[i].spans[j])) + fprintf(stderr, " fullness class %zu is empty\n", j); + else + { + dsa_pointer span_pointer = area->control->pools[i].spans[j]; + + fprintf(stderr, " fullness class %zu:\n", j); + while (DsaPointerIsValid(span_pointer)) + { + dsa_area_span *span; + + span = dsa_get_address(area, span_pointer); + fprintf(stderr, + " span descriptor at " + DSA_POINTER_FORMAT ", superblock at " + DSA_POINTER_FORMAT + ", pages = %zu, objects free = %hu/%hu\n", + span_pointer, span->start, span->npages, + span->nallocatable, span->nmax); + span_pointer = span->nextspan; + } + } + } + } + LWLockRelease(DSA_SCLASS_LOCK(area, i)); + } +} + +/* + * Return the smallest size that you can successfully provide to + * dsa_create_in_place. + */ +size_t +dsa_minimum_size(void) +{ + size_t size; + int pages = 0; + + size = MAXALIGN(sizeof(dsa_area_control)) + + MAXALIGN(sizeof(FreePageManager)); + + /* Figure out how many pages we need, including the page map... */ + while (((size + FPM_PAGE_SIZE - 1) / FPM_PAGE_SIZE) > pages) + { + ++pages; + size += sizeof(dsa_pointer); + } + + return pages * FPM_PAGE_SIZE; +} + +/* + * Workhorse function for dsa_create and dsa_create_in_place. + */ +static dsa_area * +create_internal(void *place, size_t size, + int tranche_id, + dsm_handle control_handle, + dsm_segment *control_segment) +{ + dsa_area_control *control; + dsa_area *area; + dsa_segment_map *segment_map; + size_t usable_pages; + size_t total_pages; + size_t metadata_bytes; + int i; + + /* Sanity check on the space we have to work in. */ + if (size < dsa_minimum_size()) + elog(ERROR, "dsa_area space must be at least %zu, but %zu provided", + dsa_minimum_size(), size); + + /* Now figure out how much space is usable */ + total_pages = size / FPM_PAGE_SIZE; + metadata_bytes = + MAXALIGN(sizeof(dsa_area_control)) + + MAXALIGN(sizeof(FreePageManager)) + + total_pages * sizeof(dsa_pointer); + /* Add padding up to next page boundary. */ + if (metadata_bytes % FPM_PAGE_SIZE != 0) + metadata_bytes += FPM_PAGE_SIZE - (metadata_bytes % FPM_PAGE_SIZE); + Assert(metadata_bytes <= size); + usable_pages = (size - metadata_bytes) / FPM_PAGE_SIZE; + + /* + * Initialize the dsa_area_control object located at the start of the + * space. + */ + control = (dsa_area_control *) place; + memset(place, 0, sizeof(*control)); + control->segment_header.magic = + DSA_SEGMENT_HEADER_MAGIC ^ control_handle ^ 0; + control->segment_header.next = DSA_SEGMENT_INDEX_NONE; + control->segment_header.prev = DSA_SEGMENT_INDEX_NONE; + control->segment_header.usable_pages = usable_pages; + control->segment_header.freed = false; + control->segment_header.size = DSA_INITIAL_SEGMENT_SIZE; + control->handle = control_handle; + control->max_total_segment_size = (size_t) -1; + control->total_segment_size = size; + control->segment_handles[0] = control_handle; + for (i = 0; i < DSA_NUM_SEGMENT_BINS; ++i) + control->segment_bins[i] = DSA_SEGMENT_INDEX_NONE; + control->refcnt = 1; + control->lwlock_tranche_id = tranche_id; + + /* + * Create the dsa_area object that this backend will use to access the + * area. Other backends will need to obtain their own dsa_area object by + * attaching. + */ + area = palloc(sizeof(dsa_area)); + area->control = control; + area->mapping_pinned = false; + memset(area->segment_maps, 0, sizeof(dsa_segment_map) * DSA_MAX_SEGMENTS); + area->high_segment_index = 0; + area->freed_segment_counter = 0; + LWLockInitialize(&control->lock, control->lwlock_tranche_id); + for (i = 0; i < DSA_NUM_SIZE_CLASSES; ++i) + LWLockInitialize(DSA_SCLASS_LOCK(area, i), + control->lwlock_tranche_id); + + /* Set up the segment map for this process's mapping. */ + segment_map = &area->segment_maps[0]; + segment_map->segment = control_segment; + segment_map->mapped_address = place; + segment_map->header = (dsa_segment_header *) place; + segment_map->fpm = (FreePageManager *) + (segment_map->mapped_address + + MAXALIGN(sizeof(dsa_area_control))); + segment_map->pagemap = (dsa_pointer *) + (segment_map->mapped_address + + MAXALIGN(sizeof(dsa_area_control)) + + MAXALIGN(sizeof(FreePageManager))); + + /* Set up the free page map. */ + FreePageManagerInitialize(segment_map->fpm, segment_map->mapped_address); + /* There can be 0 usable pages if size is dsa_minimum_size(). */ + + if (usable_pages > 0) + FreePageManagerPut(segment_map->fpm, metadata_bytes / FPM_PAGE_SIZE, + usable_pages); + + /* Put this segment into the appropriate bin. */ + control->segment_bins[contiguous_pages_to_segment_bin(usable_pages)] = 0; + segment_map->header->bin = contiguous_pages_to_segment_bin(usable_pages); + + return area; +} + +/* + * Workhorse function for dsa_attach and dsa_attach_in_place. + */ +static dsa_area * +attach_internal(void *place, dsm_segment *segment, dsa_handle handle) +{ + dsa_area_control *control; + dsa_area *area; + dsa_segment_map *segment_map; + + control = (dsa_area_control *) place; + Assert(control->handle == handle); + Assert(control->segment_handles[0] == handle); + Assert(control->segment_header.magic == + (DSA_SEGMENT_HEADER_MAGIC ^ handle ^ 0)); + + /* Build the backend-local area object. */ + area = palloc(sizeof(dsa_area)); + area->control = control; + area->mapping_pinned = false; + memset(&area->segment_maps[0], 0, + sizeof(dsa_segment_map) * DSA_MAX_SEGMENTS); + area->high_segment_index = 0; + + /* Set up the segment map for this process's mapping. */ + segment_map = &area->segment_maps[0]; + segment_map->segment = segment; /* NULL for in-place */ + segment_map->mapped_address = place; + segment_map->header = (dsa_segment_header *) segment_map->mapped_address; + segment_map->fpm = (FreePageManager *) + (segment_map->mapped_address + MAXALIGN(sizeof(dsa_area_control))); + segment_map->pagemap = (dsa_pointer *) + (segment_map->mapped_address + MAXALIGN(sizeof(dsa_area_control)) + + MAXALIGN(sizeof(FreePageManager))); + + /* Bump the reference count. */ + LWLockAcquire(DSA_AREA_LOCK(area), LW_EXCLUSIVE); + if (control->refcnt == 0) + { + /* We can't attach to a DSA area that has already been destroyed. */ + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("could not attach to dynamic shared area"))); + } + ++control->refcnt; + area->freed_segment_counter = area->control->freed_segment_counter; + LWLockRelease(DSA_AREA_LOCK(area)); + + return area; +} + +/* + * Add a new span to fullness class 1 of the indicated pool. + */ +static void +init_span(dsa_area *area, + dsa_pointer span_pointer, + dsa_area_pool *pool, dsa_pointer start, size_t npages, + uint16 size_class) +{ + dsa_area_span *span = dsa_get_address(area, span_pointer); + size_t obsize = dsa_size_classes[size_class]; + + /* + * The per-pool lock must be held because we manipulate the span list for + * this pool. + */ + Assert(LWLockHeldByMe(DSA_SCLASS_LOCK(area, size_class))); + + /* Push this span onto the front of the span list for fullness class 1. */ + if (DsaPointerIsValid(pool->spans[1])) + { + dsa_area_span *head = (dsa_area_span *) + dsa_get_address(area, pool->spans[1]); + + head->prevspan = span_pointer; + } + span->pool = DsaAreaPoolToDsaPointer(area, pool); + span->nextspan = pool->spans[1]; + span->prevspan = InvalidDsaPointer; + pool->spans[1] = span_pointer; + + span->start = start; + span->npages = npages; + span->size_class = size_class; + span->ninitialized = 0; + if (size_class == DSA_SCLASS_BLOCK_OF_SPANS) + { + /* + * A block-of-spans contains its own descriptor, so mark one object as + * initialized and reduce the count of allocatable objects by one. + * Doing this here has the side effect of also reducing nmax by one, + * which is important to make sure we free this object at the correct + * time. + */ + span->ninitialized = 1; + span->nallocatable = FPM_PAGE_SIZE / obsize - 1; + } + else if (size_class != DSA_SCLASS_SPAN_LARGE) + span->nallocatable = DSA_SUPERBLOCK_SIZE / obsize; + span->firstfree = DSA_SPAN_NOTHING_FREE; + span->nmax = span->nallocatable; + span->fclass = 1; +} + +/* + * Transfer the first span in one fullness class to the head of another + * fullness class. + */ +static bool +transfer_first_span(dsa_area *area, + dsa_area_pool *pool, int fromclass, int toclass) +{ + dsa_pointer span_pointer; + dsa_area_span *span; + dsa_area_span *nextspan; + + /* Can't do it if source list is empty. */ + span_pointer = pool->spans[fromclass]; + if (!DsaPointerIsValid(span_pointer)) + return false; + + /* Remove span from head of source list. */ + span = dsa_get_address(area, span_pointer); + pool->spans[fromclass] = span->nextspan; + if (DsaPointerIsValid(span->nextspan)) + { + nextspan = (dsa_area_span *) + dsa_get_address(area, span->nextspan); + nextspan->prevspan = InvalidDsaPointer; + } + + /* Add span to head of target list. */ + span->nextspan = pool->spans[toclass]; + pool->spans[toclass] = span_pointer; + if (DsaPointerIsValid(span->nextspan)) + { + nextspan = (dsa_area_span *) + dsa_get_address(area, span->nextspan); + nextspan->prevspan = span_pointer; + } + span->fclass = toclass; + + return true; +} + +/* + * Allocate one object of the requested size class from the given area. + */ +static inline dsa_pointer +alloc_object(dsa_area *area, int size_class) +{ + dsa_area_pool *pool = &area->control->pools[size_class]; + dsa_area_span *span; + dsa_pointer block; + dsa_pointer result; + char *object; + size_t size; + + /* + * Even though ensure_active_superblock can in turn call alloc_object if + * it needs to allocate a new span, that's always from a different pool, + * and the order of lock acquisition is always the same, so it's OK that + * we hold this lock for the duration of this function. + */ + Assert(!LWLockHeldByMe(DSA_SCLASS_LOCK(area, size_class))); + LWLockAcquire(DSA_SCLASS_LOCK(area, size_class), LW_EXCLUSIVE); + + /* + * If there's no active superblock, we must successfully obtain one or + * fail the request. + */ + if (!DsaPointerIsValid(pool->spans[1]) && + !ensure_active_superblock(area, pool, size_class)) + { + result = InvalidDsaPointer; + } + else + { + /* + * There should be a block in fullness class 1 at this point, and it + * should never be completely full. Thus we can either pop an object + * from the free list or, failing that, initialize a new object. + */ + Assert(DsaPointerIsValid(pool->spans[1])); + span = (dsa_area_span *) + dsa_get_address(area, pool->spans[1]); + Assert(span->nallocatable > 0); + block = span->start; + Assert(size_class < DSA_NUM_SIZE_CLASSES); + size = dsa_size_classes[size_class]; + if (span->firstfree != DSA_SPAN_NOTHING_FREE) + { + result = block + span->firstfree * size; + object = dsa_get_address(area, result); + span->firstfree = NextFreeObjectIndex(object); + } + else + { + result = block + span->ninitialized * size; + ++span->ninitialized; + } + --span->nallocatable; + + /* If it's now full, move it to the highest-numbered fullness class. */ + if (span->nallocatable == 0) + transfer_first_span(area, pool, 1, DSA_FULLNESS_CLASSES - 1); + } + + Assert(LWLockHeldByMe(DSA_SCLASS_LOCK(area, size_class))); + LWLockRelease(DSA_SCLASS_LOCK(area, size_class)); + + return result; +} + +/* + * Ensure an active (i.e. fullness class 1) superblock, unless all existing + * superblocks are completely full and no more can be allocated. + * + * Fullness classes K of 0..N are loosely intended to represent blocks whose + * utilization percentage is at least K/N, but we only enforce this rigorously + * for the highest-numbered fullness class, which always contains exactly + * those blocks that are completely full. It's otherwise acceptable for a + * block to be in a higher-numbered fullness class than the one to which it + * logically belongs. In addition, the active block, which is always the + * first block in fullness class 1, is permitted to have a higher allocation + * percentage than would normally be allowable for that fullness class; we + * don't move it until it's completely full, and then it goes to the + * highest-numbered fullness class. + * + * It might seem odd that the active block is the head of fullness class 1 + * rather than fullness class 0, but experience with other allocators has + * shown that it's usually better to allocate from a block that's moderately + * full rather than one that's nearly empty. Insofar as is reasonably + * possible, we want to avoid performing new allocations in a block that would + * otherwise become empty soon. + */ +static bool +ensure_active_superblock(dsa_area *area, dsa_area_pool *pool, + int size_class) +{ + dsa_pointer span_pointer; + dsa_pointer start_pointer; + size_t obsize = dsa_size_classes[size_class]; + size_t nmax; + int fclass; + size_t npages = 1; + size_t first_page; + size_t i; + dsa_segment_map *segment_map; + + Assert(LWLockHeldByMe(DSA_SCLASS_LOCK(area, size_class))); + + /* + * Compute the number of objects that will fit in a block of this size + * class. Span-of-spans blocks are just a single page, and the first + * object isn't available for use because it describes the block-of-spans + * itself. + */ + if (size_class == DSA_SCLASS_BLOCK_OF_SPANS) + nmax = FPM_PAGE_SIZE / obsize - 1; + else + nmax = DSA_SUPERBLOCK_SIZE / obsize; + + /* + * If fullness class 1 is empty, try to find a span to put in it by + * scanning higher-numbered fullness classes (excluding the last one, + * whose blocks are certain to all be completely full). + */ + for (fclass = 2; fclass < DSA_FULLNESS_CLASSES - 1; ++fclass) + { + span_pointer = pool->spans[fclass]; + + while (DsaPointerIsValid(span_pointer)) + { + int tfclass; + dsa_area_span *span; + dsa_area_span *nextspan; + dsa_area_span *prevspan; + dsa_pointer next_span_pointer; + + span = (dsa_area_span *) + dsa_get_address(area, span_pointer); + next_span_pointer = span->nextspan; + + /* Figure out what fullness class should contain this span. */ + tfclass = (nmax - span->nallocatable) + * (DSA_FULLNESS_CLASSES - 1) / nmax; + + /* Look up next span. */ + if (DsaPointerIsValid(span->nextspan)) + nextspan = (dsa_area_span *) + dsa_get_address(area, span->nextspan); + else + nextspan = NULL; + + /* + * If utilization has dropped enough that this now belongs in some + * other fullness class, move it there. + */ + if (tfclass < fclass) + { + /* Remove from the current fullness class list. */ + if (pool->spans[fclass] == span_pointer) + { + /* It was the head; remove it. */ + Assert(!DsaPointerIsValid(span->prevspan)); + pool->spans[fclass] = span->nextspan; + if (nextspan != NULL) + nextspan->prevspan = InvalidDsaPointer; + } + else + { + /* It was not the head. */ + Assert(DsaPointerIsValid(span->prevspan)); + prevspan = (dsa_area_span *) + dsa_get_address(area, span->prevspan); + prevspan->nextspan = span->nextspan; + } + if (nextspan != NULL) + nextspan->prevspan = span->prevspan; + + /* Push onto the head of the new fullness class list. */ + span->nextspan = pool->spans[tfclass]; + pool->spans[tfclass] = span_pointer; + span->prevspan = InvalidDsaPointer; + if (DsaPointerIsValid(span->nextspan)) + { + nextspan = (dsa_area_span *) + dsa_get_address(area, span->nextspan); + nextspan->prevspan = span_pointer; + } + span->fclass = tfclass; + } + + /* Advance to next span on list. */ + span_pointer = next_span_pointer; + } + + /* Stop now if we found a suitable block. */ + if (DsaPointerIsValid(pool->spans[1])) + return true; + } + + /* + * If there are no blocks that properly belong in fullness class 1, pick + * one from some other fullness class and move it there anyway, so that we + * have an allocation target. Our last choice is to transfer a block + * that's almost empty (and might become completely empty soon if left + * alone), but even that is better than failing, which is what we must do + * if there are no blocks at all with freespace. + */ + Assert(!DsaPointerIsValid(pool->spans[1])); + for (fclass = 2; fclass < DSA_FULLNESS_CLASSES - 1; ++fclass) + if (transfer_first_span(area, pool, fclass, 1)) + return true; + if (!DsaPointerIsValid(pool->spans[1]) && + transfer_first_span(area, pool, 0, 1)) + return true; + + /* + * We failed to find an existing span with free objects, so we need to + * allocate a new superblock and construct a new span to manage it. + * + * First, get a dsa_area_span object to describe the new superblock block + * ... unless this allocation is for a dsa_area_span object, in which case + * that's surely not going to work. We handle that case by storing the + * span describing a block-of-spans inline. + */ + if (size_class != DSA_SCLASS_BLOCK_OF_SPANS) + { + span_pointer = alloc_object(area, DSA_SCLASS_BLOCK_OF_SPANS); + if (!DsaPointerIsValid(span_pointer)) + return false; + npages = DSA_PAGES_PER_SUPERBLOCK; + } + + /* Find or create a segment and allocate the superblock. */ + LWLockAcquire(DSA_AREA_LOCK(area), LW_EXCLUSIVE); + segment_map = get_best_segment(area, npages); + if (segment_map == NULL) + { + segment_map = make_new_segment(area, npages); + if (segment_map == NULL) + { + LWLockRelease(DSA_AREA_LOCK(area)); + return false; + } + } + + /* + * This shouldn't happen: get_best_segment() or make_new_segment() + * promised that we can successfully allocate npages. + */ + if (!FreePageManagerGet(segment_map->fpm, npages, &first_page)) + elog(FATAL, + "dsa_allocate could not find %zu free pages for superblock", + npages); + LWLockRelease(DSA_AREA_LOCK(area)); + + /* Compute the start of the superblock. */ + start_pointer = + DSA_MAKE_POINTER(get_segment_index(area, segment_map), + first_page * FPM_PAGE_SIZE); + + /* + * If this is a block-of-spans, carve the descriptor right out of the + * allocated space. + */ + if (size_class == DSA_SCLASS_BLOCK_OF_SPANS) + { + /* + * We have a pointer into the segment. We need to build a dsa_pointer + * from the segment index and offset into the segment. + */ + span_pointer = start_pointer; + } + + /* Initialize span and pagemap. */ + init_span(area, span_pointer, pool, start_pointer, npages, size_class); + for (i = 0; i < npages; ++i) + segment_map->pagemap[first_page + i] = span_pointer; + + return true; +} + +/* + * Return the segment map corresponding to a given segment index, mapping the + * segment in if necessary. For internal segment book-keeping, this is called + * with the area lock held. It is also called by dsa_free and dsa_get_address + * without any locking, relying on the fact they have a known live segment + * index and they always call check_for_freed_segments to ensures that any + * freed segment occupying the same slot is detached first. + */ +static dsa_segment_map * +get_segment_by_index(dsa_area *area, dsa_segment_index index) +{ + if (unlikely(area->segment_maps[index].mapped_address == NULL)) + { + dsm_handle handle; + dsm_segment *segment; + dsa_segment_map *segment_map; + + /* + * If we are reached by dsa_free or dsa_get_address, there must be at + * least one object allocated in the referenced segment. Otherwise, + * their caller has a double-free or access-after-free bug, which we + * have no hope of detecting. So we know it's safe to access this + * array slot without holding a lock; it won't change underneath us. + * Furthermore, we know that we can see the latest contents of the + * slot, as explained in check_for_freed_segments, which those + * functions call before arriving here. + */ + handle = area->control->segment_handles[index]; + + /* It's an error to try to access an unused slot. */ + if (handle == DSM_HANDLE_INVALID) + elog(ERROR, + "dsa_area could not attach to a segment that has been freed"); + + segment = dsm_attach(handle); + if (segment == NULL) + elog(ERROR, "dsa_area could not attach to segment"); + if (area->mapping_pinned) + dsm_pin_mapping(segment); + segment_map = &area->segment_maps[index]; + segment_map->segment = segment; + segment_map->mapped_address = dsm_segment_address(segment); + segment_map->header = + (dsa_segment_header *) segment_map->mapped_address; + segment_map->fpm = (FreePageManager *) + (segment_map->mapped_address + + MAXALIGN(sizeof(dsa_segment_header))); + segment_map->pagemap = (dsa_pointer *) + (segment_map->mapped_address + + MAXALIGN(sizeof(dsa_segment_header)) + + MAXALIGN(sizeof(FreePageManager))); + + /* Remember the highest index this backend has ever mapped. */ + if (area->high_segment_index < index) + area->high_segment_index = index; + + Assert(segment_map->header->magic == + (DSA_SEGMENT_HEADER_MAGIC ^ area->control->handle ^ index)); + } + + /* + * Callers of dsa_get_address() and dsa_free() don't hold the area lock, + * but it's a bug in the calling code and undefined behavior if the + * address is not live (ie if the segment might possibly have been freed, + * they're trying to use a dangling pointer). + * + * For dsa.c code that holds the area lock to manipulate segment_bins + * lists, it would be a bug if we ever reach a freed segment here. After + * it's marked as freed, the only thing any backend should do with it is + * unmap it, and it should always have done that in + * check_for_freed_segments_locked() before arriving here to resolve an + * index to a segment_map. + * + * Either way we can assert that we aren't returning a freed segment. + */ + Assert(!area->segment_maps[index].header->freed); + + return &area->segment_maps[index]; +} + +/* + * Return a superblock to the free page manager. If the underlying segment + * has become entirely free, then return it to the operating system. + * + * The appropriate pool lock must be held. + */ +static void +destroy_superblock(dsa_area *area, dsa_pointer span_pointer) +{ + dsa_area_span *span = dsa_get_address(area, span_pointer); + int size_class = span->size_class; + dsa_segment_map *segment_map; + + + /* Remove it from its fullness class list. */ + unlink_span(area, span); + + /* + * Note: Here we acquire the area lock while we already hold a per-pool + * lock. We never hold the area lock and then take a pool lock, or we + * could deadlock. + */ + LWLockAcquire(DSA_AREA_LOCK(area), LW_EXCLUSIVE); + check_for_freed_segments_locked(area); + segment_map = + get_segment_by_index(area, DSA_EXTRACT_SEGMENT_NUMBER(span->start)); + FreePageManagerPut(segment_map->fpm, + DSA_EXTRACT_OFFSET(span->start) / FPM_PAGE_SIZE, + span->npages); + /* Check if the segment is now entirely free. */ + if (fpm_largest(segment_map->fpm) == segment_map->header->usable_pages) + { + dsa_segment_index index = get_segment_index(area, segment_map); + + /* If it's not the segment with extra control data, free it. */ + if (index != 0) + { + /* + * Give it back to the OS, and allow other backends to detect that + * they need to detach. + */ + unlink_segment(area, segment_map); + segment_map->header->freed = true; + Assert(area->control->total_segment_size >= + segment_map->header->size); + area->control->total_segment_size -= + segment_map->header->size; + dsm_unpin_segment(dsm_segment_handle(segment_map->segment)); + dsm_detach(segment_map->segment); + area->control->segment_handles[index] = DSM_HANDLE_INVALID; + ++area->control->freed_segment_counter; + segment_map->segment = NULL; + segment_map->header = NULL; + segment_map->mapped_address = NULL; + } + } + + /* Move segment to appropriate bin if necessary. */ + if (segment_map->header != NULL) + rebin_segment(area, segment_map); + + LWLockRelease(DSA_AREA_LOCK(area)); + + /* + * Span-of-spans blocks store the span which describes them within the + * block itself, so freeing the storage implicitly frees the descriptor + * also. If this is a block of any other type, we need to separately free + * the span object also. This recursive call to dsa_free will acquire the + * span pool's lock. We can't deadlock because the acquisition order is + * always some other pool and then the span pool. + */ + if (size_class != DSA_SCLASS_BLOCK_OF_SPANS) + dsa_free(area, span_pointer); +} + +static void +unlink_span(dsa_area *area, dsa_area_span *span) +{ + if (DsaPointerIsValid(span->nextspan)) + { + dsa_area_span *next = dsa_get_address(area, span->nextspan); + + next->prevspan = span->prevspan; + } + if (DsaPointerIsValid(span->prevspan)) + { + dsa_area_span *prev = dsa_get_address(area, span->prevspan); + + prev->nextspan = span->nextspan; + } + else + { + dsa_area_pool *pool = dsa_get_address(area, span->pool); + + pool->spans[span->fclass] = span->nextspan; + } +} + +static void +add_span_to_fullness_class(dsa_area *area, dsa_area_span *span, + dsa_pointer span_pointer, + int fclass) +{ + dsa_area_pool *pool = dsa_get_address(area, span->pool); + + if (DsaPointerIsValid(pool->spans[fclass])) + { + dsa_area_span *head = dsa_get_address(area, + pool->spans[fclass]); + + head->prevspan = span_pointer; + } + span->prevspan = InvalidDsaPointer; + span->nextspan = pool->spans[fclass]; + pool->spans[fclass] = span_pointer; + span->fclass = fclass; +} + +/* + * Detach from an area that was either created or attached to by this process. + */ +void +dsa_detach(dsa_area *area) +{ + int i; + + /* Detach from all segments. */ + for (i = 0; i <= area->high_segment_index; ++i) + if (area->segment_maps[i].segment != NULL) + dsm_detach(area->segment_maps[i].segment); + + /* + * Note that 'detaching' (= detaching from DSM segments) doesn't include + * 'releasing' (= adjusting the reference count). It would be nice to + * combine these operations, but client code might never get around to + * calling dsa_detach because of an error path, and a detach hook on any + * particular segment is too late to detach other segments in the area + * without risking a 'leak' warning in the non-error path. + */ + + /* Free the backend-local area object. */ + pfree(area); +} + +/* + * Unlink a segment from the bin that contains it. + */ +static void +unlink_segment(dsa_area *area, dsa_segment_map *segment_map) +{ + if (segment_map->header->prev != DSA_SEGMENT_INDEX_NONE) + { + dsa_segment_map *prev; + + prev = get_segment_by_index(area, segment_map->header->prev); + prev->header->next = segment_map->header->next; + } + else + { + Assert(area->control->segment_bins[segment_map->header->bin] == + get_segment_index(area, segment_map)); + area->control->segment_bins[segment_map->header->bin] = + segment_map->header->next; + } + if (segment_map->header->next != DSA_SEGMENT_INDEX_NONE) + { + dsa_segment_map *next; + + next = get_segment_by_index(area, segment_map->header->next); + next->header->prev = segment_map->header->prev; + } +} + +/* + * Find a segment that could satisfy a request for 'npages' of contiguous + * memory, or return NULL if none can be found. This may involve attaching to + * segments that weren't previously attached so that we can query their free + * pages map. + */ +static dsa_segment_map * +get_best_segment(dsa_area *area, size_t npages) +{ + size_t bin; + + Assert(LWLockHeldByMe(DSA_AREA_LOCK(area))); + check_for_freed_segments_locked(area); + + /* + * Start searching from the first bin that *might* have enough contiguous + * pages. + */ + for (bin = contiguous_pages_to_segment_bin(npages); + bin < DSA_NUM_SEGMENT_BINS; + ++bin) + { + /* + * The minimum contiguous size that any segment in this bin should + * have. We'll re-bin if we see segments with fewer. + */ + size_t threshold = (size_t) 1 << (bin - 1); + dsa_segment_index segment_index; + + /* Search this bin for a segment with enough contiguous space. */ + segment_index = area->control->segment_bins[bin]; + while (segment_index != DSA_SEGMENT_INDEX_NONE) + { + dsa_segment_map *segment_map; + dsa_segment_index next_segment_index; + size_t contiguous_pages; + + segment_map = get_segment_by_index(area, segment_index); + next_segment_index = segment_map->header->next; + contiguous_pages = fpm_largest(segment_map->fpm); + + /* Not enough for the request, still enough for this bin. */ + if (contiguous_pages >= threshold && contiguous_pages < npages) + { + segment_index = next_segment_index; + continue; + } + + /* Re-bin it if it's no longer in the appropriate bin. */ + if (contiguous_pages < threshold) + { + rebin_segment(area, segment_map); + + /* + * But fall through to see if it's enough to satisfy this + * request anyway.... + */ + } + + /* Check if we are done. */ + if (contiguous_pages >= npages) + return segment_map; + + /* Continue searching the same bin. */ + segment_index = next_segment_index; + } + } + + /* Not found. */ + return NULL; +} + +/* + * Create a new segment that can handle at least requested_pages. Returns + * NULL if the requested total size limit or maximum allowed number of + * segments would be exceeded. + */ +static dsa_segment_map * +make_new_segment(dsa_area *area, size_t requested_pages) +{ + dsa_segment_index new_index; + size_t metadata_bytes; + size_t total_size; + size_t total_pages; + size_t usable_pages; + dsa_segment_map *segment_map; + dsm_segment *segment; + + Assert(LWLockHeldByMe(DSA_AREA_LOCK(area))); + + /* Find a segment slot that is not in use (linearly for now). */ + for (new_index = 1; new_index < DSA_MAX_SEGMENTS; ++new_index) + { + if (area->control->segment_handles[new_index] == DSM_HANDLE_INVALID) + break; + } + if (new_index == DSA_MAX_SEGMENTS) + return NULL; + + /* + * If the total size limit is already exceeded, then we exit early and + * avoid arithmetic wraparound in the unsigned expressions below. + */ + if (area->control->total_segment_size >= + area->control->max_total_segment_size) + return NULL; + + /* + * The size should be at least as big as requested, and at least big + * enough to follow a geometric series that approximately doubles the + * total storage each time we create a new segment. We use geometric + * growth because the underlying DSM system isn't designed for large + * numbers of segments (otherwise we might even consider just using one + * DSM segment for each large allocation and for each superblock, and then + * we wouldn't need to use FreePageManager). + * + * We decide on a total segment size first, so that we produce tidy + * power-of-two sized segments. This is a good property to have if we + * move to huge pages in the future. Then we work back to the number of + * pages we can fit. + */ + total_size = DSA_INITIAL_SEGMENT_SIZE * + ((size_t) 1 << (new_index / DSA_NUM_SEGMENTS_AT_EACH_SIZE)); + total_size = Min(total_size, DSA_MAX_SEGMENT_SIZE); + total_size = Min(total_size, + area->control->max_total_segment_size - + area->control->total_segment_size); + + total_pages = total_size / FPM_PAGE_SIZE; + metadata_bytes = + MAXALIGN(sizeof(dsa_segment_header)) + + MAXALIGN(sizeof(FreePageManager)) + + sizeof(dsa_pointer) * total_pages; + + /* Add padding up to next page boundary. */ + if (metadata_bytes % FPM_PAGE_SIZE != 0) + metadata_bytes += FPM_PAGE_SIZE - (metadata_bytes % FPM_PAGE_SIZE); + if (total_size <= metadata_bytes) + return NULL; + usable_pages = (total_size - metadata_bytes) / FPM_PAGE_SIZE; + Assert(metadata_bytes + usable_pages * FPM_PAGE_SIZE <= total_size); + + /* See if that is enough... */ + if (requested_pages > usable_pages) + { + /* + * We'll make an odd-sized segment, working forward from the requested + * number of pages. + */ + usable_pages = requested_pages; + metadata_bytes = + MAXALIGN(sizeof(dsa_segment_header)) + + MAXALIGN(sizeof(FreePageManager)) + + usable_pages * sizeof(dsa_pointer); + + /* Add padding up to next page boundary. */ + if (metadata_bytes % FPM_PAGE_SIZE != 0) + metadata_bytes += FPM_PAGE_SIZE - (metadata_bytes % FPM_PAGE_SIZE); + total_size = metadata_bytes + usable_pages * FPM_PAGE_SIZE; + + /* Is that too large for dsa_pointer's addressing scheme? */ + if (total_size > DSA_MAX_SEGMENT_SIZE) + return NULL; + + /* Would that exceed the limit? */ + if (total_size > area->control->max_total_segment_size - + area->control->total_segment_size) + return NULL; + } + + /* Create the segment. */ + segment = dsm_create(total_size, 0); + if (segment == NULL) + return NULL; + dsm_pin_segment(segment); + if (area->mapping_pinned) + dsm_pin_mapping(segment); + + /* Store the handle in shared memory to be found by index. */ + area->control->segment_handles[new_index] = + dsm_segment_handle(segment); + /* Track the highest segment index in the history of the area. */ + if (area->control->high_segment_index < new_index) + area->control->high_segment_index = new_index; + /* Track the highest segment index this backend has ever mapped. */ + if (area->high_segment_index < new_index) + area->high_segment_index = new_index; + /* Track total size of all segments. */ + area->control->total_segment_size += total_size; + Assert(area->control->total_segment_size <= + area->control->max_total_segment_size); + + /* Build a segment map for this segment in this backend. */ + segment_map = &area->segment_maps[new_index]; + segment_map->segment = segment; + segment_map->mapped_address = dsm_segment_address(segment); + segment_map->header = (dsa_segment_header *) segment_map->mapped_address; + segment_map->fpm = (FreePageManager *) + (segment_map->mapped_address + + MAXALIGN(sizeof(dsa_segment_header))); + segment_map->pagemap = (dsa_pointer *) + (segment_map->mapped_address + + MAXALIGN(sizeof(dsa_segment_header)) + + MAXALIGN(sizeof(FreePageManager))); + + /* Set up the free page map. */ + FreePageManagerInitialize(segment_map->fpm, segment_map->mapped_address); + FreePageManagerPut(segment_map->fpm, metadata_bytes / FPM_PAGE_SIZE, + usable_pages); + + /* Set up the segment header and put it in the appropriate bin. */ + segment_map->header->magic = + DSA_SEGMENT_HEADER_MAGIC ^ area->control->handle ^ new_index; + segment_map->header->usable_pages = usable_pages; + segment_map->header->size = total_size; + segment_map->header->bin = contiguous_pages_to_segment_bin(usable_pages); + segment_map->header->prev = DSA_SEGMENT_INDEX_NONE; + segment_map->header->next = + area->control->segment_bins[segment_map->header->bin]; + segment_map->header->freed = false; + area->control->segment_bins[segment_map->header->bin] = new_index; + if (segment_map->header->next != DSA_SEGMENT_INDEX_NONE) + { + dsa_segment_map *next = + get_segment_by_index(area, segment_map->header->next); + + Assert(next->header->bin == segment_map->header->bin); + next->header->prev = new_index; + } + + return segment_map; +} + +/* + * Check if any segments have been freed by destroy_superblock, so we can + * detach from them in this backend. This function is called by + * dsa_get_address and dsa_free to make sure that a dsa_pointer they have + * received can be resolved to the correct segment. + * + * The danger we want to defend against is that there could be an old segment + * mapped into a given slot in this backend, and the dsa_pointer they have + * might refer to some new segment in the same slot. So those functions must + * be sure to process all instructions to detach from a freed segment that had + * been generated by the time this process received the dsa_pointer, before + * they call get_segment_by_index. + */ +static void +check_for_freed_segments(dsa_area *area) +{ + size_t freed_segment_counter; + + /* + * Any other process that has freed a segment has incremented + * freed_segment_counter while holding an LWLock, and that must precede + * any backend creating a new segment in the same slot while holding an + * LWLock, and that must precede the creation of any dsa_pointer pointing + * into the new segment which might reach us here, and the caller must + * have sent the dsa_pointer to this process using appropriate memory + * synchronization (some kind of locking or atomic primitive or system + * call). So all we need to do on the reading side is ask for the load of + * freed_segment_counter to follow the caller's load of the dsa_pointer it + * has, and we can be sure to detect any segments that had been freed as + * of the time that the dsa_pointer reached this process. + */ + pg_read_barrier(); + freed_segment_counter = area->control->freed_segment_counter; + if (unlikely(area->freed_segment_counter != freed_segment_counter)) + { + /* Check all currently mapped segments to find what's been freed. */ + LWLockAcquire(DSA_AREA_LOCK(area), LW_EXCLUSIVE); + check_for_freed_segments_locked(area); + LWLockRelease(DSA_AREA_LOCK(area)); + } +} + +/* + * Workhorse for check_for_freed_segments(), and also used directly in path + * where the area lock is already held. This should be called after acquiring + * the lock but before looking up any segment by index number, to make sure we + * unmap any stale segments that might have previously had the same index as a + * current segment. + */ +static void +check_for_freed_segments_locked(dsa_area *area) +{ + size_t freed_segment_counter; + int i; + + Assert(LWLockHeldByMe(DSA_AREA_LOCK(area))); + freed_segment_counter = area->control->freed_segment_counter; + if (unlikely(area->freed_segment_counter != freed_segment_counter)) + { + for (i = 0; i <= area->high_segment_index; ++i) + { + if (area->segment_maps[i].header != NULL && + area->segment_maps[i].header->freed) + { + dsm_detach(area->segment_maps[i].segment); + area->segment_maps[i].segment = NULL; + area->segment_maps[i].header = NULL; + area->segment_maps[i].mapped_address = NULL; + } + } + area->freed_segment_counter = freed_segment_counter; + } +} + +/* + * Re-bin segment if it's no longer in the appropriate bin. + */ +static void +rebin_segment(dsa_area *area, dsa_segment_map *segment_map) +{ + size_t new_bin; + dsa_segment_index segment_index; + + new_bin = contiguous_pages_to_segment_bin(fpm_largest(segment_map->fpm)); + if (segment_map->header->bin == new_bin) + return; + + /* Remove it from its current bin. */ + unlink_segment(area, segment_map); + + /* Push it onto the front of its new bin. */ + segment_index = get_segment_index(area, segment_map); + segment_map->header->prev = DSA_SEGMENT_INDEX_NONE; + segment_map->header->next = area->control->segment_bins[new_bin]; + segment_map->header->bin = new_bin; + area->control->segment_bins[new_bin] = segment_index; + if (segment_map->header->next != DSA_SEGMENT_INDEX_NONE) + { + dsa_segment_map *next; + + next = get_segment_by_index(area, segment_map->header->next); + Assert(next->header->bin == new_bin); + next->header->prev = segment_index; + } +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/mmgr/freepage.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/mmgr/freepage.c new file mode 100644 index 00000000000..8f9ea090faa --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/mmgr/freepage.c @@ -0,0 +1,1886 @@ +/*------------------------------------------------------------------------- + * + * freepage.c + * Management of free memory pages. + * + * The intention of this code is to provide infrastructure for memory + * allocators written specifically for PostgreSQL. At least in the case + * of dynamic shared memory, we can't simply use malloc() or even + * relatively thin wrappers like palloc() which sit on top of it, because + * no allocator built into the operating system will deal with relative + * pointers. In the future, we may find other cases in which greater + * control over our own memory management seems desirable. + * + * A FreePageManager keeps track of which 4kB pages of memory are currently + * unused from the point of view of some higher-level memory allocator. + * Unlike a user-facing allocator such as palloc(), a FreePageManager can + * only allocate and free in units of whole pages, and freeing an + * allocation can only be done given knowledge of its length in pages. + * + * Since a free page manager has only a fixed amount of dedicated memory, + * and since there is no underlying allocator, it uses the free pages + * it is given to manage to store its bookkeeping data. It keeps multiple + * freelists of runs of pages, sorted by the size of the run; the head of + * each freelist is stored in the FreePageManager itself, and the first + * page of each run contains a relative pointer to the next run. See + * FreePageManagerGetInternal for more details on how the freelists are + * managed. + * + * To avoid memory fragmentation, it's important to consolidate adjacent + * spans of pages whenever possible; otherwise, large allocation requests + * might not be satisfied even when sufficient contiguous space is + * available. Therefore, in addition to the freelists, we maintain an + * in-memory btree of free page ranges ordered by page number. If a + * range being freed precedes or follows a range that is already free, + * the existing range is extended; if it exactly bridges the gap between + * free ranges, then the two existing ranges are consolidated with the + * newly-freed range to form one great big range of free pages. + * + * When there is only one range of free pages, the btree is trivial and + * is stored within the FreePageManager proper; otherwise, pages are + * allocated from the area under management as needed. Even in cases + * where memory fragmentation is very severe, only a tiny fraction of + * the pages under management are consumed by this btree. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/utils/mmgr/freepage.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" +#include "lib/stringinfo.h" +#include "miscadmin.h" + +#include "utils/freepage.h" +#include "utils/relptr.h" + + +/* Magic numbers to identify various page types */ +#define FREE_PAGE_SPAN_LEADER_MAGIC 0xea4020f0 +#define FREE_PAGE_LEAF_MAGIC 0x98eae728 +#define FREE_PAGE_INTERNAL_MAGIC 0x19aa32c9 + +/* Doubly linked list of spans of free pages; stored in first page of span. */ +struct FreePageSpanLeader +{ + int magic; /* always FREE_PAGE_SPAN_LEADER_MAGIC */ + Size npages; /* number of pages in span */ + RelptrFreePageSpanLeader prev; + RelptrFreePageSpanLeader next; +}; + +/* Common header for btree leaf and internal pages. */ +typedef struct FreePageBtreeHeader +{ + int magic; /* FREE_PAGE_LEAF_MAGIC or + * FREE_PAGE_INTERNAL_MAGIC */ + Size nused; /* number of items used */ + RelptrFreePageBtree parent; /* uplink */ +} FreePageBtreeHeader; + +/* Internal key; points to next level of btree. */ +typedef struct FreePageBtreeInternalKey +{ + Size first_page; /* low bound for keys on child page */ + RelptrFreePageBtree child; /* downlink */ +} FreePageBtreeInternalKey; + +/* Leaf key; no payload data. */ +typedef struct FreePageBtreeLeafKey +{ + Size first_page; /* first page in span */ + Size npages; /* number of pages in span */ +} FreePageBtreeLeafKey; + +/* Work out how many keys will fit on a page. */ +#define FPM_ITEMS_PER_INTERNAL_PAGE \ + ((FPM_PAGE_SIZE - sizeof(FreePageBtreeHeader)) / \ + sizeof(FreePageBtreeInternalKey)) +#define FPM_ITEMS_PER_LEAF_PAGE \ + ((FPM_PAGE_SIZE - sizeof(FreePageBtreeHeader)) / \ + sizeof(FreePageBtreeLeafKey)) + +/* A btree page of either sort */ +struct FreePageBtree +{ + FreePageBtreeHeader hdr; + union + { + FreePageBtreeInternalKey internal_key[FPM_ITEMS_PER_INTERNAL_PAGE]; + FreePageBtreeLeafKey leaf_key[FPM_ITEMS_PER_LEAF_PAGE]; + } u; +}; + +/* Results of a btree search */ +typedef struct FreePageBtreeSearchResult +{ + FreePageBtree *page; + Size index; + bool found; + unsigned split_pages; +} FreePageBtreeSearchResult; + +/* Helper functions */ +static void FreePageBtreeAdjustAncestorKeys(FreePageManager *fpm, + FreePageBtree *btp); +static Size FreePageBtreeCleanup(FreePageManager *fpm); +static FreePageBtree *FreePageBtreeFindLeftSibling(char *base, + FreePageBtree *btp); +static FreePageBtree *FreePageBtreeFindRightSibling(char *base, + FreePageBtree *btp); +static Size FreePageBtreeFirstKey(FreePageBtree *btp); +static FreePageBtree *FreePageBtreeGetRecycled(FreePageManager *fpm); +static void FreePageBtreeInsertInternal(char *base, FreePageBtree *btp, + Size index, Size first_page, FreePageBtree *child); +static void FreePageBtreeInsertLeaf(FreePageBtree *btp, Size index, + Size first_page, Size npages); +static void FreePageBtreeRecycle(FreePageManager *fpm, Size pageno); +static void FreePageBtreeRemove(FreePageManager *fpm, FreePageBtree *btp, + Size index); +static void FreePageBtreeRemovePage(FreePageManager *fpm, FreePageBtree *btp); +static void FreePageBtreeSearch(FreePageManager *fpm, Size first_page, + FreePageBtreeSearchResult *result); +static Size FreePageBtreeSearchInternal(FreePageBtree *btp, Size first_page); +static Size FreePageBtreeSearchLeaf(FreePageBtree *btp, Size first_page); +static FreePageBtree *FreePageBtreeSplitPage(FreePageManager *fpm, + FreePageBtree *btp); +static void FreePageBtreeUpdateParentPointers(char *base, FreePageBtree *btp); +static void FreePageManagerDumpBtree(FreePageManager *fpm, FreePageBtree *btp, + FreePageBtree *parent, int level, StringInfo buf); +static void FreePageManagerDumpSpans(FreePageManager *fpm, + FreePageSpanLeader *span, Size expected_pages, + StringInfo buf); +static bool FreePageManagerGetInternal(FreePageManager *fpm, Size npages, + Size *first_page); +static Size FreePageManagerPutInternal(FreePageManager *fpm, Size first_page, + Size npages, bool soft); +static void FreePagePopSpanLeader(FreePageManager *fpm, Size pageno); +static void FreePagePushSpanLeader(FreePageManager *fpm, Size first_page, + Size npages); +static Size FreePageManagerLargestContiguous(FreePageManager *fpm); +static void FreePageManagerUpdateLargest(FreePageManager *fpm); + +#ifdef FPM_EXTRA_ASSERTS +static Size sum_free_pages(FreePageManager *fpm); +#endif + +/* + * Initialize a new, empty free page manager. + * + * 'fpm' should reference caller-provided memory large enough to contain a + * FreePageManager. We'll initialize it here. + * + * 'base' is the address to which all pointers are relative. When managing + * a dynamic shared memory segment, it should normally be the base of the + * segment. When managing backend-private memory, it can be either NULL or, + * if managing a single contiguous extent of memory, the start of that extent. + */ +void +FreePageManagerInitialize(FreePageManager *fpm, char *base) +{ + Size f; + + relptr_store(base, fpm->self, fpm); + relptr_store(base, fpm->btree_root, (FreePageBtree *) NULL); + relptr_store(base, fpm->btree_recycle, (FreePageSpanLeader *) NULL); + fpm->btree_depth = 0; + fpm->btree_recycle_count = 0; + fpm->singleton_first_page = 0; + fpm->singleton_npages = 0; + fpm->contiguous_pages = 0; + fpm->contiguous_pages_dirty = true; +#ifdef FPM_EXTRA_ASSERTS + fpm->free_pages = 0; +#endif + + for (f = 0; f < FPM_NUM_FREELISTS; f++) + relptr_store(base, fpm->freelist[f], (FreePageSpanLeader *) NULL); +} + +/* + * Allocate a run of pages of the given length from the free page manager. + * The return value indicates whether we were able to satisfy the request; + * if true, the first page of the allocation is stored in *first_page. + */ +bool +FreePageManagerGet(FreePageManager *fpm, Size npages, Size *first_page) +{ + bool result; + Size contiguous_pages; + + result = FreePageManagerGetInternal(fpm, npages, first_page); + + /* + * It's a bit counterintuitive, but allocating pages can actually create + * opportunities for cleanup that create larger ranges. We might pull a + * key out of the btree that enables the item at the head of the btree + * recycle list to be inserted; and then if there are more items behind it + * one of those might cause two currently-separated ranges to merge, + * creating a single range of contiguous pages larger than any that + * existed previously. It might be worth trying to improve the cleanup + * algorithm to avoid such corner cases, but for now we just notice the + * condition and do the appropriate reporting. + */ + contiguous_pages = FreePageBtreeCleanup(fpm); + if (fpm->contiguous_pages < contiguous_pages) + fpm->contiguous_pages = contiguous_pages; + + /* + * FreePageManagerGetInternal may have set contiguous_pages_dirty. + * Recompute contiguous_pages if so. + */ + FreePageManagerUpdateLargest(fpm); + +#ifdef FPM_EXTRA_ASSERTS + if (result) + { + Assert(fpm->free_pages >= npages); + fpm->free_pages -= npages; + } + Assert(fpm->free_pages == sum_free_pages(fpm)); + Assert(fpm->contiguous_pages == FreePageManagerLargestContiguous(fpm)); +#endif + return result; +} + +#ifdef FPM_EXTRA_ASSERTS +static void +sum_free_pages_recurse(FreePageManager *fpm, FreePageBtree *btp, Size *sum) +{ + char *base = fpm_segment_base(fpm); + + Assert(btp->hdr.magic == FREE_PAGE_INTERNAL_MAGIC || + btp->hdr.magic == FREE_PAGE_LEAF_MAGIC); + ++*sum; + if (btp->hdr.magic == FREE_PAGE_INTERNAL_MAGIC) + { + Size index; + + + for (index = 0; index < btp->hdr.nused; ++index) + { + FreePageBtree *child; + + child = relptr_access(base, btp->u.internal_key[index].child); + sum_free_pages_recurse(fpm, child, sum); + } + } +} +static Size +sum_free_pages(FreePageManager *fpm) +{ + FreePageSpanLeader *recycle; + char *base = fpm_segment_base(fpm); + Size sum = 0; + int list; + + /* Count the spans by scanning the freelists. */ + for (list = 0; list < FPM_NUM_FREELISTS; ++list) + { + + if (!relptr_is_null(fpm->freelist[list])) + { + FreePageSpanLeader *candidate = + relptr_access(base, fpm->freelist[list]); + + do + { + sum += candidate->npages; + candidate = relptr_access(base, candidate->next); + } while (candidate != NULL); + } + } + + /* Count btree internal pages. */ + if (fpm->btree_depth > 0) + { + FreePageBtree *root = relptr_access(base, fpm->btree_root); + + sum_free_pages_recurse(fpm, root, &sum); + } + + /* Count the recycle list. */ + for (recycle = relptr_access(base, fpm->btree_recycle); + recycle != NULL; + recycle = relptr_access(base, recycle->next)) + { + Assert(recycle->npages == 1); + ++sum; + } + + return sum; +} +#endif + +/* + * Compute the size of the largest run of pages that the user could + * successfully get. + */ +static Size +FreePageManagerLargestContiguous(FreePageManager *fpm) +{ + char *base; + Size largest; + + base = fpm_segment_base(fpm); + largest = 0; + if (!relptr_is_null(fpm->freelist[FPM_NUM_FREELISTS - 1])) + { + FreePageSpanLeader *candidate; + + candidate = relptr_access(base, fpm->freelist[FPM_NUM_FREELISTS - 1]); + do + { + if (candidate->npages > largest) + largest = candidate->npages; + candidate = relptr_access(base, candidate->next); + } while (candidate != NULL); + } + else + { + Size f = FPM_NUM_FREELISTS - 1; + + do + { + --f; + if (!relptr_is_null(fpm->freelist[f])) + { + largest = f + 1; + break; + } + } while (f > 0); + } + + return largest; +} + +/* + * Recompute the size of the largest run of pages that the user could + * successfully get, if it has been marked dirty. + */ +static void +FreePageManagerUpdateLargest(FreePageManager *fpm) +{ + if (!fpm->contiguous_pages_dirty) + return; + + fpm->contiguous_pages = FreePageManagerLargestContiguous(fpm); + fpm->contiguous_pages_dirty = false; +} + +/* + * Transfer a run of pages to the free page manager. + */ +void +FreePageManagerPut(FreePageManager *fpm, Size first_page, Size npages) +{ + Size contiguous_pages; + + Assert(npages > 0); + + /* Record the new pages. */ + contiguous_pages = + FreePageManagerPutInternal(fpm, first_page, npages, false); + + /* + * If the new range we inserted into the page manager was contiguous with + * an existing range, it may have opened up cleanup opportunities. + */ + if (contiguous_pages > npages) + { + Size cleanup_contiguous_pages; + + cleanup_contiguous_pages = FreePageBtreeCleanup(fpm); + if (cleanup_contiguous_pages > contiguous_pages) + contiguous_pages = cleanup_contiguous_pages; + } + + /* See if we now have a new largest chunk. */ + if (fpm->contiguous_pages < contiguous_pages) + fpm->contiguous_pages = contiguous_pages; + + /* + * The earlier call to FreePageManagerPutInternal may have set + * contiguous_pages_dirty if it needed to allocate internal pages, so + * recompute contiguous_pages if necessary. + */ + FreePageManagerUpdateLargest(fpm); + +#ifdef FPM_EXTRA_ASSERTS + fpm->free_pages += npages; + Assert(fpm->free_pages == sum_free_pages(fpm)); + Assert(fpm->contiguous_pages == FreePageManagerLargestContiguous(fpm)); +#endif +} + +/* + * Produce a debugging dump of the state of a free page manager. + */ +char * +FreePageManagerDump(FreePageManager *fpm) +{ + char *base = fpm_segment_base(fpm); + StringInfoData buf; + FreePageSpanLeader *recycle; + bool dumped_any_freelist = false; + Size f; + + /* Initialize output buffer. */ + initStringInfo(&buf); + + /* Dump general stuff. */ + appendStringInfo(&buf, "metadata: self %zu max contiguous pages = %zu\n", + relptr_offset(fpm->self), fpm->contiguous_pages); + + /* Dump btree. */ + if (fpm->btree_depth > 0) + { + FreePageBtree *root; + + appendStringInfo(&buf, "btree depth %u:\n", fpm->btree_depth); + root = relptr_access(base, fpm->btree_root); + FreePageManagerDumpBtree(fpm, root, NULL, 0, &buf); + } + else if (fpm->singleton_npages > 0) + { + appendStringInfo(&buf, "singleton: %zu(%zu)\n", + fpm->singleton_first_page, fpm->singleton_npages); + } + + /* Dump btree recycle list. */ + recycle = relptr_access(base, fpm->btree_recycle); + if (recycle != NULL) + { + appendStringInfoString(&buf, "btree recycle:"); + FreePageManagerDumpSpans(fpm, recycle, 1, &buf); + } + + /* Dump free lists. */ + for (f = 0; f < FPM_NUM_FREELISTS; ++f) + { + FreePageSpanLeader *span; + + if (relptr_is_null(fpm->freelist[f])) + continue; + if (!dumped_any_freelist) + { + appendStringInfoString(&buf, "freelists:\n"); + dumped_any_freelist = true; + } + appendStringInfo(&buf, " %zu:", f + 1); + span = relptr_access(base, fpm->freelist[f]); + FreePageManagerDumpSpans(fpm, span, f + 1, &buf); + } + + /* And return result to caller. */ + return buf.data; +} + + +/* + * The first_page value stored at index zero in any non-root page must match + * the first_page value stored in its parent at the index which points to that + * page. So when the value stored at index zero in a btree page changes, we've + * got to walk up the tree adjusting ancestor keys until we reach an ancestor + * where that key isn't index zero. This function should be called after + * updating the first key on the target page; it will propagate the change + * upward as far as needed. + * + * We assume here that the first key on the page has not changed enough to + * require changes in the ordering of keys on its ancestor pages. Thus, + * if we search the parent page for the first key greater than or equal to + * the first key on the current page, the downlink to this page will be either + * the exact index returned by the search (if the first key decreased) + * or one less (if the first key increased). + */ +static void +FreePageBtreeAdjustAncestorKeys(FreePageManager *fpm, FreePageBtree *btp) +{ + char *base = fpm_segment_base(fpm); + Size first_page; + FreePageBtree *parent; + FreePageBtree *child; + + /* This might be either a leaf or an internal page. */ + Assert(btp->hdr.nused > 0); + if (btp->hdr.magic == FREE_PAGE_LEAF_MAGIC) + { + Assert(btp->hdr.nused <= FPM_ITEMS_PER_LEAF_PAGE); + first_page = btp->u.leaf_key[0].first_page; + } + else + { + Assert(btp->hdr.magic == FREE_PAGE_INTERNAL_MAGIC); + Assert(btp->hdr.nused <= FPM_ITEMS_PER_INTERNAL_PAGE); + first_page = btp->u.internal_key[0].first_page; + } + child = btp; + + /* Loop until we find an ancestor that does not require adjustment. */ + for (;;) + { + Size s; + + parent = relptr_access(base, child->hdr.parent); + if (parent == NULL) + break; + s = FreePageBtreeSearchInternal(parent, first_page); + + /* Key is either at index s or index s-1; figure out which. */ + if (s >= parent->hdr.nused) + { + Assert(s == parent->hdr.nused); + --s; + } + else + { + FreePageBtree *check; + + check = relptr_access(base, parent->u.internal_key[s].child); + if (check != child) + { + Assert(s > 0); + --s; + } + } + +#ifdef USE_ASSERT_CHECKING + /* Debugging double-check. */ + { + FreePageBtree *check; + + check = relptr_access(base, parent->u.internal_key[s].child); + Assert(s < parent->hdr.nused); + Assert(child == check); + } +#endif + + /* Update the parent key. */ + parent->u.internal_key[s].first_page = first_page; + + /* + * If this is the first key in the parent, go up another level; else + * done. + */ + if (s > 0) + break; + child = parent; + } +} + +/* + * Attempt to reclaim space from the free-page btree. The return value is + * the largest range of contiguous pages created by the cleanup operation. + */ +static Size +FreePageBtreeCleanup(FreePageManager *fpm) +{ + char *base = fpm_segment_base(fpm); + Size max_contiguous_pages = 0; + + /* Attempt to shrink the depth of the btree. */ + while (!relptr_is_null(fpm->btree_root)) + { + FreePageBtree *root = relptr_access(base, fpm->btree_root); + + /* If the root contains only one key, reduce depth by one. */ + if (root->hdr.nused == 1) + { + /* Shrink depth of tree by one. */ + Assert(fpm->btree_depth > 0); + --fpm->btree_depth; + if (root->hdr.magic == FREE_PAGE_LEAF_MAGIC) + { + /* If root is a leaf, convert only entry to singleton range. */ + relptr_store(base, fpm->btree_root, (FreePageBtree *) NULL); + fpm->singleton_first_page = root->u.leaf_key[0].first_page; + fpm->singleton_npages = root->u.leaf_key[0].npages; + } + else + { + FreePageBtree *newroot; + + /* If root is an internal page, make only child the root. */ + Assert(root->hdr.magic == FREE_PAGE_INTERNAL_MAGIC); + relptr_copy(fpm->btree_root, root->u.internal_key[0].child); + newroot = relptr_access(base, fpm->btree_root); + relptr_store(base, newroot->hdr.parent, (FreePageBtree *) NULL); + } + FreePageBtreeRecycle(fpm, fpm_pointer_to_page(base, root)); + } + else if (root->hdr.nused == 2 && + root->hdr.magic == FREE_PAGE_LEAF_MAGIC) + { + Size end_of_first; + Size start_of_second; + + end_of_first = root->u.leaf_key[0].first_page + + root->u.leaf_key[0].npages; + start_of_second = root->u.leaf_key[1].first_page; + + if (end_of_first + 1 == start_of_second) + { + Size root_page = fpm_pointer_to_page(base, root); + + if (end_of_first == root_page) + { + FreePagePopSpanLeader(fpm, root->u.leaf_key[0].first_page); + FreePagePopSpanLeader(fpm, root->u.leaf_key[1].first_page); + fpm->singleton_first_page = root->u.leaf_key[0].first_page; + fpm->singleton_npages = root->u.leaf_key[0].npages + + root->u.leaf_key[1].npages + 1; + fpm->btree_depth = 0; + relptr_store(base, fpm->btree_root, + (FreePageBtree *) NULL); + FreePagePushSpanLeader(fpm, fpm->singleton_first_page, + fpm->singleton_npages); + Assert(max_contiguous_pages == 0); + max_contiguous_pages = fpm->singleton_npages; + } + } + + /* Whether it worked or not, it's time to stop. */ + break; + } + else + { + /* Nothing more to do. Stop. */ + break; + } + } + + /* + * Attempt to free recycled btree pages. We skip this if releasing the + * recycled page would require a btree page split, because the page we're + * trying to recycle would be consumed by the split, which would be + * counterproductive. + * + * We also currently only ever attempt to recycle the first page on the + * list; that could be made more aggressive, but it's not clear that the + * complexity would be worthwhile. + */ + while (fpm->btree_recycle_count > 0) + { + FreePageBtree *btp; + Size first_page; + Size contiguous_pages; + + btp = FreePageBtreeGetRecycled(fpm); + first_page = fpm_pointer_to_page(base, btp); + contiguous_pages = FreePageManagerPutInternal(fpm, first_page, 1, true); + if (contiguous_pages == 0) + { + FreePageBtreeRecycle(fpm, first_page); + break; + } + else + { + if (contiguous_pages > max_contiguous_pages) + max_contiguous_pages = contiguous_pages; + } + } + + return max_contiguous_pages; +} + +/* + * Consider consolidating the given page with its left or right sibling, + * if it's fairly empty. + */ +static void +FreePageBtreeConsolidate(FreePageManager *fpm, FreePageBtree *btp) +{ + char *base = fpm_segment_base(fpm); + FreePageBtree *np; + Size max; + + /* + * We only try to consolidate pages that are less than a third full. We + * could be more aggressive about this, but that might risk performing + * consolidation only to end up splitting again shortly thereafter. Since + * the btree should be very small compared to the space under management, + * our goal isn't so much to ensure that it always occupies the absolutely + * smallest possible number of pages as to reclaim pages before things get + * too egregiously out of hand. + */ + if (btp->hdr.magic == FREE_PAGE_LEAF_MAGIC) + max = FPM_ITEMS_PER_LEAF_PAGE; + else + { + Assert(btp->hdr.magic == FREE_PAGE_INTERNAL_MAGIC); + max = FPM_ITEMS_PER_INTERNAL_PAGE; + } + if (btp->hdr.nused >= max / 3) + return; + + /* + * If we can fit our right sibling's keys onto this page, consolidate. + */ + np = FreePageBtreeFindRightSibling(base, btp); + if (np != NULL && btp->hdr.nused + np->hdr.nused <= max) + { + if (btp->hdr.magic == FREE_PAGE_LEAF_MAGIC) + { + memcpy(&btp->u.leaf_key[btp->hdr.nused], &np->u.leaf_key[0], + sizeof(FreePageBtreeLeafKey) * np->hdr.nused); + btp->hdr.nused += np->hdr.nused; + } + else + { + memcpy(&btp->u.internal_key[btp->hdr.nused], &np->u.internal_key[0], + sizeof(FreePageBtreeInternalKey) * np->hdr.nused); + btp->hdr.nused += np->hdr.nused; + FreePageBtreeUpdateParentPointers(base, btp); + } + FreePageBtreeRemovePage(fpm, np); + return; + } + + /* + * If we can fit our keys onto our left sibling's page, consolidate. In + * this case, we move our keys onto the other page rather than vice versa, + * to avoid having to adjust ancestor keys. + */ + np = FreePageBtreeFindLeftSibling(base, btp); + if (np != NULL && btp->hdr.nused + np->hdr.nused <= max) + { + if (btp->hdr.magic == FREE_PAGE_LEAF_MAGIC) + { + memcpy(&np->u.leaf_key[np->hdr.nused], &btp->u.leaf_key[0], + sizeof(FreePageBtreeLeafKey) * btp->hdr.nused); + np->hdr.nused += btp->hdr.nused; + } + else + { + memcpy(&np->u.internal_key[np->hdr.nused], &btp->u.internal_key[0], + sizeof(FreePageBtreeInternalKey) * btp->hdr.nused); + np->hdr.nused += btp->hdr.nused; + FreePageBtreeUpdateParentPointers(base, np); + } + FreePageBtreeRemovePage(fpm, btp); + return; + } +} + +/* + * Find the passed page's left sibling; that is, the page at the same level + * of the tree whose keyspace immediately precedes ours. + */ +static FreePageBtree * +FreePageBtreeFindLeftSibling(char *base, FreePageBtree *btp) +{ + FreePageBtree *p = btp; + int levels = 0; + + /* Move up until we can move left. */ + for (;;) + { + Size first_page; + Size index; + + first_page = FreePageBtreeFirstKey(p); + p = relptr_access(base, p->hdr.parent); + + if (p == NULL) + return NULL; /* we were passed the rightmost page */ + + index = FreePageBtreeSearchInternal(p, first_page); + if (index > 0) + { + Assert(p->u.internal_key[index].first_page == first_page); + p = relptr_access(base, p->u.internal_key[index - 1].child); + break; + } + Assert(index == 0); + ++levels; + } + + /* Descend left. */ + while (levels > 0) + { + Assert(p->hdr.magic == FREE_PAGE_INTERNAL_MAGIC); + p = relptr_access(base, p->u.internal_key[p->hdr.nused - 1].child); + --levels; + } + Assert(p->hdr.magic == btp->hdr.magic); + + return p; +} + +/* + * Find the passed page's right sibling; that is, the page at the same level + * of the tree whose keyspace immediately follows ours. + */ +static FreePageBtree * +FreePageBtreeFindRightSibling(char *base, FreePageBtree *btp) +{ + FreePageBtree *p = btp; + int levels = 0; + + /* Move up until we can move right. */ + for (;;) + { + Size first_page; + Size index; + + first_page = FreePageBtreeFirstKey(p); + p = relptr_access(base, p->hdr.parent); + + if (p == NULL) + return NULL; /* we were passed the rightmost page */ + + index = FreePageBtreeSearchInternal(p, first_page); + if (index < p->hdr.nused - 1) + { + Assert(p->u.internal_key[index].first_page == first_page); + p = relptr_access(base, p->u.internal_key[index + 1].child); + break; + } + Assert(index == p->hdr.nused - 1); + ++levels; + } + + /* Descend left. */ + while (levels > 0) + { + Assert(p->hdr.magic == FREE_PAGE_INTERNAL_MAGIC); + p = relptr_access(base, p->u.internal_key[0].child); + --levels; + } + Assert(p->hdr.magic == btp->hdr.magic); + + return p; +} + +/* + * Get the first key on a btree page. + */ +static Size +FreePageBtreeFirstKey(FreePageBtree *btp) +{ + Assert(btp->hdr.nused > 0); + + if (btp->hdr.magic == FREE_PAGE_LEAF_MAGIC) + return btp->u.leaf_key[0].first_page; + else + { + Assert(btp->hdr.magic == FREE_PAGE_INTERNAL_MAGIC); + return btp->u.internal_key[0].first_page; + } +} + +/* + * Get a page from the btree recycle list for use as a btree page. + */ +static FreePageBtree * +FreePageBtreeGetRecycled(FreePageManager *fpm) +{ + char *base = fpm_segment_base(fpm); + FreePageSpanLeader *victim = relptr_access(base, fpm->btree_recycle); + FreePageSpanLeader *newhead; + + Assert(victim != NULL); + newhead = relptr_access(base, victim->next); + if (newhead != NULL) + relptr_copy(newhead->prev, victim->prev); + relptr_store(base, fpm->btree_recycle, newhead); + Assert(fpm_pointer_is_page_aligned(base, victim)); + fpm->btree_recycle_count--; + return (FreePageBtree *) victim; +} + +/* + * Insert an item into an internal page. + */ +static void +FreePageBtreeInsertInternal(char *base, FreePageBtree *btp, Size index, + Size first_page, FreePageBtree *child) +{ + Assert(btp->hdr.magic == FREE_PAGE_INTERNAL_MAGIC); + Assert(btp->hdr.nused <= FPM_ITEMS_PER_INTERNAL_PAGE); + Assert(index <= btp->hdr.nused); + memmove(&btp->u.internal_key[index + 1], &btp->u.internal_key[index], + sizeof(FreePageBtreeInternalKey) * (btp->hdr.nused - index)); + btp->u.internal_key[index].first_page = first_page; + relptr_store(base, btp->u.internal_key[index].child, child); + ++btp->hdr.nused; +} + +/* + * Insert an item into a leaf page. + */ +static void +FreePageBtreeInsertLeaf(FreePageBtree *btp, Size index, Size first_page, + Size npages) +{ + Assert(btp->hdr.magic == FREE_PAGE_LEAF_MAGIC); + Assert(btp->hdr.nused <= FPM_ITEMS_PER_LEAF_PAGE); + Assert(index <= btp->hdr.nused); + memmove(&btp->u.leaf_key[index + 1], &btp->u.leaf_key[index], + sizeof(FreePageBtreeLeafKey) * (btp->hdr.nused - index)); + btp->u.leaf_key[index].first_page = first_page; + btp->u.leaf_key[index].npages = npages; + ++btp->hdr.nused; +} + +/* + * Put a page on the btree recycle list. + */ +static void +FreePageBtreeRecycle(FreePageManager *fpm, Size pageno) +{ + char *base = fpm_segment_base(fpm); + FreePageSpanLeader *head = relptr_access(base, fpm->btree_recycle); + FreePageSpanLeader *span; + + span = (FreePageSpanLeader *) fpm_page_to_pointer(base, pageno); + span->magic = FREE_PAGE_SPAN_LEADER_MAGIC; + span->npages = 1; + relptr_store(base, span->next, head); + relptr_store(base, span->prev, (FreePageSpanLeader *) NULL); + if (head != NULL) + relptr_store(base, head->prev, span); + relptr_store(base, fpm->btree_recycle, span); + fpm->btree_recycle_count++; +} + +/* + * Remove an item from the btree at the given position on the given page. + */ +static void +FreePageBtreeRemove(FreePageManager *fpm, FreePageBtree *btp, Size index) +{ + Assert(btp->hdr.magic == FREE_PAGE_LEAF_MAGIC); + Assert(index < btp->hdr.nused); + + /* When last item is removed, extirpate entire page from btree. */ + if (btp->hdr.nused == 1) + { + FreePageBtreeRemovePage(fpm, btp); + return; + } + + /* Physically remove the key from the page. */ + --btp->hdr.nused; + if (index < btp->hdr.nused) + memmove(&btp->u.leaf_key[index], &btp->u.leaf_key[index + 1], + sizeof(FreePageBtreeLeafKey) * (btp->hdr.nused - index)); + + /* If we just removed the first key, adjust ancestor keys. */ + if (index == 0) + FreePageBtreeAdjustAncestorKeys(fpm, btp); + + /* Consider whether to consolidate this page with a sibling. */ + FreePageBtreeConsolidate(fpm, btp); +} + +/* + * Remove a page from the btree. Caller is responsible for having relocated + * any keys from this page that are still wanted. The page is placed on the + * recycled list. + */ +static void +FreePageBtreeRemovePage(FreePageManager *fpm, FreePageBtree *btp) +{ + char *base = fpm_segment_base(fpm); + FreePageBtree *parent; + Size index; + Size first_page; + + for (;;) + { + /* Find parent page. */ + parent = relptr_access(base, btp->hdr.parent); + if (parent == NULL) + { + /* We are removing the root page. */ + relptr_store(base, fpm->btree_root, (FreePageBtree *) NULL); + fpm->btree_depth = 0; + Assert(fpm->singleton_first_page == 0); + Assert(fpm->singleton_npages == 0); + return; + } + + /* + * If the parent contains only one item, we need to remove it as well. + */ + if (parent->hdr.nused > 1) + break; + FreePageBtreeRecycle(fpm, fpm_pointer_to_page(base, btp)); + btp = parent; + } + + /* Find and remove the downlink. */ + first_page = FreePageBtreeFirstKey(btp); + if (parent->hdr.magic == FREE_PAGE_LEAF_MAGIC) + { + index = FreePageBtreeSearchLeaf(parent, first_page); + Assert(index < parent->hdr.nused); + if (index < parent->hdr.nused - 1) + memmove(&parent->u.leaf_key[index], + &parent->u.leaf_key[index + 1], + sizeof(FreePageBtreeLeafKey) + * (parent->hdr.nused - index - 1)); + } + else + { + index = FreePageBtreeSearchInternal(parent, first_page); + Assert(index < parent->hdr.nused); + if (index < parent->hdr.nused - 1) + memmove(&parent->u.internal_key[index], + &parent->u.internal_key[index + 1], + sizeof(FreePageBtreeInternalKey) + * (parent->hdr.nused - index - 1)); + } + parent->hdr.nused--; + Assert(parent->hdr.nused > 0); + + /* Recycle the page. */ + FreePageBtreeRecycle(fpm, fpm_pointer_to_page(base, btp)); + + /* Adjust ancestor keys if needed. */ + if (index == 0) + FreePageBtreeAdjustAncestorKeys(fpm, parent); + + /* Consider whether to consolidate the parent with a sibling. */ + FreePageBtreeConsolidate(fpm, parent); +} + +/* + * Search the btree for an entry for the given first page and initialize + * *result with the results of the search. result->page and result->index + * indicate either the position of an exact match or the position at which + * the new key should be inserted. result->found is true for an exact match, + * otherwise false. result->split_pages will contain the number of additional + * btree pages that will be needed when performing a split to insert a key. + * Except as described above, the contents of fields in the result object are + * undefined on return. + */ +static void +FreePageBtreeSearch(FreePageManager *fpm, Size first_page, + FreePageBtreeSearchResult *result) +{ + char *base = fpm_segment_base(fpm); + FreePageBtree *btp = relptr_access(base, fpm->btree_root); + Size index; + + result->split_pages = 1; + + /* If the btree is empty, there's nothing to find. */ + if (btp == NULL) + { + result->page = NULL; + result->found = false; + return; + } + + /* Descend until we hit a leaf. */ + while (btp->hdr.magic == FREE_PAGE_INTERNAL_MAGIC) + { + FreePageBtree *child; + bool found_exact; + + index = FreePageBtreeSearchInternal(btp, first_page); + found_exact = index < btp->hdr.nused && + btp->u.internal_key[index].first_page == first_page; + + /* + * If we found an exact match we descend directly. Otherwise, we + * descend into the child to the left if possible so that we can find + * the insertion point at that child's high end. + */ + if (!found_exact && index > 0) + --index; + + /* Track required split depth for leaf insert. */ + if (btp->hdr.nused >= FPM_ITEMS_PER_INTERNAL_PAGE) + { + Assert(btp->hdr.nused == FPM_ITEMS_PER_INTERNAL_PAGE); + result->split_pages++; + } + else + result->split_pages = 0; + + /* Descend to appropriate child page. */ + Assert(index < btp->hdr.nused); + child = relptr_access(base, btp->u.internal_key[index].child); + Assert(relptr_access(base, child->hdr.parent) == btp); + btp = child; + } + + /* Track required split depth for leaf insert. */ + if (btp->hdr.nused >= FPM_ITEMS_PER_LEAF_PAGE) + { + Assert(btp->hdr.nused == FPM_ITEMS_PER_INTERNAL_PAGE); + result->split_pages++; + } + else + result->split_pages = 0; + + /* Search leaf page. */ + index = FreePageBtreeSearchLeaf(btp, first_page); + + /* Assemble results. */ + result->page = btp; + result->index = index; + result->found = index < btp->hdr.nused && + first_page == btp->u.leaf_key[index].first_page; +} + +/* + * Search an internal page for the first key greater than or equal to a given + * page number. Returns the index of that key, or one greater than the number + * of keys on the page if none. + */ +static Size +FreePageBtreeSearchInternal(FreePageBtree *btp, Size first_page) +{ + Size low = 0; + Size high = btp->hdr.nused; + + Assert(btp->hdr.magic == FREE_PAGE_INTERNAL_MAGIC); + Assert(high > 0 && high <= FPM_ITEMS_PER_INTERNAL_PAGE); + + while (low < high) + { + Size mid = (low + high) / 2; + Size val = btp->u.internal_key[mid].first_page; + + if (first_page == val) + return mid; + else if (first_page < val) + high = mid; + else + low = mid + 1; + } + + return low; +} + +/* + * Search a leaf page for the first key greater than or equal to a given + * page number. Returns the index of that key, or one greater than the number + * of keys on the page if none. + */ +static Size +FreePageBtreeSearchLeaf(FreePageBtree *btp, Size first_page) +{ + Size low = 0; + Size high = btp->hdr.nused; + + Assert(btp->hdr.magic == FREE_PAGE_LEAF_MAGIC); + Assert(high > 0 && high <= FPM_ITEMS_PER_LEAF_PAGE); + + while (low < high) + { + Size mid = (low + high) / 2; + Size val = btp->u.leaf_key[mid].first_page; + + if (first_page == val) + return mid; + else if (first_page < val) + high = mid; + else + low = mid + 1; + } + + return low; +} + +/* + * Allocate a new btree page and move half the keys from the provided page + * to the new page. Caller is responsible for making sure that there's a + * page available from fpm->btree_recycle. Returns a pointer to the new page, + * to which caller must add a downlink. + */ +static FreePageBtree * +FreePageBtreeSplitPage(FreePageManager *fpm, FreePageBtree *btp) +{ + FreePageBtree *newsibling; + + newsibling = FreePageBtreeGetRecycled(fpm); + newsibling->hdr.magic = btp->hdr.magic; + newsibling->hdr.nused = btp->hdr.nused / 2; + relptr_copy(newsibling->hdr.parent, btp->hdr.parent); + btp->hdr.nused -= newsibling->hdr.nused; + + if (btp->hdr.magic == FREE_PAGE_LEAF_MAGIC) + memcpy(&newsibling->u.leaf_key, + &btp->u.leaf_key[btp->hdr.nused], + sizeof(FreePageBtreeLeafKey) * newsibling->hdr.nused); + else + { + Assert(btp->hdr.magic == FREE_PAGE_INTERNAL_MAGIC); + memcpy(&newsibling->u.internal_key, + &btp->u.internal_key[btp->hdr.nused], + sizeof(FreePageBtreeInternalKey) * newsibling->hdr.nused); + FreePageBtreeUpdateParentPointers(fpm_segment_base(fpm), newsibling); + } + + return newsibling; +} + +/* + * When internal pages are split or merged, the parent pointers of their + * children must be updated. + */ +static void +FreePageBtreeUpdateParentPointers(char *base, FreePageBtree *btp) +{ + Size i; + + Assert(btp->hdr.magic == FREE_PAGE_INTERNAL_MAGIC); + for (i = 0; i < btp->hdr.nused; ++i) + { + FreePageBtree *child; + + child = relptr_access(base, btp->u.internal_key[i].child); + relptr_store(base, child->hdr.parent, btp); + } +} + +/* + * Debugging dump of btree data. + */ +static void +FreePageManagerDumpBtree(FreePageManager *fpm, FreePageBtree *btp, + FreePageBtree *parent, int level, StringInfo buf) +{ + char *base = fpm_segment_base(fpm); + Size pageno = fpm_pointer_to_page(base, btp); + Size index; + FreePageBtree *check_parent; + + check_stack_depth(); + check_parent = relptr_access(base, btp->hdr.parent); + appendStringInfo(buf, " %zu@%d %c", pageno, level, + btp->hdr.magic == FREE_PAGE_INTERNAL_MAGIC ? 'i' : 'l'); + if (parent != check_parent) + appendStringInfo(buf, " [actual parent %zu, expected %zu]", + fpm_pointer_to_page(base, check_parent), + fpm_pointer_to_page(base, parent)); + appendStringInfoChar(buf, ':'); + for (index = 0; index < btp->hdr.nused; ++index) + { + if (btp->hdr.magic == FREE_PAGE_INTERNAL_MAGIC) + appendStringInfo(buf, " %zu->%zu", + btp->u.internal_key[index].first_page, + relptr_offset(btp->u.internal_key[index].child) / FPM_PAGE_SIZE); + else + appendStringInfo(buf, " %zu(%zu)", + btp->u.leaf_key[index].first_page, + btp->u.leaf_key[index].npages); + } + appendStringInfoChar(buf, '\n'); + + if (btp->hdr.magic == FREE_PAGE_INTERNAL_MAGIC) + { + for (index = 0; index < btp->hdr.nused; ++index) + { + FreePageBtree *child; + + child = relptr_access(base, btp->u.internal_key[index].child); + FreePageManagerDumpBtree(fpm, child, btp, level + 1, buf); + } + } +} + +/* + * Debugging dump of free-span data. + */ +static void +FreePageManagerDumpSpans(FreePageManager *fpm, FreePageSpanLeader *span, + Size expected_pages, StringInfo buf) +{ + char *base = fpm_segment_base(fpm); + + while (span != NULL) + { + if (span->npages != expected_pages) + appendStringInfo(buf, " %zu(%zu)", fpm_pointer_to_page(base, span), + span->npages); + else + appendStringInfo(buf, " %zu", fpm_pointer_to_page(base, span)); + span = relptr_access(base, span->next); + } + + appendStringInfoChar(buf, '\n'); +} + +/* + * This function allocates a run of pages of the given length from the free + * page manager. + */ +static bool +FreePageManagerGetInternal(FreePageManager *fpm, Size npages, Size *first_page) +{ + char *base = fpm_segment_base(fpm); + FreePageSpanLeader *victim = NULL; + FreePageSpanLeader *prev; + FreePageSpanLeader *next; + FreePageBtreeSearchResult result; + Size victim_page = 0; /* placate compiler */ + Size f; + + /* + * Search for a free span. + * + * Right now, we use a simple best-fit policy here, but it's possible for + * this to result in memory fragmentation if we're repeatedly asked to + * allocate chunks just a little smaller than what we have available. + * Hopefully, this is unlikely, because we expect most requests to be + * single pages or superblock-sized chunks -- but no policy can be optimal + * under all circumstances unless it has knowledge of future allocation + * patterns. + */ + for (f = Min(npages, FPM_NUM_FREELISTS) - 1; f < FPM_NUM_FREELISTS; ++f) + { + /* Skip empty freelists. */ + if (relptr_is_null(fpm->freelist[f])) + continue; + + /* + * All of the freelists except the last one contain only items of a + * single size, so we just take the first one. But the final free + * list contains everything too big for any of the other lists, so we + * need to search the list. + */ + if (f < FPM_NUM_FREELISTS - 1) + victim = relptr_access(base, fpm->freelist[f]); + else + { + FreePageSpanLeader *candidate; + + candidate = relptr_access(base, fpm->freelist[f]); + do + { + if (candidate->npages >= npages && (victim == NULL || + victim->npages > candidate->npages)) + { + victim = candidate; + if (victim->npages == npages) + break; + } + candidate = relptr_access(base, candidate->next); + } while (candidate != NULL); + } + break; + } + + /* If we didn't find an allocatable span, return failure. */ + if (victim == NULL) + return false; + + /* Remove span from free list. */ + Assert(victim->magic == FREE_PAGE_SPAN_LEADER_MAGIC); + prev = relptr_access(base, victim->prev); + next = relptr_access(base, victim->next); + if (prev != NULL) + relptr_copy(prev->next, victim->next); + else + relptr_copy(fpm->freelist[f], victim->next); + if (next != NULL) + relptr_copy(next->prev, victim->prev); + victim_page = fpm_pointer_to_page(base, victim); + + /* Decide whether we might be invalidating contiguous_pages. */ + if (f == FPM_NUM_FREELISTS - 1 && + victim->npages == fpm->contiguous_pages) + { + /* + * The victim span came from the oversized freelist, and had the same + * size as the longest span. There may or may not be another one of + * the same size, so contiguous_pages must be recomputed just to be + * safe. + */ + fpm->contiguous_pages_dirty = true; + } + else if (f + 1 == fpm->contiguous_pages && + relptr_is_null(fpm->freelist[f])) + { + /* + * The victim span came from a fixed sized freelist, and it was the + * list for spans of the same size as the current longest span, and + * the list is now empty after removing the victim. So + * contiguous_pages must be recomputed without a doubt. + */ + fpm->contiguous_pages_dirty = true; + } + + /* + * If we haven't initialized the btree yet, the victim must be the single + * span stored within the FreePageManager itself. Otherwise, we need to + * update the btree. + */ + if (relptr_is_null(fpm->btree_root)) + { + Assert(victim_page == fpm->singleton_first_page); + Assert(victim->npages == fpm->singleton_npages); + Assert(victim->npages >= npages); + fpm->singleton_first_page += npages; + fpm->singleton_npages -= npages; + if (fpm->singleton_npages > 0) + FreePagePushSpanLeader(fpm, fpm->singleton_first_page, + fpm->singleton_npages); + } + else + { + /* + * If the span we found is exactly the right size, remove it from the + * btree completely. Otherwise, adjust the btree entry to reflect the + * still-unallocated portion of the span, and put that portion on the + * appropriate free list. + */ + FreePageBtreeSearch(fpm, victim_page, &result); + Assert(result.found); + if (victim->npages == npages) + FreePageBtreeRemove(fpm, result.page, result.index); + else + { + FreePageBtreeLeafKey *key; + + /* Adjust btree to reflect remaining pages. */ + Assert(victim->npages > npages); + key = &result.page->u.leaf_key[result.index]; + Assert(key->npages == victim->npages); + key->first_page += npages; + key->npages -= npages; + if (result.index == 0) + FreePageBtreeAdjustAncestorKeys(fpm, result.page); + + /* Put the unallocated pages back on the appropriate free list. */ + FreePagePushSpanLeader(fpm, victim_page + npages, + victim->npages - npages); + } + } + + /* Return results to caller. */ + *first_page = fpm_pointer_to_page(base, victim); + return true; +} + +/* + * Put a range of pages into the btree and freelists, consolidating it with + * existing free spans just before and/or after it. If 'soft' is true, + * only perform the insertion if it can be done without allocating new btree + * pages; if false, do it always. Returns 0 if the soft flag caused the + * insertion to be skipped, or otherwise the size of the contiguous span + * created by the insertion. This may be larger than npages if we're able + * to consolidate with an adjacent range. + */ +static Size +FreePageManagerPutInternal(FreePageManager *fpm, Size first_page, Size npages, + bool soft) +{ + char *base = fpm_segment_base(fpm); + FreePageBtreeSearchResult result; + FreePageBtreeLeafKey *prevkey = NULL; + FreePageBtreeLeafKey *nextkey = NULL; + FreePageBtree *np; + Size nindex; + + Assert(npages > 0); + + /* We can store a single free span without initializing the btree. */ + if (fpm->btree_depth == 0) + { + if (fpm->singleton_npages == 0) + { + /* Don't have a span yet; store this one. */ + fpm->singleton_first_page = first_page; + fpm->singleton_npages = npages; + FreePagePushSpanLeader(fpm, first_page, npages); + return fpm->singleton_npages; + } + else if (fpm->singleton_first_page + fpm->singleton_npages == + first_page) + { + /* New span immediately follows sole existing span. */ + fpm->singleton_npages += npages; + FreePagePopSpanLeader(fpm, fpm->singleton_first_page); + FreePagePushSpanLeader(fpm, fpm->singleton_first_page, + fpm->singleton_npages); + return fpm->singleton_npages; + } + else if (first_page + npages == fpm->singleton_first_page) + { + /* New span immediately precedes sole existing span. */ + FreePagePopSpanLeader(fpm, fpm->singleton_first_page); + fpm->singleton_first_page = first_page; + fpm->singleton_npages += npages; + FreePagePushSpanLeader(fpm, fpm->singleton_first_page, + fpm->singleton_npages); + return fpm->singleton_npages; + } + else + { + /* Not contiguous; we need to initialize the btree. */ + Size root_page; + FreePageBtree *root; + + if (!relptr_is_null(fpm->btree_recycle)) + root = FreePageBtreeGetRecycled(fpm); + else if (soft) + return 0; /* Should not allocate if soft. */ + else if (FreePageManagerGetInternal(fpm, 1, &root_page)) + root = (FreePageBtree *) fpm_page_to_pointer(base, root_page); + else + { + /* We'd better be able to get a page from the existing range. */ + elog(FATAL, "free page manager btree is corrupt"); + } + + /* Create the btree and move the preexisting range into it. */ + root->hdr.magic = FREE_PAGE_LEAF_MAGIC; + root->hdr.nused = 1; + relptr_store(base, root->hdr.parent, (FreePageBtree *) NULL); + root->u.leaf_key[0].first_page = fpm->singleton_first_page; + root->u.leaf_key[0].npages = fpm->singleton_npages; + relptr_store(base, fpm->btree_root, root); + fpm->singleton_first_page = 0; + fpm->singleton_npages = 0; + fpm->btree_depth = 1; + + /* + * Corner case: it may be that the btree root took the very last + * free page. In that case, the sole btree entry covers a zero + * page run, which is invalid. Overwrite it with the entry we're + * trying to insert and get out. + */ + if (root->u.leaf_key[0].npages == 0) + { + root->u.leaf_key[0].first_page = first_page; + root->u.leaf_key[0].npages = npages; + FreePagePushSpanLeader(fpm, first_page, npages); + return npages; + } + + /* Fall through to insert the new key. */ + } + } + + /* Search the btree. */ + FreePageBtreeSearch(fpm, first_page, &result); + Assert(!result.found); + if (result.index > 0) + prevkey = &result.page->u.leaf_key[result.index - 1]; + if (result.index < result.page->hdr.nused) + { + np = result.page; + nindex = result.index; + nextkey = &result.page->u.leaf_key[result.index]; + } + else + { + np = FreePageBtreeFindRightSibling(base, result.page); + nindex = 0; + if (np != NULL) + nextkey = &np->u.leaf_key[0]; + } + + /* Consolidate with the previous entry if possible. */ + if (prevkey != NULL && prevkey->first_page + prevkey->npages >= first_page) + { + bool remove_next = false; + Size result; + + Assert(prevkey->first_page + prevkey->npages == first_page); + prevkey->npages = (first_page - prevkey->first_page) + npages; + + /* Check whether we can *also* consolidate with the following entry. */ + if (nextkey != NULL && + prevkey->first_page + prevkey->npages >= nextkey->first_page) + { + Assert(prevkey->first_page + prevkey->npages == + nextkey->first_page); + prevkey->npages = (nextkey->first_page - prevkey->first_page) + + nextkey->npages; + FreePagePopSpanLeader(fpm, nextkey->first_page); + remove_next = true; + } + + /* Put the span on the correct freelist and save size. */ + FreePagePopSpanLeader(fpm, prevkey->first_page); + FreePagePushSpanLeader(fpm, prevkey->first_page, prevkey->npages); + result = prevkey->npages; + + /* + * If we consolidated with both the preceding and following entries, + * we must remove the following entry. We do this last, because + * removing an element from the btree may invalidate pointers we hold + * into the current data structure. + * + * NB: The btree is technically in an invalid state a this point + * because we've already updated prevkey to cover the same key space + * as nextkey. FreePageBtreeRemove() shouldn't notice that, though. + */ + if (remove_next) + FreePageBtreeRemove(fpm, np, nindex); + + return result; + } + + /* Consolidate with the next entry if possible. */ + if (nextkey != NULL && first_page + npages >= nextkey->first_page) + { + Size newpages; + + /* Compute new size for span. */ + Assert(first_page + npages == nextkey->first_page); + newpages = (nextkey->first_page - first_page) + nextkey->npages; + + /* Put span on correct free list. */ + FreePagePopSpanLeader(fpm, nextkey->first_page); + FreePagePushSpanLeader(fpm, first_page, newpages); + + /* Update key in place. */ + nextkey->first_page = first_page; + nextkey->npages = newpages; + + /* If reducing first key on page, ancestors might need adjustment. */ + if (nindex == 0) + FreePageBtreeAdjustAncestorKeys(fpm, np); + + return nextkey->npages; + } + + /* Split leaf page and as many of its ancestors as necessary. */ + if (result.split_pages > 0) + { + /* + * NB: We could consider various coping strategies here to avoid a + * split; most obviously, if np != result.page, we could target that + * page instead. More complicated shuffling strategies could be + * possible as well; basically, unless every single leaf page is 100% + * full, we can jam this key in there if we try hard enough. It's + * unlikely that trying that hard is worthwhile, but it's possible we + * might need to make more than no effort. For now, we just do the + * easy thing, which is nothing. + */ + + /* If this is a soft insert, it's time to give up. */ + if (soft) + return 0; + + /* Check whether we need to allocate more btree pages to split. */ + if (result.split_pages > fpm->btree_recycle_count) + { + Size pages_needed; + Size recycle_page; + Size i; + + /* + * Allocate the required number of pages and split each one in + * turn. This should never fail, because if we've got enough + * spans of free pages kicking around that we need additional + * storage space just to remember them all, then we should + * certainly have enough to expand the btree, which should only + * ever use a tiny number of pages compared to the number under + * management. If it does, something's badly screwed up. + */ + pages_needed = result.split_pages - fpm->btree_recycle_count; + for (i = 0; i < pages_needed; ++i) + { + if (!FreePageManagerGetInternal(fpm, 1, &recycle_page)) + elog(FATAL, "free page manager btree is corrupt"); + FreePageBtreeRecycle(fpm, recycle_page); + } + + /* + * The act of allocating pages to recycle may have invalidated the + * results of our previous btree research, so repeat it. (We could + * recheck whether any of our split-avoidance strategies that were + * not viable before now are, but it hardly seems worthwhile, so + * we don't bother. Consolidation can't be possible now if it + * wasn't previously.) + */ + FreePageBtreeSearch(fpm, first_page, &result); + + /* + * The act of allocating pages for use in constructing our btree + * should never cause any page to become more full, so the new + * split depth should be no greater than the old one, and perhaps + * less if we fortuitously allocated a chunk that freed up a slot + * on the page we need to update. + */ + Assert(result.split_pages <= fpm->btree_recycle_count); + } + + /* If we still need to perform a split, do it. */ + if (result.split_pages > 0) + { + FreePageBtree *split_target = result.page; + FreePageBtree *child = NULL; + Size key = first_page; + + for (;;) + { + FreePageBtree *newsibling; + FreePageBtree *parent; + + /* Identify parent page, which must receive downlink. */ + parent = relptr_access(base, split_target->hdr.parent); + + /* Split the page - downlink not added yet. */ + newsibling = FreePageBtreeSplitPage(fpm, split_target); + + /* + * At this point in the loop, we're always carrying a pending + * insertion. On the first pass, it's the actual key we're + * trying to insert; on subsequent passes, it's the downlink + * that needs to be added as a result of the split performed + * during the previous loop iteration. Since we've just split + * the page, there's definitely room on one of the two + * resulting pages. + */ + if (child == NULL) + { + Size index; + FreePageBtree *insert_into; + + insert_into = key < newsibling->u.leaf_key[0].first_page ? + split_target : newsibling; + index = FreePageBtreeSearchLeaf(insert_into, key); + FreePageBtreeInsertLeaf(insert_into, index, key, npages); + if (index == 0 && insert_into == split_target) + FreePageBtreeAdjustAncestorKeys(fpm, split_target); + } + else + { + Size index; + FreePageBtree *insert_into; + + insert_into = + key < newsibling->u.internal_key[0].first_page ? + split_target : newsibling; + index = FreePageBtreeSearchInternal(insert_into, key); + FreePageBtreeInsertInternal(base, insert_into, index, + key, child); + relptr_store(base, child->hdr.parent, insert_into); + if (index == 0 && insert_into == split_target) + FreePageBtreeAdjustAncestorKeys(fpm, split_target); + } + + /* If the page we just split has no parent, split the root. */ + if (parent == NULL) + { + FreePageBtree *newroot; + + newroot = FreePageBtreeGetRecycled(fpm); + newroot->hdr.magic = FREE_PAGE_INTERNAL_MAGIC; + newroot->hdr.nused = 2; + relptr_store(base, newroot->hdr.parent, + (FreePageBtree *) NULL); + newroot->u.internal_key[0].first_page = + FreePageBtreeFirstKey(split_target); + relptr_store(base, newroot->u.internal_key[0].child, + split_target); + relptr_store(base, split_target->hdr.parent, newroot); + newroot->u.internal_key[1].first_page = + FreePageBtreeFirstKey(newsibling); + relptr_store(base, newroot->u.internal_key[1].child, + newsibling); + relptr_store(base, newsibling->hdr.parent, newroot); + relptr_store(base, fpm->btree_root, newroot); + fpm->btree_depth++; + + break; + } + + /* If the parent page isn't full, insert the downlink. */ + key = newsibling->u.internal_key[0].first_page; + if (parent->hdr.nused < FPM_ITEMS_PER_INTERNAL_PAGE) + { + Size index; + + index = FreePageBtreeSearchInternal(parent, key); + FreePageBtreeInsertInternal(base, parent, index, + key, newsibling); + relptr_store(base, newsibling->hdr.parent, parent); + if (index == 0) + FreePageBtreeAdjustAncestorKeys(fpm, parent); + break; + } + + /* The parent also needs to be split, so loop around. */ + child = newsibling; + split_target = parent; + } + + /* + * The loop above did the insert, so just need to update the free + * list, and we're done. + */ + FreePagePushSpanLeader(fpm, first_page, npages); + + return npages; + } + } + + /* Physically add the key to the page. */ + Assert(result.page->hdr.nused < FPM_ITEMS_PER_LEAF_PAGE); + FreePageBtreeInsertLeaf(result.page, result.index, first_page, npages); + + /* If new first key on page, ancestors might need adjustment. */ + if (result.index == 0) + FreePageBtreeAdjustAncestorKeys(fpm, result.page); + + /* Put it on the free list. */ + FreePagePushSpanLeader(fpm, first_page, npages); + + return npages; +} + +/* + * Remove a FreePageSpanLeader from the linked-list that contains it, either + * because we're changing the size of the span, or because we're allocating it. + */ +static void +FreePagePopSpanLeader(FreePageManager *fpm, Size pageno) +{ + char *base = fpm_segment_base(fpm); + FreePageSpanLeader *span; + FreePageSpanLeader *next; + FreePageSpanLeader *prev; + + span = (FreePageSpanLeader *) fpm_page_to_pointer(base, pageno); + + next = relptr_access(base, span->next); + prev = relptr_access(base, span->prev); + if (next != NULL) + relptr_copy(next->prev, span->prev); + if (prev != NULL) + relptr_copy(prev->next, span->next); + else + { + Size f = Min(span->npages, FPM_NUM_FREELISTS) - 1; + + Assert(relptr_offset(fpm->freelist[f]) == pageno * FPM_PAGE_SIZE); + relptr_copy(fpm->freelist[f], span->next); + } +} + +/* + * Initialize a new FreePageSpanLeader and put it on the appropriate free list. + */ +static void +FreePagePushSpanLeader(FreePageManager *fpm, Size first_page, Size npages) +{ + char *base = fpm_segment_base(fpm); + Size f = Min(npages, FPM_NUM_FREELISTS) - 1; + FreePageSpanLeader *head = relptr_access(base, fpm->freelist[f]); + FreePageSpanLeader *span; + + span = (FreePageSpanLeader *) fpm_page_to_pointer(base, first_page); + span->magic = FREE_PAGE_SPAN_LEADER_MAGIC; + span->npages = npages; + relptr_store(base, span->next, head); + relptr_store(base, span->prev, (FreePageSpanLeader *) NULL); + if (head != NULL) + relptr_store(base, head->prev, span); + relptr_store(base, fpm->freelist[f], span); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/mmgr/generation.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/mmgr/generation.c new file mode 100644 index 00000000000..4fb8663cd6b --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/mmgr/generation.c @@ -0,0 +1,1134 @@ +/*------------------------------------------------------------------------- + * + * generation.c + * Generational allocator definitions. + * + * Generation is a custom MemoryContext implementation designed for cases of + * chunks with similar lifespan. + * + * Portions Copyright (c) 2017-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/utils/mmgr/generation.c + * + * + * This memory context is based on the assumption that the chunks are freed + * roughly in the same order as they were allocated (FIFO), or in groups with + * similar lifespan (generations - hence the name of the context). This is + * typical for various queue-like use cases, i.e. when tuples are constructed, + * processed and then thrown away. + * + * The memory context uses a very simple approach to free space management. + * Instead of a complex global freelist, each block tracks a number + * of allocated and freed chunks. The block is classed as empty when the + * number of free chunks is equal to the number of allocated chunks. When + * this occurs, instead of freeing the block, we try to "recycle" it, i.e. + * reuse it for new allocations. This is done by setting the block in the + * context's 'freeblock' field. If the freeblock field is already occupied + * by another free block we simply return the newly empty block to malloc. + * + * This approach to free blocks requires fewer malloc/free calls for truly + * first allocated, first free'd allocation patterns. + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "lib/ilist.h" +#include "port/pg_bitutils.h" +#include "utils/memdebug.h" +#include "utils/memutils.h" +#include "utils/memutils_memorychunk.h" +#include "utils/memutils_internal.h" + + +#define Generation_BLOCKHDRSZ MAXALIGN(sizeof(GenerationBlock)) +#define Generation_CHUNKHDRSZ sizeof(MemoryChunk) + +#define Generation_CHUNK_FRACTION 8 + +typedef struct GenerationBlock GenerationBlock; /* forward reference */ + +typedef void *GenerationPointer; + +/* + * GenerationContext is a simple memory context not reusing allocated chunks, + * and freeing blocks once all chunks are freed. + */ +typedef struct GenerationContext +{ + MemoryContextData header; /* Standard memory-context fields */ + + /* Generational context parameters */ + Size initBlockSize; /* initial block size */ + Size maxBlockSize; /* maximum block size */ + Size nextBlockSize; /* next block size to allocate */ + Size allocChunkLimit; /* effective chunk size limit */ + + GenerationBlock *block; /* current (most recently allocated) block, or + * NULL if we've just freed the most recent + * block */ + GenerationBlock *freeblock; /* pointer to a block that's being recycled, + * or NULL if there's no such block. */ + GenerationBlock *keeper; /* keep this block over resets */ + dlist_head blocks; /* list of blocks */ +} GenerationContext; + +/* + * GenerationBlock + * GenerationBlock is the unit of memory that is obtained by generation.c + * from malloc(). It contains zero or more MemoryChunks, which are the + * units requested by palloc() and freed by pfree(). MemoryChunks cannot + * be returned to malloc() individually, instead pfree() updates the free + * counter of the block and when all chunks in a block are free the whole + * block can be returned to malloc(). + * + * GenerationBlock is the header data for a block --- the usable space + * within the block begins at the next alignment boundary. + */ +struct GenerationBlock +{ + dlist_node node; /* doubly-linked list of blocks */ + GenerationContext *context; /* pointer back to the owning context */ + Size blksize; /* allocated size of this block */ + int nchunks; /* number of chunks in the block */ + int nfree; /* number of free chunks */ + char *freeptr; /* start of free space in this block */ + char *endptr; /* end of space in this block */ +}; + +/* + * GenerationIsValid + * True iff set is valid generation set. + */ +#define GenerationIsValid(set) \ + (PointerIsValid(set) && IsA(set, GenerationContext)) + +/* + * GenerationBlockIsValid + * True iff block is valid block of generation set. + */ +#define GenerationBlockIsValid(block) \ + (PointerIsValid(block) && GenerationIsValid((block)->context)) + +/* + * We always store external chunks on a dedicated block. This makes fetching + * the block from an external chunk easy since it's always the first and only + * chunk on the block. + */ +#define ExternalChunkGetBlock(chunk) \ + (GenerationBlock *) ((char *) chunk - Generation_BLOCKHDRSZ) + +/* Inlined helper functions */ +static inline void GenerationBlockInit(GenerationContext *context, + GenerationBlock *block, + Size blksize); +static inline bool GenerationBlockIsEmpty(GenerationBlock *block); +static inline void GenerationBlockMarkEmpty(GenerationBlock *block); +static inline Size GenerationBlockFreeBytes(GenerationBlock *block); +static inline void GenerationBlockFree(GenerationContext *set, + GenerationBlock *block); + + +/* + * Public routines + */ + + +/* + * GenerationContextCreate + * Create a new Generation context. + * + * parent: parent context, or NULL if top-level context + * name: name of context (must be statically allocated) + * minContextSize: minimum context size + * initBlockSize: initial allocation block size + * maxBlockSize: maximum allocation block size + */ +MemoryContext +GenerationContextCreate(MemoryContext parent, + const char *name, + Size minContextSize, + Size initBlockSize, + Size maxBlockSize) +{ + Size firstBlockSize; + Size allocSize; + GenerationContext *set; + GenerationBlock *block; + + /* ensure MemoryChunk's size is properly maxaligned */ + StaticAssertDecl(Generation_CHUNKHDRSZ == MAXALIGN(Generation_CHUNKHDRSZ), + "sizeof(MemoryChunk) is not maxaligned"); + + /* + * First, validate allocation parameters. Asserts seem sufficient because + * nobody varies their parameters at runtime. We somewhat arbitrarily + * enforce a minimum 1K block size. We restrict the maximum block size to + * MEMORYCHUNK_MAX_BLOCKOFFSET as MemoryChunks are limited to this in + * regards to addressing the offset between the chunk and the block that + * the chunk is stored on. We would be unable to store the offset between + * the chunk and block for any chunks that were beyond + * MEMORYCHUNK_MAX_BLOCKOFFSET bytes into the block if the block was to be + * larger than this. + */ + Assert(initBlockSize == MAXALIGN(initBlockSize) && + initBlockSize >= 1024); + Assert(maxBlockSize == MAXALIGN(maxBlockSize) && + maxBlockSize >= initBlockSize && + AllocHugeSizeIsValid(maxBlockSize)); /* must be safe to double */ + Assert(minContextSize == 0 || + (minContextSize == MAXALIGN(minContextSize) && + minContextSize >= 1024 && + minContextSize <= maxBlockSize)); + Assert(maxBlockSize <= MEMORYCHUNK_MAX_BLOCKOFFSET); + + /* Determine size of initial block */ + allocSize = MAXALIGN(sizeof(GenerationContext)) + + Generation_BLOCKHDRSZ + Generation_CHUNKHDRSZ; + if (minContextSize != 0) + allocSize = Max(allocSize, minContextSize); + else + allocSize = Max(allocSize, initBlockSize); + + /* + * Allocate the initial block. Unlike other generation.c blocks, it + * starts with the context header and its block header follows that. + */ + set = (GenerationContext *) malloc(allocSize); + if (set == NULL) + { + MemoryContextStats(TopMemoryContext); + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"), + errdetail("Failed while creating memory context \"%s\".", + name))); + } + + /* + * Avoid writing code that can fail between here and MemoryContextCreate; + * we'd leak the header if we ereport in this stretch. + */ + dlist_init(&set->blocks); + + /* Fill in the initial block's block header */ + block = (GenerationBlock *) (((char *) set) + MAXALIGN(sizeof(GenerationContext))); + /* determine the block size and initialize it */ + firstBlockSize = allocSize - MAXALIGN(sizeof(GenerationContext)); + GenerationBlockInit(set, block, firstBlockSize); + + /* add it to the doubly-linked list of blocks */ + dlist_push_head(&set->blocks, &block->node); + + /* use it as the current allocation block */ + set->block = block; + + /* No free block, yet */ + set->freeblock = NULL; + + /* Mark block as not to be released at reset time */ + set->keeper = block; + + /* Fill in GenerationContext-specific header fields */ + set->initBlockSize = initBlockSize; + set->maxBlockSize = maxBlockSize; + set->nextBlockSize = initBlockSize; + + /* + * Compute the allocation chunk size limit for this context. + * + * Limit the maximum size a non-dedicated chunk can be so that we can fit + * at least Generation_CHUNK_FRACTION of chunks this big onto the maximum + * sized block. We must further limit this value so that it's no more + * than MEMORYCHUNK_MAX_VALUE. We're unable to have non-external chunks + * larger than that value as we store the chunk size in the MemoryChunk + * 'value' field in the call to MemoryChunkSetHdrMask(). + */ + set->allocChunkLimit = Min(maxBlockSize, MEMORYCHUNK_MAX_VALUE); + while ((Size) (set->allocChunkLimit + Generation_CHUNKHDRSZ) > + (Size) ((Size) (maxBlockSize - Generation_BLOCKHDRSZ) / Generation_CHUNK_FRACTION)) + set->allocChunkLimit >>= 1; + + /* Finally, do the type-independent part of context creation */ + MemoryContextCreate((MemoryContext) set, + T_GenerationContext, + MCTX_GENERATION_ID, + parent, + name); + + ((MemoryContext) set)->mem_allocated = firstBlockSize; + + return (MemoryContext) set; +} + +/* + * GenerationReset + * Frees all memory which is allocated in the given set. + * + * The code simply frees all the blocks in the context - we don't keep any + * keeper blocks or anything like that. + */ +void +GenerationReset(MemoryContext context) +{ + GenerationContext *set = (GenerationContext *) context; + dlist_mutable_iter miter; + + Assert(GenerationIsValid(set)); + +#ifdef MEMORY_CONTEXT_CHECKING + /* Check for corruption and leaks before freeing */ + GenerationCheck(context); +#endif + + /* + * NULLify the free block pointer. We must do this before calling + * GenerationBlockFree as that function never expects to free the + * freeblock. + */ + set->freeblock = NULL; + + dlist_foreach_modify(miter, &set->blocks) + { + GenerationBlock *block = dlist_container(GenerationBlock, node, miter.cur); + + if (block == set->keeper) + GenerationBlockMarkEmpty(block); + else + GenerationBlockFree(set, block); + } + + /* set it so new allocations to make use of the keeper block */ + set->block = set->keeper; + + /* Reset block size allocation sequence, too */ + set->nextBlockSize = set->initBlockSize; + + /* Ensure there is only 1 item in the dlist */ + Assert(!dlist_is_empty(&set->blocks)); + Assert(!dlist_has_next(&set->blocks, dlist_head_node(&set->blocks))); +} + +/* + * GenerationDelete + * Free all memory which is allocated in the given context. + */ +void +GenerationDelete(MemoryContext context) +{ + /* Reset to release all releasable GenerationBlocks */ + GenerationReset(context); + /* And free the context header and keeper block */ + free(context); +} + +/* + * GenerationAlloc + * Returns pointer to allocated memory of given size or NULL if + * request could not be completed; memory is added to the set. + * + * No request may exceed: + * MAXALIGN_DOWN(SIZE_MAX) - Generation_BLOCKHDRSZ - Generation_CHUNKHDRSZ + * All callers use a much-lower limit. + * + * Note: when using valgrind, it doesn't matter how the returned allocation + * is marked, as mcxt.c will set it to UNDEFINED. In some paths we will + * return space that is marked NOACCESS - GenerationRealloc has to beware! + */ +void * +GenerationAlloc(MemoryContext context, Size size) +{ + GenerationContext *set = (GenerationContext *) context; + GenerationBlock *block; + MemoryChunk *chunk; + Size chunk_size; + Size required_size; + + Assert(GenerationIsValid(set)); + +#ifdef MEMORY_CONTEXT_CHECKING + /* ensure there's always space for the sentinel byte */ + chunk_size = MAXALIGN(size + 1); +#else + chunk_size = MAXALIGN(size); +#endif + required_size = chunk_size + Generation_CHUNKHDRSZ; + + /* is it an over-sized chunk? if yes, allocate special block */ + if (chunk_size > set->allocChunkLimit) + { + Size blksize = required_size + Generation_BLOCKHDRSZ; + + block = (GenerationBlock *) malloc(blksize); + if (block == NULL) + return NULL; + + context->mem_allocated += blksize; + + /* block with a single (used) chunk */ + block->context = set; + block->blksize = blksize; + block->nchunks = 1; + block->nfree = 0; + + /* the block is completely full */ + block->freeptr = block->endptr = ((char *) block) + blksize; + + chunk = (MemoryChunk *) (((char *) block) + Generation_BLOCKHDRSZ); + + /* mark the MemoryChunk as externally managed */ + MemoryChunkSetHdrMaskExternal(chunk, MCTX_GENERATION_ID); + +#ifdef MEMORY_CONTEXT_CHECKING + chunk->requested_size = size; + /* set mark to catch clobber of "unused" space */ + Assert(size < chunk_size); + set_sentinel(MemoryChunkGetPointer(chunk), size); +#endif +#ifdef RANDOMIZE_ALLOCATED_MEMORY + /* fill the allocated space with junk */ + randomize_mem((char *) MemoryChunkGetPointer(chunk), size); +#endif + + /* add the block to the list of allocated blocks */ + dlist_push_head(&set->blocks, &block->node); + + /* Ensure any padding bytes are marked NOACCESS. */ + VALGRIND_MAKE_MEM_NOACCESS((char *) MemoryChunkGetPointer(chunk) + size, + chunk_size - size); + + /* Disallow access to the chunk header. */ + VALGRIND_MAKE_MEM_NOACCESS(chunk, Generation_CHUNKHDRSZ); + + return MemoryChunkGetPointer(chunk); + } + + /* + * Not an oversized chunk. We try to first make use of the current block, + * but if there's not enough space in it, instead of allocating a new + * block, we look to see if the freeblock is empty and has enough space. + * If not, we'll also try the same using the keeper block. The keeper + * block may have become empty and we have no other way to reuse it again + * if we don't try to use it explicitly here. + * + * We don't want to start filling the freeblock before the current block + * is full, otherwise we may cause fragmentation in FIFO type workloads. + * We only switch to using the freeblock or keeper block if those blocks + * are completely empty. If we didn't do that we could end up fragmenting + * consecutive allocations over multiple blocks which would be a problem + * that would compound over time. + */ + block = set->block; + + if (block == NULL || + GenerationBlockFreeBytes(block) < required_size) + { + Size blksize; + GenerationBlock *freeblock = set->freeblock; + + if (freeblock != NULL && + GenerationBlockIsEmpty(freeblock) && + GenerationBlockFreeBytes(freeblock) >= required_size) + { + block = freeblock; + + /* + * Zero out the freeblock as we'll set this to the current block + * below + */ + set->freeblock = NULL; + } + else if (GenerationBlockIsEmpty(set->keeper) && + GenerationBlockFreeBytes(set->keeper) >= required_size) + { + block = set->keeper; + } + else + { + /* + * The first such block has size initBlockSize, and we double the + * space in each succeeding block, but not more than maxBlockSize. + */ + blksize = set->nextBlockSize; + set->nextBlockSize <<= 1; + if (set->nextBlockSize > set->maxBlockSize) + set->nextBlockSize = set->maxBlockSize; + + /* we'll need a block hdr too, so add that to the required size */ + required_size += Generation_BLOCKHDRSZ; + + /* round the size up to the next power of 2 */ + if (blksize < required_size) + blksize = pg_nextpower2_size_t(required_size); + + block = (GenerationBlock *) malloc(blksize); + + if (block == NULL) + return NULL; + + context->mem_allocated += blksize; + + /* initialize the new block */ + GenerationBlockInit(set, block, blksize); + + /* add it to the doubly-linked list of blocks */ + dlist_push_head(&set->blocks, &block->node); + + /* Zero out the freeblock in case it's become full */ + set->freeblock = NULL; + } + + /* and also use it as the current allocation block */ + set->block = block; + } + + /* we're supposed to have a block with enough free space now */ + Assert(block != NULL); + Assert((block->endptr - block->freeptr) >= Generation_CHUNKHDRSZ + chunk_size); + + chunk = (MemoryChunk *) block->freeptr; + + /* Prepare to initialize the chunk header. */ + VALGRIND_MAKE_MEM_UNDEFINED(chunk, Generation_CHUNKHDRSZ); + + block->nchunks += 1; + block->freeptr += (Generation_CHUNKHDRSZ + chunk_size); + + Assert(block->freeptr <= block->endptr); + + MemoryChunkSetHdrMask(chunk, block, chunk_size, MCTX_GENERATION_ID); +#ifdef MEMORY_CONTEXT_CHECKING + chunk->requested_size = size; + /* set mark to catch clobber of "unused" space */ + Assert(size < chunk_size); + set_sentinel(MemoryChunkGetPointer(chunk), size); +#endif +#ifdef RANDOMIZE_ALLOCATED_MEMORY + /* fill the allocated space with junk */ + randomize_mem((char *) MemoryChunkGetPointer(chunk), size); +#endif + + /* Ensure any padding bytes are marked NOACCESS. */ + VALGRIND_MAKE_MEM_NOACCESS((char *) MemoryChunkGetPointer(chunk) + size, + chunk_size - size); + + /* Disallow access to the chunk header. */ + VALGRIND_MAKE_MEM_NOACCESS(chunk, Generation_CHUNKHDRSZ); + + return MemoryChunkGetPointer(chunk); +} + +/* + * GenerationBlockInit + * Initializes 'block' assuming 'blksize'. Does not update the context's + * mem_allocated field. + */ +static inline void +GenerationBlockInit(GenerationContext *context, GenerationBlock *block, + Size blksize) +{ + block->context = context; + block->blksize = blksize; + block->nchunks = 0; + block->nfree = 0; + + block->freeptr = ((char *) block) + Generation_BLOCKHDRSZ; + block->endptr = ((char *) block) + blksize; + + /* Mark unallocated space NOACCESS. */ + VALGRIND_MAKE_MEM_NOACCESS(block->freeptr, + blksize - Generation_BLOCKHDRSZ); +} + +/* + * GenerationBlockIsEmpty + * Returns true iff 'block' contains no chunks + */ +static inline bool +GenerationBlockIsEmpty(GenerationBlock *block) +{ + return (block->nchunks == 0); +} + +/* + * GenerationBlockMarkEmpty + * Set a block as empty. Does not free the block. + */ +static inline void +GenerationBlockMarkEmpty(GenerationBlock *block) +{ +#if defined(USE_VALGRIND) || defined(CLOBBER_FREED_MEMORY) + char *datastart = ((char *) block) + Generation_BLOCKHDRSZ; +#endif + +#ifdef CLOBBER_FREED_MEMORY + wipe_mem(datastart, block->freeptr - datastart); +#else + /* wipe_mem() would have done this */ + VALGRIND_MAKE_MEM_NOACCESS(datastart, block->freeptr - datastart); +#endif + + /* Reset the block, but don't return it to malloc */ + block->nchunks = 0; + block->nfree = 0; + block->freeptr = ((char *) block) + Generation_BLOCKHDRSZ; +} + +/* + * GenerationBlockFreeBytes + * Returns the number of bytes free in 'block' + */ +static inline Size +GenerationBlockFreeBytes(GenerationBlock *block) +{ + return (block->endptr - block->freeptr); +} + +/* + * GenerationBlockFree + * Remove 'block' from 'set' and release the memory consumed by it. + */ +static inline void +GenerationBlockFree(GenerationContext *set, GenerationBlock *block) +{ + /* Make sure nobody tries to free the keeper block */ + Assert(block != set->keeper); + /* We shouldn't be freeing the freeblock either */ + Assert(block != set->freeblock); + + /* release the block from the list of blocks */ + dlist_delete(&block->node); + + ((MemoryContext) set)->mem_allocated -= block->blksize; + +#ifdef CLOBBER_FREED_MEMORY + wipe_mem(block, block->blksize); +#endif + + free(block); +} + +/* + * GenerationFree + * Update number of chunks in the block, and if all chunks in the block + * are now free then discard the block. + */ +void +GenerationFree(void *pointer) +{ + MemoryChunk *chunk = PointerGetMemoryChunk(pointer); + GenerationBlock *block; + GenerationContext *set; +#if (defined(MEMORY_CONTEXT_CHECKING) && defined(USE_ASSERT_CHECKING)) \ + || defined(CLOBBER_FREED_MEMORY) + Size chunksize; +#endif + + /* Allow access to the chunk header. */ + VALGRIND_MAKE_MEM_DEFINED(chunk, Generation_CHUNKHDRSZ); + + if (MemoryChunkIsExternal(chunk)) + { + block = ExternalChunkGetBlock(chunk); + + /* + * Try to verify that we have a sane block pointer: the block header + * should reference a generation context. + */ + if (!GenerationBlockIsValid(block)) + elog(ERROR, "could not find block containing chunk %p", chunk); + +#if (defined(MEMORY_CONTEXT_CHECKING) && defined(USE_ASSERT_CHECKING)) \ + || defined(CLOBBER_FREED_MEMORY) + chunksize = block->endptr - (char *) pointer; +#endif + } + else + { + block = MemoryChunkGetBlock(chunk); + + /* + * In this path, for speed reasons we just Assert that the referenced + * block is good. Future field experience may show that this Assert + * had better become a regular runtime test-and-elog check. + */ + Assert(GenerationBlockIsValid(block)); + +#if (defined(MEMORY_CONTEXT_CHECKING) && defined(USE_ASSERT_CHECKING)) \ + || defined(CLOBBER_FREED_MEMORY) + chunksize = MemoryChunkGetValue(chunk); +#endif + } + +#ifdef MEMORY_CONTEXT_CHECKING + /* Test for someone scribbling on unused space in chunk */ + Assert(chunk->requested_size < chunksize); + if (!sentinel_ok(pointer, chunk->requested_size)) + elog(WARNING, "detected write past chunk end in %s %p", + ((MemoryContext) block->context)->name, chunk); +#endif + +#ifdef CLOBBER_FREED_MEMORY + wipe_mem(pointer, chunksize); +#endif + +#ifdef MEMORY_CONTEXT_CHECKING + /* Reset requested_size to InvalidAllocSize in freed chunks */ + chunk->requested_size = InvalidAllocSize; +#endif + + block->nfree += 1; + + Assert(block->nchunks > 0); + Assert(block->nfree <= block->nchunks); + + /* If there are still allocated chunks in the block, we're done. */ + if (block->nfree < block->nchunks) + return; + + set = block->context; + + /* Don't try to free the keeper block, just mark it empty */ + if (block == set->keeper) + { + GenerationBlockMarkEmpty(block); + return; + } + + /* + * If there is no freeblock set or if this is the freeblock then instead + * of freeing this memory, we keep it around so that new allocations have + * the option of recycling it. + */ + if (set->freeblock == NULL || set->freeblock == block) + { + /* XXX should we only recycle maxBlockSize sized blocks? */ + set->freeblock = block; + GenerationBlockMarkEmpty(block); + return; + } + + /* Also make sure the block is not marked as the current block. */ + if (set->block == block) + set->block = NULL; + + /* + * The block is empty, so let's get rid of it. First remove it from the + * list of blocks, then return it to malloc(). + */ + dlist_delete(&block->node); + + set->header.mem_allocated -= block->blksize; + free(block); +} + +/* + * GenerationRealloc + * When handling repalloc, we simply allocate a new chunk, copy the data + * and discard the old one. The only exception is when the new size fits + * into the old chunk - in that case we just update chunk header. + */ +void * +GenerationRealloc(void *pointer, Size size) +{ + MemoryChunk *chunk = PointerGetMemoryChunk(pointer); + GenerationContext *set; + GenerationBlock *block; + GenerationPointer newPointer; + Size oldsize; + + /* Allow access to the chunk header. */ + VALGRIND_MAKE_MEM_DEFINED(chunk, Generation_CHUNKHDRSZ); + + if (MemoryChunkIsExternal(chunk)) + { + block = ExternalChunkGetBlock(chunk); + + /* + * Try to verify that we have a sane block pointer: the block header + * should reference a generation context. + */ + if (!GenerationBlockIsValid(block)) + elog(ERROR, "could not find block containing chunk %p", chunk); + + oldsize = block->endptr - (char *) pointer; + } + else + { + block = MemoryChunkGetBlock(chunk); + + /* + * In this path, for speed reasons we just Assert that the referenced + * block is good. Future field experience may show that this Assert + * had better become a regular runtime test-and-elog check. + */ + Assert(GenerationBlockIsValid(block)); + + oldsize = MemoryChunkGetValue(chunk); + } + + set = block->context; + +#ifdef MEMORY_CONTEXT_CHECKING + /* Test for someone scribbling on unused space in chunk */ + Assert(chunk->requested_size < oldsize); + if (!sentinel_ok(pointer, chunk->requested_size)) + elog(WARNING, "detected write past chunk end in %s %p", + ((MemoryContext) set)->name, chunk); +#endif + + /* + * Maybe the allocated area already is >= the new size. (In particular, + * we always fall out here if the requested size is a decrease.) + * + * This memory context does not use power-of-2 chunk sizing and instead + * carves the chunks to be as small as possible, so most repalloc() calls + * will end up in the palloc/memcpy/pfree branch. + * + * XXX Perhaps we should annotate this condition with unlikely()? + */ + if (oldsize >= size) + { +#ifdef MEMORY_CONTEXT_CHECKING + Size oldrequest = chunk->requested_size; + +#ifdef RANDOMIZE_ALLOCATED_MEMORY + /* We can only fill the extra space if we know the prior request */ + if (size > oldrequest) + randomize_mem((char *) pointer + oldrequest, + size - oldrequest); +#endif + + chunk->requested_size = size; + + /* + * If this is an increase, mark any newly-available part UNDEFINED. + * Otherwise, mark the obsolete part NOACCESS. + */ + if (size > oldrequest) + VALGRIND_MAKE_MEM_UNDEFINED((char *) pointer + oldrequest, + size - oldrequest); + else + VALGRIND_MAKE_MEM_NOACCESS((char *) pointer + size, + oldsize - size); + + /* set mark to catch clobber of "unused" space */ + set_sentinel(pointer, size); +#else /* !MEMORY_CONTEXT_CHECKING */ + + /* + * We don't have the information to determine whether we're growing + * the old request or shrinking it, so we conservatively mark the + * entire new allocation DEFINED. + */ + VALGRIND_MAKE_MEM_NOACCESS(pointer, oldsize); + VALGRIND_MAKE_MEM_DEFINED(pointer, size); +#endif + + /* Disallow access to the chunk header. */ + VALGRIND_MAKE_MEM_NOACCESS(chunk, Generation_CHUNKHDRSZ); + + return pointer; + } + + /* allocate new chunk */ + newPointer = GenerationAlloc((MemoryContext) set, size); + + /* leave immediately if request was not completed */ + if (newPointer == NULL) + { + /* Disallow access to the chunk header. */ + VALGRIND_MAKE_MEM_NOACCESS(chunk, Generation_CHUNKHDRSZ); + return NULL; + } + + /* + * GenerationAlloc() may have returned a region that is still NOACCESS. + * Change it to UNDEFINED for the moment; memcpy() will then transfer + * definedness from the old allocation to the new. If we know the old + * allocation, copy just that much. Otherwise, make the entire old chunk + * defined to avoid errors as we copy the currently-NOACCESS trailing + * bytes. + */ + VALGRIND_MAKE_MEM_UNDEFINED(newPointer, size); +#ifdef MEMORY_CONTEXT_CHECKING + oldsize = chunk->requested_size; +#else + VALGRIND_MAKE_MEM_DEFINED(pointer, oldsize); +#endif + + /* transfer existing data (certain to fit) */ + memcpy(newPointer, pointer, oldsize); + + /* free old chunk */ + GenerationFree(pointer); + + return newPointer; +} + +/* + * GenerationGetChunkContext + * Return the MemoryContext that 'pointer' belongs to. + */ +MemoryContext +GenerationGetChunkContext(void *pointer) +{ + MemoryChunk *chunk = PointerGetMemoryChunk(pointer); + GenerationBlock *block; + + /* Allow access to the chunk header. */ + VALGRIND_MAKE_MEM_DEFINED(chunk, Generation_CHUNKHDRSZ); + + if (MemoryChunkIsExternal(chunk)) + block = ExternalChunkGetBlock(chunk); + else + block = (GenerationBlock *) MemoryChunkGetBlock(chunk); + + /* Disallow access to the chunk header. */ + VALGRIND_MAKE_MEM_NOACCESS(chunk, Generation_CHUNKHDRSZ); + + Assert(GenerationBlockIsValid(block)); + return &block->context->header; +} + +/* + * GenerationGetChunkSpace + * Given a currently-allocated chunk, determine the total space + * it occupies (including all memory-allocation overhead). + */ +Size +GenerationGetChunkSpace(void *pointer) +{ + MemoryChunk *chunk = PointerGetMemoryChunk(pointer); + Size chunksize; + + /* Allow access to the chunk header. */ + VALGRIND_MAKE_MEM_DEFINED(chunk, Generation_CHUNKHDRSZ); + + if (MemoryChunkIsExternal(chunk)) + { + GenerationBlock *block = ExternalChunkGetBlock(chunk); + + Assert(GenerationBlockIsValid(block)); + chunksize = block->endptr - (char *) pointer; + } + else + chunksize = MemoryChunkGetValue(chunk); + + /* Disallow access to the chunk header. */ + VALGRIND_MAKE_MEM_NOACCESS(chunk, Generation_CHUNKHDRSZ); + + return Generation_CHUNKHDRSZ + chunksize; +} + +/* + * GenerationIsEmpty + * Is a GenerationContext empty of any allocated space? + */ +bool +GenerationIsEmpty(MemoryContext context) +{ + GenerationContext *set = (GenerationContext *) context; + dlist_iter iter; + + Assert(GenerationIsValid(set)); + + dlist_foreach(iter, &set->blocks) + { + GenerationBlock *block = dlist_container(GenerationBlock, node, iter.cur); + + if (block->nchunks > 0) + return false; + } + + return true; +} + +/* + * GenerationStats + * Compute stats about memory consumption of a Generation context. + * + * printfunc: if not NULL, pass a human-readable stats string to this. + * passthru: pass this pointer through to printfunc. + * totals: if not NULL, add stats about this context into *totals. + * print_to_stderr: print stats to stderr if true, elog otherwise. + * + * XXX freespace only accounts for empty space at the end of the block, not + * space of freed chunks (which is unknown). + */ +void +GenerationStats(MemoryContext context, + MemoryStatsPrintFunc printfunc, void *passthru, + MemoryContextCounters *totals, bool print_to_stderr) +{ + GenerationContext *set = (GenerationContext *) context; + Size nblocks = 0; + Size nchunks = 0; + Size nfreechunks = 0; + Size totalspace; + Size freespace = 0; + dlist_iter iter; + + Assert(GenerationIsValid(set)); + + /* Include context header in totalspace */ + totalspace = MAXALIGN(sizeof(GenerationContext)); + + dlist_foreach(iter, &set->blocks) + { + GenerationBlock *block = dlist_container(GenerationBlock, node, iter.cur); + + nblocks++; + nchunks += block->nchunks; + nfreechunks += block->nfree; + totalspace += block->blksize; + freespace += (block->endptr - block->freeptr); + } + + if (printfunc) + { + char stats_string[200]; + + snprintf(stats_string, sizeof(stats_string), + "%zu total in %zu blocks (%zu chunks); %zu free (%zu chunks); %zu used", + totalspace, nblocks, nchunks, freespace, + nfreechunks, totalspace - freespace); + printfunc(context, passthru, stats_string, print_to_stderr); + } + + if (totals) + { + totals->nblocks += nblocks; + totals->freechunks += nfreechunks; + totals->totalspace += totalspace; + totals->freespace += freespace; + } +} + + +#ifdef MEMORY_CONTEXT_CHECKING + +/* + * GenerationCheck + * Walk through chunks and check consistency of memory. + * + * NOTE: report errors as WARNING, *not* ERROR or FATAL. Otherwise you'll + * find yourself in an infinite loop when trouble occurs, because this + * routine will be entered again when elog cleanup tries to release memory! + */ +void +GenerationCheck(MemoryContext context) +{ + GenerationContext *gen = (GenerationContext *) context; + const char *name = context->name; + dlist_iter iter; + Size total_allocated = 0; + + /* walk all blocks in this context */ + dlist_foreach(iter, &gen->blocks) + { + GenerationBlock *block = dlist_container(GenerationBlock, node, iter.cur); + int nfree, + nchunks; + char *ptr; + bool has_external_chunk = false; + + total_allocated += block->blksize; + + /* + * nfree > nchunks is surely wrong. Equality is allowed as the block + * might completely empty if it's the freeblock. + */ + if (block->nfree > block->nchunks) + elog(WARNING, "problem in Generation %s: number of free chunks %d in block %p exceeds %d allocated", + name, block->nfree, block, block->nchunks); + + /* check block belongs to the correct context */ + if (block->context != gen) + elog(WARNING, "problem in Generation %s: bogus context link in block %p", + name, block); + + /* Now walk through the chunks and count them. */ + nfree = 0; + nchunks = 0; + ptr = ((char *) block) + Generation_BLOCKHDRSZ; + + while (ptr < block->freeptr) + { + MemoryChunk *chunk = (MemoryChunk *) ptr; + GenerationBlock *chunkblock; + Size chunksize; + + /* Allow access to the chunk header. */ + VALGRIND_MAKE_MEM_DEFINED(chunk, Generation_CHUNKHDRSZ); + + if (MemoryChunkIsExternal(chunk)) + { + chunkblock = ExternalChunkGetBlock(chunk); + chunksize = block->endptr - (char *) MemoryChunkGetPointer(chunk); + has_external_chunk = true; + } + else + { + chunkblock = MemoryChunkGetBlock(chunk); + chunksize = MemoryChunkGetValue(chunk); + } + + /* move to the next chunk */ + ptr += (chunksize + Generation_CHUNKHDRSZ); + + nchunks += 1; + + /* chunks have both block and context pointers, so check both */ + if (chunkblock != block) + elog(WARNING, "problem in Generation %s: bogus block link in block %p, chunk %p", + name, block, chunk); + + + /* is chunk allocated? */ + if (chunk->requested_size != InvalidAllocSize) + { + /* now make sure the chunk size is correct */ + if (chunksize < chunk->requested_size || + chunksize != MAXALIGN(chunksize)) + elog(WARNING, "problem in Generation %s: bogus chunk size in block %p, chunk %p", + name, block, chunk); + + /* check sentinel */ + Assert(chunk->requested_size < chunksize); + if (!sentinel_ok(chunk, Generation_CHUNKHDRSZ + chunk->requested_size)) + elog(WARNING, "problem in Generation %s: detected write past chunk end in block %p, chunk %p", + name, block, chunk); + } + else + nfree += 1; + + /* if chunk is allocated, disallow access to the chunk header */ + if (chunk->requested_size != InvalidAllocSize) + VALGRIND_MAKE_MEM_NOACCESS(chunk, Generation_CHUNKHDRSZ); + } + + /* + * Make sure we got the expected number of allocated and free chunks + * (as tracked in the block header). + */ + if (nchunks != block->nchunks) + elog(WARNING, "problem in Generation %s: number of allocated chunks %d in block %p does not match header %d", + name, nchunks, block, block->nchunks); + + if (nfree != block->nfree) + elog(WARNING, "problem in Generation %s: number of free chunks %d in block %p does not match header %d", + name, nfree, block, block->nfree); + + if (has_external_chunk && nchunks > 1) + elog(WARNING, "problem in Generation %s: external chunk on non-dedicated block %p", + name, block); + + } + + Assert(total_allocated == context->mem_allocated); +} + +#endif /* MEMORY_CONTEXT_CHECKING */ diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/mmgr/mcxt.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/mmgr/mcxt.c new file mode 100644 index 00000000000..beabfec00f4 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/mmgr/mcxt.c @@ -0,0 +1,1732 @@ +/*------------------------------------------------------------------------- + * + * mcxt.c + * POSTGRES memory context management code. + * + * This module handles context management operations that are independent + * of the particular kind of context being operated on. It calls + * context-type-specific operations via the function pointers in a + * context's MemoryContextMethods struct. + * + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/mmgr/mcxt.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "mb/pg_wchar.h" +#include "miscadmin.h" +#include "storage/proc.h" +#include "storage/procarray.h" +#include "storage/procsignal.h" +#include "utils/fmgrprotos.h" +#include "utils/memdebug.h" +#include "utils/memutils.h" +#include "utils/memutils_internal.h" +#include "utils/memutils_memorychunk.h" + + +static void BogusFree(void *pointer); +static void *BogusRealloc(void *pointer, Size size); +static MemoryContext BogusGetChunkContext(void *pointer); +static Size BogusGetChunkSpace(void *pointer); + +extern void *ArenaAlloc(MemoryContext context, Size size); +extern void ArenaFree(void *pointer); +extern void *ArenaRealloc(void *pointer, Size size); +extern void ArenaReset(MemoryContext context); +extern void ArenaDelete(MemoryContext context); +extern MemoryContext ArenaGetChunkContext(void *pointer); +extern Size ArenaGetChunkSpace(void *pointer); +extern bool ArenaIsEmpty(MemoryContext context); +extern void ArenaStats(MemoryContext context, + MemoryStatsPrintFunc printfunc, void *passthru, + MemoryContextCounters *totals, + bool print_to_stderr); +#ifdef MEMORY_CONTEXT_CHECKING +extern void ArenaCheck(MemoryContext context); +#endif + +extern void *MkqlAlloc(MemoryContext context, Size size); +extern void MkqlFree(void *pointer); +extern void *MkqlRealloc(void *pointer, Size size); +extern void MkqlReset(MemoryContext context); +extern void MkqlDelete(MemoryContext context); +extern MemoryContext MkqlGetChunkContext(void *pointer); +extern Size MkqlGetChunkSpace(void *pointer); +extern bool MkqlIsEmpty(MemoryContext context); +extern void MkqlStats(MemoryContext context, + MemoryStatsPrintFunc printfunc, void *passthru, + MemoryContextCounters *totals, + bool print_to_stderr); +#ifdef MEMORY_CONTEXT_CHECKING +extern void MkqlCheck(MemoryContext context); +#endif + +/***************************************************************************** + * GLOBAL MEMORY * + *****************************************************************************/ + +static const MemoryContextMethods mcxt_methods[] = { + /* aset.c */ + [MCTX_ASET_ID].alloc = AllocSetAlloc, + [MCTX_ASET_ID].free_p = AllocSetFree, + [MCTX_ASET_ID].realloc = AllocSetRealloc, + [MCTX_ASET_ID].reset = AllocSetReset, + [MCTX_ASET_ID].delete_context = AllocSetDelete, + [MCTX_ASET_ID].get_chunk_context = AllocSetGetChunkContext, + [MCTX_ASET_ID].get_chunk_space = AllocSetGetChunkSpace, + [MCTX_ASET_ID].is_empty = AllocSetIsEmpty, + [MCTX_ASET_ID].stats = AllocSetStats, +#ifdef MEMORY_CONTEXT_CHECKING + [MCTX_ASET_ID].check = AllocSetCheck, +#endif + + /* generation.c */ + [MCTX_GENERATION_ID].alloc = GenerationAlloc, + [MCTX_GENERATION_ID].free_p = GenerationFree, + [MCTX_GENERATION_ID].realloc = GenerationRealloc, + [MCTX_GENERATION_ID].reset = GenerationReset, + [MCTX_GENERATION_ID].delete_context = GenerationDelete, + [MCTX_GENERATION_ID].get_chunk_context = GenerationGetChunkContext, + [MCTX_GENERATION_ID].get_chunk_space = GenerationGetChunkSpace, + [MCTX_GENERATION_ID].is_empty = GenerationIsEmpty, + [MCTX_GENERATION_ID].stats = GenerationStats, +#ifdef MEMORY_CONTEXT_CHECKING + [MCTX_GENERATION_ID].check = GenerationCheck, +#endif + + /* slab.c */ + [MCTX_SLAB_ID].alloc = SlabAlloc, + [MCTX_SLAB_ID].free_p = SlabFree, + [MCTX_SLAB_ID].realloc = SlabRealloc, + [MCTX_SLAB_ID].reset = SlabReset, + [MCTX_SLAB_ID].delete_context = SlabDelete, + [MCTX_SLAB_ID].get_chunk_context = SlabGetChunkContext, + [MCTX_SLAB_ID].get_chunk_space = SlabGetChunkSpace, + [MCTX_SLAB_ID].is_empty = SlabIsEmpty, + [MCTX_SLAB_ID].stats = SlabStats, +#ifdef MEMORY_CONTEXT_CHECKING + [MCTX_SLAB_ID].check = SlabCheck, +#endif + + /* alignedalloc.c */ + [MCTX_ALIGNED_REDIRECT_ID].alloc = NULL, /* not required */ + [MCTX_ALIGNED_REDIRECT_ID].free_p = AlignedAllocFree, + [MCTX_ALIGNED_REDIRECT_ID].realloc = AlignedAllocRealloc, + [MCTX_ALIGNED_REDIRECT_ID].reset = NULL, /* not required */ + [MCTX_ALIGNED_REDIRECT_ID].delete_context = NULL, /* not required */ + [MCTX_ALIGNED_REDIRECT_ID].get_chunk_context = AlignedAllocGetChunkContext, + [MCTX_ALIGNED_REDIRECT_ID].get_chunk_space = AlignedAllocGetChunkSpace, + [MCTX_ALIGNED_REDIRECT_ID].is_empty = NULL, /* not required */ + [MCTX_ALIGNED_REDIRECT_ID].stats = NULL, /* not required */ +#ifdef MEMORY_CONTEXT_CHECKING + [MCTX_ALIGNED_REDIRECT_ID].check = NULL, /* not required */ +#endif + + + /* + * Unused (as yet) IDs should have dummy entries here. This allows us to + * fail cleanly if a bogus pointer is passed to pfree or the like. It + * seems sufficient to provide routines for the methods that might get + * invoked from inspection of a chunk (see MCXT_METHOD calls below). + */ + + [MCTX_UNUSED1_ID].free_p = BogusFree, + [MCTX_UNUSED1_ID].realloc = BogusRealloc, + [MCTX_UNUSED1_ID].get_chunk_context = BogusGetChunkContext, + [MCTX_UNUSED1_ID].get_chunk_space = BogusGetChunkSpace, + + /* Arena based allocator */ + [MCTX_UNUSED2_ID].alloc = ArenaAlloc, + [MCTX_UNUSED2_ID].free_p = ArenaFree, + [MCTX_UNUSED2_ID].realloc = ArenaRealloc, + [MCTX_UNUSED2_ID].reset = ArenaReset, + [MCTX_UNUSED2_ID].delete_context = ArenaDelete, + [MCTX_UNUSED2_ID].get_chunk_context = ArenaGetChunkContext, + [MCTX_UNUSED2_ID].get_chunk_space = ArenaGetChunkSpace, + [MCTX_UNUSED2_ID].is_empty = ArenaIsEmpty, + [MCTX_UNUSED2_ID].stats = ArenaStats, +#ifdef MEMORY_CONTEXT_CHECKING + [MCTX_UNUSED2_ID].check = ArenaCheck, +#endif + + /* MKQL based allocator */ + [MCTX_UNUSED3_ID].alloc = MkqlAlloc, + [MCTX_UNUSED3_ID].free_p = MkqlFree, + [MCTX_UNUSED3_ID].realloc = MkqlRealloc, + [MCTX_UNUSED3_ID].reset = MkqlReset, + [MCTX_UNUSED3_ID].delete_context = MkqlDelete, + [MCTX_UNUSED3_ID].get_chunk_context = MkqlGetChunkContext, + [MCTX_UNUSED3_ID].get_chunk_space = MkqlGetChunkSpace, + [MCTX_UNUSED3_ID].is_empty = MkqlIsEmpty, + [MCTX_UNUSED3_ID].stats = MkqlStats, +#ifdef MEMORY_CONTEXT_CHECKING + [MCTX_UNUSED3_ID].check = MkqlCheck, +#endif + + [MCTX_UNUSED4_ID].free_p = BogusFree, + [MCTX_UNUSED4_ID].realloc = BogusRealloc, + [MCTX_UNUSED4_ID].get_chunk_context = BogusGetChunkContext, + [MCTX_UNUSED4_ID].get_chunk_space = BogusGetChunkSpace, +}; + +/* + * CurrentMemoryContext + * Default memory context for allocations. + */ +__thread MemoryContext CurrentMemoryContext = NULL; +MemoryContext* ImplPtrCurrentMemoryContext() { return &CurrentMemoryContext; } + +/* + * Standard top-level contexts. For a description of the purpose of each + * of these contexts, refer to src/backend/utils/mmgr/README + */ +__thread MemoryContext TopMemoryContext = NULL; +__thread MemoryContext ErrorContext = NULL; +__thread MemoryContext PostmasterContext = NULL; +__thread MemoryContext CacheMemoryContext = NULL; +MemoryContext* ImplPtrCacheMemoryContext() { return &CacheMemoryContext; } +__thread MemoryContext MessageContext = NULL; +__thread MemoryContext TopTransactionContext = NULL; +__thread MemoryContext CurTransactionContext = NULL; + +/* This is a transient link to the active portal's memory context: */ +__thread MemoryContext PortalContext = NULL; + +static void MemoryContextCallResetCallbacks(MemoryContext context); +static void MemoryContextStatsInternal(MemoryContext context, int level, + bool print, int max_children, + MemoryContextCounters *totals, + bool print_to_stderr); +static void MemoryContextStatsPrint(MemoryContext context, void *passthru, + const char *stats_string, + bool print_to_stderr); + +/* + * You should not do memory allocations within a critical section, because + * an out-of-memory error will be escalated to a PANIC. To enforce that + * rule, the allocation functions Assert that. + */ +#define AssertNotInCriticalSection(context) \ + Assert(CritSectionCount == 0 || (context)->allowInCritSection) + +/* + * Call the given function in the MemoryContextMethods for the memory context + * type that 'pointer' belongs to. + */ +#define MCXT_METHOD(pointer, method) \ + mcxt_methods[GetMemoryChunkMethodID(pointer)].method + +/* + * GetMemoryChunkMethodID + * Return the MemoryContextMethodID from the uint64 chunk header which + * directly precedes 'pointer'. + */ +static inline MemoryContextMethodID +GetMemoryChunkMethodID(const void *pointer) +{ + uint64 header; + + /* + * Try to detect bogus pointers handed to us, poorly though we can. + * Presumably, a pointer that isn't MAXALIGNED isn't pointing at an + * allocated chunk. + */ + Assert(pointer == (const void *) MAXALIGN(pointer)); + + /* Allow access to the uint64 header */ + VALGRIND_MAKE_MEM_DEFINED((char *) pointer - sizeof(uint64), sizeof(uint64)); + + header = *((const uint64 *) ((const char *) pointer - sizeof(uint64))); + + /* Disallow access to the uint64 header */ + VALGRIND_MAKE_MEM_NOACCESS((char *) pointer - sizeof(uint64), sizeof(uint64)); + + return (MemoryContextMethodID) (header & MEMORY_CONTEXT_METHODID_MASK); +} + +/* + * GetMemoryChunkHeader + * Return the uint64 chunk header which directly precedes 'pointer'. + * + * This is only used after GetMemoryChunkMethodID, so no need for error checks. + */ +static inline uint64 +GetMemoryChunkHeader(const void *pointer) +{ + uint64 header; + + /* Allow access to the uint64 header */ + VALGRIND_MAKE_MEM_DEFINED((char *) pointer - sizeof(uint64), sizeof(uint64)); + + header = *((const uint64 *) ((const char *) pointer - sizeof(uint64))); + + /* Disallow access to the uint64 header */ + VALGRIND_MAKE_MEM_NOACCESS((char *) pointer - sizeof(uint64), sizeof(uint64)); + + return header; +} + +/* + * Support routines to trap use of invalid memory context method IDs + * (from calling pfree or the like on a bogus pointer). As a possible + * aid in debugging, we report the header word along with the pointer + * address (if we got here, there must be an accessible header word). + */ +static void +BogusFree(void *pointer) +{ + elog(ERROR, "pfree called with invalid pointer %p (header 0x%016llx)", + pointer, (unsigned long long) GetMemoryChunkHeader(pointer)); +} + +static void * +BogusRealloc(void *pointer, Size size) +{ + elog(ERROR, "repalloc called with invalid pointer %p (header 0x%016llx)", + pointer, (unsigned long long) GetMemoryChunkHeader(pointer)); + return NULL; /* keep compiler quiet */ +} + +static MemoryContext +BogusGetChunkContext(void *pointer) +{ + elog(ERROR, "GetMemoryChunkContext called with invalid pointer %p (header 0x%016llx)", + pointer, (unsigned long long) GetMemoryChunkHeader(pointer)); + return NULL; /* keep compiler quiet */ +} + +static Size +BogusGetChunkSpace(void *pointer) +{ + elog(ERROR, "GetMemoryChunkSpace called with invalid pointer %p (header 0x%016llx)", + pointer, (unsigned long long) GetMemoryChunkHeader(pointer)); + return 0; /* keep compiler quiet */ +} + + +/***************************************************************************** + * EXPORTED ROUTINES * + *****************************************************************************/ + + +/* + * MemoryContextInit + * Start up the memory-context subsystem. + * + * This must be called before creating contexts or allocating memory in + * contexts. TopMemoryContext and ErrorContext are initialized here; + * other contexts must be created afterwards. + * + * In normal multi-backend operation, this is called once during + * postmaster startup, and not at all by individual backend startup + * (since the backends inherit an already-initialized context subsystem + * by virtue of being forked off the postmaster). But in an EXEC_BACKEND + * build, each process must do this for itself. + * + * In a standalone backend this must be called during backend startup. + */ +void +MemoryContextInit(void) +{ + Assert(TopMemoryContext == NULL); + + /* + * First, initialize TopMemoryContext, which is the parent of all others. + */ + TopMemoryContext = AllocSetContextCreate((MemoryContext) NULL, + "TopMemoryContext", + ALLOCSET_DEFAULT_SIZES); + + /* + * Not having any other place to point CurrentMemoryContext, make it point + * to TopMemoryContext. Caller should change this soon! + */ + CurrentMemoryContext = TopMemoryContext; + + /* + * Initialize ErrorContext as an AllocSetContext with slow growth rate --- + * we don't really expect much to be allocated in it. More to the point, + * require it to contain at least 8K at all times. This is the only case + * where retained memory in a context is *essential* --- we want to be + * sure ErrorContext still has some memory even if we've run out + * elsewhere! Also, allow allocations in ErrorContext within a critical + * section. Otherwise a PANIC will cause an assertion failure in the error + * reporting code, before printing out the real cause of the failure. + * + * This should be the last step in this function, as elog.c assumes memory + * management works once ErrorContext is non-null. + */ + ErrorContext = AllocSetContextCreate(TopMemoryContext, + "ErrorContext", + 8 * 1024, + 8 * 1024, + 8 * 1024); + MemoryContextAllowInCriticalSection(ErrorContext, true); +} + +/* + * MemoryContextReset + * Release all space allocated within a context and delete all its + * descendant contexts (but not the named context itself). + */ +void +MemoryContextReset(MemoryContext context) +{ + Assert(MemoryContextIsValid(context)); + + /* save a function call in common case where there are no children */ + if (context->firstchild != NULL) + MemoryContextDeleteChildren(context); + + /* save a function call if no pallocs since startup or last reset */ + if (!context->isReset) + MemoryContextResetOnly(context); +} + +/* + * MemoryContextResetOnly + * Release all space allocated within a context. + * Nothing is done to the context's descendant contexts. + */ +void +MemoryContextResetOnly(MemoryContext context) +{ + Assert(MemoryContextIsValid(context)); + + /* Nothing to do if no pallocs since startup or last reset */ + if (!context->isReset) + { + MemoryContextCallResetCallbacks(context); + + /* + * If context->ident points into the context's memory, it will become + * a dangling pointer. We could prevent that by setting it to NULL + * here, but that would break valid coding patterns that keep the + * ident elsewhere, e.g. in a parent context. So for now we assume + * the programmer got it right. + */ + + context->methods->reset(context); + context->isReset = true; + VALGRIND_DESTROY_MEMPOOL(context); + VALGRIND_CREATE_MEMPOOL(context, 0, false); + } +} + +/* + * MemoryContextResetChildren + * Release all space allocated within a context's descendants, + * but don't delete the contexts themselves. The named context + * itself is not touched. + */ +void +MemoryContextResetChildren(MemoryContext context) +{ + MemoryContext child; + + Assert(MemoryContextIsValid(context)); + + for (child = context->firstchild; child != NULL; child = child->nextchild) + { + MemoryContextResetChildren(child); + MemoryContextResetOnly(child); + } +} + +/* + * MemoryContextDelete + * Delete a context and its descendants, and release all space + * allocated therein. + * + * The type-specific delete routine removes all storage for the context, + * but we have to recurse to handle the children. + * We must also delink the context from its parent, if it has one. + */ +void +MemoryContextDelete(MemoryContext context) +{ + Assert(MemoryContextIsValid(context)); + /* We had better not be deleting TopMemoryContext ... */ + Assert(context != TopMemoryContext); + /* And not CurrentMemoryContext, either */ + Assert(context != CurrentMemoryContext); + + /* save a function call in common case where there are no children */ + if (context->firstchild != NULL) + MemoryContextDeleteChildren(context); + + /* + * It's not entirely clear whether 'tis better to do this before or after + * delinking the context; but an error in a callback will likely result in + * leaking the whole context (if it's not a root context) if we do it + * after, so let's do it before. + */ + MemoryContextCallResetCallbacks(context); + + /* + * We delink the context from its parent before deleting it, so that if + * there's an error we won't have deleted/busted contexts still attached + * to the context tree. Better a leak than a crash. + */ + MemoryContextSetParent(context, NULL); + + /* + * Also reset the context's ident pointer, in case it points into the + * context. This would only matter if someone tries to get stats on the + * (already unlinked) context, which is unlikely, but let's be safe. + */ + context->ident = NULL; + + context->methods->delete_context(context); + + VALGRIND_DESTROY_MEMPOOL(context); +} + +/* + * MemoryContextDeleteChildren + * Delete all the descendants of the named context and release all + * space allocated therein. The named context itself is not touched. + */ +void +MemoryContextDeleteChildren(MemoryContext context) +{ + Assert(MemoryContextIsValid(context)); + + /* + * MemoryContextDelete will delink the child from me, so just iterate as + * long as there is a child. + */ + while (context->firstchild != NULL) + MemoryContextDelete(context->firstchild); +} + +/* + * MemoryContextRegisterResetCallback + * Register a function to be called before next context reset/delete. + * Such callbacks will be called in reverse order of registration. + * + * The caller is responsible for allocating a MemoryContextCallback struct + * to hold the info about this callback request, and for filling in the + * "func" and "arg" fields in the struct to show what function to call with + * what argument. Typically the callback struct should be allocated within + * the specified context, since that means it will automatically be freed + * when no longer needed. + * + * There is no API for deregistering a callback once registered. If you + * want it to not do anything anymore, adjust the state pointed to by its + * "arg" to indicate that. + */ +void +MemoryContextRegisterResetCallback(MemoryContext context, + MemoryContextCallback *cb) +{ + Assert(MemoryContextIsValid(context)); + + /* Push onto head so this will be called before older registrants. */ + cb->next = context->reset_cbs; + context->reset_cbs = cb; + /* Mark the context as non-reset (it probably is already). */ + context->isReset = false; +} + +/* + * MemoryContextCallResetCallbacks + * Internal function to call all registered callbacks for context. + */ +static void +MemoryContextCallResetCallbacks(MemoryContext context) +{ + MemoryContextCallback *cb; + + /* + * We pop each callback from the list before calling. That way, if an + * error occurs inside the callback, we won't try to call it a second time + * in the likely event that we reset or delete the context later. + */ + while ((cb = context->reset_cbs) != NULL) + { + context->reset_cbs = cb->next; + cb->func(cb->arg); + } +} + +/* + * MemoryContextSetIdentifier + * Set the identifier string for a memory context. + * + * An identifier can be provided to help distinguish among different contexts + * of the same kind in memory context stats dumps. The identifier string + * must live at least as long as the context it is for; typically it is + * allocated inside that context, so that it automatically goes away on + * context deletion. Pass id = NULL to forget any old identifier. + */ +void +MemoryContextSetIdentifier(MemoryContext context, const char *id) +{ + Assert(MemoryContextIsValid(context)); + context->ident = id; +} + +/* + * MemoryContextSetParent + * Change a context to belong to a new parent (or no parent). + * + * We provide this as an API function because it is sometimes useful to + * change a context's lifespan after creation. For example, a context + * might be created underneath a transient context, filled with data, + * and then reparented underneath CacheMemoryContext to make it long-lived. + * In this way no special effort is needed to get rid of the context in case + * a failure occurs before its contents are completely set up. + * + * Callers often assume that this function cannot fail, so don't put any + * elog(ERROR) calls in it. + * + * A possible caller error is to reparent a context under itself, creating + * a loop in the context graph. We assert here that context != new_parent, + * but checking for multi-level loops seems more trouble than it's worth. + */ +void +MemoryContextSetParent(MemoryContext context, MemoryContext new_parent) +{ + Assert(MemoryContextIsValid(context)); + Assert(context != new_parent); + + /* Fast path if it's got correct parent already */ + if (new_parent == context->parent) + return; + + /* Delink from existing parent, if any */ + if (context->parent) + { + MemoryContext parent = context->parent; + + if (context->prevchild != NULL) + context->prevchild->nextchild = context->nextchild; + else + { + Assert(parent->firstchild == context); + parent->firstchild = context->nextchild; + } + + if (context->nextchild != NULL) + context->nextchild->prevchild = context->prevchild; + } + + /* And relink */ + if (new_parent) + { + Assert(MemoryContextIsValid(new_parent)); + context->parent = new_parent; + context->prevchild = NULL; + context->nextchild = new_parent->firstchild; + if (new_parent->firstchild != NULL) + new_parent->firstchild->prevchild = context; + new_parent->firstchild = context; + } + else + { + context->parent = NULL; + context->prevchild = NULL; + context->nextchild = NULL; + } +} + +/* + * MemoryContextAllowInCriticalSection + * Allow/disallow allocations in this memory context within a critical + * section. + * + * Normally, memory allocations are not allowed within a critical section, + * because a failure would lead to PANIC. There are a few exceptions to + * that, like allocations related to debugging code that is not supposed to + * be enabled in production. This function can be used to exempt specific + * memory contexts from the assertion in palloc(). + */ +void +MemoryContextAllowInCriticalSection(MemoryContext context, bool allow) +{ + Assert(MemoryContextIsValid(context)); + + context->allowInCritSection = allow; +} + +/* + * GetMemoryChunkContext + * Given a currently-allocated chunk, determine the MemoryContext that + * the chunk belongs to. + */ +MemoryContext +GetMemoryChunkContext(void *pointer) +{ + return MCXT_METHOD(pointer, get_chunk_context) (pointer); +} + +/* + * GetMemoryChunkSpace + * Given a currently-allocated chunk, determine the total space + * it occupies (including all memory-allocation overhead). + * + * This is useful for measuring the total space occupied by a set of + * allocated chunks. + */ +Size +GetMemoryChunkSpace(void *pointer) +{ + return MCXT_METHOD(pointer, get_chunk_space) (pointer); +} + +/* + * MemoryContextGetParent + * Get the parent context (if any) of the specified context + */ +MemoryContext +MemoryContextGetParent(MemoryContext context) +{ + Assert(MemoryContextIsValid(context)); + + return context->parent; +} + +/* + * MemoryContextIsEmpty + * Is a memory context empty of any allocated space? + */ +bool +MemoryContextIsEmpty(MemoryContext context) +{ + Assert(MemoryContextIsValid(context)); + + /* + * For now, we consider a memory context nonempty if it has any children; + * perhaps this should be changed later. + */ + if (context->firstchild != NULL) + return false; + /* Otherwise use the type-specific inquiry */ + return context->methods->is_empty(context); +} + +/* + * Find the memory allocated to blocks for this memory context. If recurse is + * true, also include children. + */ +Size +MemoryContextMemAllocated(MemoryContext context, bool recurse) +{ + Size total = context->mem_allocated; + + Assert(MemoryContextIsValid(context)); + + if (recurse) + { + MemoryContext child; + + for (child = context->firstchild; + child != NULL; + child = child->nextchild) + total += MemoryContextMemAllocated(child, true); + } + + return total; +} + +/* + * MemoryContextStats + * Print statistics about the named context and all its descendants. + * + * This is just a debugging utility, so it's not very fancy. However, we do + * make some effort to summarize when the output would otherwise be very long. + * The statistics are sent to stderr. + */ +void +MemoryContextStats(MemoryContext context) +{ + /* A hard-wired limit on the number of children is usually good enough */ + MemoryContextStatsDetail(context, 100, true); +} + +/* + * MemoryContextStatsDetail + * + * Entry point for use if you want to vary the number of child contexts shown. + * + * If print_to_stderr is true, print statistics about the memory contexts + * with fprintf(stderr), otherwise use ereport(). + */ +void +MemoryContextStatsDetail(MemoryContext context, int max_children, + bool print_to_stderr) +{ + MemoryContextCounters grand_totals; + + memset(&grand_totals, 0, sizeof(grand_totals)); + + MemoryContextStatsInternal(context, 0, true, max_children, &grand_totals, print_to_stderr); + + if (print_to_stderr) + fprintf(stderr, + "Grand total: %zu bytes in %zu blocks; %zu free (%zu chunks); %zu used\n", + grand_totals.totalspace, grand_totals.nblocks, + grand_totals.freespace, grand_totals.freechunks, + grand_totals.totalspace - grand_totals.freespace); + else + + /* + * Use LOG_SERVER_ONLY to prevent the memory contexts from being sent + * to the connected client. + * + * We don't buffer the information about all memory contexts in a + * backend into StringInfo and log it as one message. That would + * require the buffer to be enlarged, risking an OOM as there could be + * a large number of memory contexts in a backend. Instead, we log + * one message per memory context. + */ + ereport(LOG_SERVER_ONLY, + (errhidestmt(true), + errhidecontext(true), + errmsg_internal("Grand total: %zu bytes in %zu blocks; %zu free (%zu chunks); %zu used", + grand_totals.totalspace, grand_totals.nblocks, + grand_totals.freespace, grand_totals.freechunks, + grand_totals.totalspace - grand_totals.freespace))); +} + +/* + * MemoryContextStatsInternal + * One recursion level for MemoryContextStats + * + * Print this context if print is true, but in any case accumulate counts into + * *totals (if given). + */ +static void +MemoryContextStatsInternal(MemoryContext context, int level, + bool print, int max_children, + MemoryContextCounters *totals, + bool print_to_stderr) +{ + MemoryContextCounters local_totals; + MemoryContext child; + int ichild; + + Assert(MemoryContextIsValid(context)); + + /* Examine the context itself */ + context->methods->stats(context, + print ? MemoryContextStatsPrint : NULL, + (void *) &level, + totals, print_to_stderr); + + /* + * Examine children. If there are more than max_children of them, we do + * not print the rest explicitly, but just summarize them. + */ + memset(&local_totals, 0, sizeof(local_totals)); + + for (child = context->firstchild, ichild = 0; + child != NULL; + child = child->nextchild, ichild++) + { + if (ichild < max_children) + MemoryContextStatsInternal(child, level + 1, + print, max_children, + totals, + print_to_stderr); + else + MemoryContextStatsInternal(child, level + 1, + false, max_children, + &local_totals, + print_to_stderr); + } + + /* Deal with excess children */ + if (ichild > max_children) + { + if (print) + { + if (print_to_stderr) + { + int i; + + for (i = 0; i <= level; i++) + fprintf(stderr, " "); + fprintf(stderr, + "%d more child contexts containing %zu total in %zu blocks; %zu free (%zu chunks); %zu used\n", + ichild - max_children, + local_totals.totalspace, + local_totals.nblocks, + local_totals.freespace, + local_totals.freechunks, + local_totals.totalspace - local_totals.freespace); + } + else + ereport(LOG_SERVER_ONLY, + (errhidestmt(true), + errhidecontext(true), + errmsg_internal("level: %d; %d more child contexts containing %zu total in %zu blocks; %zu free (%zu chunks); %zu used", + level, + ichild - max_children, + local_totals.totalspace, + local_totals.nblocks, + local_totals.freespace, + local_totals.freechunks, + local_totals.totalspace - local_totals.freespace))); + } + + if (totals) + { + totals->nblocks += local_totals.nblocks; + totals->freechunks += local_totals.freechunks; + totals->totalspace += local_totals.totalspace; + totals->freespace += local_totals.freespace; + } + } +} + +/* + * MemoryContextStatsPrint + * Print callback used by MemoryContextStatsInternal + * + * For now, the passthru pointer just points to "int level"; later we might + * make that more complicated. + */ +static void +MemoryContextStatsPrint(MemoryContext context, void *passthru, + const char *stats_string, + bool print_to_stderr) +{ + int level = *(int *) passthru; + const char *name = context->name; + const char *ident = context->ident; + char truncated_ident[110]; + int i; + + /* + * It seems preferable to label dynahash contexts with just the hash table + * name. Those are already unique enough, so the "dynahash" part isn't + * very helpful, and this way is more consistent with pre-v11 practice. + */ + if (ident && strcmp(name, "dynahash") == 0) + { + name = ident; + ident = NULL; + } + + truncated_ident[0] = '\0'; + + if (ident) + { + /* + * Some contexts may have very long identifiers (e.g., SQL queries). + * Arbitrarily truncate at 100 bytes, but be careful not to break + * multibyte characters. Also, replace ASCII control characters, such + * as newlines, with spaces. + */ + int idlen = strlen(ident); + bool truncated = false; + + strcpy(truncated_ident, ": "); + i = strlen(truncated_ident); + + if (idlen > 100) + { + idlen = pg_mbcliplen(ident, idlen, 100); + truncated = true; + } + + while (idlen-- > 0) + { + unsigned char c = *ident++; + + if (c < ' ') + c = ' '; + truncated_ident[i++] = c; + } + truncated_ident[i] = '\0'; + + if (truncated) + strcat(truncated_ident, "..."); + } + + if (print_to_stderr) + { + for (i = 0; i < level; i++) + fprintf(stderr, " "); + fprintf(stderr, "%s: %s%s\n", name, stats_string, truncated_ident); + } + else + ereport(LOG_SERVER_ONLY, + (errhidestmt(true), + errhidecontext(true), + errmsg_internal("level: %d; %s: %s%s", + level, name, stats_string, truncated_ident))); +} + +/* + * MemoryContextCheck + * Check all chunks in the named context. + * + * This is just a debugging utility, so it's not fancy. + */ +#ifdef MEMORY_CONTEXT_CHECKING +void +MemoryContextCheck(MemoryContext context) +{ + MemoryContext child; + + Assert(MemoryContextIsValid(context)); + + context->methods->check(context); + for (child = context->firstchild; child != NULL; child = child->nextchild) + MemoryContextCheck(child); +} +#endif + +/* + * MemoryContextCreate + * Context-type-independent part of context creation. + * + * This is only intended to be called by context-type-specific + * context creation routines, not by the unwashed masses. + * + * The memory context creation procedure goes like this: + * 1. Context-type-specific routine makes some initial space allocation, + * including enough space for the context header. If it fails, + * it can ereport() with no damage done. + * 2. Context-type-specific routine sets up all type-specific fields of + * the header (those beyond MemoryContextData proper), as well as any + * other management fields it needs to have a fully valid context. + * Usually, failure in this step is impossible, but if it's possible + * the initial space allocation should be freed before ereport'ing. + * 3. Context-type-specific routine calls MemoryContextCreate() to fill in + * the generic header fields and link the context into the context tree. + * 4. We return to the context-type-specific routine, which finishes + * up type-specific initialization. This routine can now do things + * that might fail (like allocate more memory), so long as it's + * sure the node is left in a state that delete will handle. + * + * node: the as-yet-uninitialized common part of the context header node. + * tag: NodeTag code identifying the memory context type. + * method_id: MemoryContextMethodID of the context-type being created. + * parent: parent context, or NULL if this will be a top-level context. + * name: name of context (must be statically allocated). + * + * Context routines generally assume that MemoryContextCreate can't fail, + * so this can contain Assert but not elog/ereport. + */ +void +MemoryContextCreate(MemoryContext node, + NodeTag tag, + MemoryContextMethodID method_id, + MemoryContext parent, + const char *name) +{ + /* Creating new memory contexts is not allowed in a critical section */ + Assert(CritSectionCount == 0); + + /* Initialize all standard fields of memory context header */ + node->type = tag; + node->isReset = true; + node->methods = &mcxt_methods[method_id]; + node->parent = parent; + node->firstchild = NULL; + node->mem_allocated = 0; + node->prevchild = NULL; + node->name = name; + node->ident = NULL; + node->reset_cbs = NULL; + + /* OK to link node into context tree */ + if (parent) + { + node->nextchild = parent->firstchild; + if (parent->firstchild != NULL) + parent->firstchild->prevchild = node; + parent->firstchild = node; + /* inherit allowInCritSection flag from parent */ + node->allowInCritSection = parent->allowInCritSection; + } + else + { + node->nextchild = NULL; + node->allowInCritSection = false; + } + + VALGRIND_CREATE_MEMPOOL(node, 0, false); +} + +/* + * MemoryContextAlloc + * Allocate space within the specified context. + * + * This could be turned into a macro, but we'd have to import + * nodes/memnodes.h into postgres.h which seems a bad idea. + */ +void * +MemoryContextAlloc(MemoryContext context, Size size) +{ + void *ret; + + Assert(MemoryContextIsValid(context)); + AssertNotInCriticalSection(context); + + if (!AllocSizeIsValid(size)) + elog(ERROR, "invalid memory alloc request size %zu", size); + + context->isReset = false; + + ret = context->methods->alloc(context, size); + if (unlikely(ret == NULL)) + { + MemoryContextStats(TopMemoryContext); + + /* + * Here, and elsewhere in this module, we show the target context's + * "name" but not its "ident" (if any) in user-visible error messages. + * The "ident" string might contain security-sensitive data, such as + * values in SQL commands. + */ + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"), + errdetail("Failed on request of size %zu in memory context \"%s\".", + size, context->name))); + } + + VALGRIND_MEMPOOL_ALLOC(context, ret, size); + + return ret; +} + +/* + * MemoryContextAllocZero + * Like MemoryContextAlloc, but clears allocated memory + * + * We could just call MemoryContextAlloc then clear the memory, but this + * is a very common combination, so we provide the combined operation. + */ +void * +MemoryContextAllocZero(MemoryContext context, Size size) +{ + void *ret; + + Assert(MemoryContextIsValid(context)); + AssertNotInCriticalSection(context); + + if (!AllocSizeIsValid(size)) + elog(ERROR, "invalid memory alloc request size %zu", size); + + context->isReset = false; + + ret = context->methods->alloc(context, size); + if (unlikely(ret == NULL)) + { + MemoryContextStats(TopMemoryContext); + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"), + errdetail("Failed on request of size %zu in memory context \"%s\".", + size, context->name))); + } + + VALGRIND_MEMPOOL_ALLOC(context, ret, size); + + MemSetAligned(ret, 0, size); + + return ret; +} + +/* + * MemoryContextAllocZeroAligned + * MemoryContextAllocZero where length is suitable for MemSetLoop + * + * This might seem overly specialized, but it's not because newNode() + * is so often called with compile-time-constant sizes. + */ +void * +MemoryContextAllocZeroAligned(MemoryContext context, Size size) +{ + void *ret; + + Assert(MemoryContextIsValid(context)); + AssertNotInCriticalSection(context); + + if (!AllocSizeIsValid(size)) + elog(ERROR, "invalid memory alloc request size %zu", size); + + context->isReset = false; + + ret = context->methods->alloc(context, size); + if (unlikely(ret == NULL)) + { + MemoryContextStats(TopMemoryContext); + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"), + errdetail("Failed on request of size %zu in memory context \"%s\".", + size, context->name))); + } + + VALGRIND_MEMPOOL_ALLOC(context, ret, size); + + MemSetLoop(ret, 0, size); + + return ret; +} + +/* + * MemoryContextAllocExtended + * Allocate space within the specified context using the given flags. + */ +void * +MemoryContextAllocExtended(MemoryContext context, Size size, int flags) +{ + void *ret; + + Assert(MemoryContextIsValid(context)); + AssertNotInCriticalSection(context); + + if (!((flags & MCXT_ALLOC_HUGE) != 0 ? AllocHugeSizeIsValid(size) : + AllocSizeIsValid(size))) + elog(ERROR, "invalid memory alloc request size %zu", size); + + context->isReset = false; + + ret = context->methods->alloc(context, size); + if (unlikely(ret == NULL)) + { + if ((flags & MCXT_ALLOC_NO_OOM) == 0) + { + MemoryContextStats(TopMemoryContext); + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"), + errdetail("Failed on request of size %zu in memory context \"%s\".", + size, context->name))); + } + return NULL; + } + + VALGRIND_MEMPOOL_ALLOC(context, ret, size); + + if ((flags & MCXT_ALLOC_ZERO) != 0) + MemSetAligned(ret, 0, size); + + return ret; +} + +/* + * HandleLogMemoryContextInterrupt + * Handle receipt of an interrupt indicating logging of memory + * contexts. + * + * All the actual work is deferred to ProcessLogMemoryContextInterrupt(), + * because we cannot safely emit a log message inside the signal handler. + */ +void +HandleLogMemoryContextInterrupt(void) +{ + InterruptPending = true; + LogMemoryContextPending = true; + /* latch will be set by procsignal_sigusr1_handler */ +} + +/* + * ProcessLogMemoryContextInterrupt + * Perform logging of memory contexts of this backend process. + * + * Any backend that participates in ProcSignal signaling must arrange + * to call this function if we see LogMemoryContextPending set. + * It is called from CHECK_FOR_INTERRUPTS(), which is enough because + * the target process for logging of memory contexts is a backend. + */ +void +ProcessLogMemoryContextInterrupt(void) +{ + LogMemoryContextPending = false; + + /* + * Use LOG_SERVER_ONLY to prevent this message from being sent to the + * connected client. + */ + ereport(LOG_SERVER_ONLY, + (errhidestmt(true), + errhidecontext(true), + errmsg("logging memory contexts of PID %d", MyProcPid))); + + /* + * When a backend process is consuming huge memory, logging all its memory + * contexts might overrun available disk space. To prevent this, we limit + * the number of child contexts to log per parent to 100. + * + * As with MemoryContextStats(), we suppose that practical cases where the + * dump gets long will typically be huge numbers of siblings under the + * same parent context; while the additional debugging value from seeing + * details about individual siblings beyond 100 will not be large. + */ + MemoryContextStatsDetail(TopMemoryContext, 100, false); +} + +void * +palloc(Size size) +{ + /* duplicates MemoryContextAlloc to avoid increased overhead */ + void *ret; + MemoryContext context = CurrentMemoryContext; + + Assert(MemoryContextIsValid(context)); + AssertNotInCriticalSection(context); + + if (!AllocSizeIsValid(size)) + elog(ERROR, "invalid memory alloc request size %zu", size); + + context->isReset = false; + + ret = context->methods->alloc(context, size); + if (unlikely(ret == NULL)) + { + MemoryContextStats(TopMemoryContext); + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"), + errdetail("Failed on request of size %zu in memory context \"%s\".", + size, context->name))); + } + + VALGRIND_MEMPOOL_ALLOC(context, ret, size); + + return ret; +} + +void * +palloc0(Size size) +{ + /* duplicates MemoryContextAllocZero to avoid increased overhead */ + void *ret; + MemoryContext context = CurrentMemoryContext; + + Assert(MemoryContextIsValid(context)); + AssertNotInCriticalSection(context); + + if (!AllocSizeIsValid(size)) + elog(ERROR, "invalid memory alloc request size %zu", size); + + context->isReset = false; + + ret = context->methods->alloc(context, size); + if (unlikely(ret == NULL)) + { + MemoryContextStats(TopMemoryContext); + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"), + errdetail("Failed on request of size %zu in memory context \"%s\".", + size, context->name))); + } + + VALGRIND_MEMPOOL_ALLOC(context, ret, size); + + MemSetAligned(ret, 0, size); + + return ret; +} + +void * +palloc_extended(Size size, int flags) +{ + /* duplicates MemoryContextAllocExtended to avoid increased overhead */ + void *ret; + MemoryContext context = CurrentMemoryContext; + + Assert(MemoryContextIsValid(context)); + AssertNotInCriticalSection(context); + + if (!((flags & MCXT_ALLOC_HUGE) != 0 ? AllocHugeSizeIsValid(size) : + AllocSizeIsValid(size))) + elog(ERROR, "invalid memory alloc request size %zu", size); + + context->isReset = false; + + ret = context->methods->alloc(context, size); + if (unlikely(ret == NULL)) + { + if ((flags & MCXT_ALLOC_NO_OOM) == 0) + { + MemoryContextStats(TopMemoryContext); + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"), + errdetail("Failed on request of size %zu in memory context \"%s\".", + size, context->name))); + } + return NULL; + } + + VALGRIND_MEMPOOL_ALLOC(context, ret, size); + + if ((flags & MCXT_ALLOC_ZERO) != 0) + MemSetAligned(ret, 0, size); + + return ret; +} + +/* + * MemoryContextAllocAligned + * Allocate 'size' bytes of memory in 'context' aligned to 'alignto' + * bytes. + * + * Currently, we align addresses by requesting additional bytes from the + * MemoryContext's standard allocator function and then aligning the returned + * address by the required alignment. This means that the given MemoryContext + * must support providing us with a chunk of memory that's larger than 'size'. + * For allocators such as Slab, that's not going to work, as slab only allows + * chunks of the size that's specified when the context is created. + * + * 'alignto' must be a power of 2. + * 'flags' may be 0 or set the same as MemoryContextAllocExtended(). + */ +void * +MemoryContextAllocAligned(MemoryContext context, + Size size, Size alignto, int flags) +{ + MemoryChunk *alignedchunk; + Size alloc_size; + void *unaligned; + void *aligned; + + /* wouldn't make much sense to waste that much space */ + Assert(alignto < (128 * 1024 * 1024)); + + /* ensure alignto is a power of 2 */ + Assert((alignto & (alignto - 1)) == 0); + + /* + * If the alignment requirements are less than what we already guarantee + * then just use the standard allocation function. + */ + if (unlikely(alignto <= MAXIMUM_ALIGNOF)) + return MemoryContextAllocExtended(context, size, flags); + + /* + * We implement aligned pointers by simply allocating enough memory for + * the requested size plus the alignment and an additional "redirection" + * MemoryChunk. This additional MemoryChunk is required for operations + * such as pfree when used on the pointer returned by this function. We + * use this redirection MemoryChunk in order to find the pointer to the + * memory that was returned by the MemoryContextAllocExtended call below. + * We do that by "borrowing" the block offset field and instead of using + * that to find the offset into the owning block, we use it to find the + * original allocated address. + * + * Here we must allocate enough extra memory so that we can still align + * the pointer returned by MemoryContextAllocExtended and also have enough + * space for the redirection MemoryChunk. Since allocations will already + * be at least aligned by MAXIMUM_ALIGNOF, we can subtract that amount + * from the allocation size to save a little memory. + */ + alloc_size = size + PallocAlignedExtraBytes(alignto); + +#ifdef MEMORY_CONTEXT_CHECKING + /* ensure there's space for a sentinel byte */ + alloc_size += 1; +#endif + + /* perform the actual allocation */ + unaligned = MemoryContextAllocExtended(context, alloc_size, flags); + + /* set the aligned pointer */ + aligned = (void *) TYPEALIGN(alignto, (char *) unaligned + + sizeof(MemoryChunk)); + + alignedchunk = PointerGetMemoryChunk(aligned); + + /* + * We set the redirect MemoryChunk so that the block offset calculation is + * used to point back to the 'unaligned' allocated chunk. This allows us + * to use MemoryChunkGetBlock() to find the unaligned chunk when we need + * to perform operations such as pfree() and repalloc(). + * + * We store 'alignto' in the MemoryChunk's 'value' so that we know what + * the alignment was set to should we ever be asked to realloc this + * pointer. + */ + MemoryChunkSetHdrMask(alignedchunk, unaligned, alignto, + MCTX_ALIGNED_REDIRECT_ID); + + /* double check we produced a correctly aligned pointer */ + Assert((void *) TYPEALIGN(alignto, aligned) == aligned); + +#ifdef MEMORY_CONTEXT_CHECKING + alignedchunk->requested_size = size; + /* set mark to catch clobber of "unused" space */ + set_sentinel(aligned, size); +#endif + + /* Mark the bytes before the redirection header as noaccess */ + VALGRIND_MAKE_MEM_NOACCESS(unaligned, + (char *) alignedchunk - (char *) unaligned); + + /* Disallow access to the redirection chunk header. */ + VALGRIND_MAKE_MEM_NOACCESS(alignedchunk, sizeof(MemoryChunk)); + + return aligned; +} + +/* + * palloc_aligned + * Allocate 'size' bytes returning a pointer that's aligned to the + * 'alignto' boundary. + * + * Currently, we align addresses by requesting additional bytes from the + * MemoryContext's standard allocator function and then aligning the returned + * address by the required alignment. This means that the given MemoryContext + * must support providing us with a chunk of memory that's larger than 'size'. + * For allocators such as Slab, that's not going to work, as slab only allows + * chunks of the size that's specified when the context is created. + * + * 'alignto' must be a power of 2. + * 'flags' may be 0 or set the same as MemoryContextAllocExtended(). + */ +void * +palloc_aligned(Size size, Size alignto, int flags) +{ + return MemoryContextAllocAligned(CurrentMemoryContext, size, alignto, flags); +} + +/* + * pfree + * Release an allocated chunk. + */ +void +pfree(void *pointer) +{ +#ifdef USE_VALGRIND + MemoryContextMethodID method = GetMemoryChunkMethodID(pointer); + MemoryContext context = GetMemoryChunkContext(pointer); +#endif + + MCXT_METHOD(pointer, free_p) (pointer); + +#ifdef USE_VALGRIND + if (method != MCTX_ALIGNED_REDIRECT_ID) + VALGRIND_MEMPOOL_FREE(context, pointer); +#endif +} + +/* + * repalloc + * Adjust the size of a previously allocated chunk. + */ +void * +repalloc(void *pointer, Size size) +{ +#ifdef USE_VALGRIND + MemoryContextMethodID method = GetMemoryChunkMethodID(pointer); +#endif +#if defined(USE_ASSERT_CHECKING) || defined(USE_VALGRIND) + MemoryContext context = GetMemoryChunkContext(pointer); +#endif + void *ret; + + if (!AllocSizeIsValid(size)) + elog(ERROR, "invalid memory alloc request size %zu", size); + + AssertNotInCriticalSection(context); + + /* isReset must be false already */ + Assert(!context->isReset); + + ret = MCXT_METHOD(pointer, realloc) (pointer, size); + if (unlikely(ret == NULL)) + { + MemoryContext cxt = GetMemoryChunkContext(pointer); + + MemoryContextStats(TopMemoryContext); + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"), + errdetail("Failed on request of size %zu in memory context \"%s\".", + size, cxt->name))); + } + +#ifdef USE_VALGRIND + if (method != MCTX_ALIGNED_REDIRECT_ID) + VALGRIND_MEMPOOL_CHANGE(context, pointer, ret, size); +#endif + + return ret; +} + +/* + * repalloc_extended + * Adjust the size of a previously allocated chunk, + * with HUGE and NO_OOM options. + */ +void * +repalloc_extended(void *pointer, Size size, int flags) +{ +#if defined(USE_ASSERT_CHECKING) || defined(USE_VALGRIND) + MemoryContext context = GetMemoryChunkContext(pointer); +#endif + void *ret; + + if (!((flags & MCXT_ALLOC_HUGE) != 0 ? AllocHugeSizeIsValid(size) : + AllocSizeIsValid(size))) + elog(ERROR, "invalid memory alloc request size %zu", size); + + AssertNotInCriticalSection(context); + + /* isReset must be false already */ + Assert(!context->isReset); + + ret = MCXT_METHOD(pointer, realloc) (pointer, size); + if (unlikely(ret == NULL)) + { + if ((flags & MCXT_ALLOC_NO_OOM) == 0) + { + MemoryContext cxt = GetMemoryChunkContext(pointer); + + MemoryContextStats(TopMemoryContext); + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"), + errdetail("Failed on request of size %zu in memory context \"%s\".", + size, cxt->name))); + } + return NULL; + } + + VALGRIND_MEMPOOL_CHANGE(context, pointer, ret, size); + + return ret; +} + +/* + * repalloc0 + * Adjust the size of a previously allocated chunk and zero out the added + * space. + */ +void * +repalloc0(void *pointer, Size oldsize, Size size) +{ + void *ret; + + /* catch wrong argument order */ + if (unlikely(oldsize > size)) + elog(ERROR, "invalid repalloc0 call: oldsize %zu, new size %zu", + oldsize, size); + + ret = repalloc(pointer, size); + memset((char *) ret + oldsize, 0, (size - oldsize)); + return ret; +} + +/* + * MemoryContextAllocHuge + * Allocate (possibly-expansive) space within the specified context. + * + * See considerations in comment at MaxAllocHugeSize. + */ +void * +MemoryContextAllocHuge(MemoryContext context, Size size) +{ + void *ret; + + Assert(MemoryContextIsValid(context)); + AssertNotInCriticalSection(context); + + if (!AllocHugeSizeIsValid(size)) + elog(ERROR, "invalid memory alloc request size %zu", size); + + context->isReset = false; + + ret = context->methods->alloc(context, size); + if (unlikely(ret == NULL)) + { + MemoryContextStats(TopMemoryContext); + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"), + errdetail("Failed on request of size %zu in memory context \"%s\".", + size, context->name))); + } + + VALGRIND_MEMPOOL_ALLOC(context, ret, size); + + return ret; +} + +/* + * repalloc_huge + * Adjust the size of a previously allocated chunk, permitting a large + * value. The previous allocation need not have been "huge". + */ +void * +repalloc_huge(void *pointer, Size size) +{ + /* this one seems not worth its own implementation */ + return repalloc_extended(pointer, size, MCXT_ALLOC_HUGE); +} + +/* + * MemoryContextStrdup + * Like strdup(), but allocate from the specified context + */ +char * +MemoryContextStrdup(MemoryContext context, const char *string) +{ + char *nstr; + Size len = strlen(string) + 1; + + nstr = (char *) MemoryContextAlloc(context, len); + + memcpy(nstr, string, len); + + return nstr; +} + +char * +pstrdup(const char *in) +{ + return MemoryContextStrdup(CurrentMemoryContext, in); +} + +/* + * pnstrdup + * Like pstrdup(), but append null byte to a + * not-necessarily-null-terminated input string. + */ +char * +pnstrdup(const char *in, Size len) +{ + char *out; + + len = strnlen(in, len); + + out = palloc(len + 1); + memcpy(out, in, len); + out[len] = '\0'; + + return out; +} + +/* + * Make copy of string with all trailing newline characters removed. + */ +char * +pchomp(const char *in) +{ + size_t n; + + n = strlen(in); + while (n > 0 && in[n - 1] == '\n') + n--; + return pnstrdup(in, n); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/mmgr/memdebug.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/mmgr/memdebug.c new file mode 100644 index 00000000000..ec50a30d5f7 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/mmgr/memdebug.c @@ -0,0 +1,93 @@ +/*------------------------------------------------------------------------- + * + * memdebug.c + * Declarations used in memory context implementations, not part of the + * public API of the memory management subsystem. + * + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/backend/utils/mmgr/memdebug.c + * + * + * About CLOBBER_FREED_MEMORY: + * + * If this symbol is defined, all freed memory is overwritten with 0x7F's. + * This is useful for catching places that reference already-freed memory. + * + * About MEMORY_CONTEXT_CHECKING: + * + * Since we usually round request sizes up to the next power of 2, there + * is often some unused space immediately after a requested data area. + * Thus, if someone makes the common error of writing past what they've + * requested, the problem is likely to go unnoticed ... until the day when + * there *isn't* any wasted space, perhaps because of different memory + * alignment on a new platform, or some other effect. To catch this sort + * of problem, the MEMORY_CONTEXT_CHECKING option stores 0x7E just beyond + * the requested space whenever the request is less than the actual chunk + * size, and verifies that the byte is undamaged when the chunk is freed. + * + * + * About USE_VALGRIND and Valgrind client requests: + * + * Valgrind provides "client request" macros that exchange information with + * the host Valgrind (if any). Under !USE_VALGRIND, memdebug.h stubs out + * currently-used macros. + * + * When running under Valgrind, we want a NOACCESS memory region both before + * and after the allocation. The chunk header is tempting as the preceding + * region, but mcxt.c expects to able to examine the standard chunk header + * fields. Therefore, we use, when available, the requested_size field and + * any subsequent padding. requested_size is made NOACCESS before returning + * a chunk pointer to a caller. However, to reduce client request traffic, + * it is kept DEFINED in chunks on the free list. + * + * The rounded-up capacity of the chunk usually acts as a post-allocation + * NOACCESS region. If the request consumes precisely the entire chunk, + * there is no such region; another chunk header may immediately follow. In + * that case, Valgrind will not detect access beyond the end of the chunk. + * + * See also the cooperating Valgrind client requests in mcxt.c. + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "utils/memdebug.h" + +#ifdef RANDOMIZE_ALLOCATED_MEMORY + +/* + * Fill a just-allocated piece of memory with "random" data. It's not really + * very random, just a repeating sequence with a length that's prime. What + * we mainly want out of it is to have a good probability that two palloc's + * of the same number of bytes start out containing different data. + * + * The region may be NOACCESS, so make it UNDEFINED first to avoid errors as + * we fill it. Filling the region makes it DEFINED, so make it UNDEFINED + * again afterward. Whether to finally make it UNDEFINED or NOACCESS is + * fairly arbitrary. UNDEFINED is more convenient for SlabRealloc(), and + * other callers have no preference. + */ +void +randomize_mem(char *ptr, size_t size) +{ + static int save_ctr = 1; + size_t remaining = size; + int ctr; + + ctr = save_ctr; + VALGRIND_MAKE_MEM_UNDEFINED(ptr, size); + while (remaining-- > 0) + { + *ptr++ = ctr; + if (++ctr > 251) + ctr = 1; + } + VALGRIND_MAKE_MEM_UNDEFINED(ptr - size, size); + save_ctr = ctr; +} + +#endif /* RANDOMIZE_ALLOCATED_MEMORY */ diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/mmgr/portalmem.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/mmgr/portalmem.c new file mode 100644 index 00000000000..d756ae80341 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/mmgr/portalmem.c @@ -0,0 +1,1291 @@ +/*------------------------------------------------------------------------- + * + * portalmem.c + * backend portal memory management + * + * Portals are objects representing the execution state of a query. + * This module provides memory management services for portals, but it + * doesn't actually run the executor for them. + * + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/utils/mmgr/portalmem.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/xact.h" +#include "catalog/pg_type.h" +#include "commands/portalcmds.h" +#include "funcapi.h" +#include "miscadmin.h" +#include "storage/ipc.h" +#include "utils/builtins.h" +#include "utils/memutils.h" +#include "utils/snapmgr.h" +#include "utils/timestamp.h" + +/* + * Estimate of the maximum number of open portals a user would have, + * used in initially sizing the PortalHashTable in EnablePortalManager(). + * Since the hash table can expand, there's no need to make this overly + * generous, and keeping it small avoids unnecessary overhead in the + * hash_seq_search() calls executed during transaction end. + */ +#define PORTALS_PER_USER 16 + + +/* ---------------- + * Global state + * ---------------- + */ + +#define MAX_PORTALNAME_LEN NAMEDATALEN + +typedef struct portalhashent +{ + char portalname[MAX_PORTALNAME_LEN]; + Portal portal; +} PortalHashEnt; + +static __thread HTAB *PortalHashTable = NULL; + +#define PortalHashTableLookup(NAME, PORTAL) \ +do { \ + PortalHashEnt *hentry; \ + \ + hentry = (PortalHashEnt *) hash_search(PortalHashTable, \ + (NAME), HASH_FIND, NULL); \ + if (hentry) \ + PORTAL = hentry->portal; \ + else \ + PORTAL = NULL; \ +} while(0) + +#define PortalHashTableInsert(PORTAL, NAME) \ +do { \ + PortalHashEnt *hentry; bool found; \ + \ + hentry = (PortalHashEnt *) hash_search(PortalHashTable, \ + (NAME), HASH_ENTER, &found); \ + if (found) \ + elog(ERROR, "duplicate portal name"); \ + hentry->portal = PORTAL; \ + /* To avoid duplicate storage, make PORTAL->name point to htab entry */ \ + PORTAL->name = hentry->portalname; \ +} while(0) + +#define PortalHashTableDelete(PORTAL) \ +do { \ + PortalHashEnt *hentry; \ + \ + hentry = (PortalHashEnt *) hash_search(PortalHashTable, \ + PORTAL->name, HASH_REMOVE, NULL); \ + if (hentry == NULL) \ + elog(WARNING, "trying to delete portal name that does not exist"); \ +} while(0) + +static __thread MemoryContext TopPortalContext = NULL; + + +/* ---------------------------------------------------------------- + * public portal interface functions + * ---------------------------------------------------------------- + */ + +/* + * EnablePortalManager + * Enables the portal management module at backend startup. + */ +void +EnablePortalManager(void) +{ + HASHCTL ctl; + + Assert(TopPortalContext == NULL); + + TopPortalContext = AllocSetContextCreate(TopMemoryContext, + "TopPortalContext", + ALLOCSET_DEFAULT_SIZES); + + ctl.keysize = MAX_PORTALNAME_LEN; + ctl.entrysize = sizeof(PortalHashEnt); + + /* + * use PORTALS_PER_USER as a guess of how many hash table entries to + * create, initially + */ + PortalHashTable = hash_create("Portal hash", PORTALS_PER_USER, + &ctl, HASH_ELEM | HASH_STRINGS); +} + +/* + * GetPortalByName + * Returns a portal given a portal name, or NULL if name not found. + */ +Portal +GetPortalByName(const char *name) +{ + Portal portal; + + if (PointerIsValid(name)) + PortalHashTableLookup(name, portal); + else + portal = NULL; + + return portal; +} + +/* + * PortalGetPrimaryStmt + * Get the "primary" stmt within a portal, ie, the one marked canSetTag. + * + * Returns NULL if no such stmt. If multiple PlannedStmt structs within the + * portal are marked canSetTag, returns the first one. Neither of these + * cases should occur in present usages of this function. + */ +PlannedStmt * +PortalGetPrimaryStmt(Portal portal) +{ + ListCell *lc; + + foreach(lc, portal->stmts) + { + PlannedStmt *stmt = lfirst_node(PlannedStmt, lc); + + if (stmt->canSetTag) + return stmt; + } + return NULL; +} + +/* + * CreatePortal + * Returns a new portal given a name. + * + * allowDup: if true, automatically drop any pre-existing portal of the + * same name (if false, an error is raised). + * + * dupSilent: if true, don't even emit a WARNING. + */ +Portal +CreatePortal(const char *name, bool allowDup, bool dupSilent) +{ + Portal portal; + + Assert(PointerIsValid(name)); + + portal = GetPortalByName(name); + if (PortalIsValid(portal)) + { + if (!allowDup) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_CURSOR), + errmsg("cursor \"%s\" already exists", name))); + if (!dupSilent) + ereport(WARNING, + (errcode(ERRCODE_DUPLICATE_CURSOR), + errmsg("closing existing cursor \"%s\"", + name))); + PortalDrop(portal, false); + } + + /* make new portal structure */ + portal = (Portal) MemoryContextAllocZero(TopPortalContext, sizeof *portal); + + /* initialize portal context; typically it won't store much */ + portal->portalContext = AllocSetContextCreate(TopPortalContext, + "PortalContext", + ALLOCSET_SMALL_SIZES); + + /* create a resource owner for the portal */ + portal->resowner = ResourceOwnerCreate(CurTransactionResourceOwner, + "Portal"); + + /* initialize portal fields that don't start off zero */ + portal->status = PORTAL_NEW; + portal->cleanup = PortalCleanup; + portal->createSubid = GetCurrentSubTransactionId(); + portal->activeSubid = portal->createSubid; + portal->createLevel = GetCurrentTransactionNestLevel(); + portal->strategy = PORTAL_MULTI_QUERY; + portal->cursorOptions = CURSOR_OPT_NO_SCROLL; + portal->atStart = true; + portal->atEnd = true; /* disallow fetches until query is set */ + portal->visible = true; + portal->creation_time = GetCurrentStatementStartTimestamp(); + + /* put portal in table (sets portal->name) */ + PortalHashTableInsert(portal, name); + + /* for named portals reuse portal->name copy */ + MemoryContextSetIdentifier(portal->portalContext, portal->name[0] ? portal->name : "<unnamed>"); + + return portal; +} + +/* + * CreateNewPortal + * Create a new portal, assigning it a random nonconflicting name. + */ +Portal +CreateNewPortal(void) +{ + static __thread unsigned int unnamed_portal_count = 0; + + char portalname[MAX_PORTALNAME_LEN]; + + /* Select a nonconflicting name */ + for (;;) + { + unnamed_portal_count++; + sprintf(portalname, "<unnamed portal %u>", unnamed_portal_count); + if (GetPortalByName(portalname) == NULL) + break; + } + + return CreatePortal(portalname, false, false); +} + +/* + * PortalDefineQuery + * A simple subroutine to establish a portal's query. + * + * Notes: as of PG 8.4, caller MUST supply a sourceText string; it is not + * allowed anymore to pass NULL. (If you really don't have source text, + * you can pass a constant string, perhaps "(query not available)".) + * + * commandTag shall be NULL if and only if the original query string + * (before rewriting) was an empty string. Also, the passed commandTag must + * be a pointer to a constant string, since it is not copied. + * + * If cplan is provided, then it is a cached plan containing the stmts, and + * the caller must have done GetCachedPlan(), causing a refcount increment. + * The refcount will be released when the portal is destroyed. + * + * If cplan is NULL, then it is the caller's responsibility to ensure that + * the passed plan trees have adequate lifetime. Typically this is done by + * copying them into the portal's context. + * + * The caller is also responsible for ensuring that the passed prepStmtName + * (if not NULL) and sourceText have adequate lifetime. + * + * NB: this function mustn't do much beyond storing the passed values; in + * particular don't do anything that risks elog(ERROR). If that were to + * happen here before storing the cplan reference, we'd leak the plancache + * refcount that the caller is trying to hand off to us. + */ +void +PortalDefineQuery(Portal portal, + const char *prepStmtName, + const char *sourceText, + CommandTag commandTag, + List *stmts, + CachedPlan *cplan) +{ + Assert(PortalIsValid(portal)); + Assert(portal->status == PORTAL_NEW); + + Assert(sourceText != NULL); + Assert(commandTag != CMDTAG_UNKNOWN || stmts == NIL); + + portal->prepStmtName = prepStmtName; + portal->sourceText = sourceText; + portal->qc.commandTag = commandTag; + portal->qc.nprocessed = 0; + portal->commandTag = commandTag; + portal->stmts = stmts; + portal->cplan = cplan; + portal->status = PORTAL_DEFINED; +} + +/* + * PortalReleaseCachedPlan + * Release a portal's reference to its cached plan, if any. + */ +static void +PortalReleaseCachedPlan(Portal portal) +{ + if (portal->cplan) + { + ReleaseCachedPlan(portal->cplan, NULL); + portal->cplan = NULL; + + /* + * We must also clear portal->stmts which is now a dangling reference + * to the cached plan's plan list. This protects any code that might + * try to examine the Portal later. + */ + portal->stmts = NIL; + } +} + +/* + * PortalCreateHoldStore + * Create the tuplestore for a portal. + */ +void +PortalCreateHoldStore(Portal portal) +{ + MemoryContext oldcxt; + + Assert(portal->holdContext == NULL); + Assert(portal->holdStore == NULL); + Assert(portal->holdSnapshot == NULL); + + /* + * Create the memory context that is used for storage of the tuple set. + * Note this is NOT a child of the portal's portalContext. + */ + portal->holdContext = + AllocSetContextCreate(TopPortalContext, + "PortalHoldContext", + ALLOCSET_DEFAULT_SIZES); + + /* + * Create the tuple store, selecting cross-transaction temp files, and + * enabling random access only if cursor requires scrolling. + * + * XXX: Should maintenance_work_mem be used for the portal size? + */ + oldcxt = MemoryContextSwitchTo(portal->holdContext); + + portal->holdStore = + tuplestore_begin_heap(portal->cursorOptions & CURSOR_OPT_SCROLL, + true, work_mem); + + MemoryContextSwitchTo(oldcxt); +} + +/* + * PinPortal + * Protect a portal from dropping. + * + * A pinned portal is still unpinned and dropped at transaction or + * subtransaction abort. + */ +void +PinPortal(Portal portal) +{ + if (portal->portalPinned) + elog(ERROR, "portal already pinned"); + + portal->portalPinned = true; +} + +void +UnpinPortal(Portal portal) +{ + if (!portal->portalPinned) + elog(ERROR, "portal not pinned"); + + portal->portalPinned = false; +} + +/* + * MarkPortalActive + * Transition a portal from READY to ACTIVE state. + * + * NOTE: never set portal->status = PORTAL_ACTIVE directly; call this instead. + */ +void +MarkPortalActive(Portal portal) +{ + /* For safety, this is a runtime test not just an Assert */ + if (portal->status != PORTAL_READY) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("portal \"%s\" cannot be run", portal->name))); + /* Perform the state transition */ + portal->status = PORTAL_ACTIVE; + portal->activeSubid = GetCurrentSubTransactionId(); +} + +/* + * MarkPortalDone + * Transition a portal from ACTIVE to DONE state. + * + * NOTE: never set portal->status = PORTAL_DONE directly; call this instead. + */ +void +MarkPortalDone(Portal portal) +{ + /* Perform the state transition */ + Assert(portal->status == PORTAL_ACTIVE); + portal->status = PORTAL_DONE; + + /* + * Allow portalcmds.c to clean up the state it knows about. We might as + * well do that now, since the portal can't be executed any more. + * + * In some cases involving execution of a ROLLBACK command in an already + * aborted transaction, this is necessary, or we'd reach AtCleanup_Portals + * with the cleanup hook still unexecuted. + */ + if (PointerIsValid(portal->cleanup)) + { + portal->cleanup(portal); + portal->cleanup = NULL; + } +} + +/* + * MarkPortalFailed + * Transition a portal into FAILED state. + * + * NOTE: never set portal->status = PORTAL_FAILED directly; call this instead. + */ +void +MarkPortalFailed(Portal portal) +{ + /* Perform the state transition */ + Assert(portal->status != PORTAL_DONE); + portal->status = PORTAL_FAILED; + + /* + * Allow portalcmds.c to clean up the state it knows about. We might as + * well do that now, since the portal can't be executed any more. + * + * In some cases involving cleanup of an already aborted transaction, this + * is necessary, or we'd reach AtCleanup_Portals with the cleanup hook + * still unexecuted. + */ + if (PointerIsValid(portal->cleanup)) + { + portal->cleanup(portal); + portal->cleanup = NULL; + } +} + +/* + * PortalDrop + * Destroy the portal. + */ +void +PortalDrop(Portal portal, bool isTopCommit) +{ + Assert(PortalIsValid(portal)); + + /* + * Don't allow dropping a pinned portal, it's still needed by whoever + * pinned it. + */ + if (portal->portalPinned) + ereport(ERROR, + (errcode(ERRCODE_INVALID_CURSOR_STATE), + errmsg("cannot drop pinned portal \"%s\"", portal->name))); + + /* + * Not sure if the PORTAL_ACTIVE case can validly happen or not... + */ + if (portal->status == PORTAL_ACTIVE) + ereport(ERROR, + (errcode(ERRCODE_INVALID_CURSOR_STATE), + errmsg("cannot drop active portal \"%s\"", portal->name))); + + /* + * Allow portalcmds.c to clean up the state it knows about, in particular + * shutting down the executor if still active. This step potentially runs + * user-defined code so failure has to be expected. It's the cleanup + * hook's responsibility to not try to do that more than once, in the case + * that failure occurs and then we come back to drop the portal again + * during transaction abort. + * + * Note: in most paths of control, this will have been done already in + * MarkPortalDone or MarkPortalFailed. We're just making sure. + */ + if (PointerIsValid(portal->cleanup)) + { + portal->cleanup(portal); + portal->cleanup = NULL; + } + + /* There shouldn't be an active snapshot anymore, except after error */ + Assert(portal->portalSnapshot == NULL || !isTopCommit); + + /* + * Remove portal from hash table. Because we do this here, we will not + * come back to try to remove the portal again if there's any error in the + * subsequent steps. Better to leak a little memory than to get into an + * infinite error-recovery loop. + */ + PortalHashTableDelete(portal); + + /* drop cached plan reference, if any */ + PortalReleaseCachedPlan(portal); + + /* + * If portal has a snapshot protecting its data, release that. This needs + * a little care since the registration will be attached to the portal's + * resowner; if the portal failed, we will already have released the + * resowner (and the snapshot) during transaction abort. + */ + if (portal->holdSnapshot) + { + if (portal->resowner) + UnregisterSnapshotFromOwner(portal->holdSnapshot, + portal->resowner); + portal->holdSnapshot = NULL; + } + + /* + * Release any resources still attached to the portal. There are several + * cases being covered here: + * + * Top transaction commit (indicated by isTopCommit): normally we should + * do nothing here and let the regular end-of-transaction resource + * releasing mechanism handle these resources too. However, if we have a + * FAILED portal (eg, a cursor that got an error), we'd better clean up + * its resources to avoid resource-leakage warning messages. + * + * Sub transaction commit: never comes here at all, since we don't kill + * any portals in AtSubCommit_Portals(). + * + * Main or sub transaction abort: we will do nothing here because + * portal->resowner was already set NULL; the resources were already + * cleaned up in transaction abort. + * + * Ordinary portal drop: must release resources. However, if the portal + * is not FAILED then we do not release its locks. The locks become the + * responsibility of the transaction's ResourceOwner (since it is the + * parent of the portal's owner) and will be released when the transaction + * eventually ends. + */ + if (portal->resowner && + (!isTopCommit || portal->status == PORTAL_FAILED)) + { + bool isCommit = (portal->status != PORTAL_FAILED); + + ResourceOwnerRelease(portal->resowner, + RESOURCE_RELEASE_BEFORE_LOCKS, + isCommit, false); + ResourceOwnerRelease(portal->resowner, + RESOURCE_RELEASE_LOCKS, + isCommit, false); + ResourceOwnerRelease(portal->resowner, + RESOURCE_RELEASE_AFTER_LOCKS, + isCommit, false); + ResourceOwnerDelete(portal->resowner); + } + portal->resowner = NULL; + + /* + * Delete tuplestore if present. We should do this even under error + * conditions; since the tuplestore would have been using cross- + * transaction storage, its temp files need to be explicitly deleted. + */ + if (portal->holdStore) + { + MemoryContext oldcontext; + + oldcontext = MemoryContextSwitchTo(portal->holdContext); + tuplestore_end(portal->holdStore); + MemoryContextSwitchTo(oldcontext); + portal->holdStore = NULL; + } + + /* delete tuplestore storage, if any */ + if (portal->holdContext) + MemoryContextDelete(portal->holdContext); + + /* release subsidiary storage */ + MemoryContextDelete(portal->portalContext); + + /* release portal struct (it's in TopPortalContext) */ + pfree(portal); +} + +/* + * Delete all declared cursors. + * + * Used by commands: CLOSE ALL, DISCARD ALL + */ +void +PortalHashTableDeleteAll(void) +{ + HASH_SEQ_STATUS status; + PortalHashEnt *hentry; + + if (PortalHashTable == NULL) + return; + + hash_seq_init(&status, PortalHashTable); + while ((hentry = hash_seq_search(&status)) != NULL) + { + Portal portal = hentry->portal; + + /* Can't close the active portal (the one running the command) */ + if (portal->status == PORTAL_ACTIVE) + continue; + + PortalDrop(portal, false); + + /* Restart the iteration in case that led to other drops */ + hash_seq_term(&status); + hash_seq_init(&status, PortalHashTable); + } +} + +/* + * "Hold" a portal. Prepare it for access by later transactions. + */ +static void +HoldPortal(Portal portal) +{ + /* + * Note that PersistHoldablePortal() must release all resources used by + * the portal that are local to the creating transaction. + */ + PortalCreateHoldStore(portal); + PersistHoldablePortal(portal); + + /* drop cached plan reference, if any */ + PortalReleaseCachedPlan(portal); + + /* + * Any resources belonging to the portal will be released in the upcoming + * transaction-wide cleanup; the portal will no longer have its own + * resources. + */ + portal->resowner = NULL; + + /* + * Having successfully exported the holdable cursor, mark it as not + * belonging to this transaction. + */ + portal->createSubid = InvalidSubTransactionId; + portal->activeSubid = InvalidSubTransactionId; + portal->createLevel = 0; +} + +/* + * Pre-commit processing for portals. + * + * Holdable cursors created in this transaction need to be converted to + * materialized form, since we are going to close down the executor and + * release locks. Non-holdable portals created in this transaction are + * simply removed. Portals remaining from prior transactions should be + * left untouched. + * + * Returns true if any portals changed state (possibly causing user-defined + * code to be run), false if not. + */ +bool +PreCommit_Portals(bool isPrepare) +{ + bool result = false; + HASH_SEQ_STATUS status; + PortalHashEnt *hentry; + + hash_seq_init(&status, PortalHashTable); + + while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL) + { + Portal portal = hentry->portal; + + /* + * There should be no pinned portals anymore. Complain if someone + * leaked one. Auto-held portals are allowed; we assume that whoever + * pinned them is managing them. + */ + if (portal->portalPinned && !portal->autoHeld) + elog(ERROR, "cannot commit while a portal is pinned"); + + /* + * Do not touch active portals --- this can only happen in the case of + * a multi-transaction utility command, such as VACUUM, or a commit in + * a procedure. + * + * Note however that any resource owner attached to such a portal is + * still going to go away, so don't leave a dangling pointer. Also + * unregister any snapshots held by the portal, mainly to avoid + * snapshot leak warnings from ResourceOwnerRelease(). + */ + if (portal->status == PORTAL_ACTIVE) + { + if (portal->holdSnapshot) + { + if (portal->resowner) + UnregisterSnapshotFromOwner(portal->holdSnapshot, + portal->resowner); + portal->holdSnapshot = NULL; + } + portal->resowner = NULL; + /* Clear portalSnapshot too, for cleanliness */ + portal->portalSnapshot = NULL; + continue; + } + + /* Is it a holdable portal created in the current xact? */ + if ((portal->cursorOptions & CURSOR_OPT_HOLD) && + portal->createSubid != InvalidSubTransactionId && + portal->status == PORTAL_READY) + { + /* + * We are exiting the transaction that created a holdable cursor. + * Instead of dropping the portal, prepare it for access by later + * transactions. + * + * However, if this is PREPARE TRANSACTION rather than COMMIT, + * refuse PREPARE, because the semantics seem pretty unclear. + */ + if (isPrepare) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot PREPARE a transaction that has created a cursor WITH HOLD"))); + + HoldPortal(portal); + + /* Report we changed state */ + result = true; + } + else if (portal->createSubid == InvalidSubTransactionId) + { + /* + * Do nothing to cursors held over from a previous transaction + * (including ones we just froze in a previous cycle of this loop) + */ + continue; + } + else + { + /* Zap all non-holdable portals */ + PortalDrop(portal, true); + + /* Report we changed state */ + result = true; + } + + /* + * After either freezing or dropping a portal, we have to restart the + * iteration, because we could have invoked user-defined code that + * caused a drop of the next portal in the hash chain. + */ + hash_seq_term(&status); + hash_seq_init(&status, PortalHashTable); + } + + return result; +} + +/* + * Abort processing for portals. + * + * At this point we run the cleanup hook if present, but we can't release the + * portal's memory until the cleanup call. + */ +void +AtAbort_Portals(void) +{ + HASH_SEQ_STATUS status; + PortalHashEnt *hentry; + + hash_seq_init(&status, PortalHashTable); + + while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL) + { + Portal portal = hentry->portal; + + /* + * When elog(FATAL) is progress, we need to set the active portal to + * failed, so that PortalCleanup() doesn't run the executor shutdown. + */ + if (portal->status == PORTAL_ACTIVE && shmem_exit_inprogress) + MarkPortalFailed(portal); + + /* + * Do nothing else to cursors held over from a previous transaction. + */ + if (portal->createSubid == InvalidSubTransactionId) + continue; + + /* + * Do nothing to auto-held cursors. This is similar to the case of a + * cursor from a previous transaction, but it could also be that the + * cursor was auto-held in this transaction, so it wants to live on. + */ + if (portal->autoHeld) + continue; + + /* + * If it was created in the current transaction, we can't do normal + * shutdown on a READY portal either; it might refer to objects + * created in the failed transaction. See comments in + * AtSubAbort_Portals. + */ + if (portal->status == PORTAL_READY) + MarkPortalFailed(portal); + + /* + * Allow portalcmds.c to clean up the state it knows about, if we + * haven't already. + */ + if (PointerIsValid(portal->cleanup)) + { + portal->cleanup(portal); + portal->cleanup = NULL; + } + + /* drop cached plan reference, if any */ + PortalReleaseCachedPlan(portal); + + /* + * Any resources belonging to the portal will be released in the + * upcoming transaction-wide cleanup; they will be gone before we run + * PortalDrop. + */ + portal->resowner = NULL; + + /* + * Although we can't delete the portal data structure proper, we can + * release any memory in subsidiary contexts, such as executor state. + * The cleanup hook was the last thing that might have needed data + * there. But leave active portals alone. + */ + if (portal->status != PORTAL_ACTIVE) + MemoryContextDeleteChildren(portal->portalContext); + } +} + +/* + * Post-abort cleanup for portals. + * + * Delete all portals not held over from prior transactions. */ +void +AtCleanup_Portals(void) +{ + HASH_SEQ_STATUS status; + PortalHashEnt *hentry; + + hash_seq_init(&status, PortalHashTable); + + while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL) + { + Portal portal = hentry->portal; + + /* + * Do not touch active portals --- this can only happen in the case of + * a multi-transaction command. + */ + if (portal->status == PORTAL_ACTIVE) + continue; + + /* + * Do nothing to cursors held over from a previous transaction or + * auto-held ones. + */ + if (portal->createSubid == InvalidSubTransactionId || portal->autoHeld) + { + Assert(portal->status != PORTAL_ACTIVE); + Assert(portal->resowner == NULL); + continue; + } + + /* + * If a portal is still pinned, forcibly unpin it. PortalDrop will not + * let us drop the portal otherwise. Whoever pinned the portal was + * interrupted by the abort too and won't try to use it anymore. + */ + if (portal->portalPinned) + portal->portalPinned = false; + + /* + * We had better not call any user-defined code during cleanup, so if + * the cleanup hook hasn't been run yet, too bad; we'll just skip it. + */ + if (PointerIsValid(portal->cleanup)) + { + elog(WARNING, "skipping cleanup for portal \"%s\"", portal->name); + portal->cleanup = NULL; + } + + /* Zap it. */ + PortalDrop(portal, false); + } +} + +/* + * Portal-related cleanup when we return to the main loop on error. + * + * This is different from the cleanup at transaction abort. Auto-held portals + * are cleaned up on error but not on transaction abort. + */ +void +PortalErrorCleanup(void) +{ + HASH_SEQ_STATUS status; + PortalHashEnt *hentry; + + hash_seq_init(&status, PortalHashTable); + + while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL) + { + Portal portal = hentry->portal; + + if (portal->autoHeld) + { + portal->portalPinned = false; + PortalDrop(portal, false); + } + } +} + +/* + * Pre-subcommit processing for portals. + * + * Reassign portals created or used in the current subtransaction to the + * parent subtransaction. + */ +void +AtSubCommit_Portals(SubTransactionId mySubid, + SubTransactionId parentSubid, + int parentLevel, + ResourceOwner parentXactOwner) +{ + HASH_SEQ_STATUS status; + PortalHashEnt *hentry; + + hash_seq_init(&status, PortalHashTable); + + while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL) + { + Portal portal = hentry->portal; + + if (portal->createSubid == mySubid) + { + portal->createSubid = parentSubid; + portal->createLevel = parentLevel; + if (portal->resowner) + ResourceOwnerNewParent(portal->resowner, parentXactOwner); + } + if (portal->activeSubid == mySubid) + portal->activeSubid = parentSubid; + } +} + +/* + * Subtransaction abort handling for portals. + * + * Deactivate portals created or used during the failed subtransaction. + * Note that per AtSubCommit_Portals, this will catch portals created/used + * in descendants of the subtransaction too. + * + * We don't destroy any portals here; that's done in AtSubCleanup_Portals. + */ +void +AtSubAbort_Portals(SubTransactionId mySubid, + SubTransactionId parentSubid, + ResourceOwner myXactOwner, + ResourceOwner parentXactOwner) +{ + HASH_SEQ_STATUS status; + PortalHashEnt *hentry; + + hash_seq_init(&status, PortalHashTable); + + while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL) + { + Portal portal = hentry->portal; + + /* Was it created in this subtransaction? */ + if (portal->createSubid != mySubid) + { + /* No, but maybe it was used in this subtransaction? */ + if (portal->activeSubid == mySubid) + { + /* Maintain activeSubid until the portal is removed */ + portal->activeSubid = parentSubid; + + /* + * A MarkPortalActive() caller ran an upper-level portal in + * this subtransaction and left the portal ACTIVE. This can't + * happen, but force the portal into FAILED state for the same + * reasons discussed below. + * + * We assume we can get away without forcing upper-level READY + * portals to fail, even if they were run and then suspended. + * In theory a suspended upper-level portal could have + * acquired some references to objects that are about to be + * destroyed, but there should be sufficient defenses against + * such cases: the portal's original query cannot contain such + * references, and any references within, say, cached plans of + * PL/pgSQL functions are not from active queries and should + * be protected by revalidation logic. + */ + if (portal->status == PORTAL_ACTIVE) + MarkPortalFailed(portal); + + /* + * Also, if we failed it during the current subtransaction + * (either just above, or earlier), reattach its resource + * owner to the current subtransaction's resource owner, so + * that any resources it still holds will be released while + * cleaning up this subtransaction. This prevents some corner + * cases wherein we might get Asserts or worse while cleaning + * up objects created during the current subtransaction + * (because they're still referenced within this portal). + */ + if (portal->status == PORTAL_FAILED && portal->resowner) + { + ResourceOwnerNewParent(portal->resowner, myXactOwner); + portal->resowner = NULL; + } + } + /* Done if it wasn't created in this subtransaction */ + continue; + } + + /* + * Force any live portals of my own subtransaction into FAILED state. + * We have to do this because they might refer to objects created or + * changed in the failed subtransaction, leading to crashes within + * ExecutorEnd when portalcmds.c tries to close down the portal. + * Currently, every MarkPortalActive() caller ensures it updates the + * portal status again before relinquishing control, so ACTIVE can't + * happen here. If it does happen, dispose the portal like existing + * MarkPortalActive() callers would. + */ + if (portal->status == PORTAL_READY || + portal->status == PORTAL_ACTIVE) + MarkPortalFailed(portal); + + /* + * Allow portalcmds.c to clean up the state it knows about, if we + * haven't already. + */ + if (PointerIsValid(portal->cleanup)) + { + portal->cleanup(portal); + portal->cleanup = NULL; + } + + /* drop cached plan reference, if any */ + PortalReleaseCachedPlan(portal); + + /* + * Any resources belonging to the portal will be released in the + * upcoming transaction-wide cleanup; they will be gone before we run + * PortalDrop. + */ + portal->resowner = NULL; + + /* + * Although we can't delete the portal data structure proper, we can + * release any memory in subsidiary contexts, such as executor state. + * The cleanup hook was the last thing that might have needed data + * there. + */ + MemoryContextDeleteChildren(portal->portalContext); + } +} + +/* + * Post-subabort cleanup for portals. + * + * Drop all portals created in the failed subtransaction (but note that + * we will not drop any that were reassigned to the parent above). + */ +void +AtSubCleanup_Portals(SubTransactionId mySubid) +{ + HASH_SEQ_STATUS status; + PortalHashEnt *hentry; + + hash_seq_init(&status, PortalHashTable); + + while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL) + { + Portal portal = hentry->portal; + + if (portal->createSubid != mySubid) + continue; + + /* + * If a portal is still pinned, forcibly unpin it. PortalDrop will not + * let us drop the portal otherwise. Whoever pinned the portal was + * interrupted by the abort too and won't try to use it anymore. + */ + if (portal->portalPinned) + portal->portalPinned = false; + + /* + * We had better not call any user-defined code during cleanup, so if + * the cleanup hook hasn't been run yet, too bad; we'll just skip it. + */ + if (PointerIsValid(portal->cleanup)) + { + elog(WARNING, "skipping cleanup for portal \"%s\"", portal->name); + portal->cleanup = NULL; + } + + /* Zap it. */ + PortalDrop(portal, false); + } +} + +/* Find all available cursors */ +Datum +pg_cursor(PG_FUNCTION_ARGS) +{ + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + HASH_SEQ_STATUS hash_seq; + PortalHashEnt *hentry; + + /* + * We put all the tuples into a tuplestore in one scan of the hashtable. + * This avoids any issue of the hashtable possibly changing between calls. + */ + InitMaterializedSRF(fcinfo, 0); + + hash_seq_init(&hash_seq, PortalHashTable); + while ((hentry = hash_seq_search(&hash_seq)) != NULL) + { + Portal portal = hentry->portal; + Datum values[6]; + bool nulls[6] = {0}; + + /* report only "visible" entries */ + if (!portal->visible) + continue; + + values[0] = CStringGetTextDatum(portal->name); + values[1] = CStringGetTextDatum(portal->sourceText); + values[2] = BoolGetDatum(portal->cursorOptions & CURSOR_OPT_HOLD); + values[3] = BoolGetDatum(portal->cursorOptions & CURSOR_OPT_BINARY); + values[4] = BoolGetDatum(portal->cursorOptions & CURSOR_OPT_SCROLL); + values[5] = TimestampTzGetDatum(portal->creation_time); + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + } + + return (Datum) 0; +} + +bool +ThereAreNoReadyPortals(void) +{ + HASH_SEQ_STATUS status; + PortalHashEnt *hentry; + + hash_seq_init(&status, PortalHashTable); + + while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL) + { + Portal portal = hentry->portal; + + if (portal->status == PORTAL_READY) + return false; + } + + return true; +} + +/* + * Hold all pinned portals. + * + * When initiating a COMMIT or ROLLBACK inside a procedure, this must be + * called to protect internally-generated cursors from being dropped during + * the transaction shutdown. Currently, SPI calls this automatically; PLs + * that initiate COMMIT or ROLLBACK some other way are on the hook to do it + * themselves. (Note that we couldn't do this in, say, AtAbort_Portals + * because we need to run user-defined code while persisting a portal. + * It's too late to do that once transaction abort has started.) + * + * We protect such portals by converting them to held cursors. We mark them + * as "auto-held" so that exception exit knows to clean them up. (In normal, + * non-exception code paths, the PL needs to clean such portals itself, since + * transaction end won't do it anymore; but that should be normal practice + * anyway.) + */ +void +HoldPinnedPortals(void) +{ + HASH_SEQ_STATUS status; + PortalHashEnt *hentry; + + hash_seq_init(&status, PortalHashTable); + + while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL) + { + Portal portal = hentry->portal; + + if (portal->portalPinned && !portal->autoHeld) + { + /* + * Doing transaction control, especially abort, inside a cursor + * loop that is not read-only, for example using UPDATE ... + * RETURNING, has weird semantics issues. Also, this + * implementation wouldn't work, because such portals cannot be + * held. (The core grammar enforces that only SELECT statements + * can drive a cursor, but for example PL/pgSQL does not restrict + * it.) + */ + if (portal->strategy != PORTAL_ONE_SELECT) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("cannot perform transaction commands inside a cursor loop that is not read-only"))); + + /* Verify it's in a suitable state to be held */ + if (portal->status != PORTAL_READY) + elog(ERROR, "pinned portal is not ready to be auto-held"); + + HoldPortal(portal); + portal->autoHeld = true; + } + } +} + +/* + * Drop the outer active snapshots for all portals, so that no snapshots + * remain active. + * + * Like HoldPinnedPortals, this must be called when initiating a COMMIT or + * ROLLBACK inside a procedure. This has to be separate from that since it + * should not be run until we're done with steps that are likely to fail. + * + * It's tempting to fold this into PreCommit_Portals, but to do so, we'd + * need to clean up snapshot management in VACUUM and perhaps other places. + */ +void +ForgetPortalSnapshots(void) +{ + HASH_SEQ_STATUS status; + PortalHashEnt *hentry; + int numPortalSnaps = 0; + int numActiveSnaps = 0; + + /* First, scan PortalHashTable and clear portalSnapshot fields */ + hash_seq_init(&status, PortalHashTable); + + while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL) + { + Portal portal = hentry->portal; + + if (portal->portalSnapshot != NULL) + { + portal->portalSnapshot = NULL; + numPortalSnaps++; + } + /* portal->holdSnapshot will be cleaned up in PreCommit_Portals */ + } + + /* + * Now, pop all the active snapshots, which should be just those that were + * portal snapshots. Ideally we'd drive this directly off the portal + * scan, but there's no good way to visit the portals in the correct + * order. So just cross-check after the fact. + */ + while (ActiveSnapshotSet()) + { + PopActiveSnapshot(); + numActiveSnaps++; + } + + if (numPortalSnaps != numActiveSnaps) + elog(ERROR, "portal snapshots (%d) did not account for all active snapshots (%d)", + numPortalSnaps, numActiveSnaps); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/mmgr/slab.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/mmgr/slab.c new file mode 100644 index 00000000000..718dd2ba03c --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/mmgr/slab.c @@ -0,0 +1,1097 @@ +/*------------------------------------------------------------------------- + * + * slab.c + * SLAB allocator definitions. + * + * SLAB is a MemoryContext implementation designed for cases where large + * numbers of equally-sized objects can be allocated and freed efficiently + * with minimal memory wastage and fragmentation. + * + * + * Portions Copyright (c) 2017-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/utils/mmgr/slab.c + * + * + * NOTE: + * The constant allocation size allows significant simplification and various + * optimizations over more general purpose allocators. The blocks are carved + * into chunks of exactly the right size, wasting only the space required to + * MAXALIGN the allocated chunks. + * + * Slab can also help reduce memory fragmentation in cases where longer-lived + * chunks remain stored on blocks while most of the other chunks have already + * been pfree'd. We give priority to putting new allocations into the + * "fullest" block. This help avoid having too many sparsely used blocks + * around and allows blocks to more easily become completely unused which + * allows them to be eventually free'd. + * + * We identify the "fullest" block to put new allocations on by using a block + * from the lowest populated element of the context's "blocklist" array. + * This is an array of dlists containing blocks which we partition by the + * number of free chunks which block has. Blocks with fewer free chunks are + * stored in a lower indexed dlist array slot. Full blocks go on the 0th + * element of the blocklist array. So that we don't have to have too many + * elements in the array, each dlist in the array is responsible for a range + * of free chunks. When a chunk is palloc'd or pfree'd we may need to move + * the block onto another dlist if the number of free chunks crosses the + * range boundary that the current list is responsible for. Having just a + * few blocklist elements reduces the number of times we must move the block + * onto another dlist element. + * + * We keep track of free chunks within each block by using a block-level free + * list. We consult this list when we allocate a new chunk in the block. + * The free list is a linked list, the head of which is pointed to with + * SlabBlock's freehead field. Each subsequent list item is stored in the + * free chunk's memory. We ensure chunks are large enough to store this + * address. + * + * When we allocate a new block, technically all chunks are free, however, to + * avoid having to write out the entire block to set the linked list for the + * free chunks for every chunk in the block, we instead store a pointer to + * the next "unused" chunk on the block and keep track of how many of these + * unused chunks there are. When a new block is malloc'd, all chunks are + * unused. The unused pointer starts with the first chunk on the block and + * as chunks are allocated, the unused pointer is incremented. As chunks are + * pfree'd, the unused pointer never goes backwards. The unused pointer can + * be thought of as a high watermark for the maximum number of chunks in the + * block which have been in use concurrently. When a chunk is pfree'd the + * chunk is put onto the head of the free list and the unused pointer is not + * changed. We only consume more unused chunks if we run out of free chunks + * on the free list. This method effectively gives priority to using + * previously used chunks over previously unused chunks, which should perform + * better due to CPU caching effects. + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "lib/ilist.h" +#include "utils/memdebug.h" +#include "utils/memutils.h" +#include "utils/memutils_memorychunk.h" +#include "utils/memutils_internal.h" + +#define Slab_BLOCKHDRSZ MAXALIGN(sizeof(SlabBlock)) + +#ifdef MEMORY_CONTEXT_CHECKING +/* + * Size of the memory required to store the SlabContext. + * MEMORY_CONTEXT_CHECKING builds need some extra memory for the isChunkFree + * array. + */ +#define Slab_CONTEXT_HDRSZ(chunksPerBlock) \ + (sizeof(SlabContext) + ((chunksPerBlock) * sizeof(bool))) +#else +#define Slab_CONTEXT_HDRSZ(chunksPerBlock) sizeof(SlabContext) +#endif + +/* + * The number of partitions to divide the blocklist into based their number of + * free chunks. There must be at least 2. + */ +#define SLAB_BLOCKLIST_COUNT 3 + +/* The maximum number of completely empty blocks to keep around for reuse. */ +#define SLAB_MAXIMUM_EMPTY_BLOCKS 10 + +/* + * SlabContext is a specialized implementation of MemoryContext. + */ +typedef struct SlabContext +{ + MemoryContextData header; /* Standard memory-context fields */ + /* Allocation parameters for this context: */ + Size chunkSize; /* the requested (non-aligned) chunk size */ + Size fullChunkSize; /* chunk size with chunk header and alignment */ + Size blockSize; /* the size to make each block of chunks */ + int32 chunksPerBlock; /* number of chunks that fit in 1 block */ + int32 curBlocklistIndex; /* index into the blocklist[] element + * containing the fullest, blocks */ +#ifdef MEMORY_CONTEXT_CHECKING + bool *isChunkFree; /* array to mark free chunks in a block during + * SlabCheck */ +#endif + + int32 blocklist_shift; /* number of bits to shift the nfree count + * by to get the index into blocklist[] */ + dclist_head emptyblocks; /* empty blocks to use up first instead of + * mallocing new blocks */ + + /* + * Blocks with free space, grouped by the number of free chunks they + * contain. Completely full blocks are stored in the 0th element. + * Completely empty blocks are stored in emptyblocks or free'd if we have + * enough empty blocks already. + */ + dlist_head blocklist[SLAB_BLOCKLIST_COUNT]; +} SlabContext; + +/* + * SlabBlock + * Structure of a single slab block. + * + * slab: pointer back to the owning MemoryContext + * nfree: number of chunks on the block which are unallocated + * nunused: number of chunks on the block unallocated and not on the block's + * freelist. + * freehead: linked-list header storing a pointer to the first free chunk on + * the block. Subsequent pointers are stored in the chunk's memory. NULL + * indicates the end of the list. + * unused: pointer to the next chunk which has yet to be used. + * node: doubly-linked list node for the context's blocklist + */ +typedef struct SlabBlock +{ + SlabContext *slab; /* owning context */ + int32 nfree; /* number of chunks on free + unused chunks */ + int32 nunused; /* number of unused chunks */ + MemoryChunk *freehead; /* pointer to the first free chunk */ + MemoryChunk *unused; /* pointer to the next unused chunk */ + dlist_node node; /* doubly-linked list for blocklist[] */ +} SlabBlock; + + +#define Slab_CHUNKHDRSZ sizeof(MemoryChunk) +#define SlabChunkGetPointer(chk) \ + ((void *) (((char *) (chk)) + sizeof(MemoryChunk))) + +/* + * SlabBlockGetChunk + * Obtain a pointer to the nth (0-based) chunk in the block + */ +#define SlabBlockGetChunk(slab, block, n) \ + ((MemoryChunk *) ((char *) (block) + Slab_BLOCKHDRSZ \ + + ((n) * (slab)->fullChunkSize))) + +#if defined(MEMORY_CONTEXT_CHECKING) || defined(USE_ASSERT_CHECKING) + +/* + * SlabChunkIndex + * Get the 0-based index of how many chunks into the block the given + * chunk is. +*/ +#define SlabChunkIndex(slab, block, chunk) \ + (((char *) (chunk) - (char *) SlabBlockGetChunk(slab, block, 0)) / \ + (slab)->fullChunkSize) + +/* + * SlabChunkMod + * A MemoryChunk should always be at an address which is a multiple of + * fullChunkSize starting from the 0th chunk position. This will return + * non-zero if it's not. + */ +#define SlabChunkMod(slab, block, chunk) \ + (((char *) (chunk) - (char *) SlabBlockGetChunk(slab, block, 0)) % \ + (slab)->fullChunkSize) + +#endif + +/* + * SlabIsValid + * True iff set is a valid slab allocation set. + */ +#define SlabIsValid(set) (PointerIsValid(set) && IsA(set, SlabContext)) + +/* + * SlabBlockIsValid + * True iff block is a valid block of slab allocation set. + */ +#define SlabBlockIsValid(block) \ + (PointerIsValid(block) && SlabIsValid((block)->slab)) + +/* + * SlabBlocklistIndex + * Determine the blocklist index that a block should be in for the given + * number of free chunks. + */ +static inline int32 +SlabBlocklistIndex(SlabContext *slab, int nfree) +{ + int32 index; + int32 blocklist_shift = slab->blocklist_shift; + + Assert(nfree >= 0 && nfree <= slab->chunksPerBlock); + + /* + * Determine the blocklist index based on the number of free chunks. We + * must ensure that 0 free chunks is dedicated to index 0. Everything + * else must be >= 1 and < SLAB_BLOCKLIST_COUNT. + * + * To make this as efficient as possible, we exploit some two's complement + * arithmetic where we reverse the sign before bit shifting. This results + * in an nfree of 0 using index 0 and anything non-zero staying non-zero. + * This is exploiting 0 and -0 being the same in two's complement. When + * we're done, we just need to flip the sign back over again for a + * positive index. + */ + index = -((-nfree) >> blocklist_shift); + + if (nfree == 0) + Assert(index == 0); + else + Assert(index >= 1 && index < SLAB_BLOCKLIST_COUNT); + + return index; +} + +/* + * SlabFindNextBlockListIndex + * Search blocklist for blocks which have free chunks and return the + * index of the blocklist found containing at least 1 block with free + * chunks. If no block can be found we return 0. + * + * Note: We give priority to fuller blocks so that these are filled before + * emptier blocks. This is done to increase the chances that mostly-empty + * blocks will eventually become completely empty so they can be free'd. + */ +static int32 +SlabFindNextBlockListIndex(SlabContext *slab) +{ + /* start at 1 as blocklist[0] is for full blocks. */ + for (int i = 1; i < SLAB_BLOCKLIST_COUNT; i++) + { + /* return the first found non-empty index */ + if (!dlist_is_empty(&slab->blocklist[i])) + return i; + } + + /* no blocks with free space */ + return 0; +} + +/* + * SlabGetNextFreeChunk + * Return the next free chunk in block and update the block to account + * for the returned chunk now being used. + */ +static inline MemoryChunk * +SlabGetNextFreeChunk(SlabContext *slab, SlabBlock *block) +{ + MemoryChunk *chunk; + + Assert(block->nfree > 0); + + if (block->freehead != NULL) + { + chunk = block->freehead; + + /* + * Pop the chunk from the linked list of free chunks. The pointer to + * the next free chunk is stored in the chunk itself. + */ + VALGRIND_MAKE_MEM_DEFINED(SlabChunkGetPointer(chunk), sizeof(MemoryChunk *)); + block->freehead = *(MemoryChunk **) SlabChunkGetPointer(chunk); + + /* check nothing stomped on the free chunk's memory */ + Assert(block->freehead == NULL || + (block->freehead >= SlabBlockGetChunk(slab, block, 0) && + block->freehead <= SlabBlockGetChunk(slab, block, slab->chunksPerBlock - 1) && + SlabChunkMod(slab, block, block->freehead) == 0)); + } + else + { + Assert(block->nunused > 0); + + chunk = block->unused; + block->unused = (MemoryChunk *) (((char *) block->unused) + slab->fullChunkSize); + block->nunused--; + } + + block->nfree--; + + return chunk; +} + +/* + * SlabContextCreate + * Create a new Slab context. + * + * parent: parent context, or NULL if top-level context + * name: name of context (must be statically allocated) + * blockSize: allocation block size + * chunkSize: allocation chunk size + * + * The MAXALIGN(chunkSize) may not exceed MEMORYCHUNK_MAX_VALUE + */ +MemoryContext +SlabContextCreate(MemoryContext parent, + const char *name, + Size blockSize, + Size chunkSize) +{ + int chunksPerBlock; + Size fullChunkSize; + SlabContext *slab; + int i; + + /* ensure MemoryChunk's size is properly maxaligned */ + StaticAssertDecl(Slab_CHUNKHDRSZ == MAXALIGN(Slab_CHUNKHDRSZ), + "sizeof(MemoryChunk) is not maxaligned"); + Assert(MAXALIGN(chunkSize) <= MEMORYCHUNK_MAX_VALUE); + + /* + * Ensure there's enough space to store the pointer to the next free chunk + * in the memory of the (otherwise) unused allocation. + */ + if (chunkSize < sizeof(MemoryChunk *)) + chunkSize = sizeof(MemoryChunk *); + + /* length of the maxaligned chunk including the chunk header */ +#ifdef MEMORY_CONTEXT_CHECKING + /* ensure there's always space for the sentinel byte */ + fullChunkSize = Slab_CHUNKHDRSZ + MAXALIGN(chunkSize + 1); +#else + fullChunkSize = Slab_CHUNKHDRSZ + MAXALIGN(chunkSize); +#endif + + /* compute the number of chunks that will fit on each block */ + chunksPerBlock = (blockSize - Slab_BLOCKHDRSZ) / fullChunkSize; + + /* Make sure the block can store at least one chunk. */ + if (chunksPerBlock == 0) + elog(ERROR, "block size %zu for slab is too small for %zu-byte chunks", + blockSize, chunkSize); + + + + slab = (SlabContext *) malloc(Slab_CONTEXT_HDRSZ(chunksPerBlock)); + if (slab == NULL) + { + MemoryContextStats(TopMemoryContext); + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"), + errdetail("Failed while creating memory context \"%s\".", + name))); + } + + /* + * Avoid writing code that can fail between here and MemoryContextCreate; + * we'd leak the header if we ereport in this stretch. + */ + + /* Fill in SlabContext-specific header fields */ + slab->chunkSize = chunkSize; + slab->fullChunkSize = fullChunkSize; + slab->blockSize = blockSize; + slab->chunksPerBlock = chunksPerBlock; + slab->curBlocklistIndex = 0; + + /* + * Compute a shift that guarantees that shifting chunksPerBlock with it is + * < SLAB_BLOCKLIST_COUNT - 1. The reason that we subtract 1 from + * SLAB_BLOCKLIST_COUNT in this calculation is that we reserve the 0th + * blocklist element for blocks which have no free chunks. + * + * We calculate the number of bits to shift by rather than a divisor to + * divide by as performing division each time we need to find the + * blocklist index would be much slower. + */ + slab->blocklist_shift = 0; + while ((slab->chunksPerBlock >> slab->blocklist_shift) >= (SLAB_BLOCKLIST_COUNT - 1)) + slab->blocklist_shift++; + + /* initialize the list to store empty blocks to be reused */ + dclist_init(&slab->emptyblocks); + + /* initialize each blocklist slot */ + for (i = 0; i < SLAB_BLOCKLIST_COUNT; i++) + dlist_init(&slab->blocklist[i]); + +#ifdef MEMORY_CONTEXT_CHECKING + /* set the isChunkFree pointer right after the end of the context */ + slab->isChunkFree = (bool *) ((char *) slab + sizeof(SlabContext)); +#endif + + /* Finally, do the type-independent part of context creation */ + MemoryContextCreate((MemoryContext) slab, + T_SlabContext, + MCTX_SLAB_ID, + parent, + name); + + return (MemoryContext) slab; +} + +/* + * SlabReset + * Frees all memory which is allocated in the given set. + * + * The code simply frees all the blocks in the context - we don't keep any + * keeper blocks or anything like that. + */ +void +SlabReset(MemoryContext context) +{ + SlabContext *slab = (SlabContext *) context; + dlist_mutable_iter miter; + int i; + + Assert(SlabIsValid(slab)); + +#ifdef MEMORY_CONTEXT_CHECKING + /* Check for corruption and leaks before freeing */ + SlabCheck(context); +#endif + + /* release any retained empty blocks */ + dclist_foreach_modify(miter, &slab->emptyblocks) + { + SlabBlock *block = dlist_container(SlabBlock, node, miter.cur); + + dclist_delete_from(&slab->emptyblocks, miter.cur); + +#ifdef CLOBBER_FREED_MEMORY + wipe_mem(block, slab->blockSize); +#endif + free(block); + context->mem_allocated -= slab->blockSize; + } + + /* walk over blocklist and free the blocks */ + for (i = 0; i < SLAB_BLOCKLIST_COUNT; i++) + { + dlist_foreach_modify(miter, &slab->blocklist[i]) + { + SlabBlock *block = dlist_container(SlabBlock, node, miter.cur); + + dlist_delete(miter.cur); + +#ifdef CLOBBER_FREED_MEMORY + wipe_mem(block, slab->blockSize); +#endif + free(block); + context->mem_allocated -= slab->blockSize; + } + } + + slab->curBlocklistIndex = 0; + + Assert(context->mem_allocated == 0); +} + +/* + * SlabDelete + * Free all memory which is allocated in the given context. + */ +void +SlabDelete(MemoryContext context) +{ + /* Reset to release all the SlabBlocks */ + SlabReset(context); + /* And free the context header */ + free(context); +} + +/* + * SlabAlloc + * Returns a pointer to allocated memory of given size or NULL if + * request could not be completed; memory is added to the slab. + */ +void * +SlabAlloc(MemoryContext context, Size size) +{ + SlabContext *slab = (SlabContext *) context; + SlabBlock *block; + MemoryChunk *chunk; + + Assert(SlabIsValid(slab)); + + /* sanity check that this is pointing to a valid blocklist */ + Assert(slab->curBlocklistIndex >= 0); + Assert(slab->curBlocklistIndex <= SlabBlocklistIndex(slab, slab->chunksPerBlock)); + + /* make sure we only allow correct request size */ + if (unlikely(size != slab->chunkSize)) + elog(ERROR, "unexpected alloc chunk size %zu (expected %zu)", + size, slab->chunkSize); + + /* + * Handle the case when there are no partially filled blocks available. + * SlabFree() will have updated the curBlocklistIndex setting it to zero + * to indicate that it has freed the final block. Also later in + * SlabAlloc() we will set the curBlocklistIndex to zero if we end up + * filling the final block. + */ + if (unlikely(slab->curBlocklistIndex == 0)) + { + dlist_head *blocklist; + int blocklist_idx; + + /* to save allocating a new one, first check the empty blocks list */ + if (dclist_count(&slab->emptyblocks) > 0) + { + dlist_node *node = dclist_pop_head_node(&slab->emptyblocks); + + block = dlist_container(SlabBlock, node, node); + + /* + * SlabFree() should have left this block in a valid state with + * all chunks free. Ensure that's the case. + */ + Assert(block->nfree == slab->chunksPerBlock); + + /* fetch the next chunk from this block */ + chunk = SlabGetNextFreeChunk(slab, block); + } + else + { + block = (SlabBlock *) malloc(slab->blockSize); + + if (unlikely(block == NULL)) + return NULL; + + block->slab = slab; + context->mem_allocated += slab->blockSize; + + /* use the first chunk in the new block */ + chunk = SlabBlockGetChunk(slab, block, 0); + + block->nfree = slab->chunksPerBlock - 1; + block->unused = SlabBlockGetChunk(slab, block, 1); + block->freehead = NULL; + block->nunused = slab->chunksPerBlock - 1; + } + + /* find the blocklist element for storing blocks with 1 used chunk */ + blocklist_idx = SlabBlocklistIndex(slab, block->nfree); + blocklist = &slab->blocklist[blocklist_idx]; + + /* this better be empty. We just added a block thinking it was */ + Assert(dlist_is_empty(blocklist)); + + dlist_push_head(blocklist, &block->node); + + slab->curBlocklistIndex = blocklist_idx; + } + else + { + dlist_head *blocklist = &slab->blocklist[slab->curBlocklistIndex]; + int new_blocklist_idx; + + Assert(!dlist_is_empty(blocklist)); + + /* grab the block from the blocklist */ + block = dlist_head_element(SlabBlock, node, blocklist); + + /* make sure we actually got a valid block, with matching nfree */ + Assert(block != NULL); + Assert(slab->curBlocklistIndex == SlabBlocklistIndex(slab, block->nfree)); + Assert(block->nfree > 0); + + /* fetch the next chunk from this block */ + chunk = SlabGetNextFreeChunk(slab, block); + + /* get the new blocklist index based on the new free chunk count */ + new_blocklist_idx = SlabBlocklistIndex(slab, block->nfree); + + /* + * Handle the case where the blocklist index changes. This also deals + * with blocks becoming full as only full blocks go at index 0. + */ + if (unlikely(slab->curBlocklistIndex != new_blocklist_idx)) + { + dlist_delete_from(blocklist, &block->node); + dlist_push_head(&slab->blocklist[new_blocklist_idx], &block->node); + + if (dlist_is_empty(blocklist)) + slab->curBlocklistIndex = SlabFindNextBlockListIndex(slab); + } + } + + /* + * Check that the chunk pointer is actually somewhere on the block and is + * aligned as expected. + */ + Assert(chunk >= SlabBlockGetChunk(slab, block, 0)); + Assert(chunk <= SlabBlockGetChunk(slab, block, slab->chunksPerBlock - 1)); + Assert(SlabChunkMod(slab, block, chunk) == 0); + + /* Prepare to initialize the chunk header. */ + VALGRIND_MAKE_MEM_UNDEFINED(chunk, Slab_CHUNKHDRSZ); + + MemoryChunkSetHdrMask(chunk, block, MAXALIGN(slab->chunkSize), + MCTX_SLAB_ID); +#ifdef MEMORY_CONTEXT_CHECKING + /* slab mark to catch clobber of "unused" space */ + Assert(slab->chunkSize < (slab->fullChunkSize - Slab_CHUNKHDRSZ)); + set_sentinel(MemoryChunkGetPointer(chunk), size); + VALGRIND_MAKE_MEM_NOACCESS(((char *) chunk) + + Slab_CHUNKHDRSZ + slab->chunkSize, + slab->fullChunkSize - + (slab->chunkSize + Slab_CHUNKHDRSZ)); +#endif + +#ifdef RANDOMIZE_ALLOCATED_MEMORY + /* fill the allocated space with junk */ + randomize_mem((char *) MemoryChunkGetPointer(chunk), size); +#endif + + /* Disallow access to the chunk header. */ + VALGRIND_MAKE_MEM_NOACCESS(chunk, Slab_CHUNKHDRSZ); + + return MemoryChunkGetPointer(chunk); +} + +/* + * SlabFree + * Frees allocated memory; memory is removed from the slab. + */ +void +SlabFree(void *pointer) +{ + MemoryChunk *chunk = PointerGetMemoryChunk(pointer); + SlabBlock *block; + SlabContext *slab; + int curBlocklistIdx; + int newBlocklistIdx; + + /* Allow access to the chunk header. */ + VALGRIND_MAKE_MEM_DEFINED(chunk, Slab_CHUNKHDRSZ); + + block = MemoryChunkGetBlock(chunk); + + /* + * For speed reasons we just Assert that the referenced block is good. + * Future field experience may show that this Assert had better become a + * regular runtime test-and-elog check. + */ + Assert(SlabBlockIsValid(block)); + slab = block->slab; + +#ifdef MEMORY_CONTEXT_CHECKING + /* Test for someone scribbling on unused space in chunk */ + Assert(slab->chunkSize < (slab->fullChunkSize - Slab_CHUNKHDRSZ)); + if (!sentinel_ok(pointer, slab->chunkSize)) + elog(WARNING, "detected write past chunk end in %s %p", + slab->header.name, chunk); +#endif + + /* push this chunk onto the head of the block's free list */ + *(MemoryChunk **) pointer = block->freehead; + block->freehead = chunk; + + block->nfree++; + + Assert(block->nfree > 0); + Assert(block->nfree <= slab->chunksPerBlock); + +#ifdef CLOBBER_FREED_MEMORY + /* don't wipe the free list MemoryChunk pointer stored in the chunk */ + wipe_mem((char *) pointer + sizeof(MemoryChunk *), + slab->chunkSize - sizeof(MemoryChunk *)); +#endif + + curBlocklistIdx = SlabBlocklistIndex(slab, block->nfree - 1); + newBlocklistIdx = SlabBlocklistIndex(slab, block->nfree); + + /* + * Check if the block needs to be moved to another element on the + * blocklist based on it now having 1 more free chunk. + */ + if (unlikely(curBlocklistIdx != newBlocklistIdx)) + { + /* do the move */ + dlist_delete_from(&slab->blocklist[curBlocklistIdx], &block->node); + dlist_push_head(&slab->blocklist[newBlocklistIdx], &block->node); + + /* + * The blocklist[curBlocklistIdx] may now be empty or we may now be + * able to use a lower-element blocklist. We'll need to redetermine + * what the slab->curBlocklistIndex is if the current blocklist was + * changed or if a lower element one was changed. We must ensure we + * use the list with the fullest block(s). + */ + if (slab->curBlocklistIndex >= curBlocklistIdx) + { + slab->curBlocklistIndex = SlabFindNextBlockListIndex(slab); + + /* + * We know there must be a block with at least 1 unused chunk as + * we just pfree'd one. Ensure curBlocklistIndex reflects this. + */ + Assert(slab->curBlocklistIndex > 0); + } + } + + /* Handle when a block becomes completely empty */ + if (unlikely(block->nfree == slab->chunksPerBlock)) + { + /* remove the block */ + dlist_delete_from(&slab->blocklist[newBlocklistIdx], &block->node); + + /* + * To avoid thrashing malloc/free, we keep a list of empty blocks that + * we can reuse again instead of having to malloc a new one. + */ + if (dclist_count(&slab->emptyblocks) < SLAB_MAXIMUM_EMPTY_BLOCKS) + dclist_push_head(&slab->emptyblocks, &block->node); + else + { + /* + * When we have enough empty blocks stored already, we actually + * free the block. + */ +#ifdef CLOBBER_FREED_MEMORY + wipe_mem(block, slab->blockSize); +#endif + free(block); + slab->header.mem_allocated -= slab->blockSize; + } + + /* + * Check if we need to reset the blocklist index. This is required + * when the blocklist this block is on has become completely empty. + */ + if (slab->curBlocklistIndex == newBlocklistIdx && + dlist_is_empty(&slab->blocklist[newBlocklistIdx])) + slab->curBlocklistIndex = SlabFindNextBlockListIndex(slab); + } +} + +/* + * SlabRealloc + * Change the allocated size of a chunk. + * + * As Slab is designed for allocating equally-sized chunks of memory, it can't + * do an actual chunk size change. We try to be gentle and allow calls with + * exactly the same size, as in that case we can simply return the same + * chunk. When the size differs, we throw an error. + * + * We could also allow requests with size < chunkSize. That however seems + * rather pointless - Slab is meant for chunks of constant size, and moreover + * realloc is usually used to enlarge the chunk. + */ +void * +SlabRealloc(void *pointer, Size size) +{ + MemoryChunk *chunk = PointerGetMemoryChunk(pointer); + SlabBlock *block; + SlabContext *slab; + + /* Allow access to the chunk header. */ + VALGRIND_MAKE_MEM_DEFINED(chunk, Slab_CHUNKHDRSZ); + + block = MemoryChunkGetBlock(chunk); + + /* Disallow access to the chunk header. */ + VALGRIND_MAKE_MEM_NOACCESS(chunk, Slab_CHUNKHDRSZ); + + /* + * Try to verify that we have a sane block pointer: the block header + * should reference a slab context. (We use a test-and-elog, not just + * Assert, because it seems highly likely that we're here in error in the + * first place.) + */ + if (!SlabBlockIsValid(block)) + elog(ERROR, "could not find block containing chunk %p", chunk); + slab = block->slab; + + /* can't do actual realloc with slab, but let's try to be gentle */ + if (size == slab->chunkSize) + return pointer; + + elog(ERROR, "slab allocator does not support realloc()"); + return NULL; /* keep compiler quiet */ +} + +/* + * SlabGetChunkContext + * Return the MemoryContext that 'pointer' belongs to. + */ +MemoryContext +SlabGetChunkContext(void *pointer) +{ + MemoryChunk *chunk = PointerGetMemoryChunk(pointer); + SlabBlock *block; + + /* Allow access to the chunk header. */ + VALGRIND_MAKE_MEM_DEFINED(chunk, Slab_CHUNKHDRSZ); + + block = MemoryChunkGetBlock(chunk); + + /* Disallow access to the chunk header. */ + VALGRIND_MAKE_MEM_NOACCESS(chunk, Slab_CHUNKHDRSZ); + + Assert(SlabBlockIsValid(block)); + + return &block->slab->header; +} + +/* + * SlabGetChunkSpace + * Given a currently-allocated chunk, determine the total space + * it occupies (including all memory-allocation overhead). + */ +Size +SlabGetChunkSpace(void *pointer) +{ + MemoryChunk *chunk = PointerGetMemoryChunk(pointer); + SlabBlock *block; + SlabContext *slab; + + /* Allow access to the chunk header. */ + VALGRIND_MAKE_MEM_DEFINED(chunk, Slab_CHUNKHDRSZ); + + block = MemoryChunkGetBlock(chunk); + + /* Disallow access to the chunk header. */ + VALGRIND_MAKE_MEM_NOACCESS(chunk, Slab_CHUNKHDRSZ); + + Assert(SlabBlockIsValid(block)); + slab = block->slab; + + return slab->fullChunkSize; +} + +/* + * SlabIsEmpty + * Is the slab empty of any allocated space? + */ +bool +SlabIsEmpty(MemoryContext context) +{ + Assert(SlabIsValid((SlabContext *) context)); + + return (context->mem_allocated == 0); +} + +/* + * SlabStats + * Compute stats about memory consumption of a Slab context. + * + * printfunc: if not NULL, pass a human-readable stats string to this. + * passthru: pass this pointer through to printfunc. + * totals: if not NULL, add stats about this context into *totals. + * print_to_stderr: print stats to stderr if true, elog otherwise. + */ +void +SlabStats(MemoryContext context, + MemoryStatsPrintFunc printfunc, void *passthru, + MemoryContextCounters *totals, + bool print_to_stderr) +{ + SlabContext *slab = (SlabContext *) context; + Size nblocks = 0; + Size freechunks = 0; + Size totalspace; + Size freespace = 0; + int i; + + Assert(SlabIsValid(slab)); + + /* Include context header in totalspace */ + totalspace = Slab_CONTEXT_HDRSZ(slab->chunksPerBlock); + + /* Add the space consumed by blocks in the emptyblocks list */ + totalspace += dclist_count(&slab->emptyblocks) * slab->blockSize; + + for (i = 0; i < SLAB_BLOCKLIST_COUNT; i++) + { + dlist_iter iter; + + dlist_foreach(iter, &slab->blocklist[i]) + { + SlabBlock *block = dlist_container(SlabBlock, node, iter.cur); + + nblocks++; + totalspace += slab->blockSize; + freespace += slab->fullChunkSize * block->nfree; + freechunks += block->nfree; + } + } + + if (printfunc) + { + char stats_string[200]; + + /* XXX should we include free chunks on empty blocks? */ + snprintf(stats_string, sizeof(stats_string), + "%zu total in %zu blocks; %u empty blocks; %zu free (%zu chunks); %zu used", + totalspace, nblocks, dclist_count(&slab->emptyblocks), + freespace, freechunks, totalspace - freespace); + printfunc(context, passthru, stats_string, print_to_stderr); + } + + if (totals) + { + totals->nblocks += nblocks; + totals->freechunks += freechunks; + totals->totalspace += totalspace; + totals->freespace += freespace; + } +} + + +#ifdef MEMORY_CONTEXT_CHECKING + +/* + * SlabCheck + * Walk through all blocks looking for inconsistencies. + * + * NOTE: report errors as WARNING, *not* ERROR or FATAL. Otherwise you'll + * find yourself in an infinite loop when trouble occurs, because this + * routine will be entered again when elog cleanup tries to release memory! + */ +void +SlabCheck(MemoryContext context) +{ + SlabContext *slab = (SlabContext *) context; + int i; + int nblocks = 0; + const char *name = slab->header.name; + dlist_iter iter; + + Assert(SlabIsValid(slab)); + Assert(slab->chunksPerBlock > 0); + + /* + * Have a look at the empty blocks. These should have all their chunks + * marked as free. Ensure that's the case. + */ + dclist_foreach(iter, &slab->emptyblocks) + { + SlabBlock *block = dlist_container(SlabBlock, node, iter.cur); + + if (block->nfree != slab->chunksPerBlock) + elog(WARNING, "problem in slab %s: empty block %p should have %d free chunks but has %d chunks free", + name, block, slab->chunksPerBlock, block->nfree); + } + + /* walk the non-empty block lists */ + for (i = 0; i < SLAB_BLOCKLIST_COUNT; i++) + { + int j, + nfree; + + /* walk all blocks on this blocklist */ + dlist_foreach(iter, &slab->blocklist[i]) + { + SlabBlock *block = dlist_container(SlabBlock, node, iter.cur); + MemoryChunk *cur_chunk; + + /* + * Make sure the number of free chunks (in the block header) + * matches the position in the blocklist. + */ + if (SlabBlocklistIndex(slab, block->nfree) != i) + elog(WARNING, "problem in slab %s: block %p is on blocklist %d but should be on blocklist %d", + name, block, i, SlabBlocklistIndex(slab, block->nfree)); + + /* make sure the block is not empty */ + if (block->nfree >= slab->chunksPerBlock) + elog(WARNING, "problem in slab %s: empty block %p incorrectly stored on blocklist element %d", + name, block, i); + + /* make sure the slab pointer correctly points to this context */ + if (block->slab != slab) + elog(WARNING, "problem in slab %s: bogus slab link in block %p", + name, block); + + /* reset the array of free chunks for this block */ + memset(slab->isChunkFree, 0, (slab->chunksPerBlock * sizeof(bool))); + nfree = 0; + + /* walk through the block's free list chunks */ + cur_chunk = block->freehead; + while (cur_chunk != NULL) + { + int chunkidx = SlabChunkIndex(slab, block, cur_chunk); + + /* + * Ensure the free list link points to something on the block + * at an address aligned according to the full chunk size. + */ + if (cur_chunk < SlabBlockGetChunk(slab, block, 0) || + cur_chunk > SlabBlockGetChunk(slab, block, slab->chunksPerBlock - 1) || + SlabChunkMod(slab, block, cur_chunk) != 0) + elog(WARNING, "problem in slab %s: bogus free list link %p in block %p", + name, cur_chunk, block); + + /* count the chunk and mark it free on the free chunk array */ + nfree++; + slab->isChunkFree[chunkidx] = true; + + /* read pointer of the next free chunk */ + VALGRIND_MAKE_MEM_DEFINED(MemoryChunkGetPointer(cur_chunk), sizeof(MemoryChunk *)); + cur_chunk = *(MemoryChunk **) SlabChunkGetPointer(cur_chunk); + } + + /* check that the unused pointer matches what nunused claims */ + if (SlabBlockGetChunk(slab, block, slab->chunksPerBlock - block->nunused) != + block->unused) + elog(WARNING, "problem in slab %s: mismatch detected between nunused chunks and unused pointer in block %p", + name, block); + + /* + * count the remaining free chunks that have yet to make it onto + * the block's free list. + */ + cur_chunk = block->unused; + for (j = 0; j < block->nunused; j++) + { + int chunkidx = SlabChunkIndex(slab, block, cur_chunk); + + + /* count the chunk as free and mark it as so in the array */ + nfree++; + if (chunkidx < slab->chunksPerBlock) + slab->isChunkFree[chunkidx] = true; + + /* move forward 1 chunk */ + cur_chunk = (MemoryChunk *) (((char *) cur_chunk) + slab->fullChunkSize); + } + + for (j = 0; j < slab->chunksPerBlock; j++) + { + if (!slab->isChunkFree[j]) + { + MemoryChunk *chunk = SlabBlockGetChunk(slab, block, j); + SlabBlock *chunkblock; + + /* Allow access to the chunk header. */ + VALGRIND_MAKE_MEM_DEFINED(chunk, Slab_CHUNKHDRSZ); + + chunkblock = (SlabBlock *) MemoryChunkGetBlock(chunk); + + /* Disallow access to the chunk header. */ + VALGRIND_MAKE_MEM_NOACCESS(chunk, Slab_CHUNKHDRSZ); + + /* + * check the chunk's blockoffset correctly points back to + * the block + */ + if (chunkblock != block) + elog(WARNING, "problem in slab %s: bogus block link in block %p, chunk %p", + name, block, chunk); + + /* check the sentinel byte is intact */ + Assert(slab->chunkSize < (slab->fullChunkSize - Slab_CHUNKHDRSZ)); + if (!sentinel_ok(chunk, Slab_CHUNKHDRSZ + slab->chunkSize)) + elog(WARNING, "problem in slab %s: detected write past chunk end in block %p, chunk %p", + name, block, chunk); + } + } + + /* + * Make sure we got the expected number of free chunks (as tracked + * in the block header). + */ + if (nfree != block->nfree) + elog(WARNING, "problem in slab %s: nfree in block %p is %d but %d chunk were found as free", + name, block, block->nfree, nfree); + + nblocks++; + } + } + + /* the stored empty blocks are tracked in mem_allocated too */ + nblocks += dclist_count(&slab->emptyblocks); + + Assert(nblocks * slab->blockSize == context->mem_allocated); +} + +#endif /* MEMORY_CONTEXT_CHECKING */ diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/probes.h b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/probes.h new file mode 100644 index 00000000000..f600a965d20 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/probes.h @@ -0,0 +1,114 @@ +#define TRACE_POSTGRESQL_TRANSACTION_START(INT1) do {} while (0) +#define TRACE_POSTGRESQL_TRANSACTION_START_ENABLED() (0) +#define TRACE_POSTGRESQL_TRANSACTION_COMMIT(INT1) do {} while (0) +#define TRACE_POSTGRESQL_TRANSACTION_COMMIT_ENABLED() (0) +#define TRACE_POSTGRESQL_TRANSACTION_ABORT(INT1) do {} while (0) +#define TRACE_POSTGRESQL_TRANSACTION_ABORT_ENABLED() (0) +#define TRACE_POSTGRESQL_LWLOCK_ACQUIRE(INT1, INT2) do {} while (0) +#define TRACE_POSTGRESQL_LWLOCK_ACQUIRE_ENABLED() (0) +#define TRACE_POSTGRESQL_LWLOCK_RELEASE(INT1) do {} while (0) +#define TRACE_POSTGRESQL_LWLOCK_RELEASE_ENABLED() (0) +#define TRACE_POSTGRESQL_LWLOCK_WAIT_START(INT1, INT2) do {} while (0) +#define TRACE_POSTGRESQL_LWLOCK_WAIT_START_ENABLED() (0) +#define TRACE_POSTGRESQL_LWLOCK_WAIT_DONE(INT1, INT2) do {} while (0) +#define TRACE_POSTGRESQL_LWLOCK_WAIT_DONE_ENABLED() (0) +#define TRACE_POSTGRESQL_LWLOCK_CONDACQUIRE(INT1, INT2) do {} while (0) +#define TRACE_POSTGRESQL_LWLOCK_CONDACQUIRE_ENABLED() (0) +#define TRACE_POSTGRESQL_LWLOCK_CONDACQUIRE_FAIL(INT1, INT2) do {} while (0) +#define TRACE_POSTGRESQL_LWLOCK_CONDACQUIRE_FAIL_ENABLED() (0) +#define TRACE_POSTGRESQL_LWLOCK_ACQUIRE_OR_WAIT(INT1, INT2) do {} while (0) +#define TRACE_POSTGRESQL_LWLOCK_ACQUIRE_OR_WAIT_ENABLED() (0) +#define TRACE_POSTGRESQL_LWLOCK_ACQUIRE_OR_WAIT_FAIL(INT1, INT2) do {} while (0) +#define TRACE_POSTGRESQL_LWLOCK_ACQUIRE_OR_WAIT_FAIL_ENABLED() (0) +#define TRACE_POSTGRESQL_LOCK_WAIT_START(INT1, INT2, INT3, INT4, INT5, INT6) do {} while (0) +#define TRACE_POSTGRESQL_LOCK_WAIT_START_ENABLED() (0) +#define TRACE_POSTGRESQL_LOCK_WAIT_DONE(INT1, INT2, INT3, INT4, INT5, INT6) do {} while (0) +#define TRACE_POSTGRESQL_LOCK_WAIT_DONE_ENABLED() (0) +#define TRACE_POSTGRESQL_QUERY_PARSE_START(INT1) do {} while (0) +#define TRACE_POSTGRESQL_QUERY_PARSE_START_ENABLED() (0) +#define TRACE_POSTGRESQL_QUERY_PARSE_DONE(INT1) do {} while (0) +#define TRACE_POSTGRESQL_QUERY_PARSE_DONE_ENABLED() (0) +#define TRACE_POSTGRESQL_QUERY_REWRITE_START(INT1) do {} while (0) +#define TRACE_POSTGRESQL_QUERY_REWRITE_START_ENABLED() (0) +#define TRACE_POSTGRESQL_QUERY_REWRITE_DONE(INT1) do {} while (0) +#define TRACE_POSTGRESQL_QUERY_REWRITE_DONE_ENABLED() (0) +#define TRACE_POSTGRESQL_QUERY_PLAN_START() do {} while (0) +#define TRACE_POSTGRESQL_QUERY_PLAN_START_ENABLED() (0) +#define TRACE_POSTGRESQL_QUERY_PLAN_DONE() do {} while (0) +#define TRACE_POSTGRESQL_QUERY_PLAN_DONE_ENABLED() (0) +#define TRACE_POSTGRESQL_QUERY_EXECUTE_START() do {} while (0) +#define TRACE_POSTGRESQL_QUERY_EXECUTE_START_ENABLED() (0) +#define TRACE_POSTGRESQL_QUERY_EXECUTE_DONE() do {} while (0) +#define TRACE_POSTGRESQL_QUERY_EXECUTE_DONE_ENABLED() (0) +#define TRACE_POSTGRESQL_QUERY_START(INT1) do {} while (0) +#define TRACE_POSTGRESQL_QUERY_START_ENABLED() (0) +#define TRACE_POSTGRESQL_QUERY_DONE(INT1) do {} while (0) +#define TRACE_POSTGRESQL_QUERY_DONE_ENABLED() (0) +#define TRACE_POSTGRESQL_STATEMENT_STATUS(INT1) do {} while (0) +#define TRACE_POSTGRESQL_STATEMENT_STATUS_ENABLED() (0) +#define TRACE_POSTGRESQL_SORT_START(INT1, INT2, INT3, INT4, INT5, INT6) do {} while (0) +#define TRACE_POSTGRESQL_SORT_START_ENABLED() (0) +#define TRACE_POSTGRESQL_SORT_DONE(INT1, INT2) do {} while (0) +#define TRACE_POSTGRESQL_SORT_DONE_ENABLED() (0) +#define TRACE_POSTGRESQL_BUFFER_READ_START(INT1, INT2, INT3, INT4, INT5, INT6) do {} while (0) +#define TRACE_POSTGRESQL_BUFFER_READ_START_ENABLED() (0) +#define TRACE_POSTGRESQL_BUFFER_READ_DONE(INT1, INT2, INT3, INT4, INT5, INT6, INT7) do {} while (0) +#define TRACE_POSTGRESQL_BUFFER_READ_DONE_ENABLED() (0) +#define TRACE_POSTGRESQL_BUFFER_FLUSH_START(INT1, INT2, INT3, INT4, INT5) do {} while (0) +#define TRACE_POSTGRESQL_BUFFER_FLUSH_START_ENABLED() (0) +#define TRACE_POSTGRESQL_BUFFER_FLUSH_DONE(INT1, INT2, INT3, INT4, INT5) do {} while (0) +#define TRACE_POSTGRESQL_BUFFER_FLUSH_DONE_ENABLED() (0) +#define TRACE_POSTGRESQL_BUFFER_EXTEND_START(INT1, INT2, INT3, INT4, INT5, INT6) do {} while (0) +#define TRACE_POSTGRESQL_BUFFER_EXTEND_START_ENABLED() (0) +#define TRACE_POSTGRESQL_BUFFER_EXTEND_DONE(INT1, INT2, INT3, INT4, INT5, INT6, INT7) do {} while (0) +#define TRACE_POSTGRESQL_BUFFER_EXTEND_DONE_ENABLED() (0) +#define TRACE_POSTGRESQL_BUFFER_CHECKPOINT_START(INT1) do {} while (0) +#define TRACE_POSTGRESQL_BUFFER_CHECKPOINT_START_ENABLED() (0) +#define TRACE_POSTGRESQL_BUFFER_CHECKPOINT_SYNC_START() do {} while (0) +#define TRACE_POSTGRESQL_BUFFER_CHECKPOINT_SYNC_START_ENABLED() (0) +#define TRACE_POSTGRESQL_BUFFER_CHECKPOINT_DONE() do {} while (0) +#define TRACE_POSTGRESQL_BUFFER_CHECKPOINT_DONE_ENABLED() (0) +#define TRACE_POSTGRESQL_BUFFER_SYNC_START(INT1, INT2) do {} while (0) +#define TRACE_POSTGRESQL_BUFFER_SYNC_START_ENABLED() (0) +#define TRACE_POSTGRESQL_BUFFER_SYNC_WRITTEN(INT1) do {} while (0) +#define TRACE_POSTGRESQL_BUFFER_SYNC_WRITTEN_ENABLED() (0) +#define TRACE_POSTGRESQL_BUFFER_SYNC_DONE(INT1, INT2, INT3) do {} while (0) +#define TRACE_POSTGRESQL_BUFFER_SYNC_DONE_ENABLED() (0) +#define TRACE_POSTGRESQL_DEADLOCK_FOUND() do {} while (0) +#define TRACE_POSTGRESQL_DEADLOCK_FOUND_ENABLED() (0) +#define TRACE_POSTGRESQL_CHECKPOINT_START(INT1) do {} while (0) +#define TRACE_POSTGRESQL_CHECKPOINT_START_ENABLED() (0) +#define TRACE_POSTGRESQL_CHECKPOINT_DONE(INT1, INT2, INT3, INT4, INT5) do {} while (0) +#define TRACE_POSTGRESQL_CHECKPOINT_DONE_ENABLED() (0) +#define TRACE_POSTGRESQL_CLOG_CHECKPOINT_START(INT1) do {} while (0) +#define TRACE_POSTGRESQL_CLOG_CHECKPOINT_START_ENABLED() (0) +#define TRACE_POSTGRESQL_CLOG_CHECKPOINT_DONE(INT1) do {} while (0) +#define TRACE_POSTGRESQL_CLOG_CHECKPOINT_DONE_ENABLED() (0) +#define TRACE_POSTGRESQL_SUBTRANS_CHECKPOINT_START(INT1) do {} while (0) +#define TRACE_POSTGRESQL_SUBTRANS_CHECKPOINT_START_ENABLED() (0) +#define TRACE_POSTGRESQL_SUBTRANS_CHECKPOINT_DONE(INT1) do {} while (0) +#define TRACE_POSTGRESQL_SUBTRANS_CHECKPOINT_DONE_ENABLED() (0) +#define TRACE_POSTGRESQL_MULTIXACT_CHECKPOINT_START(INT1) do {} while (0) +#define TRACE_POSTGRESQL_MULTIXACT_CHECKPOINT_START_ENABLED() (0) +#define TRACE_POSTGRESQL_MULTIXACT_CHECKPOINT_DONE(INT1) do {} while (0) +#define TRACE_POSTGRESQL_MULTIXACT_CHECKPOINT_DONE_ENABLED() (0) +#define TRACE_POSTGRESQL_TWOPHASE_CHECKPOINT_START() do {} while (0) +#define TRACE_POSTGRESQL_TWOPHASE_CHECKPOINT_START_ENABLED() (0) +#define TRACE_POSTGRESQL_TWOPHASE_CHECKPOINT_DONE() do {} while (0) +#define TRACE_POSTGRESQL_TWOPHASE_CHECKPOINT_DONE_ENABLED() (0) +#define TRACE_POSTGRESQL_SMGR_MD_READ_START(INT1, INT2, INT3, INT4, INT5, INT6) do {} while (0) +#define TRACE_POSTGRESQL_SMGR_MD_READ_START_ENABLED() (0) +#define TRACE_POSTGRESQL_SMGR_MD_READ_DONE(INT1, INT2, INT3, INT4, INT5, INT6, INT7, INT8) do {} while (0) +#define TRACE_POSTGRESQL_SMGR_MD_READ_DONE_ENABLED() (0) +#define TRACE_POSTGRESQL_SMGR_MD_WRITE_START(INT1, INT2, INT3, INT4, INT5, INT6) do {} while (0) +#define TRACE_POSTGRESQL_SMGR_MD_WRITE_START_ENABLED() (0) +#define TRACE_POSTGRESQL_SMGR_MD_WRITE_DONE(INT1, INT2, INT3, INT4, INT5, INT6, INT7, INT8) do {} while (0) +#define TRACE_POSTGRESQL_SMGR_MD_WRITE_DONE_ENABLED() (0) +#define TRACE_POSTGRESQL_WAL_INSERT(INT1, INT2) do {} while (0) +#define TRACE_POSTGRESQL_WAL_INSERT_ENABLED() (0) +#define TRACE_POSTGRESQL_WAL_SWITCH() do {} while (0) +#define TRACE_POSTGRESQL_WAL_SWITCH_ENABLED() (0) +#define TRACE_POSTGRESQL_WAL_BUFFER_WRITE_DIRTY_START() do {} while (0) +#define TRACE_POSTGRESQL_WAL_BUFFER_WRITE_DIRTY_START_ENABLED() (0) +#define TRACE_POSTGRESQL_WAL_BUFFER_WRITE_DIRTY_DONE() do {} while (0) +#define TRACE_POSTGRESQL_WAL_BUFFER_WRITE_DIRTY_DONE_ENABLED() (0) diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/resowner/resowner.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/resowner/resowner.c new file mode 100644 index 00000000000..c2cc18013a8 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/resowner/resowner.c @@ -0,0 +1,1555 @@ +/*------------------------------------------------------------------------- + * + * resowner.c + * POSTGRES resource owner management code. + * + * Query-lifespan resources are tracked by associating them with + * ResourceOwner objects. This provides a simple mechanism for ensuring + * that such resources are freed at the right time. + * See utils/resowner/README for more info. + * + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/resowner/resowner.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "common/cryptohash.h" +#include "common/hashfn.h" +#include "common/hmac.h" +#include "jit/jit.h" +#include "storage/bufmgr.h" +#include "storage/ipc.h" +#include "storage/predicate.h" +#include "storage/proc.h" +#include "utils/memutils.h" +#include "utils/rel.h" +#include "utils/resowner_private.h" +#include "utils/snapmgr.h" + + +/* + * All resource IDs managed by this code are required to fit into a Datum, + * which is fine since they are generally pointers or integers. + * + * Provide Datum conversion macros for a couple of things that are really + * just "int". + */ +#define FileGetDatum(file) Int32GetDatum(file) +#define DatumGetFile(datum) ((File) DatumGetInt32(datum)) +#define BufferGetDatum(buffer) Int32GetDatum(buffer) +#define DatumGetBuffer(datum) ((Buffer) DatumGetInt32(datum)) + +/* + * ResourceArray is a common structure for storing all types of resource IDs. + * + * We manage small sets of resource IDs by keeping them in a simple array: + * itemsarr[k] holds an ID, for 0 <= k < nitems <= maxitems = capacity. + * + * If a set grows large, we switch over to using open-addressing hashing. + * Then, itemsarr[] is a hash table of "capacity" slots, with each + * slot holding either an ID or "invalidval". nitems is the number of valid + * items present; if it would exceed maxitems, we enlarge the array and + * re-hash. In this mode, maxitems should be rather less than capacity so + * that we don't waste too much time searching for empty slots. + * + * In either mode, lastidx remembers the location of the last item inserted + * or returned by GetAny; this speeds up searches in ResourceArrayRemove. + */ +typedef struct ResourceArray +{ + Datum *itemsarr; /* buffer for storing values */ + Datum invalidval; /* value that is considered invalid */ + uint32 capacity; /* allocated length of itemsarr[] */ + uint32 nitems; /* how many items are stored in items array */ + uint32 maxitems; /* current limit on nitems before enlarging */ + uint32 lastidx; /* index of last item returned by GetAny */ +} ResourceArray; + +/* + * Initially allocated size of a ResourceArray. Must be power of two since + * we'll use (arraysize - 1) as mask for hashing. + */ +#define RESARRAY_INIT_SIZE 16 + +/* + * When to switch to hashing vs. simple array logic in a ResourceArray. + */ +#define RESARRAY_MAX_ARRAY 64 +#define RESARRAY_IS_ARRAY(resarr) ((resarr)->capacity <= RESARRAY_MAX_ARRAY) + +/* + * How many items may be stored in a resource array of given capacity. + * When this number is reached, we must resize. + */ +#define RESARRAY_MAX_ITEMS(capacity) \ + ((capacity) <= RESARRAY_MAX_ARRAY ? (capacity) : (capacity)/4 * 3) + +/* + * To speed up bulk releasing or reassigning locks from a resource owner to + * its parent, each resource owner has a small cache of locks it owns. The + * lock manager has the same information in its local lock hash table, and + * we fall back on that if cache overflows, but traversing the hash table + * is slower when there are a lot of locks belonging to other resource owners. + * + * MAX_RESOWNER_LOCKS is the size of the per-resource owner cache. It's + * chosen based on some testing with pg_dump with a large schema. When the + * tests were done (on 9.2), resource owners in a pg_dump run contained up + * to 9 locks, regardless of the schema size, except for the top resource + * owner which contained much more (overflowing the cache). 15 seems like a + * nice round number that's somewhat higher than what pg_dump needs. Note that + * making this number larger is not free - the bigger the cache, the slower + * it is to release locks (in retail), when a resource owner holds many locks. + */ +#define MAX_RESOWNER_LOCKS 15 + +/* + * ResourceOwner objects look like this + */ +typedef struct ResourceOwnerData +{ + ResourceOwner parent; /* NULL if no parent (toplevel owner) */ + ResourceOwner firstchild; /* head of linked list of children */ + ResourceOwner nextchild; /* next child of same parent */ + const char *name; /* name (just for debugging) */ + + /* We have built-in support for remembering: */ + ResourceArray bufferarr; /* owned buffers */ + ResourceArray bufferioarr; /* in-progress buffer IO */ + ResourceArray catrefarr; /* catcache references */ + ResourceArray catlistrefarr; /* catcache-list pins */ + ResourceArray relrefarr; /* relcache references */ + ResourceArray planrefarr; /* plancache references */ + ResourceArray tupdescarr; /* tupdesc references */ + ResourceArray snapshotarr; /* snapshot references */ + ResourceArray filearr; /* open temporary files */ + ResourceArray dsmarr; /* dynamic shmem segments */ + ResourceArray jitarr; /* JIT contexts */ + ResourceArray cryptohasharr; /* cryptohash contexts */ + ResourceArray hmacarr; /* HMAC contexts */ + + /* We can remember up to MAX_RESOWNER_LOCKS references to local locks. */ + int nlocks; /* number of owned locks */ + LOCALLOCK *locks[MAX_RESOWNER_LOCKS]; /* list of owned locks */ +} ResourceOwnerData; + + +/***************************************************************************** + * GLOBAL MEMORY * + *****************************************************************************/ + +__thread ResourceOwner CurrentResourceOwner = NULL; +__thread ResourceOwner CurTransactionResourceOwner = NULL; +__thread ResourceOwner TopTransactionResourceOwner = NULL; +__thread ResourceOwner AuxProcessResourceOwner = NULL; + +/* + * List of add-on callbacks for resource releasing + */ +typedef struct ResourceReleaseCallbackItem +{ + struct ResourceReleaseCallbackItem *next; + ResourceReleaseCallback callback; + void *arg; +} ResourceReleaseCallbackItem; + +static __thread ResourceReleaseCallbackItem *ResourceRelease_callbacks = NULL; + + +/* Internal routines */ +static void ResourceArrayInit(ResourceArray *resarr, Datum invalidval); +static void ResourceArrayEnlarge(ResourceArray *resarr); +static void ResourceArrayAdd(ResourceArray *resarr, Datum value); +static bool ResourceArrayRemove(ResourceArray *resarr, Datum value); +static bool ResourceArrayGetAny(ResourceArray *resarr, Datum *value); +static void ResourceArrayFree(ResourceArray *resarr); +static void ResourceOwnerReleaseInternal(ResourceOwner owner, + ResourceReleasePhase phase, + bool isCommit, + bool isTopLevel); +static void ReleaseAuxProcessResourcesCallback(int code, Datum arg); +static void PrintRelCacheLeakWarning(Relation rel); +static void PrintPlanCacheLeakWarning(CachedPlan *plan); +static void PrintTupleDescLeakWarning(TupleDesc tupdesc); +static void PrintSnapshotLeakWarning(Snapshot snapshot); +static void PrintFileLeakWarning(File file); +static void PrintDSMLeakWarning(dsm_segment *seg); +static void PrintCryptoHashLeakWarning(Datum handle); +static void PrintHMACLeakWarning(Datum handle); + + +/***************************************************************************** + * INTERNAL ROUTINES * + *****************************************************************************/ + + +/* + * Initialize a ResourceArray + */ +static void +ResourceArrayInit(ResourceArray *resarr, Datum invalidval) +{ + /* Assert it's empty */ + Assert(resarr->itemsarr == NULL); + Assert(resarr->capacity == 0); + Assert(resarr->nitems == 0); + Assert(resarr->maxitems == 0); + /* Remember the appropriate "invalid" value */ + resarr->invalidval = invalidval; + /* We don't allocate any storage until needed */ +} + +/* + * Make sure there is room for at least one more resource in an array. + * + * This is separate from actually inserting a resource because if we run out + * of memory, it's critical to do so *before* acquiring the resource. + */ +static void +ResourceArrayEnlarge(ResourceArray *resarr) +{ + uint32 i, + oldcap, + newcap; + Datum *olditemsarr; + Datum *newitemsarr; + + if (resarr->nitems < resarr->maxitems) + return; /* no work needed */ + + olditemsarr = resarr->itemsarr; + oldcap = resarr->capacity; + + /* Double the capacity of the array (capacity must stay a power of 2!) */ + newcap = (oldcap > 0) ? oldcap * 2 : RESARRAY_INIT_SIZE; + newitemsarr = (Datum *) MemoryContextAlloc(TopMemoryContext, + newcap * sizeof(Datum)); + for (i = 0; i < newcap; i++) + newitemsarr[i] = resarr->invalidval; + + /* We assume we can't fail below this point, so OK to scribble on resarr */ + resarr->itemsarr = newitemsarr; + resarr->capacity = newcap; + resarr->maxitems = RESARRAY_MAX_ITEMS(newcap); + resarr->nitems = 0; + + if (olditemsarr != NULL) + { + /* + * Transfer any pre-existing entries into the new array; they don't + * necessarily go where they were before, so this simple logic is the + * best way. Note that if we were managing the set as a simple array, + * the entries after nitems are garbage, but that shouldn't matter + * because we won't get here unless nitems was equal to oldcap. + */ + for (i = 0; i < oldcap; i++) + { + if (olditemsarr[i] != resarr->invalidval) + ResourceArrayAdd(resarr, olditemsarr[i]); + } + + /* And release old array. */ + pfree(olditemsarr); + } + + Assert(resarr->nitems < resarr->maxitems); +} + +/* + * Add a resource to ResourceArray + * + * Caller must have previously done ResourceArrayEnlarge() + */ +static void +ResourceArrayAdd(ResourceArray *resarr, Datum value) +{ + uint32 idx; + + Assert(value != resarr->invalidval); + Assert(resarr->nitems < resarr->maxitems); + + if (RESARRAY_IS_ARRAY(resarr)) + { + /* Append to linear array. */ + idx = resarr->nitems; + } + else + { + /* Insert into first free slot at or after hash location. */ + uint32 mask = resarr->capacity - 1; + + idx = DatumGetUInt32(hash_any((void *) &value, sizeof(value))) & mask; + for (;;) + { + if (resarr->itemsarr[idx] == resarr->invalidval) + break; + idx = (idx + 1) & mask; + } + } + resarr->lastidx = idx; + resarr->itemsarr[idx] = value; + resarr->nitems++; +} + +/* + * Remove a resource from ResourceArray + * + * Returns true on success, false if resource was not found. + * + * Note: if same resource ID appears more than once, one instance is removed. + */ +static bool +ResourceArrayRemove(ResourceArray *resarr, Datum value) +{ + uint32 i, + idx, + lastidx = resarr->lastidx; + + Assert(value != resarr->invalidval); + + /* Search through all items, but try lastidx first. */ + if (RESARRAY_IS_ARRAY(resarr)) + { + if (lastidx < resarr->nitems && + resarr->itemsarr[lastidx] == value) + { + resarr->itemsarr[lastidx] = resarr->itemsarr[resarr->nitems - 1]; + resarr->nitems--; + /* Update lastidx to make reverse-order removals fast. */ + resarr->lastidx = resarr->nitems - 1; + return true; + } + for (i = 0; i < resarr->nitems; i++) + { + if (resarr->itemsarr[i] == value) + { + resarr->itemsarr[i] = resarr->itemsarr[resarr->nitems - 1]; + resarr->nitems--; + /* Update lastidx to make reverse-order removals fast. */ + resarr->lastidx = resarr->nitems - 1; + return true; + } + } + } + else + { + uint32 mask = resarr->capacity - 1; + + if (lastidx < resarr->capacity && + resarr->itemsarr[lastidx] == value) + { + resarr->itemsarr[lastidx] = resarr->invalidval; + resarr->nitems--; + return true; + } + idx = DatumGetUInt32(hash_any((void *) &value, sizeof(value))) & mask; + for (i = 0; i < resarr->capacity; i++) + { + if (resarr->itemsarr[idx] == value) + { + resarr->itemsarr[idx] = resarr->invalidval; + resarr->nitems--; + return true; + } + idx = (idx + 1) & mask; + } + } + + return false; +} + +/* + * Get any convenient entry in a ResourceArray. + * + * "Convenient" is defined as "easy for ResourceArrayRemove to remove"; + * we help that along by setting lastidx to match. This avoids O(N^2) cost + * when removing all ResourceArray items during ResourceOwner destruction. + * + * Returns true if we found an element, or false if the array is empty. + */ +static bool +ResourceArrayGetAny(ResourceArray *resarr, Datum *value) +{ + if (resarr->nitems == 0) + return false; + + if (RESARRAY_IS_ARRAY(resarr)) + { + /* Linear array: just return the first element. */ + resarr->lastidx = 0; + } + else + { + /* Hash: search forward from wherever we were last. */ + uint32 mask = resarr->capacity - 1; + + for (;;) + { + resarr->lastidx &= mask; + if (resarr->itemsarr[resarr->lastidx] != resarr->invalidval) + break; + resarr->lastidx++; + } + } + + *value = resarr->itemsarr[resarr->lastidx]; + return true; +} + +/* + * Trash a ResourceArray (we don't care about its state after this) + */ +static void +ResourceArrayFree(ResourceArray *resarr) +{ + if (resarr->itemsarr) + pfree(resarr->itemsarr); +} + + +/***************************************************************************** + * EXPORTED ROUTINES * + *****************************************************************************/ + + +/* + * ResourceOwnerCreate + * Create an empty ResourceOwner. + * + * All ResourceOwner objects are kept in TopMemoryContext, since they should + * only be freed explicitly. + */ +ResourceOwner +ResourceOwnerCreate(ResourceOwner parent, const char *name) +{ + ResourceOwner owner; + + owner = (ResourceOwner) MemoryContextAllocZero(TopMemoryContext, + sizeof(ResourceOwnerData)); + owner->name = name; + + if (parent) + { + owner->parent = parent; + owner->nextchild = parent->firstchild; + parent->firstchild = owner; + } + + ResourceArrayInit(&(owner->bufferarr), BufferGetDatum(InvalidBuffer)); + ResourceArrayInit(&(owner->bufferioarr), BufferGetDatum(InvalidBuffer)); + ResourceArrayInit(&(owner->catrefarr), PointerGetDatum(NULL)); + ResourceArrayInit(&(owner->catlistrefarr), PointerGetDatum(NULL)); + ResourceArrayInit(&(owner->relrefarr), PointerGetDatum(NULL)); + ResourceArrayInit(&(owner->planrefarr), PointerGetDatum(NULL)); + ResourceArrayInit(&(owner->tupdescarr), PointerGetDatum(NULL)); + ResourceArrayInit(&(owner->snapshotarr), PointerGetDatum(NULL)); + ResourceArrayInit(&(owner->filearr), FileGetDatum(-1)); + ResourceArrayInit(&(owner->dsmarr), PointerGetDatum(NULL)); + ResourceArrayInit(&(owner->jitarr), PointerGetDatum(NULL)); + ResourceArrayInit(&(owner->cryptohasharr), PointerGetDatum(NULL)); + ResourceArrayInit(&(owner->hmacarr), PointerGetDatum(NULL)); + + return owner; +} + +/* + * ResourceOwnerRelease + * Release all resources owned by a ResourceOwner and its descendants, + * but don't delete the owner objects themselves. + * + * Note that this executes just one phase of release, and so typically + * must be called three times. We do it this way because (a) we want to + * do all the recursion separately for each phase, thereby preserving + * the needed order of operations; and (b) xact.c may have other operations + * to do between the phases. + * + * phase: release phase to execute + * isCommit: true for successful completion of a query or transaction, + * false for unsuccessful + * isTopLevel: true if completing a main transaction, else false + * + * isCommit is passed because some modules may expect that their resources + * were all released already if the transaction or portal finished normally. + * If so it is reasonable to give a warning (NOT an error) should any + * unreleased resources be present. When isCommit is false, such warnings + * are generally inappropriate. + * + * isTopLevel is passed when we are releasing TopTransactionResourceOwner + * at completion of a main transaction. This generally means that *all* + * resources will be released, and so we can optimize things a bit. + */ +void +ResourceOwnerRelease(ResourceOwner owner, + ResourceReleasePhase phase, + bool isCommit, + bool isTopLevel) +{ + /* There's not currently any setup needed before recursing */ + ResourceOwnerReleaseInternal(owner, phase, isCommit, isTopLevel); +} + +static void +ResourceOwnerReleaseInternal(ResourceOwner owner, + ResourceReleasePhase phase, + bool isCommit, + bool isTopLevel) +{ + ResourceOwner child; + ResourceOwner save; + ResourceReleaseCallbackItem *item; + ResourceReleaseCallbackItem *next; + Datum foundres; + + /* Recurse to handle descendants */ + for (child = owner->firstchild; child != NULL; child = child->nextchild) + ResourceOwnerReleaseInternal(child, phase, isCommit, isTopLevel); + + /* + * Make CurrentResourceOwner point to me, so that ReleaseBuffer etc don't + * get confused. + */ + save = CurrentResourceOwner; + CurrentResourceOwner = owner; + + if (phase == RESOURCE_RELEASE_BEFORE_LOCKS) + { + /* + * Abort failed buffer IO. AbortBufferIO()->TerminateBufferIO() calls + * ResourceOwnerForgetBufferIO(), so we just have to iterate till + * there are none. + * + * Needs to be before we release buffer pins. + * + * During a commit, there shouldn't be any in-progress IO. + */ + while (ResourceArrayGetAny(&(owner->bufferioarr), &foundres)) + { + Buffer res = DatumGetBuffer(foundres); + + if (isCommit) + elog(PANIC, "lost track of buffer IO on buffer %d", res); + AbortBufferIO(res); + } + + /* + * Release buffer pins. Note that ReleaseBuffer will remove the + * buffer entry from our array, so we just have to iterate till there + * are none. + * + * During a commit, there shouldn't be any remaining pins --- that + * would indicate failure to clean up the executor correctly --- so + * issue warnings. In the abort case, just clean up quietly. + */ + while (ResourceArrayGetAny(&(owner->bufferarr), &foundres)) + { + Buffer res = DatumGetBuffer(foundres); + + if (isCommit) + PrintBufferLeakWarning(res); + ReleaseBuffer(res); + } + + /* Ditto for relcache references */ + while (ResourceArrayGetAny(&(owner->relrefarr), &foundres)) + { + Relation res = (Relation) DatumGetPointer(foundres); + + if (isCommit) + PrintRelCacheLeakWarning(res); + RelationClose(res); + } + + /* Ditto for dynamic shared memory segments */ + while (ResourceArrayGetAny(&(owner->dsmarr), &foundres)) + { + dsm_segment *res = (dsm_segment *) DatumGetPointer(foundres); + + if (isCommit) + PrintDSMLeakWarning(res); + dsm_detach(res); + } + + /* Ditto for JIT contexts */ + while (ResourceArrayGetAny(&(owner->jitarr), &foundres)) + { + JitContext *context = (JitContext *) DatumGetPointer(foundres); + + jit_release_context(context); + } + + /* Ditto for cryptohash contexts */ + while (ResourceArrayGetAny(&(owner->cryptohasharr), &foundres)) + { + pg_cryptohash_ctx *context = + (pg_cryptohash_ctx *) DatumGetPointer(foundres); + + if (isCommit) + PrintCryptoHashLeakWarning(foundres); + pg_cryptohash_free(context); + } + + /* Ditto for HMAC contexts */ + while (ResourceArrayGetAny(&(owner->hmacarr), &foundres)) + { + pg_hmac_ctx *context = (pg_hmac_ctx *) DatumGetPointer(foundres); + + if (isCommit) + PrintHMACLeakWarning(foundres); + pg_hmac_free(context); + } + } + else if (phase == RESOURCE_RELEASE_LOCKS) + { + if (isTopLevel) + { + /* + * For a top-level xact we are going to release all locks (or at + * least all non-session locks), so just do a single lmgr call at + * the top of the recursion. + */ + if (owner == TopTransactionResourceOwner) + { + ProcReleaseLocks(isCommit); + ReleasePredicateLocks(isCommit, false); + } + } + else + { + /* + * Release locks retail. Note that if we are committing a + * subtransaction, we do NOT release its locks yet, but transfer + * them to the parent. + */ + LOCALLOCK **locks; + int nlocks; + + Assert(owner->parent != NULL); + + /* + * Pass the list of locks owned by this resource owner to the lock + * manager, unless it has overflowed. + */ + if (owner->nlocks > MAX_RESOWNER_LOCKS) + { + locks = NULL; + nlocks = 0; + } + else + { + locks = owner->locks; + nlocks = owner->nlocks; + } + + if (isCommit) + LockReassignCurrentOwner(locks, nlocks); + else + LockReleaseCurrentOwner(locks, nlocks); + } + } + else if (phase == RESOURCE_RELEASE_AFTER_LOCKS) + { + /* + * Release catcache references. Note that ReleaseCatCache will remove + * the catref entry from our array, so we just have to iterate till + * there are none. + * + * As with buffer pins, warn if any are left at commit time. + */ + while (ResourceArrayGetAny(&(owner->catrefarr), &foundres)) + { + HeapTuple res = (HeapTuple) DatumGetPointer(foundres); + + if (isCommit) + PrintCatCacheLeakWarning(res); + ReleaseCatCache(res); + } + + /* Ditto for catcache lists */ + while (ResourceArrayGetAny(&(owner->catlistrefarr), &foundres)) + { + CatCList *res = (CatCList *) DatumGetPointer(foundres); + + if (isCommit) + PrintCatCacheListLeakWarning(res); + ReleaseCatCacheList(res); + } + + /* Ditto for plancache references */ + while (ResourceArrayGetAny(&(owner->planrefarr), &foundres)) + { + CachedPlan *res = (CachedPlan *) DatumGetPointer(foundres); + + if (isCommit) + PrintPlanCacheLeakWarning(res); + ReleaseCachedPlan(res, owner); + } + + /* Ditto for tupdesc references */ + while (ResourceArrayGetAny(&(owner->tupdescarr), &foundres)) + { + TupleDesc res = (TupleDesc) DatumGetPointer(foundres); + + if (isCommit) + PrintTupleDescLeakWarning(res); + DecrTupleDescRefCount(res); + } + + /* Ditto for snapshot references */ + while (ResourceArrayGetAny(&(owner->snapshotarr), &foundres)) + { + Snapshot res = (Snapshot) DatumGetPointer(foundres); + + if (isCommit) + PrintSnapshotLeakWarning(res); + UnregisterSnapshot(res); + } + + /* Ditto for temporary files */ + while (ResourceArrayGetAny(&(owner->filearr), &foundres)) + { + File res = DatumGetFile(foundres); + + if (isCommit) + PrintFileLeakWarning(res); + FileClose(res); + } + } + + /* Let add-on modules get a chance too */ + for (item = ResourceRelease_callbacks; item; item = next) + { + /* allow callbacks to unregister themselves when called */ + next = item->next; + item->callback(phase, isCommit, isTopLevel, item->arg); + } + + CurrentResourceOwner = save; +} + +/* + * ResourceOwnerReleaseAllPlanCacheRefs + * Release the plancache references (only) held by this owner. + * + * We might eventually add similar functions for other resource types, + * but for now, only this is needed. + */ +void +ResourceOwnerReleaseAllPlanCacheRefs(ResourceOwner owner) +{ + Datum foundres; + + while (ResourceArrayGetAny(&(owner->planrefarr), &foundres)) + { + CachedPlan *res = (CachedPlan *) DatumGetPointer(foundres); + + ReleaseCachedPlan(res, owner); + } +} + +/* + * ResourceOwnerDelete + * Delete an owner object and its descendants. + * + * The caller must have already released all resources in the object tree. + */ +void +ResourceOwnerDelete(ResourceOwner owner) +{ + /* We had better not be deleting CurrentResourceOwner ... */ + Assert(owner != CurrentResourceOwner); + + /* And it better not own any resources, either */ + Assert(owner->bufferarr.nitems == 0); + Assert(owner->bufferioarr.nitems == 0); + Assert(owner->catrefarr.nitems == 0); + Assert(owner->catlistrefarr.nitems == 0); + Assert(owner->relrefarr.nitems == 0); + Assert(owner->planrefarr.nitems == 0); + Assert(owner->tupdescarr.nitems == 0); + Assert(owner->snapshotarr.nitems == 0); + Assert(owner->filearr.nitems == 0); + Assert(owner->dsmarr.nitems == 0); + Assert(owner->jitarr.nitems == 0); + Assert(owner->cryptohasharr.nitems == 0); + Assert(owner->hmacarr.nitems == 0); + Assert(owner->nlocks == 0 || owner->nlocks == MAX_RESOWNER_LOCKS + 1); + + /* + * Delete children. The recursive call will delink the child from me, so + * just iterate as long as there is a child. + */ + while (owner->firstchild != NULL) + ResourceOwnerDelete(owner->firstchild); + + /* + * We delink the owner from its parent before deleting it, so that if + * there's an error we won't have deleted/busted owners still attached to + * the owner tree. Better a leak than a crash. + */ + ResourceOwnerNewParent(owner, NULL); + + /* And free the object. */ + ResourceArrayFree(&(owner->bufferarr)); + ResourceArrayFree(&(owner->bufferioarr)); + ResourceArrayFree(&(owner->catrefarr)); + ResourceArrayFree(&(owner->catlistrefarr)); + ResourceArrayFree(&(owner->relrefarr)); + ResourceArrayFree(&(owner->planrefarr)); + ResourceArrayFree(&(owner->tupdescarr)); + ResourceArrayFree(&(owner->snapshotarr)); + ResourceArrayFree(&(owner->filearr)); + ResourceArrayFree(&(owner->dsmarr)); + ResourceArrayFree(&(owner->jitarr)); + ResourceArrayFree(&(owner->cryptohasharr)); + ResourceArrayFree(&(owner->hmacarr)); + + pfree(owner); +} + +/* + * Fetch parent of a ResourceOwner (returns NULL if top-level owner) + */ +ResourceOwner +ResourceOwnerGetParent(ResourceOwner owner) +{ + return owner->parent; +} + +/* + * Reassign a ResourceOwner to have a new parent + */ +void +ResourceOwnerNewParent(ResourceOwner owner, + ResourceOwner newparent) +{ + ResourceOwner oldparent = owner->parent; + + if (oldparent) + { + if (owner == oldparent->firstchild) + oldparent->firstchild = owner->nextchild; + else + { + ResourceOwner child; + + for (child = oldparent->firstchild; child; child = child->nextchild) + { + if (owner == child->nextchild) + { + child->nextchild = owner->nextchild; + break; + } + } + } + } + + if (newparent) + { + Assert(owner != newparent); + owner->parent = newparent; + owner->nextchild = newparent->firstchild; + newparent->firstchild = owner; + } + else + { + owner->parent = NULL; + owner->nextchild = NULL; + } +} + +/* + * Register or deregister callback functions for resource cleanup + * + * These functions are intended for use by dynamically loaded modules. + * For built-in modules we generally just hardwire the appropriate calls. + * + * Note that the callback occurs post-commit or post-abort, so the callback + * functions can only do noncritical cleanup. + */ +void +RegisterResourceReleaseCallback(ResourceReleaseCallback callback, void *arg) +{ + ResourceReleaseCallbackItem *item; + + item = (ResourceReleaseCallbackItem *) + MemoryContextAlloc(TopMemoryContext, + sizeof(ResourceReleaseCallbackItem)); + item->callback = callback; + item->arg = arg; + item->next = ResourceRelease_callbacks; + ResourceRelease_callbacks = item; +} + +void +UnregisterResourceReleaseCallback(ResourceReleaseCallback callback, void *arg) +{ + ResourceReleaseCallbackItem *item; + ResourceReleaseCallbackItem *prev; + + prev = NULL; + for (item = ResourceRelease_callbacks; item; prev = item, item = item->next) + { + if (item->callback == callback && item->arg == arg) + { + if (prev) + prev->next = item->next; + else + ResourceRelease_callbacks = item->next; + pfree(item); + break; + } + } +} + +/* + * Establish an AuxProcessResourceOwner for the current process. + */ +void +CreateAuxProcessResourceOwner(void) +{ + Assert(AuxProcessResourceOwner == NULL); + Assert(CurrentResourceOwner == NULL); + AuxProcessResourceOwner = ResourceOwnerCreate(NULL, "AuxiliaryProcess"); + CurrentResourceOwner = AuxProcessResourceOwner; + + /* + * Register a shmem-exit callback for cleanup of aux-process resource + * owner. (This needs to run after, e.g., ShutdownXLOG.) + */ + on_shmem_exit(ReleaseAuxProcessResourcesCallback, 0); +} + +/* + * Convenience routine to release all resources tracked in + * AuxProcessResourceOwner (but that resowner is not destroyed here). + * Warn about leaked resources if isCommit is true. + */ +void +ReleaseAuxProcessResources(bool isCommit) +{ + /* + * At this writing, the only thing that could actually get released is + * buffer pins; but we may as well do the full release protocol. + */ + ResourceOwnerRelease(AuxProcessResourceOwner, + RESOURCE_RELEASE_BEFORE_LOCKS, + isCommit, true); + ResourceOwnerRelease(AuxProcessResourceOwner, + RESOURCE_RELEASE_LOCKS, + isCommit, true); + ResourceOwnerRelease(AuxProcessResourceOwner, + RESOURCE_RELEASE_AFTER_LOCKS, + isCommit, true); +} + +/* + * Shmem-exit callback for the same. + * Warn about leaked resources if process exit code is zero (ie normal). + */ +static void +ReleaseAuxProcessResourcesCallback(int code, Datum arg) +{ + bool isCommit = (code == 0); + + ReleaseAuxProcessResources(isCommit); +} + + +/* + * Make sure there is room for at least one more entry in a ResourceOwner's + * buffer array. + * + * This is separate from actually inserting an entry because if we run out + * of memory, it's critical to do so *before* acquiring the resource. + */ +void +ResourceOwnerEnlargeBuffers(ResourceOwner owner) +{ + /* We used to allow pinning buffers without a resowner, but no more */ + Assert(owner != NULL); + ResourceArrayEnlarge(&(owner->bufferarr)); +} + +/* + * Remember that a buffer pin is owned by a ResourceOwner + * + * Caller must have previously done ResourceOwnerEnlargeBuffers() + */ +void +ResourceOwnerRememberBuffer(ResourceOwner owner, Buffer buffer) +{ + ResourceArrayAdd(&(owner->bufferarr), BufferGetDatum(buffer)); +} + +/* + * Forget that a buffer pin is owned by a ResourceOwner + */ +void +ResourceOwnerForgetBuffer(ResourceOwner owner, Buffer buffer) +{ + if (!ResourceArrayRemove(&(owner->bufferarr), BufferGetDatum(buffer))) + elog(ERROR, "buffer %d is not owned by resource owner %s", + buffer, owner->name); +} + + +/* + * Make sure there is room for at least one more entry in a ResourceOwner's + * buffer array. + * + * This is separate from actually inserting an entry because if we run out + * of memory, it's critical to do so *before* acquiring the resource. + */ +void +ResourceOwnerEnlargeBufferIOs(ResourceOwner owner) +{ + /* We used to allow pinning buffers without a resowner, but no more */ + Assert(owner != NULL); + ResourceArrayEnlarge(&(owner->bufferioarr)); +} + +/* + * Remember that a buffer IO is owned by a ResourceOwner + * + * Caller must have previously done ResourceOwnerEnlargeBufferIOs() + */ +void +ResourceOwnerRememberBufferIO(ResourceOwner owner, Buffer buffer) +{ + ResourceArrayAdd(&(owner->bufferioarr), BufferGetDatum(buffer)); +} + +/* + * Forget that a buffer IO is owned by a ResourceOwner + */ +void +ResourceOwnerForgetBufferIO(ResourceOwner owner, Buffer buffer) +{ + if (!ResourceArrayRemove(&(owner->bufferioarr), BufferGetDatum(buffer))) + elog(PANIC, "buffer IO %d is not owned by resource owner %s", + buffer, owner->name); +} + +/* + * Remember that a Local Lock is owned by a ResourceOwner + * + * This is different from the other Remember functions in that the list of + * locks is only a lossy cache. It can hold up to MAX_RESOWNER_LOCKS entries, + * and when it overflows, we stop tracking locks. The point of only remembering + * only up to MAX_RESOWNER_LOCKS entries is that if a lot of locks are held, + * ResourceOwnerForgetLock doesn't need to scan through a large array to find + * the entry. + */ +void +ResourceOwnerRememberLock(ResourceOwner owner, LOCALLOCK *locallock) +{ + Assert(locallock != NULL); + + if (owner->nlocks > MAX_RESOWNER_LOCKS) + return; /* we have already overflowed */ + + if (owner->nlocks < MAX_RESOWNER_LOCKS) + owner->locks[owner->nlocks] = locallock; + else + { + /* overflowed */ + } + owner->nlocks++; +} + +/* + * Forget that a Local Lock is owned by a ResourceOwner + */ +void +ResourceOwnerForgetLock(ResourceOwner owner, LOCALLOCK *locallock) +{ + int i; + + if (owner->nlocks > MAX_RESOWNER_LOCKS) + return; /* we have overflowed */ + + Assert(owner->nlocks > 0); + for (i = owner->nlocks - 1; i >= 0; i--) + { + if (locallock == owner->locks[i]) + { + owner->locks[i] = owner->locks[owner->nlocks - 1]; + owner->nlocks--; + return; + } + } + elog(ERROR, "lock reference %p is not owned by resource owner %s", + locallock, owner->name); +} + +/* + * Make sure there is room for at least one more entry in a ResourceOwner's + * catcache reference array. + * + * This is separate from actually inserting an entry because if we run out + * of memory, it's critical to do so *before* acquiring the resource. + */ +void +ResourceOwnerEnlargeCatCacheRefs(ResourceOwner owner) +{ + ResourceArrayEnlarge(&(owner->catrefarr)); +} + +/* + * Remember that a catcache reference is owned by a ResourceOwner + * + * Caller must have previously done ResourceOwnerEnlargeCatCacheRefs() + */ +void +ResourceOwnerRememberCatCacheRef(ResourceOwner owner, HeapTuple tuple) +{ + ResourceArrayAdd(&(owner->catrefarr), PointerGetDatum(tuple)); +} + +/* + * Forget that a catcache reference is owned by a ResourceOwner + */ +void +ResourceOwnerForgetCatCacheRef(ResourceOwner owner, HeapTuple tuple) +{ + if (!ResourceArrayRemove(&(owner->catrefarr), PointerGetDatum(tuple))) + elog(ERROR, "catcache reference %p is not owned by resource owner %s", + tuple, owner->name); +} + +/* + * Make sure there is room for at least one more entry in a ResourceOwner's + * catcache-list reference array. + * + * This is separate from actually inserting an entry because if we run out + * of memory, it's critical to do so *before* acquiring the resource. + */ +void +ResourceOwnerEnlargeCatCacheListRefs(ResourceOwner owner) +{ + ResourceArrayEnlarge(&(owner->catlistrefarr)); +} + +/* + * Remember that a catcache-list reference is owned by a ResourceOwner + * + * Caller must have previously done ResourceOwnerEnlargeCatCacheListRefs() + */ +void +ResourceOwnerRememberCatCacheListRef(ResourceOwner owner, CatCList *list) +{ + ResourceArrayAdd(&(owner->catlistrefarr), PointerGetDatum(list)); +} + +/* + * Forget that a catcache-list reference is owned by a ResourceOwner + */ +void +ResourceOwnerForgetCatCacheListRef(ResourceOwner owner, CatCList *list) +{ + if (!ResourceArrayRemove(&(owner->catlistrefarr), PointerGetDatum(list))) + elog(ERROR, "catcache list reference %p is not owned by resource owner %s", + list, owner->name); +} + +/* + * Make sure there is room for at least one more entry in a ResourceOwner's + * relcache reference array. + * + * This is separate from actually inserting an entry because if we run out + * of memory, it's critical to do so *before* acquiring the resource. + */ +void +ResourceOwnerEnlargeRelationRefs(ResourceOwner owner) +{ + ResourceArrayEnlarge(&(owner->relrefarr)); +} + +/* + * Remember that a relcache reference is owned by a ResourceOwner + * + * Caller must have previously done ResourceOwnerEnlargeRelationRefs() + */ +void +ResourceOwnerRememberRelationRef(ResourceOwner owner, Relation rel) +{ + ResourceArrayAdd(&(owner->relrefarr), PointerGetDatum(rel)); +} + +/* + * Forget that a relcache reference is owned by a ResourceOwner + */ +void +ResourceOwnerForgetRelationRef(ResourceOwner owner, Relation rel) +{ + if (!ResourceArrayRemove(&(owner->relrefarr), PointerGetDatum(rel))) + elog(ERROR, "relcache reference %s is not owned by resource owner %s", + RelationGetRelationName(rel), owner->name); +} + +/* + * Debugging subroutine + */ +static void +PrintRelCacheLeakWarning(Relation rel) +{ + elog(WARNING, "relcache reference leak: relation \"%s\" not closed", + RelationGetRelationName(rel)); +} + +/* + * Make sure there is room for at least one more entry in a ResourceOwner's + * plancache reference array. + * + * This is separate from actually inserting an entry because if we run out + * of memory, it's critical to do so *before* acquiring the resource. + */ +void +ResourceOwnerEnlargePlanCacheRefs(ResourceOwner owner) +{ + ResourceArrayEnlarge(&(owner->planrefarr)); +} + +/* + * Remember that a plancache reference is owned by a ResourceOwner + * + * Caller must have previously done ResourceOwnerEnlargePlanCacheRefs() + */ +void +ResourceOwnerRememberPlanCacheRef(ResourceOwner owner, CachedPlan *plan) +{ + ResourceArrayAdd(&(owner->planrefarr), PointerGetDatum(plan)); +} + +/* + * Forget that a plancache reference is owned by a ResourceOwner + */ +void +ResourceOwnerForgetPlanCacheRef(ResourceOwner owner, CachedPlan *plan) +{ + if (!ResourceArrayRemove(&(owner->planrefarr), PointerGetDatum(plan))) + elog(ERROR, "plancache reference %p is not owned by resource owner %s", + plan, owner->name); +} + +/* + * Debugging subroutine + */ +static void +PrintPlanCacheLeakWarning(CachedPlan *plan) +{ + elog(WARNING, "plancache reference leak: plan %p not closed", plan); +} + +/* + * Make sure there is room for at least one more entry in a ResourceOwner's + * tupdesc reference array. + * + * This is separate from actually inserting an entry because if we run out + * of memory, it's critical to do so *before* acquiring the resource. + */ +void +ResourceOwnerEnlargeTupleDescs(ResourceOwner owner) +{ + ResourceArrayEnlarge(&(owner->tupdescarr)); +} + +/* + * Remember that a tupdesc reference is owned by a ResourceOwner + * + * Caller must have previously done ResourceOwnerEnlargeTupleDescs() + */ +void +ResourceOwnerRememberTupleDesc(ResourceOwner owner, TupleDesc tupdesc) +{ + ResourceArrayAdd(&(owner->tupdescarr), PointerGetDatum(tupdesc)); +} + +/* + * Forget that a tupdesc reference is owned by a ResourceOwner + */ +void +ResourceOwnerForgetTupleDesc(ResourceOwner owner, TupleDesc tupdesc) +{ + if (!ResourceArrayRemove(&(owner->tupdescarr), PointerGetDatum(tupdesc))) + elog(ERROR, "tupdesc reference %p is not owned by resource owner %s", + tupdesc, owner->name); +} + +/* + * Debugging subroutine + */ +static void +PrintTupleDescLeakWarning(TupleDesc tupdesc) +{ + elog(WARNING, + "TupleDesc reference leak: TupleDesc %p (%u,%d) still referenced", + tupdesc, tupdesc->tdtypeid, tupdesc->tdtypmod); +} + +/* + * Make sure there is room for at least one more entry in a ResourceOwner's + * snapshot reference array. + * + * This is separate from actually inserting an entry because if we run out + * of memory, it's critical to do so *before* acquiring the resource. + */ +void +ResourceOwnerEnlargeSnapshots(ResourceOwner owner) +{ + ResourceArrayEnlarge(&(owner->snapshotarr)); +} + +/* + * Remember that a snapshot reference is owned by a ResourceOwner + * + * Caller must have previously done ResourceOwnerEnlargeSnapshots() + */ +void +ResourceOwnerRememberSnapshot(ResourceOwner owner, Snapshot snapshot) +{ + ResourceArrayAdd(&(owner->snapshotarr), PointerGetDatum(snapshot)); +} + +/* + * Forget that a snapshot reference is owned by a ResourceOwner + */ +void +ResourceOwnerForgetSnapshot(ResourceOwner owner, Snapshot snapshot) +{ + if (!ResourceArrayRemove(&(owner->snapshotarr), PointerGetDatum(snapshot))) + elog(ERROR, "snapshot reference %p is not owned by resource owner %s", + snapshot, owner->name); +} + +/* + * Debugging subroutine + */ +static void +PrintSnapshotLeakWarning(Snapshot snapshot) +{ + elog(WARNING, "Snapshot reference leak: Snapshot %p still referenced", + snapshot); +} + + +/* + * Make sure there is room for at least one more entry in a ResourceOwner's + * files reference array. + * + * This is separate from actually inserting an entry because if we run out + * of memory, it's critical to do so *before* acquiring the resource. + */ +void +ResourceOwnerEnlargeFiles(ResourceOwner owner) +{ + ResourceArrayEnlarge(&(owner->filearr)); +} + +/* + * Remember that a temporary file is owned by a ResourceOwner + * + * Caller must have previously done ResourceOwnerEnlargeFiles() + */ +void +ResourceOwnerRememberFile(ResourceOwner owner, File file) +{ + ResourceArrayAdd(&(owner->filearr), FileGetDatum(file)); +} + +/* + * Forget that a temporary file is owned by a ResourceOwner + */ +void +ResourceOwnerForgetFile(ResourceOwner owner, File file) +{ + if (!ResourceArrayRemove(&(owner->filearr), FileGetDatum(file))) + elog(ERROR, "temporary file %d is not owned by resource owner %s", + file, owner->name); +} + +/* + * Debugging subroutine + */ +static void +PrintFileLeakWarning(File file) +{ + elog(WARNING, "temporary file leak: File %d still referenced", + file); +} + +/* + * Make sure there is room for at least one more entry in a ResourceOwner's + * dynamic shmem segment reference array. + * + * This is separate from actually inserting an entry because if we run out + * of memory, it's critical to do so *before* acquiring the resource. + */ +void +ResourceOwnerEnlargeDSMs(ResourceOwner owner) +{ + ResourceArrayEnlarge(&(owner->dsmarr)); +} + +/* + * Remember that a dynamic shmem segment is owned by a ResourceOwner + * + * Caller must have previously done ResourceOwnerEnlargeDSMs() + */ +void +ResourceOwnerRememberDSM(ResourceOwner owner, dsm_segment *seg) +{ + ResourceArrayAdd(&(owner->dsmarr), PointerGetDatum(seg)); +} + +/* + * Forget that a dynamic shmem segment is owned by a ResourceOwner + */ +void +ResourceOwnerForgetDSM(ResourceOwner owner, dsm_segment *seg) +{ + if (!ResourceArrayRemove(&(owner->dsmarr), PointerGetDatum(seg))) + elog(ERROR, "dynamic shared memory segment %u is not owned by resource owner %s", + dsm_segment_handle(seg), owner->name); +} + +/* + * Debugging subroutine + */ +static void +PrintDSMLeakWarning(dsm_segment *seg) +{ + elog(WARNING, "dynamic shared memory leak: segment %u still referenced", + dsm_segment_handle(seg)); +} + +/* + * Make sure there is room for at least one more entry in a ResourceOwner's + * JIT context reference array. + * + * This is separate from actually inserting an entry because if we run out of + * memory, it's critical to do so *before* acquiring the resource. + */ +void +ResourceOwnerEnlargeJIT(ResourceOwner owner) +{ + ResourceArrayEnlarge(&(owner->jitarr)); +} + +/* + * Remember that a JIT context is owned by a ResourceOwner + * + * Caller must have previously done ResourceOwnerEnlargeJIT() + */ +void +ResourceOwnerRememberJIT(ResourceOwner owner, Datum handle) +{ + ResourceArrayAdd(&(owner->jitarr), handle); +} + +/* + * Forget that a JIT context is owned by a ResourceOwner + */ +void +ResourceOwnerForgetJIT(ResourceOwner owner, Datum handle) +{ + if (!ResourceArrayRemove(&(owner->jitarr), handle)) + elog(ERROR, "JIT context %p is not owned by resource owner %s", + DatumGetPointer(handle), owner->name); +} + +/* + * Make sure there is room for at least one more entry in a ResourceOwner's + * cryptohash context reference array. + * + * This is separate from actually inserting an entry because if we run out of + * memory, it's critical to do so *before* acquiring the resource. + */ +void +ResourceOwnerEnlargeCryptoHash(ResourceOwner owner) +{ + ResourceArrayEnlarge(&(owner->cryptohasharr)); +} + +/* + * Remember that a cryptohash context is owned by a ResourceOwner + * + * Caller must have previously done ResourceOwnerEnlargeCryptoHash() + */ +void +ResourceOwnerRememberCryptoHash(ResourceOwner owner, Datum handle) +{ + ResourceArrayAdd(&(owner->cryptohasharr), handle); +} + +/* + * Forget that a cryptohash context is owned by a ResourceOwner + */ +void +ResourceOwnerForgetCryptoHash(ResourceOwner owner, Datum handle) +{ + if (!ResourceArrayRemove(&(owner->cryptohasharr), handle)) + elog(ERROR, "cryptohash context %p is not owned by resource owner %s", + DatumGetPointer(handle), owner->name); +} + +/* + * Debugging subroutine + */ +static void +PrintCryptoHashLeakWarning(Datum handle) +{ + elog(WARNING, "cryptohash context reference leak: context %p still referenced", + DatumGetPointer(handle)); +} + +/* + * Make sure there is room for at least one more entry in a ResourceOwner's + * hmac context reference array. + * + * This is separate from actually inserting an entry because if we run out of + * memory, it's critical to do so *before* acquiring the resource. + */ +void +ResourceOwnerEnlargeHMAC(ResourceOwner owner) +{ + ResourceArrayEnlarge(&(owner->hmacarr)); +} + +/* + * Remember that a HMAC context is owned by a ResourceOwner + * + * Caller must have previously done ResourceOwnerEnlargeHMAC() + */ +void +ResourceOwnerRememberHMAC(ResourceOwner owner, Datum handle) +{ + ResourceArrayAdd(&(owner->hmacarr), handle); +} + +/* + * Forget that a HMAC context is owned by a ResourceOwner + */ +void +ResourceOwnerForgetHMAC(ResourceOwner owner, Datum handle) +{ + if (!ResourceArrayRemove(&(owner->hmacarr), handle)) + elog(ERROR, "HMAC context %p is not owned by resource owner %s", + DatumGetPointer(handle), owner->name); +} + +/* + * Debugging subroutine + */ +static void +PrintHMACLeakWarning(Datum handle) +{ + elog(WARNING, "HMAC context reference leak: context %p still referenced", + DatumGetPointer(handle)); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/sort/logtape.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/sort/logtape.c new file mode 100644 index 00000000000..14375b0796b --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/sort/logtape.c @@ -0,0 +1,1184 @@ +/*------------------------------------------------------------------------- + * + * logtape.c + * Management of "logical tapes" within temporary files. + * + * This module exists to support sorting via multiple merge passes (see + * tuplesort.c). Merging is an ideal algorithm for tape devices, but if + * we implement it on disk by creating a separate file for each "tape", + * there is an annoying problem: the peak space usage is at least twice + * the volume of actual data to be sorted. (This must be so because each + * datum will appear in both the input and output tapes of the final + * merge pass.) + * + * We can work around this problem by recognizing that any one tape + * dataset (with the possible exception of the final output) is written + * and read exactly once in a perfectly sequential manner. Therefore, + * a datum once read will not be required again, and we can recycle its + * space for use by the new tape dataset(s) being generated. In this way, + * the total space usage is essentially just the actual data volume, plus + * insignificant bookkeeping and start/stop overhead. + * + * Few OSes allow arbitrary parts of a file to be released back to the OS, + * so we have to implement this space-recycling ourselves within a single + * logical file. logtape.c exists to perform this bookkeeping and provide + * the illusion of N independent tape devices to tuplesort.c. Note that + * logtape.c itself depends on buffile.c to provide a "logical file" of + * larger size than the underlying OS may support. + * + * For simplicity, we allocate and release space in the underlying file + * in BLCKSZ-size blocks. Space allocation boils down to keeping track + * of which blocks in the underlying file belong to which logical tape, + * plus any blocks that are free (recycled and not yet reused). + * The blocks in each logical tape form a chain, with a prev- and next- + * pointer in each block. + * + * The initial write pass is guaranteed to fill the underlying file + * perfectly sequentially, no matter how data is divided into logical tapes. + * Once we begin merge passes, the access pattern becomes considerably + * less predictable --- but the seeking involved should be comparable to + * what would happen if we kept each logical tape in a separate file, + * so there's no serious performance penalty paid to obtain the space + * savings of recycling. We try to localize the write accesses by always + * writing to the lowest-numbered free block when we have a choice; it's + * not clear this helps much, but it can't hurt. (XXX perhaps a LIFO + * policy for free blocks would be better?) + * + * To further make the I/Os more sequential, we can use a larger buffer + * when reading, and read multiple blocks from the same tape in one go, + * whenever the buffer becomes empty. + * + * To support the above policy of writing to the lowest free block, the + * freelist is a min heap. + * + * Since all the bookkeeping and buffer memory is allocated with palloc(), + * and the underlying file(s) are made with OpenTemporaryFile, all resources + * for a logical tape set are certain to be cleaned up even if processing + * is aborted by ereport(ERROR). To avoid confusion, the caller should take + * care that all calls for a single LogicalTapeSet are made in the same + * palloc context. + * + * To support parallel sort operations involving coordinated callers to + * tuplesort.c routines across multiple workers, it is necessary to + * concatenate each worker BufFile/tapeset into one single logical tapeset + * managed by the leader. Workers should have produced one final + * materialized tape (their entire output) when this happens in leader. + * There will always be the same number of runs as input tapes, and the same + * number of input tapes as participants (worker Tuplesortstates). + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/utils/sort/logtape.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include <fcntl.h> + +#include "storage/buffile.h" +#include "utils/builtins.h" +#include "utils/logtape.h" +#include "utils/memdebug.h" +#include "utils/memutils.h" + +/* + * A TapeBlockTrailer is stored at the end of each BLCKSZ block. + * + * The first block of a tape has prev == -1. The last block of a tape + * stores the number of valid bytes on the block, inverted, in 'next' + * Therefore next < 0 indicates the last block. + */ +typedef struct TapeBlockTrailer +{ + long prev; /* previous block on this tape, or -1 on first + * block */ + long next; /* next block on this tape, or # of valid + * bytes on last block (if < 0) */ +} TapeBlockTrailer; + +#define TapeBlockPayloadSize (BLCKSZ - sizeof(TapeBlockTrailer)) +#define TapeBlockGetTrailer(buf) \ + ((TapeBlockTrailer *) ((char *) buf + TapeBlockPayloadSize)) + +#define TapeBlockIsLast(buf) (TapeBlockGetTrailer(buf)->next < 0) +#define TapeBlockGetNBytes(buf) \ + (TapeBlockIsLast(buf) ? \ + (- TapeBlockGetTrailer(buf)->next) : TapeBlockPayloadSize) +#define TapeBlockSetNBytes(buf, nbytes) \ + (TapeBlockGetTrailer(buf)->next = -(nbytes)) + +/* + * When multiple tapes are being written to concurrently (as in HashAgg), + * avoid excessive fragmentation by preallocating block numbers to individual + * tapes. Each preallocation doubles in size starting at + * TAPE_WRITE_PREALLOC_MIN blocks up to TAPE_WRITE_PREALLOC_MAX blocks. + * + * No filesystem operations are performed for preallocation; only the block + * numbers are reserved. This may lead to sparse writes, which will cause + * ltsWriteBlock() to fill in holes with zeros. + */ +#define TAPE_WRITE_PREALLOC_MIN 8 +#define TAPE_WRITE_PREALLOC_MAX 128 + +/* + * This data structure represents a single "logical tape" within the set + * of logical tapes stored in the same file. + * + * While writing, we hold the current partially-written data block in the + * buffer. While reading, we can hold multiple blocks in the buffer. Note + * that we don't retain the trailers of a block when it's read into the + * buffer. The buffer therefore contains one large contiguous chunk of data + * from the tape. + */ +struct LogicalTape +{ + LogicalTapeSet *tapeSet; /* tape set this tape is part of */ + + bool writing; /* T while in write phase */ + bool frozen; /* T if blocks should not be freed when read */ + bool dirty; /* does buffer need to be written? */ + + /* + * Block numbers of the first, current, and next block of the tape. + * + * The "current" block number is only valid when writing, or reading from + * a frozen tape. (When reading from an unfrozen tape, we use a larger + * read buffer that holds multiple blocks, so the "current" block is + * ambiguous.) + * + * When concatenation of worker tape BufFiles is performed, an offset to + * the first block in the unified BufFile space is applied during reads. + */ + long firstBlockNumber; + long curBlockNumber; + long nextBlockNumber; + long offsetBlockNumber; + + /* + * Buffer for current data block(s). + */ + char *buffer; /* physical buffer (separately palloc'd) */ + int buffer_size; /* allocated size of the buffer */ + int max_size; /* highest useful, safe buffer_size */ + int pos; /* next read/write position in buffer */ + int nbytes; /* total # of valid bytes in buffer */ + + /* + * Preallocated block numbers are held in an array sorted in descending + * order; blocks are consumed from the end of the array (lowest block + * numbers first). + */ + long *prealloc; + int nprealloc; /* number of elements in list */ + int prealloc_size; /* number of elements list can hold */ +}; + +/* + * This data structure represents a set of related "logical tapes" sharing + * space in a single underlying file. (But that "file" may be multiple files + * if needed to escape OS limits on file size; buffile.c handles that for us.) + * Tapes belonging to a tape set can be created and destroyed on-the-fly, on + * demand. + */ +struct LogicalTapeSet +{ + BufFile *pfile; /* underlying file for whole tape set */ + SharedFileSet *fileset; + int worker; /* worker # if shared, -1 for leader/serial */ + + /* + * File size tracking. nBlocksWritten is the size of the underlying file, + * in BLCKSZ blocks. nBlocksAllocated is the number of blocks allocated + * by ltsReleaseBlock(), and it is always greater than or equal to + * nBlocksWritten. Blocks between nBlocksAllocated and nBlocksWritten are + * blocks that have been allocated for a tape, but have not been written + * to the underlying file yet. nHoleBlocks tracks the total number of + * blocks that are in unused holes between worker spaces following BufFile + * concatenation. + */ + long nBlocksAllocated; /* # of blocks allocated */ + long nBlocksWritten; /* # of blocks used in underlying file */ + long nHoleBlocks; /* # of "hole" blocks left */ + + /* + * We store the numbers of recycled-and-available blocks in freeBlocks[]. + * When there are no such blocks, we extend the underlying file. + * + * If forgetFreeSpace is true then any freed blocks are simply forgotten + * rather than being remembered in freeBlocks[]. See notes for + * LogicalTapeSetForgetFreeSpace(). + */ + bool forgetFreeSpace; /* are we remembering free blocks? */ + long *freeBlocks; /* resizable array holding minheap */ + long nFreeBlocks; /* # of currently free blocks */ + Size freeBlocksLen; /* current allocated length of freeBlocks[] */ + bool enable_prealloc; /* preallocate write blocks? */ +}; + +static LogicalTape *ltsCreateTape(LogicalTapeSet *lts); +static void ltsWriteBlock(LogicalTapeSet *lts, long blocknum, const void *buffer); +static void ltsReadBlock(LogicalTapeSet *lts, long blocknum, void *buffer); +static long ltsGetBlock(LogicalTapeSet *lts, LogicalTape *lt); +static long ltsGetFreeBlock(LogicalTapeSet *lts); +static long ltsGetPreallocBlock(LogicalTapeSet *lts, LogicalTape *lt); +static void ltsReleaseBlock(LogicalTapeSet *lts, long blocknum); +static void ltsInitReadBuffer(LogicalTape *lt); + + +/* + * Write a block-sized buffer to the specified block of the underlying file. + * + * No need for an error return convention; we ereport() on any error. + */ +static void +ltsWriteBlock(LogicalTapeSet *lts, long blocknum, const void *buffer) +{ + /* + * BufFile does not support "holes", so if we're about to write a block + * that's past the current end of file, fill the space between the current + * end of file and the target block with zeros. + * + * This can happen either when tapes preallocate blocks; or for the last + * block of a tape which might not have been flushed. + * + * Note that BufFile concatenation can leave "holes" in BufFile between + * worker-owned block ranges. These are tracked for reporting purposes + * only. We never read from nor write to these hole blocks, and so they + * are not considered here. + */ + while (blocknum > lts->nBlocksWritten) + { + PGIOAlignedBlock zerobuf; + + MemSet(zerobuf.data, 0, sizeof(zerobuf)); + + ltsWriteBlock(lts, lts->nBlocksWritten, zerobuf.data); + } + + /* Write the requested block */ + if (BufFileSeekBlock(lts->pfile, blocknum) != 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not seek to block %ld of temporary file", + blocknum))); + BufFileWrite(lts->pfile, buffer, BLCKSZ); + + /* Update nBlocksWritten, if we extended the file */ + if (blocknum == lts->nBlocksWritten) + lts->nBlocksWritten++; +} + +/* + * Read a block-sized buffer from the specified block of the underlying file. + * + * No need for an error return convention; we ereport() on any error. This + * module should never attempt to read a block it doesn't know is there. + */ +static void +ltsReadBlock(LogicalTapeSet *lts, long blocknum, void *buffer) +{ + if (BufFileSeekBlock(lts->pfile, blocknum) != 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not seek to block %ld of temporary file", + blocknum))); + BufFileReadExact(lts->pfile, buffer, BLCKSZ); +} + +/* + * Read as many blocks as we can into the per-tape buffer. + * + * Returns true if anything was read, 'false' on EOF. + */ +static bool +ltsReadFillBuffer(LogicalTape *lt) +{ + lt->pos = 0; + lt->nbytes = 0; + + do + { + char *thisbuf = lt->buffer + lt->nbytes; + long datablocknum = lt->nextBlockNumber; + + /* Fetch next block number */ + if (datablocknum == -1L) + break; /* EOF */ + /* Apply worker offset, needed for leader tapesets */ + datablocknum += lt->offsetBlockNumber; + + /* Read the block */ + ltsReadBlock(lt->tapeSet, datablocknum, thisbuf); + if (!lt->frozen) + ltsReleaseBlock(lt->tapeSet, datablocknum); + lt->curBlockNumber = lt->nextBlockNumber; + + lt->nbytes += TapeBlockGetNBytes(thisbuf); + if (TapeBlockIsLast(thisbuf)) + { + lt->nextBlockNumber = -1L; + /* EOF */ + break; + } + else + lt->nextBlockNumber = TapeBlockGetTrailer(thisbuf)->next; + + /* Advance to next block, if we have buffer space left */ + } while (lt->buffer_size - lt->nbytes > BLCKSZ); + + return (lt->nbytes > 0); +} + +static inline unsigned long +left_offset(unsigned long i) +{ + return 2 * i + 1; +} + +static inline unsigned long +right_offset(unsigned long i) +{ + return 2 * i + 2; +} + +static inline unsigned long +parent_offset(unsigned long i) +{ + return (i - 1) / 2; +} + +/* + * Get the next block for writing. + */ +static long +ltsGetBlock(LogicalTapeSet *lts, LogicalTape *lt) +{ + if (lts->enable_prealloc) + return ltsGetPreallocBlock(lts, lt); + else + return ltsGetFreeBlock(lts); +} + +/* + * Select the lowest currently unused block from the tape set's global free + * list min heap. + */ +static long +ltsGetFreeBlock(LogicalTapeSet *lts) +{ + long *heap = lts->freeBlocks; + long blocknum; + long heapsize; + long holeval; + unsigned long holepos; + + /* freelist empty; allocate a new block */ + if (lts->nFreeBlocks == 0) + return lts->nBlocksAllocated++; + + /* easy if heap contains one element */ + if (lts->nFreeBlocks == 1) + { + lts->nFreeBlocks--; + return lts->freeBlocks[0]; + } + + /* remove top of minheap */ + blocknum = heap[0]; + + /* we'll replace it with end of minheap array */ + holeval = heap[--lts->nFreeBlocks]; + + /* sift down */ + holepos = 0; /* holepos is where the "hole" is */ + heapsize = lts->nFreeBlocks; + while (true) + { + unsigned long left = left_offset(holepos); + unsigned long right = right_offset(holepos); + unsigned long min_child; + + if (left < heapsize && right < heapsize) + min_child = (heap[left] < heap[right]) ? left : right; + else if (left < heapsize) + min_child = left; + else if (right < heapsize) + min_child = right; + else + break; + + if (heap[min_child] >= holeval) + break; + + heap[holepos] = heap[min_child]; + holepos = min_child; + } + heap[holepos] = holeval; + + return blocknum; +} + +/* + * Return the lowest free block number from the tape's preallocation list. + * Refill the preallocation list with blocks from the tape set's free list if + * necessary. + */ +static long +ltsGetPreallocBlock(LogicalTapeSet *lts, LogicalTape *lt) +{ + /* sorted in descending order, so return the last element */ + if (lt->nprealloc > 0) + return lt->prealloc[--lt->nprealloc]; + + if (lt->prealloc == NULL) + { + lt->prealloc_size = TAPE_WRITE_PREALLOC_MIN; + lt->prealloc = (long *) palloc(sizeof(long) * lt->prealloc_size); + } + else if (lt->prealloc_size < TAPE_WRITE_PREALLOC_MAX) + { + /* when the preallocation list runs out, double the size */ + lt->prealloc_size *= 2; + if (lt->prealloc_size > TAPE_WRITE_PREALLOC_MAX) + lt->prealloc_size = TAPE_WRITE_PREALLOC_MAX; + lt->prealloc = (long *) repalloc(lt->prealloc, + sizeof(long) * lt->prealloc_size); + } + + /* refill preallocation list */ + lt->nprealloc = lt->prealloc_size; + for (int i = lt->nprealloc; i > 0; i--) + { + lt->prealloc[i - 1] = ltsGetFreeBlock(lts); + + /* verify descending order */ + Assert(i == lt->nprealloc || lt->prealloc[i - 1] > lt->prealloc[i]); + } + + return lt->prealloc[--lt->nprealloc]; +} + +/* + * Return a block# to the freelist. + */ +static void +ltsReleaseBlock(LogicalTapeSet *lts, long blocknum) +{ + long *heap; + unsigned long holepos; + + /* + * Do nothing if we're no longer interested in remembering free space. + */ + if (lts->forgetFreeSpace) + return; + + /* + * Enlarge freeBlocks array if full. + */ + if (lts->nFreeBlocks >= lts->freeBlocksLen) + { + /* + * If the freelist becomes very large, just return and leak this free + * block. + */ + if (lts->freeBlocksLen * 2 * sizeof(long) > MaxAllocSize) + return; + + lts->freeBlocksLen *= 2; + lts->freeBlocks = (long *) repalloc(lts->freeBlocks, + lts->freeBlocksLen * sizeof(long)); + } + + /* create a "hole" at end of minheap array */ + heap = lts->freeBlocks; + holepos = lts->nFreeBlocks; + lts->nFreeBlocks++; + + /* sift up to insert blocknum */ + while (holepos != 0) + { + unsigned long parent = parent_offset(holepos); + + if (heap[parent] < blocknum) + break; + + heap[holepos] = heap[parent]; + holepos = parent; + } + heap[holepos] = blocknum; +} + +/* + * Lazily allocate and initialize the read buffer. This avoids waste when many + * tapes are open at once, but not all are active between rewinding and + * reading. + */ +static void +ltsInitReadBuffer(LogicalTape *lt) +{ + Assert(lt->buffer_size > 0); + lt->buffer = palloc(lt->buffer_size); + + /* Read the first block, or reset if tape is empty */ + lt->nextBlockNumber = lt->firstBlockNumber; + lt->pos = 0; + lt->nbytes = 0; + ltsReadFillBuffer(lt); +} + +/* + * Create a tape set, backed by a temporary underlying file. + * + * The tape set is initially empty. Use LogicalTapeCreate() to create + * tapes in it. + * + * In a single-process sort, pass NULL argument for fileset, and -1 for + * worker. + * + * In a parallel sort, parallel workers pass the shared fileset handle and + * their own worker number. After the workers have finished, create the + * tape set in the leader, passing the shared fileset handle and -1 for + * worker, and use LogicalTapeImport() to import the worker tapes into it. + * + * Currently, the leader will only import worker tapes into the set, it does + * not create tapes of its own, although in principle that should work. + * + * If preallocate is true, blocks for each individual tape are allocated in + * batches. This avoids fragmentation when writing multiple tapes at the + * same time. + */ +LogicalTapeSet * +LogicalTapeSetCreate(bool preallocate, SharedFileSet *fileset, int worker) +{ + LogicalTapeSet *lts; + + /* + * Create top-level struct including per-tape LogicalTape structs. + */ + lts = (LogicalTapeSet *) palloc(sizeof(LogicalTapeSet)); + lts->nBlocksAllocated = 0L; + lts->nBlocksWritten = 0L; + lts->nHoleBlocks = 0L; + lts->forgetFreeSpace = false; + lts->freeBlocksLen = 32; /* reasonable initial guess */ + lts->freeBlocks = (long *) palloc(lts->freeBlocksLen * sizeof(long)); + lts->nFreeBlocks = 0; + lts->enable_prealloc = preallocate; + + lts->fileset = fileset; + lts->worker = worker; + + /* + * Create temp BufFile storage as required. + * + * In leader, we hijack the BufFile of the first tape that's imported, and + * concatenate the BufFiles of any subsequent tapes to that. Hence don't + * create a BufFile here. Things are simpler for the worker case and the + * serial case, though. They are generally very similar -- workers use a + * shared fileset, whereas serial sorts use a conventional serial BufFile. + */ + if (fileset && worker == -1) + lts->pfile = NULL; + else if (fileset) + { + char filename[MAXPGPATH]; + + pg_itoa(worker, filename); + lts->pfile = BufFileCreateFileSet(&fileset->fs, filename); + } + else + lts->pfile = BufFileCreateTemp(false); + + return lts; +} + +/* + * Claim ownership of a logical tape from an existing shared BufFile. + * + * Caller should be leader process. Though tapes are marked as frozen in + * workers, they are not frozen when opened within leader, since unfrozen tapes + * use a larger read buffer. (Frozen tapes have smaller read buffer, optimized + * for random access.) + */ +LogicalTape * +LogicalTapeImport(LogicalTapeSet *lts, int worker, TapeShare *shared) +{ + LogicalTape *lt; + long tapeblocks; + char filename[MAXPGPATH]; + BufFile *file; + int64 filesize; + + lt = ltsCreateTape(lts); + + /* + * build concatenated view of all buffiles, remembering the block number + * where each source file begins. + */ + pg_itoa(worker, filename); + file = BufFileOpenFileSet(<s->fileset->fs, filename, O_RDONLY, false); + filesize = BufFileSize(file); + + /* + * Stash first BufFile, and concatenate subsequent BufFiles to that. Store + * block offset into each tape as we go. + */ + lt->firstBlockNumber = shared->firstblocknumber; + if (lts->pfile == NULL) + { + lts->pfile = file; + lt->offsetBlockNumber = 0L; + } + else + { + lt->offsetBlockNumber = BufFileAppend(lts->pfile, file); + } + /* Don't allocate more for read buffer than could possibly help */ + lt->max_size = Min(MaxAllocSize, filesize); + tapeblocks = filesize / BLCKSZ; + + /* + * Update # of allocated blocks and # blocks written to reflect the + * imported BufFile. Allocated/written blocks include space used by holes + * left between concatenated BufFiles. Also track the number of hole + * blocks so that we can later work backwards to calculate the number of + * physical blocks for instrumentation. + */ + lts->nHoleBlocks += lt->offsetBlockNumber - lts->nBlocksAllocated; + + lts->nBlocksAllocated = lt->offsetBlockNumber + tapeblocks; + lts->nBlocksWritten = lts->nBlocksAllocated; + + return lt; +} + +/* + * Close a logical tape set and release all resources. + * + * NOTE: This doesn't close any of the tapes! You must close them + * first, or you can let them be destroyed along with the memory context. + */ +void +LogicalTapeSetClose(LogicalTapeSet *lts) +{ + BufFileClose(lts->pfile); + pfree(lts->freeBlocks); + pfree(lts); +} + +/* + * Create a logical tape in the given tapeset. + * + * The tape is initialized in write state. + */ +LogicalTape * +LogicalTapeCreate(LogicalTapeSet *lts) +{ + /* + * The only thing that currently prevents creating new tapes in leader is + * the fact that BufFiles opened using BufFileOpenShared() are read-only + * by definition, but that could be changed if it seemed worthwhile. For + * now, writing to the leader tape will raise a "Bad file descriptor" + * error, so tuplesort must avoid writing to the leader tape altogether. + */ + if (lts->fileset && lts->worker == -1) + elog(ERROR, "cannot create new tapes in leader process"); + + return ltsCreateTape(lts); +} + +static LogicalTape * +ltsCreateTape(LogicalTapeSet *lts) +{ + LogicalTape *lt; + + /* + * Create per-tape struct. Note we allocate the I/O buffer lazily. + */ + lt = palloc(sizeof(LogicalTape)); + lt->tapeSet = lts; + lt->writing = true; + lt->frozen = false; + lt->dirty = false; + lt->firstBlockNumber = -1L; + lt->curBlockNumber = -1L; + lt->nextBlockNumber = -1L; + lt->offsetBlockNumber = 0L; + lt->buffer = NULL; + lt->buffer_size = 0; + /* palloc() larger than MaxAllocSize would fail */ + lt->max_size = MaxAllocSize; + lt->pos = 0; + lt->nbytes = 0; + lt->prealloc = NULL; + lt->nprealloc = 0; + lt->prealloc_size = 0; + + return lt; +} + +/* + * Close a logical tape. + * + * Note: This doesn't return any blocks to the free list! You must read + * the tape to the end first, to reuse the space. In current use, though, + * we only close tapes after fully reading them. + */ +void +LogicalTapeClose(LogicalTape *lt) +{ + if (lt->buffer) + pfree(lt->buffer); + pfree(lt); +} + +/* + * Mark a logical tape set as not needing management of free space anymore. + * + * This should be called if the caller does not intend to write any more data + * into the tape set, but is reading from un-frozen tapes. Since no more + * writes are planned, remembering free blocks is no longer useful. Setting + * this flag lets us avoid wasting time and space in ltsReleaseBlock(), which + * is not designed to handle large numbers of free blocks. + */ +void +LogicalTapeSetForgetFreeSpace(LogicalTapeSet *lts) +{ + lts->forgetFreeSpace = true; +} + +/* + * Write to a logical tape. + * + * There are no error returns; we ereport() on failure. + */ +void +LogicalTapeWrite(LogicalTape *lt, const void *ptr, size_t size) +{ + LogicalTapeSet *lts = lt->tapeSet; + size_t nthistime; + + Assert(lt->writing); + Assert(lt->offsetBlockNumber == 0L); + + /* Allocate data buffer and first block on first write */ + if (lt->buffer == NULL) + { + lt->buffer = (char *) palloc(BLCKSZ); + lt->buffer_size = BLCKSZ; + } + if (lt->curBlockNumber == -1) + { + Assert(lt->firstBlockNumber == -1); + Assert(lt->pos == 0); + + lt->curBlockNumber = ltsGetBlock(lts, lt); + lt->firstBlockNumber = lt->curBlockNumber; + + TapeBlockGetTrailer(lt->buffer)->prev = -1L; + } + + Assert(lt->buffer_size == BLCKSZ); + while (size > 0) + { + if (lt->pos >= (int) TapeBlockPayloadSize) + { + /* Buffer full, dump it out */ + long nextBlockNumber; + + if (!lt->dirty) + { + /* Hmm, went directly from reading to writing? */ + elog(ERROR, "invalid logtape state: should be dirty"); + } + + /* + * First allocate the next block, so that we can store it in the + * 'next' pointer of this block. + */ + nextBlockNumber = ltsGetBlock(lt->tapeSet, lt); + + /* set the next-pointer and dump the current block. */ + TapeBlockGetTrailer(lt->buffer)->next = nextBlockNumber; + ltsWriteBlock(lt->tapeSet, lt->curBlockNumber, lt->buffer); + + /* initialize the prev-pointer of the next block */ + TapeBlockGetTrailer(lt->buffer)->prev = lt->curBlockNumber; + lt->curBlockNumber = nextBlockNumber; + lt->pos = 0; + lt->nbytes = 0; + } + + nthistime = TapeBlockPayloadSize - lt->pos; + if (nthistime > size) + nthistime = size; + Assert(nthistime > 0); + + memcpy(lt->buffer + lt->pos, ptr, nthistime); + + lt->dirty = true; + lt->pos += nthistime; + if (lt->nbytes < lt->pos) + lt->nbytes = lt->pos; + ptr = (const char *) ptr + nthistime; + size -= nthistime; + } +} + +/* + * Rewind logical tape and switch from writing to reading. + * + * The tape must currently be in writing state, or "frozen" in read state. + * + * 'buffer_size' specifies how much memory to use for the read buffer. + * Regardless of the argument, the actual amount of memory used is between + * BLCKSZ and MaxAllocSize, and is a multiple of BLCKSZ. The given value is + * rounded down and truncated to fit those constraints, if necessary. If the + * tape is frozen, the 'buffer_size' argument is ignored, and a small BLCKSZ + * byte buffer is used. + */ +void +LogicalTapeRewindForRead(LogicalTape *lt, size_t buffer_size) +{ + LogicalTapeSet *lts = lt->tapeSet; + + /* + * Round and cap buffer_size if needed. + */ + if (lt->frozen) + buffer_size = BLCKSZ; + else + { + /* need at least one block */ + if (buffer_size < BLCKSZ) + buffer_size = BLCKSZ; + + /* palloc() larger than max_size is unlikely to be helpful */ + if (buffer_size > lt->max_size) + buffer_size = lt->max_size; + + /* round down to BLCKSZ boundary */ + buffer_size -= buffer_size % BLCKSZ; + } + + if (lt->writing) + { + /* + * Completion of a write phase. Flush last partial data block, and + * rewind for normal (destructive) read. + */ + if (lt->dirty) + { + /* + * As long as we've filled the buffer at least once, its contents + * are entirely defined from valgrind's point of view, even though + * contents beyond the current end point may be stale. But it's + * possible - at least in the case of a parallel sort - to sort + * such small amount of data that we do not fill the buffer even + * once. Tell valgrind that its contents are defined, so it + * doesn't bleat. + */ + VALGRIND_MAKE_MEM_DEFINED(lt->buffer + lt->nbytes, + lt->buffer_size - lt->nbytes); + + TapeBlockSetNBytes(lt->buffer, lt->nbytes); + ltsWriteBlock(lt->tapeSet, lt->curBlockNumber, lt->buffer); + } + lt->writing = false; + } + else + { + /* + * This is only OK if tape is frozen; we rewind for (another) read + * pass. + */ + Assert(lt->frozen); + } + + if (lt->buffer) + pfree(lt->buffer); + + /* the buffer is lazily allocated, but set the size here */ + lt->buffer = NULL; + lt->buffer_size = buffer_size; + + /* free the preallocation list, and return unused block numbers */ + if (lt->prealloc != NULL) + { + for (int i = lt->nprealloc; i > 0; i--) + ltsReleaseBlock(lts, lt->prealloc[i - 1]); + pfree(lt->prealloc); + lt->prealloc = NULL; + lt->nprealloc = 0; + lt->prealloc_size = 0; + } +} + +/* + * Read from a logical tape. + * + * Early EOF is indicated by return value less than #bytes requested. + */ +size_t +LogicalTapeRead(LogicalTape *lt, void *ptr, size_t size) +{ + size_t nread = 0; + size_t nthistime; + + Assert(!lt->writing); + + if (lt->buffer == NULL) + ltsInitReadBuffer(lt); + + while (size > 0) + { + if (lt->pos >= lt->nbytes) + { + /* Try to load more data into buffer. */ + if (!ltsReadFillBuffer(lt)) + break; /* EOF */ + } + + nthistime = lt->nbytes - lt->pos; + if (nthistime > size) + nthistime = size; + Assert(nthistime > 0); + + memcpy(ptr, lt->buffer + lt->pos, nthistime); + + lt->pos += nthistime; + ptr = (char *) ptr + nthistime; + size -= nthistime; + nread += nthistime; + } + + return nread; +} + +/* + * "Freeze" the contents of a tape so that it can be read multiple times + * and/or read backwards. Once a tape is frozen, its contents will not + * be released until the LogicalTapeSet is destroyed. This is expected + * to be used only for the final output pass of a merge. + * + * This *must* be called just at the end of a write pass, before the + * tape is rewound (after rewind is too late!). It performs a rewind + * and switch to read mode "for free". An immediately following rewind- + * for-read call is OK but not necessary. + * + * share output argument is set with details of storage used for tape after + * freezing, which may be passed to LogicalTapeSetCreate within leader + * process later. This metadata is only of interest to worker callers + * freezing their final output for leader (single materialized tape). + * Serial sorts should set share to NULL. + */ +void +LogicalTapeFreeze(LogicalTape *lt, TapeShare *share) +{ + LogicalTapeSet *lts = lt->tapeSet; + + Assert(lt->writing); + Assert(lt->offsetBlockNumber == 0L); + + /* + * Completion of a write phase. Flush last partial data block, and rewind + * for nondestructive read. + */ + if (lt->dirty) + { + /* + * As long as we've filled the buffer at least once, its contents are + * entirely defined from valgrind's point of view, even though + * contents beyond the current end point may be stale. But it's + * possible - at least in the case of a parallel sort - to sort such + * small amount of data that we do not fill the buffer even once. Tell + * valgrind that its contents are defined, so it doesn't bleat. + */ + VALGRIND_MAKE_MEM_DEFINED(lt->buffer + lt->nbytes, + lt->buffer_size - lt->nbytes); + + TapeBlockSetNBytes(lt->buffer, lt->nbytes); + ltsWriteBlock(lt->tapeSet, lt->curBlockNumber, lt->buffer); + } + lt->writing = false; + lt->frozen = true; + + /* + * The seek and backspace functions assume a single block read buffer. + * That's OK with current usage. A larger buffer is helpful to make the + * read pattern of the backing file look more sequential to the OS, when + * we're reading from multiple tapes. But at the end of a sort, when a + * tape is frozen, we only read from a single tape anyway. + */ + if (!lt->buffer || lt->buffer_size != BLCKSZ) + { + if (lt->buffer) + pfree(lt->buffer); + lt->buffer = palloc(BLCKSZ); + lt->buffer_size = BLCKSZ; + } + + /* Read the first block, or reset if tape is empty */ + lt->curBlockNumber = lt->firstBlockNumber; + lt->pos = 0; + lt->nbytes = 0; + + if (lt->firstBlockNumber == -1L) + lt->nextBlockNumber = -1L; + ltsReadBlock(lt->tapeSet, lt->curBlockNumber, lt->buffer); + if (TapeBlockIsLast(lt->buffer)) + lt->nextBlockNumber = -1L; + else + lt->nextBlockNumber = TapeBlockGetTrailer(lt->buffer)->next; + lt->nbytes = TapeBlockGetNBytes(lt->buffer); + + /* Handle extra steps when caller is to share its tapeset */ + if (share) + { + BufFileExportFileSet(lts->pfile); + share->firstblocknumber = lt->firstBlockNumber; + } +} + +/* + * Backspace the tape a given number of bytes. (We also support a more + * general seek interface, see below.) + * + * *Only* a frozen-for-read tape can be backed up; we don't support + * random access during write, and an unfrozen read tape may have + * already discarded the desired data! + * + * Returns the number of bytes backed up. It can be less than the + * requested amount, if there isn't that much data before the current + * position. The tape is positioned to the beginning of the tape in + * that case. + */ +size_t +LogicalTapeBackspace(LogicalTape *lt, size_t size) +{ + size_t seekpos = 0; + + Assert(lt->frozen); + Assert(lt->buffer_size == BLCKSZ); + + if (lt->buffer == NULL) + ltsInitReadBuffer(lt); + + /* + * Easy case for seek within current block. + */ + if (size <= (size_t) lt->pos) + { + lt->pos -= (int) size; + return size; + } + + /* + * Not-so-easy case, have to walk back the chain of blocks. This + * implementation would be pretty inefficient for long seeks, but we + * really aren't doing that (a seek over one tuple is typical). + */ + seekpos = (size_t) lt->pos; /* part within this block */ + while (size > seekpos) + { + long prev = TapeBlockGetTrailer(lt->buffer)->prev; + + if (prev == -1L) + { + /* Tried to back up beyond the beginning of tape. */ + if (lt->curBlockNumber != lt->firstBlockNumber) + elog(ERROR, "unexpected end of tape"); + lt->pos = 0; + return seekpos; + } + + ltsReadBlock(lt->tapeSet, prev, lt->buffer); + + if (TapeBlockGetTrailer(lt->buffer)->next != lt->curBlockNumber) + elog(ERROR, "broken tape, next of block %ld is %ld, expected %ld", + prev, + TapeBlockGetTrailer(lt->buffer)->next, + lt->curBlockNumber); + + lt->nbytes = TapeBlockPayloadSize; + lt->curBlockNumber = prev; + lt->nextBlockNumber = TapeBlockGetTrailer(lt->buffer)->next; + + seekpos += TapeBlockPayloadSize; + } + + /* + * 'seekpos' can now be greater than 'size', because it points to the + * beginning the target block. The difference is the position within the + * page. + */ + lt->pos = seekpos - size; + return size; +} + +/* + * Seek to an arbitrary position in a logical tape. + * + * *Only* a frozen-for-read tape can be seeked. + * + * Must be called with a block/offset previously returned by + * LogicalTapeTell(). + */ +void +LogicalTapeSeek(LogicalTape *lt, long blocknum, int offset) +{ + Assert(lt->frozen); + Assert(offset >= 0 && offset <= TapeBlockPayloadSize); + Assert(lt->buffer_size == BLCKSZ); + + if (lt->buffer == NULL) + ltsInitReadBuffer(lt); + + if (blocknum != lt->curBlockNumber) + { + ltsReadBlock(lt->tapeSet, blocknum, lt->buffer); + lt->curBlockNumber = blocknum; + lt->nbytes = TapeBlockPayloadSize; + lt->nextBlockNumber = TapeBlockGetTrailer(lt->buffer)->next; + } + + if (offset > lt->nbytes) + elog(ERROR, "invalid tape seek position"); + lt->pos = offset; +} + +/* + * Obtain current position in a form suitable for a later LogicalTapeSeek. + * + * NOTE: it'd be OK to do this during write phase with intention of using + * the position for a seek after freezing. Not clear if anyone needs that. + */ +void +LogicalTapeTell(LogicalTape *lt, long *blocknum, int *offset) +{ + if (lt->buffer == NULL) + ltsInitReadBuffer(lt); + + Assert(lt->offsetBlockNumber == 0L); + + /* With a larger buffer, 'pos' wouldn't be the same as offset within page */ + Assert(lt->buffer_size == BLCKSZ); + + *blocknum = lt->curBlockNumber; + *offset = lt->pos; +} + +/* + * Obtain total disk space currently used by a LogicalTapeSet, in blocks. Does + * not account for open write buffer, if any. + */ +long +LogicalTapeSetBlocks(LogicalTapeSet *lts) +{ + return lts->nBlocksWritten - lts->nHoleBlocks; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/sort/qsort_interruptible.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/sort/qsort_interruptible.c new file mode 100644 index 00000000000..f179b256248 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/sort/qsort_interruptible.c @@ -0,0 +1,16 @@ +/* + * qsort_interruptible.c: qsort_arg that includes CHECK_FOR_INTERRUPTS + */ + +#include "postgres.h" +#include "miscadmin.h" + +#define ST_SORT qsort_interruptible +#define ST_ELEMENT_TYPE_VOID +#define ST_COMPARATOR_TYPE_NAME qsort_arg_comparator +#define ST_COMPARE_RUNTIME_POINTER +#define ST_COMPARE_ARG_TYPE void +#define ST_SCOPE +#define ST_DEFINE +#define ST_CHECK_FOR_INTERRUPTS +#include "lib/sort_template.h" diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/sort/sharedtuplestore.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/sort/sharedtuplestore.c new file mode 100644 index 00000000000..236be65f221 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/sort/sharedtuplestore.c @@ -0,0 +1,602 @@ +/*------------------------------------------------------------------------- + * + * sharedtuplestore.c + * Simple mechanism for sharing tuples between backends. + * + * This module contains a shared temporary tuple storage mechanism providing + * a parallel-aware subset of the features of tuplestore.c. Multiple backends + * can write to a SharedTuplestore, and then multiple backends can later scan + * the stored tuples. Currently, the only scan type supported is a parallel + * scan where each backend reads an arbitrary subset of the tuples that were + * written. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/utils/sort/sharedtuplestore.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/htup.h" +#include "access/htup_details.h" +#include "miscadmin.h" +#include "storage/buffile.h" +#include "storage/lwlock.h" +#include "storage/sharedfileset.h" +#include "utils/sharedtuplestore.h" + +/* + * The size of chunks, in pages. This is somewhat arbitrarily set to match + * the size of HASH_CHUNK, so that Parallel Hash obtains new chunks of tuples + * at approximately the same rate as it allocates new chunks of memory to + * insert them into. + */ +#define STS_CHUNK_PAGES 4 +#define STS_CHUNK_HEADER_SIZE offsetof(SharedTuplestoreChunk, data) +#define STS_CHUNK_DATA_SIZE (STS_CHUNK_PAGES * BLCKSZ - STS_CHUNK_HEADER_SIZE) + +/* Chunk written to disk. */ +typedef struct SharedTuplestoreChunk +{ + int ntuples; /* Number of tuples in this chunk. */ + int overflow; /* If overflow, how many including this one? */ + char data[FLEXIBLE_ARRAY_MEMBER]; +} SharedTuplestoreChunk; + +/* Per-participant shared state. */ +typedef struct SharedTuplestoreParticipant +{ + LWLock lock; + BlockNumber read_page; /* Page number for next read. */ + BlockNumber npages; /* Number of pages written. */ + bool writing; /* Used only for assertions. */ +} SharedTuplestoreParticipant; + +/* The control object that lives in shared memory. */ +struct SharedTuplestore +{ + int nparticipants; /* Number of participants that can write. */ + int flags; /* Flag bits from SHARED_TUPLESTORE_XXX */ + size_t meta_data_size; /* Size of per-tuple header. */ + char name[NAMEDATALEN]; /* A name for this tuplestore. */ + + /* Followed by per-participant shared state. */ + SharedTuplestoreParticipant participants[FLEXIBLE_ARRAY_MEMBER]; +}; + +/* Per-participant state that lives in backend-local memory. */ +struct SharedTuplestoreAccessor +{ + int participant; /* My participant number. */ + SharedTuplestore *sts; /* The shared state. */ + SharedFileSet *fileset; /* The SharedFileSet holding files. */ + MemoryContext context; /* Memory context for buffers. */ + + /* State for reading. */ + int read_participant; /* The current participant to read from. */ + BufFile *read_file; /* The current file to read from. */ + int read_ntuples_available; /* The number of tuples in chunk. */ + int read_ntuples; /* How many tuples have we read from chunk? */ + size_t read_bytes; /* How many bytes have we read from chunk? */ + char *read_buffer; /* A buffer for loading tuples. */ + size_t read_buffer_size; + BlockNumber read_next_page; /* Lowest block we'll consider reading. */ + + /* State for writing. */ + SharedTuplestoreChunk *write_chunk; /* Buffer for writing. */ + BufFile *write_file; /* The current file to write to. */ + BlockNumber write_page; /* The next page to write to. */ + char *write_pointer; /* Current write pointer within chunk. */ + char *write_end; /* One past the end of the current chunk. */ +}; + +static void sts_filename(char *name, SharedTuplestoreAccessor *accessor, + int participant); + +/* + * Return the amount of shared memory required to hold SharedTuplestore for a + * given number of participants. + */ +size_t +sts_estimate(int participants) +{ + return offsetof(SharedTuplestore, participants) + + sizeof(SharedTuplestoreParticipant) * participants; +} + +/* + * Initialize a SharedTuplestore in existing shared memory. There must be + * space for sts_estimate(participants) bytes. If flags includes the value + * SHARED_TUPLESTORE_SINGLE_PASS, the files may in future be removed more + * eagerly (but this isn't yet implemented). + * + * Tuples that are stored may optionally carry a piece of fixed sized + * meta-data which will be retrieved along with the tuple. This is useful for + * the hash values used in multi-batch hash joins, but could have other + * applications. + * + * The caller must supply a SharedFileSet, which is essentially a directory + * that will be cleaned up automatically, and a name which must be unique + * across all SharedTuplestores created in the same SharedFileSet. + */ +SharedTuplestoreAccessor * +sts_initialize(SharedTuplestore *sts, int participants, + int my_participant_number, + size_t meta_data_size, + int flags, + SharedFileSet *fileset, + const char *name) +{ + SharedTuplestoreAccessor *accessor; + int i; + + Assert(my_participant_number < participants); + + sts->nparticipants = participants; + sts->meta_data_size = meta_data_size; + sts->flags = flags; + + if (strlen(name) > sizeof(sts->name) - 1) + elog(ERROR, "SharedTuplestore name too long"); + strcpy(sts->name, name); + + /* + * Limit meta-data so it + tuple size always fits into a single chunk. + * sts_puttuple() and sts_read_tuple() could be made to support scenarios + * where that's not the case, but it's not currently required. If so, + * meta-data size probably should be made variable, too. + */ + if (meta_data_size + sizeof(uint32) >= STS_CHUNK_DATA_SIZE) + elog(ERROR, "meta-data too long"); + + for (i = 0; i < participants; ++i) + { + LWLockInitialize(&sts->participants[i].lock, + LWTRANCHE_SHARED_TUPLESTORE); + sts->participants[i].read_page = 0; + sts->participants[i].npages = 0; + sts->participants[i].writing = false; + } + + accessor = palloc0(sizeof(SharedTuplestoreAccessor)); + accessor->participant = my_participant_number; + accessor->sts = sts; + accessor->fileset = fileset; + accessor->context = CurrentMemoryContext; + + return accessor; +} + +/* + * Attach to a SharedTuplestore that has been initialized by another backend, + * so that this backend can read and write tuples. + */ +SharedTuplestoreAccessor * +sts_attach(SharedTuplestore *sts, + int my_participant_number, + SharedFileSet *fileset) +{ + SharedTuplestoreAccessor *accessor; + + Assert(my_participant_number < sts->nparticipants); + + accessor = palloc0(sizeof(SharedTuplestoreAccessor)); + accessor->participant = my_participant_number; + accessor->sts = sts; + accessor->fileset = fileset; + accessor->context = CurrentMemoryContext; + + return accessor; +} + +static void +sts_flush_chunk(SharedTuplestoreAccessor *accessor) +{ + size_t size; + + size = STS_CHUNK_PAGES * BLCKSZ; + BufFileWrite(accessor->write_file, accessor->write_chunk, size); + memset(accessor->write_chunk, 0, size); + accessor->write_pointer = &accessor->write_chunk->data[0]; + accessor->sts->participants[accessor->participant].npages += + STS_CHUNK_PAGES; +} + +/* + * Finish writing tuples. This must be called by all backends that have + * written data before any backend begins reading it. + */ +void +sts_end_write(SharedTuplestoreAccessor *accessor) +{ + if (accessor->write_file != NULL) + { + sts_flush_chunk(accessor); + BufFileClose(accessor->write_file); + pfree(accessor->write_chunk); + accessor->write_chunk = NULL; + accessor->write_file = NULL; + accessor->sts->participants[accessor->participant].writing = false; + } +} + +/* + * Prepare to rescan. Only one participant must call this. After it returns, + * all participants may call sts_begin_parallel_scan() and then loop over + * sts_parallel_scan_next(). This function must not be called concurrently + * with a scan, and synchronization to avoid that is the caller's + * responsibility. + */ +void +sts_reinitialize(SharedTuplestoreAccessor *accessor) +{ + int i; + + /* + * Reset the shared read head for all participants' files. Also set the + * initial chunk size to the minimum (any increases from that size will be + * recorded in chunk_expansion_log). + */ + for (i = 0; i < accessor->sts->nparticipants; ++i) + { + accessor->sts->participants[i].read_page = 0; + } +} + +/* + * Begin scanning the contents in parallel. + */ +void +sts_begin_parallel_scan(SharedTuplestoreAccessor *accessor) +{ + int i PG_USED_FOR_ASSERTS_ONLY; + + /* End any existing scan that was in progress. */ + sts_end_parallel_scan(accessor); + + /* + * Any backend that might have written into this shared tuplestore must + * have called sts_end_write(), so that all buffers are flushed and the + * files have stopped growing. + */ + for (i = 0; i < accessor->sts->nparticipants; ++i) + Assert(!accessor->sts->participants[i].writing); + + /* + * We will start out reading the file that THIS backend wrote. There may + * be some caching locality advantage to that. + */ + accessor->read_participant = accessor->participant; + accessor->read_file = NULL; + accessor->read_next_page = 0; +} + +/* + * Finish a parallel scan, freeing associated backend-local resources. + */ +void +sts_end_parallel_scan(SharedTuplestoreAccessor *accessor) +{ + /* + * Here we could delete all files if SHARED_TUPLESTORE_SINGLE_PASS, but + * we'd probably need a reference count of current parallel scanners so we + * could safely do it only when the reference count reaches zero. + */ + if (accessor->read_file != NULL) + { + BufFileClose(accessor->read_file); + accessor->read_file = NULL; + } +} + +/* + * Write a tuple. If a meta-data size was provided to sts_initialize, then a + * pointer to meta data of that size must be provided. + */ +void +sts_puttuple(SharedTuplestoreAccessor *accessor, void *meta_data, + MinimalTuple tuple) +{ + size_t size; + + /* Do we have our own file yet? */ + if (accessor->write_file == NULL) + { + SharedTuplestoreParticipant *participant; + char name[MAXPGPATH]; + MemoryContext oldcxt; + + /* Create one. Only this backend will write into it. */ + sts_filename(name, accessor, accessor->participant); + + oldcxt = MemoryContextSwitchTo(accessor->context); + accessor->write_file = + BufFileCreateFileSet(&accessor->fileset->fs, name); + MemoryContextSwitchTo(oldcxt); + + /* Set up the shared state for this backend's file. */ + participant = &accessor->sts->participants[accessor->participant]; + participant->writing = true; /* for assertions only */ + } + + /* Do we have space? */ + size = accessor->sts->meta_data_size + tuple->t_len; + if (accessor->write_pointer + size > accessor->write_end) + { + if (accessor->write_chunk == NULL) + { + /* First time through. Allocate chunk. */ + accessor->write_chunk = (SharedTuplestoreChunk *) + MemoryContextAllocZero(accessor->context, + STS_CHUNK_PAGES * BLCKSZ); + accessor->write_chunk->ntuples = 0; + accessor->write_pointer = &accessor->write_chunk->data[0]; + accessor->write_end = (char *) + accessor->write_chunk + STS_CHUNK_PAGES * BLCKSZ; + } + else + { + /* See if flushing helps. */ + sts_flush_chunk(accessor); + } + + /* It may still not be enough in the case of a gigantic tuple. */ + if (accessor->write_pointer + size > accessor->write_end) + { + size_t written; + + /* + * We'll write the beginning of the oversized tuple, and then + * write the rest in some number of 'overflow' chunks. + * + * sts_initialize() verifies that the size of the tuple + + * meta-data always fits into a chunk. Because the chunk has been + * flushed above, we can be sure to have all of a chunk's usable + * space available. + */ + Assert(accessor->write_pointer + accessor->sts->meta_data_size + + sizeof(uint32) < accessor->write_end); + + /* Write the meta-data as one chunk. */ + if (accessor->sts->meta_data_size > 0) + memcpy(accessor->write_pointer, meta_data, + accessor->sts->meta_data_size); + + /* + * Write as much of the tuple as we can fit. This includes the + * tuple's size at the start. + */ + written = accessor->write_end - accessor->write_pointer - + accessor->sts->meta_data_size; + memcpy(accessor->write_pointer + accessor->sts->meta_data_size, + tuple, written); + ++accessor->write_chunk->ntuples; + size -= accessor->sts->meta_data_size; + size -= written; + /* Now write as many overflow chunks as we need for the rest. */ + while (size > 0) + { + size_t written_this_chunk; + + sts_flush_chunk(accessor); + + /* + * How many overflow chunks to go? This will allow readers to + * skip all of them at once instead of reading each one. + */ + accessor->write_chunk->overflow = (size + STS_CHUNK_DATA_SIZE - 1) / + STS_CHUNK_DATA_SIZE; + written_this_chunk = + Min(accessor->write_end - accessor->write_pointer, size); + memcpy(accessor->write_pointer, (char *) tuple + written, + written_this_chunk); + accessor->write_pointer += written_this_chunk; + size -= written_this_chunk; + written += written_this_chunk; + } + return; + } + } + + /* Copy meta-data and tuple into buffer. */ + if (accessor->sts->meta_data_size > 0) + memcpy(accessor->write_pointer, meta_data, + accessor->sts->meta_data_size); + memcpy(accessor->write_pointer + accessor->sts->meta_data_size, tuple, + tuple->t_len); + accessor->write_pointer += size; + ++accessor->write_chunk->ntuples; +} + +static MinimalTuple +sts_read_tuple(SharedTuplestoreAccessor *accessor, void *meta_data) +{ + MinimalTuple tuple; + uint32 size; + size_t remaining_size; + size_t this_chunk_size; + char *destination; + + /* + * We'll keep track of bytes read from this chunk so that we can detect an + * overflowing tuple and switch to reading overflow pages. + */ + if (accessor->sts->meta_data_size > 0) + { + BufFileReadExact(accessor->read_file, meta_data, accessor->sts->meta_data_size); + accessor->read_bytes += accessor->sts->meta_data_size; + } + BufFileReadExact(accessor->read_file, &size, sizeof(size)); + accessor->read_bytes += sizeof(size); + if (size > accessor->read_buffer_size) + { + size_t new_read_buffer_size; + + if (accessor->read_buffer != NULL) + pfree(accessor->read_buffer); + new_read_buffer_size = Max(size, accessor->read_buffer_size * 2); + accessor->read_buffer = + MemoryContextAlloc(accessor->context, new_read_buffer_size); + accessor->read_buffer_size = new_read_buffer_size; + } + remaining_size = size - sizeof(uint32); + this_chunk_size = Min(remaining_size, + BLCKSZ * STS_CHUNK_PAGES - accessor->read_bytes); + destination = accessor->read_buffer + sizeof(uint32); + BufFileReadExact(accessor->read_file, destination, this_chunk_size); + accessor->read_bytes += this_chunk_size; + remaining_size -= this_chunk_size; + destination += this_chunk_size; + ++accessor->read_ntuples; + + /* Check if we need to read any overflow chunks. */ + while (remaining_size > 0) + { + /* We are now positioned at the start of an overflow chunk. */ + SharedTuplestoreChunk chunk_header; + + BufFileReadExact(accessor->read_file, &chunk_header, STS_CHUNK_HEADER_SIZE); + accessor->read_bytes = STS_CHUNK_HEADER_SIZE; + if (chunk_header.overflow == 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("unexpected chunk in shared tuplestore temporary file"), + errdetail_internal("Expected overflow chunk."))); + accessor->read_next_page += STS_CHUNK_PAGES; + this_chunk_size = Min(remaining_size, + BLCKSZ * STS_CHUNK_PAGES - + STS_CHUNK_HEADER_SIZE); + BufFileReadExact(accessor->read_file, destination, this_chunk_size); + accessor->read_bytes += this_chunk_size; + remaining_size -= this_chunk_size; + destination += this_chunk_size; + + /* + * These will be used to count regular tuples following the oversized + * tuple that spilled into this overflow chunk. + */ + accessor->read_ntuples = 0; + accessor->read_ntuples_available = chunk_header.ntuples; + } + + tuple = (MinimalTuple) accessor->read_buffer; + tuple->t_len = size; + + return tuple; +} + +/* + * Get the next tuple in the current parallel scan. + */ +MinimalTuple +sts_parallel_scan_next(SharedTuplestoreAccessor *accessor, void *meta_data) +{ + SharedTuplestoreParticipant *p; + BlockNumber read_page; + bool eof; + + for (;;) + { + /* Can we read more tuples from the current chunk? */ + if (accessor->read_ntuples < accessor->read_ntuples_available) + return sts_read_tuple(accessor, meta_data); + + /* Find the location of a new chunk to read. */ + p = &accessor->sts->participants[accessor->read_participant]; + + LWLockAcquire(&p->lock, LW_EXCLUSIVE); + /* We can skip directly past overflow pages we know about. */ + if (p->read_page < accessor->read_next_page) + p->read_page = accessor->read_next_page; + eof = p->read_page >= p->npages; + if (!eof) + { + /* Claim the next chunk. */ + read_page = p->read_page; + /* Advance the read head for the next reader. */ + p->read_page += STS_CHUNK_PAGES; + accessor->read_next_page = p->read_page; + } + LWLockRelease(&p->lock); + + if (!eof) + { + SharedTuplestoreChunk chunk_header; + + /* Make sure we have the file open. */ + if (accessor->read_file == NULL) + { + char name[MAXPGPATH]; + MemoryContext oldcxt; + + sts_filename(name, accessor, accessor->read_participant); + + oldcxt = MemoryContextSwitchTo(accessor->context); + accessor->read_file = + BufFileOpenFileSet(&accessor->fileset->fs, name, O_RDONLY, + false); + MemoryContextSwitchTo(oldcxt); + } + + /* Seek and load the chunk header. */ + if (BufFileSeekBlock(accessor->read_file, read_page) != 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not seek to block %u in shared tuplestore temporary file", + read_page))); + BufFileReadExact(accessor->read_file, &chunk_header, STS_CHUNK_HEADER_SIZE); + + /* + * If this is an overflow chunk, we skip it and any following + * overflow chunks all at once. + */ + if (chunk_header.overflow > 0) + { + accessor->read_next_page = read_page + + chunk_header.overflow * STS_CHUNK_PAGES; + continue; + } + + accessor->read_ntuples = 0; + accessor->read_ntuples_available = chunk_header.ntuples; + accessor->read_bytes = STS_CHUNK_HEADER_SIZE; + + /* Go around again, so we can get a tuple from this chunk. */ + } + else + { + if (accessor->read_file != NULL) + { + BufFileClose(accessor->read_file); + accessor->read_file = NULL; + } + + /* + * Try the next participant's file. If we've gone full circle, + * we're done. + */ + accessor->read_participant = (accessor->read_participant + 1) % + accessor->sts->nparticipants; + if (accessor->read_participant == accessor->participant) + break; + accessor->read_next_page = 0; + + /* Go around again, so we can get a chunk from this file. */ + } + } + + return NULL; +} + +/* + * Create the name used for the BufFile that a given participant will write. + */ +static void +sts_filename(char *name, SharedTuplestoreAccessor *accessor, int participant) +{ + snprintf(name, MAXPGPATH, "%s.p%d", accessor->sts->name, participant); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/sort/sortsupport.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/sort/sortsupport.c new file mode 100644 index 00000000000..670e6ec1011 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/sort/sortsupport.c @@ -0,0 +1,211 @@ +/*------------------------------------------------------------------------- + * + * sortsupport.c + * Support routines for accelerated sorting. + * + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/utils/sort/sortsupport.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/gist.h" +#include "access/nbtree.h" +#include "catalog/pg_am.h" +#include "fmgr.h" +#include "utils/lsyscache.h" +#include "utils/rel.h" +#include "utils/sortsupport.h" + + +/* Info needed to use an old-style comparison function as a sort comparator */ +typedef struct +{ + FmgrInfo flinfo; /* lookup data for comparison function */ + FunctionCallInfoBaseData fcinfo; /* reusable callinfo structure */ +} SortShimExtra; + +#define SizeForSortShimExtra(nargs) (offsetof(SortShimExtra, fcinfo) + SizeForFunctionCallInfo(nargs)) + +/* + * Shim function for calling an old-style comparator + * + * This is essentially an inlined version of FunctionCall2Coll(), except + * we assume that the FunctionCallInfoBaseData was already mostly set up by + * PrepareSortSupportComparisonShim. + */ +static int +comparison_shim(Datum x, Datum y, SortSupport ssup) +{ + SortShimExtra *extra = (SortShimExtra *) ssup->ssup_extra; + Datum result; + + extra->fcinfo.args[0].value = x; + extra->fcinfo.args[1].value = y; + + /* just for paranoia's sake, we reset isnull each time */ + extra->fcinfo.isnull = false; + + result = FunctionCallInvoke(&extra->fcinfo); + + /* Check for null result, since caller is clearly not expecting one */ + if (extra->fcinfo.isnull) + elog(ERROR, "function %u returned NULL", extra->flinfo.fn_oid); + + return result; +} + +/* + * Set up a shim function to allow use of an old-style btree comparison + * function as if it were a sort support comparator. + */ +void +PrepareSortSupportComparisonShim(Oid cmpFunc, SortSupport ssup) +{ + SortShimExtra *extra; + + extra = (SortShimExtra *) MemoryContextAlloc(ssup->ssup_cxt, + SizeForSortShimExtra(2)); + + /* Lookup the comparison function */ + fmgr_info_cxt(cmpFunc, &extra->flinfo, ssup->ssup_cxt); + + /* We can initialize the callinfo just once and re-use it */ + InitFunctionCallInfoData(extra->fcinfo, &extra->flinfo, 2, + ssup->ssup_collation, NULL, NULL); + extra->fcinfo.args[0].isnull = false; + extra->fcinfo.args[1].isnull = false; + + ssup->ssup_extra = extra; + ssup->comparator = comparison_shim; +} + +/* + * Look up and call sortsupport function to setup SortSupport comparator; + * or if no such function exists or it declines to set up the appropriate + * state, prepare a suitable shim. + */ +static void +FinishSortSupportFunction(Oid opfamily, Oid opcintype, SortSupport ssup) +{ + Oid sortSupportFunction; + + /* Look for a sort support function */ + sortSupportFunction = get_opfamily_proc(opfamily, opcintype, opcintype, + BTSORTSUPPORT_PROC); + if (OidIsValid(sortSupportFunction)) + { + /* + * The sort support function can provide a comparator, but it can also + * choose not to so (e.g. based on the selected collation). + */ + OidFunctionCall1(sortSupportFunction, PointerGetDatum(ssup)); + } + + if (ssup->comparator == NULL) + { + Oid sortFunction; + + sortFunction = get_opfamily_proc(opfamily, opcintype, opcintype, + BTORDER_PROC); + + if (!OidIsValid(sortFunction)) + elog(ERROR, "missing support function %d(%u,%u) in opfamily %u", + BTORDER_PROC, opcintype, opcintype, opfamily); + + /* We'll use a shim to call the old-style btree comparator */ + PrepareSortSupportComparisonShim(sortFunction, ssup); + } +} + +/* + * Fill in SortSupport given an ordering operator (btree "<" or ">" operator). + * + * Caller must previously have zeroed the SortSupportData structure and then + * filled in ssup_cxt, ssup_collation, and ssup_nulls_first. This will fill + * in ssup_reverse as well as the comparator function pointer. + */ +void +PrepareSortSupportFromOrderingOp(Oid orderingOp, SortSupport ssup) +{ + Oid opfamily; + Oid opcintype; + int16 strategy; + + Assert(ssup->comparator == NULL); + + /* Find the operator in pg_amop */ + if (!get_ordering_op_properties(orderingOp, &opfamily, &opcintype, + &strategy)) + elog(ERROR, "operator %u is not a valid ordering operator", + orderingOp); + ssup->ssup_reverse = (strategy == BTGreaterStrategyNumber); + + FinishSortSupportFunction(opfamily, opcintype, ssup); +} + +/* + * Fill in SortSupport given an index relation, attribute, and strategy. + * + * Caller must previously have zeroed the SortSupportData structure and then + * filled in ssup_cxt, ssup_attno, ssup_collation, and ssup_nulls_first. This + * will fill in ssup_reverse (based on the supplied strategy), as well as the + * comparator function pointer. + */ +void +PrepareSortSupportFromIndexRel(Relation indexRel, int16 strategy, + SortSupport ssup) +{ + Oid opfamily = indexRel->rd_opfamily[ssup->ssup_attno - 1]; + Oid opcintype = indexRel->rd_opcintype[ssup->ssup_attno - 1]; + + Assert(ssup->comparator == NULL); + + if (indexRel->rd_rel->relam != BTREE_AM_OID) + elog(ERROR, "unexpected non-btree AM: %u", indexRel->rd_rel->relam); + if (strategy != BTGreaterStrategyNumber && + strategy != BTLessStrategyNumber) + elog(ERROR, "unexpected sort support strategy: %d", strategy); + ssup->ssup_reverse = (strategy == BTGreaterStrategyNumber); + + FinishSortSupportFunction(opfamily, opcintype, ssup); +} + +/* + * Fill in SortSupport given a GiST index relation + * + * Caller must previously have zeroed the SortSupportData structure and then + * filled in ssup_cxt, ssup_attno, ssup_collation, and ssup_nulls_first. This + * will fill in ssup_reverse (always false for GiST index build), as well as + * the comparator function pointer. + */ +void +PrepareSortSupportFromGistIndexRel(Relation indexRel, SortSupport ssup) +{ + Oid opfamily = indexRel->rd_opfamily[ssup->ssup_attno - 1]; + Oid opcintype = indexRel->rd_opcintype[ssup->ssup_attno - 1]; + Oid sortSupportFunction; + + Assert(ssup->comparator == NULL); + + if (indexRel->rd_rel->relam != GIST_AM_OID) + elog(ERROR, "unexpected non-gist AM: %u", indexRel->rd_rel->relam); + ssup->ssup_reverse = false; + + /* + * Look up the sort support function. This is simpler than for B-tree + * indexes because we don't support the old-style btree comparators. + */ + sortSupportFunction = get_opfamily_proc(opfamily, opcintype, opcintype, + GIST_SORTSUPPORT_PROC); + if (!OidIsValid(sortSupportFunction)) + elog(ERROR, "missing support function %d(%u,%u) in opfamily %u", + GIST_SORTSUPPORT_PROC, opcintype, opcintype, opfamily); + OidFunctionCall1(sortSupportFunction, PointerGetDatum(ssup)); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/sort/tuplesort.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/sort/tuplesort.c new file mode 100644 index 00000000000..bb5a1b932b2 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/sort/tuplesort.c @@ -0,0 +1,3215 @@ +/*------------------------------------------------------------------------- + * + * tuplesort.c + * Generalized tuple sorting routines. + * + * This module provides a generalized facility for tuple sorting, which can be + * applied to different kinds of sortable objects. Implementation of + * the particular sorting variants is given in tuplesortvariants.c. + * This module works efficiently for both small and large amounts + * of data. Small amounts are sorted in-memory using qsort(). Large + * amounts are sorted using temporary files and a standard external sort + * algorithm. + * + * See Knuth, volume 3, for more than you want to know about external + * sorting algorithms. The algorithm we use is a balanced k-way merge. + * Before PostgreSQL 15, we used the polyphase merge algorithm (Knuth's + * Algorithm 5.4.2D), but with modern hardware, a straightforward balanced + * merge is better. Knuth is assuming that tape drives are expensive + * beasts, and in particular that there will always be many more runs than + * tape drives. The polyphase merge algorithm was good at keeping all the + * tape drives busy, but in our implementation a "tape drive" doesn't cost + * much more than a few Kb of memory buffers, so we can afford to have + * lots of them. In particular, if we can have as many tape drives as + * sorted runs, we can eliminate any repeated I/O at all. + * + * Historically, we divided the input into sorted runs using replacement + * selection, in the form of a priority tree implemented as a heap + * (essentially Knuth's Algorithm 5.2.3H), but now we always use quicksort + * for run generation. + * + * The approximate amount of memory allowed for any one sort operation + * is specified in kilobytes by the caller (most pass work_mem). Initially, + * we absorb tuples and simply store them in an unsorted array as long as + * we haven't exceeded workMem. If we reach the end of the input without + * exceeding workMem, we sort the array using qsort() and subsequently return + * tuples just by scanning the tuple array sequentially. If we do exceed + * workMem, we begin to emit tuples into sorted runs in temporary tapes. + * When tuples are dumped in batch after quicksorting, we begin a new run + * with a new output tape. If we reach the max number of tapes, we write + * subsequent runs on the existing tapes in a round-robin fashion. We will + * need multiple merge passes to finish the merge in that case. After the + * end of the input is reached, we dump out remaining tuples in memory into + * a final run, then merge the runs. + * + * When merging runs, we use a heap containing just the frontmost tuple from + * each source run; we repeatedly output the smallest tuple and replace it + * with the next tuple from its source tape (if any). When the heap empties, + * the merge is complete. The basic merge algorithm thus needs very little + * memory --- only M tuples for an M-way merge, and M is constrained to a + * small number. However, we can still make good use of our full workMem + * allocation by pre-reading additional blocks from each source tape. Without + * prereading, our access pattern to the temporary file would be very erratic; + * on average we'd read one block from each of M source tapes during the same + * time that we're writing M blocks to the output tape, so there is no + * sequentiality of access at all, defeating the read-ahead methods used by + * most Unix kernels. Worse, the output tape gets written into a very random + * sequence of blocks of the temp file, ensuring that things will be even + * worse when it comes time to read that tape. A straightforward merge pass + * thus ends up doing a lot of waiting for disk seeks. We can improve matters + * by prereading from each source tape sequentially, loading about workMem/M + * bytes from each tape in turn, and making the sequential blocks immediately + * available for reuse. This approach helps to localize both read and write + * accesses. The pre-reading is handled by logtape.c, we just tell it how + * much memory to use for the buffers. + * + * In the current code we determine the number of input tapes M on the basis + * of workMem: we want workMem/M to be large enough that we read a fair + * amount of data each time we read from a tape, so as to maintain the + * locality of access described above. Nonetheless, with large workMem we + * can have many tapes. The logical "tapes" are implemented by logtape.c, + * which avoids space wastage by recycling disk space as soon as each block + * is read from its "tape". + * + * When the caller requests random access to the sort result, we form + * the final sorted run on a logical tape which is then "frozen", so + * that we can access it randomly. When the caller does not need random + * access, we return from tuplesort_performsort() as soon as we are down + * to one run per logical tape. The final merge is then performed + * on-the-fly as the caller repeatedly calls tuplesort_getXXX; this + * saves one cycle of writing all the data out to disk and reading it in. + * + * This module supports parallel sorting. Parallel sorts involve coordination + * among one or more worker processes, and a leader process, each with its own + * tuplesort state. The leader process (or, more accurately, the + * Tuplesortstate associated with a leader process) creates a full tapeset + * consisting of worker tapes with one run to merge; a run for every + * worker process. This is then merged. Worker processes are guaranteed to + * produce exactly one output run from their partial input. + * + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/utils/sort/tuplesort.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include <limits.h> + +#include "catalog/pg_am.h" +#include "commands/tablespace.h" +#include "executor/executor.h" +#include "miscadmin.h" +#include "pg_trace.h" +#include "storage/shmem.h" +#include "utils/memutils.h" +#include "utils/pg_rusage.h" +#include "utils/rel.h" +#include "utils/tuplesort.h" + +/* + * Initial size of memtuples array. We're trying to select this size so that + * array doesn't exceed ALLOCSET_SEPARATE_THRESHOLD and so that the overhead of + * allocation might possibly be lowered. However, we don't consider array sizes + * less than 1024. + * + */ +#define INITIAL_MEMTUPSIZE Max(1024, \ + ALLOCSET_SEPARATE_THRESHOLD / sizeof(SortTuple) + 1) + +/* GUC variables */ +#ifdef TRACE_SORT +__thread bool trace_sort = false; +#endif + +#ifdef DEBUG_BOUNDED_SORT +bool optimize_bounded_sort = true; +#endif + + +/* + * During merge, we use a pre-allocated set of fixed-size slots to hold + * tuples. To avoid palloc/pfree overhead. + * + * Merge doesn't require a lot of memory, so we can afford to waste some, + * by using gratuitously-sized slots. If a tuple is larger than 1 kB, the + * palloc() overhead is not significant anymore. + * + * 'nextfree' is valid when this chunk is in the free list. When in use, the + * slot holds a tuple. + */ +#define SLAB_SLOT_SIZE 1024 + +typedef union SlabSlot +{ + union SlabSlot *nextfree; + char buffer[SLAB_SLOT_SIZE]; +} SlabSlot; + +/* + * Possible states of a Tuplesort object. These denote the states that + * persist between calls of Tuplesort routines. + */ +typedef enum +{ + TSS_INITIAL, /* Loading tuples; still within memory limit */ + TSS_BOUNDED, /* Loading tuples into bounded-size heap */ + TSS_BUILDRUNS, /* Loading tuples; writing to tape */ + TSS_SORTEDINMEM, /* Sort completed entirely in memory */ + TSS_SORTEDONTAPE, /* Sort completed, final run is on tape */ + TSS_FINALMERGE /* Performing final merge on-the-fly */ +} TupSortStatus; + +/* + * Parameters for calculation of number of tapes to use --- see inittapes() + * and tuplesort_merge_order(). + * + * In this calculation we assume that each tape will cost us about 1 blocks + * worth of buffer space. This ignores the overhead of all the other data + * structures needed for each tape, but it's probably close enough. + * + * MERGE_BUFFER_SIZE is how much buffer space we'd like to allocate for each + * input tape, for pre-reading (see discussion at top of file). This is *in + * addition to* the 1 block already included in TAPE_BUFFER_OVERHEAD. + */ +#define MINORDER 6 /* minimum merge order */ +#define MAXORDER 500 /* maximum merge order */ +#define TAPE_BUFFER_OVERHEAD BLCKSZ +#define MERGE_BUFFER_SIZE (BLCKSZ * 32) + + +/* + * Private state of a Tuplesort operation. + */ +struct Tuplesortstate +{ + TuplesortPublic base; + TupSortStatus status; /* enumerated value as shown above */ + bool bounded; /* did caller specify a maximum number of + * tuples to return? */ + bool boundUsed; /* true if we made use of a bounded heap */ + int bound; /* if bounded, the maximum number of tuples */ + int64 availMem; /* remaining memory available, in bytes */ + int64 allowedMem; /* total memory allowed, in bytes */ + int maxTapes; /* max number of input tapes to merge in each + * pass */ + int64 maxSpace; /* maximum amount of space occupied among sort + * of groups, either in-memory or on-disk */ + bool isMaxSpaceDisk; /* true when maxSpace is value for on-disk + * space, false when it's value for in-memory + * space */ + TupSortStatus maxSpaceStatus; /* sort status when maxSpace was reached */ + LogicalTapeSet *tapeset; /* logtape.c object for tapes in a temp file */ + + /* + * This array holds the tuples now in sort memory. If we are in state + * INITIAL, the tuples are in no particular order; if we are in state + * SORTEDINMEM, the tuples are in final sorted order; in states BUILDRUNS + * and FINALMERGE, the tuples are organized in "heap" order per Algorithm + * H. In state SORTEDONTAPE, the array is not used. + */ + SortTuple *memtuples; /* array of SortTuple structs */ + int memtupcount; /* number of tuples currently present */ + int memtupsize; /* allocated length of memtuples array */ + bool growmemtuples; /* memtuples' growth still underway? */ + + /* + * Memory for tuples is sometimes allocated using a simple slab allocator, + * rather than with palloc(). Currently, we switch to slab allocation + * when we start merging. Merging only needs to keep a small, fixed + * number of tuples in memory at any time, so we can avoid the + * palloc/pfree overhead by recycling a fixed number of fixed-size slots + * to hold the tuples. + * + * For the slab, we use one large allocation, divided into SLAB_SLOT_SIZE + * slots. The allocation is sized to have one slot per tape, plus one + * additional slot. We need that many slots to hold all the tuples kept + * in the heap during merge, plus the one we have last returned from the + * sort, with tuplesort_gettuple. + * + * Initially, all the slots are kept in a linked list of free slots. When + * a tuple is read from a tape, it is put to the next available slot, if + * it fits. If the tuple is larger than SLAB_SLOT_SIZE, it is palloc'd + * instead. + * + * When we're done processing a tuple, we return the slot back to the free + * list, or pfree() if it was palloc'd. We know that a tuple was + * allocated from the slab, if its pointer value is between + * slabMemoryBegin and -End. + * + * When the slab allocator is used, the USEMEM/LACKMEM mechanism of + * tracking memory usage is not used. + */ + bool slabAllocatorUsed; + + char *slabMemoryBegin; /* beginning of slab memory arena */ + char *slabMemoryEnd; /* end of slab memory arena */ + SlabSlot *slabFreeHead; /* head of free list */ + + /* Memory used for input and output tape buffers. */ + size_t tape_buffer_mem; + + /* + * When we return a tuple to the caller in tuplesort_gettuple_XXX, that + * came from a tape (that is, in TSS_SORTEDONTAPE or TSS_FINALMERGE + * modes), we remember the tuple in 'lastReturnedTuple', so that we can + * recycle the memory on next gettuple call. + */ + void *lastReturnedTuple; + + /* + * While building initial runs, this is the current output run number. + * Afterwards, it is the number of initial runs we made. + */ + int currentRun; + + /* + * Logical tapes, for merging. + * + * The initial runs are written in the output tapes. In each merge pass, + * the output tapes of the previous pass become the input tapes, and new + * output tapes are created as needed. When nInputTapes equals + * nInputRuns, there is only one merge pass left. + */ + LogicalTape **inputTapes; + int nInputTapes; + int nInputRuns; + + LogicalTape **outputTapes; + int nOutputTapes; + int nOutputRuns; + + LogicalTape *destTape; /* current output tape */ + + /* + * These variables are used after completion of sorting to keep track of + * the next tuple to return. (In the tape case, the tape's current read + * position is also critical state.) + */ + LogicalTape *result_tape; /* actual tape of finished output */ + int current; /* array index (only used if SORTEDINMEM) */ + bool eof_reached; /* reached EOF (needed for cursors) */ + + /* markpos_xxx holds marked position for mark and restore */ + long markpos_block; /* tape block# (only used if SORTEDONTAPE) */ + int markpos_offset; /* saved "current", or offset in tape block */ + bool markpos_eof; /* saved "eof_reached" */ + + /* + * These variables are used during parallel sorting. + * + * worker is our worker identifier. Follows the general convention that + * -1 value relates to a leader tuplesort, and values >= 0 worker + * tuplesorts. (-1 can also be a serial tuplesort.) + * + * shared is mutable shared memory state, which is used to coordinate + * parallel sorts. + * + * nParticipants is the number of worker Tuplesortstates known by the + * leader to have actually been launched, which implies that they must + * finish a run that the leader needs to merge. Typically includes a + * worker state held by the leader process itself. Set in the leader + * Tuplesortstate only. + */ + int worker; + Sharedsort *shared; + int nParticipants; + + /* + * Additional state for managing "abbreviated key" sortsupport routines + * (which currently may be used by all cases except the hash index case). + * Tracks the intervals at which the optimization's effectiveness is + * tested. + */ + int64 abbrevNext; /* Tuple # at which to next check + * applicability */ + + /* + * Resource snapshot for time of sort start. + */ +#ifdef TRACE_SORT + PGRUsage ru_start; +#endif +}; + +/* + * Private mutable state of tuplesort-parallel-operation. This is allocated + * in shared memory. + */ +struct Sharedsort +{ + /* mutex protects all fields prior to tapes */ + slock_t mutex; + + /* + * currentWorker generates ordinal identifier numbers for parallel sort + * workers. These start from 0, and are always gapless. + * + * Workers increment workersFinished to indicate having finished. If this + * is equal to state.nParticipants within the leader, leader is ready to + * merge worker runs. + */ + int currentWorker; + int workersFinished; + + /* Temporary file space */ + SharedFileSet fileset; + + /* Size of tapes flexible array */ + int nTapes; + + /* + * Tapes array used by workers to report back information needed by the + * leader to concatenate all worker tapes into one for merging + */ + TapeShare tapes[FLEXIBLE_ARRAY_MEMBER]; +}; + +/* + * Is the given tuple allocated from the slab memory arena? + */ +#define IS_SLAB_SLOT(state, tuple) \ + ((char *) (tuple) >= (state)->slabMemoryBegin && \ + (char *) (tuple) < (state)->slabMemoryEnd) + +/* + * Return the given tuple to the slab memory free list, or free it + * if it was palloc'd. + */ +#define RELEASE_SLAB_SLOT(state, tuple) \ + do { \ + SlabSlot *buf = (SlabSlot *) tuple; \ + \ + if (IS_SLAB_SLOT((state), buf)) \ + { \ + buf->nextfree = (state)->slabFreeHead; \ + (state)->slabFreeHead = buf; \ + } else \ + pfree(buf); \ + } while(0) + +#define REMOVEABBREV(state,stup,count) ((*(state)->base.removeabbrev) (state, stup, count)) +#define COMPARETUP(state,a,b) ((*(state)->base.comparetup) (a, b, state)) +#define WRITETUP(state,tape,stup) ((*(state)->base.writetup) (state, tape, stup)) +#define READTUP(state,stup,tape,len) ((*(state)->base.readtup) (state, stup, tape, len)) +#define FREESTATE(state) ((state)->base.freestate ? (*(state)->base.freestate) (state) : (void) 0) +#define LACKMEM(state) ((state)->availMem < 0 && !(state)->slabAllocatorUsed) +#define USEMEM(state,amt) ((state)->availMem -= (amt)) +#define FREEMEM(state,amt) ((state)->availMem += (amt)) +#define SERIAL(state) ((state)->shared == NULL) +#define WORKER(state) ((state)->shared && (state)->worker != -1) +#define LEADER(state) ((state)->shared && (state)->worker == -1) + +/* + * NOTES about on-tape representation of tuples: + * + * We require the first "unsigned int" of a stored tuple to be the total size + * on-tape of the tuple, including itself (so it is never zero; an all-zero + * unsigned int is used to delimit runs). The remainder of the stored tuple + * may or may not match the in-memory representation of the tuple --- + * any conversion needed is the job of the writetup and readtup routines. + * + * If state->sortopt contains TUPLESORT_RANDOMACCESS, then the stored + * representation of the tuple must be followed by another "unsigned int" that + * is a copy of the length --- so the total tape space used is actually + * sizeof(unsigned int) more than the stored length value. This allows + * read-backwards. When the random access flag was not specified, the + * write/read routines may omit the extra length word. + * + * writetup is expected to write both length words as well as the tuple + * data. When readtup is called, the tape is positioned just after the + * front length word; readtup must read the tuple data and advance past + * the back length word (if present). + * + * The write/read routines can make use of the tuple description data + * stored in the Tuplesortstate record, if needed. They are also expected + * to adjust state->availMem by the amount of memory space (not tape space!) + * released or consumed. There is no error return from either writetup + * or readtup; they should ereport() on failure. + * + * + * NOTES about memory consumption calculations: + * + * We count space allocated for tuples against the workMem limit, plus + * the space used by the variable-size memtuples array. Fixed-size space + * is not counted; it's small enough to not be interesting. + * + * Note that we count actual space used (as shown by GetMemoryChunkSpace) + * rather than the originally-requested size. This is important since + * palloc can add substantial overhead. It's not a complete answer since + * we won't count any wasted space in palloc allocation blocks, but it's + * a lot better than what we were doing before 7.3. As of 9.6, a + * separate memory context is used for caller passed tuples. Resetting + * it at certain key increments significantly ameliorates fragmentation. + * readtup routines use the slab allocator (they cannot use + * the reset context because it gets deleted at the point that merging + * begins). + */ + + +static void tuplesort_begin_batch(Tuplesortstate *state); +static bool consider_abort_common(Tuplesortstate *state); +static void inittapes(Tuplesortstate *state, bool mergeruns); +static void inittapestate(Tuplesortstate *state, int maxTapes); +static void selectnewtape(Tuplesortstate *state); +static void init_slab_allocator(Tuplesortstate *state, int numSlots); +static void mergeruns(Tuplesortstate *state); +static void mergeonerun(Tuplesortstate *state); +static void beginmerge(Tuplesortstate *state); +static bool mergereadnext(Tuplesortstate *state, LogicalTape *srcTape, SortTuple *stup); +static void dumptuples(Tuplesortstate *state, bool alltuples); +static void make_bounded_heap(Tuplesortstate *state); +static void sort_bounded_heap(Tuplesortstate *state); +static void tuplesort_sort_memtuples(Tuplesortstate *state); +static void tuplesort_heap_insert(Tuplesortstate *state, SortTuple *tuple); +static void tuplesort_heap_replace_top(Tuplesortstate *state, SortTuple *tuple); +static void tuplesort_heap_delete_top(Tuplesortstate *state); +static void reversedirection(Tuplesortstate *state); +static unsigned int getlen(LogicalTape *tape, bool eofOK); +static void markrunend(LogicalTape *tape); +static int worker_get_identifier(Tuplesortstate *state); +static void worker_freeze_result_tape(Tuplesortstate *state); +static void worker_nomergeruns(Tuplesortstate *state); +static void leader_takeover_tapes(Tuplesortstate *state); +static void free_sort_tuple(Tuplesortstate *state, SortTuple *stup); +static void tuplesort_free(Tuplesortstate *state); +static void tuplesort_updatemax(Tuplesortstate *state); + +/* + * Specialized comparators that we can inline into specialized sorts. The goal + * is to try to sort two tuples without having to follow the pointers to the + * comparator or the tuple. + * + * XXX: For now, these fall back to comparator functions that will compare the + * leading datum a second time. + * + * XXX: For now, there is no specialization for cases where datum1 is + * authoritative and we don't even need to fall back to a callback at all (that + * would be true for types like int4/int8/timestamp/date, but not true for + * abbreviations of text or multi-key sorts. There could be! Is it worth it? + */ + +/* Used if first key's comparator is ssup_datum_unsigned_cmp */ +static pg_attribute_always_inline int +qsort_tuple_unsigned_compare(SortTuple *a, SortTuple *b, Tuplesortstate *state) +{ + int compare; + + compare = ApplyUnsignedSortComparator(a->datum1, a->isnull1, + b->datum1, b->isnull1, + &state->base.sortKeys[0]); + if (compare != 0) + return compare; + + /* + * No need to waste effort calling the tiebreak function when there are no + * other keys to sort on. + */ + if (state->base.onlyKey != NULL) + return 0; + + return state->base.comparetup(a, b, state); +} + +#if SIZEOF_DATUM >= 8 +/* Used if first key's comparator is ssup_datum_signed_cmp */ +static pg_attribute_always_inline int +qsort_tuple_signed_compare(SortTuple *a, SortTuple *b, Tuplesortstate *state) +{ + int compare; + + compare = ApplySignedSortComparator(a->datum1, a->isnull1, + b->datum1, b->isnull1, + &state->base.sortKeys[0]); + + if (compare != 0) + return compare; + + /* + * No need to waste effort calling the tiebreak function when there are no + * other keys to sort on. + */ + if (state->base.onlyKey != NULL) + return 0; + + return state->base.comparetup(a, b, state); +} +#endif + +/* Used if first key's comparator is ssup_datum_int32_cmp */ +static pg_attribute_always_inline int +qsort_tuple_int32_compare(SortTuple *a, SortTuple *b, Tuplesortstate *state) +{ + int compare; + + compare = ApplyInt32SortComparator(a->datum1, a->isnull1, + b->datum1, b->isnull1, + &state->base.sortKeys[0]); + + if (compare != 0) + return compare; + + /* + * No need to waste effort calling the tiebreak function when there are no + * other keys to sort on. + */ + if (state->base.onlyKey != NULL) + return 0; + + return state->base.comparetup(a, b, state); +} + +/* + * Special versions of qsort just for SortTuple objects. qsort_tuple() sorts + * any variant of SortTuples, using the appropriate comparetup function. + * qsort_ssup() is specialized for the case where the comparetup function + * reduces to ApplySortComparator(), that is single-key MinimalTuple sorts + * and Datum sorts. qsort_tuple_{unsigned,signed,int32} are specialized for + * common comparison functions on pass-by-value leading datums. + */ + +#define ST_SORT qsort_tuple_unsigned +#define ST_ELEMENT_TYPE SortTuple +#define ST_COMPARE(a, b, state) qsort_tuple_unsigned_compare(a, b, state) +#define ST_COMPARE_ARG_TYPE Tuplesortstate +#define ST_CHECK_FOR_INTERRUPTS +#define ST_SCOPE static +#define ST_DEFINE +#include "lib/sort_template.h" + +#if SIZEOF_DATUM >= 8 +#define ST_SORT qsort_tuple_signed +#define ST_ELEMENT_TYPE SortTuple +#define ST_COMPARE(a, b, state) qsort_tuple_signed_compare(a, b, state) +#define ST_COMPARE_ARG_TYPE Tuplesortstate +#define ST_CHECK_FOR_INTERRUPTS +#define ST_SCOPE static +#define ST_DEFINE +#include "lib/sort_template.h" +#endif + +#define ST_SORT qsort_tuple_int32 +#define ST_ELEMENT_TYPE SortTuple +#define ST_COMPARE(a, b, state) qsort_tuple_int32_compare(a, b, state) +#define ST_COMPARE_ARG_TYPE Tuplesortstate +#define ST_CHECK_FOR_INTERRUPTS +#define ST_SCOPE static +#define ST_DEFINE +#include "lib/sort_template.h" + +#define ST_SORT qsort_tuple +#define ST_ELEMENT_TYPE SortTuple +#define ST_COMPARE_RUNTIME_POINTER +#define ST_COMPARE_ARG_TYPE Tuplesortstate +#define ST_CHECK_FOR_INTERRUPTS +#define ST_SCOPE static +#define ST_DECLARE +#define ST_DEFINE +#include "lib/sort_template.h" + +#define ST_SORT qsort_ssup +#define ST_ELEMENT_TYPE SortTuple +#define ST_COMPARE(a, b, ssup) \ + ApplySortComparator((a)->datum1, (a)->isnull1, \ + (b)->datum1, (b)->isnull1, (ssup)) +#define ST_COMPARE_ARG_TYPE SortSupportData +#define ST_CHECK_FOR_INTERRUPTS +#define ST_SCOPE static +#define ST_DEFINE +#include "lib/sort_template.h" + +/* + * tuplesort_begin_xxx + * + * Initialize for a tuple sort operation. + * + * After calling tuplesort_begin, the caller should call tuplesort_putXXX + * zero or more times, then call tuplesort_performsort when all the tuples + * have been supplied. After performsort, retrieve the tuples in sorted + * order by calling tuplesort_getXXX until it returns false/NULL. (If random + * access was requested, rescan, markpos, and restorepos can also be called.) + * Call tuplesort_end to terminate the operation and release memory/disk space. + * + * Each variant of tuplesort_begin has a workMem parameter specifying the + * maximum number of kilobytes of RAM to use before spilling data to disk. + * (The normal value of this parameter is work_mem, but some callers use + * other values.) Each variant also has a sortopt which is a bitmask of + * sort options. See TUPLESORT_* definitions in tuplesort.h + */ + +Tuplesortstate * +tuplesort_begin_common(int workMem, SortCoordinate coordinate, int sortopt) +{ + Tuplesortstate *state; + MemoryContext maincontext; + MemoryContext sortcontext; + MemoryContext oldcontext; + + /* See leader_takeover_tapes() remarks on random access support */ + if (coordinate && (sortopt & TUPLESORT_RANDOMACCESS)) + elog(ERROR, "random access disallowed under parallel sort"); + + /* + * Memory context surviving tuplesort_reset. This memory context holds + * data which is useful to keep while sorting multiple similar batches. + */ + maincontext = AllocSetContextCreate(CurrentMemoryContext, + "TupleSort main", + ALLOCSET_DEFAULT_SIZES); + + /* + * Create a working memory context for one sort operation. The content of + * this context is deleted by tuplesort_reset. + */ + sortcontext = AllocSetContextCreate(maincontext, + "TupleSort sort", + ALLOCSET_DEFAULT_SIZES); + + /* + * Additionally a working memory context for tuples is setup in + * tuplesort_begin_batch. + */ + + /* + * Make the Tuplesortstate within the per-sortstate context. This way, we + * don't need a separate pfree() operation for it at shutdown. + */ + oldcontext = MemoryContextSwitchTo(maincontext); + + state = (Tuplesortstate *) palloc0(sizeof(Tuplesortstate)); + +#ifdef TRACE_SORT + if (trace_sort) + pg_rusage_init(&state->ru_start); +#endif + + state->base.sortopt = sortopt; + state->base.tuples = true; + state->abbrevNext = 10; + + /* + * workMem is forced to be at least 64KB, the current minimum valid value + * for the work_mem GUC. This is a defense against parallel sort callers + * that divide out memory among many workers in a way that leaves each + * with very little memory. + */ + state->allowedMem = Max(workMem, 64) * (int64) 1024; + state->base.sortcontext = sortcontext; + state->base.maincontext = maincontext; + + /* + * Initial size of array must be more than ALLOCSET_SEPARATE_THRESHOLD; + * see comments in grow_memtuples(). + */ + state->memtupsize = INITIAL_MEMTUPSIZE; + state->memtuples = NULL; + + /* + * After all of the other non-parallel-related state, we setup all of the + * state needed for each batch. + */ + tuplesort_begin_batch(state); + + /* + * Initialize parallel-related state based on coordination information + * from caller + */ + if (!coordinate) + { + /* Serial sort */ + state->shared = NULL; + state->worker = -1; + state->nParticipants = -1; + } + else if (coordinate->isWorker) + { + /* Parallel worker produces exactly one final run from all input */ + state->shared = coordinate->sharedsort; + state->worker = worker_get_identifier(state); + state->nParticipants = -1; + } + else + { + /* Parallel leader state only used for final merge */ + state->shared = coordinate->sharedsort; + state->worker = -1; + state->nParticipants = coordinate->nParticipants; + Assert(state->nParticipants >= 1); + } + + MemoryContextSwitchTo(oldcontext); + + return state; +} + +/* + * tuplesort_begin_batch + * + * Setup, or reset, all state need for processing a new set of tuples with this + * sort state. Called both from tuplesort_begin_common (the first time sorting + * with this sort state) and tuplesort_reset (for subsequent usages). + */ +static void +tuplesort_begin_batch(Tuplesortstate *state) +{ + MemoryContext oldcontext; + + oldcontext = MemoryContextSwitchTo(state->base.maincontext); + + /* + * Caller tuple (e.g. IndexTuple) memory context. + * + * A dedicated child context used exclusively for caller passed tuples + * eases memory management. Resetting at key points reduces + * fragmentation. Note that the memtuples array of SortTuples is allocated + * in the parent context, not this context, because there is no need to + * free memtuples early. For bounded sorts, tuples may be pfreed in any + * order, so we use a regular aset.c context so that it can make use of + * free'd memory. When the sort is not bounded, we make use of a + * generation.c context as this keeps allocations more compact with less + * wastage. Allocations are also slightly more CPU efficient. + */ + if (state->base.sortopt & TUPLESORT_ALLOWBOUNDED) + state->base.tuplecontext = AllocSetContextCreate(state->base.sortcontext, + "Caller tuples", + ALLOCSET_DEFAULT_SIZES); + else + state->base.tuplecontext = GenerationContextCreate(state->base.sortcontext, + "Caller tuples", + ALLOCSET_DEFAULT_SIZES); + + + state->status = TSS_INITIAL; + state->bounded = false; + state->boundUsed = false; + + state->availMem = state->allowedMem; + + state->tapeset = NULL; + + state->memtupcount = 0; + + /* + * Initial size of array must be more than ALLOCSET_SEPARATE_THRESHOLD; + * see comments in grow_memtuples(). + */ + state->growmemtuples = true; + state->slabAllocatorUsed = false; + if (state->memtuples != NULL && state->memtupsize != INITIAL_MEMTUPSIZE) + { + pfree(state->memtuples); + state->memtuples = NULL; + state->memtupsize = INITIAL_MEMTUPSIZE; + } + if (state->memtuples == NULL) + { + state->memtuples = (SortTuple *) palloc(state->memtupsize * sizeof(SortTuple)); + USEMEM(state, GetMemoryChunkSpace(state->memtuples)); + } + + /* workMem must be large enough for the minimal memtuples array */ + if (LACKMEM(state)) + elog(ERROR, "insufficient memory allowed for sort"); + + state->currentRun = 0; + + /* + * Tape variables (inputTapes, outputTapes, etc.) will be initialized by + * inittapes(), if needed. + */ + + state->result_tape = NULL; /* flag that result tape has not been formed */ + + MemoryContextSwitchTo(oldcontext); +} + +/* + * tuplesort_set_bound + * + * Advise tuplesort that at most the first N result tuples are required. + * + * Must be called before inserting any tuples. (Actually, we could allow it + * as long as the sort hasn't spilled to disk, but there seems no need for + * delayed calls at the moment.) + * + * This is a hint only. The tuplesort may still return more tuples than + * requested. Parallel leader tuplesorts will always ignore the hint. + */ +void +tuplesort_set_bound(Tuplesortstate *state, int64 bound) +{ + /* Assert we're called before loading any tuples */ + Assert(state->status == TSS_INITIAL && state->memtupcount == 0); + /* Assert we allow bounded sorts */ + Assert(state->base.sortopt & TUPLESORT_ALLOWBOUNDED); + /* Can't set the bound twice, either */ + Assert(!state->bounded); + /* Also, this shouldn't be called in a parallel worker */ + Assert(!WORKER(state)); + + /* Parallel leader allows but ignores hint */ + if (LEADER(state)) + return; + +#ifdef DEBUG_BOUNDED_SORT + /* Honor GUC setting that disables the feature (for easy testing) */ + if (!optimize_bounded_sort) + return; +#endif + + /* We want to be able to compute bound * 2, so limit the setting */ + if (bound > (int64) (INT_MAX / 2)) + return; + + state->bounded = true; + state->bound = (int) bound; + + /* + * Bounded sorts are not an effective target for abbreviated key + * optimization. Disable by setting state to be consistent with no + * abbreviation support. + */ + state->base.sortKeys->abbrev_converter = NULL; + if (state->base.sortKeys->abbrev_full_comparator) + state->base.sortKeys->comparator = state->base.sortKeys->abbrev_full_comparator; + + /* Not strictly necessary, but be tidy */ + state->base.sortKeys->abbrev_abort = NULL; + state->base.sortKeys->abbrev_full_comparator = NULL; +} + +/* + * tuplesort_used_bound + * + * Allow callers to find out if the sort state was able to use a bound. + */ +bool +tuplesort_used_bound(Tuplesortstate *state) +{ + return state->boundUsed; +} + +/* + * tuplesort_free + * + * Internal routine for freeing resources of tuplesort. + */ +static void +tuplesort_free(Tuplesortstate *state) +{ + /* context swap probably not needed, but let's be safe */ + MemoryContext oldcontext = MemoryContextSwitchTo(state->base.sortcontext); + +#ifdef TRACE_SORT + long spaceUsed; + + if (state->tapeset) + spaceUsed = LogicalTapeSetBlocks(state->tapeset); + else + spaceUsed = (state->allowedMem - state->availMem + 1023) / 1024; +#endif + + /* + * Delete temporary "tape" files, if any. + * + * Note: want to include this in reported total cost of sort, hence need + * for two #ifdef TRACE_SORT sections. + * + * We don't bother to destroy the individual tapes here. They will go away + * with the sortcontext. (In TSS_FINALMERGE state, we have closed + * finished tapes already.) + */ + if (state->tapeset) + LogicalTapeSetClose(state->tapeset); + +#ifdef TRACE_SORT + if (trace_sort) + { + if (state->tapeset) + elog(LOG, "%s of worker %d ended, %ld disk blocks used: %s", + SERIAL(state) ? "external sort" : "parallel external sort", + state->worker, spaceUsed, pg_rusage_show(&state->ru_start)); + else + elog(LOG, "%s of worker %d ended, %ld KB used: %s", + SERIAL(state) ? "internal sort" : "unperformed parallel sort", + state->worker, spaceUsed, pg_rusage_show(&state->ru_start)); + } + + TRACE_POSTGRESQL_SORT_DONE(state->tapeset != NULL, spaceUsed); +#else + + /* + * If you disabled TRACE_SORT, you can still probe sort__done, but you + * ain't getting space-used stats. + */ + TRACE_POSTGRESQL_SORT_DONE(state->tapeset != NULL, 0L); +#endif + + FREESTATE(state); + MemoryContextSwitchTo(oldcontext); + + /* + * Free the per-sort memory context, thereby releasing all working memory. + */ + MemoryContextReset(state->base.sortcontext); +} + +/* + * tuplesort_end + * + * Release resources and clean up. + * + * NOTE: after calling this, any pointers returned by tuplesort_getXXX are + * pointing to garbage. Be careful not to attempt to use or free such + * pointers afterwards! + */ +void +tuplesort_end(Tuplesortstate *state) +{ + tuplesort_free(state); + + /* + * Free the main memory context, including the Tuplesortstate struct + * itself. + */ + MemoryContextDelete(state->base.maincontext); +} + +/* + * tuplesort_updatemax + * + * Update maximum resource usage statistics. + */ +static void +tuplesort_updatemax(Tuplesortstate *state) +{ + int64 spaceUsed; + bool isSpaceDisk; + + /* + * Note: it might seem we should provide both memory and disk usage for a + * disk-based sort. However, the current code doesn't track memory space + * accurately once we have begun to return tuples to the caller (since we + * don't account for pfree's the caller is expected to do), so we cannot + * rely on availMem in a disk sort. This does not seem worth the overhead + * to fix. Is it worth creating an API for the memory context code to + * tell us how much is actually used in sortcontext? + */ + if (state->tapeset) + { + isSpaceDisk = true; + spaceUsed = LogicalTapeSetBlocks(state->tapeset) * BLCKSZ; + } + else + { + isSpaceDisk = false; + spaceUsed = state->allowedMem - state->availMem; + } + + /* + * Sort evicts data to the disk when it wasn't able to fit that data into + * main memory. This is why we assume space used on the disk to be more + * important for tracking resource usage than space used in memory. Note + * that the amount of space occupied by some tupleset on the disk might be + * less than amount of space occupied by the same tupleset in memory due + * to more compact representation. + */ + if ((isSpaceDisk && !state->isMaxSpaceDisk) || + (isSpaceDisk == state->isMaxSpaceDisk && spaceUsed > state->maxSpace)) + { + state->maxSpace = spaceUsed; + state->isMaxSpaceDisk = isSpaceDisk; + state->maxSpaceStatus = state->status; + } +} + +/* + * tuplesort_reset + * + * Reset the tuplesort. Reset all the data in the tuplesort, but leave the + * meta-information in. After tuplesort_reset, tuplesort is ready to start + * a new sort. This allows avoiding recreation of tuple sort states (and + * save resources) when sorting multiple small batches. + */ +void +tuplesort_reset(Tuplesortstate *state) +{ + tuplesort_updatemax(state); + tuplesort_free(state); + + /* + * After we've freed up per-batch memory, re-setup all of the state common + * to both the first batch and any subsequent batch. + */ + tuplesort_begin_batch(state); + + state->lastReturnedTuple = NULL; + state->slabMemoryBegin = NULL; + state->slabMemoryEnd = NULL; + state->slabFreeHead = NULL; +} + +/* + * Grow the memtuples[] array, if possible within our memory constraint. We + * must not exceed INT_MAX tuples in memory or the caller-provided memory + * limit. Return true if we were able to enlarge the array, false if not. + * + * Normally, at each increment we double the size of the array. When doing + * that would exceed a limit, we attempt one last, smaller increase (and then + * clear the growmemtuples flag so we don't try any more). That allows us to + * use memory as fully as permitted; sticking to the pure doubling rule could + * result in almost half going unused. Because availMem moves around with + * tuple addition/removal, we need some rule to prevent making repeated small + * increases in memtupsize, which would just be useless thrashing. The + * growmemtuples flag accomplishes that and also prevents useless + * recalculations in this function. + */ +static bool +grow_memtuples(Tuplesortstate *state) +{ + int newmemtupsize; + int memtupsize = state->memtupsize; + int64 memNowUsed = state->allowedMem - state->availMem; + + /* Forget it if we've already maxed out memtuples, per comment above */ + if (!state->growmemtuples) + return false; + + /* Select new value of memtupsize */ + if (memNowUsed <= state->availMem) + { + /* + * We've used no more than half of allowedMem; double our usage, + * clamping at INT_MAX tuples. + */ + if (memtupsize < INT_MAX / 2) + newmemtupsize = memtupsize * 2; + else + { + newmemtupsize = INT_MAX; + state->growmemtuples = false; + } + } + else + { + /* + * This will be the last increment of memtupsize. Abandon doubling + * strategy and instead increase as much as we safely can. + * + * To stay within allowedMem, we can't increase memtupsize by more + * than availMem / sizeof(SortTuple) elements. In practice, we want + * to increase it by considerably less, because we need to leave some + * space for the tuples to which the new array slots will refer. We + * assume the new tuples will be about the same size as the tuples + * we've already seen, and thus we can extrapolate from the space + * consumption so far to estimate an appropriate new size for the + * memtuples array. The optimal value might be higher or lower than + * this estimate, but it's hard to know that in advance. We again + * clamp at INT_MAX tuples. + * + * This calculation is safe against enlarging the array so much that + * LACKMEM becomes true, because the memory currently used includes + * the present array; thus, there would be enough allowedMem for the + * new array elements even if no other memory were currently used. + * + * We do the arithmetic in float8, because otherwise the product of + * memtupsize and allowedMem could overflow. Any inaccuracy in the + * result should be insignificant; but even if we computed a + * completely insane result, the checks below will prevent anything + * really bad from happening. + */ + double grow_ratio; + + grow_ratio = (double) state->allowedMem / (double) memNowUsed; + if (memtupsize * grow_ratio < INT_MAX) + newmemtupsize = (int) (memtupsize * grow_ratio); + else + newmemtupsize = INT_MAX; + + /* We won't make any further enlargement attempts */ + state->growmemtuples = false; + } + + /* Must enlarge array by at least one element, else report failure */ + if (newmemtupsize <= memtupsize) + goto noalloc; + + /* + * On a 32-bit machine, allowedMem could exceed MaxAllocHugeSize. Clamp + * to ensure our request won't be rejected. Note that we can easily + * exhaust address space before facing this outcome. (This is presently + * impossible due to guc.c's MAX_KILOBYTES limitation on work_mem, but + * don't rely on that at this distance.) + */ + if ((Size) newmemtupsize >= MaxAllocHugeSize / sizeof(SortTuple)) + { + newmemtupsize = (int) (MaxAllocHugeSize / sizeof(SortTuple)); + state->growmemtuples = false; /* can't grow any more */ + } + + /* + * We need to be sure that we do not cause LACKMEM to become true, else + * the space management algorithm will go nuts. The code above should + * never generate a dangerous request, but to be safe, check explicitly + * that the array growth fits within availMem. (We could still cause + * LACKMEM if the memory chunk overhead associated with the memtuples + * array were to increase. That shouldn't happen because we chose the + * initial array size large enough to ensure that palloc will be treating + * both old and new arrays as separate chunks. But we'll check LACKMEM + * explicitly below just in case.) + */ + if (state->availMem < (int64) ((newmemtupsize - memtupsize) * sizeof(SortTuple))) + goto noalloc; + + /* OK, do it */ + FREEMEM(state, GetMemoryChunkSpace(state->memtuples)); + state->memtupsize = newmemtupsize; + state->memtuples = (SortTuple *) + repalloc_huge(state->memtuples, + state->memtupsize * sizeof(SortTuple)); + USEMEM(state, GetMemoryChunkSpace(state->memtuples)); + if (LACKMEM(state)) + elog(ERROR, "unexpected out-of-memory situation in tuplesort"); + return true; + +noalloc: + /* If for any reason we didn't realloc, shut off future attempts */ + state->growmemtuples = false; + return false; +} + +/* + * Shared code for tuple and datum cases. + */ +void +tuplesort_puttuple_common(Tuplesortstate *state, SortTuple *tuple, bool useAbbrev) +{ + MemoryContext oldcontext = MemoryContextSwitchTo(state->base.sortcontext); + + Assert(!LEADER(state)); + + /* Count the size of the out-of-line data */ + if (tuple->tuple != NULL) + USEMEM(state, GetMemoryChunkSpace(tuple->tuple)); + + if (!useAbbrev) + { + /* + * Leave ordinary Datum representation, or NULL value. If there is a + * converter it won't expect NULL values, and cost model is not + * required to account for NULL, so in that case we avoid calling + * converter and just set datum1 to zeroed representation (to be + * consistent, and to support cheap inequality tests for NULL + * abbreviated keys). + */ + } + else if (!consider_abort_common(state)) + { + /* Store abbreviated key representation */ + tuple->datum1 = state->base.sortKeys->abbrev_converter(tuple->datum1, + state->base.sortKeys); + } + else + { + /* + * Set state to be consistent with never trying abbreviation. + * + * Alter datum1 representation in already-copied tuples, so as to + * ensure a consistent representation (current tuple was just + * handled). It does not matter if some dumped tuples are already + * sorted on tape, since serialized tuples lack abbreviated keys + * (TSS_BUILDRUNS state prevents control reaching here in any case). + */ + REMOVEABBREV(state, state->memtuples, state->memtupcount); + } + + switch (state->status) + { + case TSS_INITIAL: + + /* + * Save the tuple into the unsorted array. First, grow the array + * as needed. Note that we try to grow the array when there is + * still one free slot remaining --- if we fail, there'll still be + * room to store the incoming tuple, and then we'll switch to + * tape-based operation. + */ + if (state->memtupcount >= state->memtupsize - 1) + { + (void) grow_memtuples(state); + Assert(state->memtupcount < state->memtupsize); + } + state->memtuples[state->memtupcount++] = *tuple; + + /* + * Check if it's time to switch over to a bounded heapsort. We do + * so if the input tuple count exceeds twice the desired tuple + * count (this is a heuristic for where heapsort becomes cheaper + * than a quicksort), or if we've just filled workMem and have + * enough tuples to meet the bound. + * + * Note that once we enter TSS_BOUNDED state we will always try to + * complete the sort that way. In the worst case, if later input + * tuples are larger than earlier ones, this might cause us to + * exceed workMem significantly. + */ + if (state->bounded && + (state->memtupcount > state->bound * 2 || + (state->memtupcount > state->bound && LACKMEM(state)))) + { +#ifdef TRACE_SORT + if (trace_sort) + elog(LOG, "switching to bounded heapsort at %d tuples: %s", + state->memtupcount, + pg_rusage_show(&state->ru_start)); +#endif + make_bounded_heap(state); + MemoryContextSwitchTo(oldcontext); + return; + } + + /* + * Done if we still fit in available memory and have array slots. + */ + if (state->memtupcount < state->memtupsize && !LACKMEM(state)) + { + MemoryContextSwitchTo(oldcontext); + return; + } + + /* + * Nope; time to switch to tape-based operation. + */ + inittapes(state, true); + + /* + * Dump all tuples. + */ + dumptuples(state, false); + break; + + case TSS_BOUNDED: + + /* + * We don't want to grow the array here, so check whether the new + * tuple can be discarded before putting it in. This should be a + * good speed optimization, too, since when there are many more + * input tuples than the bound, most input tuples can be discarded + * with just this one comparison. Note that because we currently + * have the sort direction reversed, we must check for <= not >=. + */ + if (COMPARETUP(state, tuple, &state->memtuples[0]) <= 0) + { + /* new tuple <= top of the heap, so we can discard it */ + free_sort_tuple(state, tuple); + CHECK_FOR_INTERRUPTS(); + } + else + { + /* discard top of heap, replacing it with the new tuple */ + free_sort_tuple(state, &state->memtuples[0]); + tuplesort_heap_replace_top(state, tuple); + } + break; + + case TSS_BUILDRUNS: + + /* + * Save the tuple into the unsorted array (there must be space) + */ + state->memtuples[state->memtupcount++] = *tuple; + + /* + * If we are over the memory limit, dump all tuples. + */ + dumptuples(state, false); + break; + + default: + elog(ERROR, "invalid tuplesort state"); + break; + } + MemoryContextSwitchTo(oldcontext); +} + +static bool +consider_abort_common(Tuplesortstate *state) +{ + Assert(state->base.sortKeys[0].abbrev_converter != NULL); + Assert(state->base.sortKeys[0].abbrev_abort != NULL); + Assert(state->base.sortKeys[0].abbrev_full_comparator != NULL); + + /* + * Check effectiveness of abbreviation optimization. Consider aborting + * when still within memory limit. + */ + if (state->status == TSS_INITIAL && + state->memtupcount >= state->abbrevNext) + { + state->abbrevNext *= 2; + + /* + * Check opclass-supplied abbreviation abort routine. It may indicate + * that abbreviation should not proceed. + */ + if (!state->base.sortKeys->abbrev_abort(state->memtupcount, + state->base.sortKeys)) + return false; + + /* + * Finally, restore authoritative comparator, and indicate that + * abbreviation is not in play by setting abbrev_converter to NULL + */ + state->base.sortKeys[0].comparator = state->base.sortKeys[0].abbrev_full_comparator; + state->base.sortKeys[0].abbrev_converter = NULL; + /* Not strictly necessary, but be tidy */ + state->base.sortKeys[0].abbrev_abort = NULL; + state->base.sortKeys[0].abbrev_full_comparator = NULL; + + /* Give up - expect original pass-by-value representation */ + return true; + } + + return false; +} + +/* + * All tuples have been provided; finish the sort. + */ +void +tuplesort_performsort(Tuplesortstate *state) +{ + MemoryContext oldcontext = MemoryContextSwitchTo(state->base.sortcontext); + +#ifdef TRACE_SORT + if (trace_sort) + elog(LOG, "performsort of worker %d starting: %s", + state->worker, pg_rusage_show(&state->ru_start)); +#endif + + switch (state->status) + { + case TSS_INITIAL: + + /* + * We were able to accumulate all the tuples within the allowed + * amount of memory, or leader to take over worker tapes + */ + if (SERIAL(state)) + { + /* Just qsort 'em and we're done */ + tuplesort_sort_memtuples(state); + state->status = TSS_SORTEDINMEM; + } + else if (WORKER(state)) + { + /* + * Parallel workers must still dump out tuples to tape. No + * merge is required to produce single output run, though. + */ + inittapes(state, false); + dumptuples(state, true); + worker_nomergeruns(state); + state->status = TSS_SORTEDONTAPE; + } + else + { + /* + * Leader will take over worker tapes and merge worker runs. + * Note that mergeruns sets the correct state->status. + */ + leader_takeover_tapes(state); + mergeruns(state); + } + state->current = 0; + state->eof_reached = false; + state->markpos_block = 0L; + state->markpos_offset = 0; + state->markpos_eof = false; + break; + + case TSS_BOUNDED: + + /* + * We were able to accumulate all the tuples required for output + * in memory, using a heap to eliminate excess tuples. Now we + * have to transform the heap to a properly-sorted array. Note + * that sort_bounded_heap sets the correct state->status. + */ + sort_bounded_heap(state); + state->current = 0; + state->eof_reached = false; + state->markpos_offset = 0; + state->markpos_eof = false; + break; + + case TSS_BUILDRUNS: + + /* + * Finish tape-based sort. First, flush all tuples remaining in + * memory out to tape; then merge until we have a single remaining + * run (or, if !randomAccess and !WORKER(), one run per tape). + * Note that mergeruns sets the correct state->status. + */ + dumptuples(state, true); + mergeruns(state); + state->eof_reached = false; + state->markpos_block = 0L; + state->markpos_offset = 0; + state->markpos_eof = false; + break; + + default: + elog(ERROR, "invalid tuplesort state"); + break; + } + +#ifdef TRACE_SORT + if (trace_sort) + { + if (state->status == TSS_FINALMERGE) + elog(LOG, "performsort of worker %d done (except %d-way final merge): %s", + state->worker, state->nInputTapes, + pg_rusage_show(&state->ru_start)); + else + elog(LOG, "performsort of worker %d done: %s", + state->worker, pg_rusage_show(&state->ru_start)); + } +#endif + + MemoryContextSwitchTo(oldcontext); +} + +/* + * Internal routine to fetch the next tuple in either forward or back + * direction into *stup. Returns false if no more tuples. + * Returned tuple belongs to tuplesort memory context, and must not be freed + * by caller. Note that fetched tuple is stored in memory that may be + * recycled by any future fetch. + */ +bool +tuplesort_gettuple_common(Tuplesortstate *state, bool forward, + SortTuple *stup) +{ + unsigned int tuplen; + size_t nmoved; + + Assert(!WORKER(state)); + + switch (state->status) + { + case TSS_SORTEDINMEM: + Assert(forward || state->base.sortopt & TUPLESORT_RANDOMACCESS); + Assert(!state->slabAllocatorUsed); + if (forward) + { + if (state->current < state->memtupcount) + { + *stup = state->memtuples[state->current++]; + return true; + } + state->eof_reached = true; + + /* + * Complain if caller tries to retrieve more tuples than + * originally asked for in a bounded sort. This is because + * returning EOF here might be the wrong thing. + */ + if (state->bounded && state->current >= state->bound) + elog(ERROR, "retrieved too many tuples in a bounded sort"); + + return false; + } + else + { + if (state->current <= 0) + return false; + + /* + * if all tuples are fetched already then we return last + * tuple, else - tuple before last returned. + */ + if (state->eof_reached) + state->eof_reached = false; + else + { + state->current--; /* last returned tuple */ + if (state->current <= 0) + return false; + } + *stup = state->memtuples[state->current - 1]; + return true; + } + break; + + case TSS_SORTEDONTAPE: + Assert(forward || state->base.sortopt & TUPLESORT_RANDOMACCESS); + Assert(state->slabAllocatorUsed); + + /* + * The slot that held the tuple that we returned in previous + * gettuple call can now be reused. + */ + if (state->lastReturnedTuple) + { + RELEASE_SLAB_SLOT(state, state->lastReturnedTuple); + state->lastReturnedTuple = NULL; + } + + if (forward) + { + if (state->eof_reached) + return false; + + if ((tuplen = getlen(state->result_tape, true)) != 0) + { + READTUP(state, stup, state->result_tape, tuplen); + + /* + * Remember the tuple we return, so that we can recycle + * its memory on next call. (This can be NULL, in the + * !state->tuples case). + */ + state->lastReturnedTuple = stup->tuple; + + return true; + } + else + { + state->eof_reached = true; + return false; + } + } + + /* + * Backward. + * + * if all tuples are fetched already then we return last tuple, + * else - tuple before last returned. + */ + if (state->eof_reached) + { + /* + * Seek position is pointing just past the zero tuplen at the + * end of file; back up to fetch last tuple's ending length + * word. If seek fails we must have a completely empty file. + */ + nmoved = LogicalTapeBackspace(state->result_tape, + 2 * sizeof(unsigned int)); + if (nmoved == 0) + return false; + else if (nmoved != 2 * sizeof(unsigned int)) + elog(ERROR, "unexpected tape position"); + state->eof_reached = false; + } + else + { + /* + * Back up and fetch previously-returned tuple's ending length + * word. If seek fails, assume we are at start of file. + */ + nmoved = LogicalTapeBackspace(state->result_tape, + sizeof(unsigned int)); + if (nmoved == 0) + return false; + else if (nmoved != sizeof(unsigned int)) + elog(ERROR, "unexpected tape position"); + tuplen = getlen(state->result_tape, false); + + /* + * Back up to get ending length word of tuple before it. + */ + nmoved = LogicalTapeBackspace(state->result_tape, + tuplen + 2 * sizeof(unsigned int)); + if (nmoved == tuplen + sizeof(unsigned int)) + { + /* + * We backed up over the previous tuple, but there was no + * ending length word before it. That means that the prev + * tuple is the first tuple in the file. It is now the + * next to read in forward direction (not obviously right, + * but that is what in-memory case does). + */ + return false; + } + else if (nmoved != tuplen + 2 * sizeof(unsigned int)) + elog(ERROR, "bogus tuple length in backward scan"); + } + + tuplen = getlen(state->result_tape, false); + + /* + * Now we have the length of the prior tuple, back up and read it. + * Note: READTUP expects we are positioned after the initial + * length word of the tuple, so back up to that point. + */ + nmoved = LogicalTapeBackspace(state->result_tape, + tuplen); + if (nmoved != tuplen) + elog(ERROR, "bogus tuple length in backward scan"); + READTUP(state, stup, state->result_tape, tuplen); + + /* + * Remember the tuple we return, so that we can recycle its memory + * on next call. (This can be NULL, in the Datum case). + */ + state->lastReturnedTuple = stup->tuple; + + return true; + + case TSS_FINALMERGE: + Assert(forward); + /* We are managing memory ourselves, with the slab allocator. */ + Assert(state->slabAllocatorUsed); + + /* + * The slab slot holding the tuple that we returned in previous + * gettuple call can now be reused. + */ + if (state->lastReturnedTuple) + { + RELEASE_SLAB_SLOT(state, state->lastReturnedTuple); + state->lastReturnedTuple = NULL; + } + + /* + * This code should match the inner loop of mergeonerun(). + */ + if (state->memtupcount > 0) + { + int srcTapeIndex = state->memtuples[0].srctape; + LogicalTape *srcTape = state->inputTapes[srcTapeIndex]; + SortTuple newtup; + + *stup = state->memtuples[0]; + + /* + * Remember the tuple we return, so that we can recycle its + * memory on next call. (This can be NULL, in the Datum case). + */ + state->lastReturnedTuple = stup->tuple; + + /* + * Pull next tuple from tape, and replace the returned tuple + * at top of the heap with it. + */ + if (!mergereadnext(state, srcTape, &newtup)) + { + /* + * If no more data, we've reached end of run on this tape. + * Remove the top node from the heap. + */ + tuplesort_heap_delete_top(state); + state->nInputRuns--; + + /* + * Close the tape. It'd go away at the end of the sort + * anyway, but better to release the memory early. + */ + LogicalTapeClose(srcTape); + return true; + } + newtup.srctape = srcTapeIndex; + tuplesort_heap_replace_top(state, &newtup); + return true; + } + return false; + + default: + elog(ERROR, "invalid tuplesort state"); + return false; /* keep compiler quiet */ + } +} + + +/* + * Advance over N tuples in either forward or back direction, + * without returning any data. N==0 is a no-op. + * Returns true if successful, false if ran out of tuples. + */ +bool +tuplesort_skiptuples(Tuplesortstate *state, int64 ntuples, bool forward) +{ + MemoryContext oldcontext; + + /* + * We don't actually support backwards skip yet, because no callers need + * it. The API is designed to allow for that later, though. + */ + Assert(forward); + Assert(ntuples >= 0); + Assert(!WORKER(state)); + + switch (state->status) + { + case TSS_SORTEDINMEM: + if (state->memtupcount - state->current >= ntuples) + { + state->current += ntuples; + return true; + } + state->current = state->memtupcount; + state->eof_reached = true; + + /* + * Complain if caller tries to retrieve more tuples than + * originally asked for in a bounded sort. This is because + * returning EOF here might be the wrong thing. + */ + if (state->bounded && state->current >= state->bound) + elog(ERROR, "retrieved too many tuples in a bounded sort"); + + return false; + + case TSS_SORTEDONTAPE: + case TSS_FINALMERGE: + + /* + * We could probably optimize these cases better, but for now it's + * not worth the trouble. + */ + oldcontext = MemoryContextSwitchTo(state->base.sortcontext); + while (ntuples-- > 0) + { + SortTuple stup; + + if (!tuplesort_gettuple_common(state, forward, &stup)) + { + MemoryContextSwitchTo(oldcontext); + return false; + } + CHECK_FOR_INTERRUPTS(); + } + MemoryContextSwitchTo(oldcontext); + return true; + + default: + elog(ERROR, "invalid tuplesort state"); + return false; /* keep compiler quiet */ + } +} + +/* + * tuplesort_merge_order - report merge order we'll use for given memory + * (note: "merge order" just means the number of input tapes in the merge). + * + * This is exported for use by the planner. allowedMem is in bytes. + */ +int +tuplesort_merge_order(int64 allowedMem) +{ + int mOrder; + + /*---------- + * In the merge phase, we need buffer space for each input and output tape. + * Each pass in the balanced merge algorithm reads from M input tapes, and + * writes to N output tapes. Each tape consumes TAPE_BUFFER_OVERHEAD bytes + * of memory. In addition to that, we want MERGE_BUFFER_SIZE workspace per + * input tape. + * + * totalMem = M * (TAPE_BUFFER_OVERHEAD + MERGE_BUFFER_SIZE) + + * N * TAPE_BUFFER_OVERHEAD + * + * Except for the last and next-to-last merge passes, where there can be + * fewer tapes left to process, M = N. We choose M so that we have the + * desired amount of memory available for the input buffers + * (TAPE_BUFFER_OVERHEAD + MERGE_BUFFER_SIZE), given the total memory + * available for the tape buffers (allowedMem). + * + * Note: you might be thinking we need to account for the memtuples[] + * array in this calculation, but we effectively treat that as part of the + * MERGE_BUFFER_SIZE workspace. + *---------- + */ + mOrder = allowedMem / + (2 * TAPE_BUFFER_OVERHEAD + MERGE_BUFFER_SIZE); + + /* + * Even in minimum memory, use at least a MINORDER merge. On the other + * hand, even when we have lots of memory, do not use more than a MAXORDER + * merge. Tapes are pretty cheap, but they're not entirely free. Each + * additional tape reduces the amount of memory available to build runs, + * which in turn can cause the same sort to need more runs, which makes + * merging slower even if it can still be done in a single pass. Also, + * high order merges are quite slow due to CPU cache effects; it can be + * faster to pay the I/O cost of a multi-pass merge than to perform a + * single merge pass across many hundreds of tapes. + */ + mOrder = Max(mOrder, MINORDER); + mOrder = Min(mOrder, MAXORDER); + + return mOrder; +} + +/* + * Helper function to calculate how much memory to allocate for the read buffer + * of each input tape in a merge pass. + * + * 'avail_mem' is the amount of memory available for the buffers of all the + * tapes, both input and output. + * 'nInputTapes' and 'nInputRuns' are the number of input tapes and runs. + * 'maxOutputTapes' is the max. number of output tapes we should produce. + */ +static int64 +merge_read_buffer_size(int64 avail_mem, int nInputTapes, int nInputRuns, + int maxOutputTapes) +{ + int nOutputRuns; + int nOutputTapes; + + /* + * How many output tapes will we produce in this pass? + * + * This is nInputRuns / nInputTapes, rounded up. + */ + nOutputRuns = (nInputRuns + nInputTapes - 1) / nInputTapes; + + nOutputTapes = Min(nOutputRuns, maxOutputTapes); + + /* + * Each output tape consumes TAPE_BUFFER_OVERHEAD bytes of memory. All + * remaining memory is divided evenly between the input tapes. + * + * This also follows from the formula in tuplesort_merge_order, but here + * we derive the input buffer size from the amount of memory available, + * and M and N. + */ + return Max((avail_mem - TAPE_BUFFER_OVERHEAD * nOutputTapes) / nInputTapes, 0); +} + +/* + * inittapes - initialize for tape sorting. + * + * This is called only if we have found we won't sort in memory. + */ +static void +inittapes(Tuplesortstate *state, bool mergeruns) +{ + Assert(!LEADER(state)); + + if (mergeruns) + { + /* Compute number of input tapes to use when merging */ + state->maxTapes = tuplesort_merge_order(state->allowedMem); + } + else + { + /* Workers can sometimes produce single run, output without merge */ + Assert(WORKER(state)); + state->maxTapes = MINORDER; + } + +#ifdef TRACE_SORT + if (trace_sort) + elog(LOG, "worker %d switching to external sort with %d tapes: %s", + state->worker, state->maxTapes, pg_rusage_show(&state->ru_start)); +#endif + + /* Create the tape set */ + inittapestate(state, state->maxTapes); + state->tapeset = + LogicalTapeSetCreate(false, + state->shared ? &state->shared->fileset : NULL, + state->worker); + + state->currentRun = 0; + + /* + * Initialize logical tape arrays. + */ + state->inputTapes = NULL; + state->nInputTapes = 0; + state->nInputRuns = 0; + + state->outputTapes = palloc0(state->maxTapes * sizeof(LogicalTape *)); + state->nOutputTapes = 0; + state->nOutputRuns = 0; + + state->status = TSS_BUILDRUNS; + + selectnewtape(state); +} + +/* + * inittapestate - initialize generic tape management state + */ +static void +inittapestate(Tuplesortstate *state, int maxTapes) +{ + int64 tapeSpace; + + /* + * Decrease availMem to reflect the space needed for tape buffers; but + * don't decrease it to the point that we have no room for tuples. (That + * case is only likely to occur if sorting pass-by-value Datums; in all + * other scenarios the memtuples[] array is unlikely to occupy more than + * half of allowedMem. In the pass-by-value case it's not important to + * account for tuple space, so we don't care if LACKMEM becomes + * inaccurate.) + */ + tapeSpace = (int64) maxTapes * TAPE_BUFFER_OVERHEAD; + + if (tapeSpace + GetMemoryChunkSpace(state->memtuples) < state->allowedMem) + USEMEM(state, tapeSpace); + + /* + * Make sure that the temp file(s) underlying the tape set are created in + * suitable temp tablespaces. For parallel sorts, this should have been + * called already, but it doesn't matter if it is called a second time. + */ + PrepareTempTablespaces(); +} + +/* + * selectnewtape -- select next tape to output to. + * + * This is called after finishing a run when we know another run + * must be started. This is used both when building the initial + * runs, and during merge passes. + */ +static void +selectnewtape(Tuplesortstate *state) +{ + /* + * At the beginning of each merge pass, nOutputTapes and nOutputRuns are + * both zero. On each call, we create a new output tape to hold the next + * run, until maxTapes is reached. After that, we assign new runs to the + * existing tapes in a round robin fashion. + */ + if (state->nOutputTapes < state->maxTapes) + { + /* Create a new tape to hold the next run */ + Assert(state->outputTapes[state->nOutputRuns] == NULL); + Assert(state->nOutputRuns == state->nOutputTapes); + state->destTape = LogicalTapeCreate(state->tapeset); + state->outputTapes[state->nOutputTapes] = state->destTape; + state->nOutputTapes++; + state->nOutputRuns++; + } + else + { + /* + * We have reached the max number of tapes. Append to an existing + * tape. + */ + state->destTape = state->outputTapes[state->nOutputRuns % state->nOutputTapes]; + state->nOutputRuns++; + } +} + +/* + * Initialize the slab allocation arena, for the given number of slots. + */ +static void +init_slab_allocator(Tuplesortstate *state, int numSlots) +{ + if (numSlots > 0) + { + char *p; + int i; + + state->slabMemoryBegin = palloc(numSlots * SLAB_SLOT_SIZE); + state->slabMemoryEnd = state->slabMemoryBegin + + numSlots * SLAB_SLOT_SIZE; + state->slabFreeHead = (SlabSlot *) state->slabMemoryBegin; + USEMEM(state, numSlots * SLAB_SLOT_SIZE); + + p = state->slabMemoryBegin; + for (i = 0; i < numSlots - 1; i++) + { + ((SlabSlot *) p)->nextfree = (SlabSlot *) (p + SLAB_SLOT_SIZE); + p += SLAB_SLOT_SIZE; + } + ((SlabSlot *) p)->nextfree = NULL; + } + else + { + state->slabMemoryBegin = state->slabMemoryEnd = NULL; + state->slabFreeHead = NULL; + } + state->slabAllocatorUsed = true; +} + +/* + * mergeruns -- merge all the completed initial runs. + * + * This implements the Balanced k-Way Merge Algorithm. All input data has + * already been written to initial runs on tape (see dumptuples). + */ +static void +mergeruns(Tuplesortstate *state) +{ + int tapenum; + + Assert(state->status == TSS_BUILDRUNS); + Assert(state->memtupcount == 0); + + if (state->base.sortKeys != NULL && state->base.sortKeys->abbrev_converter != NULL) + { + /* + * If there are multiple runs to be merged, when we go to read back + * tuples from disk, abbreviated keys will not have been stored, and + * we don't care to regenerate them. Disable abbreviation from this + * point on. + */ + state->base.sortKeys->abbrev_converter = NULL; + state->base.sortKeys->comparator = state->base.sortKeys->abbrev_full_comparator; + + /* Not strictly necessary, but be tidy */ + state->base.sortKeys->abbrev_abort = NULL; + state->base.sortKeys->abbrev_full_comparator = NULL; + } + + /* + * Reset tuple memory. We've freed all the tuples that we previously + * allocated. We will use the slab allocator from now on. + */ + MemoryContextResetOnly(state->base.tuplecontext); + + /* + * We no longer need a large memtuples array. (We will allocate a smaller + * one for the heap later.) + */ + FREEMEM(state, GetMemoryChunkSpace(state->memtuples)); + pfree(state->memtuples); + state->memtuples = NULL; + + /* + * Initialize the slab allocator. We need one slab slot per input tape, + * for the tuples in the heap, plus one to hold the tuple last returned + * from tuplesort_gettuple. (If we're sorting pass-by-val Datums, + * however, we don't need to do allocate anything.) + * + * In a multi-pass merge, we could shrink this allocation for the last + * merge pass, if it has fewer tapes than previous passes, but we don't + * bother. + * + * From this point on, we no longer use the USEMEM()/LACKMEM() mechanism + * to track memory usage of individual tuples. + */ + if (state->base.tuples) + init_slab_allocator(state, state->nOutputTapes + 1); + else + init_slab_allocator(state, 0); + + /* + * Allocate a new 'memtuples' array, for the heap. It will hold one tuple + * from each input tape. + * + * We could shrink this, too, between passes in a multi-pass merge, but we + * don't bother. (The initial input tapes are still in outputTapes. The + * number of input tapes will not increase between passes.) + */ + state->memtupsize = state->nOutputTapes; + state->memtuples = (SortTuple *) MemoryContextAlloc(state->base.maincontext, + state->nOutputTapes * sizeof(SortTuple)); + USEMEM(state, GetMemoryChunkSpace(state->memtuples)); + + /* + * Use all the remaining memory we have available for tape buffers among + * all the input tapes. At the beginning of each merge pass, we will + * divide this memory between the input and output tapes in the pass. + */ + state->tape_buffer_mem = state->availMem; + USEMEM(state, state->tape_buffer_mem); +#ifdef TRACE_SORT + if (trace_sort) + elog(LOG, "worker %d using %zu KB of memory for tape buffers", + state->worker, state->tape_buffer_mem / 1024); +#endif + + for (;;) + { + /* + * On the first iteration, or if we have read all the runs from the + * input tapes in a multi-pass merge, it's time to start a new pass. + * Rewind all the output tapes, and make them inputs for the next + * pass. + */ + if (state->nInputRuns == 0) + { + int64 input_buffer_size; + + /* Close the old, emptied, input tapes */ + if (state->nInputTapes > 0) + { + for (tapenum = 0; tapenum < state->nInputTapes; tapenum++) + LogicalTapeClose(state->inputTapes[tapenum]); + pfree(state->inputTapes); + } + + /* Previous pass's outputs become next pass's inputs. */ + state->inputTapes = state->outputTapes; + state->nInputTapes = state->nOutputTapes; + state->nInputRuns = state->nOutputRuns; + + /* + * Reset output tape variables. The actual LogicalTapes will be + * created as needed, here we only allocate the array to hold + * them. + */ + state->outputTapes = palloc0(state->nInputTapes * sizeof(LogicalTape *)); + state->nOutputTapes = 0; + state->nOutputRuns = 0; + + /* + * Redistribute the memory allocated for tape buffers, among the + * new input and output tapes. + */ + input_buffer_size = merge_read_buffer_size(state->tape_buffer_mem, + state->nInputTapes, + state->nInputRuns, + state->maxTapes); + +#ifdef TRACE_SORT + if (trace_sort) + elog(LOG, "starting merge pass of %d input runs on %d tapes, " INT64_FORMAT " KB of memory for each input tape: %s", + state->nInputRuns, state->nInputTapes, input_buffer_size / 1024, + pg_rusage_show(&state->ru_start)); +#endif + + /* Prepare the new input tapes for merge pass. */ + for (tapenum = 0; tapenum < state->nInputTapes; tapenum++) + LogicalTapeRewindForRead(state->inputTapes[tapenum], input_buffer_size); + + /* + * If there's just one run left on each input tape, then only one + * merge pass remains. If we don't have to produce a materialized + * sorted tape, we can stop at this point and do the final merge + * on-the-fly. + */ + if ((state->base.sortopt & TUPLESORT_RANDOMACCESS) == 0 + && state->nInputRuns <= state->nInputTapes + && !WORKER(state)) + { + /* Tell logtape.c we won't be writing anymore */ + LogicalTapeSetForgetFreeSpace(state->tapeset); + /* Initialize for the final merge pass */ + beginmerge(state); + state->status = TSS_FINALMERGE; + return; + } + } + + /* Select an output tape */ + selectnewtape(state); + + /* Merge one run from each input tape. */ + mergeonerun(state); + + /* + * If the input tapes are empty, and we output only one output run, + * we're done. The current output tape contains the final result. + */ + if (state->nInputRuns == 0 && state->nOutputRuns <= 1) + break; + } + + /* + * Done. The result is on a single run on a single tape. + */ + state->result_tape = state->outputTapes[0]; + if (!WORKER(state)) + LogicalTapeFreeze(state->result_tape, NULL); + else + worker_freeze_result_tape(state); + state->status = TSS_SORTEDONTAPE; + + /* Close all the now-empty input tapes, to release their read buffers. */ + for (tapenum = 0; tapenum < state->nInputTapes; tapenum++) + LogicalTapeClose(state->inputTapes[tapenum]); +} + +/* + * Merge one run from each input tape. + */ +static void +mergeonerun(Tuplesortstate *state) +{ + int srcTapeIndex; + LogicalTape *srcTape; + + /* + * Start the merge by loading one tuple from each active source tape into + * the heap. + */ + beginmerge(state); + + Assert(state->slabAllocatorUsed); + + /* + * Execute merge by repeatedly extracting lowest tuple in heap, writing it + * out, and replacing it with next tuple from same tape (if there is + * another one). + */ + while (state->memtupcount > 0) + { + SortTuple stup; + + /* write the tuple to destTape */ + srcTapeIndex = state->memtuples[0].srctape; + srcTape = state->inputTapes[srcTapeIndex]; + WRITETUP(state, state->destTape, &state->memtuples[0]); + + /* recycle the slot of the tuple we just wrote out, for the next read */ + if (state->memtuples[0].tuple) + RELEASE_SLAB_SLOT(state, state->memtuples[0].tuple); + + /* + * pull next tuple from the tape, and replace the written-out tuple in + * the heap with it. + */ + if (mergereadnext(state, srcTape, &stup)) + { + stup.srctape = srcTapeIndex; + tuplesort_heap_replace_top(state, &stup); + } + else + { + tuplesort_heap_delete_top(state); + state->nInputRuns--; + } + } + + /* + * When the heap empties, we're done. Write an end-of-run marker on the + * output tape. + */ + markrunend(state->destTape); +} + +/* + * beginmerge - initialize for a merge pass + * + * Fill the merge heap with the first tuple from each input tape. + */ +static void +beginmerge(Tuplesortstate *state) +{ + int activeTapes; + int srcTapeIndex; + + /* Heap should be empty here */ + Assert(state->memtupcount == 0); + + activeTapes = Min(state->nInputTapes, state->nInputRuns); + + for (srcTapeIndex = 0; srcTapeIndex < activeTapes; srcTapeIndex++) + { + SortTuple tup; + + if (mergereadnext(state, state->inputTapes[srcTapeIndex], &tup)) + { + tup.srctape = srcTapeIndex; + tuplesort_heap_insert(state, &tup); + } + } +} + +/* + * mergereadnext - read next tuple from one merge input tape + * + * Returns false on EOF. + */ +static bool +mergereadnext(Tuplesortstate *state, LogicalTape *srcTape, SortTuple *stup) +{ + unsigned int tuplen; + + /* read next tuple, if any */ + if ((tuplen = getlen(srcTape, true)) == 0) + return false; + READTUP(state, stup, srcTape, tuplen); + + return true; +} + +/* + * dumptuples - remove tuples from memtuples and write initial run to tape + * + * When alltuples = true, dump everything currently in memory. (This case is + * only used at end of input data.) + */ +static void +dumptuples(Tuplesortstate *state, bool alltuples) +{ + int memtupwrite; + int i; + + /* + * Nothing to do if we still fit in available memory and have array slots, + * unless this is the final call during initial run generation. + */ + if (state->memtupcount < state->memtupsize && !LACKMEM(state) && + !alltuples) + return; + + /* + * Final call might require no sorting, in rare cases where we just so + * happen to have previously LACKMEM()'d at the point where exactly all + * remaining tuples are loaded into memory, just before input was + * exhausted. In general, short final runs are quite possible, but avoid + * creating a completely empty run. In a worker, though, we must produce + * at least one tape, even if it's empty. + */ + if (state->memtupcount == 0 && state->currentRun > 0) + return; + + Assert(state->status == TSS_BUILDRUNS); + + /* + * It seems unlikely that this limit will ever be exceeded, but take no + * chances + */ + if (state->currentRun == INT_MAX) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("cannot have more than %d runs for an external sort", + INT_MAX))); + + if (state->currentRun > 0) + selectnewtape(state); + + state->currentRun++; + +#ifdef TRACE_SORT + if (trace_sort) + elog(LOG, "worker %d starting quicksort of run %d: %s", + state->worker, state->currentRun, + pg_rusage_show(&state->ru_start)); +#endif + + /* + * Sort all tuples accumulated within the allowed amount of memory for + * this run using quicksort + */ + tuplesort_sort_memtuples(state); + +#ifdef TRACE_SORT + if (trace_sort) + elog(LOG, "worker %d finished quicksort of run %d: %s", + state->worker, state->currentRun, + pg_rusage_show(&state->ru_start)); +#endif + + memtupwrite = state->memtupcount; + for (i = 0; i < memtupwrite; i++) + { + SortTuple *stup = &state->memtuples[i]; + + WRITETUP(state, state->destTape, stup); + + /* + * Account for freeing the tuple, but no need to do the actual pfree + * since the tuplecontext is being reset after the loop. + */ + if (stup->tuple != NULL) + FREEMEM(state, GetMemoryChunkSpace(stup->tuple)); + } + + state->memtupcount = 0; + + /* + * Reset tuple memory. We've freed all of the tuples that we previously + * allocated. It's important to avoid fragmentation when there is a stark + * change in the sizes of incoming tuples. Fragmentation due to + * AllocSetFree's bucketing by size class might be particularly bad if + * this step wasn't taken. + */ + MemoryContextReset(state->base.tuplecontext); + + markrunend(state->destTape); + +#ifdef TRACE_SORT + if (trace_sort) + elog(LOG, "worker %d finished writing run %d to tape %d: %s", + state->worker, state->currentRun, (state->currentRun - 1) % state->nOutputTapes + 1, + pg_rusage_show(&state->ru_start)); +#endif +} + +/* + * tuplesort_rescan - rewind and replay the scan + */ +void +tuplesort_rescan(Tuplesortstate *state) +{ + MemoryContext oldcontext = MemoryContextSwitchTo(state->base.sortcontext); + + Assert(state->base.sortopt & TUPLESORT_RANDOMACCESS); + + switch (state->status) + { + case TSS_SORTEDINMEM: + state->current = 0; + state->eof_reached = false; + state->markpos_offset = 0; + state->markpos_eof = false; + break; + case TSS_SORTEDONTAPE: + LogicalTapeRewindForRead(state->result_tape, 0); + state->eof_reached = false; + state->markpos_block = 0L; + state->markpos_offset = 0; + state->markpos_eof = false; + break; + default: + elog(ERROR, "invalid tuplesort state"); + break; + } + + MemoryContextSwitchTo(oldcontext); +} + +/* + * tuplesort_markpos - saves current position in the merged sort file + */ +void +tuplesort_markpos(Tuplesortstate *state) +{ + MemoryContext oldcontext = MemoryContextSwitchTo(state->base.sortcontext); + + Assert(state->base.sortopt & TUPLESORT_RANDOMACCESS); + + switch (state->status) + { + case TSS_SORTEDINMEM: + state->markpos_offset = state->current; + state->markpos_eof = state->eof_reached; + break; + case TSS_SORTEDONTAPE: + LogicalTapeTell(state->result_tape, + &state->markpos_block, + &state->markpos_offset); + state->markpos_eof = state->eof_reached; + break; + default: + elog(ERROR, "invalid tuplesort state"); + break; + } + + MemoryContextSwitchTo(oldcontext); +} + +/* + * tuplesort_restorepos - restores current position in merged sort file to + * last saved position + */ +void +tuplesort_restorepos(Tuplesortstate *state) +{ + MemoryContext oldcontext = MemoryContextSwitchTo(state->base.sortcontext); + + Assert(state->base.sortopt & TUPLESORT_RANDOMACCESS); + + switch (state->status) + { + case TSS_SORTEDINMEM: + state->current = state->markpos_offset; + state->eof_reached = state->markpos_eof; + break; + case TSS_SORTEDONTAPE: + LogicalTapeSeek(state->result_tape, + state->markpos_block, + state->markpos_offset); + state->eof_reached = state->markpos_eof; + break; + default: + elog(ERROR, "invalid tuplesort state"); + break; + } + + MemoryContextSwitchTo(oldcontext); +} + +/* + * tuplesort_get_stats - extract summary statistics + * + * This can be called after tuplesort_performsort() finishes to obtain + * printable summary information about how the sort was performed. + */ +void +tuplesort_get_stats(Tuplesortstate *state, + TuplesortInstrumentation *stats) +{ + /* + * Note: it might seem we should provide both memory and disk usage for a + * disk-based sort. However, the current code doesn't track memory space + * accurately once we have begun to return tuples to the caller (since we + * don't account for pfree's the caller is expected to do), so we cannot + * rely on availMem in a disk sort. This does not seem worth the overhead + * to fix. Is it worth creating an API for the memory context code to + * tell us how much is actually used in sortcontext? + */ + tuplesort_updatemax(state); + + if (state->isMaxSpaceDisk) + stats->spaceType = SORT_SPACE_TYPE_DISK; + else + stats->spaceType = SORT_SPACE_TYPE_MEMORY; + stats->spaceUsed = (state->maxSpace + 1023) / 1024; + + switch (state->maxSpaceStatus) + { + case TSS_SORTEDINMEM: + if (state->boundUsed) + stats->sortMethod = SORT_TYPE_TOP_N_HEAPSORT; + else + stats->sortMethod = SORT_TYPE_QUICKSORT; + break; + case TSS_SORTEDONTAPE: + stats->sortMethod = SORT_TYPE_EXTERNAL_SORT; + break; + case TSS_FINALMERGE: + stats->sortMethod = SORT_TYPE_EXTERNAL_MERGE; + break; + default: + stats->sortMethod = SORT_TYPE_STILL_IN_PROGRESS; + break; + } +} + +/* + * Convert TuplesortMethod to a string. + */ +const char * +tuplesort_method_name(TuplesortMethod m) +{ + switch (m) + { + case SORT_TYPE_STILL_IN_PROGRESS: + return "still in progress"; + case SORT_TYPE_TOP_N_HEAPSORT: + return "top-N heapsort"; + case SORT_TYPE_QUICKSORT: + return "quicksort"; + case SORT_TYPE_EXTERNAL_SORT: + return "external sort"; + case SORT_TYPE_EXTERNAL_MERGE: + return "external merge"; + } + + return "unknown"; +} + +/* + * Convert TuplesortSpaceType to a string. + */ +const char * +tuplesort_space_type_name(TuplesortSpaceType t) +{ + Assert(t == SORT_SPACE_TYPE_DISK || t == SORT_SPACE_TYPE_MEMORY); + return t == SORT_SPACE_TYPE_DISK ? "Disk" : "Memory"; +} + + +/* + * Heap manipulation routines, per Knuth's Algorithm 5.2.3H. + */ + +/* + * Convert the existing unordered array of SortTuples to a bounded heap, + * discarding all but the smallest "state->bound" tuples. + * + * When working with a bounded heap, we want to keep the largest entry + * at the root (array entry zero), instead of the smallest as in the normal + * sort case. This allows us to discard the largest entry cheaply. + * Therefore, we temporarily reverse the sort direction. + */ +static void +make_bounded_heap(Tuplesortstate *state) +{ + int tupcount = state->memtupcount; + int i; + + Assert(state->status == TSS_INITIAL); + Assert(state->bounded); + Assert(tupcount >= state->bound); + Assert(SERIAL(state)); + + /* Reverse sort direction so largest entry will be at root */ + reversedirection(state); + + state->memtupcount = 0; /* make the heap empty */ + for (i = 0; i < tupcount; i++) + { + if (state->memtupcount < state->bound) + { + /* Insert next tuple into heap */ + /* Must copy source tuple to avoid possible overwrite */ + SortTuple stup = state->memtuples[i]; + + tuplesort_heap_insert(state, &stup); + } + else + { + /* + * The heap is full. Replace the largest entry with the new + * tuple, or just discard it, if it's larger than anything already + * in the heap. + */ + if (COMPARETUP(state, &state->memtuples[i], &state->memtuples[0]) <= 0) + { + free_sort_tuple(state, &state->memtuples[i]); + CHECK_FOR_INTERRUPTS(); + } + else + tuplesort_heap_replace_top(state, &state->memtuples[i]); + } + } + + Assert(state->memtupcount == state->bound); + state->status = TSS_BOUNDED; +} + +/* + * Convert the bounded heap to a properly-sorted array + */ +static void +sort_bounded_heap(Tuplesortstate *state) +{ + int tupcount = state->memtupcount; + + Assert(state->status == TSS_BOUNDED); + Assert(state->bounded); + Assert(tupcount == state->bound); + Assert(SERIAL(state)); + + /* + * We can unheapify in place because each delete-top call will remove the + * largest entry, which we can promptly store in the newly freed slot at + * the end. Once we're down to a single-entry heap, we're done. + */ + while (state->memtupcount > 1) + { + SortTuple stup = state->memtuples[0]; + + /* this sifts-up the next-largest entry and decreases memtupcount */ + tuplesort_heap_delete_top(state); + state->memtuples[state->memtupcount] = stup; + } + state->memtupcount = tupcount; + + /* + * Reverse sort direction back to the original state. This is not + * actually necessary but seems like a good idea for tidiness. + */ + reversedirection(state); + + state->status = TSS_SORTEDINMEM; + state->boundUsed = true; +} + +/* + * Sort all memtuples using specialized qsort() routines. + * + * Quicksort is used for small in-memory sorts, and external sort runs. + */ +static void +tuplesort_sort_memtuples(Tuplesortstate *state) +{ + Assert(!LEADER(state)); + + if (state->memtupcount > 1) + { + /* + * Do we have the leading column's value or abbreviation in datum1, + * and is there a specialization for its comparator? + */ + if (state->base.haveDatum1 && state->base.sortKeys) + { + if (state->base.sortKeys[0].comparator == ssup_datum_unsigned_cmp) + { + qsort_tuple_unsigned(state->memtuples, + state->memtupcount, + state); + return; + } +#if SIZEOF_DATUM >= 8 + else if (state->base.sortKeys[0].comparator == ssup_datum_signed_cmp) + { + qsort_tuple_signed(state->memtuples, + state->memtupcount, + state); + return; + } +#endif + else if (state->base.sortKeys[0].comparator == ssup_datum_int32_cmp) + { + qsort_tuple_int32(state->memtuples, + state->memtupcount, + state); + return; + } + } + + /* Can we use the single-key sort function? */ + if (state->base.onlyKey != NULL) + { + qsort_ssup(state->memtuples, state->memtupcount, + state->base.onlyKey); + } + else + { + qsort_tuple(state->memtuples, + state->memtupcount, + state->base.comparetup, + state); + } + } +} + +/* + * Insert a new tuple into an empty or existing heap, maintaining the + * heap invariant. Caller is responsible for ensuring there's room. + * + * Note: For some callers, tuple points to a memtuples[] entry above the + * end of the heap. This is safe as long as it's not immediately adjacent + * to the end of the heap (ie, in the [memtupcount] array entry) --- if it + * is, it might get overwritten before being moved into the heap! + */ +static void +tuplesort_heap_insert(Tuplesortstate *state, SortTuple *tuple) +{ + SortTuple *memtuples; + int j; + + memtuples = state->memtuples; + Assert(state->memtupcount < state->memtupsize); + + CHECK_FOR_INTERRUPTS(); + + /* + * Sift-up the new entry, per Knuth 5.2.3 exercise 16. Note that Knuth is + * using 1-based array indexes, not 0-based. + */ + j = state->memtupcount++; + while (j > 0) + { + int i = (j - 1) >> 1; + + if (COMPARETUP(state, tuple, &memtuples[i]) >= 0) + break; + memtuples[j] = memtuples[i]; + j = i; + } + memtuples[j] = *tuple; +} + +/* + * Remove the tuple at state->memtuples[0] from the heap. Decrement + * memtupcount, and sift up to maintain the heap invariant. + * + * The caller has already free'd the tuple the top node points to, + * if necessary. + */ +static void +tuplesort_heap_delete_top(Tuplesortstate *state) +{ + SortTuple *memtuples = state->memtuples; + SortTuple *tuple; + + if (--state->memtupcount <= 0) + return; + + /* + * Remove the last tuple in the heap, and re-insert it, by replacing the + * current top node with it. + */ + tuple = &memtuples[state->memtupcount]; + tuplesort_heap_replace_top(state, tuple); +} + +/* + * Replace the tuple at state->memtuples[0] with a new tuple. Sift up to + * maintain the heap invariant. + * + * This corresponds to Knuth's "sift-up" algorithm (Algorithm 5.2.3H, + * Heapsort, steps H3-H8). + */ +static void +tuplesort_heap_replace_top(Tuplesortstate *state, SortTuple *tuple) +{ + SortTuple *memtuples = state->memtuples; + unsigned int i, + n; + + Assert(state->memtupcount >= 1); + + CHECK_FOR_INTERRUPTS(); + + /* + * state->memtupcount is "int", but we use "unsigned int" for i, j, n. + * This prevents overflow in the "2 * i + 1" calculation, since at the top + * of the loop we must have i < n <= INT_MAX <= UINT_MAX/2. + */ + n = state->memtupcount; + i = 0; /* i is where the "hole" is */ + for (;;) + { + unsigned int j = 2 * i + 1; + + if (j >= n) + break; + if (j + 1 < n && + COMPARETUP(state, &memtuples[j], &memtuples[j + 1]) > 0) + j++; + if (COMPARETUP(state, tuple, &memtuples[j]) <= 0) + break; + memtuples[i] = memtuples[j]; + i = j; + } + memtuples[i] = *tuple; +} + +/* + * Function to reverse the sort direction from its current state + * + * It is not safe to call this when performing hash tuplesorts + */ +static void +reversedirection(Tuplesortstate *state) +{ + SortSupport sortKey = state->base.sortKeys; + int nkey; + + for (nkey = 0; nkey < state->base.nKeys; nkey++, sortKey++) + { + sortKey->ssup_reverse = !sortKey->ssup_reverse; + sortKey->ssup_nulls_first = !sortKey->ssup_nulls_first; + } +} + + +/* + * Tape interface routines + */ + +static unsigned int +getlen(LogicalTape *tape, bool eofOK) +{ + unsigned int len; + + if (LogicalTapeRead(tape, + &len, sizeof(len)) != sizeof(len)) + elog(ERROR, "unexpected end of tape"); + if (len == 0 && !eofOK) + elog(ERROR, "unexpected end of data"); + return len; +} + +static void +markrunend(LogicalTape *tape) +{ + unsigned int len = 0; + + LogicalTapeWrite(tape, &len, sizeof(len)); +} + +/* + * Get memory for tuple from within READTUP() routine. + * + * We use next free slot from the slab allocator, or palloc() if the tuple + * is too large for that. + */ +void * +tuplesort_readtup_alloc(Tuplesortstate *state, Size tuplen) +{ + SlabSlot *buf; + + /* + * We pre-allocate enough slots in the slab arena that we should never run + * out. + */ + Assert(state->slabFreeHead); + + if (tuplen > SLAB_SLOT_SIZE || !state->slabFreeHead) + return MemoryContextAlloc(state->base.sortcontext, tuplen); + else + { + buf = state->slabFreeHead; + /* Reuse this slot */ + state->slabFreeHead = buf->nextfree; + + return buf; + } +} + + +/* + * Parallel sort routines + */ + +/* + * tuplesort_estimate_shared - estimate required shared memory allocation + * + * nWorkers is an estimate of the number of workers (it's the number that + * will be requested). + */ +Size +tuplesort_estimate_shared(int nWorkers) +{ + Size tapesSize; + + Assert(nWorkers > 0); + + /* Make sure that BufFile shared state is MAXALIGN'd */ + tapesSize = mul_size(sizeof(TapeShare), nWorkers); + tapesSize = MAXALIGN(add_size(tapesSize, offsetof(Sharedsort, tapes))); + + return tapesSize; +} + +/* + * tuplesort_initialize_shared - initialize shared tuplesort state + * + * Must be called from leader process before workers are launched, to + * establish state needed up-front for worker tuplesortstates. nWorkers + * should match the argument passed to tuplesort_estimate_shared(). + */ +void +tuplesort_initialize_shared(Sharedsort *shared, int nWorkers, dsm_segment *seg) +{ + int i; + + Assert(nWorkers > 0); + + SpinLockInit(&shared->mutex); + shared->currentWorker = 0; + shared->workersFinished = 0; + SharedFileSetInit(&shared->fileset, seg); + shared->nTapes = nWorkers; + for (i = 0; i < nWorkers; i++) + { + shared->tapes[i].firstblocknumber = 0L; + } +} + +/* + * tuplesort_attach_shared - attach to shared tuplesort state + * + * Must be called by all worker processes. + */ +void +tuplesort_attach_shared(Sharedsort *shared, dsm_segment *seg) +{ + /* Attach to SharedFileSet */ + SharedFileSetAttach(&shared->fileset, seg); +} + +/* + * worker_get_identifier - Assign and return ordinal identifier for worker + * + * The order in which these are assigned is not well defined, and should not + * matter; worker numbers across parallel sort participants need only be + * distinct and gapless. logtape.c requires this. + * + * Note that the identifiers assigned from here have no relation to + * ParallelWorkerNumber number, to avoid making any assumption about + * caller's requirements. However, we do follow the ParallelWorkerNumber + * convention of representing a non-worker with worker number -1. This + * includes the leader, as well as serial Tuplesort processes. + */ +static int +worker_get_identifier(Tuplesortstate *state) +{ + Sharedsort *shared = state->shared; + int worker; + + Assert(WORKER(state)); + + SpinLockAcquire(&shared->mutex); + worker = shared->currentWorker++; + SpinLockRelease(&shared->mutex); + + return worker; +} + +/* + * worker_freeze_result_tape - freeze worker's result tape for leader + * + * This is called by workers just after the result tape has been determined, + * instead of calling LogicalTapeFreeze() directly. They do so because + * workers require a few additional steps over similar serial + * TSS_SORTEDONTAPE external sort cases, which also happen here. The extra + * steps are around freeing now unneeded resources, and representing to + * leader that worker's input run is available for its merge. + * + * There should only be one final output run for each worker, which consists + * of all tuples that were originally input into worker. + */ +static void +worker_freeze_result_tape(Tuplesortstate *state) +{ + Sharedsort *shared = state->shared; + TapeShare output; + + Assert(WORKER(state)); + Assert(state->result_tape != NULL); + Assert(state->memtupcount == 0); + + /* + * Free most remaining memory, in case caller is sensitive to our holding + * on to it. memtuples may not be a tiny merge heap at this point. + */ + pfree(state->memtuples); + /* Be tidy */ + state->memtuples = NULL; + state->memtupsize = 0; + + /* + * Parallel worker requires result tape metadata, which is to be stored in + * shared memory for leader + */ + LogicalTapeFreeze(state->result_tape, &output); + + /* Store properties of output tape, and update finished worker count */ + SpinLockAcquire(&shared->mutex); + shared->tapes[state->worker] = output; + shared->workersFinished++; + SpinLockRelease(&shared->mutex); +} + +/* + * worker_nomergeruns - dump memtuples in worker, without merging + * + * This called as an alternative to mergeruns() with a worker when no + * merging is required. + */ +static void +worker_nomergeruns(Tuplesortstate *state) +{ + Assert(WORKER(state)); + Assert(state->result_tape == NULL); + Assert(state->nOutputRuns == 1); + + state->result_tape = state->destTape; + worker_freeze_result_tape(state); +} + +/* + * leader_takeover_tapes - create tapeset for leader from worker tapes + * + * So far, leader Tuplesortstate has performed no actual sorting. By now, all + * sorting has occurred in workers, all of which must have already returned + * from tuplesort_performsort(). + * + * When this returns, leader process is left in a state that is virtually + * indistinguishable from it having generated runs as a serial external sort + * might have. + */ +static void +leader_takeover_tapes(Tuplesortstate *state) +{ + Sharedsort *shared = state->shared; + int nParticipants = state->nParticipants; + int workersFinished; + int j; + + Assert(LEADER(state)); + Assert(nParticipants >= 1); + + SpinLockAcquire(&shared->mutex); + workersFinished = shared->workersFinished; + SpinLockRelease(&shared->mutex); + + if (nParticipants != workersFinished) + elog(ERROR, "cannot take over tapes before all workers finish"); + + /* + * Create the tapeset from worker tapes, including a leader-owned tape at + * the end. Parallel workers are far more expensive than logical tapes, + * so the number of tapes allocated here should never be excessive. + */ + inittapestate(state, nParticipants); + state->tapeset = LogicalTapeSetCreate(false, &shared->fileset, -1); + + /* + * Set currentRun to reflect the number of runs we will merge (it's not + * used for anything, this is just pro forma) + */ + state->currentRun = nParticipants; + + /* + * Initialize the state to look the same as after building the initial + * runs. + * + * There will always be exactly 1 run per worker, and exactly one input + * tape per run, because workers always output exactly 1 run, even when + * there were no input tuples for workers to sort. + */ + state->inputTapes = NULL; + state->nInputTapes = 0; + state->nInputRuns = 0; + + state->outputTapes = palloc0(nParticipants * sizeof(LogicalTape *)); + state->nOutputTapes = nParticipants; + state->nOutputRuns = nParticipants; + + for (j = 0; j < nParticipants; j++) + { + state->outputTapes[j] = LogicalTapeImport(state->tapeset, j, &shared->tapes[j]); + } + + state->status = TSS_BUILDRUNS; +} + +/* + * Convenience routine to free a tuple previously loaded into sort memory + */ +static void +free_sort_tuple(Tuplesortstate *state, SortTuple *stup) +{ + if (stup->tuple) + { + FREEMEM(state, GetMemoryChunkSpace(stup->tuple)); + pfree(stup->tuple); + stup->tuple = NULL; + } +} + +int +ssup_datum_unsigned_cmp(Datum x, Datum y, SortSupport ssup) +{ + if (x < y) + return -1; + else if (x > y) + return 1; + else + return 0; +} + +#if SIZEOF_DATUM >= 8 +int +ssup_datum_signed_cmp(Datum x, Datum y, SortSupport ssup) +{ + int64 xx = DatumGetInt64(x); + int64 yy = DatumGetInt64(y); + + if (xx < yy) + return -1; + else if (xx > yy) + return 1; + else + return 0; +} +#endif + +int +ssup_datum_int32_cmp(Datum x, Datum y, SortSupport ssup) +{ + int32 xx = DatumGetInt32(x); + int32 yy = DatumGetInt32(y); + + if (xx < yy) + return -1; + else if (xx > yy) + return 1; + else + return 0; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/sort/tuplesortvariants.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/sort/tuplesortvariants.c new file mode 100644 index 00000000000..eb6cfcfd002 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/sort/tuplesortvariants.c @@ -0,0 +1,1606 @@ +/*------------------------------------------------------------------------- + * + * tuplesortvariants.c + * Implementation of tuple sorting variants. + * + * This module handles the sorting of heap tuples, index tuples, or single + * Datums. The implementation is based on the generalized tuple sorting + * facility given in tuplesort.c. Support other kinds of sortable objects + * could be easily added here, another module, or even an extension. + * + * + * Copyright (c) 2022-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/utils/sort/tuplesortvariants.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/hash.h" +#include "access/htup_details.h" +#include "access/nbtree.h" +#include "catalog/index.h" +#include "executor/executor.h" +#include "pg_trace.h" +#include "utils/datum.h" +#include "utils/lsyscache.h" +#include "utils/guc.h" +#include "utils/tuplesort.h" + + +/* sort-type codes for sort__start probes */ +#define HEAP_SORT 0 +#define INDEX_SORT 1 +#define DATUM_SORT 2 +#define CLUSTER_SORT 3 + +static void removeabbrev_heap(Tuplesortstate *state, SortTuple *stups, + int count); +static void removeabbrev_cluster(Tuplesortstate *state, SortTuple *stups, + int count); +static void removeabbrev_index(Tuplesortstate *state, SortTuple *stups, + int count); +static void removeabbrev_datum(Tuplesortstate *state, SortTuple *stups, + int count); +static int comparetup_heap(const SortTuple *a, const SortTuple *b, + Tuplesortstate *state); +static void writetup_heap(Tuplesortstate *state, LogicalTape *tape, + SortTuple *stup); +static void readtup_heap(Tuplesortstate *state, SortTuple *stup, + LogicalTape *tape, unsigned int len); +static int comparetup_cluster(const SortTuple *a, const SortTuple *b, + Tuplesortstate *state); +static void writetup_cluster(Tuplesortstate *state, LogicalTape *tape, + SortTuple *stup); +static void readtup_cluster(Tuplesortstate *state, SortTuple *stup, + LogicalTape *tape, unsigned int tuplen); +static int comparetup_index_btree(const SortTuple *a, const SortTuple *b, + Tuplesortstate *state); +static int comparetup_index_hash(const SortTuple *a, const SortTuple *b, + Tuplesortstate *state); +static void writetup_index(Tuplesortstate *state, LogicalTape *tape, + SortTuple *stup); +static void readtup_index(Tuplesortstate *state, SortTuple *stup, + LogicalTape *tape, unsigned int len); +static int comparetup_datum(const SortTuple *a, const SortTuple *b, + Tuplesortstate *state); +static void writetup_datum(Tuplesortstate *state, LogicalTape *tape, + SortTuple *stup); +static void readtup_datum(Tuplesortstate *state, SortTuple *stup, + LogicalTape *tape, unsigned int len); +static void freestate_cluster(Tuplesortstate *state); + +/* + * Data struture pointed by "TuplesortPublic.arg" for the CLUSTER case. Set by + * the tuplesort_begin_cluster. + */ +typedef struct +{ + TupleDesc tupDesc; + + IndexInfo *indexInfo; /* info about index being used for reference */ + EState *estate; /* for evaluating index expressions */ +} TuplesortClusterArg; + +/* + * Data struture pointed by "TuplesortPublic.arg" for the IndexTuple case. + * Set by tuplesort_begin_index_xxx and used only by the IndexTuple routines. + */ +typedef struct +{ + Relation heapRel; /* table the index is being built on */ + Relation indexRel; /* index being built */ +} TuplesortIndexArg; + +/* + * Data struture pointed by "TuplesortPublic.arg" for the index_btree subcase. + */ +typedef struct +{ + TuplesortIndexArg index; + + bool enforceUnique; /* complain if we find duplicate tuples */ + bool uniqueNullsNotDistinct; /* unique constraint null treatment */ +} TuplesortIndexBTreeArg; + +/* + * Data struture pointed by "TuplesortPublic.arg" for the index_hash subcase. + */ +typedef struct +{ + TuplesortIndexArg index; + + uint32 high_mask; /* masks for sortable part of hash code */ + uint32 low_mask; + uint32 max_buckets; +} TuplesortIndexHashArg; + +/* + * Data struture pointed by "TuplesortPublic.arg" for the Datum case. + * Set by tuplesort_begin_datum and used only by the DatumTuple routines. + */ +typedef struct +{ + /* the datatype oid of Datum's to be sorted */ + Oid datumType; + /* we need typelen in order to know how to copy the Datums. */ + int datumTypeLen; +} TuplesortDatumArg; + +Tuplesortstate * +tuplesort_begin_heap(TupleDesc tupDesc, + int nkeys, AttrNumber *attNums, + Oid *sortOperators, Oid *sortCollations, + bool *nullsFirstFlags, + int workMem, SortCoordinate coordinate, int sortopt) +{ + Tuplesortstate *state = tuplesort_begin_common(workMem, coordinate, + sortopt); + TuplesortPublic *base = TuplesortstateGetPublic(state); + MemoryContext oldcontext; + int i; + + oldcontext = MemoryContextSwitchTo(base->maincontext); + + Assert(nkeys > 0); + +#ifdef TRACE_SORT + if (trace_sort) + elog(LOG, + "begin tuple sort: nkeys = %d, workMem = %d, randomAccess = %c", + nkeys, workMem, sortopt & TUPLESORT_RANDOMACCESS ? 't' : 'f'); +#endif + + base->nKeys = nkeys; + + TRACE_POSTGRESQL_SORT_START(HEAP_SORT, + false, /* no unique check */ + nkeys, + workMem, + sortopt & TUPLESORT_RANDOMACCESS, + PARALLEL_SORT(coordinate)); + + base->removeabbrev = removeabbrev_heap; + base->comparetup = comparetup_heap; + base->writetup = writetup_heap; + base->readtup = readtup_heap; + base->haveDatum1 = true; + base->arg = tupDesc; /* assume we need not copy tupDesc */ + + /* Prepare SortSupport data for each column */ + base->sortKeys = (SortSupport) palloc0(nkeys * sizeof(SortSupportData)); + + for (i = 0; i < nkeys; i++) + { + SortSupport sortKey = base->sortKeys + i; + + Assert(attNums[i] != 0); + Assert(sortOperators[i] != 0); + + sortKey->ssup_cxt = CurrentMemoryContext; + sortKey->ssup_collation = sortCollations[i]; + sortKey->ssup_nulls_first = nullsFirstFlags[i]; + sortKey->ssup_attno = attNums[i]; + /* Convey if abbreviation optimization is applicable in principle */ + sortKey->abbreviate = (i == 0 && base->haveDatum1); + + PrepareSortSupportFromOrderingOp(sortOperators[i], sortKey); + } + + /* + * The "onlyKey" optimization cannot be used with abbreviated keys, since + * tie-breaker comparisons may be required. Typically, the optimization + * is only of value to pass-by-value types anyway, whereas abbreviated + * keys are typically only of value to pass-by-reference types. + */ + if (nkeys == 1 && !base->sortKeys->abbrev_converter) + base->onlyKey = base->sortKeys; + + MemoryContextSwitchTo(oldcontext); + + return state; +} + +Tuplesortstate * +tuplesort_begin_cluster(TupleDesc tupDesc, + Relation indexRel, + int workMem, + SortCoordinate coordinate, int sortopt) +{ + Tuplesortstate *state = tuplesort_begin_common(workMem, coordinate, + sortopt); + TuplesortPublic *base = TuplesortstateGetPublic(state); + BTScanInsert indexScanKey; + MemoryContext oldcontext; + TuplesortClusterArg *arg; + int i; + + Assert(indexRel->rd_rel->relam == BTREE_AM_OID); + + oldcontext = MemoryContextSwitchTo(base->maincontext); + arg = (TuplesortClusterArg *) palloc0(sizeof(TuplesortClusterArg)); + +#ifdef TRACE_SORT + if (trace_sort) + elog(LOG, + "begin tuple sort: nkeys = %d, workMem = %d, randomAccess = %c", + RelationGetNumberOfAttributes(indexRel), + workMem, sortopt & TUPLESORT_RANDOMACCESS ? 't' : 'f'); +#endif + + base->nKeys = IndexRelationGetNumberOfKeyAttributes(indexRel); + + TRACE_POSTGRESQL_SORT_START(CLUSTER_SORT, + false, /* no unique check */ + base->nKeys, + workMem, + sortopt & TUPLESORT_RANDOMACCESS, + PARALLEL_SORT(coordinate)); + + base->removeabbrev = removeabbrev_cluster; + base->comparetup = comparetup_cluster; + base->writetup = writetup_cluster; + base->readtup = readtup_cluster; + base->freestate = freestate_cluster; + base->arg = arg; + + arg->indexInfo = BuildIndexInfo(indexRel); + + /* + * If we don't have a simple leading attribute, we don't currently + * initialize datum1, so disable optimizations that require it. + */ + if (arg->indexInfo->ii_IndexAttrNumbers[0] == 0) + base->haveDatum1 = false; + else + base->haveDatum1 = true; + + arg->tupDesc = tupDesc; /* assume we need not copy tupDesc */ + + indexScanKey = _bt_mkscankey(indexRel, NULL); + + if (arg->indexInfo->ii_Expressions != NULL) + { + TupleTableSlot *slot; + ExprContext *econtext; + + /* + * We will need to use FormIndexDatum to evaluate the index + * expressions. To do that, we need an EState, as well as a + * TupleTableSlot to put the table tuples into. The econtext's + * scantuple has to point to that slot, too. + */ + arg->estate = CreateExecutorState(); + slot = MakeSingleTupleTableSlot(tupDesc, &TTSOpsHeapTuple); + econtext = GetPerTupleExprContext(arg->estate); + econtext->ecxt_scantuple = slot; + } + + /* Prepare SortSupport data for each column */ + base->sortKeys = (SortSupport) palloc0(base->nKeys * + sizeof(SortSupportData)); + + for (i = 0; i < base->nKeys; i++) + { + SortSupport sortKey = base->sortKeys + i; + ScanKey scanKey = indexScanKey->scankeys + i; + int16 strategy; + + sortKey->ssup_cxt = CurrentMemoryContext; + sortKey->ssup_collation = scanKey->sk_collation; + sortKey->ssup_nulls_first = + (scanKey->sk_flags & SK_BT_NULLS_FIRST) != 0; + sortKey->ssup_attno = scanKey->sk_attno; + /* Convey if abbreviation optimization is applicable in principle */ + sortKey->abbreviate = (i == 0 && base->haveDatum1); + + Assert(sortKey->ssup_attno != 0); + + strategy = (scanKey->sk_flags & SK_BT_DESC) != 0 ? + BTGreaterStrategyNumber : BTLessStrategyNumber; + + PrepareSortSupportFromIndexRel(indexRel, strategy, sortKey); + } + + pfree(indexScanKey); + + MemoryContextSwitchTo(oldcontext); + + return state; +} + +Tuplesortstate * +tuplesort_begin_index_btree(Relation heapRel, + Relation indexRel, + bool enforceUnique, + bool uniqueNullsNotDistinct, + int workMem, + SortCoordinate coordinate, + int sortopt) +{ + Tuplesortstate *state = tuplesort_begin_common(workMem, coordinate, + sortopt); + TuplesortPublic *base = TuplesortstateGetPublic(state); + BTScanInsert indexScanKey; + TuplesortIndexBTreeArg *arg; + MemoryContext oldcontext; + int i; + + oldcontext = MemoryContextSwitchTo(base->maincontext); + arg = (TuplesortIndexBTreeArg *) palloc(sizeof(TuplesortIndexBTreeArg)); + +#ifdef TRACE_SORT + if (trace_sort) + elog(LOG, + "begin index sort: unique = %c, workMem = %d, randomAccess = %c", + enforceUnique ? 't' : 'f', + workMem, sortopt & TUPLESORT_RANDOMACCESS ? 't' : 'f'); +#endif + + base->nKeys = IndexRelationGetNumberOfKeyAttributes(indexRel); + + TRACE_POSTGRESQL_SORT_START(INDEX_SORT, + enforceUnique, + base->nKeys, + workMem, + sortopt & TUPLESORT_RANDOMACCESS, + PARALLEL_SORT(coordinate)); + + base->removeabbrev = removeabbrev_index; + base->comparetup = comparetup_index_btree; + base->writetup = writetup_index; + base->readtup = readtup_index; + base->haveDatum1 = true; + base->arg = arg; + + arg->index.heapRel = heapRel; + arg->index.indexRel = indexRel; + arg->enforceUnique = enforceUnique; + arg->uniqueNullsNotDistinct = uniqueNullsNotDistinct; + + indexScanKey = _bt_mkscankey(indexRel, NULL); + + /* Prepare SortSupport data for each column */ + base->sortKeys = (SortSupport) palloc0(base->nKeys * + sizeof(SortSupportData)); + + for (i = 0; i < base->nKeys; i++) + { + SortSupport sortKey = base->sortKeys + i; + ScanKey scanKey = indexScanKey->scankeys + i; + int16 strategy; + + sortKey->ssup_cxt = CurrentMemoryContext; + sortKey->ssup_collation = scanKey->sk_collation; + sortKey->ssup_nulls_first = + (scanKey->sk_flags & SK_BT_NULLS_FIRST) != 0; + sortKey->ssup_attno = scanKey->sk_attno; + /* Convey if abbreviation optimization is applicable in principle */ + sortKey->abbreviate = (i == 0 && base->haveDatum1); + + Assert(sortKey->ssup_attno != 0); + + strategy = (scanKey->sk_flags & SK_BT_DESC) != 0 ? + BTGreaterStrategyNumber : BTLessStrategyNumber; + + PrepareSortSupportFromIndexRel(indexRel, strategy, sortKey); + } + + pfree(indexScanKey); + + MemoryContextSwitchTo(oldcontext); + + return state; +} + +Tuplesortstate * +tuplesort_begin_index_hash(Relation heapRel, + Relation indexRel, + uint32 high_mask, + uint32 low_mask, + uint32 max_buckets, + int workMem, + SortCoordinate coordinate, + int sortopt) +{ + Tuplesortstate *state = tuplesort_begin_common(workMem, coordinate, + sortopt); + TuplesortPublic *base = TuplesortstateGetPublic(state); + MemoryContext oldcontext; + TuplesortIndexHashArg *arg; + + oldcontext = MemoryContextSwitchTo(base->maincontext); + arg = (TuplesortIndexHashArg *) palloc(sizeof(TuplesortIndexHashArg)); + +#ifdef TRACE_SORT + if (trace_sort) + elog(LOG, + "begin index sort: high_mask = 0x%x, low_mask = 0x%x, " + "max_buckets = 0x%x, workMem = %d, randomAccess = %c", + high_mask, + low_mask, + max_buckets, + workMem, + sortopt & TUPLESORT_RANDOMACCESS ? 't' : 'f'); +#endif + + base->nKeys = 1; /* Only one sort column, the hash code */ + + base->removeabbrev = removeabbrev_index; + base->comparetup = comparetup_index_hash; + base->writetup = writetup_index; + base->readtup = readtup_index; + base->haveDatum1 = true; + base->arg = arg; + + arg->index.heapRel = heapRel; + arg->index.indexRel = indexRel; + + arg->high_mask = high_mask; + arg->low_mask = low_mask; + arg->max_buckets = max_buckets; + + MemoryContextSwitchTo(oldcontext); + + return state; +} + +Tuplesortstate * +tuplesort_begin_index_gist(Relation heapRel, + Relation indexRel, + int workMem, + SortCoordinate coordinate, + int sortopt) +{ + Tuplesortstate *state = tuplesort_begin_common(workMem, coordinate, + sortopt); + TuplesortPublic *base = TuplesortstateGetPublic(state); + MemoryContext oldcontext; + TuplesortIndexBTreeArg *arg; + int i; + + oldcontext = MemoryContextSwitchTo(base->maincontext); + arg = (TuplesortIndexBTreeArg *) palloc(sizeof(TuplesortIndexBTreeArg)); + +#ifdef TRACE_SORT + if (trace_sort) + elog(LOG, + "begin index sort: workMem = %d, randomAccess = %c", + workMem, sortopt & TUPLESORT_RANDOMACCESS ? 't' : 'f'); +#endif + + base->nKeys = IndexRelationGetNumberOfKeyAttributes(indexRel); + + base->removeabbrev = removeabbrev_index; + base->comparetup = comparetup_index_btree; + base->writetup = writetup_index; + base->readtup = readtup_index; + base->haveDatum1 = true; + base->arg = arg; + + arg->index.heapRel = heapRel; + arg->index.indexRel = indexRel; + arg->enforceUnique = false; + arg->uniqueNullsNotDistinct = false; + + /* Prepare SortSupport data for each column */ + base->sortKeys = (SortSupport) palloc0(base->nKeys * + sizeof(SortSupportData)); + + for (i = 0; i < base->nKeys; i++) + { + SortSupport sortKey = base->sortKeys + i; + + sortKey->ssup_cxt = CurrentMemoryContext; + sortKey->ssup_collation = indexRel->rd_indcollation[i]; + sortKey->ssup_nulls_first = false; + sortKey->ssup_attno = i + 1; + /* Convey if abbreviation optimization is applicable in principle */ + sortKey->abbreviate = (i == 0 && base->haveDatum1); + + Assert(sortKey->ssup_attno != 0); + + /* Look for a sort support function */ + PrepareSortSupportFromGistIndexRel(indexRel, sortKey); + } + + MemoryContextSwitchTo(oldcontext); + + return state; +} + +Tuplesortstate * +tuplesort_begin_datum(Oid datumType, Oid sortOperator, Oid sortCollation, + bool nullsFirstFlag, int workMem, + SortCoordinate coordinate, int sortopt) +{ + Tuplesortstate *state = tuplesort_begin_common(workMem, coordinate, + sortopt); + TuplesortPublic *base = TuplesortstateGetPublic(state); + TuplesortDatumArg *arg; + MemoryContext oldcontext; + int16 typlen; + bool typbyval; + + oldcontext = MemoryContextSwitchTo(base->maincontext); + arg = (TuplesortDatumArg *) palloc(sizeof(TuplesortDatumArg)); + +#ifdef TRACE_SORT + if (trace_sort) + elog(LOG, + "begin datum sort: workMem = %d, randomAccess = %c", + workMem, sortopt & TUPLESORT_RANDOMACCESS ? 't' : 'f'); +#endif + + base->nKeys = 1; /* always a one-column sort */ + + TRACE_POSTGRESQL_SORT_START(DATUM_SORT, + false, /* no unique check */ + 1, + workMem, + sortopt & TUPLESORT_RANDOMACCESS, + PARALLEL_SORT(coordinate)); + + base->removeabbrev = removeabbrev_datum; + base->comparetup = comparetup_datum; + base->writetup = writetup_datum; + base->readtup = readtup_datum; + base->haveDatum1 = true; + base->arg = arg; + + arg->datumType = datumType; + + /* lookup necessary attributes of the datum type */ + get_typlenbyval(datumType, &typlen, &typbyval); + arg->datumTypeLen = typlen; + base->tuples = !typbyval; + + /* Prepare SortSupport data */ + base->sortKeys = (SortSupport) palloc0(sizeof(SortSupportData)); + + base->sortKeys->ssup_cxt = CurrentMemoryContext; + base->sortKeys->ssup_collation = sortCollation; + base->sortKeys->ssup_nulls_first = nullsFirstFlag; + + /* + * Abbreviation is possible here only for by-reference types. In theory, + * a pass-by-value datatype could have an abbreviated form that is cheaper + * to compare. In a tuple sort, we could support that, because we can + * always extract the original datum from the tuple as needed. Here, we + * can't, because a datum sort only stores a single copy of the datum; the + * "tuple" field of each SortTuple is NULL. + */ + base->sortKeys->abbreviate = !typbyval; + + PrepareSortSupportFromOrderingOp(sortOperator, base->sortKeys); + + /* + * The "onlyKey" optimization cannot be used with abbreviated keys, since + * tie-breaker comparisons may be required. Typically, the optimization + * is only of value to pass-by-value types anyway, whereas abbreviated + * keys are typically only of value to pass-by-reference types. + */ + if (!base->sortKeys->abbrev_converter) + base->onlyKey = base->sortKeys; + + MemoryContextSwitchTo(oldcontext); + + return state; +} + +/* + * Accept one tuple while collecting input data for sort. + * + * Note that the input data is always copied; the caller need not save it. + */ +void +tuplesort_puttupleslot(Tuplesortstate *state, TupleTableSlot *slot) +{ + TuplesortPublic *base = TuplesortstateGetPublic(state); + MemoryContext oldcontext = MemoryContextSwitchTo(base->tuplecontext); + TupleDesc tupDesc = (TupleDesc) base->arg; + SortTuple stup; + MinimalTuple tuple; + HeapTupleData htup; + + /* copy the tuple into sort storage */ + tuple = ExecCopySlotMinimalTuple(slot); + stup.tuple = (void *) tuple; + /* set up first-column key value */ + htup.t_len = tuple->t_len + MINIMAL_TUPLE_OFFSET; + htup.t_data = (HeapTupleHeader) ((char *) tuple - MINIMAL_TUPLE_OFFSET); + stup.datum1 = heap_getattr(&htup, + base->sortKeys[0].ssup_attno, + tupDesc, + &stup.isnull1); + + tuplesort_puttuple_common(state, &stup, + base->sortKeys->abbrev_converter && + !stup.isnull1); + + MemoryContextSwitchTo(oldcontext); +} + +/* + * Accept one tuple while collecting input data for sort. + * + * Note that the input data is always copied; the caller need not save it. + */ +void +tuplesort_putheaptuple(Tuplesortstate *state, HeapTuple tup) +{ + SortTuple stup; + TuplesortPublic *base = TuplesortstateGetPublic(state); + MemoryContext oldcontext = MemoryContextSwitchTo(base->tuplecontext); + TuplesortClusterArg *arg = (TuplesortClusterArg *) base->arg; + + /* copy the tuple into sort storage */ + tup = heap_copytuple(tup); + stup.tuple = (void *) tup; + + /* + * set up first-column key value, and potentially abbreviate, if it's a + * simple column + */ + if (base->haveDatum1) + { + stup.datum1 = heap_getattr(tup, + arg->indexInfo->ii_IndexAttrNumbers[0], + arg->tupDesc, + &stup.isnull1); + } + + tuplesort_puttuple_common(state, &stup, + base->haveDatum1 && + base->sortKeys->abbrev_converter && + !stup.isnull1); + + MemoryContextSwitchTo(oldcontext); +} + +/* + * Collect one index tuple while collecting input data for sort, building + * it from caller-supplied values. + */ +void +tuplesort_putindextuplevalues(Tuplesortstate *state, Relation rel, + ItemPointer self, Datum *values, + bool *isnull) +{ + SortTuple stup; + IndexTuple tuple; + TuplesortPublic *base = TuplesortstateGetPublic(state); + TuplesortIndexArg *arg = (TuplesortIndexArg *) base->arg; + + stup.tuple = index_form_tuple_context(RelationGetDescr(rel), values, + isnull, base->tuplecontext); + tuple = ((IndexTuple) stup.tuple); + tuple->t_tid = *self; + /* set up first-column key value */ + stup.datum1 = index_getattr(tuple, + 1, + RelationGetDescr(arg->indexRel), + &stup.isnull1); + + tuplesort_puttuple_common(state, &stup, + base->sortKeys && + base->sortKeys->abbrev_converter && + !stup.isnull1); +} + +/* + * Accept one Datum while collecting input data for sort. + * + * If the Datum is pass-by-ref type, the value will be copied. + */ +void +tuplesort_putdatum(Tuplesortstate *state, Datum val, bool isNull) +{ + TuplesortPublic *base = TuplesortstateGetPublic(state); + MemoryContext oldcontext = MemoryContextSwitchTo(base->tuplecontext); + TuplesortDatumArg *arg = (TuplesortDatumArg *) base->arg; + SortTuple stup; + + /* + * Pass-by-value types or null values are just stored directly in + * stup.datum1 (and stup.tuple is not used and set to NULL). + * + * Non-null pass-by-reference values need to be copied into memory we + * control, and possibly abbreviated. The copied value is pointed to by + * stup.tuple and is treated as the canonical copy (e.g. to return via + * tuplesort_getdatum or when writing to tape); stup.datum1 gets the + * abbreviated value if abbreviation is happening, otherwise it's + * identical to stup.tuple. + */ + + if (isNull || !base->tuples) + { + /* + * Set datum1 to zeroed representation for NULLs (to be consistent, + * and to support cheap inequality tests for NULL abbreviated keys). + */ + stup.datum1 = !isNull ? val : (Datum) 0; + stup.isnull1 = isNull; + stup.tuple = NULL; /* no separate storage */ + } + else + { + stup.isnull1 = false; + stup.datum1 = datumCopy(val, false, arg->datumTypeLen); + stup.tuple = DatumGetPointer(stup.datum1); + } + + tuplesort_puttuple_common(state, &stup, + base->tuples && + base->sortKeys->abbrev_converter && !isNull); + + MemoryContextSwitchTo(oldcontext); +} + +/* + * Fetch the next tuple in either forward or back direction. + * If successful, put tuple in slot and return true; else, clear the slot + * and return false. + * + * Caller may optionally be passed back abbreviated value (on true return + * value) when abbreviation was used, which can be used to cheaply avoid + * equality checks that might otherwise be required. Caller can safely make a + * determination of "non-equal tuple" based on simple binary inequality. A + * NULL value in leading attribute will set abbreviated value to zeroed + * representation, which caller may rely on in abbreviated inequality check. + * + * If copy is true, the slot receives a tuple that's been copied into the + * caller's memory context, so that it will stay valid regardless of future + * manipulations of the tuplesort's state (up to and including deleting the + * tuplesort). If copy is false, the slot will just receive a pointer to a + * tuple held within the tuplesort, which is more efficient, but only safe for + * callers that are prepared to have any subsequent manipulation of the + * tuplesort's state invalidate slot contents. + */ +bool +tuplesort_gettupleslot(Tuplesortstate *state, bool forward, bool copy, + TupleTableSlot *slot, Datum *abbrev) +{ + TuplesortPublic *base = TuplesortstateGetPublic(state); + MemoryContext oldcontext = MemoryContextSwitchTo(base->sortcontext); + SortTuple stup; + + if (!tuplesort_gettuple_common(state, forward, &stup)) + stup.tuple = NULL; + + MemoryContextSwitchTo(oldcontext); + + if (stup.tuple) + { + /* Record abbreviated key for caller */ + if (base->sortKeys->abbrev_converter && abbrev) + *abbrev = stup.datum1; + + if (copy) + stup.tuple = heap_copy_minimal_tuple((MinimalTuple) stup.tuple); + + ExecStoreMinimalTuple((MinimalTuple) stup.tuple, slot, copy); + return true; + } + else + { + ExecClearTuple(slot); + return false; + } +} + +/* + * Fetch the next tuple in either forward or back direction. + * Returns NULL if no more tuples. Returned tuple belongs to tuplesort memory + * context, and must not be freed by caller. Caller may not rely on tuple + * remaining valid after any further manipulation of tuplesort. + */ +HeapTuple +tuplesort_getheaptuple(Tuplesortstate *state, bool forward) +{ + TuplesortPublic *base = TuplesortstateGetPublic(state); + MemoryContext oldcontext = MemoryContextSwitchTo(base->sortcontext); + SortTuple stup; + + if (!tuplesort_gettuple_common(state, forward, &stup)) + stup.tuple = NULL; + + MemoryContextSwitchTo(oldcontext); + + return stup.tuple; +} + +/* + * Fetch the next index tuple in either forward or back direction. + * Returns NULL if no more tuples. Returned tuple belongs to tuplesort memory + * context, and must not be freed by caller. Caller may not rely on tuple + * remaining valid after any further manipulation of tuplesort. + */ +IndexTuple +tuplesort_getindextuple(Tuplesortstate *state, bool forward) +{ + TuplesortPublic *base = TuplesortstateGetPublic(state); + MemoryContext oldcontext = MemoryContextSwitchTo(base->sortcontext); + SortTuple stup; + + if (!tuplesort_gettuple_common(state, forward, &stup)) + stup.tuple = NULL; + + MemoryContextSwitchTo(oldcontext); + + return (IndexTuple) stup.tuple; +} + +/* + * Fetch the next Datum in either forward or back direction. + * Returns false if no more datums. + * + * If the Datum is pass-by-ref type, the returned value is freshly palloc'd + * in caller's context, and is now owned by the caller (this differs from + * similar routines for other types of tuplesorts). + * + * Caller may optionally be passed back abbreviated value (on true return + * value) when abbreviation was used, which can be used to cheaply avoid + * equality checks that might otherwise be required. Caller can safely make a + * determination of "non-equal tuple" based on simple binary inequality. A + * NULL value will have a zeroed abbreviated value representation, which caller + * may rely on in abbreviated inequality check. + * + * For byref Datums, if copy is true, *val is set to a copy of the Datum + * copied into the caller's memory context, so that it will stay valid + * regardless of future manipulations of the tuplesort's state (up to and + * including deleting the tuplesort). If copy is false, *val will just be + * set to a pointer to the Datum held within the tuplesort, which is more + * efficient, but only safe for callers that are prepared to have any + * subsequent manipulation of the tuplesort's state invalidate slot contents. + * For byval Datums, the value of the 'copy' parameter has no effect. + + */ +bool +tuplesort_getdatum(Tuplesortstate *state, bool forward, bool copy, + Datum *val, bool *isNull, Datum *abbrev) +{ + TuplesortPublic *base = TuplesortstateGetPublic(state); + MemoryContext oldcontext = MemoryContextSwitchTo(base->sortcontext); + TuplesortDatumArg *arg = (TuplesortDatumArg *) base->arg; + SortTuple stup; + + if (!tuplesort_gettuple_common(state, forward, &stup)) + { + MemoryContextSwitchTo(oldcontext); + return false; + } + + /* Ensure we copy into caller's memory context */ + MemoryContextSwitchTo(oldcontext); + + /* Record abbreviated key for caller */ + if (base->sortKeys->abbrev_converter && abbrev) + *abbrev = stup.datum1; + + if (stup.isnull1 || !base->tuples) + { + *val = stup.datum1; + *isNull = stup.isnull1; + } + else + { + /* use stup.tuple because stup.datum1 may be an abbreviation */ + if (copy) + *val = datumCopy(PointerGetDatum(stup.tuple), false, + arg->datumTypeLen); + else + *val = PointerGetDatum(stup.tuple); + *isNull = false; + } + + return true; +} + + +/* + * Routines specialized for HeapTuple (actually MinimalTuple) case + */ + +static void +removeabbrev_heap(Tuplesortstate *state, SortTuple *stups, int count) +{ + int i; + TuplesortPublic *base = TuplesortstateGetPublic(state); + + for (i = 0; i < count; i++) + { + HeapTupleData htup; + + htup.t_len = ((MinimalTuple) stups[i].tuple)->t_len + + MINIMAL_TUPLE_OFFSET; + htup.t_data = (HeapTupleHeader) ((char *) stups[i].tuple - + MINIMAL_TUPLE_OFFSET); + stups[i].datum1 = heap_getattr(&htup, + base->sortKeys[0].ssup_attno, + (TupleDesc) base->arg, + &stups[i].isnull1); + } +} + +static int +comparetup_heap(const SortTuple *a, const SortTuple *b, Tuplesortstate *state) +{ + TuplesortPublic *base = TuplesortstateGetPublic(state); + SortSupport sortKey = base->sortKeys; + HeapTupleData ltup; + HeapTupleData rtup; + TupleDesc tupDesc; + int nkey; + int32 compare; + AttrNumber attno; + Datum datum1, + datum2; + bool isnull1, + isnull2; + + + /* Compare the leading sort key */ + compare = ApplySortComparator(a->datum1, a->isnull1, + b->datum1, b->isnull1, + sortKey); + if (compare != 0) + return compare; + + /* Compare additional sort keys */ + ltup.t_len = ((MinimalTuple) a->tuple)->t_len + MINIMAL_TUPLE_OFFSET; + ltup.t_data = (HeapTupleHeader) ((char *) a->tuple - MINIMAL_TUPLE_OFFSET); + rtup.t_len = ((MinimalTuple) b->tuple)->t_len + MINIMAL_TUPLE_OFFSET; + rtup.t_data = (HeapTupleHeader) ((char *) b->tuple - MINIMAL_TUPLE_OFFSET); + tupDesc = (TupleDesc) base->arg; + + if (sortKey->abbrev_converter) + { + attno = sortKey->ssup_attno; + + datum1 = heap_getattr(<up, attno, tupDesc, &isnull1); + datum2 = heap_getattr(&rtup, attno, tupDesc, &isnull2); + + compare = ApplySortAbbrevFullComparator(datum1, isnull1, + datum2, isnull2, + sortKey); + if (compare != 0) + return compare; + } + + sortKey++; + for (nkey = 1; nkey < base->nKeys; nkey++, sortKey++) + { + attno = sortKey->ssup_attno; + + datum1 = heap_getattr(<up, attno, tupDesc, &isnull1); + datum2 = heap_getattr(&rtup, attno, tupDesc, &isnull2); + + compare = ApplySortComparator(datum1, isnull1, + datum2, isnull2, + sortKey); + if (compare != 0) + return compare; + } + + return 0; +} + +static void +writetup_heap(Tuplesortstate *state, LogicalTape *tape, SortTuple *stup) +{ + TuplesortPublic *base = TuplesortstateGetPublic(state); + MinimalTuple tuple = (MinimalTuple) stup->tuple; + + /* the part of the MinimalTuple we'll write: */ + char *tupbody = (char *) tuple + MINIMAL_TUPLE_DATA_OFFSET; + unsigned int tupbodylen = tuple->t_len - MINIMAL_TUPLE_DATA_OFFSET; + + /* total on-disk footprint: */ + unsigned int tuplen = tupbodylen + sizeof(int); + + LogicalTapeWrite(tape, &tuplen, sizeof(tuplen)); + LogicalTapeWrite(tape, tupbody, tupbodylen); + if (base->sortopt & TUPLESORT_RANDOMACCESS) /* need trailing length word? */ + LogicalTapeWrite(tape, &tuplen, sizeof(tuplen)); +} + +static void +readtup_heap(Tuplesortstate *state, SortTuple *stup, + LogicalTape *tape, unsigned int len) +{ + unsigned int tupbodylen = len - sizeof(int); + unsigned int tuplen = tupbodylen + MINIMAL_TUPLE_DATA_OFFSET; + MinimalTuple tuple = (MinimalTuple) tuplesort_readtup_alloc(state, tuplen); + char *tupbody = (char *) tuple + MINIMAL_TUPLE_DATA_OFFSET; + TuplesortPublic *base = TuplesortstateGetPublic(state); + HeapTupleData htup; + + /* read in the tuple proper */ + tuple->t_len = tuplen; + LogicalTapeReadExact(tape, tupbody, tupbodylen); + if (base->sortopt & TUPLESORT_RANDOMACCESS) /* need trailing length word? */ + LogicalTapeReadExact(tape, &tuplen, sizeof(tuplen)); + stup->tuple = (void *) tuple; + /* set up first-column key value */ + htup.t_len = tuple->t_len + MINIMAL_TUPLE_OFFSET; + htup.t_data = (HeapTupleHeader) ((char *) tuple - MINIMAL_TUPLE_OFFSET); + stup->datum1 = heap_getattr(&htup, + base->sortKeys[0].ssup_attno, + (TupleDesc) base->arg, + &stup->isnull1); +} + +/* + * Routines specialized for the CLUSTER case (HeapTuple data, with + * comparisons per a btree index definition) + */ + +static void +removeabbrev_cluster(Tuplesortstate *state, SortTuple *stups, int count) +{ + int i; + TuplesortPublic *base = TuplesortstateGetPublic(state); + TuplesortClusterArg *arg = (TuplesortClusterArg *) base->arg; + + for (i = 0; i < count; i++) + { + HeapTuple tup; + + tup = (HeapTuple) stups[i].tuple; + stups[i].datum1 = heap_getattr(tup, + arg->indexInfo->ii_IndexAttrNumbers[0], + arg->tupDesc, + &stups[i].isnull1); + } +} + +static int +comparetup_cluster(const SortTuple *a, const SortTuple *b, + Tuplesortstate *state) +{ + TuplesortPublic *base = TuplesortstateGetPublic(state); + TuplesortClusterArg *arg = (TuplesortClusterArg *) base->arg; + SortSupport sortKey = base->sortKeys; + HeapTuple ltup; + HeapTuple rtup; + TupleDesc tupDesc; + int nkey; + int32 compare; + Datum datum1, + datum2; + bool isnull1, + isnull2; + + /* Be prepared to compare additional sort keys */ + ltup = (HeapTuple) a->tuple; + rtup = (HeapTuple) b->tuple; + tupDesc = arg->tupDesc; + + /* Compare the leading sort key, if it's simple */ + if (base->haveDatum1) + { + compare = ApplySortComparator(a->datum1, a->isnull1, + b->datum1, b->isnull1, + sortKey); + if (compare != 0) + return compare; + + if (sortKey->abbrev_converter) + { + AttrNumber leading = arg->indexInfo->ii_IndexAttrNumbers[0]; + + datum1 = heap_getattr(ltup, leading, tupDesc, &isnull1); + datum2 = heap_getattr(rtup, leading, tupDesc, &isnull2); + + compare = ApplySortAbbrevFullComparator(datum1, isnull1, + datum2, isnull2, + sortKey); + } + if (compare != 0 || base->nKeys == 1) + return compare; + /* Compare additional columns the hard way */ + sortKey++; + nkey = 1; + } + else + { + /* Must compare all keys the hard way */ + nkey = 0; + } + + if (arg->indexInfo->ii_Expressions == NULL) + { + /* If not expression index, just compare the proper heap attrs */ + + for (; nkey < base->nKeys; nkey++, sortKey++) + { + AttrNumber attno = arg->indexInfo->ii_IndexAttrNumbers[nkey]; + + datum1 = heap_getattr(ltup, attno, tupDesc, &isnull1); + datum2 = heap_getattr(rtup, attno, tupDesc, &isnull2); + + compare = ApplySortComparator(datum1, isnull1, + datum2, isnull2, + sortKey); + if (compare != 0) + return compare; + } + } + else + { + /* + * In the expression index case, compute the whole index tuple and + * then compare values. It would perhaps be faster to compute only as + * many columns as we need to compare, but that would require + * duplicating all the logic in FormIndexDatum. + */ + Datum l_index_values[INDEX_MAX_KEYS]; + bool l_index_isnull[INDEX_MAX_KEYS]; + Datum r_index_values[INDEX_MAX_KEYS]; + bool r_index_isnull[INDEX_MAX_KEYS]; + TupleTableSlot *ecxt_scantuple; + + /* Reset context each time to prevent memory leakage */ + ResetPerTupleExprContext(arg->estate); + + ecxt_scantuple = GetPerTupleExprContext(arg->estate)->ecxt_scantuple; + + ExecStoreHeapTuple(ltup, ecxt_scantuple, false); + FormIndexDatum(arg->indexInfo, ecxt_scantuple, arg->estate, + l_index_values, l_index_isnull); + + ExecStoreHeapTuple(rtup, ecxt_scantuple, false); + FormIndexDatum(arg->indexInfo, ecxt_scantuple, arg->estate, + r_index_values, r_index_isnull); + + for (; nkey < base->nKeys; nkey++, sortKey++) + { + compare = ApplySortComparator(l_index_values[nkey], + l_index_isnull[nkey], + r_index_values[nkey], + r_index_isnull[nkey], + sortKey); + if (compare != 0) + return compare; + } + } + + return 0; +} + +static void +writetup_cluster(Tuplesortstate *state, LogicalTape *tape, SortTuple *stup) +{ + TuplesortPublic *base = TuplesortstateGetPublic(state); + HeapTuple tuple = (HeapTuple) stup->tuple; + unsigned int tuplen = tuple->t_len + sizeof(ItemPointerData) + sizeof(int); + + /* We need to store t_self, but not other fields of HeapTupleData */ + LogicalTapeWrite(tape, &tuplen, sizeof(tuplen)); + LogicalTapeWrite(tape, &tuple->t_self, sizeof(ItemPointerData)); + LogicalTapeWrite(tape, tuple->t_data, tuple->t_len); + if (base->sortopt & TUPLESORT_RANDOMACCESS) /* need trailing length word? */ + LogicalTapeWrite(tape, &tuplen, sizeof(tuplen)); +} + +static void +readtup_cluster(Tuplesortstate *state, SortTuple *stup, + LogicalTape *tape, unsigned int tuplen) +{ + TuplesortPublic *base = TuplesortstateGetPublic(state); + TuplesortClusterArg *arg = (TuplesortClusterArg *) base->arg; + unsigned int t_len = tuplen - sizeof(ItemPointerData) - sizeof(int); + HeapTuple tuple = (HeapTuple) tuplesort_readtup_alloc(state, + t_len + HEAPTUPLESIZE); + + /* Reconstruct the HeapTupleData header */ + tuple->t_data = (HeapTupleHeader) ((char *) tuple + HEAPTUPLESIZE); + tuple->t_len = t_len; + LogicalTapeReadExact(tape, &tuple->t_self, sizeof(ItemPointerData)); + /* We don't currently bother to reconstruct t_tableOid */ + tuple->t_tableOid = InvalidOid; + /* Read in the tuple body */ + LogicalTapeReadExact(tape, tuple->t_data, tuple->t_len); + if (base->sortopt & TUPLESORT_RANDOMACCESS) /* need trailing length word? */ + LogicalTapeReadExact(tape, &tuplen, sizeof(tuplen)); + stup->tuple = (void *) tuple; + /* set up first-column key value, if it's a simple column */ + if (base->haveDatum1) + stup->datum1 = heap_getattr(tuple, + arg->indexInfo->ii_IndexAttrNumbers[0], + arg->tupDesc, + &stup->isnull1); +} + +static void +freestate_cluster(Tuplesortstate *state) +{ + TuplesortPublic *base = TuplesortstateGetPublic(state); + TuplesortClusterArg *arg = (TuplesortClusterArg *) base->arg; + + /* Free any execution state created for CLUSTER case */ + if (arg->estate != NULL) + { + ExprContext *econtext = GetPerTupleExprContext(arg->estate); + + ExecDropSingleTupleTableSlot(econtext->ecxt_scantuple); + FreeExecutorState(arg->estate); + } +} + +/* + * Routines specialized for IndexTuple case + * + * The btree and hash cases require separate comparison functions, but the + * IndexTuple representation is the same so the copy/write/read support + * functions can be shared. + */ + +static void +removeabbrev_index(Tuplesortstate *state, SortTuple *stups, int count) +{ + TuplesortPublic *base = TuplesortstateGetPublic(state); + TuplesortIndexArg *arg = (TuplesortIndexArg *) base->arg; + int i; + + for (i = 0; i < count; i++) + { + IndexTuple tuple; + + tuple = stups[i].tuple; + stups[i].datum1 = index_getattr(tuple, + 1, + RelationGetDescr(arg->indexRel), + &stups[i].isnull1); + } +} + +static int +comparetup_index_btree(const SortTuple *a, const SortTuple *b, + Tuplesortstate *state) +{ + /* + * This is similar to comparetup_heap(), but expects index tuples. There + * is also special handling for enforcing uniqueness, and special + * treatment for equal keys at the end. + */ + TuplesortPublic *base = TuplesortstateGetPublic(state); + TuplesortIndexBTreeArg *arg = (TuplesortIndexBTreeArg *) base->arg; + SortSupport sortKey = base->sortKeys; + IndexTuple tuple1; + IndexTuple tuple2; + int keysz; + TupleDesc tupDes; + bool equal_hasnull = false; + int nkey; + int32 compare; + Datum datum1, + datum2; + bool isnull1, + isnull2; + + + /* Compare the leading sort key */ + compare = ApplySortComparator(a->datum1, a->isnull1, + b->datum1, b->isnull1, + sortKey); + if (compare != 0) + return compare; + + /* Compare additional sort keys */ + tuple1 = (IndexTuple) a->tuple; + tuple2 = (IndexTuple) b->tuple; + keysz = base->nKeys; + tupDes = RelationGetDescr(arg->index.indexRel); + + if (sortKey->abbrev_converter) + { + datum1 = index_getattr(tuple1, 1, tupDes, &isnull1); + datum2 = index_getattr(tuple2, 1, tupDes, &isnull2); + + compare = ApplySortAbbrevFullComparator(datum1, isnull1, + datum2, isnull2, + sortKey); + if (compare != 0) + return compare; + } + + /* they are equal, so we only need to examine one null flag */ + if (a->isnull1) + equal_hasnull = true; + + sortKey++; + for (nkey = 2; nkey <= keysz; nkey++, sortKey++) + { + datum1 = index_getattr(tuple1, nkey, tupDes, &isnull1); + datum2 = index_getattr(tuple2, nkey, tupDes, &isnull2); + + compare = ApplySortComparator(datum1, isnull1, + datum2, isnull2, + sortKey); + if (compare != 0) + return compare; /* done when we find unequal attributes */ + + /* they are equal, so we only need to examine one null flag */ + if (isnull1) + equal_hasnull = true; + } + + /* + * If btree has asked us to enforce uniqueness, complain if two equal + * tuples are detected (unless there was at least one NULL field and NULLS + * NOT DISTINCT was not set). + * + * It is sufficient to make the test here, because if two tuples are equal + * they *must* get compared at some stage of the sort --- otherwise the + * sort algorithm wouldn't have checked whether one must appear before the + * other. + */ + if (arg->enforceUnique && !(!arg->uniqueNullsNotDistinct && equal_hasnull)) + { + Datum values[INDEX_MAX_KEYS]; + bool isnull[INDEX_MAX_KEYS]; + char *key_desc; + + /* + * Some rather brain-dead implementations of qsort (such as the one in + * QNX 4) will sometimes call the comparison routine to compare a + * value to itself, but we always use our own implementation, which + * does not. + */ + Assert(tuple1 != tuple2); + + index_deform_tuple(tuple1, tupDes, values, isnull); + + key_desc = BuildIndexValueDescription(arg->index.indexRel, values, isnull); + + ereport(ERROR, + (errcode(ERRCODE_UNIQUE_VIOLATION), + errmsg("could not create unique index \"%s\"", + RelationGetRelationName(arg->index.indexRel)), + key_desc ? errdetail("Key %s is duplicated.", key_desc) : + errdetail("Duplicate keys exist."), + errtableconstraint(arg->index.heapRel, + RelationGetRelationName(arg->index.indexRel)))); + } + + /* + * If key values are equal, we sort on ItemPointer. This is required for + * btree indexes, since heap TID is treated as an implicit last key + * attribute in order to ensure that all keys in the index are physically + * unique. + */ + { + BlockNumber blk1 = ItemPointerGetBlockNumber(&tuple1->t_tid); + BlockNumber blk2 = ItemPointerGetBlockNumber(&tuple2->t_tid); + + if (blk1 != blk2) + return (blk1 < blk2) ? -1 : 1; + } + { + OffsetNumber pos1 = ItemPointerGetOffsetNumber(&tuple1->t_tid); + OffsetNumber pos2 = ItemPointerGetOffsetNumber(&tuple2->t_tid); + + if (pos1 != pos2) + return (pos1 < pos2) ? -1 : 1; + } + + /* ItemPointer values should never be equal */ + Assert(false); + + return 0; +} + +static int +comparetup_index_hash(const SortTuple *a, const SortTuple *b, + Tuplesortstate *state) +{ + Bucket bucket1; + Bucket bucket2; + uint32 hash1; + uint32 hash2; + IndexTuple tuple1; + IndexTuple tuple2; + TuplesortPublic *base = TuplesortstateGetPublic(state); + TuplesortIndexHashArg *arg = (TuplesortIndexHashArg *) base->arg; + + /* + * Fetch hash keys and mask off bits we don't want to sort by, so that the + * initial sort is just on the bucket number. We know that the first + * column of the index tuple is the hash key. + */ + Assert(!a->isnull1); + bucket1 = _hash_hashkey2bucket(DatumGetUInt32(a->datum1), + arg->max_buckets, arg->high_mask, + arg->low_mask); + Assert(!b->isnull1); + bucket2 = _hash_hashkey2bucket(DatumGetUInt32(b->datum1), + arg->max_buckets, arg->high_mask, + arg->low_mask); + if (bucket1 > bucket2) + return 1; + else if (bucket1 < bucket2) + return -1; + + /* + * If bucket values are equal, sort by hash values. This allows us to + * insert directly onto bucket/overflow pages, where the index tuples are + * stored in hash order to allow fast binary search within each page. + */ + hash1 = DatumGetUInt32(a->datum1); + hash2 = DatumGetUInt32(b->datum1); + if (hash1 > hash2) + return 1; + else if (hash1 < hash2) + return -1; + + /* + * If hash values are equal, we sort on ItemPointer. This does not affect + * validity of the finished index, but it may be useful to have index + * scans in physical order. + */ + tuple1 = (IndexTuple) a->tuple; + tuple2 = (IndexTuple) b->tuple; + + { + BlockNumber blk1 = ItemPointerGetBlockNumber(&tuple1->t_tid); + BlockNumber blk2 = ItemPointerGetBlockNumber(&tuple2->t_tid); + + if (blk1 != blk2) + return (blk1 < blk2) ? -1 : 1; + } + { + OffsetNumber pos1 = ItemPointerGetOffsetNumber(&tuple1->t_tid); + OffsetNumber pos2 = ItemPointerGetOffsetNumber(&tuple2->t_tid); + + if (pos1 != pos2) + return (pos1 < pos2) ? -1 : 1; + } + + /* ItemPointer values should never be equal */ + Assert(false); + + return 0; +} + +static void +writetup_index(Tuplesortstate *state, LogicalTape *tape, SortTuple *stup) +{ + TuplesortPublic *base = TuplesortstateGetPublic(state); + IndexTuple tuple = (IndexTuple) stup->tuple; + unsigned int tuplen; + + tuplen = IndexTupleSize(tuple) + sizeof(tuplen); + LogicalTapeWrite(tape, &tuplen, sizeof(tuplen)); + LogicalTapeWrite(tape, tuple, IndexTupleSize(tuple)); + if (base->sortopt & TUPLESORT_RANDOMACCESS) /* need trailing length word? */ + LogicalTapeWrite(tape, &tuplen, sizeof(tuplen)); +} + +static void +readtup_index(Tuplesortstate *state, SortTuple *stup, + LogicalTape *tape, unsigned int len) +{ + TuplesortPublic *base = TuplesortstateGetPublic(state); + TuplesortIndexArg *arg = (TuplesortIndexArg *) base->arg; + unsigned int tuplen = len - sizeof(unsigned int); + IndexTuple tuple = (IndexTuple) tuplesort_readtup_alloc(state, tuplen); + + LogicalTapeReadExact(tape, tuple, tuplen); + if (base->sortopt & TUPLESORT_RANDOMACCESS) /* need trailing length word? */ + LogicalTapeReadExact(tape, &tuplen, sizeof(tuplen)); + stup->tuple = (void *) tuple; + /* set up first-column key value */ + stup->datum1 = index_getattr(tuple, + 1, + RelationGetDescr(arg->indexRel), + &stup->isnull1); +} + +/* + * Routines specialized for DatumTuple case + */ + +static void +removeabbrev_datum(Tuplesortstate *state, SortTuple *stups, int count) +{ + int i; + + for (i = 0; i < count; i++) + stups[i].datum1 = PointerGetDatum(stups[i].tuple); +} + +static int +comparetup_datum(const SortTuple *a, const SortTuple *b, Tuplesortstate *state) +{ + TuplesortPublic *base = TuplesortstateGetPublic(state); + int compare; + + compare = ApplySortComparator(a->datum1, a->isnull1, + b->datum1, b->isnull1, + base->sortKeys); + if (compare != 0) + return compare; + + /* if we have abbreviations, then "tuple" has the original value */ + + if (base->sortKeys->abbrev_converter) + compare = ApplySortAbbrevFullComparator(PointerGetDatum(a->tuple), a->isnull1, + PointerGetDatum(b->tuple), b->isnull1, + base->sortKeys); + + return compare; +} + +static void +writetup_datum(Tuplesortstate *state, LogicalTape *tape, SortTuple *stup) +{ + TuplesortPublic *base = TuplesortstateGetPublic(state); + TuplesortDatumArg *arg = (TuplesortDatumArg *) base->arg; + void *waddr; + unsigned int tuplen; + unsigned int writtenlen; + + if (stup->isnull1) + { + waddr = NULL; + tuplen = 0; + } + else if (!base->tuples) + { + waddr = &stup->datum1; + tuplen = sizeof(Datum); + } + else + { + waddr = stup->tuple; + tuplen = datumGetSize(PointerGetDatum(stup->tuple), false, arg->datumTypeLen); + Assert(tuplen != 0); + } + + writtenlen = tuplen + sizeof(unsigned int); + + LogicalTapeWrite(tape, &writtenlen, sizeof(writtenlen)); + LogicalTapeWrite(tape, waddr, tuplen); + if (base->sortopt & TUPLESORT_RANDOMACCESS) /* need trailing length word? */ + LogicalTapeWrite(tape, &writtenlen, sizeof(writtenlen)); +} + +static void +readtup_datum(Tuplesortstate *state, SortTuple *stup, + LogicalTape *tape, unsigned int len) +{ + TuplesortPublic *base = TuplesortstateGetPublic(state); + unsigned int tuplen = len - sizeof(unsigned int); + + if (tuplen == 0) + { + /* it's NULL */ + stup->datum1 = (Datum) 0; + stup->isnull1 = true; + stup->tuple = NULL; + } + else if (!base->tuples) + { + Assert(tuplen == sizeof(Datum)); + LogicalTapeReadExact(tape, &stup->datum1, tuplen); + stup->isnull1 = false; + stup->tuple = NULL; + } + else + { + void *raddr = tuplesort_readtup_alloc(state, tuplen); + + LogicalTapeReadExact(tape, raddr, tuplen); + stup->datum1 = PointerGetDatum(raddr); + stup->isnull1 = false; + stup->tuple = raddr; + } + + if (base->sortopt & TUPLESORT_RANDOMACCESS) /* need trailing length word? */ + LogicalTapeReadExact(tape, &tuplen, sizeof(tuplen)); +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/sort/tuplestore.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/sort/tuplestore.c new file mode 100644 index 00000000000..f60633df241 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/sort/tuplestore.c @@ -0,0 +1,1535 @@ +/*------------------------------------------------------------------------- + * + * tuplestore.c + * Generalized routines for temporary tuple storage. + * + * This module handles temporary storage of tuples for purposes such + * as Materialize nodes, hashjoin batch files, etc. It is essentially + * a dumbed-down version of tuplesort.c; it does no sorting of tuples + * but can only store and regurgitate a sequence of tuples. However, + * because no sort is required, it is allowed to start reading the sequence + * before it has all been written. This is particularly useful for cursors, + * because it allows random access within the already-scanned portion of + * a query without having to process the underlying scan to completion. + * Also, it is possible to support multiple independent read pointers. + * + * A temporary file is used to handle the data if it exceeds the + * space limit specified by the caller. + * + * The (approximate) amount of memory allowed to the tuplestore is specified + * in kilobytes by the caller. We absorb tuples and simply store them in an + * in-memory array as long as we haven't exceeded maxKBytes. If we do exceed + * maxKBytes, we dump all the tuples into a temp file and then read from that + * when needed. + * + * Upon creation, a tuplestore supports a single read pointer, numbered 0. + * Additional read pointers can be created using tuplestore_alloc_read_pointer. + * Mark/restore behavior is supported by copying read pointers. + * + * When the caller requests backward-scan capability, we write the temp file + * in a format that allows either forward or backward scan. Otherwise, only + * forward scan is allowed. A request for backward scan must be made before + * putting any tuples into the tuplestore. Rewind is normally allowed but + * can be turned off via tuplestore_set_eflags; turning off rewind for all + * read pointers enables truncation of the tuplestore at the oldest read point + * for minimal memory usage. (The caller must explicitly call tuplestore_trim + * at appropriate times for truncation to actually happen.) + * + * Note: in TSS_WRITEFILE state, the temp file's seek position is the + * current write position, and the write-position variables in the tuplestore + * aren't kept up to date. Similarly, in TSS_READFILE state the temp file's + * seek position is the active read pointer's position, and that read pointer + * isn't kept up to date. We update the appropriate variables using ftell() + * before switching to the other state or activating a different read pointer. + * + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/utils/sort/tuplestore.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include <limits.h> + +#include "access/htup_details.h" +#include "commands/tablespace.h" +#include "executor/executor.h" +#include "miscadmin.h" +#include "storage/buffile.h" +#include "utils/memutils.h" +#include "utils/resowner.h" + + +/* + * Possible states of a Tuplestore object. These denote the states that + * persist between calls of Tuplestore routines. + */ +typedef enum +{ + TSS_INMEM, /* Tuples still fit in memory */ + TSS_WRITEFILE, /* Writing to temp file */ + TSS_READFILE /* Reading from temp file */ +} TupStoreStatus; + +/* + * State for a single read pointer. If we are in state INMEM then all the + * read pointers' "current" fields denote the read positions. In state + * WRITEFILE, the file/offset fields denote the read positions. In state + * READFILE, inactive read pointers have valid file/offset, but the active + * read pointer implicitly has position equal to the temp file's seek position. + * + * Special case: if eof_reached is true, then the pointer's read position is + * implicitly equal to the write position, and current/file/offset aren't + * maintained. This way we need not update all the read pointers each time + * we write. + */ +typedef struct +{ + int eflags; /* capability flags */ + bool eof_reached; /* read has reached EOF */ + int current; /* next array index to read */ + int file; /* temp file# */ + off_t offset; /* byte offset in file */ +} TSReadPointer; + +/* + * Private state of a Tuplestore operation. + */ +struct Tuplestorestate +{ + TupStoreStatus status; /* enumerated value as shown above */ + int eflags; /* capability flags (OR of pointers' flags) */ + bool backward; /* store extra length words in file? */ + bool interXact; /* keep open through transactions? */ + bool truncated; /* tuplestore_trim has removed tuples? */ + int64 availMem; /* remaining memory available, in bytes */ + int64 allowedMem; /* total memory allowed, in bytes */ + int64 tuples; /* number of tuples added */ + BufFile *myfile; /* underlying file, or NULL if none */ + MemoryContext context; /* memory context for holding tuples */ + ResourceOwner resowner; /* resowner for holding temp files */ + + /* + * These function pointers decouple the routines that must know what kind + * of tuple we are handling from the routines that don't need to know it. + * They are set up by the tuplestore_begin_xxx routines. + * + * (Although tuplestore.c currently only supports heap tuples, I've copied + * this part of tuplesort.c so that extension to other kinds of objects + * will be easy if it's ever needed.) + * + * Function to copy a supplied input tuple into palloc'd space. (NB: we + * assume that a single pfree() is enough to release the tuple later, so + * the representation must be "flat" in one palloc chunk.) state->availMem + * must be decreased by the amount of space used. + */ + void *(*copytup) (Tuplestorestate *state, void *tup); + + /* + * Function to write a stored tuple onto tape. The representation of the + * tuple on tape need not be the same as it is in memory; requirements on + * the tape representation are given below. After writing the tuple, + * pfree() it, and increase state->availMem by the amount of memory space + * thereby released. + */ + void (*writetup) (Tuplestorestate *state, void *tup); + + /* + * Function to read a stored tuple from tape back into memory. 'len' is + * the already-read length of the stored tuple. Create and return a + * palloc'd copy, and decrease state->availMem by the amount of memory + * space consumed. + */ + void *(*readtup) (Tuplestorestate *state, unsigned int len); + + /* + * This array holds pointers to tuples in memory if we are in state INMEM. + * In states WRITEFILE and READFILE it's not used. + * + * When memtupdeleted > 0, the first memtupdeleted pointers are already + * released due to a tuplestore_trim() operation, but we haven't expended + * the effort to slide the remaining pointers down. These unused pointers + * are set to NULL to catch any invalid accesses. Note that memtupcount + * includes the deleted pointers. + */ + void **memtuples; /* array of pointers to palloc'd tuples */ + int memtupdeleted; /* the first N slots are currently unused */ + int memtupcount; /* number of tuples currently present */ + int memtupsize; /* allocated length of memtuples array */ + bool growmemtuples; /* memtuples' growth still underway? */ + + /* + * These variables are used to keep track of the current positions. + * + * In state WRITEFILE, the current file seek position is the write point; + * in state READFILE, the write position is remembered in writepos_xxx. + * (The write position is the same as EOF, but since BufFileSeek doesn't + * currently implement SEEK_END, we have to remember it explicitly.) + */ + TSReadPointer *readptrs; /* array of read pointers */ + int activeptr; /* index of the active read pointer */ + int readptrcount; /* number of pointers currently valid */ + int readptrsize; /* allocated length of readptrs array */ + + int writepos_file; /* file# (valid if READFILE state) */ + off_t writepos_offset; /* offset (valid if READFILE state) */ +}; + +#define COPYTUP(state,tup) ((*(state)->copytup) (state, tup)) +#define WRITETUP(state,tup) ((*(state)->writetup) (state, tup)) +#define READTUP(state,len) ((*(state)->readtup) (state, len)) +#define LACKMEM(state) ((state)->availMem < 0) +#define USEMEM(state,amt) ((state)->availMem -= (amt)) +#define FREEMEM(state,amt) ((state)->availMem += (amt)) + +/*-------------------- + * + * NOTES about on-tape representation of tuples: + * + * We require the first "unsigned int" of a stored tuple to be the total size + * on-tape of the tuple, including itself (so it is never zero). + * The remainder of the stored tuple + * may or may not match the in-memory representation of the tuple --- + * any conversion needed is the job of the writetup and readtup routines. + * + * If state->backward is true, then the stored representation of + * the tuple must be followed by another "unsigned int" that is a copy of the + * length --- so the total tape space used is actually sizeof(unsigned int) + * more than the stored length value. This allows read-backwards. When + * state->backward is not set, the write/read routines may omit the extra + * length word. + * + * writetup is expected to write both length words as well as the tuple + * data. When readtup is called, the tape is positioned just after the + * front length word; readtup must read the tuple data and advance past + * the back length word (if present). + * + * The write/read routines can make use of the tuple description data + * stored in the Tuplestorestate record, if needed. They are also expected + * to adjust state->availMem by the amount of memory space (not tape space!) + * released or consumed. There is no error return from either writetup + * or readtup; they should ereport() on failure. + * + * + * NOTES about memory consumption calculations: + * + * We count space allocated for tuples against the maxKBytes limit, + * plus the space used by the variable-size array memtuples. + * Fixed-size space (primarily the BufFile I/O buffer) is not counted. + * We don't worry about the size of the read pointer array, either. + * + * Note that we count actual space used (as shown by GetMemoryChunkSpace) + * rather than the originally-requested size. This is important since + * palloc can add substantial overhead. It's not a complete answer since + * we won't count any wasted space in palloc allocation blocks, but it's + * a lot better than what we were doing before 7.3. + * + *-------------------- + */ + + +static Tuplestorestate *tuplestore_begin_common(int eflags, + bool interXact, + int maxKBytes); +static void tuplestore_puttuple_common(Tuplestorestate *state, void *tuple); +static void dumptuples(Tuplestorestate *state); +static unsigned int getlen(Tuplestorestate *state, bool eofOK); +static void *copytup_heap(Tuplestorestate *state, void *tup); +static void writetup_heap(Tuplestorestate *state, void *tup); +static void *readtup_heap(Tuplestorestate *state, unsigned int len); + + +/* + * tuplestore_begin_xxx + * + * Initialize for a tuple store operation. + */ +static Tuplestorestate * +tuplestore_begin_common(int eflags, bool interXact, int maxKBytes) +{ + Tuplestorestate *state; + + state = (Tuplestorestate *) palloc0(sizeof(Tuplestorestate)); + + state->status = TSS_INMEM; + state->eflags = eflags; + state->interXact = interXact; + state->truncated = false; + state->allowedMem = maxKBytes * 1024L; + state->availMem = state->allowedMem; + state->myfile = NULL; + state->context = CurrentMemoryContext; + state->resowner = CurrentResourceOwner; + + state->memtupdeleted = 0; + state->memtupcount = 0; + state->tuples = 0; + + /* + * Initial size of array must be more than ALLOCSET_SEPARATE_THRESHOLD; + * see comments in grow_memtuples(). + */ + state->memtupsize = Max(16384 / sizeof(void *), + ALLOCSET_SEPARATE_THRESHOLD / sizeof(void *) + 1); + + state->growmemtuples = true; + state->memtuples = (void **) palloc(state->memtupsize * sizeof(void *)); + + USEMEM(state, GetMemoryChunkSpace(state->memtuples)); + + state->activeptr = 0; + state->readptrcount = 1; + state->readptrsize = 8; /* arbitrary */ + state->readptrs = (TSReadPointer *) + palloc(state->readptrsize * sizeof(TSReadPointer)); + + state->readptrs[0].eflags = eflags; + state->readptrs[0].eof_reached = false; + state->readptrs[0].current = 0; + + return state; +} + +/* + * tuplestore_begin_heap + * + * Create a new tuplestore; other types of tuple stores (other than + * "heap" tuple stores, for heap tuples) are possible, but not presently + * implemented. + * + * randomAccess: if true, both forward and backward accesses to the + * tuple store are allowed. + * + * interXact: if true, the files used for on-disk storage persist beyond the + * end of the current transaction. NOTE: It's the caller's responsibility to + * create such a tuplestore in a memory context and resource owner that will + * also survive transaction boundaries, and to ensure the tuplestore is closed + * when it's no longer wanted. + * + * maxKBytes: how much data to store in memory (any data beyond this + * amount is paged to disk). When in doubt, use work_mem. + */ +Tuplestorestate * +tuplestore_begin_heap(bool randomAccess, bool interXact, int maxKBytes) +{ + Tuplestorestate *state; + int eflags; + + /* + * This interpretation of the meaning of randomAccess is compatible with + * the pre-8.3 behavior of tuplestores. + */ + eflags = randomAccess ? + (EXEC_FLAG_BACKWARD | EXEC_FLAG_REWIND) : + (EXEC_FLAG_REWIND); + + state = tuplestore_begin_common(eflags, interXact, maxKBytes); + + state->copytup = copytup_heap; + state->writetup = writetup_heap; + state->readtup = readtup_heap; + + return state; +} + +/* + * tuplestore_set_eflags + * + * Set the capability flags for read pointer 0 at a finer grain than is + * allowed by tuplestore_begin_xxx. This must be called before inserting + * any data into the tuplestore. + * + * eflags is a bitmask following the meanings used for executor node + * startup flags (see executor.h). tuplestore pays attention to these bits: + * EXEC_FLAG_REWIND need rewind to start + * EXEC_FLAG_BACKWARD need backward fetch + * If tuplestore_set_eflags is not called, REWIND is allowed, and BACKWARD + * is set per "randomAccess" in the tuplestore_begin_xxx call. + * + * NOTE: setting BACKWARD without REWIND means the pointer can read backwards, + * but not further than the truncation point (the furthest-back read pointer + * position at the time of the last tuplestore_trim call). + */ +void +tuplestore_set_eflags(Tuplestorestate *state, int eflags) +{ + int i; + + if (state->status != TSS_INMEM || state->memtupcount != 0) + elog(ERROR, "too late to call tuplestore_set_eflags"); + + state->readptrs[0].eflags = eflags; + for (i = 1; i < state->readptrcount; i++) + eflags |= state->readptrs[i].eflags; + state->eflags = eflags; +} + +/* + * tuplestore_alloc_read_pointer - allocate another read pointer. + * + * Returns the pointer's index. + * + * The new pointer initially copies the position of read pointer 0. + * It can have its own eflags, but if any data has been inserted into + * the tuplestore, these eflags must not represent an increase in + * requirements. + */ +int +tuplestore_alloc_read_pointer(Tuplestorestate *state, int eflags) +{ + /* Check for possible increase of requirements */ + if (state->status != TSS_INMEM || state->memtupcount != 0) + { + if ((state->eflags | eflags) != state->eflags) + elog(ERROR, "too late to require new tuplestore eflags"); + } + + /* Make room for another read pointer if needed */ + if (state->readptrcount >= state->readptrsize) + { + int newcnt = state->readptrsize * 2; + + state->readptrs = (TSReadPointer *) + repalloc(state->readptrs, newcnt * sizeof(TSReadPointer)); + state->readptrsize = newcnt; + } + + /* And set it up */ + state->readptrs[state->readptrcount] = state->readptrs[0]; + state->readptrs[state->readptrcount].eflags = eflags; + + state->eflags |= eflags; + + return state->readptrcount++; +} + +/* + * tuplestore_clear + * + * Delete all the contents of a tuplestore, and reset its read pointers + * to the start. + */ +void +tuplestore_clear(Tuplestorestate *state) +{ + int i; + TSReadPointer *readptr; + + if (state->myfile) + BufFileClose(state->myfile); + state->myfile = NULL; + if (state->memtuples) + { + for (i = state->memtupdeleted; i < state->memtupcount; i++) + { + FREEMEM(state, GetMemoryChunkSpace(state->memtuples[i])); + pfree(state->memtuples[i]); + } + } + state->status = TSS_INMEM; + state->truncated = false; + state->memtupdeleted = 0; + state->memtupcount = 0; + state->tuples = 0; + readptr = state->readptrs; + for (i = 0; i < state->readptrcount; readptr++, i++) + { + readptr->eof_reached = false; + readptr->current = 0; + } +} + +/* + * tuplestore_end + * + * Release resources and clean up. + */ +void +tuplestore_end(Tuplestorestate *state) +{ + int i; + + if (state->myfile) + BufFileClose(state->myfile); + if (state->memtuples) + { + for (i = state->memtupdeleted; i < state->memtupcount; i++) + pfree(state->memtuples[i]); + pfree(state->memtuples); + } + pfree(state->readptrs); + pfree(state); +} + +/* + * tuplestore_select_read_pointer - make the specified read pointer active + */ +void +tuplestore_select_read_pointer(Tuplestorestate *state, int ptr) +{ + TSReadPointer *readptr; + TSReadPointer *oldptr; + + Assert(ptr >= 0 && ptr < state->readptrcount); + + /* No work if already active */ + if (ptr == state->activeptr) + return; + + readptr = &state->readptrs[ptr]; + oldptr = &state->readptrs[state->activeptr]; + + switch (state->status) + { + case TSS_INMEM: + case TSS_WRITEFILE: + /* no work */ + break; + case TSS_READFILE: + + /* + * First, save the current read position in the pointer about to + * become inactive. + */ + if (!oldptr->eof_reached) + BufFileTell(state->myfile, + &oldptr->file, + &oldptr->offset); + + /* + * We have to make the temp file's seek position equal to the + * logical position of the new read pointer. In eof_reached + * state, that's the EOF, which we have available from the saved + * write position. + */ + if (readptr->eof_reached) + { + if (BufFileSeek(state->myfile, + state->writepos_file, + state->writepos_offset, + SEEK_SET) != 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not seek in tuplestore temporary file"))); + } + else + { + if (BufFileSeek(state->myfile, + readptr->file, + readptr->offset, + SEEK_SET) != 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not seek in tuplestore temporary file"))); + } + break; + default: + elog(ERROR, "invalid tuplestore state"); + break; + } + + state->activeptr = ptr; +} + +/* + * tuplestore_tuple_count + * + * Returns the number of tuples added since creation or the last + * tuplestore_clear(). + */ +int64 +tuplestore_tuple_count(Tuplestorestate *state) +{ + return state->tuples; +} + +/* + * tuplestore_ateof + * + * Returns the active read pointer's eof_reached state. + */ +bool +tuplestore_ateof(Tuplestorestate *state) +{ + return state->readptrs[state->activeptr].eof_reached; +} + +/* + * Grow the memtuples[] array, if possible within our memory constraint. We + * must not exceed INT_MAX tuples in memory or the caller-provided memory + * limit. Return true if we were able to enlarge the array, false if not. + * + * Normally, at each increment we double the size of the array. When doing + * that would exceed a limit, we attempt one last, smaller increase (and then + * clear the growmemtuples flag so we don't try any more). That allows us to + * use memory as fully as permitted; sticking to the pure doubling rule could + * result in almost half going unused. Because availMem moves around with + * tuple addition/removal, we need some rule to prevent making repeated small + * increases in memtupsize, which would just be useless thrashing. The + * growmemtuples flag accomplishes that and also prevents useless + * recalculations in this function. + */ +static bool +grow_memtuples(Tuplestorestate *state) +{ + int newmemtupsize; + int memtupsize = state->memtupsize; + int64 memNowUsed = state->allowedMem - state->availMem; + + /* Forget it if we've already maxed out memtuples, per comment above */ + if (!state->growmemtuples) + return false; + + /* Select new value of memtupsize */ + if (memNowUsed <= state->availMem) + { + /* + * We've used no more than half of allowedMem; double our usage, + * clamping at INT_MAX tuples. + */ + if (memtupsize < INT_MAX / 2) + newmemtupsize = memtupsize * 2; + else + { + newmemtupsize = INT_MAX; + state->growmemtuples = false; + } + } + else + { + /* + * This will be the last increment of memtupsize. Abandon doubling + * strategy and instead increase as much as we safely can. + * + * To stay within allowedMem, we can't increase memtupsize by more + * than availMem / sizeof(void *) elements. In practice, we want to + * increase it by considerably less, because we need to leave some + * space for the tuples to which the new array slots will refer. We + * assume the new tuples will be about the same size as the tuples + * we've already seen, and thus we can extrapolate from the space + * consumption so far to estimate an appropriate new size for the + * memtuples array. The optimal value might be higher or lower than + * this estimate, but it's hard to know that in advance. We again + * clamp at INT_MAX tuples. + * + * This calculation is safe against enlarging the array so much that + * LACKMEM becomes true, because the memory currently used includes + * the present array; thus, there would be enough allowedMem for the + * new array elements even if no other memory were currently used. + * + * We do the arithmetic in float8, because otherwise the product of + * memtupsize and allowedMem could overflow. Any inaccuracy in the + * result should be insignificant; but even if we computed a + * completely insane result, the checks below will prevent anything + * really bad from happening. + */ + double grow_ratio; + + grow_ratio = (double) state->allowedMem / (double) memNowUsed; + if (memtupsize * grow_ratio < INT_MAX) + newmemtupsize = (int) (memtupsize * grow_ratio); + else + newmemtupsize = INT_MAX; + + /* We won't make any further enlargement attempts */ + state->growmemtuples = false; + } + + /* Must enlarge array by at least one element, else report failure */ + if (newmemtupsize <= memtupsize) + goto noalloc; + + /* + * On a 32-bit machine, allowedMem could exceed MaxAllocHugeSize. Clamp + * to ensure our request won't be rejected. Note that we can easily + * exhaust address space before facing this outcome. (This is presently + * impossible due to guc.c's MAX_KILOBYTES limitation on work_mem, but + * don't rely on that at this distance.) + */ + if ((Size) newmemtupsize >= MaxAllocHugeSize / sizeof(void *)) + { + newmemtupsize = (int) (MaxAllocHugeSize / sizeof(void *)); + state->growmemtuples = false; /* can't grow any more */ + } + + /* + * We need to be sure that we do not cause LACKMEM to become true, else + * the space management algorithm will go nuts. The code above should + * never generate a dangerous request, but to be safe, check explicitly + * that the array growth fits within availMem. (We could still cause + * LACKMEM if the memory chunk overhead associated with the memtuples + * array were to increase. That shouldn't happen because we chose the + * initial array size large enough to ensure that palloc will be treating + * both old and new arrays as separate chunks. But we'll check LACKMEM + * explicitly below just in case.) + */ + if (state->availMem < (int64) ((newmemtupsize - memtupsize) * sizeof(void *))) + goto noalloc; + + /* OK, do it */ + FREEMEM(state, GetMemoryChunkSpace(state->memtuples)); + state->memtupsize = newmemtupsize; + state->memtuples = (void **) + repalloc_huge(state->memtuples, + state->memtupsize * sizeof(void *)); + USEMEM(state, GetMemoryChunkSpace(state->memtuples)); + if (LACKMEM(state)) + elog(ERROR, "unexpected out-of-memory situation in tuplestore"); + return true; + +noalloc: + /* If for any reason we didn't realloc, shut off future attempts */ + state->growmemtuples = false; + return false; +} + +/* + * Accept one tuple and append it to the tuplestore. + * + * Note that the input tuple is always copied; the caller need not save it. + * + * If the active read pointer is currently "at EOF", it remains so (the read + * pointer implicitly advances along with the write pointer); otherwise the + * read pointer is unchanged. Non-active read pointers do not move, which + * means they are certain to not be "at EOF" immediately after puttuple. + * This curious-seeming behavior is for the convenience of nodeMaterial.c and + * nodeCtescan.c, which would otherwise need to do extra pointer repositioning + * steps. + * + * tuplestore_puttupleslot() is a convenience routine to collect data from + * a TupleTableSlot without an extra copy operation. + */ +void +tuplestore_puttupleslot(Tuplestorestate *state, + TupleTableSlot *slot) +{ + MinimalTuple tuple; + MemoryContext oldcxt = MemoryContextSwitchTo(state->context); + + /* + * Form a MinimalTuple in working memory + */ + tuple = ExecCopySlotMinimalTuple(slot); + USEMEM(state, GetMemoryChunkSpace(tuple)); + + tuplestore_puttuple_common(state, (void *) tuple); + + MemoryContextSwitchTo(oldcxt); +} + +/* + * "Standard" case to copy from a HeapTuple. This is actually now somewhat + * deprecated, but not worth getting rid of in view of the number of callers. + */ +void +tuplestore_puttuple(Tuplestorestate *state, HeapTuple tuple) +{ + MemoryContext oldcxt = MemoryContextSwitchTo(state->context); + + /* + * Copy the tuple. (Must do this even in WRITEFILE case. Note that + * COPYTUP includes USEMEM, so we needn't do that here.) + */ + tuple = COPYTUP(state, tuple); + + tuplestore_puttuple_common(state, (void *) tuple); + + MemoryContextSwitchTo(oldcxt); +} + +/* + * Similar to tuplestore_puttuple(), but work from values + nulls arrays. + * This avoids an extra tuple-construction operation. + */ +void +tuplestore_putvalues(Tuplestorestate *state, TupleDesc tdesc, + Datum *values, bool *isnull) +{ + MinimalTuple tuple; + MemoryContext oldcxt = MemoryContextSwitchTo(state->context); + + tuple = heap_form_minimal_tuple(tdesc, values, isnull); + USEMEM(state, GetMemoryChunkSpace(tuple)); + + tuplestore_puttuple_common(state, (void *) tuple); + + MemoryContextSwitchTo(oldcxt); +} + +static void +tuplestore_puttuple_common(Tuplestorestate *state, void *tuple) +{ + TSReadPointer *readptr; + int i; + ResourceOwner oldowner; + + state->tuples++; + + switch (state->status) + { + case TSS_INMEM: + + /* + * Update read pointers as needed; see API spec above. + */ + readptr = state->readptrs; + for (i = 0; i < state->readptrcount; readptr++, i++) + { + if (readptr->eof_reached && i != state->activeptr) + { + readptr->eof_reached = false; + readptr->current = state->memtupcount; + } + } + + /* + * Grow the array as needed. Note that we try to grow the array + * when there is still one free slot remaining --- if we fail, + * there'll still be room to store the incoming tuple, and then + * we'll switch to tape-based operation. + */ + if (state->memtupcount >= state->memtupsize - 1) + { + (void) grow_memtuples(state); + Assert(state->memtupcount < state->memtupsize); + } + + /* Stash the tuple in the in-memory array */ + state->memtuples[state->memtupcount++] = tuple; + + /* + * Done if we still fit in available memory and have array slots. + */ + if (state->memtupcount < state->memtupsize && !LACKMEM(state)) + return; + + /* + * Nope; time to switch to tape-based operation. Make sure that + * the temp file(s) are created in suitable temp tablespaces. + */ + PrepareTempTablespaces(); + + /* associate the file with the store's resource owner */ + oldowner = CurrentResourceOwner; + CurrentResourceOwner = state->resowner; + + state->myfile = BufFileCreateTemp(state->interXact); + + CurrentResourceOwner = oldowner; + + /* + * Freeze the decision about whether trailing length words will be + * used. We can't change this choice once data is on tape, even + * though callers might drop the requirement. + */ + state->backward = (state->eflags & EXEC_FLAG_BACKWARD) != 0; + state->status = TSS_WRITEFILE; + dumptuples(state); + break; + case TSS_WRITEFILE: + + /* + * Update read pointers as needed; see API spec above. Note: + * BufFileTell is quite cheap, so not worth trying to avoid + * multiple calls. + */ + readptr = state->readptrs; + for (i = 0; i < state->readptrcount; readptr++, i++) + { + if (readptr->eof_reached && i != state->activeptr) + { + readptr->eof_reached = false; + BufFileTell(state->myfile, + &readptr->file, + &readptr->offset); + } + } + + WRITETUP(state, tuple); + break; + case TSS_READFILE: + + /* + * Switch from reading to writing. + */ + if (!state->readptrs[state->activeptr].eof_reached) + BufFileTell(state->myfile, + &state->readptrs[state->activeptr].file, + &state->readptrs[state->activeptr].offset); + if (BufFileSeek(state->myfile, + state->writepos_file, state->writepos_offset, + SEEK_SET) != 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not seek in tuplestore temporary file"))); + state->status = TSS_WRITEFILE; + + /* + * Update read pointers as needed; see API spec above. + */ + readptr = state->readptrs; + for (i = 0; i < state->readptrcount; readptr++, i++) + { + if (readptr->eof_reached && i != state->activeptr) + { + readptr->eof_reached = false; + readptr->file = state->writepos_file; + readptr->offset = state->writepos_offset; + } + } + + WRITETUP(state, tuple); + break; + default: + elog(ERROR, "invalid tuplestore state"); + break; + } +} + +/* + * Fetch the next tuple in either forward or back direction. + * Returns NULL if no more tuples. If should_free is set, the + * caller must pfree the returned tuple when done with it. + * + * Backward scan is only allowed if randomAccess was set true or + * EXEC_FLAG_BACKWARD was specified to tuplestore_set_eflags(). + */ +static void * +tuplestore_gettuple(Tuplestorestate *state, bool forward, + bool *should_free) +{ + TSReadPointer *readptr = &state->readptrs[state->activeptr]; + unsigned int tuplen; + void *tup; + + Assert(forward || (readptr->eflags & EXEC_FLAG_BACKWARD)); + + switch (state->status) + { + case TSS_INMEM: + *should_free = false; + if (forward) + { + if (readptr->eof_reached) + return NULL; + if (readptr->current < state->memtupcount) + { + /* We have another tuple, so return it */ + return state->memtuples[readptr->current++]; + } + readptr->eof_reached = true; + return NULL; + } + else + { + /* + * if all tuples are fetched already then we return last + * tuple, else tuple before last returned. + */ + if (readptr->eof_reached) + { + readptr->current = state->memtupcount; + readptr->eof_reached = false; + } + else + { + if (readptr->current <= state->memtupdeleted) + { + Assert(!state->truncated); + return NULL; + } + readptr->current--; /* last returned tuple */ + } + if (readptr->current <= state->memtupdeleted) + { + Assert(!state->truncated); + return NULL; + } + return state->memtuples[readptr->current - 1]; + } + break; + + case TSS_WRITEFILE: + /* Skip state change if we'll just return NULL */ + if (readptr->eof_reached && forward) + return NULL; + + /* + * Switch from writing to reading. + */ + BufFileTell(state->myfile, + &state->writepos_file, &state->writepos_offset); + if (!readptr->eof_reached) + if (BufFileSeek(state->myfile, + readptr->file, readptr->offset, + SEEK_SET) != 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not seek in tuplestore temporary file"))); + state->status = TSS_READFILE; + /* FALLTHROUGH */ + + case TSS_READFILE: + *should_free = true; + if (forward) + { + if ((tuplen = getlen(state, true)) != 0) + { + tup = READTUP(state, tuplen); + return tup; + } + else + { + readptr->eof_reached = true; + return NULL; + } + } + + /* + * Backward. + * + * if all tuples are fetched already then we return last tuple, + * else tuple before last returned. + * + * Back up to fetch previously-returned tuple's ending length + * word. If seek fails, assume we are at start of file. + */ + if (BufFileSeek(state->myfile, 0, -(long) sizeof(unsigned int), + SEEK_CUR) != 0) + { + /* even a failed backwards fetch gets you out of eof state */ + readptr->eof_reached = false; + Assert(!state->truncated); + return NULL; + } + tuplen = getlen(state, false); + + if (readptr->eof_reached) + { + readptr->eof_reached = false; + /* We will return the tuple returned before returning NULL */ + } + else + { + /* + * Back up to get ending length word of tuple before it. + */ + if (BufFileSeek(state->myfile, 0, + -(long) (tuplen + 2 * sizeof(unsigned int)), + SEEK_CUR) != 0) + { + /* + * If that fails, presumably the prev tuple is the first + * in the file. Back up so that it becomes next to read + * in forward direction (not obviously right, but that is + * what in-memory case does). + */ + if (BufFileSeek(state->myfile, 0, + -(long) (tuplen + sizeof(unsigned int)), + SEEK_CUR) != 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not seek in tuplestore temporary file"))); + Assert(!state->truncated); + return NULL; + } + tuplen = getlen(state, false); + } + + /* + * Now we have the length of the prior tuple, back up and read it. + * Note: READTUP expects we are positioned after the initial + * length word of the tuple, so back up to that point. + */ + if (BufFileSeek(state->myfile, 0, + -(long) tuplen, + SEEK_CUR) != 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not seek in tuplestore temporary file"))); + tup = READTUP(state, tuplen); + return tup; + + default: + elog(ERROR, "invalid tuplestore state"); + return NULL; /* keep compiler quiet */ + } +} + +/* + * tuplestore_gettupleslot - exported function to fetch a MinimalTuple + * + * If successful, put tuple in slot and return true; else, clear the slot + * and return false. + * + * If copy is true, the slot receives a copied tuple (allocated in current + * memory context) that will stay valid regardless of future manipulations of + * the tuplestore's state. If copy is false, the slot may just receive a + * pointer to a tuple held within the tuplestore. The latter is more + * efficient but the slot contents may be corrupted if additional writes to + * the tuplestore occur. (If using tuplestore_trim, see comments therein.) + */ +bool +tuplestore_gettupleslot(Tuplestorestate *state, bool forward, + bool copy, TupleTableSlot *slot) +{ + MinimalTuple tuple; + bool should_free; + + tuple = (MinimalTuple) tuplestore_gettuple(state, forward, &should_free); + + if (tuple) + { + if (copy && !should_free) + { + tuple = heap_copy_minimal_tuple(tuple); + should_free = true; + } + ExecStoreMinimalTuple(tuple, slot, should_free); + return true; + } + else + { + ExecClearTuple(slot); + return false; + } +} + +/* + * tuplestore_advance - exported function to adjust position without fetching + * + * We could optimize this case to avoid palloc/pfree overhead, but for the + * moment it doesn't seem worthwhile. + */ +bool +tuplestore_advance(Tuplestorestate *state, bool forward) +{ + void *tuple; + bool should_free; + + tuple = tuplestore_gettuple(state, forward, &should_free); + + if (tuple) + { + if (should_free) + pfree(tuple); + return true; + } + else + { + return false; + } +} + +/* + * Advance over N tuples in either forward or back direction, + * without returning any data. N<=0 is a no-op. + * Returns true if successful, false if ran out of tuples. + */ +bool +tuplestore_skiptuples(Tuplestorestate *state, int64 ntuples, bool forward) +{ + TSReadPointer *readptr = &state->readptrs[state->activeptr]; + + Assert(forward || (readptr->eflags & EXEC_FLAG_BACKWARD)); + + if (ntuples <= 0) + return true; + + switch (state->status) + { + case TSS_INMEM: + if (forward) + { + if (readptr->eof_reached) + return false; + if (state->memtupcount - readptr->current >= ntuples) + { + readptr->current += ntuples; + return true; + } + readptr->current = state->memtupcount; + readptr->eof_reached = true; + return false; + } + else + { + if (readptr->eof_reached) + { + readptr->current = state->memtupcount; + readptr->eof_reached = false; + ntuples--; + } + if (readptr->current - state->memtupdeleted > ntuples) + { + readptr->current -= ntuples; + return true; + } + Assert(!state->truncated); + readptr->current = state->memtupdeleted; + return false; + } + break; + + default: + /* We don't currently try hard to optimize other cases */ + while (ntuples-- > 0) + { + void *tuple; + bool should_free; + + tuple = tuplestore_gettuple(state, forward, &should_free); + + if (tuple == NULL) + return false; + if (should_free) + pfree(tuple); + CHECK_FOR_INTERRUPTS(); + } + return true; + } +} + +/* + * dumptuples - remove tuples from memory and write to tape + * + * As a side effect, we must convert each read pointer's position from + * "current" to file/offset format. But eof_reached pointers don't + * need to change state. + */ +static void +dumptuples(Tuplestorestate *state) +{ + int i; + + for (i = state->memtupdeleted;; i++) + { + TSReadPointer *readptr = state->readptrs; + int j; + + for (j = 0; j < state->readptrcount; readptr++, j++) + { + if (i == readptr->current && !readptr->eof_reached) + BufFileTell(state->myfile, + &readptr->file, &readptr->offset); + } + if (i >= state->memtupcount) + break; + WRITETUP(state, state->memtuples[i]); + } + state->memtupdeleted = 0; + state->memtupcount = 0; +} + +/* + * tuplestore_rescan - rewind the active read pointer to start + */ +void +tuplestore_rescan(Tuplestorestate *state) +{ + TSReadPointer *readptr = &state->readptrs[state->activeptr]; + + Assert(readptr->eflags & EXEC_FLAG_REWIND); + Assert(!state->truncated); + + switch (state->status) + { + case TSS_INMEM: + readptr->eof_reached = false; + readptr->current = 0; + break; + case TSS_WRITEFILE: + readptr->eof_reached = false; + readptr->file = 0; + readptr->offset = 0; + break; + case TSS_READFILE: + readptr->eof_reached = false; + if (BufFileSeek(state->myfile, 0, 0, SEEK_SET) != 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not seek in tuplestore temporary file"))); + break; + default: + elog(ERROR, "invalid tuplestore state"); + break; + } +} + +/* + * tuplestore_copy_read_pointer - copy a read pointer's state to another + */ +void +tuplestore_copy_read_pointer(Tuplestorestate *state, + int srcptr, int destptr) +{ + TSReadPointer *sptr = &state->readptrs[srcptr]; + TSReadPointer *dptr = &state->readptrs[destptr]; + + Assert(srcptr >= 0 && srcptr < state->readptrcount); + Assert(destptr >= 0 && destptr < state->readptrcount); + + /* Assigning to self is a no-op */ + if (srcptr == destptr) + return; + + if (dptr->eflags != sptr->eflags) + { + /* Possible change of overall eflags, so copy and then recompute */ + int eflags; + int i; + + *dptr = *sptr; + eflags = state->readptrs[0].eflags; + for (i = 1; i < state->readptrcount; i++) + eflags |= state->readptrs[i].eflags; + state->eflags = eflags; + } + else + *dptr = *sptr; + + switch (state->status) + { + case TSS_INMEM: + case TSS_WRITEFILE: + /* no work */ + break; + case TSS_READFILE: + + /* + * This case is a bit tricky since the active read pointer's + * position corresponds to the seek point, not what is in its + * variables. Assigning to the active requires a seek, and + * assigning from the active requires a tell, except when + * eof_reached. + */ + if (destptr == state->activeptr) + { + if (dptr->eof_reached) + { + if (BufFileSeek(state->myfile, + state->writepos_file, + state->writepos_offset, + SEEK_SET) != 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not seek in tuplestore temporary file"))); + } + else + { + if (BufFileSeek(state->myfile, + dptr->file, dptr->offset, + SEEK_SET) != 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not seek in tuplestore temporary file"))); + } + } + else if (srcptr == state->activeptr) + { + if (!dptr->eof_reached) + BufFileTell(state->myfile, + &dptr->file, + &dptr->offset); + } + break; + default: + elog(ERROR, "invalid tuplestore state"); + break; + } +} + +/* + * tuplestore_trim - remove all no-longer-needed tuples + * + * Calling this function authorizes the tuplestore to delete all tuples + * before the oldest read pointer, if no read pointer is marked as requiring + * REWIND capability. + * + * Note: this is obviously safe if no pointer has BACKWARD capability either. + * If a pointer is marked as BACKWARD but not REWIND capable, it means that + * the pointer can be moved backward but not before the oldest other read + * pointer. + */ +void +tuplestore_trim(Tuplestorestate *state) +{ + int oldest; + int nremove; + int i; + + /* + * Truncation is disallowed if any read pointer requires rewind + * capability. + */ + if (state->eflags & EXEC_FLAG_REWIND) + return; + + /* + * We don't bother trimming temp files since it usually would mean more + * work than just letting them sit in kernel buffers until they age out. + */ + if (state->status != TSS_INMEM) + return; + + /* Find the oldest read pointer */ + oldest = state->memtupcount; + for (i = 0; i < state->readptrcount; i++) + { + if (!state->readptrs[i].eof_reached) + oldest = Min(oldest, state->readptrs[i].current); + } + + /* + * Note: you might think we could remove all the tuples before the oldest + * "current", since that one is the next to be returned. However, since + * tuplestore_gettuple returns a direct pointer to our internal copy of + * the tuple, it's likely that the caller has still got the tuple just + * before "current" referenced in a slot. So we keep one extra tuple + * before the oldest "current". (Strictly speaking, we could require such + * callers to use the "copy" flag to tuplestore_gettupleslot, but for + * efficiency we allow this one case to not use "copy".) + */ + nremove = oldest - 1; + if (nremove <= 0) + return; /* nothing to do */ + + Assert(nremove >= state->memtupdeleted); + Assert(nremove <= state->memtupcount); + + /* Release no-longer-needed tuples */ + for (i = state->memtupdeleted; i < nremove; i++) + { + FREEMEM(state, GetMemoryChunkSpace(state->memtuples[i])); + pfree(state->memtuples[i]); + state->memtuples[i] = NULL; + } + state->memtupdeleted = nremove; + + /* mark tuplestore as truncated (used for Assert crosschecks only) */ + state->truncated = true; + + /* + * If nremove is less than 1/8th memtupcount, just stop here, leaving the + * "deleted" slots as NULL. This prevents us from expending O(N^2) time + * repeatedly memmove-ing a large pointer array. The worst case space + * wastage is pretty small, since it's just pointers and not whole tuples. + */ + if (nremove < state->memtupcount / 8) + return; + + /* + * Slide the array down and readjust pointers. + * + * In mergejoin's current usage, it's demonstrable that there will always + * be exactly one non-removed tuple; so optimize that case. + */ + if (nremove + 1 == state->memtupcount) + state->memtuples[0] = state->memtuples[nremove]; + else + memmove(state->memtuples, state->memtuples + nremove, + (state->memtupcount - nremove) * sizeof(void *)); + + state->memtupdeleted = 0; + state->memtupcount -= nremove; + for (i = 0; i < state->readptrcount; i++) + { + if (!state->readptrs[i].eof_reached) + state->readptrs[i].current -= nremove; + } +} + +/* + * tuplestore_in_memory + * + * Returns true if the tuplestore has not spilled to disk. + * + * XXX exposing this is a violation of modularity ... should get rid of it. + */ +bool +tuplestore_in_memory(Tuplestorestate *state) +{ + return (state->status == TSS_INMEM); +} + + +/* + * Tape interface routines + */ + +static unsigned int +getlen(Tuplestorestate *state, bool eofOK) +{ + unsigned int len; + size_t nbytes; + + nbytes = BufFileReadMaybeEOF(state->myfile, &len, sizeof(len), eofOK); + if (nbytes == 0) + return 0; + else + return len; +} + + +/* + * Routines specialized for HeapTuple case + * + * The stored form is actually a MinimalTuple, but for largely historical + * reasons we allow COPYTUP to work from a HeapTuple. + * + * Since MinimalTuple already has length in its first word, we don't need + * to write that separately. + */ + +static void * +copytup_heap(Tuplestorestate *state, void *tup) +{ + MinimalTuple tuple; + + tuple = minimal_tuple_from_heap_tuple((HeapTuple) tup); + USEMEM(state, GetMemoryChunkSpace(tuple)); + return (void *) tuple; +} + +static void +writetup_heap(Tuplestorestate *state, void *tup) +{ + MinimalTuple tuple = (MinimalTuple) tup; + + /* the part of the MinimalTuple we'll write: */ + char *tupbody = (char *) tuple + MINIMAL_TUPLE_DATA_OFFSET; + unsigned int tupbodylen = tuple->t_len - MINIMAL_TUPLE_DATA_OFFSET; + + /* total on-disk footprint: */ + unsigned int tuplen = tupbodylen + sizeof(int); + + BufFileWrite(state->myfile, &tuplen, sizeof(tuplen)); + BufFileWrite(state->myfile, tupbody, tupbodylen); + if (state->backward) /* need trailing length word? */ + BufFileWrite(state->myfile, &tuplen, sizeof(tuplen)); + + FREEMEM(state, GetMemoryChunkSpace(tuple)); + heap_free_minimal_tuple(tuple); +} + +static void * +readtup_heap(Tuplestorestate *state, unsigned int len) +{ + unsigned int tupbodylen = len - sizeof(int); + unsigned int tuplen = tupbodylen + MINIMAL_TUPLE_DATA_OFFSET; + MinimalTuple tuple = (MinimalTuple) palloc(tuplen); + char *tupbody = (char *) tuple + MINIMAL_TUPLE_DATA_OFFSET; + + USEMEM(state, GetMemoryChunkSpace(tuple)); + /* read in the tuple proper */ + tuple->t_len = tuplen; + BufFileReadExact(state->myfile, tupbody, tupbodylen); + if (state->backward) /* need trailing length word? */ + BufFileReadExact(state->myfile, &tuplen, sizeof(tuplen)); + return (void *) tuple; +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/time/combocid.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/time/combocid.c new file mode 100644 index 00000000000..3c61381064d --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/time/combocid.c @@ -0,0 +1,364 @@ +/*------------------------------------------------------------------------- + * + * combocid.c + * Combo command ID support routines + * + * Before version 8.3, HeapTupleHeaderData had separate fields for cmin + * and cmax. To reduce the header size, cmin and cmax are now overlayed + * in the same field in the header. That usually works because you rarely + * insert and delete a tuple in the same transaction, and we don't need + * either field to remain valid after the originating transaction exits. + * To make it work when the inserting transaction does delete the tuple, + * we create a "combo" command ID and store that in the tuple header + * instead of cmin and cmax. The combo command ID can be mapped to the + * real cmin and cmax using a backend-private array, which is managed by + * this module. + * + * To allow reusing existing combo CIDs, we also keep a hash table that + * maps cmin,cmax pairs to combo CIDs. This keeps the data structure size + * reasonable in most cases, since the number of unique pairs used by any + * one transaction is likely to be small. + * + * With a 32-bit combo command id we can represent 2^32 distinct cmin,cmax + * combinations. In the most perverse case where each command deletes a tuple + * generated by every previous command, the number of combo command ids + * required for N commands is N*(N+1)/2. That means that in the worst case, + * that's enough for 92682 commands. In practice, you'll run out of memory + * and/or disk space way before you reach that limit. + * + * The array and hash table are kept in TopTransactionContext, and are + * destroyed at the end of each transaction. + * + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/utils/time/combocid.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/htup_details.h" +#include "access/xact.h" +#include "miscadmin.h" +#include "storage/shmem.h" +#include "utils/combocid.h" +#include "utils/hsearch.h" +#include "utils/memutils.h" + +/* Hash table to lookup combo CIDs by cmin and cmax */ +static __thread HTAB *comboHash = NULL; + +/* Key and entry structures for the hash table */ +typedef struct +{ + CommandId cmin; + CommandId cmax; +} ComboCidKeyData; + +typedef ComboCidKeyData *ComboCidKey; + +typedef struct +{ + ComboCidKeyData key; + CommandId combocid; +} ComboCidEntryData; + +typedef ComboCidEntryData *ComboCidEntry; + +/* Initial size of the hash table */ +#define CCID_HASH_SIZE 100 + + +/* + * An array of cmin,cmax pairs, indexed by combo command id. + * To convert a combo CID to cmin and cmax, you do a simple array lookup. + */ +static __thread ComboCidKey comboCids = NULL; +static __thread int usedComboCids = 0; /* number of elements in comboCids */ +static __thread int sizeComboCids = 0; /* allocated size of array */ + +/* Initial size of the array */ +#define CCID_ARRAY_SIZE 100 + + +/* prototypes for internal functions */ +static CommandId GetComboCommandId(CommandId cmin, CommandId cmax); +static CommandId GetRealCmin(CommandId combocid); +static CommandId GetRealCmax(CommandId combocid); + + +/**** External API ****/ + +/* + * GetCmin and GetCmax assert that they are only called in situations where + * they make sense, that is, can deliver a useful answer. If you have + * reason to examine a tuple's t_cid field from a transaction other than + * the originating one, use HeapTupleHeaderGetRawCommandId() directly. + */ + +CommandId +HeapTupleHeaderGetCmin(HeapTupleHeader tup) +{ + CommandId cid = HeapTupleHeaderGetRawCommandId(tup); + + Assert(!(tup->t_infomask & HEAP_MOVED)); + Assert(TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tup))); + + if (tup->t_infomask & HEAP_COMBOCID) + return GetRealCmin(cid); + else + return cid; +} + +CommandId +HeapTupleHeaderGetCmax(HeapTupleHeader tup) +{ + CommandId cid = HeapTupleHeaderGetRawCommandId(tup); + + Assert(!(tup->t_infomask & HEAP_MOVED)); + + /* + * Because GetUpdateXid() performs memory allocations if xmax is a + * multixact we can't Assert() if we're inside a critical section. This + * weakens the check, but not using GetCmax() inside one would complicate + * things too much. + */ + Assert(CritSectionCount > 0 || + TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetUpdateXid(tup))); + + if (tup->t_infomask & HEAP_COMBOCID) + return GetRealCmax(cid); + else + return cid; +} + +/* + * Given a tuple we are about to delete, determine the correct value to store + * into its t_cid field. + * + * If we don't need a combo CID, *cmax is unchanged and *iscombo is set to + * false. If we do need one, *cmax is replaced by a combo CID and *iscombo + * is set to true. + * + * The reason this is separate from the actual HeapTupleHeaderSetCmax() + * operation is that this could fail due to out-of-memory conditions. Hence + * we need to do this before entering the critical section that actually + * changes the tuple in shared buffers. + */ +void +HeapTupleHeaderAdjustCmax(HeapTupleHeader tup, + CommandId *cmax, + bool *iscombo) +{ + /* + * If we're marking a tuple deleted that was inserted by (any + * subtransaction of) our transaction, we need to use a combo command id. + * Test for HeapTupleHeaderXminCommitted() first, because it's cheaper + * than a TransactionIdIsCurrentTransactionId call. + */ + if (!HeapTupleHeaderXminCommitted(tup) && + TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tup))) + { + CommandId cmin = HeapTupleHeaderGetCmin(tup); + + *cmax = GetComboCommandId(cmin, *cmax); + *iscombo = true; + } + else + { + *iscombo = false; + } +} + +/* + * Combo command ids are only interesting to the inserting and deleting + * transaction, so we can forget about them at the end of transaction. + */ +void +AtEOXact_ComboCid(void) +{ + /* + * Don't bother to pfree. These are allocated in TopTransactionContext, so + * they're going to go away at the end of transaction anyway. + */ + comboHash = NULL; + + comboCids = NULL; + usedComboCids = 0; + sizeComboCids = 0; +} + + +/**** Internal routines ****/ + +/* + * Get a combo command id that maps to cmin and cmax. + * + * We try to reuse old combo command ids when possible. + */ +static CommandId +GetComboCommandId(CommandId cmin, CommandId cmax) +{ + CommandId combocid; + ComboCidKeyData key; + ComboCidEntry entry; + bool found; + + /* + * Create the hash table and array the first time we need to use combo + * cids in the transaction. + */ + if (comboHash == NULL) + { + HASHCTL hash_ctl; + + /* Make array first; existence of hash table asserts array exists */ + comboCids = (ComboCidKeyData *) + MemoryContextAlloc(TopTransactionContext, + sizeof(ComboCidKeyData) * CCID_ARRAY_SIZE); + sizeComboCids = CCID_ARRAY_SIZE; + usedComboCids = 0; + + hash_ctl.keysize = sizeof(ComboCidKeyData); + hash_ctl.entrysize = sizeof(ComboCidEntryData); + hash_ctl.hcxt = TopTransactionContext; + + comboHash = hash_create("Combo CIDs", + CCID_HASH_SIZE, + &hash_ctl, + HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); + } + + /* + * Grow the array if there's not at least one free slot. We must do this + * before possibly entering a new hashtable entry, else failure to + * repalloc would leave a corrupt hashtable entry behind. + */ + if (usedComboCids >= sizeComboCids) + { + int newsize = sizeComboCids * 2; + + comboCids = (ComboCidKeyData *) + repalloc(comboCids, sizeof(ComboCidKeyData) * newsize); + sizeComboCids = newsize; + } + + /* Lookup or create a hash entry with the desired cmin/cmax */ + + /* We assume there is no struct padding in ComboCidKeyData! */ + key.cmin = cmin; + key.cmax = cmax; + entry = (ComboCidEntry) hash_search(comboHash, + &key, + HASH_ENTER, + &found); + + if (found) + { + /* Reuse an existing combo CID */ + return entry->combocid; + } + + /* We have to create a new combo CID; we already made room in the array */ + combocid = usedComboCids; + + comboCids[combocid].cmin = cmin; + comboCids[combocid].cmax = cmax; + usedComboCids++; + + entry->combocid = combocid; + + return combocid; +} + +static CommandId +GetRealCmin(CommandId combocid) +{ + Assert(combocid < usedComboCids); + return comboCids[combocid].cmin; +} + +static CommandId +GetRealCmax(CommandId combocid) +{ + Assert(combocid < usedComboCids); + return comboCids[combocid].cmax; +} + +/* + * Estimate the amount of space required to serialize the current combo CID + * state. + */ +Size +EstimateComboCIDStateSpace(void) +{ + Size size; + + /* Add space required for saving usedComboCids */ + size = sizeof(int); + + /* Add space required for saving ComboCidKeyData */ + size = add_size(size, mul_size(sizeof(ComboCidKeyData), usedComboCids)); + + return size; +} + +/* + * Serialize the combo CID state into the memory, beginning at start_address. + * maxsize should be at least as large as the value returned by + * EstimateComboCIDStateSpace. + */ +void +SerializeComboCIDState(Size maxsize, char *start_address) +{ + char *endptr; + + /* First, we store the number of currently-existing combo CIDs. */ + *(int *) start_address = usedComboCids; + + /* If maxsize is too small, throw an error. */ + endptr = start_address + sizeof(int) + + (sizeof(ComboCidKeyData) * usedComboCids); + if (endptr < start_address || endptr > start_address + maxsize) + elog(ERROR, "not enough space to serialize ComboCID state"); + + /* Now, copy the actual cmin/cmax pairs. */ + if (usedComboCids > 0) + memcpy(start_address + sizeof(int), comboCids, + (sizeof(ComboCidKeyData) * usedComboCids)); +} + +/* + * Read the combo CID state at the specified address and initialize this + * backend with the same combo CIDs. This is only valid in a backend that + * currently has no combo CIDs (and only makes sense if the transaction state + * is serialized and restored as well). + */ +void +RestoreComboCIDState(char *comboCIDstate) +{ + int num_elements; + ComboCidKeyData *keydata; + int i; + CommandId cid; + + Assert(!comboCids && !comboHash); + + /* First, we retrieve the number of combo CIDs that were serialized. */ + num_elements = *(int *) comboCIDstate; + keydata = (ComboCidKeyData *) (comboCIDstate + sizeof(int)); + + /* Use GetComboCommandId to restore each combo CID. */ + for (i = 0; i < num_elements; i++) + { + cid = GetComboCommandId(keydata[i].cmin, keydata[i].cmax); + + /* Verify that we got the expected answer. */ + if (cid != i) + elog(ERROR, "unexpected command ID while restoring combo CIDs"); + } +} diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/time/snapmgr.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/time/snapmgr.c new file mode 100644 index 00000000000..87ddbcb9839 --- /dev/null +++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/utils/time/snapmgr.c @@ -0,0 +1,2381 @@ +/*------------------------------------------------------------------------- + * + * snapmgr.c + * PostgreSQL snapshot manager + * + * We keep track of snapshots in two ways: those "registered" by resowner.c, + * and the "active snapshot" stack. All snapshots in either of them live in + * persistent memory. When a snapshot is no longer in any of these lists + * (tracked by separate refcounts on each snapshot), its memory can be freed. + * + * The FirstXactSnapshot, if any, is treated a bit specially: we increment its + * regd_count and list it in RegisteredSnapshots, but this reference is not + * tracked by a resource owner. We used to use the TopTransactionResourceOwner + * to track this snapshot reference, but that introduces logical circularity + * and thus makes it impossible to clean up in a sane fashion. It's better to + * handle this reference as an internally-tracked registration, so that this + * module is entirely lower-level than ResourceOwners. + * + * Likewise, any snapshots that have been exported by pg_export_snapshot + * have regd_count = 1 and are listed in RegisteredSnapshots, but are not + * tracked by any resource owner. + * + * Likewise, the CatalogSnapshot is listed in RegisteredSnapshots when it + * is valid, but is not tracked by any resource owner. + * + * The same is true for historic snapshots used during logical decoding, + * their lifetime is managed separately (as they live longer than one xact.c + * transaction). + * + * These arrangements let us reset MyProc->xmin when there are no snapshots + * referenced by this transaction, and advance it when the one with oldest + * Xmin is no longer referenced. For simplicity however, only registered + * snapshots not active snapshots participate in tracking which one is oldest; + * we don't try to change MyProc->xmin except when the active-snapshot + * stack is empty. + * + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/utils/time/snapmgr.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <sys/stat.h> +#include <unistd.h> + +#include "access/subtrans.h" +#include "access/transam.h" +#include "access/xact.h" +#include "access/xlog.h" +#include "catalog/catalog.h" +#include "datatype/timestamp.h" +#include "lib/pairingheap.h" +#include "miscadmin.h" +#include "port/pg_lfind.h" +#include "storage/predicate.h" +#include "storage/proc.h" +#include "storage/procarray.h" +#include "storage/sinval.h" +#include "storage/sinvaladt.h" +#include "storage/spin.h" +#include "utils/builtins.h" +#include "utils/memutils.h" +#include "utils/old_snapshot.h" +#include "utils/rel.h" +#include "utils/resowner_private.h" +#include "utils/snapmgr.h" +#include "utils/syscache.h" +#include "utils/timestamp.h" + + +/* + * GUC parameters + */ +__thread int old_snapshot_threshold; /* number of minutes, -1 disables */ + +__thread volatile OldSnapshotControlData *oldSnapshotControl; + + +/* + * CurrentSnapshot points to the only snapshot taken in transaction-snapshot + * mode, and to the latest one taken in a read-committed transaction. + * SecondarySnapshot is a snapshot that's always up-to-date as of the current + * instant, even in transaction-snapshot mode. It should only be used for + * special-purpose code (say, RI checking.) CatalogSnapshot points to an + * MVCC snapshot intended to be used for catalog scans; we must invalidate it + * whenever a system catalog change occurs. + * + * These SnapshotData structs are static to simplify memory allocation + * (see the hack in GetSnapshotData to avoid repeated malloc/free). + */ +static __thread SnapshotData CurrentSnapshotData = {SNAPSHOT_MVCC}; +static __thread SnapshotData SecondarySnapshotData = {SNAPSHOT_MVCC}; +__thread SnapshotData CatalogSnapshotData = {SNAPSHOT_MVCC}; +__thread SnapshotData SnapshotSelfData = {SNAPSHOT_SELF}; +__thread SnapshotData SnapshotAnyData = {SNAPSHOT_ANY}; + +/* Pointers to valid snapshots */ +static __thread Snapshot CurrentSnapshot = NULL; +static __thread Snapshot SecondarySnapshot = NULL; +static __thread Snapshot CatalogSnapshot = NULL; +static __thread Snapshot HistoricSnapshot = NULL; + +/* + * These are updated by GetSnapshotData. We initialize them this way + * for the convenience of TransactionIdIsInProgress: even in bootstrap + * mode, we don't want it to say that BootstrapTransactionId is in progress. + */ +__thread TransactionId TransactionXmin = FirstNormalTransactionId; +__thread TransactionId RecentXmin = FirstNormalTransactionId; + +/* (table, ctid) => (cmin, cmax) mapping during timetravel */ +static __thread HTAB *tuplecid_data = NULL; + +/* + * Elements of the active snapshot stack. + * + * Each element here accounts for exactly one active_count on SnapshotData. + * + * NB: the code assumes that elements in this list are in non-increasing + * order of as_level; also, the list must be NULL-terminated. + */ +typedef struct ActiveSnapshotElt +{ + Snapshot as_snap; + int as_level; + struct ActiveSnapshotElt *as_next; +} ActiveSnapshotElt; + +/* Top of the stack of active snapshots */ +static __thread ActiveSnapshotElt *ActiveSnapshot = NULL; + +/* Bottom of the stack of active snapshots */ +static __thread ActiveSnapshotElt *OldestActiveSnapshot = NULL; + +/* + * Currently registered Snapshots. Ordered in a heap by xmin, so that we can + * quickly find the one with lowest xmin, to advance our MyProc->xmin. + */ +static int xmin_cmp(const pairingheap_node *a, const pairingheap_node *b, + void *arg); + +static __thread pairingheap RegisteredSnapshots = {&xmin_cmp, NULL, NULL}; + +/* first GetTransactionSnapshot call in a transaction? */ +__thread bool FirstSnapshotSet = false; + +/* + * Remember the serializable transaction snapshot, if any. We cannot trust + * FirstSnapshotSet in combination with IsolationUsesXactSnapshot(), because + * GUC may be reset before us, changing the value of IsolationUsesXactSnapshot. + */ +static __thread Snapshot FirstXactSnapshot = NULL; + +/* Define pathname of exported-snapshot files */ +#define SNAPSHOT_EXPORT_DIR "pg_snapshots" + +/* Structure holding info about exported snapshot. */ +typedef struct ExportedSnapshot +{ + char *snapfile; + Snapshot snapshot; +} ExportedSnapshot; + +/* Current xact's exported snapshots (a list of ExportedSnapshot structs) */ +static __thread List *exportedSnapshots = NIL; + +/* Prototypes for local functions */ +static TimestampTz AlignTimestampToMinuteBoundary(TimestampTz ts); +static Snapshot CopySnapshot(Snapshot snapshot); +static void FreeSnapshot(Snapshot snapshot); +static void SnapshotResetXmin(void); + +/* + * Snapshot fields to be serialized. + * + * Only these fields need to be sent to the cooperating backend; the + * remaining ones can (and must) be set by the receiver upon restore. + */ +typedef struct SerializedSnapshotData +{ + TransactionId xmin; + TransactionId xmax; + uint32 xcnt; + int32 subxcnt; + bool suboverflowed; + bool takenDuringRecovery; + CommandId curcid; + TimestampTz whenTaken; + XLogRecPtr lsn; +} SerializedSnapshotData; + +Size +SnapMgrShmemSize(void) +{ + Size size; + + size = offsetof(OldSnapshotControlData, xid_by_minute); + if (old_snapshot_threshold > 0) + size = add_size(size, mul_size(sizeof(TransactionId), + OLD_SNAPSHOT_TIME_MAP_ENTRIES)); + + return size; +} + +/* + * Initialize for managing old snapshot detection. + */ +void +SnapMgrInit(void) +{ + bool found; + + /* + * Create or attach to the OldSnapshotControlData structure. + */ + oldSnapshotControl = (volatile OldSnapshotControlData *) + ShmemInitStruct("OldSnapshotControlData", + SnapMgrShmemSize(), &found); + + if (!found) + { + SpinLockInit(&oldSnapshotControl->mutex_current); + oldSnapshotControl->current_timestamp = 0; + SpinLockInit(&oldSnapshotControl->mutex_latest_xmin); + oldSnapshotControl->latest_xmin = InvalidTransactionId; + oldSnapshotControl->next_map_update = 0; + SpinLockInit(&oldSnapshotControl->mutex_threshold); + oldSnapshotControl->threshold_timestamp = 0; + oldSnapshotControl->threshold_xid = InvalidTransactionId; + oldSnapshotControl->head_offset = 0; + oldSnapshotControl->head_timestamp = 0; + oldSnapshotControl->count_used = 0; + } +} + +/* + * GetTransactionSnapshot + * Get the appropriate snapshot for a new query in a transaction. + * + * Note that the return value may point at static storage that will be modified + * by future calls and by CommandCounterIncrement(). Callers should call + * RegisterSnapshot or PushActiveSnapshot on the returned snap if it is to be + * used very long. + */ +Snapshot +GetTransactionSnapshot(void) +{ + /* + * Return historic snapshot if doing logical decoding. We'll never need a + * non-historic transaction snapshot in this (sub-)transaction, so there's + * no need to be careful to set one up for later calls to + * GetTransactionSnapshot(). + */ + if (HistoricSnapshotActive()) + { + Assert(!FirstSnapshotSet); + return HistoricSnapshot; + } + + /* First call in transaction? */ + if (!FirstSnapshotSet) + { + /* + * Don't allow catalog snapshot to be older than xact snapshot. Must + * do this first to allow the empty-heap Assert to succeed. + */ + InvalidateCatalogSnapshot(); + + Assert(pairingheap_is_empty(&RegisteredSnapshots)); + Assert(FirstXactSnapshot == NULL); + + if (IsInParallelMode()) + elog(ERROR, + "cannot take query snapshot during a parallel operation"); + + /* + * In transaction-snapshot mode, the first snapshot must live until + * end of xact regardless of what the caller does with it, so we must + * make a copy of it rather than returning CurrentSnapshotData + * directly. Furthermore, if we're running in serializable mode, + * predicate.c needs to wrap the snapshot fetch in its own processing. + */ + if (IsolationUsesXactSnapshot()) + { + /* First, create the snapshot in CurrentSnapshotData */ + if (IsolationIsSerializable()) + CurrentSnapshot = GetSerializableTransactionSnapshot(&CurrentSnapshotData); + else + CurrentSnapshot = GetSnapshotData(&CurrentSnapshotData); + /* Make a saved copy */ + CurrentSnapshot = CopySnapshot(CurrentSnapshot); + FirstXactSnapshot = CurrentSnapshot; + /* Mark it as "registered" in FirstXactSnapshot */ + FirstXactSnapshot->regd_count++; + pairingheap_add(&RegisteredSnapshots, &FirstXactSnapshot->ph_node); + } + else + CurrentSnapshot = GetSnapshotData(&CurrentSnapshotData); + + FirstSnapshotSet = true; + return CurrentSnapshot; + } + + if (IsolationUsesXactSnapshot()) + return CurrentSnapshot; + + /* Don't allow catalog snapshot to be older than xact snapshot. */ + InvalidateCatalogSnapshot(); + + CurrentSnapshot = GetSnapshotData(&CurrentSnapshotData); + + return CurrentSnapshot; +} + +/* + * GetLatestSnapshot + * Get a snapshot that is up-to-date as of the current instant, + * even if we are executing in transaction-snapshot mode. + */ +Snapshot +GetLatestSnapshot(void) +{ + /* + * We might be able to relax this, but nothing that could otherwise work + * needs it. + */ + if (IsInParallelMode()) + elog(ERROR, + "cannot update SecondarySnapshot during a parallel operation"); + + /* + * So far there are no cases requiring support for GetLatestSnapshot() + * during logical decoding, but it wouldn't be hard to add if required. + */ + Assert(!HistoricSnapshotActive()); + + /* If first call in transaction, go ahead and set the xact snapshot */ + if (!FirstSnapshotSet) + return GetTransactionSnapshot(); + + SecondarySnapshot = GetSnapshotData(&SecondarySnapshotData); + + return SecondarySnapshot; +} + +/* + * GetOldestSnapshot + * + * Get the transaction's oldest known snapshot, as judged by the LSN. + * Will return NULL if there are no active or registered snapshots. + */ +Snapshot +GetOldestSnapshot(void) +{ + Snapshot OldestRegisteredSnapshot = NULL; + XLogRecPtr RegisteredLSN = InvalidXLogRecPtr; + + if (!pairingheap_is_empty(&RegisteredSnapshots)) + { + OldestRegisteredSnapshot = pairingheap_container(SnapshotData, ph_node, + pairingheap_first(&RegisteredSnapshots)); + RegisteredLSN = OldestRegisteredSnapshot->lsn; + } + + if (OldestActiveSnapshot != NULL) + { + XLogRecPtr ActiveLSN = OldestActiveSnapshot->as_snap->lsn; + + if (XLogRecPtrIsInvalid(RegisteredLSN) || RegisteredLSN > ActiveLSN) + return OldestActiveSnapshot->as_snap; + } + + return OldestRegisteredSnapshot; +} + +/* + * GetCatalogSnapshot + * Get a snapshot that is sufficiently up-to-date for scan of the + * system catalog with the specified OID. + */ +Snapshot +GetCatalogSnapshot(Oid relid) +{ + /* + * Return historic snapshot while we're doing logical decoding, so we can + * see the appropriate state of the catalog. + * + * This is the primary reason for needing to reset the system caches after + * finishing decoding. + */ + if (HistoricSnapshotActive()) + return HistoricSnapshot; + + return GetNonHistoricCatalogSnapshot(relid); +} + +/* + * GetNonHistoricCatalogSnapshot + * Get a snapshot that is sufficiently up-to-date for scan of the system + * catalog with the specified OID, even while historic snapshots are set + * up. + */ +Snapshot +GetNonHistoricCatalogSnapshot(Oid relid) +{ + /* + * If the caller is trying to scan a relation that has no syscache, no + * catcache invalidations will be sent when it is updated. For a few key + * relations, snapshot invalidations are sent instead. If we're trying to + * scan a relation for which neither catcache nor snapshot invalidations + * are sent, we must refresh the snapshot every time. + */ + if (CatalogSnapshot && + !RelationInvalidatesSnapshotsOnly(relid) && + !RelationHasSysCache(relid)) + InvalidateCatalogSnapshot(); + + if (CatalogSnapshot == NULL) + { + /* Get new snapshot. */ + CatalogSnapshot = GetSnapshotData(&CatalogSnapshotData); + + /* + * Make sure the catalog snapshot will be accounted for in decisions + * about advancing PGPROC->xmin. We could apply RegisterSnapshot, but + * that would result in making a physical copy, which is overkill; and + * it would also create a dependency on some resource owner, which we + * do not want for reasons explained at the head of this file. Instead + * just shove the CatalogSnapshot into the pairing heap manually. This + * has to be reversed in InvalidateCatalogSnapshot, of course. + * + * NB: it had better be impossible for this to throw error, since the + * CatalogSnapshot pointer is already valid. + */ + pairingheap_add(&RegisteredSnapshots, &CatalogSnapshot->ph_node); + } + + return CatalogSnapshot; +} + +/* + * InvalidateCatalogSnapshot + * Mark the current catalog snapshot, if any, as invalid + * + * We could change this API to allow the caller to provide more fine-grained + * invalidation details, so that a change to relation A wouldn't prevent us + * from using our cached snapshot to scan relation B, but so far there's no + * evidence that the CPU cycles we spent tracking such fine details would be + * well-spent. + */ +void +InvalidateCatalogSnapshot(void) +{ + if (CatalogSnapshot) + { + pairingheap_remove(&RegisteredSnapshots, &CatalogSnapshot->ph_node); + CatalogSnapshot = NULL; + SnapshotResetXmin(); + } +} + +/* + * InvalidateCatalogSnapshotConditionally + * Drop catalog snapshot if it's the only one we have + * + * This is called when we are about to wait for client input, so we don't + * want to continue holding the catalog snapshot if it might mean that the + * global xmin horizon can't advance. However, if there are other snapshots + * still active or registered, the catalog snapshot isn't likely to be the + * oldest one, so we might as well keep it. + */ +void +InvalidateCatalogSnapshotConditionally(void) +{ + if (CatalogSnapshot && + ActiveSnapshot == NULL && + pairingheap_is_singular(&RegisteredSnapshots)) + InvalidateCatalogSnapshot(); +} + +/* + * SnapshotSetCommandId + * Propagate CommandCounterIncrement into the static snapshots, if set + */ +void +SnapshotSetCommandId(CommandId curcid) +{ + if (!FirstSnapshotSet) + return; + + if (CurrentSnapshot) + CurrentSnapshot->curcid = curcid; + if (SecondarySnapshot) + SecondarySnapshot->curcid = curcid; + /* Should we do the same with CatalogSnapshot? */ +} + +/* + * SetTransactionSnapshot + * Set the transaction's snapshot from an imported MVCC snapshot. + * + * Note that this is very closely tied to GetTransactionSnapshot --- it + * must take care of all the same considerations as the first-snapshot case + * in GetTransactionSnapshot. + */ +static void +SetTransactionSnapshot(Snapshot sourcesnap, VirtualTransactionId *sourcevxid, + int sourcepid, PGPROC *sourceproc) +{ + /* Caller should have checked this already */ + Assert(!FirstSnapshotSet); + + /* Better do this to ensure following Assert succeeds. */ + InvalidateCatalogSnapshot(); + + Assert(pairingheap_is_empty(&RegisteredSnapshots)); + Assert(FirstXactSnapshot == NULL); + Assert(!HistoricSnapshotActive()); + + /* + * Even though we are not going to use the snapshot it computes, we must + * call GetSnapshotData, for two reasons: (1) to be sure that + * CurrentSnapshotData's XID arrays have been allocated, and (2) to update + * the state for GlobalVis*. + */ + CurrentSnapshot = GetSnapshotData(&CurrentSnapshotData); + + /* + * Now copy appropriate fields from the source snapshot. + */ + CurrentSnapshot->xmin = sourcesnap->xmin; + CurrentSnapshot->xmax = sourcesnap->xmax; + CurrentSnapshot->xcnt = sourcesnap->xcnt; + Assert(sourcesnap->xcnt <= GetMaxSnapshotXidCount()); + if (sourcesnap->xcnt > 0) + memcpy(CurrentSnapshot->xip, sourcesnap->xip, + sourcesnap->xcnt * sizeof(TransactionId)); + CurrentSnapshot->subxcnt = sourcesnap->subxcnt; + Assert(sourcesnap->subxcnt <= GetMaxSnapshotSubxidCount()); + if (sourcesnap->subxcnt > 0) + memcpy(CurrentSnapshot->subxip, sourcesnap->subxip, + sourcesnap->subxcnt * sizeof(TransactionId)); + CurrentSnapshot->suboverflowed = sourcesnap->suboverflowed; + CurrentSnapshot->takenDuringRecovery = sourcesnap->takenDuringRecovery; + /* NB: curcid should NOT be copied, it's a local matter */ + + CurrentSnapshot->snapXactCompletionCount = 0; + + /* + * Now we have to fix what GetSnapshotData did with MyProc->xmin and + * TransactionXmin. There is a race condition: to make sure we are not + * causing the global xmin to go backwards, we have to test that the + * source transaction is still running, and that has to be done + * atomically. So let procarray.c do it. + * + * Note: in serializable mode, predicate.c will do this a second time. It + * doesn't seem worth contorting the logic here to avoid two calls, + * especially since it's not clear that predicate.c *must* do this. + */ + if (sourceproc != NULL) + { + if (!ProcArrayInstallRestoredXmin(CurrentSnapshot->xmin, sourceproc)) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("could not import the requested snapshot"), + errdetail("The source transaction is not running anymore."))); + } + else if (!ProcArrayInstallImportedXmin(CurrentSnapshot->xmin, sourcevxid)) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("could not import the requested snapshot"), + errdetail("The source process with PID %d is not running anymore.", + sourcepid))); + + /* + * In transaction-snapshot mode, the first snapshot must live until end of + * xact, so we must make a copy of it. Furthermore, if we're running in + * serializable mode, predicate.c needs to do its own processing. + */ + if (IsolationUsesXactSnapshot()) + { + if (IsolationIsSerializable()) + SetSerializableTransactionSnapshot(CurrentSnapshot, sourcevxid, + sourcepid); + /* Make a saved copy */ + CurrentSnapshot = CopySnapshot(CurrentSnapshot); + FirstXactSnapshot = CurrentSnapshot; + /* Mark it as "registered" in FirstXactSnapshot */ + FirstXactSnapshot->regd_count++; + pairingheap_add(&RegisteredSnapshots, &FirstXactSnapshot->ph_node); + } + + FirstSnapshotSet = true; +} + +/* + * CopySnapshot + * Copy the given snapshot. + * + * The copy is palloc'd in TopTransactionContext and has initial refcounts set + * to 0. The returned snapshot has the copied flag set. + */ +static Snapshot +CopySnapshot(Snapshot snapshot) +{ + Snapshot newsnap; + Size subxipoff; + Size size; + + Assert(snapshot != InvalidSnapshot); + + /* We allocate any XID arrays needed in the same palloc block. */ + size = subxipoff = sizeof(SnapshotData) + + snapshot->xcnt * sizeof(TransactionId); + if (snapshot->subxcnt > 0) + size += snapshot->subxcnt * sizeof(TransactionId); + + newsnap = (Snapshot) MemoryContextAlloc(TopTransactionContext, size); + memcpy(newsnap, snapshot, sizeof(SnapshotData)); + + newsnap->regd_count = 0; + newsnap->active_count = 0; + newsnap->copied = true; + newsnap->snapXactCompletionCount = 0; + + /* setup XID array */ + if (snapshot->xcnt > 0) + { + newsnap->xip = (TransactionId *) (newsnap + 1); + memcpy(newsnap->xip, snapshot->xip, + snapshot->xcnt * sizeof(TransactionId)); + } + else + newsnap->xip = NULL; + + /* + * Setup subXID array. Don't bother to copy it if it had overflowed, + * though, because it's not used anywhere in that case. Except if it's a + * snapshot taken during recovery; all the top-level XIDs are in subxip as + * well in that case, so we mustn't lose them. + */ + if (snapshot->subxcnt > 0 && + (!snapshot->suboverflowed || snapshot->takenDuringRecovery)) + { + newsnap->subxip = (TransactionId *) ((char *) newsnap + subxipoff); + memcpy(newsnap->subxip, snapshot->subxip, + snapshot->subxcnt * sizeof(TransactionId)); + } + else + newsnap->subxip = NULL; + + return newsnap; +} + +/* + * FreeSnapshot + * Free the memory associated with a snapshot. + */ +static void +FreeSnapshot(Snapshot snapshot) +{ + Assert(snapshot->regd_count == 0); + Assert(snapshot->active_count == 0); + Assert(snapshot->copied); + + pfree(snapshot); +} + +/* + * PushActiveSnapshot + * Set the given snapshot as the current active snapshot + * + * If the passed snapshot is a statically-allocated one, or it is possibly + * subject to a future command counter update, create a new long-lived copy + * with active refcount=1. Otherwise, only increment the refcount. + */ +void +PushActiveSnapshot(Snapshot snapshot) +{ + PushActiveSnapshotWithLevel(snapshot, GetCurrentTransactionNestLevel()); +} + +/* + * PushActiveSnapshotWithLevel + * Set the given snapshot as the current active snapshot + * + * Same as PushActiveSnapshot except that caller can specify the + * transaction nesting level that "owns" the snapshot. This level + * must not be deeper than the current top of the snapshot stack. + */ +void +PushActiveSnapshotWithLevel(Snapshot snapshot, int snap_level) +{ + ActiveSnapshotElt *newactive; + + Assert(snapshot != InvalidSnapshot); + Assert(ActiveSnapshot == NULL || snap_level >= ActiveSnapshot->as_level); + + newactive = MemoryContextAlloc(TopTransactionContext, sizeof(ActiveSnapshotElt)); + + /* + * Checking SecondarySnapshot is probably useless here, but it seems + * better to be sure. + */ + if (snapshot == CurrentSnapshot || snapshot == SecondarySnapshot || + !snapshot->copied) + newactive->as_snap = CopySnapshot(snapshot); + else + newactive->as_snap = snapshot; + + newactive->as_next = ActiveSnapshot; + newactive->as_level = snap_level; + + newactive->as_snap->active_count++; + + ActiveSnapshot = newactive; + if (OldestActiveSnapshot == NULL) + OldestActiveSnapshot = ActiveSnapshot; +} + +/* + * PushCopiedSnapshot + * As above, except forcibly copy the presented snapshot. + * + * This should be used when the ActiveSnapshot has to be modifiable, for + * example if the caller intends to call UpdateActiveSnapshotCommandId. + * The new snapshot will be released when popped from the stack. + */ +void +PushCopiedSnapshot(Snapshot snapshot) +{ + PushActiveSnapshot(CopySnapshot(snapshot)); +} + +/* + * UpdateActiveSnapshotCommandId + * + * Update the current CID of the active snapshot. This can only be applied + * to a snapshot that is not referenced elsewhere. + */ +void +UpdateActiveSnapshotCommandId(void) +{ + CommandId save_curcid, + curcid; + + Assert(ActiveSnapshot != NULL); + Assert(ActiveSnapshot->as_snap->active_count == 1); + Assert(ActiveSnapshot->as_snap->regd_count == 0); + + /* + * Don't allow modification of the active snapshot during parallel + * operation. We share the snapshot to worker backends at the beginning + * of parallel operation, so any change to the snapshot can lead to + * inconsistencies. We have other defenses against + * CommandCounterIncrement, but there are a few places that call this + * directly, so we put an additional guard here. + */ + save_curcid = ActiveSnapshot->as_snap->curcid; + curcid = GetCurrentCommandId(false); + if (IsInParallelMode() && save_curcid != curcid) + elog(ERROR, "cannot modify commandid in active snapshot during a parallel operation"); + ActiveSnapshot->as_snap->curcid = curcid; +} + +/* + * PopActiveSnapshot + * + * Remove the topmost snapshot from the active snapshot stack, decrementing the + * reference count, and free it if this was the last reference. + */ +void +PopActiveSnapshot(void) +{ + ActiveSnapshotElt *newstack; + + newstack = ActiveSnapshot->as_next; + + Assert(ActiveSnapshot->as_snap->active_count > 0); + + ActiveSnapshot->as_snap->active_count--; + + if (ActiveSnapshot->as_snap->active_count == 0 && + ActiveSnapshot->as_snap->regd_count == 0) + FreeSnapshot(ActiveSnapshot->as_snap); + + pfree(ActiveSnapshot); + ActiveSnapshot = newstack; + if (ActiveSnapshot == NULL) + OldestActiveSnapshot = NULL; + + SnapshotResetXmin(); +} + +/* + * GetActiveSnapshot + * Return the topmost snapshot in the Active stack. + */ +Snapshot +GetActiveSnapshot(void) +{ + Assert(ActiveSnapshot != NULL); + + return ActiveSnapshot->as_snap; +} + +/* + * ActiveSnapshotSet + * Return whether there is at least one snapshot in the Active stack + */ +bool +ActiveSnapshotSet(void) +{ + return ActiveSnapshot != NULL; +} + +/* + * RegisterSnapshot + * Register a snapshot as being in use by the current resource owner + * + * If InvalidSnapshot is passed, it is not registered. + */ +Snapshot +RegisterSnapshot(Snapshot snapshot) +{ + if (snapshot == InvalidSnapshot) + return InvalidSnapshot; + + return RegisterSnapshotOnOwner(snapshot, CurrentResourceOwner); +} + +/* + * RegisterSnapshotOnOwner + * As above, but use the specified resource owner + */ +Snapshot +RegisterSnapshotOnOwner(Snapshot snapshot, ResourceOwner owner) +{ + Snapshot snap; + + if (snapshot == InvalidSnapshot) + return InvalidSnapshot; + + /* Static snapshot? Create a persistent copy */ + snap = snapshot->copied ? snapshot : CopySnapshot(snapshot); + + /* and tell resowner.c about it */ + ResourceOwnerEnlargeSnapshots(owner); + snap->regd_count++; + ResourceOwnerRememberSnapshot(owner, snap); + + if (snap->regd_count == 1) + pairingheap_add(&RegisteredSnapshots, &snap->ph_node); + + return snap; +} + +/* + * UnregisterSnapshot + * + * Decrement the reference count of a snapshot, remove the corresponding + * reference from CurrentResourceOwner, and free the snapshot if no more + * references remain. + */ +void +UnregisterSnapshot(Snapshot snapshot) +{ + if (snapshot == NULL) + return; + + UnregisterSnapshotFromOwner(snapshot, CurrentResourceOwner); +} + +/* + * UnregisterSnapshotFromOwner + * As above, but use the specified resource owner + */ +void +UnregisterSnapshotFromOwner(Snapshot snapshot, ResourceOwner owner) +{ + if (snapshot == NULL) + return; + + Assert(snapshot->regd_count > 0); + Assert(!pairingheap_is_empty(&RegisteredSnapshots)); + + ResourceOwnerForgetSnapshot(owner, snapshot); + + snapshot->regd_count--; + if (snapshot->regd_count == 0) + pairingheap_remove(&RegisteredSnapshots, &snapshot->ph_node); + + if (snapshot->regd_count == 0 && snapshot->active_count == 0) + { + FreeSnapshot(snapshot); + SnapshotResetXmin(); + } +} + +/* + * Comparison function for RegisteredSnapshots heap. Snapshots are ordered + * by xmin, so that the snapshot with smallest xmin is at the top. + */ +static int +xmin_cmp(const pairingheap_node *a, const pairingheap_node *b, void *arg) +{ + const SnapshotData *asnap = pairingheap_const_container(SnapshotData, ph_node, a); + const SnapshotData *bsnap = pairingheap_const_container(SnapshotData, ph_node, b); + + if (TransactionIdPrecedes(asnap->xmin, bsnap->xmin)) + return 1; + else if (TransactionIdFollows(asnap->xmin, bsnap->xmin)) + return -1; + else + return 0; +} + +/* + * SnapshotResetXmin + * + * If there are no more snapshots, we can reset our PGPROC->xmin to + * InvalidTransactionId. Note we can do this without locking because we assume + * that storing an Xid is atomic. + * + * Even if there are some remaining snapshots, we may be able to advance our + * PGPROC->xmin to some degree. This typically happens when a portal is + * dropped. For efficiency, we only consider recomputing PGPROC->xmin when + * the active snapshot stack is empty; this allows us not to need to track + * which active snapshot is oldest. + * + * Note: it's tempting to use GetOldestSnapshot() here so that we can include + * active snapshots in the calculation. However, that compares by LSN not + * xmin so it's not entirely clear that it's the same thing. Also, we'd be + * critically dependent on the assumption that the bottommost active snapshot + * stack entry has the oldest xmin. (Current uses of GetOldestSnapshot() are + * not actually critical, but this would be.) + */ +static void +SnapshotResetXmin(void) +{ + Snapshot minSnapshot; + + if (ActiveSnapshot != NULL) + return; + + if (pairingheap_is_empty(&RegisteredSnapshots)) + { + MyProc->xmin = InvalidTransactionId; + return; + } + + minSnapshot = pairingheap_container(SnapshotData, ph_node, + pairingheap_first(&RegisteredSnapshots)); + + if (TransactionIdPrecedes(MyProc->xmin, minSnapshot->xmin)) + MyProc->xmin = minSnapshot->xmin; +} + +/* + * AtSubCommit_Snapshot + */ +void +AtSubCommit_Snapshot(int level) +{ + ActiveSnapshotElt *active; + + /* + * Relabel the active snapshots set in this subtransaction as though they + * are owned by the parent subxact. + */ + for (active = ActiveSnapshot; active != NULL; active = active->as_next) + { + if (active->as_level < level) + break; + active->as_level = level - 1; + } +} + +/* + * AtSubAbort_Snapshot + * Clean up snapshots after a subtransaction abort + */ +void +AtSubAbort_Snapshot(int level) +{ + /* Forget the active snapshots set by this subtransaction */ + while (ActiveSnapshot && ActiveSnapshot->as_level >= level) + { + ActiveSnapshotElt *next; + + next = ActiveSnapshot->as_next; + + /* + * Decrement the snapshot's active count. If it's still registered or + * marked as active by an outer subtransaction, we can't free it yet. + */ + Assert(ActiveSnapshot->as_snap->active_count >= 1); + ActiveSnapshot->as_snap->active_count -= 1; + + if (ActiveSnapshot->as_snap->active_count == 0 && + ActiveSnapshot->as_snap->regd_count == 0) + FreeSnapshot(ActiveSnapshot->as_snap); + + /* and free the stack element */ + pfree(ActiveSnapshot); + + ActiveSnapshot = next; + if (ActiveSnapshot == NULL) + OldestActiveSnapshot = NULL; + } + + SnapshotResetXmin(); +} + +/* + * AtEOXact_Snapshot + * Snapshot manager's cleanup function for end of transaction + */ +void +AtEOXact_Snapshot(bool isCommit, bool resetXmin) +{ + /* + * In transaction-snapshot mode we must release our privately-managed + * reference to the transaction snapshot. We must remove it from + * RegisteredSnapshots to keep the check below happy. But we don't bother + * to do FreeSnapshot, for two reasons: the memory will go away with + * TopTransactionContext anyway, and if someone has left the snapshot + * stacked as active, we don't want the code below to be chasing through a + * dangling pointer. + */ + if (FirstXactSnapshot != NULL) + { + Assert(FirstXactSnapshot->regd_count > 0); + Assert(!pairingheap_is_empty(&RegisteredSnapshots)); + pairingheap_remove(&RegisteredSnapshots, &FirstXactSnapshot->ph_node); + } + FirstXactSnapshot = NULL; + + /* + * If we exported any snapshots, clean them up. + */ + if (exportedSnapshots != NIL) + { + ListCell *lc; + + /* + * Get rid of the files. Unlink failure is only a WARNING because (1) + * it's too late to abort the transaction, and (2) leaving a leaked + * file around has little real consequence anyway. + * + * We also need to remove the snapshots from RegisteredSnapshots to + * prevent a warning below. + * + * As with the FirstXactSnapshot, we don't need to free resources of + * the snapshot itself as it will go away with the memory context. + */ + foreach(lc, exportedSnapshots) + { + ExportedSnapshot *esnap = (ExportedSnapshot *) lfirst(lc); + + if (unlink(esnap->snapfile)) + elog(WARNING, "could not unlink file \"%s\": %m", + esnap->snapfile); + + pairingheap_remove(&RegisteredSnapshots, + &esnap->snapshot->ph_node); + } + + exportedSnapshots = NIL; + } + + /* Drop catalog snapshot if any */ + InvalidateCatalogSnapshot(); + + /* On commit, complain about leftover snapshots */ + if (isCommit) + { + ActiveSnapshotElt *active; + + if (!pairingheap_is_empty(&RegisteredSnapshots)) + elog(WARNING, "registered snapshots seem to remain after cleanup"); + + /* complain about unpopped active snapshots */ + for (active = ActiveSnapshot; active != NULL; active = active->as_next) + elog(WARNING, "snapshot %p still active", active); + } + + /* + * And reset our state. We don't need to free the memory explicitly -- + * it'll go away with TopTransactionContext. + */ + ActiveSnapshot = NULL; + OldestActiveSnapshot = NULL; + pairingheap_reset(&RegisteredSnapshots); + + CurrentSnapshot = NULL; + SecondarySnapshot = NULL; + + FirstSnapshotSet = false; + + /* + * During normal commit processing, we call ProcArrayEndTransaction() to + * reset the MyProc->xmin. That call happens prior to the call to + * AtEOXact_Snapshot(), so we need not touch xmin here at all. + */ + if (resetXmin) + SnapshotResetXmin(); + + Assert(resetXmin || MyProc->xmin == 0); +} + + +/* + * ExportSnapshot + * Export the snapshot to a file so that other backends can import it. + * Returns the token (the file name) that can be used to import this + * snapshot. + */ +char * +ExportSnapshot(Snapshot snapshot) +{ + TransactionId topXid; + TransactionId *children; + ExportedSnapshot *esnap; + int nchildren; + int addTopXid; + StringInfoData buf; + FILE *f; + int i; + MemoryContext oldcxt; + char path[MAXPGPATH]; + char pathtmp[MAXPGPATH]; + + /* + * It's tempting to call RequireTransactionBlock here, since it's not very + * useful to export a snapshot that will disappear immediately afterwards. + * However, we haven't got enough information to do that, since we don't + * know if we're at top level or not. For example, we could be inside a + * plpgsql function that is going to fire off other transactions via + * dblink. Rather than disallow perfectly legitimate usages, don't make a + * check. + * + * Also note that we don't make any restriction on the transaction's + * isolation level; however, importers must check the level if they are + * serializable. + */ + + /* + * Get our transaction ID if there is one, to include in the snapshot. + */ + topXid = GetTopTransactionIdIfAny(); + + /* + * We cannot export a snapshot from a subtransaction because there's no + * easy way for importers to verify that the same subtransaction is still + * running. + */ + if (IsSubTransaction()) + ereport(ERROR, + (errcode(ERRCODE_ACTIVE_SQL_TRANSACTION), + errmsg("cannot export a snapshot from a subtransaction"))); + + /* + * We do however allow previous committed subtransactions to exist. + * Importers of the snapshot must see them as still running, so get their + * XIDs to add them to the snapshot. + */ + nchildren = xactGetCommittedChildren(&children); + + /* + * Generate file path for the snapshot. We start numbering of snapshots + * inside the transaction from 1. + */ + snprintf(path, sizeof(path), SNAPSHOT_EXPORT_DIR "/%08X-%08X-%d", + MyProc->backendId, MyProc->lxid, list_length(exportedSnapshots) + 1); + + /* + * Copy the snapshot into TopTransactionContext, add it to the + * exportedSnapshots list, and mark it pseudo-registered. We do this to + * ensure that the snapshot's xmin is honored for the rest of the + * transaction. + */ + snapshot = CopySnapshot(snapshot); + + oldcxt = MemoryContextSwitchTo(TopTransactionContext); + esnap = (ExportedSnapshot *) palloc(sizeof(ExportedSnapshot)); + esnap->snapfile = pstrdup(path); + esnap->snapshot = snapshot; + exportedSnapshots = lappend(exportedSnapshots, esnap); + MemoryContextSwitchTo(oldcxt); + + snapshot->regd_count++; + pairingheap_add(&RegisteredSnapshots, &snapshot->ph_node); + + /* + * Fill buf with a text serialization of the snapshot, plus identification + * data about this transaction. The format expected by ImportSnapshot is + * pretty rigid: each line must be fieldname:value. + */ + initStringInfo(&buf); + + appendStringInfo(&buf, "vxid:%d/%u\n", MyProc->backendId, MyProc->lxid); + appendStringInfo(&buf, "pid:%d\n", MyProcPid); + appendStringInfo(&buf, "dbid:%u\n", MyDatabaseId); + appendStringInfo(&buf, "iso:%d\n", XactIsoLevel); + appendStringInfo(&buf, "ro:%d\n", XactReadOnly); + + appendStringInfo(&buf, "xmin:%u\n", snapshot->xmin); + appendStringInfo(&buf, "xmax:%u\n", snapshot->xmax); + + /* + * We must include our own top transaction ID in the top-xid data, since + * by definition we will still be running when the importing transaction + * adopts the snapshot, but GetSnapshotData never includes our own XID in + * the snapshot. (There must, therefore, be enough room to add it.) + * + * However, it could be that our topXid is after the xmax, in which case + * we shouldn't include it because xip[] members are expected to be before + * xmax. (We need not make the same check for subxip[] members, see + * snapshot.h.) + */ + addTopXid = (TransactionIdIsValid(topXid) && + TransactionIdPrecedes(topXid, snapshot->xmax)) ? 1 : 0; + appendStringInfo(&buf, "xcnt:%d\n", snapshot->xcnt + addTopXid); + for (i = 0; i < snapshot->xcnt; i++) + appendStringInfo(&buf, "xip:%u\n", snapshot->xip[i]); + if (addTopXid) + appendStringInfo(&buf, "xip:%u\n", topXid); + + /* + * Similarly, we add our subcommitted child XIDs to the subxid data. Here, + * we have to cope with possible overflow. + */ + if (snapshot->suboverflowed || + snapshot->subxcnt + nchildren > GetMaxSnapshotSubxidCount()) + appendStringInfoString(&buf, "sof:1\n"); + else + { + appendStringInfoString(&buf, "sof:0\n"); + appendStringInfo(&buf, "sxcnt:%d\n", snapshot->subxcnt + nchildren); + for (i = 0; i < snapshot->subxcnt; i++) + appendStringInfo(&buf, "sxp:%u\n", snapshot->subxip[i]); + for (i = 0; i < nchildren; i++) + appendStringInfo(&buf, "sxp:%u\n", children[i]); + } + appendStringInfo(&buf, "rec:%u\n", snapshot->takenDuringRecovery); + + /* + * Now write the text representation into a file. We first write to a + * ".tmp" filename, and rename to final filename if no error. This + * ensures that no other backend can read an incomplete file + * (ImportSnapshot won't allow it because of its valid-characters check). + */ + snprintf(pathtmp, sizeof(pathtmp), "%s.tmp", path); + if (!(f = AllocateFile(pathtmp, PG_BINARY_W))) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not create file \"%s\": %m", pathtmp))); + + if (fwrite(buf.data, buf.len, 1, f) != 1) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not write to file \"%s\": %m", pathtmp))); + + /* no fsync() since file need not survive a system crash */ + + if (FreeFile(f)) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not write to file \"%s\": %m", pathtmp))); + + /* + * Now that we have written everything into a .tmp file, rename the file + * to remove the .tmp suffix. + */ + if (rename(pathtmp, path) < 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not rename file \"%s\" to \"%s\": %m", + pathtmp, path))); + + /* + * The basename of the file is what we return from pg_export_snapshot(). + * It's already in path in a textual format and we know that the path + * starts with SNAPSHOT_EXPORT_DIR. Skip over the prefix and the slash + * and pstrdup it so as not to return the address of a local variable. + */ + return pstrdup(path + strlen(SNAPSHOT_EXPORT_DIR) + 1); +} + +/* + * pg_export_snapshot + * SQL-callable wrapper for ExportSnapshot. + */ +Datum +pg_export_snapshot(PG_FUNCTION_ARGS) +{ + char *snapshotName; + + snapshotName = ExportSnapshot(GetActiveSnapshot()); + PG_RETURN_TEXT_P(cstring_to_text(snapshotName)); +} + + +/* + * Parsing subroutines for ImportSnapshot: parse a line with the given + * prefix followed by a value, and advance *s to the next line. The + * filename is provided for use in error messages. + */ +static int +parseIntFromText(const char *prefix, char **s, const char *filename) +{ + char *ptr = *s; + int prefixlen = strlen(prefix); + int val; + + if (strncmp(ptr, prefix, prefixlen) != 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid snapshot data in file \"%s\"", filename))); + ptr += prefixlen; + if (sscanf(ptr, "%d", &val) != 1) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid snapshot data in file \"%s\"", filename))); + ptr = strchr(ptr, '\n'); + if (!ptr) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid snapshot data in file \"%s\"", filename))); + *s = ptr + 1; + return val; +} + +static TransactionId +parseXidFromText(const char *prefix, char **s, const char *filename) +{ + char *ptr = *s; + int prefixlen = strlen(prefix); + TransactionId val; + + if (strncmp(ptr, prefix, prefixlen) != 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid snapshot data in file \"%s\"", filename))); + ptr += prefixlen; + if (sscanf(ptr, "%u", &val) != 1) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid snapshot data in file \"%s\"", filename))); + ptr = strchr(ptr, '\n'); + if (!ptr) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid snapshot data in file \"%s\"", filename))); + *s = ptr + 1; + return val; +} + +static void +parseVxidFromText(const char *prefix, char **s, const char *filename, + VirtualTransactionId *vxid) +{ + char *ptr = *s; + int prefixlen = strlen(prefix); + + if (strncmp(ptr, prefix, prefixlen) != 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid snapshot data in file \"%s\"", filename))); + ptr += prefixlen; + if (sscanf(ptr, "%d/%u", &vxid->backendId, &vxid->localTransactionId) != 2) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid snapshot data in file \"%s\"", filename))); + ptr = strchr(ptr, '\n'); + if (!ptr) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid snapshot data in file \"%s\"", filename))); + *s = ptr + 1; +} + +/* + * ImportSnapshot + * Import a previously exported snapshot. The argument should be a + * filename in SNAPSHOT_EXPORT_DIR. Load the snapshot from that file. + * This is called by "SET TRANSACTION SNAPSHOT 'foo'". + */ +void +ImportSnapshot(const char *idstr) +{ + char path[MAXPGPATH]; + FILE *f; + struct stat stat_buf; + char *filebuf; + int xcnt; + int i; + VirtualTransactionId src_vxid; + int src_pid; + Oid src_dbid; + int src_isolevel; + bool src_readonly; + SnapshotData snapshot; + + /* + * Must be at top level of a fresh transaction. Note in particular that + * we check we haven't acquired an XID --- if we have, it's conceivable + * that the snapshot would show it as not running, making for very screwy + * behavior. + */ + if (FirstSnapshotSet || + GetTopTransactionIdIfAny() != InvalidTransactionId || + IsSubTransaction()) + ereport(ERROR, + (errcode(ERRCODE_ACTIVE_SQL_TRANSACTION), + errmsg("SET TRANSACTION SNAPSHOT must be called before any query"))); + + /* + * If we are in read committed mode then the next query would execute with + * a new snapshot thus making this function call quite useless. + */ + if (!IsolationUsesXactSnapshot()) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("a snapshot-importing transaction must have isolation level SERIALIZABLE or REPEATABLE READ"))); + + /* + * Verify the identifier: only 0-9, A-F and hyphens are allowed. We do + * this mainly to prevent reading arbitrary files. + */ + if (strspn(idstr, "0123456789ABCDEF-") != strlen(idstr)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid snapshot identifier: \"%s\"", idstr))); + + /* OK, read the file */ + snprintf(path, MAXPGPATH, SNAPSHOT_EXPORT_DIR "/%s", idstr); + + f = AllocateFile(path, PG_BINARY_R); + if (!f) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid snapshot identifier: \"%s\"", idstr))); + + /* get the size of the file so that we know how much memory we need */ + if (fstat(fileno(f), &stat_buf)) + elog(ERROR, "could not stat file \"%s\": %m", path); + + /* and read the file into a palloc'd string */ + filebuf = (char *) palloc(stat_buf.st_size + 1); + if (fread(filebuf, stat_buf.st_size, 1, f) != 1) + elog(ERROR, "could not read file \"%s\": %m", path); + + filebuf[stat_buf.st_size] = '\0'; + + FreeFile(f); + + /* + * Construct a snapshot struct by parsing the file content. + */ + memset(&snapshot, 0, sizeof(snapshot)); + + parseVxidFromText("vxid:", &filebuf, path, &src_vxid); + src_pid = parseIntFromText("pid:", &filebuf, path); + /* we abuse parseXidFromText a bit here ... */ + src_dbid = parseXidFromText("dbid:", &filebuf, path); + src_isolevel = parseIntFromText("iso:", &filebuf, path); + src_readonly = parseIntFromText("ro:", &filebuf, path); + + snapshot.snapshot_type = SNAPSHOT_MVCC; + + snapshot.xmin = parseXidFromText("xmin:", &filebuf, path); + snapshot.xmax = parseXidFromText("xmax:", &filebuf, path); + + snapshot.xcnt = xcnt = parseIntFromText("xcnt:", &filebuf, path); + + /* sanity-check the xid count before palloc */ + if (xcnt < 0 || xcnt > GetMaxSnapshotXidCount()) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid snapshot data in file \"%s\"", path))); + + snapshot.xip = (TransactionId *) palloc(xcnt * sizeof(TransactionId)); + for (i = 0; i < xcnt; i++) + snapshot.xip[i] = parseXidFromText("xip:", &filebuf, path); + + snapshot.suboverflowed = parseIntFromText("sof:", &filebuf, path); + + if (!snapshot.suboverflowed) + { + snapshot.subxcnt = xcnt = parseIntFromText("sxcnt:", &filebuf, path); + + /* sanity-check the xid count before palloc */ + if (xcnt < 0 || xcnt > GetMaxSnapshotSubxidCount()) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid snapshot data in file \"%s\"", path))); + + snapshot.subxip = (TransactionId *) palloc(xcnt * sizeof(TransactionId)); + for (i = 0; i < xcnt; i++) + snapshot.subxip[i] = parseXidFromText("sxp:", &filebuf, path); + } + else + { + snapshot.subxcnt = 0; + snapshot.subxip = NULL; + } + + snapshot.takenDuringRecovery = parseIntFromText("rec:", &filebuf, path); + + /* + * Do some additional sanity checking, just to protect ourselves. We + * don't trouble to check the array elements, just the most critical + * fields. + */ + if (!VirtualTransactionIdIsValid(src_vxid) || + !OidIsValid(src_dbid) || + !TransactionIdIsNormal(snapshot.xmin) || + !TransactionIdIsNormal(snapshot.xmax)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid snapshot data in file \"%s\"", path))); + + /* + * If we're serializable, the source transaction must be too, otherwise + * predicate.c has problems (SxactGlobalXmin could go backwards). Also, a + * non-read-only transaction can't adopt a snapshot from a read-only + * transaction, as predicate.c handles the cases very differently. + */ + if (IsolationIsSerializable()) + { + if (src_isolevel != XACT_SERIALIZABLE) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("a serializable transaction cannot import a snapshot from a non-serializable transaction"))); + if (src_readonly && !XactReadOnly) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("a non-read-only serializable transaction cannot import a snapshot from a read-only transaction"))); + } + + /* + * We cannot import a snapshot that was taken in a different database, + * because vacuum calculates OldestXmin on a per-database basis; so the + * source transaction's xmin doesn't protect us from data loss. This + * restriction could be removed if the source transaction were to mark its + * xmin as being globally applicable. But that would require some + * additional syntax, since that has to be known when the snapshot is + * initially taken. (See pgsql-hackers discussion of 2011-10-21.) + */ + if (src_dbid != MyDatabaseId) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot import a snapshot from a different database"))); + + /* OK, install the snapshot */ + SetTransactionSnapshot(&snapshot, &src_vxid, src_pid, NULL); +} + +/* + * XactHasExportedSnapshots + * Test whether current transaction has exported any snapshots. + */ +bool +XactHasExportedSnapshots(void) +{ + return (exportedSnapshots != NIL); +} + +/* + * DeleteAllExportedSnapshotFiles + * Clean up any files that have been left behind by a crashed backend + * that had exported snapshots before it died. + * + * This should be called during database startup or crash recovery. + */ +void +DeleteAllExportedSnapshotFiles(void) +{ + char buf[MAXPGPATH + sizeof(SNAPSHOT_EXPORT_DIR)]; + DIR *s_dir; + struct dirent *s_de; + + /* + * Problems in reading the directory, or unlinking files, are reported at + * LOG level. Since we're running in the startup process, ERROR level + * would prevent database start, and it's not important enough for that. + */ + s_dir = AllocateDir(SNAPSHOT_EXPORT_DIR); + + while ((s_de = ReadDirExtended(s_dir, SNAPSHOT_EXPORT_DIR, LOG)) != NULL) + { + if (strcmp(s_de->d_name, ".") == 0 || + strcmp(s_de->d_name, "..") == 0) + continue; + + snprintf(buf, sizeof(buf), SNAPSHOT_EXPORT_DIR "/%s", s_de->d_name); + + if (unlink(buf) != 0) + ereport(LOG, + (errcode_for_file_access(), + errmsg("could not remove file \"%s\": %m", buf))); + } + + FreeDir(s_dir); +} + +/* + * ThereAreNoPriorRegisteredSnapshots + * Is the registered snapshot count less than or equal to one? + * + * Don't use this to settle important decisions. While zero registrations and + * no ActiveSnapshot would confirm a certain idleness, the system makes no + * guarantees about the significance of one registered snapshot. + */ +bool +ThereAreNoPriorRegisteredSnapshots(void) +{ + if (pairingheap_is_empty(&RegisteredSnapshots) || + pairingheap_is_singular(&RegisteredSnapshots)) + return true; + + return false; +} + +/* + * HaveRegisteredOrActiveSnapshot + * Is there any registered or active snapshot? + * + * NB: Unless pushed or active, the cached catalog snapshot will not cause + * this function to return true. That allows this function to be used in + * checks enforcing a longer-lived snapshot. + */ +bool +HaveRegisteredOrActiveSnapshot(void) +{ + if (ActiveSnapshot != NULL) + return true; + + /* + * The catalog snapshot is in RegisteredSnapshots when valid, but can be + * removed at any time due to invalidation processing. If explicitly + * registered more than one snapshot has to be in RegisteredSnapshots. + */ + if (CatalogSnapshot != NULL && + pairingheap_is_singular(&RegisteredSnapshots)) + return false; + + return !pairingheap_is_empty(&RegisteredSnapshots); +} + + +/* + * Return a timestamp that is exactly on a minute boundary. + * + * If the argument is already aligned, return that value, otherwise move to + * the next minute boundary following the given time. + */ +static TimestampTz +AlignTimestampToMinuteBoundary(TimestampTz ts) +{ + TimestampTz retval = ts + (USECS_PER_MINUTE - 1); + + return retval - (retval % USECS_PER_MINUTE); +} + +/* + * Get current timestamp for snapshots + * + * This is basically GetCurrentTimestamp(), but with a guarantee that + * the result never moves backward. + */ +TimestampTz +GetSnapshotCurrentTimestamp(void) +{ + TimestampTz now = GetCurrentTimestamp(); + + /* + * Don't let time move backward; if it hasn't advanced, use the old value. + */ + SpinLockAcquire(&oldSnapshotControl->mutex_current); + if (now <= oldSnapshotControl->current_timestamp) + now = oldSnapshotControl->current_timestamp; + else + oldSnapshotControl->current_timestamp = now; + SpinLockRelease(&oldSnapshotControl->mutex_current); + + return now; +} + +/* + * Get timestamp through which vacuum may have processed based on last stored + * value for threshold_timestamp. + * + * XXX: So far, we never trust that a 64-bit value can be read atomically; if + * that ever changes, we could get rid of the spinlock here. + */ +TimestampTz +GetOldSnapshotThresholdTimestamp(void) +{ + TimestampTz threshold_timestamp; + + SpinLockAcquire(&oldSnapshotControl->mutex_threshold); + threshold_timestamp = oldSnapshotControl->threshold_timestamp; + SpinLockRelease(&oldSnapshotControl->mutex_threshold); + + return threshold_timestamp; +} + +void +SetOldSnapshotThresholdTimestamp(TimestampTz ts, TransactionId xlimit) +{ + SpinLockAcquire(&oldSnapshotControl->mutex_threshold); + Assert(oldSnapshotControl->threshold_timestamp <= ts); + Assert(TransactionIdPrecedesOrEquals(oldSnapshotControl->threshold_xid, xlimit)); + oldSnapshotControl->threshold_timestamp = ts; + oldSnapshotControl->threshold_xid = xlimit; + SpinLockRelease(&oldSnapshotControl->mutex_threshold); +} + +/* + * XXX: Magic to keep old_snapshot_threshold tests appear "working". They + * currently are broken, and discussion of what to do about them is + * ongoing. See + * https://www.postgresql.org/message-id/20200403001235.e6jfdll3gh2ygbuc%40alap3.anarazel.de + */ +void +SnapshotTooOldMagicForTest(void) +{ + TimestampTz ts = GetSnapshotCurrentTimestamp(); + + Assert(old_snapshot_threshold == 0); + + ts -= 5 * USECS_PER_SEC; + + SpinLockAcquire(&oldSnapshotControl->mutex_threshold); + oldSnapshotControl->threshold_timestamp = ts; + SpinLockRelease(&oldSnapshotControl->mutex_threshold); +} + +/* + * If there is a valid mapping for the timestamp, set *xlimitp to + * that. Returns whether there is such a mapping. + */ +static bool +GetOldSnapshotFromTimeMapping(TimestampTz ts, TransactionId *xlimitp) +{ + bool in_mapping = false; + + Assert(ts == AlignTimestampToMinuteBoundary(ts)); + + LWLockAcquire(OldSnapshotTimeMapLock, LW_SHARED); + + if (oldSnapshotControl->count_used > 0 + && ts >= oldSnapshotControl->head_timestamp) + { + int offset; + + offset = ((ts - oldSnapshotControl->head_timestamp) + / USECS_PER_MINUTE); + if (offset > oldSnapshotControl->count_used - 1) + offset = oldSnapshotControl->count_used - 1; + offset = (oldSnapshotControl->head_offset + offset) + % OLD_SNAPSHOT_TIME_MAP_ENTRIES; + + *xlimitp = oldSnapshotControl->xid_by_minute[offset]; + + in_mapping = true; + } + + LWLockRelease(OldSnapshotTimeMapLock); + + return in_mapping; +} + +/* + * TransactionIdLimitedForOldSnapshots + * + * Apply old snapshot limit. This is intended to be called for page pruning + * and table vacuuming, to allow old_snapshot_threshold to override the normal + * global xmin value. Actual testing for snapshot too old will be based on + * whether a snapshot timestamp is prior to the threshold timestamp set in + * this function. + * + * If the limited horizon allows a cleanup action that otherwise would not be + * possible, SetOldSnapshotThresholdTimestamp(*limit_ts, *limit_xid) needs to + * be called before that cleanup action. + */ +bool +TransactionIdLimitedForOldSnapshots(TransactionId recentXmin, + Relation relation, + TransactionId *limit_xid, + TimestampTz *limit_ts) +{ + TimestampTz ts; + TransactionId xlimit = recentXmin; + TransactionId latest_xmin; + TimestampTz next_map_update_ts; + TransactionId threshold_timestamp; + TransactionId threshold_xid; + + Assert(TransactionIdIsNormal(recentXmin)); + Assert(OldSnapshotThresholdActive()); + Assert(limit_ts != NULL && limit_xid != NULL); + + /* + * TestForOldSnapshot() assumes early pruning advances the page LSN, so we + * can't prune early when skipping WAL. + */ + if (!RelationAllowsEarlyPruning(relation) || !RelationNeedsWAL(relation)) + return false; + + ts = GetSnapshotCurrentTimestamp(); + + SpinLockAcquire(&oldSnapshotControl->mutex_latest_xmin); + latest_xmin = oldSnapshotControl->latest_xmin; + next_map_update_ts = oldSnapshotControl->next_map_update; + SpinLockRelease(&oldSnapshotControl->mutex_latest_xmin); + + /* + * Zero threshold always overrides to latest xmin, if valid. Without some + * heuristic it will find its own snapshot too old on, for example, a + * simple UPDATE -- which would make it useless for most testing, but + * there is no principled way to ensure that it doesn't fail in this way. + * Use a five-second delay to try to get useful testing behavior, but this + * may need adjustment. + */ + if (old_snapshot_threshold == 0) + { + if (TransactionIdPrecedes(latest_xmin, MyProc->xmin) + && TransactionIdFollows(latest_xmin, xlimit)) + xlimit = latest_xmin; + + ts -= 5 * USECS_PER_SEC; + } + else + { + ts = AlignTimestampToMinuteBoundary(ts) + - (old_snapshot_threshold * USECS_PER_MINUTE); + + /* Check for fast exit without LW locking. */ + SpinLockAcquire(&oldSnapshotControl->mutex_threshold); + threshold_timestamp = oldSnapshotControl->threshold_timestamp; + threshold_xid = oldSnapshotControl->threshold_xid; + SpinLockRelease(&oldSnapshotControl->mutex_threshold); + + if (ts == threshold_timestamp) + { + /* + * Current timestamp is in same bucket as the last limit that was + * applied. Reuse. + */ + xlimit = threshold_xid; + } + else if (ts == next_map_update_ts) + { + /* + * FIXME: This branch is super iffy - but that should probably + * fixed separately. + */ + xlimit = latest_xmin; + } + else if (GetOldSnapshotFromTimeMapping(ts, &xlimit)) + { + } + + /* + * Failsafe protection against vacuuming work of active transaction. + * + * This is not an assertion because we avoid the spinlock for + * performance, leaving open the possibility that xlimit could advance + * and be more current; but it seems prudent to apply this limit. It + * might make pruning a tiny bit less aggressive than it could be, but + * protects against data loss bugs. + */ + if (TransactionIdIsNormal(latest_xmin) + && TransactionIdPrecedes(latest_xmin, xlimit)) + xlimit = latest_xmin; + } + + if (TransactionIdIsValid(xlimit) && + TransactionIdFollowsOrEquals(xlimit, recentXmin)) + { + *limit_ts = ts; + *limit_xid = xlimit; + + return true; + } + + return false; +} + +/* + * Take care of the circular buffer that maps time to xid. + */ +void +MaintainOldSnapshotTimeMapping(TimestampTz whenTaken, TransactionId xmin) +{ + TimestampTz ts; + TransactionId latest_xmin; + TimestampTz update_ts; + bool map_update_required = false; + + /* Never call this function when old snapshot checking is disabled. */ + Assert(old_snapshot_threshold >= 0); + + ts = AlignTimestampToMinuteBoundary(whenTaken); + + /* + * Keep track of the latest xmin seen by any process. Update mapping with + * a new value when we have crossed a bucket boundary. + */ + SpinLockAcquire(&oldSnapshotControl->mutex_latest_xmin); + latest_xmin = oldSnapshotControl->latest_xmin; + update_ts = oldSnapshotControl->next_map_update; + if (ts > update_ts) + { + oldSnapshotControl->next_map_update = ts; + map_update_required = true; + } + if (TransactionIdFollows(xmin, latest_xmin)) + oldSnapshotControl->latest_xmin = xmin; + SpinLockRelease(&oldSnapshotControl->mutex_latest_xmin); + + /* We only needed to update the most recent xmin value. */ + if (!map_update_required) + return; + + /* No further tracking needed for 0 (used for testing). */ + if (old_snapshot_threshold == 0) + return; + + /* + * We don't want to do something stupid with unusual values, but we don't + * want to litter the log with warnings or break otherwise normal + * processing for this feature; so if something seems unreasonable, just + * log at DEBUG level and return without doing anything. + */ + if (whenTaken < 0) + { + elog(DEBUG1, + "MaintainOldSnapshotTimeMapping called with negative whenTaken = %ld", + (long) whenTaken); + return; + } + if (!TransactionIdIsNormal(xmin)) + { + elog(DEBUG1, + "MaintainOldSnapshotTimeMapping called with xmin = %lu", + (unsigned long) xmin); + return; + } + + LWLockAcquire(OldSnapshotTimeMapLock, LW_EXCLUSIVE); + + Assert(oldSnapshotControl->head_offset >= 0); + Assert(oldSnapshotControl->head_offset < OLD_SNAPSHOT_TIME_MAP_ENTRIES); + Assert((oldSnapshotControl->head_timestamp % USECS_PER_MINUTE) == 0); + Assert(oldSnapshotControl->count_used >= 0); + Assert(oldSnapshotControl->count_used <= OLD_SNAPSHOT_TIME_MAP_ENTRIES); + + if (oldSnapshotControl->count_used == 0) + { + /* set up first entry for empty mapping */ + oldSnapshotControl->head_offset = 0; + oldSnapshotControl->head_timestamp = ts; + oldSnapshotControl->count_used = 1; + oldSnapshotControl->xid_by_minute[0] = xmin; + } + else if (ts < oldSnapshotControl->head_timestamp) + { + /* old ts; log it at DEBUG */ + LWLockRelease(OldSnapshotTimeMapLock); + elog(DEBUG1, + "MaintainOldSnapshotTimeMapping called with old whenTaken = %ld", + (long) whenTaken); + return; + } + else if (ts <= (oldSnapshotControl->head_timestamp + + ((oldSnapshotControl->count_used - 1) + * USECS_PER_MINUTE))) + { + /* existing mapping; advance xid if possible */ + int bucket = (oldSnapshotControl->head_offset + + ((ts - oldSnapshotControl->head_timestamp) + / USECS_PER_MINUTE)) + % OLD_SNAPSHOT_TIME_MAP_ENTRIES; + + if (TransactionIdPrecedes(oldSnapshotControl->xid_by_minute[bucket], xmin)) + oldSnapshotControl->xid_by_minute[bucket] = xmin; + } + else + { + /* We need a new bucket, but it might not be the very next one. */ + int distance_to_new_tail; + int distance_to_current_tail; + int advance; + + /* + * Our goal is for the new "tail" of the mapping, that is, the entry + * which is newest and thus furthest from the "head" entry, to + * correspond to "ts". Since there's one entry per minute, the + * distance between the current head and the new tail is just the + * number of minutes of difference between ts and the current + * head_timestamp. + * + * The distance from the current head to the current tail is one less + * than the number of entries in the mapping, because the entry at the + * head_offset is for 0 minutes after head_timestamp. + * + * The difference between these two values is the number of minutes by + * which we need to advance the mapping, either adding new entries or + * rotating old ones out. + */ + distance_to_new_tail = + (ts - oldSnapshotControl->head_timestamp) / USECS_PER_MINUTE; + distance_to_current_tail = + oldSnapshotControl->count_used - 1; + advance = distance_to_new_tail - distance_to_current_tail; + Assert(advance > 0); + + if (advance >= OLD_SNAPSHOT_TIME_MAP_ENTRIES) + { + /* Advance is so far that all old data is junk; start over. */ + oldSnapshotControl->head_offset = 0; + oldSnapshotControl->count_used = 1; + oldSnapshotControl->xid_by_minute[0] = xmin; + oldSnapshotControl->head_timestamp = ts; + } + else + { + /* Store the new value in one or more buckets. */ + int i; + + for (i = 0; i < advance; i++) + { + if (oldSnapshotControl->count_used == OLD_SNAPSHOT_TIME_MAP_ENTRIES) + { + /* Map full and new value replaces old head. */ + int old_head = oldSnapshotControl->head_offset; + + if (old_head == (OLD_SNAPSHOT_TIME_MAP_ENTRIES - 1)) + oldSnapshotControl->head_offset = 0; + else + oldSnapshotControl->head_offset = old_head + 1; + oldSnapshotControl->xid_by_minute[old_head] = xmin; + oldSnapshotControl->head_timestamp += USECS_PER_MINUTE; + } + else + { + /* Extend map to unused entry. */ + int new_tail = (oldSnapshotControl->head_offset + + oldSnapshotControl->count_used) + % OLD_SNAPSHOT_TIME_MAP_ENTRIES; + + oldSnapshotControl->count_used++; + oldSnapshotControl->xid_by_minute[new_tail] = xmin; + } + } + } + } + + LWLockRelease(OldSnapshotTimeMapLock); +} + + +/* + * Setup a snapshot that replaces normal catalog snapshots that allows catalog + * access to behave just like it did at a certain point in the past. + * + * Needed for logical decoding. + */ +void +SetupHistoricSnapshot(Snapshot historic_snapshot, HTAB *tuplecids) +{ + Assert(historic_snapshot != NULL); + + /* setup the timetravel snapshot */ + HistoricSnapshot = historic_snapshot; + + /* setup (cmin, cmax) lookup hash */ + tuplecid_data = tuplecids; +} + + +/* + * Make catalog snapshots behave normally again. + */ +void +TeardownHistoricSnapshot(bool is_error) +{ + HistoricSnapshot = NULL; + tuplecid_data = NULL; +} + +bool +HistoricSnapshotActive(void) +{ + return HistoricSnapshot != NULL; +} + +HTAB * +HistoricSnapshotGetTupleCids(void) +{ + Assert(HistoricSnapshotActive()); + return tuplecid_data; +} + +/* + * EstimateSnapshotSpace + * Returns the size needed to store the given snapshot. + * + * We are exporting only required fields from the Snapshot, stored in + * SerializedSnapshotData. + */ +Size +EstimateSnapshotSpace(Snapshot snapshot) +{ + Size size; + + Assert(snapshot != InvalidSnapshot); + Assert(snapshot->snapshot_type == SNAPSHOT_MVCC); + + /* We allocate any XID arrays needed in the same palloc block. */ + size = add_size(sizeof(SerializedSnapshotData), + mul_size(snapshot->xcnt, sizeof(TransactionId))); + if (snapshot->subxcnt > 0 && + (!snapshot->suboverflowed || snapshot->takenDuringRecovery)) + size = add_size(size, + mul_size(snapshot->subxcnt, sizeof(TransactionId))); + + return size; +} + +/* + * SerializeSnapshot + * Dumps the serialized snapshot (extracted from given snapshot) onto the + * memory location at start_address. + */ +void +SerializeSnapshot(Snapshot snapshot, char *start_address) +{ + SerializedSnapshotData serialized_snapshot; + + Assert(snapshot->subxcnt >= 0); + + /* Copy all required fields */ + serialized_snapshot.xmin = snapshot->xmin; + serialized_snapshot.xmax = snapshot->xmax; + serialized_snapshot.xcnt = snapshot->xcnt; + serialized_snapshot.subxcnt = snapshot->subxcnt; + serialized_snapshot.suboverflowed = snapshot->suboverflowed; + serialized_snapshot.takenDuringRecovery = snapshot->takenDuringRecovery; + serialized_snapshot.curcid = snapshot->curcid; + serialized_snapshot.whenTaken = snapshot->whenTaken; + serialized_snapshot.lsn = snapshot->lsn; + + /* + * Ignore the SubXID array if it has overflowed, unless the snapshot was + * taken during recovery - in that case, top-level XIDs are in subxip as + * well, and we mustn't lose them. + */ + if (serialized_snapshot.suboverflowed && !snapshot->takenDuringRecovery) + serialized_snapshot.subxcnt = 0; + + /* Copy struct to possibly-unaligned buffer */ + memcpy(start_address, + &serialized_snapshot, sizeof(SerializedSnapshotData)); + + /* Copy XID array */ + if (snapshot->xcnt > 0) + memcpy((TransactionId *) (start_address + + sizeof(SerializedSnapshotData)), + snapshot->xip, snapshot->xcnt * sizeof(TransactionId)); + + /* + * Copy SubXID array. Don't bother to copy it if it had overflowed, + * though, because it's not used anywhere in that case. Except if it's a + * snapshot taken during recovery; all the top-level XIDs are in subxip as + * well in that case, so we mustn't lose them. + */ + if (serialized_snapshot.subxcnt > 0) + { + Size subxipoff = sizeof(SerializedSnapshotData) + + snapshot->xcnt * sizeof(TransactionId); + + memcpy((TransactionId *) (start_address + subxipoff), + snapshot->subxip, snapshot->subxcnt * sizeof(TransactionId)); + } +} + +/* + * RestoreSnapshot + * Restore a serialized snapshot from the specified address. + * + * The copy is palloc'd in TopTransactionContext and has initial refcounts set + * to 0. The returned snapshot has the copied flag set. + */ +Snapshot +RestoreSnapshot(char *start_address) +{ + SerializedSnapshotData serialized_snapshot; + Size size; + Snapshot snapshot; + TransactionId *serialized_xids; + + memcpy(&serialized_snapshot, start_address, + sizeof(SerializedSnapshotData)); + serialized_xids = (TransactionId *) + (start_address + sizeof(SerializedSnapshotData)); + + /* We allocate any XID arrays needed in the same palloc block. */ + size = sizeof(SnapshotData) + + serialized_snapshot.xcnt * sizeof(TransactionId) + + serialized_snapshot.subxcnt * sizeof(TransactionId); + + /* Copy all required fields */ + snapshot = (Snapshot) MemoryContextAlloc(TopTransactionContext, size); + snapshot->snapshot_type = SNAPSHOT_MVCC; + snapshot->xmin = serialized_snapshot.xmin; + snapshot->xmax = serialized_snapshot.xmax; + snapshot->xip = NULL; + snapshot->xcnt = serialized_snapshot.xcnt; + snapshot->subxip = NULL; + snapshot->subxcnt = serialized_snapshot.subxcnt; + snapshot->suboverflowed = serialized_snapshot.suboverflowed; + snapshot->takenDuringRecovery = serialized_snapshot.takenDuringRecovery; + snapshot->curcid = serialized_snapshot.curcid; + snapshot->whenTaken = serialized_snapshot.whenTaken; + snapshot->lsn = serialized_snapshot.lsn; + snapshot->snapXactCompletionCount = 0; + + /* Copy XIDs, if present. */ + if (serialized_snapshot.xcnt > 0) + { + snapshot->xip = (TransactionId *) (snapshot + 1); + memcpy(snapshot->xip, serialized_xids, + serialized_snapshot.xcnt * sizeof(TransactionId)); + } + + /* Copy SubXIDs, if present. */ + if (serialized_snapshot.subxcnt > 0) + { + snapshot->subxip = ((TransactionId *) (snapshot + 1)) + + serialized_snapshot.xcnt; + memcpy(snapshot->subxip, serialized_xids + serialized_snapshot.xcnt, + serialized_snapshot.subxcnt * sizeof(TransactionId)); + } + + /* Set the copied flag so that the caller will set refcounts correctly. */ + snapshot->regd_count = 0; + snapshot->active_count = 0; + snapshot->copied = true; + + return snapshot; +} + +/* + * Install a restored snapshot as the transaction snapshot. + * + * The second argument is of type void * so that snapmgr.h need not include + * the declaration for PGPROC. + */ +void +RestoreTransactionSnapshot(Snapshot snapshot, void *source_pgproc) +{ + SetTransactionSnapshot(snapshot, NULL, InvalidPid, source_pgproc); +} + +/* + * XidInMVCCSnapshot + * Is the given XID still-in-progress according to the snapshot? + * + * Note: GetSnapshotData never stores either top xid or subxids of our own + * backend into a snapshot, so these xids will not be reported as "running" + * by this function. This is OK for current uses, because we always check + * TransactionIdIsCurrentTransactionId first, except when it's known the + * XID could not be ours anyway. + */ +bool +XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot) +{ + /* + * Make a quick range check to eliminate most XIDs without looking at the + * xip arrays. Note that this is OK even if we convert a subxact XID to + * its parent below, because a subxact with XID < xmin has surely also got + * a parent with XID < xmin, while one with XID >= xmax must belong to a + * parent that was not yet committed at the time of this snapshot. + */ + + /* Any xid < xmin is not in-progress */ + if (TransactionIdPrecedes(xid, snapshot->xmin)) + return false; + /* Any xid >= xmax is in-progress */ + if (TransactionIdFollowsOrEquals(xid, snapshot->xmax)) + return true; + + /* + * Snapshot information is stored slightly differently in snapshots taken + * during recovery. + */ + if (!snapshot->takenDuringRecovery) + { + /* + * If the snapshot contains full subxact data, the fastest way to + * check things is just to compare the given XID against both subxact + * XIDs and top-level XIDs. If the snapshot overflowed, we have to + * use pg_subtrans to convert a subxact XID to its parent XID, but + * then we need only look at top-level XIDs not subxacts. + */ + if (!snapshot->suboverflowed) + { + /* we have full data, so search subxip */ + if (pg_lfind32(xid, snapshot->subxip, snapshot->subxcnt)) + return true; + + /* not there, fall through to search xip[] */ + } + else + { + /* + * Snapshot overflowed, so convert xid to top-level. This is safe + * because we eliminated too-old XIDs above. + */ + xid = SubTransGetTopmostTransaction(xid); + + /* + * If xid was indeed a subxact, we might now have an xid < xmin, + * so recheck to avoid an array scan. No point in rechecking + * xmax. + */ + if (TransactionIdPrecedes(xid, snapshot->xmin)) + return false; + } + + if (pg_lfind32(xid, snapshot->xip, snapshot->xcnt)) + return true; + } + else + { + /* + * In recovery we store all xids in the subxip array because it is by + * far the bigger array, and we mostly don't know which xids are + * top-level and which are subxacts. The xip array is empty. + * + * We start by searching subtrans, if we overflowed. + */ + if (snapshot->suboverflowed) + { + /* + * Snapshot overflowed, so convert xid to top-level. This is safe + * because we eliminated too-old XIDs above. + */ + xid = SubTransGetTopmostTransaction(xid); + + /* + * If xid was indeed a subxact, we might now have an xid < xmin, + * so recheck to avoid an array scan. No point in rechecking + * xmax. + */ + if (TransactionIdPrecedes(xid, snapshot->xmin)) + return false; + } + + /* + * We now have either a top-level xid higher than xmin or an + * indeterminate xid. We don't know whether it's top level or subxact + * but it doesn't matter. If it's present, the xid is visible. + */ + if (pg_lfind32(xid, snapshot->subxip, snapshot->subxcnt)) + return true; + } + + return false; +} |